setTimeout的this指向:學習apply、call、bind

<!DOCTYPE html>
 
<html>
 
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript">
            var name = "李四";
 
            function Coder(name) {
                this.name = name;
 
                function alerts() {
                    console.log('alert:' + this.name);
                }
                this.getName = function() {
                    console.log('this.getName'+this.name)
                };
                this.delayGetName = function() {
                    setTimeout(function() {
                        console.log('--:' + this.name)
                    }, 1000);
                };
                this.delayGetName0 = function() {
                    setTimeout(() => {
                        console.log('0:' + this.name);
                    }, 1000);
                };
                this.delayGetName1 = function() {
                    var that = this;
                    setTimeout(function() {
                        console.log('1:' + that.name);
                    }, 1000);
                };
                this.delayGetName2 = function() {
                    setTimeout(function() {
                        console.log('2:' + this.name);
                    }.bind(this), 1000);
                };
                this.delayGetName3 = function() {
                    setTimeout(function() {
                        console.log('3:' + this.name);
                    }.call(this), 1000);
                };
                this.delayGetName4 = function() {
                    setTimeout(function() {
                        console.log('4:' + this.name);
                    }.apply(this), 1000);
                };
                this.delayGetName5 = function() {
                    setTimeout(alerts.bind(this), 1000);
                };
                this.delayGetName6 = function() {
                    setTimeout(this.getName.bind(this), 1000);
                };
            }
            var me = new Coder('張三');
            me.delayGetName();
            me.delayGetName0();
            me.delayGetName1();
            me.delayGetName2();
            me.delayGetName3();
            me.delayGetName4();
            me.delayGetName5();
            me.delayGetName6();
        </script>
    </head>
 
    <body>
    </body>
 
</html>
複製代碼


call,apply 不行的緣由是:bind返回的是函數,bind 是返回對應函數,便於稍後調用;apply 、call 則是當即調用 。









apply、call、bind

apply call借用他人的函數方法javascript


網上文章雖多,大多複製粘貼,且晦澀難懂,我但願可以經過這篇文章,可以清晰的提高對apply、call、bind的認識,而且列出一些它們的妙用加深記憶。html

apply、call前端

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

先來一個栗子:es6

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"} , 可是若是咱們有一個對象banana= {color : "yellow"} ,咱們不想對它從新定義 say 方法,那麼咱們能夠經過 call 或 apply 用 apple 的 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用其它對象的方法來操做。數組

見解(不一樣角度):瀏覽器

  • fruits() 打印出別的color 用call修改color的this指針
  • banana 沒有say() 因此借用fruits()中的say() 來打印

apply、call 的區別bash

對於 apply、call 兩者而言,做用徹底同樣,只是接受參數的方式不太同樣。例如,有一個函數定義以下:app

var func = function(arg1, arg2) {

};複製代碼

就能夠經過以下方式來調用:

func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])複製代碼

call(this) apply(this) 指的是func()這個函數自己

其中 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] */複製代碼

讓array1 具有Array的push方法

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

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

讓numbers 具有Math的max方法
number 自己沒有 max 方法,可是 Math 有,咱們就能夠藉助 call 或者 apply 使用其方法。

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

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

四、類(僞)數組 => 正真數組 具有數組方法

var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));複製代碼

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

深刻理解運用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() 方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數。

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

bar(); // undefined
var func = bar.bind(foo);
func(); // 3複製代碼

把foo.bar的值傳入bar()中來

這裏咱們建立了一個新的函數 func,當使用 bind() 建立一個綁定函數以後,它被執行的時候,它的 this 會被設置成 foo , 而不是像咱們調用 bar() 時的全局做用域。

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複製代碼
  • bind() 回調函數 被調用才執行 若當即執行函數 bind()() 多個括號`
  • call() apply() 當即執行函數 立刻執行`

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

也就是說,區別是,當你但願改變上下文環境以後並不是當即執行,而是回調執行的時候,使用 bind() 方法。而 apply/call 則會當即執行函數。

再總結一下:

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





apply、call的區別和用途

做爲一個前端程序媛,在提高學習的道路上,不可避免的與apply和call相遇了。以前因爲它倆出鏡率有點低,都靜靜的擦肩而過了!今天不當心被它倆的魅力所吸引,加上本小姐心情好,就讓咱們好好的相識一下吧O(∩_∩)O~

ECAMScript 3給Function的原型定義了兩個方法,它們是Function.prototype.call和Function.prototype.apply。

一.call和apply的區別

一、Function.prototype.call 和 Function.prototype.apply 都是很是經常使用的方。它們的做用如出一轍,區別僅在於傳入參數的形式的不一樣。

apply 接受兩個參數,第一個參數指定了函數體內 this 對象的指向,

第二個參數爲一個帶下標的集合,這個集合能夠爲數組,也能夠爲類數組,apply 方法把這個集合中的元素做爲參數傳遞給被調用的函數

var func = function( a, b, c ){ 
    console.log([a,b,c]); //輸出:[1,2,3]
};
func.apply( null, [ 1, 2, 3 ] );複製代碼

call 傳入的參數數量不固定, 跟apply 相同的是,第一個參數也是表明函數體內的 this 指向, 從第二個參數開始日後,每一個參數被依次傳入函數

