箭头函数没有 this
let bottle = {
nickname: "bottle",
sayHi(){
setTimeout(function(){
console.log('Hello, ', this.nickname)
}, 1000)
// 或箭头函数
setTimeout(() => {
console.log('Hi, ', this.nickname)
}, 1000)
}
};
bottle.sayHi()
// Hello, undefined
// Hi, bottle
复制代码
报错是因为 Hello, undefined 是因为运行时 this=Window , Window.nickname 为 undefined。
但箭头函数就没事,因为箭头函数没有 this。在外部上下文中,this 的查找与普通变量搜索完全相同。this 指向定义时的环境。
箭头函数 vs bind
箭头函数 => 和正常函数通过 .bind(this) 调用有一个微妙的区别:
.bind(this)创建该函数的 “绑定版本”。- 箭头函数
=>不会创建任何绑定。该函数根本没有this。在外部上下文中,this的查找与普通变量搜索完全相同。
获取this指向实例
JavaScript 新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,希望方法中的 this 是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般 this 就丢失了。
例如:
let bottle = {
nickname: "bottle",
sayHello() {
console.log(`Hello, ${this.nickname}!`)
},
sayHi(){
setTimeout(function(){
console.log('Hello, ', this.nickname)
}, 1000)
}
};
// 问题一
bottle.sayHi();
// Hello, undefined!
// 问题二
setTimeout(bottle.sayHello, 1000);
// Hello, undefined!
问题一的 this.nickname 是 undefined ,原因是 this 指向是在运行函数时确定的,而不是定义函数时候确定的,再因为 sayHi 中 setTimeout 在全局环境下执行,所以 this 指向 setTimeout 的上下文:window。
问题二的 this.nickname 是 undefined ,是因为 setTimeout 仅仅只是获取函数 bottle.sayHello 作为 setTimeout 回调函数,this 和 bottle 对象分离了。
问题二可以写为:
// 在这种情况下,this 指向全局作用域
let func = bottle.sayHello;
setTimeout(func, 1000);
// 用户上下文丢失
// 浏览器上,访问的实际上是 Window 上下文
那么怎么解决这两个问题喃?
解决方案一: 缓存 this 与包装
首先通过缓存 this 解决问题: bottle.sayHi();
let bottle = {
nickname: "bottle",
sayHello() {
console.log(`Hello, ${this.nickname}!`)
},
sayHi(){
var _this = this // 缓存this
setTimeout(function(){
console.log('Hello, ', _this.nickname)
}, 1000)
}
};
bottle.sayHi();
// Hello, bottle
那问题二 setTimeout(bottle.sayHello, 1000); 喃?
let bottle = {
nickname: "bottle",
sayHello() {
console.log(`Hello, ${this.nickname}!`);
}
};
// 加一个包装层
setTimeout(() => {
bottle.sayHello()
}, 1000);
// Hello, bottle!
这样看似解决了问题二,但如果我们在 setTimeout 异步触发之前更新 bottle 值又会怎么样呢?
var bottle = {
nickname: "bottle",
sayHello() {
console.log(`Hello, ${this.nickname}!`);
}
};
setTimeout(() => {
bottle.sayHello()
}, 1000);
// 更新 bottle
bottle = {
nickname: "haha",
sayHello() {
console.log(`Hi, ${this.nickname}!`)
}
};
// Hi, haha!
bottle.sayHello() 最终打印为 Hi, haha! ,那么怎么解决这种事情发生呢?
解决方案二: bind
bind() 最简单的用法是创建一个新绑定函数,当这个新绑定函数被调用时,this 键值为其提供的值,其参数列表前几项值为创建时指定的参数序列,绑定函数与被调函数具有相同的函数体(ES5中)。
let bottle = {
nickname: "bottle",
sayHello() {
console.log(`Hello, ${this.nickname}!`);
}
};
// 未绑定,“this” 指向全局作用域
let sayHello = bottle.sayHello
console.log(sayHello())
// Hello, undefined!
// 绑定
let bindSayHello = sayHello.bind(bottle)
// 创建一个新函数,将 this 绑定到 bottle 对象
console.log(bindSayHello())
// Hello, bottle!
所以,从原来的函数和原来的对象创建一个绑定函数,则能很漂亮地解决上面两个问题:
let bottle = {
nickname: "bottle",
sayHello() {
console.log(`Hello, ${this.nickname}!`);
},
sayHi(){
// 使用 bind
setTimeout(function(){
console.log('Hello, ', this.nickname)
}.bind(this), 1000)
// 或箭头函数
setTimeout(() => {
console.log('Hello, ', this.nickname)
}, 1000)
}
};
// 问题一:完美解决
bottle.sayHi()
// Hello, bottle
// Hello, bottle
let sayHello = bottle.sayHello.bind(bottle); // (*)
sayHello();
// Hello, bottle!
// 问题二:完美解决
setTimeout(sayHello, 1000);
// Hello, bottle!
// 更新 bottle
bottle = {
nickname: "haha",
sayHello() {
console.log(`Hi, ${this.nickname}!`)
}
};
问题一,可以通过 bind 或箭头函数完美解决。
问题二,使用bind,最终更新 bottle 后, setTimeout(sayHello, 1000); 打印依然是 Hello, bottle!,完美解决!