作用域與閉包

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

第一句報(bào)錯(cuò)罕拂,a未定義虾标,很正常淮阐。第二句蔓腐、第三句輸出都是undefined矩乐,說(shuō)明瀏覽器在執(zhí)行console.log(a)時(shí),已經(jīng)知道了a是undefined,但卻不知道a是10(第三句中)散罕。

在一段js代碼拿過(guò)來(lái)真正一句一句運(yùn)行之前分歇,瀏覽器已經(jīng)做了一些準(zhǔn)備工作,也就是我們所說(shuō)的變量提升欧漱。

不僅如此职抡,需要注意代碼注釋中的兩個(gè)名詞——函數(shù)表達(dá)式函數(shù)聲明。雖然兩者都很常用误甚,但是這兩者在“準(zhǔn)備工作”時(shí)缚甩,卻是兩種待遇。

image.png
console.log(f1)  //function f1(){}
function f1(){} 

上例是函數(shù)聲明,解析文件的過(guò)程中是會(huì)把聲明先讀取,也就是瀏覽器知道了這個(gè)調(diào)用的函數(shù)是存在的

就算是在js文件中函數(shù)調(diào)用在前窑邦,函數(shù)聲明在后擅威,這也是沒(méi)有問(wèn)題的

console.log(f2); //undefined
var f2 = function(){}

上例是函數(shù)表達(dá)式,實(shí)際上也是一個(gè)var一個(gè)函數(shù)的過(guò)程

解析函數(shù)的時(shí),瀏覽器會(huì)把var 的東西先提到最前面

即var f1 = undefined奕翔,沒(méi)有定義的裕寨,你調(diào)用它的函數(shù)有什么作用呢?

所以執(zhí)行肯定出現(xiàn)了錯(cuò)誤

在script文件中派继,在文件執(zhí)行或者函數(shù)執(zhí)行的最開(kāi)始宾袜,瀏覽器會(huì)最先讀取函數(shù)/變量的聲明,將一些賦值的變量也拿出來(lái)驾窟,也就是我們說(shuō)的聲明提升(只有var 聲明的變量才會(huì)提升庆猫,let,const的不會(huì))

我們要知道,在準(zhǔn)備工作中完成了哪些工作:

  • 變量绅络、函數(shù)表達(dá)式——變量聲明月培,默認(rèn)賦值為undefined;
  • this——賦值恩急;
  • 函數(shù)聲明——賦值杉畜;

這三種數(shù)據(jù)的準(zhǔn)備情況我們稱(chēng)之為“執(zhí)行上下文”或者“執(zhí)行上下文環(huán)境”。

為證明我們的觀點(diǎn)衷恭,在控制臺(tái)寫(xiě)上以下代碼此叠,你會(huì)看到

于是我們就可以理解下面兩種不同的情況
情況1

fn('zhangsan');
function fn(name){
   alert(name);
}  //運(yùn)行后沒(méi)有錯(cuò)誤,并且網(wǎng)頁(yè)彈出警告框

情況2

f1('zty');
var f1 = function(name ){
  alert(name)
}
函數(shù).png

注意:函數(shù)聲明就是以function開(kāi)頭的随珠,函數(shù)賦值則不是
函數(shù)聲明中的函數(shù)可以是匿名的灭袁,函數(shù)賦值不可以。

讓我們來(lái)看看一道比較經(jīng)典的題:

    function yideng() { 
        console.log(1);
    }
    (function () { 
        if (false) {
            function yideng() {
                console.log(2);
            }
        }
        yideng();
    })();
    //yideng is not a function

在早期的瀏覽器中窗看,彈出來(lái)的結(jié)果應(yīng)該是2,(因?yàn)樽兞刻嵘灼纾麄€(gè) function yideng() { }會(huì)被提升到整個(gè)函數(shù)作用域的頂端)但是如果彈出來(lái)是2的話,我們寫(xiě)的if語(yǔ)句就完全沒(méi)有意義了显沈。后來(lái)瀏覽器為了修正這個(gè)錯(cuò)誤软瞎,像這種情況下面的函數(shù)提升就只是 var yideng提升到函數(shù)作用域的頂端,本題中false所以沒(méi)進(jìn)入函數(shù)體,所以yideng()就會(huì)報(bào)錯(cuò)yideng is not a function铜涉。而不是像第一題那樣智玻,整個(gè)函數(shù)體都提到前面。
據(jù)說(shuō)除了if芙代,好像while吊奢,switch民逼,for也一樣丁寄。

關(guān)于this

http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
上面這個(gè)是阮一峰老師的關(guān)于一些this的內(nèi)部機(jī)制的理解

在箭頭函數(shù)出來(lái)之前,我們只知道this要在函數(shù)執(zhí)行時(shí)才能知道指的是什么

