这个世界上的大部分传奇,不过是普普通通的人们,将心意化作了行动而已。
今日学习内容 1、JS 红皮书 P213-219 第八章:对象、类与面向对象编程
今日笔记 1、对象标识及相等判定: 在 ECMAScript 6 之前,有些特殊情况即使是===操作符也无能为力:
1 2 3 4 5 6 7 8 9 10 11 console .log (true === 1 ); console .log ({} === {}); console .log ("2" === 2 ); console .log (+0 === -0 ); console .log (+0 === 0 ); console .log (-0 === 0 ); console .log (NaN === NaN ); console .log (isNaN (NaN ));
为改善这类情况,ECMAScript 6 规范新增了 Object.is(),这个方法与===很像,但同时也考虑到了上述边界情形。这个方法必须接收两个参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 console .log (Object .is (true , 1 )); console .log (Object .is ({}, {})); console .log (Object .is ("2" , 2 )); console .log (Object .is (+0 , -0 )); console .log (Object .is (+0 , 0 )); console .log (Object .is (-0 , 0 )); console .log (Object .is (NaN , NaN )); function recursivelyCheckEqual (x, ...rest ) { return ( Object .is (x, rest[0 ]) && (rest.length < 2 || recursivelyCheckEqual (...rest)) ); }
2、增强的对象语法: ECMAScript 6 为定义和操作对象新增了很多极其有用的语法糖特性。这些特性都没有改变现有引擎的行为,但极大地提升了处理对象的方便程度。
属性值简写: 在给对象添加变量的时候,开发者经常会发现属性名和变量名是一样的。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let name = "Nicholas" ;let person = { name : name, }; let name = "Matt" ;let person = { name, }; console .log (person); function makePerson (name ) { return { name, }; } let person = makePerson ("Matt" );console .log (person.name ); function makePerson (a ) { return { name : a, }; } var person = makePerson ("Matt" );console .log (person.name );
可计算属性: 在引入可计算属性之前,如果想使用变量的值作为属性,那么必须先声明对象,然后使用中括号语法来添加属性。换句话说,不能在对象字面量中直接动态命名属性。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const nameKey = "name" ;const ageKey = "age" ;const jobKey = "job" ;let person = {};person[nameKey] = "Matt" ; person[ageKey] = 27 ; person[jobKey] = "Software engineer" ; console .log (person); const nameKey = "name" ;const ageKey = "age" ;const jobKey = "job" ;let person = { [nameKey]: "Matt" , [ageKey]: 27 , [jobKey]: "Software engineer" , }; console .log (person); const nameKey = "name" ;const ageKey = "age" ;const jobKey = "job" ;let uniqueToken = 0 ;function getUniqueKey (key ) { return `${key} _${uniqueToken++} ` ; } let person = { [getUniqueKey (nameKey)]: "Matt" , [getUniqueKey (ageKey)]: 27 , [getUniqueKey (jobKey)]: "Software engineer" , }; console .log (person);
注意 可计算属性表达式中抛出任何错误都会中断对象创建。如果计算属性的表达式有副作用,那就要小心了,因为如果表达式抛出错误,那么之前完成的计算是不能回滚的。
简写方法名: 在给对象定义方法时,通常都要写一个方法名、冒号,然后再引用一个匿名函数表达式,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 let person = { sayName : function (name ) { console .log (`My name is ${name} ` ); }, }; person.sayName ("Matt" ); let person = { sayName (name ) { console .log (`My name is ${name} ` ); }, }; person.sayName ("Matt" );
简写方法名对获取函数和设置函数也是适用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let person = { name_ : "" , get name () { return this .name_ ; }, set name (name ) { this .name_ = name; }, sayName ( ) { console .log (`My name is ${this .name_} ` ); }, }; person.name = "Matt" ; person.sayName ();
简写方法名与可计算属性键相互兼容:
1 2 3 4 5 6 7 const methodKey = "sayName" ;let person = { [methodKey](name) { console .log (`My name is ${name} ` ); }, }; person.sayName ("Matt" );
3、对象解构: ECMAScript 6 新增了对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作。简单地说,对象解构就是使用与对象匹配的结构来实现对象属性赋值。 下面的例子展示了两段等价的代码,首先是不使用对象解构的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let person = { name : "Matt" , age : 27 , }; let personName = person.name , personAge = person.age ; console .log (personName); console .log (personAge); let person = { name : "Matt" , age : 27 , }; let { name : personName, age : personAge } = person;console .log (personName); console .log (personAge);
使用解构,可以在一个类似对象字面量的结构中,声明多个变量,同时执行多个赋值操作。如果想让变量直接使用属性的名称,那么可以使用简写语法,比如:
1 2 3 4 5 6 7 let person = { name : 'Matt' , age : 27 }; let { name, age } = person; console .log (name); console .log (age);
解构赋值不一定与对象的属性匹配。赋值的时候可以忽略某些属性,而如果引用的属性不存在,则该变量的值就是 undefined:
1 2 3 4 5 6 7 let person = { name : 'Matt' , age : 27 }; let { name, job } = person; console .log (name); console .log (job);
也可以在解构赋值的同时定义默认值,这适用于前面刚提到的引用的属性不存在于源对象中的情况:
1 2 3 4 5 6 7 let person = { name : 'Matt' , age : 27 }; let { name, job='Software engineer' } = person; console .log (name); console .log (job);
解构在内部使用函数 ToObject()(不能在运行时环境中直接访问)把源数据结构转换为对象。这意味着在对象解构的上下文中,原始值会被当成对象。这也意味着(根据 ToObject()的定义),null和 undefined 不能被解构,否则会抛出错误。
1 2 3 4 5 6 let { length } = 'foobar' ; console .log (length); let { constructor : c } = 4 ; console .log (c === Number ); let { _ } = null ; let { _ } = undefined ;
解构并不要求变量必须在解构表达式中声明。不过,如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中:
1 2 3 4 5 6 7 let personName, personAge; let person = { name : 'Matt' , age : 27 }; ({name : personName, age : personAge} = person); console .log (personName, personAge);
3、嵌套解构: 解构对于引用嵌套的属性或赋值目标没有限制。为此,可以通过解构来复制对象属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 let person = { name : 'Matt' , age : 27 , job : { title : 'Software engineer' } }; let personCopy = {};({ name : personCopy.name , age : personCopy.age , job : personCopy.job } = person); person.job .title = 'Hacker' console .log (person); console .log (personCopy); let person = { name : 'Matt' , age : 27 , job : { title : 'Software engineer' } }; let { job : { title } } = person; console .log (title);
在外层属性没有定义的情况下不能使用嵌套解构。无论源对象还是目标对象都一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let person = { job : { title : 'Software engineer' } }; let personCopy = {}; ({ foo : { bar : personCopy.bar } } = person); ({ job : { title : personCopy.job .title } } = person);
部分解构: 需要注意的是,涉及多个属性的解构赋值是一个输出无关的顺序化操作。如果一个解构表达式涉及多个赋值,开始的赋值成功而后面的赋值出错,则整个解构赋值只会完成一部分:
1 2 3 4 5 6 7 8 9 10 11 let person = { name : 'Matt' , age : 27 }; let personName, personBar, personAge; try { ({name : personName, foo : { bar : personBar }, age : personAge} = person); } catch (e) {} console .log (personName, personBar, personAge);
参数上下文匹配: 在函数参数列表中也可以进行解构赋值。对参数的解构赋值不会影响 arguments 对象,但可以在函数签名中声明在函数体内使用局部变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let person = { name : 'Matt' , age : 27 }; function printPerson (foo, {name, age}, bar ) { console .log (arguments ); console .log (name, age); } function printPerson2 (foo, {name: personName, age: personAge}, bar ) { console .log (arguments ); console .log (personName, personAge); } printPerson ('1st' , person, '2nd' ); printPerson2 ('1st' , person, '2nd' );