javascript 函數(shù)及作用域總結(jié)介紹
在js中使用函數(shù)注意三點(diǎn):
1、函數(shù)被調(diào)用時(shí),它是運(yùn)行在他被聲明時(shí)的語法環(huán)境中的;
2、函數(shù)自己無法運(yùn)行,它總是被對(duì)象調(diào)用的,函數(shù)運(yùn)行時(shí),函數(shù)體內(nèi)的this指針指向調(diào)用該函數(shù)的對(duì)象,如果調(diào)用函數(shù)時(shí)沒有明確指定該對(duì)象, this 默認(rèn)指向 window ( strict 模式除外,本文不涉及 strict 模式);
3、函數(shù)是一種帶有可執(zhí)行代碼的對(duì)象類型數(shù)據(jù)。
一、聲明函數(shù)
1、使用 function 關(guān)鍵字
function myfun(a,b){ //聲明名為myfun的函數(shù)
return a+b;
}
2、 聲明匿名函數(shù)
function(a,b){ return a+b;}匿名函數(shù)自身是無法保存的,由于在js中函數(shù)是一種對(duì)象型數(shù)據(jù),因此可以把匿名函數(shù)賦給變量來保存。
var myfun = function(a,b){ return a+b;}
3、使用函數(shù)構(gòu)造器Function //注意首字母大寫
Function 是js內(nèi)置的一個(gè)函數(shù),他是所有函數(shù)對(duì)象的構(gòu)造器。(其他數(shù)據(jù)對(duì)象也有自己的內(nèi)置構(gòu)造函數(shù),比如Number,Object等,這些構(gòu)造函數(shù)自己的構(gòu)造器就是Function,因?yàn)樗麄兌际呛瘮?shù))。
var myfun = new Function('a,b','return a+b;'); 其中最后一個(gè)參數(shù)是函數(shù)體,前面的參數(shù)都是函數(shù)的形式參數(shù)名,個(gè)數(shù)不定,因?yàn)樾枰米址畟鲄順?gòu)造,函數(shù)較長(zhǎng)時(shí)這種寫法很不方便,一般很少用,也許你會(huì)用它來構(gòu)造特定的返回值從而取代 eval函數(shù)。
需要注意的是,全局變量和全局函數(shù)都可以看作window對(duì)象的屬性,如果存在同名的函數(shù)和變量,只能有一個(gè)生效(實(shí)際上只有一個(gè)屬性),試試下面的代碼。
function a(){ alert('a');}
alert(window.a); //訪問window對(duì)象的屬性也可以省去window不寫
var a=1;
alert(window.a);
函 數(shù)和變量的聲明都發(fā)生在代碼解析期,不同的是,變量在解析期只聲明不賦值,因此,同一個(gè)作用域內(nèi)存在同名的函數(shù)和變量時(shí),在代碼運(yùn)行期執(zhí)行到變量賦值之 前,同名函數(shù)生效,同名變量賦值之后(用新的數(shù)據(jù)覆蓋了該window對(duì)象屬性原有的值),變量生效(但是要注意,在firefox 下, 在 with 偽閉包內(nèi)聲明的函數(shù),只能在聲明之后才能被調(diào)用,即,firefox 的 with 內(nèi)沒有對(duì)函數(shù)預(yù)先聲明)。
with({}){
a(); //在 firefox 下 a 是未聲明
function a(){ console.log("function a is called");}
}
如果同名稱的函數(shù)被多次聲明,后面聲明的將覆蓋前面聲明的,如:
alert(func1);//彈出func1(){alert(2);}
func1(){
alert(1);
}
alert(func1); //彈出func1(){alert(2);}
func1(){ //這是最后一次聲明的func1,以該函數(shù)為準(zhǔn)
alert(2);
}
alert(func1); //彈出func1(){alert(2);}
var func1 = function(){ //注意 ,這里是變量賦值,不是函數(shù)聲明
alert(3);
}
alert(func1); //彈出function(){alert(3);}
除了 IE8 及IE8以下的瀏覽器,表達(dá)式中的函數(shù)聲明都會(huì)返回匿名函數(shù),不會(huì)成功聲明具名函數(shù)
if(function fun(){}){
alert(fun); // error,不會(huì)成功聲明名稱為 fun 的函數(shù),但在IE8 及以下的瀏覽器中中會(huì)成功聲明一個(gè)函數(shù) fun
}
(function fun(){ });
alert(fun); //error但是即使在 IE8 一下, 表達(dá)式中的具名函數(shù)也不能覆蓋該作用于下同名的變量:
var fun = 1; //該變量不能被函數(shù)表達(dá)式中的函數(shù)名覆蓋
var f = function fun(){};
alert(f); //function fun(){};
alert(fun); //1
注意區(qū)別:
if(fun = function (){}){
alert(fun); // ok,這里聲明了一個(gè)變量,該變量保存了一個(gè)匿名函數(shù)
}
js函數(shù)是引用型的對(duì)象
var a = function(){};
var b=a;
b.x=2;
alert(a.x); //2
二、函數(shù)的參數(shù)
js函數(shù)不會(huì)檢查函數(shù)調(diào)用時(shí)傳入的參數(shù)個(gè)數(shù)與定義他時(shí)的形式參數(shù)個(gè)數(shù)是否一致,一般地,js函數(shù)調(diào)用時(shí)可以接收的參數(shù)個(gè)數(shù)為25個(gè),當(dāng)然不同的瀏覽器可能有差異,ECMAScript標(biāo)準(zhǔn)對(duì)這一點(diǎn)并沒有規(guī)范。
如果你不確定函數(shù)調(diào)用時(shí)傳入了多少個(gè)參數(shù),可以使用函數(shù)的arguments對(duì)象。
arguments 有點(diǎn)像數(shù)組,arguments.length 為傳入的參數(shù)個(gè)數(shù),arguments[0] 是第一個(gè)參數(shù),arguments[1]是第二個(gè)參數(shù),類推...
函數(shù)對(duì)象的length屬性:這個(gè)屬性很少用到,甚至很少人知道,函數(shù)的length屬性就是該函數(shù)定義時(shí)的形式參數(shù)個(gè)數(shù)。
function myfun(a,b){
alert(arguments.length); //彈出調(diào)用時(shí)實(shí)際傳入的參數(shù)個(gè)數(shù)
alert(arguments[0]); //對(duì)應(yīng)參數(shù)a
return a+b;
}
alert(myfun.length); //形參個(gè)數(shù),2
arguments對(duì)象還有其他屬性,比如常用的arguments.callee ,指向該函數(shù)自身。
要注意:如果函數(shù)內(nèi)部聲明了與形參同名的子函數(shù)(同域內(nèi),變量未賦值時(shí)同名函數(shù)生效),arguments 的相應(yīng)值也會(huì)被修改,但是,在作用域內(nèi)使用 var 聲明了同名的 變量則不會(huì)導(dǎo)致 arguments 的參數(shù)值被函數(shù)替換(但firefox 依然替換)。
function aa(a , b,c){ //js 群的一道題
function a(){}
console.log(a); //function a
console.log(aa);
//如果作用域內(nèi)沒有 var a ,則 arguments[0] 為 function a (friefox(version 17) 則一定是function a)
console.log(arguments[0]);
var a = "ee"; //注銷此句,考擦 arguments[0] 將變?yōu)?a 函數(shù)
var aa = "444";
arguments = 6;
console.log(a);
console.log(aa);
console.log(arguments);
}
aa(1,2,3);
三、函數(shù)的返回值
js函數(shù)使用 return 語句返回值。
一切數(shù)據(jù)類型都可以作為函數(shù)的返回值(包括函數(shù)),js函數(shù)也可以沒有返回值。
四、函數(shù)調(diào)用
函數(shù)自己是不會(huì)運(yùn)行的,當(dāng)它運(yùn)行時(shí),總是存在一個(gè)調(diào)用它的對(duì)象。
默認(rèn)情況下,在任何語法環(huán)境中,如果沒有顯式指定函數(shù)的調(diào)用對(duì)象,就是指通過window對(duì)象來調(diào)用該函數(shù),此時(shí),函數(shù)體內(nèi)的this指針指向window對(duì)象。
function myfun(a,b){
alert(this);
return a+b;
}
myfun(1,2); // 調(diào)用函數(shù)并傳入2個(gè)參數(shù),這2個(gè)參數(shù)分別對(duì)應(yīng)形式參數(shù)a,b調(diào)用函數(shù)時(shí),如果傳入的參數(shù)個(gè)數(shù)超過形式參數(shù),就只有用arguments加下標(biāo)來接收了。
由于沒有顯式指定調(diào)用函數(shù)的對(duì)象,alert(this)將彈出 window對(duì)象。這種調(diào)用方法是最常見的。
用于顯式指定函數(shù)的調(diào)用對(duì)象方法有三個(gè):
1、如果一個(gè)函數(shù)被賦為一個(gè)對(duì)象的屬性值,這個(gè)函數(shù)只能通過該對(duì)象來訪問(但并非是說該函數(shù)只能被該對(duì)象調(diào)用),通過該對(duì)象調(diào)用這個(gè)函數(shù)的方式類似以面向?qū)ο缶幊陶Z言中的方法調(diào)用(實(shí)際上在js中也習(xí)慣使用方法這種稱呼)。
var obj={}; //定義一個(gè)對(duì)象
obj.fun=function(a,b){
alert(this); //彈出this指針
return a+b;
} //對(duì)象屬性值為函數(shù)
alert(obj.fun);// 訪問fun函數(shù)。 只能通過該對(duì)象來訪問這個(gè)函數(shù)
obj.fun(1,2); //通過obj對(duì)象來調(diào)用fun函數(shù),將彈出obj對(duì)象。這種方式也稱為調(diào)用obj對(duì)象的fun方法。
[code]
function fun(a,b){
alert(this);
return a+b;
}
var obj={};
fun.call(obj,1,2); //通過obj對(duì)象來調(diào)用fun函數(shù),并傳入2個(gè)參數(shù),彈出的指針為obj對(duì)象。
var obj2={};
obj2.fun2 = function(a,b){ //obj2對(duì)象的屬性fun2是一個(gè)函數(shù)
alert(this);
return a+b;
};
obj2.fun2.call(obj,1,2); //通過obj對(duì)象來調(diào)用obj2對(duì)象的fun2屬性值所保存的函數(shù),彈出的this指針是obj對(duì)象
//比較隱蔽的方法調(diào)用:數(shù)組調(diào)用一個(gè)函數(shù)[9,function(){ alert(this[0]); }][1]();
//使用window對(duì)象調(diào)用函數(shù)下面幾種方法是等價(jià)的
fun(1,2);
window.fun(1,2); //如果fun函數(shù)是全局函數(shù)
fun.call(window,1,2);
fun.call(this,1,2); //如果該句代碼在全局環(huán)境下(或者被window對(duì)象調(diào)用的函數(shù)體內(nèi)),因?yàn)樵撜Z法環(huán)境下的this就是指向window對(duì)象。
func.call(); //如果函數(shù)不需要傳參
func.call(null,1,2);
func.call(undefined,1,2);var name = "window";
function kkk(){
console.log(this.name); // not ie
}
kkk(); //window
kkk.call(kkk); //kkk 函數(shù)被自己調(diào)用了
另一種比較容易疏忽的錯(cuò)誤是,在A 對(duì)象的方法中,執(zhí)行了使用了 B 對(duì)象的方法調(diào)用,試圖在 B 對(duì)象的方法里使用 this 來訪問 A 對(duì)象,這在各種回調(diào)函數(shù)中比較常見,最常見的情形就是 ajax 回調(diào)函數(shù)中使用 this 。
var obj = {
data:null,
getData:function(){
$.post(url,{param:token},function(dataBack){ //jQuery ajax post method
this.data = dataBack; //試圖將服務(wù)器返回的數(shù)據(jù)賦給 obj.data ,但這里的 this 已經(jīng)指向 jQuery 的 ajax 對(duì)象了
},'json');
}
}
//正確做法
var obj = {
data:null,
getData:function(){
var host = this; //保存 obj 對(duì)象的引用
$.post(url,{param:"token"},function(dataBack){
host.data = dataBack;
},'json');
}
}
3、apply方法調(diào)用:
apply方法與call方法唯一不同的地方是函數(shù)傳參方式不同。
obj2.fun2.call(obj,1,2); 改為 apply方式就是obj2.fun2.apply(obj,[1,2]);
apply使用類數(shù)組方式傳參,除數(shù)組外,還可以使用arguments、HTMLCollection來傳參,但arguments并非數(shù)組,如:
var obj={};
function fun_1(x,y){
function fun_2(a,b){
return a+b;
}
fun_2.apply(obj,arguments); //用fun_1的arguments對(duì)象來傳參,實(shí)際上是接收了x,y
}apply 傳參在IE8 及IE8一下的瀏覽器中喲2個(gè)問題
在 call 和 apply 調(diào)用中,如果傳入標(biāo)量數(shù)據(jù)(true/false ,string,number),函數(shù)運(yùn)行時(shí)將把他們傳入的基本數(shù)據(jù)包裝成對(duì)象,然后把this指向包裝后的對(duì)象,試試下面的代碼。
function a(){
alert(typeof this);
alert(this.constructor);
alert(this);
}
a.call(false);
a.call(100);
a.call('hello');
甚至可以用這個(gè)特點(diǎn)來傳參數(shù),但是不建議這種用法:
function a(){ alert(1+this); } //對(duì)象在運(yùn)算中自動(dòng)進(jìn)行類型轉(zhuǎn)換
a.call(100); //101
4、函數(shù)作為對(duì)象構(gòu)造器
當(dāng)函數(shù)使用 new 運(yùn)算作為對(duì)象構(gòu)造器運(yùn)行時(shí),this 指向新構(gòu)造出對(duì)象,如果該構(gòu)造函數(shù)的返回值不是 null 以外的對(duì)象,構(gòu)造函數(shù)運(yùn)行完畢將返回 this 指向的對(duì)象,否則返回原定義的對(duì)象。
function Fun(){
this.a = 1;
this.b = 3;
console.log(this); //{a:1,b:2}
// return {a:999}; //加上此舉 ,將返回 {a:999}
}
var obj = new Fun(); //obj = {a:1,b:2} ,如果沒有參數(shù),也可以寫成 var obj = new Fun;
五、函數(shù)作用域
js的變量作用域是函數(shù)級(jí)的,在js里沒有類似c語言的塊級(jí)作用域。
js函數(shù)內(nèi)的變量無法在函數(shù)外面訪問,在函數(shù)內(nèi)卻可以訪問函數(shù)外的變量,函數(shù)內(nèi)的變量稱為局部變量。
js函數(shù)可以嵌套,多個(gè)函數(shù)的層層嵌套構(gòu)成了多個(gè)作用域的層層嵌套,這稱為js的作用域鏈。
js作用域鏈的變量訪問規(guī)則是:如果當(dāng)前作用域內(nèi)存在要訪問的變量,則使用當(dāng)前作用域的變量,否則到上一層作用域內(nèi)尋找,直到全局作用域,如果找不到,則該變量為未聲明。
注意,變量的聲明在代碼解析期完成,如果當(dāng)前作用域的變量的聲明和賦值語句寫在變量訪問語句后面,js函數(shù)會(huì)認(rèn)為當(dāng)前作用域已經(jīng)存在要訪問的變量不再向上級(jí)作用域查找,但是,由于變量的賦值發(fā)生的代碼運(yùn)行期,訪問的到變量將是undefined.
如:
var c=1000;
function out(){
var a=1;
var b=2;
function fun(){
alert(a); //undefined
var a=10;
alert(a); //10
alert(b); //2
alert(c); //1000
}
fun();
}
out();
六、匿名函數(shù)的調(diào)用
匿名函數(shù)的使用在js很重要,由于js中一切數(shù)據(jù)都是對(duì)象,包括函數(shù),因此經(jīng)常使用函數(shù)作為另一個(gè)函數(shù)的參數(shù)或返回值。
如果匿名函數(shù)沒有被保存,則運(yùn)行后即被從內(nèi)存中釋放。
匿名函數(shù)的調(diào)用方式一般是直接把匿名函數(shù)放在括號(hào)內(nèi)替代函數(shù)名。如:
(function(a,b){ return a+b;})(1,2); //聲明并執(zhí)行匿名函數(shù),運(yùn)行時(shí)傳入兩個(gè)參數(shù):1和2
//或者
(function(a,b){ return a+b;}(1,2));
//下面這種寫法是錯(cuò)誤的:
function(a,b){ return a+b;}(1,2);
由于js中語句結(jié)束的分號(hào)可以省略,js引擎會(huì)認(rèn)為function(a,b){ return a+b;}是一句語句結(jié)束,因此匿名函數(shù)只聲明了沒有被調(diào)用,如果語句沒有傳參(1,2)寫成(),還會(huì)導(dǎo)致錯(cuò)誤,js中空括號(hào)是語法錯(cuò)誤。
下面這種寫法是正確的。
var ab = function(a,b){ return a+b;}(1,2); // ab=3
js 解析語法時(shí),如果表達(dá)式出現(xiàn)在賦值運(yùn)算或操作符運(yùn)算中,是"貪婪匹配"的(盡量求值)
function(t){ return 1+t;}(); //error
var f = function(t){ return t+1;}(); // ok
~ function(t){return t+1;}(); //ok
+ function(t){return t+1;}(); //ok
如果你只是想把一個(gè)匿名函數(shù)賦給一個(gè)變量,記得在賦值語句后面加上分號(hào),否則,如果后面跟了小括號(hào)就變成了函數(shù)調(diào)用了,尤其是小括號(hào)與函數(shù)結(jié)尾之間分隔了多行時(shí),這種錯(cuò)誤往往很難發(fā)現(xiàn)。
實(shí)際開發(fā)中,匿名函數(shù)可能以運(yùn)算值的方式返回,這種情況可能不容易看出,比如
var a =1;
var obj = {a:2,f:function(){ return this.a;}};
(1,obj.f)(); //1 逗號(hào)表達(dá)式反悔了一個(gè)匿名函數(shù),當(dāng)這個(gè)匿名函數(shù)被調(diào)用時(shí),函數(shù)體內(nèi)的 thsi 指向 window
聲 明并立即運(yùn)行匿名函數(shù)被稱為”自執(zhí)行函數(shù)“,自執(zhí)行函數(shù)經(jīng)常用于封裝一段js代碼。由于函數(shù)作用域的特點(diǎn),自執(zhí)行函數(shù)內(nèi)的變量無法被外部訪問,放在函數(shù)內(nèi) 的代碼不會(huì)對(duì)外面的代碼產(chǎn)生影響,可以避免造成變量污染。js開發(fā)很容易造成變量污染,在開發(fā)中經(jīng)常引入其他編碼人員開發(fā)的代碼,如果不同的編碼人員定義 了同名稱不同含義的全局變量或函數(shù),便造成了變量污染,同一作用域內(nèi)出現(xiàn)同名的變量或函數(shù),后來的將覆蓋前面的。
(function(){
//自己的代碼.....
})();匿名函數(shù)還可以使內(nèi)存及時(shí)釋放:因?yàn)樽兞勘宦暶髟谀涿瘮?shù)內(nèi),如果這些變量沒有在匿名函數(shù)之外被引用,那么這個(gè)函數(shù)運(yùn)行完畢,里面的變量所占據(jù)的內(nèi)存就會(huì)立即釋放。
函數(shù)的name:在firefox等瀏覽器,函數(shù)有一個(gè)name屬性,就是該函數(shù)的函數(shù)名,但是這個(gè)屬性在IE中不存在,另外,匿名函數(shù)的name為空值。
var a=function(){}
alert(a.name); //undefined,a是一個(gè)存儲(chǔ)了一個(gè)匿名函數(shù)的變量
function b(){}
alert(b.name); //b ,but undefined for IE
七、函數(shù)被調(diào)用時(shí),運(yùn)行在他被定義時(shí)的環(huán)境中
無論函數(shù)在哪里被調(diào)用,被誰調(diào)用,都無法改變其被聲明時(shí)的語法環(huán)境,這決定了函數(shù)的運(yùn)行環(huán)境
var x=99;
var inerFun=null;
function fun1(){
alert(x);
}
function holder(){
var x = 100;
var fun2 = fun1;
inerFun = function(){ alert(x);}
fun1(); //99
fun2();//99
inerFun(); //100
}
holder();
fun1(); //99
inerFun(); //100
//另一個(gè)例子:
var x = 100;
var y=77;
var a1={
x:99,
xx:function(){
//var y=88; //如果注釋這個(gè)變量,y將是全局變量的77
alert(y); //沒有使用this指針,調(diào)用函數(shù)的對(duì)象無法影響y的值,函數(shù)運(yùn)行時(shí)將從這里按作用域鏈逐級(jí)搜索取值
alert(this.x); //使用了 this 指針,調(diào)用函數(shù)的
}
}
a1.xx();
a1.xx.call(window);
var jj = a1.xx;
jj(); //效果跟a1.xx.call(window); 一樣//試試下面代碼
var x=99;
function xb(){
this.x=100;
this.a = (function(){return this.x}).call(this); //new 的時(shí)候執(zhí)行了,匿名函數(shù)被 實(shí)例化的對(duì)象 調(diào)用
this.b = (function(){return this.x})(); //new 的時(shí)候執(zhí)行了,匿名函數(shù)被window調(diào)用
this.method = function(){return this.x;}
}
var xbObj = new xb();
console.log(xbObj.x);
console.log(xbObj.a);
console.log(xbObj.b);
console.log(xbObj.method());
注意區(qū)分調(diào)用函數(shù)的對(duì)象、函數(shù)聲明時(shí)的語法環(huán)境、函數(shù)調(diào)用語句的語法環(huán)境這幾個(gè)概念
1、調(diào)用函數(shù)的對(duì)象(或者說函數(shù)的調(diào)用方式)決定了函數(shù)運(yùn)行時(shí)函數(shù)體內(nèi)的this指針指向誰
2、函數(shù)聲明時(shí)的語法環(huán)境決定了函數(shù)運(yùn)行時(shí)的訪問權(quán)限
3、函數(shù)調(diào)用語句的語法環(huán)境決定了函數(shù)是否真的能夠被調(diào)用及何時(shí)被調(diào)用(只有函數(shù)在某個(gè)語法環(huán)境是可見的,這個(gè)函數(shù)才能被調(diào)用)
函數(shù)在運(yùn)行時(shí),產(chǎn)生一個(gè) arguments 對(duì)象可以訪問傳入函數(shù)內(nèi)的參數(shù),arguments 有一個(gè)屬性可以指向函數(shù)自身:arguments.callee.
函數(shù)運(yùn)行時(shí),函數(shù)的 caller 屬性可以指向本函數(shù)調(diào)用語句所在函數(shù),比如,a函數(shù)在b函數(shù)體內(nèi)被調(diào)用,則當(dāng)a函數(shù)運(yùn)行時(shí),a.caller就指向了b函數(shù),如果a 函數(shù)在全局環(huán)境中被調(diào)用則 a.caller=null
arguments 和a.caller 的值與函數(shù)的每一次調(diào)用直接關(guān)聯(lián),他們都是在函數(shù)運(yùn)行時(shí)產(chǎn)生的,只能在函數(shù)體內(nèi)訪問。
IE8及IE8以下瀏覽器中,a 函數(shù)的內(nèi)的 arguments.caller( IE9之后這個(gè)屬性被移除) 指向 a.caller 執(zhí)行時(shí)的 arguments (arguments.caller.callee === a.caller),
七、字符串實(shí)時(shí)解析中的函數(shù)調(diào)用:eval()、new Function()、setTimeout()、setInterval()
eval() 與 window.eval()
function a(){
console.log('out of b');
}
function b(){
function a(){ console.log("in b"); }
var f = function(){ a(); };
eval('a()'); // in b
window.eval('a()'); //out of b ,ie 6\7\8 in b, ie 9 out of b
(new Function('a();'))(); //out of b
setTimeout('a()',1000); // out of b
setTimeout(f,2000);// in b
}
b();
eval() 中的代碼執(zhí)行于eval() 語句所處的作用域內(nèi):
var Objinit = function(){
var param = 123;
return {
execute:function(codes){
eval(codes);
},
setCallback:function(f){
this.callback = f;
},
fireCallback:function(){
this.callback && this.callback.call(this);
},
getParam:function(){
return param;
}
}
};
var obj = Objinit ();
var param = 'outerParam';
console.log(param,obj.getParam()); //outerParam 123
obj.execute('param = 456');
console.log(param,obj.getParam()); //outerParam 456
obj.setCallback(function(){ eval("param = 8888")});
obj.fireCallback();
console.log(param,obj.getParam()); //8888 456
obj.setCallback(function(){ eval("eval(param = 9999)")});
obj.fireCallback();
console.log(param,obj.getParam()); //9999 456eval()
IE 中 ,window.execScript(); 相當(dāng)于 window.eval()
八、函數(shù)閉包
要理解函數(shù)閉包,先了解 js 的垃圾自動(dòng)回收機(jī)制。
number、string、boolean、undefined、null 在運(yùn)算和賦值操作中是復(fù)制傳值,而對(duì)象類型的數(shù)據(jù)按引用傳值,
js 的同一個(gè)對(duì)象型數(shù)據(jù)可能被多次引用,如果某個(gè)對(duì)象不再被引用,或者兩個(gè)對(duì)象之間互相引用之外不在被第三方所引用,瀏覽器會(huì)自動(dòng)釋放其占用的內(nèi)存空間。
函數(shù)被引用:函數(shù)被賦為其他對(duì)象的屬性值,或者函數(shù)內(nèi)部定義的數(shù)據(jù)在該函數(shù)外被使用,閉包的形成基于后一種情形。
var f;
function fun(){
var a =1;
f = function(){ return ++a;};
}
fun(); //產(chǎn)生一個(gè)閉包
f(); // 閉包中 a=2
f(); // 閉包中 a =3 ,模擬靜態(tài)變量
在 fun 內(nèi) 聲明的匿名函數(shù)賦給 fun 外的變量 f,該匿名函數(shù)內(nèi)使用了在 fun 內(nèi)聲明的變量 a,于是 f可以訪問 變量 a,為了維持這種訪問權(quán)限(f執(zhí) 行時(shí)需要訪問a,但何時(shí)執(zhí)行未定), fun() 執(zhí)行完畢產(chǎn)生的變量 a 不能被釋放(除非f 中的函數(shù)被釋放),于是產(chǎn)生了一個(gè)閉包(變量 a 被封 閉了,供 f 使用)。
產(chǎn)生閉包的關(guān)鍵是,一個(gè)在函數(shù) A內(nèi)的聲明的函數(shù) B被傳出 A 之外,并且 B 函數(shù)內(nèi)使用了在 函數(shù)A 內(nèi)生成的數(shù)據(jù)(聲明或按值傳參),
函數(shù)B傳出函數(shù)A之外的方式有多種,如:
function fun(){
var a =1;
return {a:123,b:456, c: function(){ return ++a;} };
}
var f = fun();
f.c(); //a=2
廣義上來說,函數(shù)運(yùn)行時(shí)都會(huì)形成閉包,沒有數(shù)據(jù)在函數(shù)外被引用時(shí),閉包的生命周期很短:函數(shù)執(zhí)行完畢即釋放。
function fun(){
var a =1;
return function(){ return ++a;};
}
var f1 = fun(); //一份閉包
var f2 = fun(); //另一份閉包
alert(f1()); //2
alert(f1()); //3
alert(f2()); //2
alert(f2()); //3
這兩份閉包中的變量 a 是不同的數(shù)據(jù),每產(chǎn)生一份閉包, fun() 執(zhí)行了一次, 變量聲明語句也執(zhí)行了一次。
js oop 編程中閉包可以用于模擬私有成員、構(gòu)造單體類
function MakeItem(name,val){
var myName,myVal; //私有屬性
//私有方法
function setName(name){
myname=name;
}
//私有 方法
function setVal(val){
myVal=val;
}
//執(zhí)行new構(gòu)造對(duì)象時(shí)調(diào)用內(nèi)部私有方法
setName(name);
setVal(val);
//公共方法
this.getName=function(){
return myName;
}
this.getVal=function(){
return myVal;
}
}
var obj = new MakeItem("name",100);
obj.myname; //undefined 無法在外面訪問私有屬性
obj.getName(); //ok
下面是一種單體類構(gòu)建方法
var Singleton = (function(){
var instance = null; //在閉包中保存單體類的實(shí)例
var args = null;
var f = function(){
if(!instance){
if(this===window){
args = Array.prototype.slice.call(arguments,0);
instance = new arguments.callee();
}else{
this.init.apply(this,args||arguments);
instance = this;
}
}
return instance;
};
f.prototype = {
init:function(a,b,c){
this.a = a;
this.b = b;
this.c = c;
this.method1 = function(){ console.log("method 1"); };
this.method1 = function(){ console.log("method 1"); };
console.log("init instance");
}
};
f.prototype.constructor = f.prototype.init;
return f;
})();
//單體的使用
var obj1 = Singleton(1,2,3);
var obj2 = new Singleton();
var obj3 = new Singleton();
console.log(obj1===obj2,obj2===obj3); //true
console.log(obj1);
//一個(gè)單體類聲明函數(shù)
var SingletonDefine= function(fun){
return (function(){
var instance = null;
var args = null;
var f = function(){
if(!instance){
if(this===window){
args = Array.prototype.slice.call(arguments,0);
instance = new arguments.callee();
}else{
fun.apply(this,args||arguments);
instance = this;
}
}
return instance;
};
f.prototype = fun.prototype;
f.prototype.constructor = fun;
return f;
})();
};
var fun = function(a,b,c){
this.a = a;
this.b = b;
this.c = c;
this.method1 = function(){ console.log("method 1"); };
console.log("init instance");
};
fun.prototype.method2 = function(){ console.log('method 2'); };
//單體類聲明函數(shù)用法
var Singleton = SingletonDefine(fun);
var obj1 = Singleton(8,9,10);
var obj2 = new Singleton();
var obj3 = new Singleton(3,2,1);
console.log(obj1===obj2,obj2===obj3);
console.log(obj1);
//console.log(obj1.toSource()); //firefox
obj1.method1();
obj1.method2();
IE6 的內(nèi)存泄露與閉包
在IE 6 中,非原生js對(duì)象(DOM 等)的循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄露,使用閉包時(shí)如果涉及非 js 原生對(duì)象引用時(shí)要注意。
function fun(){
var node = document.getElementById('a');
node.onclick = function(){ alert(node.value); };
node = null; //打斷循環(huán)引用防止內(nèi)存泄露
node 保存的是 DOM 對(duì)象,DOM對(duì)象存在于 fun 之外(并且一直存在,即使刪除也只是從文檔樹移出),fun 執(zhí)行后產(chǎn)生閉包,也構(gòu)成DOM對(duì)象與回調(diào)函數(shù)的循環(huán)引用(node-function-node),在IE 6 下發(fā)生內(nèi)存泄露。
相關(guān)文章
等待指定時(shí)間后自動(dòng)跳轉(zhuǎn)或關(guān)閉當(dāng)前頁面的js代碼
本文為大家詳細(xì)介紹下如何通過js實(shí)現(xiàn)等待指定時(shí)間后自動(dòng)跳轉(zhuǎn)或關(guān)閉當(dāng)前頁面的腳步代碼,感興趣的朋友可以參考下哈,希望對(duì)大家有所幫助2013-07-07js實(shí)現(xiàn)固定區(qū)域內(nèi)的不重疊隨機(jī)圓
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)固定區(qū)域內(nèi)的不重疊隨機(jī)圓,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10JavaScript基于面向?qū)ο髮?shí)現(xiàn)的無縫滾動(dòng)輪播示例
這篇文章主要介紹了JavaScript基于面向?qū)ο髮?shí)現(xiàn)的無縫滾動(dòng)輪播,結(jié)合實(shí)例形式分析了JavaScript面向?qū)ο髮?shí)現(xiàn)的無縫滾動(dòng)輪播相關(guān)對(duì)象定義、初始化及功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-01-01純javascript移動(dòng)優(yōu)先的幻燈片效果
這篇文章主要介紹了純javascript實(shí)現(xiàn)移動(dòng)優(yōu)先的幻燈片效果,感興趣的小伙伴們可以參考一下2015-11-11js實(shí)現(xiàn)左右兩側(cè)浮動(dòng)廣告
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)左右兩側(cè)浮動(dòng)廣告,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07