最近在读《你不知道的JavaScript》上卷,看到了关于this的相关文章,发现在日常学习的时候自己对this 的理解真的很匮乏,读完书才发现其实this内部有一套属于自己的绑定规则。
前置知识: 调用位置
在学习this 的绑定规则之前,首先要理解一个概念就是调用位置。从字面上理解的就是指函数被调用的位置,但是如果遇到函数嵌套调用比较深或者某些编程模式隐藏了真正的调用位置就没有办法判断真正的调用位置了。
那么这个时候就要靠调用栈来判断
判断如下函数:
function foo () {
console.log('foo')
}
对于foo函数,当前的调用栈是 :window → foo ,所以调用的位置应该是全局作用域
如果不理解的话可以在浏览器环境下做如下尝试
function foo () {
console.log('foo')
}
foo()
window.foo()
打印结果都是foo
那么对于嵌套的函数
function foo () {
console.log('foo')
bar()
}
function bar() {
console.log('bar')
}
bar的调用栈应该是:window→foo→bar那么bar 的调用位置应该是foo
了解this绑定的朋友可以发现上述找调用栈的过程其实就是在找this 指向的内容。接下来就具体介绍一下this 的绑定规则。
默认绑定
尝试以下代码:
let name = 'tianqin'
function foo () {
console.log(this.name)
}
foo()
console.log(window.name)
看一下打印结果:
可以发现声明在全局作用域中的变量成了全局对象的一个属性,而且本质上也是同一个东西,这个时候this指向了全局对象(window)
在这段代码中,foo函数的调用不携带任何修饰,直接使用的,所以只能使用默认绑定默认绑定在了window对象上,
注意:
严格模式下,全局对象无法使用默认绑定规则,this会绑定到undefined
隐式绑定
当函数引用有上下文对象时,隐式绑定规则会把函数调用的this绑定到这个上下文对象上。这个上下文对象可以理解为被谁包含或者被谁拥有。
观察如下代码:
function foo () {
console.log(this.name)
}
let obj = {
name: 'obj',
foo: foo
}
obj.foo()
打印结果应该是obj,因为foo现在的上下文对象是obj,也就是说foo现在被foo所拥有 obj.name 与 this.name 此时是等效的,但是通过隐式绑定判断this 的指向并不是一劳永逸的,因为存在绑定丢失的问题
隐式绑定的绑定丢失问题
- 隐式绑定存在绑定丢失的现象
分析如下代码:
let name = 'window'
function foo () {
name: 'foo'
console.log(this.name)
}
let obj = {
name: 'obj',
foo: foo
}
var baz = obj.foo
baz()
这段代码最后输出的应该是window,因为此时this指向了window,准确的说使用了默认绑定规则
原因就在于 var baz = obj.foo baz是obj.foo的一个引用,但是baz真正引用的是foo函数本身,那么来看baz,它此时是一个不带任何修饰的函数调用,此时应该使用默认绑定,严格模式下就是undefined。
绑定丢失解决方法——硬绑定
讲硬绑定可能不太容易理解,但是如果说 bind() 那么一定会豁然开朗。
起初硬绑定只是显示绑定的一个变种,在函数内部手动调用call、apply方法,强制改变this 的指向,之后无论在外部怎么改变this 的指向,都不会发生变化了。
// 硬绑定
function foo () {
console.log(this.name)
}
const obj = {
name : 'obj'
}
const test = {
name: 'test'
}
var bar = function () {
foo.call(obj)
}
bar() // obj
bar.call(test) // obj
比较常见的用法就是可以封装一些辅助的函数:
function foo (something) {
console.log(this.name, something)
return this.name + something
}
function bind (fn, obj) {
return function () {
return fn.apply(obj, arguments)
}
}
const obj = {
name: 1
}
const bar = bind(foo, obj)
console.log(bar(2))
其实这种硬绑定 的方式是非常常见的,所以ES5提供了内置的bind方法。
new 绑定
function foo (a) {
this.a = a
}
const bar = new foo(2)
console.log(bar.a)
引自《你不知道的JavaScript》(上卷)
使用 new 来调用 foo(…) 时,我们会构造一个新对象并把它绑定到 foo(…) 调用中的this上。 new 是最后一种可以影响函数调用时this绑定行为的方法 ,我们称之为new绑定。
判断this
上述已经介绍了this的基本绑定规则,接下来看一下判断this绑定指向的方法:
- 函数是否在new中调用 (是否采用了new绑定?)如果是的话this绑定的就是新创建的对象
- 函数是否通过call\apply或者使用了bind 等硬绑定手段,如果是的话,this指向指向的对象
- 函数是否在某个上下文对象中调用,即是否采用了隐式绑定? 如果是的话this就绑定在那个对象上下文上。
- 如果以上都不是那会采用隐式绑定,如果是严格模式,那么就绑定为undefined。
至此再无this 问题!
欢迎各位补充~