提示💡

将当前函数添加到context上, 调用并传入参数。

call()

ES5 写法

Function.prototype.myCall = function(context) {
  // 首先判断调用对象
  if(typeof this !== 'function') {
    throw new TypeError('error')
  }
  // 获取参数
  var args = [...arguments].slice(1); //第一个是对象,从第二个开始
  var result = null
  // 判断content(this指针要指向的对象)是否传入, 如果没有设置为window
  context = context || window
  // 将调用函数设置为对象的方法
  context.fn = this
  // 调用函数
  result = context.fn(...args);
  // 删除属性
  delete context.fn
  return result
}

ES6 写法

提示💡

  • 1.判断当前this是否为函数,防止Function.prototype.myCall() 直接调用
  • 2.context 为可选参数,如果不传的话默认上下文为 window
  • 3.为context 创建一个 Symbol(保证不会重名)属性,将当前函数赋值给这个属性
  • 4.处理参数,传入第一个参数后的其余参数
  • 4.调用函数后即删除该Symbol属性
Function.prototype.myCall = function(context = window, ...args) {
  // 首先判断调用对象
  if(typeof this !== 'function') {
    throw new TypeError('error')
  }
  //将调用函数设置为对象的方法
  const fn = Symbol(); // 避免重名
  context[fn] = this
  // 调用函数
  const result = context[fn](...args);
  // 删除属性
  delete context[fn]
  return result
}

测试代码

const obj = {};
 
const f = function () {
  return this;
};
 
f() === window // true
f.myCall(obj) === obj // true

apply()

提示💡

apply实现类似call,参数为数组。

ES5 写法

Function.prototype.myApply = function (context) {
  // 首先判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('error')
  }
  var result = null
  // 判断传入的对象是否存在,在浏览器中默认是window, 在node.js中默认是Object
  context = context || window
  // 把当前调用的函数赋值给传入对象的
  // context.fn 可以理解为: context.prototype.
  context.fn = this
  if (arguments[1]) {
    result = context.fn([...arguments[1]]) // 调用赋值的函数
  }
  delete context.fn
  return result
}

ES6写法

Function.prototype.myApply = function (context = window, args) {
  // 首先判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('error')
  }
  if (this === Function.prototype) return; // 用于防止 Function.prototype.myCall() 直接调用
  // 把当前调用的函数赋值给传入对象的
  // context.fn 可以理解为: context.prototype.
  const fn = Symbol();
  context[fn] = this
  let result;
  if (Array.isArray(args)) {
	result = context[fn](...args);
  } else {
	result = context[fn]();
  }
  delete context.fn
  return result
}

测试代码

function f(x, y){
  console.log(x + y);
}
 
f.myCall(null, 1, 1) // 2
f.myApply(null, [1, 1]) // 2

bind()

简单版本

Function.prototype.myBind = function (obj) {
    const fn = this;
    return function () {
        fn.apply(obj);
    };
};

测试代码

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};
 
var func = counter.inc.myBind(counter);
func();
counter.count // 1

进阶版本: 函数柯里化、new关键字

提示💡

  • 修改this指向
  • 动态传递参数(函数柯里化)
// 方式一:只在bind中传递函数参数
fn.bind(obj,1,2)()
 
// 方式二:在bind中传递函数参数,也在返回函数中传递参数
fn.bind(obj,1)(2)
  • 兼容new关键字
Function.prototype.myBind = function (context) {
    // 判断调用对象是否为函数
    if (typeof this !== "function") {
        throw new TypeError("Error");
    }
    // 获取参数
    const args = [...arguments].slice(1),
          fn = this;
    return function Fn() {
        // 根据调用方式,传入不同绑定值
        return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments)); 
    }
}

测试代码

function fn(...args) {
  console.log(this, args);
}
let obj = {
  myname: "张三",
};
 
const bindFn = fn.myBind(obj, 0); // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1, 2); // this指向obj
fn(1, 2); // this指向window
new bindFn(1, 2); // 兼容new关键字

扩展阅读