call、apply、bind

116 阅读3分钟
一、call、apply的区别  接受的参数不一样,call是一个一个参数接受,apply是接受一个数组
二、call、apply和bind的区别
  1、三者都是用来改变函数的this对象的指向
  2、三者第一个参数都是this要指向的对象
  3、三者都可以利用后续参数传参
  4、bind返回对应函数,便于稍后调用;apply、call立即调用

1、Function.prototype.call()方法、Function.prototype.apply()方法

// 数组追加
var arr1 = [12, 'foo', {name: 'joe'}, -2]
var arr2 = ['Doe', 55, 100]
Array.prototype.push.apply(arr1, arr2) // arr1 [12, 'foo', {name: 'joe'}, -2, 'Dow', 55, 100]

// 获取数组中最大最小值
var numbers = [5, 458, 123, -12]
console.log(Math.max.apply(Math, numbers)) // 458
console.log(Math.min.call(Math, ...numbers)) // -12

//验证是否为数组
function functionIsArray (obj) {
  return Object.prototype.toString.call(obj) === '[object Array]'
}

// 定义一个log方法,让它可以代理console.log方法
function log () {
  console.log.apply(console, arguments)
}
log(1, 2, 3) // 1 2 3

//给每个log消息添加一个“(app)”的前缀
function log () {
  // arguments是个类(伪)数组,通过array.prototype.slice.call转化为标准数组,再使用数组方法unshift
  var args = Array.prootype.slice.call(arguments)
  args.unshift('(app)')
  console.log.apply(console, args)
}
log('hello world') // (app) hello world

2、Function.prototype.bind()方法

bind()方法,主要就是将函数绑定到某个对象上
bind()会创建一个函数,该函数体内的this对象的值,会被绑定到传入bind()的第一个参数
列如,f.bind(obj),实际可以理解为obj.f(),这时,f函数体内的this指向obj

列子1

var a = {
  b: function () {
    var func = function () {
      console.log(this.c)
    }
    func();
  },
  c: 'Hello!'
}
a.b(); // undefined  ==> 这里的this指向的是全局作用域,所以返回undefined

var a = {
  b: function () {
    var that = this;
    var func = function () {
      console.log(that.c)
    }
    func();
  },
  c: 'Hello!'
}
a.b(); // Hello! ==> 可以通过赋值的方式将this赋值给that

var a = {
  b: function () {
    var func = function () {
      console.log(this.c)
    }.bind(this);
    func();
  }
  c: 'Hello!'
}
a.b(); // Hello! ==> 通过bind方式绑定this
var a = {
  b: function () {
    var func = function () {
      console.log(this.c)
    }
    func.bind(this)();
  },
  c: 'Hello!'
}
a.b(); // Hello! ==> 通过bind的方式绑定this

例子2

function f (y, z) {
  return this.x + y + z
}
var m = f.bind({x: 1}, 2)
console.log(m(3))
// 6 ==> bind()方法会把它的第一个实参绑定给 f函数体内的this,所以this指向{x: 1}对象
从第二个参数起,会依次传递给原函数,第二个参数2
最后调用m(3),最后一个参数是3

例子3

var a = document.write
a('hello'); // Error ==> 直接调用a,this指向的global或window对象,所以会报错
a.bind(document)('hello') // hello
a.call(document, 'hello') // hello
==> 通过bind或者call方式绑定thisdocument对象,即可正常调用

例子4:偏函数,使用bind的方式预定义参数

function list () {
  // arguments是类数组,使用Array.prototype.slice.call转换为真正的数组,可调用数组方法
  return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3) // [1, 2, 3]
// a的第一个参数undefined表示this指向,第二个参数10表示list中真正的第一个参数,依次类推var a = list.bind(undefined, 10)
var list2 = a(); // [10]
var list3 = a(1, 2, 3) // [10, 1, 2, 3]

new调用绑定函数时,bind的第一个参数无效

// 使用bind返回的结果是个function,function可被new调用。
// 当new调用绑定函数时,bind的第一个参数无效
function Person (name, age) {
  this.name = name;
  this.age = age;
}
var _Person = Person.bind({});
var p = new _Person('zs', 30); // Person {name:'zs', age: 30}

bind配合setTimeout,setInterval

// setTimeout、setInterval 很容易把this指向window
// 当使用对象的方法时,需要this引用对象,需要显式地把this绑定到回调函数以便继续使用对象
var canvas = {
  render: function () {
    this.update();
    this.draw();
  },
  update: function() { // ... },
  draw: function() { // ... }
}
window.setInterval(canvas.render, 100/60) // render方法中的this其实被指向了window
window.setInterval(canvas.render.bind(canvs), 1000) // 使用bind,显式地把this绑定到回调函数
// 类似的还有dom的事件监听

bind实现

// bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们绑定的this就需要被忽略
// 处理构造函数场景下的兼容
Function.prototype.bind = Function.prototype.bind || function (context) {
  if (typeof this !== 'function') { // 确保调用bind方法的一定要是一个函数
    throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
  }
  var args = Array.prototype.slice.call(arguments, 1);
  var self = this;
  var F = function() {};
  F.prototype = this.prototype;
  var bound = function () {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return self.apply(this instanceof F ? this : context || this, finalArgs);
  }
  bound.prototype = new F();
  return bound;
}