JavaScript 中的模拟实现

237 阅读4分钟

本文主要围绕着 call、apply、bind、instanceof、new 的模拟实现,除了会用,更需要对其有更深刻的理解。

call

call 修改 this 指向

  • 实例
var name = 'window name'

var dog = {
  name: 'dog name',
  getDog: function () {
    console.log(this.name, [...arguments])
  }
}

var cat = {
  name: 'cat name',
  getCat: function () {
    console.log(this.name, [...arguments])
  }
}

cat.getCat.call(dog, 1, 3); // "dog name"

dog.getDog.call(cat, 4, 5, 6); // "cat name"


  • 模拟实现
Function.prototype._call = function (context = window, ...args) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  
  const key = Symbol() // 为 context 创建唯一的属性 key, 防止与 context 内部本身属性冲突
  
	// 已知 this = cat.getCat = function () {...}
  // context[key] = this, 等价于下面
  // var dog = {
  //  name: 'dog name',
  //  getDog: function () {
	//  	console.log(this.name, [...arguments])
  //	},
  // 	getCat: function () {
  //		console.log(this.name, [...arguments])
  // 	}
	// }
  context[key] = this 
  
  // 下一步执行 context 新增实例[key] 新方法, 因为此事这个方法已经放到了 dog 身上,所以等价于 dog.getCat()
  // dog 对象中 this 指向它本身,所以打印出 this.name = 'dog name'
  // 在此需要对 this 指向有很深的理解,this 永远指向 最后调用 它的那个对象
  const result = context[key](...args)
  
  // 最后删除为 context 添加的新属性 key
  delete context[key]
  
  // 函数有可能会有返回值,也需要一并返回
  return result
}

apply

apply 修改 this 指向,与 call 区别仅在于参数传递方式

  • 实例
var name = 'window name'

var dog = {
  name: 'dog name',
  getDog: function () {
    console.log(this.name, [...arguments])
  }
}

var cat = {
  name: 'cat name',
  getCat: function () {
    console.log(this.name, [...arguments])
  }
}

cat.getCat.apply(this); // "window name"

cat.getCat.apply(dog, [1], 3); // "dog name" [1]

dog.getDog.apply(cat, [4, 5, 6]); // "cat name" [4, 5, 6]


  • 模拟实现
function isArray(target) {
  return '[object Array]' === Object.prototype.toString.call(target);
}

Function.prototype._apply = function (context = window, args) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  
  if (!isArray(args)){
    throw new TypeError('Error')
    return false
  }
  // 与 call 解析一致 区别仅在参数解析方式
  const key = Symbol()
  context[key] = this 
  const result = context[key](...args)
  delete context[key]
  return result
}

bind

bind 修改 this 指向,与 call、apply 区别在于,bind调用返回的是一个新函数

  • 实例
var name = 'window name'

var dog = {
  name: 'dog name',
  getDog: function () {
    console.log('getDog', this.name, [...arguments])
  }
}

var cat = {
  name: 'cat name',
  getCat: function () {
    console.log('getCat', this.name, [...arguments])
  }
}

cat.getCat.bind(this)(); // "window name"

cat.getCat.bind(dog, 10)(1, 3, 4); // "dog name" [10, 1, 3, 4]

dog.getDog.bind(cat)([4, 5, 6], 7); // "cat name" [[4, 5, 6], 7]

var factory = dog.getDog.bind(cat, 1)
var fac = new factory()
fac.name = 'fac name'
console.log(fac) //  { name : 'fac name'}

  • 模拟实现
Function.prototype._bind = function (context = window, ...args) {  
  const _this = this
  return function F() {
    // 需要知道:bind 返回的是一个函数,函数可能被 new, 作为构造函数被调用,如上实例 new factory()
    // 此事 this 失效,需要指向 fac 实例对象,
    if (this instanceof F) { 
      return new _this(...args, ...arguments) //等价于 new dog.getDog()
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

原型 / 构造函数 / 实例

为方便下面对 new instanceof 理解,需要提前对原型链有深的理解和实例、构造函数、原型对象之间的关系的理解

// 创建对象的4种方式
var o1 = {name: 'o1'}

var o2 = new Object({name: '02'})

function O3(){
  this.name = 'o3'
}
var o3 = new O3()

var P = {name: 'o4'}
var o4 = Object.create(P)
o4.__propto__ = P

// 实例:有 o1 o2 o3 o4
// 构造函数:有 O3 【任何一个函数只要被new使用了,就可称之为构造函数】
// 原型对象: O3.prototype
// 实例属性: o1.__proto__ o2.__proto__ o3.__proto__ o4.__proto__

// 实例 === 构造函数 
console.log(o3.__proto__.constructor === O3)

// 构造函数 === 原型对象
console.log(O3.prototype.constructor === O3)

// 实例 === 原型对象
console.log(o3.__proto__ === O3.prototype)

// 只有实例对象才有__proto__ 构造函数也有__proto__
// 只有构造函数有prototype属性


// 原型链
// 		原型链是由原型对象组成,每个对象都有 __proto__ 属性,
// 		指向了创建该对象的构造函数的原型,__proto__ 将对象连接起来组成了原型链。
// 		是一个用来 实现继承和共享属性 的有限的对象链

new

构造函数的实现创建对象

function myNew (context) {
   var obj = {} // 创建空对象
   obj.__proto__ = context.prototype // 设置原型链 //obj = Object.create(context.prototype)
   var result = context.call(obj) // 通过 call 为空对象添加属性 
   return result instanceof Object ? result : obj //返回值是否为对象,如果是对象,就使用构造函数的返回值,否则返回创建的对象
}

function Animation () {
  this.name = 'animation'
}

var cat = myNew(Animation)
console.log(cat)

instanceof

在看具体实现之前,你需要提前对原型链有深刻理解

  • 模拟实现
function _instanceof (left, right) {
  let leftValue = left.__proto__
  let rightValue = right.prototype
  while(true) {
    if (leftValue === null) {
      return false
    }
    if (leftValue === rightValue) {
      return true
    }
    leftValue = leftValue.__proto__ // 通过实例属性一层层往上找
  }
}

继承

call

子类无法继承父类的原型 prototype 的方法

function Animate(name) {
  this.name = name
}
Animate.prototype.getName = function() {
  return this.name
}
function Cat(food) {
  this.food = food
  Animate.call(this) // Animate prototype 方法调用不了
}
Cat.prototype.getFood = function () {
  this.food = food
}
var cat = new Cat('first')

原型链

子类可以引用父类的实例属性,但是需要绑定的时候,传值,否则为undefined,灵活度不高, 不同实例中属性值改变会相互影响 原因:原型链的对象不同实例中是共用的既:cat1.proto.name = 'cat1' 一改都改

function Animate(name) {
  this.name = name
}
Animate.prototype.getName = function() {
  return this.name
}
function Cat(food, name) {
  this.food = food
}
Cat.prototype = new Animate('cat');
Cat.prototype.constructor = Cat

Cat.prototype.getFood = function () {
  return this.food
}
var cat1 = new Cat('first')
var cat2 = new Cat('cat2')

组合模式-寄生

function Animate(name) {
  this.name = name
}
Animate.prototype.getName = function() {
  return this.name
}
function Cat(food, name) {
  this.food = food
  Animate.call(this, name)
}

Cat.prototype = new Animate(); // 1

//Cat.prototype = Object.create(Animate.prototype) //2
Cat.prototype.constructor = Cat

Cat.prototype.getFood = function () {
  return this.food
}
var cat = new Cat('first', 'cat')

ES6

class Animate {
  constructor(foo) {
    this.foo = foo
  }
  getFood() {
    console.log(this.foo)
  }
}
class Cat extends Animate {
  constructor(foo, bar) {
    super(foo)
    this.bar = bar
  }
}

工具

JSBin 模拟练习