一, 属性的可枚举性和遍历
1. 可枚举性
对象的每个属性都有一个描述对象, 用来控制该属性的行为. Object.getOwnPropertyDescriptor(obj, 'foo');
- var obj = { foo: 123 };
- Object.getOwnPropertyDescriptor(obj, 'foo')
- // {value: 123, writable: true, enumerable: true, configurable: true}
描述对象的 enumerable 属性称为 "可枚举属性", 如果该属性为 false, 某些操作会忽略当前属性.
目前有四个操作会忽略 enumerable 为 false 的属性: a. for...in 循环: 只遍历对象自身的和继承的可枚举的属性. b. Object.keys(): 返回对象自身的所有可枚举的属性的键名. c. JSON.stringify(): 只字符串化对象自身的可枚举属性. d. Object.assigin(): 忽略 enumerable 为 false 的属性, 只拷贝对象自身的可枚举属性.
注意: (1)ES6 规定, 所有 class 的原型的方法都是不可枚举的. (2)操作中引入继承属性会让问题复杂化, 大多时候, 我们只关心对象自身的属性. 所有尽量不使用 for...in 循环, 而用 Object.keys() 代替.
2. 属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性. (1)for...in 遍历自身的和继承的可枚举的属性 (不含 Symbol 属性). (2)Object.keys(obj) 返回一个数组, 包括对象自身的(不含继承的) 所有可枚举的属性 (不含 Symbol 属性) 的键名. (3)Object.getOwnPropertyNames(obj) 返回一个数组, 包含对象自身的所有属性 (不含 Symbol 属性, 但是包含不可枚举的属性) 的键名. (4)Object.getOwnPropertySymbols(obj) 返回一个数组, 包含对象自身的所有 Symbol 属性的键名. (5)Reflect.ownKeys(obj) 返回一个数组, 包含对象自身的所有键名, 不管键名是 Symbol 或是字符串, 或是否可枚举.
以上 5 中方法遍历对象键名, 都遵守同样的属性遍历的次序规则: a. 首先遍历所有数值键, 按照数值升序排列. b. 其次遍历所有字符串键, 按照加入时间升序排列. c. 最后遍历所有 Symbol 键, 按照加入时间升序排列.
二, Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptor() 方法会返回某个对象属性的描述对象. Object.getOwnPropertyDescriptors() 方法, 返回指定对象的所有自身属性的描述对象.
- var obj = {
- foo: 123,
- get bar() { return 'abc' }
- };
- Object.getOwnPropertyDescriptors(obj);
- // { foo:
- // { value: 123,
- // writable: true,
- // enumerable: true,
- // configurable: true },
- // bar:
- // { get: [Function: get bar],
- // set: undefined,
- // enumerable: true,
- // configurable: true } }
该方法, 主要解决 Object.assign() 无法正确拷贝 get 属性和 set 属性的问题. Object.getOwnPropertyDescriptors 方法配合 Object.defineProperties 方法, 就可以实现正确拷贝.
- var source = {
- set foo(value) {
- console.log(value);
- }
- };
- var target = {};
- Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
- Object.getOwnPropertyDescriptor(target, 'foo');
- // { get: undefined,
- // set: [Function: set foo],
- // enumerable: true,
- // configurable: true }
三, 双下划綫 proto 属性
双下划綫 proto 属性, 用来读取或设置当前对象的 propertype 对象. 目前, 所有浏览器都部署了这个属性. 该属性没有写入 ES6 正文, 而是写入了附录, 原因是双下划线, 说明它本质上是一个内部属性, 而不是一个正式的对外的 API, 只是由于浏览器的广泛支持, 才被加入 ES6. ES6 标准规定, 只有浏览器必须部署这个属性, 其他运行环境不一定需要部署. 无论从语义的角度, 还是兼容性的角度, 都不要使用这个属性.
四, Object.setPrototypeOf()
Object.setPrototypeOf 方法的作用与 proto 相同, 用来设置一个对象的 prototype 对象, 返回参数对象本身. 该方法是 ES6 正式推荐的设置原型对象的方法.
- // 格式
- Object.setPrototypeOf(object, prototype);
- let proto = {};
- let obj = { x: 10 };
- Object.setPrototypeOf(obj, proto);
- proto.y = 20;
- proto.z = 40;
- obj.x; // 10
- obj.y; // 20
- obj.z; // 40
如果第一个参数不是对象, 会自动转为对象. 但由于返回的还是第一个参数, 所以这个操作不会产生任何效果.
- Object.setPrototypeOf(1, {}) === 1 // true
- Object.setPrototypeOf('foo', {}) === 'foo' // true
- Object.setPrototypeOf(true, {}) === true // true
由于 undefined 和 null 无法转为对象, 所以如果第一个参数是 undefined 或 null, 就会报错.
- Object.setPrototypeOf(undefined, {});
- // TypeError: Object.setPrototypeOf called on null or undefined
- Object.setPrototypeOf(null, {});
- // TypeError: Object.setPrototypeOf called on null or undefined
五, Object.getPrototypeOf()
Object.getPrototypeOf() 方法用于读取一个对象的原型对象.
- let proto = {};
- let obj = { x: 10 };
- Object.setPrototypeOf(obj, proto);
- proto.y = 20;
- proto.z = 40;
- Object.getPrototypeOf(obj) === proto;//true
- Object.getPrototypeOf(obj);// {y: 20, z: 40}
如果参数不是对象, 会被自动转为对象. 如果参数是 undefined 或 null, 它们无法转为对象, 会报错.
- Object.getPrototypeOf(1);
- //Number {0, constructor: ƒ, toExponential: ƒ, toFixed: ƒ, toPrecision: ƒ, toString: ƒ, ...}
- Object.getPrototypeOf('str');
- //String {"", length: 0, constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, ...}
- Object.getPrototypeOf(true);
- //Boolean {false, constructor: ƒ, toString: ƒ, valueOf: ƒ}
- Object.getPrototypeOf(null);
- //Cannot convert undefined or null to object at Function.getPrototypeOf
- Object.getPrototypeOf(undefined);
- //Cannot convert undefined or null to object at Function.getPrototypeOf
六, super 关键字
super 关键字指向当前对象的原型对象.
- var proto = {foo: 'hello'};
- var obj = {
- foo: 'world',
- find(){
- return super.foo;
- }
- };
- Object.setPrototypeOf(obj, proto);
- obj.find();// "hello"
注: super 关键字表示原型对象时, 只能用在对象的方法之中, 用在其他地方都会报错.
- const obj = {
- foo: super.foo
- }
- //SyntaxError: 'super' keyword unexpected here
- const obj = {
- foo: () => super.foo
- }
- // SyntaxError: 'super' keyword unexpected here
- const obj = {
- foo: function () {
- return super.foo
- }
- }
- // SyntaxError: 'super' keyword unexpected here
目前, 只有对象方法的简写法可以让 JavaScript 引擎确认, 定义的是对象的方法.
- const proto = {
- x: 'hello',
- foo() {
- console.log(this.x);
- },
- };
- const obj = {
- x: 'world',
- foo() {
- super.foo();
- }
- };
- Object.setPrototypeOf(obj, proto);
- obj.foo(); // 'world'
上例中, super.foo 指向原型对象 proto 的 foo 方法, 但是 proto 的 foo 方法绑定的 this 是当前对象 obj, 因此输出的是 world.
七, Object.keys(),Object.values(),Object.entries()
(1)Object.keys() Object.keys() 方法, 返回的是一个数组, 成员是参数对象自身所有可枚举的属性键名.
- var obj = {foo: 'bar', baz: 42};
- var arr = Object.keys(obj);
- for(let key of arr){
- console.log(obj[key])
- }
- // bar
- // 42
(2)Object.values() Object.values() 方法返回一个数组, 成员是参数对象自身的所有可枚举属性的键值. 该方法会过滤属性名为 Symbol 值的属性. 返回数组成员顺序, 与上面遍历属性的排列规则一致.
- var obj = { 100: 'a', 2: 'b', 7: 'c', [Symbol()]: 123,};
- Object.values(obj);// ["b", "c", "a"]
如果 Object.values() 方法的参数是一个字符串, 会返回各个字符组成的一个数组. 因为字符串会先转成一个类似数组的对象. 字符串的每个字符, 就是该对象的一个属性. 因此 Object.values 返回每个属性的键值, 就是每个字符组成的数组.
Object.values('foo');//["f", "o", "o"]
如果参数不是对象, Object.values() 会先将其转为对象. 由于数值和布尔值的包装对象都不会为实例添加非继承属性, 所有会返回空数组. null 和 undefined 会报错.
- Object.values(1);//[]
- Object.values(true);//[]
- Object.values(null);//TypeError: Cannot convert undefined or null to objectat Function.values
- Object.values(undefined);//TypeError: Cannot convert undefined or null to objectat Function.values
(3)Object.entries() Object.entries() 方法返回一个数组, 成员是参数对象自身所有可枚举属性的键值对数组. 会忽略 Symbol 值.
- var obj = { 100: 'a', 2: 'b', 7: 'c', [Symbol()]: 123,};
- Object.entries(obj);
- //[["2", "b"], ["7", "c"], ["100", "a"]]
该方法的用途是遍历对象的属性:
- var obj = { 100: 'a', 2: 'b', 7: 'c', [Symbol()]: 123,};
- for(let [k, v] of Object.entries(obj)){
- console.log(k + ':'+ v);
- }
- // 2:b
- // 7:c
- // 100:a
Object.entries 方法的另一个用处是, 将对象转化为真正的 Map 结构.
- var obj = {foo: 'bar', baz: 42};
- var map = new Map(Object.entries(obj));
- map;;// {"foo" => "bar", "baz" => 42}
八, 对象的扩展运算符
1. 结构赋值
(1)用法
对象的解构赋值用于从一个对象取值, 相当于将目标对象自身的所有可遍历的, 但尚未被读取的属性, 分配到指定的对象上面. 所有的键和它们的值, 都会被拷贝到新对象上面.
- let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
- x; // 1
- y; // 2
- z; // { a: 3, b: 4 }
上例中, 变量 z 是解构赋值所在的对象. 它获取等号右边的所有尚未读取的键(a 和 b), 将它们连同值一起拷贝过来. 解构赋值要求等号右边是一个对象, 如果等号右边是 undefined 或 null, 就会报错, 因为它们无法转为对象. 解构赋值必须是最后一个参数, 否则会报错.
- let { x, y, ...z } = null;// TypeError: Cannot destructure property `x` of 'undefined' or 'null'.
- let { x, ...y, ...z } = {a:1,b:2,c:3}; // SyntaxError: Rest element must be last element
(2)注意问题
a. 解构赋值的拷贝是浅拷贝, 如果一个键的值是复合类型的值(数组, 对象, 函数), 那么解构赋值拷贝的是这个值的引用, 而不是这个值的副本.
- var obj = {a: {b: 1}};
- var {...x} = obj;
- obj.a.b = 2;
- x.a.b;//2
b. 扩展运算符的解构赋值, 不能复制继承自原型对象的属性.
- let obj1 = {a: 1};
- let obj2 = {b: 2};
- obj2._proto_ = obj1;
- let {...obj3} = obj2;
- obj3;//{b: 2, _proto_: {a: 1}}
- obj3.a;//undefined
2. 扩展运算符
(1)用途
a. 拷贝对象 对象的扩展运算符 (...) 用于取出参数对象的所有可遍历属性, 拷贝到当前对象之中.
- let z = {a: 3, b: 4};
- let n = {...z};
- n;//{a: 3, b: 4}
上例中, 只拷贝了对象实例的属性. b. 合并对象
- var obj1 = {a: 1};
- var obj2 = {b: 2};
- var obj3 = {...obj1,...obj2};
- obj3;//{a: 1, b: 2}
- // 等同于
- var obj4 = Object.assign({}, obj1, obj2);
- obj4//{a: 1, b: 2}
(2)注意问题
a. 如果用户自定义的属性放在扩展运算符后面, 则扩展运算符内部的同名属性会被覆盖掉.
- var obj1 = {a:1};
- var a = 3;
- var obj2 = {...obj1, a};
- obj3;//{a: 3}
b. 扩展运算符后面可以跟表达式.
- var x = 2;
- const obj = {
- ...(x> 1 ? {a: 1} : {}),
- b: 2,
- };
- obj;//{a: 1, b: 2}
c. 如果扩展运算符后面是一个空对象, 则没有任何效果. d. 如果扩展运算符后面是 null 或 undefined, 这两个值会被忽略, 不会报错. e. 扩展运算符的参数对象之中, 如果有取值函数 get, 这个函数是会执行的.
来源: http://www.qdfuns.com/article/46690/97d4db34b439d43beb54be5a6964661b.html