在js中使用函數注意三點:
1、函數被呼叫時,它是運行在他被聲明時的語法環境中的;
2、函數本身無法運行,它總是被對象調用的,函數運行時,函數體內的this指針指向調用該函數的對象,如果調用函數時沒有明確指定該對象, this 默認指向window ( strict模式除外,本文不涉及strict 模式);
3、函數是一種帶有可執行程式碼的物件類型資料。
一、宣告函數
1、使用function 關鍵字
function myfun(a,b){ //宣告名為myfun的函式
return a b;
function(a,b){ return a b;}匿名函數本身是無法保存的,由於在js中函數是一種物件型數據,因此可以把匿名函數賦給變數來保存。
var myfun = function(a,b){ return a b;}
Function 是js內建的函數,他是所有函數物件的建構器。 (其他資料物件也有自己的內建建構函數,像是Number,Object等,這些建構子自己的建構子就是Function,因為他們都是函數)。
var myfun = new Function('a,b','return a b;'); 其中最後一個參數是函數體,前面的參數都是函數的形式參數名,個數不定,因為需要用字串傳參來構造,函數較長時這種寫法很不方便,一般很少用,也許你會用它來構造特定的返回值從而取代eval函數。
function a(){ alert('a'); }
alert(window.a); //存取window物件的屬性也可以省去window不寫
var a=1;
alert(func1);//彈出func1(){alert(2);}
func1(){
alert(1);
}
alert(func1); //彈出func1(){alert(2);}
func1(){ //這是最後一次宣告的func1,以此函數為準
alert(2);
}
alert(func1); //彈出func1(){alert(2);}
var func1 = function(){ //注意 ,這裡是變數賦值,不是函數宣告
alert(3);
}
}
(function fun(){ });
alert(fun); //error但是即使在 IE8 一下, 表達式中的具名函數也不能覆蓋該作用於下同名的變數:
var fun = 1; //該變數不能被函數表達式中的函數名稱覆蓋
var f = function fun(){};
alert(f); //function fun(){ };
alert(fun); //1
if(fun = function (){}){
alert(fun); // ok,這裡宣告了一個變量,該變數保存了一個匿名函數
}
js函數是引用型的物件
var a = function(){};
var b=a;
b.x=2;
alert(a.x); //2
二、函數的參數
js函數不會檢查函數呼叫時傳入的參數個數與定義他時的形式參數個數是否一致,一般地,js函數呼叫時可以接收的參數個數為25個,當然不同的瀏覽器可能有差異,ECMAScript標準對這一點並沒有規範。
如果你不確定函數呼叫時傳入了多少個參數,可以使用函數的arguments物件。
arguments 有點像數組,arguments.length 為傳入的參數個數,arguments[0] 是第一個參數,arguments[1]是第二個參數,類推...
函數物件的length屬性:這個屬性很少用到,甚至很少人知道,函數的length屬性就是該函數定義時的形式參數個數。
alert(arguments.length); //彈出呼叫時實際傳入的參數個數
alert(arguments[0]); //對應參數a
return a b;
}
alert(myfun.length); //形參數,2
要注意:如果函數內部宣告了與形參同名的子函數(同域內,變數未賦值時同名函數生效),arguments 的對應值也會被修改,但是,在作用域內使用var 宣告了同名的變數則不會導致arguments 的參數值被函數取代(但firefox 依然替換)。
四、函數呼叫
函數本身是不會運作的,當它運行時,總是存在一個呼叫它的物件。
預設情況下,在任何語法環境中,如果沒有明確指定函數的呼叫對象,就是指透過window對象來呼叫該函數,此時,函數體內的this指針指向window對象。return a b;
}
myfun(1,2); // 呼叫函數並傳入2個參數,這2個參數分別對應形式參數a,b呼叫函數時,如果傳入的參數個數超過形式參數,就只有用arguments加下標來接收了。
由於沒有明確指定呼叫函數的對象,alert(this)將彈出 window對象。這種呼叫方法是最常見的。
用於明確指定函數的呼叫物件方法有三:
1、如果一個函數被賦為一個物件的屬性值,這個函數只能透過該物件來存取(但並非是說該函數只能被該物件呼叫),透過該物件呼叫這個函數的方式類似以物件導向程式語言中的方法呼叫(實際上在js中也習慣使用方法這種稱呼)。
obj.fun=function(a,b){
alert(this); //彈出this指標
return a b;
} //物件屬性值為函數
alert(obj.fun);// 存取fun函數。 只能透過該物件來存取這個函數
obj.fun(1,2); //透過obj物件來呼叫fun函數,將彈出obj物件。這種方式也稱為呼叫obj物件的fun方法。
alert(this);
return a b;
}
var obj={};
fun.call(obj,1,2); //透過obj物件來呼叫fun函數,並傳入2個參數,彈出的指標為obj物件。
var obj2={};
obj2.fun2 = function(a,b){ //obj2物件的屬性fun2是一個函數
alert(this);
return a b;
};
obj2.fun2.call(obj,1,2); //透過obj物件來呼叫obj2物件的fun2屬性值所保存的函數,彈出的this指標是obj物件
//比較隱藏的方法呼叫:陣列呼叫一個函數[9,function(){ alert(this[0]); }][1]();
//使用window物件呼叫函數以下幾種方法是等價的
fun(1,2);
window.fun(1,2); //如果fun函數是全域函數
fun.call(window,1,2);
fun.call(this,1,2); //如果該句代碼在全域環境下(或被window物件呼叫的函數體內),因為該語法環境下的this就是指向window物件。
func.call(); //如果函數不需要傳參
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 函數被自己調用了
}
}
}
}
//正確做法
var obj = {
data:null,
getData:function(){
. post(url,{param:"token"},function(dataBack){
host.data = dataBack;
apply方法與call方法唯一不同的地方是函數傳參方式不同。
obj2.fun2.call(obj,1,2); 改為 apply方式就是obj2.fun2.apply(obj,[1,2]);
apply使用類別數組方式傳參,除數組外,還可以使用arguments、HTMLCollection來傳參,但arguments並非數組,如:
var obj={};
function fun_1(x,y){
function fun_2(a,b){
return a b;
}
fun_2.apply(obj,arguments); //用fun_1的arguments物件來傳參,實際上是接收了x,y
}apply 傳參在IE8 及IE8一下的瀏覽器中喲2個問題
在call 和apply 調用中,如果傳入標量數據(true/false ,string,number),函數運行時將把他們傳入的基本數據包裝成對象,然後把this指向包裝後的對象,試試下面的程式碼。
function a(){
alert(typeof this);
alert(this.constructor);
alert(this);
}
a.call(false);
a.call(100);
a.call('hello');
甚至可以用這個特點來傳參數,但不建議這種用法:
function a(){ alert(1 this); } //物件在運算中自動進行型別轉換
a.call(100); //101
4、函數作為物件建構器
當函數使用new 運算作為對象建構器運行時,this 指向新構造出對象,如果該構造函數的返回值不是null 以外的對象,構造函數運行完畢將返回this 指向的對象,否則返回原定義的對象。
this.a = 1;
this.b = 3;
console.log(this); //{a:1,b:2}
// return {a:999}; //加上此舉 ,將回傳 {a:999}
}
js的變數作用域是函數級的,在js裡沒有類似c語言的區塊級作用域。
js程式設計環境的頂級作用域是window物件下的範圍,稱為全域作用域,全域作用域中的變數稱為全域變數。
js函數內的變數無法在函數外面訪問,但在函數內可以存取函數外的變量,函數內的變數稱為局部變數。
js函數可以嵌套,多個函數的層層嵌套構成了多個作用域的層層嵌套,這稱為js的作用域鏈。
js作用域鏈的變數存取規則是:如果目前作用域內存在要存取的變量,則使用目前作用域的變量,否則到上一層作用域內尋找,直到全域作用域,如果找不到,則該變數為未宣告。
注意,變數的宣告在程式碼解析期完成,如果目前作用域的變數的宣告和賦值語句寫在變數存取語句後面,js函數會認為目前作用域已經存在要存取的變數不再向上級作用域查找,但是,由於變數的賦值發生的程式碼運行期,訪問的到變數將是undefined.
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();
}
匿名函數的使用在js很重要,由於js中一切資料都是對象,包括函數,因此經常使用函數作為另一個函數的參數或返回值。
如果匿名函數沒有被保存,則運行後即被從記憶體中釋放。
匿名函數的呼叫方式一般是直接把匿名函數放在括號內替代函數名。如:
(function(a,b){ return a b;})(1,2); //宣告並執行匿名函數,執行階段傳入兩個參數:1和2
//或
(function(a,b){ return a b;}(1,2));
//下面這種寫法是錯的:
function(a,b){ return a b;}(1,2);
由於js中語句結束的分號可以省略,js引擎會認為function(a,b){ return a b;}是一句語句結束,因此匿名函數只聲明了沒有被調用,如果語句沒有傳參( 1,2)寫成(),也會導致錯誤,js中空括號是語法錯誤。
下面這種寫法是正確的。
var ab = function(a,b){ return a b;}(1,2); // ab=3
js 解析語法時,如果表達式出現在賦值運算或運算子運算中,是"貪婪匹配"的(盡量求值)
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
如果你只是想把一個匿名函數賦給一個變量,記得在賦值語句後面加上分號,否則,如果後面跟了小括號就變成了函數調用了,尤其是小括號與函數結尾之間分隔了多行時,這種錯誤往往很難發現。
實際開發中,匿名函數可能以運算值的方式傳回,這種情況可能不容易看出,例如
var a =1;
var obj = {a:2,f:function(){ return this.a;}};
(1,obj.f)(); //1 逗號表達式反悔了一個匿名函數,當這個匿名函數被呼叫時,函數體內的 thsi 指向 window
聲音 明並立即運行匿名函數稱為」自執行函數“,自執行函數經常用於封裝一段js程式碼。由於函數作用域的特點,自執行函數內的變數無法被外部訪問,放在函數內 的程式碼不會對外面的程式碼產生影響,可以避免造成變數污染。 js開發很容易造成變數污染,在開發中經常引入其他編碼人員開發的程式碼,如果不同的編碼人員定義了同名稱不同含義的全域變數或函數,便造成了變數污染,同一作用域內出現同名的變量或函數,後來的將覆蓋前面的。
(function(){
//自己的程式碼.....
})();匿名函數還可以讓記憶體及時釋放:因為變數被宣告在匿名函數內,如果這些變數沒有在匿名函數之外被引用,那麼這個函數運行完畢,裡面的變數所佔據的記憶體就會立即釋放。
函數的name:在firefox等瀏覽器,函數有一個name屬性,就是該函數的函數名,但是這個屬性在IE中不存在,另外,匿名函數的name為空值。
var a=function(){}
alert(a.name); //undefined,a是儲存了一個匿名函數的變數
function b(){}
alert(b .name); //b ,but undefined for IE
七、函數被呼叫時,運行在他被定義時的環境中
無論函數在哪裡被調用,被誰調用,都無法改變其被聲明時的語法環境,這決定了函數的運行環境
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
//另一個例子:
var x = 100;
var y=77;
var a1={
x:99,
xx:function(){
//var y=88; / /如果註解這個變量,y將是全域變數的77
alert(y); //沒有使用this指針,呼叫函數的物件無法影響y的值,函數執行階段將從這裡按作用域鏈逐級搜尋取值
alert(this.x); //使用了this 指針,呼叫函數的
}
}
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 的時候執行了,匿名函數被實例化的物件呼叫
this.b = (function(){return this.x})(); //new 的時候執行了,匿名函數被window調用
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());
1、呼叫函數的物件(或說函數的呼叫方式)決定了函數運行時函數體內的this指標指向誰
2、函數宣告時的語法環境決定了函數執行階段的存取權限
3、函數呼叫語句的語法環境決定了函數是否真的能夠被呼叫及何時被呼叫(只有函數在某個語法環境是可見的,這個函數才能被呼叫)
函數在運行時,產生一個 arguments 物件可以存取傳入函數內的參數,arguments 有一個屬性可以指向函數本身:arguments.callee.
函數運行時,函數的caller 屬性可以指向本函數調用語句所在函數,例如,a函數在b函數體內被調用,則當a函數運行時,a.caller就指向了b函數,如果a 函數在全域環境中被呼叫則a.caller=null
arguments 和a.caller 的值與函數的每一次呼叫直接關聯,他們都是在函數運行時產生的,只能在函數體內存取。
IE8及IE8以下瀏覽器中,a 函數的內的arguments.caller( IE9之後這個屬性被移除) 指向a.caller 執行時的arguments (arguments.caller.callee === a.caller),
七、字串即時解析中的函數呼叫:eval()、new Function()、setTimeout()、setInterval()
eval() 與window.eval()
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()
字串中解析出的程式碼運在 eval 所在的作用域,window.eval()封裝運行在頂級作用域(低版本 chrome 及以下 IE9)則同eval())。
IE中,window.execScript(); 相當於window.eval()
new Function()、setTimeout()、setInterval() 的第一個字串參數所解析得到的程式碼,都是在頂層作用域執行。
要理解函數閉包,先了解js的垃圾自動復原機制。
number、string、boolean、undefined、null 在侵犯和賦值操作中是複製傳值,而物件類型的資料按引用傳值,js 的同一個物件類型資料可能被多次引用,如果某個物件不再被引用,或者兩個物件之間相互引用之外不被第三方所引用,瀏覽器會自動釋放其佔用的記憶體空間。 函數被引用:函數被賦為其他物件的屬性值,或是函數內部定義的資料在該函數外部被使用,閉包的基於後面的一種情況。 🎜>複製程式碼
程式碼如下:
var f;函數 fun(){
var a =1;
產生閉包的關鍵是,一個在函數A內的聲明的函數B被傳出A之外,並且B函數內使用了在函數A內產生的數據(或按值傳參),
函數B傳出函數外部的方式有多種,如:
複製程式碼程式碼如下:
return {a:123,b:456, c: function(){ return a;} };
}
var a =1;
警報(f1()); //3
警報(f2()); //2
警報(f2()); //3
這兩份閉包中的變數 a 是不同的數據,每產生一份閉包, fun() 執行了一次, 變數宣告語句也執行了一次。
js oop 程式設計中閉包可以用來模擬私有成員、建構單體類別
//私人方法
function setName(name){
myname=name;
}
//私人 方法
function setVal(val){
myVal=val;
}
//執行new建構物件時呼叫內部私有方法
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
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{
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);
//一个单体类声明函数
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("メソッド 1") };
console.log("init インスタンス");
};
fun.prototype.method2 = function(){ console.log('メソッド 2') };
//単一クラス宣言関数の使用法
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();
IE 6 では、非ネイティブ JS オブジェクト (DOM など) への循環参照によりメモリ リークが発生します。クロージャを使用する場合は、非ネイティブ オブジェクト参照を使用するときに注意してください。
関数 fun(){
var node = document.getElementById('a');
node.onclick = function(){alert(node.value) };
node = null //メモリリークを防ぐために循環参照を解除します
ノードは、fun の外部に存在する DOM オブジェクトを保存します (常に存在し、削除されてもドキュメント ツリーから削除されるだけです) fun が実行された後、クロージャが生成され、これも DOM 間の循環参照を構成します。オブジェクトとコールバック関数 (ノード-関数-ノード) を使用すると、IE 6 でメモリ リークが発生します。