做用域,對於JavaScript語言來講無處不在,變量做用域,函數做用域(運行時上下文和定義時上下文),做用域污染等等都跟做用域息息相關,掌握JavaScript做用於規則,能夠很好地避免一些極端的狀況。前端
在JavaScript語言下定義一個全局對象或者變量是最容易不過的了,這種全局變量最容易聲明而且使用,由於它能被整個程序所訪問到,可是經驗豐富的程序員應該儘可能避免使用全局變量,它有以下的缺點:程序員
可是,也不是說必定禁止使用全局變量,在一些狀況下全局變量的使用是不可避免的,例如,它是各個獨立組件之間進行交互的惟一途徑。web
儘量的使用局部變量,少用全局變量。 面試
局部變量:IIFE,let,cosnt等。編程
this.foo; // undefined window.foo; // undefined var foo = 'luffy';//聲明一個全局變量,會自動綁定到window對象下 this.foo; // 'luffy' window.foo; // 'luffy'
可使用全局對象來判斷程序是否在瀏覽器環境下能夠運行數組
是否支持地理位置瀏覽器
if(navigator.geolocation){ //user geolocation }
是否支持HTML5閉包
if (window.applicationCache) { alert("你的瀏覽器支持HTML5"); } else { alert("你的瀏覽器不支持HTML5"); }
等等不少種新特性的檢測。(須要時請問BD)app
在JavaScript中,若是不使用var關鍵字進行變量聲明,該變量會被隱式轉換成全局變量,所以會形成沒必要要的全局空間污染。模塊化
function swap(a,b) { temp = a; // temp變成了global a = b; b = temp; }
這段程序沒有使用var來聲明temp變量,最終致使之外的建立了一個從全局的變量temp,雖然代碼執行起來也沒有錯誤。正確的實現就是在函數體內部使用var temp;
將temp聲明成局部變量。
在ES6新特性中,引入了塊級做用域這個概念,所以還可使用let,const來聲明局部變量。
with語句做用是讓代碼運行在特定對象做用域內。with語句是JavaScript最使人詬病的特性,不可靠且效率低下。
總而言之一句話,避免使用with語句便可
努力掌握JavaScript閉包這一律念,對於前端程序員來講會有很是大的幫助。理解閉包不用死背概念,理解三個基本事實就能夠。
function outerFoo() { var outVar = '外部變量'; return function() { console.log('我在函數體內部,訪問到了:' + outVar); } } var foo = outerFoo(); foo(); //我在函數體內部,訪問到了:外部變量
上面這個例子,函數內部返回一個函數,對於foo來講,能夠訪問到自身外部定義的非全局變量(outVar是outerFoo函數體內的局部變量)。
仍然是上面那個例子,外部函數已經結束執行過程,返回值賦值給了foo變量(結果是一個函數),如今foo是window做用域下的一個函數,如圖所示:
能夠看到,window下的foo函數正確的訪問到了outFoo函數體內部的局部變量。
緣由:JavaScript的函數值包含了比調用它們的時候執行的那部分代碼還要多的信息,也就是說它們還在內部存儲了它們可能會引用的定義在其封閉做用域內的變量,這種在其所涵蓋的做用域內部跟蹤變量的函數叫作閉包。
上述的foo函數就是一個閉包,其代碼自己引用了外部變量outVar,每一次foo函數被調用,都會引用到這個outVar變量。
對於閉包,有一個很高的評價:閉包是JavaScript最優雅最有表現力的特性之一
function box() { var val = undefined; //給變量賦值undefined比不給變量賦值要優秀 return { set: function(newVal) { val = newVal; }, get: function() { return val; }, type: function() { return typeof val } } } var b = box(); b.type();//'undefined' b.set(17.2); b.get();//17.2 b.type();//'number'
上述例子產生了一個包含三個閉包的對象,這三個閉包分別對應函數返回對象的set、get、type屬性。它們共享外部變量val,而且set閉包還能夠更新val的值。
實際上,閉包存儲的是外部變量的引用,而不是它們真實值的副本.因此閉包是能夠更新改變外部變量的值的。
在ES6,JavaScript開始引入塊級做用域,使用塊級做用域聲明的變量只有在包含他們的封閉語句或代碼塊{}內部可使用。可是,在ES6以前,是不存在塊級做用域的。var聲明的變量會被綁定到離它聲明最近的那個做用域上,若是找不到就綁定到最外層window上。
function isWinner(player, others) { var higest = 0; for(var i = 0, n = others.length; i < n; i++){ var player = others[i]; //此處重複聲明瞭一個player變量 if(player.score > higest) { higest = player.score; } } return player.score > higest; }
上述函數目的是判斷player是不是最高分,可是,在函數內部重複聲明瞭player變量,所以,每一次循環都修改了函數體的傳入值player變量自己,最終結果確定不是預期的。
在ES5版本,JavaScript的變量做用域概念存在兩種,函數級做用域和全局做用域。ES6增長了塊級做用域,使用let、const能夠聲明塊級做用域變量。
當即執行的函數表達式(IIFE),是前端面試過程基本都會問到的問題。下面使用一個老生常談的面試題來說解它:
window.onload = function() { var result = []; for (var i = 0; i < 5; i++) { result[i] = function() { console.log(i); } } console.log(result[1]());// 5 }
你們應該都知道,最後的執行結果應該是5,而且,result數組內部存的值都是5。
形成結果的緣由能夠用上面閉包的基本事實來解釋,內部函數保存的變量實際上是外部變量的引用,也就是說,result的每個元素內部所引用的i值會隨着for循環變化而變化,當咱們調用
result[1]();
這條語句的時候,i值已經變成了5,因此輸出是5。
解決辦法,就是使用當即執行函數來建立一個局部做用域來解決。
window.onload = function() { var result = []; for (var i = 0; i < 5; i++) { (function(j) { result[j] = function() { console.log(j); } })(i) } console.log(result[1]());// 1 }
上面使用當即執行函數獲得了正確的結果,緣由就是它建立了一段塊級做用域,當即函數內部將外部變量i的值當作參數穿入內部也就是參數j,以後內部使用的一直都是j這個局部變量,因此就獲得了正確的運行結果。
上面IIFE是經過建立一個塊級做用域的方式解決的這個問題,其實在ES6中,很是簡便的就能夠解決,那就是使用let關鍵字定義i值,由於let定義的變量就是塊級做用域變量。
for(let i = 0; i < 5l i++)
便可獲得預期答案。在使用IIFE的時候要注意幾件事:首先,代碼塊不能包含任何跳出塊的break語句和continue語句,由於在函數外部使用break和continue語句是不合法的。其次,若是代碼塊引用了this或特別的arguments變量,IIFE會改變它們的語義。
這條規則對於如今的環境和正確使用JavaScript語法規則編程的程序員來講不太使用,簡單來講就是下面這種狀況。
//推薦使用定義函數的方式 //第一種:函數聲明 function double(x) { return x*2; } //第二種:函數表達式 var foo = function(){ return x*2; } //不合理的定義函數的方式 var f = function three(x) { return x*3; }
千萬不要使用兩種混搭的這種形式,雖然在目前的衆多瀏覽器中都是合法的,可是在低版本終會存在問題,而且,變量three的做用域只是在自身函數體內部,在其餘地方都是不能被引用的,而變量f能夠被外部引用 。
function foo() { return 'global'; } function test(x) { function foo() { return 'local'; } var result = []; if (x) { result.push(foo()); } result.push(foo()); return result; } test(true); // ['local','local'] test(false); // ['local']
這種嵌套函數聲明的方式也容易出現問題,全局做用域下聲明瞭函數foo,在test函數體內部又聲明瞭一個foo函數,最後輸出的結果是函數體內部foo的執行結果。對於上面這種狀況,應該在test函數體內部使用函數表達式的形式聲明一個新變量。
function foo() { return 'global'; } function test(x) { var g = foo; var result = []; if (x) { g = function() { return 'local'; } result.push(g()); } result.push(g()); return result; } test(true); // ['local','local'] test(false); // ['global']
關於eval函數理解不是很深,沒太理解這兩節的內容,等有時間從頭翻翻紅寶書回來再看看。