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也存在塊級做用域。以下面幾種狀況:app
with異步
var obj = {a: 2, b: 2, c: 2}; with (obj) { //均做用於obj上 a = 5; b = 5; c = 5; }
let函數
let是ES6新增的定義變量的方法,其定義的變量僅存在於最近的{}以內。以下this
var foo = true; if (foo) { let bar = foo * 2; bar = something( bar ); console.log( bar ); } console.log( bar ); // ReferenceError
constcode
與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!
瞭解這些了後,咱們來聊聊閉包。什麼叫閉包?簡單的說就是一個函數內嵌套另外一個函數,這就會造成一個閉包。這樣提及來可能比較抽象,那麼咱們就舉例說明。可是在距離以前,咱們再複習下這句話,來,跟着大聲讀一遍,「不管函數是在哪裏調用,也不管函數是如何調用的,其肯定的詞法做用域永遠都是在函數被聲明的時候肯定下來的」。
來,下面咱們看一個經典的閉包的例子:ip
for (var i=1; i<=9; i++) { setTimeout( function timer(){ console.log( i ); },1000 ); }
運行的結果是啥捏?你可能期待每隔一秒出來一、二、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();