你真的了解javascript中的bind吗?

111 阅读3分钟

bing使用场景

bind()函数会创建一个新的绑定函数, 在bind()被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。bind创建了一个绑定函数, 他的this执行被指定为第一个参数, 且不会再被改变。

function fn() {
  console.log(this.a)
}
const obj = {
  a: 'hello'
}
const fnA = fn.bind(obj)

fnA() // hello

const obj2 = {
  a: 'fuck'
}
// 绑定函数不能再改变this指向
fnA.call(obj2) // hello
// 即便是再次使用bind也是不能改变this指向的, 因为bind只生效一次!
const fnAA = fnA.bind(obj2)
fnAA() // hello
// 普通的函数可以指定运行时的this指向
fn.call(obj2)

js新手常常会犯的错误就是从对象里取出方法来执行, 期望方法中的this执行原来的对象

this.x = 9 // 浏览器中, this指向全局的window对象

let module = {
  x: 81,
  getX: function () { return this.x }
}

module.getX() // 81

let retrieveX = module.getX
// 丢失原本的对象
retrieveX() // 9

// 使用bind绑定作用域
var boundGetX = retrieveX.bind(module)
boundGetX() // 81

bind的基本作用如上所诉, 接着来讨论一下绑定函数, bind函数创建一个绑定函数, 绑定函数包装了原函数对象, 调用绑定函数通常会导致指向包装函数, 绑定函数有以下的内部属性:

  • [[BoundTargetFunction]] - 包装的函数对象
  • [[BoundThis]] - 在调用包装函数时始终作为 this 值传递的值。
  • [[BoundArguments]] - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表。
  • [[Call]] - 执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个this值和一个包含通过调用表达式传递给函数的参数的列表。

绑定函数也可以使用new运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的this值会被忽略,但前置参数仍会提供给模拟函数。

偏函数

bind()还可以用来创建偏函数, 它可以使得一个函数拥有预设的初始参数, 只要将这些参数(如果有的话)作为bind()的参数写在this后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。

function add (a, b) { return a+b }

let addThirtySeven = add.bind(null, 37)

addThirtySeven(5) // 37 + 5 = 42
addThirtySeven(5, 10) // 37 + 5 = 42   , 第二个参数被忽略

配合setTimeout

在默认情况下,使用window.setTimeout()时,this关键字会指向window(或 global)对象。当类的方法中需要 this 指向类的实例时,你可能需要显式地把 this 绑定到回调函数,就不会丢失该实例的引用。

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!'); // 有bind绑定后, 才能访问到petalCount
};

var flower = new LateBloomer();
flower.bloom();  // 一秒钟后,调用 'declare' 方法

作为构造函数使用的绑定函数

bind虽然能办到, 但不推荐这么做

绑定函数自动适应于使用new操作符去构造一个由目标函数创建的新实例。当一个绑定函数是用来构建一个值的,原来提供的this就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。

function Point(x, y) {
  this.x = x
  this.y = y
}
Point.prototype.toString = function () {
  return `${this.x}, ${this.y}`
}

let p = new Point(1, 2)
console.log(p.toString()) // 1, 2

let emptyObj = {}
let YAxisPoint = Point.bind(emptyObj, 0)
// let YAxisPoint = Point.bind(null, 0)

let axisPoint = new YAxisPoint(5)
console.log(axisPoint.toString()) // 0. 5
console.log(emptyObj.x) // undefined

axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new YAxisPoint(17, 42) instanceof Point; // true

// 注意: 此时的YAxisPoint可以作为普通函数来调用
YAxisPoint(13)
console.log(emptyObj.x, emptyObj.y) // 0 13

快捷调用

bind可以指定this值, 这意味着我们可以为某些调用提供快捷写法, 比如

let slice = Array.prototype.slice
slice.apply(1)

利用bind改写后:

var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice);

slice(1);

slice 是 Function.prototypeapply() 方法的绑定函数,并且将 Array.prototype 的 slice() 方法作为 this 的值。这意味着我们压根儿用不着上面那个 apply()调用了。