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