如何实现 new 、apply 、call 、bind 的底层逻

2021-09-26  本文已影响0人  PYFang

new 原理介绍

new 关键词的主要作用是执行一个构造函数、返回一个实例对象,根据构造函数的怀况中,来确定是否可以接受参数的传递。

function Person() {
  this.name = 'Jack'
}
let p = new Person()
console.log(p.name) // Jack

new 在生成实例的过程中进行的步骤:

当构造函数中有return一个对象的操作时:

function Person() {
  this.name = 'Jack'
  return{age:18}
}
let p = new Person()
console.log(p) // {age:18}
console.log(p.name) // undefined
console.log(p.age) // 18

上面代码可以看出,当构造函数return一个和this无关的对象时,new会直接返回这个新对象 ,而不是通过new执行步骤生成的this对象 。
注:构造函数必须返回是一个对象,如果返回不是对象,那么还是按照new的实现步骤返回新生成的对象。

function Person() {
  this.name = 'Jack'
  return 'tom'
}
let p = new Person()
console.log(p) // {name:'Jack'}
console.log(p.name) // Jack

new 被调用后大致做了哪几件事情

总结:new关键词执行之后总是会返回一个对象,要么是实例对象,要么是return语句指定的对象

apply & call & bind 原理介绍

call 、apply 和 bind 是挂在 Function 对象上的三个方法,调用这三个方法必须是一个函数

语法:

func.call(thisArg,param1,param2,...)
func.apply(thisArg,param1,param2,...)
func.bind(thisArg,param1,param2,...)

thisArgthis所指向的对象,后面的param1,param2为函数的function的多个参数,如果不需要参数可不写

相同点:都可改变 function 的 this 指向

call & apply 区别:
传参的写法不同

bind 与 call & apply区别:

借用

A对象有个getName的方法,B 对象也需要临时使用同样的方法,那么这个时候可以借用A 对象的getName方法
例:

let a = {
  name: 'jack',
  getName: function(msg) {
    return msg + this.name
  }
}
let b = {
  name: 'lily'
}
console.log(a.getName('hello~')) // hello~jack
console.log(a.getName.call(b, 'hello~')) // hello~lily
console.log(a.getName.apply(b, ['hello~'])) // hello~lily
let name = a.getName.bind(b, 'hello~')
console.log(name()) // hello~lily

apply & call & bind 的使用场景

1、判断数据类型

Object.prototype.toString几乎可以判断所有类型的数据

function getType(obj) {
  let type = typeof obj
  if (type !== 'object') {
    return type
  }
  return Object.prototype.toString.call(obj).replace(/^$/, '$1')
}

判断数据类型就是借用了Object.prototype.toString方法,最后返回用来判断传入的object字符串来确定最后的数据类型

2、类数组的借用方法

类数组因为不是真正的数组,所以没有数组类型上自带的种种方法,可以利用一些方法云借用数组的方法。

var arrayLike = {
  0: 'java',
  1: 'script',
  length: 2
}
Array.prototype.push.call(arrayLike, 'jack', 'lily')
console.log(typeof arrayLike) // object
console.log(arrayLike) // {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}
3、获取数组的最大 / 最小值

apply来实现数组中判断最大 / 最小值,apply直接传递数组作为调用方法的参数,也可以减少一步展开数组

let arr = [13, 6, 10, 11, 16]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(Math, arr)
console.log(max) // 16
console.log(min) // 6
继承
function Parent3() {
  this.name = 'parent3'
  this.play = [1, 2, 3]
}
Parent3.prototype.getName = function() {
  return this.name
}
function Child3() {
  // 第二次调用 Parent3()
  Parent3.call(this)
  this.type = 'child3'
}
Child3.prototype = new Parent3()
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3
var s3 = new Child3()
s3.play.push(4)
console.log(s3.getName()) //parent3
apply 和 call 的实现
Function.prototype.call = function(context, ...args) {
  var context = context || window
  context.fn = this
  var result = eval('context.fn(...args)')
  delete context.fn
  return result
}
Function.prototype.apply = function(context, args) {
  let context = context || window
  context.fn = this
  let result = eval('context.fn(...args)')
  delete context.fn
  return result
}

这两个方法是直接返回执行结果 ,而 bind 方法是返回一个函数,因此这里直接用 eval 执行得到结果

bind 的实现

bind 的实现思路基本和 apply 一样,但是在最后实现返回结果这里 bind 不需要直接执行,因此不再需要用 eval 而是需要通过返回一个函数的方式将结果返回之后再通过执行这个结果,得到想要的执行效果

Function.prototype.bind = function(context, ...args) {
  if (typeof this !== 'function') {
    throw new Error('this must be a function')
  }
  var self = this
  var fbound = function() {
    self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)))
  }
  if (this.prototype) {
    fbound.prototype = Object.create(this.prototype)
  }
  return fbound
}
总结
总结
上一篇下一篇

猜你喜欢

热点阅读