手写JavaScript call/apply/bind方法

324 阅读1分钟

本文模拟实现JS中call/apply/bind方法。本文内容参考自面试官问:能否模拟实现JS的bind方法面试官问:能否模拟实现JS的call和apply方法两篇。

模拟实现 call 方法

func.call(thisArg, param1, param2)
  • call方法接受多个参数,第一个参数thisArg为替换func函数内this的对象,第二个参数以后都是传入函数的参数

  • 严格模式下,func内的 this 指向 thisArg

  • 非严格模式下,thisArg为null或undefined时,func内的this指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象

Function.prototype.callFn = function call(thisArg){
    var argsArray = [];
    var argumentsLength = arguments.length;
    for(var i = 0; i < argumentsLength - 1; i++){=
        argsArray[i] = arguments[i + 1];
    }
    return this.applyFn(thisArg, argsArray);
}

模拟实现 apply 方法

func.apply(thisArg, [argsArray])
  • apply方法接受两个参数,第一个参数thisArg为替换func函数内this的对象,第二个参数为传入函数的数组

  • 严格模式下,func内的 this 指向 thisArg

  • 非严格模式下,thisArg为null或undefined时,func内的this指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象

function getGlobalObject(){
  return this;
}

Function.prototype.applyFn = function apply(thisArg, argsArray) {
  if (typeof this !== 'function') {
    throw new TypeError(this + 'is not a function.');
  }
  if (typeof argsArray === 'undefined' || argsArray === null) {
    argsArray = [];
  }
  if (argsArray !== new Object(argsArray)) {
    throw new TypeError('CreateListFromArrayLike called on non-object.')
  }
  if (typeof thisArg === 'undefined' || argsArray === null) {
    thisArg = getGlobalObject();
  }
  thisArg = new Object(thisArg);
  var fn = Symbol('fn');
  thisArg[fn] = this;
  var result = thisArg[fn](...argsArray);
  delete thisArg[fn];
  return result;
}

var obj = { name: 'wwx' };
function a (m, p, q) {
  console.log(this.name);
  console.log(m, p, q);
}
var applyResult = a.apply(obj, [1, 2, 3]);

模拟实现 bind 方法

console.log(typeof Function.prototype.bind); // function
console.log(typeof Function.prototype.bind());  // function
console.log(Function.prototype.bind.name);  // bind
console.log(Function.prototype.bind().name);  // bound
var obj = { name: 'wwx' };
function original(a, b) {
    console.log(this.name);
    console.log(a, b);
    return false;
}
var bound = original.bind(obj, 1);
var boundResult = bound(2);  // 'wwx', 1, 2
console.log(boundResult);  // false
console.log(original.bind.name);  // 'bind'
console.log(original.bind.length);  // 1
console.log(original.bind().length);  // 2 返回original函数的行参个数
console.log(bound.name);  // 'bound original'
console.log((function(){}).bind().name);  // 'bound '
console.log((function(){}).bind().length);  // 0

第一版:修改this指向,合并参数

Function.prototype.bindFn = function bind(thisArg) {
  if (typeof this !== 'function') {
    throw new TypeError(this + 'must be a function.')
  }
  // 存储函数本身
  var self = this;
  var args = [].slice.call(arguments, 1);
  var bound = function() {
    var boundArgs = Array.from(arguments);
    return self.apply(thisArg, [...args, ...boundArgs]);
  }
  return bound;
}
var obj = { name: 'wwx' };
function original(a, b) {
  console.log(this.name);
  console.log(a, b);
}
var bound = original.bind(obj, 1);
bound(2);

第二版:考虑new

var obj = {
  name: 'wwx'
}
function original(a, b) {
  this.num1 = a
  this.num2 = b;
}
var bound = original.bind(obj, 1);
var newBoundResult = new bound(2);
console.log('newBoundResult:', newBoundResult);
// newBoundResult: original { num1: 1, num2: 2 }
// this指向了new bound()生成的新对象。
Function.prototype.bindFn = function bind(thisArg) {
  if (typeof this !== 'function') {
    throw new TypeError(this + 'must be a function.')
  }
  var self = this;
  var args = [].slice.call(arguments, 1);
  var bound = function () {
    var boundArgs = Array.from(arguments);
    var finalArgs = [...args, ...boundArgs];
    if (this instanceof bound) {
      if (self.prototype) {
        function Empty() {}
        Empty.prototype = self.prototype;
        bound.prototype = new Empty();
      }
      var result = self.apply(this, finalArgs);
      var isObject = typeof result === 'object' && result !== null;
      var isFunction = typeof result === 'function';
      if (isObject || isFunction) {
        return result;
      }
      return this;
    } else {
      return self.apply(thisArg, finalArgs);
    }
  }
  return bound;
}

bound函数的namelength通过Object.defineProperties来实现:

Object.defineProperties(bound, {
    'length': {
        value: self.length
    },
    'name': {
        value: 'bound ' + self.name
    }
})

参考文章

32个手写JS,巩固你的JS基础

面试官问:能否模拟实现JS的bind方法

面试官问:能否模拟实现JS的call和apply方法