談一談原型和原型鏈

1 寫在前面

JavaScript中除了基本類型外的數據類型,都是對象。可是因爲其沒有 類(class,ES6引入了class,但只是語法糖)的概念,如何將全部對象聯繫起來就成了一個問題,因而就有了原型和原型鏈的概念。瀏覽器

2 爲何會有原型和原型鏈

在這裏插入圖片描述
1994年,網景公司(Netscape)發佈了Navigator瀏覽器0.9版,可是剛開始的Js沒有繼承機制,更別提像同時期興盛的C++和Java這樣擁有面向對象的概念。在實際的開發過程當中,工程師們發現沒有繼承機制很難解決一些問題,必須有一種機制能將全部的對象關聯起來。Brendan Eich鑑於以上狀況,但不想把Js設計得過爲複雜,因而引入了new關鍵詞和constructor構造函數來簡化對象的設計,引入了prototype函數對象來包含全部實例對象的構造函數的屬性和方法,引入了proto和原型鏈的概念解決繼承的問題。app

3 相關名詞概念

原型

  • JavaScript的全部對象中都包含了一個 [proto] 內部屬性,這個屬性所對應的就是該對象的原型
  • JavaScript的函數對象,除了原型 [proto] 以外,還預置了 prototype 屬性
  • 當函數對象做爲構造函數建立實例時,該 prototype 屬性值將被做爲實例對象的原型 [proto]。

原型鏈

  • 當一個對象調用的屬性/方法自身不存在時,就會去本身 [proto] 關聯的前輩 prototype 對象上去找
  • 若是沒找到,就會去該 prototype 原型 [proto] 關聯的前輩 prototype 去找。依次類推,直到找到屬性/方法或 undefined 爲止。從而造成了所謂的「原型鏈」

也就是說

JavaScript中的對象,都有一個內置屬性[[Prototype]],指向這個對象的原型對象。當查找一個屬性或方法時,若是在當前對象中找不到定義,會繼續在當前對象的原型對象中查找;若是原型對象中依然沒有找到,會繼續在原型對象的原型中查找(原型也是對象,也有它本身的原型);如此繼續,直到找到爲止,或者查找到最頂層的原型對象中也沒有找到,就結束查找,返回undefined。能夠看出,這個查找過程是一個鏈式的查找,每一個對象都有一個到它自身原型對象的連接,這些連接組件的整個鏈條就是原型鏈。擁有相同原型的多個對象,他們的共同特徵正是經過這種查找模式體現出來的。
在上面的查找過程,咱們提到了最頂層的原型對象,這個對象就是Object.prototype,這個對象中保存了最經常使用的方法,如toString、valueOf、hasOwnProperty等,所以咱們才能在任何對象中使用這些方法。函數

4 建立對象的方式

一、字面量方式

當經過字面量方式建立對象時,它的原型就是Object.prototype。雖然咱們沒法直接訪問內置屬性[[Prototype]],但咱們能夠經過Object.getPrototypeOf()或對象的__proto__獲取對象的原型。this

var obj = {};
Object.getPrototypeOf(obj) === Object.prototype;   // true
obj.__proto__  === Object.prototype;            // true

二、構造函數的調用

經過函數的構造調用(注意,咱們不把它叫作構造函數,由於JavaScript中一樣沒有構造函數的概念,全部的函數都是平等的,只不過用來建立對象時,函數的調用方式不一樣而已)也是一種經常使用的建立對象的方式。基於同一個函數建立出來的對象,理應能夠共享一些相同的屬性或方法,但這些屬性或方法若是放在Object.prototype裏,那麼全部的對象均可以使用它們了,做用域太大,顯然不合適。因而,JavaScript在定義一個函數時,同時爲這個函數定義了一個 默認的prototype屬性,全部共享的屬性或方法,都放到這個屬性所指向的對象中。由此看出,經過一個函數的構造調用建立的對象,它的原型就是這個函數的prototype指向的對象。spa

var f = function(name) { this.name = name };
f.prototype.getName = function() { return this.name; }   //在prototype下存放全部對象的共享方法
var obj = new f('JavaScript');
obj.getName();                  // JavaScript
obj.__proto__ === f.prototype;  // true

三、Object.create()

第三種經常使用的建立對象的方式是使用Object.create()。這個方法會以你傳入的對象做爲建立出來的對象的原型。prototype

var obj = {};
var obj2 = Object.create(obj);
obj2.__proto__ === obj;       // true

這種方式還能夠模擬對象的「繼承」行爲。設計

function Foo(name) {
    this.name = name;
}

Foo.prototype.myName = function() {
    return this.name;
};

function Bar(name,label) {
    Foo.call( this, name );   //
    this.label = label;
}

// temp對象的原型是Foo.prototype
var temp = Object.create( Foo.prototype );  

// 經過new Bar() 建立的對象,其原型是temp, 而temp的原型是Foo.prototype,
// 從而兩個原型對象Bar.prototype和Foo.prototype 有了"繼承"關係
Bar.prototype = temp;

Bar.prototype.myLabel = function() {
    return this.label;
};

var a = new Bar( "a", "obj a" );

a.myName(); // "a"
a.myLabel(); // "obj a"
a.__proto__.__proto__ === Foo.prototype;  //true

5 __proto__和prototype

這是容易混淆的兩個屬性。__proto__指向當前對象的原型,prototype是函數才具備的屬性,默認狀況下,new 一個函數建立出的對象,其原型都指向這個函數的prototype屬性。
在這裏插入圖片描述3d

每一個實例對象(object )都有一個私有屬性(稱之爲 __proto__)指向它的原型對象(prototype)。該原型對象也有一個本身的原型對象 ,層層向上直到一個對象的原型對象爲 null。根據定義,null 沒有原型,並做爲這個原型鏈中的最後一個環節。code

在這裏插入圖片描述

6 Javascript如何實現繼承?

1.構造函數綁定:使用 call 或 apply 方法,將父對象的構造函數綁定在子對象上對象

function Cat(name,color){
  Animal.apply(this, arguments);
  this.name = name;
  this.color = color;
}
  1. 實例繼承:將子對象的 prototype 指向父對象的一個實例
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
  1. 拷貝繼承:若是把父對象的全部屬性和方法,拷貝進子對象
function extend(Child, Parent) {
     var p = Parent.prototype;
     var c = Child.prototype;
     for (var i in p) {
        c[i] = p[i];
     }
     c.uber = p;
    }
  1. 原型繼承:將子對象的 prototype 指向父對象的 prototype
function extend(Child, Parent) {
        var F = function(){};
       F.prototype = Parent.prototype;
       Child.prototype = new F();
       Child.prototype.constructor = Child;
       Child.uber = Parent.prototype;
    }
  1. ES6 語法糖 extends:class ColorPoint extends Point {}
class ColorPoint extends Point {
       constructor(x, y, color) {
          super(x, y); // 調用父類的constructor(x, y)
          this.color = color;
       }
       toString() {
          return this.color + ' ' + super.toString(); // 調用父類的toString()
       }
    }

若有侵權,請聯繫刪除

相關文章
相關標籤/搜索