国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

深入理解JavaScript系列(4)

2018-06-09 15:56 更新

本文是深入理解JavaScript系列的第篇讀文筆記,博客原文在這里。

內(nèi)容簡要

本文闡述的內(nèi)容是JavaScript中經(jīng)常遇到的兩個(gè)知識(shí)點(diǎn):自執(zhí)行函數(shù)函數(shù)閉包。如果你之前稍微接觸過過JavaScript,你應(yīng)該能夠明白我所指的意思,這里我就不像大叔原文中那么較真這個(gè)行為的具體叫法了。

在JavaScript的世界中,如果你能夠?qū)ψ詧?zhí)行函數(shù)和函數(shù)閉包了若指掌,在實(shí)際編碼中能夠信手拈來,那么,一般來說你JavaScript的功力至少有中級以上了,呵呵,這可能還是一種保守的估計(jì)。

如果你有讀過流行JavaScript類庫源碼的話,你可能會(huì)發(fā)現(xiàn),源碼的作者對自執(zhí)行函數(shù)和函數(shù)閉包的使用是比較頻繁的,再結(jié)合一些具體的業(yè)務(wù)場景,往往會(huì)得到一些非常美妙的設(shè)計(jì)。如果你去悉心品讀,可能會(huì)發(fā)現(xiàn)高手寫出來的JavaScript代碼和新手寫出的JavaScript代碼完全是天壤之別。

BACKBONE

大叔的原文中只針對自執(zhí)行函數(shù)作了比較透徹的說明,而對函數(shù)閉包僅僅用了一個(gè)示例就一筆帶過。這篇讀文筆記中,我將會(huì)針對這兩點(diǎn)分別作一些詳細(xì)說明,盡量用簡明的話將我理解中的這兩個(gè)概念闡述清楚。

自執(zhí)行函數(shù)

什么是自執(zhí)行?

首先,什么叫自執(zhí)行?在JavaScript中函數(shù)在執(zhí)行的時(shí)候會(huì)創(chuàng)建一個(gè)叫做執(zhí)行上下文的東西,這里問題又來了,這個(gè)執(zhí)行上下文又是什么東西呢?

簡單的說,執(zhí)行上下文就是JavaScript代碼在執(zhí)行時(shí)創(chuàng)建的一個(gè)容器,這個(gè)容器中可以隨時(shí)創(chuàng)建只屬于這一塊代碼的變量,函數(shù)聲明等等。

其實(shí),執(zhí)行上下文是ECMA-262規(guī)定的一個(gè)非常抽象的概念,我們這里只要對這個(gè)概念有個(gè)把握就可以了,更多的解釋我就不多作筆墨了,如有興趣,可查閱相關(guān)文檔。

上面說到,執(zhí)行上下文其實(shí)代碼執(zhí)行時(shí)才會(huì)生成的一個(gè)東西,如果我就簡單的寫一段JavaScript代碼放在這里,我并沒有在瀏覽器中引入這個(gè)JavaScript片段執(zhí)行它,那么它就不會(huì)有執(zhí)行上下文了。所以,自執(zhí)行的含義,簡單來說,一段JavaScript代碼中自己執(zhí)行了。這里的一段JavaScript代碼一般都是指一個(gè)JavaScript函數(shù),所以這里的自執(zhí)行就是指函數(shù)調(diào)用。

我們來看個(gè)例子,

function makeCounter() {
    // 只能在makeCounter內(nèi)部訪問i
    var i = 0;
    return function () {
        console.log(++i);
    };
}
// 注意,counter和counter2是不同的實(shí)例,分別有自己范圍內(nèi)的i。
var counter = makeCounter();
counter(); // logs: 1
counter(); // logs: 2
var counter2 = makeCounter();
counter2(); // logs: 1
counter2(); // logs: 2
alert(i); // 引用錯(cuò)誤:i沒有defind(因?yàn)閕是存在于makeCounter內(nèi)部)。

這里,我們每次調(diào)用函數(shù)makeCounter()時(shí),其實(shí)都會(huì)生成一個(gè)獨(dú)立的執(zhí)行上下文。具體來看,makeCounter生成的執(zhí)行上下文中包含了一個(gè)變量i以及一個(gè)匿名函數(shù)。

這里需要特別提出的一點(diǎn)是,每個(gè)獨(dú)立的執(zhí)行上下文,其中的變量都是相互獨(dú)立的,即countercounter2其實(shí)是不同的實(shí)例。

問題的核心

當(dāng)你聲明類似這樣的函數(shù),

function foo() {
    // function body
}
var foo2 = function() {
    // function body
}

我們可以簡單在函數(shù)名foo(或者變量名foo2)的后面加上()即可實(shí)現(xiàn)自執(zhí)行。如下,

foo();
foo2();

那是不是意為著我只要在函數(shù)的后面加上一對()就可以達(dá)到自執(zhí)行的目的呢?我們看下面的代碼,

function() {
    return 'test';
}();
function foo() {
    return 'test2';
}();

