前言
看了很多書籍和文章不皆,使用用閉包的原因說的真是管中窺豹,憑什么說函數(shù)作用域外部無法訪問就用閉包熊楼,看下面的代碼霹娄,照樣也能訪問內(nèi)部變量,函數(shù)也可以獲取內(nèi)部變量鲫骗。
var count = 0;
function foo() {
count++;
var a = 1;
var b = 2;
return {
a,
b
};
}
console.log(foo().a, count); // 1 1
console.log(foo().b, count); // 2 2
此處count是記錄函數(shù)調(diào)用了幾次犬耻,在這里調(diào)用了兩次。
思考
那我用普通函數(shù)就好啦执泰,用閉包干嘛呀枕磁?
解答
- 肯定是想在外部獲取函數(shù)內(nèi)部數(shù)據(jù)。
- 不知道你們注意到?jīng)]有术吝,我想獲取
a,b
计济,是不是調(diào)用了兩次foo函數(shù)
,count = 2
; 這樣就執(zhí)行創(chuàng)建了兩次var a = 1; var b = 1;
當(dāng)這其中代碼很多很復(fù)雜的時(shí)候排苍,你就消耗了很多性能
沦寂。- 職責(zé)單一,不耦合代碼是我們書寫良好js的基本功淘衙,我們期望a,b職責(zé)不同传藏,應(yīng)該有自己的函數(shù)去做處理。
- 其他的避免全局變量污染。
- 傳參減少作用域查詢毯侦。
我們將上述代碼改變下
var count = 0;
function foo() {
count++;
var a = 1;
var b = 2;
return {
fna: function() {
return a;
},
fnb: function() {
return b;
}
};
}
var f = foo();
console.log(f.fna(), count); // 1 1
console.log(f.fnb(), count); // 2 1
很多人是不是想說西壮,看起來好像更復(fù)雜了呀,確實(shí)復(fù)雜了叫惊。但是卻有兩個(gè)好處。1做修、職責(zé)單一霍狰;2、只調(diào)用了一次foo()饰及,count一直是1蔗坯;無論你想獲取多少次函數(shù)數(shù)據(jù),都只調(diào)用一次外層函數(shù)燎含,性能好;
如果你覺得閉包就這樣宾濒,還用它干嘛,那就錯(cuò)了屏箍,它能做很多事情绘梦。從簡(jiǎn)到難:
??函數(shù)內(nèi)部其實(shí)都是私有屬性赴魁,我們可以自由暴露其為共用屬性卸奉。
function foo() {
var num = Math.random();
return function fn() {
return num;
};
}
var f = foo();
console.log(f());
function foo () {
var num = Math.random();
return {
get_num : function () {
return num;
},
set_num: function( value ) {
return num = value;
}
}
}
??教大家寫jquery實(shí)現(xiàn)原理榄棵。
(function() {
var jQuery = function() {};
jQuery.custom = function() {
return "jQuery的自定義方法";
};
window.jQuery = window.$ = jQuery;
})();
console.log($.custom());
經(jīng)典面試題:以下函數(shù)打印什么潘拱?
for (var i = 0; i <= 5; i++) {
setTimeout(function timer() {
console.log(i); // 6個(gè)6
}, 0);
}
如何打印0疹鳄,1,2芦岂,3瘪弓,4,5呢盔腔?
for (var i = 0; i <= 5; i++) {
(function(i_) {
setTimeout(function timer() {
console.log(i_);
}, 0);
})(i);
}
:
1杠茬、
js循環(huán)會(huì)進(jìn)入隊(duì)列
(先進(jìn)先出的一種數(shù)據(jù)結(jié)構(gòu),數(shù)組弛随、對(duì)象就是數(shù)據(jù)結(jié)構(gòu))瓢喉。
2、promise是微異步舀透,setTimeout是宏異步栓票,意思就是setTimeout會(huì)等隊(duì)列執(zhí)行完成以后,在執(zhí)行
。你想想循環(huán)執(zhí)行完了這個(gè)i是多少走贪,你在for循環(huán)外層打印佛猛,結(jié)果就是執(zhí)行完的,就是6坠狡。
3继找、那咋辦,咱用緩存呀逃沿,在for循環(huán)隊(duì)列的時(shí)候婴渡,我把i給緩存下來
呀,匿名閉包就可以在內(nèi)部訪問外層的作用域凯亮,拿過來保存了边臼。
ps:var換成let塊級(jí)作用域也可以,簡(jiǎn)單方便假消。
也可以這么寫代碼實(shí)現(xiàn):(更好理解是緩存的原因)
for (var i = 0; i <= 5; i++) {
function fn() {
var i_ = i;
setTimeout(function timer() {
console.log(i_);
}, 0);
}
fn();
}
??以斐波那契數(shù)列為例,給大家普及下這是什么東西富拗,就是兔子數(shù)列臼予。
在第一個(gè)月有一對(duì)剛出生的小兔子,在第二個(gè)月小兔子變成大兔子并開始懷孕媒峡,第三個(gè)月大兔子會(huì)生下一對(duì)小兔子瘟栖,并且以后每個(gè)月都會(huì)生下一對(duì)小兔子。 如果每對(duì)兔子都經(jīng)歷這樣的出生谅阿、成熟半哟、生育的過程,并且兔子永遠(yuǎn)不死签餐,那么兔子的總數(shù)是如何變化的寓涨?得到的就是斐波那契數(shù)列。
月份:兔子數(shù)目氯檐。如下就是num對(duì)應(yīng)foo(num)的值戒良。
var count = 0;
function foo(num) {
count++;
if (num === 0) return 0;
if (num === 1) return 1;
return foo(num - 1) + foo(num - 2);
}
var f = foo(15);
console.log(f); // 610
console.log(count); // 1973
我們的count就是用來計(jì)數(shù)的,記錄到底執(zhí)行了多少次這個(gè)函數(shù)冠摄,發(fā)現(xiàn)1973次糯崎,當(dāng)num越大調(diào)用次數(shù)越大,建議不要num設(shè)置很大河泳,要么頁(yè)面卡死沃呢,要么爆棧了。
這里拋出時(shí)間復(fù)雜度與空間復(fù)雜度拆挥,如何計(jì)算薄霜,后續(xù)會(huì)有專門文章去寫。
- 時(shí)間復(fù)雜度:O(2^N)
- 空間復(fù)雜度:O(N)
時(shí)間復(fù)雜度是指數(shù)階,屬于爆炸增量函數(shù)惰瓜,在程序設(shè)計(jì)中我們應(yīng)該避免這樣的復(fù)雜度否副。
改進(jìn)版:
var data = [1, 1];
var count = 0;
function foo(num) {
count++;
var v = data[num];
if (v === undefined) {
v = foo(num - 1) + foo(num - 2);
data[num] = v;
}
return v;
}
foo(15);
console.log(count); // 29
大家可以看出這個(gè)遞歸
,其實(shí)我們也是對(duì)遞歸
進(jìn)行緩存優(yōu)化崎坊。
緩存思路:
- 普通遞歸备禀,最郁悶的地方是,每次進(jìn)入新的遞歸奈揍,之前算好的數(shù)據(jù)痹届,它還會(huì)重新計(jì)算。所以性能極差打月。
- 其實(shí)斐波那契數(shù)列本身就是一個(gè)數(shù)組,我們可以預(yù)定義一個(gè)數(shù)組data去緩存之前已經(jīng)計(jì)算的數(shù)據(jù)蚕捉,這樣深層遞歸就不用重新計(jì)算了奏篙。
- 當(dāng)大家用遞歸的時(shí)候一定要使用緩存機(jī)制,提高的性能超一般的大迫淹。新的時(shí)間復(fù)雜度:O(N) 秘通;空間復(fù)雜度:O(1)。
模塊化封裝:
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
模塊化適用:
MyModules.define('bar', [], function() {
function hello(who) {
return 'Let me introduce: ' + who;
}
return {
hello: hello
};
});
MyModules.define('foo', ['bar'], function(bar) {
var hungry = 'hippo';
function awesome() {
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome: awesome
};
});
var bar = MyModules.get('bar');
var foo = MyModules.get('foo');
當(dāng)然并非那么簡(jiǎn)單肺稀,你還需要做到:先加載所有定義好的模塊,然后再使用应民,這里推薦AMD话原、CMD、es6 module诲锹。
閉包存在缺點(diǎn)
有兩個(gè)缺點(diǎn):
普及下垃圾回收機(jī)制兩種方法:標(biāo)記清除和引用計(jì)數(shù)繁仁,有興趣的同學(xué)可以自行學(xué)習(xí)。
解決辦法:解除引用循環(huán)
function foo() {
var num = Math.random();
return function fn() {
return num;
};
}
var f = foo();
f = null; // 釋放內(nèi)存
當(dāng)我們使用閉包的時(shí)候归园,那么瀏覽器的GC(垃圾收集器)就無法自動(dòng)釋放內(nèi)存黄虱,當(dāng)你不使用的時(shí)候就設(shè)置
f = null
,下次GC運(yùn)行時(shí)就會(huì)去釋放緩存庸诱。
解決辦法:不需要的變量設(shè)置null
function assignHandler(){
var el= document.getElementById("div");
el.onclick = function(){
};
el = null;
}
更多內(nèi)容可以看我的集錄: 全面攻陷js:更新中...