摘要:閉包是一個擁有自由訪問另一個函數作用域的表達式通常是一個函數。意味著內部函數擁有外部函數的作用域。所以通過函數構造方式分構造函數無論何時都是應該被避免的。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions_and_function_scope
Javascript 函數 函數作用域
通常來說,函數就是一個可以被外部調用的(或者函數本身遞歸調用)的“子程序”。和程序本身一樣,一個函數的函數體是由一系列的語句組成的,函數可以接受函數,也可以返回一個返回值。
在Javascript中函數也是對象,也可以像其他對象一樣,添加屬性和方法等。具體來講,它們是Function對象。
用法簡介在Javascript中,每個函數其實都是一個Function對象,查看Function界面,了解Function屬性和方法。
要想返回一個返回值,函數必須使用return語句指定所要返回的值(使用new關鍵詞的構造函數除外)。如果一個函數沒有return語句,則它默認返回undefined。
調用函數時,傳入函數的值為函數的實參,對應位置的函數參數名為形參。如果實參是一個包含原始值(數字,字符串,布爾值)的變量,則就算函數在內部改變了形參的值,返回后該實參的值也不會變壞。如果實參是一個對象引用,則對應形參和實參會指向同一個對象,如果函數內部形參引用對象的值發生了改變,則實參指向的引用對象的值也會改變:
/*定義函數 myFunc*/ function myFunc(theObject) { theObject.brand = "Toyota"; } /* *定義變量 mycar; *創建并初始化一個對象; *將對象的引用賦值給變量mycar; */ var mycar = { brand : "Honda", model : "Accord", year : 1998 }; window.alert(mycar.brand); //彈出“Honda” myFunc(mycar); window.alert(mycar.brand); //彈出“Toyota”
函數執行時,this不會指向正在運行的函數本身,而是指向調用該函數的對象。如果你想在函數內部獲取自身的引用,只能使用函數名或者arguments.callee屬性。
定義函數定義函數一共有三種方法:
函數聲明(function語句)有一個特別的語法來聲明函數:
function name([param,[param,[...param]]]) { //statements }
name
函數名
param
函數的參數名稱,一個函數最多有255個參數。
statements
函數體內執行的語句
函數表達式很函數聲明有著很多相同之處,他們甚至有著相同的語法:
function[name]([param,[param,[...param]]]) { //statements }
name
可以省略
param
函數的參數名稱,一個函數最多有255個參數。
statements
函數體內執行的語句
和其他類型一樣,Function對象可以使用new操作符來創建:
[new] Function (arg1, arg2, ... argN, functionBody)
arg1, arg2, ... argN
一個或多個變量名稱,來作為函數的形參名.類型為字符串,值必須為一個合法的JavaScript標識符,例如Function("x", "y","alert(x+y)"),或者逗號連接的多個標識符,例如Function("x,y","alert(x+y)")
functionBody
一個字符串,包含了組成函數的函數體的一條或多條語句.
即使不使用new操作符,直接調用Function函數,效果也是一樣的,仍然可以正常創建一個函數.
arguments對象注意: 不推薦使用Function構造函數來創建函數.因為在這種情況下,函數體必須由一個字符串來指定.這樣會阻止一些JS引擎做程序優化,還會引起一些其他問題.
在函數內部,你可以使用arguments對象獲取到該函數的所有傳入參數. 查看 arguments.
作用域和函數堆棧調用其他函數時的作用域部分和函數部分
遞歸函數能指向并引用它自己,有三種引用方法引用函數它自己:
通過函數名
arguments.callee
指向該函數的變量
考慮到一下的函數定義:
var foo = function bar() { //statements }
在這個函數內部,下面就是上面所說的三種:
bar()
arguments.callee
foo()
一個函數調用它自己叫做遞歸函數。在某些方面,遞歸函數像一個循環(loop),同樣的代碼多次執行同時有一個控制語句(為了避免無限循環),比如下面的循環:
var x = 0; while (x < 10){ x++ }; console.log(x); //輸出10
能變換為迭代函數并調用該函數:
function loop(x) { if(x>=10){console.log(x);} else{loop(x+1);} } loop(0); //返回10
然而,一些算法并不是簡單的迭代循環。比如,獲得DOM節點使用遞歸就更方便:
function walkTree(node) { if (node == null) return; for(var i = 0; i < node.childNodes.length; i++){ walkTree(node.childNodes[i]); } }
比較這些循環方程,每一個遞歸調用都會調用更多的調用。
將遞歸算法轉換成非遞歸算法大多是能實現的,但是經常邏輯會變得更加復雜并且要使用更多的堆棧,事實上,遞歸本身就使用堆棧:函數堆棧。
這種類似堆棧的行為能在下圖中看到:
function foo(i){ if(i<0) return; console.log("begin: " + i); foo(i - 1); console.log("end: " + i); } foo(3);嵌套函數和閉包
你可以將一個函數嵌套在另一個函數內部,被嵌套函數只是他外部函數的一個私有函數,這種情況我們將它叫做閉包。
閉包是一個擁有自由訪問另一個函數作用域的表達式(通常是一個函數)。
當一個被嵌套函數是一個閉包時,意味著一個被嵌套函數能繼承它的外部函數的參數(arguments)和變量(variables)。意味著內部函數擁有外部函數的作用域。
總而言之:
內部函數能擁有訪問外部函數的語句
內部的函數定義了一個閉包:內部函數能使用外部函數的參數(arguments)和變量(variables),但是外部函數不能使用內部函數的參數(arguments)和變量(variables)。
下例演示一個函數閉包:
function addSquares(a,b){ function square(x){ return x*x; } return square(a)+square(b); } console.log(addSquares(1,2)); //返回5
由于內部函數形成了一個閉包,你可以把內部函數當作返回值返回:
function outSide(x){ function inSide(y){ return x + y; } return inSide; } fnInside = outSide(1); console.log(fnInside(2)); //返回3 console.log(outSide(2)(10)); //返回12變量的保存
注意,x變量是如何在inside函數返回后被保存的。一個閉包必須保存它的作用域中的參數和變量。每一次調用有可能提供不同的參數,這樣一個新的閉包都會在外層調用時被創建,只有當要返回的內部函數不再被訪問時內存才會被清空。
這和保存來自其他對象的引用沒有區別,但是后一種情況更加少見因為一個函數不會直接設置引用并且不能檢查它們。
函數能多層嵌套,A函數中B函數,B函數中包含C函數。在這個函數里B和C都是閉包。因此B函數能訪問A函數,C函數能訪問B函數。并且,C函數也能訪問A函數。這樣看,閉包能包含多層嵌套。它們遞歸的包含外層的作用域,這種情況我們稱之為作用域鏈。
例子如下:
function A(x){ function B(y){ function C(z){ console.log(x + y + z); } C(2); } B(6); } A(1);
此例中C能訪問B中y和A中的x,這些之所以能成立是因為:
1.B為A的閉包,B能訪問A的變量和參數。
2.C為B的閉包
3.因為B包含A的作用域,所以C也包含A的作用域。換言之,C包含B和A的作用域鏈。
然而反過來卻不行,A不能訪問B,B也不能訪問C
命名沖突當閉包中兩個變量或者參數擁有相同的名字,這種情況叫做命名沖突。越內層的函數擁有越高的優先權,最內層的作用域擁有最高的優先權,最外層的作用域的優先權最低。作用域鏈的原理如此。作用域最前端是最內層的作用域,最后是最外層。考慮如下例子
function outside(){ var x = 10; function inside(x){ return x; } return inside; } result = outside()(20); //返回20
上例中的命名沖突發生在return的x是返回inside里的參數x還是外層代碼的x,此處的作用域鏈為(inside,outside,全局),然而內層的x優先級比外層的x優先級高,于是返回的是內層的x。
函數構造器vs.函數聲明vs.函數表達式比較如下:
1.用函數構造器構造函數并添加參數:
var multiply = new Function("x", "y", "return x*y;");
2.函數聲明:
function multiply(x, y) { return x*y; }
3.函數表達式:
var multiply = function(x, y) { return x*y; }
4.函數命名表達式
var multiply = function func_name(x, y) { return x*y; }
上面的代碼大約都是干的同樣的事情,除了一些微妙的區別:
函數名和函數指定的變量名是有區別的:
函數名是不能改變的,函數指定變量名是可以改變的
函數名的使用只能和函數體一起使用,如果嘗試在函數體外使用函數名的話會報錯error或者返回undefined。比如:
y = function x(){};
alert(x);
另一方面,函數指定變量名(比如最后一例中的multiply)只被它自身(變量名)的作用域所約束,這個作用域一定包含函數聲明的作用域。
如同以上四個例子所示,函數名與函數指定變量名不同。它們彼此之間沒有關聯。
函數聲明同樣會創建一個和函數名一樣的變量。不像那些在函數表達式中定義的那樣,在函數聲明中定義的函數能在它們定義的作用域中通過它們的命名被訪問:
function x(){ } console.log(x); //返回function(){};
接下來的例子會展示函數名和函數指定變量是如何無關的。即使函數名被指定給了其他的變量名,返回的仍然是函數名:
function foo(){}; console.log(foo); var bar = foo; console.log(bar);
一個由“new Function()”創建的新函數是沒有函數名的。在spiderMonkey的javascript引擎中,如果函數名為“anonymous”序列化的函數將會顯示,比如下例中使用“console.log(new Function())”的輸出:
function anonymous(){}; console.log(new Function()); //返回“function anonymous(){}”
不像函數表達式定義的函數調用必須在函數表達式后面,函數聲明的函數調用可以在函數的前面,如下:
foo(); function foo(){ alert("FOOO"); }
函數表達式中定義的函數會繼承當前的作用域。就是說該函數是一個閉包。而使用Function創建的函數不會繼承任何的作用域除了全局作用域(所有的函數都會繼承)。
函數表達式和函數聲明定義的函數只會解析一次,但那些使用Function構造的卻不是。就是說,通過new Function方式構建函數時內部的字符每一次都會重解析,函數本身不會重分析,于是函數表達式比“new Function(...)”這種方式要快。所以通過函數構造方式分構造函數無論何時都是應該被避免的。這應該被記下來,然而,函數表達式和函數聲明嵌套在通過函數構造器構造的中且立即執行就不會重復解析多次,如下例:
var foo = (new Function("var bar="fooo!"; return(function(){ alert("bar"); })"))(); foo();
函數聲明通常會很輕易的變成函數表達式,函數聲明不再是函數聲明的原因可能是因為它滿足以下兩個條件:
1.變成了表達式的一部分。
2.它不再是函數的“資源元素”,一個“資源元素”是腳本或者函數中的一段非嵌套語句:
var x = 0; //資源元素 if (x == 0) { //資源元素 x = 10; //不是資源元素 function boo() {} //不是資源元素 } function foo(){ //資源元素 var y = 20; //資源元素 function bar() {} //資源元素 while(y == 10){ //資源元素 function blah(){} //不是資源元素 y++; //不是資源元素 } }
例如:
//函數聲明 function foo(){} //函數表達式 (function bar(){}) //函數表達式 var x = function hello(){}
if(x){ //函數表達式 function hello(){} }
//函數聲明 function a(){ //函數聲明 function b(){} if(0) { //函數表達式 function c(){} } }條件執行函數
函數可以定義在條件語句里通過普通的function語句和new Function語句。但請注意以下這種情況ECMAScript5中不再允許出現函數語句,所以這個特性在跨瀏覽器中并不能表現的很好,你不能再編程中完全依賴它。
在下面的代碼中,這個zero函數永遠都不能定義和執行。因為if(0)永遠都返回false;
if(0) { function zero(){ document.writeln("This is a zero."); } }
如果這個條件發生改變,if(1)那么zero就會被定義。
注意:盡管這一類函數看上去就像是函數聲明,但是它實際上是函數表達式。因為它被包含在其他的條件語句中。看函數表達式和函數聲明有何不同.
注意:一些javascript的解析器,不包括SpiderMonkey,錯誤的把命名函數表達式當成了函數定義。這樣會導致zero會被定義即使if返回的是false。安全的方法是將匿名函數指定給變量:
if(0) { var zero = function(){ document.writeln("This is zero."); } }函數和事件處理程序
在JavaScript中, DOM事件處理程序只能是函數(相反的,其他DOM規范的綁定語言還可以使用一個包含handleEvent方法的對象做為事件處理程序)(譯者注:這句話已經過時,目前在大部分非IE[6,7,8]的瀏覽器中,都已經支持一個對象作為事件處理程序). 在事件被觸發時,該函數會被調用,一個 event 對象會作為第一個也是唯一的一個參數傳入該函數.像其他參數一樣,如果不需要使用該event對象, 則對應的形參可以省略.
通常的HTML事件對象包括:window(Window對象,包括frames),document(HTMLdocument對象和elements(各種元素對象)。在HTMLDOM中,事件對象擁有時間處理程序屬性,這個屬性通常是小寫的有on前綴的,比如:onfocus。一個更加靈活的添加事件對象的方法有DOM2級事件。
注意:事件是DOM的一部分,而不是javascript的一部分。
下例會個window對象的onfocus事件綁定一個函數:
window.onfocus = function() { document.body.style.backgroundColor = "blue"; }
如果函數綁定到了變量上,那么可以將事件指向該變量。如下:
var setBgColor = new Function("document.body.style.backgroundColor = "while" ");
你可以像如下的方法那樣使用它們:
1.給DOM的事件屬性指向變量:
document.form1.colorbutton.onclick = setBgColor;
2.HTML標簽屬性:
上例在執行過程中的效果如下代碼:
document.form1.colorbutton.onclick = function onclick(){ setBgColor(); }
注意:怎樣在HTML中調用一個onclick返回事件的屬性。可以像如下這樣使用:
就像其他的參數參考的函數一樣,事件處理程序同樣是方法,在函數中返回的this對象會指向調用該方法的對象。比如:
window.onfocus上例中在onfocus上綁定的事件處理程序的this對象就會指向window對象。
給事件綁定的有傳參的事件處理程序,必須被包含在其他的函數中:document.form1.button1.onclick = function(){ setBgColor("some color"); }函數的局部變量arguments:一個"類數組"的對象,包含了傳入當前函數的所有實參;
arguments.callee:指向當前函數;
arguments.caller:指向調用當前函數的函數,請使用arguments.callee.caller代替;
arguments.length:arguments對象的中元素的個數。同步于個人博客:http://penouc.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78097.html
摘要:而閉包的神奇之處正是可以阻止這件事情的發生。事實上內部作用域依然存在,因此沒有被回收頻繁使用閉包可能導致內存泄漏。拜所聲明的位置所賜,它擁有涵蓋內部作用域的閉包,使得該作用域能夠一直存活,以供在之后任何時間進行引用。 作用域 作用域(scope),程序設計概念,通常來說,一段程序代碼中所用到的變量并不總是有效/可用的,而限定這個變量的可用性的代碼范圍就是這個變量的作用域。通俗一點就是我...
摘要:理解的函數基礎要搞好深入淺出原型使用原型模型,雖然這經常被當作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統的類繼承還要強大。中文指南基本操作指南二繼續熟悉的幾對方法,包括,,。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家帶來幫助....(據說是阿里的前端妹子寫的) this 的值到底...
閱讀 4221·2021-09-26 10:17
閱讀 871·2021-09-22 15:02
閱讀 3446·2021-09-06 15:00
閱讀 1055·2021-07-25 16:52
閱讀 2734·2019-08-29 16:16
閱讀 2515·2019-08-29 13:25
閱讀 1588·2019-08-26 13:51
閱讀 2182·2019-08-26 10:58