对象的拓展
对象属性的简洁表示法
//几个例子
function f(x,y){
return {x,y}
}
f(1,2)//Objext {x:1,y:2}
module.exports = { getItem, setItem, clear };
const cart = {
_wheels: 4,
get wheels() {
return this._wheels;
},
set wheels(value) {
if (value < this._wheels) {
throw new Error('数值太小了!');
}
this._wheels = value;
}
}
cart.wheels//4
set.wheels = 1//Error'数值太小了!'
属性名表达式
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
上面代码中,[keyA]和[keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而 myObject 最后只有一个[object Object]属性。
方法的 name 属性
函数的 name 属性,返回函数名。对象方法也是函数,因此也有 name 属性。
const person = {
sayName() {
console.log('hello!');
}
};
person.sayName.name // "sayName"
如果对象的方法使用了取值函数和存值函数,则 name 属性不是在改方法上面,而是在该方法的描述对象的 get set 属性上面
const obj = {
get foo(){},
set foo(x){}
}
const descriptor = Object.getOwnPropertyDescriptor(obj,"foo");
//getOwnPropertyDescriptor方法返回指定对象上一个自有属性对应的属性描述符。
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
如果对象的方法是一个 Symbol 值,那么 name 属性返回的是这个 Symbol 值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
Object.is()
ES5 用 == 和=== 两个运算符比较两个值是否相等,但都有缺点。
第一个会自动转换数据类型,后面的 NaN 不等于自身。以及+0 等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is 就是部署这个算法的新方法。它用来比较两个值是否严格相等,与===的不同之处只有+0 不等于-0 NaN 等于自身。
ES5 可以通过下面的代码,部署 Object.is。
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()方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象上
其中第一个参数是目标对象,后面的参数都是源对象
const target= {a:1};
const source1 = {b:2};
const source2 = {c:3};
Object.assign(target,source1,source2)
Object.assign 拷贝的属性是有限制的,之拷贝源对象的自身属性,不拷贝继承属性,也不拷贝不可枚举的属性
Object.assign({b: 'c'},
Object.defineProperty({}, 'invisible', {
enumerable: false,
value: 'hello'
})
)
// { b: 'c' }
属性名为 Symbol 值的属性,也会被 Object.assign 拷贝。
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
Object.assign 方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
源对象 obj1 的 a 属性是对象,目标对象拷贝的是对象的引用。对象的任何变化都会反映到目标对象上
对于嵌套的对象,一旦遇到同名属性,Object.assign 的处理方法是替换
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }
也就是说对象的复制最多到一层
Object.assign 的常见用途
为对象添加方法
Object.assign(SomeClass.prototype,{
someMethod(arg1,arg2){
//...
}
})
SomeClass.prototype.someMethod = function (arg1, arg2) {
//···
};
克隆对象
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}//保持继承链
合并多个对象
const merge =
(target, ...sources) => Object.assign(target, ...sources);
//or
const merge =
(...sources) => Object.assign({}, ...sources);
为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}
DEFAULTS 对象是默认值,options 是用户提供的参数,processContent 将 DEFAULTS 和 options 合并成一个新对象,如果两者有同名属性,则 options 的值将覆盖 DEFAULTS 的属性值。当然,属性都是简单类型数据,不然将有可能发生前面所说的属性替换。
属性的可枚举性 属性的遍历
对象的每个属性都有一个 Descriptor 来控制属性的行为,object.getOwnPropertyDescriptor 方法可以获取该属性描述的对象。
枚举
引入“可枚举”(enumerable)这个概念的最初目的,就是让某些属性可以规避掉 for…in 操作
let obj = {foo:123};
Object.getOwnPropertyDescriptor(obj,foo);
//{value: 123, writable: true, enumerable: true, configurable: true}
enumerable 属性成为可枚举性,设为 false 表示某些操作会忽略此属性。目前有 4 种。
for..in 循环只遍历对象自身和继承的可枚举的属性
Object.keys() 只遍历自身的可枚举属性键名
JSON.stringfy()只串行化自身的可枚举属性
Object.assign() 之拷贝对象自身的可枚举属性 继承的和不可枚举的都会被忽略
另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。
总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用 for…in 循环,而用 Object.keys()代替。
遍历
ES6 一共 5 中方法遍历对象的属性
for…in
for…in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
Object.keys(obj)
Object.keys 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols 返回一个数组,包含对象自身的所有 Symbol 属性的键名。
Reflect.ownKeys(obj)
Reflect.ownKeys 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
上面代码中,Reflect.ownKeys 方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性 2 和 10,其次是字符串属性 b 和 a,最后是 Symbol 属性。
Object.getOwnPropetryDescriptions()
Object.getOwnPropertyDescriptors 返回指定对象的所有自身属性的描述对象
该方法的引用目的是为了解决 Object.assign()无法正确拷贝 get 属性和 set 属性的问题
proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()
proto
_proto__,用来读取或设置当前对象的[[prototype]]对象。
//es6
const obj = {
method:function(){...}
};
obj.__proto__ = someOtherObj;
//es5
var obj = Object.create(someOtherObj);
obj.method = function() { ... };
只有浏览器必须部署这个属性。其它环境不一定需要部署。
let shape = function () {};
var p = {
a: function () {
console.log('a');
}
};
let circle = new shape();
circle.__proto__ = p;//原型对象被改变了
circle.a(); // a
console.log(shape.prototype === circle.__proto__);//false
最好使用 Object.create(),Object.setPrototypeOf(),Object.getPrototypeOf()来代替
Object.setPrototypeOf()
用来设置一个对象的 prototype 对象,返回参数对象本身,是 ES6 正式推荐的设置原型对象的方法
//es6
const o = Object.setPrototypeOf({}, null);
//es5
function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
举个栗子
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
//上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。
Object.getPrototypeOf()
该方法与 Object.setPrototypeOf 方法配套,用于读取一个对象的原型对象。
举个栗子
function Rectangle() {
// ...
}
const rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false
super 关键字
this 关键字总是指向函数所在的当前对象,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
}
// 报错,super用在一个函数里面,赋值给foo属性
const obj = {
foo: () => super.foo
}
// 报错
const obj = {
foo: function () {
return super.foo
}
JavaScript 引擎内部,super.foo 等同于 Object.getPrototypeOf(this).foo(属性)或 Object.getPrototypeOf(this).foo.call(this)(方法)。
Object.keys(),Object.values(),Object.entries()
与前面数组的拓展类似
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]
}