js 概念,閉包,call,apply,prototype等

bill1315發表於2013-02-18

1,型別

javascript 簡單型別null,undefined,boolean,string,number,複雜型別為object。js是區分大小寫的,不要Number, String, Object, Function等JavaScript內建函式混淆了。

 undefined:   代表一切未知的事物,啥都沒有,無法想象,程式碼也就更無法去處理了。
                      注意:typeof(undefined) 返回也是 undefined。
                              可以將undefined賦值給任何變數或屬性,但並不意味了清除了該變數,反而會因此多了一個屬性

    null:            有那麼一個概念,但沒有東西。無中似有,有中還無。雖難以想象,但已經可以用程式碼來處理了。
                      注意:typeof(null)返回object,但null並非object,具有null值的變數也並非object。

    boolean:      是就是,非就非,沒有疑義。對就對,錯就錯,絕對明確。既能被程式碼處理,也可以控制程式碼的流程。

    number:      線性的事物,大小和次序分明,多而不亂。便於程式碼進行批量處理,也控制程式碼的迭代和迴圈等。
                      注意:typeof(NaN)和typeof(Infinity)都返回number 。
                              NaN參與任何數值計算的結構都是NaN,而且 NaN != NaN 。
                              Infinity / Infinity = NaN 。

    string:         面向人類的理性事物,而不是機器訊號。人機資訊溝通,程式碼據此理解人的意圖等等,都靠它了。

簡單型別都不是物件,JavaScript沒有將物件化的能力賦予這些簡單型別。直接被賦予簡單型別常量值的識別符號、變數和引數都不是一個物件。

所謂“物件化”,就是可以將資料和程式碼組織成複雜結構的能力。JavaScript中只有object型別和function型別提供了物件化的能力。

(以上來自

悟透JavaScripthttp://www.cnblogs.com/leadzen/archive/2008/02/25/1073404.html

)

2,函式

 

   function myfunc()
    {
        alert(
"hello");
    };
    
    alert(
typeof(myfunc));


   執行之後可以看到typeof(myfunc)返回的是function。以上的函式寫法我們稱之為“定義式”的,如果我們將其改寫成下面的“變數式”的,就更容易理解了:

    var myfunc = function ()
        {
            alert(
"hello");
        };
    
    alert(
typeof(myfunc));


    這裡明確定義了一個變數myfunc,它的初始值被賦予了一個function的實體。因此,typeof(myfunc)返回的也是function。其實,這兩種函式的寫法是等價的,除了一點細微差別,其內部實現完全相同。也就是說,我們寫的這些JavaScript函式只是一個命了名的變數而已,其變數型別即為function,變數的值就是我們編寫的函式程式碼體。

第二種變數方式:

  var myfunc = function ()
        {
            alert(
"hello");
        };
    myfunc(); 
//第一次呼叫myfunc,輸出hello
    
    myfunc 
= function ()
        {
            alert(
"yeah");
        };    
    myfunc(); 
//第二次呼叫myfunc,將輸出yeah

第一種定義方式:

function myfunc ()
    {
        alert(
"hello");
    };
    myfunc(); 
//這裡呼叫myfunc,輸出yeah而不是hello
    
    
function myfunc ()
    {
        alert(
"yeah");
    };    
    myfunc(); 
//這裡呼叫myfunc,當然輸出yeah

兩次呼叫都只是最後那個函式裡輸出的值!顯然第一個函式沒有起到任何作用。JavaScript執行引擎並非一行一行地分析和執行程式,而是一段一段地分析執行的。而且,在同一段程式的分析執行中,定義式的函式語句會被提取出來優先執行。函式定義執行完之後,才會按順序執行其他語句程式碼。也就是說,在第一次呼叫myfunc之前,第一個函式語句定義的程式碼邏輯,已被第二個函式定義語句覆蓋了。所以,兩次都呼叫都是執行最後一個函式邏輯了。

一段程式碼中的定義式函式語句會優先執行,這似乎有點象靜態語言的編譯概念。所以,這一特徵也被有些人稱為:JavaScript的“預編譯”。

 大多數情況下,我們也沒有必要去糾纏這些細節問題。只要你記住一點:JavaScript裡的程式碼也是一種資料,同樣可以被任意賦值和修改的,而它的值就是程式碼的邏輯。只是,與一般資料不同的是,函式是可以被呼叫執行的。

3,物件

 

var anObject = {};  //一個物件
    anObject.aProperty = "Property of object";  //物件的一個屬性
    anObject.aMethod = function(){alert("Method of object")}; //物件的一個方法
    //主要看下面:
    alert(anObject["aProperty"]);   //可以將物件當陣列以屬性名作為下標來訪問屬性
    anObject["aMethod"]();          //可以將物件當陣列以方法名作為下標來呼叫方法
    forvar s in anObject)           //遍歷物件的所有屬性和方法進行迭代化處理
        alert(s + " is a " + typeof(anObject[s]));


    同樣對於function型別的物件也是一樣:

    var aFunction = function() {};  //一個函式
    aFunction.aProperty = "Property of function";  //函式的一個屬性
    aFunction.aMethod = function(){alert("Method of function")}; //函式的一個方法
    //主要看下面:
    alert(aFunction["aProperty"]);   //可以將函式當陣列以屬性名作為下標來訪問屬性
    aFunction["aMethod"]();          //可以將函式當陣列以方法名作為下標來呼叫方法
    forvar s in aFunction)           //遍歷函式的所有屬性和方法進行迭代化處理
        alert(s + " is a " + typeof(aFunction[s]));


    是的,物件和函式可以象陣列一樣,用屬性名或方法名作為下標來訪問並處理。

   function型別的東西都是和object型別一樣的東西,這種東西被我們稱為“物件”。我們的確可以這樣去看待這些“物件”,因為它們既有“屬性”也有“方法”。

  JavaScript中也有this,但這個this卻與C++、C#或Java等語言的this不同。一般程式語言的this就是物件自己,而 JavaScript的this卻並不一定!this可能是我,也可能是你,可能是他,反正是我中有你,你中有我,這就不能用原來的那個“自我”來理解 JavaScript這個this的含義了。為此,我們必須首先放下原來物件的那個“自我”。

  在JavaScript函式中,你只能把this看成當前要服務的“這個”物件。this是一個特殊的內建引數,根據this引數,您可以訪問到“這個”物件的屬性和方法,但卻不能給this引數賦值。在一般物件語言中,方法體程式碼中的this可以省略的,成員預設都首先是“自己”的。但JavaScript卻不同,由於不存在“自我”,當訪問“這個”物件時,this不可省略!

JSON物件:

  JSON的形式就是用大括“{}”號包括起來的專案列表,每一個專案間並用逗號“,”分隔,而專案就是用冒號“:”分隔的屬性名和屬性值。這是典型的字典表示形式,也再次表明了 JavaScript裡的物件就是字典結構。不管多麼複雜的物件,都可以被一句JSON程式碼來建立並賦值。

    其實,JSON就是JavaScript物件最好的序列化形式,它比XML更簡潔也更省空間。物件可以作為一個JSON形式的字串,在網路間自由傳遞和交換資訊。而當需要將這個JSON字串變成一個JavaScript物件時,只需要使用eval函式這個強大的數碼轉換引擎,就立即能得到一個JavaScript記憶體物件。

new物件:

  除JSON外,在JavaScript中我們可以使用new操作符結合一個函式的形式來建立物件。例如:

    function MyFunc() {};         //定義一個空函式
    var anObj = new MyFunc();  //使用new操作符,藉助MyFun函式,就建立了一個物件


    JavaScript的這種建立物件的方式可真有意思,如何去理解這種寫法呢?
 
   其實,可以把上面的程式碼改寫成這種等價形式:

    function MyFunc(){};
    
var anObj = {};     //建立一個物件
    MyFunc.call(anObj); //將anObj物件作為this指標呼叫MyFunc函式

我們就可以這樣理解,JavaScript先用new操作符建立了一個物件,緊接著就將這個物件作為this引數呼叫了後面的函式。其實,JavaScript內部就是這麼做的,而且任何函式都可以被這樣呼叫!
 

4,作用域:函式內部宣告變數的時候,一定要使用var命令。如果不用的話,你實際上宣告瞭一個全域性變數!

    function f1(){
    n=1;
  }

  f1();

  alert(n); // 1
    function f1(){
    var n=1;
  }

  f1();

  alert(n); // error

5,閉包:閉包就是能夠讀取其他函式內部變數的函式。

由於在Javascript語言中,只有函式內部的子函式才能讀取區域性變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”。

所以,在本質上,閉包就是將函式內部和函式外部連線起來的一座橋樑。

閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函式內部的變數,另一個就是讓這些變數的值始終保持在記憶體中。

前兩年,微軟在設計AJAX類庫的初期,用了一種被稱為“閉包”(closure)的技術來模擬“類”。其大致模型如下:

    function Person(firstName, lastName, age)
    {
        
//私有變數:
        var _firstName = firstName;
        
var _lastName = lastName;

        
//公共變數:
        this.age = age;

        
//方法:
        this.getName = function()
        {
            
return(firstName + " " + lastName);
        };
        
this.SayHello = function()
        {
            alert(
"Hello, I'm " + firstName + " " + lastName);
        };
    };
    
    
var BillGates = new Person("Bill""Gates"53);
    
var SteveJobs = new Person("Steve""Jobs"53);
    
    BillGates.SayHello();
    SteveJobs.SayHello();
    alert(BillGates.getName() 
+ " " + BillGates.age);
    alert(BillGates.firstName);     
//這裡不能訪問到私有變數

 很顯然,這種模型的類描述特別象C#語言的描述形式,在一個建構函式裡依次定義了私有成員、公共屬性和可用的方法。特別是“閉包”機制可以模擬對私有成員的保護機制,做得非常漂亮。

    所謂的“閉包”,就是在建構函式體內定義另外的函式作為目標物件的方法函式,而這個物件的方法函式反過來引用外層外層函式體中的臨時變數。這使得只要目標物件在生存期內始終能保持其方法,就能間接保持原建構函式體當時用到的臨時變數值。儘管最開始的建構函式呼叫已經結束,臨時變數的名稱也都消失了,但在目標物件的方法內卻始終能引用到該變數的值,而且該值只能通這種方法來訪問。即使再次呼叫相同的建構函式,但只會生成新物件和方法,新的臨時變數只是對應新的值,和上次那次呼叫的是各自獨立的。的確很巧妙!

    但是前面我們說過,給每一個物件設定一份方法是一種很大的浪費。還有,“閉包”這種間接保持變數值的機制,往往會給JavaSript的垃圾回收器製造難題。特別是遇到物件間複雜的迴圈引用時,垃圾回收的判斷邏輯非常複雜。無獨有偶,IE瀏覽器早期版本確實存在JavaSript垃圾回收方面的記憶體洩漏問題。再加上“閉包”模型在效能測試方面的表現不佳,微軟最終放棄了“閉包”模型,而改用“原型”模型。
 

 6,prototype

JavaScript的所有function型別的物件都有一個prototype屬性。這個prototype屬性本身又是一個object型別的物件,因此我們也可以給這個prototype物件新增任意的屬性和方法。既然prototype是物件的“原型”,那麼由該函式構造出來的物件應該都會具有這個“原型”的特性。事實上,在建構函式的prototype上定義的所有屬性和方法,都是可以通過其構造的物件直接訪問和呼叫的。也可以這麼說,prototype提供了一群同類物件共享屬性和方法的機制。

 

   function Person(name)
    {
        
this.name = name;   //設定物件屬性,每個物件各自一份屬性資料
    };
    
    Person.prototype.SayHello 
= function()  //給Person函式的prototype新增SayHello方法。
    {
        alert(
"Hello, I'm " + this.name);
    }

    
var BillGates = new Person("Bill Gates");   //建立BillGates物件
    var SteveJobs = new Person("Steve Jobs");   //建立SteveJobs物件

    BillGates.SayHello();   
