说说bind、call、apply 区别?

callapply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。

除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组。

let a = {
    value: 1
}
function getValue(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value)
}
getValue.call(a, 'yck', '24')
getValue.apply(a, ['yck', '24'])

bind和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind实现柯里化。

如何实现一个 bind 函数

对于实现以下几个函数,可以从几个方面思考

  • 不传入第一个参数,那么默认为 window
  • 改变了 this 指向,让新的对象可以执行该函数。那么思路是否可以变成给新的对象添加一个函数,然后在执行完以后删除?
Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

如何实现一个call函数

Function.prototype.myCall = function (context) {
  var context = context || window
  // 给 context 添加一个属性
  // getValue.call(a, 'yck', '24') => a.fn = getValue
  context.fn = this
  // 将 context 后面的参数取出来
  var args = [...arguments].slice(1)
  // getValue.call(a, 'yck', '24') => a.fn('yck', '24')
  var result = context.fn(...args)
  // 删除 fn
  delete context.fn
  return result
}

如何实现一个apply函数

Function.prototype.myApply = function (context) {
  var 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/apply

作用

用来改变函数内部 this 的指向。

特点

任何函数都可以调用这两个方法,说明它们是添加在函数原型上的方法(Function.prototype)。

console.dir(Function.prototype)

调用 callapply 的函数会立即执行。

callapply 的返回值就是函数的返回值。

var name = '一尾流莺'
var obj = {
  name: 'warbler',
}
function foo() {
  console.log(this.name);
  return 'success'
}
foo.call(obj) //=> warbler
console.log(foo.call(obj)); // => success

调用 callapply 指向 undefined 或者 null ,会将 this 指向 window

function foo() {
  console.log(this)
}
foo.call(undefined)
foo.call(null)
foo.apply(undefined)
foo.apply(null)

调用 callapply 指向一个值类型, 会将 this 指向由它们的构造函数创建的实例。

function foo() {
  console.log(this)
}
foo.call(11)
foo.call('11')
foo.call(true)

[图片上传失败...(image-15f2a1-1666010480746)]

调用 callapply 指向一个引用类型, 会将 this 指向这个对象。

我们声明了一个全局变量 name 和一个全局作用域下的函数 foo

var name = '一尾流莺'
var obj = {
  name: 'warbler',
}
function foo() {
  console.log(this.name)
}
foo() //=> 一尾流莺

这段代码很好理解,name 等价于 window.name , foo() 等价于 window.foo() ,我们打印出this.name,当前的 this 指向它的调用者 window, 也就是 window.name 得到 一尾流莺。 但是如果我想打印出 warbler 该怎么办呢?在 obj 里面再定义一个 obj.fn 么? 当然不需要, 我们只需要调用 call/apply 改变 this 的指向,指向 obj 这个对象就可以了。这个时候 this.name 等价于 obj.name ,就得到了 warbler 。

foo.call(obj) //=> warbler
foo.apply(obj) //=> warbler

call 和 apply的区别

除了传参的形式不同没什么区别。

传给fn的参数写法不同:

  • call 接收多个参数,第一个为函数上下文也就是 this ,后边参数为函数本身的参数。
  • apply 接收两个参数,第一个参数为函数上下文 this,第二个参数为函数参数只不过是通过一个 数组 的形式传入的。

只要记住 apply 是以 a 开头,它传给 fun 的参数是 Array,也是以 a 开头的,就可以很好的分别这两个函数了。

手写call/apply

手写call

var name = '一尾流莺'
var obj = {
  name: 'warbler',
}
function foo() {
  console.dir(this);
  return 'success'
}

/**
* Object()方法
* 如果传入的是值类型 会返回对应类型的构造函数创建的实例
* 如果传入的是对象 返回对象本身
* 如果传入 undefined 或者 null 会返回空对象
*/
Function.prototype._call = function(ctx, ...args) {
  // 判断上下文类型 如果是undefined或者 null 指向window
  // 否则使用 Object() 将上下文包装成对象
  const o = ctx == undefined ? window : Object(ctx)
  // 如何把函数foo的this 指向 ctx这个上下文呢
  // 把函数foo赋值给对象o的一个属性  用这个对象o去调用foo  this就指向了这个对象o
  // 下面的this就是调用_call的函数foo  我们把this给对象o的属性fn 就是把函数foo赋值给了o.fn
  //给context新增一个独一无二的属性以免覆盖原有属性
  const key = Symbol()
  o[key] = this
  // 立即执行一次
  const result = o[key](...args)
  // 删除这个属性
  delete o[key]
  // 把函数的返回值赋值给_call的返回值
  return result
}

手写 apply

之前讲过,callapply 的唯一区别就是传递参数的不同,所以我们只需要改一下对参数的处理,其它的和 call 一致就可以了。

var age = 10
var obj = {
  age: 20,
}
function foo(a, b) {
  console.dir(this.age + a + b);
}
// 只需要把第二个参数改成数组形式就可以了。
Function.prototype._apply = function(ctx, array = []) {
  const o = ctx == undefined ? window : Object(ctx)
  //给context新增一个独一无二的属性以免覆盖原有属性
  const key = Symbol()
  o[key] = this
  const result = o[key](...array)
  delete o[key]
  return result
}
foo(3, 4) // => 17
foo._apply(obj, [3, 4]) //=> 27

bind

作用

也是用来改变函数内部 this 的指向。

bind 和 call/apply 的区别

是否立刻执行

  • call/apply 改变了函数的 this 上下文后 马上 执行该函数。
  • bind 则是返回改变了上下文后的函数, 不执行该函数

返回值的区别:

  • call/apply 返回 fun 的执行结果。
  • bind 返回 fun 的拷贝,并指定了 funthis 指向,保存了 fun 的参数。
var name = '一尾流莺'
var obj = {
  name: 'warbler',
}

// this 指向调用者document
document.onclick = function() {
  console.dir(this); // => #document
}

// this 指向 obj
document.onclick = function() {
  console.dir(this); // => #Object{name:'warbler}
}.bind(obj)

手写bind

Function.prototype._bind = function(ctx, ...args) {
  // 下面的this就是调用_bind的函数,保存给_self
  const _self = this
  // bind 要返回一个函数, 就不会立即执行了
  const newFn = function(...rest) {
    // 调用 call 修改 this 指向
    return _self.call(ctx, ...args, ...rest)
  }
  if (_self.prototype) {
    // 复制源函数的prototype给newFn 一些情况下函数没有prototype,比如箭头函数
    newFn.prototype = Object.create(_self.prototype);
  }
  return newFn
}

你可能感兴趣的:(说说bind、call、apply 区别?)