JS的面向對象編程的(閉包,作用域,原型鏈,封裝,繼承,多態)

閉包的概念
//=============================================================
閉包就是能夠讀取其他函數內部變量的函數。

1,變量的作用域無非就是兩種:

全局變量和局部變量。

2,如何從外部讀取局部變量?

  function f1(){
    n=999;
    function f2(){
      alert(n); // 999
    }
  }

在上面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2都是可見的。但是反過來就不行,f2內部的局部變量,對f1 就是不可見的。這就是Javascript語言特有的“鏈式作用域”結構(chain scope),

子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。

既然f2可以讀取f1中的局部變量,那麼只要把f2作爲返回值,我們不就可以在f1外部讀取它的內部變量了嗎!

function f1(){
    n=999;
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999

3,閉包的用途
閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。
請看下面的代碼。

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數f1中的局部變量n一直保存在內存中,並沒有在f1調用後被自動清除。

爲什麼會這樣呢?原因就在於f1是f2的父函數,而f2被賦給了一個全局變量,這導致f2始終在內存中,而f2的存在依賴於f1,因此f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。

另一個值得注意的地方,就是“nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,因此 nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個匿名函數本身也是一個閉包,所以nAdd相當於是一個setter,可以在函數外部對函數內部的局部變量進行操作。

4,使用閉包的注意點

1)由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。

2)閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。
//=============================================================
作用域

1,變量作用域

在JavaScript中全局變量的作用域比較簡單,它的作用域是全局的,在代碼的任何地方都是有定義的。然而函數的參數和局部變量只在函數體內有定義。另外局部變量的優先級要高於同名的全局變量,也就是說當局部變量與全局變量重名時,局部變量會覆蓋全局變量

2,函數作用域

在JavaScript中變量的作用域,並非和C、Java等編程語言似得,在變量聲明的代碼段之外是不可見的,我們通常稱爲塊級作用域,然而在JavaScript中使用的是函數作用域(變量在聲明它們的函數體以及這個函數體嵌套的任意函數體都是有定義的)。
//=============================================================
原型鏈

1、原型對象

在Javascript中,當系統加載構造函數後,會自動在內存中生成一個對象,這個對象就是原型對象。兩者之間在內存中表現爲相對獨立,不存在誰包含誰的關係。但是兩者之間又有一些關聯,在構造函數的內部存在一個prototype屬性指向原型對象,同時在原型對象的內存也存在一個屬性constructor其指向了構造函數。

在這裏插入圖片描述
2、原型對象的作用

當構造函數實例化的對象訪問一個不存在的屬性或方法,系統會自動到當前構造器所指向的原型對象中去尋找,找到則直接使用。

在這裏插入圖片描述

3、原型對象的應用場景

在實際項目開發中,我們都會使用別人開發的Javascript類庫,如果我們發現當前代碼庫不存在我們需要的屬性或方法,我們不能直接去修改別人的源代碼,又不想爲每個實例化對象單獨定義相關屬性或方法,那麼不妨考慮使用原型對象進行擴展。

//=============================================================
封裝

通常寫js組件開發的,都會用到匿名函數的寫法去封裝一個對象,與外界形成一個閉包的作用域。

1、使用函數有兩步:

1.1,定義函數,又叫聲明函數, 封裝函數。
定義函數的三個要素:功能,參數,返回值。

function 函數名(形參){
函數代碼
return 結果
}
1.2,調用函數
var 變量 = 函數名(實參);

2、對函數的參數和返回值的理解

2.1、函數的參數就是完成一件事情的已知條件,就是輸入。

2.2、函數的返回值就是事情完成的結果。就是輸出。

//=============================================================
繼承

一、object()方法

json格式的發明人Douglas Crockford,提出了一個object()函數,可以做到這一點。

function object(o) {    
    function F() {}    
    F.prototype = o;    
    return new F();  
}

二、淺拷貝

除了使用"prototype鏈"以外,還有另一種思路:把父對象的屬性,全部拷貝給子對象,也能實現繼承。

