JS 里的 this 其实是一个很好理解的概念,只不过 JS 为我们隐藏了一些细节促使我们理解 this 的时候往往特别模糊,今天就好好地解释一下 JS 里的 this 到底是个什么东西
函数的调用
你有没有发现在我们把对象和函数联系起来用的时候会出下面这种情况
let obj = {
name: '白日梦',
age: 18,
hello: function(){
console.log(`你好,我叫${this.name},我今年${this.age}岁了`)
}
}
let sayHi = obj.hello
sayHi() // 你好,我叫,我今年undefined岁了
obj.hello() // 你好,我叫白日梦,我今年18岁了
sayHi 函数和 obj.hello 函数都是打印出同样的内容,但是结果却不一样,sayHi 也是 obj.hello 赋值给它的
这就体现了 this 的妙用,他们的 this 不是同一个 this,所以会打印出不同结果
那怎么才能知道它们的 this 指向哪里呢,很简单,在函数调用的时候用 call 或 apply
// 沿用上面的代码
sayHi.call(undefined) // 你好,我叫,我今年undefined岁了
obj.hello.call(obj) // 你好,我叫白日梦,我今年18岁了
加上 call 后并不影响结果
其实在函数调用后加 call 才是最完全的写法,call 的第一个参数就是这个函数所指向的 this
在这里有个公式:
对象.函数名.call(对象, arguments)
谁引用了函数,谁就代表 this,但如果像 sayHi 没有对象引用就直接调用的,它默认的 this 为 null 或 undefined,那么浏览器就会自动把它的 this 变成 window(window 的 name 是空字符串,window 没有 age 这个属性)
现在理解为什么 sayHi 和 obj.hello 的结果不一样了吧,因为他们所指的 this 不一样
不加 call 的函数调用其实是一个语法糖
[ ] 语法的 this 应用
var length = 10 // 用 var 才能把 length 挂载到 window 上
function fn(){
console.log(this.length)
}
let obj = {
length: 5,
long: function(fn){
fn()
arguments[0]()
}
}
obj.long(fn,1)
先别想这段代码是什么逻辑,我们先把所有的函数调用加上 call
// 沿用上面代码
fn.call(undefined) // 打印出10
obj.long.call(obj,fn,1)
fn 和 obj.long 调用加上 call 一目了然
但是 argument[0] 的调用怎么加 call 呢
我们也可以套公式,arguments.0.call(arguments),这样虽然不符合规范,但是可以让我们更好的理解
arguments[0].call(arguments) // 打印出 2
arguments[0] 就是 fn,arguments 的长度是 2,所以结果为 2
没有想到吧,this 还可以这样用
箭头函数
新出的 ES6 语法里的箭头函数没有 this,所以在你需要用到 this 的时候最好使用 ES5 的函数语法
正因为箭头函数里没有 this,所以它会继承上一层函数的 this,这点要注意,它与上一层函数之间不存在谁指向谁
总结
- 当你调用一个函数的时候,最好 call 一下这个函数,它的第一个参数就代表 this,这样有助于你理解代码
- 当你遇到一个函数而不清楚它的 this 指向谁,那么你就可以用公式 对象.函数名.call(对象, arguments)
- apply 和 call 的使用方法基本一致,区别就是 call() 方法接受的是参数列表,而 apply() 方法接受的是一个参数数组