菜鸟教程 JavaScript 教程:https://www.runoob.com/js/js-tutorial.html
强烈推荐书籍《JavaScript高级程序设计 第4版》:https://book.douban.com/subject/35175321/
示例:
From:https://www.jb51.net/article/80177.htm
JS 中的 所有对象 都是 继承自 Object对象
"对象" 是一组相似数据和功能的集合,用它可以来模拟现实世界中的任何东西。
在 Javascript 中,创建对象的方式通常有两种方式:
var person = new Object();
person.name = "狼狼的蓝胖子";
person.age = 25;
var person = {
name: "狼狼的蓝胖子",
age: 25
};
注意:
obj_1 = {
name: 'obj_1'
}
const {name} = obj_1; // 相当于 name = obj_1.name
console.log(name) // obj
console.log(name === obj_1.name) // true
不管通过哪种方式创建了对象实例后,该 实例 都会拥有 下面的属性和方法,下面将会逐个说明。
"constructor 属性" 是用来保存当前对象的构造函数的,前面的例子中,constructor 保存的就是 Object 方法。
var obj1 = new Object();
obj1.id = "obj1";
var obj2 = {
"id": "obj2"
};
console.log(obj1.constructor); //function Object(){}
console.log(obj2.constructor); //function Object(){}
console.log(obj1.constructor === obj2.constructor) // true
hasOwnProperty 方法接收一个字符串参数,该参数表示属性名称,用来判断该属性是否在当前对象实例中,而不是在对象的原型链中。我们来看看下面这个例子:
var arr = [];
console.log(arr.hasOwnProperty("length")); //true
console.log(arr.hasOwnProperty("hasOwnProperty")); //false
在这个例子中,首先定义了一个数组对象的 实例arr,我们知道,数组对象实际是通过 原型链 ( 下面会介绍 ) 继承了Object 对象,然后拥有自己的一些属性,可以通过 hasOwnProperty 方法 判断 length 是 arr 自己的属性,然而 hasOwnProperty方法 是在 原型链 上的属性。
hasOwnProperty 方法 可以和 for..in 结合起来获取 对象自己的 key
isPrototype 方法接收一个对象,用来判断当前对象是否在传入的参数对象的原型链上,说起来有点抽象,我们来看看代码。
function MyObject() {}
var obj = new MyObject();
console.log(Object.prototype.isPrototypeOf(obj));
上面代码中 MyObject 是继承自 Object 对象的,而在JS中,继承是通过 prototype 来实现的,所以 Object 的 prototype 必定在 MyObject 对象实例的原型链上。
prototypeIsEnumerable 用来判断给定的属性是否可以被 for..in 语句给枚举出来。看下面代码:
var obj = {
name: "objName"
}
for (var i in obj) {
console.log(i);
}
执行这段代码输出字符串 “name”,这就说明通过 for…in 语句可以得到 obj 的 name 这个属性,但是我们知道,obj 的属性还有很多,比如 constructor,比如hasOwnPrototype 等等,但是它们没有被输出,说明这些属性不能被 for…in 给枚举出来,可以通过 propertyIsEnumerable 方法来得到。
console.log(obj.propertyIsEnumerable("constructor")); // false
判断 “constructor” 是否可以被枚举,输出 false 说明无法被枚举出来。
toLocalString 方法返回对象的字符串表示,和代码的执行环境有关。
var obj = {};
console.log(obj.toLocaleString()); //[object Object]
var date = new Date();
console.log(date.toLocaleString()); //2021/4/15 下午1:30:15
toString 用来返回对象的字符串表示。
var obj = {};
console.log(obj.toString()); //[object Object]
var date = new Date();
console.log(date.toString()); //Sun Feb 28 2021 13:40:36 GMT+0800 (中国标准时间)
valueOf 方法返回对象的原始值,可能是字符串、数值 或 bool值 等,看具体的对象。
var obj = {
name: "obj"
};
console.log(obj.valueOf()); //Object {name: "obj"}
var arr = [1];
console.log(arr.valueOf()); //[1]
var date = new Date();
console.log(date.valueOf()); //1456638436303
如代码所示,三个不同的对象实例调用 valueOf 返回不同的数据。
在 Javascript 中,属性有两种类型,分别是
我们来看看这两种属性具体是什么东西。
数据属性:可以理解为我们平时定义对象时赋予的属性,它可以进行读和写。但是,ES5中定义了一些 特性,这些特性是用来描述属性的各种特征。即 特性的作用 是描述 属性的各种特征。特性是内部值,不能直接访问到。特性通过用两对方括号表示,比如[[Enumerable]]。
属性的特性会有一些默认值,要修改特性的默认值,必须使用 ES5 定义的新方法 Object.defineProperty 方法来修改
数据属性有4个描述其特征的特性,下面将依次说明每一个特性:
(1)[[Configurable]]:该特性表示是否可以通过 delete 操作符来删除属性,默认值是 true。
var obj = {};
obj.name = "myname";
delete obj.name;
console.log(obj.name);//undefined
这段代码很明显,通过 delete 删除了 obj 的 name 属性后,我们再访问 name 属性就访问不到了。
我们通过 Object.defineProperty 方法来修改 [[Configurable]] 特性。
var obj = {};
obj.name = "myname";
Object.defineProperty(obj, "name", {
configurable: false
})
delete obj.name;
console.log(obj.name); //myname
通过将 configurable 特性设置成 false 之后,delete 就无法删除 name 属性了,如果在严格模式下,使用 delete 去删除就会报错。
(2)[[Enumerable]]:表示是否能够通过 for…in 语句来枚举出属性,默认是 true
我们来看看前面的例子:
var obj = {
name: "objName"
}
for (var i in obj) {
console.log(i);//name
}
这段代码只输出了 name 属性,我们来将 constructor 属性的 [[Enumerable]] 设置为 true 试试。
var obj = {
name: "objName"
}
Object.defineProperty(obj, "constructor", {
enumerable: true
})
for (var i in obj) {
console.log(i);//name,constructor
}
console.log(obj.propertyIsEnumerable("constructor"));//true
这段代码中,for…in 循环得到了 name 和 constructor 两个属性,而通过 propertyIsEnumerable 方法来判断 constructor 也返回了true。
(3)[[Writable]]:表示属性值是否可以修改,默认为true。如果 [[Writable]] 被设置成 false,尝试修改时将没有效果,在严格模式下会报错
(4)[[Value]]:表示属性的值,默认为 undefined
我们通过一个简单的例子来看看这两个特性:
var obj = {
name: "name"
};
console.log(obj.name);//name
Object.defineProperty(obj, "name", {
value: "newValue",
writable: false
})
console.log(obj.name);//newValue
obj.name = "oldValue";
console.log(obj.name);//newValue
我们首先定义了 obj 对象的 name 属性值为 “name”,然后通过 defineProperty 方法来修改值,并且将其设置为不可修改的。接着我们再修改 name 属性的值,可以发现修改无效。
如果我们通过 defineProperty 来修改 name 属性的值,是否可以修改呢?答案是可以的:
Object.defineProperty(obj, "name", {
value: "oldValue"
})
console.log(obj.name); //oldValue
访问器属性有点类似于 C# 中的属性,和数据属性的区别在于,它没有数据属性的 [[Writable]] 和 [[Value]] 两个特性,而是拥有一对 getter 和 setter 函数。
getter 和 setter 是一个很有用的东西,假设有两个属性,其中第二个属性值会随着第一个属性值的变化而变化。这种场景在我们平时的编码中起始是非常常见的。在之前的做法中,我们往往要去手动修改第二个属性的值,那现在我们就可以通过 get 和 set 函数来解决这个问题。看下面这个例子:
var person = {
age: 10
}
Object.defineProperty(person, "type", {
get: function () {
if (person.age > 17) {
return "成人";
}
return "小孩";
}
})
console.log(person.type);//小孩
person.age = 18;
console.log(person.type);//成人
通过修改 age 的值,type 的值也会相应的修改,这样我们就不用再手动的去修改 type 的值了。
下面这种方式也是可以实现同样的效果:
var person = {
_age: 10,
type: "小孩"
}
Object.defineProperty(person, "age", {
get: function () {
return this._age;
},
set: function (newValue) {
this._age = newValue;
this.type = newValue > 17 ? "成人" : "小孩";
}
})
console.log(person.type);
person.age = 18;
console.log(person.type);
关于访问器属性,有几点要注意:
ES5 提供了一些读取或操作属性特性的方法,前面用到的 Object.defineProperty 就是其中之一。我总结了一些比较常用的方法如下:
定义一个对象的属性,这个方法前面我们已经用到多次,简单说说其用法。
Object.defineProperty(obj, propName, descriptor);
defineProperty 有点类似于定于在 Object 上的静态方法,通过 Object 直接调用,它接收3个参数:
例子如下:
var obj = {};
Object.defineProperty(obj, "name", {
value: "name",
configurable: true,
writable: true,
enumerable: true
});
和 defineProperty 类似,是用来定义对象属性的,不同的是它可以用来同时定义多个属性,我们通过命名也可以看出来,用法如下:
var obj = {};
Object.defineProperty(obj, {
"name": {
value: "name",
configurable: true,
writable: true,
enumerable: true
},
"age": {
value: 20
}
});
ES5 中还提供了一个读取特性值的方法,该方法接收对象及其属性名作为两个参数,返回一个对象,根据属性类型的不同,返回对象会包含不同的值。
var person = {
_age: 10,
type: "小孩"
}
Object.defineProperty(person, "age", {
get: function () {
return this._age;
},
set: function (newValue) {
this._age = newValue;
this.type = newValue > 17 ? "成人" : "小孩";
}
})
console.log(Object.getOwnPropertyDescriptor(person, "type"));//Object {value: "成人", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person, "age")); //Object {enumerable: false, configurable: false, get: function(),set: function ()}
在 ES5 中,Object 对象上新增了一批方法,这些方法可以直接通过 Object 进行访问,前面用到的 defineProperty 就是新增的方法之一。除此之外还有很多方法,我将其总结归纳如下:
在前面我们提到,创建一个对象有两种方法:构造函数 和 对象字面量。
这两种方法有一个缺点就是:如果要创建多个对象,写起来很繁琐,所以后来就有了一种创建自定义构造函数的方法来创建对象,如下所示:
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("Jack", 15);
这种方式可以很方便的创建多个同样的对象,也是目前比较常用的方法。
ES5 提供的 Object.create 方法也是一个创建对象的方法,这个方法允许为创建的对象选择原型对象,不需要定义一个构造函数。用法如下:
var obj = Object.create(Object.prototype, {
name: {
value: "Jack"
}
})
console.log(obj.name); //Jack
这个方法接收的第一个参数作为被创建对象的原型,第二个参数是对象的属性。
注意:在这个例子中,name属性是无法被修改的,因为它没有设置writable特性,默认则为false。
个人看法:Object.create这种创建对象的方式略显繁琐,除非是需要修改属性的特性,否则不建议使用这种方式创建对象。
Object.keys 是 es5 中新增的方法,用来获取对象自身所有的可枚举的属性名,但不包括原型中的属性,然后返回一个由属性名组成的数组。
注意它同 for..in 一样不能保证属性按对象原来的顺序输出。
function Parent() {
this.lastName = "Black"
}
function Child(firstName) {
this.firstName = firstName;
}
Child.prototype = new Parent();
var son = new Child("Jack");
console.log(Object.keys(son)); //["firstName"]
代码中返回了 firstName,并没有返回从 prototype 继承而来的 lastName 和 不可枚举的相关属性。
// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']
// array like object
var obj = {0: 'a', 1: 'b', 2: 'c'};
console.log(Object.keys(obj)); // console: ['0', '1', '2']
// array like object with random key ordering
var anObj = {100: 'a', 2: 'b', 7: 'c'};
console.log(Object.keys(anObj)); // console: ['2', '7', '100']
// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {
getFoo: {
value: function () {
return this.foo;
}
}
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']
在一些旧的浏览器中,我们可以使用 hasOwnProperty 和 for…in 来达到类似的效果。
function Parent() {
this.lastName = "Black"
}
function Child(firstName) {
this.firstName = firstName;
}
Child.prototype = new Parent();
var son = new Child("Jack");
// 注意:这里如果不支持 Object.keys,则把 Object.keys 定义为一个函数
Object.keys = Object.keys ||
function (obj) {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys;
};
console.log(Object.keys(son));
Object.getOwnPropertyNames 也是 es5 中新增的方法,返回对象自身的所有属性的属性名(包括可枚举和不可枚举的所有属性)组成的数组,但不会获取原型链上的属性。
function Parent() {
this.lastName = "Black"
}
function Child(firstName) {
this.firstName = firstName;
}
Child.prototype = new Parent();
var son = new Child("Jack");
Object.defineProperty(son, "age", {
enumerable: false
})
console.log(Object.keys(son));//["firstName"]
console.log(Object.getOwnPropertyNames(son));//["firstName", "age"]
我们定义给 son 对象定义了一个不可枚举的属性 age,然后通过 keys 和 getOwnPropertyNames 两个方法来获取属性列表,能明显看出了两者区别。
这个主要是前面提到的三个方法:
ES5 中提供了一系列限制对象被修改的方法,用来防止被某些对象被无意间修改导致的错误。每种限制类型包含一个判断方法和一个设置方法。
Object.preventExtensions() 用来限制对象的扩展,设置之后,对象将无法添加新属性,用法如下:
Object.preventExtensions(obj);
该方法接收一个要被设置成无法扩展的对象作为参数,需要注意两点:
function Person(name) {
this.name = name;
}
var person = new Person("Jack");
Object.preventExtensions(person);
delete person.name;
console.log(person.name);//undefined
Person.prototype.age = 15;
console.log(person.age);//15
Object.isExtensible 方法用来判断一个对象是否可扩展,默认情况是 true
Object.seal 可以密封一个对象并返回被密封的对象。
密封对象无法添加或删除已有属性,也无法修改属性的 enumerable,writable,configurable,但是可以修改属性值。
function Person(name) {
this.name = name;
}
var person = new Person("Jack");
Object.seal(person);
delete person.name;
console.log(person.name); //Jack
将对象密封后,使用 delete 删除对象属性,还是可以访问得到属性。
通过 Object.isSealed 可以用来判断一个对象是否被密封了。
Object.freeze 方法用来冻结一个对象,被冻结的对象将无法添加,修改,删除属性值,也无法修改属性的特性值,即这个对象无法被修改。
function Person(name) {
this.name = name;
}
var person = new Person("Jack");
Object.freeze(person);
delete person.name;
console.log(person.name);//Jack
Person.prototype.age = 15;
console.log(person.age);//15
分析上面的代码我们可以发现,被冻结的对象无法删除自身的属性,但是通过其原型对象还是可以新增属性的。
通过Object.isFrozen可以用来判断一个对象是否被冻结了。
可以发现:这三个限制对象的方法的限制程度是依次上升的。
参考:https://www.cnblogs.com/fozero/p/10997520.html
Object.keys(obj) 方法是 JavaScript 中用于遍历对象属性的一个方法 。它传入的参数是一个对象,返回的是一个数组,数组中包含的是该对象所有的属性名。如:
var cat = {
name: 'mini',
age: 2,
color: 'yellow',
desc:"cute"
}
console.log(Object.keys(cat)); // ["name", "age", "color", "desc"]
这里有一道关于 Object.keys 的题目:输出对象中值大于 2的 key 的数组
var data = {a: 1, b: 2, c: 3, d: 4};
Object.keys(data).filter(function (x) {
return 1;
})
/*
期待输出:["c","d"]
请问1处填什么?
正确答案:1 :data[x]>2
*/
Object.keys 是 es5 中新增的方法,用来获取对象自身所有的可枚举的属性名,但不包括原型中的属性,然后返回一个由属性名组成的数组。注意它同 for..in 一样不能保证属性按对象原来的顺序输出。
Object.getOwnPropertyNames 也是 es5 中新增的方法,返回对象的所有自身属性的属性名(包括不可枚举的属性)组成的数组,但不会获取原型链上的属性。
Array.filter(function)
对数组进行过滤返回符合条件的数组。
Object.values 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历( enumerable )属性的键值。
var obj = {foo: "bar", baz: 42};
Object.values(obj)
// ["bar", 42]
返回数组的成员顺序,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b、c、a。Object.values 只返回对象自身的可遍历属性。
var obj = {100: 'a', 2: 'b', 7: 'c'};
Object.values(obj)
// ["b", "c", "a"]
如果 Object.values 方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo')
// ['f', 'o', 'o']
上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。因此,Object.values返回每个属性的键值,就是各个字符组成的一个数组。
如果参数不是对象,Object.values 会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values 会返回空数组。
Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__ 。
语法:Object.create(proto, [propertiesObject])
参数
:proto 新创建对象的原型对象。
:propertiesObject 可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。
返回值:一个新对象,带着指定的原型对象和属性。如:
var parent = {
x: 1,
y: 1
}
var child = Object.create(parent, {
z: { // z会成为创建对象的属性
writable: true,
configurable: true,
value: "newAdd"
}
});
console.log(child)//{z: "newAdd"}z: "newAdd"__proto__: x: 1y: 1__proto__: Object
function A() {
this.a = 1;
this.b = 2;
}
A.prototype.drive = function () {
console.log('drivvvvvvvvvv');
}
//方式1
function B() {
}
B.prototype = Object.create(new A()); //这里采用了new 一个实例
//方式2
function C() {
A.call(this);
}
C.prototype = Object.create(A.prototype) //这里使用的是父类的原型
以上两种方式有什么区别?
1 的缺点:
执行了 new,相当于运行了一遍 A ,如果在 A 里做了一些其它事情(如改变全局变量)就会有副作用。
用 A 创建的对象做原型,里面可能会有一些冗余的属性。
2 模拟了 new 的执行过程
判断对象自身属性中是否具有指定的属性。这个方法是不包括对象原型链上的方法的。
判断某个对象是否拥有某个属性,判断的方法有很多,常用的方法就是 object.hasOwnProperty('×××')
var obj = {
name: 'fei'
}
console.log(obj.hasOwnProperty('name')) // true
console.log(obj.hasOwnProperty('toString')) // false
以上,obj 对象存在的 name 属性的时候,调用这个方法才是返回 true,我们知道其实每个对象实例的原型链上存在 toString 方法,在这里打印 false,说明这个方法只是表明实例对象的属性,不包括原型链上的属性。
Object.getOwnPropertyNames() 方法返回对象的所有自身属性的属性名(包括不可枚举的属性)组成的数组,但不会获取原型链上的属性。
function A(a, aa) {
this.a = a;
this.aa = aa;
this.getA = function () {
return this.a;
}
}
// 原型方法
A.prototype.aaa = function () {
};
var B = new A('b', 'bb');
B.myMethodA = function () {
};
// 不可枚举方法
Object.defineProperty(B, 'myMethodB', {
enumerable: false,
value: function () {
}
});
Object.getOwnPropertyNames(B); // ["a", "aa", "getA", "myMethodA", "myMethodB"]
Object.getOwnPropertyNames 和 Object.keys 的区别,
'use strict';
(function () {
if (!Object.getOwnPropertyNames) {
console.log('浏览器不支持getOwnPropertyNames');
return;
}
//人类的构造函数
var person = function (name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sing = function () {
console.log('sing');
}
}
//new 一个ladygaga
var gaga = new person('ladygaga', 26, 'girl');
//给嘎嘎发放一个不可枚举的身份证
Object.defineProperty(gaga, 'id', {
value: '1234567890',
enumerable: false
});
//查看gaga的个人信息
var arr = Object.getOwnPropertyNames(gaga);
document.write(arr); //output: name,age,sex,sing,id
document.write('');
//注意和getOwnPropertyNames的区别,不可枚举的id没有输出
var arr1 = Object.keys(gaga);
document.write(arr1); //output: name,age,sex,sing
})();
Object.assign 方法用于对象的合并,将源对象( source )的所有可枚举属性,复制到目标对象( target )。
var target = {a: 1};
var source1 = {b: 2};
var source2 = {c: 3};
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
1、如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
2、如果只有一个参数,Object.assign 会直接返回该参数。
var obj = {a: 1};
Object.assign(obj) === obj // true
3、如果该参数不是对象,则会先转成对象,然后返回。
4、由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
5、Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
上面代码中,源对象obj1的a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
常见用途
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
上面方法通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
//···
},
anotherMethod() {
//···
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
//···
};
SomeClass.prototype.anotherMethod = function () {
//···
};
上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用 assign 方法添加到 SomeClass.prototype 之中。
function clone(origin) {
return Object.assign({}, origin);
}
上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。
将多个对象合并到某个对象。
const merge =(target, ...sources) => Object.assign(target, ...sources);
如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。
const merge =(...sources) => Object.assign({}, ...sources);
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
let options = Object.assign({}, DEFAULTS, options);
}
DEFAULTS 对象是默认值,options 对象是用户提供的参数。Object.assign 方法将 DEFAULTS 和 options 合并成一个新对象,如果两者有同名属性,则 option 的属性值会覆盖 DEFAULTS 的属性值。注意,由于存在深拷贝的问题,DEFAULTS 对象 和 options对象的所有属性的值,都只能是简单类型,而不能指向另一个对象。否则,将导致DEFAULTS 对象的该属性不起作用。
参考 es6 javascript 对象方法Object.assign():https://blog.csdn.net/qq_30100043/article/details/53422657
Object.defineProperty 可以用来定义新属性或修改原有的属性
使用构造函数定义对象和属性
var obj = new Object; //obj = {}
obj.name = "张三"; //添加描述
obj.say = function(){}; //添加行为
语法:Object.defineProperty(obj, prop, descriptor)
参数说明
给对象的属性添加特性描述,目前提供两种形式:
修改或定义对象的某个属性的时候,给这个属性添加一些特性, 数据描述中的属性都是可选的
var obj = {
test:"hello"
}
//对象已有的属性添加特性描述
/*
Object.defineProperty(obj,"test",{
configurable:true | false,
enumerable:true | false,
value:任意类型的值,
writable:true | false
});
*/
//对象新添加的属性的特性描述
/*
Object.defineProperty(obj,"newKey",{
configurable:true | false,
enumerable:true | false,
value:任意类型的值,
writable:true | false
});
*/
使用存取器描述属性的特性的时候,允许设置以下特性属性, 当使用了getter 或 setter 方法,不允许使用 writable 和 value 这两个属性
var obj = {};
Object.defineProperty(obj, "newKey", {
get: function () {} | undefined,
set: function (value) {} | undefined,
configurable: true | false,
enumerable: true | false
});
getter 是一种获得属性值的方法。setter是一种设置属性值的方法。使用 get/set 属性来定义对应的方法
var obj = {};
var initValue = 'hello';
Object.defineProperty(obj, "newKey", {
get: function () {
//当获取值的时候触发的函数
return initValue;
},
set: function (value) {
//当设置值的时候触发的函数,设置的新值通过参数value拿到
initValue = value;
}
});
//获取值console.log(obj.newKey); //hello
//设置值
obj.newKey = 'change value';
console.log(obj.newKey); //change value
var obj = {};
obj.toString();
上面定义了一个 空对象obj,当调用 obj 的 toString() 理论上会报 undefined 错误,但是实际上不会报错
运算符 -- JavaScript 标准参考教程(alpha):http://javascript.ruanyifeng.com/grammar/operator.html#toc6
JavaScript 中 === 和 == 的区别 ( 参考:https://www.zhihu.com/question/31442029 ):
- "===" 叫做 恒等 运算符。( 即 类型和值 都相等 )( 其实叫 全等运算符 更合适。即内存中每个bit位都一样 )
严格相等运算符 === 的运算规则如下:
(1) 不同类型值。如果两个值的类型不同,直接返回false。
(2) 同一类的原始类型值。同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false。
(3) 同一类的复合类型值。两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个对象。
(4) undefined 和 null。undefined 和 null 与自身严格相等。
null === null //true
undefined === undefined //true- "==" 叫做 相等 运算符。( 只 判断数据的值 )
相等运算符 == 在比较相同类型的数据时,与严格相等运算符完全一样。
在比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。
类型转换规则如下:
(1) 原始类型的值。原始类型的数据会转换成数值类型再进行比较。字符串和布尔值都会转换成数值。
(2) 对象与原始类型值比较。对象(这里指广义的对象,包括数值和函数)与原始类型的值比较时,对象转化成原始类型的值,再进行比较。
(3) undefined和null。undefined和null与其他类型的值比较时,结果都为false,它们互相比较时结果为true。
(4) 相等运算符的缺点。相等运算符会隐藏类型的转换,然后带来一些违反直觉的结果。- 因为"=="不严谨,可能会带来一些违反直觉的后果,建议尽量不要使用 相等运算符 == ,而是使用 严格相等运算符 ===
示例:
'' == '0' // false
0 == '' // true。'' 会先转换成数值0,再和0比较,所以是 true
0 === 0 // truefalse == 'false' // false
false == 0 // truefalse == undefined // false
false == null // false
null == undefined // true'\t\r\n' == 0
var a = undefined; if(!a){ console.log('a'); // 1 } if(a == null){ console.log('a'); // 1 } if( a === null ){ console.log('a'); // 无输出 }
js中 !== 和 != 的区别:
- != 会转换成相同类型 进行比较。即 在表达式两边的数据类型不一致时,会隐式转换为相同数据类型,然后对值进行比较;
- !== 不会进行类型转换,在比较时除了对值进行比较以外,还比较两边的数据类型, 它是 恒等运算符 === 的非形式。
解释:上面是定义 obj 变量指向一个空对象,当对象定义的一瞬间,就会瞬间产生一个 __proto__ 的属性 ,这个属性指向 window.Object.prototype。
上面这个搜索的过程是由 __proto__ 组成的链子一直走下去的,这个过程就叫做 原型链
上面是一个 "链",下面继续深入,看下数组
var arr = []
arr.push(1) // [1]
再复杂一下,arr.valueOf() 做了什么?
JS中一切皆对象。对象是拥有属性和方法的数据。JS函数也是对象
实际上,函数 是 Function类型 的 实例,此时可以把每一个创建出来的函数,当成是Function类型的实例对象,
所以函数本身拥有的对象属性是来源于 Function,Fn.Constructor 即为 Function
但是与此同时要注意:Function.prototype.__proto__ === Object.prototype
可以理解为:构造器函数的构造函数是Object
也可以简单的理解:函数即对象
如果上面看不懂,可以继续看下面就会明白。。。
每个 函数 都有一个 原型对象(prototype)
原型对象 都包含一个指向 构造函数 的 指针,
而 实例(instance) 都包含一个指向 原型对象 的 内部指针。
1. 在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。构造函数首字母一般大写。
示例:使用构造函数创建一个对象,在这个例子中,Person 就是一个构造函数,然后使用 new 创建了一个 实例对象 person。
function Person() {
}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin
示例:
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function() {
alert(this.name)
}
}
var person1 = new Person('Zaxlct', 28, 'Engineer')
var person2 = new Person('Mick', 23, 'Doctor')
person1 和 person2 都是 Person 的实例。这两个实例都有一个 constructor (构造函数)属性,该属性(是一个指针)指向 Person。 即:
console.log(person1.constructor == Person) //true
console.log(person2.constructor == Person) //true
2.构造函数的执行过程
function Person(name, sex, age){
this.name = name;
this.sex = sex;
this.age = age;
}
p1 = new Person('king', '男', 100);
function Cat(){
this.cat = 'cat';
}
guaiguai = new Cat(); // guaiguai 是 Cat 的实例化对象
guaiguai.__proto__ // 因为guaiguai是Cat的实例化对象,
// 所以 __proto__ 指向的是 构造函数的 prototype
// 这个就叫 构造函数的原型对象
guaiguai.__proto__ === Cat.prototype // true
每个 函数 都有一个 prototype 属性,就是我们经常在各种例子中看到的那个 prototype ,指向调用该构造函数而创建的 实例的原型,
比如:上面例子中的 person1 和 person2 的原型
function Person() {}
Person.prototype.name = 'Zaxlct'
Person.prototype.age = 28
Person.prototype.job = 'Engineer'
Person.prototype.sayName = function() {
alert(this.name)
}
var person1 = new Person()
person1.sayName() // 'Zaxlct'
var person2 = new Person()
person2.sayName() // 'Zaxlct'
console.log(person1.sayName == person2.sayName) //true
示例:
function Person() {
}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin
那这个函数的 prototype 属性到底指向的是什么呢?是这个函数的原型吗?
其实,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数 而创建 的 实例的原型,也就是这个例子中的 person1 和 person2 的原型。
那什么是原型呢 ?
可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
让我们用一张图表示构造函数和实例原型之间的关系:
在这张图中我们用 Object.prototype 表示实例原型。
那么我们该怎么表示实例与实例原型,也就是 person 和 Person.prototype 之间的关系呢,这时候我们就要讲到第二个属性:__proto__
给构造函数添加属性
var Person = function(a){
this.a = a;
return this.a;
}
person_1 = new Person()
Person.prototype.return666 = function(){
return 666;
}
person_2 = new Person();
console.log(person_1.return666);
这是每一个JavaScript对象(除了 null )都具有的一个属性,叫 __proto__,这个属性会指向该对象的原型。
为了证明这一点,我们可以在火狐或者谷歌中输入:
function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
于是我们更新下关系图:
既然 实例对象 和 构造函数 都可以指向原型,那么 原型 是否有属性指向 构造函数或者实例 呢?
但是 原型 指向 构造函数 倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
为了验证这一点,我们可以尝试:
function Person() {}
var person1 = new Person()
console.log(Person === Person.prototype.constructor) // true
console.log(person1.__proto__ === Person.prototype) // true
所以再更新下关系图:
function a(){}
a.constructor === Function.constructor // true构造函数 的 constructor 指向 构造函数自身
综上我们已经得出:
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
了解了构造函数、实例原型、和实例之间的关系,接下来我们讲讲 实例 和 原型 的关系:
当读取 实例的属性 时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
举个例子:
function Person() {
}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin
在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性,就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype 中查找,幸运的是我们找到了 name 属性,结果为 Kevin。
但是万一还没有找到呢?原型的原型又是什么呢?
在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin
所以原型对象是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向 构造函数的 prototype ,所以我们再更新下关系图:
那 Object.prototype 的原型呢?
null,不信我们可以打印:console.log(Object.prototype.__proto__ === null) // true
所以查到属性的时候查到 Object.prototype 就可以停止查找了。所以最后一张关系图就是
顺便还要说一下,图中由 相互关联的原型组成的链状结构 就是 原型链,也就是蓝色的这条线。
最后,补充三点大家可能不会注意的地方:
constructor
首先是 constructor 属性,我们看个例子:
function Person() {
}
var person = new Person();
console.log(person.constructor === Person); // true
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到 constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:
person.constructor === Person.prototype.constructor
__proto__
其次是 __proto__ ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)。
真的是继承吗?
最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承'属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
小括号 的 作用:
自执行函数
1. 先来看个最简单的自执行函数
(function(){
}());
相当于声明并调用
var b = function () {
}
b()
2. 自执行函数也可以有名字
function b(){
...
}()
3. 执行函数也可以传参
function b(i){
console.log(i)
}(5)
总结:自执行函数 在调用上与普通函数一样,可以匿名,可以传参。只不过是在声明的时候自调用了一次
常见的自执行匿名函数:
不常见的自执行匿名函数:
~function(x, y) {
return x+y;
}(3, 4);
+function(x,y){
return x+y;
}(3,4);
++function(x,y){
return x+y;
}(3,4);
-function(x,y){
return x+y;
}(3,4);
--function(x,y){
return x+y;
}(3,4);
[function(){
console.log(this) // 浏览器的控制台输出window
}(this)]
匿名函数前加 typeof
typeof function(){
console.log(this) // 浏览器得控制台输出window
}(this)
示例:匿名函数自执行 实现 异步函数递归
(function async(i) {
if (i >= 5){ return }
else{
setTimeout(() => {
console.log(i)
i++
async(i)
}, 1000)
}
})(0)
自执行匿名函数 执行耗时结果:
但有些结论是有意义的:括号和加减号最优
封装:把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口。
在ES6之前,是不存在 class 这个语法糖类的。所以实现大多采用原型对象和构造函数
在ES6之后,存在class这个语法糖类。当你使用class的时候,它会默认调用constructor这个函数,来接收一些参数,并构造出一个新的实例对象(this)并将它返回,因此它被称为constructor构造方法(函数)。
在函数内部定义的变量和函数如果不对外提供接口,那么外部将无法访问到,也就是变为私有变量和私有函数。
function Obj() {
var a = 0 //私有变量
var fn = function() {
//私有函数
}
}
var o = new Obj()
console.log(o.a) //undefined
console.log(o.fn) //undefined
当定义一个函数后通过 “.”为其添加的属性和函数,通过对象本身仍然可以访问得到,但是其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数。
function Obj() {}
Obj.a = 0 //静态变量
Obj.fn = function() {
//静态函数
}
console.log(Obj.a) //0
console.log(typeof Obj.fn) //function
var o = new Obj()
console.log(o.a) //undefined
console.log(typeof o.fn) //undefine
在面向对象编程中除了一些库函数我们还是希望在对象定义的时候同时定义一些属性和方法,实例化后可以访问,JavaScript也能做到这样。
function Obj(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法
}
}
console.log(typeof Obj.a); //undefined
console.log(typeof Obj.fn); //undefined
var o=new Obj();
console.log(typeof o.a); //object
console.log(typeof o.fn); //function
function Foo() {
getName = function() {
alert(1)
}
return this
}
Foo.getName = function() {
alert(2)
}
Foo.prototype.getName = function() {
alert(3)
}
var getName = function() {
alert(4)
}
function getName() {
alert(5)
}
请写出以下输出结果:
Foo.getName()
getName()
Foo().getName()
getName()
new Foo.getName()
new Foo().getName()
new new Foo().getName()
解读:首先定义了一个叫 Foo 的函数,之后为 Foo 创建了一个叫 getName 的静态属性存储了一个匿名函数,之后为 Foo 的原型对象新创建了一个叫 getName 的匿名函数。之后又通过函数变量表达式创建了一个 getName 的函数,最后再声明一个叫 getName 函数。
先来剧透一下答案,再来看看具体分析
//答案:
Foo.getName() // 2
getName() // 4
Foo().getName() // 1
getName() // 1
new Foo.getName() // 2
new Foo().getName() // 3
new new Foo().getName() // 3
function Foo() {
getName = function() {
alert(1)
}
return this
}
var getName //只提升变量声明
function getName() {
alert(5)
} //提升函数声明,覆盖var的声明
Foo.getName = function() {
alert(2)
}
Foo.prototype.getName = function() {
alert(3)
}
getName = function() {
alert(4)
} //最终的赋值再次覆盖function getName声明
function(){alert(1)}
。后面三问都是考察 js 的运算符优先级问
继承:继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。
比如我有个构造函数A,然后又有个构造函数B,但是B想要使用A里的一些属性和方法,一种办法就是让我们自身化身为CV侠,复制粘贴一波。还有一种就是利用继承,我让B直接继承了A里的功能,这样我就能用它了。
原型链继承
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
A instanceof B
实例对象A instanceof 构造函数B
检测A的原型链(__proto__)上是否有B.prototype,有则返回true,否则返回false
多态的实际含义是:同一操作作用于不同的对象上,可以产生不同的解释和不同的执行结果。
class中的多态:extends、super
对于js多态的详细解释:https://juejin.cn/post/6844904126011146254
this 永远指向最后调用它的那个对象
this,是指当前的本身,在非严格模式下this指向的是全局对象window,而在严格模式下会绑定到undefined。
this 的 5种绑定方式:
再次强调:
- this 永远指向最后调用它的那个对象
- 匿名函数的 this 永远指向 window
- 使用 call() 或者 apply() 的函数会直接执行
- bing() 是创建一个新的函数,需要手动调用才会执行
- 如果 call、appy、bind 接收到的第一个参数是空或者 null,undefine 的话,则会忽略这个参数
- forEach、map、filter 函数的第二个参数也是能显式绑定 this 的
没有绑定到任何对象的 变量、函数、属性 等,都是 绑定到 window 对象。
var a = 10; // a 属于 window
function foo(){
console.log(this.a); // this 指向 window
console.log(this); // window 对象
console.log(window) // window 对象
console.log(this === window); // true
}
foo(); // 10
console.log(window.a); // 10
console.log(window.a === this.a); // true
使用 let 、const 声明的 变量,不会绑定到 window
let a = 10;
const b = 20;
function foo(){
console.log(this.a);
console.log(this.b);
}
foo();
console.log(window.a);
var a = 1;
function foo(){
var a = 2
console.log(this);
console.log(this.a); // foo 属于 window ,所以 打印 1
}
foo();
修改代码:
var a = 1;
function foo(){
var a = 2
function inner(){
console.log(this.a);
}
inner();
}
foo(); // 打印 1
foo 函数属于 window,inner 是 foo 的内嵌函数,所以 this 指向 window
示例代码:
function foo(){
console.log(this.a);
}
var obj = {a:1, foo}; // var obj = {foo} 等价于 var obj = {foo: foo}
var a = 2;
obj.foo(); // 打印 1
隐式丢失:就是被隐式绑定的函数,在特定的情况下会丢失绑定对象。
特定情况是指:
示例:
function foo(){
console.log(this.a);
}
var obj = {a:1, foo};
var a = 2;
var foo2 = obj.foo;
obj.foo(); // 1
foo2(); // 2 这里 foo2(); 是被 window 调用的,即 window.foo2(); 使的 this 指向 window
示例:
function foo(){
console.log(this.a);
}
function doFoo(fn){
console.log(this);
fn();
}
var obj = {a:1, foo};
var a = 2;
doFoo(obj.foo); // 打印 2
示例:
function foo(){
console.log(this.a);
}
function doFoo(fn){
console.log(this);
fn();
}
var obj = {a:1, foo};
var a = 2;
var obj2 = {a:3, doFoo};
obj2.doFoo(obj.foo); // 打印 2
结论:如果把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的 this 指向无关。在非严格模式下,会把该函数的 this 绑定到 window 上。严格模式下绑定到 undefine
示例:
var obj1 = {
a:1
};
var obj2 = {
a:2,
foo1: function(){
console.log(this.a); // 打印 2
},
foo2: function(){
setTimeout(function(){
console.log(this); // 打印 window 对象
console.log(this.a); // 打印 3
}, 0);
}
};
var a =3;
obj2.foo1();
obj2.foo2(); // this 永远指向最后调用它的那个对象。。。
使用 call() 或者 apply() 的函数是会直接执行的。
bind() 是创建一个新的函数,需要手动调用才会执行
专利局( JS 混淆,大量运用原型链 )( F12 打开控制台 ):http://cpquery.sipo.gov.cn/
示例( 函数内嵌套的函数中的 this 指向 window ):
var obj1 = {
a:1
};
var obj2 = {
a:2,
foo1: function(){
console.log(this.a);
},
foo2: function(){
var that = this;
console.log(that); // 打印 obj2 对象
function inner(){
console.log(this); // 打印 window 对象
console.log(this.a); // 打印 window 对象 的 a
}
inner();
inner.call(obj2); // 改变 this 指向 obj2, 所以 打印 2
}
};
var a =3;
obj2.foo1(); // 打印 2
obj2.foo2(); // 打印 3
示例:
function foo(){
console.log(this.a);
return function(){
console.log(this.a)
};
}
var obj = {a:1};
var a = 2;
foo();
foo.call(obj);
foo().call(obj); // foo的返回值是一个函数,再进行call,改变this指向
//结果:
// 2 ---> foo(); 输出结果
// 1 ---> foo().call(obj); 改变this指向 obj, 输出 1
// 2 --->
// 1 ---> foo 的返回值,再进行call,改变this指向
function Person(name){
this.name = name;
this.foo1 = function(){
console.log(this.name);
};
this.foo2 = function(){
return function(){
console.log(this.name);
};
};
}
var person1 = new Person('person1');
person1.foo1();
person1.foo2()();
var name = 'window'
function Person(name){
this.name = name;
this.foo = function(){
console.log(this.name);
return function(){
console.log(this.name);
};
};
}
var person1 = new Person('person1');
var person2 = new Person('person2');
person1.foo.call(person2)();
person1.foo().call(person2);
箭头函数绑定中:this 的指向由外层作用域决定的,并且指向函数定义时的 this,而不是执行时的 this。
var obj = {
name: 'obj',
foo1: () => {
console.log(this.name);
},
foo2: function(){
console.log(this.name);
return () => {console.log(this.name)};
}
};
var name = 'window';
obj.foo1();
obj.foo2()();
结果:
window
obj
obj
示例:
var name = 'window';
function Person(name){
this.name = name;
this.foo1 = function(){
console.log(this.name);
};
this.foo2 = ()=>{console.log(this.name);};
}
var Person2 = {
name: 'Person2',
foo2: ()=>{ console.log(this.name); }
};
var person1 = new Person('person1');
person1.foo1();
person1.foo2();
Person2.foo2();
结果:
person1
person1
window
示例:( 箭头函数 与 call 结合 )
箭头函数里面的 this 是由外层作用域来决定的,并且指向函数定义时的 this,而不是执行时的 this
字面量创建的对象,作用域是 window,如果里面有箭头函数属性的话, this 指向的是 window
构造函数创建的对象,作用域是可以理解为是这个构造函数,且这个构造函数的 this 是指向新建的对象的,因此 this 指向这个对象
箭头函数的 this 是无法通过 bind、call、apply 来直接修改,但是可以用过改变作用域中 this 的指向来间接修改
var name = 'window';
var obj1 = {
name: 'obj1',
foo1: function(){
console.log(this.name);
return ()=>{ console.log(this.name); }
},
foo2: ()=>{
console.log(this.name);
return function(){
console.log(this.name);
}
}
}
var obj2 = { name: 'obj2' }
obj1.foo1.call(obj2)()
obj1.foo1().call(obj2)
obj1.foo2.call(obj2)()
obj1.foo2().call(obj2)
结果:
obj2
obj2
obj1
obj1
window
window
window
obj2
1. 新生成了一个对象
2. 链接到原型
3. 绑定 this
4. 返回新对象
更多可以参看 《JavaScript高级程序设计 第4版》