作者简介:
李中凯老师,8年前端开发,前端负责人,擅长JavaScript/Vue。
公众号:1024译站
掘金文章专栏:https://juejin.im/user/57c7cb8a0a2b58006b1b8666/posts
主要分享:Vue.js, JavaScript,CSS
Vue.js 自从诞生以来,凭借它简洁优雅的 API、不错的性能和较低的学习门槛,赢得了广大前端开发者的青睐。大部分情况下,用 Vue.js 开发项目比较轻松,如丝般顺滑。但偶尔还是会碰到一些莫名其妙的问题,比如这个:
this is undefined
如果你也碰到了,别急,你不是一个人。我之前也碰到过,今天就来解释下为什么会有这个问题,以及解决方法。
平时也经常碰到Cannot read property 'name' of undefined
之类的错误,但通常是某个变量未定义导致的。现在this
都变成undefined
了,确实有点诡异。我猜,你很可能是用错了箭头函数,像这样:
这个问题的根源,在于没搞清楚普通函数和箭头函数的区别。这里如果把箭头函数换成普通function
,问题迎刃而解。
接下来我们将深入理解其中的原理。毕竟,稀里糊涂地解决了问题,下次碰到类似的问题又一脸懵逼了。
两种主要的函数形式
JavaScript 有两种函数形式,它们的关键区别在于处理this
的方式。我们都知道,this
问题困扰了不少前端开发,没弄清楚它的作用机制前,确实会踩很多坑。
1常规函数
Vue.js 组件里的常规函数可以用几种方式定义。
第一种不太常用,因为写起来比较啰嗦:
methods: {
regularFunction: function() {
// Do some stuff
}
}
第二种是简写方式,用得比较多:
methods: {
shorthandFunction() {
// Do some stuff
}
}
这样定义的函数,里面的this
会指向函数的“所有者”,这里就是当前 Vue 组件。
大部分情况下,你应该在 Vue 中使用常规函数,特别是在这几个地方:
-
methods
-
computed 属性
-
watched 属性
常规函数通常就是你想要的,但是箭头函数也不是洪水猛兽,它也有自己的用武之地。
2
箭头函数
箭头函数由于写起来更简洁高效,很受欢迎。但是在定义 Vue 组件的方法时,好像也没什么优势:
methods: {
arrowFunction: () => {
// Do some stuff
}
}
真正的区别在于它们对this
的处理方式。
在箭头函数中,this
并不指向函数的所有者。
箭头函数使用所谓的词法作用域,简单说就是this
指向它所在的上下文。
如果你尝试在 Vue 组件的箭头函数里访问this
会报错,因为this
不存在!
data() {
return {
text: 'This is a message',
};
},
methods: {
arrowFunction: () => {
console.log(this.text); // ERROR! this is undefined
}
}
简单粗暴的做法就是不要在 Vue 组件里用箭头函数。这样会替你省掉不少麻烦和困惑。
既然this
是罪魁祸首,箭头函数只是背锅侠,那有时候还是可以用箭头函数的,只要你没有用到this
:
computed: {
location: () => window.location,
}
现在我们知道了两种函数的区别,那使用它们的正确姿势是什么呢?
匿名函数
匿名函数用于临时创建、不需要在其他地方调用的函数,它没有名字,没有绑定到某个变量上。
匿名函数通常用在下面这些场景中:
-
使用
fetch
或axios
获取数据 -
filter
,map
和reduce
之类的函数式方法 -
Vue 组件方法内部
代码举例:
// 获取数据
fetch('/getSomeData').then((data) => {
this.data = data;
});
// 函数式方法
const array = [1, 2, 3, 4, 5];
const filtered = array.filter(number => number > 3);
const mapped = array.map(number => number * 2);
const reduced = array.reduce((prev, next) => prev + next);
从上面的例子中可以看到,在定义匿名函数的时候,人们通常喜欢使用箭头函数。使用箭头函数大概有这么几个理由:
-
语法更精简
-
可读性更好
-
this
指向 外层上下文
Vue 方法内部使用匿名函数的时候,箭头函数也很方便。
等等,刚才不是说访问 this
的时候箭头函数不好使吗?
不要误会,看清楚区别:我们是在常规函数内部使用箭头函数,这样常规函数就会将this
设置为当前 Vue 组件实例,箭头函数就可以直接使用这个this
了。
data() {
return {
match: 'This is a message',
};
},
computed: {
filteredMessages(messages) {
console.log(this); // Vue 组件实例
const filteredMessages = messages.filter(
// 引用组件实例
(message) => message.includes(this.match)
);
return filteredMessages;
}
}
这里之所以能访问
这里之所以能访问到 this.match
是因为箭头函数的上下文跟filteredMessages
方法的上下文是一致的,而filteredMessages
是个常规函数而已,因此它的上下文就是组件实例。
搞明白了这个原理,下面的例子也就能理解了:
export default {
data() {
return {
dataFromServer: undefined,
};
},
methods: {
fetchData() {
fetch('/dataEndpoint')
.then(data => {
this.dataFromServer = data;
})
.catch(err => console.error(err));
}
}
};
Promise 里的箭头函数可以正常访问this
。
Lodash 或者 Underscore 的正确用法
再回到文章开头截图里的那个错误,想要用 Lodash 里的 debounce
方法,直接用箭头函数包裹起来是不行的,因为this
并不指向组件实例。可以用下面的方法解决:
created() {
this.onLogin = _.debounce(this.onLogin, 500);
},
methods: {
onLogin() {
// Do some things here
}
}
就这么简单!
什么是词法作用域
前面提到过,常规函数和箭头函数的区别在于词法作用域,那什么是词法作用域呢?
先说作用域,也就是变量所处的程序范围。比如,window
变量拥有全局作用域,任何位置都能访问它。大部分变量被限制在函数、class或者模块内部,处于局部作用域。
再说词法,词法的意思是作用域是由代码书写方式决定的。有些编程语言是在程序运行时决定作用域的,这会导致很多困惑,因此大部分语言用的是词法作用域。
箭头函数使用词法作用域,而常规函数不是。
这里最棘手的部分是词法作用域如何影响函数中的this
。对于箭头函数,this
绑定到外部作用域的this
。常规函数绑定this
的方式有些奇怪,这就是为什么要引入箭头函数,它确实为我们解决了一个很头疼的问题。
// window 作用域
window.value = 'Bound to the window';
const object = {
// object 的作用域
value: 'Bound to the object',
arrowFunction: () => {
// 箭头函数使用 window 作用域的 `this`
console.log(this.value); // 'Bound to the window'
},
regularFunction() {
// 常规函数使用所属对象 object 作用域的 `this`
console.log(this.value); // 'Bound to the object'
}
};
现在应该清楚 JavaScript 函数的作用域机制了吧。但是,普通函数的这种默认行为机制其实是可以设定的。
可以通过call
或者apply
来执行函数,也可以用函数的bind
方法 :
func.call(obj, arg1, arg2);
func.apply(obj, args);
const boundFunction = unboundFunction.bind(this);
总结
本文讲述了 Vue.js 项目里常见的一种错误,并解释了错误产生的原因,对比了普通函数和箭头函数,介绍了词法作用域的相关知识。希望对你有所帮助。
本文已经获得李中凯老师授权转发,其他人若有兴趣转载,请直接联系作者授权。
作者简介:
李中凯老师,8年前端开发,前端负责人,擅长JavaScript/Vue。
公众号:1024译站
掘金文章专栏:https://juejin.im/user/57c7cb8a0a2b58006b1b8666/posts
主要分享:Vue.js, JavaScript,CSS