摘要:盡可能的使用局部變量,少用全局變量。正確的實現就是在函數體內部使用將聲明成局部變量。在新特性中,引入了塊級作用域這個概念,因此還可以使用,來聲明局部變量。它們共享外部變量,并且閉包還可以更新的值。
變量作用域
作用域,對于JavaScript語言來說無處不在,變量作用域,函數作用域(運行時上下文和定義時上下文),作用域污染等等都跟作用域息息相關,掌握JavaScript作用于規則,可以很好地避免一些極端的情況。
盡量少用全局對象在JavaScript語言下定義一個全局對象或者變量是最容易不過的了,這種全局變量最容易聲明并且使用,因為它能被整個程序所訪問到,但是經驗豐富的程序員應該盡量避免使用全局變量,它有如下的缺點:
全局變量會污染共享的公共命名空間,可能會導致意外的命名沖突
全局變量不利于模塊化,它會導致獨立組件之間的不必要耦合
但是,也不是說一定禁止使用全局變量,在一些情況下全局變量的使用是不可避免的,例如,它是各個獨立組件之間進行交互的唯一途徑。
盡可能的使用局部變量,少用全局變量。
局部變量: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)
始終聲明局部變量在JavaScript中,如果不使用var關鍵字進行變量聲明,該變量會被隱式轉換成全局變量,因此會造成不必要的全局空間污染。
function swap(a,b) { temp = a; // temp變成了global a = b; b = temp; }
這段程序沒有使用var來聲明temp變量,最終導致以外的創建了一個從全局的變量temp,雖然代碼執行起來也沒有錯誤。正確的實現就是在函數體內部使用var temp;將temp聲明成局部變量。
避免使用with語句在ES6新特性中,引入了塊級作用域這個概念,因此還可以使用let,const來聲明局部變量。
with語句作用是讓代碼運行在特定對象作用域內。with語句是JavaScript最令人詬病的特性,不可靠且效率低下。
熟練掌握閉包總而言之一句話,避免使用with語句即可
努力掌握JavaScript閉包這一概念,對于前端程序員來說會有非常大的幫助。理解閉包不用死背概念,理解三個基本事實就可以。
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創建局部變量 間接調用eval函數優于直接調用
關于eval函數理解不是很深,沒太理解這兩節的內容,等有時間從頭翻翻紅寶書回來再看看。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90067.html
摘要:如果為假值,不傳或者傳入,函數都會返回但是,傳入這個值是完全有可能的,所以這種判斷形勢是不正確的或者使用來判斷也可以原始類型優于封裝類型對象擁有六個原始值基本類型布爾值,數字,字符串,,和對象。 作為一個前端新人,多讀書讀好書,夯實基礎是十分重要的,正如蓋樓房一樣,底層穩固了,才能越壘越高。從開始學習到現在,基礎的讀了紅寶書《JavaScript高級程序設計》,犀牛書《JavaScri...
摘要:對象字面量定義一個空對象這里的空指的是其自身屬性為空,對象繼承了的屬性和方法添加屬性方法完全刪除屬性方法自定義構造函數用操作符調用構造函數時,函數內部會發發生以下情況創建一個新對象,并且引用了該對象并繼承了該函數的原型屬性和方法被加入到的引 對象字面量 //定義一個空對象,這里的空指的是其自身屬性為空,dog對象繼承了Object.prototype的屬性和方法 var dog={} ...
摘要:對象被傳遞到從句中被捕獲。一些語言提供了尾遞歸優化。這意味著如果一個函數返回自身遞歸調用的結果,那么調用的過程會被替換為一個循環,可以顯著提高速度。構建一個帶尾遞歸的函數。語言精粹讀書筆記函數 第四章 函數 Functions (二) 參數 arguments arguments數組: 函數可以通過此參數訪問所有它被調用時傳遞給它的參數列表,包括哪些沒有被分配給函數聲明時定義的形式參數...
閱讀 3471·2023-04-25 18:52
閱讀 2484·2021-11-22 15:31
閱讀 1224·2021-10-22 09:54
閱讀 3010·2021-09-29 09:42
閱讀 605·2021-09-26 09:55
閱讀 909·2021-09-13 10:28
閱讀 1098·2019-08-30 15:56
閱讀 2107·2019-08-30 15:55