1.属性的简洁表示法

ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。

  1. const foo = 'bar';
  2. const baz = {foo};
  3. baz //{foo:"bar"}
  4. //等同于
  5. const baz = {foo:foo}

上面代码中,变量foo直接写在大括号里,属性名就是变量名,属性值就是变量值。

  1. function f(x,y){
  2. return {x,y};
  3. }
  4. //等同于
  5. function f(x,y){
  6. return {x:x,y:y};
  7. }
  8. f(1,2) //Object {x:1,y:2}

除了属性简写,方法也可以简写。

  1. const o = {
  2. method(){
  3. return "Hello";
  4. }
  5. };
  6. //等同于
  7. const o = {
  8. method:function(){
  9. return "Hello!";
  10. }
  11. };

CommonJS模块输出一组变量,就非常适合使用简洁写法。

  1. let ms = {};
  2. function getItem(key){
  3. return key in ms ? ms[key] : null;
  4. }
  5. function setItem(key,value){
  6. ms[key] = value;
  7. }
  8. function clear(){
  9. ms = {};
  10. }
  11. module.exports = {getItem,setItem,clear};
  12. //等同于
  13. module,exports = {
  14. getItem:getItem,
  15. setItem:setItem,
  16. clear:clear
  17. };

2.属性名表达式

JavaScript 定义对象的属性有两种方法:

  1. obj.foo = true;
  2. obj['a' + 'bc'] = 123;

但是,,如果使用字面量方式定义对象(使用大括号),在ES5中只能使用方法一(标识符)定义属性。

  1. var obj = {
  2. foo:true,
  3. abc:123
  4. };

ES6:

  1. let propKey = 'foo';
  2. let obj = {
  3. [propKey]:true,
  4. ['a'+'bc']:123
  5. };
  6. //obj.abc
  7. //123
  8. let lastWord = 'last word';
  9. const a = {
  10. 'first word':'hello',
  11. [lastWord] :'world'
  12. };
  13. a['first word'] //"hello"
  14. a[lastWord] //"world"
  15. a['lastWord'] //"world"

注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]。
**

  1. const keyA = {a:1};
  2. const keyB = {b:2};
  3. const myObject = {
  4. [keyA]:'valueA',
  5. [keyB]:'valueB'
  6. };
  7. myObject //{[object Object]: "valueB"}
  8. [object Object]: "valueB"__proto__: Object

3.方法的name属性

函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。

  1. const obj = {
  2. get foo() {},
  3. set foo(x){}
  4. };
  5. obj.foo.name //Uncaught TypeError: Cannot read property 'name' of undefined
  6. const descriptor = Object.getOwnPropertyDescriptor(obj,'foo');
  7. descriptor.get.name //"get foo"
  8. descriptor.set.name //"set foo"

特例:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的而函数,name返回anonymous。

  1. (new Function()).name //"anonymous"
  2. var doSomething = function(){
  3. //...
  4. };
  5. doSomething.bind().name //"bound doSomething"

如果对象的方法是一个Symbol值,那么name属性返回的是这个Symbol的描述。

  1. const key1 = Symbol('description');
  2. const key2 = Symbol();
  3. let obj = {
  4. [key1](){},
  5. [key2](){},
  6. };
  7. obj[key1].name //"{description}"
  8. obj[key2].name //""

4.属性的可枚举性和遍历


可枚举性

对象的每一个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

  1. let obj = {foo:123};
  2. Object.getOwenPropertyDescriptor(obj,'foo')
  3. //{value: 123, writable: true, enumerable: true, configurable: true}

描述对象的enumerable属性,称为可枚举性,如果该属性为false,就表示某些操作会忽略当前属性:

  • for…in循环:只遍历对象自身的和继承的可枚举的属性。
  • Object.keys():返回对象自身的所有可枚举的属性的键名。
  • JSON.stringify():只串行化对象自身的可枚举的属性。
  • Object.assign():忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性。

这四个属性中,前三个是ES5就有的,最后一个Object.assign()是ES6新增的。其中,只有for..in会返回集继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。
ES6规定,所有Class的原型的方法都是不可枚举的。

操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以尽量不要用for...in循环,而用Object.keys()代替。
**


属性的遍历

  1. for…in

for...in循环遍历对象自身的和可继承的枚举属性(不含Symbol属性)。

  1. Object.keys(obj)

Object.keys()返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)的键名。

  1. Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames()返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是不包括不可枚举属性)的键名。

  1. Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols()返回一个数组,包含对象自身的所有Symbol属性的键名。

  1. Reflect.ownKeys(obj)

**Reflect.ownKey()**返回一个数组,包含对象自身的所有键名,不管键名是Symbol或字符串,也不管是否可枚举。

以上5中方法遍历对象的键名,都遵守同样的属性遍历的次序规则:

  • 首先遍历所有数值键,按照数值升、升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有Symbol键,按照加入时间升序排列。

