首頁 >web前端 >js教程 >js中如何將call及apply和bind用原生來實現

js中如何將call及apply和bind用原生來實現

不言
不言原創
2018-07-23 11:26:582127瀏覽

這篇文章給大家分享的內容是關於js中如何將call及apply和bind用原生來實現,有一定的參考價值,有需要的朋友可以參考一下。

因為關乎到了this指向的問題,call、apply和bind的用法可以說是老生常談了。這篇文章的主要功能是利用js原生方法對三個方法進行實現,升入了解其中的原理,對相關知識點有更好的掌握。 github位址call、apply和bind的原生實作

call與apply

#簡單介紹:call和apply方法都是使用一個指定的this值和對應的參數前提下呼叫某個函數或方法。差別則在於call是透過傳多個參數的方式,而apply則是傳入一個陣列。
舉個例子:

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就指向誰。所以實作的方法就是在傳入的物件中加入這麼一個方法,然後再去執行這個方法。為了保持物件一直,執行完之後再把這個物件給刪除了。是不是很簡單^-^。
初步體驗

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版本):

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實作:

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

bind也是函數的方法,作用也是改變this執行,同時也是能傳多個參數。與call和apply不同的是bind方法不會立即執行,而是傳回一個改變上下文this指向後的函數,原函數並沒有被改變。而如果函數本身是綁定了 this 物件的函數,那麼 apply 和 call 不會像預期那樣執行。
初步體驗:

Function.prototype.bind = function (context) {
  var me = this
  return function () { // bind之后得到的函数
    return me.call(context)  // 执行是改变this执行
  }
}

#加入參數:

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('男')

相關推薦:

js的模組化分析(命名空間)

對於JS繼承詳細介紹( 原型鏈,建構函數,組合,原型式,寄生式,寄生組合,Class extends)

以上是js中如何將call及apply和bind用原生來實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn