javascript函數及做用域的小結

本文結合基本javascript的權威書籍中的內容,根據本身的理解,經過相關示例向你們展現了函數的聲明,參數,調用,閉包等一些內容,但願你們可以喜歡,若有不足,也但願提出建議,你們共同進步。javascript

在js中使用函數注意三點:
一、函數被調用時,它是運行在他被聲明時的語法環境中的;java

二、函數本身沒法運行,它老是被對象調用的,函數運行時,函數體內的this指針指向調用該函數的對象,若是調用函數時沒有明確指定該對象, this 默認指向 window ( strict 模式除外,本文不涉及 strict 模式);node

三、函數是一種帶有可執行代碼的對象類型數據。ajax

1、聲明函數

一、使用 function 關鍵字chrome

代碼以下:編程

function myfun(a,b){ //聲明名爲myfun的函數

return a+b;json

} 數組


二、 聲明匿名函數

function(a,b){ return a+b;}匿名函數自身是沒法保存的,因爲在js中函數是一種對象型數據,所以能夠把匿名函數賦給變量來保存。瀏覽器

var myfun = function(a,b){ return a+b;}

三、使用函數構造器Function //注意首字母大寫服務器

Function 是js內置的一個函數,他是全部函數對象的構造器。(其餘數據對象也有本身的內置構造函數,好比Number,Object等,這些構造函數本身的構造器就是Function,由於他們都是函數)。

var myfun = new Function('a,b','return a+b;'); 其中最後一個參數是函數體,前面的參數都是函數的形式參數名,個數不定,由於須要用字符串傳參來構造,函數較長時這種寫法很不方便,通常不多用,也許你會 用它來構造特定的返回值從而取代 eval函數。

須要注意的是,全局變量和全局函數均可以看做window對象的屬性,若是存在同名的函數和變量,只能有一個生效(實際上只有一個屬性),試試下面的代碼。

function a(){ alert('a');}

alert(window.a);  //訪問window對象的屬性也能夠省去window不寫

var a=1;

alert(window.a);


函數和變量的聲明都發生在代碼解析期,不一樣的是,變量在解析期只聲明不賦值,所以,同一個做用域內存在同名的函數和變量時,在代碼運行期執行到變量賦值之 前,同名函數生效,同名變量賦值以後(用新的數據覆蓋了該window對象屬性原有的值),變量生效(可是要注意,在firefox 下, 在 with 僞閉包內聲明的函數,只能在聲明以後才能被調用,即,firefox 的 with 內沒有對函數預先聲明)。
代碼以下:

with({}){
 a();  //在 firefox 下 a 是未聲明
 function a(){ console.log("function a is called");}
}

若是同名稱的函數被屢次聲明,後面聲明的將覆蓋前面聲明的,如:
代碼以下:

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);

}

alert(func1);  //彈出function(){alert(3);}


除了 IE8 及IE8如下的瀏覽器,表達式中的函數聲明都會返回匿名函數,不會成功聲明具名函數
代碼以下:

if(function fun(){}){
   alert(fun); // error,不會成功聲明名稱爲 fun 的函數,但在IE8 及如下的瀏覽器中中會成功聲明一個函數 fun

}

