前言
我在上一篇【javascript基礎】基本概念中介紹了javascript的一些基本概念,多謝大家的閱讀和意見,自己寫的東西可以被大家閱讀,真心高興,剛開始發布的時候我一直盯着閱讀人數,雖然知道大家可能就是點開一下而已,但是還是給我一些繼續寫下去的信心。那今天寫一些關於javascript函數的一些知識,幫助大家熟悉或者復習一些函數的基本知識。
PS:最近jQuery源碼交流群( 239147101)加了不少熱新人,希望大家還是以學習為主,盡量少灌水,給大家一個好的提升自己的環境。
函數
函數在任何一種編程語言中都是一個很重要的結構或者組成部分,編程中的復雜結構和功能都會有函數的參與。javascript中的函數是一個對象,函數對象時Function類型的實例,由於Function類型是一個引用類型,那么函數可以擁有自己的方法和屬性,同時也因為函數是一個對象,那么函數名是一個指向函數對象的指針,可以被賦值。下面詳細介紹函數的各個部分。
創建函數
函數的創建有三種方式,分別為使用Function的構造函數、函數聲明、函數表達式,下面分別介紹這三種方法。
Function構造函數
這種方式是直接new出來一個Function 實例,通過使用Function的構造函數進行創建函數。Function構造函數可以接收任意多個參數,但是最后一個參數會被認為是函數體,前面的所以參數被當做被創建出來的函數的參數。
var test = new Function("a","b","return a + b");//參數a和b,函數體return a + b console.log(test(1,2));//3
我們可以看出比較的麻煩,並且《javascript高級程序設計》也不推薦我們使用這種方式,主要是因為瀏覽器要解析常規的javascript代碼之外,還要解析傳入的參數字符串,這個類似eval()的解釋,影響性能。
函數表達式
這種方式是創建的常見方式之一,具體請看
var test = function(a,b){ return a + b; }
console.log(test(1,2));
上面的代碼就是創建一個函數,使用test()進行調用。其實,上面的代碼是先創建了一個匿名的函數,之后把這個匿名的函數賦值給test變量。每個函數有一個name屬性,這個屬性不是ECMA標准的一部分,但是許多地方可以使用它。我們可以給上面的函數起一個名字,具體下面代碼
//函數的名字newName var test = function newName(a,b){ return a + b; } console.log(test.name);//newName //匿名函數 var nTest = function (a,b){ return a + b; } console.log(nTest.name);//""
這個屬性在后面詳細解釋吧。
函數聲明
這種方式和C語言中的很類似,這種是最常見的一種創建函數的方法。是通過關鍵字function直接聲明,請看
function test(a,b){ return a + b; } console.log(test(1,2));//3 console.log(test.name);//test
區別
以上介紹了三個創建函數的方式,現在介紹三種的區別,確切的說是后兩種的區別,因為Function不推薦使用,性能是一大原因。區別就是使用函數聲明這種方式會使函數的聲明提前,類似前面我們提到的變量申明的提前。也就是說,使用函數申明方式,我們可以將函數的聲明放在調用函數代碼的后面,因為解析器會在代碼執行之前將函數的聲明提升,提到源代碼樹的頂部,而函數表達式方式則會報錯,具體請看
//調用函數 console.log(test(1,2));//3 //創建函數(函數申明方式) function test(a,b){ return a + b; } //上面的函數相等於 //創建函數(函數申明方式) //function test(a,b){ // return a + b; //} //console.log(test(1,2));//3 //調用函數 console.log(ntest(1,2));//TypeError: undefined is not a function //創建函數(函數表達式方式) var ntest = function (a,b){ return a + b; }
not重載
javascript語言不像java那些語言有函數重載這一概念,其實函數名就是一個指針,指向一個Function實例的地址,當然只能指向一個函數,當然沒有重載的概念了,只有覆蓋,后面定義的函數覆蓋前面定義的函數,具體請看
function test(a,b){ return a + b; } //下面的函數覆蓋上面的 function test(a,b){ return a + b + 100; } console.log(test(0,0));//100
也就是說如果一個同名的函數表達式和函數申明的函數在一起,無論位置是怎么樣的,最后的函數就會是用函數表達式創建的函數,因為函數申明會提升到頂部嘛,看看下面的代碼
var test = function (a,b){ return a + b -100; } function test(a,b){//會被下面的函數覆蓋 return a + b; } function test(a,b){//會被函數表達式覆蓋 return a + b + 100; } console.log(test(0,0));//-100
內部屬性
函數的內部有兩個重要的對象:arguments和this。
arguments
arguments是一個類似組對象,包含所以傳入函數的所有參數, 寫程序或者面試中常問的就是如何將arguments轉化完成一個數組,請看
Array.prototype.slice.call(arguments); Array.prototype.slice.call(arguments,0); Array.prototype.slice.call(arguments,0,arguments.length); Array.apply(this, arguments); //沒用過 Array.apply(null, arguments); //沒用過
arguments有一個length屬性,表示函數傳入參數的個數,還有一個callee屬性,這是一個指針,指向擁有這個arguments的函數,這個主要是在函數內部調用自己時使用,也就是遞歸時使用。看個例子就明白了
function test(count){ console.log("參數:"+arguments[0]+"個數:"+arguments.length); if(count <= 0){ console.log("遞歸"+count+" 結束了"); }else{ console.log("遞歸"+count); arguments.callee(--count);//調用自己 } } test(3); /* 參數:3個數:1 遞歸3 參數:2個數:1 遞歸2 參數:1個數:1 遞歸1 參數:0個數:1 遞歸0 結束了 */
this
javascript中的this和java中的this差不多,this引用的是函數的執行環境,就是this在不同的執行環境中引用的是不同的對象,執行環境這里還沒有說到,以后會詳細介紹,這里的this也是簡單的介紹一下,我以后會整理一些面試題,幫助大家理解。看例子吧,
//全局變量 var color = "red";//相當於window.color = "red" //定義一個對象 var obj = {color : "blue"}; function pop(color){ alert(this.color); } pop();//相當於window.pop();輸入"red" //obj對象增加一個方法,將pop賦值給它 obj.pop = pop; obj.pop(); //輸出"blue"
解釋一下,this這個對象是在函數執行時才綁定的,可以說是一個動態的。pop函數是定義在window下的一個函數,也就是全局作用域的一個函數,當直接執行pop函數時,就是在全局作用域下調用pop時。this引用的是window,this.color就是window.color,輸出red。當我們把pop這個函數賦值給obj這個對象並且調用pop的時候,this引用的就是obj這個對象,this.color 就是obj.color,輸出blue。
函數屬性和方法
這里說一下函數的屬性和方法,包括length,name,prototype,apply,call這幾個。
length
這個屬性比較簡單,就是表示定義函數時定義的參數的個數,要和arguments.length區分開,arguments.length表示實際輸入的參數個數,看例子
function test(a,b){ console.log("輸入的參數個數:"+arguments.length); console.log("定義的參數個數:"+test.length); } test();//0,2 test(1);//1,2 test(1,2)//2,2 test(1,2,3)//3,2 //函數的內部我們可以通過arguments[i],取得輸入的參數,假如定義一個參數,輸入兩個參數,那怎么取得第二個參數呢 function testA(c){ console.log("輸入的參數個數:"+arguments.length); console.log("定義的參數個數:"+test.length); console.log("第二個參數:"+arguments[1]); } testA(1,100);2,2,100 //這里可以遍歷取得所有的參數,不講了
name
這個屬性在前面提到了一點,這個就是函數的名字,我們在創建函數的時候說了這個屬性,這個屬性不是標准屬性,但是很多地方就使用這個屬性,主要也是在遞歸調用上使用。name屬性是只讀屬性,不能修改它的值。直接看例子
//修改name屬性 function test(){ console.log(test.name); //修改name屬性 test.name = "newName"; console.log(test.name); } test();//test,test //函數內部使用name屬性,遞歸調用 function testD(count){ console.log("參數:"+arguments[0]+"個數:"+arguments.length); if(count <= 0){ console.log("遞歸"+count+" 結束了"); }else{ console.log("遞歸"+count); testD(--count);//調用自己 } } testD(3); /* 參數:3個數:1 遞歸3 參數:2個數:1 遞歸2 參數:1個數:1 遞歸1 參數:0個數:1 遞歸0 結束了 */
name屬性的使用和arguments.callee()的效果是一樣的,只不過arguments.callee()更方便些,當函數名字更改時程序不用更改。
prototype
函數的prototype屬性是一個很重要的屬性,特別是在自定義引用類型和實現繼承時。我們現在這簡單的介紹一下它,因為這個屬性足以單獨寫一篇文章。我們可以認為prototype是一個模板,在new 一個對象時候會參照這個模板,將模板里的屬性和方法復制給對象,當然你不定義這個模板,這個模板不存在方法和屬性。簡單例子
function People(name){ this.name = name; } //prototype中的屬性 People.prototype.home = "jilin"; var hainan = new People("hainan"); console.log(hainan.home);//jilin
先簡單介紹到這,后面單獨詳細說。
call和apply
這兩個方法作用是一樣的,就是改變this作用域的值,在特定的作用域中調用自己,也就是設置this的指向,不同點在於參數接收方式不同。apply方法需要兩個參數,第一個是指定的作用域,就是要把this指向的對象,第二個是參數數組,函數調用需要的參數,這個參數數組也可以是arguments這個偽數組。call的第一個參數也是給this綁定的值,其他的參數個數不定,其他的參數就是函數調用需要的參數,和apply不同,你要一個一個的都列舉出來。看例子
function sum(a,b){ return a + b; } //arguments參數 function callSum1(a,b){ return sum.apply(this,arguments); } //數組參數 function callSum2(a,b){ return sum.apply(this,[a,b]); } //列舉所有參數 function callSum3(a,b){ return sum.call(this,a,b); } console.log(callSum1(1,2)); console.log(callSum2(1,2)); console.log(callSum3(1,2));
上面是傳遞參數的例子,再看看改變this指向的例子
//全局變量 var color = "red";//相當於window.color = "red" //定義一個對象 var obj = {color : "blue"}; function pop(color){ alert(this.color); } pop();//相當於window.pop();輸入"red" pop.call(this);//red pop.call(obj);//blue
解釋一下,pop.call(this)這句代碼改變了this的指向,因為是在全局中調用的函數,this指向window,輸出window.color。pop.call(obj)這句代碼將this指向了obj,所以輸出obj.color。
小結
把函數這部分的基礎和大家說了一下,自己講代碼敲了一遍實驗了一下,有些東西看着容易懂,寫起來還是挺困難的,希望大家也要多寫寫吧,我一直以為作文就不好,這是難為大家了。要放假了,有點想家了,想吃家里的酸菜了。
