http://www.cnblogs.com/wangfupeng1988/p/3986420.html
執(zhí)行上下文
什么是執(zhí)行上下文(也叫做“執(zhí)行上下文環(huán)境”)?暫且不下定義,先看一段代碼:
console.log(a) //ReferenceError: a is not defined
console.log(a) //undefined
var a
console.log(a) //undefined
var a = 100
第一句報錯,a未定義,很正常。第二句、第三句輸出都是undefined,說明瀏覽器在執(zhí)行console.log(a)時,已經(jīng)知道了a是undefined,但卻不知道a是10(第三句中)。
在一段js代碼拿過來真正一句一句運行之前,瀏覽器已經(jīng)做了一些準(zhǔn)備工作,也就是我們所說的變量提升。
不僅如此,需要注意代碼注釋中的兩個名詞——函數(shù)表達(dá)式和函數(shù)聲明。雖然兩者都很常用,但是這兩者在“準(zhǔn)備工作”時,卻是兩種待遇。

console.log(f1) //function f1(){}
function f1(){}
上例是函數(shù)聲明,解析文件的過程中是會把聲明先讀取,也就是瀏覽器知道了這個調(diào)用的函數(shù)是存在的
就算是在js文件中函數(shù)調(diào)用在前,函數(shù)聲明在后,這也是沒有問題的
console.log(f2); //undefined
var f2 = function(){}
上例是函數(shù)表達(dá)式,實際上也是一個var一個函數(shù)的過程
解析函數(shù)的時,瀏覽器會把var 的東西先提到最前面
即var f1 = undefined,沒有定義的,你調(diào)用它的函數(shù)有什么作用呢?
所以執(zhí)行肯定出現(xiàn)了錯誤
在script文件中,在文件執(zhí)行或者函數(shù)執(zhí)行的最開始,瀏覽器會最先讀取函數(shù)/變量的聲明,將一些賦值的變量也拿出來,也就是我們說的聲明提升(只有var 聲明的變量才會提升,let,const的不會)
我們要知道,在準(zhǔn)備工作中完成了哪些工作:
- 變量、函數(shù)表達(dá)式——變量聲明,默認(rèn)賦值為undefined;
- this——賦值;
- 函數(shù)聲明——賦值;
這三種數(shù)據(jù)的準(zhǔn)備情況我們稱之為“執(zhí)行上下文”或者“執(zhí)行上下文環(huán)境”。
為證明我們的觀點,在控制臺寫上以下代碼,你會看到
于是我們就可以理解下面兩種不同的情況
情況1
fn('zhangsan');
function fn(name){
alert(name);
} //運行后沒有錯誤,并且網(wǎng)頁彈出警告框
情況2
f1('zty');
var f1 = function(name ){
alert(name)
}