下面這個函數,就是在做拷貝:

function extendCopy(p) {    
    var c = {};    
    for(var i in p) {      
        c[i] = p[i];    
    }    
    c.uber = p;    
    return c;  
}

三、深拷貝

所謂"深拷貝",就是能夠實現真正意義上的數組和對象的拷貝。它的實現並不難,只要遞歸調用"淺拷貝"就行了。

function deepCopy(p, c) {    
    var c = c || {};    
    for(var i in p) {      
        if(typeof p[i] === 'object') {        
            c[i] = (p[i].constructor === Array) ? [] : {};        
            deepCopy(p[i], c[i]);      
        } else {         
            c[i] = p[i];      
        }    
    }    
    return c;  
}

多態

面向對象編程中的多態性是能夠創建具有多種形式的變量,函數或對象。

多態的實際含義是:
同一操作作用於不同的對象上面,可以產生不同的解釋和不同的執行結果
換句話說,給不同的對象發送同一個消息的時候,這些對象會根據這個消息分別給出不同的反饋。從字面上來理解多態不太容易,下面我們來舉例說明一下。
主人家裏養了兩隻動物,分別是一隻鴨和一隻雞,當主人向它們發出“叫”的命令時,鴨會“嘎嘎嘎”地叫,而雞會“咯咯咯”地叫。這兩隻動物都會以自己的方式來發出叫聲。它們同樣“都是動物,並且可以發出叫聲”,但根據主人的指令,它們會各自發出不同的叫聲。
if和else if能實現簡單的多態

對象的多態性

下面是改寫後的代碼,首先我們把不變的部分隔離出來,那就是所有的動物都會發出叫聲:

var makeSound = function( animal ){
    animal.sound();
};

然後把可變的部分各自封裝起來,我們剛纔談到的多態性實際上指的是對象的多態性:

var Duck = function(){}
Duck.prototype.sound = function(){
    console.log( '嘎嘎嘎' );
};

var Chicken = function(){}
Chicken.prototype.sound = function(){
    console.log( '咯咯咯' );
};

makeSound( new Duck() ); // 嘎嘎嘎
makeSound( new Chicken() ); // 咯咯咯

如果有一天又增加了一隻狗,這時候只要簡單地追加一些代碼就可以了,而不用改動以前的makeSound 函數,如下所示:

var Dog = function(){}
Dog.prototype.sound = function(){
    console.log( '汪汪汪' );
};
makeSound( new Dog() ); // 汪汪汪

使用繼承得到多態效果

使用繼承來得到多態效果,是讓對象表現出多態性的最常用手段。繼承通常包括實現繼承和接口繼承。

JavaScript的多態

從前面我們得知,多態的思想實際上是把“做什麼”和“誰去做”分離開來,要實現這一點,歸根結底先要消除類型之間的耦合關係。如果類型之間的耦合關係沒有被消除,那麼我們在makeSound方法中指定了發出叫聲的對象是某個類型,它就不可能再被替換爲另外一個類型。在 Java 中,可以通過向上轉型來實現多態。

而 JavaScript 的變量類型在運行期是可變的。一個JavaScript對象,既可以表示Duck類型的對象,又可以表示 Chicken 類型的對象,這意味着 JavaScript 對象的多態性是與生俱來的。

這種與生俱來的多態性並不難解釋。JavaScript作爲一門動態類型語言,它在編譯時沒有類型檢查的過程,既沒有檢查創建的對象類型,又沒有檢查傳遞的參數類型。我們既可以往makeSound函數裏傳遞duck對象當作參數,也可以傳遞 chicken 對象當作參數。

由此可見,某一種動物能否發出叫聲,只取決於它有沒有makeSound方法,而不取決於它是否是某種類型的對象,這裏不存在任何程度上的“類型耦合”。在JavaScript中,並不需要諸如向上轉型之類的技術來取得多態的效果。

多態:一個指令,多種狀態(方式)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章