老生常談JavaScript設計模式之call,apply,this詳解
前言:
最近在看曾探老師的《JavaScript設計模式》,說實話,可能是我基礎還不夠扎實,書里的很多內容對我來說不是“一看就懂”,而是“看了三遍才懂一點點”。特別是像代碼邏輯、概念解釋這些地方,常常是一次看不懂,就來兩次;兩次還迷糊,就再來一次。但我慢慢也想通了,學習本來就是一個不斷重復的過程。每一次重讀,都能從字里行間中發(fā)現新的理解,就像挖寶藏一樣,每次翻一翻都有新收獲。為了讓自己記得更牢,也為了讓以后復習的時候不那么枯燥,我在反復閱讀第二章“this、call、apply”之后,試著用自己的話,把知識點編成了一個小故事的形式,講給自己聽。
我想,這不僅是一種學習方式,也是我對這本書的一種致敬。
this找家
想象一下,this就像是一個四處旅行的人,他總在尋找它的“家”,但是問題來了,這個人(this)的家并不固定,取決于他是怎么被邀請這個地方的,在javaScript中,this就是一個旅行者,他的指向完全依賴于函數的調用方式。
如果是對象的方法調用:比如obj.myMethod(),那么this的家就安在obj里面。
如果是作為構造函數調用:比如new MyClass(),這時候this就有了自己的新家,一個新的對象實例
如果是直接調用函數,比如myFunction(),那么嚴格模式下,this就會變得無家可歸,即undefined,而在非嚴格模式下,則默認回到了全局對象瀏覽器的window的懷抱。
call 和 apply 導游
現在,比如我們的this旅行者到了一個陌生的城市,不知道如何找到正確的家,這時候就可以請call和apply兩位熱心的導游出現了。call這個導游很細心,他會親自帶著this去正確的地方,并且還允許你指定額外的參數列表,就像myFunction.call(context,arg1,arg2)。在這里,context就是我們希望this去的家,而后面參數就是這次旅行所需要的裝備。apply與call不同的是,apply更喜歡一次性把所有的東西都準備好再出發(fā),所以除了第一個參數用來指定this應該去的"家"外,它接收第二個參數為一個數組或類數組對象,里面包含了所需要傳遞給函數的參數,如myFunction.apply(context,[arg1,arg2]),總之,當你想要控制this的指向,或者需要特定的方式傳遞參數的時候,call和apply就是你的得力導游,他們不僅能幫this找到回家的路,還能確保旅途愉快順利。
this的指向
第一站、作為對象的方法被調用(this回到家)
有一天,this被一個叫做obj的大叔收留了,成為了大叔家的傭人。
var obj = {
a: 1,
getA: function() {
console.log(this === obj); // true
console.log(this.a); // 1
}
};
obj.getA();著obj家里,this每天多的都很開心,this開心的喊:“我終于找到加啦!我是obj家的孩子”,因為他是在obj家里被調用的,所以他就是的孩子。
第二站、普通函數調用(this離家出走)
但是有一天,this不小心跑出了obj的家,在外面遇到一個叫getName的陌生人:
var getName = obj.getName; getName(); // 輸出 undefined 或 globalName
??這時候的this已經不是obj的孩子了,他又變成了流浪漢,回到了全局大哥window家,不過別擔心,如果this在全局如果window家過的不開心,可以開啟"use strict"【嚴格模式】模式,this更加自尊自愛,寧愿當個“無家可歸的孤獨者”,也不愿意隨便認爹! 很有骨氣吧,用孤獨換的骨氣 哈哈哈。
第三站、構造器調用(this自己當爹)
后來啊,有骨氣的this長大了,學會了獨立,決定自己當爸爸!
function Person(name) {
this.name = name;
}
var tom = new Person('Tom');
console.log(tom.name); // Tom結果this發(fā)現,自己辛辛苦苦當的爹,娃娃居然被別人搶走了。
第四站、Function.prototype.call 或 Function.prototype.apply 調用
這時候,兩位神秘人物登場了,他們就是大名鼎鼎JavaScript劇場的導演call和apply!,他們告訴this:“this你很堅強,我們被你打動了,現在開始,你想演誰都可以,我?guī)湍銚Q身份。”,于是:
var obj1 = { name: '鋼鐵俠' };
var obj2 = { name: '蜘蛛俠' };
function sayHi() {
console.log("我是" + this.name);
}
sayHi.call(obj1); // 我是鋼鐵俠
sayHi.apply(obj2); // 我是蜘蛛俠this得到兩位大哥的幫助,開始在不同的角色之間切換,一會是鋼鐵俠,一會是蜘蛛俠,忙的不亦樂乎,體驗那種既可以當英雄,又可以有家的感覺。
有一天,this在div家做客,突然被一個叫做callback的函數騙走了身份信息:
<div id="div1">我是一個 div</div>
<script>
document.getElementById('div1').onclick = function () {
alert(this.id); // div1 ?
var callback = function () {
alert(this.id); // window ?
}
callback();
};
</script>這個時候,this又跑回了window家,而它本應該屬于哪個div,怎么辦呢,this想了個辦法:
var that = this;
var callback = function () {
alert(that.id); // div1 ?
}就像this孩子給自己辦一張身份證,再也不怕他迷路啦。
修復 document.getElementById 的 this
有一次,this被借去演習,結果卻跑到了document.getElementById里面,導致整個劇組都亂了套。
<html> <body> <div id="div1">我是一個 div</div> </body> <script> var getId = document.getElementById; getId( 'div1' ); // 報錯,this不在指向document </script> </html>
getId( ‘div1’ );就會報錯,this不在指向document,原來this應該是docuement家的人,結果卻被當成window使用了,apply導演不舍得this,就決定動用自己的能力,立刻修復:
document.getElementById = (function( func ){
return function(){
return func.apply( document, arguments );
}
})( document.getElementById );
var getId = document.getElementById;
var div = getId( 'div1' );
alert (div.id); // 輸出: div1終于,this回到了正確的家庭,拯救了整個劇組,經過這次慘痛的教訓,導演對這次事故進行分析總結:

導演發(fā)現,在Chrome執(zhí)行過后發(fā)現,var getId = document.getElementById拋出了一個異常,因為許多的引擎document.getElementById方法的內部實現張需要用到this。這個this本來被期望指向document,當getElementById方法作為document對象的屬性被調用時,方法內部的this確實是指向document的,但是當getId來引用document.getElementById之后,再調用getId,此時就成了普通函數調用,函數內部的this指向了Window,而不是原來的document。
導演發(fā)現可以使用apply,把document當作this傳入getId函數,幫助“修正”this:
<script>
document.getElementById = (function (func) {
return function () {
return func.apply(document, arguments);
};
})(document.getElementById);
var getId = document.getElementById;
var div = getId("div1");
alert(div.id); // 輸出: div1
</script>
終于通過導演的不懈努力與分析讓this回到了正確的家庭總結了整個劇組,甚至還總結了口訣:
this是誰調用了我,我就跟誰混。call/apply是this的導演,想讓this演誰都可以。
發(fā)生了上面的事情之后,this學會了如何在javaScript世界中找到自己的歸屬,它不再輕易迷路,也不再害怕被借來借去,從此要記住:
多問問this:“whu ar you ?" 你是誰?多請call和apply來幫忙。別忘了用bind或that=this給this上個保險!
call和Apply
Apply序
apply是一個豪放派的導演,他習慣把所有演員打包成一個團隊(數組或類數組),然后一次性推到主演面前。
func.apply(thisArg, [arg1, arg2, ...]);
盡管風格不同,但是目標是一致的,幫助函數找到正確的舞臺背景(改變this的指向),并讓每個演員都能發(fā)揮最大的作用(正確的傳遞)。
Apply正文:
apply接收兩個參數,第一個參數指定了函數體內this的指向,第二個參數為一個帶下標的集合,這個集合可以為數組,也可以為類數組,apply方法把這個結合中的元素作為參數傳遞給被調用的函數:
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] ); 在這段代碼中,參數 1、2、3 被放在數組中一起傳入 func 函數,它們分別對應 func 參數列表中的 a、b、c。
call序
call是一位細心講究的導演,他喜歡把演員(參數)一個個親自介紹給主演(函數),確保每個演員都有明確的角色。
func.call(thisArg, arg1, arg2, ...);
call正文:
call傳入的參數數量不固定,跟apply相同的是,第一個參數也是代表函數體內的this指向,從第二個參數開始往后,每個參數被依次傳入函數:
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ]
};
func.call( null, 1, 2, 3 );當調用一個函數的時候,JavaScript的解釋器并不會計較形參和實參的數量、類型、以及順序上的區(qū)別,JavaScript的參數在內部就用一個數組來表示的,從這個意義上說,apply比call使用率更高,不必關心具體多少個參數被傳入函數,只要作用apply一股腦地推過去即可。
注意:
當使用call或者apply的時候,如果我們傳入的第一個參數為null,函數體內的this會默認值想宿主對象,在瀏覽器中則是windows。
Function.prototype.bind
Function.prototype.bind是一個秘密裝備,能讓你隨心所欲的控制函數內部的this指向,即使在遙遠的地方調用這個函數時也能保持“它”的初心,首先我們要打造一個屬于自己的 Function.prototype.bind我們先把 func 函數的引用保存起來,然后返回一個新的函數。當我們在將來執(zhí)行 func 函數時,實際上先執(zhí)行的是這個剛剛返回的新函數。在新函數內部,self.apply( context, arguments )這句代碼才是執(zhí)行原來的 func 函數,并且指定 context對象為 func 函數體內的 this。
Function.prototype.bind = function(context) {
var self = this; // 保存原函數,就像把寶劍藏在腰間
return function() { // 返回一個新的函數,這就是我們的新武器
return self.apply(context, arguments); // 當使用這把寶劍時,它會自動指向正確的方向
}
};
var obj = { name: 'sven' }; // 我們的英雄 sven
var func = function() {
alert(this.name); // 輸出:sven
}.bind(obj);
func(); // 英雄登場!上面代碼中,bind就像給函數穿上了一層魔法鎧甲,無論什么時候,只要調用它,它就會帶著obj的這個身份出現。但是,真正的超級英雄不能只有一技之長!我們需要讓 bind更加強大,讓它不僅能綁定 this,還能預先填入一些參數。這就像是給我們的寶劍裝上了“魔法石”,讓它變得更強大:
Function.prototype.bind = function() {
var self = this, // 保存原函數
context = [].shift.call(arguments), // 需要綁定的 this 上下文,也就是英雄的身份
args = [].slice.call(arguments); // 把剩下的參數轉換成數組,作為魔法石
return function() { // 返回一個新的函數,這是我們的終極武器
return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
// 組合兩次傳入的參數,作為新函數的參數
}
};
var obj = { name: 'sven' }; // 英雄 sven 再次登場
var func = function(a, b, c, d) {
alert(this.name); // 輸出:sven
alert([a, b, c, d]); // 輸出:[1, 2, 3, 4]
}.bind(obj, 1, 2);
func(3, 4); // 召喚英雄,帶上所有的魔法石!改變 this 指向:為函數找到真正的家
在一個戲劇性的場景中,有一名為getName的函數,它渴望知道自己到底屬于哪個家族:
var obj1 = { name: 'sven' };
var obj2 = { name: 'anne' };
window.name = 'global';
function getName() {
alert(this.name);
}
getName(); // 輸出: global
// 現在,Call 導演登場,幫助 getName 找到了它的真正歸屬——obj1 家族
getName.call(obj1); // 輸出: sven通過call和apply大導演的幫助下,getName終于找到了它真正的家!
借用其他對象的方法:杜鵑鳥的智慧
JavaScript中也有一種"借殼生蛋"的藝術,就像杜鵑鳥將自己的蛋托付給其他鳥類孵化一樣,這里call和apply也能幫我們借用其他對象的方法來完成一些任務。
例1:
var A = function( name ){
this.name = name;
};
var B = function(){
A.apply( this, arguments );
};
B.prototype.getName = function(){
return this.name;
};
var b = new B( 'sven' );
console.log( b.getName() ); // 輸出: 'sven'上面例子中,B借用了A的構造函數邏輯來初始化自己的實例屬性,可以說是繼承了A的部分功能,準確來說是構造函數繼承或借用構造函數。
函數的參數列表 arguments 是一個類數組對象,雖然它也有“下標”,但它并非真正的數組,所以也不能像數組一樣,進行排序操作或者往集合里添加一個新的元素。
我們常常會借用 Array.prototype 對象上的方法。比如想往 arguments 中添加一個新的元素,通常會借用Array.prototype.push:
(function() {
Array.prototype.push.call(arguments, 3);
console.log(arguments); // 輸出: [1, 2, 3]
})(1, 2);Call 導演巧妙地讓 Array.prototype.push 方法在非數組的對象上施展魔法,成功地添加了一個新成員。
無論是改變this的指向,還是靈活地傳遞參數,甚至是借用其他對象的方法,Call 和 Apply 總能以其獨特的技巧讓每一個函數都成為舞臺上的明星。
到此這篇關于老生常談JavaScript設計模式之call,apply,this的文章就介紹到這了,更多相關js call,apply,this內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- JavaScript改變this指向的四種方法(bind、call、apply 和箭頭函數)
- JavaScript中的this/call/apply/bind的使用及區(qū)別
- JS中this的指向以及call、apply的作用
- 詳解JS中的this、apply、call、bind(經典面試題)
- 淺談JavaScript中的apply/call/bind和this的使用
- JS中改變this指向的方法(call和apply、bind)
- JavaScript中的this,call,apply使用及區(qū)別詳解
- Javascript技術難點之apply,call與this之間的銜接
- 簡單對比分析JavaScript中的apply,call與this的使用
- JavaScript call apply使用 JavaScript對象的方法綁定到DOM事件后this指向問題
相關文章
Event altKey,ctrlKey,shiftKey屬性解析
本篇文章主要是對Event altKey,ctrlKey,shiftKey屬性解析了詳細的分析介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12

