Object 对象的扩展
属性的简写
es6允许对属性进行简写,可以直接使用变量,变量名直接成为了属性名。
var a = "hello";
var b = {a};
//等同于
var b = {
a:a
}
属性里的方法也可以简写
var a = {
b(){...}
}
//等同于
var a = {
b: function(){
...
}
}
在CommonJS模块输出变量时,也就是node模块导出时,这种简写就显得十分方便,我们直接导致一个对象,对象里面使用简写。
var a = {};
var b = {};
module.exports = {a,b}
而属性的赋值器,也采用了这种写法
var a = {
b: "hello",
get b() {
return this.b;
},
set b(value) {
this.b = value;
}
}
如果是一个Generator函数,简写的时候需要在前面加上*
符号
var a = {
*b() {
yield "hello";
}
}
属性名表达式
es6支持属性名可以使用表达式生成,原来都是固定了,必须为string,现在还可以使用计算生成
var a = {
["b"+"c"]: true,
};
a.bc; //true
通过将计算的代码放入方括号中,便可以使用表达式了,为此,我们甚至可以使用变量。
var bc = "d";
var a = {
[bc]: true,
};
a.d; //true
使用这种方式,我们还可以自定义方法名。
var a = {
["h"+"ello"](){
return "hello";
},
};
a.hello(); //"hello"
但是需要注意:属性名表达式不能和简写形式同时使用,否则会报错。
var a = "b";
var b = "d";
var c = {[a]};//报错
//正确写法
var c = {
[a]:b
};
c.b; //"d"
如果表达式最后返回的是一个object对象,它其实会被转为字符串[object Object]
,所以需要避免这种情况。
var a = {
name:"a"
};
var b = {
name:"b"
};
var c = {
[a]:a,
[b]:b,
}
console.log(c); //Object { "[object Object]": {…} }
你会发现只有一个[object Object],原因是相同的key值,后面的会覆盖前面的。
方法的name属性
函数的name属性返回函数名,对象中的方法也是函数,他也有name属性。
var a = {
b(){}
};
a.b.name; //"b"
Object.is()
在es5中,判断两个值是否相等,只有两种方式:==
和===
,但是这两种方式都有缺点,前者他会自动转换数据类型,后者两个NaN判断为不相等,+0和-0却又相等。
为了更明确的判断,es6增加了这个方法,他的行为与===
全等运算符相同,但是可以正确判断NaN和+0-0。
Object.is("a","a"); //true
Object.is({},{}); //false
Object.is(NaN,NaN); //true
Object.is(+0,-0); //false
Object.assign()
Object.assign用于将源对象所有可枚举的属性复制到目标对象,所以他是一个浅克隆,如果源对象的属性是一个引用类型,复制的其实是这个对象的引用。
如果源对象的引用类型发生改变,复制的值也会发生改变。
Object.assign方法可以有多个源对象,所以,如果有相同的属性,后面的会替换前面的。
var a = {name:"a"};
var b = {name:"b",age:20};
Object.assign(a,b);
//{name:"b",age:20}
Object.assign的第一个参数,是目标对象,后面的参数为源对象,源对象可以有多个,通过逗号隔开。
如果只有一个参数,也就是目标对象参数,那么Object.assign会直接返回这个目标对象。
默认格式转换
如果参数不是一个对象,会被默认转换为一个对象,但是undefined和null无法转为对象,所以如果他们作为目标对象,是会报错的,如果是作为源对象,它其实会被忽略。
Object(转换的值);
处理数组
Object.assign([1,2,3],[4,5]); //[4,5,3]
原因是因为数组被转换为对象了,数组的下标就是对象的属性名,于是相同的属性名被后来的替换了。
Object.assign()的常见用途
- 为对象添加属性
class Point {
constructor(x,y) {
Object.assign(this,{x,y});
}
}
这种用法也常用在给vue的this上下文添加属性或替换属性的值。
- 为对象添加方法
以前都是通过prototype原型链添加,每次都要书写这个属性,太麻烦了,使用Object.assign可以减少书写重复代码
Object.assign(obj.prototype,{a(){...}},{b(){...}})
- 浅克隆对象
var a = {name:"a"};
Object.assign({},a);
如果需要保持继承对象的原型链,那么就需要这样写
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto),origin);
}
Object.getPrototypeOf
方法返回指定对象的原型,Object.create
创建一个新对象,第一个参数是原型,也可以是null,这里使用了获取到的原型,然后再进行复制属性。
- 合并多个对象
const merge = (...sources) => {
return Object.assign({},...sources);
}
- 为属性指定默认值
const a = {
b:1,
c:2
};
function processContent(options) {
return Object.assign({},a,options);
}
这样,我们默认设置的值和传入的options参数会进行一个合并操作,如果没有传入对应的属性,使用的就是默认的a预设值。
当然这种方法也有缺陷,就是所有的值都必须是简单值,不能是对象,如果是对象,当存在相同的key时,替换的是整个值,而不是对象里面的相同项
var a = {
b:1,
c: {
name:"a",
type:"xxx"
}
}
Object.assign({},a,{c:{ccc:""}});
//{b:1,c:{ccc:""}}
属性的可枚举性
对象中每一个属性都具有一个描述对象Descriptor,这个描述对象类似于属性的配置选项,用于控制改属性的行为。
如何获取一个属性的描述对象?
var a = {name:"a"};
Object.getOwnPropertyDescriptor(a,"name");
//{
// value: "a",
// writable: true,
// enumerable: true,
// configurable: true
//}
其中enumerable就是控制属性是否可枚举,默认是true,当我们设置为false时,部分方法就无法遍历出该属性
Object.defineProperties(a,{
name: {
enumerable: false
}
})
当属性被设置为无法枚举后,下面这些方法就无法获取到对应的值!
- for...in循环,只能遍历自身和继承的可枚举属性
- Object.keys(),返回对象自身可枚举的属性键名数组
- JSON.stringify(),只串行话对象自身可枚举的属性。
- Object.assign(),只复制对象自身的可枚举属性。
上面4中方法,只有for...in可以遍历继承的属性,而enumerable的存在,本身就是为了让某些属性可以规避for...in循环,比如原型的toString方法就是通过这种方式,不会被for...in遍历出来的。
Object.getOwnPropertyDescriptor(Object.prototype,'toString').enumerable;
//false
另外,es6规定,class的原型链的方法都是不可枚举的。
总的来说,原型链的继承会使得属性变得复杂,但事实上我们一般情况下只关系对象自身的属性,所以推荐是尽量不要用for..in循环,而是改用Object.keys()代替。
属性的遍历
es6一共有5中方法可以遍历对象的属性。
- for...in循环,遍历对象自身和继承的可枚举属性,不包含Symbol属性
- Object.keys(obj),返回数组,数组中包含对象自身的可枚举属性,不包含继承和Symbol属性
- Object.getOwnPropertyNames(obj),返回一个数组,数组中包含对象自身所有属性,包括不可枚举属性,但是不包含Symbol属性
- Object.getOwnPropertySymbols(obj),返回一个数组,数组包含对象自身的所有Symbol属性
- Reflect.ownKeys(obj),返回一个数组,包含对象自身的所有属性,不管是属性名还是Symbol,或者是字符串,也不管是否不可枚举
以上5中方法,属性的顺序都按照以下规则:
- 首先是属性名为数值的,0,1,2这种数值,从小到大排序
- 其次按照属性名为字符的属性,按照生成的时间顺序排序
- 再就是Symbol属性,按照生成的时间排序
__proto__
该属性是一个浏览器广泛支持的一个属性,但是并不是es6规定的一个属性,也只有浏览器需要部署这个属性,其他环境则不用,该属性可以用来读取和设置对象的prototype对象,目前ie11及其他浏览器都支持该属性。
但是并不建议使用,如果你要设置一个prototype对象,使用Object.setPrototypeOf()
,读取使用Object.getPrototypeOf()
,创建使用Object.create()
来进行代替。
如果一个对象本身部署了该属性,这个属性的值就是对象的原型。
Object.getPrototypeOf()和Object.setPrototypeOf()
该方法和Object.setPrototypeOf()配合使用,get用于获取对象的prototype原型,set则用于设置对象的prototype原型。
function Rectangle() {
...
}
var a = new Rectangle();
Object.getPrototypeOf(a) === Rectangle.prototype; //true
//修改
Object.setPrototypeOf(a,Object.prototype);
Object.getPrototypeOf(a) === Rectangle.prototype; //false
如果获取的参数他不是一个对象,会被默认转换为对象。
Object.getPrototypeOf(1);
//等同于
Object.getPrototypeOf(Number(1));
如果是undecided和null,他们无法转换为对象,所以会报错。
Object.keys(),Object.values(),Object.entries()
Object.keys()
es6引入了这个方法,它会返回一个对象所有的key键名的数组,不包含继承的所有可遍历的属性键值。
const a = {a:1,b:2};
Object.keys(a); //["a","b"];
es7中还将Object.values(),Object.entries()加入,和keys一起作为遍历一个对象的补充手段,配置for...of使用
const a = {a:1,b:2};
let {keys} = Object;
for(let key of keys(a)) {
console.log(key);
}
//a
//b
Object.values()
该方法返回一个数组,成员就是参数对象的所有可遍历的键值,不包含继承。
const a = {a:1,b:2};
Object.values(a); //[1,2];
这个数组的顺序,和属性的遍历讲的顺序是一样的,先是数字的key从小到大,然后是字符的key,按照创建顺序,然后是Symbol,这里Symbol是被忽略的。
const a = {a:1,2:"r",1:"f"};
Object.values(a); //["f","r",1];
当我们使用Object.create()去创建一个对象的时候,需要注意一下。该方法第一个参数是原型,可以为null,第二个参数,是添加到创建的新对象上的可枚举属性,但是这个枚举是需要自己设置的,他其实就是一个描述对象的集合,默认的enumerable属性都是false
var a = Object.create({},{
p: {
value:42
}
});
console.log(a); // {p:42}
Object.values(a); // []
values得到的是空数组,因为enumerable是false,虽然p属性已经是a的自身属性了,但是因为不可枚举,values就无法获取到。
所以我们要手动设置
var a = Object.create({},{
p: {
value:42,
enumerable:true
}
});
console.log(a); // {p:42}
Object.values(a); // [42]
keys,values,entries都会对非对象的参数进行对象转换,但是undefined和null都是无法转换的,所以使用这些方法转换他们时会报错。
Object.entries()
Object.entries()方法返回一个数组,成员是参数对象自身的所有可枚举属性和属性值的数组。不包含继承。
var a = {b:1,c:2};
Object.entries(a); //[["b",1],["c",2]]
这个方法除了返回的值不一样,其他和values都一样。
Object.entries()的基本用途就是遍历对象的属性,另一个用处就是将对象转为Map对象。
var a = {b:1,c:2};
var map = new Map(Object.entries(a));
//Map {b:1,c:2}
对象的扩展运算符
解构赋值
const {a,b,...c} = {a:1,b:2,c:3,d:4,e:5};
a //1
b //2
c // {c:3,d:4,e:5}
c会将后面所有可遍历的,但尚未被读取的属性都赋值到自己。
因为解构,所以扩展运算符要写在最后面,并且等号右边不是undefined或者null,否则就会报错。
解构赋值,他也是浅复制,所以,如果值是对象,解构赋值复制的只是引用,而不是副本。
另外使用扩展运算符解构赋值也不会继承原型的属性。
var a = Object.create({x:1,y:2});
a.z = 3;
let {x,...{y,z}} = a;
x //1
y //undefined
z //3
x因为没有使用扩展运算符,就是单纯的解构赋值,它是可以读取到prototype的,所以x有值,后面使用了扩展运算符,再又进行了一次解构,y对应的是原型的y,但是扩展运算符不会继承原型属性,所以拿不到,于是成了undefined,z是因为后来的设置的,z不是原型而是自身,所以有值。
解构赋值的另一个用处就是扩展某个函数的参数,引入其他操作
function a({b,c,...d}) {
}
b和c用于解构,d则通过扩展运算符接收多余的参数,以便进行其他操作。
扩展运算符
扩展运算符用于取出参数对象的所有可遍历属性,并且其复制到当前对象中
var a = {b:1,c:2};
let n = {...a}; //{b:1,c:2}
这种方式等同于Object.assign({},a)
这种方式只是复制了对象的自身属性,如果要完整的克隆一个对象,就需要复制对象的原型,可以这样做。
var a = {b:1,c:2};
Object.assign(Object.getPrototypeOf(a),a);
扩展运算符可用于两个或者对个对象合并
let ab = {...a,...b};
原则依旧是后面的覆盖前面的
let ab = {...a,x:1,y:2};
如果a里面有x或者y,会被后面的覆盖,所以这种特性也可以用来继承一个对象属性,只修改几个属性。
var a = {b:1,c:2,d:3};
var b = {...a,b:4}; //{b:4,c:2,d:3}
和数组的扩展运算符一样,对象的扩展运算符也可以在后面接运算使用
var a = ...(x>1?b:c)
如果后面接的是一个空对象,则无任何效果,如果是undefined或者null,则会被忽略,不会报错。
var a = {...{},b:1}; //{b:1}
var b = {...undefined,...null}; //{}
如果扩展运算符的参数对象中有取值的get函数,在运算时会被运行,因为扩展也是一次取值。
//这个时候不会抛出错误,因为x没有被执行
let aWithXGetter = {
...a,
get x(){
throw new Error("not throw yet")
}
}
//这个时候会抛出错误,因为x被执行了
let aWithXGetter = {
...a,
...get x(){
throw new Error("not throw yet")
}
}
Object.getOwnPropertyDescriptors()
es5中Object.getOwnPropertyDescriptors用于返回某个对象属性的描述对象,在es7中,Object.getOwnPropertyDescriptors用于返回指定对象所有的自身属性(非继承属性)的描述对象。
es5中Object.getOwnPropertyDescriptors接收两个参数,第一个是对象,第二个是要获取描述对象的属性名
var a = {b:1,c:2};
Object.getOwnPropertyDescriptors(a,"b");
//{...}
但是现在es6就不一样了,他只接收一个参数,返回这个参数对象的所有属性的描述对象
var a = {b:1,c:2};
Object.getOwnPropertyDescriptors(a)
// {b:{....},c:{...}}
该方法的引入主要是为了解决Object.assign()无法正确复制get属性和set属性的问题。
const a = {
set foo(value) {
...
}
}
const b = Object.assign({},a);
console.log(b); //{foo:undefined}
Object.getOwnPropertyDescriptors(b);
// {
// foo:{
// configurable: true
// enumerable: true
// value: undefined
// writable: true
// }
}
你会发现没有set方法了。
因为Object.assign只会复制属性,并不会复制赋值的方法或者取值的方法,所以如果要正确的复制这个对象,需要Object.getOwnPropertyDescriptors和Object.defineProperties配合使用
const b = Object.defineProperties({},Object.getOwnPropertyDescriptors(a))
getOwnPropertyDescriptors可以获取到所有的描述,通过defineProperties重新定义一个对象。
getOwnPropertyDescriptors的另一个用处就是配合Object.create方法,将对象的属性,克隆到一个新对象上,这属于浅克隆
const clone = Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj))
Null传导运算符
在编程中,如果我们要读取对象中某个属性,往往需要判断对象是否存在。比如:
const firstName = (message && message.body && message.body.user && message.body.user.firstName) || "default";
这样层层判断非常麻烦,所以现在有个提案,引入Null传导运算符,简化上面的写法
const firstName = message?.body?.user?.firstName || "default";
上面代码中有很多个?.
运算符,只要其中一个返回undefined或者null,整个就不继续运算了,直接返回undefined。
他有四种用法:
- obj?.prop 读取对象属性
- obj?.[expr] 同上
- func?.(...args) 函数或者对象方法调用
- new C?.(...args) 构造函数的调用
例子:
//如果a存在,就运行a.b.c().d
a?.b.c().d;
//如果a不存在,下面代码无效果
a?.b = 42;
delete a?.b
//函数
function c(value) {
console.log(value);
}
c?.(1); //1
函数因为有参数,后面就接一个括号,括号里面放参数。并且运行
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据