深入理解javascript作用域和閉包
來源:易賢網(wǎng) 閱讀:651 次 日期:2016-06-15 09:44:53
溫馨提示:易賢網(wǎng)小編為您整理了“深入理解javascript作用域和閉包”,方便廣大網(wǎng)友查閱!

作用域

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

1. 變量聲明提前

示例1:

var scope=global;

function scopetest(){

console.log(scope);

var scope=local

}

scopetest(); //undefined

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

var scope=global;

function scopetest(){

var scope;

console.log(scope);

scope=local

}

scopetest(); //local

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

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

和其他我們常用的語言不同,在javascript中沒有塊級(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ù)中都有定義,這也帶來了一些我們稍不注意就會(huì)碰到的“潛規(guī)則”:

>

var scope = hello;

function scopetest() {

console.log(scope);//①

var scope = no;

console.log(scope);//②

}

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

var scope = hello;

function scopetest() {

var scope;

console.log(scope);//①

scope = no;

console.log(scope);//②

}

聲明提前、全局變量優(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ì)象的有序訪問。

作用域鏈的前端是當(dāng)前代碼執(zhí)行環(huán)境的變量對(duì)象,常被稱之為“活躍對(duì)象”,變量的查找會(huì)從第一個(gè)鏈的對(duì)象開始,如果對(duì)象中包含變量屬性,那么就停止查找,如果沒有就會(huì)繼續(xù)向上級(jí)作用域鏈查找,直到找到全局對(duì)象中:

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

閉包

基礎(chǔ)概念

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

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)用總是能夠訪問外部函數(shù)中的變量。在一個(gè)函數(shù)內(nèi)部定義的函數(shù),會(huì)將外部函數(shù)的活躍對(duì)象添加到自己的作用域鏈中,因此上面實(shí)例中通過內(nèi)部函數(shù)能夠訪問外部函數(shù)的屬性,這也是javascript模擬私有變量的一種方式。

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

閉包中的變量

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

//該實(shí)例不太合理,有一定延遲因素,此處主要為了說明閉包循環(huán)中存在的問題

function timemanage() {

for (var i = 0; i < 5; i++) {

settimeout(function() {

console.log(i);

},1000)

};

}

上面的程序并沒有按照我們預(yù)期的輸入1-5的數(shù)字,而是5次全部輸出了5。再來看一個(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。通過以上兩個(gè)例子可以看出閉包在帶有循環(huán)的內(nèi)部函數(shù)使用時(shí)存在的問題:因?yàn)槊總€(gè)函數(shù)的作用域鏈中都保存著對(duì)外部函數(shù)(timemanage、createclosure)的活躍對(duì)象,因此,他們都引用著同一變量i,當(dāng)外部函數(shù)返回時(shí),此時(shí)的i值為5,所以內(nèi)部的每個(gè)函數(shù)i的值也為5。

那么如何解決這個(gè)問題呢?我們可以通過匿名包裹器(匿名自執(zhí)行函數(shù)表達(dá)式)來強(qiáng)制返回預(yù)期的結(jié)果:

9

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

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

閉包中的this

在閉包中使用this時(shí)要特別注意,稍微不慎可能會(huì)引起問題。通常我們理解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:

9

var scope = global;

var object = {

scope:local,

getscope:function(){

return function(){

return this.scope;

}

}

}

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

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

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)行期上下文銷毀時(shí),由于必要仍存在對(duì)活躍對(duì)象的引用,導(dǎo)致活躍對(duì)象無法銷毀,這意味著閉包比普通函數(shù)占用

更多信息請(qǐng)查看腳本欄目
易賢網(wǎng)手機(jī)網(wǎng)站地址:深入理解javascript作用域和閉包
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請(qǐng)考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!

2025國考·省考課程試聽報(bào)名

  • 報(bào)班類型
  • 姓名
  • 手機(jī)號(hào)
  • 驗(yàn)證碼
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 加入群交流 | 手機(jī)站點(diǎn) | 投訴建議
工業(yè)和信息化部備案號(hào):滇ICP備2023014141號(hào)-1 云南省教育廳備案號(hào):云教ICP備0901021 滇公網(wǎng)安備53010202001879號(hào) 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號(hào)
云南網(wǎng)警備案專用圖標(biāo)
聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號(hào):hfpxwx
咨詢QQ:526150442(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報(bào)警專用圖標(biāo)