JS中原型鏈,說簡單也簡單。
首先明確: 函數(shù)(Function)才有prototype屬性啼肩,對象(除Object)擁有__proto__。
首先衙伶,我畫了一張圖祈坠。
所謂原型鏈,指的就是圖中的proto這一條指針鏈矢劲!
原型鏈的頂層就是Object.prototype赦拘,而這個對象的是沒有原型對象的。
可在chrome的控制臺里面輸入:
Object.__proto__
輸出是:
functionEmpty(){}
原型鏈芬沉,如此而已躺同。
對于新人來說,JavaScript的原型是一個很讓人頭疼的事情花嘶,一來prototype容易與__proto__混淆笋籽,二來它們之間的各種指向?qū)嵲谟行?fù)雜,其實市面上已經(jīng)有非常多的文章在嘗試說清楚椭员,有一張所謂很經(jīng)典的圖车海,上面畫了各種線條,一會連接這個一會連接那個,說實話我自己看得就非常頭暈侍芝,更談不上完全理解了研铆。所以我自己也想嘗試一下,看看能不能把原型中的重要知識點拆分出來州叠,用最簡單的圖表形式說清楚棵红。
我們知道原型是一個對象,其他對象可以通過它實現(xiàn)屬性繼承咧栗。但是尼瑪除了prototype逆甜,又有一個__proto__是用來干嘛的?長那么像致板,讓人怎么區(qū)分呢交煞?它們都指向誰,那么混亂怎么記罢寤颉素征?原型鏈又是什么鬼?相信不少初學(xué)者甚至有一定經(jīng)驗的老鳥都不一定能完全說清楚萝挤,下面用三張簡單的圖御毅,配合一些示例代碼來理解一下。
一怜珍、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、構(gòu)造器方式*/
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 {}(即構(gòu)造器function A 的原型對象)
console.log(a.__proto__.__proto__); //Object {}(即構(gòu)造器function Object 的原型對象)
console.log(a.__proto__.__proto__.__proto__); //null
我在寫一篇圖解prototype和__proto__的區(qū)別時欺税,搜資料搜到了一個有意思的現(xiàn)象,下面這兩個運算返回的結(jié)果是一樣的:
Function instanceof Object;//true
Object instanceof Function;//true
這個是怎么一回事呢揭璃?要從運算符instanceof說起晚凿。
一、instanceof究竟是運算什么的瘦馍?
我曾經(jīng)簡單理解instanceof只是檢測一個對象是否是另個對象new出來的實例(例如var a = new Object()歼秽,a instanceof Object返回true),但實際instanceof的運算規(guī)則上比這個更復(fù)雜情组。
首先w3c上有官方解釋(傳送門燥筷,有興趣的同學(xué)可以去看看),但是一如既往地讓人無法一目了然地看懂……
知乎上有同學(xué)把這個解釋翻譯成人能讀懂的語言(傳送門)院崇,看起來似乎明白一些了:
//假設(shè)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檢測左側(cè)的__proto__原型鏈上,是否存在右側(cè)的prototype原型拨扶。
二凳鬓、圖解構(gòu)造器Function和Object的關(guān)系
我們再配合代碼來看一下就明白了:
//①構(gòu)造器Function的構(gòu)造器是它自身
Function.constructor=== Function;//true
//②構(gòu)造器Object的構(gòu)造器是Function(由此可知所有構(gòu)造器的constructor都指向Function)
Object.constructor === Function;//true
//③構(gòu)造器Function的__proto__是一個特殊的匿名函數(shù)function() {}
console.log(Function.__proto__);//function() {}
//④這個特殊的匿名函數(shù)的__proto__指向Object的prototype原型。
Function.__proto__.__proto__ === Object.prototype//true
//⑤Object的__proto__指向Function的prototype患民,也就是上面③中所述的特殊匿名函數(shù)
Object.__proto__ === Function.prototype;//true
Function.prototype === Function.__proto__;//true
三缩举、當(dāng)構(gòu)造器Object和Function遇到instanceof
我們回過頭來看第一部分那個“奇怪的現(xiàn)象”,從上面那個圖中我們可以看到:
Function.__proto__.__proto__ === Object.prototype;//true
Object.__proto__ === Function.prototype;//true
所以再看回第一點中我們說的instanceof的運算規(guī)則匹颤,F(xiàn)unction instanceof Object 和?Object instanceof Function運算的結(jié)果當(dāng)然都是true啦仅孩!
如果看完以上,你還覺得上面的關(guān)系看暈了的話印蓖,只需要記住下面兩個最重要的關(guān)系杠氢,其他關(guān)系就可以推導(dǎo)出來了:
1、所有的構(gòu)造器的constructor都指向Function
2另伍、Function的prototype指向一個特殊匿名函數(shù),而這個特殊匿名函數(shù)的__proto__指向Object.prototype
至于prototype和__proto__的關(guān)系如何推導(dǎo)绞旅,可以參考我寫的上一篇博客《三張圖搞懂JavaScript的原型對象與原型鏈》
本文嘗試闡述Js中原型(prototype)摆尝、原型鏈(prototype chain)等概念及其作用機制。上一篇文章(圖解Javascript上下文與作用域)介紹了Js中變量作用域的相關(guān)概念因悲,實際上關(guān)注的一個核心問題是:“在執(zhí)行當(dāng)前這行代碼時Js解釋器可以獲取哪些變量”堕汞,而原型與原型鏈實際上還是關(guān)于這一問題。
我們知道晃琳,在Js中一切皆為對象(Object)讯检,但是Js中并沒有類(class);Js是基于原型(prototype-based)來實現(xiàn)的面向?qū)ο螅∣OP)的編程范式的卫旱,但并不是所有的對象都擁有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的一個關(guān)鍵詞,用于定義函數(shù)類型的變量适贸,有兩種語法形式:
functionf1(){?
??console.log('This is function f1!');
}
typeof(f1);??//=> 'function'
varf2 =?function(){?
??console.log('This is function f2!');
}
typeof(f2);??//=> 'function'
如果用更加面向?qū)ο蟮姆椒▉矶x函數(shù)灸芳,可以用Function:
varf3 =?newFunction("console.log('This is function f3!');");?
f3();????????//=> 'This is function f3!'?
typeof(f3);??//=> 'function'
typeof(Function);?//=> 'function'
實際上Function就是一個用于構(gòu)造函數(shù)類型變量的類,或者說是函數(shù)類型實例的構(gòu)造函數(shù)(constructor)拜姿;與之相似有的Object或String烙样、Number等,都是Js內(nèi)置類型實例的構(gòu)造函數(shù)蕊肥。比較特殊的是Object谒获,它用于生成對象類型,其簡寫形式為{}:
varo1 =?newObject();?
typeof(o1);??????//=> 'object'
varo2 = {};?
typeof(o2);?????//=> 'object'
typeof(Object);?//=> 'function'
2.prototypeVS__proto__
清楚了上面的概念之后再來看prototype:
Each function has two properties:lengthandprototype
prototype和length是每一個函數(shù)類型自帶的兩個屬性,而其它非函數(shù)類型并沒有(開頭的例子已經(jīng)說明)究反,這一點之所以比較容易被忽略或誤解寻定,是因為所有類型的構(gòu)造函數(shù)本身也是函數(shù),所以它們自帶了prototype屬性:
// Node
console.log(Object.prototype);??//=> {}?
console.log(Function.prototype);//=> [Function: Empty]?
console.log(String.prototype);??//=> [String: '']
除了prototype之外精耐,Js中的所有對象(undefined狼速、null等特殊情況除外)都有一個內(nèi)置的[[Prototype]]屬性,指向它“父類”的prototype卦停,這個內(nèi)置屬性在ECMA標(biāo)準(zhǔn)中并沒有給出明確的獲取方式向胡,但是許多Js的實現(xiàn)(如Node、大部分瀏覽器等)都提供了一個__proto__屬性來指代這一[[Prototype]]惊完,我們通過下面的例子來說明實例中的__proto__是如何指向構(gòu)造函數(shù)的prototype的:
varPerson =?function(){};?
Person.prototype.type =?'Person';?
Person.prototype.maxAge = 100;
varp =?newPerson();?
console.log(p.maxAge);?
p.name =?'rainy';
Person.prototype.constructor === Person;??//=> true?
p.__proto__ === Person.prototype;?????????//=> true?
console.log(p.prototype);?????????????????//=> undefined
上面的代碼示例可以用下圖解釋:
Person是一個函數(shù)類型的變量僵芹,因此自帶了prototype屬性,prototype屬性中的constructor又指向Person本身小槐;通過new關(guān)鍵字生成的Person類的實例p1拇派,通過__proto__屬性指向了Person的原型。這里的__proto__只是為了說明實例p1在內(nèi)部實現(xiàn)的時候與父類之間存在的關(guān)聯(lián)(指向父類的原型)凿跳,在實際操作過程中實例可以直接通過.獲取父類原型中的屬性件豌,從而實現(xiàn)了繼承的功能。
3. 原型鏈
清楚了prototype與__proto__的概念與關(guān)系之后我們會對“Js中一切皆為對象”這句話有更加深刻的理解控嗜。進(jìn)而我們會想到茧彤,既然__proto__是(幾乎)所有對象都內(nèi)置的屬性,而且指向父類的原型疆栏,那是不是意味著我們可以“逆流而上”一直找到源頭呢曾掂?我們來看下面的例子:
// Node
varObj =?function(){};?
varo =?newObj();?
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關(guān)鍵詞的作用就是完成上圖所示實例與父類原型之間關(guān)系的串接,并創(chuàng)建一個新的對象博助;instanceof關(guān)鍵詞的作用也可以從上圖中看出险污,實際上就是判斷__proto__(以及__proto__.__proto__...)所指向是否父類的原型:
varObj =?function(){};?
varo =?newObj();
o?instanceofObj;?//=> true?
o?instanceofObject;?//=> true?
o?instanceofFunction;?//=> false
o.__proto__ === Obj.prototype;?//=> true?
o.__proto__.__proto__ === Object.prototype;?//=> true?
o.__proto__.__proto__ === Function;??//=> false
JS 面向?qū)ο笾玩?/p>
對象的原型鏈
只要是對象就有原型
原型也是對象
只要是對象就有原型, 并且原型也是對象, 因此只要定義了一個對象, 那么就可以找到他的原型, 如此反復(fù), 就可以構(gòu)成一個對象的序列, 這個結(jié)構(gòu)就被成為原型鏈
原型鏈到哪里是一個頭?
一個默認(rèn)的原型鏈結(jié)構(gòu)是什么樣子的?
原型鏈結(jié)構(gòu)對已知語法結(jié)構(gòu)有什么修正?
原型鏈的結(jié)構(gòu)
原型鏈繼承就是利用就是修改原型鏈結(jié)構(gòu)( 增加、刪除富岳、修改節(jié)點中的成員 ), 從而讓實例對象可以使用整個原型鏈中的所有成員( 屬性和方法 )
使用原型鏈繼承必須滿足屬性搜索原則
屬性搜索原則
所謂的屬性搜索原則, 就是對象在訪問屬性與方法的時候, 首先在當(dāng)前對象中查找
如果當(dāng)前對象中存儲在屬性或方法, 停止查找, 直接使用該屬性與方法
如果對象沒有改成員, 那么再其原型對象中查找
如果原型對象含有該成員, 那么停止查找, 直接使用
如果原型還沒有, 就到原型的原型中查找
如此往復(fù), 直到直到 Object.prototype 還沒有, 那么就返回 undefind.
如果是調(diào)用方法就包錯, 該 xxxx 不是一個函數(shù)
原型鏈結(jié)構(gòu)圖
構(gòu)造函數(shù) 對象原型鏈結(jié)構(gòu)圖
function Person (){}; var p = new Person();
{} 對象原型鏈結(jié)構(gòu)圖
[] 數(shù)組原型鏈結(jié)構(gòu)圖
Object.prototype對應(yīng)的構(gòu)造函數(shù)
div 對應(yīng)的構(gòu)造函數(shù)
div -> DivTag.prototype( 就是 o ) -> Object.prototype -> null
var o = {
? ? appendTo: function ( dom ) {
? ? }
};
function DivTag() {}
DivTag.prototype = o;
var div = new DivTag();
函數(shù)的構(gòu)造函數(shù) Function
在 js 中 使用 Function 可以實例化函數(shù)對象. 也就是說在 js 中函數(shù)與普通對象一樣, 也是一個對象類型( 非常特殊 )
函數(shù)是對象, 就可以使用對象的動態(tài)特性
函數(shù)是對象, 就有構(gòu)造函數(shù)創(chuàng)建函數(shù)
函數(shù)是函數(shù), 可以創(chuàng)建其他對象(函數(shù)的構(gòu)造函數(shù)也是函數(shù))
函數(shù)是唯一可以限定變量作用域的結(jié)構(gòu)
函數(shù)是 Function 的實例
new Function( arg0, arg1, arg2, ..., argN, body );
Function 中的參數(shù)全部是字符串
該構(gòu)造函數(shù)的作用是將 參數(shù)鏈接起來組成函數(shù)
如果參數(shù)只有一個, 那么表示函數(shù)體
如果參數(shù)有多個, 那么最后一個參數(shù)表示新函數(shù)體, 前面的所有參數(shù)表示新函數(shù)的參數(shù)
如果沒有參數(shù), 表示創(chuàng)建一個空函數(shù)
創(chuàng)建一個打印一句話的函數(shù)
? ? // 傳統(tǒng)的
? ? function foo () {
? ? ? ? console.log( '你好' );
? ? }
? ? // Function
? ? var func = new Function( 'console.log( "你好" );' );
? ? // 功能上, 這里 foo 與 func 等價
創(chuàng)建一個空函數(shù)
? ? // 傳統(tǒng)
? ? function foo () {}
? ? // Function
? ? var func = new Function();
傳入函數(shù)內(nèi)一個數(shù)字, 打印該數(shù)字
? ? // 傳統(tǒng)
? ? function foo ( num ) {
? ? ? ? console.log( num );
? ? }
? ? // Function
? ? var func = new Function ( "num" ,"console.log( num );" );
? ? func();
利用 Function 創(chuàng)建一個函數(shù), 要求傳入兩個數(shù)字, 打印其和
? ? var func = new Function( 'num1', 'num2', 'console.log( num1 + num2 );' );
練習(xí): 利用 Function 創(chuàng)建一個函數(shù), 要求允許函數(shù)調(diào)用時傳入任意個數(shù)參數(shù), 并且函數(shù)返回這些數(shù)字中最大的數(shù)字.
練習(xí): 利用 Function 創(chuàng)建一個求三個數(shù)中最大數(shù)的函數(shù).
? ? // 傳統(tǒng)
? ? function foo ( a, b, c ) {
? ? ? ? var res = a > b ? a : b;
? ? ? ? res = res > c ? res : c;
? ? ? ? return res;
? ? }
? ? // Function
? ? var func = new Function( 'a', 'b', 'c', 'var res = a > b ? a : b;res = res > c ? res : c;return res;' )
解決代碼太長的辦法:
利用 加法 連接字符串
var func = new Function( 'a', 'b', 'c',
? ? ? ? 'var res = a > b ? a : b;' +
? ? ? ? 'res = res > c ? res : c;' +
? ? ? ? 'return res;' );
利用字符串特性( 剛學(xué) )
function foo ( a, b, c ) {
? ? var res = a > b ? a : b;
? ? res = res > c ? res : c;
? ? return res;
}
var func = new Function( 'a', 'b', 'c', 'return foo( a, b, c );' );
ES6 的語法( 少瀏覽器實現(xiàn) )
使用 鍵盤左上角的 左單引號 表示可換行字符串的界定符
(最終)利用 DOM 的特性完成該方法
arguments 對象
arguments 是一個偽數(shù)組對象. 它表示在函數(shù)調(diào)用的過程中傳入的所有參數(shù)的集合.
在函數(shù)調(diào)用過程中沒有規(guī)定參數(shù)的個數(shù)與類型, 因此函數(shù)調(diào)用就具有靈活的特性, 那么為了方便使用,
在 每一個函數(shù)調(diào)用的過程中, 函數(shù)代碼體內(nèi)有一個默認(rèn)的對象 arguments, 它存儲著實際傳入的所有參數(shù).
js 中函數(shù)并沒有規(guī)定必須如何傳參
定義函數(shù)的時候不寫參數(shù), 一樣可以調(diào)用時傳遞參數(shù)
定義的時候?qū)懥藚?shù), 調(diào)用的時候可以不傳參
定義的時候?qū)懥艘粎?shù), 調(diào)用的時候可以隨意的傳遞多個而參數(shù)
在代碼設(shè)計中, 如果需要函數(shù)帶有任意個參數(shù)的時候, 一般就不帶任何參數(shù), 所有的 參數(shù)利用 arguments 來獲取.
一般的函數(shù)定義語法, 可以寫成:
? ? function foo ( /* ... */ ) {
? ? }
利用 Function 創(chuàng)建一個函數(shù), 要求允許函數(shù)調(diào)用時傳入任意個數(shù)參數(shù), 并且函數(shù)返回這些數(shù)字中最大的數(shù)字.
? ? function foo ( ) {
? ? ? ? // 所有的參數(shù)都在 arguments 中. 將其當(dāng)做數(shù)組使用
? ? ? ? // 問題而已轉(zhuǎn)換成在有一個數(shù)組中求最大值
? ? ? ? var args = arguments;
? ? ? ? var max = args[ 0 ];
? ? ? ? for ( var i = 1; i < args.length; i++ ) {
? ? ? ? ? ? if ( max < args[ i ] ) {
? ? ? ? ? ? ? ? max = args[ i ];
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return max;
? ? }
練習(xí): 利用 Function 寫一個函數(shù), 要求傳入任意個數(shù)字 求和
函數(shù)的原型鏈結(jié)構(gòu)
任意的一個函數(shù), 都是相當(dāng)于 Function 的實例. 類似于 {} 與 new Object() 的關(guān)系
? ? function foo () {};
? ? // 告訴解釋器, 有一個對象叫 foo, 它是一個函數(shù)
? ? // 相當(dāng)于 new Function() 得到一個 函數(shù)對象
函數(shù)有__proto__屬性
函數(shù)的構(gòu)造函數(shù)是 Function
函數(shù)應(yīng)該繼承自Function.prototype
Fucntion.prototype繼承自O(shè)bject.protoype
構(gòu)造函數(shù)有prototype, 實例對象才有__proto__指向原型, 構(gòu)造函數(shù)的原型才有 constructor 指向構(gòu)造函數(shù)
intanceof
array instanceof Array
判斷 構(gòu)造函數(shù) Array 的原型 是否在 實例對象 array 的原型鏈存在