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

資訊專欄INFORMATION COLUMN

You Don't Know Js 閱讀筆記

wanglu1209 / 642人閱讀

摘要:回調(diào)傳遞函數(shù)是將函數(shù)當(dāng)做值并作為參數(shù)傳遞給函數(shù)。這個例子中就是因為事件綁定機制中的傳入了回調(diào)函數(shù),產(chǎn)生了閉包,引用著所在的作用域,所以此處的數(shù)據(jù)無法從內(nèi)存中釋放。

javascript作用域

一門語言需要一套設(shè)計良好的規(guī)則來存儲變量,并且之后可以方便的找到這些變量,這逃規(guī)則被稱為作用域。

這也意味著當(dāng)我們訪問一個變量的時候,決定這個變量能否訪問到的依據(jù)就是這個作用域。

一、詞法作用域

作用域共有兩種主要的工作模型,第一種是最為普通的,被大多數(shù)編程語言(包括javascript)采用的詞法作用域,另一種叫做動態(tài)作用域。而我們平時所提及的作用域,就是這里所說的詞法作用域

要了解詞法作用域,必須要了解javascript引擎以及編譯器的大概工作方式。一般程序中的源碼在執(zhí)行前會進行編譯三步驟。

分詞/語法分析

解析/語法分析

代碼生成

而在分詞/詞法分析這個步驟,就已經(jīng)確定了詞法作用域。也就說作用域在我們書寫代碼的時候就已經(jīng)確定了,引用書中的文字

詞法作用域就是定義在詞法階段的作用域,換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域?qū)懺谀睦飦頉Q定的。

具體結(jié)合編譯器、作用域、引擎來講,編譯器在分詞階段,針對特定的環(huán)境就會生成一個詞法作用域,然后對源代碼中的var a = 3;類似的聲明進行識別,當(dāng)遇到var a,編譯器會詢問作用域中是否有a變量,若無,則在作用域中新增一個a變量。編譯完成之后,引擎執(zhí)行編譯后的代碼,引擎在執(zhí)行的過程中遇到a變量,會去作用域中查找是否有a變量,若有,則將a賦值2。對于var a = 2;一條語句會在兩個過程中操作,正是變量提升現(xiàn)象的原因。(稍后講到)

那什么時候會生成一個詞法作用域呢?

二、函數(shù)作用域

這幅圖所展示的三個氣泡,就代表了三個作用域,而編譯器遇到一個函數(shù)定義,就會生成一個作用域。例如當(dāng)編譯器遇到foo函數(shù),會創(chuàng)建一個作用域,再將這個函數(shù)內(nèi)部的標(biāo)識符(a/b/bar)放到詞法作用域中。這個步驟在編譯階段就完成了。當(dāng)js引擎執(zhí)行foo函數(shù)的時候,遇到a變量,就會去詢問早就創(chuàng)建好的作用域是否有a變量存在。

在作用域外,是無法訪問作用域內(nèi)的變量的。

例如

function foo() {
    var a = 3;
}
console.log(a); //undefied

正是這個特性,可以被用來實現(xiàn)隱藏內(nèi)部變量
將重要變量聲明放入一個函數(shù)聲明的作用域中,可以防止被作用域外部的語句所引用甚至更改。

根據(jù)函數(shù)作用域,可以引申出如何判斷一個函數(shù)是函數(shù)聲明還是一個函數(shù)表達(dá)式。
最重要的區(qū)別是他們的名稱標(biāo)識符將會綁定在何處。

先聲明一點,任何匿名函數(shù)都是可以添加名稱標(biāo)識符的。例如

setTimeout(function timer() {
    console.log(1)
}, 1000)

對于函數(shù)聲明,名稱標(biāo)識符是綁定在當(dāng)前作用域上的。即可在函數(shù)當(dāng)前作用域調(diào)用這個名稱標(biāo)識符。

