对象的扩展
0、一句话总结
- 属性名,以及是属性的函数的简洁写法,写起来简单易阅读
- 属性名可以用变量字符串拼接起来(话说以前也有吧?)
- 函数都有name属性,但set和get也要加前缀
- Object.is判断两个变量是否相等
- Object.assign可以合并对象的非原型链上,且可枚举属性
- Object.getOwnPropertyDescriptor查看属性是否可枚举、可修改、可赋值
- Object.keys获取对象非原型链上,且可枚举属性的key
- Object.values获取对象非原型链上,且可枚举属性的值
- Object.entries同时获取上面两个
- 当属性在原型链上,或者不可枚举时,会被很多方法忽视(参照6.1)
- __proto__和prototype的关系(参照7.1)
- Object.setPrototypeOf(obj, prototype)设置__proto__属性
- Object.getPrototypeOf(obj)获取__proto__属性
- es7新增对对象有效的扩展运算符…(三个点)
本篇没有的请翻看【对象的扩展】系列的其他篇
4、Object的扩展
4.1、判断两个变量是否相等(Object.is)
Object.is(value1, value2);
简单的说,判断2个值是否相等,相等返回true,不等返回false。
类似===,但与其在某些表现略有差异,如代码:
Object.is(+0, -0); //false
+0 === -0; //true
Object.is(NaN, NaN); //true
NaN === NaN; //false
另外需要在注意的是,按引用传递类型,比如{}或者[],哪怕看起来都是空数组、空对象,但2个不同的变量作为参数时,他们是不相等的。
(我似乎提升
4.2、将对象的属性合并到另外一个对象之中(Object.assign)
4.2.1、定义
Object.assign(target, …sources)
简单来说,就是将参数2,3,4……里面的对象的所有 可枚举 属性,添加到参数1这个对象之中。
返回值是参数1这个对象(但因为对象是按引用传递,所以返回值和参数1是一样的);
需要注意的是,被添加对象的属性的数据类型,如果是按引用传递,那么该属性将以按引用传递的形式添加到第一个参数之中。
var foo = {
a: "1"
}
var child = {
name: "child"
}
var bar = {
child,
b: "2"
}
var target = {
name: "target"
}
var result = Object.assign(target, foo, bar);
console.log(target); //Object {name: "target", a: "1", child: Object, b: "2"}
console.log(target.child === child); //true
console.log(result === target); //true
4.2.2、属性覆盖:
同名属性,参数位置靠后者覆盖靠前者。
var foo = {
name: "1"
}
var target = {
name: "target"
}
Object.assign(target, foo);
console.log(target.name); //"1"
4.2.3、必须可枚举
可枚举,具体来说,就是该属性enumerable值为false时,如代码
var foo = Object.defineProperty({}, "name", {
value: "foo",
enumerable: false
})
console.log(foo.name); //"foo"
var target = {
name: "target"
}
Object.assign(target, foo);
console.log(target.name); //"target"
注意:
通过 Object.defineProperty 方法添加新属性,未设置的属性名,不然上面里不包含writable和configurable,其默认值为false
4.2.4、参数一必须是对象或者可以转为对象
参数一如果不是对象,那么至少要能转为对象,例如:
var num = Object.assign(1);
console.log(num); //Number {[[PrimitiveValue]]: 1}
//作为对比
new Number(1); //Number {[[PrimitiveValue]]: 1}
说明num是一个Number类型的对象。他可以像number一样进行正常的数学运算;
也可以像一个对象一样添加属性,只要不对它进行赋值,那么就会一直保持是对象状态。
如代码:
var num = Object.assign(1);
typeof num; //"object"
Object.assign(num, {name: "obj"});
num.name; //"obj"
num + 10; //11
Object.prototype.toString.call(num); //"[object Number]"
类似可以推导到Boolean类型、String类型等基本类型,甚至可以对正则表达式使用,如代码:
var reg = Object.assign(/\d/, {name: "reg"})
reg; // /\d/
reg.name; //"reg"
但,基本类型里,null
和 undefined
不行。
另外,参数一也不能是函数,不管是声明的还是变量赋值的都不行,除非通过new生成该函数的实例。
var test = function () {
}
var res = Object.assign(test, {name: "fun"});
//Uncaught TypeError: Cannot assign to read only property 'name' of function 'function () {}'
4.2.5、其他参数会隐式转换为对象,并且将可枚举属性添加进去
之前提过,只有 可枚举 的属性可以添加,因此假如其他参数是number或者boolean显然是不行的,即使被转换为对象,也无法添加。
因此,基本类型里只有string有意义,例如”string”转为对象后,key为”0”时,值为”s”。
而扩展类型里,Array也是有意义的,他的下标就是key,并且该key可以被枚举。
4.2.6、再次提示,这种拷贝方法是浅拷贝
如果只是要合并属性是可以用的,但注意是浅拷贝,也就是数据类型为按引用传递类型时,只拷贝引用,而不是创建一个新的。
如果有深拷贝的需求,请自行寻找办法。
另外,因为这种方式是浅拷贝,所以不会递归执行,
因此也不必担心参数2和参数3有同名属性且该属性是按引用传递,参数3覆盖参数2在参数1的属性时,导致参数2这个对象的属性的值受到影响。
具体来说,其实质就是:
//a,b,c都有属性name,且name是不同的对象
Object.assign(a,b,c);
a.name = b.name;
a.name = c.name;
//结果是a.name = c.name,但b.name的值因为不是递归,所以自身不受影响。
4.2.7、对setter和getter不会正确生效
简单来说,set和get复制过来后,复制的仅是值,而不包括setter和getter的函数。如代码:
let foo = {
_val: 0,
get val() {
return this._val + 1;
},
set val(val) {
this._val = val;
}
}
var bar = Object.assign({}, foo);
foo; //{_val: 0}
bar; //{_val: 0, val: 1}
foo.val = 1;
foo.val; //2
bar.val = 1;
bar.val; //1
注意看以上,从foo通过assign将属性合并到bar后,val属性变成一个普通变量了。
对val属性的赋值和获取,也没有正确生效。
解决办法:
通过Object.getOwnPropertyDescriptors(obj)
(参照4.3)和Object.defineProperties()
两个方法完成获取和定义。
如代码:
let foo = {
_val: 0,
get val() {
return this._val + 1;
},
set val(val) {
this._val = val;
}
}
var getAttributes = Object.getOwnPropertyDescriptors(foo);
console.log(getAttributes); //这个自行查看吧,跟defineProperties的参数2是相同的,显示configurable、setter等属性
let bar = Object.defineProperties({}, getAttributes);
bar; //{_val: 0}
bar.val = 1;
bar.val; //2
4.3、查看是否可枚举、可修改、可赋值(Object.getOwnPropertyDescriptor)
Object.getOwnPropertyDescriptor(obj, prop)
Object.getOwnPropertyDescriptors(obj) //需要es7,IE11不支持
获取enumberable、configurable和writable属性,以及value,和通过Object.defineProperty设置其的行为类似。
第一个获取指定属性的,第二个获取所有属性的,就像设置他们一样的形式来获取;
let foo = {
name: "foo",
test: "test"
}
Object.defineProperty(foo, "name", {
enumerable: false
});
let one = Object.getOwnPropertyDescriptor(foo, "name");
console.log(one); //{value: "foo", writable: true, enumerable: false, configurable: true}
let all = Object.getOwnPropertyDescriptors(foo);
console.log(all); //Object {name: Object, test: Object}
//name和test也是对象,对象的属性参考上面的one
4.4、获取对象所有key(Object.keys)
Object.keys(obj)
获取obj对象,非原型链上,enumberable的值为true的所有属性的key,依次放到数组中并返回该数组。
var obj = Object.create({a: "A"}, {
b: {
value: "B",
enumerable: true
}
});
console.log(Object.keys(obj)); //["b"]
console.log(Object.values(obj)); //["B"]
4.5、获取对象所有值(Object.values)
Object.values(obj)
按照MDN说法,这个是实验性的,IE不兼容。但是我实测结果,IE11开IE9的文档模式,这个方法是可以正常执行的。
效果是获取obj对象,非原型链上,enumberable的值为true的所有属性的values。同样依次放到一个数组中并返回该数组。
代码参照4.4
4.6、同时获取对象的所有key和value(Object.entries)
Object.entries(obj)
兼容性不好,IE11才能跑,10都不行。
也是只对非原型链,以及enumerable值为true的才有效。
返回一个数组,数组的每个元素是一个key+val组合。
返回数组的每个元素也是一个数组,下标为0的地方为key,下标为1的为value。
如代码:
var obj = Object.create({a: "A"}, {
b: {
value: "B",
enumerable: true
},
c: {
value: "C",
enumerable: true
}
});
console.log(Object.entries(obj)); //[["b", "B"], ["c", "C"]]
5.属性的可枚举性
5.1、如何设置可枚举性
Object.defineProperty(obj, prop, descriptor)
Object.defineProperties(obj, props)
第一个设置单个属性的,第二个可以同时设置多个属性。
通过设置属性的enumerable属性来设置其是否可枚举,true可枚举,false不可枚举。
在未锁定前可以设置,锁定后不可修改(并且会报错)
var foo = {
name: "foo"
}
Object.defineProperty(foo, "name", {
configurable: false
});
var res = Object.getOwnPropertyDescriptor(foo, "name");
console.log(res); //Object {value: "foo", writable: true, enumerable: true, configurable: false}
Object.defineProperty(foo, "name", {
enumerable: false
}); //Uncaught TypeError: Cannot redefine property: name
5.2、查看是否可枚举(以及其他)
参照4.3中的:
Object.getOwnPropertyDescriptor(obj, prop)
Object.getOwnPropertyDescriptors(obj) //需要es7,IE11不支持
5.3、当可枚举属性为false时,被忽视的情况
for in
遍历对象属性;
Object.keys()
获取所有key
JSON.stringify()
JSON序列化
原因是以上都会去遍历对象的key,然后根据key来获取值,如果不能枚举显然不能遍历咯。
var foo = Object.defineProperty({test: "test"}, "name", {
value: "foo",
enumerable: false,
writable: true,
configurable: true
})
for (var i in foo) {
console.log(i);
}
//test
console.log(Object.keys(foo)); //["test"]
console.log(JSON.stringify(foo)); //{"test":"test"}