首先我們都知道js的一個函數(shù)定義是這樣的
function func(){ //聲明一個普通的函數(shù)
//省略代碼
}
而沒有名字的函數(shù)叫匿名函數(shù),是長這樣的
function(){ //聲明一個匿名函數(shù)怎棱,一般這樣聲明方式是用于回調(diào)函數(shù)
//省略代碼
}
或者我們習(xí)慣用一個變量來保存匿名函數(shù)旁赊,那么這個變量成為函數(shù)本身
var func = function(){ //匿名函數(shù)保存到func變量內(nèi)
//省略代碼
}
同樣,在調(diào)用函數(shù)的時候绞蹦,使用函數(shù)名或者變量名后面加上一個括號力奋,像以下這樣
func(); //調(diào)用函數(shù)
再者,也可以在聲明函數(shù)的時候直接調(diào)用幽七,這種叫做即時執(zhí)行函數(shù)景殷,需要加上兩個括號,像這樣
(function(){ //即時執(zhí)行函數(shù)
//省略代碼
})();
當(dāng)然,函數(shù)是可以有返回值的猿挚,如果是即時執(zhí)行函數(shù)有返回值咐旧,那么效果會怎樣?
var func = (function(){ //即時執(zhí)行函數(shù)
var i = 1;
return i;
})();
這樣寫亭饵,func獲取的就不只匿名函數(shù)休偶,而是函數(shù)的返回結(jié)果,func = 1
----------------------------------------我是分割線-----------------------------------------
剛剛辜羊,我們的函數(shù)返回的是一個整數(shù)踏兜,但如果我們的函數(shù)返回的是另一個函數(shù)呢?就成了閉包八秃,像這樣:
var func = (function(){ //即時執(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)能訪問變量 i
//省略代碼
}
//作用域內(nèi)能訪問變量 i
return resultFunc;
})();
//函數(shù)體外能不能訪問變量 i
這樣聲明的 i 變量外部是不能訪問的疹尾,是不是很像面向?qū)ο蟮乃接谐蓡T變量?接下來我們試試內(nèi)部方法
var func = (function(){
var i = 5;
var privateFunc = function(){ //聲明了局部變量
//省略代碼
}
var resultFunc = function(){
privateFunc(); //調(diào)用局部函數(shù)
//省略代碼
}
return resultFunc;
})();
小結(jié)一下:實際情況下骤肛,我們可以嘗試這樣寫:
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);
這樣看纳本,其實 resultFunc 更像是一個構(gòu)造函數(shù),這個函數(shù)在我們new func()的時候腋颠,必須且只執(zhí)行一次繁成,并且這個函數(shù)可以接受外部參數(shù)的哦。
----------------------------------------我是分割線-----------------------------------------
好了淑玫,接下來我們可以通過prototype的方式為其添加一些公開函數(shù)巾腕,而這些函數(shù)都是可以訪問局部變量及局部函數(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("這個是公共的方法,還有我的名字是"+this.myName);
}
return resultFunc;
})();
var test = new func(9);
test.publicFunc();
console.log(test.myName);
在外部能通過"."的方式調(diào)用prototype的內(nèi)容,prototype函數(shù)體內(nèi)通過this.訪問自身變量絮蒿。
就這樣尊搬,公共及私有成員方法都通過閉包實現(xiàn)出來。
----------------------------------------再次分割線-----------------------------------------
通過以上的土涝,公共變量及方法都是保存在prototype內(nèi)佛寿,那么其實如果想模擬面向?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(){}; //這里建立一個空白對象
nullObj.prototype = extend.prototype; //空白對象的prototype指向父類的prototype
_child.prototype = new nullObj(); //新建nullObj(實際上是復(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)容茵肃,不然會影響父類的代碼,只能通過new nullObj 復(fù)制給 _child.prototype
調(diào)用測試
var c = new child();
c.sayHi();
console.log(c.name);
可以看到這里首先是調(diào)用了父類的構(gòu)造函數(shù)袭祟,再調(diào)用子類的構(gòu)造函數(shù)验残,而后sayHi方法被子類繼承過來,而name內(nèi)容變成了子類的child字符串巾乳。
采用閉包的方式跟ES6的class有什么不一樣您没?
● ES6的class不存在私有成員鸟召,內(nèi)部通過this訪問變量或函數(shù),外部通過"."方式訪問氨鹏。
● 采用閉包方式可以擁有私有成員欧募,公共成員跟ES6訪問方式一樣,訪問私有成員因為作用域的關(guān)系仆抵,只要直接調(diào)用就好了跟继。
● ES6的使用比閉包方式要簡單,可以根據(jù)自己的情況選擇使用镣丑。