注意:函數(shù)聲明就是以function開頭的,函數(shù)賦值則不是
函數(shù)聲明中的函數(shù)可以是匿名的,函數(shù)賦值不可以。
讓我們來看看一道比較經(jīng)典的題:
function yideng() {
console.log(1);
}
(function () {
if (false) {
function yideng() {
console.log(2);
}
}
yideng();
})();
//yideng is not a function
在早期的瀏覽器中,彈出來的結(jié)果應(yīng)該是2,(因為變量提升,整個 function yideng() { }會被提升到整個函數(shù)作用域的頂端)但是如果彈出來是2的話,我們寫的if語句就完全沒有意義了。后來瀏覽器為了修正這個錯誤,像這種情況下面的函數(shù)提升就只是 var yideng提升到函數(shù)作用域的頂端,本題中false所以沒進(jìn)入函數(shù)體,所以yideng()就會報錯yideng is not a function。而不是像第一題那樣,整個函數(shù)體都提到前面。
據(jù)說除了if,好像while,switch,for也一樣。
關(guān)于this
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
上面這個是阮一峰老師的關(guān)于一些this的內(nèi)部機制的理解
在箭頭函數(shù)出來之前,我們只知道this要在函數(shù)執(zhí)行時才能知道指的是什么
主要場景
- 作為構(gòu)造函數(shù)執(zhí)行
function Foo(name){
this.name = name;
alert(name);
console.log(this.name);
}
var f1 = Foo('zhangsan'); //控制臺上面打印出zhangsan
var f2 = Foo('hello'); //打印出hello
在這里,this的存在就顯得比較有意義了,很多個對象都調(diào)用這個相同的方法,this到時指向很多對象,可以靈活使用,也就不用寫那么多的一樣的函數(shù)了
- 作為對象屬性執(zhí)行
var obj = {
name:'a',
fn: function(){
console.log(this.name);
}
}
obj.fn(); //a
這時候的this指向執(zhí)行這個屬性的對象 ,所以也就是a
想想,如果不是作為obj的屬性來調(diào)用時,會是怎么樣的一種情況?
var obj = {
name:'a',
fn: function(){
console.log(this.name);
console.log('kkk');
}
}
var fn1 = obj.fn;
fn1()//undefined
正如上面的代碼:如果fn函數(shù)被賦值到了另一個變量中,并沒有作為obj的一個屬性被調(diào)用,那么this的值就是window,this.x為undefined。
- 作為普通函數(shù)執(zhí)行
function fn(){
console.log(this);
}
fn(); // this指向全局對象
- call bind apply
function fn1(name,age){
console.log(name);
console.log(this);
}
fn1.call({x:100},'zhangsan',20);
//執(zhí)行結(jié)果:zhangsan {x:100)}
//也就說call能讓this指向它所指定的東西,在例子中 就是{x:100}
//常用的就是call.
apply用法與call相同,但是后面是以數(shù)組形式傳遞的
fn1.call({x:200},['zhangsan',20]);
var f2 = function(name){
console.log(name);
console.log(this);
}.bind({y:100},'lisi')
其實關(guān)于js中的this一直是前端一個經(jīng)久不衰的話題,很多筆試題都在this上面做文章。
this指向的四種情況我們就大概講到這里,來做幾道課后題吧!
var obj = {
x: 10,
fn: function(){
function f(){
console.log(this);//Window
}
f()
}
};
obj.fn()
什么?答案不是obj嗎,不是說作為對象屬性來執(zhí)行的時候是指向?qū)ο蟮膯幔?/p>

別急,且聽我慢慢道來!
函數(shù)f雖然是在obj.fn內(nèi)部定義的,但是它仍然是一個普通的函數(shù),this仍然指向window。作為對象屬性來執(zhí)行的時候是指向?qū)ο蟮倪@句話在這個函數(shù)里指的是fn而不是f,fn是在obj里面定義的,但f是在obj.fn里面定義的。不知道這個解釋你看懂了嗎?
var x = 3;
var y = 4;
var obj = {
x: 1,
y: 6,
getX: function() {
var x =5;
return function() {
return this.x;
}();
},
getY: function() {
var y =7;
return this.y;
}
}
console.log(obj.getX())
console.log(obj.getY())
先給兩分鐘的時間思考一下,這道題的結(jié)果應(yīng)該是:
3 6

