猶記得第一次寫(xiě)JS的時(shí)候,還是從定義一個(gè)函數(shù)開(kāi)始第喳。
function first() {
....
}
那個(gè)時(shí)候只是把函數(shù)作為一個(gè)組織代碼的方式而已,過(guò)后不久,寫(xiě)網(wǎng)頁(yè)應(yīng)用寞射,jquery占據(jù)了我的絕大部分時(shí)間。寫(xiě)的一手很溜的JQ選擇器引矩,是我自認(rèn)為牛逼的開(kāi)始侵浸。浮華褪盡,裝逼再多掏觉,也慢慢明白需要返璞歸真。因此织盼,近來(lái)開(kāi)始研習(xí)js的基本功酱塔,理解它一些不可思議的魔法沥邻。偶得心得一二羊娃。
0x00 引言
函數(shù)是JS的第一型對(duì)象,對(duì)象在JS中擁有以下特性:
可以通過(guò)字面量創(chuàng)建
賦值給變量邮利,數(shù)組和其他對(duì)象的屬性
作為參數(shù)傳遞函數(shù)
作為函數(shù)的返回值
擁有動(dòng)態(tài)創(chuàng)建并賦值的屬性
也就是說(shuō),function f(){};
其實(shí)是建立了一個(gè)f
的變量缅糟,指向一個(gè)函數(shù)實(shí)體祷愉。
0x01 屬性
函數(shù)也是有屬性的,這一點(diǎn)不要驚訝二鳄。正如上面那個(gè)例子提到的。function f(){};
,f
函數(shù)可以通過(guò)一個(gè)叫name
屬性髓窜,查看函數(shù)的名字:
> function f(){};
<·function f(){}
>f.name
<·"f"
而其中還有一個(gè)特別注明的屬性--prototype
欺殿。在很多JS的面向?qū)ο蟮慕虒W(xué)上面都會(huì)提到這個(gè)給函數(shù)動(dòng)態(tài)增加屬性和方法的函數(shù)屬性。那么這個(gè)屬性是什么呢程拭?
其實(shí)只要簡(jiǎn)單輸出的一下棍潘,你就明白:
> f.prototype
<· object{}
這個(gè)屬性代表的是一個(gè)對(duì)象,而如果你繼續(xù)探究的話亦歉,就要說(shuō)到另一個(gè)屬性constructor
,這個(gè)屬性肴楷,稍微面向?qū)ο缶幊袒A(chǔ)的人,都能明白赛蔫,這個(gè)屬性代表的是構(gòu)造器。這個(gè)屬性是函數(shù)和對(duì)象都有的鞭盟,表示這個(gè)函數(shù)或者對(duì)象的構(gòu)造器是那一個(gè)瑰剃。
如果我們查看prototype
的構(gòu)造器的話,你會(huì)發(fā)現(xiàn):
> f.prototype.constructor
<· function f(){}
其實(shí)就是函數(shù)f
,這樣你就明白為什么歇竟,prototype
屬性為什么能夠給函數(shù)增加屬性和方法了抵恋,應(yīng)為這東西就是該函數(shù)本身作為構(gòu)造器構(gòu)造的一個(gè)對(duì)象。
prototype
和constructor
談到構(gòu)造器的時(shí)候盅安,再深入說(shuō)明世囊。需要明白的是,函數(shù)也是有屬性的株憾。
函數(shù)還有一個(gè)特殊的屬性arguments
,透過(guò)字面意思你也能知道墙歪,這個(gè)屬性指向的是調(diào)用這個(gè)函數(shù)的參數(shù)列表贝奇。這是一個(gè)特殊的對(duì)象。它可以被當(dāng)作數(shù)組遍歷使用,但是他不是一個(gè)數(shù)組髓帽,所以不能使用數(shù)組的方法來(lái)操作它。
正是應(yīng)為這個(gè)屬性的存在衡查,所以即使函數(shù)調(diào)用函數(shù)的參數(shù)對(duì)象是不完整的也依舊可以調(diào)用這個(gè)函數(shù)必盖。而額外的參數(shù),也可以通過(guò)arguments
來(lái)調(diào)用使用歌粥。
0x02 上下文
上下文(context
)是很多語(yǔ)言都有的一個(gè)概念失驶。在Java中有著十分嚴(yán)格的規(guī)定和約束,但是JS中卻是靈活多變。在函數(shù)的內(nèi)部棉圈,上下文用this
表示眷蜓。在Java等面向?qū)ο笳Z(yǔ)言中,this
表示的是調(diào)用這個(gè)方法或者屬性的對(duì)象吁系。
例如:obj.foo = function(){return this;};
這樣的方式很好理解,調(diào)用obj.foo()
方法氏捞,返回的是對(duì)象obj
冒版。
但如果我們直接生命一個(gè)函數(shù),沒(méi)有把它賦予給任何一個(gè)對(duì)象呢捆等?
> function f() {return this;};
> f();
<· window{...}
這樣很好理解续室,當(dāng)沒(méi)有給一個(gè)函數(shù)指定上下文的時(shí)候,它的上下文是window
挺狰。當(dāng)然丰泊,這是在瀏覽器中,其他環(huán)境中我還沒(méi)有研究瞳购。但遠(yuǎn)離應(yīng)該是相同的,即沒(méi)有賦予上下文環(huán)境的時(shí)候年堆,賦予的最頂級(jí)的上下文環(huán)境盏浇。
將一個(gè)函數(shù)賦予給一個(gè)對(duì)象作為它的方法,是一種改變函數(shù)上下文的方式绢掰。如果我們想要?jiǎng)討B(tài)的改變函數(shù)的上下文呢。就比如谊却,一段代碼,我們要等運(yùn)行到一個(gè)結(jié)果后捕透,才能指定這個(gè)函數(shù)的上下文來(lái)調(diào)用碴萧。這就是JS中常用的回調(diào)機(jī)制,而這個(gè)時(shí)候就要用到的函數(shù)的兩個(gè)特殊的方法call()
和apply()
破喻。
這兩個(gè)方法的作用曹质,都是為一個(gè)函數(shù)指定上下文。不同的是羽德,調(diào)用的參數(shù)不同。
-
call:
call
方法第一個(gè)參數(shù)接收的是要指定給函數(shù)的上下文章蚣,之后的參數(shù)依次賦予函數(shù)對(duì)應(yīng)的參數(shù)姨夹。f.call(obj, arg1, arg2, arg3)
-
apply:
apply
方法第一個(gè)參數(shù)也是接收的指定給函數(shù)的上下文,第二個(gè)參數(shù)接收的是一個(gè)數(shù)組或者arguments
峭沦。將數(shù)組的元素依次匹配到函數(shù)上够颠,或者將arguments
傳遞給函數(shù)調(diào)用榄鉴。f.apply(obj, [arg1, arg2, arg3])
或f.apply(obj, arguments)
0x03 閉包
閉包這種特性,可以理解為作用域剃诅。當(dāng)然驶忌,這和Java等語(yǔ)言中的作用域是不一樣笑跛。JS中的“作用域”更加具有函數(shù)式語(yǔ)言的特性聊品。
舉個(gè)栗子:
if(flag) {
var a = 3;
} else {
var a = 4;
}
console.log(a);
這樣的代碼中,a
是可以被調(diào)用的翻屈。而在Java中這是會(huì)報(bào)錯(cuò)伸眶。這就是說(shuō)明,JS中的“作用域”其實(shí)不是和Java中那樣是以代碼塊為原子的厘贼,而是以函數(shù)為原子的。這就被稱之為閉包毁欣。
閉包的原則赁遗,很通俗的一條語(yǔ)言可以概括你的是我的,我的還是我的哭尝。
即一個(gè)函數(shù)的中的資源剖煌,不可以被外部訪問(wèn),但其內(nèi)部可以訪問(wèn)耕姊。如果下面一個(gè)函數(shù)的嵌套:
function a() {
function b() {
function c() {
...
}
}
}
a
外面不能調(diào)用b
和c
,a
中可以調(diào)用b
,但不能調(diào)用c
茉兰。依次類(lèi)推,內(nèi)部可以訪問(wèn)外部规脸,外部不能訪問(wèn)內(nèi)部。