Object.create()和深拷貝


  1. 語法:
    Object.create(proto, [propertiesObject])
    //方法創建一個新對象,使用現有的對象來提供新創建的對象的proto
  2. 參數:
  • proto : 必須。表示新建對象的原型對象,即該參數會被賦值到目標對象(即新對象,或說是最后返回的對象)的原型上。該參數可以是null對象, 函數的prototype屬性 (創建空的對象時需傳null , 否則會拋出TypeError異常)。
  • propertiesObject : 可選。 添加到新創建對象的可枚舉屬性(即其自身的屬性,而不是原型鏈上的枚舉屬性)對象的屬性描述符以及相應的屬性名稱。這些屬性對應Object.defineProperties()的第二個參數。
    3 返回值:
    在指定原型對象上添加新屬性后的對象。
  1. 案例說明:

1)創建對象的方式不同

new Object() 通過構造函數來創建對象, 添加的屬性是在自身實例下。
Object.create() es6創建對象的另一種方式,可以理解為繼承一個對象, 添加的屬性是在原型下。

// new Object() 方式創建 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() 方式創建 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()方法創建的對象時,屬性是在原型下面的,也可以直接訪問 b.rep // {rep: "apple"} ,
此時這個值不是吧b自身的,是它通過原型鏈proto來訪問到b的值。



2)創建對象屬性的性質不同

// 創建一個以另一個空對象為原型,且擁有一個屬性p的對象 o = Object.create({}, { p: { value: 42 } }) // 省略了的屬性特性默認為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() 用第二個參數來創建非空對象的屬性描述符默認是為false的,而構造函數或字面量方法創建的對象屬性的描述符默認為true。看下圖解析:


 
 



3)創建空對象時不同

 
 

當用構造函數或對象字面量方法創建空對象時,對象時有原型屬性的,即有 _proto_;
當用Object.create()方法創建空對象時,對象是沒有原型屬性的。

 



4)__proto__ 屬性
JavaScript 的對象繼承是通過原型鏈實現的。ES6 提供了更多原型對象的操作方法。
__proto__屬性(前后各兩個下划線),用來讀取或設置當前對象的prototype對象。目前只有瀏覽器環境必須部署有這個屬性,其他運行環境不一定要部署,因此不建議使用這個屬性,而是使用下面這些來 Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)代替。

  • Object.create()
    描述:該方法創建一個新對象,使用現有的對象來提供新創建的對象的__proto__
    格式:Object.create(proto[, propertiesObject])
    用法:如果用傳統的方法要給一個對象的原型上添加屬性和方法,是通過 __propt__ 實現的
var proto = { y: 20, z: 40, showNum(){} }; var o = Object.create(proto); 
 
 

如果是不用Object,create()方法,我們是如何給對象原型添加屬性和方法的?
------ 通過構造函數或者類,例如:

//創建一個構造函數或者類 var People = function(){} People.prototype.y = 20 People.prototype.showNum = function() {} //通過構造函數創建實例 var p = new People(); console.log(p.__proto__ === People.prototype) // true 
 
 
 
 

現在有 Object.create() 就簡單的多了

 

  • Object.setPrototypeOf
    描述:該方法的作用與 __proto__ 相同,用來設置一個對象的 prototype 對象,返回參數對象本身。它是 ES6 正式推薦的設置原型對象的方法。
    格式:Object.setPrototypeOf(object, prototype)
    用法:
var proto = { y: 20, z: 40 }; var o = { x: 10 }; Object.setPrototypeOf(o, proto); 
 
 

輸出結果中看出,添加的方法是在原型上的。就類似於

obj.__proto__ = proto;

 

  • Object.getPrototypeOf()
    描述:用於讀取一個對象的原型對象;
    格式:Object.getPrototypeOf(obj);
    用法:
Object.getPrototypeOf('foo') === String.prototype // true Object.getPrototypeOf(true) === Boolean.prototype // true 