讓我們來探究一下吧!
obj.getX跟我們上面說的題其實差不多,this所在的function其實只是定義在obj.getX中的一個普通函數(shù),this自然是指向Window的,從作用域的角度來看,他會先在第一層尋找自己的this.name,找到了,匿名函數(shù)的執(zhí)行環(huán)境具有全局性,而且這個時候是指向window的,因為這是一個普通的函數(shù)。所以應(yīng)該是3。
obj.getY就很循規(guī)蹈矩了,getY就是obj里面的一個屬性,作為obj的屬性執(zhí)行時指向obj,obj的作用域里面的x就等于6。
我們繼續(xù)。
var length = 10;
function fn(){
console.log(this.length);
}
var obj = {
length:5,
method:function(fn){
fn();
}
}
obj.method(fn);
答案是10,一樣的道理。function fn其實是指向全局的啦!
this指向也是ES6箭頭函數(shù)的一個重要的特性,下次再單獨用一篇文章來寫。
關(guān)于作用域
說到JS其中一座大山,就是我們大名鼎鼎的閉包了。
不過在說閉包之前,我們要先說說作用域!
首先應(yīng)該明確,Js無塊級作用域
#include <stdio.h>
void main() {
int i=2;
i--;
if(i) {
int j=3;
}
printf("%d/n",j);
}
以C語言為例,這個會彈出錯誤,因為j是在if的塊中定義的,如果跳出了這個塊,是訪問不到的。
但是在Js中,不同塊中的還是可以訪問的到的。
if(true){
var i = 7;
}console.log(i);//7
父級作用域
var a = 100;
function fn1(){
var b = 200;
console.log(a);
console.log(b);
}
fn1的父級作用域是window
函數(shù)里面定義的數(shù),在外面是改變不了的,外面是污染不了里面的數(shù)的。看下面的題:
var a = 100;
function f1(){
var b = 200;
console.log(a);
console.log(b);
}
var a = 10000;
var b = 20000;
f1();//10000 200;
javascript除了全局作用域之外,只有函數(shù)可以創(chuàng)建的作用域。
所以,我們在聲明變量時,全局代碼要在代碼前端聲明,函數(shù)中要在函數(shù)體一開始就聲明好。
之前就踩了一個坑!
let person = {
a:2,
say:()=>{
console.log(this.a)
}
}
因為我把方法寫在了對象里,而對象的括號是不能封閉作用域的。所以此時的this還是指向全局對象,所以不是指向person。
讓我們繼續(xù)說一下作用域:

