JavaScript 筆記之原型鏈的深度剖析

先看一下下面的代碼,若是你很簡單就能理解,那麼能夠跳過這一篇文章了。html

var x = new y();

// 一、全部的實例的原型都是由其構造函數的原型對象:
x.__proto__ === y.prototype

// 二、全部的構造函數都是 Function 的實例,有1可知:
y.__proto__ === Function.prototype

// 三、由於構造函數的原型對象也是對象,因此由1可知:
y.prototype.__proto__ === Object.prototype

// 四、Object 是頂級對象,因此它不符合第3條規則:
Object.prototype.__proto__ // is null
複製代碼

沒看明白的咱們繼續。。。函數


想要理解原型鏈,先知原型爲什麼物

先了解一下原型模式吧:原型模式post

在原型模式中咱們能夠利用過一個原型對象來指明咱們所要建立對象的類型,而後經過複製這個對象的方法來得到與該對象如出一轍的對象實例,因此:ui

  • 實例是由原型拷貝而來的。

再其次,咱們都知道,JS中的聲明,實際是 new 了一個構造函數,好比:this

var a = {};

// 實際是
var a = new Object();
複製代碼

可知,a 是一個對象實例,因此 new Object 構造函數作了什麼?spa

  • 構造函數拷貝了相應的對象並返回了這個拷貝,即拷貝原型返回了一個實例。
  • 構造函數存在一個原型對象,這個原型對象就是構造函數拷貝的對象。
  • 構造函數的 prototype 字段所指向它要拷貝的原型對象,即「構造函數.prototype」

構造函數.prototype ---> 以 String 爲例就是 String.prototypeprototype

由上述能夠,實例的原型就是構造函數上的原型對象,而JS中,爲了實現繼承,實例的 __proto__ 字段所指向它的原型,,即「實例.__proto__」。code

實例.__proto__ ---> 以 var a = {} 爲例,就是 a.__proto__htm

構造函數.prototype 指向它要拷貝的原型對象,而 實例.__proto__ 指向它的原型,都指向同一個對象,故:對象

var x = new y();
x.__proto__ === y.prototype
複製代碼

如今應該知道原型是什麼了吧。

函數實例

其實構造函數是一個函數實例,因此能夠知道:

Object.__proto__ === Function.prototype // true
Array.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true

// 特殊的 Function
Function.__proto__ === Function.prototype // true
複製代碼

萬物皆對象

再研究一下,由於函數的 prototype 一個對象實例,因此構造函數的原型對象的原型屬於誰呢? 答案是:Object ,萬物皆對象。

Array.prototype.__proto__ === Object.prototype // true
String.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true

// 物皆對象,因此 Object 的原型對象已是頂級了
Object.prototype.__proto__ // null
複製代碼

因此,這裏已經解釋了最開始的四行代碼。

順帶提一下:constructor

以上就是原型和原型對象的概念,順便提一個原型對象上的屬性:constructor ,它指向創造這個原型對象的函數,即:

// 函數的原型對象的構造函數就是它本身
Array.prototype.constructor === Array // true
String.prototype.constructor === String // true
Function.prototype.constructor === Function // true
Object.prototype.constructor === Object // true

// 實例的原型的構造函數就是聲明的這個實例
var a = [], b = {}, c = '';
a.__proto__.constructor === Array // true
b.__proto__.constructor === Object // true
c.__proto__.constructor === String // true
複製代碼

如今來理解一下原型鏈。

何爲原型鏈

其實上面有代碼已經初現端倪了:

Array.prototype.__proto__ === Object.prototype // true
複製代碼

構造函數的原型對象的原型,是否是有些拗口,這其實就是原型鏈,再來一個看幾個代碼:

var a = [];
a.__proto__ // = Array.prototype
a.__proto__.constructor // = Array
a.__proto__.constructor.prototype // = Array.prototype
複製代碼

有一萬中寫法來顯示一些東西,其實原型鏈的大體概念是:

  • 實例使用屬性和方法會依次檢索自己和其原型,而原型也屬實例,故也會檢索自己和其原型,因此會造成一級一級地向上檢測查找的鏈式結構,這種結構就稱原型鏈。

因此上面的例子能夠這樣簡寫:

var a = [], b = {}, c = '';
a.constructor === Array // true
b.constructor === Object // true
c.constructor === String // true
複製代碼

由於 a 沒有 constructor ,因此會去找原型上的 constructor ,so...

再看一個例子,由於原型鏈的存在,因此能夠在構造函數的原型對象上添加方法,這樣全部由此函數建立的實例均可以調用此方法:

function a(){}
a.prototype.say = function(x){ console.log(x) }

var b = new a();
var c = new a();

b.say(‘hello’); // hello
c.say(‘你好’); // 你好
複製代碼

簡單提一下 ES6 的類的定義

上面的寫法怎麼看會有點奇怪,ES6 新增了 class 的聲明,能夠這樣寫一個類:

// 定義類
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayHello() {
    console.log(`你好,我是${this.name},我今年${this.age}歲`);
  }
}

// 實例
var my = new User('jack', 22);
my.sayHello(); // 你好,我是jack,我今年22歲
複製代碼

對比一下ES5代碼:

// 定義類
function User(name, age){
  this.name = name;
  this.age = age;
}
User.prototype.sayHello = function(){
  console.log(`你好,我是${this.name},我今年${this.age}歲`);
}

// 實例
var my = new User('jack', 22);
my.sayHello(); // 你好,我是jack,我今年22歲
複製代碼

Class 定義其自己是個語法糖,能夠用ES5來實現,具體參考此處

OK,說完了,原型鏈就這一點點。

相關文章
相關標籤/搜索