5.super关键字

super关键字指向当前对象的原型对象。

  1. const proto = {
  2. foo:'hello'
  3. };
  4. const obj = {
  5. foo:'world',
  6. find(){
  7. return super.foo;
  8. }
  9. };
  10. Object.setPrototypeOf(boj,proto);
  11. obj.find() //"hello"

上面代码中,对象obj.find()方法之中,通过super.foo引用了原型对象proto的foo属性。

注意,super关键字表示原型对象时,只能在对象的方法之中,用在其他地方都会报错,例如:
**

  1. const obj = {
  2. foo:super.foo
  3. }
  4. const obj = {
  5. foo:()=>super.foo
  6. }
  7. const obj = {
  8. foo:function(){
  9. return super.foo
  10. }
  11. }

上面三种用法都会报错,因为对于JavaScript引擎来说,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。目前,只有对象方法的简写法可以让JavaScript引擎确认,定义的是对象的方法。

JavaScript引擎内部,super.foo等同于Object.getPrototypeOf(this).foo属性或Object.getPrototypeOf(this).foo.call(this)(方法)。

  1. const proto = {
  2. x:'hello',
  3. foo(){
  4. console.log(this.x);
  5. },
  6. };
  7. const obj = {
  8. x:'world',
  9. foo(){
  10. super.foo();
  11. }
  12. }
  13. Object.setPrototypeOf(obj,proto);
  14. obj.foo() //"world"

上面代码中,super.foo指向原型对象protofoo方法,但是绑定的this还是当前对象obj,因此输出的就是world

6.对象的扩展运算符

解构赋值

对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有的可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。

  1. let {x,y,...z} = {x:1,y:2,a:3,b:4};
  2. x // 1
  3. y //2
  4. z //{a:3,b:4}

由于解构赋值要求等号右边是一个对象,所以如果等号右边是Undefinednull,就会报错,因为它们无法转为对象。

  1. let {...z} = null;
  2. let {...z} = undefined;

解构赋值必须是最后一个参数,否则会报错。

注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数),那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

  1. let obj = {a:{b:1}};
  2. let {...x} = obj;
  3. obj.a.b = 2;
  4. x.a.b //2

另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性,

  1. let o1 = {a:1};
  2. let o2 = {b:2};
  3. o2.__proto__ = o1;
  4. let {...o3} = o2;
  5. o3 //{b:2}
  6. o3.a //undefined

o3只复制了o2自身的属性,没有复制它的原型对象o1的属性。

  1. const o = Object.create({x:1,y:2});
  2. o.z=3;
  3. let {x,...newObj } = o;
  4. let {y,z} = newObj;
  5. x //1
  6. y //defined
  7. z //3

上面代码中,变量x是单纯的解构赋值,所以可以读取对象o继承的属性;变量yz是扩展运算符的解构赋值,所以只能读取对象o自身的属性,所以变量z可以赋值成功。

ES6规定,变量声明语句之中,如果使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式,所以上面代码引入了中间变量newObj,如果写成下面这样会报错:

  1. let {x,...{y,z}} = o;
  2. //Uncaught SyntaxError: `...` must be followed by an identifier in declaration contexts

解构赋值的一个用处,是扩展某个函数的参数,引入其他操作。

  1. function baseFunction({a,b}){
  2. //...
  3. }
  4. function wrapperFunction({x,y,...restConfig}){
  5. //使用了x和y参数进行操作
  6. //其余参数传给原始函数
  7. return baseFunction(restConfig);
  8. }

扩展运算符

对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

  1. let z ={a:3,b:4};
  2. let n = { ...z};
  3. n //{a:3,b:4}

由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。

  1. let foo = {...['a','b','c']};
  2. foo
  3. //{0: "a", 1: "b", 2: "c"}

如果扩展运算符后面是一个空对象,则没有任何效果。
如果扩展运算符后面不是个对象,则会自动将其转为对象。
如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。

  1. {...'hello'}
  2. //0: "h"
  3. //1: "e"
  4. //2: "l"
  5. //3: "l"
  6. //4: "o"

对象的扩展运算符等同于使用Object.assign()方法。

  1. let aClone = {...a};
  2. // =
  3. let aClone = Object.assign({},a);

上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法

  1. const clone1 = {
  2. __proto__ :Object.getPrototypeOf(obj),
  3. ...obj
  4. };
  5. const clone2 = Object.assign(
  6. Object.create(Object.getPrototypeOf(obj)),
  7. obj
  8. };
  9. const clone3 = Object.create(
  10. Object.getPrototypeOf(obj),
  11. Object.getOwnPropertyDescriptors(obj)
  12. )

上面的写法一的__proto__属性在非浏览器的环境不一定部署,因此推荐使用写法二和写法三。

扩展运算符可以用于合并两个对象。

  1. let ab = {...a,...b};
  2. //=
  3. let ab = Object.assign({},a,b);