主要場(chǎng)景

  1. 作為構(gòu)造函數(shù)執(zhí)行
    function Foo(name){        
        this.name = name;
        alert(name);
        console.log(this.name);
    }
    var f1 = Foo('zhangsan');  //控制臺(tái)上面打印出zhangsan
    var f2 = Foo('hello'); //打印出hello

在這里菲茬,this的存在就顯得比較有意義了铺呵,很多個(gè)對(duì)象都調(diào)用這個(gè)相同的方法裹驰,this到時(shí)指向很多對(duì)象,可以靈活使用片挂,也就不用寫(xiě)那么多的一樣的函數(shù)了

  1. 作為對(duì)象屬性執(zhí)行
    var obj = {
        name:'a',
        fn: function(){
            console.log(this.name);
      }
    }
     obj.fn();   //a

這時(shí)候的this指向執(zhí)行這個(gè)屬性的對(duì)象 幻林,所以也就是a
想想,如果不是作為obj的屬性來(lái)調(diào)用時(shí)音念,會(huì)是怎么樣的一種情況沪饺?

    var obj = {
        name:'a',
        fn: function(){
            console.log(this.name);  
            console.log('kkk');
      }
    }
    var fn1 =  obj.fn;  
    fn1()//undefined

正如上面的代碼:如果fn函數(shù)被賦值到了另一個(gè)變量中,并沒(méi)有作為obj的一個(gè)屬性被調(diào)用闷愤,那么this的值就是window整葡,this.x為undefined。

  1. 作為普通函數(shù)執(zhí)行
    function fn(){
        console.log(this);
    }
    fn(); // this指向全局對(duì)象
  1. call bind apply
    function fn1(name讥脐,age){
          console.log(name);
          console.log(this);
        }
    fn1.call({x:100},'zhangsan'遭居,20);
    //執(zhí)行結(jié)果:zhangsan  {x:100)}
    //也就說(shuō)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')

其實(shí)關(guān)于js中的this一直是前端一個(gè)經(jīng)久不衰的話題旬渠,很多筆試題都在this上面做文章俱萍。

this指向的四種情況我們就大概講到這里,來(lái)做幾道課后題吧告丢!

var obj = {
    x: 10,
    fn: function(){
        function f(){
            console.log(this);//Window
        }
        f()
    }
};
obj.fn()

什么鼠次?答案不是obj嗎,不是說(shuō)作為對(duì)象屬性來(lái)執(zhí)行的時(shí)候是指向?qū)ο蟮膯幔?/p>


別急芋齿,且聽(tīng)我慢慢道來(lái)!

函數(shù)f雖然是在obj.fn內(nèi)部定義的成翩,但是它仍然是一個(gè)普通的函數(shù)觅捆,this仍然指向window。作為對(duì)象屬性來(lái)執(zhí)行的時(shí)候是指向?qū)ο蟮倪@句話在這個(gè)函數(shù)里指的是fn而不是f麻敌,fn是在obj里面定義的栅炒,但f是在obj.fn里面定義的。不知道這個(gè)解釋你看懂了嗎?

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())

先給兩分鐘的時(shí)間思考一下赢赊,這道題的結(jié)果應(yīng)該是:
3 6

image.png

讓我們來(lái)探究一下吧乙漓!
obj.getX跟我們上面說(shuō)的題其實(shí)差不多,this所在的function其實(shí)只是定義在obj.getX中的一個(gè)普通函數(shù)释移,this自然是指向Window的叭披,從作用域的角度來(lái)看,他會(huì)先在第一層尋找自己的this.name,找到了玩讳,匿名函數(shù)的執(zhí)行環(huán)境具有全局性涩蜘,而且這個(gè)時(shí)候是指向window的,因?yàn)檫@是一個(gè)普通的函數(shù)熏纯。所以應(yīng)該是3同诫。
obj.getY就很循規(guī)蹈矩了,getY就是obj里面的一個(gè)屬性樟澜,作為obj的屬性執(zhí)行時(shí)指向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其實(shí)是指向全局的啦萍膛!

this指向也是ES6箭頭函數(shù)的一個(gè)重要的特性吭服,下次再單獨(dú)用一篇文章來(lái)寫(xiě)。

關(guān)于作用域

說(shuō)到JS其中一座大山蝗罗,就是我們大名鼎鼎的閉包了艇棕。
不過(guò)在說(shuō)閉包之前,我們要先說(shuō)說(shuō)作用域串塑!

首先應(yīng)該明確沼琉,Js無(wú)塊級(jí)作用域

    #include <stdio.h> 
    void main() { 
      int i=2; 
      i--; 
      if(i) { 
        int j=3; 
     } 
      printf("%d/n",j); 
    }

以C語(yǔ)言為例,這個(gè)會(huì)彈出錯(cuò)誤桩匪,因?yàn)閖是在if的塊中定義的打瘪,如果跳出了這個(gè)塊,是訪問(wèn)不到的傻昙。
但是在Js中闺骚,不同塊中的還是可以訪問(wèn)的到的。

if(true){
  var i = 7;
}console.log(i);//7

