最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解javascript作用域和閉包

 更新時(shí)間:2014年09月23日 09:08:08   投稿:hebedich  
作用域和作用域鏈?zhǔn)莏avascript中非常重要的特性,對(duì)于他們的理解直接關(guān)系到對(duì)于整個(gè)javascript體系的理解,而閉包又是對(duì)作用域的延伸,也是在實(shí)際開(kāi)發(fā)中經(jīng)常使用的一個(gè)特性,實(shí)際上,不僅僅是javascript,在很多語(yǔ)言中都提供了閉包的特性。

作用域

作用域是一個(gè)變量和函數(shù)的作用范圍,javascript中函數(shù)內(nèi)聲明的所有變量在函數(shù)體內(nèi)始終是可見(jiàn)的,在javascript中有全局作用域和局部作用域,但是沒(méi)有塊級(jí)作用域,局部變量的優(yōu)先級(jí)高于全局變量,通過(guò)幾個(gè)示例來(lái)了解下javascript中作用域的那些“潛規(guī)則”(這些也是在前端面試中經(jīng)常問(wèn)到的問(wèn)題)。

1. 變量聲明提前
示例1:

var scope="global";
function scopeTest(){
  console.log(scope);
  var scope="local" 
}
scopeTest(); //undefined

此處的輸出是undefined,并沒(méi)有報(bào)錯(cuò),這是因?yàn)樵谇懊嫖覀兲岬降暮瘮?shù)內(nèi)的聲明在函數(shù)體內(nèi)始終可見(jiàn),上面的函數(shù)等效于:

var scope="global";
function scopeTest(){
  var scope;
  console.log(scope);
  scope="local" 
}
scopeTest(); //local

注意,如果忘記var,那么變量就被聲明為全局變量了。

2. 沒(méi)有塊級(jí)作用域

和其他我們常用的語(yǔ)言不同,在Javascript中沒(méi)有塊級(jí)作用域:

function scopeTest() {
  var scope = {};
  if (scope instanceof Object) {
    var j = 1;
    for (var i = 0; i < 10; i++) {
      //console.log(i);
    }
    console.log(i); //輸出10
  }
  console.log(j);//輸出1

}

在javascript中變量的作用范圍是函數(shù)級(jí)的,即在函數(shù)中所有的變量在整個(gè)函數(shù)中都有定義,這也帶來(lái)了一些我們稍不注意就會(huì)碰到的“潛規(guī)則”:

var scope = "hello";
function scopeTest() {
  console.log(scope);//①
  var scope = "no";
  console.log(scope);//②
}

在①處輸出的值竟然是undefined,簡(jiǎn)直喪心病狂啊,我們已經(jīng)定義了全局變量的值啊,這地方不應(yīng)該為hello嗎?其實(shí),上面的代碼等效于:

var scope = "hello";
function scopeTest() {
  var scope;
  console.log(scope);//①
  scope = "no";
  console.log(scope);//②
}

聲明提前、全局變量?jī)?yōu)先級(jí)低于局部變量,根據(jù)這兩條規(guī)則就不難理解為什么輸出undefined了。

作用域鏈

在javascript中,每個(gè)函數(shù)都有自己的執(zhí)行上下文環(huán)境,當(dāng)代碼在這個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的作用域鏈,作用域鏈?zhǔn)且粋€(gè)對(duì)象列表或?qū)ο箧?,它保證了變量對(duì)象的有序訪問(wèn)。
作用域鏈的前端是當(dāng)前代碼執(zhí)行環(huán)境的變量對(duì)象,常被稱(chēng)之為“活躍對(duì)象”,變量的查找會(huì)從第一個(gè)鏈的對(duì)象開(kāi)始,如果對(duì)象中包含變量屬性,那么就停止查找,如果沒(méi)有就會(huì)繼續(xù)向上級(jí)作用域鏈查找,直到找到全局對(duì)象中:

作用域鏈的逐級(jí)查找,也會(huì)影響到程序的性能,變量作用域鏈越長(zhǎng)對(duì)性能影響越大,這也是我們盡量避免使用全局變量的一個(gè)主要原因。

閉包

基礎(chǔ)概念
作用域是理解閉包的一個(gè)前提,閉包是指在當(dāng)前作用域內(nèi)總是能訪問(wèn)外部作用域中的變量。

function createClosure(){
  var name = "jack";
  return {
    setStr:function(){
      name = "rose";
    },
    getStr:function(){
      return name + ":hello";
    }
  }
}
var builder = new createClosure();
builder.setStr();
console.log(builder.getStr()); //rose:hello

上面的示例在函數(shù)中返回了兩個(gè)閉包,這兩個(gè)閉包都維持著對(duì)外部作用域的引用,因此不管在哪調(diào)用總是能夠訪問(wèn)外部函數(shù)中的變量。在一個(gè)函數(shù)內(nèi)部定義的函數(shù),會(huì)將外部函數(shù)的活躍對(duì)象添加到自己的作用域鏈中,因此上面實(shí)例中通過(guò)內(nèi)部函數(shù)能夠訪問(wèn)外部函數(shù)的屬性,這也是javascript模擬私有變量的一種方式。

注意:由于閉包會(huì)額外的附帶函數(shù)的作用域(內(nèi)部匿名函數(shù)攜帶外部函數(shù)的作用域),因此,閉包會(huì)比其它函數(shù)多占用些內(nèi)存空間,過(guò)度的使用可能會(huì)導(dǎo)致內(nèi)存占用的增加。

閉包中的變量

在使用閉包時(shí),由于作用域鏈機(jī)制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個(gè)值,這引起的一個(gè)副作用就是如果內(nèi)部函數(shù)在一個(gè)循環(huán)中,那么變量的值始終為最后一個(gè)值。

  //該實(shí)例不太合理,有一定延遲因素,此處主要為了說(shuō)明閉包循環(huán)中存在的問(wèn)題
  function timeManage() {
    for (var i = 0; i < 5; i++) {
      setTimeout(function() {
        console.log(i);
      },1000)
    };
  }

上面的程序并沒(méi)有按照我們預(yù)期的輸入1-5的數(shù)字,而是5次全部輸出了5。再來(lái)看一個(gè)示例:

function createClosure(){
  var result = [];
  for (var i = 0; i < 5; i++) {
    result[i] = function(){
      return i;
    }
  }
  return result;
}

調(diào)用createClosure()[0]()返回的是5,createClosure()[4]()返回值仍然是5。通過(guò)以上兩個(gè)例子可以看出閉包在帶有循環(huán)的內(nèi)部函數(shù)使用時(shí)存在的問(wèn)題:因?yàn)槊總€(gè)函數(shù)的作用域鏈中都保存著對(duì)外部函數(shù)(timeManage、createClosure)的活躍對(duì)象,因此,他們都引用著同一變量i,當(dāng)外部函數(shù)返回時(shí),此時(shí)的i值為5,所以?xún)?nèi)部的每個(gè)函數(shù)i的值也為5。
那么如何解決這個(gè)問(wèn)題呢?我們可以通過(guò)匿名包裹器(匿名自執(zhí)行函數(shù)表達(dá)式)來(lái)強(qiáng)制返回預(yù)期的結(jié)果:

function timeManage() {
  for (var i = 0; i < 5; i++) {
    (function(num) {
      setTimeout(function() {
        console.log(num);
      }, 1000);
    })(i);
  }
}

或者在閉包匿名函數(shù)中再返回一個(gè)匿名函數(shù)賦值:

function timeManage() {
  for (var i = 0; i < 10; i++) {
    setTimeout((function(e) {
      return function() {
        console.log(e);
      }
    })(i), 1000)
  }
}
//timeManager();輸出1,2,3,4,5
function createClosure() {
  var result = [];
  for (var i = 0; i < 5; i++) {
    result[i] = function(num) {
      return function() {
        console.log(num);
      }
    }(i);
  }
  return result;
}
//createClosure()[1]()輸出1;createClosure()[2]()輸出2

