JavaScript原型系列(一)構造函數、原型和原型鏈

大方無隅,大器晚成,大音希聲,大象無形。——《道德經》javascript

JavaScript原型系列(一)構造函數、原型和原型鏈html

JavaScript原型系列(二)什麼是原型繼承java

JavaScript原型系列(三)Function、Object、Null等等的關係和雞蛋問題瀏覽器

簡介

首先要了解幾個屬性constructorprototype[[prototype]]__proto__分別做用是什麼,還要理解幾個概念原型原型鏈構造函數函數

結合代碼先把上面的的屬性和記錄清楚。post

構造函數

constrcutor是一種用於建立和初始化class建立的對象的特殊方法。 構造函數自己就是一個函數,與普通函數沒有任何區別,不過爲了規範通常將其首字母大寫。構造函數普通函數的區別在於,使用 new 生成實例的函數就是構造函數,直接調用的就是普通函數。下面示例代碼:性能

function ConstructorFun (name) {
        this.name = name;
    }
    // 經過new關鍵字建立實例
    let constructorfun = new ConstructorFun();
複製代碼

其實ConstructorFun就是一個普通函數,可是在經過new關鍵字生成實例的時候,就能夠把這個函數叫作構造函數;ui

constructor

除了nullundefined其餘不管是經過new生成的實例,仍是經過字面量生成的變量,普通的函數都是有constructor屬性的。this

代碼以下:spa

function ConstructorFun (name) {
        this.name = name;
    }
    // 經過new關鍵字建立實例
    var constructorfun = new ConstructorFun();
    constructorfun.constructor === ConstructorFun; // true
    var number = 111;
    console.log(number.constructor); // ƒ Number() { [native code] }
複製代碼

構造函數擴展

  • let a = {} 實際上是 let a = new Object() 的語法糖
  • let a = [] 實際上是 let a = new Array() 的語法糖
  • function Foo(){ … } 實際上是 var Foo = new Function(…)
  • 可使用 instanceof 判斷一個函數是否爲一個變量的構造函數

手動實現一個instanceof函數以下:

// 模擬實現instanceof
    function selfInstanceof (left, right) {  //left 表示左表達式,right 表示右表達式
        let cur = left.__proto__; // 取的cur的隱式原型
        let parent = right.prototype; // 取的right的顯式原型
        while(true) {
            if (cur === null) { // 若是cur爲null 直接返回false
                return false;
            }
            if (cur === parent) { // 若是cur與parent相同 返回true
                return true;
            }
            cur = cur.__proto__; // 上面兩個條件都不知足,繼續向上一層原型鏈查找
        }
    }
複製代碼

constructor 的值是否可更改

對於引用類型來講constructor 屬性值是能夠修改的,可是對於基本類型來講是隻讀的。

注意:nullundefined 是沒有 constructor 屬性的。

原型

官方解釋原型:"JavaScript常被描述爲一種基於原型的語言(prototype-based language)————每一個對象擁有一個原型對象,對象以其原型爲模板、從原型繼承方法和屬性。" 每一個函數都有一個特殊的屬性就叫做原型(prototype),請看下面代碼:

function Foo () {}
    console.log(Foo.prototype);
複製代碼

效果以下圖所示:

JavaScript-prototype
Foo.prototype上有兩個屬性,一個是 constructor它指向了函數自己;另外一個是 __proto__它指向了 Object.prototype

構造函數Foo有一個指向原型的指針,原型Foo.prototype有一個指向構造函數的指針Foo.prototype.constructor,用下面的圖來表示更清晰一點:

JavaScript-prototype

其實更重要的是任何一個prototype對象都有一個constructor屬性,指向這個構造函數。

proto

在上面看到__proto__這個屬性,每一個實例對象(object)都有一個隱式原型屬性(稱之爲__proto__)指向了建立該對象的構造函數的原型

function Foo (name) { this.name = name; }
    var foo = new Foo();
複製代碼

效果圖以下:

JavaScript-prototype
當經過 new Foo()生成的實例對象 foo,它有一個 __proto__屬性指向 Foo.prototype,能夠經過如下代碼驗證:

foo.__proto__ === Foo.prototype; // true
複製代碼

FooFoo.prototypeFoo.prototype.constructorfoo.__proto__三者的關係以下圖所示:

JavaScript-prototype
__proto__ 屬性在 ES6 時才被標準化,以確保 Web 瀏覽器的兼容性,可是不推薦使用,除了標準化的緣由以外還有性能問題。爲了更好的支持,推薦使用 Object.getPrototypeOf()

經過改變一個對象的 [[Prototype]] 屬性來改變和繼承屬性會對性能形成很是嚴重的影響,而且性能消耗的時間也不是簡單的花費在 obj.__proto__ = ... 語句上, 它還會影響到全部繼承自該 [[Prototype]] 的對象,若是你關心性能,你就不該該修改一個對象的 [[Prototype]]

若是要讀取或修改對象的 [[Prototype]] 屬性,建議使用以下方案,可是此時設置對象的 [[Prototype]] 依舊是一個緩慢的操做,若是性能是一個問題,就要避免這種操做。

// 獲取
    Object.getPrototypeOf()
    Reflect.getPrototypeOf()

    // 修改
    Object.setPrototypeOf()
    Reflect.setPrototypeOf()
複製代碼

若是要建立一個新對象,同時繼承另外一個對象的 [[Prototype]] ,推薦使用 Object.create()

function Parent() {
        age: 50
    };
    var p = new Parent();
    var child = Object.create(p);
複製代碼

[[Prototype]]

[[Prototype]] 是對象的一個內部屬性,外部代碼沒法直接訪問。

遵循 ECMAScript 標準,someObject.[[Prototype]] 符號用於指向 someObject 的原型

原型鏈

每一個對象擁有一個原型對象,經過 __proto__ 指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null。這種關係被稱爲原型鏈 (prototype chain),經過原型鏈一個對象會擁有定義在其餘對象中的屬性和方法。看一面一張經典的圖可能更直觀:

JavaScript-prototype

看一下面的代碼:

function Foo () {}
    var foo = new Foo();
    foo.__proto__ === Foo.prototype; // true
    foo.__proto__.__proto__ === Object.prototype; // true
    foo.__proto__.__proto__.__proto__ === null; // true
複製代碼

下面的圖能夠很好的展現上面的代碼prototype__proto__指向問題。

JavaScript-prototype

特殊的Symbol

Symbol是基礎數據類型,它能夠經過Symbol(123)生成實例,不能經過new Symbol()生成實例,Symbol不是構造函數,可是它有constructor屬性。

let sSymbol = Symbol('symbol');
    let errSymbol = new Symbol('symbol'); // Uncaught TypeError: Symbol is not a constructor

    Symbol.constructor; // ƒ Symbol() { [native code] }
複製代碼

總結

  • 每個函數對象都有一個prototype屬性,指向函數對象的原型,原型對象上有一個constructor屬性指向構造函數自己。
  • 引用類型 constructor 屬性值是能夠修改的,可是對於基本類型來講是只讀的,固然 nullundefined 沒有 constructor 屬性。
  • __proto__ 屬性在 ES6 時被標準化,但由於性能問題並不推薦使用,推薦使用 Object.getPrototypeOf()
  • __proto__ 是每一個實例上都有的屬性,prototype 是構造函數的屬性,在實例上並不存在,因此這兩個並不同,但 foo.__proto__Foo.prototype 指向同一個對象。
  • 每一個對象擁有一個原型對象,經過__proto__指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null,這就是原型鏈。

參考

從新認識構造函數、原型和原型鏈

JS 系列二:原型與原型鏈

對象原型

相關文章
相關標籤/搜索