你不知道的javascript之Object.create 和new區別
前幾天有碰到原型的問題。之前以為自己對原型還是有所瞭解,但是細細研究,發現自己對原型的理解還是太年輕了。
Object.create 和new
建立物件的方式,我以我碰到的兩種建立方式,Object.create 和new來說明
var Base = function () {}
var o1 = new Base();
var o2 = Object.create(Base);
那這樣到底有什麼不一樣呢?
我先來一段Object.create的實現方式
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
可以看出來。Object.create是內部定義一個物件,並且讓F.prototype物件 賦值為引進的物件/函式 o,並return出一個新的物件。
再看看var o1 = new Base()的時候new做了什麼。
JavaScript 實際上執行的是:
var o1 = new Object();
o1.[[Prototype]] = Base.prototype;
Base.call(o1);
new做法是新建一個obj物件o1,並且讓o1的__proto__
指向了Base.prototype物件。並且使用call 進行強轉作用環境。從而實現了例項的建立。
我們來看看兩個物件列印情況。
看似是一樣的。
我們對原來的程式碼進行改進一下。
var Base = function () {
this.a = 2
}
var o1 = new Base();
var o2 = Object.create(Base);
console.log(o1.a);
console.log(o2.a);
可以看到Object.create 失去了原來物件的屬性的訪問。
那再看看prototype呢?(一開始沒理解prototype和__proto__
的關係。造成對這兩種方式的建立理解非常費解)。
再一次對程式碼進行改進。
var Base = function () {
this.a = 2
}
Base.prototype.a = 3;
var o1 = new Base();
var o2 = Object.create(Base);
console.log(o1.a);
console.log(o2.a);
我一開始以為輸出的值是2,3。。。以為prototype還是存在的。。結果發現真的發錯特錯。我們看執行的結果。
依舊是如此。
那我們就以圖說話。
(F在建立後被銷燬)
看完上圖,我們就知道了,為什麼通過Object.create構造的連Base原型上的屬性都訪問不到,因為他壓根就沒有指向他的prototype。這也就說明了__proto__
和 prototype
的區別。所以上面在prototype定義的a,只是Base的prototype物件上的一個屬性。
再來看看就是:
- new關鍵字必須是以function定義的。
- Object.create 則 function和object都可以進行構建。
小結
比較 | new | Object.create |
---|---|---|
建構函式 | 保留原建構函式屬性 | 丟失原建構函式屬性 |
原型鏈 | 原建構函式prototype屬性 | 原建構函式/(物件)本身 |
作用物件 | function | function和object |
instanceof 和 isPrototypeOf
寫了建立一個物件例項,並且說了通過原型鏈來完成這一個個物件之間的聯絡,但是你怎麼知道就一定含有呢?所以我們需要一個判斷機制。
function Foo(){
//...
}
Foo.prototype.ff = 2;
var a = new Foo();
a instanceof Foo; //true
instanceof 說的是在a的整條[[Prototype]] 是否含有Foo.prototype物件。 但是這個方法只能實現物件(a)和函式(帶.prototype引用的Foo),如果你想判斷兩個物件(a 和 b)是否通過[[Prototype]]鏈關聯。只用instanceof就無法實現。
所以這裡用到了isPrototypeOf。
var a = {};
var b = Object.ceate(a);
b.isPrototypeOf(a);//在a的[[Prototype]]是否出現過b來判斷。
來看看isPrototypeOf實現方式。
function isRelatedTo(o1,o2){
function F(){}
F.prototype = o2;
return o1 instanceof F;
}
上述函式通過了構建一個輔助函式F,構建了一個prototype物件。從而達到instanceof比較的條件。
console.log(a.isPrototypeOf(b) === isRelatedTo(b,a));// true
constructor
舉例來說,.constructor是在函式宣告時候的預設屬性。
我們先來看看下面的程式碼。
function Foo(){
}
console.log(Foo.prototype.constructor === Foo);//true
var a = new Foo();
console.log(a.constructor === Foo);//true
看起來a.constructor === Foo 為真,意味著a的確有一個.constructor指向Foo的.constructor屬性。
但是可能出於不理解,或者很多的誤操作,都會導致我們.constructor指向的丟失。如下:
function Foo(){
}
Foo.prototype = {}
var a1 = new Foo();
console.log(a1.constructor === Foo);//false
console.log(a1.constructor === Object);//true
可以看到a1並沒有.constructor屬性。那是為什麼呢。?因為a1沒有.constructor屬性,他會委託[[prototype]]鏈上的Foo.prototype。但是新建的Foo.prototype也沒有.constructor,所以繼續往上找,一直到了頂端的Object.prototype。
再來,為了絕對的保證我的程式碼可靠,不被一些錯誤操作,影響我們的執行。
function Foo(){
}
Foo.prototype = {}
var a1 = new Foo();
Object.defineProperty(Foo.prototype, "constructor", {
enumerable: false,
writeable:true,
configureable: true,
value: Foo // 讓.constructor指向Foo
})
想要說明的就是一點對於.constructor,我們並不能完全信任,稍不留神,一個手誤或者不懂原理就去改物件。會發生慘烈的指向錯誤,所以認為constructor的意思是“由…構造”,這個誤解代價太高了。
所以可以看出.constructor是一個非常不可靠,並且不安全的引用。在開發中儘量避免使用這些引用。如果用了,請記得檢查你的原型,避免出現.constructor丟失。