原生实现JavaScript的call()apply()bind()

Posted cwxblog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原生实现JavaScript的call()apply()bind()相关的知识,希望对你有一定的参考价值。

前言

call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向。
我们先来看一个例子

let obj = 
	name:  "大卫"

function person() 
	console.log("name", this.name)

// 用原生apply调用
person.apply(obj) // name: 大卫

实现上,可以理解这样理解call, apply 的实现过程:

  1. 将函数添加到绑定对象的属性
  2. 执行该函数
  3. 删除该属性
let obj = 
	name:  "大卫"function person() 
		console.log("name", this.name)
	

obj.person(); // name: 大卫

具体实现如下

call

  1. call方法的第一个参数也是this的指向,后面传入的是一个参数列表
  2. call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
  3. call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。

es6实现

// 1. 保存this(当前函数)
// 2. 添加函数作为obj的属性
// 3. 执行该函数
// 4. 删除该属性(函数)
// 5. 返回函数执行结果
Function.prototype.call2 = function (obj, ...args) 
  obj = obj || window;
  const symbol = Symbol("fn");
  obj[symbol] = this;

  const result = obj[symbol](...args);

  delete obj[symbol];
  return result;
;
// example
let obj = 
  name: "大卫"
;
function myName(age, height) 
  console.log(this.name);
  console.log("年龄:", age);
  console.log("身高:", height);

myName.call2(obj, 23, 172);
// 大卫
// 年龄: 23
// 身高: 172

es5实现

Function.prototype.call3 = function (context) 
  var context = context || window;
  context.fn = this;

  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) 
    args.push("arguments[" + i + "]");
  

  var result = eval("context.fn(" + args + ")");

  delete context.fn;
  return result;
;

// example
var value3 = 2;
var obj3 = 
  value3: 1
;
function bar3(name, age) 
  console.log(this.value3);
  return 
    value: this.value3,
    name: name,
    age: age
  ;


bar3.call3(null); // 2 ,this执行为空时,默认为window对象

console.log(bar.call3(obj3, "kevin", 18));
// 1
// 
//    age: 18
//    name: "kevin"
//    value: 1
// 

apply

  1. apply接受两个参数,第一个参数是this的指向对象,第二个参数是函数接受的参数,以数组的形式传入

es6实现

// 1. 保存this(当前函数)
// 2. 添加函数作为obj的属性
// 3. 执行该函数
// 4. 删除该属性(函数)
// 5. 返回函数执行结果
Function.prototype.apply2 = function(obj, args) 
  let obj = obj || window;
  if (typeof this !== 'function') 
      throw new Error('Type Error')
  
   const symbol = Symbol('fn');
   obj[symbol] = this;
   
   const res = obj[symbol](...args);
   
   delete obj[symbol]
   return res;

// example
let obj = 
    name: "大卫"

function myName(age, height) 
    console.log(this.name)
    console.log("年龄:", age)
    console.log("身高:", height)

myName.apply2(obj, [23,172])

es5实现

Function.prototype.apply3 = function (context, arr) 
  var context = Object(context) || window;
  context.fn = this;

  var result;
  if (!arr) 
    result = context.fn();
   else 
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) 
      args.push("arr[" + i + "]");
    
    result = eval("context.fn(" + args + ")");
  

  delete context.fn;
  return result;
;

bind

  1. bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)
  2. 改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
  3. 由此我们可以首先得出 bind 函数的两个特点:
    • 返回一个函数
    • 可以传入参数

es6实现 利用apply

Function.prototype.bind2 = function (obj, ...args) 
  if (typeof this !== 'function') 
    throw new Error('Type error')
    
  const self = this;
  return function F() 
    // 考虑new的情况
    if (this instanceof F) 
      return new self(...args, ...arguments)
    
    return self.apply(obj, [...args, ...arguments])
  ;
;

// example
let obj = 
  name: "大卫"

function myName(age, height) 
  console.log(this.name)
  console.log("年龄:", age)
  console.log("身高:", height)

let newMyName = myName.bind2(obj, 22);
newMyName(172)

es5实现

Function.prototype.bind3 = function (obj) 
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);

  var fNOP = function () ;

  var fBound = function () 
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(this instanceof fNOP ? this : obj, args.concat(bindArgs));
  ;

  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
;

参考链接:
JavaScript深入之call和apply的模拟实现
Function.prototype.call()
Function.prototype.apply()
Function.prototype.bind()

以上是关于原生实现JavaScript的call()apply()bind()的主要内容,如果未能解决你的问题,请参考以下文章

原生实现JavaScript的call()apply()bind()

原生实现JavaScript的call()apply()bind()

JavaScript中改变this的指向方法(call和apple)

Javascript call和apply的模拟实现

javascript 原生bind方法实现

原生JavaScript实现callapply和bind