<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆 - 16  文章 - 42  trackbacks - 0
    <2006年10月>
    24252627282930
    1234567
    891011121314
    15161718192021
    22232425262728
    2930311234

    失業(yè)中…

    常用鏈接

    留言簿(7)

    隨筆檔案(16)

    技術(shù)Blog

    搜索

    •  

    最新隨筆

    最新評論

    閱讀排行榜

    評論排行榜

    8.8. 函數(shù)作用域與閉包
    ??????? 如第四章所述,JavaScript函數(shù)的函數(shù)體在局部作用域中執(zhí)行,局部作用域不同于全局作用域.本章將解釋這些內(nèi)容和相關(guān)的作用域問題,包括閉包.[*]

    [*] 本章包含超前的內(nèi)容,如果你是第一次閱讀,可以跳過.

    8.8.1. 詞法作用域(Lexical Scoping)
    ??????? JavaScript中的函數(shù)是基于詞法作用域的,而不是動態(tài)作用域.這句話的意思是JavaScript中的函數(shù)運行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里.定義一個函數(shù)時,當(dāng)前作用域鏈被保存起來并成為該函數(shù)內(nèi)部狀態(tài)的一部分.作用域鏈的頂層(最初一層)是由全局對象構(gòu)成的,這和詞法作用域沒什么明顯的關(guān)聯(lián).然而,當(dāng)你定義一個嵌套函數(shù)時,作用域鏈將包含外層函數(shù)(嵌套函數(shù)的外層函數(shù).原文:the containing function).這就意味著,被嵌套的函數(shù)可以訪問外層函數(shù)的所有參數(shù)和局部變量.

    ??????? 注意:盡管在一個函數(shù)定義的時候,作用域鏈就已經(jīng)固定了,但是作用域鏈中定義的屬性并不是固定的.作用域鏈?zhǔn)?活的"("live"),當(dāng)函數(shù)被調(diào)用的時候,它有權(quán)訪問任何當(dāng)前被關(guān)聯(lián)的數(shù)據(jù).

    8.8.2. 調(diào)用對象(The Call Object)
    ??????? 當(dāng)JavaScript解釋器調(diào)用函數(shù)的時候,首先,它把作用域設(shè)置到作用域鏈,在函數(shù)被定義的時候,該作用域鏈已經(jīng)有效.接下來,解釋器添加一個叫做調(diào)用對象(ECMAScript規(guī)范使用術(shù)語:activation object,活動對象)的對象到作用域鏈的頭部.引用Arguments對象的arguments屬性為函數(shù)初始化調(diào)用對象.接下來,添加函數(shù)的命名參數(shù)到調(diào)用對象.所有用var語句定義的局部變量也都在這個對象中定義.因為調(diào)用對象在作用域鏈的頭部,局部變量,函數(shù)參數(shù)和參數(shù)對象都在函數(shù)的作用域內(nèi).也就是說它們隱藏了所有同名的在更早的作用域中定義的屬性.

    注意:與arguments不同,this是關(guān)鍵字,而不是調(diào)用對象的一個屬性.

    8.8.3. 調(diào)用對象作為命名空間(The Call Object as a Namespace)
    ??????? 有時,用定義一個簡單函數(shù)的方法創(chuàng)建一個調(diào)用對象是很有用的,這個調(diào)用對象可以扮演一個臨時命名空間的角色,如此一來,你定義的變量和創(chuàng)建的屬性都不會破壞全局命名空間.例如:假設(shè)你有一個Javascrip代碼文件,你希望把它用到很多不同的Javascript程序中(或者,用于客戶端Javascript,在很多不同的web頁上).假設(shè)這些代碼像其它代碼一樣定義了中間變量來保存計算結(jié)果.現(xiàn)在的問題是因為這些代碼將用于很多不同的程序,你無法知道此變量是否和其它引入該文件的程序的變量相沖突.

    ??????? 解決的方法是把代碼放到函數(shù)里,然后調(diào)用這個函數(shù).如此一來,變量是被定義在函數(shù)的調(diào)用對象中:

    function ?init()? {
    ????
    // ?代碼從這里開始
    ???? // ?任何變量聲明都會成為調(diào)用對象的屬性
    ???? // ?如此不會破壞全局命名空間.
    }

    init();??
    // ?不要忘了調(diào)用這個函數(shù)哦!

    ??????? 這段代碼只給全局命名空間添加了一個"init"屬性,該屬性引用init函數(shù).如果定義一個函數(shù)還嫌太多,那么你可以用一個表達式定義和調(diào)用一個匿名函數(shù).像這樣的JavaScript語法如下:

    ( function ()? {?? // ?這個函數(shù)沒有名字.
    ???? // ?代碼從這里開始
    ???? // ?任何變量聲明都會成為調(diào)用對象的屬性
    ???? // ?如此不會破壞全局命名空間.
    }
    )();?????????? // 結(jié)束函數(shù)直接量,并調(diào)用該函數(shù).


    ??????? 注意:函數(shù)直接量外面的括號是JavaScript語法所必需的.

    8.8.4. 嵌套函數(shù)作為閉包(Nested Functions as Closures)
    ??????? JavaScript允許函數(shù)嵌套,允許把函數(shù)作為數(shù)據(jù),允許使用詞法作用域,把這些結(jié)合使用能創(chuàng)造出功能強大的令人驚奇的效果.讓我們開始探索,考慮一下函數(shù)g被定義在函數(shù)f中.當(dāng)f被調(diào)用的時候,作用域鏈由為函數(shù)f調(diào)用生成的調(diào)用對象跟隨在全局對象之后構(gòu)成.g函數(shù)被定義在f函數(shù)里,因此,這個作用域鏈作為g函數(shù)定義的一部分被保存起來.當(dāng)g函數(shù)被調(diào)用的時候,作用域鏈包括三個部分:g函數(shù)自己的調(diào)用對象,f函數(shù)的調(diào)用對象和全局對象.

    ??????? 嵌套函數(shù)在相同的它們被定義的詞法作用域里被調(diào)用的時候是很容易理解的.例如,下面的代碼并沒有什么特別:

    var ?x? = ? " global " ;
    function ?f()? {
    ????
    var ?x? = ? " local " ;
    ????
    function ?g()? {?alert(x);?}
    ????g();
    }

    f();??
    // ?調(diào)用這個函數(shù)顯示?"local"

    ??????? 然而,在JavaScript中,函數(shù)可以像其它值一樣作為數(shù)據(jù),因此可以在函數(shù)中返回一個函數(shù),賦值給對象的屬性,存儲在數(shù)組中等等.這也沒有什么特別的,除了嵌套的函數(shù)被調(diào)用的時候.考慮下面的代碼,它包含一個返回嵌套函數(shù)的函數(shù).每次被調(diào)用的時候,它都返回一個函數(shù).被返回的函數(shù)的JavaScript代碼總是相同的,但是,因為每次調(diào)用外層函數(shù)時的參數(shù)不同,每次被調(diào)用的時候,它(被返回的嵌套函數(shù))創(chuàng)建的作用域也有些許不同.(也就是說,對于外層函數(shù)的每次調(diào)用,都會在作用域鏈中產(chǎn)生一個不同的調(diào)用對象.)如果你把返回函數(shù)保存在數(shù)組中,然后每一個調(diào)用一次,你將發(fā)現(xiàn)每一個函數(shù)都返回不同的值.因為每一個函數(shù)都由相同的JavaScript代碼構(gòu)成,并且每一次都是從相同的作用域中調(diào)用,所以,唯一能造成返回值不同的因素就是函數(shù)被定義的作用域:

    // ?每次調(diào)用這個函數(shù)的時候返回一個函數(shù)
    //
    ?函數(shù)被定義的作用域在每次調(diào)用時都不同
    function ?makefunc(x)? {
    ????
    return ? function ()? {? return ?x;?}
    }


    // ?調(diào)用幾次?makefunc()?,?把結(jié)果保存到數(shù)組中:
    var ?a? = ?[makefunc( 0 ),?makefunc( 1 ),?makefunc( 2 )];

    // ?現(xiàn)在調(diào)用這些函數(shù)并顯示結(jié)果.
    //
    ?盡管函數(shù)體是相同的,但是作用域是不同的,所以每次調(diào)用返回不同的結(jié)果:
    alert(a[ 0 ]());?? // ?Displays?0
    alert(a[ 1 ]());?? // ?Displays?1
    alert(a[ 2 ]());?? // ?Displays?2

    ??????? 這段代碼的結(jié)果是正確的,是根據(jù)詞法作用域規(guī)則的嚴(yán)謹(jǐn)?shù)膽?yīng)用所期待的:函數(shù)被執(zhí)行在它被定義的作用域內(nèi).然而,這些結(jié)果令人吃驚的原因是,你期待的局部作用域在定義它們的函數(shù)退出的時候就不存在了.事實上,這是正常現(xiàn)象.當(dāng)函數(shù)被調(diào)用的時候,解釋器創(chuàng)建一個調(diào)用對象并把它放到作用域鏈的頭部.當(dāng)函數(shù)退出的時候,解釋器從作用域鏈上刪除這個調(diào)用對象.在沒有嵌套函數(shù)被定義的時候,調(diào)用對象是唯一引用作用域鏈的對象.當(dāng)調(diào)用對象從作用域鏈上刪除時,就再也沒有對它的引用了,它將被GC(garbage collected)回收.

    ??????? 但是,嵌套函數(shù)改變了這些.如果嵌套函數(shù)被創(chuàng)建,這個函數(shù)的定義引用調(diào)用對象,因為這個調(diào)用對象是函數(shù)被定義的作用域鏈的頂部.如果嵌套函數(shù)只是被外層函數(shù)使用,對嵌套函數(shù)的唯一引用在調(diào)用對象里.當(dāng)外層函數(shù)返回時,只有嵌套函數(shù)引用調(diào)用對象,調(diào)用對象引用嵌套函數(shù),除此之外,再也沒有其它的什么引用任何一個,因此,這兩個對象就只能被GC使用了.

    ??????? 如果你保存了一個嵌套函數(shù)的引用到全局作用域,情況就有所不同了.你把嵌套函數(shù)作為外層函數(shù)的返回值,或者把嵌套函數(shù)保存為其它對象的屬性.在這種情況下,就有了一個對嵌套函數(shù)的外部引用,所以,嵌套函數(shù)在它的外部函數(shù)的調(diào)用對象中保持著它的引用.結(jié)果是,為外層函數(shù)調(diào)用生成的調(diào)用對象仍然有效,外層函數(shù)的參數(shù)和變量的名字和值也保留在這個調(diào)用對象里.JavaScript代碼無法直接訪問調(diào)用對象,但是,它定義的作為作用域鏈的一部分的屬性仍用于嵌套函數(shù)的任何調(diào)用.(注意:如果外層函數(shù)保存了兩個嵌套函數(shù)的全局引用,那么就有兩個嵌套函數(shù)共享同一個調(diào)用對象,通過調(diào)用一個函數(shù)對調(diào)用對象的改變對另一個嵌套函數(shù)是可見的)

    ??????? JavaScript函數(shù)是被執(zhí)行的代碼和執(zhí)行它們的作用域的組合.這個代碼和作用域的組合在計算機科學(xué)著作中被稱作:閉包(closure).所有的JavaScript函數(shù)都是閉包.然而,這些閉包只在象上面討論的那樣時才有趣:當(dāng)一個嵌套的函數(shù)被輸出到它被定義的作用域之外.只有嵌套函數(shù)被如此使用時,才被明確的稱為閉包.

    ??????? 閉包是有趣并且功能強大的技術(shù).盡管它們不會被普通的使用在日常JavaScript編程中,它仍然值得我們?nèi)ダ斫?如果你理解閉包,你理解作用域鏈和函數(shù)調(diào)用對象,那么,你才能真正的稱自己為高級JavaScript程序員(JSer :) ).

    8.8.4.1. 閉包的例子(Closure examples)
    ??????? 有時,你會想寫一個函數(shù),希望它能跨調(diào)用保存一個值.這個值不能保存在局部變量里,因為調(diào)用對象不會跨調(diào)用存在.全局變量是可以的,但是它會破壞全局命名空間.在8.6.3.章節(jié)中,我展現(xiàn)了一個名為uniqueInteger()的函數(shù),它用一個屬性保存這個恒久的值.你可以用閉包更進一步實現(xiàn),創(chuàng)建一個恒久的私有的變量.下面是不用閉包寫的一個函數(shù):

    // ?每次調(diào)用返回一個不同的整數(shù)
    uniqueID? = ? function ()? {
    ????
    if ?( ! arguments.callee.id)?arguments.callee.id? = ? 0 ;
    ????
    return ?arguments.callee.id ++ ;
    }
    ;

    ??????? 這種方法的問題在于任何人都能設(shè)置這個uniqueID.id為0,而破壞了該函數(shù)不能返回同一個值兩次的約定.你可以通過保存這個恒久值到一個只有你自己的函數(shù)有權(quán)訪問的閉包里的方法來防止別人設(shè)置:

    uniqueID? = ?( function ()? {?? // ?這個函數(shù)的調(diào)用對象保存值
    ???? var ?id? = ? 0 ;??????????? // ?這是私有恒久的那個值
    ???? // ?外層函數(shù)返回一個有權(quán)訪問恒久值的嵌套的函數(shù)
    ???? // ?那就是我們保存在變量uniqueID里的嵌套函數(shù).
    ???? return ? function ()? {? return ?id ++ ;?} ;?? // ?返回,自加.
    }
    )();? // ?在定義后調(diào)用外層函數(shù).

    ??????? 例子8-6是第二個閉包的例子.它示范的是像第一個一樣的私有恒久變量,但是這個能被多個函數(shù)共享.

    ??????? Example 8-6. Private properties with closures

    // ?這個函數(shù)為對象o的指定名稱的屬性添加了訪問方法
    //
    ?方法名為:get<name>和set<name>.
    //
    ?如果提供了一個判斷函數(shù),setter方法將在保存前判斷參數(shù)是不是有效的
    //
    ?如果檢驗失敗,setter方法拋出一個異常
    //
    ?這個函數(shù)的與眾不同之處在于,用getter和setter方法操作的屬性值并不是存儲在對象o里面,
    //
    ?相反的,值被存儲在函數(shù)的局部變量里.
    //
    ?getter和setter方法也被定義為函數(shù)的局部方法,因此有權(quán)訪問這個局部變量.
    //
    ?注意:對于兩個訪問方法,該值是私有的,除了setter方法,無法修改或設(shè)置它.
    function ?makeProperty(o,?name,?predicate)? {
    ????
    var ?value;?? // ?This?is?the?property?value

    ????
    // ?getter方法只是簡單的返回值.
    ????o[ " get " ? + ?name]? = ? function ()? {? return ?value;?} ;

    ????
    // ?setter保存值,如果校驗失敗則拋出異常
    ????o[ " set " ? + ?name]? = ? function (v)? {
    ????????
    if ?(predicate? && ? ! predicate(v))
    ????????????
    throw ? " set " ? + ?name? + ? " :?invalid?value? " ? + ?v;
    ????????
    else
    ????????????value?
    = ?v;
    ????}
    ;
    }


    // ?下面的代碼演示makeProperty()?方法.
    var ?o? = ? {} ;?? // ?這是一個空對象

    // ?添加屬性訪問方法getName()?和?setName()
    //
    ?確保只允許字符串值
    makeProperty(o,? " Name " ,? function (x)? {? return ? typeof ?x? == ? " string " ;?} );

    o.setName(
    " Frank " );?? // ?設(shè)置屬性值
    print(o.getName());?? // ?獲得屬性值
    o.setName( 0 );???????? // ?試圖設(shè)置錯誤類型的值

    ??????? 我知道的最簡單最有用的使用閉包的例子是Steve Yen創(chuàng)建的斷點程序,它發(fā)布在 http://trimpath.com ,是TrimPath客戶端框架的一部分.斷點是函數(shù)內(nèi)的一個點,代碼執(zhí)行到該點停止,給程序員檢查變量,表達式,調(diào)用函數(shù)等的值的機會.Steve的斷點技術(shù)用閉包捕捉函數(shù)的當(dāng)前作用域(包括局部變量和函數(shù)參數(shù)),用全局的eval()函數(shù)組合這些就可以檢查作用域了.eval()函數(shù)計算JavaScript代碼字符串并返回結(jié)果.下面是一個以自檢閉包方式工作的嵌套函數(shù).


    // 捕捉當(dāng)前作用域,可以用eval()檢查
    var inspector = function($) { return eval($); }

    ??????? 這個函數(shù)用了很少見的標(biāo)識符$作為參數(shù)名,這樣可以減少在計劃檢查的作用域內(nèi)命名沖突的可能性.

    ??????? (接下來部分代碼與所述內(nèi)容無關(guān),譯略)

    8.8.4.2. 閉包和IE中的內(nèi)存泄露(Closures and memory leaks in Internet Explorer)
    ??????? MS的IE瀏覽器在ActiveX對象和客戶端DOM元素的GC方面表現(xiàn)較弱.客戶端對象按引用計數(shù),當(dāng)引用數(shù)為0的時候釋放對象.這種方法在循環(huán)引用的時候就失效了,例如,當(dāng)一個核心JavaScript對象引用一個文檔元素,而那個文檔元素又有一個屬性(比如是一個事件句柄)引用該核心JavaScript對象.

    ??????? 在IE客戶端編程使用閉包的時候,這種循環(huán)引用經(jīng)常出現(xiàn).當(dāng)你使用閉包的時候,記住,封閉(enclosing)函數(shù)的調(diào)用對象,包括函數(shù)所有的參數(shù)和局部變量,都將和閉包一樣"長壽".如果任何函數(shù)參數(shù)或者局部變量引用了一個客戶端對象,就會發(fā)生內(nèi)存泄露.

    ??????? 關(guān)于這個問題的完整討論超出本書范圍,詳情請參見:?

    http://msdn.microsoft.com/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp

    本文出處:JavaScript - the definitive guide,5th edition
    譯: 梅雪香
    時間:2006-10-29

    posted on 2006-10-29 18:38 梅雪香 閱讀(1281) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 久久精品国产亚洲网站| 亚洲精品乱码久久久久蜜桃 | 希望影院高清免费观看视频| 黄色三级三级免费看| 亚洲人成电影网站| 亚洲国产精品国自产电影| 亚洲 自拍 另类小说综合图区| 日韩视频在线精品视频免费观看 | 日韩人妻无码免费视频一区二区三区 | 亚洲色最新高清av网站| 久久精品国产亚洲AV无码偷窥| 国产AV无码专区亚洲AV漫画| 国产一区二区三区在线免费观看 | 四虎成人免费观看在线网址 | 久久久亚洲精品国产| 中文字幕亚洲不卡在线亚瑟| 免费v片视频在线观看视频| 成年女人免费视频播放体验区| 131美女爱做免费毛片| 99久久99热精品免费观看国产| a毛片在线免费观看| 美女无遮挡拍拍拍免费视频| 深夜免费在线视频| 高潮毛片无遮挡高清免费视频| 久久精品国产亚洲av品善| 亚洲日本VA午夜在线电影| 亚洲依依成人亚洲社区| 亚洲人成色77777在线观看| 亚洲看片无码在线视频 | 18勿入网站免费永久| ww4545四虎永久免费地址| 久草视频免费在线| 蜜臀AV免费一区二区三区| 在线成人爽a毛片免费软件| 91久久精品国产免费一区| 亚洲一区二区三区免费观看 | 亚洲无成人网77777| 亚洲午夜电影在线观看高清 | 在线观看的免费网站无遮挡| 免费专区丝袜脚调教视频| 无码少妇一区二区浪潮免费|