摘要:嚴(yán)格的說,在也存在塊級作用域。如果多帶帶調(diào)用函數(shù),比如,此時,該函數(shù)的指向全局對象,也就是??拥⌒∶髟诜椒▋?nèi)部一開始就捕獲用而不是小明指向參數(shù)為空另一個與類似的方法是,唯一區(qū)別是把參數(shù)打包成再傳入把參數(shù)按順序傳入。
先上幾道面試題練練手
var bb = 1; function aa(bb) { bb = 2; alert(bb); } aa(bb); alert(bb);
var a="undefined"; var b="false"; var c=""; function assert(aVar){ if(aVar) alert(true); else alert(false); } assert(a); assert(b); assert(c);
function Foo() { var i = 0; return function() { console.log(i++); }; } Foo(); var f1 = Foo(), f2 = Foo(); f1(); f1(); f2();
var foo = true; if (foo) { let bar = foo * 2; bar = something( bar ); console.log( bar ); } console.log( bar );
var foo = true; if (foo) { var a = 2; const b = 3; //僅存在于if的{}內(nèi) a = 3; b = 4; // 出錯,值不能修改 } console.log( a ); // 3 console.log( b ); // ReferenceError!閉包的深度遞進(jìn)
在JavaScript中,作用域是基于函數(shù)來界定的。也就是說屬于一個函數(shù)內(nèi)部的代碼,函數(shù)內(nèi)部以及內(nèi)部嵌套的代碼都可以訪問函數(shù)的變量。
順便講講常見的兩種error,ReferenceError和TypeError。如上圖,如果在bar里使用了d,那么經(jīng)過查詢都沒查到,那么就會報一個ReferenceError;如果bar里使用了b,但是沒有正確引用,如b.abc(),這會導(dǎo)致TypeError。
嚴(yán)格的說,在JavaScript也存在塊級作用域。如下面幾種情況:
with
var obj = {a: 2, b: 2, c: 2}; with (obj) { //均作用于obj上 a = 5; b = 5; c = 5; }
let
let是ES6新增的定義變量的方法,其定義的變量僅存在于最近的{}之內(nèi)。如下
var foo = true; if (foo) { let bar = foo * 2; bar = something( bar ); console.log( bar ); } console.log( bar ); // ReferenceError
const
與let一樣,唯一不同的是const定義的變量值不能修改。如下:
var foo = true; if (foo) { var a = 2; const b = 3; //僅存在于if的{}內(nèi) a = 3; b = 4; // 出錯,值不能修改 } console.log( a ); // 3 console.log( b ); // ReferenceError!
了解這些了后,我們來聊聊閉包。什么叫閉包?簡單的說就是一個函數(shù)內(nèi)嵌套另一個函數(shù),這就會形成一個閉包。這樣說起來可能比較抽象,那么我們就舉例說明。但是在距離之前,我們再復(fù)習(xí)下這句話,來,跟著大聲讀一遍,“無論函數(shù)是在哪里調(diào)用,也無論函數(shù)是如何調(diào)用的,其確定的詞法作用域永遠(yuǎn)都是在函數(shù)被聲明的時候確定下來的”。
來,下面我們看一個經(jīng)典的閉包的例子:
for (var i=1; i<=9; i++) { setTimeout( function timer(){ console.log( i ); },1000 ); }
運(yùn)行的結(jié)果是啥捏?你可能期待每隔一秒出來1、2、3...10。那么試一下,按F12,打開console,將代碼粘貼,回車!咦???等一下,擦擦眼睛,怎么會運(yùn)行了10次10捏?這是腫么回事呢?咋眼睛還不好使了呢?不要著急,等我給你忽悠!
現(xiàn)在,再看看上面的代碼,由于setTimeout是異步的,那么在真正的1000ms結(jié)束前,其實10次循環(huán)都已經(jīng)結(jié)束了。我們可以將代碼分成兩部分分成兩部分,一部分處理i++,另一部分處理setTimeout函數(shù)。那么上面的代碼等同于下面的:
// 第一個部分 i++; i++; // 總共做10次 // 第二個部分 setTimeout(function() { console.log(i); }, 1000); setTimeout(function() { console.log(i); }, 1000); // 總共做10次
看到這里,相信你已經(jīng)明白了為什么是上面的運(yùn)行結(jié)果了吧。那么,我們來找找如何解決這個問題,讓它運(yùn)行如我們所料!
因為setTimeout中的匿名函數(shù)沒有將i作為參數(shù)傳入來固定這個變量的值,讓其保留下來, 而是直接引用了外部作用域中的i, 因此i變化時,也影響到了匿名函數(shù)。其實要讓它運(yùn)行的跟我們料想的一樣很簡單,只需要將setTimeout函數(shù)定義在一個多帶帶的作用域里并將i傳進(jìn)來即可。如下:
for (var i=1; i<=9; i++) { (function(){ var j = i; setTimeout( function timer(){ console.log( j ); }, 1000 ); })(); }
不要激動,勇敢的去試一下,結(jié)果肯定如你所料。那么再看一個實現(xiàn)方案:
for (var i=1; i<=9; i++) { (function(j){ setTimeout( function timer(){ console.log( j ); }, 1000 ); })( i ); }
啊,居然這么簡單啊,你肯定在這么想了!那么,看一個更優(yōu)雅的實現(xiàn)方案:
for (let i=1; i<=9; i++) { setTimeout( function timer(){ console.log( i ); }, 1000 ); }
咦?!腫么回事呢?是不是出錯了,不著急,我這里也出錯了。這是因為let需要在strict mode中執(zhí)行。具體如何使用strict mode模式,自行谷歌吧
再整理一些面試題吧var x = 1; var y = 0; var z = 0; function add(n){n=n+1;} y = add(x); function add(n){n=n+3;} z = add(x); console.log(x,y,z); //兩個函數(shù)沒有返回值,打印1 undefined undefined
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: "小明", birth: 1990, age: getAge }; xiaoming.age(); // 25, 正常結(jié)果 getAge(); // NaN
多帶帶調(diào)用函數(shù)getAge怎么返回了NaN?請注意,我們已經(jīng)進(jìn)入到了JavaScript的一個大坑里。JavaScript的函數(shù)內(nèi)部如果調(diào)用了this,那么這個this到底指向誰?
答案是,視情況而定!如果以對象的方法形式調(diào)用,比如xiaoming.age(),該函數(shù)的this指向被調(diào)用的對象,也就是xiaoming,這是符合我們預(yù)期的。
如果多帶帶調(diào)用函數(shù),比如getAge(),此時,該函數(shù)的this指向全局對象,也就是window。
坑爹啊!
var xiaoming = { name: "小明", birth: 1990, age: function () { var that = this; // 在方法內(nèi)部一開始就捕獲this function getAgeFromBirth() { var y = new Date().getFullYear(); return y - that.birth; // 用that而不是this } return getAgeFromBirth(); } }; xiaoming.age(); // 25
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: "小明", birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this指向xiaoming, 參數(shù)為空
另一個與apply()類似的方法是call(),唯一區(qū)別是:
apply()把參數(shù)打包成Array再傳入;
call()把參數(shù)按順序傳入。
function foo() { var x = "Hello, " + y; alert(x);//hello,undefined var y = "Bob"; } foo();
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/80067.html
摘要:閉包引起的內(nèi)存泄漏總結(jié)從理論的角度將由于作用域鏈的特性中所有函數(shù)都是閉包但是從應(yīng)用的角度來說只有當(dāng)函數(shù)以返回值返回或者當(dāng)函數(shù)以參數(shù)形式使用或者當(dāng)函數(shù)中自由變量在函數(shù)外被引用時才能成為明確意義上的閉包。 文章同步到github js的閉包概念幾乎是任何面試官都會問的問題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。 什么是閉包 我先列出一些官方及經(jīng)典書籍等書中給出的概念,這些概念雖然...
摘要:所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體。所以本文中將以維基百科中的定義為準(zhǔn)即在計算機(jī)科學(xué)中,閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。 閉包(closure)是JavaScript中一個神秘的概念,許多人都對它難以理解,我也一直處于似懂非懂的狀態(tài),前幾天深入了解了一下執(zhí)行環(huán)境以及作用域鏈,可戳查看詳情,而閉包與作用域及作用域鏈的關(guān)系密不可分,所...
摘要:閉包是什么這是一個在面試的過程中出現(xiàn)的概率為以上的問題,也是我們張口就來的問題。文章推薦我們面試中在被問到閉包這個問題是要注意的幾點(diǎn)閉包的延伸,讓面試變得 閉包是什么?這是一個在面試的過程中出現(xiàn)的概率為60%以上的問題,也是我們張口就來的問題。但是我們往往發(fā)現(xiàn),在面試的過程中我們的回答并不那么讓面試官滿意,我們雖然能張口說出一些但是卻不能系統(tǒng)的對這個問題進(jìn)行回答。面試官希望加入自己團(tuán)隊...
摘要:前言這個系列是翻譯自中的直接闖關(guān)作用域鏈和閉包作用域,作用域鏈,閉包和垃圾回收機(jī)制都有一個共同點(diǎn)學(xué)了就忘閉包到底是干啥的啥時候發(fā)生垃圾回收機(jī)制作用域鏈到底是啥這個教程讓你發(fā)現(xiàn)這些都是小意思。 前言 這個系列是翻譯自 nodeschool.io中的 scope-chains-closures 直接闖關(guān): npm install -g scope-chains-closures scope...
摘要:閉包里面保存的變量只有被方法引用了的變量這個例子里,閉包里只有并沒有。那最后來說說的問題閉包到底是什么閉包是一個作用域。鑒于在的調(diào)試窗口,是放在下面的那閉包這個作用域是個什么范圍被后代方法子方法,孫子方法。。。 首先給js的作用域這個話題打標(biāo)簽:2,var, 全局變量,局部變量,函數(shù),undefined, 作用域提升,賦值不會提升,ReferenceError, 同名覆蓋。打完標(biāo)簽之后...
閱讀 3425·2021-09-22 16:00
閱讀 3470·2021-09-07 10:26
閱讀 3030·2019-08-30 15:55
閱讀 2869·2019-08-30 13:48
閱讀 1377·2019-08-30 12:58
閱讀 2180·2019-08-30 11:15
閱讀 959·2019-08-30 11:08
閱讀 536·2019-08-29 18:41