手写实现call/apply/bind/new/Object.create原理

899 阅读3分钟

一、call原理

定义:使用一个指定的this值和单独给出的一个或者多个参数来调用一个函数

语法:function.call(thisArg, arg1, arg2, ...)

原理分析:其实就是将要调用的函数作为指定对象的一个属性,然后执行指定对象中的属性(方法),最后将该对象上的属性删除,将执行的结果返回。如果未指定对象(如:传入null、undefined),那默认就指向全局对象(window)

具体实现如下:

Function.prototype.call2 = function (context) {
  context = context || window 
  context.fn = this // this就是要执行的函数
  var args = []
  for (var i = 1; i<arguments.length; i++) {
    args.push('arguments['+i+']')
  }
  var result = eval('context.fn('+args+')')
  delete context.fn
  return result
}

二、apply原理

apply实现原理同上,唯一的区别就是,传入的参数是数组

Function.prototype.apply2 = function (context, arr) {
  context = context || window
  context.fn = this
  var result
  if (!arr) {
    result = context.fn()
  } else {
    var args = []
    for (var i = 0; i < arr.length; i++) {
      args.push('arguments['+i+']')
    }
    result = eval('context.fn('+args+')')
  }
  delete context.fn
  return result
}

三、bind原理

定义:bind方法会创建一个新的函数,在bind被调用时候,这个新的函数的this指向为bind的第一个参数,而其余参数将作为新函数的参数,供调用时使用

语法:function.bind(thisArg[, arg1[, arg2[, ...]]])

原理分析:

  1. bind方法会返回一个新的函数
  2. 新函数执行时,它的this指向bind函数的第一个参数
  3. 新函数执行时,会将bind函数除第一参数之外的其余参数,与调用新函数时传入的参数合并,供调用的时候使用

初步实现如下:

Function.prototype.bind2 = function (context) {
  var fn = this
  var arg = Array.prototype.slice.call(arguments, 1)
  var fbound = function () {
    var bindArg = Array.prototype.slice.call(arguments)
    fn.apply(context, arg.concat(bindArg))
  }
  return fbound
}

bind还有一个特性,就是如果bind返回的函数(fbound)被当成构造函数使用,那么该函数内部(fbound)的this指向的是它的实例,而忽略bind传入的context

举个例子:

var value = 2
var foo = {
    value: 1
}
function bar(name, age) {
    this.habit = 'shopping';  // 因为此时this指向实例obj
    console.log(this.value);
    console.log(name);
    console.log(age);
}
bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy')
var obj = new bindFoo('18');
console.log(obj.habit);
console.log(obj.friend);

//输出: 
// undefined
// daisy
// 18
// shopping
// kevin

最终实现如下:

Function.prototype.bind2 = function (context) {
  var self = this
  var args = Array.prototype.slice.call(arguments, 1) // 将类数组转换成数组
  
  var fNOP = function () {}

  var fbound = function () {
    // 作为构造函数,this就是fbound的一个实例,而self指向绑定函数(构造函数)
    // 作为普通函数,this指向window,self指向绑定函数
    var bindArg = Array.prototype.slice.call(arguments)
    return self.apply(this instanceof self ? this : context, args.concat(bindArg))
  } 

  fNOP.prototype = self.prototype;
  fbound.prototype = new fNOP()

  // 这里不这样写的原因是,如果一旦改了fbound.prototype,那么绑定函数的原型也就被修改了
  // fbound.prototype = self.prototype;

  return fbound
}

四、new 原理

特性分析:

  1. new XXX()会返回一个新的对象(那不就是new一个空对象么 如:var obj = new Object())
  2. 新的对象能访问构造函数的属性(哦,那就是使用var result = XXX.apply(obj)这样obj就有构造函数中的属性了)
  3. 新的对象的能访问构造函数中原型属性(那不就是obj._proto_ = XXX.prototype么)
  4. 如果构造函数中返回的是一个对象,那么new的结果就是该对象(判断result结果)

举个例子,验证一下第4点

function Person () {
  this.name = 'zbq'
  this.age = 18
  return {
    name: 'lisi',
    age: 12
  }
}

var obj = new Person()
console.log(obj)
// { name: 'lisi', age: 12 }

具体实现如下:

function objectFactory() {
  // 参数中第一个参数为构造函数
  var Constructor = [].shift.call(arguments)
  var obj = new Object()
  obj.__proto__ = Constructor.prototype

  var result = Constructor.apply(obj, arguments)
  return typeof result === 'object' ? (result || obj) : obj  // 由于typeof null => 'object',如果result为null,就返回obj 
}

五、Object.create实现

定义:Object.create静态方法以一个现有对象作为原型,创建一个新对象

语法

Object.create(proto, propertiesObject) 

参数分析:
proto:新创建对象的原型对象
propertiesObject(可选):如果该参数被指定且不为 [`undefined`]则该传入对象[可枚举的自有属性]将为新创建的对象添加具有对应属性名称的属性描述符。这些属性对应于 [`Object.defineProperties()`]的第二个参数。
Object.myCreate = function (proto, propertyObject = undefined) {
  if (propertyObject === null) {
    // 这里没有判断propertyObject是不是原始包装对象
    throw 'TypeError'
  } else {
    function Fn () {}
    Fn.prototype = proto
    const obj = new Fn()
    if (propertyObject !== undefined) {
      Object.defineProperties(obj, propertyObject)
    }
    if (proto === null) {
      // 建立一个没有原型对象的对象,Object.create(null)
      obj.__proto__ = null
    }
    return obj
  }
}

六、参考