【笔记】Object原型方法整理
Dec 1, 2017笔记jsesObject原型方法整理
这些方法基本都是ES5版本添加的,IE9起实现。
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。 常用于原型链式继承和对象拷贝。
原型链式继承及Object.create
在没有create()方法的时候,我们将如下封装一个object函数:1
2
3
4
5function object(o) {
function F() {}
F.prototype = o;
return new F();
}
并实现继承,如:1
2
3
4
5
6
7
8var person = {
name: 'Micheal',
firends: ['Shelby', 'Van']
};
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.firends.push('Rob');
Object的实现同上object()方法,
语法:1
Object.create(proto, [propertiesObject])
参数:
- proto:新创建对象的原型对象。
- propertiesObject:可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。
返回值:
- 一个新对象,带着指定的原型对象和属性。
注意:如果propertiesObject参数是 null 或非原始包装对象,则抛出一个 TypeError 异常。
继承实现
1 | // Shape - 父类(superclass) |
对象拷贝
由于Object的特性,我们可以借助以实现对象的拷贝工作,注意:拷贝的是原型上的属性。
如1
2
3
4
5
6
7
8
9
10
11
12let obj1 = {
a: 1
};
let obj2 = Object.create(obj1);
console.log(obj2); // {}
console.log(obj2.a); // 1
obj2.a = 123;
console.log(obj2); // {a: 123}
console.log(obj2.a); // 123
console.log(obj1.a); // 1
实现“干净”的字典对象
早期部分for...in
的遍历中,会将对象的构造器、原型属性/方法也列入循环之中,为了避免这种情况,大多都利用obj.hasOwnProperty()方法进行过滤,如1
2
3
4
5
6
7
8
9
10
11var obj = {
a: 1
};
console.log(obj2.constructor); // Object()
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
// ...
}
}
但有了Object.create()方法后,我们可以通过Object.create(null)创建“干净”的对象,如1
2
3
4
5
6
7
8
9
10
11
12var obj = Object.create(null, {
a: {
writable: true,
enumberable: true,
value: 1
}
});
console.log(obj.constructor); // undefined
for (var i in obj) {
// ...
}
后面的Object.setPrototypeOf()也可实现类似的功能
Object.assign()
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
早期实现
在没有Object.assign()方法的时候,其功能通常由一个for...in
循环创建对象属性实现,可参考jQuery/Zepto的$.extend()方法。如1
2
3
4
5
6
7
8
9
10
11function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
extend(target[key], source[key], deep)
}
else if (source[key] !== undefined) target[key] = source[key]
}
介绍
语法:1
Object.assign(target, ...sources)
参数:
- target:目标对象。
- sources:源对象。
返回值:
- 目标对象。
Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。
String类型和 Symbol 类型的属性都会被拷贝。
执行异常会打断后续拷贝任务。
复制对象
利用Object.assign()可复制对象(拷贝可枚举属性),如
1 | const obj = { a: 1 }; |
注意:针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。
1 | let obj1 = { a: 0 , b: { c: 0}}; |
合并对象
利用Object.assign()可以实现对象的合并,如
1 | const o1 = { a: 1 }; |
原始类型会被包装为对象
如:1
2
3
4
5
6
7
8
9const v1 = "abc";
const v2 = true;
const v3 = 10;
const v4 = Symbol("foo")
const obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
DOM设置属性
如下:1
2
3
4
5
6
7
8
9
10
11let $dom = document.querySelector('#test');
const props = {
src: 'https://localhost/1.png',
alt: 'this is a picture',
onClick () {
alert('Picture!');
}
};
Object.assign(element, props);
Object.entries()
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。
基本
语法:1
Object.entries(obj)
参数:
- obj:可以返回其可枚举属性的键值对的对象。
返回值:
- 给定对象自身可枚举属性的键值对数组。
如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]
// array like object
const obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.entries(obj)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]
// array like object with random key ordering
const anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.entries(anObj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]
// getFoo is property which isn't enumerable
const myObj = Object.create({}, { getFoo: { value() { return this.foo; } } });
myObj.foo = 'bar';
console.log(Object.entries(myObj)); // [ ['foo', 'bar'] ]
// non-object argument will be coerced to an object
console.log(Object.entries('foo')); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ]
// iterate through key-value gracefully
const obj = { a: 5, b: 7, c: 9 };
for (const [key, value] of Object.entries(obj)) {
console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
}
// Or, using array extras
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
});
将Object转为Map
因为new Map() 构造函数接受一个可迭代的entries。借助Object.entries方法你可以很容易的将Object转换为Map,如:
1 | var obj = { foo: "bar", baz: 42 }; |
而Map转为Object则通过Object.fromEntries()方法,如1
2
3
4
5
6
7
8
9const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
const obj = Object.fromEntries(entries);
console.log(obj);
// expected output: Object { foo: "bar", baz: 42 }
Object.freeze()和Object.isFrozen()
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性(enumerable)、可配置性(configurable)、可写性(writable),以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
因此通过Object.preventExtensions()
和Object.freeze()
处理后的对象在此通常判断为true。
基本
语法:1
Object.freeze(obj)
参数:
- obj:要被冻结的对象。
返回值:
- 被冻结的对象。
被冻结对象自身的所有属性都不可能以任何方式被修改。数据属性的值不可更改,访问器属性(有getter和setter)也同样(但由于是函数调用,给人的错觉是还是可以修改这个属性)。如果一个属性的值是个对象,则这个对象中的属性是可以修改的,除非它也是个冻结对象。数组作为一种对象,被冻结,其元素不能被修改。没有数组元素可以被添加或移除。
Object.isFrozen()
该方法判断一个对象是否被冻结。一个对象是冻结的是指它不可扩展,所有属性都是不可配置的,且所有数据属性(即没有getter或setter组件的访问器的属性)都是不可写的。
VueJS中冻结数据以提高性能
VueJS在绑定数据中,会对数据的getter/setter进行转换处理,通过Object.freeze()可避免此操作从而大幅提高性能。不过要注意前提是保证数据不会修改,因此通常仅用作于接口数据的处理。如
1 | new Vue({ |
Object.preventExtensions()
Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
基本
语法:1
Object.preventExtensions(obj)
参数:
- obj:将要变得不可扩展的对象。
返回值:
- 已经不可扩展的对象。
注意,一般来说,不可扩展对象的属性可能仍然可被删除。尝试将新属性添加到不可扩展对象将静默失败或抛出TypeError
Object.preventExtensions()仅阻止添加自身的属性。但属性仍然可以添加到对象原型。
1 | // Object.preventExtensions将原对象变的不可扩展,并且返回原对象. |
Object.isExtensible()
Object.isExtensible()方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
Object.preventExtensions,Object.seal 或 Object.freeze 方法都可以标记一个对象为不可扩展(non-extensible)。
Object.seal()
该方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
基本
语法:1
Object.seal(obj)
参数:
- obj:将要被密封的对象。
返回值:
- 被密封的对象。
1 | var obj = { |
Object.isSealed()
该方法判断一个对象是否被密封。密封对象是指那些不可 扩展 的,且所有自身属性都不可配置且因此不可删除(但不一定是不可写)的对象。
Object.seal()和Object.freeze()
Object.seal做了如下几件事情,
- (1)给对象设置,
Object.preventExtension(obj1)
,
禁止更改原型,禁止添加属性, - (2)为对象的每一个属性设置,
writable:false
,
禁止更改属性值, - (3)为对象的每一个属性设置,
configurable:false
。
禁止删除属性,禁止更改writable为true,禁止更改enumerable为false,禁止更改configuable为true
而Object.freeze做了两件事情,
- (1)给对象设置,
Object.preventExtension(obj1)
,
禁止更改原型,禁止添加属性, - (2)为对象的每一个属性设置,
configurable:false
,
禁止更改属性值,
与Object.freeze不同的是,Object.seal后的对象是可写的writable:true
。
Object.setPrototypeOf()
该方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。
警告: 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.proto = … 语句上的时间花费,而且可能会延伸到任何代码,那些可以访问任何[[Prototype]]已被更改的对象的代码。如果你关心性能,你应该避免设置一个对象的 [[Prototype]]。相反,你应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象。
基本
语法:1
Object.setPrototypeOf(obj, prototype)
参数:
- obj:要设置其原型的对象。
- prototype:该对象的新原型(一个对象 或 null)。
如果对象的[[Prototype]]被修改成不可扩展(通过 Object.isExtensible()查看),就会抛出 TypeError异常。如果prototype参数不是一个对象或者null(例如,数字,字符串,boolean,或者 undefined),则什么都不做。否则,该方法将obj的[[Prototype]]修改为新的值。
实现“干净”的字典对象
类似于Object.create()对其的实现原理,
如:1
2
3
4
5
6
7
8
9
10
11var obj = { a: 1 };
console.log(obj.constructor); // Object
Object.setPrototypeOf(obj);
console.log(obj.constructor); // undefined
for (var i in obj) {
// ...
}
Object.is()
该方法判断两个值是否是相同的值。
基本
语法:1
Object.is(value1, value2);
参数:
- value1:第一个需要比较的值。
- value2:第二个需要比较的值。
返回值:
- 表示两个参数是否相同的布尔值 。
Object.is() 判断两个值是否相同。如果下列任何一项成立,则两个值相同:
- 两个值都是 undefined
- 两个值都是 null
- 两个值都是 true 或者都是 false
- 两个值是由相同个数的字符按照相同的顺序组成的字符串
- 两个值指向同一个对象
- 两个值都是数字并且
- 都是正零 +0
- 都是负零 -0
- 都是 NaN
- 都是除零和 NaN 外的其它同一个数字
这种相等性判断逻辑和传统的 == 运算不同,== 运算符会对它两边的操作数做隐式类型转换(如果它们类型不同),然后才进行相等性比较,(所以才会有类似 “” == false 等于 true 的现象),但 Object.is 不会做这种类型转换。
这与 === 运算符的判定方式也不一样。=== 运算符(和== 运算符)将数字值 -0 和 +0 视为相等,并认为 Number.NaN 不等于 NaN。
1 | Object.is('foo', 'foo'); // true |
解决了坑爹的NaN != NaN
大家都知道,NaN不等于任何值,包括它本身,因此之前判断NaN的方式都是通过isNaN()方法。现在有了Object.is()方法后,我们可以更好得判断,如1
2let val = NaN;
Object.is(val, NaN); // true
相关链接
- [https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create]{https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create}
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/seal
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com