js內功修煉之九陽神功--原型鏈

寫在前面

若是說JavaScript是一本武學典籍,那麼原型鏈就是九陽神功。在金庸的武俠小說裏面,對九陽神功是這樣描述的:
"練成「九陽神功」後,會易筋洗髓;生出氤氳紫氣;內力自生速度奇快,無窮無盡,普通拳腳也能使出絕大攻擊力;防護力無可匹敵,自動護體,反彈外力攻擊,成就金剛不壞之軀;習者速度將受到極大加成;更是療傷聖典,百病不生,諸毒不侵。至陽熱氣全力施展可將人焚爲焦炭,專門克破全部寒性和陰毒內力。"可見其功法強大。
那麼,如何修煉好js中的九陽神功呢?真正的功法大成的技術是從底層上去理解,那種工程師和碼農的區別就在於對底層的理解,當你寫完一行代碼,或者你碰見一個bug,解決的速度取決於你對底層的理解。什麼是底層?我目前我的的理解是「在你寫每一行代碼的時候,它將如何在相應的虛擬機或者V8引擎中是如何運行的,更厲害的程序員甚至知道每條數據的操做是在堆裏面仍是在棧裏面,都作到了然於胸,這是JavaScript的內功最高境界(反正我如今是作不到,我不知道大家能不能,哈哈哈)」。程序員


1、Js原型鏈的簡單理解

**理解原型鏈以前首先要了解js的基本類型和引用類型:
一、基本類型
基本類型有Undefined、Null、Boolean、Number 和String。這些類型在內存中分別佔有固定大小的空間,他們的值保存在棧空間,
咱們經過按值來訪問的。
基本類型:簡單的數據段,存放在棧內存中,佔據固定大小的空間。
二、引用類型
引用類型,值大小不固定,棧內存中存放地址指向堆內存中的對象。是按引用訪問的
存放在堆內存中的對象,變量實際保存的是一個指針,這個指針指向另外一個位置。每一個空間大小不同,要根據狀況開進行特定的分配。
當咱們須要訪問引用類型(如對象,數組,函數等)的值時,首先從棧中得到該對象的地址指針,而後再從堆內存中取得所需的數據。**web

js的原型鏈說簡單也簡單,說難也難。編程

首先說明:函數(Function)纔有prototype屬性,對象(除了Object)擁有_proto_.
原型鏈的頂層就是Object.prototype,而這個對象的是沒有原型對象的。
能夠在Chrome輸入:數組

Object.__proto__

輸出的是:瀏覽器

ƒ () { [native code] }

能夠看到這個沒有.prototype屬性。函數

2、prototype和_proto_的區別

咱們知道原型是一個對象,其餘對象能夠經過它實現屬性繼承。spa

clipboard.png

var a = {};
console.log(a.prototype);  //undefined
console.log(a.__proto__);  //Object {}

var b = function(){}
console.log(b.prototype);  //b {}
console.log(b.__proto__);  //function() {}

clipboard.png

/*一、字面量方式*/
var a = {};
console.log(a.__proto__);  //Object {}

console.log(a.__proto__ === a.constructor.prototype); //true

/*二、構造器方式*/
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}

console.log(a.__proto__ === a.constructor.prototype); //true

/*三、Object.create()方式*/
var a1 = {a:1}
var a2 = Object.create(a1);
console.log(a2.__proto__); //Object {a: 1}

console.log(a.__proto__ === a.constructor.prototype); //false(此處即爲圖1中的例外狀況)

clipboard.png

var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}(即構造器function A 的原型對象)
console.log(a.__proto__.__proto__); //Object {}(即構造器function Object 的原型對象)
console.log(a.__proto__.__proto__.__proto__); //null

instanceof到底是運算什麼的?

我曾經簡單理解instanceof只是檢測一個對象是不是另個對象new出來的實例(例如var a = new Object(),a instanceof Object返回true),但實際instanceof的運算規則上比這個更復雜。prototype

//假設instanceof運算符左邊是L,右邊是R
L instanceof R 
//instanceof運算時,經過判斷L的原型鏈上是否存在R.prototype
L.__proto__.__proto__ ..... === R.prototype ?

//若是存在返回true 不然返回false
注意:instanceof運算時會遞歸查找L的原型鏈,即L.__proto__.__proto__.__proto__.__proto__...直到找到了或者找到頂層爲止。指針

因此一句話理解instanceof的運算規則爲:code

instanceof檢測左側的__proto__原型鏈上,是否存在右側的prototype原型。

圖解構造器Function和Object的關係

clipboard.png

咱們再配合代碼來看一下就明白了:

//①構造器Function的構造器是它自身
Function.constructor=== Function;//true

//②構造器Object的構造器是Function(由此可知全部構造器的constructor都指向Function)
Object.constructor === Function;//true



//③構造器Function的__proto__是一個特殊的匿名函數function() {}
console.log(Function.__proto__);//function() {}

//④這個特殊的匿名函數的__proto__指向Object的prototype原型。
Function.__proto__.__proto__ === Object.prototype//true

//⑤Object的__proto__指向Function的prototype,也就是上面③中所述的特殊匿名函數
Object.__proto__ === Function.prototype;//true
Function.prototype === Function.__proto__;//true

當構造器Object和Function遇到instanceof

Function.__proto__.__proto__ === Object.prototype;//true
Object.__proto__ === Function.prototype;//true

若是看完以上,你還以爲上面的關係看暈了的話,只須要記住下面兩個最重要的關係,其餘關係就能夠推導出來了:

一、全部的構造器的constructor都指向Function

二、Function的prototype指向一個特殊匿名函數,而這個特殊匿名函數的__proto__指向Object.prototype