(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

2、函數的參數

js函數不會檢查函數調用時傳入的參數個數與定義他時的形式參數個數是否一致,通常地,js函數調用時能夠接收的參數個數爲25個,固然不一樣的瀏覽器可能有差別,ECMAScript標準對這一點並無規範。

若是你不肯定函數調用時傳入了多少個參數,可使用函數的arguments對象。

arguments 有點像數組,被稱爲僞數組,它是用來保存實參的數據,arguments.length 爲傳入的參數個數,arguments[0] 是第一個參數,arguments[1]是第二個參數,類推...

函數對象的length屬性:這個屬性不多用到,甚至不多人知道,函數的length屬性就是該函數定義時的形式參數個數。

 代碼以下:

function myfun(a,b){

alert(arguments.length);  //彈出調用時實際傳入的參數個數

alert(arguments[0]); //對應參數a

return a+b;

}

alert(myfun.length);   //形參個數,2


arguments對象還有其餘屬性,好比經常使用的arguments.callee ,指向該函數自身。

要注意:若是函數內部聲明瞭與形參同名的子函數(同域內,變量未賦值時同名函數生效),arguments 的相應值也會被修改,可是,在做用域內使用 var 聲明瞭同名的 變量則不會致使 arguments 的參數值被函數替換(但firefox 依然替換)。

代碼以下:

function aa(a , b,c){ //js 羣的一道題
    function a(){}

    console.log(a); //function a
    console.log(aa);

    //若是做用域內沒有 var a ,則 arguments[0] 爲 function a (friefox(version 17) 則必定是function a)
    console.log(arguments[0]);
    var a = "ee";  //註銷此句,考擦 arguments[0] 將變爲 a 函數
    var aa = "444";
    arguments = 6;
    console.log(a);
    console.log(aa);
    console.log(arguments);
}
aa(1,2,3);

3、函數的返回值

js函數使用 return 語句返回值。

一切數據類型均可以做爲函數的返回值(包括函數),js函數也能夠沒有返回值。

4、函數調用

函數本身是不會運行的,當它運行時,老是存在一個調用它的對象。

默認狀況下,在任何語法環境中,若是沒有顯式指定函數的調用對象,就是指經過window對象來調用該函數,此時,函數體內的this指針指向window對象。

代碼以下:

function myfun(a,b){

 alert(this);

return a+b;

}

myfun(1,2); // 調用函數並傳入2個參數,這2個參數分別對應形式參數a,b調用函數時,若是傳入的參數個數超過形式參數,就只有用arguments加下標來接收了。


因爲沒有顯式指定調用函數的對象,alert(this)將彈出 window對象。這種調用方法是最多見的。
 
用於顯示指定函數的調用對象方法有三個:

1、若是一個函數被賦爲一個對象的屬性值,這個函數只能經過該對象來訪問(但並不是是說該函數只能被該對象調用),經過該對象調用這個函數的方式相似以面向對象編程語言中的方法調用(實際上在js中也習慣使用方法這種稱呼)。

代碼以下:

var obj={}; //定義一個對象

obj.fun=function(a,b){

alert(this); //彈出this指針

return a+b;

} //對象屬性值爲函數

alert(obj.fun);// 訪問fun函數。 只能經過該對象來訪問這個函數

obj.fun(1,2);  //經過obj對象來調用fun函數,將彈出obj對象。這種方式也稱爲調用obj對象的fun方法。


2、 任意指定函數的調用對象:在某個語法環境中,若是能夠同時訪問到函數fun和對象obj,只要你願意,能夠指定經過obj對象來調用fun函數。指定方法 有2種:call方法和apply方法。(由於window對象是瀏覽器環境下的頂級對象,在任何語法環境中都能訪問到window對象,所以,任何函數 均可以經過window對象來調用)
代碼以下:

function fun(a,b){

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 函數被本身調用了


另外一種比較容易疏忽的錯誤是,在A 對象的方法中,執行了使用了 B 對象的方法調用,試圖在 B 對象的方法裏使用 this 來訪問 A 對象,這在各類回調函數中比較常見,最多見的情形就是 ajax 回調函數中使用 this 。
代碼以下:

var obj = {
   data:null,
   getData:function(){
          $.post(url,{param:token},function(dataBack){ //jQuery ajax post method
                 this.data = dataBack; //試圖將服務器返回的數據賦給 obj.data ,但這裏的 this 已經指向 jQuery 的 ajax 對象了
           },'json');  
   }
}

//正確作法
var obj = {
   data:null,
   getData:function(){
          var host = this; //保存 obj 對象的引用
          $.post(url,{param:"token"},function(dataBack){
                 host.data = dataBack;
           },'json');  
   }
}

3、函數做爲對象構造器(構造函數)

當函數使用 new 運算做爲對象構造器運行時,this 指向新構造出對象,若是該構造函數的返回值不是 null 之外的對象,構造函數運行完畢將返回 this 指向的對象,不然返回原定義的對象。

代碼以下:

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} ,若是沒有參數,也能夠寫成 var obj = new Fun;


5、函數做用域

js的變量做用域是函數級的,在js裏沒有相似c語言的塊級做用域。

js編程環境的頂級做用域是window對象下的範圍,稱爲全局做用域,全局做用域中的變量稱爲全局變量。

js函數內的變量沒法在函數外面訪問,在函數內卻能夠訪問函數外的變量,函數內的變量稱爲局部變量。

js函數能夠嵌套,多個函數的層層嵌套構成了多個做用域的層層嵌套,這稱爲js的做用域鏈。

js做用域鏈的變量訪問規則是:若是當前做用域內存在要訪問的變量,則使用當前做用域的變量,不然到上一層做用域內尋找,直到全局做用域,若是找不到,則該變量爲未聲明。

注意,變量的聲明在代碼解析期完成,若是當前做用域的變量的聲明和賦值語句寫在變量訪問語句後面,js函數會認爲當前做用域已經存在要訪問的變量再也不向上級做用域查找,可是,因爲變量的賦值發生的代碼運行期,訪問的到變量將是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();

6、匿名函數的調用

匿名函數的使用在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

7、函數被調用時,運行在他被定義時的環境中

不管函數在哪裏被調用,被誰調用,都沒法改變其被聲明時的語法環境,這決定了函數的運行環境。

代碼以下:

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

 

 //另外一個例子:

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());


注意區分調用函數的對象、函數聲明時的語法環境、函數調用語句的語法環境這幾個概念

一、調用函數的對象(或者說函數的調用方式)決定了函數運行時函數體內的this指針指向誰。

二、函數聲明時的語法環境決定了函數運行時的訪問權限。

三、函數調用語句的語法環境決定了函數是否真的可以被調用及什麼時候被調用(只有函數在某個語法環境是可見的,這個函數才能被調用)。

函數在運行時,產生一個 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)。

8、字符串實時解析中的函數調用: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() 中的代碼執行於eval() 語句所處的做用域內:
代碼以下:

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()


字符串中解析出的代碼運在 eval 所在的做用域,window.eval() 則是運行在頂級做用域(低版本 chrome 和 低於IE9 則同 eval()).

IE 中 ,window.execScript();  至關於 window.eval()

new Function()、setTimeout()、setInterval()  的第一個字符串參數所解析獲得的代碼,都是在頂級做用域執行。

 

最後說一下IE6的內存泄漏

在IE 6 中,非原生js對象(DOM 等)的循環引用會致使內存泄露,使用閉包時若是涉及非 js 原生對象引用時要注意。

function fun(){

var node = document.getElementById('a');
node.onclick = function(){ alert(node.value); };

node = null; //打斷循環引用防止內存泄露

node 保存的是 DOM 對象,DOM對象存在於 fun 以外(而且一直存在,即便刪除也只是從文檔樹移出),fun 執行後產生閉包,也構成DOM對象與回調函數的循環引用(node-function-node),在IE 6 下發生內存泄露。

相關文章
相關標籤/搜索