午夜国产狂喷潮在线观看|国产AⅤ精品一区二区久久|中文字幕AV中文字幕|国产看片高清在线

    深入理解javascript作用域和閉包
    來源:易賢網(wǎng) 閱讀:803 次 日期:2014-10-15 15:30:04
    溫馨提示:易賢網(wǎng)小編為您整理了“深入理解javascript作用域和閉包”,方便廣大網(wǎng)友查閱!

    作用域

    作用域是一個變量和函數(shù)的作用范圍,javascript中函數(shù)內(nèi)聲明的所有變量在函數(shù)體內(nèi)始終是可見的,在javascript中有全局作用域和局部作用域,但是沒有塊級作用域,局部變量的優(yōu)先級高于全局變量,通過幾個示例來了解下javascript中作用域的那些“潛規(guī)則”(這些也是在前端面試中經(jīng)常問到的問題)。

    1. 變量聲明提前

    示例1:

    var scope="global";

    function scopeTest(){

    console.log(scope);

    var scope="local"

    }

    scopeTest(); //undefined

    此處的輸出是undefined,并沒有報錯,這是因為在前面我們提到的函數(shù)內(nèi)的聲明在函數(shù)體內(nèi)始終可見,上面的函數(shù)等效于:

    var scope="global";

    function scopeTest(){

    var scope;

    console.log(scope);

    scope="local"

    }

    scopeTest(); //local

    注意,如果忘記var,那么變量就被聲明為全局變量了。

    2. 沒有塊級作用域

    和其他我們常用的語言不同,在Javascript中沒有塊級作用域:

    function scopeTest() {

    var scope = {};

    if (scope instanceof Object) {

    var j = 1;

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

    //console.log(i);

    }

    console.log(i); //輸出10

    }

    console.log(j);//輸出1

    }

    在javascript中變量的作用范圍是函數(shù)級的,即在函數(shù)中所有的變量在整個函數(shù)中都有定義,這也帶來了一些我們稍不注意就會碰到的“潛規(guī)則”:

    >

    var scope = "hello";

    function scopeTest() {

    console.log(scope);//①

    var scope = "no";

    console.log(scope);//②

    }

    在①處輸出的值竟然是undefined,簡直喪心病狂啊,我們已經(jīng)定義了全局變量的值啊,這地方不應(yīng)該為hello嗎?其實,上面的代碼等效于:

    var scope = "hello";

    function scopeTest() {

    var scope;

    console.log(scope);//①

    scope = "no";

    console.log(scope);//②

    }

    聲明提前、全局變量優(yōu)先級低于局部變量,根據(jù)這兩條規(guī)則就不難理解為什么輸出undefined了。

    作用域鏈

    在javascript中,每個函數(shù)都有自己的執(zhí)行上下文環(huán)境,當(dāng)代碼在這個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的作用域鏈,作用域鏈?zhǔn)且粋€對象列表或?qū)ο箧湥WC了變量對象的有序訪問。

    作用域鏈的前端是當(dāng)前代碼執(zhí)行環(huán)境的變量對象,常被稱之為“活躍對象”,變量的查找會從第一個鏈的對象開始,如果對象中包含變量屬性,那么就停止查找,如果沒有就會繼續(xù)向上級作用域鏈查找,直到找到全局對象中:

    作用域鏈的逐級查找,也會影響到程序的性能,變量作用域鏈越長對性能影響越大,這也是我們盡量避免使用全局變量的一個主要原因。

    閉包

    基礎(chǔ)概念

    作用域是理解閉包的一個前提,閉包是指在當(dāng)前作用域內(nèi)總是能訪問外部作用域中的變量。

    function createClosure(){

    var name = "jack";

    return {

    setStr:function(){

    name = "rose";

    },

    getStr:function(){

    return name + ":hello";

    }

    }

    }

    var builder = new createClosure();

    builder.setStr();

    console.log(builder.getStr()); //rose:hello

    上面的示例在函數(shù)中返回了兩個閉包,這兩個閉包都維持著對外部作用域的引用,因此不管在哪調(diào)用總是能夠訪問外部函數(shù)中的變量。在一個函數(shù)內(nèi)部定義的函數(shù),會將外部函數(shù)的活躍對象添加到自己的作用域鏈中,因此上面實例中通過內(nèi)部函數(shù)能夠訪問外部函數(shù)的屬性,這也是javascript模擬私有變量的一種方式。

    注意:由于閉包會額外的附帶函數(shù)的作用域(內(nèi)部匿名函數(shù)攜帶外部函數(shù)的作用域),因此,閉包會比其它函數(shù)多占用些內(nèi)存空間,過度的使用可能會導(dǎo)致內(nèi)存占用的增加。

    閉包中的變量

    在使用閉包時,由于作用域鏈機制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個值,這引起的一個副作用就是如果內(nèi)部函數(shù)在一個循環(huán)中,那么變量的值始終為最后一個值。

    //該實例不太合理,有一定延遲因素,此處主要為了說明閉包循環(huán)中存在的問題

    function timeManage() {

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

    setTimeout(function() {

    console.log(i);

    },1000)

    };

    }

    上面的程序并沒有按照我們預(yù)期的輸入1-5的數(shù)字,而是5次全部輸出了5。再來看一個示例:

    function createClosure(){

    var result = [];

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

    result[i] = function(){

    return i;

    }

    }

    return result;

    }

    調(diào)用createClosure()[0]()返回的是5,createClosure()[4]()返回值仍然是5。通過以上兩個例子可以看出閉包在帶有循環(huán)的內(nèi)部函數(shù)使用時存在的問題:因為每個函數(shù)的作用域鏈中都保存著對外部函數(shù)(timeManage、createClosure)的活躍對象,因此,他們都引用著同一變量i,當(dāng)外部函數(shù)返回時,此時的i值為5,所以內(nèi)部的每個函數(shù)i的值也為5。

    那么如何解決這個問題呢?我們可以通過匿名包裹器(匿名自執(zhí)行函數(shù)表達(dá)式)來強制返回預(yù)期的結(jié)果:

    9

    function timeManage() {

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

    (function(num) {

    setTimeout(function() {

    console.log(num);

    }, 1000);

    })(i);

    }

    }

    或者在閉包匿名函數(shù)中再返回一個匿名函數(shù)賦值:

    function timeManage() {

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

    setTimeout((function(e) {

    return function() {

    console.log(e);

    }

    })(i), 1000)

    }

    }

    //timeManager();輸出1,2,3,4,5

    function createClosure() {

    var result = [];

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

    result[i] = function(num) {

    return function() {

    console.log(num);

    }

    }(i);

    }

    return result;

    }

    //createClosure()[1]()輸出1;createClosure()[2]()輸出2

    無論是匿名包裹器還是通過嵌套匿名函數(shù)的方式,原理上都是由于函數(shù)是按值傳遞,因此會將變量i的值復(fù)制給實參num,在匿名函數(shù)的內(nèi)部又創(chuàng)建了一個用于返回num的匿名函數(shù),這樣每個函數(shù)都有了一個num的副本,互不影響了。

    閉包中的this

    在閉包中使用this時要特別注意,稍微不慎可能會引起問題。通常我們理解this對象是運行時基于函數(shù)綁定的,全局函數(shù)中this對象就是window對象,而當(dāng)函數(shù)作為對象中的一個方法調(diào)用時,this等于這個對象(TODO 關(guān)于this做一次整理)。由于匿名函數(shù)的作用域是全局性的,因此閉包的this通常指向全局對象window:

    9

    var scope = "global";

    var object = {

    scope:"local",

    getScope:function(){

    return function(){

    return this.scope;

    }

    }

    }

    調(diào)用object.getScope()()返回值為global而不是我們預(yù)期的local,前面我們說過閉包中內(nèi)部匿名函數(shù)會攜帶外部函數(shù)的作用域,那為什么沒有取得外部函數(shù)的this呢?每個函數(shù)在被調(diào)用時,都會自動創(chuàng)建this和arguments,內(nèi)部匿名函數(shù)在查找時,搜索到活躍對象中存在我們想要的變量,因此停止向外部函數(shù)中的查找,也就永遠(yuǎn)不可能直接訪問外部函數(shù)中的變量了??傊?,在閉包中函數(shù)作為某個對象的方法調(diào)用時,要特別注意,該方法內(nèi)部匿名函數(shù)的this指向的是全局變量。

    幸運的是我們可以很簡單的解決這個問題,只需要把外部函數(shù)作用域的this存放到一個閉包能訪問的變量里面即可:

    var scope = "global";

    var object = {

    scope:"local",

    getScope:function(){

    var that = this;

    return function(){

    return that.scope;

    }

    }

    }

    object.getScope()()返回值為local。

    內(nèi)存與性能

    由于閉包中包含與函數(shù)運行期上下文相同的作用域鏈引用,因此,會產(chǎn)生一定的負(fù)面作用,當(dāng)函數(shù)中活躍對象和運行期上下文銷毀時,由于必要仍存在對活躍對象的引用,導(dǎo)致活躍對象無法銷毀,這意味著閉包比普通函數(shù)占用更多的內(nèi)存空間,在IE瀏覽器下還可能會導(dǎo)致內(nèi)存泄漏的問題,如下:

    function bindEvent(){

    var target = document.getElementById("elem");

    target.onclick = function(){

    console.log(target.name);

    }

    }

    上面例子中匿名函數(shù)對外部對象target產(chǎn)生一個引用,只要是匿名函數(shù)存在,這個引用就不會消失,外部函數(shù)的target對象也不會被銷毀,這就產(chǎn)生了一個循環(huán)引用。解決方案是通過創(chuàng)建target.name副本減少對外部變量的循環(huán)引用以及手動重置對象:

    function bindEvent(){

    var target = document.getElementById("elem");

    var name = target.name;

    target.onclick = function(){

    console.log(name);

    }

    target = null;

    }

    閉包中如果存在對外部變量的訪問,無疑增加了標(biāo)識符的查找路徑,在一定的情況下,這也會造成性能方面的損失。解決此類問題的辦法我們前面也曾提到過:盡量將外部變量存入到局部變量中,減少作用域鏈的查找長度。

    總結(jié):閉包不是javascript獨有的特性,但是在javascript中有其獨特的表現(xiàn)形式,使用閉包我們可以在javascript中定義一些私有變量,甚至模仿出塊級作用域,但閉包在使用過程中,存在的問題我們也需要了解,這樣才能避免不必要問題的出現(xiàn)。

    更多信息請查看IT技術(shù)專欄

    更多信息請查看腳本欄目
    易賢網(wǎng)手機網(wǎng)站地址:深入理解javascript作用域和閉包
    由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!

    2025國考·省考課程試聽報名

    • 報班類型
    • 姓名
    • 手機號
    • 驗證碼
    關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 新媒體/短視頻平臺 | 手機站點 | 投訴建議
    工業(yè)和信息化部備案號:滇ICP備2023014141號-1 云南省教育廳備案號:云教ICP備0901021 滇公網(wǎng)安備53010202001879號 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號
    云南網(wǎng)警備案專用圖標(biāo)
    聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號:hfpxwx
    咨詢QQ:1093837350(9:00—18:00)版權(quán)所有:易賢網(wǎng)
    云南網(wǎng)警報警專用圖標(biāo)