【javascript基礎】2、函數


前言

我在上一篇【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。

小結

把函數這部分的基礎和大家說了一下,自己講代碼敲了一遍實驗了一下,有些東西看着容易懂,寫起來還是挺困難的,希望大家也要多寫寫吧,我一直以為作文就不好,這是難為大家了。要放假了,有點想家了,想吃家里的酸菜了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM