成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入解析js中的函數(shù)

imccl / 3560人閱讀

摘要:當我們把函數(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ù)簡介

但是其實,函數(shù)的本質(zhì)就是對象。確切一點來說,其實是第一類對象(first-class object)。關(guān)于第一類對象,wiki解釋如下:

第一類對象又稱第一類公民,在編程語言中指的是一個具有以下特性的實體:

能夠作為參數(shù)被傳遞

能夠從一個函數(shù)結(jié)果中返回

能夠被修改和賦值給變量

雖然看起來高大上,但是我們只要先記住,在js里函數(shù)也是對象,可以擁有自己的屬性和方法,而它和一般js對象的區(qū)別是:可以被調(diào)用,也就是可執(zhí)行。

當然,函數(shù)還有一個明顯的特點就是,提供作用域:在函數(shù)作用域內(nèi)的變量都是局部變量,對外部不可見。由于js中其他代碼塊,比如forwhile循環(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ù)量不對等情況和arguments

Javascript沒有在函數(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ù)字為索引

calleecaller

es5的非嚴格模式下,我們可以使用calleecaller這兩個屬性,

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)

軟件庫設計
設計一個庫的時候,很重要的就是設計通用性和復用性的代碼,因為無法提前預測到需要的每一個功能,而且用戶也不會總是需要用到所有的功能,利用回調(diào)模式,很容易設計出具有核心功能有同時提供自選項的函數(shù)(比如前面提到的節(jié)點篩選函數(shù),核心功能是篩選,又能根據(jù)需要插入后續(xù)操作)。

返回函數(shù)

剛剛在回調(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

相關(guān)文章

  • Vue.js 模板解析器原理 - 來自《深入淺出Vue.js》第九章

    摘要:模板解析器原理本文來自深入淺出模板編譯原理篇的第九章,主要講述了如何將模板解析成,這一章的內(nèi)容是全書最復雜且燒腦的章節(jié)。循環(huán)模板的偽代碼如下截取模板字符串并觸發(fā)鉤子函數(shù)為了方便理解,我們手動模擬解析器的解析過程。 Vue.js 模板解析器原理 本文來自《深入淺出Vue.js》模板編譯原理篇的第九章,主要講述了如何將模板解析成AST,這一章的內(nèi)容是全書最復雜且燒腦的章節(jié)。本文未經(jīng)排版,真...

    pinecone 評論0 收藏0
  • ECMA_作用域深入&This關(guān)鍵字

    摘要:預解析聲明告知瀏覽器在全局作用域中有一個變量名為的變量。執(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: ...

    Harriet666 評論0 收藏0
  • [ JS 基礎 ] JS 中 instanceof 運算符深入解析 (2)

    摘要:在高級的技巧中會用來創(chuàng)建作用域安全的構(gòu)造函數(shù)。運算符希望左操作數(shù)是一個對象,右操作數(shù)表示對象的類。中對象的類似通過初始化它們的構(gòu)造函數(shù)來定義的。為了理解運算符是如何工作的,必須首先理解原型鏈原型鏈可作為的繼承機制。 在js高級的技巧中會用instanceof來創(chuàng)建作用域安全的構(gòu)造函數(shù)。instanceof運算符希望左操作數(shù)是一個對象,右操作數(shù)表示對象的類。如果左側(cè)的對象是右側(cè)類的...

    劉厚水 評論0 收藏0
  • 【進階1-2期】JavaScript深入之執(zhí)行上下文棧和變量對象

    摘要:本計劃一共期,每期重點攻克一個面試重難點,如果你還不了解本進階計劃,點擊查看前端進階的破冰之旅本期推薦文章深入之執(zhí)行上下文棧和深入之變量對象,由于微信不能訪問外鏈,點擊閱讀原文就可以啦。 (關(guān)注福利,關(guān)注本公眾號回復[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實戰(zhàn)、面試指導) 本周正式開始前端進階的第一期,本周的主題是調(diào)用堆棧,今天是第二天。 本計劃一共28期,每期...

    Richard_Gao 評論0 收藏0
  • JavaScript是如何工作的:深入類和繼承內(nèi)部原理+Babel和 TypeScript 之間轉(zhuǎn)換

    摘要:下面是用實現(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 是...

    PrototypeZ 評論0 收藏0

發(fā)表評論

0條評論

imccl

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<