Skip to content

模拟实现new、bind、call和apply #1

@impeiran

Description

@impeiran

new

new之后发生了这些事:新对象的原型prototype等于目标的原型prototype,构造函数constructor指向目标,并执行目标函数,隐式返回新对象。

function _new (target, ...args) {
  // 利用该api,以target的prototype为原型生成对象,并包含constructor
  const context = Object.create(target.prototype);
  const ret = target.apply(context, args)
  // 若无显式返回对象 则返回context
  return ret instanceof Object ? ret : context;
}

bind

将函数绑定另一个上下文环境,改变其内部的this指向,同一函数在进行多次bind后,只有第一次bind时能生效,后续无法再改变其上下文。

在日常开发中,有时也有需要不侵入原函数的实现来改变this指向的场景。因此以下模拟一个bind的实现。

function bind (fn, context) {
  return function bindWrapper () {
    return fn.apply(context, arguments);
  }
}

apply

将函数以首个参数作为上下文(改变this),执行后续参数数组,并返回结果。

如果要模拟实现,且不使用bind的话,原理上可以将该函数以一个特殊Key值挂载到指定上下文对象上,然后调用后进行delete即可

Function.prototype._apply = function _apply (context, args) {
  const fn = this;

  // context为null时,应将上下文设为window
  context = context || window;

  // 随机生成一个key值,可自行替换生成的随机规则,这里暂采用时间戳
  const key = +new Date();

  context[key] = fn;

  // 用eval处理参数的传递
  // 保存调用结果,后续返回
  const result = eval('context[key](' + args.map((item, idx) =>{
    return `arguments[1][${idx}]`
  }).join(',') + ')');

  delete context[key];

  return result;
};
call

作用与apply一样,区别就是传递的参数,不以数组的形式,而是以正常传值形式

Function.prototype._call = function _call () {
  const fn = this;
  const context = arguments[0] || window;

  // 截取arguments
  const args = new Array(arguments.length - 1);
  for (let i = 1; i < arguments.length; i++) {
    args[i - 1] = `arguments[${i}]`
  }

  const key = +new Date();

  context[key] = fn;

  const result = eval('context[key](' + args.join(',') + ')');

  delete context[key];

  return result;

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions