首先我們都知道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);

這樣看,其實(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);

在外部能通過(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();

以下是子類的繼承方式
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);

可以看到這里首先是調(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ù)自己的情況選擇使用。