父級(jí)作用域

var a = 100;
    function fn1(){
        var b = 200;
        console.log(a);
        console.log(b);
    }

fn1的父級(jí)作用域是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)建的作用域敦捧。

所以,我們?cè)诼暶髯兞繒r(shí)碰镜,全局代碼要在代碼前端聲明兢卵,函數(shù)中要在函數(shù)體一開(kāi)始就聲明好。

之前就踩了一個(gè)坑绪颖!

let person = {
    a:2,
    say:()=>{
        console.log(this.a)
    }
}

因?yàn)槲野逊椒▽?xiě)在了對(duì)象里秽荤,而對(duì)象的括號(hào)是不能封閉作用域的。所以此時(shí)的this還是指向全局對(duì)象菠发,所以不是指向person王滤。

讓我們繼續(xù)說(shuō)一下作用域:

image.png

如我們上圖:全局,fn滓鸠,bar都分別有自己的作用域雁乡,作用域有上下級(jí)的關(guān)系,上下級(jí)關(guān)系的確定就看函數(shù)是在哪個(gè)作用域下創(chuàng)建的糜俗。例如踱稍,fn作用域下創(chuàng)建了bar函數(shù),那么“fn作用域”就是“bar作用域”的上級(jí)悠抹。

作用域的作用就是能隔離變量珠月,不同作用域下同名變量不會(huì)有沖突。bar需要a就拿bar下面的a楔敌,fn需要a就拿fn下面的a,全局需要就拿全局的a啤挎。

ES6的let和我們所謂的塊結(jié)合就能達(dá)到我們所期待的塊級(jí)作用域的效果了。

作用域和上下文環(huán)境

閉包

http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

閉包使用場(chǎng)景:
  1. 函數(shù)作用一個(gè)返回值
    function f1(){
        var a = 100;
        return function(){
            console.log(a);
        }
    }

    var f2 = f1(); //f2(){console.log(a)}卵凑,執(zhí)行時(shí)候去父作用域中尋找(定義時(shí)候的)
    var a = 200;//這里的a 與上面的a 是兩碼事
    f2(); //100,
  1. 函數(shù)作為一個(gè)參數(shù)來(lái)傳遞
        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()的時(shí)候勺卢,會(huì)創(chuàng)建一個(gè)包含this,arguments.value1,value2的活動(dòng)對(duì)象伙判,(位于compare()函數(shù)作用域鏈中的第一位),全局執(zhí)行環(huán)境的變量對(duì)象(this,result,compare)在compare()函數(shù)作用域的第二位黑忱。

后臺(tái)的環(huán)境都有一個(gè)表示變量的對(duì)象——變量對(duì)象宴抚,全局環(huán)境的變量對(duì)象始終存在,像compare()函數(shù)的變量對(duì)象甫煞,是局部變量的菇曲,是只在函數(shù)執(zhí)行過(guò)程中存在。創(chuàng)建compare這個(gè)函數(shù)的時(shí)候抚吠,會(huì)先創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈羊娃,保存在內(nèi)部的scope屬性中,當(dāng)你執(zhí)行compare這個(gè)函數(shù)的時(shí)候埃跷,會(huì)先為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境蕊玷,然后將scope屬性中的對(duì)象復(fù)制進(jìn)行你作用域鏈的構(gòu)建,此后又有一些函數(shù)內(nèi)部的一些變量對(duì)象被推進(jìn)到作用域鏈的前端弥雹,在compare()這個(gè)函數(shù)中垃帅,其作用域就包括全局變量和局部變量。作用域鏈本質(zhì)上是一個(gè)指向變量對(duì)象的指針列表剪勿。

一般來(lái)講贸诚,函數(shù)執(zhí)行完畢,局部變量(活動(dòng)對(duì)象)就會(huì)被銷(xiāo)毀厕吉。內(nèi)存中僅保存全局作用域鏈酱固,但是閉包的情況有所不同。

出現(xiàn)閉包的情況時(shí)头朱,在函數(shù)內(nèi)部定義的函數(shù)會(huì)將外部函數(shù)的活動(dòng)對(duì)象添加到它的作用域中运悲,在下面的代碼中,匿名函數(shù)從f1()中被返回的時(shí)候项钮,他的作用域鏈也被初始化為f1函數(shù)的活動(dòng)對(duì)象的全局變量對(duì)象班眯,匿名函數(shù)可以訪問(wèn)所有在f1中定義的所有變量,更重要的是烁巫,在f1函數(shù)執(zhí)行完畢的時(shí)候署隘,他的活動(dòng)對(duì)象也不會(huì)被銷(xiāo)毀,因?yàn)槟涿瘮?shù)的作用域鏈仍然在引用這個(gè)活動(dòng)對(duì)象亚隙,直到匿名函數(shù)被銷(xiāo)毀之后磁餐,f1的活動(dòng)對(duì)象才會(huì)被銷(xiāo)毀。

  function f1(){
    var a = 100;
    return function(){
    console.log(a);
    }
  }
  var f2 = f1()  //利用上面這個(gè)函數(shù)來(lái)創(chuàng)建一個(gè)新的函數(shù)
  var result = f2()//調(diào)用函數(shù)
  f2 = null //解除對(duì)匿名函數(shù)的引用阿弃。

