1. 程式人生 > >你不知道的javascript之Object.create 和new區別

你不知道的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物件上的一個屬性。

再來看看就是:

  1. new關鍵字必須是以function定義的。
  2. 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丟失。