由於關乎到了this指向的問題,call、apply和bind的用法能夠說是老生常談了。這篇文章的主要做用是利用js原生方法對三個方法進行實現,升入瞭解其中的原理,對相關知識點有更好的掌握。github地址call、apply和bind的原生實現git
簡單介紹:call和apply方法都是使用一個指定的this值和對應的參數前提下調用某個函數或方法。區別則在於call是經過傳多個參數的方式,而apply則是傳入一個數組。
舉個例子:es6
var obj = { name: 'linxin' } function func(age, sex) { console.log(this.name,age,sex); } func.call(obj,12,'女'); // linxin 12 女 func.apply(obj, [18, '女']); //linxin 18 女
思路:在JavaScript中的this指向說到了:函數還能夠做爲某個對象的方法調用,這時this就指這個上級對象。也就是咱們平時說的,誰調用,this就指向誰。因此實現的方法就是在傳入的對象中添加這麼一個方法,而後再去執行這個方法。爲了保持對象一直,在執行完以後再把這個對象給刪除了。是否是很簡單^-^。
初體驗:github
Function.prototype.newCall = function(context) { context.fn = this; // 經過this獲取call的函數 context.fn(); delete context.fn; } let foo = { value: 1 } function bar() { console.log(this.value); } bar.newCall (foo); // 1
這樣就完成了基礎版本的實現,可是若是說有傳參數呢?
因此咱們能夠進行優化一下,由於傳入的參數數量是不肯定的,因此咱們能夠從Arguments對象中去獲取,這個比較簡單。問題是參數是不肯定的,咱們如何傳入到咱們要執行的函數中去呢 ? 這裏咱們有兩種選擇:一種是經過eval拼接的方式,另外一種就要用到es6了。
體驗升級(eval版本):數組
Function.prototype.newCall = function(context) { context.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']'); } eval('context.fn(' + args +')'); delete context.fn; } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } sayHi.newCall (person, 25, '男'); // Abiel 25 男
體驗升級(ES6版本):app
Function.prototype.newCall = function(context) { context.fn = this; context.fn(...Array.from(arguments).slice(1)); delete context.fn; } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } sayHi.newCall (person, 25, '男'); // Abiel 25 男
讓然ES6的方法還能夠不用到arguments就能實現
ES6版本再升級:函數
Function.prototype.newCall = function(context, ...parameter) { context.fn = this; context.fn(...parameter); delete context.fn; } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } sayHi.newCall (person, 25, '男'); // Abiel 25 男
這樣咱們基本上實現了call的功能,可是仍是存在一些隱患和區別。
當對象自己就有fn這個方法的時候,就有大問題了。
當call傳入的對象是null的時候,或者其餘一些類型的時候,函數會報錯。
終極體驗:優化
Function.prototype.newCall = function(context, ...parameter) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null) } let fn = Symbol() context[fn] = this context[fn](...parameter); delete context[fn] } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } sayHi.newCall (person, 25, '男'); // Abiel 25 男
實現了call以後,apply也是一樣的思路。
apply實現:this
Function.prototype.newApply = function(context, parameter) { if (typeof context === 'object') { context = context || window } else { context = Object.create(null) } let fn = Symbol() context[fn] = this context[fn](parameter); delete context[fn] }
bind也是函數的方法,做用也是改變this執行,同時也是能傳多個參數。與call和apply不一樣的是bind方法不會當即執行,而是返回一個改變上下文this指向後的函數,原函數並無被改變。而且若是函數自己是一個綁定了 this 對象的函數,那 apply 和 call 不會像預期那樣執行。
初體驗:prototype
Function.prototype.bind = function (context) { var me = this return function () { // bind以後獲得的函數 return me.call(context) // 執行是改變this執行 } }
加入參數:code
Function.prototype.bind = function (context,...innerArgs) { var me = this return function (...finnalyArgs) { return me.call(context,...innerArgs,...finnalyArgs) } } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } let personSayHi = sayHi.bind(person, 25) personSayHi('男')