(六)this绑定的例外

被忽略的this

如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则

function foo () {
  console.log(this)
}
var a = 2
foo.call(null) // 2

什么情况下会在call、apply传入null呢?

常见做法就是使用apply来“展开”一个数组

function foo (a, b) {
  console.log("a:" + a, "b:" + b)
}
// 把数组展开成参数
foo.apply(null, [2, 3]) // a:2 b:3
// 使用bind进行柯里化
var bar = foo.bind(null, 2)
bar(3) // a:2 b:3

上面两种方法都需要传入一个参数当做this的绑定对象,如果函数不关心this,而我们还是需要传入一个占位值,此时传入null就是一个不错的选择

es6中可以使用...来展开一个数组,比如foo(...[1,2,3])foo(1,2,3)是一样的,这样也可以避免了不必要的this绑定问题

更安全的this

然而,如果我们总是使用null来忽略this绑定的话,可能会产生一些副作用。如果某个函数确实使用到了this(比如第三方库中的一个函数),那默认绑定规则会把this绑定到全局对象(浏览器中这个对象时window),这很有可能会导致不可预计的后果(比如修改全局对象)

call、apply就会忽略传入的null或undefined而使用默认绑定规则

我们现在需要做的就是把this绑定到一个对象上,且这个对象不会对你的程序产生任何副作用。通常这个对象是一个空的非委托对象。如果我们在忽略this绑定时总是传入一个DMZ对象,那就什么都不用担心啦,因为任何对于this的使用都会被限制在这个空对象中,而不会对全局对象产生任何影响。

在js中创建一个空对象最简单的方法都是Object.create(null)。虽然Object.create(null){}很像,但是前者并不会创建Object.prototype这个委托。所以它比{}更空

function foo (a, b) {
  console.log("a:" + a, "b" + b)
}
// 我们创建的空对象
var MDZ = Object.create(null)
// 把数组展开成参数
foo.apply(MDZ, [2, 3]) // a: 2, b: 3
// 是用bind进行柯里化
var bar = foo.bind(MDZ, 2)
bar(3) // a: 2, b: 3

间接引用

我们很有可能(有意或无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定的规则。
【间接引用容易赋值时发生】

function foo () {
  console.log(this.a)
}
var a = 2
var o = {a: 3, foo: foo} // 假设 o.foo = 0x99
var p = {a: 4}
o.foo() // 3 隐式绑定
(p.foo = o.foo)() // 2 注意这里是 0x99()

这里p.foo = o.foo 的返回值是目标函数的引用,直接调用目标函数引用,即调用位置是foo()而不是o.foo()p.foo()。因此这里使用的是默认绑定

注意:对于默认绑定来说,决定this绑定对象是undefined还是全局对象,取决于函数体是否处于严格模式,而不是函数的调用位置是否在严格模式下调用

软绑定

this词法之箭头函数

ES6中介绍了一种无法使用上述四种规则的特殊函数:即箭头函数
箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符 =>定义的。

箭头函数是根据外层(函数或者全局)作用域来决定this的

function foo(){
  // 返回一个箭头函数
  return (a) => {
    console.log(this.a)
  }
}
var obj1 = {
  a: 2
}
var obj2 = {
  a: 3
}
var bar = foo.call(obj1) // 将foo里面的this更改为obj1
bar.call(obj2) // 2

bar其实就相当于foo,只不过里面的thisobj1并且将此函执行。(返回了一个箭头函数)
bar.call(obj2) 即执行上面返回的那个箭头函数,因为箭头函数的this无法被修改(即使是new也不可以),因此这个箭头函数里面的this还是foo被调用时并指定thisobj1的那个

箭头函数的this指向

【要知道的】:箭头函数的this取值,规则非常简单,因为this在箭头函数中,可以看做一个普通变量。

箭头函数没有自己的this值,箭头函数中所使用的this都是来自函数作用域链,它的取值遵循普通普通变量一样的规则,在函数作用域链中一层一层往上找。

而且箭头函数的this不是在调用的时候确定的,而是在定义的时候确定的:

  1. 箭头函数外层是否是函数:则this就是外层函数中的this

【箭头函数最常用于回调函数中,例如事件处理器或定时器】

function foo () {
  setTimeout(() => {
    // 这里的this在词法上继承自foo()
    console.log(this.a)
  })
}
var obj = {
  a:2
}
foo.call(obj) // 2

箭头函数可以向bind(…)一样确保函数的this被绑定到指定对象上,此外,其重要性还体现在了,它用更常见的词法作用域取代了传统中的this机制,而这种机制,其实我们在es6之前就已经在使用了,只不过样式有所不用而已

function foo () {
  var self = this
  setTimeout(function(){
    console.log(self.a)
  },100)
}
var obj = {
  a: 2
}
foo.call(obj) // 2

虽然 self = this和箭头函数都可以取代bind(...),但是从本质上来说,他们想更取代的都是this机制

如果是经常编写this风格的代码,且绝大部分都会使用到self = this 或者 箭头函数来否定this机制,那么或许更建议你:

  1. 只是用词法作用域,并完全抛弃错误的this风格的代码
  2. 完全采用this风格,在必要时使用bind(...),尽量避免使用 self = this 和箭头函数

当然,这两种代码风格的程序都可以正常运行,但是在同一个函数中或者同一个程序中混合使用这两种风格通常会是代码更难维护,并且也更难编写

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值