4.1)原型屬性的繼承
這里結合一個例子來說說這幾個方法的使用:
場景:拷貝一個構造函數的實例。

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(); 

打印出 實例c 看看結構是怎樣的


 
 

其中 color 屬性在實例上,而其他的原型上。
現在來拷貝一個 實例 c2

var c2 = Object.assign({},c) console.log(c2.color); //red console.log(c2.a); //undefined 

因為 Object.assing 是不能拷貝到繼承或原型上的方法的。所以 實例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 

這樣就實現了原型屬性的拷貝。
Object.getPrototypeOf(c) 既 originProto 得到的是原型上的 //{a: 1, b: 2, c: 3};
Object.create(originProto) 既 originProto2 既是創建了一個 {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()的參數理解為:第一個參數是放在新對象的原型上的,第二個參數是放在新對象的實例上的。
所以上面例子
Object.getPrototypeOf() 得到的是 c 對象的原型,然后作為第一個參數,所以會在新對象c2 的原型上。
Object.getOwnPropertyDescriptors() 得到是 c 對象自身的可枚舉屬性,作為第二個參數,放在 c2 的實例上。

為什么說推薦這個方法呢?因為Object.assign() 方法不能正確拷貝 get ,set 屬性。

例如,我們給 c 實例添加一個 "colorGet" 屬性,並設置該屬性的get 描述符:

var c = new ColoredTriangle(); Object.defineProperty(c,'colorGet', { enumerable: true, // 設為可枚舉,不然 Object.assign 方法會過濾該屬性 get(){ return "Could it return " + this.color } }); var c3 = Object.assign(Object.create(Object.getPrototypeOf(c)), c) 

結果如下:


 
 

這里沒有拷貝到 "colorGet" 的 get 描述符,而是直接把獲取到的值賦值給 "colorGet" 。

那對於 get 描述符要怎么獲取呢? Object.getOwnPropertyDescriptors就專為解決這問題而生。
而又因為要拷貝原型上的屬性,所以結合Object.create、Object.getPrototypeOf 方法一起使用。即上面的第二種實現方法,如下:

var c = new ColoredTriangle(); Object.defineProperty(c,'colorGet', { enumerable: true, // 設為可枚舉,不然 Object.assign 方法會過濾該屬性 get(){ return "Could it return " + this.color } }); var c3 = Object.create(Object.getPrototypeOf(c), Object.getOwnPropertyDescriptors(c)); 

結果如下:


 
 

此時已經成功的拷貝到了get描述符啦。
雖然說實際開發上很少會要去修改 get 描述符,但是知道多一種方法,遇到這種情況時就知道該怎么去解決了。

注意:這些都只是一個層級的深拷貝。

 




上面實現 原型屬性拷貝 中的兩種方法中用到了 Object.getOwnPropertyDescriptorsObject.assing()Object.createObject.getPrototypeOf()方法,通常這幾種方法都有一起結合使用。
如果上面的例子還不理解,這里把他簡單的拿到 對象的繼承 來講解。理解的話就可以忽略啦。



4.2)原型屬性的繼承
以前,繼承另一個對象,常常寫成下面這樣。

const obj = { __proto__: prot, foo: 123, }; 

ES6 規定__proto__只有瀏覽器要部署,其他環境不用部署。如果去除__proto__,可以用 Object.create() 和 Object.assign() 來實現。

//現在可以這樣寫 方法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 屬性是一個取值函數,Object.assign不會復制這個取值函數,只會拿到值以后,將這個值賦上去。

而 Object.getOwnPropertyDescriptors() 可以解決這個問題 實現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)) 
 
 



說了那么多種拷貝方法,怎么去選擇呢,還是要看實際應用中的情況:

如果只是拷貝 自身可枚舉屬性,就可以只用 Object.assign 方法;
如果是要拷貝原型上的屬性,就需要 Object.assign , Object.create, Object.getPrototypeOf 方法結合使用
如果是拷貝get /set 屬性,就需要 結合 Ojbect.getOwnPropertyDescriptors 方法

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM