Object原型方法整理

这些方法基本都是ES5版本添加的,IE9起实现。

Object.create()

方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。 常用于原型链式继承和对象拷贝。

原型链式继承及Object.create

在没有create()方法的时候,我们将如下封装一个object函数:

1
2
3
4
5
function object(o) {
function F() {}
F.prototype = o;
return new F();
}

并实现继承,如:

1
2
3
4
5
6
7
8
var 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
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
29
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}

// 父类的方法
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};

// Rectangle - 子类(subclass)
function Rectangle() {
Shape.call(this); // call super constructor.
}

// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?',
rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'

对象拷贝

由于Object的特性,我们可以借助以实现对象的拷贝工作,注意:拷贝的是原型上的属性


1
2
3
4
5
6
7
8
9
10
11
12
let 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
11
var 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
12
var 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
11
function 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
2
3
const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

注意:针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let obj1 = { a: 0 , b: { c: 0}}; 
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}

obj1.a = 1;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}

obj2.a = 2;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 0}}

obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}}
console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}}

// Deep Clone
obj1 = { a: 0 , b: { c: 0}};
let obj3 = JSON.parse(JSON.stringify(obj1));
obj1.a = 4;
obj1.b.c = 4;
console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}

合并对象

利用Object.assign()可以实现对象的合并,如

1
2
3
4
5
6
7
const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };

const obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。

原始类型会被包装为对象

如:

1
2
3
4
5
6
7
8
9
const 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
11
let $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
29
const 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
2
3
var obj = { foo: "bar", baz: 42 }; 
var map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baz: 42 }

而Map转为Object则通过Object.fromEntries()方法,如

1
2
3
4
5
6
7
8
9
const 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
2
3
4
5
6
7
8
9
10
new Vue({
data: {
list: []
},
mounted () {
get('test.php', data => {
this.list = Object.freeze(data);
})
}
})

Object.preventExtensions()

Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。

基本

语法:

1
Object.preventExtensions(obj)

参数:

  • obj:将要变得不可扩展的对象。

返回值:

  • 已经不可扩展的对象。

注意,一般来说,不可扩展对象的属性可能仍然可被删除。尝试将新属性添加到不可扩展对象将静默失败或抛出TypeError

Object.preventExtensions()仅阻止添加自身的属性。但属性仍然可以添加到对象原型。

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
29
// Object.preventExtensions将原对象变的不可扩展,并且返回原对象.
var obj = {};
var obj2 = Object.preventExtensions(obj);
obj === obj2; // true

// 字面量方式定义的对象默认是可扩展的.
var empty = {};
Object.isExtensible(empty) //=== true

// ...但可以改变.
Object.preventExtensions(empty);
Object.isExtensible(empty) //=== false

// 使用Object.defineProperty方法为一个不可扩展的对象添加新属性会抛出异常.
var nonExtensible = { removable: true };
Object.preventExtensions(nonExtensible);
Object.defineProperty(nonExtensible, "new", { value: 8675309 }); // 抛出TypeError异常

// 在严格模式中,为一个不可扩展对象的新属性赋值会抛出TypeError异常.
function fail()
{
"use strict";
nonExtensible.newProperty = "FAIL"; // throws a TypeError
}
fail();

// 一个不可扩展对象的原型是不可更改的,__proto__是个非标准魔法属性,可以更改一个对象的原型.
var fixed = Object.preventExtensions({});
fixed.__proto__ = { oh: "hai" }; // 抛出TypeError异常

Object.isExtensible()

Object.isExtensible()方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
Object.preventExtensions,Object.seal 或 Object.freeze 方法都可以标记一个对象为不可扩展(non-extensible)。

Object.seal()

该方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。

基本

语法:

1
Object.seal(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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var obj = {
prop: function() {},
foo: 'bar'
};

// New properties may be added, existing properties
// may be changed or removed.
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;

var o = Object.seal(obj);

o === obj; // true
Object.isSealed(obj); // === true

// Changing property values on a sealed object
// still works.
obj.foo = 'quux';

// But you can't convert data properties to accessors,
// or vice versa.
Object.defineProperty(obj, 'foo', {
get: function() { return 'g'; }
}); // throws a TypeError

// Now any changes, other than to property values,
// will fail.
obj.quaxxor = 'the friendly duck';
// silently doesn't add the property
delete obj.foo;
// silently doesn't delete the property

// ...and in strict mode such attempts
// will throw TypeErrors.
function fail() {
'use strict';
delete obj.foo; // throws a TypeError
obj.sparky = 'arf'; // throws a TypeError
}
fail();

// Attempted additions through
// Object.defineProperty will also throw.
Object.defineProperty(obj, 'ohai', {
value: 17
}); // throws a TypeError
Object.defineProperty(obj, 'foo', {
value: 'eit'
}); // changes existing property value

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
11
var 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Object.is('foo', 'foo');     // true
Object.is(window, window); // true

Object.is('foo', 'bar'); // false
Object.is([], []); // false

var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo); // true
Object.is(foo, bar); // false

Object.is(null, null); // true

// 特例
Object.is(0, -0); // false
Object.is(0, +0); // true
Object.is(-0, -0); // true
Object.is(NaN, 0/0); // true

解决了坑爹的NaN != NaN

大家都知道,NaN不等于任何值,包括它本身,因此之前判断NaN的方式都是通过isNaN()方法。现在有了Object.is()方法后,我们可以更好得判断,如

1
2
let val = NaN;
Object.is(val, NaN); // true


相关链接