遺憾的是,這兩種方式,不管是在匿名函數(shù)后加()還是在普通的函數(shù)聲明后加()都達(dá)不到讓函數(shù)自執(zhí)行的目的。這兩種情況下,你都會(huì)得到一個(gè)報(bào)錯(cuò)。

上面提到的兩種錯(cuò)誤方式,其實(shí)出錯(cuò)的原理還不太一樣,

  • 前者是JavaScript在解析function關(guān)鍵字時(shí),默認(rèn)其是函數(shù)聲明,函數(shù)聲明要求必須有一個(gè)函數(shù)名。
  • 后者是一個(gè)函數(shù)聲明,函數(shù)聲明后直接跟一個(gè)(),這個(gè)()其實(shí)是一個(gè)分組操作符,這里報(bào)錯(cuò)的原因是因?yàn)榉纸M操作符需要一個(gè)表達(dá)式語句而不是一個(gè)聲明語句。

自執(zhí)行函數(shù)表達(dá)式

經(jīng)過上面的說明,我們知道,不管是匿名函數(shù)(雖然這個(gè)匿名的聲明也有問題)還是函數(shù)foo其實(shí)都只是函數(shù)聲明,而這里的()是一個(gè)運(yùn)算符,它要求前面的東西必須為(函數(shù))表達(dá)式!

所以,我們只需要將()前面的內(nèi)容變成函數(shù)表達(dá)式就行了。我們看下面的代碼,

// 下面2個(gè)括弧()都會(huì)立即執(zhí)行
(function(){ /* code */ }());
(function(){ /* code */ })();
// 由于括弧()和JS的&&,異或,逗號等操作符是在函數(shù)表達(dá)式和函數(shù)聲明上消除歧義的
// 所以一旦解析器知道其中一個(gè)已經(jīng)是表達(dá)式了,其它的也都默認(rèn)為表達(dá)式了
var i = function(){ /* code */ }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
// 如果你不在意返回值,或者不怕難以閱讀
// 你甚至可以在function前面加一元操作符號
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();
// 上面這種使用一元表達(dá)式這種方式其實(shí)是不太常見的
// 而且有時(shí)候肯定在一些場景下存在一些弊端,因?yàn)橐辉磉_(dá)式會(huì)有一個(gè)不為undefined的返回值
// 要想返回值為undefined,那么最保險(xiǎn)的就是使用void關(guān)鍵字
void function(){/* code */}();

一般常用的兩種形式就是(function(){}());(function(){})();,大叔的原文中說第一種是推薦的寫法,但是不知道為什么現(xiàn)在很多人都是用的第二種~~

區(qū)別

原文中還提到了這個(gè)話題。額,其實(shí)是英文原文的作者提到的。其實(shí)在我看來,自執(zhí)行匿名函數(shù)立即執(zhí)行函數(shù)表達(dá)式的區(qū)別基本上可以忽略,在實(shí)際的使用其實(shí)都是一回事,只不過兩種形式的函數(shù)主體不太一致。如下代碼,

(function() {
    return '我是自執(zhí)行匿名函數(shù)';
})();
(function foo() {
    foo();
})();

好吧,我承認(rèn)第二種其實(shí)是不太常見的。

Module模式

想想前篇文章說的Module模式,我們常常使用Module模式配合自執(zhí)行函數(shù)來封裝一個(gè)工具。

下面是一個(gè)例子,

// 創(chuàng)建一個(gè)立即調(diào)用的匿名函數(shù)表達(dá)式
// return一個(gè)變量,其中這個(gè)變量里包含你要暴露的東西
// 返回的這個(gè)變量將賦值給counter,而不是外面聲明的function自身
var counter = (function () {
    var i = 0;
    return {
        get: function () {
            return i;
        },
        set: function (val) {
            i = val;
        },
        increment: function () {
            return ++i;
        }
    };
} ());
// counter是一個(gè)帶有多個(gè)屬性的對象,上面的代碼對于屬性的體現(xiàn)其實(shí)是方法
counter.get(); // 0
counter.set(3);
counter.increment(); // 4
counter.increment(); // 5
counter.i; // undefined 因?yàn)閕不是返回對象的屬性
i; // 引用錯(cuò)誤: i 沒有定義(因?yàn)閕只存在于閉包)

當(dāng)然這里還用到了閉包的概念。我自己就經(jīng)常使用這種技巧來封裝一些配置類或者工具類的東西。封裝后,只要暴露一個(gè)對象就可以了,從而達(dá)到了對內(nèi)部變量的隱藏。

函數(shù)閉包

什么叫閉包?

什么叫(函數(shù))閉包呢?各種專業(yè)文獻(xiàn)上對這個(gè)詞的解釋比較抽象,不是太好理解。我個(gè)人對閉包的理解就是:閉包就是一個(gè)帶有了父作用域相關(guān)變量的函數(shù)。或者更加通俗一點(diǎn)就是:閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。

我想先談?wù)凧avaScript中為什么會(huì)有閉包這個(gè)東西。

我們知道在JavaScript中,函數(shù)第一等公民,函數(shù)的用途非常廣泛,函數(shù)可以參數(shù)傳入另一個(gè)函數(shù),還可以返回值從一個(gè)函數(shù)中返回。我們看下面的代碼,

