在js中,全部的函數在被調用的時候都會默認傳入兩個參數,一個是this,還有一個是arguments。在默認狀況下this都是指當前的調用函數的對象。可是有時候咱們須要改變this的指向,也就是說使函數能夠被其餘對象來調用,那麼咱們應該怎樣作呢?這時候咱們就可使用call,apply和bind方法了。
那麼call,apply和bind來自哪裏?
在js中全部的函數都是Function的實例,並且對於Function來講,它的原型即Function.prototype中含有不少東西,其中call,apply和bind方法就是Function原型中的方法,因此根據原型的規則,全部的函數均可以使用原型中屬性和方法,因此來講,對於全部的函數均可以使用call,apply和bind方法。數組
使用 apply, 你能夠繼承其餘對象的方法:app
注意這裏apply()的第一個參數是null,在非嚴格模式下,第一個參數爲null或者undefined時會自動替換爲指向全局對象,apply()的第二個參數爲數組或類數組。函數
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
方法:ui
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
用其它對象的方法來操做this
call()是apply()的一顆語法糖,做用和apply()同樣,一樣可實現繼承,惟一的區別就在於call()接收的是參數列表,而apply()則接收參數數組。prototype
bind()的做用與call()和apply()同樣,都是能夠改變函數運行時上下文,區別是call()和apply()在調用函數以後會當即執行,而bind()方法調用並改變函數運行時上下文後,返回一個新的函數,供咱們須要時再調用。code
this.num = 9; var mymodule = { num: 81, getNum: function() { console.log(this.num); } }; mymodule.getNum(); // 81 var getNum = mymodule.getNum; getNum(); // 9, 由於在這個例子中,"this"指向全局對象 var boundGetNum = getNum.bind(mymodule); boundGetNum(); // 81
bind()
方法會建立一個新函數,稱爲綁定函數,當調用這個綁定函數時,綁定函數會以建立它時傳入bind()
方法的第一個參數做爲this
,傳入bind()
方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數。對象
一、call的arg傳參需一個一個傳,apply則直接傳一個數組。繼承
function hello(name,age){ console.log(name); console.log(age); } hello.call(this,"tsrot",24); hello.apply(this,["tsrot",24]);
二、call和apply直接執行函數,而bind須要再一次調用。get
var obj = { x: 81, }; var foo = { getX: function() { return this.x; } } console.log(foo.getX.bind(obj)()); console.log(foo.getX.call(obj)); console.log(foo.getX.apply(obj));
三、如何選用
一、實現繼承
function Animal(name) { this.name = name; this.showName = function () { console.log(this.name); } } function Cat(name) { Animal.call(this, name); } var cat = new Cat('Black Cat'); cat.showName();
二、數組追加
var array1 = [1 , 2 , 3, 5]; var array2 = ["xie" , "li" , "qun" , "tsrot"]; Array.prototype.push.apply(array1, array2); console.log(array1);
三、獲取數組中的最大值和最小值
var num = [1,3,5,7,2,-10,11]; var maxNum = Math.max.apply(Math, num); var minNum = Math.min.apply(Math, num); console.log(maxNum); console.log(minNum);
四、將僞數組轉化爲數組
var fakeArr = {0:'a',1:'b',length:2}; var arr1 = Array.prototype.slice.call(fakeArr); console.log(arr1[0]); var arr2 = [].slice.call(fakeArr); console.log(arr2[0]); arr1.push("c"); console.log(arr1);
五、保存this變量
var foo = { bar : 1, eventBind: function(){ var _this = this ; $('.someClass').on('click',function(event) { console.log(_this.bar); }); } } var foo = { bar : 1, eventBind: function(){ $('.someClass').on('click',function(event) { console.log(this.bar); }.bind(this)); } }
首先從如下幾點來考慮如何實現這幾個函數
window
this
指向,讓新的對象能夠執行該函數,並能接受參數先來實現call
Function.prototype.myCall = function(context) { if (typeof this !== 'function') { throw new TypeError('Error') } context = context || window ; context.fn = this ; const args = [...arguments].slice(1); const result = context.fn(...args) ; delete context.fn ; return result ; }
如下是對實現的分析:
context
爲可選參數,若是不傳的話默認上下文爲window
context
建立一個fn
屬性,並將值設置爲須要調用的函數call
能夠傳入多個參數做爲調用函數的參數,因此須要將參數剝離出來以上就是實現call
的思路,apply
的實現也相似,區別在於對參數的處理,因此就不一一分析思路了
Function.prototype.myApply = function(context) { if (typeof this !== 'function') { throw new TypeError('Error') } context = context || window ; context.fn = this; let result // 處理參數和 call 有區別 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn ; return result; }
bind
的實現對比其餘兩個函數略微地複雜了一點,由於bind
須要返回一個函數,須要判斷一些邊界問題,如下是bind
的實現
Function.prototype.myBind = function (context) { if (typeof this !== 'function') { throw new TypeError('Error') } const _this = this const args = [...arguments].slice(1) // 返回一個函數 return function F() { // 由於返回了一個函數,咱們能夠 new F(),因此須要判斷 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
如下是對實現的分析:
bind
返回了一個函數,對於函數來講有兩種方式調用,一種是直接調用,一種是經過new
的方式,咱們先來講直接調用的方式apply
的方式實現,可是對於參數須要注意如下狀況:由於bind
能夠實現相似這樣的代碼f.bind(obj, 1)(2)
,因此咱們須要將兩邊的參數拼接起來,因而就有了這樣的實現args.concat(...arguments)
new
的方式,對於new
的狀況來講,不會被任何方式改變this
,因此對於這種狀況咱們須要忽略傳入的this
將不斷更新完善,期待您的批評指正!