相關(guān)面試題目

說(shuō)一下對(duì)于變量提升的理解

Js中函數(shù)和變量的聲明總是會(huì)被解析器提到方法體的最頂部诊霹。(注意,函數(shù)表達(dá)式不會(huì)哦)看上文的關(guān)于作用域這一小節(jié)
最簡(jiǎn)單的例子:

console.log(f);
var  f = 100;  //undefined,被提到最前面去的只是var f(l類(lèi)型是undefined)

var e = 100;
console.log(e);  //100
    
fn('zhangsan');
function fn(name){
  this.name = name;
  alert(this.name);
 }//運(yùn)行的時(shí)候彈zhangsan恤浪,因?yàn)楹瘮?shù)已經(jīng)被聲明了畅哑,function fn以及被提到最前面去了

f5('xiaosan');
var f5=function(name){
  this.name = name;
  alert(this.name);
 } //出現(xiàn)錯(cuò)誤,因?yàn)檫@是函數(shù)的賦值而不是聲明
閉包塊級(jí)作用域

一般會(huì)給出這樣的代碼水由,讓你看看可能的運(yùn)行結(jié)果

請(qǐng)寫(xiě)出如下點(diǎ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)典的一道題吧翱汀D嗾拧!>现怠媚创!大家可以閉著眼睛說(shuō)不管點(diǎn)擊哪個(gè)li都只會(huì)輸出6。(跟Js的單線程和異步隊(duì)列有關(guān))

解決方法有三個(gè):

  • ES6的let
    for (let i = 0; i < list_li.length; i++) {
        list_li[i].onclick = function() { 
            console.log(i+1);
        }
    }

關(guān)于let 可以看看暫時(shí)性死區(qū)這個(gè)知識(shí)點(diǎn)彤恶。

  • 閉包钞钙,立即執(zhí)行函數(shù)
  for(var i = 0; i < list_li.length; i++){
    (function(i){
        list_li[i].onclick = function(){
            console.log(i+1)
        }
    })(i)
  }

閉包保存這個(gè)變量鳄橘。

  • 最后一種方法是佳哥說(shuō)出來(lái)的時(shí)候恍然大悟,這才是最in的解決方案啊我靠芒炼。這道題考的是this啊瘫怜,不是閉包也不是作用域。本刽。鲸湃。
    for (var  i = 0; i < list_li.length; i++) {
        list_li[i].onclick = function() { 
            console.log(this.innerHTML);
        }
    }
補(bǔ)充知識(shí)

立即執(zhí)行函數(shù)會(huì)為每次循環(huán)創(chuàng)建一個(gè)單獨(dú)的作用域

立即執(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á)式后面加上一個(gè)括號(hào)會(huì)立即執(zhí)行。
有一個(gè)非常普遍的進(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
如何理解作用域
  1. 自由變量

  2. 作用域鏈炸裆,即自由變量的查找

  3. 閉包的兩個(gè)場(chǎng)景

     var a = 100;
         function f1(){
         console.log(a);//此時(shí)在函數(shù)作用域中找不到a,我們把a(bǔ) 稱(chēng)為自由變量
     }
    

在f1作用域中找不到a ,我們就沿著作用域鏈去上一級(jí)尋找蝙寨,a的父級(jí)作用域就是window(看f1是在哪里定義的)然后我們找到了var a = 100;于是開(kāi)開(kāi)心心的打印出 100

  1. 場(chǎng)景1:函數(shù)作為參數(shù)被傳遞
    場(chǎng)景2:函數(shù)作為一個(gè)返回值晒衩。
    一般大家是這么形容閉包的:當(dāng)一個(gè)函數(shù)的返回值是另外一個(gè)函數(shù),而返回的那個(gè)函數(shù)如果調(diào)用了其父函數(shù)內(nèi)部的其他值墙歪,如果返回的這個(gè)函數(shù)在外部被執(zhí)行听系,就產(chǎn)生了閉包。
    閉包是指有權(quán)訪問(wèn)另外一個(gè)函數(shù)作用域的變量的函數(shù)虹菲。
    創(chuàng)建閉包的常見(jiàn)方式靠胜,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另外一個(gè)函數(shù)
    例如下面的例子,外部執(zhí)行了這個(gè)函數(shù)毕源,是可以訪問(wèn)的到a 這個(gè)變量的浪漠,之所以能夠訪問(wèn)得到這個(gè)變量,主要還是因?yàn)閮?nèi)部函數(shù)的作用域中包含著外面這個(gè)函數(shù)f1的作用域霎褐。

     function f1(){
         var a = 100;
         return function(){
             console.log(a);
         }
     }
    
     var f2 = f1(); //f2(){console.log(a)}址愿,執(zhí)行時(shí)候去父作用域中尋找(定義時(shí)候的)
     var a = 200;//這里的a 與上面的a 是兩碼事
     f2(); //100
    

