1. 程式人生 > >js中的new操作符與Object.create()的作用與區別

js中的new操作符與Object.create()的作用與區別

fcm 並不會 copyright 性能 reat 現在 所有 tar tool

js中的new操作符與Object.create()的作用與區別

https://blog.csdn.net/mht1829/article/details/76785231

2017年08月06日 19:19:26 閱讀數:1058

一、new 操作符

JavaScript 中 new 的機制實際上和面向類的語言完全不同。

在 JavaScript 中,構造函數只是一些使用 new 操作符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。實際上,它們甚至都不能說是一種特殊的函數類型,它們只是被 new 操作符調用的普通函數而已。

使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操作。
1. 創建(或者說構造)一個全新的對象。
2. 這個新對象會被執行 [[ 原型 ]] ([[Prototype]])連接。
3. 這個新對象會綁定到函數調用的 this 。
4. 如果函數沒有返回其他對象,那麽 new 表達式中的函數調用會自動返回這個新對象。

二、Object.create

調用Object.create(..) 會憑空創建一個“新”對象並把新對象內部的 [[Prototype]] 關聯到你指定的對象。

分別使用這兩個方法進行原型繼承,

進行一個小試驗:

function A(id,name){
this.id=5;
this.name=name;
this.sex=‘nan‘;

this.colors=[‘red‘,‘blue‘];
}
A.prototype.speak=function(){
console.log(this.id);
}
function B(id,name){

A.call(this,id,name);
this.id=id;
this.name=name;
}
//B.prototype=Object.create(A.prototype);
B.prototype=new A();//使用new會調用一次A的構造函數,在結合使用構造函數技術時就會調用2次構造,這是第一次
var test1=new B(3,‘test1‘);
var test2=new B(4,‘test2‘);
test1.colors.push(‘black‘);
console.log(test1.colors); //["red", "blue", "black"]
console.log(test2.colors); //["red", "blue", "black"]
console.log(test1.sex);//使用Object.create() 結果為undefined;使用new時結果為‘nan’。
//test1.speak();

如上代碼所示:當對象B的prototype通過new A()方式進行原型關聯時,會產生一些副作用。第一:這裏給A的this添加數據屬性B也能通過[[Prototype]]鏈查找訪問到此屬性,因此此方法修改函數A會直接影響其後代(這裏為函數B),後果不堪設想。

第二:使用原型鏈實現繼承時,原型的所有屬性和方法被實例共享,這種共享對函數非常合適,對於基本類型值的屬性實例可以覆蓋原型,但是對於包含引用類型值的屬性來說,每個實例的修改都會改變原型的屬性,因為引用類型的屬性包含的是一個指向引用對象的指針(如數組類型)。因此,這裏通過原型繼承後,原型實際上會變成另一個類型的實例。於是,原先的實例屬性也就順理成章得變成了現在的原型屬性。(如B的實例對象中的colors屬性)。這個副作用使用Object.create()方法也存在。

可以使用借用構造函數技術解決這個問題。在B類型的構造函數第一行寫上A.call(this,id,name);原理是:讓B的實例對擁有引用類型值的屬性,修改B實例時就不會修改它原型上的屬性。

第三:在創建子類型的實例時,無法向超類型的構造函數中傳遞參數。(實際上,是沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數傳遞參數)。
原因分析: 參看new 操作過程的第3步,new會將這個新對象會綁定到函數調用的 this (相當於java語言的實例化對象,對象擁有了類A的實例屬性)。所以當執行B.prototype=new A(); B.prototype就擁有了id,name和sex屬性。而對象test1並沒有,訪問test1.sex時會通過原型鏈找到sex屬性。

new操作和create()操作前後B.prototype的變化具體如下圖所示。圖左為B默認的prototype對象,圖中為new操作後B

的prototype對象,圖右為create()操作後B點prototype對象。從中可以看出共同點:
1、B都沒有B.prototype.constructor屬性,(其實.constructor屬性只是在B函數聲明時默認的一個公有並且不可枚舉

屬性,如果創建了一個新對象並替換了函數默認的 .prototype 對象引用,那麽新對象並不會自動獲得 .constructor 屬

性。)
2、B的原型鏈上都繼承了A的原型,關聯了A的原型方法。

技術分享圖片技術分享圖片技術分享圖片

再來看看test1對象,圖左為new操作後的test1對象原型鏈,圖右為Object.create()操作後的原型鏈。可以清楚的知道為什麽前者test1.sex能打印出‘nan‘,而後者為undefined。

技術分享圖片技術分享圖片

另外,關聯原型還有1種常見的錯誤做法
B.prototype = A.prototype;//和你想要的機制不一樣,此時並不會創建一個關聯到A.prototype的新對象,它只是讓

B.prototype直接引用了A.prototype對象。因此當你直接執行類似"B.prototype.sex=...."的賦值語句時會直接修改

A.prototype對象本身。

因此,要創建一個合適的關聯對象,最好的方法是Object.create(..),而不是有副作用的new A()。這樣做唯一的缺點

就是需要創建一個新對象然後把舊對象拋棄掉(有性能損失:拋棄的對象需要進行垃圾回收),不能直接修改已有的

默認對象。

這只是ES6之前最好的辦法,ES6 添加了輔助函數Object.setPrototypeOf(..),可以用標準並且可靠的方法來修改關聯。

如下圖所示,此方法不必拋棄默認的 B.prototype,對其進行直接修改。

技術分享圖片

ES6 class 繼承方式

[javascript] view plain copy
  1. class C {
  2. constructor(name, id) {
  3. this.name = name;
  4. this.id = id;
  5. }
  6. say() {
  7. console.log(this.name)
  8. }
  9. }
  10. class D extends C {
  11. constructor(name, id, age) {
  12. super(name, id); // 這裏必須先調用super方法
  13. this.age = age;
  14. }
  15. }
  16. let obD = new D(‘mht‘, 22, 33)
  17. obD.say()
版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/mht1829/article/details/76785231 個人分類: javascript

js中的new操作符與Object.create()的作用與區別