如我們上圖:全局,fn,bar都分別有自己的作用域,作用域有上下級的關(guān)系,上下級關(guān)系的確定就看函數(shù)是在哪個作用域下創(chuàng)建的。例如,fn作用域下創(chuàng)建了bar函數(shù),那么“fn作用域”就是“bar作用域”的上級。
作用域的作用就是能隔離變量,不同作用域下同名變量不會有沖突。bar需要a就拿bar下面的a,fn需要a就拿fn下面的a,全局需要就拿全局的a。
ES6的let和我們所謂的塊結(jié)合就能達(dá)到我們所期待的塊級作用域的效果了。
作用域和上下文環(huán)境
閉包
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
閉包使用場景:
- 函數(shù)作用一個返回值
function f1(){
var a = 100;
return function(){
console.log(a);
}
}
var f2 = f1(); //f2(){console.log(a)},執(zhí)行時候去父作用域中尋找(定義時候的)
var a = 200;//這里的a 與上面的a 是兩碼事
f2(); //100,
- 函數(shù)作為一個參數(shù)來傳遞
function f1(){
var a = 100;
return function(){
console.log(a);
}
}
var f2 = f1();
function f3(fn){
var a = 200;
fn();
}
f3(f2); //100
閉包的特殊之處
先看一下這段代碼:
function compare(value1,value2){
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
var result = compare(5,10)
上面先定義了compare()函數(shù),然后在全局作用域中調(diào)用了他。
當(dāng)?shù)谝淮握{(diào)用compare()的時候,會創(chuàng)建一個包含this,arguments.value1,value2的活動對象,(位于compare()函數(shù)作用域鏈中的第一位),全局執(zhí)行環(huán)境的變量對象(this,result,compare)在compare()函數(shù)作用域的第二位。
后臺的環(huán)境都有一個表示變量的對象——變量對象,全局環(huán)境的變量對象始終存在,像compare()函數(shù)的變量對象,是局部變量的,是只在函數(shù)執(zhí)行過程中存在。創(chuàng)建compare這個函數(shù)的時候,會先創(chuàng)建一個預(yù)先包含全局變量對象的作用域鏈,保存在內(nèi)部的scope屬性中,當(dāng)你執(zhí)行compare這個函數(shù)的時候,會先為函數(shù)創(chuàng)建一個執(zhí)行環(huán)境,然后將scope屬性中的對象復(fù)制進(jìn)行你作用域鏈的構(gòu)建,此后又有一些函數(shù)內(nèi)部的一些變量對象被推進(jìn)到作用域鏈的前端,在compare()這個函數(shù)中,其作用域就包括全局變量和局部變量。作用域鏈本質(zhì)上是一個指向變量對象的指針列表。
一般來講,函數(shù)執(zhí)行完畢,局部變量(活動對象)就會被銷毀。內(nèi)存中僅保存全局作用域鏈,但是閉包的情況有所不同。
出現(xiàn)閉包的情況時,在函數(shù)內(nèi)部定義的函數(shù)會將外部函數(shù)的活動對象添加到它的作用域中,在下面的代碼中,匿名函數(shù)從f1()中被返回的時候,他的作用域鏈也被初始化為f1函數(shù)的活動對象的全局變量對象,匿名函數(shù)可以訪問所有在f1中定義的所有變量,更重要的是,在f1函數(shù)執(zhí)行完畢的時候,他的活動對象也不會被銷毀,因為匿名函數(shù)的作用域鏈仍然在引用這個活動對象,直到匿名函數(shù)被銷毀之后,f1的活動對象才會被銷毀。
function f1(){
var a = 100;
return function(){
console.log(a);
}
}
var f2 = f1() //利用上面這個函數(shù)來創(chuàng)建一個新的函數(shù)
var result = f2()//調(diào)用函數(shù)
f2 = null //解除對匿名函數(shù)的引用。
相關(guān)面試題目
說一下對于變量提升的理解
Js中函數(shù)和變量的聲明總是會被解析器提到方法體的最頂部。(注意,函數(shù)表達(dá)式不會哦)看上文的關(guān)于作用域這一小節(jié)
最簡單的例子:
console.log(f);
var f = 100; //undefined,被提到最前面去的只是var f(l類型是undefined)
var e = 100;
console.log(e); //100
fn('zhangsan');
function fn(name){
this.name = name;
alert(this.name);
}//運行的時候彈zhangsan,因為函數(shù)已經(jīng)被聲明了,function fn以及被提到最前面去了
f5('xiaosan');
var f5=function(name){
this.name = name;
alert(this.name);
} //出現(xiàn)錯誤,因為這是函數(shù)的賦值而不是聲明
閉包塊級作用域
一般會給出這樣的代碼,讓你看看可能的運行結(jié)果
請寫出如下點擊的輸出值,并用三種辦法正確輸出li里面的數(shù)字。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script type="text/javascript">
var list_li = document.getElementsByTagName("li");
for (var i = 0; i < list_li.length; i++) {
list_li[i].onclick = function() { console.log(i);
}
}
</script>
真的是很經(jīng)典的一道題啊?。。?!大家可以閉著眼睛說不管點擊哪個li都只會輸出6。(跟Js的單線程和異步隊列有關(guān))
解決方法有三個:
- ES6的let
for (let i = 0; i < list_li.length; i++) {
list_li[i].onclick = function() {
console.log(i+1);
}
}
關(guān)于let 可以看看暫時性死區(qū)這個知識點。
- 閉包,立即執(zhí)行函數(shù)
for(var i = 0; i < list_li.length; i++){
(function(i){
list_li[i].onclick = function(){
console.log(i+1)
}
})(i)
}
閉包保存這個變量。
- 最后一種方法是佳哥說出來的時候恍然大悟,這才是最in的解決方案啊我靠。這道題考的是this啊,不是閉包也不是作用域。。。
for (var i = 0; i < list_li.length; i++) {
list_li[i].onclick = function() {
console.log(this.innerHTML);
}
}
補充知識
立即執(zhí)行函數(shù)會為每次循環(huán)創(chuàng)建一個單獨的作用域
立即執(zhí)行的函數(shù)表達(dá)式,只要定義完成,不需要調(diào)用,馬上執(zhí)行
(function(){ })(i)
var a = 2;
(function foo(){
var a = 3;
console.log(a);//3
})();
console.log(a);//2
函數(shù)表達(dá)式后面加上一個括號會立即執(zhí)行。
有一個非常普遍的進(jìn)階用法,就是把他們當(dāng)作函數(shù)調(diào)用并且傳遞函數(shù)進(jìn)去
var a = 2;
(function IIFE(global){
var a = 3;
console.log(a);//3
})(window);
console.log(a);//2
如何理解作用域
自由變量
作用域鏈,即自由變量的查找
-
閉包的兩個場景
var a = 100; function f1(){ console.log(a);//此時在函數(shù)作用域中找不到a,我們把a 稱為自由變量 }
在f1作用域中找不到a ,我們就沿著作用域鏈去上一級尋找,a的父級作用域就是window(看f1是在哪里定義的)然后我們找到了var a = 100;于是開開心心的打印出 100
-
場景1:函數(shù)作為參數(shù)被傳遞
場景2:函數(shù)作為一個返回值。
一般大家是這么形容閉包的:當(dāng)一個函數(shù)的返回值是另外一個函數(shù),而返回的那個函數(shù)如果調(diào)用了其父函數(shù)內(nèi)部的其他值,如果返回的這個函數(shù)在外部被執(zhí)行,就產(chǎn)生了閉包。
閉包是指有權(quán)訪問另外一個函數(shù)作用域的變量的函數(shù)。
創(chuàng)建閉包的常見方式,就是在一個函數(shù)內(nèi)部創(chuàng)建另外一個函數(shù)
例如下面的例子,外部執(zhí)行了這個函數(shù),是可以訪問的到a 這個變量的,之所以能夠訪問得到這個變量,主要還是因為內(nèi)部函數(shù)的作用域中包含著外面這個函數(shù)f1的作用域。function f1(){ var a = 100; return function(){ console.log(a); } } var f2 = f1(); //f2(){console.log(a)},執(zhí)行時候去父作用域中尋找(定義時候的) var a = 200;//這里的a 與上面的a 是兩碼事 f2(); //100
本質(zhì)上無論何時何地,如果將函數(shù)當(dāng)作第一級的值類型并且到處傳遞,你就會看到閉包在這些函數(shù)中的應(yīng)用。在定時器,事件監(jiān)聽器,Ajax請求,跨窗口通信,Web Worker或者其他任務(wù)中,只要使用了回調(diào)函數(shù),實際上就是在使用閉包。
下面是一個由于沒有理解閉包和作用域而可能會遇到的坑
for(var i = 1;i <=5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000)
}//彈出來了5個6
與我們的預(yù)期不一樣,我們預(yù)期是依次彈出1,2,3,4,5
根據(jù)作用域的工作原理,實際情況是盡管循環(huán)中的五個函數(shù)是在各個迭
代中分別定義的,像上面的代碼中,他們都被封閉在一個共享的全局作用
域中,每次循環(huán),每個函數(shù)都保存著全局作用域中的活動對象,其中包括i
值,也就是說每個函數(shù)的作用域鏈中都保存著全局作用域中的i,當(dāng)主線程
中的i++這些操作結(jié)束之后,i已經(jīng)變成6了,閉包只能取得包含函數(shù)中任何
變量的最后一個值也就是6,在時間執(zhí)行到了timer函數(shù)的時候,因為js解析
的時候遇到setTimeout會把回調(diào)抽出來,放在一個單獨的隊列里面,等到
當(dāng)前處理機空閑了,再去執(zhí)行那個隊列里的東西。處理機先將for循環(huán)全都
處理完畢,再去執(zhí)行SetTimeout這個函數(shù)。此時全局變量的這個i就是6,
因此無法達(dá)到預(yù)期。
解決方法
for(var i = 1;i<=5;i++)
(function(i){
setTimeout(function timer(){
console.log(i); //i是自由變量,去父級作用域里面找,我們找到了立即函數(shù)傳進(jìn)來的i(每次for循環(huán)后都執(zhí)行一次立即函數(shù))
即函數(shù)為每次循環(huán)都開拓一個單獨的作用域(塊級作用域),在ES6中只要用{ }就是一個塊級作用域了,不用怕會被覆蓋
},i*1000)
})(i);
其實將i 前面的var 改成let 也是可以得到我們想要的效果,因為let聲明的也是一個塊級作用域。
上面的區(qū)別在于,我們加了一個自執(zhí)行函數(shù),只要定義,不需要調(diào)用,而且可以立馬執(zhí)行,還會產(chǎn)生一個專門的塊級作用域,(function(i))(j)就是把j的值傳給i,產(chǎn)生了這么一個塊級作用域。
閉包在實際開發(fā)中的作用
模塊化中經(jīng)常需要用到閉包
function f1(){
var something = "something";
return function(){
console.log(something);
}
}
var foo = f1();
foo(); //外面的根本觸碰不到函數(shù)里面的變量
實際運用2
用于封裝變量,收斂權(quán)限。
function firstLoad(){
var _list = [];//變量前面帶有_表明變量是私有變量
return function(id){
if(_list.indexOf(id) >= 0){
return false;//這個Id對應(yīng)的位置上已經(jīng)有東西
}else{
_list.push(id);//這個Id對應(yīng)的位置上壓東西進(jìn)去
return true;
}
}
}
var load = firstLoad();
load(10); //true ,第一次加載.
load(10); //false,因為上面已經(jīng)加載過了
主要就是只有通過firstload返回的函數(shù)才能去操作__list這個數(shù)組,如果你在外面直接去操作__list是不可能的。因為__list是沒有被直接暴露出來的。
理解了這兩段代碼的結(jié)果,也許你就理解了什么是閉包。
var name = "The Window";
var object = {
name:"my object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()());//var name = "The Window";
var name = "The Window";
var object = {
name:"my object",
getNameFunc:function(){
var that = this;
return function(){
return that.name;
};
}
};
object.getNameFunc()();
// "my object"
談?wù)勎易约旱睦斫猓驗槲覀兪紫纫磧蓚€函數(shù)其實都產(chǎn)生了閉包,但是在return function()中return this.name,我們要注意到這其實是一個匿名函數(shù),從作用域的角度來看,他會先在第一層尋找自己的this.name,找到了,匿名函數(shù)的執(zhí)行環(huán)境具有全局性,而且這個時候是指向window的,因為這是一個普通的函數(shù)。
第二段代碼中,將getNameFunc作用域里面的this保存在閉包可以訪問到的變量里,就可以讓閉包訪問這個對象。(argument也是同樣一個道理)
(也就是上面講的作為函數(shù)對象執(zhí)行)
var obj={name:'hhhh',
func:function(){
return this.name;}}
obj.func()//"hhhh"
//至于為什么是getNameFunc()();
var object = {
name:"my object",
getNameFunc:function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc());
// ? (){ return this.name; }
一個括號其實就是得到的return的值是那個function
再加一個括號就是再去執(zhí)行那個function
閉包為什么會造成內(nèi)存泄漏
先談?wù)動嫈?shù)垃圾收集,如果0個引用指向他,那么該對象就可以被垃圾收集了。但是計數(shù)垃圾收集有一個嚴(yán)重的問題,就是循環(huán)引用的對象是不能回收的,IE9之前就是用javascript引擎就是使用標(biāo)記清除策略來實現(xiàn)。
循環(huán)引用(兩個對象的相互引用),在函數(shù)調(diào)用之后他們是無用的可以釋放,但是計數(shù)算法認(rèn)為,由于兩個對象的每一個至少都是被引用一次的,兩者都不能被垃圾回收。例如下面的:
function problem(){
var ObjectA = new Object();
var ObjectB = new Object();
ObjectA.someOtherObject = ObjectB;
ObjectB.someOtherObject = ObjectA;
}
在IE9之前,BOM和DOM對象都是使用C++以COM對象的形式來實現(xiàn)的,COM對象的垃圾收集機制采用的就是計數(shù)策略,所以IE中只要涉及到BOM和DOM對象,就會存在循環(huán)引用的問題。
像下面這樣子:
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;
閉包實際上很容易造成JavaScript對象和DOM對象的隱蔽循環(huán)引用。也就是說,只要閉包的作用域鏈中保存著一個HTML元素,那么就意味著這個元素將無法被銷毀。
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert (element.id);
};
}
注意,閉包的概念是一個函數(shù)的返回值是另外一個函數(shù),返回的那個函數(shù)如果調(diào)用了其父函數(shù)內(nèi)部的其他值,這是一個element元素事件處理的一個閉包。這個閉包創(chuàng)建了一個循環(huán)引用。
- JavaScript對象element引用了一個DOM對象(其id為“someElement”); JS(element) ----> DOM(someElemet)
- 該DOM對象的onclick屬性引用了匿名函數(shù)閉包,而閉包可以引用外部函數(shù)assignHandler 的整個活動對象,包括element ; DOM(someElement.onclick) ---->JS(element)
匿名函數(shù)一直保存著對assginHandler()活動對象的引用,它所占的內(nèi)存永遠(yuǎn)不會被回收。
所以可以稍稍改進(jìn):
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert (id);
};
element = null;
}
可以首先通過引用一個副本變量消除循環(huán)引用,但是這個閉包包含外部函數(shù)的全部活動對象,所以就算不直接引用,匿名函數(shù)一直都包含著對element的引用。所以最后需要手動設(shè)置element變量為null.
閉包的很大作用是可以讀取函數(shù)內(nèi)部的變量,另一個就是讓這些變量的值始終保持在內(nèi)存中。
只要變量被任何一個閉包使用了,就會被添到詞法環(huán)境中,被該作用域下所有閉包共享。
關(guān)于函數(shù)提升
alert(a) 1
a(); 2
var a=3;
function a(){
alert(10)
}
alert(a) 3
a=6;
a(); 4
請寫出彈出值,并解釋為什么?
由于函數(shù)提升,整個式子會變成下面這樣
var a;
function a(){
alert(10)
}
alert(a) 1
a(); 2
a=3;
alert(a); 3
a=6;
a(); 4
很明顯,會得到下面的結(jié)果
1. f a(){alert(10)} //變量提升,并且函數(shù)優(yōu)先級高于變量,如果相同命名,變量會被覆蓋。
小知識:
console.log(c)//? c(){console.log('hahha')
function c(){
console.log('hahha')
}
var c = 1;
console.log(c) //? c(){console.log('hahha')
var c = 1;
function c(){
console.log('hahha')
}
- 10 //執(zhí)行了a(){alert(10)}
- 3 //給a賦值了3
- a is not a function 此時已經(jīng)給a賦值了6,a已經(jīng)不是函數(shù)了,所以會彈出錯誤
關(guān)于函數(shù)變量提升的一些特殊情況
function yideng() {
console.log(1);
}
(function () {
if (false) {
function yideng() {
console.log(2);
}
}
yideng();
})();
//yideng is not a function
在早期的瀏覽器中,彈出來的結(jié)果應(yīng)該是2,(因為變量提升,整個 function yideng() { }會被提升到整個函數(shù)作用域的頂端)但是如果彈出來是2的話,我們寫的if語句就完全沒有意義了。后來瀏覽器為了修正這個錯誤,像這種情況下面的函數(shù)提升就只是 `var yideng`提升到函數(shù)作用域的頂端,本題中false所以沒進(jìn)入函數(shù)體,所以yideng()就會報錯yideng is not a function。而不是像第一題那樣,整個函數(shù)體都提到前面。
據(jù)說除了if,好像while,switch,for也一樣。
補充小知識:
if(false){
var a = 1;
}
console.log(a)//undefined
關(guān)于this
寫出以下輸出值,解釋為什么?
this.a = 20;
var test = {
a: 40,
init:()=> {
console.log(this.a);
function go() {
console.log(this.a);
}
go.prototype.a = 50;
return go;
}
};
new(test.init())(); //20 50
箭頭函數(shù)的this綁定父級的詞法作用域,所以他的this.a固定了是20。
go本身沒有a 這個屬性,所以new出來的對象也沒有這個屬性,會到原型鏈上面去找。所以是50。
this.a = 20;
var test = {
a: 40,
init:()=> {
console.log(this.a);
function go() {
this.a = 60;
console.log(this.a);
}
go.prototype.a = 50;
return go;
}
};
var p = test.init(); //20
p(); // 60
new(test.init())(); // 60,60
剛開始我以為輸出的結(jié)果會是20 60 20 60
var p = test.init(); 此時test.init()會執(zhí)行一遍。此時的箭頭函數(shù)中的this綁定了最外層的this(test父級作用域的this),也就是this.a = 20
p() 此時相當(dāng)于執(zhí)行g(shù)o(),注意:這個時候是var p = test.init(),所以執(zhí)行p()時的this是指向windows,也就是我們現(xiàn)在看到的最外層的this.a = 20的那個this,此時會執(zhí)行this.a = 60,將windows的this.a 更改成60,執(zhí)行之后打印出來的this.a=60.
new(test.init())():test.init()會取到已經(jīng)綁定了的父級詞法作用域的this,不過此時的this.a已經(jīng)被改為60,所以打印出來60。繼續(xù)執(zhí)行,打印出60。
思考一下,如果是下面這種情況的話,會打印出來什么呢?

