摘要:當我們把函數(shù)傳遞給函數(shù),并且讓能夠在某一時刻執(zhí)行,這種情況我們稱函數(shù)是回調(diào)函數(shù),簡稱回調(diào)。返回函數(shù)剛剛在回調(diào)函數(shù)部分,說的是函數(shù)作為另一個函數(shù)的參數(shù)傳遞,接下來說說函數(shù)作為另一邊函數(shù)的結(jié)果返回。
寫在前面
由于詞語匱乏,本文繼續(xù)沿用"深入解析xxx"這個俗套的命名,但是是真的很深入(你要信我啊)。如果本文對你有用,歡迎收藏,如果喜歡我的文章,歡迎點贊和關(guān)注專欄。
函數(shù)可以說是js的基礎,無處不在,功能又十分強大,本文將簡單介紹函數(shù)的特點并且重點介紹各種各樣的用法。廢話不多說,開車~
友情提示,由于本文涵蓋的內(nèi)容比較全面,不免篇幅稍長,中途請注意休息。
但是其實,函數(shù)的本質(zhì)就是對象。確切一點來說,其實是第一類對象(first-class object)。關(guān)于第一類對象,wiki解釋如下:
第一類對象又稱第一類公民,在編程語言中指的是一個具有以下特性的實體:
能夠作為參數(shù)被傳遞
能夠從一個函數(shù)結(jié)果中返回
能夠被修改和賦值給變量
雖然看起來高大上,但是我們只要先記住,在js里函數(shù)也是對象,可以擁有自己的屬性和方法,而它和一般js對象的區(qū)別是:可以被調(diào)用,也就是可執(zhí)行。
當然,函數(shù)還有一個明顯的特點就是,提供作用域:在函數(shù)作用域內(nèi)的變量都是局部變量,對外部不可見。由于js中其他代碼塊,比如for和while循環(huán)等并不提供作用域,所以有很多地方會利用函數(shù)來控制作用域。在后面會一一提到。
預備知識這一塊在之前講閉包的時候其實提到了一些,但是還是簡單介紹下。
函數(shù)作用域在類似C語言的編程語言中,花括號{}表示一個作用域:在作用域內(nèi)的變量對外不可見,這個稱為塊級作用域,但是在js中沒有塊級作用域,只有函數(shù)作用域:在函數(shù)體內(nèi)聲明的變量,在整個函數(shù)體內(nèi)有定義。
function fun(){ for(var j =1;j<10;j++){ } console.log(j)//10 } console.log(j)//undefined
這個例子中變量j定義在函數(shù)體中,那么在函數(shù)體內(nèi)可以訪問,在外部則無法訪問。
作用域鏈作用域鏈,就是一個類似鏈表的解構(gòu),它表示當前代碼有權(quán)訪問的作用域的訪問順序。舉個例子:
var a = 1; function fun(){ var a = 2 console.log(a) } fun()//2
在這里,執(zhí)行fun()時,作用域鏈上有2個作用域,第一個是fun,第二個是全局環(huán)境,按照順序,首先訪問內(nèi)容的作用域,找到了a變量,那么就不繼續(xù)尋找,如果這里沒有var a = 2,那么會繼續(xù)向外尋找,最終輸出的就是1。
只要記住,作用域鏈都是從當前函數(shù)作用域向外一層層延伸的,所以內(nèi)部作用域可以訪問外部變量,反之則不行。
聲明提升看下這個例子:
function fun(){ console.log(a) var a = 1; } fun();//underfined
是不是覺得很奇怪,這里既沒有未定義報錯,也沒有輸出1,因為這里的代碼其實相當于這樣寫:
function fun(){ var a; console.log(a) a = 1; } fun();//underfined
可以看到,其實變量a的聲明,相當于被提前到當前函數(shù)作用域的頂部,這就是所謂的聲明提升,但是要注意,聲明雖然提升了,賦值a=1并沒有被提升,否則這個例子應該直接輸出1。
接下來再舉1個例子回顧下這一階段的知識:
var a = 1; var b = 4; function fun (){ console.log(a); var a = 2; var b = 3; console.log(b); } fun (); console.log(b);
具體結(jié)果大家可以跑跑看。
函數(shù)的創(chuàng)建通常來說,有2種創(chuàng)建函數(shù)的方式:函數(shù)表達式、函數(shù)聲明。
函數(shù)表達式函數(shù)表達式通常具有如下形式:
var funA = function funName(param1,param2){ //函數(shù)體 }
當然,更常見來說這里的funName是不寫的,寫與不寫的區(qū)別是,在不同瀏覽器中,獲得的函數(shù)對象中name屬性的值會被處理成不行的形式。
//這個例子可以在ie firefox webkit內(nèi)核的瀏覽器分別跑一下看看結(jié)果 var fun1 = function(){} var fun2 = function funName(){} console.log(fun1) console.log(fun2)
寫函數(shù)名字有個比較好用的地方是在遞歸的時候,可以很方便使用:
//階乘函數(shù) var fun1 = function recu(x){ if(x<=1) return 1; else return x*recu(x-1) }函數(shù)聲明
函數(shù)聲明形式一般如下:
function funName(){ //函數(shù)體 }
這個和函數(shù)表達式的區(qū)別就是,使用函數(shù)聲明的方式在js里會有"提升",而使用表達式方式寫沒有提升所以函數(shù)表達式定義的函數(shù)無法提前使用
fun1();//fun1 fun2();//報錯 function fun1 (){ console.log("fun1") } var fun2 = function(){ console.log("fun2") }
因為前面說過,賦值部分不會提升,而函數(shù)表達式的寫法本質(zhì)上也是一個變量聲明和賦值,形如var x = function...,x的聲明被提升,但是右邊的賦值部分要等待代碼執(zhí)行到這句的時候才生效。
舉個更容易理解的例子:
console.log(fun2)//underfined fun2();//報錯 var fun2 = function(){ console.log("fun2") }
同理,變量fun2已聲明,但未賦值。所以這里console.log的時候不報錯,運行的時候才報錯。看不懂請再回顧下預備知識的聲明提升部分。
函數(shù)參數(shù)函數(shù)的參數(shù)一般分成形參和實參,形參是函數(shù)定義時預期傳入的參數(shù),實參是函數(shù)調(diào)用時實際傳入?yún)?shù)。
參數(shù)數(shù)量不對等情況和argumentsJavascript沒有在函數(shù)調(diào)用時對實參做任何檢查。 所以可能出現(xiàn)以下情況:
當傳入的實參比形參個數(shù)要少的時候,剩下的形參會被自動設置為underfined,所以在寫函數(shù)的時候,我們經(jīng)常要注意是否要給參數(shù)一些默認值
function fun(a){ var a = a || "" //如果傳入a就使用a,否則a設置為空字符串 }
如果我們的函數(shù)使用了可選參數(shù),那么可選參數(shù)的位置必須放在最后,否則,使用者調(diào)用時候,就要顯式傳入underfind,比如fun(underfined,a)表示第一個參數(shù)不傳入。
當傳入的實參比形參個數(shù)要多的時候,我們可以通過標識符arguments對象來獲得參數(shù)
function fun(a){ if(arguments.length>1)console.log(arguments[1])}; var a=1,b=2; fun(a,b);//2
這個例子中,通過arguments輸出了實參b的值。值得一提的是,arguments并不是數(shù)組,而是一個對象,只是恰好使用數(shù)字為索引
callee 和 calleres5的非嚴格模式下,我們可以使用callee 和 caller這兩個屬性,
callee 表示當前正在執(zhí)行的函數(shù),通常用法是在匿名函數(shù)中寫遞歸調(diào)用
caller 表示調(diào)用當前正在執(zhí)行函數(shù)的函數(shù),可以用來訪問調(diào)用棧,這個屬性是非標準的,但是大部分的瀏覽器都實現(xiàn)。更詳細的用法可以查看MDN。
函數(shù)的模式模式其實就是函數(shù)的各種應用方式,也是本文的重點
api模式api模式主要是給函數(shù)提供更好的接口。
回調(diào)模式最前面已經(jīng)提到,函數(shù)是對象,并且可以被作為參數(shù)傳遞給其他的函數(shù)。
當我們把函數(shù)A傳遞給函數(shù)B,并且讓B能夠在某一時刻執(zhí)行A,這種情況我們稱函數(shù)A是回調(diào)函數(shù)(callback function),簡稱回調(diào)。
舉個例子,假設這樣一個背景:假設現(xiàn)在我們需要處理一批dom節(jié)點,處理大概分2步,第一步,篩選出符合要求的一部分節(jié)點,第二步,對這部分數(shù)據(jù)做一些css樣式修改。那我們一般會先想到這樣寫:
//篩選函數(shù) function filterNodes(nodes){ var i = 0; var result = []; for(i = 0; i按照上面定義的2個函數(shù),先用filterNodes篩選符合要求額節(jié)點,然后將結(jié)果作為operate函數(shù)的參數(shù),這樣邏輯上是完全沒問題的,只是有一個地方:其實我們已經(jīng)2次遍歷了符合要求的節(jié)點:第一次是在篩選時,第二次是在樣式操作時。這里有辦法優(yōu)化嗎?,如果我們直接把樣式操作直接寫到result.push()后面,是可以減少一次遍歷的,但是這樣filterNodes函數(shù)就不是一個純粹的篩選節(jié)點的數(shù)了。所以我們可以使用回調(diào)模式來解決,只需稍微修改下:
//篩選函數(shù) function filterNodes(nodes,callback){ var i = 0; var result = []; for(i = 0; i這樣改造之后,2個函數(shù)依然各自擁有自己的邏輯,而且我們可以通過調(diào)用filterNodes時,傳遞不同參數(shù)的辦法,來控制我們想要的功能。
回調(diào)函數(shù)還有很多的常見用途:
異步事件監(jiān)聽
最常見的例子莫過于我們?yōu)槲臋n添加監(jiān)聽事件:document.addEventListener("click",[回調(diào)函數(shù)],false)有了回調(diào)模式以后,程序可以以異步的模式運行:只有用戶觸發(fā)了某些交互行為,才會調(diào)用到我們指定的函數(shù)。
超時方法 setTimeout()和 setTimeInterval()
這兩個函數(shù)也一樣接受回調(diào)函數(shù)setTimeout([回調(diào)函數(shù)],200)軟件庫設計
返回函數(shù)
設計一個庫的時候,很重要的就是設計通用性和復用性的代碼,因為無法提前預測到需要的每一個功能,而且用戶也不會總是需要用到所有的功能,利用回調(diào)模式,很容易設計出具有核心功能有同時提供自選項的函數(shù)(比如前面提到的節(jié)點篩選函數(shù),核心功能是篩選,又能根據(jù)需要插入后續(xù)操作)。剛剛在回調(diào)函數(shù)部分,說的是函數(shù)作為另一個函數(shù)的參數(shù)傳遞,接下來說說函數(shù)作為另一邊函數(shù)的結(jié)果返回??聪旅嬉粋€計時器例子:
var counter = function(){ var count = 0; return function(){ return count++ } } var f = counter(); f();//1 f();//2其實這里就是一個閉包的實例,關(guān)于閉包,在我的另一篇文章里有更詳細的描述點擊前往
配置對象配置對象模式其實就是讓用對象作為函數(shù)的參數(shù)。
這種模式經(jīng)常用在建立一個庫,或者寫的函數(shù)要提供給外部調(diào)用時。因為它能提供很簡潔的接口。假設這樣一個例子:function operate(para1,para2){}如果我們正在寫一個庫函數(shù),一開始我們預料到的參數(shù)只會有para1,para2,但是隨著不斷拓展,后來參數(shù)變多了,而且出現(xiàn)了一些可選參數(shù)para3,para4:
function operate(para1,para2,para3,para4...)此時我們需要很小心的把可選參數(shù)放在后面,使用者在調(diào)用的時候還必須很小心的對上位置,比如說:
operate(p1,p2,null,p4)//這里的null不可省略此時,參數(shù)數(shù)量太多,使用起來需要很小心記住參數(shù)順序,很不方便。所以就要采用配置對象的寫法,即把參數(shù)寫成一個對象:
function operate(config){} var conf = { para1:..., para2:..., para4:..., } operate(con)這樣的寫法
優(yōu)點是:使用者不需要記住參數(shù)順序,代碼也顯得更簡潔,
缺點是:使用時要嚴格記住參數(shù)的名稱,并且屬性名稱無法被壓縮
通常在操作dom對象的css樣式時候會用這樣的寫法,因為css樣式有很多,但是名稱很容易記住,比如
var style ={ color:"..." border:"..." }柯里化start18/08/08編輯
柯里化內(nèi)容已添加,傳送門
end18/08/08編輯
柯里化的內(nèi)容比較長,難度也稍大,后續(xù)另開一篇來寫吧~~。
初始化模式初始化模式的主要作用是不污染全局命名空間,使用臨時變量來完成初始化任務,使任務更加簡潔
即時函數(shù)即時函數(shù)模式(immeddiate Function pattern),是一種支持在定義函數(shù)后立即執(zhí)行該函數(shù)的語法。也叫作自調(diào)用和自執(zhí)行函數(shù)(function(){ //函數(shù)內(nèi)容 }()) //也可以這樣寫 (function(){ //函數(shù)內(nèi)容 })()這里給出了即時函數(shù)的兩種寫法,它的作用是可以給初始化的代碼提供一個存放的空間:比如在頁面初始化時,需要一些臨時變量來完成一次初始化,但是這些工作只需要執(zhí)行一次,執(zhí)行之后就不再需要這些臨時變量,那么我們就不必浪費全局變量來創(chuàng)建這些變量,此時使用即時函數(shù),可以把所有代碼打包起來,并且不會泄露到全局作用域。比如:
(function(){ var initName = "" alert(initName) }());當然,即時函數(shù)也可以傳遞參數(shù),
(function(initName){ alert(initName) }("hello"));同樣也可以有返回值:
var result = (function(){ return 1 }()); console.log(result)//1即時函數(shù)經(jīng)常用在寫一些自包含模塊,這樣的好處是可以確保頁面在有無該模塊的情況下都能良好運行,很方便的可以分離出來,用于測試或者實現(xiàn),或者根據(jù)需要實現(xiàn)“禁用”功能。例如:
//moudle1.js (function(){ //模塊代碼 }//)按照這一的形式寫模塊。可以根據(jù)需要加載模塊。
即時對象初始化這個模式和即使函數(shù)模式很相似,區(qū)別在于我們的函數(shù)寫在一個對象的方法上。通常我們在一個對象上寫上init方法,并且在創(chuàng)建對象之后立即執(zhí)行該方法。如下:
({ //初始化的屬性和配置 name:"Mike", age:"12", //其他方法 ... //初始化 init:function(){ ... } }).init();這個語法其實相當于在創(chuàng)建一個普通的對象并且,然后在創(chuàng)建之后立刻調(diào)用init方法。這種做法和即時函數(shù)的目的是一致的:在執(zhí)行一次性初始化任務時保護全局命名空間。但是可以寫出更加復雜的結(jié)構(gòu),比如私有方法等,而在即時函數(shù)里面只能把所有的方法都寫成函數(shù)。
初始化時分支初始化時分支經(jīng)常用在某個生命周期中做一次性測試的情境中。所謂的一次性測試就是:在本次生命周期中,某些屬性不可能改變,比如瀏覽器內(nèi)核等。典型的例子是瀏覽器嗅探.
看過javacscript高級程序設計的話,對這個例子一定很眼熟:
var utils = { addListener:function(el,type,fn){ if(typeof window.addEvenrtListener === "function"){ el.addEventerListener(type,fn,false); } else if(typeof window.attachEvent === "function"){ //ie el.attachEvent("on" + type,fn) } else{ //其他瀏覽器 el.["on"+ type] = fn } } ...//刪除方法類似 }這個例子是為了寫一個能夠支持跨瀏覽器處理事件的方法,但是有個缺點:每次在處理事件時都要檢測一次瀏覽器的類型。我們知道,其實在一次頁面的生命周期里,其實只需要檢測一次就夠了,所以可以利用初始化分支來這樣改寫:
var utils = { addListener:null } if(typeof window.addEvenrtListener === "function"){ utils.addListener = function(el,type,fn){ el.addEventerListener(type,fn,false); } } else if(typeof window.attachEvent === "function"){ //ie utils.addListener = function(el,type,fn){ el.attachEvent("on" + type,fn) } } else{ //其他瀏覽器 utils.addListener = function(el,type,fn){ el.["on"+ type] = fn } }這樣的話就可以在加載時完成一次嗅探。
性能模式性能模式,主要是在某些情況下加快代碼的運行。
備忘模式備忘模式的核心是使用函數(shù)屬性,緩存能計算結(jié)果。以便后續(xù)調(diào)用時可以不必重新計算。
這么做的基礎主要是之前提到過的,函數(shù)本質(zhì)還是對象(這句話已經(jīng)重復n次了),既然是對象自然可以擁有屬性和方法,例子:var fun = function(key){ if(!fun.cache[key]){ //不存在對應緩存,那么計算 var result = {} ...//計算過程 fun.cache[key] = result } return fun.cache[key] }這里舉了一個比較簡單的例子,在獲取對應數(shù)據(jù)的時候,先判斷有無緩存,有的話直接獲?。粵]有的話計算一次并緩存到對應位置。之后便無需重復計算。
當然,這里的key我們假設是基本類型的值,如果是復雜類型的值,需要先序列化。
自定義模式
另外,在函數(shù)內(nèi)的fun可以通過前面提到的arguments.callee來代替,只要不在es5的嚴格模式下就行。自定義函數(shù)的原理很簡單:首先創(chuàng)建一個函數(shù)并保存到一個變量f。然后在創(chuàng)建一個新函數(shù),也保存在這個變量f,那么f最終指向的應該是新的函數(shù)。那么如果我們讓這個過程發(fā)生在舊的函數(shù)內(nèi)部,那么就實現(xiàn)了惰性函數(shù)。話不多說,看例子:
var fun = function(){ console.log("在這里執(zhí)行一些初始化工作") fun = function(){ console.log("在這里執(zhí)行正常工作時需要執(zhí)行的工作") } } fun();//在這里執(zhí)行一些初始化工作 fun();//在這里執(zhí)行正常工作時需要執(zhí)行的工作 fun();//在這里執(zhí)行正常工作時需要執(zhí)行的工作在這里我們執(zhí)行了一次初始化任務以后,函數(shù)就變成了正常的函數(shù),之后的執(zhí)行就可以減少工作。
總結(jié)這是2018年寫的第一篇長文(其實一共就寫了2篇,哈哈哈)希望今年自己可以好好努力,把“深入”系列貫徹到底。也希望大家都有所進步。
然后依然是每次都一樣的結(jié)尾,如果內(nèi)容有錯誤的地方歡迎指出;如果對你有幫助,歡迎點贊和收藏,轉(zhuǎn)載請征得同意后著明出處,如果有問題也歡迎私信交流,主頁添加了郵箱地址~溜了
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/92411.html
摘要:模板解析器原理本文來自深入淺出模板編譯原理篇的第九章,主要講述了如何將模板解析成,這一章的內(nèi)容是全書最復雜且燒腦的章節(jié)。循環(huán)模板的偽代碼如下截取模板字符串并觸發(fā)鉤子函數(shù)為了方便理解,我們手動模擬解析器的解析過程。 Vue.js 模板解析器原理 本文來自《深入淺出Vue.js》模板編譯原理篇的第九章,主要講述了如何將模板解析成AST,這一章的內(nèi)容是全書最復雜且燒腦的章節(jié)。本文未經(jīng)排版,真...
摘要:預解析聲明告知瀏覽器在全局作用域中有一個變量名為的變量。執(zhí)行代碼的就是棧內(nèi)存,作用域也是棧內(nèi)存。關(guān)鍵字在中主要研究都是函數(shù)中的中的代表的是當前行為執(zhí)行的主體方法,函數(shù),事件中的上下文代表的是當前行為執(zhí)行的環(huán)境區(qū)域例如小明在沙縣小吃吃蛋炒飯。 基本認識 數(shù)據(jù)類型 基本數(shù)據(jù)類型 string, number, null, boolean, undefined 引用數(shù)據(jù)類型 object: ...
摘要:在高級的技巧中會用來創(chuàng)建作用域安全的構(gòu)造函數(shù)。運算符希望左操作數(shù)是一個對象,右操作數(shù)表示對象的類。中對象的類似通過初始化它們的構(gòu)造函數(shù)來定義的。為了理解運算符是如何工作的,必須首先理解原型鏈原型鏈可作為的繼承機制。 在js高級的技巧中會用instanceof來創(chuàng)建作用域安全的構(gòu)造函數(shù)。instanceof運算符希望左操作數(shù)是一個對象,右操作數(shù)表示對象的類。如果左側(cè)的對象是右側(cè)類的...
摘要:本計劃一共期,每期重點攻克一個面試重難點,如果你還不了解本進階計劃,點擊查看前端進階的破冰之旅本期推薦文章深入之執(zhí)行上下文棧和深入之變量對象,由于微信不能訪問外鏈,點擊閱讀原文就可以啦。 (關(guān)注福利,關(guān)注本公眾號回復[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實戰(zhàn)、面試指導) 本周正式開始前端進階的第一期,本周的主題是調(diào)用堆棧,今天是第二天。 本計劃一共28期,每期...
摘要:下面是用實現(xiàn)轉(zhuǎn)成抽象語法樹如下還支持繼承以下是轉(zhuǎn)換結(jié)果最終的結(jié)果還是代碼,其中包含庫中的一些函數(shù)。可以使用新的易于使用的類定義,但是它仍然會創(chuàng)建構(gòu)造函數(shù)和分配原型。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 15 篇。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 如果你錯過了前面的章節(jié),可以在這里找到它們: JavaScript 是...
閱讀 3378·2021-09-08 09:45
閱讀 1286·2019-08-30 15:53
閱讀 1563·2019-08-30 14:12
閱讀 1004·2019-08-29 17:01
閱讀 2602·2019-08-29 15:35
閱讀 431·2019-08-29 13:09
閱讀 2011·2019-08-29 12:32
閱讀 3113·2019-08-26 18:37