若是說JavaScript是一本武學典籍,那麼原型鏈就是九陽神功。在金庸的武俠小說裏面,對九陽神功是這樣描述的:
"練成「九陽神功」後,會易筋洗髓;生出氤氳紫氣;內力自生速度奇快,無窮無盡,普通拳腳也能使出絕大攻擊力;防護力無可匹敵,自動護體,反彈外力攻擊,成就金剛不壞之軀;習者速度將受到極大加成;更是療傷聖典,百病不生,諸毒不侵。至陽熱氣全力施展可將人焚爲焦炭,專門克破全部寒性和陰毒內力。"可見其功法強大。
那麼,如何修煉好js中的九陽神功呢?真正的功法大成的技術是從底層上去理解,那種工程師和碼農的區別就在於對底層的理解,當你寫完一行代碼,或者你碰見一個bug,解決的速度取決於你對底層的理解。什麼是底層?我目前我的的理解是「在你寫每一行代碼的時候,它將如何在相應的虛擬機或者V8引擎中是如何運行的,更厲害的程序員甚至知道每條數據的操做是在堆裏面仍是在棧裏面,都作到了然於胸,這是JavaScript的內功最高境界(反正我如今是作不到,我不知道大家能不能,哈哈哈)」。程序員
**理解原型鏈以前首先要了解js的基本類型和引用類型:
一、基本類型
基本類型有Undefined、Null、Boolean、Number 和String。這些類型在內存中分別佔有固定大小的空間,他們的值保存在棧空間,
咱們經過按值來訪問的。
基本類型:簡單的數據段,存放在棧內存中,佔據固定大小的空間。
二、引用類型
引用類型,值大小不固定,棧內存中存放地址指向堆內存中的對象。是按引用訪問的。
存放在堆內存中的對象,變量實際保存的是一個指針,這個指針指向另外一個位置。每一個空間大小不同,要根據狀況開進行特定的分配。
當咱們須要訪問引用類型(如對象,數組,函數等)的值時,首先從棧中得到該對象的地址指針,而後再從堆內存中取得所需的數據。**web
js的原型鏈說簡單也簡單,說難也難。編程
首先說明:函數(Function)纔有prototype屬性,對象(除了Object)擁有_proto_.
原型鏈的頂層就是Object.prototype,而這個對象的是沒有原型對象的。
能夠在Chrome輸入:數組
Object.__proto__
輸出的是:瀏覽器
ƒ () { [native code] }
能夠看到這個沒有.prototype屬性。函數
咱們知道原型是一個對象,其餘對象能夠經過它實現屬性繼承。spa
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() {}
/*一、字面量方式*/ 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中的例外狀況)
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只是檢測一個對象是不是另個對象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的構造器是它自身 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
Function.__proto__.__proto__ === Object.prototype;//true Object.__proto__ === Function.prototype;//true
若是看完以上,你還以爲上面的關係看暈了的話,只須要記住下面兩個最重要的關係,其餘關係就能夠推導出來了:
一、全部的構造器的constructor都指向Function
二、Function的prototype指向一個特殊匿名函數,而這個特殊匿名函數的__proto__指向Object.prototype
咱們知道,在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和length是每個函數類型自帶的兩個屬性,而其它非函數類型並無(開頭的例子已經說明),這一點之因此比較容易被忽略或誤解,是由於全部類型的構造函數自己也是函數,因此它們自帶了prototype屬性:
除了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
圖示解釋上面的代碼:
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
從上面的例子和圖解能夠看出,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();
2.{} 對象原型鏈結構圖
3.數組的原型鏈結構圖
4.Object.prototype對應的構造函數
總結:
從本質上理解:對象和函數都是保存在堆當中的引用類型,後面一系列的操做都是爲了使用或者訪問其屬性,那麼不管是prototype仍是_proto_都是函數或者Object自帶的指針,容許外界的其餘一些函數或者Object去使用本身的一些屬性。
更多的文章請關注公衆號:碼客小棧,天天不定時的更新web好文