javascript中的閉包以及dojo中的dojo.hitch


  一、javascript的高級特性--閉包

  理解closure前,首先明白函數作用域的概念。也就是全局變量和局部變量。在函數體內部可以調用全局變量,而函數體內被申明的變量不能被外部調用。

      比較特殊的是,在js中,函數內部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!

  其次,要理解javascript中的垃圾回收機制,在Javascript中,如果一個對象不再被引用,那么這個對象就會被GC回收。如果兩個對象互相引用,而不再被第3者所引用,那么這兩個互相引用的對象也會被回收。

  了解這兩個概念后,我們在看閉包。

  “官方”給出的解釋是:閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。
      這句話通俗的來說就是:JavaScript中所有的function都是一個閉包。不過一般來說,嵌套的function所產生的閉包更為強大,也是大部分時候我們所謂的“閉包”。看下面這段解釋閉包的經典代碼:

  

function a() { 
 var i = 0; 
 function b() { alert(++i); } 
 return b;
}
var c = a();
c();

  在這里,a被b引用了。而b又被c引用了。這是對象不會被gc回收。這就形成了所謂的閉包。也就是說,在a執行完並返回后,閉包使得Javascript的垃圾回收機制GC不會收回a所占用的資源,因為a的內部函數b的執行需要依賴a中的變量。

  好了,深入理解之。再引入一片概念(copy....)

  函數的執行環境(execution context)、活動對象(call object)、作用域(scope)、作用域鏈(scope chain)。以函數a從定義到執行的過程為例闡述這幾個概念。

  1. 定義函數a的時候,js解釋器會將函數a的作用域鏈(scope chain)設置為定義a時a所在的“環境”,如果a是一個全局函數,則scope chain中只有window對象。
  2. 執行函數a的時候,a會進入相應的執行環境(excution context)
  3. 在創建執行環境的過程中,首先會為a添加一個scope屬性,即a的作用域,其值就為第1步中的scope chain。即a.scope=a的作用域鏈。
  4. 然后執行環境會創建一個活動對象(call object)。活動對象也是一個擁有屬性的對象,但它不具有原型而且不能通過JavaScript代碼直接訪問。創建完活動對象后,把活動對象添加到a的作用域鏈的最頂端。此時a的作用域鏈包含了兩個對象:a的活動對象和window對象。
  5. 下一步是在活動對象上添加一個arguments屬性,它保存着調用函數a時所傳遞的參數。
  6. 最后把所有函數a的形參和內部的函數b的引用也添加到a的活動對象上。在這一步中,完成了函數b的的定義,因此如同第3步,函數b的作用域鏈被設置為b所被定義的環境,即a的作用域。

  到此,整個函數a從定義到執行的步驟就完成了。此時a返回函數b的引用給c,又函數b的作用域鏈包含了對函數a的活動對象的引用,也就是說b可以訪問到a中定義的所有變量和函數。函數b被c引用,函數b又依賴函數a,因此函數a在返回后不會被GC回收。

   在理解原理之后,我們來看看引用場景。

  1.保護了函數a的安全性。

  2.在內存中維持一個變量。

  3.通過保護變量的安全實現JS私有屬性和私有方法。

  

  二、dojo.hitch()

  看完閉包后,再來說說,我寫本文的目的。dojo.hitch(scope,method).

  官方解釋是:Dojo.hitch is a neat function. It returns a function that will execute a given function in a given scope. This function allows you to control how a function executes, particularly in asynchronous operations。

  看以下代碼:

  

var args = {
  url: "foo",
  load: this.dataLoaded
};
dojo.xhrGet(args);

  這段代碼執行會怎么樣呢?會報錯。說dataLoaded不是個方法,或找不到。為什么呢。從代碼中可以看出參數args的load想調用外部一個dataLoaded的方法。但是this引用卻指向了args。而args里面可沒有dataLoaded。

  如何解決呢?

  

var args = {
  url: "foo",
  load: dojo.hitch(this, "dataLoaded")
};
dojo.xhrGet(args);

    用dojo.hitch來控制dataLoaded函數執行的作用域為this,這個問題也就迎刃而解了。

 

  說到了hitch,我們就來看看它在dojo庫里是怎么實現的?

  在說原理之前,還是一樣先引入兩個javascript 的方法, Function.apply(obj,args) 和 Function.call(obj,[param1[,param2[,…[,paramN]]]])。

  這兩個函數所做的事情沒有太多差別,差異在於他們傳入的參數,一個是args任意參數,一個是數組的參數。

  大致用法如下,obj1.method.call(obj2,argument1...),

  call的作用就是把obj1的方法放到obj2上使用,后面的argument1..這些做為其他參數傳入。也可以說是obj1能夠劫持obj2的方法並繼承obj2的屬性。

  繼承?對,就是繼承。我們可以這么做。

  

<script>
            function Animal()
            {
                this.name  = 'Animal';

                this.sleep = function(who)
                {
                    alert(who + "   sleep!!");
                }
            }
            function Dog(){
                    Animal.call(this);
                
            }
            function test(){
                var dog = new Dog();
                dog.sleep("dog");
            }
        </script>

結果是 dog sleep!!。

Animal.call(this)就使得,用Animal對象代替Dog中的this對象,這樣Dog也就繼承了Animal中的所有屬性和方法了。這就實現了javascript里的繼承。

<script>
            function Animal()
            {
                this.name  = 'Animal';
                this.eat = function()
                {
                    alert(this.name + "  eat!!!");
                }
            }
            function Dog(){
                this.name = 'Dog';        
            }
            function test(){
                var animal = new Animal();
                var dog = new Dog();
                animal.eat();
                animal.eat.call(dog);
            }
        </script>

結果是Animal eat!!!! 和dog eat!!! 這就是常規的用法了。讓方法執行在dog的作用域上。this.name就變成了dog。

  嗯,很熟悉吧。其實dojo.hitch()也是這么做的。它用的是apply方法。在前面也提到了,apply和call只有參數的區別。

    好了,以下就是dojo.hitch的源碼,

dojo.hitch = function(/*Object*/scope, /*Function|String*/method /*,...*/){
        
        if(arguments.length > 2){
            return d._hitchArgs.apply(d, arguments); // Function
        }
        if(!method){
            method = scope;
            scope = null;
        }
        if(d.isString(method)){
            scope = scope || d.global;
            if(!scope[method]){ throw(['dojo.hitch: scope["', method, '"] is null (scope="', scope, '")'].join('')); }
            return function(){ return scope[method].apply(scope, arguments || []); }; // Function
        }
        return !scope ? method : function(){ return method.apply(scope, arguments || []); }; // Function
    };

     細節不表,還是很簡單吧,就是使用apply將對應的方法apply到scope上,並傳入相應的參數。

   現在在公司做javascript和dojo。從輕量級的jquery到重量級的dojo,深深地體會到dojo的強大。相信后面有很多文章會介紹dojo和javascript的一些高級特性。

  

  

  

  

  

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM