深入Javascript中apply、call、bind

最近在看一下node package的源碼,發現很多里面都包含了function這個對象的apply、call、bind這三個方法,于是想拿出來再看看。。

apply、call

在 javascript 中,call 和 apply 都是為了改變某個函數運行時的上下文(context)而存在的,換句話說,就是為了改變函數體內部 this 的指向。JavaScript 的一大特點是,函數存在【定義時上下文】和【運行時上下文】以及【上下文是可以改變的】這樣的概念。

下面先來看個小例子吧。。。。莫著急,咱們慢慢說

function TestFunc () {}TestFunc.prototype = {color: "red",doSomething: function() {console.log("My color is " + this.color);}};var testFunc = new TestFunc();
testFunc.doSomething();  //  My color is red

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

var other = {color: "yellow"};testFunc.doSomething.call(other);    //  My color is yellow
testFunc.doSomething.apply(other);   //  My color is yellow

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

利用上面的代碼,我們改一下來輸出這兩個不同對象調用doSomething是在控制臺輸出this,看一下這兩種調用中this是不是不同。。。

function TestFunc () {}TestFunc.prototype = {color: "red",doSomething: function() {console.log("My color is " + this.color);console.log(this);}};var testFunc = new TestFunc();testFunc.doSomething();  //  My color is redvar other = {color: "yellow"};testFunc.doSomething.call(other);    //  My color is yellowtestFunc.doSomething.apply(other);   //  My color is yellow

控制臺如下:你會發現確實不同哦

當我們再調用apply或call的時候,不傳入任何參數,或者是第一個參數為null的話又會是怎樣的呢,再看下面這個栗子:

var hehe = "Global";var color = 'blue';function TestFunc () {}TestFunc.prototype = {color: "red",doSomething: function() {console.log("My color is " + this.color);console.log(this);}};var testFunc = new TestFunc();testFunc.doSomething();  //  My color is redvar other = {color: "yellow"};testFunc.doSomething.call(other);    //  My color is yellowtestFunc.doSomething.apply(other);   //  My color is yellow
testFunc.doSomething().call();     //  My color is bluetestFunc.doSomething().call(null);  //  //  My color is blue

從輸出的結果你會看出但什么也不傳或者是第一個參數為null的時候,這個【上下文】應該就是函數【運行時上下文】,放在<script>里面其實就是window對象。

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 這個數組來遍歷所有的參數。為了鞏固加深記憶,下面列舉一些常用用法:

1、數組之間追加

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

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

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 使用其方法。

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

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

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

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);
};

是不是對apply,call這兩個function有了不同的理解了呢。。。表急。。咱們來看看bind。。

?

bind

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

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

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

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

由于 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 是無法生效的。

function getName() {console.log(this.hehe);}var obj11 = {hehe: "jason li"};//   直接運行方法結果是
        getName();//  通過call或者是apply來使用這個function
        getName.call(obj11);//  通過使用bind構建一個新的Function,并指定運行時的上下文,根據bind(綁定)就好像把getName綁定在了obj11對象上一樣var func = getName.bind(obj11);func();

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 則是立即調用 。

轉載于:https://www.cnblogs.com/duhuo/p/4382410.html

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

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

相關文章

優秀案例!教您如何設計現代簡約風格網頁

我們看到越來越多的設計師開始擁抱簡約的網站設計方法&#xff0c;消除網站不必要的元素&#xff0c;保留真正重要的內容&#xff0c;干凈、直觀的設計&#xff0c;就像今天的我們將展示的這些網站。 下面是一組不同風格的簡約設計的網站例子&#xff0c;美麗的導航、整潔的菜單…

怎么清理mysql的死鏈接_什么是死鏈接?如何正確處理死鏈接

什么是死鏈接&#xff1f;我們應該如何正確處理死鏈接呢&#xff1f;小剛SEO為你解答。什么是死鏈接&#xff1f;因鏈接更改或搜索引擎所收錄的網站路徑被刪除了&#xff0c;形成沒法打開的死鏈接。死鏈接的危害&#xff1a;1.死鏈數據過多時&#xff0c;對網站訪問體驗和用戶轉…

數據分頁和使用存儲過程的數據分頁

--使用存儲過程的數據分頁 --pageSize 一頁有多少條 --pageIndex 第幾頁 --totalCount 總共有多少條 --分頁的第一種方法 select top(10) * from Ams_Area where ar_id not in ( Select top(0) ar_id from Ams_Area order by ar_id ) order by ar_id --分頁的第二種方…

mysql generaton_Mysql 集成隨機唯一id mysql unique number generation

一句代碼&#xff1a;SELECT FLOOR(10000 RAND() * 89999) AS random_numberFROM table1WHERE "random_number" NOT IN (SELECT unique_id FROM table2)LIMIT 1隨機生成5位數字&#xff0c;并且不重復。上面的代碼真是給人無限的聯想力。例如&#xff1a;function g…

NavMeshAgent 動態加載障礙物

如果你想讓游戲人物繞開一些物體, 這些物體動態生成出來的.只需要給物體添加NavMeshObstacle組件即可 1. 綠色方塊添加NavMeshObstacle組件 2. 紅色方塊沒有添加NavMeshObstacle組件,被小球穿透了%>_<% 轉載于:https://www.cnblogs.com/plateFace/p/4385629.html

多任務 schedule python_Python3.6 Schedule 模塊定時任務 (實例講解)

Python3.6 Schedule 模塊定時任務 (實例講解)Python 是一種面向對象解釋型計算機程序設計語言, 由 Guido van Rossum 于 1989 年底發明, 第一個公開發行版發行于 1991 年 Python 語法簡潔而清晰, 具有豐富和強大的類庫它常被昵稱為膠水語言, 它能夠把用其他語言制作的各種模塊 …

