- 語法:
Object.create(proto, [propertiesObject])
//方法創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的proto。 - 參數(shù):
- proto : 必須。表示新建對象的原型對象,即該參數(shù)會被賦值到目標(biāo)對象(即新對象,或說是最后返回的對象)的原型上。該參數(shù)可以是
null,對象, 函數(shù)的prototype屬性(創(chuàng)建空的對象時(shí)需傳null , 否則會拋出TypeError異常)。 - propertiesObject : 可選。 添加到新創(chuàng)建對象的可枚舉屬性(即其自身的屬性,而不是原型鏈上的枚舉屬性)對象的屬性描述符以及相應(yīng)的屬性名稱。這些屬性對應(yīng)
Object.defineProperties()的第二個參數(shù)。
3 返回值:
在指定原型對象上添加新屬性后的對象。
- 案例說明:
1)創(chuàng)建對象的方式不同
new Object() 通過構(gòu)造函數(shù)來創(chuàng)建對象, 添加的屬性是在自身實(shí)例下。
Object.create() es6創(chuàng)建對象的另一種方式,可以理解為繼承一個對象, 添加的屬性是在原型下。
// new Object() 方式創(chuàng)建
var a = { rep : 'apple' }
var b = new Object(a)
console.log(b) // {rep: "apple"}
console.log(b.__proto__) // {}
console.log(b.rep) // {rep: "apple"}
// Object.create() 方式創(chuàng)建
var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b) // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // {rep: "apple"}
Object.create()方法創(chuàng)建的對象時(shí),屬性是在原型下面的,也可以直接訪問 b.rep // {rep: "apple"} ,
此時(shí)這個值不是吧b自身的,是它通過原型鏈proto來訪問到b的值。
2)創(chuàng)建對象屬性的性質(zhì)不同
// 創(chuàng)建一個以另一個空對象為原型,且擁有一個屬性p的對象
o = Object.create({}, { p: { value: 42 } })
// 省略了的屬性特性默認(rèn)為false,所以屬性p是不可寫,不可枚舉,不可配置的:
o.p = 24
o.p
//42
o.q = 12
for (var prop in o) {
console.log(prop)
}
//"q"
delete o.p
//false
Object.create() 用第二個參數(shù)來創(chuàng)建非空對象的屬性描述符默認(rèn)是為false的,而構(gòu)造函數(shù)或字面量方法創(chuàng)建的對象屬性的描述符默認(rèn)為true??聪聢D解析:

3)創(chuàng)建空對象時(shí)不同

當(dāng)用構(gòu)造函數(shù)或?qū)ο笞置媪糠椒▌?chuàng)建空對象時(shí),對象時(shí)有原型屬性的,即有
_proto_;當(dāng)用Object.create()方法創(chuàng)建空對象時(shí),對象是沒有原型屬性的。
4)__proto__ 屬性
JavaScript 的對象繼承是通過原型鏈實(shí)現(xiàn)的。ES6 提供了更多原型對象的操作方法。
__proto__屬性(前后各兩個下劃線),用來讀取或設(shè)置當(dāng)前對象的prototype對象。目前只有瀏覽器環(huán)境必須部署有這個屬性,其他運(yùn)行環(huán)境不一定要部署,因此不建議使用這個屬性,而是使用下面這些來 Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)代替。
-
Object.create()
描述:該方法創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的__proto__;
格式:Object.create(proto[, propertiesObject])
用法:如果用傳統(tǒng)的方法要給一個對象的原型上添加屬性和方法,是通過__propt__實(shí)現(xiàn)的
var proto = {
y: 20,
z: 40,
showNum(){}
};
var o = Object.create(proto);

如果是不用Object,create()方法,我們是如何給對象原型添加屬性和方法的?
------ 通過構(gòu)造函數(shù)或者類,例如:
//創(chuàng)建一個構(gòu)造函數(shù)或者類
var People = function(){}
People.prototype.y = 20
People.prototype.showNum = function() {}
//通過構(gòu)造函數(shù)創(chuàng)建實(shí)例
var p = new People();
console.log(p.__proto__ === People.prototype) // true