執(zhí)行的時候20
因為這里是var 不是this.a
另外一個小知識:
function test(){
console.log(this)
}
var obj = {
f:test
}
(obj.f)() //Cannot read property 'f' of undefined
?????什么鬼??
原來是因為沒有加;在瀏覽器看來
var obj = {f:test}(obj.f)() 是這樣連在一起的。那肯定會報錯??!
加上分號之后,我們再來看一下這段代碼運行的結(jié)果是怎么樣的。
function test(){
console.log(this)
}
var obj = {
f:test
};
(obj.f)()//{f: ?}
很明顯,指向了obj這個對象,因為是obj調(diào)用了test()。
那如果我們再這樣改一下:
function test(){
console.log(this)
}
var obj = {
f:test
};
(false || obj.f)()
猜猜結(jié)果是什么?是window
?????這又是什么鬼???
短路語句的結(jié)果不也是obj.f,運算的結(jié)果應(yīng)該也是obj才對啊。
但是()里面是計算,所以里面應(yīng)該是表達(dá)式,也就是說里面相當(dāng)于var xx = obj.f,然后xx(),此時this肯定是指向全局變量window
關(guān)于閉包塊級作用域
請寫出如下點擊的輸出值,并用三種辦法正確輸出li里面的數(shù)字。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script type="text/javascript">
var list_li = document.getElementsByTagName("li");
for (var i = 0; i < list_li.length; i++) {
list_li[i].onclick = function() { console.log(i);
}
}
</script>
真的是很經(jīng)典的一道題?。。。?!大家可以閉著眼睛說不管點擊哪個li都只會輸出6。(跟Js的單線程和異步隊列有關(guān))
解決方法有三個:
-
ES6的let
for (let i = 0; i < list_li.length; i++) { list_li[i].onclick = function() { console.log(i+1); } }
關(guān)于let 可以看看暫時性死區(qū)這個知識點。
-
閉包,立即執(zhí)行函數(shù)
for(var i = 0; i < list_li.length; i++){ (function(i){ list_li[i].onclick = function(){ console.log(i+1) } })(i) }
閉包保存這個變量。怎么去理解閉包保存這個變量呢,也就是說這里每次執(zhí)行立即執(zhí)行函數(shù)的時候,會傳遞一個i(實參)進(jìn)去函數(shù)級的作用域,在這個函數(shù)級作用域里面onclick的回調(diào)函數(shù)使用了這個i,也就是我們常說的閉包了,還要注意的一點是i在這里的按值傳遞的,所以每個i都是獨立的各不影響的。
-
最后一種方法是佳哥說出來的時候恍然大悟,這才是最in的解決方案啊我靠。這道題考的是this啊,不是閉包也不是作用域。。。
for (var i = 0; i < list_li.length; i++) { list_li[i].onclick = function() { console.log(this.innerHTML); } }