function、Function、Object和{}

咱們知道,在Js中一切皆爲對象(Object),可是Js中並無類(class);Js是基於原型(prototype-based)來實現的面向對象(OOP)的編程範式的,但並非全部的對象都擁有prototype這一屬性:

var a = {}; 
console.log(a.prototype);  //=> undefined
 
var b = function(){}; 
console.log(b.prototype);  //=> {}
 
var c = 'Hello'; 
console.log(c.prototype);  //=> undefined

prototype是每一個function定義時自帶的屬性,可是Js中function自己也是對象,咱們先來看一下下面幾個概念的差異:
function是Js的一個關鍵詞,用於定義函數類型的變量,有兩種語法形式:

function f1(){ 
  console.log('This is function f1!');
}
typeof(f1);  //=> 'function'
 
var f2 = function(){ 
  console.log('This is function f2!');
}
typeof(f2);  //=> 'function'

若是用更加面向對象的方法來定義函數,能夠用Function:

var f3 = new Function("console.log('This is function f3!');"); 
f3();        //=> 'This is function f3!' 
typeof(f3);  //=> 'function'
 
typeof(Function); //=> 'function'

實際上Function就是一個用於構造函數類型變量的類,或者說是函數類型實例的構造函數(constructor);與之類似有的Object或String、Number等,都是Js內置類型實例的構造函數。比較特殊的是Object,它用於生成對象類型,其簡寫形式爲{}:

var o1 = new Object(); 
typeof(o1);      //=> 'object'
 
var o2 = {}; 
typeof(o2);     //=> 'object'
 
typeof(Object); //=> 'function'

prototype VS_proto_

prototype和length是每個函數類型自帶的兩個屬性,而其它非函數類型並無(開頭的例子已經說明),這一點之因此比較容易被忽略或誤解,是由於全部類型的構造函數自己也是函數,因此它們自帶了prototype屬性:

clipboard.png

除了prototype以外,Js中的全部對象(undefined、null等特殊狀況除外)都有一個內置的[[Prototype]]屬性,指向它「父類」的prototype,這個內置屬性在ECMA標準中並無給出明確的獲取方式,可是許多Js的實現(如Node、大部分瀏覽器等)都提供了一個__proto__屬性來指代這一[[Prototype]],咱們經過下面的例子來講明實例中的__proto__是如何指向構造函數的prototype的:

var Person = function(){}; 
Person.prototype.type = 'Person'; 
Person.prototype.maxAge = 100;
 
var p = new Person(); 
console.log(p.maxAge); 
p.name = 'rainy';
 
Person.prototype.constructor === Person;  //=> true 
p.__proto__ === Person.prototype;         //=> true 
console.log(p.prototype);                 //=> undefined

圖示解釋上面的代碼:

clipboard.png

Person是一個函數類型的變量,所以自帶了prototype屬性,prototype屬性中的constructor又指向Person自己;經過new關鍵字生成的Person類的實例p1,經過__proto__屬性指向了Person的原型。這裏的__proto__只是爲了說明實例p1在內部實現的時候與父類之間存在的關聯(指向父類的原型),在實際操做過程當中實例能夠直接經過.獲取父類原型中的屬性,從而實現了繼承的功能。

核心圖解

var Obj = function(){}; 
var o = new Obj(); 
o.__proto__ === Obj.prototype;  //=> true 
o.__proto__.constructor === Obj; //=> true
 
Obj.__proto__ === Function.prototype; //=> true 
Obj.__proto__.constructor === Function; //=> true
 
Function.__proto__ === Function.prototype; //=> true 
Object.__proto__ === Object.prototype;     //=> false 
Object.__proto__ === Function.prototype;   //=> true
 
Function.__proto__.constructor === Function;//=> true 
Function.__proto__.__proto__;               //=> {} 
Function.__proto__.__proto__ === o.__proto__.__proto__; //=> true 
o.__proto__.__proto__.__proto__ === null;   //=> true

clipboard.png
從上面的例子和圖解能夠看出,prototype對象也有__proto__屬性,向上追溯一直到null

new關鍵詞的做用就是完成上圖所示實例與父類原型之間關係的串接,並建立一個新的對象;instanceof關鍵詞的做用也能夠從上圖中看出,實際上就是判斷__proto__(以及__proto__.__proto__...)所指向是否父類的原型:

var Obj = function(){}; 
var o = new Obj();
 
o instanceof Obj; //=> true 
o instanceof Object; //=> true 
o instanceof Function; //=> false
 
o.__proto__ === Obj.prototype; //=> true 
o.__proto__.__proto__ === Object.prototype; //=> true 
o.__proto__.__proto__ === Function;  //=> false

原型鏈的結構
1.原型鏈繼承就是利用就是修改原型鏈結構( 增長、刪除、修改節點中的成員 ), 從而讓實例對象可使用整個原型鏈中的全部成員( 屬性和方法 )
2.使用原型鏈繼承必須知足屬性搜索原則

屬性搜索原則
1.構造函數 對象原型鏈結構圖

function Person (){}; var p = new Person();

clipboard.png

2.{} 對象原型鏈結構圖

clipboard.png

3.數組的原型鏈結構圖

clipboard.png

4.Object.prototype對應的構造函數

clipboard.png

總結:
從本質上理解:對象和函數都是保存在堆當中的引用類型,後面一系列的操做都是爲了使用或者訪問其屬性,那麼不管是prototype仍是_proto_都是函數或者Object自帶的指針,容許外界的其餘一些函數或者Object去使用本身的一些屬性。

更多的文章請關注公衆號:碼客小棧,天天不定時的更新web好文

clipboard.png

相關文章
相關標籤/搜索