阮一峰ES6教程读书笔记(五)对象的扩展

2019-09-18  本文已影响0人  前端艾希

1. 属性的简洁表示法

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

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

当然,除了属性可以简写,方法也可以简写,比如:

const obj = {
    print (param) {
        console.log(param)
    }
}

2. 属性名表达式

我们知道,读取对象的属性时可通过obj.key或者obj[key]这两种方式,不同的是第一种方式key必须是一个确定的键名,而第二种方法的key可以是确定的键名也能使一个表达式

let [key, age] = ['name', 23]

let obj = {
    [key]: 'bing',
    age: age
}
obj // {name: "bing", age: 23}

let obj = {
    [key]: 'bing',
    [age]: age
}
obj // {name: "bing", 23: 23}

通过上面的代码可以看出来,如果给给对象的键名加上[],对象的键名就会变成一个JavaScript表达式,表达式计算出来的值就是对象的键名,如果上述例子不能很好理解,看下面这个例子:

let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

a[lastWord]a['last word']的值是一样的,因为lastWord变量的值就是last word,所以这两个值其实是一个对象的同一个键名对应的值。
需要注意的是如果表达式是一个对象,默认情况下会自动将对象转为字符串[object Object]

const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

3. 方法的 name 属性

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

const obj = {
    print () {
        console.log('hello world')
    }
}

obj.print.name // print

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

const obj = {
  get foo() {},
  set foo(x) {}
};

obj.foo.name
// TypeError: Cannot read property 'name' of undefined

const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

descriptor.get.name // "get foo"
descriptor.set.name // "set foo"

有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous

(new Function()).name // "anonymous"

var doSomething = function() {
  // ...
};
f = doSomething.bind(this)
f.name // "bound doSomething"

4. 属性的可枚举性

对象的每个属性都有一个描述对象,用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该对象的属性描述对象,描述对象的enumerable竖向称为可枚举性,如果该属性值为false,那么就说明该属性不可枚举,即某些操作或忽略该属性,目前有四个操作会忽略不可枚举的属性。

引入可枚举属性的目的就在于让某些属性可以规避某些遍历操作。
由此引发思考,当拷贝对象时,我们如果仅仅使用for...in遍历了对象的可枚举属性,那么不可枚举属性就会被遗漏,那么对象拷贝就变得不严谨,所以我使用Object.getOwnPropertyNames方法来替代```for...in``直接遍历对象:

function deepCopy(obj) {
  if (obj instanceof Date) { return new Date(obj) }
  if (obj instanceof RegExp) { return new RegExp(obj)}  
  let result = new obj.__proto__.constructor()  
  for (let key of Object.getOwnPropertyNames(obg) {
    if (obj.hasOwnProperty(key) && obj[key] !== obj) { 
      if (typeof obj[key] === 'object' && obj[key] !== null) {
        result[key] = deepCopy(obj[key]);
      } else {
        result[key] = obj[key];
      }
    }
  }
  return result;
}

5. super 关键字

ES6 新增了关键字super,指向当前对象的原型对象。

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

值得注意的是super关键字只能用在对象的方法中,用在其他任何地方都会报错。

// 报错
const obj = {
  foo: super.foo
}

// 报错
const obj = {
  foo: () => super.foo
}

// 报错
const obj = {
  foo: function () {
    return super.foo
  }
}

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

6. 对象的扩展运算符

之前在数组的扩展中介绍了扩展运算符(...),ES2018将这个运算符引入了对象,因为数组是一种特殊的对象,所以理论上,扩展运算符本就应该应用于对象中。

6.1 使用扩展运算符解构赋值

let obj = {
    name: 'bing',
    age: 23,
    id: 007
}

let {name, ...rest} = obj
name // 'bing'
rest // {age: 23, id: 7}

6.2 使用扩展运算符浅拷贝对象

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

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

如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象,因此返回的不是空对象。

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

跟数组一样,扩展运算符还可以合并两个对象

let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

如果对象中有重复的键值,那么后面的会覆盖前面的,相当于重复赋值。

let obj1 = {name:'bing'}
let obj2 = {name: 'yan'}
let obj = {...obj1, ...obj2}

obj // {name: "yan"}


参考链接

作者:阮一峰
链接:http://es6.ruanyifeng.com/#docs/destructuring

上一篇 下一篇

猜你喜欢

热点阅读