現(xiàn)在有 Object.create() 就簡單的多了
-
Object.setPrototypeOf
描述:該方法的作用與__proto__相同,用來設(shè)置一個對象的prototype對象,返回參數(shù)對象本身。它是 ES6 正式推薦的設(shè)置原型對象的方法。
格式:Object.setPrototypeOf(object, prototype)
用法:
var proto = {
y: 20,
z: 40
};
var o = { x: 10 };
Object.setPrototypeOf(o, proto);

輸出結(jié)果中看出,添加的方法是在原型上的。就類似于
obj.__proto__ = proto;
-
Object.getPrototypeOf()
描述:用于讀取一個對象的原型對象;
格式:Object.getPrototypeOf(obj);
用法:
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
4.1)原型屬性的繼承
這里結(jié)合一個例子來說說這幾個方法的使用:
場景:拷貝一個構(gòu)造函數(shù)的實(shí)例。
var triangle = {a: 1, b: 2, c: 3};
function ColoredTriangle() {
this.color = 'red';
}
//ColoredTriangle.prototype = triangle; //ColoredTriangle.prototype.constructor === ColoredTriangle// false
Object.assign(ColoredTriangle.prototype, triangle) //ColoredTriangle.prototype.constructor === ColoredTriangle// true
var c = new ColoredTriangle();
打印出 實(shí)例c 看看結(jié)構(gòu)是怎樣的

其中 color 屬性在實(shí)例上,而其他的原型上。
現(xiàn)在來拷貝一個 實(shí)例 c2
var c2 = Object.assign({},c)
console.log(c2.color); //red
console.log(c2.a); //undefined
因?yàn)?Object.assing 是不能拷貝到繼承或原型上的方法的。所以 實(shí)例c2 沒有 a 這個屬性。那要怎么要才能拷貝到原型上的方法呢?
4.1.1)第一種方法
var originProto = Object.getPrototypeOf(c);
var originProto2 = Object.create(originProto);
var c2 = Object.assign(originProto2, c);
//var c2 = Object.assign(Object.create(Object.getPrototypeOf(c)), c)
console.log(c2.color); // red
console.log(c2.a); // 1
這樣就實(shí)現(xiàn)了原型屬性的拷貝。
Object.getPrototypeOf(c) 既 originProto 得到的是原型上的 //{a: 1, b: 2, c: 3};
Object.create(originProto) 既 originProto2 既是創(chuàng)建了一個 {a: 1, b: 2, c: 3} 在原型上的新對象;
Object.assign(originProto2, c) 在源對象originProto2 上合并對象 c;
4.1.2)第二種方法 (推薦)
var c = new ColoredTriangle();
var c2 = Object.create(Object.getPrototypeOf(c), Object.getOwnPropertyDescriptors(c));
console.log(c2.color); // red
console.log(c2.a); // 1
可以把Object.create()的參數(shù)理解為:第一個參數(shù)是放在新對象的原型上的,第二個參數(shù)是放在新對象的實(shí)例上的。
所以上面例子
Object.getPrototypeOf() 得到的是 c 對象的原型,然后作為第一個參數(shù),所以會在新對象c2 的原型上。
Object.getOwnPropertyDescriptors() 得到是 c 對象自身屬性(包括可枚舉和不可枚舉的),作為第二個參數(shù),放在 c2 的實(shí)例上。
為什么說推薦這個方法呢?因?yàn)镺bject.assign() 方法不能正確拷貝 get ,set 屬性。
例如,我們給 c 實(shí)例添加一個 "colorGet" 屬性,并設(shè)置該屬性的get 描述符:
var c = new ColoredTriangle();
Object.defineProperty(c,'colorGet', {
enumerable: true, // 設(shè)為可枚舉,不然 Object.assign 方法會過濾該屬性
get(){
return "Could it return " + this.color
}
});
var c3 = Object.assign(Object.create(Object.getPrototypeOf(c)), c)
結(jié)果如下:

這里沒有拷貝到 "colorGet" 的 get 描述符,而是直接把獲取到的值賦值給 "colorGet" 。
那對于 get 描述符要怎么獲取呢? Object.getOwnPropertyDescriptors就專為解決這問題而生。
而又因?yàn)橐截愒蜕系膶傩?,所以結(jié)合Object.create、Object.getPrototypeOf 方法一起使用。即上面的第二種實(shí)現(xiàn)方法,如下:
var c = new ColoredTriangle();
Object.defineProperty(c,'colorGet', {
enumerable: true, // 設(shè)為可枚舉,不然 Object.assign 方法會過濾該屬性
get(){
return "Could it return " + this.color
}
});
var c3 = Object.create(Object.getPrototypeOf(c), Object.getOwnPropertyDescriptors(c));
結(jié)果如下:

此時(shí)已經(jīng)成功的拷貝到了get描述符啦。
雖然說實(shí)際開發(fā)上很少會要去修改 get 描述符,但是知道多一種方法,遇到這種情況時(shí)就知道該怎么去解決了。
注意:這些都只是一個層級的深拷貝。
上面實(shí)現(xiàn) 原型屬性拷貝 中的兩種方法中用到了 Object.getOwnPropertyDescriptors 、Object.assing() 、Object.create、Object.getPrototypeOf()方法,通常這幾種方法都有一起結(jié)合使用。
如果上面的例子還不理解,這里把他簡單的拿到 對象的繼承 來講解。理解的話就可以忽略啦。
4.2)原型屬性的繼承
以前,繼承另一個對象,常常寫成下面這樣。
const obj = {
__proto__: prot,
foo: 123,
};
ES6 規(guī)定__proto__只有瀏覽器要部署,其他環(huán)境不用部署。如果去除__proto__,可以用 Object.create() 和 Object.assign() 來實(shí)現(xiàn)。
//現(xiàn)在可以這樣寫 方法1
const obj = Object.create(prot);
obj.foo = 123;
// 或者 方法2
const obj = Object.assign(
Object.create(prot),
{
foo: 123,
}
);
// 或者 方法3
const obj = Object.create(prot,Object.getOwnPropertyDescriptors({ foo: 123 }));
但是 Object.assign() 無法正確拷貝get屬性和set屬性的問題。例如:
var prot = {x: 1, y: 2}
var obj = {
__proto__: prot,
foo: 100,
bar(){ return this.foo},
get baz() {return this.foo}
};
var obj2 = Object.assign(Object.create(prot), obj)

上圖中,obj 對象的 foo 屬性是一個取值函數(shù),Object.assign不會復(fù)制這個取值函數(shù),只會拿到值以后,將這個值賦上去。
而 Object.getOwnPropertyDescriptors() 可以解決這個問題 實(shí)現(xiàn)get 、set 屬性的正確拷貝,即方法3 ,如下:
var prot = {x: 1, y: 2}
var obj = {
__proto__: prot,
foo: 100,
bar(){ return this.foo},
get baz() {return this.foo}
};
var obj2 = Object.create(prot, Object.getOwnPropertyDescriptors(obj))

說了那么多種拷貝方法,怎么去選擇呢,還是要看實(shí)際應(yīng)用中的情況:
如果只是拷貝 自身可枚舉屬性,就可以只用 Object.assign 方法;
如果是要拷貝原型上的屬性,就需要 Object.assign , Object.create, Object.getPrototypeOf 方法結(jié)合使用
如果是拷貝get /set 屬性,就需要 結(jié)合 Ojbect.getOwnPropertyDescriptors 方法
參考資料: