- 函數(shù)變量提升:在執(zhí)行代碼之前會先讀取函數(shù)聲明贱傀。這就意味著可以把函數(shù)聲明放在調(diào)用它的語句后面乾戏。
- 函數(shù)聲明:
say()
function say(){
}
- 函數(shù)表達式
fun ();//error 函數(shù)表達式不存在函數(shù)變量提升
var fun = function(){
//這個function是匿名函數(shù)
}
遞歸
- 使用arguments.callee()實現(xiàn)遞歸粪躬。在編寫遞歸函數(shù)時挖炬,使用 arguments.callee 總比使用函數(shù)名更保險传泊。
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1);
}
}
- 命名函數(shù)表達式的形式創(chuàng)建(在嚴格模式中arguments.callee會報錯)
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1);
}
}); //即使是修改了factorial變量名函數(shù)內(nèi)部也能調(diào)用到f()命名函數(shù)
閉包
閉包是指有權訪問另一個函數(shù)作用域中的變量的函數(shù)
-
捋一下一般函數(shù)的一生:
- 當創(chuàng)建一個函數(shù)時雪情,會生成一個預先包含全局變量對象(變量對象包括作用域鏈上的所有對象遵岩,全局對象永遠在變量對象上,而局部環(huán)境的變量對象只在函數(shù)執(zhí)行過程中存在)作用域鏈旺罢,存放在函數(shù)的[[Scope]]屬性中
- 當調(diào)用函數(shù)執(zhí)行時旷余,函數(shù)會創(chuàng)建執(zhí)行環(huán)境绢记。首先他會吸收[[Scope]]屬性作為執(zhí)行作用域鏈,隨后再將arguments組成的活動對象推入執(zhí)行環(huán)境作用域鏈的前端正卧。
-
當函數(shù)執(zhí)行完畢蠢熄,局部活動對象銷毀,全局活動對象保存在內(nèi)存中
一般函數(shù)的執(zhí)行環(huán)境
-
捋一下閉包函數(shù)的一生:
首先用父函數(shù)和匿名函數(shù)來表示外層函數(shù)和返回的內(nèi)層函數(shù)- 當調(diào)用執(zhí)行父函數(shù)創(chuàng)建匿名函數(shù)后炉旷,即使父函數(shù)已經(jīng)執(zhí)行完畢签孔,但是其活動對象不會被銷毀,因為他被匿名函數(shù)引用
-
匿名函數(shù)執(zhí)行時窘行,會吸收父函數(shù)和全局變量對象作為執(zhí)行作用域鏈饥追,隨后再將自己局部變量和arguments組成的活動對象推入作用域鏈前端
所以匿名函數(shù)的作用域鏈是:
argument和局部變量組成的活動對象
父函數(shù)的活動對象
全局變量對象
父函數(shù)和匿名函數(shù)執(zhí)行環(huán)境
閉包會攜帶包含它的函數(shù)的作用域,因此會比其他函數(shù)占用更多的內(nèi)存罐盔。
閉包與變量
閉包只能取得包含函數(shù)中任何變量的最后一個值
閉包父函數(shù)的活動對象只會保留父函數(shù)執(zhí)行完畢后的一次但绕。舉個栗子:父函數(shù)執(zhí)行 a=1 a=2 a=3,執(zhí)行完畢后a=3,保存給內(nèi)部匿名函數(shù)的活動對象的a就是等于3惶看。所有返回的匿名函數(shù)都是指向相同作用域下的那個a
function createFunctions() {
debugger
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
i = 2 //將i重新賦值為2捏顺,則返回結果都是2
return result;
}
const a = createFunctions()
console.log(a[0]())//2
console.log(a[5]())//2
- 當createFunctions執(zhí)行時,父函數(shù)創(chuàng)建i變量纬黎,i隨著程序執(zhí)行變成0幅骄,變成1,變成2本今。拆座。。變成10冠息,變成2挪凑。當createFunctions完全執(zhí)行完畢后,才會將自己的活動變量交給匿名函數(shù)铐达,此時交出去的i是2a场!瓮孙!唐断,所以返回的所有匿名函數(shù)得到的i都是2!杭抠!
- 解決方法:
- 返回匿名自執(zhí)行函數(shù)脸甘,匿名自執(zhí)行函數(shù)再返回閉包函數(shù),匿名自治性函數(shù)傳參給閉包函數(shù)返回偏灿。
- 將var改成let丹诀,每個for循環(huán)都是不同的作用域,返回的不再是同一個i
關于this對象
- this 對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的。
目前知道的this指向
- 全局下指向window
- 對象中函數(shù)執(zhí)行對象
- 匿名閉包函數(shù)的this指向全局window(每個函數(shù)在被調(diào)用時都會自動取得兩個特殊變量:this 和 arguments铆遭。內(nèi)部函數(shù)在搜索這兩個變量時硝桩,只會搜索到其活動對象為止,因此永遠不可能直接訪問外部函數(shù)中的這兩個變量)
- 獲得父函數(shù)this的方法枚荣,將父函數(shù)this賦值給變量傳遞
閉包帶來的內(nèi)存泄露
- 閉包會導致父函數(shù)局部變量一直保存在內(nèi)存中碗脊,因為閉包函數(shù)一直保持著對他的引用
- 怎么解決?
- 將局部變量中需要用到的值取出來引用橄妆,例如const age = obj.age
- 將父函數(shù)的局部變量變?yōu)閚ull obj=null(為什么都沒有引用obj還不會被垃圾回收呢衙伶?因為閉包函數(shù)會引用所有父函數(shù)的活動對象)
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
模仿塊級作用域
- 在匿名函數(shù)中定義的任何變量,都會在執(zhí)行結束時被銷毀
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //導致一個錯誤害碾!
}
- 這種技術經(jīng)常在全局作用域中被用在函數(shù)外部矢劲,從而限制向全局作用域中添加過多的變量和函數(shù)。
- 這種做法可以減少閉包占用的內(nèi)存問題慌随,因為沒有指向匿名函數(shù)的引用芬沉。只要函數(shù)執(zhí)行完畢,就可以立即銷毀其作用域鏈了儒陨。
私有變量(閉包還可以用在對象創(chuàng)建私有變量)
- 私有變量:函數(shù)中固定的變量(函數(shù)參數(shù)和函數(shù)的局部變量)
- 特權方法:能訪問到私有變量的方法
- 使用閉包花嘶,匿名函數(shù)返回父函數(shù)中的局部變量
- 構造函數(shù)中,通過get(),set()返回私有變量屬性
靜態(tài)私有屬性:在私有作用域中用原型返回私有屬性
模塊模式:如果必須創(chuàng)建一個對象并以某些數(shù)據(jù)對其進行初始化蹦漠,同時還要公開一些能夠訪問這些私有數(shù)據(jù)的方法,那么就可以使用模塊模式车海。和閉包差不多笛园,就是返回了個對象
var application = function () {
//私有變量和函數(shù)
var components = new Array();
//初始化
components.push(new BaseComponent());
//公共
return {
getComponentCount: function () {
return components.length;
},
registerComponent: function (component) {
if (typeof component == "object") {
components.push(component);
}
}
};
}();
增強的模塊模式
- 單例必須是某種類型的實例,同時還必須添加某些屬性和(或)方法對其加以增強的情況(就是返回了一個對象侍芝,該對象又是某個構造函數(shù)的實例)
var application = function () {
//私有變量和函數(shù)
var components = new Array();
//初始化
components.push(new BaseComponent());
//創(chuàng)建 application 的一個局部副本
var app = new BaseComponent();
//公共接口
app.getComponentCount = function () {
return components.length;
};
app.registerComponent = function (component) {
if (typeof component == "object") {
components.push(component);
}
};
//返回這個副本
return app;
}();