主要內容數組
瞭解函數的調用過程有助於深刻學習與分析JavaScript代碼. 本文是JavaScript高級這 個系列中的第三篇文章,主要介紹JavaScript中函數的四種使用形式.
在JavaScript中,函數是一等公民,函數在JavaScript中是一個數據類型,而非像C#或 其餘描述性語言那樣僅僅做爲一個模塊來使用. 函數有四種調用模式,分別是:函數調用形
式、方法調用形式、構造器形式、以及apply形式. 這裏全部的調用模式中,最主要的區別在
於關鍵字 this 的意義. 下面分別介紹這個幾種調用形式.瀏覽器
函數調用形式是最多見的形式,也是最好理解的形式. 所謂函數形式就是通常聲明函數 後直接調用便是. 例如:app
1 // 聲明一個函數,並調用 2 function func() { 3 alert("Hello World"); 4 } 5 func();
或者函數
1 // 使用函數的Lambda表達式定義函數,而後調用 2 var func = function() { 3 alert("你好,傳智播客"); 4 }; 5 func();
這兩段代碼都會在瀏覽器中彈出一個對話框,顯示字符串中的文字. 這個就是函數調用.學習
能夠發現函數調用很簡單,就是平時學習的同樣. 這裏的關鍵是,在函數調用模式中, 函數裏的 this 關鍵字指全局對象,若是在瀏覽器中就是 window 對象. 例如:動畫
1 var func = function() { 2 alert(this); 3 }; 4 func();
此時,會彈出對話框,打印出 [object Window]this
函數調用模式很簡單,是最基本的調用方式. 可是一樣的是函數,將其賦值給一個對 象的成員之後,就不同了. 將函數賦值給對象的成員後,那麼這個就不在稱爲函數,而
應該叫作方法. 例如:spa
1 // 定義一個函數 2 var func = function() { 3 alert("我是一個函數麼?"); 4 }; 5 // 將其賦值給一個對象 6 var o = {}; 7 o.fn = func; // 注意這裏不要加圓括號 8 // 調用 9 o.fn();
此時,o.fn 則是方法,不是函數了. 實際上 fn 的方法體與 func 是如出一轍的,可是這
裏有個微妙的不一樣. 看下面的代碼:code
1 // 接上面的代碼 2 alert(o.fn === func);
打印結果是 true ,這個代表兩個函數是同樣的東西. 可是修改一下函數的代碼:對象
1 // 修改函數體 2 var func = function() { 3 alert(this); 4 }; 5 var o = {}; 6 o.fn = func; 7 // 比較 8 alert(o.fn === func); 9 // 調用 10 func(); 11 o.fn();
這裏的運行結果是,兩個函數是相同的,所以打印結果是 true. 可是因爲兩個函數的調用
是不同的,func 的調用,打印的是 [object Window],而 o.fn 的打印結果是 [object Object].
這裏即是函數調用與方法調用的區別. 函數調用中,this 專指全局對象 window,而 在方法中 this 專指當前對象. 即 o.fn 中的 this 指的就是對象 o.
一樣是函數,在單純的函數模式下,this 表示 window;在對象方法模式下,this 指 的是當前對象. 除了這兩種狀況,JavaScript 中函數還能夠是構造器. 將函數做爲構造器
來使用的語法就是在函數調用前面加上一個 new 關鍵字. 如代碼:
1 // 定義一個構造函數 2 var Person = function() { 3 this.name = "傳智播客"; 4 this.sayHello = function() { 5 alert("你好,這裏是" + this.name); 6 }; 7 }; 8 // 調用構造器,建立對象 9 var p = new Person(); 10 // 使用對象 11 p.sayHello();
上面的案例首先建立一個構造函數Person,而後使用構造函數建立對象p. 這裏使用 new 語法. 而後在使用對象調用sayHello()方法. 這個使用構造函數建立對象的案例比較簡單.
從案例能夠看到,此時 this 指的是對象自己.
除了上面簡單的使用之外,函數做爲構造器還有幾個變化. 分別爲:
一、 全部須要由對象使用的屬性,必須使用 this 引導;
二、 函數的 return 語句意義被改寫,若是返回非對象,就返回this;
咱們須要分析建立對象的過程,方能知道 this 的意義. 以下面代碼:
1 var Person = function() { 2 this.name = "傳智播客"; 3 }; 4 var p = new Person();
這裏首先定義了函數 Person,下面分析一下整個執行:
一、 程序在執行到這一句的時候,不會執行函數體,所以 JavaScript 的解釋器並不知道這個
函數的內容.
二、 接下來執行 new 關鍵字,建立對象,解釋器開闢內存,獲得對象的引用,將新對象的引
用交給函數.
三、 緊接着執行函數,將傳過來的對象引用交給 this. 也就是說,在構造方法中,this 就是
剛剛被 new 建立出來的對象.
四、 而後爲 this 添加成員,也就是爲對象添加成員.
五、 最後函數結束,返回 this,將 this 交給左邊的變量.
分析過構造函數的執行之後,能夠獲得,構造函數中的 this 就是當前對象.
在構造函數中 return 的意義發生了變化,首先若是在構造函數中,若是返回的是一個對 象,那麼就保留原意. 若是返回的是非對象,好比數字、布爾和字符串,那麼就返回 this,如
果沒有 return 語句,那麼也返回 this. 看下面代碼:
1 // 返回一個對象的 return 2 var ctr = function() { 3 this.name = "趙曉虎"; 4 return { 5 name:"牛亮亮" 6 }; 7 }; 8 // 建立對象 9 var p = new ctr(); 10 // 訪問name屬性 11 alert(p.name);
執行代碼,這裏打印的結果是"牛亮亮". 由於構造方法中返回的是一個對象,那麼保留 return
的意義,返回內容爲 return 後面的對象. 再看下面代碼:
1 // 定義返回非對象數據的構造器 2 var ctr = function() { 3 this.name = "趙曉虎"; 4 return "牛亮亮"; 5 }; 6 // 建立對象 7 var p = new ctr(); 8 // 使用 9 alert(p); 10 alert(p.name);
代碼運行結果是,先彈窗打印[object Object],而後打印"趙曉虎". 由於這裏 return 的是一個
字符串,屬於基本類型,那麼這裏的 return 語句無效,返回的是 this 對象. 所以第一個打印的 是[object Object]而第二個不會打印 undefined.
除了上述三種調用模式之外,函數做爲對象還有 apply 方法與 call 方法可使用,這即是 第四種調用模式,我稱其爲 apply 模式.
首先介紹 apply 模式,首先這裏 apply 模式既能夠像函數同樣使用,也能夠像方法同樣使用 能夠說是一個靈活的使用方法. 首先看看語法:
函數名.apply(對象, 參數數組);
這裏看語法比較晦澀,仍是使用案例來講明:
一、 新建兩個 js 文件,分別爲"js1.js"與"js2.js";
二、 添加代碼
1 // js1.js 文件中 2 var func1 = function() { 3 this.name = "傳智播客"; 4 }; 5 func1.apply(null); 6 alert(name); 7 8 // js2.js 文件 9 var func2 = function() { 10 this.name = "傳值播客"; 11 }; 12 var o = {}; 13 func2.apply(o); 14 alert(o.name);
三、 分別運行着兩段代碼,能夠發現第一個文件中的 name 屬性已經加載到全局對象 window 中;
而第二個文件中的 name 屬性是在傳入的對象 o 中. 即第一個至關於函數調用,第二個至關
於方法調用.
這裏的參數是方法自己所帶的參數,可是須要用數組的形式存儲在. 好比代碼:
1 // 一個數組的例子 2 var arr1 = [1,2,3,[4,5],[6,7,8]]; 3 // 將其展開 4 var arr2 = arr1.conact.apply([], arr1);
而後介紹一下 call 模式. call 模式與 apply 模式最大的不一樣在於 call 中的參數不用數組. 看下面代碼就清楚了:
1 // 定義方法 2 var func = function(name, age, sex) { 3 this.name = name; 4 this.age = age; 5 this.sex = sex; 6 }; 7 // 建立對象 8 var o = {}; 9 // 給對象添加成員 10 // apply 模式 11 var p1 = func.apply(o, ["趙曉虎", 19, "男"]); 12 // call 模式 13 var p2 = func.call(o, "趙曉虎", 19, "男");
上面的代碼,apply 模式與 call 模式的結果是同樣的.
實際上,使用 apply 模式和 call 模式,能夠任意的操做控制 this 的意義,在函數 js 的設 計模式中使用普遍. 簡單小結一下,js 中的函數調用有四種模式,分別是:函數式、方法式、構造
器式和 apply 式. 而這些模式中,this 的含義分別爲:在函數中 this 是全局對象 window,在方
法中 this 指當前對象,在構造函數中 this 是被建立的對象,在 apply 模式中 this 能夠隨意的
指定. 在 apply 模式中若是使用 null,就是函數模式,若是使用對象,就是方法模式.
下面經過一個案例結束本篇吧. 案例說明:有一個div,id爲dv,鼠標移到上面去高度增大2倍,鼠標離開恢復. 下面直接上js代碼
1 var dv = document.getElementById("dv"); 2 var height = parseInt(dv.style.height || dv.offsetHeight); 3 var intervalId; 4 dv.onmouseover = function() { 5 // 中止已經在執行的動畫 6 clearInterval(intervalId); 7 // 獲得目標高度 8 var toHeight = height * 2; 9 // 得到當前對象 10 var that = this; 11 // 開器計時器,緩慢變化 12 intervalId = setInterval(function() { 13 // 獲得如今的高度 14 var height = parseInt(dv.style.height || dv.offsetHeight); 15 // 記錄每次須要變化的步長 16 var h = Math.ceil(Math.abs(height - toHeight) / 10); 17 // 判斷變化,若是步長爲0就中止計時器 18 if( h > 0 ) { 19 // 這裏爲何要用that呢?思考一下吧 20 that.style.height = (height + h) + "px"; 21 } else { 22 clearInterval(intervalId); 23 } 24 }, 20); 25 }; 26 dv.onmouseout = function() { 27 // 原理和以前同樣 28 clearInterval(intervalId); 29 var toHeight = height; 30 var that = this; 31 intervalId = setInterval(function() { 32 var height = parseInt(dv.style.height || dv.offsetHeight); 33 var h = Math.ceil(Math.abs(height - toHeight) / 10); 34 if( h > 0 ) { 35 that.style.height = (height - h) + "px"; 36 } else { 37 clearInterval(intervalId); 38 } 39 }, 20); 40 };