無(wú)論是匿名包裹器還是通過(guò)嵌套匿名函數(shù)的方式,原理上都是由于函數(shù)是按值傳遞,因此會(huì)將變量i的值復(fù)制給實(shí)參num,在匿名函數(shù)的內(nèi)部又創(chuàng)建了一個(gè)用于返回num的匿名函數(shù),這樣每個(gè)函數(shù)都有了一個(gè)num的副本,互不影響了。

閉包中的this

在閉包中使用this時(shí)要特別注意,稍微不慎可能會(huì)引起問(wèn)題。通常我們理解this對(duì)象是運(yùn)行時(shí)基于函數(shù)綁定的,全局函數(shù)中this對(duì)象就是window對(duì)象,而當(dāng)函數(shù)作為對(duì)象中的一個(gè)方法調(diào)用時(shí),this等于這個(gè)對(duì)象(TODO 關(guān)于this做一次整理)。由于匿名函數(shù)的作用域是全局性的,因此閉包的this通常指向全局對(duì)象window:

var scope = "global";
var object = {
  scope:"local",
  getScope:function(){
    return function(){
      return this.scope;
    }
  }
}

調(diào)用object.getScope()()返回值為global而不是我們預(yù)期的local,前面我們說(shuō)過(guò)閉包中內(nèi)部匿名函數(shù)會(huì)攜帶外部函數(shù)的作用域,那為什么沒(méi)有取得外部函數(shù)的this呢?每個(gè)函數(shù)在被調(diào)用時(shí),都會(huì)自動(dòng)創(chuàng)建this和arguments,內(nèi)部匿名函數(shù)在查找時(shí),搜索到活躍對(duì)象中存在我們想要的變量,因此停止向外部函數(shù)中的查找,也就永遠(yuǎn)不可能直接訪問(wèn)外部函數(shù)中的變量了??傊陂]包中函數(shù)作為某個(gè)對(duì)象的方法調(diào)用時(shí),要特別注意,該方法內(nèi)部匿名函數(shù)的this指向的是全局變量。

幸運(yùn)的是我們可以很簡(jiǎn)單的解決這個(gè)問(wèn)題,只需要把外部函數(shù)作用域的this存放到一個(gè)閉包能訪問(wèn)的變量里面即可:

var scope = "global";
var object = {
  scope:"local",
  getScope:function(){
    var that = this;
    return function(){
      return that.scope;
    }
  }
}
object.getScope()()返回值為local。

內(nèi)存與性能

由于閉包中包含與函數(shù)運(yùn)行期上下文相同的作用域鏈引用,因此,會(huì)產(chǎn)生一定的負(fù)面作用,當(dāng)函數(shù)中活躍對(duì)象和運(yùn)行期上下文銷(xiāo)毀時(shí),由于必要仍存在對(duì)活躍對(duì)象的引用,導(dǎo)致活躍對(duì)象無(wú)法銷(xiāo)毀,這意味著閉包比普通函數(shù)占用更多的內(nèi)存空間,在IE瀏覽器下還可能會(huì)導(dǎo)致內(nèi)存泄漏的問(wèn)題,如下:

 function bindEvent(){
  var target = document.getElementById("elem");
  target.onclick = function(){
    console.log(target.name);
  }
 }

上面例子中匿名函數(shù)對(duì)外部對(duì)象target產(chǎn)生一個(gè)引用,只要是匿名函數(shù)存在,這個(gè)引用就不會(huì)消失,外部函數(shù)的target對(duì)象也不會(huì)被銷(xiāo)毀,這就產(chǎn)生了一個(gè)循環(huán)引用。解決方案是通過(guò)創(chuàng)建target.name副本減少對(duì)外部變量的循環(huán)引用以及手動(dòng)重置對(duì)象:

 function bindEvent(){
  var target = document.getElementById("elem");
  var name = target.name;
  target.onclick = function(){
    console.log(name);
  }
  target = null;
 }

閉包中如果存在對(duì)外部變量的訪問(wèn),無(wú)疑增加了標(biāo)識(shí)符的查找路徑,在一定的情況下,這也會(huì)造成性能方面的損失。解決此類(lèi)問(wèn)題的辦法我們前面也曾提到過(guò):盡量將外部變量存入到局部變量中,減少作用域鏈的查找長(zhǎng)度。

