一、参考资料
二、运行环境
- Windows11
- Visual Studio Code v2022
- Node.js v16.5.01
- Vue/cli v5.0.6
三、案例需求分析
基于上一次的学习,最终使用Vue-CLI脚手架实现的TodoList案例效果如下图所示:
现对案例提出几个需求:
- 确保页面刷新后,当前的待办事项数据不会丢失。
- 提供编辑功能,可以修改已经添加的待办事项。
- 为熟悉Vue2动画样式的简单使用,给TodoList添加一个动态的效果。
最终实现的效果如下:
四、相关知识
4.1 JavaScript 原型对象
参考资料:菜鸟教程 JavaScript 高阶
JavaScript中的对象构造器
function Person(name, age) {
this.name = name;
this.age = age;
}
var me = new Person("uni",22);
var myGirl = new Person("who", 22);
在JavaScript中已存在构造器的实例对象中是不能添加新的属性
比如要添加一个属性 hobby 表示爱好
错误示范:
Person.hobby = "吃饭";
正确示范: (修改原先的对象声明)
function Person(name, age, hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
1. prototype 继承
所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法:
比如 JS 自带的一些对象:
- Date 对象从 Date.prototype 继承
- Array 对象从 Array.prototype 继承
同理,我们之前定义的Person对象也会从 Person.prototype 继承属性和方法。
所有 JavaScript 中的对象都是位于原型链顶端的 Object
的实例,这类似于Java语言中所有的类都是Object的子类,都有Object类的 toString()
方法。
JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾(即Object)。
简单说,不管是JS内置的Date 对象, Array 对象, 还是我们自定义的 Person 对象都从 Object.prototype 继承。
2. 通过prototype添加属性和方法
适用场景:
- 在所有已经存在的对象添加新的属性或方法
- 在对象的构造函数中添加属性或方法
应用案例:
- 给对象的构造函数添加新的属性
Person.prototype.hobby= "吃饭";
- 给对象的构造函数添加新的方法
function Person(first, last, age, eyecolor) {
this.name = name;
this.age = age;
}
Person.prototype.hobby = function() {
return this.name + "的爱好是吃饭";
};
具体案例:
先定义一个Person对象的构造器,只有name和age两种属性,根据构造器创建一个uni对象,当点击按钮后就对该对象的prototype原型对象添加一个新的属性和新的方法,然后在通过alert打印出来:
具体实现代码的如下(文件格式为.html):
<!DOCTYPE html>
<head>
<h1>测试内容</h1>
<button onclick="test(this)">初始化对象</button>
</head>
<body>
<script type="text/javascript">
function Person(name, age){
this.name = name
this.age = age
}
let uni = new Person('uni', 22)
function test(btn){
// 获取上一级的h1标签
let h1 = btn.previousElementSibling
// 通过prototype添加属性
Person.prototype.hobby = "吃饭"
Person.prototype.showName = function(){
return "我是: " + this.name
}
// 返回JSON格式的测试内容
h1.innerHTML = JSON.stringify(uni)
// 将对象打印到控制台
console.log(uni)
// 通过对象访问原型的属性
alert(uni.showName(), uni.hobby)
}
</script>
</body>
</html>
运行效果:
点击初始化对象的按钮,弹窗显示了对象通过prototype设置的方法和属性
点击确定后,对象的内容将以JSON格式显示在 h1 标题,如下图所示
4.2 Vue的生命周期
根据Vue官方介绍的:查看
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。
同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
在英文版中可能描述的会更详细一点:查看
每个Vue组件实例在创建时都会经历一系列初始化步骤
例如,它需要设置数据观察、编译模板、将实例装载到DOM,以及在数据更改时更新DOM。
同时,它还运行称为生命周期挂钩的函数,使用户有机会在特定阶段添加自己的代码。
Vue的生命周期如下图所示:
根据官方介绍,生命周期钩子(Hooks)就是上图中的红线圆框所表示的函数,本质上就是会在指定阶段执行的函数。
在实现这个案例时,我们主要用到三个钩子函数,分别是
- beforeCreate() ,Vue中数据代理和数据劫持创建前
- monted(),即DOM元素挂载后
- beforeDestory(),Vue组件销毁前
4.2.1 beforeCreate() & 全局事件总线
参考资料:视频资料
在之前我们通过vue创建了多个组件,各个组件有上下层调用的关系,那么根据新的需求,现在就存在组件之间的数据通信问题,比如在点击编辑过后,TodoItem组件需要将编辑后的信息传递给App组件,因为数据是存储在App中的,为了维持Vue提供的MVVM模型,我们尽量不在被引入的子组件中直接去修改从父组件App里props传递的数据。
Vue的每个组件都是VueComponent的实例对象,根据原型链的知识,它们的__proto__
属性是指向Vue原型对象的,这样就可以做到Vue之间的多个组件数据共享。
(下图摘自视频资料)
图中比较重要的等式:
VueComponent.prototype.__proto__ === Vue.prototype
恒等于 t r u e true true
注意:这里的VueComponent和Vue都是原型对象,而不是实例对象。
数据通信核心思路:提供一个Vue组件,存入Vue.prototype中专门用于数据通信。
在引入Vue的JS代码文件中进行配置
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this
}
}).$mount('#app')
这里我们通过 beforeCreate() 钩子来定义Vue的原型变量,$bus
,值就为Vue实例对象本身,这样的话,Vue内部的Vue Component组件实例对象就可以访问到 $bus
,这里的 $bus
称作全局事件的总线。
接下来总结以下全局事件总线的用法:
- 全局事件总线是一种Vue2组件之间的通信方式,适用于任意组件之间通信
- 配置全局事件总线
new Vue({
...
// 在钩子函数中定义
beforeCreate(){
Vue.prototype.$bus = this
},
})
- 使用事件总线
- 接收数据:A组件想接收数据,则在A组件中给
$bus$
绑定自定义事件,事件的回调留在A组件自身。
methods(){
update(data) {
... }
}
...
mounted(){
this.$bus.$on('xxxx', this.update)
}
- 提供数据
this.$bus.$emit('xxx', 数据)
Vue2的$emit
和$on
接下来会做补充。
4.3 消息发布和消息订阅 & $emit 和 $on
我们可以使用现有的JS插件,比如pubsub来实现这种消息发布与订阅。
在Vue2的各组件数据通信中,为了确保数据能顺利传送 ,通常两个组件之间需要提前对消息进行定义。
所谓的消息通常就是指函数,一方负责定义接收的函数参数以及函数的实现逻辑(消息订阅者),另一方则负责指定调用的函数,以及传入的数据(消息发布者)。
// 安装发布与订阅插件 : npm i pubsub-js
// 订阅消息(返回一个消息ID)
const pubsubId = pubsub.subscribe('消息名称', 回调函数(消息名, 消息参数))
// 发布消息
pubsub.publish('