而函數(shù)表達(dá)式,名稱標(biāo)識符是綁定在自身的函數(shù)作用域中的。

按照這個區(qū)別,來看以下幾個函數(shù)。

function foo1() {console.log(1)}
foo1(); // 1
var bar = function foo2() {console.log(1)}
foo2() // undefined
(function foo3() {console.log(1)})()
foo3() // undefined

以上的函數(shù)就只有foo1是函數(shù)聲明。

三、塊作用域

在js語言中,除了函數(shù),創(chuàng)建作用域的方式還可以通過塊作用域。對于js而言,循環(huán)、ifelse塊并沒有創(chuàng)建塊作用域的功能。

通過ES3規(guī)范的try/catch的catch語句可以創(chuàng)建一個塊作用域,其中聲明的變量僅在catch中有效。
try-catch也正是let關(guān)鍵字的向前兼容方。

try {
 undefined(); // 執(zhí)行一個非法操作來強制制造一個異常
} catch(err) {
    console.log(err);
}
console.log(err); // err not found

ES6引入了let關(guān)鍵字,提供了除var以外的另一種變量聲明方式,let為其聲明的變量隱式地劫持了所在的塊作用域。

if (true) {
    {
        let bar = 3;
        bar = someting(bar);
        console.log(bar)
    }
}
console.log(bar) // undefined

作于的一個中括號起到劃分塊作用域的作用,顯示的區(qū)別于var等變量。我們可能在之后會修改代碼,看到這個中括號會直白的認(rèn)識到這個是一個塊作用域。

四、變量提升

在第一節(jié)我已經(jīng)提到了,對于var a = 3;這樣一條語句,編譯器通過分詞、解析、最后生成機器可以讀的代碼。

而javascript實際上會將其看成兩個聲明:var a、a = 3。第一個聲明在編譯階段進行,第二個賦值聲明會留在原地等待執(zhí)行。

所以在引擎工作去執(zhí)行代碼時,進入到函數(shù)作用域內(nèi)時,首先會執(zhí)行var a操作,而這個過程就好像變量從原先的位置被移動作用域最上面一樣。

console.log(a); // undefined
var a = 3;

相當(dāng)于

var a;
console.log(a); // undefined
a = 3;

另外函數(shù)聲明也會發(fā)生變量提升的現(xiàn)象(連實際函數(shù)值也提升,即可以在函數(shù)聲明前調(diào)用)。而行數(shù)表達(dá)式var a = function foo1() {}發(fā)生提升的是a變量,函數(shù)本身不會發(fā)生提升。

foo(); // 不是ReferenceError 而是 TypeError
var foo = function bar() {}

ReferenceError TypeError
這是兩個錯誤標(biāo)記,第一個錯誤標(biāo)記是查詢變量時,若在作用域中查找不到這個變量則發(fā)出,第二個標(biāo)記是能查找到變量(即使是endefined),但是這個變量被錯誤的調(diào)用(比如對null,undefined進行調(diào)用),發(fā)出。

作用域閉包 一、經(jīng)典的閉包

閉包是基于詞法作用域書寫代碼時所產(chǎn)生的自然結(jié)果。

基于詞法作用域產(chǎn)生的結(jié)果,這有點類似于詞法作用域的產(chǎn)生條件。這也意味著閉包在書寫代碼的時候就已經(jīng)形成了。

看一個最經(jīng)典的閉包例子

function foo () {
    var a = 1;
    function bar () {
        console.log(a); //1
    }
    return bar;
}
var baz = foo();
baz();

基于這個經(jīng)典的例子,結(jié)合書中的話

一個函數(shù)在定義時的詞法作用域以外的地方被調(diào)用,可以記住并訪問原先所在的詞法作用域時,就產(chǎn)生了閉包。也即被返回出去的函數(shù)被調(diào)用時依然持有對該作用域的引用。這個引用就是閉包。

