一、實現原型拷貝
1.1、代碼
tips:為了體現原型鏈,寫了繼承實現的代碼,這部分可跳過~
<script> /* 創建包含原型鏈的實驗對象obj1-- start */ function inheritPrototype(subType, superType) { var prototype = Object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function SuperType(name) { this.name = name; this.colors = ['red', 'yellow', 'black']; } SuperType.prototype.sayName = function() { alert(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sagAge = function() { alert(this.age); }; var obj1 = new SubType('puppy', 12); /* 完成實驗對象obj1的創建 --end */ /* 原型拷貝-- start */ var obj2 = Object.getPrototypeOf(obj1); var obj3 = Object.assign(Object.create(obj2), obj1); /* 原型拷貝-- end */ console.log(obj1); console.log(obj2); console.log(obj3); </script>
以上代碼用於驗證原型鏈的拷貝,代碼分析如下。
1.2、代碼分析
step1:設置
擁有原型鏈的實例obj1,其屬性如下:

其中,自身屬性:age、colors、name;原型鏈屬性:sayAge、sayName;可忽略部分為對象的默認原型屬性,本實驗不考察。
step2:使用 Object.getPrototypeOf() 取得實例對象obj1的
原型屬性對象
obj2,屬性如下:

step3:使用 Object.create() 將原型屬性對象obj2設置成
新對象的原型屬性;
step4:使用 Object.assign() 實現
自身屬性的拷貝,將其疊加到擁有原型屬性的新對象上,形成obj3。

如上,通過step2-step4的過程,實現了對obj1的原型鏈拷貝,形成新的對象obj3。
1.3、拷貝代碼整合(淺拷貝、深拷貝、原型拷貝)
function clone(origin) { return Object.assign({}, origin); } function moreClone(origin) { let oriProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(oriProto), origin); }
函數clone為簡單復制,得到原對象自身可枚舉屬性的拷貝;
函數moreClone可拷貝原始對象的繼承鏈及自身可枚舉的屬性。
tips:這里所用的拷貝方法為淺拷貝。深拷貝、淺拷貝簡單說就是看源、目標的所有屬性是否相互影響(自身屬性、或者原型屬性是否指向同一個地址),如果影響就是淺拷貝,如果不影響則為深拷貝。如果origin內有復雜類型的數據,會使得屬性指向相同地址,從而使得源、目標對象之間相互影響。
如上圖,對obj1的colors數據插入一個值,數組的length增1;同時,發現obj3的length也增加了1。淺拷貝得證。
jquery的$.extend實現的深度拷貝可通過for...in實現,也就是其深拷貝是對自身的與繼承的可枚舉屬性進行~其丟掉了原型鏈。所以,原型拷貝與深拷貝之間有所區別,可根據需要進行選擇。在使用原生JS的環境下,可通過for...in來實現深拷貝。
將obj1深拷貝給obj4,如下:
let obj4 = {}; $.extend(true, obj4, obj1); console.log(obj4);
chrome控制台輸出如下:
該結構與通過for...in輸出的obj1的結構相同,其丟失了obj1當中的繼承的原型屬性。(此處的__proto__為對象基礎類型的原型屬性,而非繼承自自類的原型屬性)。
對obj1復雜類型colors插入值,obj4不受影響。深拷貝,得證。
所以,通過for...in可實現深拷貝、Object.create()結合Object.getPrototypeOf()、Object.assign()實現原型拷貝。
二、Object方法深入了解
以上用到了對象的三種方法,來實現原型鏈的拷貝。依照第一節中的實驗代碼,加深對這些方法的理解:
2.1、Object.getPrototypeOf(obj1)
概念:返回指定對象的prototype(原型);
如1.2 step2中的圖可知,其返回的原型為obj1的原型。
2.2、Object.create(proto, [propertiesObject])
概念:是一種新的對象創建的方法,其有兩個參數:
第一個proto,為要創建的對象的原型;
第二個propertiesObject,為對象的屬性描述符,與Object.definePropertyOf()同。
如圖:

上圖通過 Object.create() 分別創建 obj1 與 obj2 對應的新對象,從圖中可得出以下結論:
第一個參數:作為新對象的原型對象”__proto__”;
第二個參數:為屬性描述符對象。(通過Object.defineProperties(obj, props)的第二個參數了解)
所以,Object.create(proto, [propertiesObject]) 可創建一個帶有原型屬性的新對象。
2.3、Object.assign(target, …sources)
概念:將一個或多個源對象自身的所有
可枚舉屬性 復制 到目標對象。
如圖:

將obj1復制給一個空對象,可以看到obj1的原型屬性未被復制。由於原型屬性並非對象自身的屬性,未被復制。
三、附加Object.defineProperty(obj, prop, descriptor)
概念:直接在一個對象上定義新的屬性或修改現有屬性,並返回該對象。
參數:obj,被操作對象;
prop:要定義或修改的屬性的名稱;
descriptor:
將被定義或修改的屬性的描述符。
屬性的描述符相關描述如下:

看實例:
var obj = {}; Object.defineProperty(obj, ‘test', { configurable: false, enumerable: false, get() { return this._value; }, set(value) { this._value = value + ' yeap'; return true; } }); console.log(obj.property1) //undefined obj.test = 2 console.log(obj.property1) // 2 yeap
可以看到,給obj.test賦值時,會先通過屬性描述符進行處理;通過set方法,將value處理以后,賦值給_value;當取值時,返回_value的值。
如上,通過設置 set() 和 get() ,可對屬性值的存取進行處理。該屬性操作方法已廣泛用於數據雙向綁定的一些MVVM框架中,其中VUE就使用了該方法。其通過Object.defineProperty方法,實現setter和getter,形成依賴追蹤,從而在數據被訪問或修改時通知變化。
tips:Object.defineProperties(obj, props)是Object.defineProperty(obj, prop, descriptor)的擴展,可一次設置多個屬性的描述符。
示例:
Object.defineProperties(obj, { test1: { configurable: false, enumerable: false, get() { return this._value; }, set(value) { this._value = value + ' yeap'; return true; } }, test2: { value: ‘cutcut’, writable: true } });