Object.create()

  1. 語法:
    Object.create(proto, [propertiesObject])
    //方法創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的proto。
  2. 參數(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. 案例說明:

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.createObject.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 方法


參考資料:

  1. MDN
  2. 阮一峰:es6對象的新增方法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容