本質(zhì)上無(wú)論何時(shí)何地,如果將函數(shù)當(dāng)作第一級(jí)的值類(lèi)型并且到處傳遞冻璃,你就會(huì)看到閉包在這些函數(shù)中的應(yīng)用响谓。在定時(shí)器,事件監(jiān)聽(tīng)器省艳,Ajax請(qǐng)求娘纷,跨窗口通信,Web Worker或者其他任務(wù)中跋炕,只要使用了回調(diào)函數(shù)赖晶,實(shí)際上就是在使用閉包。

下面是一個(gè)由于沒(méi)有理解閉包和作用域而可能會(huì)遇到的坑

    for(var i = 1;i <=5;i++){
        setTimeout(function timer(){
            console.log(i);
        },i*1000)
    }//彈出來(lái)了5個(gè)6

與我們的預(yù)期不一樣辐烂,我們預(yù)期是依次彈出1,2,3,4,5
根據(jù)作用域的工作原理遏插,實(shí)際情況是盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭
代中分別定義的捂贿,像上面的代碼中,他們都被封閉在一個(gè)共享的全局作用
域中涩堤,每次循環(huán)眷蜓,每個(gè)函數(shù)都保存著全局作用域中的活動(dòng)對(duì)象,其中包括i
值胎围,也就是說(shuō)每個(gè)函數(shù)的作用域鏈中都保存著全局作用域中的i,當(dāng)主線程
中的i++這些操作結(jié)束之后德召,i已經(jīng)變成6了白魂,閉包只能取得包含函數(shù)中任何
變量的最后一個(gè)值也就是6,在時(shí)間執(zhí)行到了timer函數(shù)的時(shí)候上岗,因?yàn)閖s解析
的時(shí)候遇到setTimeout會(huì)把回調(diào)抽出來(lái)福荸,放在一個(gè)單獨(dú)的隊(duì)列里面,等到
當(dāng)前處理機(jī)空閑了肴掷,再去執(zhí)行那個(gè)隊(duì)列里的東西敬锐。處理機(jī)先將for循環(huán)全都
處理完畢,再去執(zhí)行SetTimeout這個(gè)函數(shù)呆瞻。此時(shí)全局變量的這個(gè)i就是6台夺,
因此無(wú)法達(dá)到預(yù)期。

解決方法

for(var i = 1;i<=5;i++)
    (function(i){
        setTimeout(function timer(){
            console.log(i);  //i是自由變量痴脾,去父級(jí)作用域里面找颤介,我們找到了立即函數(shù)傳進(jìn)來(lái)的i(每次for循環(huán)后都執(zhí)行一次立即函數(shù))
即函數(shù)為每次循環(huán)都開(kāi)拓一個(gè)單獨(dú)的作用域(塊級(jí)作用域),在ES6中只要用{  }就是一個(gè)塊級(jí)作用域了赞赖,不用怕會(huì)被覆蓋
        },i*1000)
    })(i);

其實(shí)將i 前面的var 改成let 也是可以得到我們想要的效果滚朵,因?yàn)閘et聲明的也是一個(gè)塊級(jí)作用域。

上面的區(qū)別在于前域,我們加了一個(gè)自執(zhí)行函數(shù)辕近,只要定義,不需要調(diào)用匿垄,而且可以立馬執(zhí)行移宅,還會(huì)產(chǎn)生一個(gè)專(zhuān)門(mén)的塊級(jí)作用域,(function(i))(j)就是把j的值傳給i,產(chǎn)生了這么一個(gè)塊級(jí)作用域年堆。
閉包在實(shí)際開(kāi)發(fā)中的作用
模塊化中經(jīng)常需要用到閉包
    function f1(){
        var something = "something";
        return function(){
            console.log(something);
        }
    }

    var foo = f1();
    foo(); //外面的根本觸碰不到函數(shù)里面的變量
實(shí)際運(yùn)用2

用于封裝變量吞杭,收斂權(quán)限。

    function firstLoad(){
        var _list = [];//變量前面帶有_表明變量是私有變量
        return function(id){
            if(_list.indexOf(id) >= 0){
                return false;//這個(gè)Id對(duì)應(yīng)的位置上已經(jīng)有東西
            }else{
                _list.push(id);//這個(gè)Id對(duì)應(yīng)的位置上壓東西進(jìn)去
                return true;
            }
        }
    }

    var load = firstLoad();
    load(10); //true ,第一次加載.
    load(10); //false,因?yàn)樯厦嬉呀?jīng)加載過(guò)了

主要就是只有通過(guò)firstload返回的函數(shù)才能去操作__list這個(gè)數(shù)組变丧,如果你在外面直接去操作__list是不可能的芽狗。因?yàn)開(kāi)_list是沒(méi)有被直接暴露出來(lái)的。

