Object.is()
ES5比较两个值是否相等,只有两个运算符:相等运算符(==
)和严格相等运算符(===
)。它们都有缺点,前者会自动转换数据类型,后者的NaN
不等于自身,以及+0
等于-0
。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is
就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo') // true
Object.is({}, {}) // false
不同之处只有两个:一是+0
不等于-0
,二是NaN
等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
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.create()
生成实例对象的常用方法是,使用new
命令让构造函数返回一个实例。但是很多时候,只能拿到一个实例对象,它可能根本不是由构建函数生成的,那么能不能从一个实例对象,生成另一个实例对象呢?
JavaScript 提供了Object.create()
方法,用来满足这种需求。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。
// 原型对象
var A = {
print: function () {
console.log('hello');
}
};
// 实例对象
var B = Object.create(A);
Object.getPrototypeOf(B) === A // true
A.isPrototypeOf(B) // true
B.print() // hello
B.print === A.print // true
上面代码中,Object.create()
方法以A
对象为原型,生成了B
对象。B
继承了A
的所有属性和方法。
实际上,Object.create()
方法可以用下面的代码代替。
if (typeof Object.create !== 'function') {
Object.create = function (obj) {
function F() {}
F.prototype = obj;
return new F();
};
}
上面代码表明,Object.create()
方法的实质是新建一个空的构造函数F
,然后让F.prototype
属性指向参数对象obj
,最后返回一个F
的实例,从而实现让该实例继承obj
的属性。
下面三种方式生成的新对象是等价的。
var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();
如果想要生成一个不继承任何属性(比如没有toString()
和valueOf()
方法)的对象,可以将Object.create()
的参数设为null
。
var obj = Object.create(null);
obj.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'
上面代码中,对象obj
的原型是null
,它就不具备一些定义在Object.prototype
对象上面的属性,比如valueOf()
方法。
使用Object.create()
方法的时候,必须提供对象原型,即参数不能为空,或者不是对象,否则会报错。
Object.create()
// TypeError: Object prototype may only be an Object or null
Object.create(123)
// TypeError: Object prototype may only be an Object or null
Object.create()
方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。
var obj1 = { p: 1 };
var obj2 = Object.create(obj1);
obj1.p = 2;
obj2.p // 2
上面代码中,修改对象原型obj1
会影响到实例对象obj2
。
除了对象的原型,Object.create()
方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性。
var obj = Object.create({}, {
p1: {
value: 123,
enumerable: true,
configurable: true,
writable: true,
},
p2: {
value: 'abc',
enumerable: true,
configurable: true,
writable: true,
}
});
// 等同于
var obj = Object.create({});
obj.p1 = 123;
obj.p2 = 'abc';
Object.create()
方法生成的对象,继承了它的原型对象的构造函数。
function A() {}
var a = new A();
var b = Object.create(a);
b.constructor === A // true
b instanceof A // true
上面代码中,b
对象的原型是a
对象,因此继承了a
对象的构造函数A
Object.assign()
基本用法
Object.assign
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign
方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
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}
如果只有一个参数,Object.assign
会直接返回该参数。
var obj = { a: 1 };
Object.assign(obj) === obj // true
如果该参数不是对象,则会先转成对象,然后返回。
typeof Object.assign(2) // "object"
由于undefined
和null
无法转成对象,所以如果它们作为参数,就会报错。
Object.assign(undefined) // 报错
Object.assign(null) // 报错
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined
和null
不在首参数,就不会报错。
let obj = { a: 1 };
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
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" }
上面代码中,v1
、v2
、v3
分别是字符串、布尔值和数值,结果只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。
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
拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false
)。
Object.assign({ b: 'c' },
Object.defineProperty({}, 'invisible', {
enumerable: false,
value: 'hello'
})
) // { b: 'c' }
上面代码中,Object.assign
要拷贝的对象只有一个不可枚举属性invisible
,这个属性并没有被拷贝进去。
属性名为Symbol值的属性,也会被Object.assign
拷贝。
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
注意点
Object.assign
方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
var obj1 = { a: { b: 1 } };
var obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
上面代码中,源对象obj1
的a
属性的值是一个对象,Object.assign
拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
对于这种嵌套的对象,一旦遇到同名属性,Object.assign
的处理方法是替换,而不是添加。
var target = { a: { b: 'c', d: 'e' } }
var source = { a: { b: 'hello' } }
Object.assign(target, source) // { a: { b: 'hello' } }
上面代码中,target
对象的a
属性被source
对象的a
属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }
的结果。这通常不是开发者想要的,需要特别小心。
有一些函数库提供Object.assign
的定制版本(比如Lodash的_.defaultsDeep
方法),可以解决浅拷贝的问题,得到深拷贝的合并。
注意,Object.assign
可以用来处理数组,但是会把数组视为对象。
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
上面代码中,Object.assign
把数组视为属性名为0、1、2的对象,因此源数组的0号属性4
覆盖了目标数组的0号属性1
。
常见用途
Object.assign
方法有很多用处。
( 1 )为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, { x, y });
}
}
上面方法通过Object.assign
方法,将x
属性和y
属性添加到Point
类的对象实例。
( 2 )为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用assign方法添加到SomeClass.prototype之中。
( 3 )克隆对象
function clone(origin) {
return Object.assign({}, origin);
}
上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
( 4 )合并多个对象
将多个对象合并到某个对象。
const merge = (target, ...sources) =>Object.assign(target, ...sources);
如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。
const merge = (...sources) => Object.assign({}, ...sources);
( 5 )为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}
上面代码中,DEFAULTS
对象是默认值,options
对象是用户提供的参数。Object.assign
方法将DEFAULTS
和options
合并成一个新对象,如果两者有同名属性,则option
的属性值会覆盖DEFAULTS
的属性值。
注意,由于存在深拷贝的问题,DEFAULTS
对象和options
对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS
对象的该属性很可能不起作用。
const DEFAULTS = {
url: {
host: 'example.com',
port: 7070
},
};
processContent({ url: { port: 8000 } })
// {
// url: {port: 8000}
// }
上面代码的原意是将url.port
改成8000,url.host
不变。实际结果却是options.url
覆盖掉DEFAULTS.url
,所以url.host
就不存在了。
属性的可枚举性
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
描述对象的enumerable
属性,称为”可枚举性“,如果该属性为false
,就表示某些操作会忽略当前属性。
ES5有三个操作会忽略enumerable
为false
的属性。
· for...in
循环:只遍历对象自身的和继承的可枚举的属性
· Object.keys()
:返回对象自身的所有可枚举的属性的键名
· JSON.stringify()
:只串行化对象自身的可枚举的属性
ES6新增了一个操作Object.assign()
,会忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
这四个操作之中,只有for...in
会返回继承的属性。实际上,引入enumerable
的最初目的,就是让某些属性可以规避掉for...in
操作。比如,对象原型的toString
方法,以及数组的length
属性,就通过这种手段,不会被for...in
遍历到。
Object.getOwnPropertyDescriptor(Object.prototype,'toString').enumerable
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false
上面代码中,toString
和length
属性的enumerable
都是false
,因此for...in
不会遍历到这两个继承自原型的属性。
另外,ES6规定,所有Class的原型的方法都是不可枚举的。
Object.getOwnPropertyDescriptor(class{ foo(){ } }.prototype,'foo').enumerable
// false
总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in
循环,而用Object.keys()
代替。
Object.freeze()
Object.freeze()
- 冻结一个对象,被冻结后的对象不能被修改。
- 不能添加新属性
- 不能删除已有属性
- 不能修改对象已有属性的可枚举性、可配置性、可写性
- 不能修改已有的属性值(浅 不能修改,只管一层,第二次以后的对象还是可以修改)
- 冻结的对象原型也不能修改 严格模式执行上述操作会报错,非严格模式会忽略。
let obj1 = {
a: 'aaa',
b: 'bbb',
c: {
d: 'cccc'
}
}
let obj2 = Object.freeze(obj1)
console.log(obj1 === obj2); // true 返回的仍旧是原对象
// 但是修改是无效的
obj2.a = 'hhh'
console.log(obj2); // { a: 'aaa', b: 'bbb', c: { d: 'cccc' } }
obj2.c.d = 'cheny'
console.log(obj2); // { a: 'aaa', b: 'bbb', c: { d: 'cheny' } }
只管一层,深层的对象还是能修改
Object.isFrozen:
判断一个对象是否已经被冻结
const obj = {
a: 1,
b: {
c: 2
}
}
console.log(obj.a) // 1
console.log(obj.b.c) // 2
obj.a = 3
obj.b.c = 4
console.log(obj.a) // 3
console.log(obj.b.c) // 4
Object.isFrozen(obj) // false
Object.freeze(obj)
Object.isFrozen(obj) // true
obj.a = 5
obj.b.c = 6
console.log(obj.a) // 3
console.log(obj.b.c) // 6
Object.seal()
Object.seal()
該方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。
const object1 = {
property1: 42
};
Object.seal(object1);
object1.property1 = 33;
console.log(object1.property1); // expected output: 33
delete object1.property1; // cannot delete when sealed
console.log(object1.property1); // expected output: 33
Object.isSealed()
判断一个对象是否被密封。
密封对象是指那些不可扩展
的,且所有自身属性都不可配置且因此不可删除(但不一定是不可写)的对象。
// 新建的对象默认不是密封的.
var empty = {};
Object.isSealed(empty); //false
// 如果你把一个空对象变的不可扩展,则它同时也会变成个密封对象.
Object.preventExtensions(empty);
Object.isSealed(empty); // === true
// 但如果这个对象不是空对象,则它不会变成密封对象,因为密封对象的所有自身属性必须是不可配置的.
var hasProp = { fee: "fie foe fum" };
Object.preventExtensions(hasProp);
Object.isSealed(hasProp); // === false
// 如果把这个属性变的不可配置,则这个属性也就成了密封对象.
Object.defineProperty(hasProp, 'fee', {
configurable: false
});
Object.isSealed(hasProp); //true
// 最简单的方法来生成一个密封对象,当然是使用Object.seal.
var sealed = {};
Object.seal(sealed);
Object.isSealed(sealed); //true
// 一个密封对象同时也是不可扩展的.
Object.isExtensible(sealed); //false
// 一个密封对象也可以是一个冻结对象,但不是必须的.
Object.isFrozen(sealed); //true ,所有的属性都是不可写的
var s2 = Object.seal({ p: 3 });
Object.isFrozen(s2); //false, 属性"p"可写
var s3 = Object.seal({ get p() { return 0; } });
Object.isFrozen(s3); // === true ,访问器属性不考虑可写不可写,只考虑是否可配置
Object.preventExtensions()
Object.preventExtensions()
让一个对象变的不可扩展,也就是永远不能再添加新的属性。
const object1 = {};
Object.preventExtensions(object1);
try {
Object.defineProperty(object1, 'property1', {
value: 42
});
} catch (e) {
console.log(e);
// expected output: TypeError: Cannot define property property1, object is not extensible
}
Object.isExtensible()
判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
默认情况下,对象是可扩展的:即可以为他们添加新的属性。以及它们的 proto 属性可以被更改。Object.preventExtensions,Object.seal 或 Object.freeze 方法都可以标记一个对象为不可扩展(non-extensible)。
// 新对象默认是可扩展的.
let empty = {};
console.log(Object.isExtensible(empty)); // === true
// ...可以变的不可扩展.
Object.preventExtensions(empty);
console.log(Object.isExtensible(empty)); // === false
// 密封对象是不可扩展的.
let sealed = Object.seal({});
console.log(Object.isExtensible(sealed)); // === false
// 冻结对象也是不可扩展.
let frozen = Object.freeze({});
console.log(Object.isExtensible(frozen)); // === false
Object.prototype.isPrototypeOf()
实例对象的isPrototypeOf
方法,用来判断该对象是否为参数对象的原型。
var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
o2.isPrototypeOf(o3) // true
o1.isPrototypeOf(o3) // true
上面代码中,o1
和o2
都是o3
的原型。这表明只要实例对象处在参数对象的原型链上,isPrototypeOf
方法都返回true
。
Object.prototype.isPrototypeOf({}) // true
Object.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf(/xyz/) // true
Object.prototype.isPrototypeOf(Object.create(null)) // false
上面代码中,由于Object.prototype
处于原型链的最顶端,所以对各种实例都返回true
,只有直接继承自null
的对象除外。
Object.hasOwnProperty(key)
检测对象有没有指定的key
hasOwnProperty()
方法会返回一个布尔值,指示对象是否具有指定的属性作为自身(不继承)属性。 obj.hasOwnProperty(prop)
参数
Object.prototype.hasOwnProperty.call(object, key)
prop
:要检测的属性 字符串 名称或者 Symbol
。
返回值:用来判断某个对象是否含有指定的属性的 Boolean
。
所有继承了 Object
的对象都会继承到 hasOwnProperty
方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in
运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
let obj=new Object();
obj.a=5;
console.log(obj.hasOwnProperty('a’)) // true
delete obj.a;
console.log(obj.hasOwnProperty(‘a’)). //false
Object.defineProperty
Object.defineProperty
语法: Object.defineProperty(obj, prop, descriptor)
参数说明:
obj
:必需。目标对象prop
:必需。需定义或修改的属性的名字或 Symbol
descriptor``:必需。
要定义或修改的属性描述符
返回值: 被传递给函数的对象。
let object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false
});
object1.property1 = 77;
console.log(object1.property1); // 42
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符
注意:一个描述符只能是这两者其中之一,不能同时是两者。
数据描述符具有以下可选键值:
- value:该属性对应的值。可以是任何有效的JavaScript值(数值,对象,函数等)。 默认为 undefined。
- writable:当 writable 属性设置为 false 时,该属性被称为“不可写的”。它不能被重新赋值。 默认为 false不可修改。
举个例子 🌰就知道了:
let obj = {}
Object.defineProperty(obj, 'a', {
value: '666',
writable: false
});
delete obj.a; // false
obj.a = '333'
obj.a // '666'
从上面例子可以看出,obj.a 不可以被删除也不可以被修改。
存取描述符具有以下可选键值:
get
:属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined。set
:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为 undefined。
共享键值(默认值是指在使用 Object.defineProperty() 定义属性时的默认值):
configurable
:当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除(不能保证该属性能被修改,只有当 writable 为 true 时,才能修改该属性)。默认为 false。(configurable属性控制了属性描述对象的可写性。为false时不能修改属性描述对象)enumerable
:enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。 默认为 false。 举个例子 🌰:
let obj = { name: 'xm', age: 18 }
Object.defineProperty(obj, 'a', {
value: '666',
enumerable: false
});
for (i in obj) {
console.log(i)
}
// name
// age
Object.keys(obj) // ["name", "age"]
Object.defineProperty(obj, 'b', {
value: '666',
configurable: true
});
obj.b = '333'
obj.b // '666'
delete obj.b // true
// 可以修改描述符对象中的属性
Object.defineProperty(obj, 'b', {
value: '666',
configurable: true,
writable: true
});
obj.b = '333'
obj.b // '333'
// 将属性c的描述符对象设为不可修改
Object.defineProperty(obj, 'c', {
value: '666',
configurable: false,
writable: true
});
// 可以将 writable: true 修改为 false,反过来不行,enumerable不能修改
Object.defineProperty(obj, 'c', {
configurable: false,
writable: false
});
// 会报错
Object.defineProperty(obj, 'c', {
configurable: false,
writable: true
});
「描述符默认值」:
① 拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
② 属性值和函数的键 value、get 和 set 字段的默认值为 undefiend。
注意:如果一个描述符不具有 value、writable、get 和 set 中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 value 或 writable 和 get 或 set 键,则会产生一个异常。
也就是属性描述符只能有两种形式:
let Person = {};
Object.defineProperty(Person, 'age', {
value: 15,
writable: false, // 是否可以修改
configurable: false, //是否可配置
enumerable: true //是否可被枚举
})
let Person2 = {};
Object.defineProperty(Person2, 'name', {
get() {
return name
},
set(val) {
name = val
},
enumerable: false, //是否可配置
})
我们还可以自定义 Setters 和 Getters
下面的例子展示了如何实现一个自存档对象。当设置temperature 属性时,archive 数组会收到日志条目。
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function () {
console.log('get!');
return temperature;
},
set: function (value) {
temperature = value;
archive.push({ val: temperature });
}
});
this.getArchive = function () { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
//还可以简单的实现一个 Vue 双向数据绑定。
<!DOCTYPE html >
<html lang="en" >
<head>
<meta charset="UTF-8" />
<meta name="viewport" content = "width=device-width, initial-scale=1.0" />
<meta http - equiv="X-UA-Compatible" content = "ie=edge" />
<title>Document < /title>
< /head>
< body >
<div id="app" >
<input type="text" id = "a" />
<span id="b" > </span>
< /div>
< /body>
< script >
var obj = {}; //定义一个空对象
var val = "zhao"; //赋予初始值
Object.defineProperty(obj, "val", {
//定义要修改对象的属性
get: function () {
return val;
},
set: function (newVal) {
val = newVal; //定义val等于修改后的内容
document.getElementById("a").value = val; //让文本框的内容等于val
document.getElementById("b").innerHTML = val; //让span的内容等于val
},
});
document.addEventListener("keyup", function (e) {
//当在文本框输入内容时让对象里你定义的val等于文本框的值
obj.val = e.target.value;
});
</script>
< /html>
Object.defineProperties()
MDN定义:Object.defineProperties()
方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
语法:Object.defineProperties(obj, props)
参数与 Objec.defineProperty()
相似。
- obj 在其上定义或修改属性的对象。
- props 要定义其可枚举属性或修改的属性描述符的对象。对象中存在的属性描述符主要有两种:数据描述符和访问器描述符
描述符与 Objec.defineProperty()
相同
返回值:传递给函数的对象。
从语法和参数上来看 Objec.defineProperty()
与 Object.defineProperties 十分相似,区别是一个只能定义一个属性,一个可以定义多个属性。
例子:
var obj = {};
Object.defineProperties(obj, {
'property1': {
value: true,
writable: true
},
'property2': {
value: 'Hello',
writable: false
}
});
Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor
ES5有一个Object.getOwnPropertyDescriptor
方法,返回某个对象属性的描述对象(descriptor)。
var obj = { p: 'a' };
Object.getOwnPropertyDescriptor(obj, 'p')
// Object { value: "a",
// writable: true,
// enumerable: true,
// configurable: true
// }
Object.getOwnPropertyDescriptors
ES2017 引入了Object.getOwnPropertyDescriptors
方法,返回指定对象所有自身属性(非继承属性)的描述对象。
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
上面代码中,Object.getOwnPropertyDescriptors
方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。
该方法的实现非常容易。
function getOwnPropertyDescriptors(obj) {
const result = {};
for (let key of Reflect.ownKeys(obj)) {
result[key] = Object.getOwnPropertyDescriptor(obj, key);
}
return result;
}
该方法的引入目的,主要是为了解决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 }
上面代码中,source
对象的foo
属性的值是一个赋值函数,Object.assign
方法将这个属性拷贝给target1
对象,结果该属性的值变成了undefined
。这是因为Object.assign
方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。
这时,Object.getOwnPropertyDescriptors
方法配合Object.defineProperties
方法,就可以实现正确拷贝。
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 }
上面代码中,将两个对象合并的逻辑提炼出来,就是下面这样。
const shallowMerge = (target, source) => Object.defineProperties(
target,
Object.getOwnPropertyDescriptors(source)
);
Object.getOwnPropertyDescriptors
方法的另一个用处,是配合Object.create
方法,将对象属性克隆到一个新对象。这属于浅拷贝。
const clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
// 或者
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
上面代码会克隆对象obj
。
另外,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 example
let a = { a: 'a' };
let b = { b: 'b' };
let c = { c: 'c' };
let d = mix(c).with(a, b);
上面代码中,对象a
和b
被混入了对象c
。
出于完整性的考虑,Object.getOwnPropertyDescriptors
进入标准以后,还会有Reflect.getOwnPropertyDescriptors
方法。
Object.getOwnPropertyNames()
返回一个数组,该数组对元素是 obj
自身拥有的枚举或不可枚举属性名称字符串。(不包含Symbol类型)
let obj1 = {}
Object.defineProperties(obj1, {
name: {
value: 'hhh',
enumerable: true
},
age: {
value: 18,
enumerable: false
}
})
console.log(obj1.name); // hhh
console.log(obj1.age); // 18
// 只会打印可枚举的
for (const key in obj1) {
console.log(key);
} // name
// 要是想把所有的key都获取到 使用 Object.getOwnPropertyNames()
let keys = Object.getOwnPropertyNames(obj1)
console.log(keys); // [ 'name', 'age' ] 不可枚举的 age 也打印了出来
bject.getOwnPropertySymbols()
返回一个给定对象自身的所有 Symbol 属性的数组。
与Object.getOwnPropertyNames()类似,您可以将给定对象的所有符号属性作为 Symbol 数组获取。 请注意,Object.getOwnPropertyNames()本身不包含对象的 Symbol 属性,只包含字符串属性。
let obj1 = {}
let symbol1 = Symbol('symbol1')
let symbol2 = Symbol.for('symbol2')
Object.defineProperties(obj1, {
name: {
value: 'hhh',
enumerable: true
},
age: {
value: 18,
enumerable: false
},
[symbol1]: {
value: 'localsymbol'
},
[symbol2]: {
value: 'globalSymbol'
},
})
let keys = Object.getOwnPropertyNames(obj1)
console.log(keys); // [ 'name', 'age' ] 无法获取到symbol,只能获取到字符串key
let key2 = Object.getOwnPropertySymbols(obj1)
console.log(key2); // [ Symbol(symbol1), Symbol(symbol2) ]
console.log(obj1[key2[0]]); // localsymbol
console.log(obj1[key2[1]]); // globalSymbol
Object.keys()、values()、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)属性的键值。
var obj = { foo: 'bar', baz: 42 };
Object.values(obj)// ["bar", 42]
var obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)// ["b", "c", "a"]
上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b
、c
、a
。
Object.values
只返回对象自身的可遍历属性。
var obj = Object.create({}, { p: { value: 42 } });
Object.values(obj) // []
上面代码中,Object.create
方法的第二个参数添加的对象属性(属性p
),如果不显式声明,默认是不可遍历的,因为p
的属性描述对象的enumerable
默认是false
,Object.values
不会返回这个属性。只要把enumerable
改成true
,Object.values
就会返回属性p
的值。
var 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)属性的键值对数组。
var 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
结构。
var obj = { foo: 'bar', baz: 42 };
var 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;
}
Object.fromEntries()
把键值对列表转换为一个对象。
Map转Object
const map = new Map([['foo', 'bar'], ['baz', 42]]);
const obj = Object.fromEntries(map);
console.log(obj); // { foo: "bar", baz: 42 }
Array转Object
const arr = [['0', 'a'], ['1', 'b'], ['2', 'c']];
const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }
对象转换
对象先通过 Object.entries()
转变为数组
然后数组通过map过滤一遍
然后返回的新数组再通过Object.fromEntries()
转换为对象,达到对象转换的效果
const object1 = { a: 1, b: 2, c: 3 };
const object2 = Object.fromEntries(
Object.entries(object1)
.map(([key, val]) => [key, val * 2])
);
console.log(object2);
// { a: 2, b: 4, c: 6 }
属性的遍历
ES6一共有5种方法可以遍历对象的属性。
( 1 ) for...in
for...in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
( 2 ) Object.keys(obj)
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。
( 3 ) Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。
( 4 ) Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有Symbol属性。
( 5 ) 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属性。
__proto__
属性、setPrototypeOf()、getPrototypeOf()
__proto__
属性``
__proto__
属性(前后各两个下划线),用来读取或设置当前对象的prototype
对象。目前,所有浏览器(包括 IE11)都部署了这个属性。
// es6的写法
var obj = {
method: function () { ... }
};
obj.__proto__ = someOtherObj;
// es5的写法
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)
// 用法
var o = Object.setPrototypeOf({}, null);
该方法等同于下面的函数。
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.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.setPrototypeOf
方法配套,用于读取一个对象的原型对象。
下面是一个例子。
function Rectangle() {
// ...
}
var rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.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 // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
如果参数是undefined
或null
,它们无法转为对象,所以会报错。
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object
Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object