【優雅代碼】深入淺出 妙用Javascript中apply、call、bind

這篇文章實在是很難下筆,因為網上相關文章不勝枚舉。

巧合的是前些天看到阮老師的一篇文章的一句話:

“對我來說,博客首先是一種知識管理工具,其次才是傳播工具。我的技術文章,主要用來整理我還不懂的知識。我只寫那些我還沒有完全掌握的東西,那些我精通的東西,往往沒有動力寫。炫耀從來不是我的動機,好奇才是。"

對于這句話,不能贊同更多,也讓我下決心好好寫這篇,網上文章雖多,大多復制粘貼,且晦澀難懂,我希望能夠通過這篇文章,能夠清晰的提升對apply、call、bind的認識,并且列出一些它們的妙用加深記憶。

? ?apply、call?

在 javascript 中,call 和 apply 都是為了改變某個函數運行時的上下文(context)而存在的,換句話說,就是為了改變函數體內部 this 的指向。

JavaScript 的一大特點是,函數存在「定義時上下文」和「運行時上下文」以及「上下文是可以改變的」這樣的概念。

先來一個栗子:

function fruits() {}fruits.prototype = {color: "red",say: function() {console.log("My color is " + this.color);}
}var apple = new fruits;
apple.say();	//My color is red

但是如果我們有一個對象banana= {color : "yellow"}?,我們不想對它重新定義 say 方法,那么我們可以通過 call 或 apply 用 apple 的 say 方法:

banana = {color: "yellow"
}
apple.say.call(banana);		//My color is yellow
apple.say.apply(banana);	//My color is yellow

所以,可以看出 call 和 apply 是為了動態改變 this 而出現的,當一個 object 沒有某個方法(本栗子中banana沒有say方法),但是其他的有(本栗子中apple有say方法),我們可以借助call或apply用其它對象的方法來操作。

?

apply、call 的區別

對于 apply、call 二者而言,作用完全一樣,只是接受參數的方式不太一樣。例如,有一個函數定義如下:

var func = function(arg1, arg2) {};

就可以通過如下方式來調用:

func.call(this, arg1, arg2); 
func.apply(this, [arg1, arg2])

其中 this 是你想指定的上下文,他可以是任何一個 JavaScript 對象(JavaScript 中一切皆對象),call 需要把參數按順序傳遞進去,而 apply 則是把參數放在數組里。  

JavaScript 中,某個函數的參數數量是不固定的,因此要說適用條件的話,當你的參數是明確知道數量時用 call 。
而不確定的時候用 apply,然后把參數 push 進數組傳遞進去。當參數數量不確定時,函數內部也可以通過 arguments 這個偽數組來遍歷所有的參數。
?
為了鞏固加深記憶,下面列舉一些常用用法:

數組之間追加