//通過BillGates物件直接呼叫到SayHello方法
    SteveJobs.SayHello();   //通過SteveJobs物件直接呼叫到SayHello方法

    alert(BillGates.SayHello 
== SteveJobs.SayHello); //因為兩個物件是共享prototype的SayHello,所以顯示:true


    程式執行的結果表明,建構函式的prototype上定義的方法確實可以通過物件直接呼叫到,而且程式碼是共享的。顯然,把方法設定到prototype的寫法顯得優雅多了,儘管呼叫形式沒有變,但邏輯上卻體現了方法與類的關係。

    在JavaScript中,prototype不但能讓物件共享自己財富,而且prototype還有尋根問祖的天性,從而使得先輩們的遺產可以代代相傳。當從一個物件那裡讀取屬性或呼叫方法時,如果該物件自身不存在這樣的屬性或方法,就會去自己關聯的prototype物件那裡尋找;如果prototype沒有,又會去prototype自己關聯的前輩prototype那裡尋找,直到找到或追溯過程結束為止。

    在JavaScript內部,物件的屬性和方法追溯機制是通過所謂的prototype鏈來實現的。當用new操作符構造物件時,也會同時將建構函式的prototype物件指派給新建立的物件,成為該物件內建的原型物件。物件內建的原型物件應該是對外不可見的,儘管有些瀏覽器(如Firefox)可以讓我們訪問這個內建原型物件,但並不建議這樣做。內建的原型物件本身也是物件,也有自己關聯的原型物件,這樣就形成了所謂的原型鏈。

    在原型鏈的最末端,就是Object建構函式prototype屬性指向的那一個原型物件。這個原型物件是所有物件的最老祖先,這個老祖宗實現了諸如toString等所有物件天生就該具有的方法。其他內建建構函式,如Function, Boolean, String, Date和RegExp等的prototype都是從這個老祖宗傳承下來的,但他們各自又定義了自身的屬性和方法,從而他們的子孫就表現出各自宗族的那些特徵。

    這不就是“繼承”嗎?是的,這就是“繼承”,是JavaScript特有的“原型繼承”。

 function Person(name)   //基類建構函式
 2     {
 3         this.name = name;
 4     };
 5     
 6     Person.prototype.SayHello = function()  //給基類建構函式的prototype新增方法
 7     {
 8         alert("Hello, I'm " + this.name);
 9     };
10     
11     function Employee(name, salary) //子類建構函式
12     {
13         Person.call(this, name);    //呼叫基類建構函式
14         this.salary = salary;
15     };
16     
17     Employee.prototype = new Person();  //建一個基類的物件作為子類原型的原型,這裡很有意思
18     
19     Employee.prototype.ShowMeTheMoney = function()  //給子類添建構函式的prototype新增方法
20     {
21         alert(this.name + " $" + this.salary);
22     };
23 
24     var BillGates = new Person("Bill Gates");   //建立基類Person的BillGates物件
25     var SteveJobs = new Employee("Steve Jobs"1234);   //建立子類Employee的SteveJobs物件
26 
27     BillGates.SayHello();       //通過物件直接呼叫到prototype的方法
28     SteveJobs.SayHello();       //通過子類物件直接呼叫基類prototype的方法,關注!
29     SteveJobs.ShowMeTheMoney(); //通過子類物件直接呼叫子類prototype的方法
30 
31     alert(BillGates.SayHello == SteveJobs.SayHello); //顯示:true,表明prototype的方法是共享的

7,call,apply

call和apply,它們的作用都是將函式繫結到另外一個物件上去執行

兩者的格式和引數定義:

call( thisArg [,arg1,arg2,… ] ); // 引數列表,arg1,arg2,...

apply(thisArg [,argArray] ); // 引數陣列,argArray

上面兩個函式內部的this指標,都會被賦值為thisArg,這可實現將函式作為另外一個物件的方法執行的目的

區分apply,call就一句話,

  foo.call(this, arg1,arg2,arg3) == foo.apply(this, arguments)==this.foo(arg1, arg2, arg3)

 

call, apply都屬於Function.prototype的一個方法,它是JavaScript引擎內在實現的,因為屬於Function.prototype,所以每個Function物件例項,也就是每個方法都有call, apply屬性.既然作為方法的屬性,那它們的使用就當然是針對方法的了.這兩個方法是容易混淆的,因為它們的作用一樣,只是使用方式不同.

相同點:兩個方法產生的作用是完全一樣的

不同點:方法傳遞的引數不同

我們就上面的foo.call(this, arg1, arg2, arg3)展開分析.

foo是一個方法,this是方法執行時上下文相關物件,arg1, arg2, arg3是傳給foo方法的引數.這裡所謂的方法執行時上下文相關物件, 如果有物件導向的程式設計基礎,那很好理解,就是在類例項化後物件中的this.

在JavaScript中,程式碼總是有一個上下文物件,程式碼處理該物件之內. 上下文物件是通過this變數來體現的, 這個this變數永遠指向當前程式碼所處的物件中.

call, apply作用就是借用別人的方法來呼叫,就像呼叫自己的一樣.

call, apply方法區別是,從第二個引數起, call方法引數將依次傳遞給借用的方法作引數, 而apply直接將這些引數放到一個陣列中再傳遞, 最後借用方法的引數列表是一樣的.

當引數明確時可用call, 當引數不明確時可用apply給合arguments

 

 

相關文章