被忽略的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
,只不过里面的this
是 obj1
并且将此函执行。(返回了一个箭头函数)
bar.call(obj2)
即执行上面返回的那个箭头函数,因为箭头函数的this
无法被修改(即使是new
也不可以),因此这个箭头函数里面的this
还是foo
被调用时并指定this
是obj1
的那个
箭头函数的this指向
【要知道的】:箭头函数的this取值,规则非常简单,因为this在箭头函数中,可以看做一个普通变量。
箭头函数没有自己的this值,箭头函数中所使用的this都是来自函数作用域链,它的取值遵循普通普通变量一样的规则,在函数作用域链中一层一层往上找。
而且箭头函数的this不是在调用的时候确定的,而是在定义的时候确定的:
- 箭头函数外层是否是函数:则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机制,那么或许更建议你:
- 只是用词法作用域,并完全抛弃错误的this风格的代码
- 完全采用this风格,在必要时使用
bind(...)
,尽量避免使用self = this
和箭头函数
当然,这两种代码风格的程序都可以正常运行,但是在同一个函数中或者同一个程序中混合使用这两种风格通常会是代码更难维护,并且也更难编写