1. Object.is()
用来比较两个值是否严格相等,与严格运算符(===)的行为基本一致。不同之处在于 +0 != -0 以及NaN等于自身。
**
2.Object.assign()
基本用法
Object.assign用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果只有一个参数,Object.assign会直接返回参数。
如果该参数不是对象,则会先转成对象,然后返回(undefined和null无法转换成对象)。
如果非对象参数出现在源对象的位置(非首参数),那么处理规则有所不同。首相,这些参数都会转成对象,如果无法转成对象 ,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
**
Object(true) //{[[PrimitiveValue]]:true}Object(10) //{[[PrimitiveValue]]:10}Object('abc') //{0:"a",1:"b",2:"c",length:3,[[PrimitiveValue]]:"abc"}
**
上面的代码中,布尔值、数值、字符串分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性[[PrimitiveValue]]上面,这个属性是不会被Object.assign拷贝的。只有字符串的包装对象,会产生可枚举的实义属性,那些属性则会被拷贝。
Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不可拷贝不可枚举的属性。
Object.assign({b:'c'}Object.defineProperty({},'invisible',{enumerable:false,value:'hello'}))//{b:'c'}
上面代码中,Object.assign要拷贝的对象只有一个不可枚举属性invisible,这个属性并没有被拷贝进去。
注意点
1.浅拷贝
2.同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
const target = {a:{b:'c',d:'e'}}const source = {a:{b:'hello'}}Object.assign(target,source)//{a:{b:'hello'}}
上面代码中,target对象的a属性被source对象的a属性整个替换。
3.数组的处理
会把数组视为对象
Object.assign([1,2,3],[4,5])//[4,5,3]
上面代码中,Object.assign把数组视为属性名为0、1、2的对象,因此源数组的0号属性4覆盖了目标数组的0号属性1。
4.取值函数的处理
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});}}
2.为对象添加方法
Object.assign(SomeClass.prototype,{someMethod(arg1,arg2){...},anothorMethod(){...}});//等同于SomeClass.prototype.someMethod = function(arg1,arg2){...};SomeClass.prototype.anotherMethod = function(){...};
3.克隆对象
function clone(origin){let originProto = Object.getPrototypeOf(origin);return Object.assign(Object.create(originProto),origin);}
4.合并多个对象
const merge = (target,...sources)=>Object.assign(target,...sources);
5.为属性指定默认值
const DEFAULTS = {logLevel = 0,outputFormat:'html'};function processContent(options){options = Object.assign({},DEFAULTS,options);console.log(options);//...}
注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的属性很可能不起作用。
**
3.Object.getOwnPropertyDescriptors()
ES5的Object.getOwnPropertyDescriptors()方法会返回某个对象属性的描述对象(descriptor)。Object.getOwnPropertyDescriptors()方法可以实现一个对象继承另一个对象。以前,继承另一个对象,常常写成下面这样。
const obj = {__proto__: prot,foo: 123,};
ES6 规定__proto__只有浏览器要部署,其他环境不用部署。如果去除__proto__,上面代码就要改成下面这样。
const obj = Object.create(prot);obj.foo = 123;// 或者const obj = Object.assign(Object.create(prot),{foo: 123,});
有了Object.getOwnPropertyDescriptors(),我们就有了另一种写法。
const obj = Object.create(prot,Object.getOwnPropertyDescriptors({foo: 123,}));
Object.getOwnPropertyDescriptors()也可以用来实现 Mixin(混入)模式。
let mix = (object) => ({with: (...mixins) => mixins.reduce((c, mixin) => Object.create(c, Object.getOwnPropertyDescriptors(mixin)), object)});// multiple mixins examplelet a = {a: 'a'};let b = {b: 'b'};let c = {c: 'c'};let d = mix(c).with(a, b);d.c // "c"d.b // "b"d.a // "a"
上面代码返回一个新的对象d,代表了对象a和b被混入了对象c的操作。
出于完整性的考虑,Object.getOwnPropertyDescriptors()进入标准以后,以后还会新增Reflect.getOwnPropertyDescriptors()方法。
4.proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()
JavaScript 语言的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作方法。
proto属性
__proto__属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。目前,所有浏览器(包括 IE11)都部署了这个属性。
// es5 的写法const obj = {method: function() { ... }};obj.__proto__ = someOtherObj;// es6 的写法var obj = Object.create(someOtherObj);obj.method = function() { ... };
该属性没有写入 ES6 的正文,而是写入了附录,原因是__proto__前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的 API,只是由于浏览器广泛支持,才被加入了 ES6。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
实现上,__proto__调用的是Object.prototype.__proto__,具体实现如下。
Object.defineProperty(Object.prototype, '__proto__', {get() {let _thisObj = Object(this);return Object.getPrototypeOf(_thisObj);},set(proto) {if (this === undefined || this === null) {throw new TypeError();}if (!isObject(this)) {return undefined;}if (!isObject(proto)) {return undefined;}let status = Reflect.setPrototypeOf(this, proto);if (!status) {throw new TypeError();}},});function isObject(value) {return Object(value) === value;}
如果一个对象本身部署了__proto__属性,该属性的值就是对象的原型。
Object.getPrototypeOf({ __proto__: null })// null
Object.setPrototypeOf()
Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。
// 格式Object.setPrototypeOf(object, prototype)// 用法const o = Object.setPrototypeOf({}, null);
该方法等同于下面的函数。
function setPrototypeOf(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 // 10obj.y // 20obj.z // 40
上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。
如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。
Object.setPrototypeOf(1, {}) === 1 // trueObject.setPrototypeOf('foo', {}) === 'foo' // trueObject.setPrototypeOf(true, {}) === true // true
由于undefined和null无法转为对象,所以如果第一个参数是undefined或null,就会报错。
Object.setPrototypeOf(undefined, {})// TypeError: Object.setPrototypeOf called on null or undefinedObject.setPrototypeOf(null, {})// TypeError: Object.setPrototypeOf called on null or undefined
Object.getPrototypeOf()
该方法与Object.setPrototypeOf方法配套,用于读取一个对象的原型对象。
Object.getPrototypeOf(obj);
下面是一个例子。
function Rectangle() {// ...}const rec = new Rectangle();Object.getPrototypeOf(rec) === Rectangle.prototype// trueObject.setPrototypeOf(rec, Object.prototype);Object.getPrototypeOf(rec) === Rectangle.prototype// false
如果参数不是对象,会被自动转为对象。
// 等同于 Object.getPrototypeOf(Number(1))Object.getPrototypeOf(1)// Number {[[PrimitiveValue]]: 0}// 等同于 Object.getPrototypeOf(String('foo'))Object.getPrototypeOf('foo')// String {length: 0, [[PrimitiveValue]]: ""}// 等同于 Object.getPrototypeOf(Boolean(true))Object.getPrototypeOf(true)// Boolean {[[PrimitiveValue]]: false}Object.getPrototypeOf(1) === Number.prototype // trueObject.getPrototypeOf('foo') === String.prototype // trueObject.getPrototypeOf(true) === Boolean.prototype // true
如果参数是undefined或null,它们无法转为对象,所以会报错。
Object.getPrototypeOf(null)// TypeError: Cannot convert undefined or null to objectObject.getPrototypeOf(undefined)// TypeError: Cannot convert undefined or null to object
5.Object.keys(),Object.values(),Object.entries()
Object.keys()
ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
var obj = { foo: 'bar', baz: 42 };Object.keys(obj)// ["foo", "baz"]
ES2017 引入了跟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]}
Object.values()
Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
const obj = { foo: 'bar', baz: 42 };Object.values(obj)// ["bar", 42]
返回数组的成员顺序,与本章的《属性的遍历》部分介绍的排列规则一致。
const obj = { 100: 'a', 2: 'b', 7: 'c' };Object.values(obj)// ["b", "c", "a"]
上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b、c、a。Object.values只返回对象自身的可遍历属性。
const obj = Object.create({}, {p: {value: 42}});Object.values(obj) // []
上面代码中,Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的,因为p的属性描述对象的enumerable默认是false,Object.values不会返回这个属性。只要把enumerable改成true,Object.values就会返回属性p的值。
const obj = Object.create({}, {p:{value: 42,enumerable: true}});Object.values(obj) // [42]
Object.values会过滤属性名为 Symbol 值的属性。
Object.values({ [Symbol()]: 123, foo: 'abc' });// ['abc']
如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo')// ['f', 'o', 'o']
上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。因此,Object.values返回每个属性的键值,就是各个字符组成的一个数组。
如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。
Object.values(42) // []Object.values(true) // []
Object.entries()
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 };Object.entries(obj)// [ ["foo", "bar"], ["baz", 42] ]
除了返回值不一样,该方法的行为与Object.values基本一致。
如果原对象的属性名是一个 Symbol 值,该属性会被忽略。
Object.entries({ [Symbol()]: 123, foo: 'abc' });// [ [ 'foo', 'abc' ] ]
上面代码中,原对象有两个属性,Object.entries只输出属性名非 Symbol 值的属性。将来可能会有Reflect.ownEntries()方法,返回对象自身的所有属性。Object.entries的基本用途是遍历对象的属性。
let obj = { one: 1, two: 2 };for (let [k, v] of Object.entries(obj)) {console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);}// "one": 1// "two": 2
Object.entries方法的另一个用处是,将对象转为真正的Map结构。
const obj = { foo: 'bar', baz: 42 };const map = new Map(Object.entries(obj));map // Map { foo: "bar", baz: 42 }
自己实现Object.entries方法,非常简单。
// Generator函数的版本function* entries(obj) {for (let key of Object.keys(obj)) {yield [key, obj[key]];}}// 非Generator函数的版本function entries(obj) {let arr = [];for (let key of Object.keys(obj)) {arr.push([key, obj[key]]);}return arr;}
6.Object.fromEntries()
Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。
Object.fromEntries([['foo', 'bar'],['baz', 42]])// { foo: "bar", baz: 42 }
该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。
// 例一const entries = new Map([['foo', 'bar'],['baz', 42]]);Object.fromEntries(entries)// { foo: "bar", baz: 42 }// 例二const map = new Map().set('foo', true).set('bar', false);Object.fromEntries(map)// { foo: true, bar: false }
该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象。
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))// { foo: "bar", baz: "qux" }