總結(jié):閉包不是javascript獨(dú)有的特性,但是在javascript中有其獨(dú)特的表現(xiàn)形式,使用閉包我們可以在javascript中定義一些私有變量,甚至模仿出塊級(jí)作用域,但閉包在使用過(guò)程中,存在的問(wèn)題我們也需要了解,這樣才能避免不必要問(wèn)題的出現(xiàn)。

相關(guān)文章

  • 使用 stylelint檢查CSS_StyleLint

    使用 stylelint檢查CSS_StyleLint

    你需要一個(gè)防止錯(cuò)誤產(chǎn)生的機(jī)器,可以理解CSS并且理解你:你的意圖、喜好、主意以及弱點(diǎn)。 只要是它可以阻止的錯(cuò)誤它都會(huì)持續(xù)阻止。同時(shí),你和你的同事可以一直改善機(jī)器,擴(kuò)展它的功能并且削弱其局限性。
    2016-04-04
  • Javascript學(xué)習(xí)筆記9 prototype封裝繼承

    Javascript學(xué)習(xí)筆記9 prototype封裝繼承

    在上文中,我利用prototype的原理做了一個(gè)封裝的New,然后我就想到,我是否可以用prototype的原理進(jìn)一步封裝面向?qū)ο蟮囊恍┗咎卣髂??比如繼承。
    2010-01-01
  • JavaScript:Date類(lèi)型全面解析

    JavaScript:Date類(lèi)型全面解析

    下面小編就為大家?guī)?lái)一篇JavaScript:Date類(lèi)型全面解析。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,祝大家游戲愉快哦
    2016-05-05
  • 詳解JavaScript中的blink()方法的使用

    詳解JavaScript中的blink()方法的使用

    這篇文章主要介紹了詳解JavaScript中的blink()方法的使用,是JS入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-06-06
  • JavaScript中toString()方法的使用詳解

    JavaScript中toString()方法的使用詳解

    這篇文章主要介紹了JavaScript中toString()方法的使用詳解,是JS入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-06-06
  • javascript中的括號(hào)()用法小結(jié)

    javascript中的括號(hào)()用法小結(jié)

    ()作為一個(gè)數(shù)學(xué)運(yùn)算中的基本符號(hào),在計(jì)算機(jī)程序中也被大量使用,最基本的就是提高運(yùn)算優(yōu)先級(jí),JS中也不例外
    2014-04-04
  • javascript學(xué)習(xí)筆記(一) 在html中使用javascript

    javascript學(xué)習(xí)筆記(一) 在html中使用javascript

    javascript學(xué)習(xí)筆記之在html中使用javascript 的方法,需要的朋友可以參考下
    2012-06-06
  • AngularJS 指令詳細(xì)介紹

    AngularJS 指令詳細(xì)介紹

    本文主要介紹AngularJS 指令,這里詳細(xì)介紹AngularJS 指令知識(shí)并提供實(shí)例代碼,有需要的小伙伴可以參考下
    2016-07-07
  • JS中==與===操作符的比較

    JS中==與===操作符的比較

    js判斷兩個(gè)變量是否相等的一些操作符的比較。
    2009-03-03
  • window.onload和$(function(){})的區(qū)別介紹

    window.onload和$(function(){})的區(qū)別介紹

    window.onload和$(function(){})有什么區(qū)別。window.onload表示頁(yè)面加載完了后(包括dom和js),再執(zhí)行函數(shù)里面的內(nèi)容,感興趣的朋友可以了解下
    2013-10-10

最新評(píng)論

徐汇区| 佳木斯市| 铅山县| 肥东县| 秦安县| 崇信县| 台中县| 白水县| 水城县| 永昌县| 新源县| 江永县| 揭东县| 广德县| 江达县| 花垣县| 准格尔旗| 左云县| 枝江市| 大同县| 福建省| 新泰市| 剑河县| 沽源县| 桐乡市| 封开县| 嘉义市| 镶黄旗| 筠连县| 敦煌市| 龙山县| 凤城市| 灌南县| 双桥区| 三原县| 修文县| 金乡县| 慈溪市| 怀来县| 荣成市| 平昌县|