理解了這兩段代碼的結(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ù)勎易约旱睦斫獾尉ⅲ驗(yàn)槲覀兪紫纫磧蓚€(gè)函數(shù)其實(shí)都產(chǎn)生了閉包,但是在return function()中return this.name顾复,我們要注意到這其實(shí)是一個(gè)匿名函數(shù)班挖,從作用域的角度來(lái)看,他會(huì)先在第一層尋找自己的this.name,找到了芯砸,匿名函數(shù)的執(zhí)行環(huán)境具有全局性萧芙,而且這個(gè)時(shí)候是指向window的,因?yàn)檫@是一個(gè)普通的函數(shù)假丧。

第二段代碼中双揪,將getNameFunc作用域里面的this保存在閉包可以訪問(wèn)到的變量里,就可以讓閉包訪問(wèn)這個(gè)對(duì)象包帚。(argument也是同樣一個(gè)道理)

(也就是上面講的作為函數(shù)對(duì)象執(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; }

一個(gè)括號(hào)其實(shí)就是得到的return的值是那個(gè)function
再加一個(gè)括號(hào)就是再去執(zhí)行那個(gè)function

閉包為什么會(huì)造成內(nèi)存泄漏

先談?wù)動(dòng)?jì)數(shù)垃圾收集渔期,如果0個(gè)引用指向他,那么該對(duì)象就可以被垃圾收集了渴邦。但是計(jì)數(shù)垃圾收集有一個(gè)嚴(yán)重的問(wèn)題疯趟,就是循環(huán)引用的對(duì)象是不能回收的,IE9之前就是用javascript引擎就是使用標(biāo)記清除策略來(lái)實(shí)現(xiàn)谋梭。
循環(huán)引用(兩個(gè)對(duì)象的相互引用)信峻,在函數(shù)調(diào)用之后他們是無(wú)用的可以釋放,但是計(jì)數(shù)算法認(rèn)為章蚣,由于兩個(gè)對(duì)象的每一個(gè)至少都是被引用一次的站欺,兩者都不能被垃圾回收。例如下面的:

    function problem(){
        var ObjectA = new Object();
        var ObjectB = new Object();

        ObjectA.someOtherObject = ObjectB;
        ObjectB.someOtherObject = ObjectA;
        }

在IE9之前纤垂,BOM和DOM對(duì)象都是使用C++以COM對(duì)象的形式來(lái)實(shí)現(xiàn)的矾策,COM對(duì)象的垃圾收集機(jī)制采用的就是計(jì)數(shù)策略,所以IE中只要涉及到BOM和DOM對(duì)象峭沦,就會(huì)存在循環(huán)引用的問(wèn)題贾虽。
像下面這樣子:

      var element = document.getElementById("some_element");
      var myObject = new Object();
      myObject.element = element;
      element.someObject = myObject;

閉包實(shí)際上很容易造成JavaScript對(duì)象和DOM對(duì)象的隱蔽循環(huán)引用。也就是說(shuō)吼鱼,只要閉包的作用域鏈中保存著一個(gè)HTML元素蓬豁,那么就意味著這個(gè)元素將無(wú)法被銷(xiāo)毀。

      function assignHandler(){
        var element = document.getElementById("someElement");
        element.onclick = function(){
            alert (element.id);
       }; 
    }

注意菇肃,閉包的概念是一個(gè)函數(shù)的返回值是另外一個(gè)函數(shù)地粪,返回的那個(gè)函數(shù)如果調(diào)用了其父函數(shù)內(nèi)部的其他值,這是一個(gè)element元素事件處理的一個(gè)閉包琐谤。這個(gè)閉包創(chuàng)建了一個(gè)循環(huán)引用蟆技。

  1. JavaScript對(duì)象element引用了一個(gè)DOM對(duì)象(其id為“someElement”); JS(element) ----> DOM(someElemet)
  2. 該DOM對(duì)象的onclick屬性引用了匿名函數(shù)閉包,而閉包可以引用外部函數(shù)assignHandler 的整個(gè)活動(dòng)對(duì)象质礼,包括element 旺聚; DOM(someElement.onclick) ---->JS(element)
    匿名函數(shù)一直保存著對(duì)assginHandler()活動(dòng)對(duì)象的引用,它所占的內(nèi)存永遠(yuǎn)不會(huì)被回收眶蕉。

所以可以稍稍改進(jìn):

        function assignHandler(){
        var element = document.getElementById("someElement");
        var id = element.id;
        element.onclick = function(){
            alert (id);
       }; 
       element = null;
    }

可以首先通過(guò)引用一個(gè)副本變量消除循環(huán)引用砰粹,但是這個(gè)閉包包含外部函數(shù)的全部活動(dòng)對(duì)象,所以就算不直接引用造挽,匿名函數(shù)一直都包含著對(duì)element的引用碱璃。所以最后需要手動(dòng)設(shè)置element變量為null.

