我转过几个弯 绕过那个小雨楼
拿着蒲扇摆着衣衫渡着紧箍咒
不问天涯不停留 喝过几壶酒
不过年少白头道义放胸口
倘若明天之后 遥看前尘剑封侯
似那天上神仙无所求
朝朝暮暮君如梦醒十分不为何理由
是真是假是惶恐是无休
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
又过了几个弯 算尽天量道莫慌
踏这田园闻这芳草香
跌跌撞撞仗剑天涯折煞不枉无笔良
是梦是幻是温柔是家乡
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
谁能与我一醉方休
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
函数因为有参数,后面就接一个括号,括号里面放参数。并且运行
评论(0)