属性的简洁表示法
ES6允许在对象之中,只写属性名,不写属性值。这时,属性值等于属性名所代表的变量。
var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}
// 等同于
var baz = {foo: foo};
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
var o = {
method() {
return "Hello!";
}
};
// 等同于
var o = {
method: function() {
return "Hello!";
}
};
属性名表达式
定义对象属性时可以使用表达式来代表属性名,甚至方法名:
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
let obj1 = {
['h'+'ello']() {
return 'hi';
}
};
obj1.hello() // hi
Object.is()
ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
在ES5中可以给Object对象添加这么一个方法来解决+-0和NaN的问题:
Object.defineProperty(Object, 'is', {
value: function(x, y) {
if (x === y) {
// 针对+0 不等于 -0的情况
return x !== 0 || 1 / x === 1 / y;
}
// 针对NaN的情况
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true
});
Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
第一个是目标,后面的都是源。同名属性会被覆盖。
var target = { a: 1, b: 1 };
var source1 = { b: 2, c: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果传入的参数不是对象,则会被先被转换为对象,undefined和null无法转成对象,如果它们是目标则会报错,是源会被跳过。
Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。对于包装对象,布尔值和数字的包装对象都没有可枚举的属性,只有字符串有:
Object(true) // {[[PrimitiveValue]]: true}
Object(10) // {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
所以:
var v1 = 'abc';
var v2 = true;
var v3 = 10;
var obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
要注意的是:
这个方法执行浅拷贝,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
遇到同名的属性会被直接替换,如果是属性对象也会直接替换,不会进一步把这个对象里面的属性做添加处理。
Object.assign方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。get和set这种访问器属性是不会被复制的。
const source = {
set foo(value) {
console.log(value);
}
};
const target1 = {};
Object.assign(target1, source);
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
// writable: true,
// enumerable: true,
// configurable: true }
应用
比如你想为某类对象添加默认值:
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
}
属性的可枚举性
我们都知道,对象的每个属性都有一个描述对象用来控制该属性的行为,其中enumerable描述这个属性是否是可枚举的。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
在ES5中,有3个操作会考虑到enumerable:
- for…in循环:只遍历对象自身的和继承的可枚举的属性
- Object.keys():返回对象自身的所有可枚举的属性的键名
- JSON.stringify():只串行化对象自身的可枚举的属性
在ES6中,又新增了Object.assign(),会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
另外,ES6规定,所有Class的原型的方法都是不可枚举的。
属性的遍历
ES6一共有5种方法可以遍历对象的属性。
- for…in:循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
- Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。
- Object.getOwnPropertyNames(obj):返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。
- Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有Symbol属性。
- Reflect.ownKeys(obj):返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。
关于遍历出的属性的顺序:
- 首先遍历所有属性名为数值的属性,按照数字排序。
- 其次遍历所有属性名为字符串的属性,按照生成时间排序。
- 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。
proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()
这几个属性都与对象原型的设置有关。
proto属性
这个属性并不是一个对外开放的API并不建议使用,虽然使用它可以非常方便的指定和修改一个对象的原型:
// es6的写法
var obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;
//or
Object.getPrototypeOf({ __proto__: null })
// null
// es5的写法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };
Object.setPrototypeOf()
用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。
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.getPrototypeOf()
用于读取一个对象的prototype对象。
function Rectangle() {
}
var rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false
Object.values(),Object.entries()
与Object.keys()相对应,在ES7中还可能加入Object.values(),Object.entries()。
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。Object.values会过滤属性名为Symbol值的属性。
console.log(Object.values({ [Symbol()]: 123, foo: 'abc', a:{b:'hahaha'}}));
//["abc",{b:"hahaha"}]
Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
var obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj));
// [ ["foo", "bar"], ["baz", 42] ]
对象的扩展运算符
ES7有一个提案,将Rest运算符(解构赋值)/扩展运算符(…)引入对象。Babel转码器已经支持这项功能。
解构赋值
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
解构赋值必须是最后一个参数,否则会报错。
注意,解构赋值的拷贝是浅拷贝。
解构赋值不会拷贝继承自原型对象的属性。
扩展运算符
扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
var a = {q:1,r:3};
var b = {c:2,v:4};
let ab = { ...a, ...b };
console.log(ab);
// 等同于
//let ab = Object.assign({}, a, b);
如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
get与set
复习一下,在对象中,get和set是属性不是方法:
var test = {
_age:0,
get age() {
return this._age;
},
set age(value) {
if (value > 100)
this._age = new Date().getFullYear() - value;
else
this._age = value;
}
};
test.age = 1994;
console.log(test.age);
Object.getOwnPropertyDescriptors()
我们知道ES5有一个Object.getOwnPropertyDescriptor方法,返回某个对象属性的描述对象(descriptor)。
ES7中有一个提案,提出了Object.getOwnPropertyDescriptors(),没错,只多一个s。返回指定对象所有自身属性(非继承属性)的描述对象。
const obj = {
foo: 123,
get bar() { return 'abc' }
};
console.log(Object.getOwnPropertyDescriptors(obj));
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
这样的格式正好适合Object.defineProperties方法来定义对象的属性,所以如果你要拷贝带有存取描述符get和set的对象时,可以使用Object.getOwnPropertyDescriptors()方法来替代Object.assign()方法。
const source = {
set foo(value) {
console.log(value);
}
};
const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: foo],
// enumerable: true,
// configurable: true }
当然这还是浅拷贝。
也可以配合使用Object.create方法来复制对象。
继承对象:
var superClass = {name:'LYM'};
const child = Object.create(
superClass,
Object.getOwnPropertyDescriptors({
age: 23,
})
);
console.log(Object.getOwnPropertyDescriptors(child));
// { age:
// { value: 23,
// writable: true,
// enumerable: true,
// configurable: true } }
为啥没有name属性?这个方法不遍历父类的属性哦。
使用这个方法也可以完成对象的混入,把多个对象混到一个里,就是读取每个源对象的所有属性放到一起。
let mix = (object) => ({
with: (...mixins) => mixins.reduce(
(c, mixin) => Object.create(
c, Object.getOwnPropertyDescriptors(mixin)
), object)
});
// multiple mixins example
let a = {a: 'a'};
let b = {b: 'b'};
let c = {c: 'c'};
let d = mix(c).with(a, b);