先確定一點,javascript中函數(shù)是可以作為值被傳遞的?;谶@個特性,有多種方法可以行成閉包。只要在一個作用域中,將函數(shù)作為值傳遞到另一個詞法作用域中并調(diào)用,就會形成閉包。

function foo() {
    var a = 2;
    function baz() {
        console.log(a);
    }
    bar(baz);
}
function bar(fn) {
    fn();
}
// 回調(diào)傳遞函數(shù)
var fn;
function foo() {
    var a = 2;
    function baz() {
        console.log(a);
    }
    fn = baz;
}
function bar() {
    fn();
}
foo();
bar(); //2
// 間接傳遞函數(shù)

無論通過何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執(zhí)行這個函數(shù)都會使用閉包。

二、回調(diào) == 閉包

再看上一節(jié),回調(diào)中傳遞函數(shù)的例子。

function foo() {
    var a = 2;
    function baz() {
        console.log(a);
    }
    bar(baz);
}
function bar(fn) {
    fn();
}
// 回調(diào)傳遞函數(shù)

是將函數(shù)當(dāng)做值并作為參數(shù)傳遞給函數(shù)。再來看

function wait(message) {
    setTimeout(function timer () {
        console.log(message); // hello world
    }, 1000)
}
wait("hello world");

setTimeout作為js內(nèi)置的工具函數(shù),將timer 函數(shù)當(dāng)做值傳進去,在setTimeout定義函數(shù)內(nèi)對傳進來的timer進行了調(diào)用。類似于

function setTimeout(fn) {
    // 延遲多少毫秒
    fn();
}

回調(diào)函數(shù)timer在另一個詞法作用域內(nèi)調(diào)用,但是能訪問原先作用域內(nèi)的參數(shù)(message)。

類似jquery中的事件綁定,涉及到傳遞回調(diào)函數(shù),就都有閉包的產(chǎn)生!

三、閉包在循環(huán)中的表現(xiàn)

最令人困惑的閉包表現(xiàn)就是在循環(huán)中了。像我們剛剛提及到的setTimeout、事件綁定等回調(diào)函數(shù)都會產(chǎn)生閉包。

for(var i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i);
    }, i*1000)
}

這個循環(huán)的本意是想間隔1秒打印1、2、3、4、5,結(jié)果卻每隔1秒輸出了5次6!
結(jié)合在第二節(jié)中對setTimeout函數(shù)的解析,這個誤區(qū)將很快解開。

首先要明白for循環(huán)沒有塊作用域的概念,即在這個循環(huán)中5次迭代都是在同一個作用域中進行的。
要清楚timer函數(shù)不是在這個作用域中被調(diào)用的,它作為參數(shù)在其他的作用域中調(diào)用。

function timer() {
    console.log(i);
}

這個函數(shù)包括其中的形式參數(shù)i原原本本的被傳遞,在迭代過程中i不會被賦值。
而五次迭代完成后,共用的作用域中的i的值已經(jīng)變成了6 。在其他作用域中的timer函數(shù)調(diào)用過程中需要查詢i,因為產(chǎn)生了閉包,i的值會去原始的作用域中查找,即全是6

得不到預(yù)期效果的錯其實都在于for循環(huán)中共用一個作用域。想改進也很簡單,即在迭代的過程中,創(chuàng)建對應(yīng)的作用域。另外值得注意的一點是需要把每次迭代的i值傳到作用域內(nèi)。

for(var i = 1; i <= 5; i++) {
    (function (j) {
        setTimeout(function timer () {
            console.log(j)
        }, j* 1000)
    })(i)
}
四、閉包的垃圾回收

本來一個變量被使用完之后就可以利用垃圾回收機制進行垃圾回收,但因為閉包的產(chǎn)生,阻止了這一行為。

function process(data) {
    //
}
var someReallyBigData = {};
process( someReallyBigData );
var $btn = $(".j_Btn");
$btn.on("click", function clicker() {});

這個例子中就是因為事件綁定機制中的傳入了clicker回調(diào)函數(shù),產(chǎn)生了閉包,引用著clicker所在的作用域,所以此處的someReallyBigData數(shù)據(jù)無法從內(nèi)存中釋放。

解決辦法也有,聲明一個塊作用域,讓引擎清楚的知道沒有必要保存someReallyBigData餓了。

function process(data) {
    //
}
{
    let someReallyBigData = {};
    process( someReallyBigData );
}
var $btn = $(".j_Btn");
$btn.on("click", function clicker() {});

閱讀心得,轉(zhuǎn)載請注明出處。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/78518.html

相關(guān)文章

  • 讀書筆記(you don&#039;t know js): this的理解(沒寫完...)

    摘要:基本概念首先,函數(shù)不能存儲的值,指向哪里,取決于調(diào)用它的對象。如果沒有這個對象,那默認(rèn)就是調(diào)用非嚴(yán)格模式下。也就是說是在運行的時候定義的,不是在綁定的時候定義的。 基本概念 首先,函數(shù)不能存儲this的值,this指向哪里,取決于調(diào)用它的對象。如果沒有這個對象,那默認(rèn)就是window調(diào)用(非嚴(yán)格模式下)。也就是說this是在運行的時候定義的,不是在綁定的時候定義的。 funct...

    freewolf 評論0 收藏0
  • You Don&#039;t Know JS閱讀理解——作用域

    摘要:在我們的程序中有很多變量標(biāo)識符,我們現(xiàn)在或者將來將使用它。當(dāng)我們使用時,如果并沒有找到這個變量,在非嚴(yán)格模式下,程序會默認(rèn)幫我們在全局創(chuàng)建一個變量。詞法作用域也就是說,變量的作用域就是他聲明的時候的作用域。 作用域 定義 首先我們來想想作用域是用來干什么的。在我們的程序中有很多變量(標(biāo)識符identifier),我們現(xiàn)在或者將來將使用它。那么多變量,我咋知道我有沒有聲明或者定義過他呢,...

    codeKK 評論0 收藏0
  • You don&#039;t know cross-origin

    摘要:為什么會存在跨域問題同源策略由于出于安全考慮,瀏覽器規(guī)定不能操作其他域下的頁面,不能接受其他域下的請求不只是,引用非同域下的字體文件,還有引用非同域下的圖片,也被同源策略所約束只要協(xié)議域名端口有一者不同,就被視為非同域。 showImg(https://segmentfault.com/img/remote/1460000017093859?w=1115&h=366); Why 為什么...

    hersion 評論0 收藏0
  • You Don&#039;t Know JS閱讀理解——this

    摘要:運行規(guī)則根據(jù)的運作原理,我們可以看到,的值和調(diào)用棧通過哪些函數(shù)的調(diào)用運行到調(diào)用當(dāng)前函數(shù)的過程以及如何被調(diào)用有關(guān)。 1. this的誕生 假設(shè)我們有一個speak函數(shù),通過this的運行機制,當(dāng)使用不同的方法調(diào)用它時,我們可以靈活的輸出不同的name。 var me = {name: me}; function speak() { console.log(this.name); }...

    tianren124 評論0 收藏0
  • 一起來讀you don&#039;t know javascript(一)

    摘要:一到底是一門什么樣的計算機編程語言表里不一表面上是動態(tài)解釋執(zhí)行的腳本語言,實際上它是一門編譯語言。與眾不同與傳統(tǒng)語言不同的是,它不是提前編譯的,編譯記過也不能在分布式系統(tǒng)中進行移植。千篇一律引擎進行編譯的步驟和傳統(tǒng)的編譯語言非常相似。 一、JavaScript到底是一門什么樣的計算機編程語言? JavaScript表里不一:表面上是動態(tài)、解釋執(zhí)行的腳本語言,實際上它是一門編譯語言。 ...

    Anchorer 評論0 收藏0

發(fā)表評論

0條評論

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