UVa 11468 (AC自動機 概率DP) Substring

將K個模板串構成一個AC自動機&#xff0c;那些能匹配到的單詞節點都稱之為禁止節點。 然后問題就變成了在Tire樹上走L步且不經過禁止節點的概率。 根據全概率公式用記憶化搜索求解。 1 #include <cstdio>2 #include <cstring>3 #include <queue>4 using name…

mysql 檢查點_my05_mysql檢查點簡述

簡單描述一下mysql 檢查點&#xff0c;對mysql數據庫恢復的理解有所幫助。數據庫版本mysql> selectversion();-----------| version() |-----------| 8.0.11 |-----------1 row in set (0.00 sec)檢查點查看mysql>show engine innodb status\G;---LOG---Log sequence num…

VS2010無法執行自動化測試解決方案

在實際的工作過程中&#xff0c;當你發現你的VS2010無法執行自動化測試用例&#xff0c;剛好你發現你的電腦安裝有VS2012&#xff0c;那么好了&#xff0c;請卸載你的VS2012再試試...轉載于:https://www.cnblogs.com/captainR/p/3566751.html

停止Hadoop或HBase集群的腳本

#!/bin/sh #echo "waring" #read NAME #等待用戶輸入并把輸入的值付給NAME NAME$1 #將腳本第一個參數賦給NAME #引用變量時加上"{}",是個好習慣,利于shell辨別變量邊界 if [ -z ${NAME} ] ; then #執行腳本沒有輸入參數,默認關閉hadoopstop-all.sh elif [ …

css 偽元素分享!!!

最近接觸到的css 偽元素覺得還算不錯 分享下&#xff1a; 1、清楚內盒浮動設置&#xff1a; .back_list ul{padding:12px 0 0 12px;zoom:1;} .back_list ul:after{clear: both;content: ".";display: block;height: 0;visibility: hidden;}/*清楚內盒浮動設置*/ 2、偽…

公鑰和私鑰 java_公鑰與私鑰 - yxhxj2006 - BlogJava

評論# re: 公鑰與私鑰 [未登錄]2014-01-08 17:43workeruseful for me 回復 更多評論# re: 公鑰與私鑰2014-04-18 11:05Eva特別棒&#xff01; 謝謝&#xff01;worker回復 更多評論# re: 公鑰與私鑰 [未登錄]2014-06-11 17:10mike# re: 公鑰與私鑰2014-11-10 17:05游客太有用…

zepto學習之路--源代碼提取

最近在看zepto的源代碼&#xff0c;把一些有用的函數摘出來&#xff0c;看看zepto是怎么實現的&#xff0c;自己做的時候也可以用。說實話&#xff0c;zepto的實現有一些看起來還是很晦澀的&#xff0c;可能是自己的水平不夠&#xff0c;看不透作者的真正的意圖。 1、zepto的正…

java byte 整數_java整數與byte數組的轉換實現代碼

java整數與byte數組的轉換實現代碼這里對java中整數與byte數組的轉換進行了實現&#xff0c;平時的項目中很少用的到&#xff0c;但是特定需求的時候還是需要的&#xff0c;這里就記錄下&#xff0c;親測可用&#xff0c;實現代碼&#xff1a;public class NumberUtil {/*** in…

藍橋杯 花朵數

一個N位的十進制正整數&#xff0c;如果它的每個位上的數字的N次方的和等于這個數本身&#xff0c;則稱其為花朵數。 例如&#xff1a; 當N3時&#xff0c;153就滿足條件&#xff0c;因為 1^3 5^3 3^3 153&#xff0c;這樣的數字也被稱為水仙花數&#xff08;其中&#xff0…

windows 2003添加刪除windows組件中無iis應用程序服務器項的解決方法

解決方法如下: 1.開始 -- 運行,輸入 c:\Windows\inf\sysoc.inf,會打開這個文件;在sysoc.inf中找到"[Components]"這一段,并繼續找到類 似"iisiis.dll,OcEntry,iis.inf,hide,7" 的一行字,把這一行替換為"iisiis.dll,OcEntry,iis.inf,,7"。如果已經…

java打印菱形代碼_Java打印菱形高效簡潔代碼

importjava.util.Scanner;publicclass打印菱形{publicstaticvoidmain(String[]args){/**菱形**************************/ScannerinputScannernewScanner(System.in);System.out.prin...import java.util.Scanner;public class 打印菱形 {public static void main(String[] arg…

QT mainwindow四件套

最近在學習QT。下面總結一下mainwindow的設置步驟。 使用的平臺為vs2013qt5.3.2qt-vs-addin1.2.3 1)安裝軟件 首先安裝vs2013&#xff0c;這個不多介紹。 然后安裝qt5.3.2和addin1.2.3。并設置相關環境。詳細見http://tieba.baidu.com/p/3451630520?pid61264366864#6126436686…

go mysql recover_golang用panic和recover做業務流程中斷的嘗試

隨著使用golang越來越頻繁&#xff0c;發現golang有一個地方非常不方便&#xff0c;就是在錯誤處理方面。先來看看golang中通常的錯誤處理方法&#xff1a;通常的error處理package mainimport ("errors""fmt")func a() (err error) {err errors.New("…

ROC曲線【轉】

ROC曲線&#xff08;Receiver Operating Characteeristic Curve&#xff09;是顯示Classification模型真正率和假正率之間折中的一種圖形化方法 解讀ROC圖的一些概念定義&#xff1a; 真正&#xff08;True Positive , TP&#xff09;被模型預測為正的正樣本 假負&#xff08;F…