小編給大家分享一下如何搞定this綁定方法call apply bind,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
先來看一個例子
var obj = {}; //創建一個對象 obj.name = "James"; //給對象添加一個屬性 obj.say = function(){ //給對象添加一個方法 console.log('My name is' + this.name); }; obj.say(); //this指向obj,所以輸出"My name is James" var fn = obj.say; fn(); //this指向了window,全局中沒有name變量,所以只能輸出"My name is",沒有name
由于fn里面的this指向了window而不是obj,所以name沒有輸出。如果也想讓fn輸出James,也就意味著必須讓fn里面的this指向obj,這個時候需要我們強行改變this指向了。
call方法,可以指定函數內部this的指向(即函數執行時所在的作用域),然后在所指定的作用域中,調用該函數。
var obj = {}; //創建一個對象 obj.name = "James"; //給對象添加一個屬性 obj.say = function(){ //給對象添加一個方法 console.log('My name is' + this.name); }; obj.say(); //this指向obj,所以輸出"My name is James" var fn = obj.say; fn.call(obj); //輸出"My name is James"
上面的代碼中,fn函數的this指向window,call方法改變了this的指向,讓this對象指向了obj,然后在對象obj的作用域中運行函數fn
call方法的參數是一個對象,如果參數為空,null和undefined,則默認傳入全局對象。我們把上面的例子做下修改:
var name = 'Kobe'; //定義全局變量 var obj = {}; //創建一個對象 obj.name = "James"; //給對象添加一個屬性 obj.say = function(){ //給對象添加一個方法 console.log('My name is' + this.name); }; var fn = obj.say; fn.call(); //輸出"My name is Kobe" fn.call(null); //輸出"My name is Kobe" fn.call(undefined); //輸出"My name is Kobe" fn.call(window); //輸出"My name is Kobe" fn.call(obj); //輸出"My name is James"
call方法還可以接受多個參數 func.call(this要指向的那個對象, 參數1, 參數2……),call方法的第一個參數就是this要指向的那個對象,后面的參數則是調用函數時需要所需的參數
var uname ='Kobe'; var team ='Lakers'; var num =24; var obj = {}; obj.uname ='Westbook'; obj.team ='Thunder'; obj.num =0; obj.introduce =function(){ console.log('My name is'+ this.uname+', I am from'+this.team+',My number is'+this.num); }; var fn = obj.introduce; fn(); //My name is Kobe, I am from Lakers,My number is 24 fn.call(obj,uname,team,num); //My name is Westbook, I am from Thunder,My number is 0
這段代碼中,fn函數的this指向window,執行fn時輸出的就是全局變量。通過call方法把this指向了obj對象,然后再obj的作用域中運行函數fn,所以輸出的就是Westbook而不再是Kobe了
apply方法的作用與call方法類似,也是改變this指向,然后再調用該函數。唯一的區別就是,它接收一個數組作為函數執行時的參數,使用格式如下:
func.apply(this要指向的那個對象,[參數1,參數2……])
所以上面的例子中,除了使用call方法以外,我們還可以使用apply方法
fn.apply(obj,[uname,team,num]); //My name is Westbook, I am from Thunder,My number is 0
apply方法的實際應用:
1、找出數組中最大的元素
Math對象提供了獲得最大值和最小值的API
Math.max(a,b,c...); 獲得參數中最大的值
Math.min(a,b,c...); 獲得參數中最小的值
問題:max和min不支持獲取數組中的最大值,因為Javascript沒有提供獲取數組最大值的API,解決辦法就是:Math.max.apply(null,[a,b,b...]);
2、將數組的空元素變為undefined
空元素與undefined的差別在于,數組的forEach方法會跳過空元素,但是不會跳過undefined。所以,在遍歷內部元素的時候會得到不同的結果
var arr=['a', ,'b']; function show(i){ console.log(i); }; arr.forEach(show);//a b Array.apply(null,arr).forEach(show); //a undefined b
3、把類數組對象轉為真正的數組(call和apply都可以)
Array.prototype.slice.call(obj)-->obj是一個類數組,通過這個函數可以把類數組轉換成一個真正的數組(參數位置就是類數組)。
上面的apply/call方法中的參數都是對象,返回結果都是數組,這就起到了把對象轉成數組的目的。同時可以看出,這個方法起作用的前提是,被處理的對象必須有length屬性,以及相對應的數字鍵
Object.prototype.toString.call(arg)-->arg可以是任意一種類型的值,這個函數最精確的判斷出參數的類型。
4、綁定回調函數的對象
<button id="button">Button</button>
var obj = new Object(); obj.fun = function(){ console.log(this===obj); //this對象指向obj }; var fun = function(){ //全局函數fun,this指向window obj.fun.apply(obj); //強行把this指向obj (這里也可以用call) }; var button = document.getElementById('button'); button.onclick=function(){ fun(); }
點擊按鈕以后,控制臺將會顯示true。由于apply方法(或者call方法)不僅綁定函數執行時所在的對象,還會立即執行函數,因此不得不把綁定語句寫在一個函數體內。更簡潔的寫法是采用下面介紹的bind方法。
bind方法用于將函數體內的this綁定到某個對象,然后返回一個新函數。
var obj = {}; obj.name = "Kobe"; obj.say = function(){ //this指向obj console.log("My name is"+this.name); }; var fn = obj.say.bind(obj); //全局函數fn,this指向window fn(); //My name is Kobe
obj的say方法賦值給一個全局變量fn以后,fn的this指向了window,所以是無法輸出name的?,F在通過bind方法強行改變讓this指向obj對象。
bind方法比call/apply方法更進一步的是,除了綁定this以外,還可以綁定原函數的參數
var add = function (x, y) { console.log (x * this.m + y * this.n); }; var obj = { m: 2, n: 3 }; var newAdd = add.bind(obj, 5); newAdd(6); //28 5*2+6*3 newAdd(8); //34 5*2+8*3 newAdd(10); //40 5*2+10*3
上面代碼中,bind不僅綁定了this對象,還將add函數的第一個參數x綁定成5,然后返回一個新的函數newAdd,這個函數只要再接受一個參數y就能運行了
為了進一步驗證,我們可以把上面的代碼稍微改變一下
var add = function (x, y) { console.log (x * this.m + y * this.n); }; var obj = { m: 2, n: 3 }; var newAdd = add.bind(obj, 5, 6); newAdd(6); //28 newAdd(8); //28 newAdd(10); //28 newAdd(11,12); //28
現在是同時綁定了參數x為5,y為6,后續無論怎么往newAdd函數中傳值,都不能改變輸出結果
如果bind方法的第一個參數是null或者undefined,那么,this就綁定到了全局對象window
function add(x, y) { console.log(x + y); } var plus = add.bind(null, 5); //this指向window,x綁定成5 plus(10); // 15 y=10 5+10 plus(10,10); //15 x已經綁定成5,無法改成10
上面代碼中,函數add內部并沒有this,使用bind方法的主要目的是綁定參數x,以后每次運行新函數plus,就只需要提供另一個參數y就夠了。而且因為add內部沒有this,所以bind的第一個參數是null,不過這里如果是其他對象,也沒有影響。
對于那些不支持bind方法的老式瀏覽器,可以自行定義bind方法。
if(!('bind' in Function.prototype)){ Function.prototype.bind = function(){ var fn = this; var context = arguments[0]; var args = Array.prototype.slice.call(arguments, 1); return function(){ return fn.apply(context, args); } } }
bind方法使用注意點:
1、每次返回一個新函數
bind方法每運行一次,就返回一個新函數,這會產生一些問題。比如:事件監聽時,不能寫成下面這樣
element.addEventListener('click', o.m.bind(o)); element.removeEventListener('click', o.m.bind(o));
上面代碼中,click事件綁定bind方法生成的一個匿名函數。這樣會導致無法取消綁定,所以,上面的代碼是無效的。正確的寫法:
var listener = o.m.bind(o); element.addEventListener('click', listener); // ... element.removeEventListener('click', listener);
2、結合回調函數使用
回調函數是Javascript最常用的模式之一,但是一個常見的錯誤是,將包含this的方法直接當回調函數
var counter = { count: 0, inc: function () { 'use strict'; //注意:嚴格模式下this對象是undefined this.count++; //this對象不是counter alert(this); } }; function callIt(callback) { //全局函數callIt的this對象在嚴格模式下也不是window了 callback(); } callIt(counter.inc); //Uncaught TypeError: Cannot read property 'count' of undefined
上面代碼中,counter.inc方法被當作回調函數,傳入了callIt,調用時其內部的this指向callIt運行時所在的對象,即頂層對象window,所以得不到預想結果。注意,上面的counter.inc方法內部使用了嚴格模式,在該模式下,this指向頂層對象時會報錯,一般模式不會。
解決方法就是使用bind方法,將counter.inc綁定counter。
var counter = { count: 0, inc: function () { 'use strict'; //注意:嚴格模式下this對象是undefined console.log(this.count++); //this對象不是counter } }; function callIt(callback) { //全局函數callIt的this對象在嚴格模式下也不是window了 callback(); } callIt(counter.inc.bind(counter)); //0 注意前++和后++的區別,前++返回新值,后++返回舊值 callIt(counter.inc.bind(counter)); //1
還有一種情況比較隱蔽,就是某些數組方法可以接受一個函數當作參數。這些函數內部的this指向,很可能也會出錯。
var obj = { name: '張三', times: [1, 2, 3], print: function () { //this指向obj this.times.forEach(function (n) {//this指向window console.log(this.name); }); } }; obj.print();
只需要把this的指向劃分出來,很容易就能判斷不會有輸出結果,因為全局沒有name。解決這個問題也是通過bind綁定this
var obj = { name: '張三', times: [1, 2, 3], print: function () { //this指向obj this.times.forEach(function (n) { console.log(this.name); }.bind(this) //遍歷數組的同時綁定了this ); } }; obj.print(); //張三*3
除此之外,還可以用留住this的方法,具體可以參照“撲朔迷離的this關鍵字”一文
var obj = { name: '張三', times: [1, 2, 3], print: function () { var me=this; //留住this this.times.forEach(function (n) { console.log(me.name); }); } }; obj.print(); 張三*3
3、結合call方法使用
利用bind方法,可以改寫一些JavaScript原生方法的使用形式,以數組的slice方法為例
[1, 2, 3].slice(0, 1); //從第0位開始,截取1位,返回一個數組 // [1] // 等同于 Array.prototype.slice.call([1, 2, 3], 0, 1); // [1]
上面的代碼中,數組的slice方法從[1, 2, 3]里面,按照指定位置和長度切分出另一個數組。這樣做的本質是在[1, 2, 3]上面調用Array.prototype.slice方法,因此可以用call方法表達這個過程,得到同樣的結果。
call方法實質上是調用Function.prototype.call方法,因此上面的表達式可以用bind方法改寫。
var slice = Function.prototype.call.bind(Array.prototype.slice); slice([1, 2, 3], 0, 1) // [1]
可以看到,利用bind方法,將[1, 2, 3].slice(0, 1)變成了slice([1, 2, 3], 0, 1)的形式。這種形式的改變還可以用于其他數組方法。
var push = Function.prototype.call.bind(Array.prototype.push); var pop = Function.prototype.call.bind(Array.prototype.pop); var a = [1 ,2 ,3]; push(a, 4) //在數組末尾添加一個元素4 a // [1, 2, 3, 4] pop(a) //刪除數組最后一個元素 a // [1, 2, 3]
如果再進一步,將Function.prototype.call方法綁定到Function.prototype.bind對象,就意味著bind的調用形式也可以被改寫。
function f() { console.log(this.v); } var o = { v: 123 }; var bind = Function.prototype.call.bind(Function.prototype.bind); bind(f, o)() // 123
上面代碼表示,將Function.prototype.call方法綁定Function.prototype.bind以后,bind方法的使用形式從f.bind(o),變成了bind(f, o)。
以上是“如何搞定this綁定方法call apply bind”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。