function fn1() {
    var a = 1;
    return function fn2() {
        return 1 + a;
    };
}
var foo = fn1(); // typeof foo === 'function'
foo(); // 2

這里foo = fn1()后,foo其實(shí)是一個(gè)函數(shù)引用,通俗點(diǎn)說,foo就是一個(gè)函數(shù)表達(dá)式。那么這個(gè)foo在執(zhí)行的時(shí)候,它需要訪問變量a,但是這個(gè)a并沒有在fn2中定義,它是定義在fn1中的。所以foo(也就是fn2)在執(zhí)行的過程中,會(huì)向其父作用域(即fn1所在的作用域)查找變量a。此時(shí),fn2中就保持了一個(gè)對父作用域的引用。

類似這樣的場景就是我們所說的(函數(shù))閉包。其實(shí)閉包從某種意義上來說,就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。

閉包的作用

閉包最大的作用有兩個(gè),

  • 讀取函數(shù)內(nèi)部的變量
  • 保持對變量的持續(xù)引用

我們來看下面的一個(gè)例子,

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()()); // The Window

作一點(diǎn)改動(dòng)后,

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var self = this;
        return function(){
            return self.name;
        };
    }
};
alert(object.getNameFunc()()); // My Object

我們來稍微分析一下。第一種情況中,

object.getNameFunc()的執(zhí)行結(jié)果是其實(shí)是一個(gè)函數(shù)引用。而且這個(gè)getNameFunc函數(shù)在執(zhí)行時(shí),其內(nèi)部的this指針是指向object的。接下來,object.getNameFunc()()其實(shí)等價(jià)于,

var name = "The Window";
(function() {
    return this.name;
})();

這個(gè)代碼片段在執(zhí)行的時(shí)候,會(huì)檢索this的值。這里,它最終檢索的結(jié)果就是全局對象window,然后返回的結(jié)果就是name = 'The Window'。

而第二種情況中,我們使用變量self暫存了匿名函數(shù)(其實(shí)就是getNameFunc函數(shù)表達(dá)式)的this指針,而這個(gè)this指針在運(yùn)行時(shí)的指向正是object。函數(shù)getNameFunc返回的匿名函數(shù)毫無疑問,它是一個(gè)閉包,而且它保持了對父作用域變量self的持續(xù)引用。

更多內(nèi)容,推薦閱讀阮一峰的學(xué)習(xí)Javascript閉包(Closure)。

常見誤區(qū)

在使用閉包的時(shí)候,有一個(gè)常見的誤區(qū),我們看下面的代碼,

for (var i = 0; i < 10; i++) {
    setTimeout(function(){
        console.log(i);
    }, 1000);
}

這段代碼的運(yùn)行結(jié)果將會(huì)連續(xù)打印10個(gè)10。

你可能會(huì)問:啊?怎么會(huì)這樣?不是說好的打印從0到9的序列么?

我們來稍微分析一下。

for循環(huán)中連續(xù)創(chuàng)建了10個(gè)延時(shí)函數(shù),每個(gè)延時(shí)函數(shù)的函數(shù)體是打印迭代變量i。這里我們先忽略10個(gè)延時(shí)函數(shù)由于創(chuàng)建先后順序以及CPU時(shí)間片造成誤差。當(dāng)10次循環(huán)結(jié)束后,肯定還是沒有經(jīng)過1000ms,不過此時(shí)由于迭代的結(jié)果,迭代變量i已經(jīng)變成10了。接下里延時(shí)計(jì)時(shí)器結(jié)束,開始執(zhí)行延時(shí)函數(shù),函數(shù)中需要訪問變量i,不幸的是,此時(shí)的i已經(jīng)變成10了,所以打印出來的10個(gè)數(shù)據(jù)都是10。

那我們?nèi)绾涡薷哪軌蜻_(dá)到我們本來的目的呢?即按照迭代變量的順序,依次打印出0-9呢?

for (var i = 0; i < 10; i++) {
    setTimeout((function(index){
        return function() {
            console.log(index);
        };
    })(i), 1000);
}

代碼中應(yīng)該看的很清楚了,延時(shí)函數(shù)中使用了一個(gè)閉包,這個(gè)閉包保持了對父作用域中參考變量index的持續(xù)引用,而這個(gè)index是隨著每次for循環(huán)實(shí)時(shí)傳遞進(jìn)來的迭代變量。所以它將會(huì)打印出0-9。

總結(jié)

這篇對自執(zhí)行函數(shù)(或者叫立即調(diào)用匿名函數(shù)表達(dá)式)以及函數(shù)閉包作了細(xì)致的闡述,基本上涵蓋了這兩個(gè)知識(shí)點(diǎn)所有的方方面面,更多的內(nèi)容就是需要在實(shí)際編碼中進(jìn)行實(shí)戰(zhàn)了。

我還是想強(qiáng)調(diào)那句話,只要對JavaScript中的這兩個(gè)要點(diǎn)了若指掌,編碼時(shí)能夠做到信手拈來,那么假以時(shí)日必定能夠成為JavaScript高手。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號