对象的新增方法
比较两个值是否相等:== 或 ===。
== 的缺点:自动转换数据类型。
=== 的缺点:NaN 不等于自身,+0 等于 -0。
新增方法:Object.is()
console.log(NaN === NaN); // false
console.log(+0 === -0); // true
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0)); // false
ES5 环境模拟:
Object.defineProperty(Object, 'Is', {
value: function(a, b) {
if (a === b) {
// 针对 +0 和 -0
return a !== 0 || 1 / a === 1 / b; // 注 Infinity 和 -Infinity 不相等,1 / 0 为 Infinity,1 / -0 为 -Infinity
}
// 针对 NaN
return a !== a && b !== b;
},
writable: true,
enumerable: false,
configurable: false,
});
console.log(Object.Is(NaN, NaN)); // true
console.log(Object.Is(0, -0)); // false
console.log(Object.Is(0, 0)); // true
console.log(Object.Is({}, {})); // false
Object.assign()
用于对象的合并,将原对象的所有可枚举属性,复制到目标对象上。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
const obj = Object.assign(target, source1, source2);
console.log(obj); // {a: 1, b: 2, c: 3}
console.log(target); // {a: 1, b: 2, c: 3}
console.log(source1); // {b: 2}
console.log(source2); // {c: 3}
如果只有一个参数,会直接返回这个参数。
const source = { a: 1 };
const obj = Object.assign(source);
console.log(source === obj); // true
如果参数不是对象,会先转成对象再返回。
const obj = Object.assign(1);
console.log(obj); // Number {1}
console.log(typeof obj); // object
undefined 和 null 不能转为对象,所以这两个值作为参数时,会报错。
const obj = Object.assign(null); // Uncaught TypeError: Cannot convert undefined or null to object
const obj = Object.assign(undefined); // Uncaught TypeError: Cannot convert undefined or null to object
如果非对象参数出现值源对象的位置,如果无法转成对象,会跳过,所以 undefined 和 null 都不会报错。
const target = {};
const obj = Object.assign(target, 1);
console.log(obj); // {}
console.log(obj === target); // true
const target = {};
const obj = Object.assign(target, null);
console.log(obj); // {}
console.log(obj === target); // true
const target = {};
const obj = Object.assign(target, undefined);
console.log(obj); // {}
console.log(obj === target); // true
其他类型的值不在首参数,也不会报错。字符串会以数组形式拷贝入目标对象,其他都不会产生效果。
const val1 = 1;
const obj = Object.assign({}, val1);
console.log(obj); // {}
const val2 = 'abcd';
const obj = Object.assign({}, val2);
console.log(obj); // {0: "a", 1: "b", 2: "c", 3: "d"}
const val3 = true;
const obj = Object.assign({}, val3);
console.log(obj); // {}
只有字符串会被合到目标对象上,这是因为只有字符串的包装对象会产生可枚举属性。
console.log(Object(1)); // {[[PrimitiveValue]]: 1}
console.log(Object(true)); // {[[PrimitiveValue]]: true}
console.log(Object('abc')); // {0: 'a', 1: 'b', 2: 'c', length: 3, [[PrimitiveValue]]: 'abc'}
上面三种类型都被转成对应的包装对象,它们的原始值都在包装对象的 [[PrimitiveValue]] 上,这个属性不能被 Object.assign() 拷贝。
只有字符串的包装对象,会产生可枚举的实义属性,可被拷贝。
Object.assign() 只能拷贝自身的属性(不能拷贝继承的),也不能拷贝不可枚举的。
属性名为 Symbol 的值,也会被 Object.assign() 拷贝。
console.log(Object.assign({}, { [Symbol()]: 1 })); // {Symbol(): 1}
注意点
浅拷贝
const obj = {
a: {
val: 1,
},
};
const newObj = Object.assign({}, obj);
newObj.a.val = 2;
console.log(newObj); // {a: {val: 2}}
console.log(obj); // {a: {val: 2}}
同名属性的替换
同名属性只会替换,不会添加
const obj = {
a: {
name: 'a',
},
};
const obj2 = {
a: {
age: 18,
},
};
const newObj = Object.assign({}, obj, obj2);
console.log(newObj); // {a: {age: 18}};
数组的处理
可以处理数组,但是会把数组当成对象来处理。
const obj = Object.assign(['a', 'b', 'c'], ['d', 'e']);
console.log(obj); // ["d", "e", "c"]
取值函数的处理
Object.assign() 只能进行值的复制,复制的是取值函数,会先求值再复制。
const obj = {
get foo() {
return 'foo value';
},
};
const newObj = Object.assign({}, obj);
console.log(newObj); // {foo: "foo value"}
常见用途
为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, { x, y }); // 为 Point 类的对象实例添加属性
}
}
const point1 = new Point('103', '25');
console.log(point1); // Point {x: "103", y: "25"}
为对象添加方法
function P() {}
Object.assign(P.prototype, {
hello() {
console.log('Hello');
},
});
克隆对象
只能克隆原始对象自身的值,不能克隆继承的值。
function P() {}
P.prototype.name = 'P';
const p1 = new P();
console.log(p1); // P {}
const p2 = Object.assign({}, p1);
console.log(p2); // {}
// 拷贝继承的属性,可这样操作
const proto = Object.getPrototypeOf(p1);
const p3 = Object.assign(Object.create(proto), p1);
console.log(p3); // P {}
合并多个对象
const merge = (target, ...source) => Object.assign(target, ...source);
console.log(merge({}, { a: 1 }, { b: 2 }, { c: 3 }, { d: { name: 'd' } })); // {a: 1, b: 2, c: 3, d: {name: 'd'}}
console.log(merge({}, { a: 1 }, { b: 2 }, { b: 'b', c: 3 }, { d: { name: 'd' } })); // {a: 1, b: "b", c: 3, d: {name: 'd'}}
为属性指定默认值
const defaultParams = {
name: 'Wendy',
};
function foo(params) {
params = Object.assign({}, defaultParams, params);
}
注意:如果属性对应的值不是简单对象,会被替换,而不会增量添加。
Object.getOwnPropertyDescriptors()
ES5: Object.getOwnPropertyDescriptor()
const obj = {
name: 'obj',
};
console.log(Object.getOwnPropertyDescriptor(obj, 'name'));
// {value: "obj", writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptors()
返回对象的所有属性描述。
const obj = {
name: 'obj',
age: 18,
};
console.log(Object.getOwnPropertyDescriptors(obj));
/**
* {
* age: {value: 18, writable: true, enumerable: true, configurable: true},
* name: {value: "obj", writable: true, enumerable: true, configurable: true}
* }
*/
getOwnPropertyDescriptors 的实现:
const obj = {
name: 'obj',
age: 18,
};
const getOwnPropertyDescriptors = obj => {
const descriptorMap = {};
for (let key of Reflect.ownKeys(obj)) {
descriptorMap[key] = Object.getOwnPropertyDescriptor(obj, key);
}
return descriptorMap;
};
console.log(getOwnPropertyDescriptors(obj));
/**
* {
* age: {value: 18, writable: true, enumerable: true, configurable: true},
* name: {value: "obj", writable: true, enumerable: true, configurable: true}
* }
*/
解决 Object.assign() 无法正确拷贝 get 和 set 属性的问题。
const obj = {
name: 'obj',
age: 18,
get gender() {
return 'female';
},
set country(value) {
console.log(value);
},
};
console.log(Object.assign({}, obj)); // {name: "obj", age: 18, gender: "female", country: undefined}
const obj2 = {};
Object.defineProperties(obj2, Object.getOwnPropertyDescriptors(obj));
console.log(Object.getOwnPropertyDescriptors(obj2));
/**
* {
* age: {value: 18, writable: true, enumerable: true, configurable: true},
* country: {get: undefined, set: ƒ, enumerable: true, configurable: true},
* gender: {get: ƒ, set: undefined, enumerable: true, configurable: true},
* name: {value: "obj", writable: true, enumerable: true, configurable: true}
* }
*/
配合 Object.create() 将属性克隆到一个新对象--浅拷贝。
const clone = obj => Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
const proto = {
age: 18,
};
const obj = {
name: 'obj',
friends: [
{
name: 'obj3',
},
],
};
const o = Object.create(proto, Object.getOwnPropertyDescriptors(obj));
console.log(o); // {name: "obj", friends: [{name: "obj3"}]}
const cloneO = clone(o);
console.log(cloneO); // {name: "obj", friends: [{name: "obj3"}]}
cloneO.friends.push({ name: 'obj2' });
console.log(o); // {name: "obj", friends: [{name: "obj3"}, {name: "obj2"}]} <--- 浅拷贝
console.log(cloneO); // {name: "obj", friends: [{name: "obj3"}, {name: "obj2"}]}
proto 属性, Object.setPrototypeOf(), Object.getPrototypeOf()
proto 属性
读取或设置当前对象的 prototype 属性。
// es5 写法
const proto = {
name: 'proto'
};
const obj = {
__proto__: proto,
age: 18,
getName: function() {
return this.name;
}
};
console.log(obj);
/**
* {
* age: 18,
* getName: ƒ (),
* __proto__: {
* name: "proto",
* __proto__: Object
* }
* }
*/
// es6 写法
const obj = Object.create(proto);
obj.age = 18;
obj.getName = function() {
return this.name;
}
console.log(obj);
/**
* {
* age: 18,
* getName: ƒ (),
* __proto__: {
* name: "proto",
* __proto__: Object
* }
* }
*/
proto 是一个内部属性,标准规定只有浏览器必须部署这个属性,其他运行环境不一定需要部署。
使用下面的方法代替:
Object.setPrototypeOf() // 写操作
Object.getPrototypeOf() // 读操作
Object.create() // 生成操作
Object.setPrototypeOf()
与 proto 作用相同,设置对象的 prototype 对象,返回参数对象本身。
ES6 正式推荐的设置原型对象的方法。
const proto = {
getName() {
return this.getName;
}
};
const o = Object.setPrototypeOf({}, proto);
console.log(o);
/**
* {
* __proto__: {
* getName: f getName()
* }
* }
*/
Object.getPrototypeOf()
读取一个对象的原型对象。
const proto = {
getName() {
return this.getName;
}
};
const o = Object.setPrototypeOf({}, proto);
console.log(o);
console.log(Object.getPrototypeOf(o) === proto); // true
如果参数不是对象,会自动转为对象。
const o = Object.getPrototypeOf(1);
console.log(o); // Number {[[PrimitiveValue]]: 0}
// 等同于
const o2 = Object.getPrototypeOf(Number(1));
console.log(o2); // Number {[[PrimitiveValue]]: 0}
const o = Object.getPrototypeOf('string');
console.log(o); // String {[[PrimitiveValue]]: ""}
const o2 = Object.getPrototypeOf(String('string'));
console.log(o2); // String {[[PrimitiveValue]]: ""}
参数是 undefined 或 null,会无法转换,报错。
const o = Object.getPrototypeOf(null); // Uncaught TypeError: Cannot convert undefined or null to object
Object.keys(), Object.values(), Object.entries()
Object.keys()
ES5 返回一个数组,包含参数自身的所有(不含继承的)可遍历属性的键名。
const obj = {
key1: '1',
key2: '2'
}
const o = Object.create(obj);
o.key3 = '3';
const keys = Object.keys(o);
console.log(keys); // ["key3"] <-- 只返回自身的不含继承的key, key1 和 key2 是继承的
ES6 引入了 Object.valuse() 和 Object.entries 供 for...of 循环使用。
const { keys, values, entries } = Object;
const obj = {
a: 1,
b: 2,
};
for (const i of keys(obj)) {
console.log(i);
}
// a
// b
for (const i of values(obj)) {
console.log(i);
}
// 1
// 2
for (const i of entries(obj)) {
console.log(i);
}
// ["a", 1]
// ["b", 2]
Object.values()
返回一个数组,包含参数自身所有(不含继承的)可遍历属性的键值。
返回数组的成员顺序:参考第10章对象扩展中属性的遍历。
- 先遍历所有数值键,按数值升序排列
- 再遍历字符串键,按加入时间升序
- 遍历 Symbol 键,按加入时间升序
const obj = {
3: 'a',
1: 'b',
2: 'c'
};
console.log(Object.values(obj)); // ["b", "c", "a"] <-- 按 key 的数值升序排列
console.log(Object.keys(obj)); // ["1", "2", "3"] <-- key 按数值升序排列
只返回自身的可遍历属性。
const obj = Object.create({
country: 'China'
}, {
name: {
value: '不可遍历'
},
age: {
value: '可遍历',
enumerable: true
}
});
console.log(obj); // {name: "不可遍历"}
console.log(Object.values(obj)); // ['可遍历']
null 和 undefined 报错
Object.values(null); // Uncaught TypeError: Cannot convert undefined or null to object
string 转为对象
console.log(Object.values('abc')); // ["a", "b", "c"]
console.log(Object('abc')); // String {"abc"} <-- string 包装对象,为实例添加非继承的属性
// 0: 'a'
// 1: 'b'
// 2: 'c'
// [[PrimitiveValue]]: "abc"
数字和布尔值转化成对应的包装对象时,不能为实例添加非继承的属性,所以返回 []
console.log(Object(1)); // Number {1}
// [[PrimitiveValue]]: 1
console.log(Object.values(1)); // []
console.log(Object(true)); // Boolean {true}
// [[PrimitiveValue]]: true
console.log(Object.values(true)); // []
Object.entries()
返回一个数组,包含对象自身的所有(不含继承的)可遍历属性的键值对数组。
const obj = {
a: 1,
b: 2
};
console.log(Object.entries(obj)); // [["a", 1], ["b", 2]]
用途
1、遍历对象的所有属性
const obj = {
a: 1,
b: 2,
c: {name: 'c'}
};
for (let [k, v] of Object.entries(obj)) {
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
}
// "a": 1
// "b": 2
// "c": {"name":"c"}
2、将对象转为真正的map结构
const obj = {
a: 1,
b: 2,
c: {name: 'c'}
};
const map = new Map(Object.entries(obj));
console.log(map); // Map(3) {"a" => 1, "b" => 2, "c" => {name: "c"}}
function entries(obj) {
return Object.keys(obj).map(key => [key, obj[key]]);
}
const obj = {
a: 1,
b: 2
}
console.log(entries(obj)); // [["a', 1], ["b", 2]]
Object.fromEntries()
是 Object.entries() 的逆操作,将一个键值对数组转为对象。
const arr = [
['a', 1],
['b', 'b value']
];
console.log(Object.fromEntries(arr)); // {a: 1, b: "b value"}
适合将键值对的数据结构转为对象,特别适合将 Map 结构转为对象。
const map = new Map().set('a', 'a value').set('b', 'b value');
console.log(Object.fromEntries(map)); // {a: "a value", b: "b value"}
可以将 url 中的查询参数转成对象。
const url = 'https://developer.mozilla.org/en-US/search?q=URLSearchParams&q2=param2';
const params = url.split('?')[1].split('&').map(val => [val.split('=')[0], val.split('=')[1]]);
console.log(Object.fromEntries(params)); // {q: "URLSearchParams", q2: "param2"}
该方法是配合 URLSearchParams 对象,将查询字符串转为对象。
const url = 'https://developer.mozilla.org/en-US/search?q=URLSearchParams&q2=param2';
const params = url.split('?')[1];
const searchParams = new URLSearchParams(params);
console.log(Object.fromEntries(searchParams)); // {q: "URLSearchParams", q2: "param2"}
补充:
URLSearchParams()
URLSearchParams() 构造器创建并返回一个新的URLSearchParams 对象。 开头的'?' 字符会被忽略。MDN
注:有兼容性问题。
const url = new URL('https://example.com?foo=1&bar=2');
console.log(url.search); // '?foo=1&bar=2'
const params = new URLSearchParams(url.search);
console.log(Object.fromEntries(params)); // {foo: "1", bar: "2"}