JS中原型鏈,說簡單也簡單。
首先明確: 函數(Function)才有prototype屬性,對象(除Object)擁有proto。
所謂原型鏈,指的就是圖中的proto這一條指針鏈!
原型鏈的頂層就是Object.prototype,而這個對象的是沒有原型對象的。
可在chrome的控制臺里面輸入:
Object.__proto__
輸出是:
function Empty() {}
原型鏈,如此而已。
對于新人來說,JavaScript的原型是一個很讓人頭疼的事情,一來prototype容易與proto混淆,二來它們之間的各種指向實在有些復雜,其實市面上已經有非常多的文章在嘗試說清楚,有一張所謂很經典的圖,上面畫了各種線條,一會連接這個一會連接那個,說實話我自己看得就非常頭暈,更談不上完全理解了。所以我自己也想嘗試一下,看看能不能把原型中的重要知識點拆分出來,用最簡單的圖表形式說清楚。
我們知道原型是一個對象,其他對象可以通過它實現屬性繼承。但是尼瑪除了prototype,又有一個proto是用來干嘛的?長那么像,讓人怎么區(qū)分呢?它們都指向誰,那么混亂怎么記啊?原型鏈又是什么鬼?相信不少初學者甚至有一定經驗的老鳥都不一定能完全說清楚,下面用三張簡單的圖,配合一些示例代碼來理解一下。
一、prototype和proto的區(qū)別

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() {}

