ES6 对象的扩展

984 阅读8分钟

1.1属性、方法简洁表示

允许直接写入变量和函数作为对象属性和方法

let birth = '2000/01/01';

const Person = {

  name: '张三',

  //等同于birth: birth
  birth,

  // 等同于hello: function ()...
  hello() { console.log('我的名字是', this.name); }

};

1.2表达式作为属性名

在ES5中,字面量方式定义对象时,不可以使用表达式作为属性名。ES6字面量定义对象时允许这种方式,也就是将表达式放在方括号内,也可以定义方法名。

let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world',
  ['h' + 'ello']() {
    return 'hi';
  }
};

console.log(a['first word']); // "hello"
console.log(a[lastWord]); // "world"
console.log(a['last word']); // "world"
console.log(a.hello()); // hi
console.log(a['last word']); // "world"

1.3对象方法有name属性

方法的name属性返回函数名

const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"

特殊情况:

  1. 对象的方法使用了取值函数(getter)和存值函数(setter),name属性不在该方法上,而是在该方法的属性的描述对象的get和set属性上。
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"
  1. bind方法创造的函数,name属性返回bound加上原函数的名字。
  2. Function构造函数创造的函数,name属性返回anonymous
(new Function()).name // "anonymous"

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

1.4属性可枚举、可遍历

1.可枚举性

对象每个属性都有描述对象,描述对象有enumerable属性,称可枚举性,如果属性为false,表示不可枚举。

四个忽略enumerable为false属性的操作 1.for...in循环;Object.keys();JSON.stringify();Object.assign()(ES6新增)

2.属性的遍历

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

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

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

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

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

1.5 super关键字

super关键字指向当前对象的原型对象,super表示原型对象时,只能用在对象的方法中。

const proto = {
  foo: 'hello'
};

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

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

1.6对象的扩展运算符

用于取出对象理所有可遍历的属性,拷贝到当前对象。

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

扩展运算符等同于使用Object.assign(),只拷贝对象实例的属性

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

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

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

如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。方便修改对象部分现有属性。

let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });

如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。

let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);

其他特点可参照数组的扩展运算符。

1.7对象的解构赋值

用于从一个对象取值

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

参照前面解构赋值

1.8对象的新增方法

1.8.1 Object.is()

比较两个值是否严格相等,与===行为基本一致,不同之处在于:===中,+0等于-0,NaN不等于自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

1.8.2 Object.assign()

用法

用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。返回目标对象。

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

注意点

  1. 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性;
  2. 如果只有一个参数会直接返回该参数;
  3. 如果第一个参数不是对象,会转成对象,undefined和null无法转成对象,作为参数会报错;
  4. 如果非对象参数不是第一个参数,且无法转成对象,就直接跳过。
const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
  1. 只拷贝源对象自身属性,不拷贝继承属性,也不拷贝不可枚举属性
  2. 属性名为Symbol值的属性,也会被拷贝
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
  1. 浅拷贝
  2. 对于嵌套的对象,一旦遇到同名属性,Object.assign方法会进行替换
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }

  1. 可以处理数组,把数组视为对象
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。 10. Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

const source = {
  get foo() { return 1 }
};
const target = {};

Object.assign(target, source)
// { foo: 1 }

source对象的foo属性是一个取值函数,Object.assign不会复制这个取值函数,只会拿到值以后,将这个值复制过去。

常见用途

  1. 给对象添加属性
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}
  1. 给对象添加方法
Object.assign(obj, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});
  1. 克隆对象(浅拷贝,不能克隆继承值)
Object.assign({}, origin);
  1. 合并多个对象
Object.assign(target, ...sources);
  1. 给属性指定默认值
const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
}

1.8.3 Object.getOwnPropertyDescriptors()

用来获取一个对象的==所有==自身属性的描述符。参数为任意对象。

解决了Object.assign()无法正确拷贝get和set属性的问题。Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,实现正确拷贝。

const source = {
  set foo(value) {
    console.log(value);
  }
};

const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
//   set: [Function: set foo],
//   enumerable: true,
//   configurable: true }

配合Object.create()方法,将对象属性克隆到新对象(浅拷贝)

const clone = Object.create(Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj));

实现一个对象继承另一个对象

const obj = Object.create(
  prot,
  Object.getOwnPropertyDescriptors({
    foo: 123,
  })
);

1.8.5 Object.keys(), Object.values(), Object.entries()

1.Object.keys()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。

参数:obj-要返回其枚举自身属性的对象

2.Object.values()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

参数:obj-要返回其枚举自身属性键值的对象

Object.entries()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

参数:obj-可以返回其可枚举属性的键值对的对象。

Object.keys,Object.values和Object.entries,作为遍历一个对象的补充手段,供for...of循环使用。

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

1.8.6 ES5常用对象方法

1.Object.defineProperty()

会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

参数:

(1)obj-要在其上定义属性的对象

(2)prop-要定义或修改属性的名称

(3)descriptor-将被定义或修改的属性描述符

descriptor可以设置的值有

  • [value]:属性的值。
  • [writable]:该属性是否可写,如果设置成 false,则任何对该属性改写的操作都无效(但不会报错)。
  • [configurable]:如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化。
  • [enumerable]:可枚举性。
  • [get]:一旦目标对象访问该属性,就会调用这个方法,并返回结果。
  • [set]:一旦目标对象设置该属性,就会调用这个方法。

实际运用

优化对象获取和修改属性的方式

//加入有一个目标节点, 想设置其位移
var targetDom = document.getElementById('target');
var transformText = 'translateX(' + 10 + 'px)';
targetDom.style.webkitTransform = transformText;
targetDom.style.transform = transformText;

// 用defineProperty方法优化
Object.defineProperty(dom, 'translateX', {
set: function(value) {
    var transformText = 'translateX(' + value + 'px)';
    dom.style.webkitTransform = transformText;
    dom.style.transform = transformText;
}
//这样再后面调用的时候, 十分简单
dom.translateX = 10;
dom.translateX = -10;

MVVM中数据‘双向绑定’实现

<!DOCTYPE html>
 <html>
  <head>
    <meta charset="utf-8">
    <title>标题</title>
  </head>
  <body>
    <h3>使用Object.defineProperty实现简单的双向数据绑定</h3>
    <input type="text" id="input" />
    <div id="div"></div>
    
	<script>
        var obj = {};
        var inputVal = document.getElementById("input");
        var div = document.getElementById("div");
 
        Object.defineProperty(obj, "name", {
          set: function(newVal) {
            inputVal.value = newVal;
            div.innerHTML = newVal;
          }
        });
        inputVal.addEventListener('input', function(e){
          obj.name = e.target.value;
        });
    </script>
	
  </body>
</html>

2.Object.seal()

封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置, 当前属性的值只要可写就可以改变。

参数:obj-要被密封的对象

// 如果属性值可写
let obj = Object.defineProperty({},'name',{
  value:'hello',
  writable:true
})
Object.seal(obj);
console.log(obj.name); // hello
obj.name = 'world';
delete obj.name;
console.log(obj.name); // world 

// 如果属性值不可写
let obj = Object.defineProperty({},'name',{
  value:'hello',
  writable:false,
})
Object.seal(obj);
console.log(obj.name); // hello
obj.name = 'world';
delete obj.name;
console.log(obj.name); // hello

3.Object.freeze()

可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。

参数:obj-要被冻结的对象

let obj = Object.defineProperty({},'name',{
  value:'hello',
  writable:true
})
Object.freeze(obj);
console.log(obj.name); // hello
obj.name = 'world';
delete obj.name;
console.log(obj.name); // hello


摘自阮一峰

参照MDN