let

jslet为声明变量的关键字,用let声明的变量,而这里的变量可概括为三种类型,原始类型(数字、字符串、布尔值等)、对象类型、函数类型,对于原始类型,我们可以直接修改其值,对于对象和函数类型我们既可以修改其属性,也可以修改其地址(将对象和函数指向另一对象或函数)。

let a = 1;
let b = {c:1,d:"e"};
a = 2;//合法
b.c = 3;//合法
b = {f:2,g:"h"};//合法

const

与let相反,const修饰的是常量,对于原始类型,我们不能修改其值;对于对象或函数类型,我们不能修改其地址指向,但是可以修改对象或函数的成员属性的值。

const a = 1;
const b = {c:1,d:"e"};
a = 2;//报错
b.c = 3;//合法
b = {f:2,g:"h"};//报错

踩坑

在日常开发中,可能感觉不到letconst的作用,因此我们容易把letconst滥用,来看下面一段代码:

<script setup lang="ts">
import {onBeforeMount, onMounted, provide, reactive, ref, watch} from "vue";
import {Book, IBook} from "../model";
import Author from "./Author.vue";

//组件内部的数据
let book = reactive(new Book());

//组件挂载前请求
onBeforeMount( () => {
  provide('author', book.author);
  requestBookData().then((data) => {
    book = data;
  });
});

//模拟网络请求
const requestBookData =  () => {
  return new Promise<IBook>((resolve, reject) => {
    setTimeout(() => {
      const data = new Book();
      data.name = 'Vue3 Vite';
      data.price = 100;
      data.author.name = 'coco';
      data.author.sex = 'man';
      data.author.age = 18;
      resolve(data as IBook);
    }, 1000);
  });
}

</script>

<template>
  <div class="coco-book">
    <h1>书名:{{ book.name }}</h1>
    <p>价格:{{ book.price }}</p>
    <Author/>
  </div>
</template>

<style scoped>

</style>

这是一个名为Book的组件,我们通过模拟网络请求的方式在组件挂载前去获取该组件的响应式数据,然后将book的数据渲染出来,可是渲染结果却出乎意料,渲染结果如下:

image-20230530163835292

可以看到book数据还是初始化状态,可我们明明通过请求数据的方式给booK对象赋值了呀。找原因找了很久,终于发现了错误的根源,原因就是我在声明book时用的时let修饰符,我为什么要用let修饰符呢,因为赋值简单,只需要直接更改book的指向即可。就是因为这一偷懒的缘故导了这个出乎意料的结果。因为网络请求时异步的,浏览器的主线程在网络请求时并不会阻塞,因此onBeforeMount钩子函数将不会阻塞DOM的渲染时机,DOM在渲染挂载完毕后book数据可能还未请求到,此时DOM绑定的book对象还是初始化状态,在网络请求结束后虽然book的数据更新了,但是我们实际上是更改了book对象指向的地址,而DOM绑定的响应式数据执行的仍为原book对象执行的地址,因此页面数据不会刚更新。正确的写法如下:

<script setup lang="ts">
import {onBeforeMount, onMounted, provide, reactive, ref, watch} from "vue";
import {Book, IBook} from "../model";
import Author from "./Author.vue";

//组件内部的数据
const book = reactive(new Book()); //修改为常量,地址不可变

//组件挂载前请求
onBeforeMount( () => {
  provide('author', book.author);
  requestBookData().then((data) => { //对属性赋值
    book.name = data.name;
    book.price = data.price;
    book.author.name = data.author.name;
    book.author.sex = data.author.sex;
    book.author.age = data.author.age;
  });
});

//模拟网络请求
const requestBookData =  () => {
  return new Promise<IBook>((resolve, reject) => {
    setTimeout(() => {
      const data = new Book();
      data.name = 'Vue3 Vite';
      data.price = 100;
      data.author.name = 'coco';
      data.author.sex = 'man';
      data.author.age = 18;
      resolve(data as IBook);
    }, 1000);
  });
}

</script>

<template>
  <div class="coco-book">
    <h1>书名:{{ book.name }}</h1>
    <p>价格:{{ book.price }}</p>
    <Author/>
  </div>
</template>

<style scoped>

</style>

渲染结果如下:

image-20230530165300838

小结

Vue中,我们声明变量和常量必须想好它的使用场景,对于响应式数据,永远不要使用let去修饰,以免出现不可预料的Bug