js call、apply、bind的实现

三个函数实际上是老生常谈了,网上也有太多关于他们的实现。开始只是理解别人的实现,实际上是似懂非懂。只有本身实现出来,而且逐行理解,才是真的懂了。 文章从三个函数的使用入手,结合使用场景逐步实现。

call的使用

window.name = 'window'
var obj = {
 name: 'obj'
}
function getName(p1, p2) {
 console.log(p1, p2)
 console.log(this.name)
}
getName('str1', 'str2') 
getName.call(obj, 'str1', 'str2') 

// 函数运行结果能够思考一下。
复制代码

怎么记住call呢,其实就是getName这个方法执行了,不是window去执行,是call括号内的参数去执行。数组

能够联想到一个很典型的使用场景 [].prototype.shift.call(arguments),起初很不理解这种写法,后来一想,其实就是 arguments 不是数组,它没有shift方法能够直接来用,就把数组的shift方法拿来用。app

call的实现

1 明确是谁调用call,答案,是函数。函数

2 call接收的参数是什么?第一个参数是要改变的this指针,也就是上面说到了,是谁去执行这个函数。若无指定,默认为windowthis

3 call接收的第二个,第三个,等等,参数,是用来作什么的?答,就是做为调用call的那个函数所需的参数。spa

function myCall(context) {
  // 1
  if (typeof this !== 'function'){
	throw new TypeError('error')
  }
  // 2
  context = context || window
  // 3
  context.fn = this
  // 4
  const args = [...arguments].slice(1)
  // 5
  const result = context.fn(...args)
  // 6
  delete context.fn
  return result
}
Function.prototype.myCall = myCall

getName.myCall(obj, 'str1', 'str2')
复制代码
  • 1 在myCall方法实现体中的this是什么? 回想一下,谁调用函数,this就是谁。那谁来调用call方法呢,是函数,因此这个this,就是调用call方法的函数,对于本例子来讲,就是getName。若是不是函数,直接报错。
  • 2 myCall方法若是没有参数,那么默认为window
  • 3 本例中的context是传进来的obj对象,给这个对象添加一个方法,这个方法,就是this,也就是getName
  • 4 获取参数 ,在本例中,对应的是 'str1', 'str2'
  • 5 结果很明确了,就是 fn是方法,是getName。context是传进来的obj对象,那么就是实现了,obj调用getName方法,参数为'str1', 'str2'。(谁调用方法,this就指向谁)
  • 6 删除对象上函数,返回结果

apply的使用

apply使用与call大致一致,只是接受参数的方法不一样。call能够接收多个参数。apply接收的第一个参数是this,第二个参数是 所需参数所组成的数组prototype

window.name = 'window'
var obj = {
 name: 'obj'
}
function getName(p1, p2) {
  console.log(p1, p2)
  console.log(this.name)
}
getName('str1', 'str2') 
getName.apply(obj, ['str1', 'str2'])
复制代码

apply的实现

Function.prototype.myApply = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  var result
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}
复制代码

和上述的call实现基本相似,就参数处理有些不一样,再也不赘述。指针

bind的使用

为何有call apply后还要有个bind ? 当咱们须要绑定一个点击事件的时候,就改变回调函数的this,怎么破?由于 call apply都是当即执行了,因此bind登场。看一下下面这个例子吧。code

var obj = {
  name: 'obj'
}

document.addEventListener('click',myClick.bind(obj,'p1','p2'),false);

function myClick(p1,p2){
  console.log(this.name, p1, p2)
}
复制代码

MDN的解释是:bind()方法会建立一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以建立它时传入 bind()方法的第一个参数做为 this,传入 bind() 方法的第二个以及之后的参数加上绑定函数运行时自己的参数按照顺序做为原函数的参数来调用原函数。cdn

注意:bind方法的返回值是函数对象

bind的实现

bind()最简单的用法是建立一个函数,使这个函数不论怎么调用都有一样的this值。

由于bind返回值是函数,那么函数除了直接运行以外,还能够做为构造函数放在new 操做符以后,因此bind的实现就要把这种状况考虑进去。

前置知识点 new

任何函数均可以做为构造函数,放在 new 以后使用,那么new的过程是怎么样的呢?大致分为如下几步,具体不深究。

  1. 生成空对象
  2. 空对象的原型属性指向构造函数的原型对象
  3. 给这个空对象添加属性
  4. 返回这个对象。

上面所说的那个空对象就是构造函数内部的this,而且 对于 new 的状况来讲,不会被任何方式改变 this

window.name = 'window'
var obj = {
  name: 'obj'
}
function Fun(p1,p2){
  console.log(this)
  console.log(this.__proto__ === Fun.prototype)
  console.log(this.name)
  this.a = p1
  this.b = p2
  console.log(this)
}
var c = new Fun('str1', 'str2')
console.log(c)
复制代码

运行结果以下:

再来看一下,直接执行Fun的返回结果,代码不作修改,直接执行 Fun('str1', 'str2')

两次结果的不一样,本文不展开叙述。那么举这个例子是想说明什么呢? 答 : 函数充当构造函数和普通函数,运行时内部的this指向不一样。 接下来看bind函数的模拟实现

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
const _this = this
const args = [...arguments].slice(1)
// 返回函数
return function F() {
  // 1 判断是否用做构造函数
  if (this instanceof F) {
    return new _this(...args, ...arguments)
  }
  // 2 用做普通函数
  return _this.apply(context, args.concat(...arguments))
 }
}
// 仍是用上述举例子
window.name = 'window'
var obj = {
  name: 'obj'
}
function Fun(p1, p2){
  this.a = p1
  this.b = p2
  console.log(this.name)
  console.log(p1, p2)
}
var f1 = Fun.bind(obj, 'str1')
f1('str2')
复制代码

运行结果以下,可见 改变了fn1函数的this指向

再来看一下 ,去掉 f1('str2') ,换成以下语句的运行结果

// f1('str2')
var b = new f1('str2')
console.log(b)
复制代码

没有逐行的说明,不过对于内部实现,有new的前置介绍和注释,相信开始的call实现代码都看懂了的话,这个bind方法也会一目了然

相关文章
相关标签/搜索