/*1、字面量方式*/
var a = {};
console.log(a.__proto__); //Object {}
console.log(a.__proto__ === a.constructor.prototype); //true
/*2、構造器方式*/
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}
console.log(a.__proto__ === a.constructor.prototype); //true
/*3、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
我在寫一篇圖解prototype和proto的區(qū)別時,搜資料搜到了一個有意思的現象,下面這兩個運算返回的結果是一樣的:
Function instanceof Object;//true
Object instanceof Function;//true
這個是怎么一回事呢?要從運算符instanceof說起。
一、instanceof究竟是運算什么的?
我曾經簡單理解instanceof只是檢測一個對象是否是另個對象new出來的實例(例如var a = new Object(),a instanceof Object返回true),但實際instanceof的運算規(guī)則上比這個更復雜。
首先w3c上有官方解釋,但是一如既往地讓人無法一目了然地看懂……
知乎上有同學把這個解釋翻譯成人能讀懂的語言,看起來似乎明白一些了:
//假設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的運算規(guī)則為:
instanceof****檢測左側的proto原型鏈上,是否存在右側的prototype原型。
二、圖解構造器Function和Object的關系

我們再配合代碼來看一下就明白了:
//①構造器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
所以再看回第一點中我們說的instanceof的運算規(guī)則,Function instanceof Object 和 Object instanceof Function運算的結果當然都是true啦!
如果看完以上,你還覺得上面的關系看暈了的話,只需要記住下面兩個最重要的關系,其他關系就可以推導出來了:
1、所有的構造器的constructor都指向Function
2、Function的prototype指向一個特殊匿名函數,而這個特殊匿名函數的proto指向Object.prototype
本文嘗試闡述Js中原型(prototype)、原型鏈(prototype chain)等概念及其作用機制。上一篇文章([圖解Javascript上下文與作用域]介紹了Js中變量作用域的相關概念,實際上關注的一個核心問題是:“在執(zhí)行當前這行代碼時Js解釋器可以獲取哪些變量”,而原型與原型鏈實際上還是關于這一問題。
我們知道,在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本身也是對象,我們先來看一下下面幾個概念的差別:
1. function、Function、Object和{}
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'
2. prototype VS __proto__
清楚了上面的概念之后再來看prototype:
Each function has two properties:
lengthandprototype
prototype和length是每一個函數類型自帶的兩個屬性,而其它非函數類型并沒有(開頭的例子已經說明),這一點之所以比較容易被忽略或誤解,是因為所有類型的構造函數本身也是函數,所以它們自帶了prototype屬性:
// Node
console.log(Object.prototype); //=> {}
console.log(Function.prototype);//=> [Function: Empty]
console.log(String.prototype); //=> [String: '']
除了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在內部實現的時候與父類之間存在的關聯(指向父類的原型),在實際操作過程中實例可以直接通過.獲取父類原型中的屬性,從而實現了繼承的功能。
3. 原型鏈
清楚了prototype與__proto__的概念與關系之后我們會對“Js中一切皆為對象”這句話有更加深刻的理解。進而我們會想到,既然__proto__是(幾乎)所有對象都內置的屬性,而且指向父類的原型,那是不是意味著我們可以“逆流而上”一直找到源頭呢?我們來看下面的例子:
// Node
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關鍵詞的作用就是完成上圖所示實例與父類原型之間關系的串接,并創(chuàng)建一個新的對象;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
JS 面向對象之原型鏈
對象的原型鏈
- 只要是對象就有原型
- 原型也是對象
- 只要是對象就有原型, 并且原型也是對象, 因此只要定義了一個對象, 那么就可以找到他的原型, 如此反復, 就可以構成一個對象的序列, 這個結構就被成為原型鏈
- 原型鏈到哪里是一個頭?
- 一個默認的原型鏈結構是什么樣子的?
- 原型鏈結構對已知語法結構有什么修正?
原型鏈的結構
- 原型鏈繼承就是利用就是修改原型鏈結構( 增加、刪除、修改節(jié)點中的成員 ), 從而讓實例對象可以使用整個原型鏈中的所有成員( 屬性和方法 )
- 使用原型鏈繼承必須滿足屬性搜索原則
屬性搜索原則
- 所謂的屬性搜索原則, 就是對象在訪問屬性與方法的時候, 首先在當前對象中查找
- 如果當前對象中存儲在屬性或方法, 停止查找, 直接使用該屬性與方法
- 如果對象沒有改成員, 那么再其原型對象中查找
- 如果原型對象含有該成員, 那么停止查找, 直接使用
- 如果原型還沒有, 就到原型的原型中查找
- 如此往復, 直到直到 Object.prototype 還沒有, 那么就返回 undefind.
- 如果是調用方法就包錯, 該 xxxx 不是一個函數
原型鏈結構圖
-
構造函數 對象原型鏈結構圖
function Person (){}; var p = new Person();
image -
{} 對象原型鏈結構圖
image -
[] 數組原型鏈結構圖
image -
Object.prototype對應的構造函數
image div 對應的構造函數
div -> DivTag.prototype( 就是 o ) -> Object.prototype -> null
var o = {
appendTo: function ( dom ) {
}
};
function DivTag() {}
DivTag.prototype = o;
var div = new DivTag();

函數的構造函數 Function
在 js 中 使用 Function 可以實例化函數對象. 也就是說在 js 中函數與普通對象一樣, 也是一個對象類型( 非常特殊 )
- 函數是對象, 就可以使用對象的動態(tài)特性
- 函數是對象, 就有構造函數創(chuàng)建函數
- 函數是函數, 可以創(chuàng)建其他對象(函數的構造函數也是函數)
- 函數是唯一可以限定變量作用域的結構

函數是 Function 的實例
<pre style="margin: 10px 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word;">new Function( arg0, arg1, arg2, ..., argN, body );</pre>
- Function 中的參數全部是字符串
- 該構造函數的作用是將 參數鏈接起來組成函數
- 如果參數只有一個, 那么表示函數體
- 如果參數有多個, 那么最后一個參數表示新函數體, 前面的所有參數表示新函數的參數
- 如果沒有參數, 表示創(chuàng)建一個空函數
arguments 對象
arguments 是一個偽數組對象. 它表示在函數調用的過程中傳入的所有參數的集合.
在函數調用過程中沒有規(guī)定參數的個數與類型, 因此函數調用就具有靈活的特性, 那么為了方便使用,
在 每一個函數調用的過程中, 函數代碼體內有一個默認的對象 arguments, 它存儲著實際傳入的所有參數.
js 中函數并沒有規(guī)定必須如何傳參
- 定義函數的時候不寫參數, 一樣可以調用時傳遞參數
- 定義的時候寫了參數, 調用的時候可以不傳參
- 定義的時候寫了一參數, 調用的時候可以隨意的傳遞多個而參數
在代碼設計中, 如果需要函數帶有任意個參數的時候, 一般就不帶任何參數, 所有的 參數利用 arguments 來獲取.
一般的函數定義語法, 可以寫成:
function foo ( ) {
}
利用 Function 創(chuàng)建一個函數, 要求允許函數調用時傳入任意個數參數, 并且函數返回這些數字中最大的數字.
function foo ( ) {
// 所有的參數都在 arguments 中. 將其當做數組使用
// 問題而已轉換成在有一個數組中求最大值
var args = arguments;
var max = args[ 0 ];
for ( var i = 1; i < args.length; i++ ) {
if ( max < args[ i ] ) {
max = args[ i ];
}
}
return max;
}
練習: 利用 Function 寫一個函數, 要求傳入任意個數字 求和
函數的原型鏈結構
任意的一個函數, 都是相當于 Function 的實例. 類似于 {} 與 new Object() 的關系
function foo () {};
// 告訴解釋器, 有一個對象叫 foo, 它是一個函數
// 相當于 new Function() 得到一個 函數對象</pre>
- 函數有
__proto__屬性 - 函數的構造函數是 Function
- 函數應該繼承自
Function.prototype -
Fucntion.prototype繼承自Object.protoype - 構造函數有prototype, 實例對象才有proto指向原型, 構造函數的原型才有 constructor 指向構造函數
intanceof
array instanceof Array
判斷 構造函數 Array 的原型 是否在 實例對象 array 的原型鏈存在