var array1 = [12 , "foo" , {name "Joe"} , -2458];  
var array2 = ["Doe" , 555 , 100];  
Array.prototype.push.apply(array1, array2);  
/* array1 值為  [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

獲取數組中的最大值和最小值

var  numbers = [5, 458 , 120 , -215 ];  
var maxInNumbers = Math.max.apply(Math, numbers),	//458maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215);	//458

number 本身沒有 max 方法,但是 Math 有,我們就可以借助 call 或者 apply 使用其方法。

驗證是否是數組(前提是toString()方法沒有被重寫過)

functionisArray(obj){  return Object.prototype.toString.call(obj) === '[object Array]' ;
}

類(偽)數組使用數組方法

var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

Javascript中存在一種名為偽數組的對象結構。比較特別的是?arguments 對象,還有像調用?getElementsByTagName?,?document.childNodes?之類的,它們返回NodeList對象都屬于偽數組。不能應用 Array下的 push , pop 等方法。

但是我們能通過 Array.prototype.slice.call 轉換為真正的數組的帶有 length 屬性的對象,這樣 domNodes 就可以應用 Array 下的所有方法了。

?

深入理解運用apply、call

下面就借用一道面試,來更深入的去理解下 apply 和 call 。

定義一個 log 方法,讓它可以代理 console.log 方法,常見的解決方法是:

function log(msg) {console.log(msg);
}
log(1);    //1
log(1,2);    //1

上面方法可以解決最基本的需求,但是當傳入參數的個數是不確定的時候,上面的方法就失效了,這個時候就可以考慮使用 apply 或者 call,注意這里傳入多少個參數是不確定的,所以使用apply是最好的,方法如下:

function log(){console.log.apply(console, arguments);
};
log(1);    //1
log(1,2);    //1 2

接下來的要求是給每一個 log 消息添加一個"(app)"的前輟,比如:

log("hello world");    //(app)hello world

該怎么做比較優雅呢?這個時候需要想到arguments參數是個偽數組,通過 Array.prototype.slice.call 轉化為標準數組,再使用數組方法unshift,像這樣:

function log(){var args = Array.prototype.slice.call(arguments);args.unshift('(app)');console.log.apply(console, args);
};

?

? ?bind 詳解

說完了 apply 和 call ,再來說說bind。bind() 方法與 apply 和 call 很相似,也是可以改變函數體內 this 的指向。

MDN的解釋是:bind()方法會創建一個新函數,稱為綁定函數,當調用這個綁定函數時,綁定函數會以創建它時傳入?bind()方法的第一個參數作為?this,傳入?bind()?方法的第二個以及以后的參數加上綁定函數運行時本身的參數按照順序作為原函數的參數來調用原函數。

直接來看看具體如何使用,在常見的單體模式中,通常我們會使用 _this , that , self 等保存 this ,這樣我們可以在改變了上下文之后繼續引用到它。?像這樣:

var foo = {bar : 1,eventBind: function(){var _this = this;$('.someClass').on('click',function(event) {/* Act on the event */console.log(_this.bar);		//1});}
}

由于 Javascript 特有的機制,上下文環境在 eventBind:function(){ } 過渡到?$('.someClass').on('click',function(event) {?}) 發生了改變,上述使用變量保存 this?這些方式都是有用的,也沒有什么問題。當然使用 bind() 可以更加優雅的解決這個問題:

var foo = {bar : 1,eventBind: function(){$('.someClass').on('click',function(event) {/* Act on the event */console.log(this.bar);		//1}.bind(this));}
}

在上述代碼里,bind() 創建了一個函數,當這個click事件綁定在被調用的時候,它的 this 關鍵詞會被設置成被傳入的值(這里指調用bind()時傳入的參數)。因此,這里我們傳入想要的上下文 this(其實就是 foo ),到 bind() 函數中。然后,當回調函數被執行的時候, this 便指向?foo?對象。再來一個簡單的栗子:

var bar = function(){
console.log(this.x);
}
var foo = {
x:3
}
bar(); // undefined
var func = bar.bind(foo);
func(); // 3

這里我們創建了一個新的函數 func,當使用 bind() 創建一個綁定函數之后,它被執行的時候,它的 this 會被設置成 foo , 而不是像我們調用 bar() 時的全局作用域。

有個有趣的問題,如果連續 bind() 兩次,亦或者是連續 bind() 三次那么輸出的值是什么呢?像這樣:

var bar = function(){console.log(this.x);
}
var foo = {x:3
}
var sed = {x:4
}
var func = bar.bind(foo).bind(sed);
func();	//? var fiv = {x:5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func();	//? 

答案是,兩次都仍將輸出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是無效的。更深層次的原因, bind() 的實現,相當于使用函數在內部包了一個 call / apply ,第二次 bind() 相當于再包住第一次 bind() ,故第二次以后的 bind 是無法生效的。

  

? ?apply、call、bind比較

那么 apply、call、bind 三者相比較,之間又有什么異同呢?何時使用 apply、call,何時使用 bind 呢。簡單的一個栗子:

var obj = {x: 81,
};var foo = {getX: function() {return this.x;}
}console.log(foo.getX.bind(obj)());	//81
console.log(foo.getX.call(obj));	//81
console.log(foo.getX.apply(obj));	//81

三個輸出的都是81,但是注意看使用 bind() 方法的,他后面多了對括號。

也就是說,區別是,當你希望改變上下文環境之后并非立即執行,而是回調執行的時候,使用 bind() 方法。而 apply/call 則會立即執行函數。

?

再總結一下:

  • apply 、 call 、bind 三者都是用來改變函數的this對象的指向的;
  • apply 、 call 、bind 三者第一個參數都是this要指向的對象,也就是想指定的上下文;
  • apply 、 call 、bind 三者都可以利用后續參數傳參;
  • bind?是返回對應函數,便于稍后調用;apply 、call 則是立即調用 。

?

本文實例出現的所有代碼,在我的github上可以下載。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

轉載于:https://www.cnblogs.com/coco1s/p/4833199.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/397471.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/397471.shtml
英文地址,請注明出處:http://en.pswp.cn/news/397471.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

PHP筆記隨筆

1.CSS控制頁面文字不能復制: body{-webkit-user-select:none;} 2.【php過濾漢字和非漢字】 $sc"aaad....##--__i漢字過濾"; //iconv("UTF-8","GB2312",$sc);utf-8轉碼 echo $temperegi_replace("[^\x80-\xff]",""…

qt linux 添加庫文件路徑,Linux下Qt調用共享庫文件.so

jvm--4垃圾收集6. 垃圾收集GC (1)當需要排查各種內存溢出,內存泄漏等問題,當GC成為系統達到更高性能的瓶頸時,我們就需要對這些自動化的GC進行監控和調節. (2)PC計數器.本地方法棧.虛擬機棧,隨方法或者線 ...GET和POSTAjax與Comet 1. Ajax Asynchronous Javascriptxml :能夠向服…

js進階 14-8 表單序列化函數serializeArray()和serialize()的區別是什么

js進階 14-8 表單序列化函數serializeArray()和serialize()的區別是什么 一、總結 一句話總結:兩者都是對表單進行序列化,serializeArray()返回的是json對象,serialize()返回的是json形式的字符串,使用起來都是一樣的 1、$&#x…

HDU 2842 Chinese Rings(矩陣高速功率+遞歸)

職務地址:HDU 2842 這個游戲是一個九連環的游戲。 如果當前要卸下前n個環。由于要滿足前n-2個都卸下,所以要先把前n-2個卸下。須要f(n-2)次。然后把第n個卸下須要1次,然后這時候要卸下第n-1個。然后此時前n-2個都已經被卸下了。這時候把前n-2…

硬鏈接與軟連接

linux系統硬鏈接和軟連接: 1文件都由文件名和數據組成,在linux中文件被分為兩個部分:用戶數據和元數據。用戶數據:即文件數據塊,記錄真實數據的地方。元數據:文件的附加屬性,記錄文件的大小&…

linux 7.2中文命令,CentOS7如何支持中文顯示

1.查看系統是否安裝有中文語言包locale -a | grep "zh_CN" 命令含義:列出所有可用的公共語言環境的名稱,包含有"zh_CN"若出現圖中所示幾項,那么說明系統中已經安裝了語言包,不需要在安裝。含義是:…

html-拖拽

html-拖拽(draggable"true")拖拽的7個事件:> 拖拽塊.οndragstartfunction(){console.log("拖拽開始");}> 拖拽塊.οndragfunction(){console.log("拖拽中");}> 拖拽塊.οndragendfunction(){console…

大道至簡

道在中國哲學中,是一個重要的概念,表示“終極真理”。此一概念,不單為哲學流派諸子百家所重視,也被宗教流派道教等所使用。大道至簡是指大道理(基本原理、方法和規律)是極其簡單的,簡單到一兩句…

別人7天樂,運維還苦逼值班?

你被點名值班了嗎?或者你的朋友、隔壁七大姑八大姨的侄子被點名值班了嗎? 國慶將至,大家都開始研究各種度假攻略了,國內游、國外游、地球游、外星游。。。然而總有一票人,默默地職守著 -- tIT 公司運營支撐組/運維組。…

【常用損失函數】

一、Smooth L1 Loss 1.公式: 2.原因: L1損失使權值稀疏但是導數不連續,L2損失導數連續可以防止過擬合但對噪聲不夠魯棒,分段結合兩者優勢。 二、Focal Loss 1.公式: 2.作用: 使得正負樣本平衡的同時&#x…

ORA-01940: cannot drop a user that is currently connected解決方法

我們在刪除數據庫用戶時候會碰到如下錯誤 SQL> DROP USER sys_xj cascade; DROP USER sys_xj cascade*ERROR at line 1:ORA-01940: cannot drop a user that is currently connected 解決方法: 1.查詢出還在連接的此用戶會話進程 SQL> SELECT SID,SERIAL# FR…

實現對象克隆

實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆,代碼如下 package com.lovo; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; i…

linux 讀取內存顆粒,linux查看主板內存槽與內存信息的命令dmidecode怎么用

在Linux中,我們常常使用命令來實現許多操作,比如查看內存信息等,下面小編就為大家帶來一篇linux查看主板內存槽與內存信息的命令dmidecode方法。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來…

python 圖像處理(從安裝Pillow開始)

python 圖像處理(從安裝Pillow開始) python2.x及以下用的是PIL(圖像處理庫是 PIL(Python Image Library)),最新版本是 1.1.7 可在http://www.pythonware.com/products/pil/index.htm 下載和學習。 不過從該網站可看出它不支持python3.x Pillow由PIL而來(支持3.x)&…

手機還是不要隨便更新的好

新入mate9pro 不到一個月,手賤升級了系統版本,出現導航搜索不到衛星的情況,軟件下載了高德地圖、騰訊地圖、百度地圖,逐一卸載安裝重試,沒一個能成功的,后來又下載了專業搜星軟件,還是搜不到衛星…

Java對象容器——List

為什么80%的碼農都做不了架構師?>>> 在Java中,我們可以用數組來存放同類型的變量或對象,但是數組有一個缺陷,它的長度不可變,必須在定義時給定其長度,所以說在一些場合下不適用。例如我們要存放…

STL學習筆記(數值算法)

運用數值算法之前必須先加入頭文件<numeric> 加工運算后產生結果 1.對序列進行某種運算 T accumulate(InputIterator beg,InputIterator end, T initValue) T accumulate(InputIterator beg,InputIterator end, T initValue,BinaryFunc op) 1.第一種形式計算InitValue和…

angualejs

為什么80%的碼農都做不了架構師&#xff1f;>>> http://segmentfault.com/a/1190000000347412 http://www.xker.com/page/e2015/06/199141.html http://www.runoob.com/angularjs/angularjs-application.html http://blog.csdn.net/lglgsy456/article/details/3690…

linux函數地址獲取函數名,函數名/函數地址/函數指針

函數指針&#xff1a;1。指針變量 2。指針變量指向函數這正如用指針變量可指向整型變量、字符型、數組一樣。在編譯時&#xff0c;每一個函數都有一個入口地址&#xff0c;該入口地址就是函數指針所指向的地址。可利用該指針變量調用函數&#xff0c;就如同用指針變量可引用其他…

SPOJ SORTBIT Sorted bit squence (數位DP,入門)

題意&#xff1a; 給出一個范圍[m,n]&#xff0c;按照二進制表示中的1的個數從小到大排序&#xff0c;若1的個數相同&#xff0c;則按照十進制大小排序。求排序后的第k個數。注意&#xff1a;m*n>0。 思路&#xff1a; 也是看論文的。一開始也能想到是這種解法&#xff0c;枚…