javaScript閉包實(shí)現(xiàn)類與繼承(非ES6)

首先我們都知道js的一個(gè)函數(shù)定義是這樣的

function func(){ //聲明一個(gè)普通的函數(shù)
  //省略代碼
}

而沒有名字的函數(shù)叫匿名函數(shù),是長(zhǎng)這樣的

function(){ //聲明一個(gè)匿名函數(shù),一般這樣聲明方式是用于回調(diào)函數(shù)
  //省略代碼
}

或者我們習(xí)慣用一個(gè)變量來(lái)保存匿名函數(shù),那么這個(gè)變量成為函數(shù)本身

var func = function(){ //匿名函數(shù)保存到func變量?jī)?nèi)
  //省略代碼
}

同樣,在調(diào)用函數(shù)的時(shí)候,使用函數(shù)名或者變量名后面加上一個(gè)括號(hào),像以下這樣

func(); //調(diào)用函數(shù)

再者,也可以在聲明函數(shù)的時(shí)候直接調(diào)用,這種叫做即時(shí)執(zhí)行函數(shù),需要加上兩個(gè)括號(hào),像這樣

(function(){ //即時(shí)執(zhí)行函數(shù)
  //省略代碼
})();

當(dāng)然,函數(shù)是可以有返回值的,如果是即時(shí)執(zhí)行函數(shù)有返回值,那么效果會(huì)怎樣?

var func = (function(){ //即時(shí)執(zhí)行函數(shù)
   var i = 1;
   return i;
})();

這樣寫,func獲取的就不只匿名函數(shù),而是函數(shù)的返回結(jié)果,func = 1

----------------------------------------我是分割線-----------------------------------------

剛剛,我們的函數(shù)返回的是一個(gè)整數(shù),但如果我們的函數(shù)返回的是另一個(gè)函數(shù)呢?就成了閉包,像這樣:

var func = (function(){ //即時(shí)執(zhí)行函數(shù)
   var resultFunc = function(){ //要返回的函數(shù)
       //省略代碼
   }
   return resultFunc;
})();

以上這樣寫 func就是resultFunc函數(shù),這樣有什么用呢?看下面的代碼,注意變量 i 的作用域

var func = (function(){
   var i = 5;   //聲明了局部變量
   var resultFunc = function(){ 
        console.log(i);      //返回的函數(shù)體內(nèi)能訪問(wèn)變量 i
       //省略代碼
   }
   //作用域內(nèi)能訪問(wèn)變量 i
   return resultFunc;
})();
//函數(shù)體外能不能訪問(wèn)變量 i

這樣聲明的 i 變量外部是不能訪問(wèn)的,是不是很像面向?qū)ο蟮乃接谐蓡T變量?接下來(lái)我們?cè)囋噧?nèi)部方法

var func = (function(){
   var i = 5;  
   var privateFunc = function(){  //聲明了局部變量
        //省略代碼
    }
   var resultFunc = function(){ 
        privateFunc();  //調(diào)用局部函數(shù)
       //省略代碼
   }
   return resultFunc;
})();

小結(jié)一下:實(shí)際情況下,我們可以嘗試這樣寫:

var func = (function(){
   var i = 5;  
   var privateFunc = function(){  //聲明了局部變量
        console.log("執(zhí)行了privateFunc局部函數(shù)");
    }
   var resultFunc = function(j){ 
        console.log("外部變量:"+j);
        console.log("內(nèi)部變量:"+i);
        privateFunc();  //調(diào)用局部函數(shù)
       //省略代碼
   }
   return resultFunc;
})();

var test = new func(9);
執(zhí)行結(jié)果

這樣看,其實(shí) resultFunc 更像是一個(gè)構(gòu)造函數(shù),這個(gè)函數(shù)在我們new func()的時(shí)候,必須且只執(zhí)行一次,并且這個(gè)函數(shù)可以接受外部參數(shù)的哦。

----------------------------------------我是分割線-----------------------------------------

好了,接下來(lái)我們可以通過(guò)prototype的方式為其添加一些公開函數(shù),而這些函數(shù)都是可以訪問(wèn)局部變量及局部函數(shù)的:

var func = (function(){
   var i = 5;  
   var privateFunc = function(){  //聲明了局部變量
        console.log("執(zhí)行了privateFunc局部函數(shù)");
    }
   var resultFunc = function(j){ 
        console.log("外部變量:"+j);
        console.log("內(nèi)部變量:"+i);
        privateFunc();  //調(diào)用局部函數(shù)
       //省略代碼
   }
   var _proto = resultFunc.prototype; //取出prototype變量
    _proto.myName = "ken"; //prototype的變量
    _proto.publicFunc = function(){ //prototype的方法
        console.log("這個(gè)是公共的方法,還有我的名字是"+this.myName);
   }
   return resultFunc;
})();

var test = new func(9);
test.publicFunc();
console.log(test.myName);
運(yùn)行結(jié)果

在外部能通過(guò)"."的方式調(diào)用prototype的內(nèi)容,prototype函數(shù)體內(nèi)通過(guò)this.訪問(wèn)自身變量。
就這樣,公共及私有成員方法都通過(guò)閉包實(shí)現(xiàn)出來(lái)。

----------------------------------------再次分割線-----------------------------------------

通過(guò)以上的,公共變量及方法都是保存在prototype內(nèi),那么其實(shí)如果想模擬面向?qū)ο蟮睦^承,只要把prototype拷貝就可以了,先整理一下父類的代碼:

var Super = (function(){
    function _super(){
        console.log("Super constructor");
        this.name = "Super";
    }
    var _proto = _super.prototype;
    _proto.sayHi = function(){
        console.log("hello ! my name is "+this.getMyName());
    }
    _proto.getMyName = function(){
            return this.name;
        }
    return _super;
})();

var s = new Super();
s.sayHi();
父類被new的時(shí)候結(jié)果

以下是子類的繼承方式

var child = (function(){
  var extend = Super;         //定義要繼承的父類
  function _child(){          //子類的構(gòu)造函數(shù)
    extend.call(this);        //讓父類內(nèi)部的this替換成子類的this,執(zhí)行函數(shù)
    console.log("child constructor");
    this.name="child";        //覆蓋子類的name
  }
  var nullObj = function(){};  //這里建立一個(gè)空白對(duì)象
  nullObj.prototype = extend.prototype; //空白對(duì)象的prototype指向父類的prototype
  _child.prototype = new nullObj();     //新建nullObj(實(shí)際上是復(fù)制一份)的prototype給_child
  _child.prototype.constructor = _child;//把_child的構(gòu)造函數(shù)放回prototype里,因prototype剛剛已經(jīng)被覆蓋了
  var _proto = _child.prototype;  //取得prototype
  ///這里可以繼續(xù)添加子類的方法
  return _child;
})();

注意:nullObj.prototype = extend.prototype; 這里nullObj.prototype是引用,不能直接修改nullObj.prototype內(nèi)容,不然會(huì)影響父類的代碼,只能通過(guò)new nullObj 復(fù)制給 _child.prototype

調(diào)用測(cè)試

var c = new child();
c.sayHi();
console.log(c.name);
運(yùn)行結(jié)果

可以看到這里首先是調(diào)用了父類的構(gòu)造函數(shù),再調(diào)用子類的構(gòu)造函數(shù),而后sayHi方法被子類繼承過(guò)來(lái),而name內(nèi)容變成了子類的child字符串。

采用閉包的方式跟ES6的class有什么不一樣?
● ES6的class不存在私有成員,內(nèi)部通過(guò)this訪問(wèn)變量或函數(shù),外部通過(guò)"."方式訪問(wèn)。
● 采用閉包方式可以擁有私有成員,公共成員跟ES6訪問(wèn)方式一樣,訪問(wèn)私有成員因?yàn)樽饔糜虻年P(guān)系,只要直接調(diào)用就好了。
● ES6的使用比閉包方式要簡(jiǎn)單,可以根據(jù)自己的情況選擇使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容