閉包的很大作用是可以讀取函數(shù)內(nèi)部的變量,另一個(gè)就是讓這些變量的值始終保持在內(nèi)存中饭入。

只要變量被任何一個(gè)閉包使用了厘贼,就會(huì)被添到詞法環(huán)境中,被該作用域下所有閉包共享圣拄。

關(guān)于函數(shù)提升
    alert(a)    1
    a();   2
    var a=3;
    function a(){
        alert(10)
    } 
    alert(a)    3
    a=6;
    a();   4

請(qǐng)寫(xiě)出彈出值,并解釋為什么毁欣?
由于函數(shù)提升庇谆,整個(gè)式子會(huì)變成下面這樣

  var a;
  function a(){
     alert(10)
  } 
  alert(a)    1
  a();   2
  a=3;
  alert(a);   3    
  a=6;
  a();   4

很明顯凭疮,會(huì)得到下面的結(jié)果
1. f a(){alert(10)} //變量提升饭耳,并且函數(shù)優(yōu)先級(jí)高于變量,如果相同命名执解,變量會(huì)被覆蓋寞肖。
小知識(shí):

    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')
    }
  1. 10 //執(zhí)行了a(){alert(10)}
  2. 3 //給a賦值了3
  3. a is not a function 此時(shí)已經(jīng)給a賦值了6,a已經(jīng)不是函數(shù)了,所以會(huì)彈出錯(cuò)誤
關(guān)于函數(shù)變量提升的一些特殊情況
    function yideng() { 
        console.log(1);
    }
    (function () { 
        if (false) {
            function yideng() {
                console.log(2);
            }
        }
        yideng();
    })();
    //yideng is not a function
在早期的瀏覽器中衰腌,彈出來(lái)的結(jié)果應(yīng)該是2,(因?yàn)樽兞刻嵘麦。麄€(gè)         function yideng() { }會(huì)被提升到整個(gè)函數(shù)作用域的頂端)但是如果彈出來(lái)是2的話,我們寫(xiě)的if語(yǔ)句就完全沒(méi)有意義了右蕊。后來(lái)瀏覽器為了修正這個(gè)錯(cuò)誤琼稻,像這種情況下面的函數(shù)提升就只是 `var yideng`提升到函數(shù)作用域的頂端,本題中false所以沒(méi)進(jìn)入函數(shù)體饶囚,所以yideng()就會(huì)報(bào)錯(cuò)yideng is not a function帕翻。而不是像第一題那樣,整個(gè)函數(shù)體都提到前面萝风。

據(jù)說(shuō)除了if嘀掸,好像while,switch规惰,for也一樣睬塌。
補(bǔ)充小知識(shí):

    if(false){
        var a = 1;
    }
    console.log(a)//undefined
關(guān)于this

寫(xiě)出以下輸出值,解釋為什么?

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綁定父級(jí)的詞法作用域衫仑,所以他的this.a固定了是20梨与。
go本身沒(méi)有a 這個(gè)屬性,所以new出來(lái)的對(duì)象也沒(méi)有這個(gè)屬性文狱,會(huì)到原型鏈上面去找粥鞋。所以是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

剛開(kāi)始我以為輸出的結(jié)果會(huì)是20 60 20 60
var p = test.init(); 此時(shí)test.init()會(huì)執(zhí)行一遍呻粹。此時(shí)的箭頭函數(shù)中的this綁定了最外層的this(test父級(jí)作用域的this),也就是this.a = 20
p() 此時(shí)相當(dāng)于執(zhí)行g(shù)o()苏研,注意:這個(gè)時(shí)候是var p = test.init(),所以執(zhí)行p()時(shí)的this是指向windows等浊,也就是我們現(xiàn)在看到的最外層的this.a = 20的那個(gè)this,此時(shí)會(huì)執(zhí)行this.a = 60摹蘑,將windows的this.a 更改成60,執(zhí)行之后打印出來(lái)的this.a=60.
new(test.init())():test.init()會(huì)取到已經(jīng)綁定了的父級(jí)詞法作用域的this筹燕,不過(guò)此時(shí)的this.a已經(jīng)被改為60,所以打印出來(lái)60。繼續(xù)執(zhí)行衅鹿,打印出60撒踪。

思考一下,如果是下面這種情況的話大渤,會(huì)打印出來(lái)什么呢制妄?

image.png

執(zhí)行的時(shí)候20
因?yàn)檫@里是var 不是this.a

另外一個(gè)小知識(shí):

    function test(){
        console.log(this)
    }
    var obj = {
        f:test
    }
    (obj.f)()  //Cannot read property 'f' of undefined

?泵三?耕捞??烫幕?什么鬼俺抽??
原來(lái)是因?yàn)闆](méi)有加纬霞;在瀏覽器看來(lái)
var obj = {f:test}(obj.f)() 是這樣連在一起的凌埂。那肯定會(huì)報(bào)錯(cuò)啊诗芜!

加上分號(hào)之后瞳抓,我們?cè)賮?lái)看一下這段代碼運(yùn)行的結(jié)果是怎么樣的。

    function test(){
        console.log(this)
    }
    var obj = {
        f:test
    };
    (obj.f)()//{f: ?}

很明顯伏恐,指向了obj這個(gè)對(duì)象孩哑,因?yàn)槭莖bj調(diào)用了test()。
那如果我們?cè)龠@樣改一下:

    function test(){
        console.log(this)
    }
    var obj = {
        f:test
    };
    (false || obj.f)()

猜猜結(jié)果是什么翠桦?是window
横蜒?胳蛮??丛晌?仅炊?這又是什么鬼?澎蛛?抚垄?
短路語(yǔ)句的結(jié)果不也是obj.f,運(yùn)算的結(jié)果應(yīng)該也是obj才對(duì)啊谋逻。
但是()里面是計(jì)算呆馁,所以里面應(yīng)該是表達(dá)式,也就是說(shuō)里面相當(dāng)于var xx = obj.f毁兆,然后xx(),此時(shí)this肯定是指向全局變量window

關(guān)于閉包塊級(jí)作用域

請(qǐng)寫(xiě)出如下點(diǎ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)典的一道題捌椤7睦啊!>グ拧摹菠!大家可以閉著眼睛說(shuō)不管點(diǎn)擊哪個(gè)li都只會(huì)輸出6。(跟Js的單線程和異步隊(duì)列有關(guān))
解決方法有三個(gè):

  • ES6的let

      for (let i = 0; i < list_li.length; i++) {
          list_li[i].onclick = function() { 
              console.log(i+1);
          }
      }
    

關(guān)于let 可以看看暫時(shí)性死區(qū)這個(gè)知識(shí)點(diǎn)骗爆。

  • 閉包,立即執(zhí)行函數(shù)

    for(var i = 0; i < list_li.length; i++){
      (function(i){
          list_li[i].onclick = function(){
              console.log(i+1)
          }
      })(i)
    }
    

閉包保存這個(gè)變量蔽介。怎么去理解閉包保存這個(gè)變量呢摘投,也就是說(shuō)這里每次執(zhí)行立即執(zhí)行函數(shù)的時(shí)候,會(huì)傳遞一個(gè)i(實(shí)參)進(jìn)去函數(shù)級(jí)的作用域虹蓄,在這個(gè)函數(shù)級(jí)作用域里面onclick的回調(diào)函數(shù)使用了這個(gè)i犀呼,也就是我們常說(shuō)的閉包了,還要注意的一點(diǎn)是i在這里的按值傳遞的薇组,所以每個(gè)i都是獨(dú)立的各不影響的外臂。

  • 最后一種方法是佳哥說(shuō)出來(lái)的時(shí)候恍然大悟,這才是最in的解決方案啊我靠律胀。這道題考的是this啊宋光,不是閉包也不是作用域。炭菌。罪佳。

      for (var  i = 0; i < list_li.length; i++) {
          list_li[i].onclick = function() { 
              console.log(this.innerHTML);
          }
      }
    

內(nèi)存泄漏

http://www.ruanyifeng.com/blog/2017/04/memory-leak.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市黑低,隨后出現(xiàn)的幾起案子赘艳,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕾管,死亡現(xiàn)場(chǎng)離奇詭異枷踏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)掰曾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)旭蠕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人婴梧,你說(shuō)我怎么就攤上這事下梢。” “怎么了塞蹭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵孽江,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我番电,道長(zhǎng)岗屏,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任漱办,我火速辦了婚禮这刷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娩井。我一直安慰自己暇屋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布洞辣。 她就那樣靜靜地躺著咐刨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扬霜。 梳的紋絲不亂的頭發(fā)上定鸟,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音著瓶,去河邊找鬼联予。 笑死,一個(gè)胖子當(dāng)著我的面吹牛材原,可吹牛的內(nèi)容都是我干的沸久。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼余蟹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼麦向!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起客叉,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诵竭,失蹤者是張志新(化名)和其女友劉穎话告,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體卵慰,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沙郭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年鼻听,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叠骑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讨彼,死狀恐怖鲤嫡,靈堂內(nèi)的尸體忽然破棺而出送挑,到底是詐尸還是另有隱情,我是刑警寧澤暖眼,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布惕耕,位于F島的核電站,受9級(jí)特大地震影響诫肠,放射性物質(zhì)發(fā)生泄漏司澎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一栋豫、第九天 我趴在偏房一處隱蔽的房頂上張望挤安。 院中可真熱鬧,春花似錦丧鸯、人聲如沸蛤铜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)昂羡。三九已至,卻和暖如春摔踱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怨愤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工派敷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撰洗。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓篮愉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親差导。 傳聞我的和親對(duì)象是個(gè)殘疾皇子试躏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容