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