Vue3+TypeScript从入门到进阶(一)——Vue3简介及介绍——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(二)——Vue2和Vue3的区别——附沿途学习案例及项目实战代码
在使用createApp的时候,我们传入了一个对象,接下来我们详细解析一下之前传入的属性分别代表什么含义。
**template属性:**表示的是Vue需要帮助我们渲染的模板信息:
目前我们看到它里面有很多的HTML标签,这些标签会替换掉我们挂载到的元素(比如id为app的div)的innerHTML;
模板中有一些奇怪的语法,比如 {{}},比如 @click,这些都是模板特有的语法,我们会在后面讲到;
但是这个模板的写法有点过于别扭了,并且IDE很有可能没有任何提示,阻碍我们编程的效率。
Vue提供了两种方式:
方式一:使用script标签,并且标记它的类型为 x-template;
方式二:使用任意标签(通常使用template标签,因为不会被浏览器渲染),设置id;
template元素是一种用于保存客户端内容的机制,该内容再加载页面时不会被呈现,但随后可以在运行时使用JavaScript实例化;
方式一:使用script标签
// template写法一
<body>
<div id="app">哈哈哈哈啊</div>
<script type="x-template" id="why">
<div>
<h2>{{message}}</h2>
<h2>{{counter}}</h2>
<button @click='increment'>+1</button>
<button @click='decrement'>-1</button>
</div>
</script>
<script src="../js/vue.js"></script>
<script>
document.querySelector("#why")
Vue.createApp({
template: '#why',
data: function() {
return {
message: "Hello World",
counter: 100
}
},
// 定义各种各样的方法
methods: {
increment() {
console.log("点击了+1");
this.counter++;
},
decrement() {
console.log("点击了-1");
this.counter--;
}
}
}).mount('#app');
</script>
</body>
方式二:使用template
// template写法二
<body>
<div id="app"></div>
<template id="why">
<div>
<h2>{{message}}</h2>
<h2>{{counter}}</h2>
<button @click='increment'>+1</button>
<button @click='decrement'>-1</button>
<button @click="btnClick">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
document.querySelector("#why")
Vue.createApp({
template: '#why',
data: function() {
return {
message: "Hello World",
counter: 100
}
},
// 定义各种各样的方法
methods: {
increment() {
console.log("点击了+1");
this.counter++;
},
decrement() {
console.log("点击了-1");
this.counter--;
},
btnClick: () => {
// this === window? 不可以
// 写成一个箭头函数时, 这个this就是window
// 在箭头函数中是不绑定this, 但是函数中如果使用了this
console.log(this);
},
btn2Click: function() {
// this === window? 不可以
// 写成一个箭头函数时, 这个this就是window
// 在箭头函数中是不绑定this, 但是函数中如果使用了this
console.log(this);
}
}
}).mount('#app');
</script>
</body>
这个时候,在createApp的对象中,我们需要传入的template以 # 开头:
data属性是传入一个函数,并且该函数需要返回一个对象:
在Vue2.x的时候,也可以传入一个对象(虽然官方推荐是一个函数);
在Vue3.x的时候,必须传入一个函数,否则就会直接在浏览器中报错;
data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或者访问都会在劫持中被处理:
所以我们在template中通过 {{counter}} 访问counter,可以从对象中获取到数据;
所以我们修改counter的值时,template中的 {{counter}}也会发生改变;
具体这种响应式的原理,我们后面会有专门的篇幅来讲解。
methods属性是一个对象,通常我们会在这个对象中定义很多的方法:
这些方法可以被绑定到 template 模板中;
在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性;
对于有经验的同学,在这里我提一个问题,官方文档有这么一段描述:
问题一:为什么不能使用箭头函数(官方文档有给出解释)?
问题二:不使用箭头函数的情况下,this到底指向的是什么?(可以作为一道面试题)
当然,这里还可以定义很多其他的属性,我们会在后续进行讲解:
比如props、computed、watch、emits、setup等等;
也包括很多的生命周期函数;
不用着急,我们会一个个学习它们的。
如果我们希望实现一个计数器的案例:
点击+1,那么内容会显示数字+1;
点击-1,那么内容会显示数字-1;
我们可以选择很多种方式来实现:
计数器原生实现
<body>
<h2 class="counter">0</h2>
<button class="increment">+1</button>
<button class="decrement">-1</button>
<script>
// 1.获取所有的元素
const counterEl = document.querySelector(".counter");
const incrementEl = document.querySelector(".increment");
const decrementEl = document.querySelector(".decrement");
// 2.定义变量
let counter = 100;
counterEl.innerHTML = counter;
// 3.监听按钮的点击
incrementEl.addEventListener("click", () => {
counter += 1;
counterEl.innerHTML = counter;
});
decrementEl.addEventListener("click", () => {
counter -= 1;
counterEl.innerHTML = counter;
});
</script>
</body>
计数器Vue实现
// 计数器案例-Vue
<body>
<div id="app">哈哈哈哈啊</div>
<script src="../js/vue.js"></script>
<script>
Vue.createApp({
template: `
{{message}}
{{counter}}
`,
data: function() {
return {
message: "Hello World",
counter: 100
}
},
// 定义各种各样的方法
methods: {
increment() {
console.log("点击了+1");
this.counter++;
},
decrement() {
console.log("点击了-1");
this.counter--;
}
}
}).mount('#app');
</script>
</body>
methods方法绑定this
问题回顾:
问题一:为什么不能使用箭头函数(官方文档有给出解释)?
问题二:不使用箭头函数的情况下,this到底指向的是什么?(可以作为一道面试题)
问题一:不能使用箭头函数?
我们在methods中要使用data返回对象中的数据:
那么我们这个this能不能是window呢?
不可以是window,因为window中我们无法获取到data返回对象中的数据;
但是如果我们使用箭头函数,那么这个this就会是window了;
为什么是window呢?
这里涉及到箭头函数使用this的查找规则,它会在自己的上层作用于中来查找this;
最终刚好找到的是script作用于中的this,所以就是window;
this到底是如何查找和绑定的呢?
在我的公众号有另外一篇文章,专门详细的讲解了this的绑定规则;
https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRlTA;
认真学习之后你绝对对this的绑定一清二楚;
问题二:this到底指向什么?
事实上Vue的源码当中就是对methods中的所有函数进行了遍历,并且通过bind绑定了this:
VSCode代码片段
我们在前面练习Vue的过程中,有些代码片段是需要经常写的,我们再VSCode中我们可以生成一个代码片段,方便我们快速生成。
VSCode中的代码片段有固定的格式,所以我们一般会借助于一个在线工具来完成。
具体的步骤如下:
第一步,复制自己需要生成代码片段的代码;
第二步,https://snippet-generator.app/在该网站中生成代码片段;
第三步,在VSCode中配置代码片段;
代码片段过程
React的开发模式:
React使用的jsx,所以对应的代码都是编写的类似于js的一种语法;
之后通过Babel将jsx编译成 React.createElement 函数调用;
Vue也支持jsx的开发模式(后续有时间也会讲到):
但是大多数情况下,使用基于HTML的模板语法;
在模板中,允许开发者以声明式的方式将DOM和底层组件实例的数据绑定在一起;
在底层的实现中,Vue将模板编译成虚拟DOM渲染函数,这个我会在后续给大家讲到;
所以,对于学习Vue来说,学习模板语法是非常重要的。
Mustache双大括号语法
如果我们希望把数据显示到模板(template)中,使用最多的语法是 “Mustache”语法 (双大括号) 的文本插值。
并且我们前端提到过,data返回的对象是有添加到Vue的响应式系统中;
当data中的数据发生改变时,对应的内容也会发生更新。
当然,Mustache中不仅仅可以是data中的属性,也可以是一个JavaScript的表达式。
另外这种用法是错误的:
<template id="my-app">
<!-- 1.mustache的基本使用 -->
<h2>{{message}} - {{message}}</h2>
<!-- 2.是一个表达式 -->
<h2>{{counter * 10}}</h2>
<h2>{{ message.split(" ").reverse().join(" ") }}</h2>
<!-- 3.也可以调用函数 -->
<!-- 可以使用computed(计算属性) -->
<h2>{{getReverseMessage()}}</h2>
<!-- 4.三元运算符 -->
<h2>{{ isShow ? "哈哈哈": "" }}</h2>
<button @click="toggle">切换</button>
<!-- 错误用法 -->
<!-- var name = "abc" -> 赋值语句 -->
<!-- <h2>{{var name = "abc"}}</h2>
<h2>{{ if(isShow) { return "哈哈哈" } }}</h2> -->
</template>
v-once指令
v-once用于指定元素或者组件只渲染一次:
当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过;
该指令可以用于性能优化;
<h2 v-once>{{counter}}</h2>
<button @click="increment">+1</button>
如果是子节点,也是只会渲染一次:
<div v-once>
<h2>{{counter}}</h2>
<h2>{{message}}</h2>
</div>
<button @click="increment">+1</button>
v-text指令
用于更新元素的 textContent:
<template id="my-app">
<h2 v-text="message"></h2>
<!--等价于-->
<h2>{{message}}</h2>
</template>
v-html
默认情况下,如果我们展示的内容本身是 html 的,那么vue并不会对其进行特殊的解析。
v-pre
v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:
<template id="my-app">
<h2 v-pre>{{message}}</h2>
</template>
v-cloak
这个指令保持在元素上直到关联组件实例结束编译。
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app"></div>
<template id="my-app">
<h2 v-cloak>{{message}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
<div> 不会显示,直到编译结束。
v-bind的绑定属性
前端讲的一系列指令,主要是将值插入到模板内容中。
但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定。
比如动态绑定a元素的href属性;
比如动态绑定img元素的src属性;
绑定属性我们使用v-bind:
缩写::
预期:any (with argument) | Object (without argument)
参数:attrOrProp (optional)
修饰符:
.camel - 将 kebab-case attribute 名转换为 camelCase。
用法:动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。
绑定基本属性
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值(这个学到组件时再介绍);
在开发中,有哪些属性需要动态进行绑定呢?
<div id="app"></div>
<!-- vue2 template模板中只能有一个根元素 -->
<!-- vue3 是允许template中有多个根元素 -->
<template id="my-app">
<!-- 1.v-bind的基本使用 -->
<img v-bind:src="imgUrl" alt="">
<a v-bind:href="link">百度一下</a>
<!-- 2.v-bind提供一个语法糖 : -->
<img :src="imgUrl" alt="">
<img src="imgUrl" alt="">
</template>
v-bind有一个对应的语法糖,也就是简写方式。
在开发中,我们通常会使用语法糖的形式,因为这样更加简洁。
绑定class介绍
在开发中,有时候我们的元素class也是动态的,比如:
当数据为某个状态时,字体显示红色。
当数据另一个状态时,字体显示黑色。
绑定class有两种方式:
对象语法
数组语法
绑定class – 对象语法
**对象语法:**我们可以传给 :class (v-bind:class 的简写) 一个对象,以动态地切换 class。
<template id="my-app">
<div :class="className">哈哈哈哈</div>
<!-- 对象语法: {'active': boolean} -->
<div :class="{'active': isActive}">呵呵呵呵</div>
<button @click="toggle">切换</button>
<!-- 也可以有多个键值对 -->
<div :class="{active: isActive, title: true}">呵呵呵呵</div>
<!-- 默认的class和动态的class结合 -->
<div class="abc cba" :class="{active: isActive, title: true}">
呵呵呵呵
</div>
<!-- 将对象放到一个单独的属性中 -->
<div class="abc cba" :class="classObj">呵呵呵呵</div>
<!-- 将返回的对象放到一个methods(computed)方法中 -->
<div class="abc cba" :class="getClassObj()">呵呵呵呵</div>
</template>
绑定class – 数组语法
**数组语法:**我们可以把一个数组传给 :class,以应用一个 class 列表;
<template id="my-app">
<div :class="['abc', title]">哈哈哈哈</div>
<div :class="['abc', title, isActive ? 'active': '']">哈哈哈哈</div>
<div :class="['abc', title, {active: isActive}]">哈哈哈哈</div>
</template>
绑定style介绍
我们可以利用v-bind:style来绑定一些CSS内联样式:
这次因为某些样式我们需要根据数据动态来决定;
比如某段文字的颜色,大小等等;
CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名;
绑定class有两种方式:
对象语法
数组语法
绑定style演练
对象语法:
<template id="my-app">
<!-- :style="{cssPropertyName: cssPropertyValue}" -->
<div :style="{color: finalColor, 'font-size': '30px'}">哈哈哈哈</div>
<div :style="{color: finalColor, fontSize: '30px'}">哈哈哈哈</div>
<div :style="{color: finalColor, fontSize: finalFontSize + 'px'}">哈哈哈哈</div>
<!-- 绑定一个data中的属性值, 并且是一个对象 -->
<div :style="finalStyleObj">呵呵呵呵</div>
<!-- 调用一个方法 -->
<div :style="getFinalStyleObj()">呵呵呵呵</div>
</template>
数组语法:
<template id="my-app">
<div :style="[style1Obj, style2Obj]">哈哈哈</div>
<img :src="" alt="">
<a :href=""></a>
<div :class></div>
</template>
动态绑定属性
在某些情况下,我们属性的名称可能也不是固定的:
前端我们无论绑定src、href、class、style,属性名称都是固定的;
如果属性名称不是固定的,我们可以使用 :[属性名]=“值” 的格式来定义;
这种绑定的方式,我们称之为动态绑定属性;
<template id="my-app">
<div :[name]="value">哈哈哈</div>
</template>
绑定一个对象
如果我们希望将一个对象的所有属性,绑定到元素上的所有属性,应该怎么做呢?
案例:info对象会被拆解成div的各个属性
<template id="my-app">
<div v-bind="info">哈哈哈哈</div>
<div :="info">哈哈哈哈</div>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
info: {
name: "why",
age: 18,
height: 1.88
}
}
}
}
Vue.createApp(App).mount('#app');
</script>
v-on绑定事件
前面我们绑定了元素的内容和属性,在前端开发中另外一个非常重要的特性就是交互。
在前端开发中,我们需要经常和用户进行各种各样的交互:
这个时候,我们就必须监听用户发生的事件,比如点击、拖拽、键盘事件等等
在Vue中如何监听事件呢?使用v-on指令。
接下来我们来看一下v-on的用法:
v-on的使用:
缩写:@
预期:Function | Inline Statement | Object
参数:event
修饰符:
.stop - 调用 event.stopPropagation()。
.prevent - 调用 event.preventDefault()。
.capture - 添加事件侦听器时使用 capture 模式。
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.{keyAlias} - 仅当事件是从特定键触发时才触发回调。
.once - 只触发一次回调。
.left - 只当点击鼠标左键时触发。
.right - 只当点击鼠标右键时触发。
.middle - 只当点击鼠标中键时触发。
.passive - { passive: true } 模式添加侦听器
用法:绑定事件监听
v-on的基本使用
我们可以使用v-on来监听一下点击的事件:
<!-- 完整写法: v-on:监听的事件="methods中方法" -->
<button v-on:click="btn1Click">按钮1</button>
<div class="area" v-on:mousemove="mouseMove">div</div>
<!-- 绑定一个表达式: inline statement -->
<button @click="counter++">{{counter}}</button>
v-on:click可以写成@click,是它的语法糖写法:
<!-- 语法糖 -->
<button @click="btn1Click">按钮1</button>
当然,我们也可以绑定其他的事件:
<!--绑定鼠标移动事件-->
<div @mouseover="mouseMove">div的区域</div>
如果我们希望一个元素绑定多个事件,这个时候可以传入一个对象:
<!-- 绑定一个对象 -->
<div class="area" v-on="{click: btn1Click, mousemove: mouseMove}"></div>
<div class="area" @="{click: btn1Click, mousemove: mouseMove}"></div>
v-on参数传递
当通过methods中定义方法,以供@click调用时,需要注意参数问题:
情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。
情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
<template id="my-app">
<!-- 默认传入event对象, 可以在方法中获取 -->
<button @click="btn1Click">按钮1</button>
<!-- $event可以获取到事件发生时的事件对象 -->
<button @click="btn2Click($event, 'coderwhy', 18)">按钮2</button>
</template>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
},
methods: {
btn1Click(event) {
console.log(event);
},
btn2Click(event, name, age) {
console.log(name, age, event);
}
}
}
Vue.createApp(App).mount('#app');
</script>
v-on的修饰符
v-on支持修饰符,修饰符相当于对事件进行了一些特殊的处理:
修饰符:
.stop - 调用 event.stopPropagation()。
.prevent - 调用 event.preventDefault()。
.capture - 添加事件侦听器时使用 capture 模式。
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.{keyAlias} - 仅当事件是从特定键触发时才触发回调。
.once - 只触发一次回调。
.left - 只当点击鼠标左键时触发。
.right - 只当点击鼠标右键时触发。
.middle - 只当点击鼠标中键时触发。
.passive - { passive: true } 模式添加侦听器
<body>
<div id="app"></div>
<template id="my-app">
<div @click="divClick">
<button @click.stop="btnClick">按钮</button>
</div>
<input type="text" @keyup.enter="enterKeyup">
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
message: "Hello World"
}
},
methods: {
divClick() {
console.log("divClick");
},
btnClick() {
console.log('btnClick');
},
enterKeyup(event) {
console.log("keyup", event.target.value);
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
在某些情况下,我们需要根据当前的条件决定某些元素或组件是否渲染,这个时候我们就需要进行条件判断了。
Vue提供了下面的指令来进行条件判断:
v-if
v-else
v-else-if
v-show
下面我们来对它们进行学习。
v-if、v-else、v-else-if
v-if、v-else、v-else-if用于根据条件来渲染某一块的内容:
这些内容只有在条件为true时,才会被渲染出来;
这三个指令与JavaScript的条件语句if、else、else if类似;
<template id="my-app">
<input type="text" v-model="score">
<h2 v-if="score > 90">优秀</h2>
<h2 v-else-if="score > 60">良好</h2>
<h2 v-else>不及格</h2>
</template>
v-if的渲染原理:
v-if是惰性的;
当条件为false时,其判断的内容完全不会被渲染或者会被销毁掉;
当条件为true时,才会真正渲染条件块中的内容;
template元素
因为v-if是一个指令,所以必须将其添加到一个元素上:
但是如果我们希望切换的是多个元素呢?
此时我们渲染div,但是我们并不希望div这种元素被渲染;
这个时候,我们可以选择使用template;
template元素可以当做不可见的包裹元素,并且在v-if上使用,但是最终template不会被渲染出来:
<template id="my-app">
<template v-if="isShowHa">
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
<h2>哈哈哈哈</h2>
</template>
<template v-else>
<h2>呵呵呵呵</h2>
<h2>呵呵呵呵</h2>
<h2>呵呵呵呵</h2>
</template>
</template>
v-show
v-show和v-if的用法看起来是一致的,也是根据一个条件决定是否显示元素或者组件:
<template id="my-app">
<h2 v-show="isShow">哈哈哈哈</h2>
</template>
v-show和v-if的区别
首先,在用法上的区别:
v-show是不支持template;
v-show不可以和v-else一起使用;
其次,本质的区别:
v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有渲染的,只是通过CSS的display属性来进行切换;
v-if当条件为false时,其对应的原生压根不会被渲染到DOM中;
开发中如何进行选择呢?
如果我们的原生需要在显示和隐藏之间频繁的切换,那么使用v-show;
如果不会频繁的发生切换,那么使用v-if;
在真实开发中,我们往往会从服务器拿到一组数据,并且需要对其进行渲染。
这个时候我们可以使用v-for来完成;
v-for类似于JavaScript的for循环,可以用于遍历一组数据;
v-for基本使用
v-for的基本格式是 “item in 数组”:
数组通常是来自data或者prop,也可以是其他方式;
item是我们给每项元素起的一个别名,这个别名可以自定来定义;
我们知道,在遍历一个数组的时候会经常需要拿到数组的索引:
如果我们需要索引,可以使用格式: “(item, index) in 数组”;
注意上面的顺序:数组元素项item是在前面的,索引项index是在后面的;
<template id="my-app">
<h2>电影列表</h2>
<ul>
<!-- 遍历数组 -->
<li v-for="(movie, index) in movies">{{index+1}}.{{movie}}</li>
</ul>
<h2>个人信息</h2>
<ul>
<!-- 遍历对象 -->
<li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
</ul>
<h2>遍历数字</h2>
<ul>
<li v-for="(num, index) in 10">{{num}}-{{index}}</li>
</ul>
</template>
v-for支持的类型
v-for也支持遍历对象,并且支持有一二三个参数:
一个参数: “value in object”;
二个参数: “(value, key) in object”;
三个参数: “(value, key, index) in object”;
v-for同时也支持数字的遍历:
<h2>个人信息</h2>
<ul>
<!-- 遍历对象 -->
<li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
</ul>
<h2>遍历数字</h2>
<ul>
<li v-for="(num, index) in 10">{{num}}-{{index}}</li>
</ul>
template元素
类似于v-if,你可以使用 template 元素来循环渲染一段包含多个元素的内容:
<template id="my-app">
<ul>
<template v-for="(value, key) in info">
<li>{{key}}</li>
<li>{{value}}</li>
<li class="divider"></li>
</template>
</ul>
</template>
数组更新检测
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换数组的方法
v-for中的key是什么作用?
在使用v-for进行列表渲染时,我们通常会给元素或者组件绑定一个key属性。
这个key属性有什么作用呢?我们先来看一下官方的解释:
key属性主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes;
如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法;
而使用key时,它会基于key的变化重新排列元素顺序,并且会移除/销毁key不存在的元素;
官方的解释对于初学者来说并不好理解,比如下面的问题:
什么是新旧nodes,什么是VNode?
没有key的时候,如何尝试修改和复用的?
有key的时候,如何基于key重新排列的?
认识VNode
我们先来解释一下VNode的概念:
因为目前我们还没有比较完整的学习组件的概念,所以目前我们先理解HTML元素创建出来的VNode;
VNode的全称是Virtual Node,也就是虚拟节点;
事实上,无论是组件还是元素,它们最终在Vue中表示出来的都是一个个VNode;
VNode的本质是一个JavaScript的对象;
虚拟DOM
如果我们不只是一个简单的div,而是有一大堆的元素,那么它们应该会形成一个VNode Tree:
插入F的案例
我们先来看一个案例:这个案例是当我点击按钮时会在中间插入一个f;
<body>
<div id="app"></div>
<template id="my-app">
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
<button @click="insertF">插入F元素</button>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
letters: ['a', 'b', 'c', 'd']
}
},
methods: {
insertF() {
this.letters.splice(2, 0, 'f')
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
我们可以确定的是,这次更新对于ul和button是不需要进行更新,需要更新的是我们li的列表:
在Vue中,对于相同父元素的子元素节点并不会重新渲染整个列表;
因为对于列表中 a、b、c、d它们都是没有变化的;
在操作真实DOM的时候,我们只需要在中间插入一个f的li即可;
那么Vue中对于列表的更新究竟是如何操作的呢?
Vue事实上会对于有key和没有key会调用两个不同的方法;
有key,那么就使用 patchKeyedChildren方法;
没有key,那么久使用 patchUnkeyedChildren方法
Vue源码对于key的判断
没有key的操作(源码)
没有key的过程如下
我们会发现上面的diff算法效率并不高:
c和d来说它们事实上并不需要有任何的改动;
但是因为我们的c被f所使用了,所有后续所有的内容都要一次进行改动,并且最后进行新增;
有key执行操作(源码)
有key的diff算法如下
第一步的操作是从头开始进行遍历、比较:
a和b是一致的会继续进行比较;
c和f因为key不一致,所以就会break跳出循环;
第二步的操作是从尾部开始进行遍历、比较:
第三步是如果旧节点遍历完毕,但是依然有新的节点,那么就新增节点:
第四步是如果新的节点遍历完毕,但是依然有旧的节点,那么就移除旧节点:
第五步是最特色的情况,中间还有很多未知的或者乱序的节点:
所以我们可以发现,Vue在进行diff算法的时候,会尽量利用我们的key来进行优化操作:
在没有key的时候我们的效率是非常低效的;
在进行插入或者重置顺序的时候,保持相同的key可以让diff算法更加的高效;
复杂data的处理方式
我们知道,在模板中可以直接通过插值语法显示一些data中的数据。
但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示;
比如我们需要对多个data数据进行运算、三元运算符来决定结果、数据进行某种转化后显示;
在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算;
在模板中放入太多的逻辑会让模板过重和难以维护;
并且如果多个地方都使用到,那么会有大量重复的代码;
我们有没有什么方法可以将逻辑抽离出去呢?
可以,其中一种方式就是将逻辑抽取到一个method中,放到methods的options中;
但是,这种做法有一个直观的弊端,就是所有的data使用过程都会变成了一个方法的调用;
另外一种方式就是使用计算属性computed;
认识计算属性computed
什么是计算属性呢?
官方并没有给出直接的概念解释;
而是说:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性;
计算属性将被混入到组件实例中。所有 getter 和 setter 的 this 上下文自动地绑定为组件实例;
计算属性的用法:
选项:computed
类型:{ [key: string]: Function | { get: Function, set: Function } }
那接下来我们通过案例来理解一下这个计算属性。
案例实现思路
我们来看三个案例:
案例一:我们有两个变量:firstName和lastName,希望它们拼接之后在界面上显示;
案例二:我们有一个分数:score
当score大于60的时候,在界面上显示及格;
当score小于60的时候,在界面上显示不及格;
案例三:我们有一个变量message,记录一段文字:比如Hello World
某些情况下我们是直接显示这段文字;
某些情况下我们需要对这段文字进行反转;
我们可以有三种实现思路:
思路一:在模板语法中直接使用表达式;
思路二:使用method对逻辑进行抽取;
思路三:使用计算属性computed;
思路一的实现:模板语法
缺点一:模板中存在大量的复杂逻辑,不便于维护(模板中表达式的初衷是用于简单的计算);
缺点二:当有多次一样的逻辑时,存在重复的代码;
缺点三:多次使用的时候,很多运算也需要多次执行,没有缓存;
// 01_三个案例的实现-插值语法
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{firstName + " " + lastName}}</h2>
<h2>{{score >= 60 ? '及格': '不及格'}}</h2>
<h2>{{message.split(" ").reverse().join(" ")}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant",
score: 80,
message: "Hello World"
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
思路二的实现:method实现
缺点一:我们事实上先显示的是一个结果,但是都变成了一种方法的调用;
缺点二:多次使用方法的时候,没有缓存,也需要多次计算;
// 02_三个案例的实现-methods
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{getFullName()}}</h2>
<h2>{{getResult()}}</h2>
<h2>{{getReverseMessage()}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant",
score: 80,
message: "Hello World"
}
},
methods: {
getFullName() {
return this.firstName + " " + this.lastName;
},
getResult() {
return this.score >= 60 ? "及格": "不及格";
},
getReverseMessage() {
return this.message.split(" ").reverse().join(" ");
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
思路三的实现:computed实现
p注意:计算属性看起来像是一个函数,但是我们在使用的时候不需要加(),这个后面讲setter和getter时会讲到;
我们会发现无论是直观上,还是效果上计算属性都是更好的选择;
并且计算属性是有缓存的;
// 03_三个案例的实现-computed
<body>
<div id="app"></div>
<template id="my-app">
<h2>{{fullName}}</h2>
<h2>{{result}}</h2>
<h2>{{reverseMessage}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant",
score: 80,
message: "Hello World"
}
},
computed: {
// 定义了一个计算属性叫fullname
fullName() {
return this.firstName + " " + this.lastName;
},
result() {
return this.score >= 60 ? "及格": "不及格";
},
reverseMessage() {
return this.message.split(" ").reverse().join(" ");
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
计算属性 vs methods
在上面的实现思路中,我们会发现计算属性和methods的实现看起来是差别是不大的,而且我们多次提到计算属性有缓存的。
接下来我们来看一下同一个计算多次使用,计算属性和methods的差异:
计算属性的缓存
这是什么原因呢?
这是因为计算属性会基于它们的依赖关系进行缓存;
在数据不发生变化时,计算属性是不需要重新计算的;
但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算;
计算属性的setter和getter
计算属性在大多数情况下,只需要一个getter方法即可,所以我们会将计算属性直接写成一个函数。
但是,如果我们确实想设置计算属性的值呢?
<body>
<div id="app"></div>
<template id="my-app">
<button @click="changeFullName">修改fullName</button>
<h2>{{fullName}}</h2>
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
firstName: "Kobe",
lastName: "Bryant"
}
},
computed: {
// fullName 的 getter方法
fullName() {
return this.firstName + " " + this.lastName;
},
// fullName的getter和setter方法
fullName: {
get: function() {
return this.firstName + " " + this.lastName;
},
set: function(newValue) {
console.log(newValue);
const names = newValue.split(" ");
this.firstName = names[0];
this.lastName = names[1];
}
}
},
methods: {
changeFullName() {
this.fullName = "Coder Why";
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
源码如何对setter和getter处理呢?
你可能觉得很奇怪,Vue内部是如何对我们传入的是一个getter,还是说是一个包含setter和getter的对象进行处理的呢?
认识侦听器watch
什么是侦听器呢?
开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中;
当数据变化时,template会自动进行更新来显示最新的数据;
但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了;
侦听器的用法如下:
**选项:**watch
类型:{ [key: string]: string | Function | Object | Array}
侦听器案例
举个例子:
比如现在我们希望用户在input中输入一个问题;
每当用户输入了最新的内容,我们就获取到最新的内容,并且使用该问题去服务器查询答案;
那么,我们就需要实时的去获取最新的数据变化;
<body>
<div id="app"></div>
<template id="my-app">
您的问题: <input type="text" v-model="question">
<!-- <button @click="queryAnswer">查找答案</button> -->
</template>
<script src="../js/vue.js"></script>
<script>
const App = {
template: '#my-app',
data() {
return {
// 侦听question的变化时, 去进行一些逻辑的处理(JavaScript, 网络请求)
question: "Hello World",
anwser: ""
}
},
watch: {
// question侦听的data中的属性的名称
// newValue变化后的新值
// oldValue变化前的旧值
question: function(newValue, oldValue) {
console.log("新值: ", newValue, "旧值", oldValue);
this.queryAnswer();
}
},
methods: {
queryAnswer() {
console.log(`你的问题${this.question}的答案是哈哈哈哈哈`);
this.anwser = "";
}
}
}
Vue.createApp(App).mount('#app');
</script>
</body>
侦听器watch的配置选项
我们先来看一个例子:
当我们点击按钮的时候会修改info.name的值;
这个时候我们使用watch来侦听info,可以侦听到吗?答案是不可以。
这是因为默认情况下,watch只是在侦听info的引用变化,对于内部属性的变化是不会做出响应的:
这个时候我们可以使用一个选项deep进行更深层的侦听;
注意前面我们说过watch里面侦听的属性对应的也可以是一个Object;
还有另外一个属性,是希望一开始的就会立即执行一次:
这个时候我们使用immediate选项;
这个时候无论后面数据是否有变化,侦听的函数都会有限执行一次;
watch: {
// 默认情况下我们的侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听)
// 深度侦听/立即执行(一定会执行一次)
info: {
handler: function(newInfo, oldInfo) {
console.log("newValue:", newInfo.nba.name, "oldValue:", oldInfo.nba.name);
},
deep: true, // 深度侦听
// immediate: true // 立即执行
}
},
侦听器watch的其他方式(一)
watch: {
// 方法名
b: 'someMethod',
// 你可以传入回调数组,它们会被逐一调用
e: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
侦听器watch的其他方式(二)
另外一个是Vue3文档中没有提到的,但是Vue2文档中有提到的是侦听对象的属性
"info.name": function(newName, oldName) {
console.log(newName, oldName);
}
还有另外一种方式就是使用 $watch 的API:
我们可以在created的生命周期(后续会讲到)中,使用 this.$watchs 来侦听;
第一个参数是要侦听的源;
第二个参数是侦听的回调函数callback;
第三个参数是额外的其他选项,比如deep、immediate;
created() {
const unwatch = this.$watch("info", function(newInfo, oldInfo) {
console.log(newInfo, oldInfo);
}, {
deep: true,
immediate: true
})
// unwatch()
}
现在我们来做一个相对综合一点的练习:书籍购物车
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67LNDw8u-1653814410134)(C:\WUYUXIN\Code\Vue3\typora\Vue3+TypeScript从入门到进阶(三)—附沿途学习案例.assets\image-20220302165245679.png)]
案例说明:
1.在界面上以表格的形式,显示一些书籍的数据;
2.在底部显示书籍的总价格;
3.点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-);
4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~);
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="app"></div>
<template id="my-app">
<template v-if="books.length > 0">
<table>
<thead>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</thead>
<tbody>
<tr v-for="(book, index) in books">
<td>{{index + 1}}</td>
<td>{{book.name}}</td>
<td>{{book.date}}</td>
<td>{{formatPrice(book.price)}}</td>
<td>
<button :disabled="book.count <= 1" @click="decrement(index)">-</button>
<span class="counter">{{book.count}}</span>
<button @click="increment(index)">+</button>
</td>
<td>
<button @click="removeBook(index)">移除</button>
</td>
</tr>
</tbody>
</table>
<h2>总价格: {{formatPrice(totalPrice)}}</h2>
</template>
<template v-else>
<h2>购物车为空~</h2>
</template>
</template>
<script src="../js/vue.js"></script>
<script src="./index.js"></script>
</body>
</html>
// index.js
Vue.createApp({
template: "#my-app",
data() {
return {
books: [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
]
}
},
computed: {
// vue2: filter/map/reduce
totalPrice() {
let finalPrice = 0;
for (let book of this.books) {
finalPrice += book.count * book.price;
}
return finalPrice;
},
// Vue3不支持过滤器了, 推荐两种做法: 使用计算属性/使用全局的方法
filterBooks() {
return this.books.map(item => {
const newItem = Object.assign({}, item);
newItem.price = "¥" + item.price;
return newItem;
})
}
},
methods: {
increment(index) {
// 通过索引值获取到对象
this.books[index].count++
},
decrement(index) {
this.books[index].count--
},
removeBook(index) {
this.books.splice(index, 1);
},
formatPrice(price) {
return "¥" + price;
}
}
}).mount("#app");
// style.css
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th, td {
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
.counter {
margin: 0 5px;
}
Vue3+TypeScript从入门到进阶(四)——Vue3基础知识点(中)——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(五)——Vue3基础知识点(下)——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(六)——TypeScript知识点——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(七)——项目实战——附沿途学习案例及项目实战代码
Vue3+TypeScript从入门到进阶(八)——项目打包和自动化部署——附沿途学习案例及项目实战代码
https://gitee.com/wu_yuxin/vue3-learning.git
https://gitee.com/wu_yuxin/vue3-ts-cms.git
基本用法
ES6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称之为解构(Destructuring)
// 以前为变量赋值,只能直接指定值
var a = 1;
var b = 2;
var c = 3;
// ES6允许写成这样
var [a,b,c] = [1,2,3];
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
下面是一些使用嵌套数组进行解构的例子:
let [foo,[[bar],baz]] = [1,[[2],3]];
foo // 1
bar // 2
baz // 3
let [,,third] = ["foo","bar","baz"];
third // "baz"
let [head,...tail] = [1,2,3,4];
head // 1
tail // [2,3,4]
let [x,y,...z] = ['a'];
x // "a"
y // undefined
z // []
默认值
解构赋值允许制定默认值
var [foo = true] = [];
foo // true
[x,y='b'] = ['a'];
// x='a', y='b'
注意,ES6内部使用严格相等运算符(===),判断一个位置是否有值。
所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。
var [x=1] = [undefined];
x //1
var [x=1] = [null];
x // null
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值:
function f(){
console.log('aaa');
}
let [x=f()] = [1];
上面的代码中,因为x能取到值,所以函数f()根本不会执行。上面的代码其实等价于下面的代码:
let x;
if([1][0] === undefined){
x = f();
}else{
x = [1][0];
}
默认值可以引用解构赋值的其他变量,但该变量必须已经声明:
let [x=1,y=x] = [];
// x=1; y=1
let [x=1,y=x] = [2];
// x=2; y=2
let [x=1,y=x] = [1,2];
// x=1; y=2
let [x=y,y=1] = []; // ReferenceError
上面最后一个表达式,因为x用到默认值是y时,y还没有声明。
1、最简单的案例
看下面的案例
let person = {
name: 'yhb',
age: 20
}
/*
注意:下面虽然看起来是创建了一个对象,对象中有两个属性 name 和 age
但是:其实是声明了两个变量
name:等于对象person 中的name属性的值
age:等于对象person 中的 age属性的值
*/
let { name, age } = person
console.log(name,age)
如上面注释中所说,声明了变量 name和age,然后分别从对象person中寻找与变量同名的属性,并将属性的值赋值给变量
所以,这里的关键,就是首先要知道对象中都有哪些属性,然后再使用字面量的方式声明与其同名的变量
2、属性不存在怎么办
如果不小心声明了一个对象中不存在的属性怎么办?
或者,实际情况下,可能是我们就是想再声明一个变量,但是这个变量也不需要从对象中获取值,这个时候,此变量的值就是 undefined
let person = {
name: 'yhb',
age: 20
}
let { name, age,address } = person
console.log(name,age,address)
此时,可以给变量加入一个默认值
let { name, age,address='北京' } = person
3、属性太受欢迎怎么办
当前声明了 name 和 age 变量,其值就是person对象中name和age属性的值,如果还有其他变量也想获取这两个属性的值怎么办?
let { name, age, address = '北京' } = person
console.log(name, age, address)
let { name, age } = person
console.log(name, age)
上面的方法肯定不行,会提示定义了重复的变量 name 和 age
那怎么办呢?
难道只能放弃结构赋值,使用老旧的方式吗?
let l_name=person.name
let l_age=person.age
console.log(l_name,l_age)
其实不然!
let {name:l_name,age:l_age}=person
console.log(l_name,l_age)
说明:
声明变量 l_name 并从对象person中获取name属性的值赋予此变量
声明变量 l_age, 并从对象person中获取age属性的值赋予此变量
这里的重点是下面这行代码
let {name:l_name,age:l_age}=person
按照创建对象字面量的逻辑,name 为键,l_name 为值。但注意,这里是声明变量,并不是创建对象字面量,所以争取的解读应该是
声明变量 l_name,并从person 对象中找到与 name 同名的属性,然后将此属性的值赋值给变量 l_name
所以,我们最后输出的是变量 l_name和l_age
console.log(l_name,l_age)
当然这种状态下,也是可以给变量赋予默认值的
let { name:l_name, age:l_age, address:l_address='北京' }=person
4、嵌套对象如何解构赋值
let person = {
name: 'yhb',
age: 20,
address: {
province: '河北省',
city: '保定'
}
}
// 从对象 person 中找到 address 属性,并将值赋给变量 address
let {address}=person
// 从对象 address 中找到 province 属性,并将值赋给变量 province
let {province}=address
console.log(province)
上面代码一层层的进行结构赋值,也可以简写为如下形式
let {address:{province}}=person
从peson 对象中找到 address 属性,取出其值赋值给冒号前面的变量 address,然后再将 变量address 的值赋值给 冒号 后面的变量 {province},相当于下面的写法
let {province}=address
1、字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
三个连续的点具有两个含义:展开运算符(spread operator)和剩余运算符(rest operator)。
展开运算符
展开运算符允许迭代器在接收器内部分别展开或扩展。迭代器和接收器可以是任何可以循环的对象,例如数组、对象、集合、映射等。你可以把一个容器的每个部分分别放入另一个容器。
const newArray = ['first', ...anotherArray];
剩余参数
剩余参数语法允许我们将无限数量的参数表示为数组。命名参数的位置可以在剩余参数之前。
const func = (first, second, ...rest) => {};
用例
定义是非常有用的,但是很难仅从定义中理解概念。我认为用日常用例会加强对定义的理解。
复制数组
当我们需要修改一个数组,但又不想改变原始数组(其他人可能会使用它)时,就必须复制它。
const fruits = ['apple', 'orange', 'banana'];
const fruitsCopied = [...fruits]; // ['apple', 'orange', 'banana']
console.log(fruits === fruitsCopied); // false
// 老方法
fruits.map(fruit => fruit);
它正在选择数组中的每个元素,并将每个元素放在新的数组结构中。我们也可以使用 map 操作符实现数组的复制并进行身份映射。
唯一数组
如果我们想从数组中筛选出重复的元素,那么最简单的解决方案是什么?
Set 对象仅存储唯一的元素,并且可以用数组填充。它也是可迭代的,因此我们可以将其展开到新的数组中,并且得到的数组中的值是唯一的。
const fruits = ['apple', 'orange', 'banana', 'banana'];
const uniqueFruits = [...new Set(fruits)]; // ['apple', 'orange', 'banana']
// old way
fruits.filter((fruit, index, arr) => arr.indexOf(fruit) === index);
串联数组
可以用 concat 方法连接两个独立的数组,但是为什么不再次使用展开运算符呢?
const fruits = ['apple', 'orange', 'banana'];
const vegetables = ['carrot'];
const fruitsAndVegetables = [...fruits, ...vegetables]; // ['apple', 'orange', 'banana', 'carrot']
const fruitsAndVegetables = ['carrot', ...fruits]; // ['carrot', 'apple', 'orange', 'banana']
// 老方法
const fruitsAndVegetables = fruits.concat(vegetables);
fruits.unshift('carrot');
将参数作为数组进行传递
当传递参数时,展开运算符能够使我们的代码更具可读性。在 ES6 之前,我们必须将该函数应用于 arguments。现在我们可以将参数展开到函数中,从而使代码更简洁。
const mixer = (x, y, z) => console.log(x, y, z);
const fruits = ['apple', 'orange', 'banana'];
mixer(...fruits); // 'apple', 'orange', 'banana'
// 老方法
mixer.apply(null, fruits);
数组切片
使用 slice 方法切片更加直接,但是如果需要的话,展开运算符也可以做到。但是必须一个个地去命名其余的元素,所以从大数组中进行切片的话,这不是个好方法。
const fruits = ['apple', 'orange', 'banana'];
const [apple, ...remainingFruits] = fruits; // ['orange', 'banana']
// 老方法
const remainingFruits = fruits.slice(1);
将参数转换为数组
Javascript 中的参数是类似数组的对象。你可以用索引来访问它,但是不能调用像 map、filter 这样的数组方法。参数是一个可迭代的对象,那么我们做些什么呢?在它们前面放三个点,然后作为数组去访问!
const mixer = (...args) => console.log(args);
mixer('apple'); // ['apple']
将 NodeList 转换为数组
参数就像从 querySelectorAll 函数返回的 NodeList 一样。它们的行为也有点像数组,只是没有对应的方法。
[...document.querySelectorAll('div')];
// 老方法
Array.prototype.slice.call(document.querySelectorAll('div'));
复制对象
最后,我们介绍对象操作。复制的工作方式与数组相同。在以前它可以通过 Object.assign 和一个空的对象常量来实现。
const todo = { name: 'Clean the dishes' };
const todoCopied = { ...todo }; // { name: 'Clean the dishes' }
console.log(todo === todoCopied); // false
// 老方法
Object.assign({}, todo);
合并对象
合并的唯一区别是具有相同键的属性将被覆盖。最右边的属性具有最高优先级。
const todo = { name: 'Clean the dishes' };
const state = { completed: false };
const nextTodo = { name: 'Ironing' };
const merged = { ...todo, ...state, ...nextTodo }; // { name: 'Ironing', completed: false }
// 老方法
Object.assign({}, todo, state, nextTodo);
需要注意的是,合并仅在层次结构的第一级上创建副本。层次结构中的更深层次将是相同的引用。
将字符串拆分为字符
最后是字符串。你可以用展开运算符把字符串拆分为字符。当然,如果你用空字符串调用 split 方法也是一样的。
const country = 'USA';
console.log([...country]); // ['U', 'S', 'A']
// 老方法
country.split('');
在学习vue3的顶层编写方式时的父子组件通信的时候,我们会看到一些比较老(2020、2021年初)的博客里面会有使用defineEmit的,但是如果我们用比较新版本的Vue3的话,就会报错。原因是,新版本的Vue3将defineEmit改成了defineEmits了
Webpack从入门到进阶(一)—附沿路学习案例代码
Webpack从入门到进阶(二)—附沿路学习案例代码
Webpack从入门到进阶(三)—附沿路学习案例代码
数据可视化-echarts入门、常见图表案例、超详细配置解析及项目案例
Vue项目开发-仿蘑菇街电商APP
Vue 知识点汇总(上)–附案例代码及项目地址
Vue 知识点汇总(下)–附案例代码及项目地址
JavaScript面向对象编程浅析
JavaScript设计模式浅析
SingleSpa及qiankun入门、源码分析及案例