var func = function( a, b, c ){ 
    console.log([a,b,c]); //輸出:[1,2,3]
};
func.call( null, 1, 2, 3 );複製代碼

二、當使用 call 或者 apply 的時候,若是咱們傳入的第一個參數爲 null,函數體內的 this 會指 向默認的宿主對象,在瀏覽器中則是 window

var func = function( a, b, c ){ 
    console.log(this === window); // 輸出:true
};
func.apply( null, [ 1, 2, 3 ] );複製代碼

但若是是在嚴格模式下,函數體內的 this 仍是爲 null

"use strict";
    console.log(this === null); // 輸出:true
};
func.apply( null, [ 1, 2, 3 ] );複製代碼

三、有時候咱們使用 call 或者 apply 的目的不在於指 定this 指向,而是另有用途,好比借用其餘對象的方法。那麼咱們能夠傳入 null 來代替某個具體的對象

var a=Math.max.apply( null, [ 1, 2, 5, 3, 4 ] );
console.log(a);// 輸出:5
複製代碼

二.call和apply的用途

1. 改變this指向

例一

var obj1={ 
    name: 'sven'
};
var obj2={ 
    name: 'anne'
};
window.name = 'window';
var getName = function(){ 
    console.log ( this.name );
};
getName(); // 輸出: window
getName.call( obj1 );// 輸出: sven
getName.call(obj2 ); // 輸出: anne複製代碼

其中在執行

<strong>getName.call( obj1 );</strong>複製代碼

時,相似於執行

<strong>var getName = function(){ 
    console.log ( obj1.name );// 輸出: sven
}; </strong>複製代碼

例二

document.getElementById( 'div1' ).onclick = function(){
    console.log( this.id );// 輸出: div1
    var func = function(){ 
        console.log ( this.id );// 輸出: undefined
    } 
    func();
}; 
//修正後
document.getElementById( 'div1' ).onclick = function(){
    var func = function(){ 
        console.log ( this.id );// 輸出: div1
    } 
    func.call(this);
}; 
複製代碼

2. Function.prototype.bind

Function.prototype.bind = function( context ){ 
    var self = this; // 保存原函數
    return function(){ // 返回一個新的函數
        return self.apply( context, arguments );//執行新的函數的時候,會把以前傳入的context看成新的函數體的this
    } 
};
var obj={ 
    name: 'sven'
};
var func = function(){ 
    console.log ( this.name );// 輸出: sven
}.bind( obj); 
func();
//複雜化
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 ) ) ); 
        //執行新的函數的時候,會把以前傳入的context看成新的函數體的this
        //而且組合兩次分別傳入的參數,做爲新的函數的參數
    } 
};
var obj={ 
    name: 'sven'
};
var func = function( a, b, c, d ){
    console.log ( this.name ); // 輸出: sven 
    console.log([a,b,c,d]) //輸出: [1,2,3,4]
}.bind( obj, 1, 2 ); 
func( 3, 4 );複製代碼

3. 借用其餘對象的方法

借用方法的第一種場景是「借用構造函數」,經過這種技術,能夠實現一些相似繼承的效果

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'複製代碼

借用方法的第二種場景——函數的參數列表 arguments 是一個類數組對象,雖然它也有「下標」,但它並不是正的數組,因此也不能像數組同樣,進行排序操做或者往集合裏添加一個新的元素。

這種狀況下,咱們經常 會借用 Array.prototype 對象上的方法。

好比想往 arguments 中添加一個新的元素,一般會借用 Array.prototype.push;

想把 arguments 轉成真正的數組的時候,能夠借用 Array.prototype.slice 方法;

想截取arguments 列表中的一個元素時,能夠借用 Array.prototype.shift 方法。

var a={};
Array.prototype.push.call( a, 'first' );
console.log ( a.length ); // 輸出: 1 
console.log(a[0]); //輸出: first
//這段代碼在大部分瀏覽器裏都能順利執行,但因爲引擎的內部實現存在差別,若是在低版本的 IE瀏覽器 中執行,必須顯式地給對象 a 設置 length屬性
var a={ 
    length: 0
}; 複製代碼

借用 Array.prototype.push 方法的對象還要知足如下兩個條件
一、對象自己要能夠存取屬性
二、對象的 length 屬性可讀寫。

360雲盤代碼下載:yunpan.cn/ckcdvDALGAk… (提取碼:d76d)

三.在es6的箭頭函數(=>)下,call和apply的「失效」

箭頭函數有幾個使用注意點。

(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。

(2)不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。

(3)不可使用arguments對象,該對象在函數體內不存在。若是要用,能夠用Rest參數代替。

(4)不可使用yield命令,所以箭頭函數不能用做Generator函數。

上面四點中,第一點尤爲值得注意。this對象的指向是可變的,可是在箭頭函數中,它是固定的。

function foo() {
  return () =&gt; {
    return () =&gt; {
      return () =&gt; {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1複製代碼

上面代碼之中,只有一個this,就是函數foo的this,因此t一、t二、t3都輸出一樣的結果。由於全部的內層函數都是箭頭函數,都沒有本身的this,它們的this其實都是最外層foo函數的this。

因爲箭頭函數沒有本身的this,因此固然也就不能用call()、apply()、bind()這些方法去改變this的指向。

相關文章
相關標籤/搜索