Vue.js 项目出现这个错误,别慌!

作者简介:
李中凯老师,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 获取数据

  • filtermap和 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

发布了756 篇原创文章 · 获赞 1062 · 访问量 59万+

猜你喜欢

转载自blog.csdn.net/jnshu_it/article/details/104378868