国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

javascript中關(guān)于作用域和閉包

zacklee / 3439人閱讀

摘要:在代碼執(zhí)行時(shí),對(duì)應(yīng)的作用域鏈常常是保持靜態(tài)的。當(dāng)語(yǔ)句執(zhí)行完畢后,會(huì)把作用域鏈恢復(fù)到原始狀態(tài)。在全局作用域中創(chuàng)建的函數(shù),其作用域鏈會(huì)自動(dòng)成為全局作用域中的一員。

列表項(xiàng)目

前言

學(xué)習(xí)了javascript已經(jīng)很久了,關(guān)于這個(gè)語(yǔ)言中的這兩個(gè)特性也是早已耳熟能詳,但是在實(shí)際的使用的過程中或者是遇到相關(guān)的問題的時(shí)候,還是不能很好的解決。
因此我覺得很有必要深入的學(xué)習(xí)并且記錄這個(gè)問題,以便在今后的學(xué)習(xí)和使用的過程中回顧。

正文 1. 全局作用域

瀏覽器環(huán)境

所有瀏覽器都支持 window 對(duì)象,它表示瀏覽器窗口,JavaScript 全局對(duì)象、函數(shù)以及變量均自動(dòng)成為 window 對(duì)象的成員。

所以,全局變量是 window 對(duì)象的屬性,全局函數(shù)是 window 對(duì)象的方法,甚至 HTML DOM 的 document 也是 window 對(duì)象的屬性之一。

全局變量是JavaScript里生命周期(一個(gè)變量多長(zhǎng)時(shí)間內(nèi)保持一定的值)最長(zhǎng)的變量,其將跨越整個(gè)程序,可以被程序中的任何函數(shù)方法訪問。

在全局下聲明的變量都會(huì)在window對(duì)象下,都在全局作用域中,我們可以通過window對(duì)象訪問,也可以直接訪問。

Node環(huán)境

全局對(duì)象是global對(duì)象,與window類似,在全局下聲明的所有的變量都在global對(duì)象之下,都在全局作用域中,可以通過glocal訪問,也可以通過變量名訪問。

var name = "clam";
console.log(name); // clam
// 瀏覽器環(huán)境
console.log(window.name); // clam
// Node環(huán)境
console.log(global.name); // clam

聲明方式產(chǎn)生全局變量

在js的任何位置,聲明變量的時(shí)候沒有使用var關(guān)鍵字,這個(gè)變量就是全局變量。

function add(a,b){
    return sum = a+b;
}

add(1,2); // 3
console.log(sum); //3

/* 相當(dāng)于以下代碼

 */
var sum;
function add(a,b){
    return sum = a+b;
}
add(1,2) ; // 3
console.log(sum);
```

全局變量存在于整個(gè)函數(shù)的生命周期中,然而其在全局范圍內(nèi)很容易被篡改,我們?cè)谑褂萌肿兞繒r(shí)一定要小心,盡量不要使用全局變量。在函數(shù)內(nèi)部聲明變量沒有使用var也會(huì)產(chǎn)生全局變量,會(huì)為我們?cè)斐梢恍┗靵y,比如變量覆蓋等。所以,我們?cè)诼暶髯兞康娜魏螘r(shí)候最好都要帶上var。

全局變量存在于程序的整個(gè)生命周期,但并不是通過其引用我們一定可以訪問到全局變量。

2. 詞法作用域

詞法作用域:函數(shù)在定義它們的作用域里運(yùn)行,而不是在執(zhí)行它們的作用域里運(yùn)行。也就是說詞法作用域取決于源碼,通過靜態(tài)分析就能確定,因此詞法作用域也叫做靜態(tài)作用域。with和eval除外,所以只能說JS的作用域機(jī)制非常接近詞法作用域(Lexical scope)。詞法作用域也可以理解為一個(gè)變量的可見性,及其文本表述的模擬值。

var name = "global";

function fun() {
    var name = "clam";
    return name;
}
console.log(fun()); // 輸出:clam
console.log(name); // 輸出:global

在通常情況下,變量的查詢從最近接的綁定上下文開始,向外部逐漸擴(kuò)展,直到查詢到第一個(gè)綁定,一旦完成查找就結(jié)束搜索。就像上例,先查找離它最近的name="clam",查詢完成后就結(jié)束了,將第一個(gè)獲取的值作為變量的值。

3. 動(dòng)態(tài)作用域

動(dòng)態(tài)作用域與詞法作用域相對(duì)而言的,不同于詞法作用域在定義時(shí)確定,動(dòng)態(tài)作用域在執(zhí)行時(shí)確定,其生存周期到代碼片段執(zhí)行為止。動(dòng)態(tài)變量存在于動(dòng)態(tài)作用域中,任何給定的綁定的值,在確定調(diào)用其函數(shù)之前,都是不可知的。

在代碼執(zhí)行時(shí),對(duì)應(yīng)的作用域鏈常常是保持靜態(tài)的。然而當(dāng)遇到with語(yǔ)句、call方法、apply方法和try-catch中的catch時(shí),會(huì)改變作用域鏈的。以with為例,在遇到with語(yǔ)句時(shí),會(huì)將傳入的對(duì)象屬性作為局部變量來顯示,使其便于訪問,也就是說把一個(gè)新的對(duì)象添加到了作用域鏈的頂端,這樣必然影響對(duì)局部標(biāo)志符的解析。當(dāng)with語(yǔ)句執(zhí)行完畢后,會(huì)把作用域鏈恢復(fù)到原始狀態(tài)

// demo
var name = "global";

// 使用with之前
console.log(name); // 輸出:global

with({name:"jeri"}){
    console.log(name); // 輸出:jeri
}

// 使用with之后,作用域鏈恢復(fù)
console.log(name); // 輸出:global

在作用域鏈中有動(dòng)態(tài)作用域時(shí),this引用也會(huì)變得更加復(fù)雜,不再指向第一次創(chuàng)建時(shí)的上下文,而是由調(diào)用者確定。比如在使用apply或call方法時(shí),傳入它們的第一個(gè)參數(shù)就是被引用的對(duì)象。

function globalThis() {
    console.log(this);
}

globalThis(); // 輸出:Window {document: document,external: Object…}
globalThis.call({name:"clam"}); // 輸出:Object {name: "clam"}
globalThis.apply({name:"clam"},[]); // 輸出:Object {name: "clam"}

因?yàn)閠his引用是動(dòng)態(tài)作用域,所以在編程過程中一定要注意this引用的變化,及時(shí)跟蹤this的變動(dòng)。

4 .函數(shù)作用域

函數(shù)作用域,顧名思義就是在定義函數(shù)時(shí)候產(chǎn)生的作用域,這個(gè)作用域也可以稱為局部作用域。和全局作用域相反,函數(shù)作用域一般只在函數(shù)的代碼片段內(nèi)可訪問到,外部不能進(jìn)行變量訪問。在函數(shù)內(nèi)部定義的變量存在于函數(shù)作用域中,其生命周期隨著函數(shù)的執(zhí)行結(jié)束而結(jié)束。

var name = "global";

function fun() {
    var name = "clam";
    console.log(name); // 輸出:jeri

    with ({name:"with"}) {
        console.log(name); // 輸出:with
    }
    console.log(name); // 輸出:clam
}

fun();

// 不能訪問函數(shù)作用域
console.log(name); // 輸出:global
5. 沒有塊級(jí)作用域

不同于其他編程語(yǔ)言,在JavaScript里并沒有塊級(jí)作用域,也就是說在for、if、while等語(yǔ)句內(nèi)部的聲明的變量與在外部聲明是一樣的,在這些語(yǔ)句外部也可以訪問和修改這些變量的值.

function fun() {
    
    if(0 < 2) {
        var name = "clam";
    }    
    console.log(name); // 輸出:clam
    name = "klay";
    console.log(name); // 輸出:klay
}

fun();
6. 作用域鏈

JavaScript里一切皆為對(duì)象,包括函數(shù)。函數(shù)對(duì)象和其它對(duì)象一樣,擁有可以通過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內(nèi)部屬性。其中一個(gè)內(nèi)部屬性是作用域,包含了函數(shù)被創(chuàng)建的作用域中對(duì)象的集合,稱為函數(shù)的作用域鏈,它用來保證對(duì)執(zhí)行環(huán)境有權(quán)訪問的變量和函數(shù)的有序訪問

當(dāng)一個(gè)函數(shù)創(chuàng)建后,它的作用域鏈會(huì)被創(chuàng)建此函數(shù)的作用域中可訪問的數(shù)據(jù)對(duì)象填充。在全局作用域中創(chuàng)建的函數(shù),其作用域鏈會(huì)自動(dòng)成為全局作用域中的一員。而當(dāng)函數(shù)執(zhí)行時(shí),其活動(dòng)對(duì)象就會(huì)成為作用域鏈中的第一個(gè)對(duì)象(活動(dòng)對(duì)象:對(duì)象包含了函數(shù)的所有局部變量、命名參數(shù)、參數(shù)集合以及this)。在程序執(zhí)行時(shí),Javascript引擎會(huì)通過搜索上下文的作用域鏈來解析諸如變量和函數(shù)名這樣的標(biāo)識(shí)符。其會(huì)從作用域鏈的最里面開始檢索,按照由內(nèi)到外的順序,直到完成查找,一旦完成查找就結(jié)束搜索。如果沒有查詢到標(biāo)識(shí)符聲明,則報(bào)錯(cuò)。當(dāng)函數(shù)執(zhí)行結(jié)束,運(yùn)行期上下文被銷毀,活動(dòng)對(duì)象也隨之銷毀。

var name = "global";

function fun() {
    console.log(name); // output:global
    name = "change";
    // 函數(shù)內(nèi)部可以修改全局變量
    console.log(name); // output:change
    // 先查詢活動(dòng)對(duì)象
    var age = "18";
    console.log(age); // output:18
}

fun();

// 函數(shù)執(zhí)行完畢,執(zhí)行環(huán)境銷毀
console.log(age); // output:Uncaught ReferenceError: age is not defined
7. 閉包

閉包是JavaScript的一大謎團(tuán),關(guān)于這個(gè)問題有很多文章進(jìn)行講述,然而依然有相當(dāng)數(shù)量的程序員對(duì)這個(gè)概念理解不透徹。閉包的官方定義為:一個(gè)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù)),因而這些變量也是該表達(dá)式的一部分。

一句話概括就是:閉包就是一個(gè)函數(shù),捕獲作用域內(nèi)的外部綁定。這些綁定是為之后使用而被綁定,即使作用域已經(jīng)銷毀。

自由變量

自由變量與閉包的關(guān)系是,自由變量閉合于閉包的創(chuàng)建。閉包背后的邏輯是,如果一個(gè)函數(shù)內(nèi)部有其他函數(shù),那么這些內(nèi)部函數(shù)可以訪問在這個(gè)外部函數(shù)中聲明的變量(這些變量就稱之為自由變量)。然而,這些變量可以被內(nèi)部函數(shù)捕獲,從高階函數(shù)(返回另一個(gè)函數(shù)的函數(shù)稱為高階函數(shù))中return語(yǔ)句實(shí)現(xiàn)“越獄”,以供以后使用。內(nèi)部函數(shù)在沒有任何局部聲明之前(既不是被傳入,也不是局部聲明)使用的變量就是被捕獲的變量。

function makeAdder(captured) {
    return function(free) {
        var ret = free + captured;
        console.log(ret);
    }
}

var add10 = makeAdder(10);

add10(2); // 輸出:12

從上例可知,外部函數(shù)中的變量captured被執(zhí)行加法的返回函數(shù)捕獲,內(nèi)部函數(shù)從未聲明過captured變量,卻可以引用它。

如果我們?cè)賱?chuàng)建一個(gè)加法器將捕獲到同名變量captured,但有不同的值,因?yàn)檫@個(gè)加法器是在調(diào)用makeAdder之后被創(chuàng)建:

var add16 = makeAdder(16);
 
add16(18); // 輸出:34
 
add10(10); // 輸出:20

變量遮蔽

在JavaScript中,當(dāng)變量在一定作用域內(nèi)聲明,然后在另一個(gè)同名變量在一個(gè)較低的作用域聲明,會(huì)發(fā)生變量的遮蔽。

var name = "clam";
var name = "klay"

function glbShadow() {
    var name = "fun";

    console.log(name); // 輸出:fun
}

glbShadow();

console.log(name); // 輸出:tom

當(dāng)在一個(gè)變量同一作用域內(nèi)聲明了多次時(shí),最后一次聲明會(huì)生效,會(huì)遮蔽以前的聲明。

變量聲明的遮蔽很好理解,然而函數(shù)參數(shù)的遮蔽就略顯復(fù)雜。

var shadowed = 0;

function argShadow(shadowed) {
    var str = ["Value is",shadowed].join(" ");
    console.log(str);
}

argShadow(108); // output:Value is 108

argShadow(); // output:Value is

函數(shù)argShadow的參數(shù)shadowed覆蓋了全局作用域內(nèi)的同名變量。即使沒有傳遞任何參數(shù),仍然綁定的是shadowed,并沒有訪問到全局變量shadowed = 0

任何情況下,離得最近的變量綁定優(yōu)先級(jí)最高。

var shadowed = 0;

function varShadow(shadowed) {
    var shadowed = 123;
    var str = ["Value is",shadowed].join(" ");
    console.log(str);
}

varShadow(108); // output:Value is 123

varShadow(); // output:Value is 123

varShadow(108)打印出來的并不是108而是123,即使沒有參數(shù)傳入也是打印的123,先訪問離得最近的變量綁定。

遮蔽變量同樣發(fā)生在閉包內(nèi)部

function captureShadow(shadowed) {

    console.log(shadowed); // output:108
    
    return function(shadowed) {

        console.log(shadowed); // output:2
        var ret = shadowed + 1;
        console.log(ret); // output:3
    }
}

var closureShadow = captureShadow(108);

closureShadow(2);

在編寫JavaScript代碼時(shí),因?yàn)樽兞空诒螘?huì)使很多變量綁定超出我們的控制,我們應(yīng)盡量避免變量遮蔽,一定要注意變量命名。

典型的誤區(qū)

首先看下面的代碼:

var test = function() {
    var ret = [];

    for(var i = 0; i < 5; i++) {
        ret[i] = function() {
            return i;  
        }
    }

    return ret;
};
var test0 = test()[0]();
console.log(test0); // 輸出:5

var test1 = test()[1]();
console.log(test1); //輸出:5

從上面的例子可知,test這個(gè)函數(shù)執(zhí)行之后返回一個(gè)函數(shù)數(shù)組,表面上看數(shù)組內(nèi)的每個(gè)函數(shù)都應(yīng)該返回自己的索引值,然而并不是如此。當(dāng)外部函數(shù)執(zhí)行完畢后,外部函數(shù)雖然其執(zhí)行環(huán)境已經(jīng)銷毀,但閉包依然保留著對(duì)其中變量綁定的引用,仍然駐留在內(nèi)存之中。當(dāng)外部函數(shù)執(zhí)行完畢之后,才會(huì)執(zhí)行內(nèi)部函數(shù),而這時(shí)內(nèi)部函數(shù)捕獲的變量綁定已經(jīng)是外部函數(shù)執(zhí)行之后的最終變量值了,所以這些函數(shù)都引用的是同一個(gè)變量i=5

// 更加優(yōu)雅的描述方式
for(var i = 0; i < 5; i++) {

    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

// 每隔1秒輸出一個(gè)5

按照我們的推斷,上例應(yīng)該輸出1,2,3,4,5。然而,事實(shí)上輸出的是連續(xù)5個(gè)5。為什么出現(xiàn)這種詭異的狀況呢?其本質(zhì)上還是由閉包特性造成的,閉包可以捕獲外部作用域的變量綁定。

上面這個(gè)函數(shù)片段在執(zhí)行時(shí),其內(nèi)部函數(shù)和外部函數(shù)并不是同步執(zhí)行的,因?yàn)楫?dāng)調(diào)用setTimeout時(shí)會(huì)有一個(gè)延時(shí)事件排入隊(duì)列,等所有同步代碼執(zhí)行完畢后,再依次執(zhí)行隊(duì)列中的延時(shí)事件,而這個(gè)時(shí)候 i 已經(jīng) 是5了。

那怎么解決這個(gè)問題呢?我們是不是可以在每個(gè)循環(huán)執(zhí)行時(shí),給內(nèi)部函數(shù)傳進(jìn)一個(gè)變量的拷貝,使其在每次創(chuàng)建閉包時(shí),都捕獲一個(gè)變量綁定。因?yàn)槲覀兠看蝹鲄⒉煌敲疵看尾东@的變量綁定也是不同的,也就避免了最后輸出5個(gè)5的狀況。實(shí)例如下:

for(var i = 0; i < 5; i++) {

    (function(j) {

        setTimeout(function() {
            console.log(j);  
        }, 1000);
    })(i);
}

// 輸出:0,1,2,3,4

閉包具有非常強(qiáng)大的功能,函數(shù)內(nèi)部可以引用外部的參數(shù)和變量,但其參數(shù)和變量不會(huì)被垃圾回收機(jī)制回,常駐內(nèi)存,會(huì)增大內(nèi)存使用量,使用不當(dāng)很容易造成內(nèi)存泄露。但,閉包也是javascript語(yǔ)言的一大特點(diǎn),主要應(yīng)用閉包場(chǎng)合為:設(shè)計(jì)私有的方法和變量

模擬私有變量

從上文的敘述我們知道,變量的捕獲發(fā)生在創(chuàng)建閉包的時(shí)候,那么我們可以把閉包捕獲到的變量作為私有變量。

var closureDemo = (function() {
    var PRIVATE = 0;

    return {
        inc:function(n) {
            return PRIVATE += n;
        },
        dec:function(n) {
            return PRIVATE -= n;
        }
    };
})();

var testInc = closureDemo.inc(10);
//console.log(testInc);
// 輸出:10

var testDec = closureDemo.dec(7);
//console.log(testDec);
// 輸出:3

closureDemo.div = function(n) {
    return PRIVATE / n;
};

var testDiv = closureDemo.div(3);
console.log(testDiv);
//輸出:Uncaught ReferenceError: PRIVATE is not defined

自執(zhí)行函數(shù)closureDemo執(zhí)行完畢之后,自執(zhí)行函數(shù)作用域和PRIVATE隨之銷毀,但PRIVATE仍滯留在內(nèi)存中,也就是加入到closureDemo.incclosureDemo.dec的作用域鏈中,閉包也就完成了變量的捕獲。但之后新加入的closureDemo.div并不能在作用域中繼續(xù)尋找到PRIVATE了。因?yàn)椋瘮?shù)只有被調(diào)用時(shí)才會(huì)執(zhí)行函數(shù)里面的代碼,變量的捕獲也只發(fā)生在創(chuàng)建閉包時(shí),所以之后新加入的div方法并不能捕獲PRIVATE

創(chuàng)建特權(quán)方法

通過閉包我們可以創(chuàng)建私有作用域,那么也就可以創(chuàng)建私有變量和私有函數(shù)。創(chuàng)建私有函數(shù)的方式和聲明私有變量方法一致,只要在函數(shù)內(nèi)部聲明函數(shù)就可以了。當(dāng)然,既然可以模擬私有變量和私有函數(shù),我們也可以利用閉包這個(gè)特性,創(chuàng)建特權(quán)方法。

(function() {

    // 私有變量和私有函數(shù)
    var privateVar = 10;

    function privateFun() {
        return false;
    };

    // 構(gòu)造函數(shù)
    MyObj = function() {

    };

    // 公有/特權(quán)方法
    MyObj.prototype.publicMethod = function() {
        privateVar ++;
        return privateFun();
    }
})();

上面這個(gè)實(shí)例創(chuàng)建了一個(gè)私有作用域,并封裝了一個(gè)構(gòu)造函數(shù)和對(duì)應(yīng)的方法。需要注意的是在上面的實(shí)例中,在聲明MyObj這個(gè)函數(shù)時(shí),使用的是不帶var的函數(shù)表達(dá)式,我們希望產(chǎn)生的是一個(gè)全局函數(shù)而不是局部的,不然我們依然在外部無法訪問。所以,MyObj就成為了一個(gè)全局變量,能夠在外部進(jìn)行訪問,我們?cè)谠蜕隙x的方法publicMethod也就可以使用,通過這個(gè)方法我們也就可以訪問私有函數(shù)和私有變量了。

總的來說,因?yàn)殚]包奇特的特性,可以通過它實(shí)現(xiàn)一些強(qiáng)大的功能。但,我們?cè)谌粘>幊讨校惨_的使用閉包,要時(shí)刻注意回收不用的變量,避免內(nèi)存泄露。

總結(jié)

感謝原文作者,并且附上原文鏈接:JavaScript之作用域與閉包詳解

花費(fèi)了數(shù)小時(shí)來研讀這篇分享,對(duì)javascript中的作用域和閉包的問題有了比較深刻的認(rèn)識(shí)和學(xué)習(xí)。在今后的編碼過程中,希望可以將今天學(xué)到的東西及時(shí)應(yīng)用,反復(fù)的鞏固。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/89207.html

相關(guān)文章

  • 深入javascript——作用域和閉包

    摘要:注意由于閉包會(huì)額外的附帶函數(shù)的作用域內(nèi)部匿名函數(shù)攜帶外部函數(shù)的作用域,因此,閉包會(huì)比其它函數(shù)多占用些內(nèi)存空間,過度的使用可能會(huì)導(dǎo)致內(nèi)存占用的增加。 作用域和作用域鏈?zhǔn)莏avascript中非常重要的特性,對(duì)于他們的理解直接關(guān)系到對(duì)于整個(gè)javascript體系的理解,而閉包又是對(duì)作用域的延伸,也是在實(shí)際開發(fā)中經(jīng)常使用的一個(gè)特性,實(shí)際上,不僅僅是javascript,在很多語(yǔ)言中都...

    oogh 評(píng)論0 收藏0
  • javascript作用域和閉包之我見

    摘要:查詢是在作用域鏈中,一級(jí)級(jí)的往上查找該變量的引用。作用域和作用域鏈作用域的概念,應(yīng)該兩張圖幾句話就能解釋吧。這個(gè)建筑代表程序中的嵌套作用域鏈。一層嵌一層的作用域形成了作用域鏈,變量在作用域鏈中的函數(shù)內(nèi)得到了自己的定義。 javascript作用域和閉包之我見 看了《你不知道的JavaScript(上卷)》的第一部分——作用域和閉包,感受頗深,遂寫一篇讀書筆記加深印象。路過的大牛歡迎指點(diǎn)...

    SoapEye 評(píng)論0 收藏0
  • JavaScript作用域和閉包

    摘要:依然持有對(duì)該作用域的引用,而這個(gè)引用就叫作閉包。循環(huán)和閉包正常情況下,我們對(duì)這段代碼行為的預(yù)期是分別輸出數(shù)字,每秒一次,每次一個(gè)。 一、作用域 作用域共有兩種主要的工作模型:第一種是最為普遍的,被大多數(shù)編程語(yǔ)言所采用的詞法作用域,另外一種叫作動(dòng)態(tài)作用域; JavaScript所采用的作用域模式是詞法作用域。 1.詞法作用域 詞法作用域意味著作用域是由書寫代碼時(shí)函數(shù)聲明的位置來決定...

    animabear 評(píng)論0 收藏0
  • JavaScript作用域和閉包

    摘要:依然持有對(duì)該作用域的引用,而這個(gè)引用就叫作閉包。循環(huán)和閉包正常情況下,我們對(duì)這段代碼行為的預(yù)期是分別輸出數(shù)字,每秒一次,每次一個(gè)。 一、作用域 作用域共有兩種主要的工作模型:第一種是最為普遍的,被大多數(shù)編程語(yǔ)言所采用的詞法作用域,另外一種叫作動(dòng)態(tài)作用域; JavaScript所采用的作用域模式是詞法作用域。 1.詞法作用域 詞法作用域意味著作用域是由書寫代碼時(shí)函數(shù)聲明的位置來決定...

    CNZPH 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<