2023/3/12 Vue核心知识的学习 - v-model双向绑定原理
迪丽瓦拉
2024-06-02 02:28:21
0

1 M[模型] - V[页面视图] - VM[VM实例对象]

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。
在这里插入图片描述



单项数据流动:

{{this.$createElement.length}}

MVVM模型:

1.M:模型(Model):对应data中的数据
2.V:视图(View):模板
3.VM:视图模型(ViewModel):Vue实例对象 观察发现:
1.data中所有的属性,最后都出现在VM身上。
2.VM身上的所有属性即Vue原型上所有属性,在Vue模板中都可以直接使用。

2 JS 相关知识 - 为对象添加一个属性 - Object.defineProperty


在这里插入图片描述
通过遍历对象可以发现 - age 是不可以被枚举的
在这里插入图片描述

2.1 控制属性是否可以枚举 enumerable:true

    Object.defineProperty(person, 'age', {value: 18,enumerable:true, //控制属性是否可以枚举,默认值为false})

在这里插入图片描述
可以被枚举,但是不可以被修改
在这里插入图片描述

2.2 控制属性是否可以被修改 writable:true && configurable:true - 控制属性是否可以被删除

    Object.defineProperty(person, 'age', {value: 18,enumerable:true, //控制属性是否可以枚举,默认值为falsewritable:true, //控制属性是否可以被修改,默认值为falseconfigurable:true //控制属性是否可以被删除,默认值为false})

2.3 对象属性存在的 - get() set()



Title




在这里插入图片描述

3 引出理解数据代理

数据代理:通过一个对象代理对另一个对象中属性的操作 (读/写)



Title




在这里插入图片描述

4 Vue中的数据代理

将鼠标放到实例中的数据上,可以看到也提示了“Invoke property getter”。也就是说当有人访问name的时候,getter就在工作。
在这里插入图片描述
在这里插入图片描述
vm._data === data 为ture。也就是说_data完全来自于data:
在这里插入图片描述
1.Vue创建实例对象vm—>2.Vue收集data数据—>3.Vue往vm的_data上添加name、address(通过getter)等属性:
在这里插入图片描述
数据代理:通过vm.xxx来代理vm._data.xxx的操作(读/写),目的就是为了编码更方便。

_data中的属性做了一个数据劫持,把data里面的属性做了修改/升级,以便更好地完成响应式操作。

基本原理:

通过Object.defineProperty()把_data对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个getter/setter。在getter/setter内部去操作(读/写)_data中对应的属性。

5 vm数据更新时的一个问题-this.personList[0] = { 更新值 }不起作用



Title


  • {{ item.name }} - {{ item.age }}

在这里插入图片描述
以上代码是起作用的,这是为什么呢?原因如下所示【getter setter】
在这里插入图片描述
但是,我们修改为如下代码就不起作用了:
在这里插入图片描述
结果如下所示:这是为什么呢?
在这里插入图片描述
这是因为,如下所示:
在这里插入图片描述

6 引出 Vue监测数据的原理_对象



Title


学校名称:{{ name }}
学校地址:{{ address }}

在这里插入图片描述



Document





Vue监测数据的原理,就是靠setter。

7 Vue.set()方法

  1. 假设数组a中不存在对象b,Vue中访问a.b不会报错,只是不显示(Vue中默认undefined不显示),Vue中访问b会报错。
  2. Vue.set 只能给data里的某个对象追加属性,不能直接给data追加属性。
  3. 向响应式对象中添加一个property,并确保这个新property同样是响应式的【具备getter setter】,且触发视图更新。
    在这里插入图片描述





学校信息

学校名称:{{ name }}

学校地址:{{ address }}


学生信息

姓名:{{ student.name }}

性别:{{ student.sex }}

年龄:真实{{ student.age.rAge }}, 对外{{ student.age.sAge }}

朋友们:

  • {{ f.name }}--{{ f.age }}

8 Vue监测数据的原理_数组

原生Javascript数组使用的方法,例如push,就是从Array原型中找到的。可用 arr.push === Array.prototype.push 验证。 而Vue中的push却不等于 Array.prototype.push ,因为Vue中的push是经过包装的。






学生信息

爱好:

  • {{ item }}

在这里插入图片描述
在这里插入图片描述
针对于上面的数组,有了解决方案:如下
在这里插入图片描述
以后对于数组的修改,使用如下方法:
在这里插入图片描述
Vue监视数据的原理:

  1. Vue会监视data中所有层次的数据。
  2. 如何监测对象中的数据?
 通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1)对象中后追加的属性,Vue默认不做响应式处理。
(2)如需给后添加的属性做响应式,请使用如下API:vm.set(target,propertyName/index,value) 或vm.$set(target,propertyName/index,value)
  1. 如何监测数组中的数据?
 通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1)调用原生对应的方法对数组进行更新。
(2)重新解析模板,进而更新页面。
  1. 在Vue修改数组中的某个元素一定要用如下方法:
(1)使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2)Vue.set() 或 vm.$set()

特别注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm的根数据对象添加属性!

9 v-model双向绑定原理

定义:vue中双向绑定就是指v-model指令,可以绑定一个响应式数据到视图,同时视图中变化能同步改变该值。

在这里插入图片描述
通过Object.defineProperty( )对属性设置一个set函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了。
在这里插入图片描述
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。

我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。

如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。

因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。

接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。

10 手撕源码-模板解析

在这里插入图片描述



Title



{{ name }}

{{ title }}
我们自定义vue.js源码: class Vue {constructor(options) {this.$el = document.querySelector(options.el)this.$data = options.datathis.compile(this.$el)}compile(node) {node.childNodes.forEach((item, index) => {if (item.nodeType == 1) {this.compile(item)}if (item.nodeType == 3) {let reg = /\{\{(.*?)\}\}/g;let text = item.textContent;item.textContent = text.replace(reg, (match,vmKey) => {vmKey = vmKey.trim()return this.$data[vmKey]})}})} }

11 手撕源码-添加事件

在这里插入图片描述



Title



{{ name }}

{{ title }}
class Vue {constructor(options) {this.$optinos = optionsthis.$el = document.querySelector(options.el)this.$data = options.datathis.compile(this.$el)}compile(node) {node.childNodes.forEach((item, index) => {if (item.nodeType == 1) {if (item.hasAttribute('@click')) {let vmKey = item.getAttribute('@click').trim();item.addEventListener('click', (event) => {this.eventFn = this.$optinos.methods[vmKey].bind(this);this.eventFn(event)})}if (item.childNodes.length > 0) {this.compile(item)}}if (item.nodeType == 3) {let reg = /\{\{(.*?)\}\}/g;let text = item.textContent;item.textContent = text.replace(reg, (match,vmKey) => {vmKey = vmKey.trim()return this.$data[vmKey]})}})} }

12 手撕源码-数据劫持

引出一个问题:
在这里插入图片描述
我们在绑定事件的方法 btn() 方法中打印 this:data里面的数据我们需要使用 this.$data.xxx方可获取到,但是我们vue中直接通过this.xxx就可以获取到数据这里就引出了本章节要讨论的问题了- 数据劫持的问题
在这里插入图片描述
我们这里的解决问题的思路是:Object.defineProperty()

在这里插入图片描述
在这里插入图片描述
源码如下所示:

class Vue {constructor(options) {this.$optinos = optionsthis.$el = document.querySelector(options.el)this.$data = options.datathis.proxyData()this.compile(this.$el)}// 使用来自于data里面的数据给Vue赋值属性//  data中的属性值和Vue对象的属性保持双向(劫持)proxyData() {for (let key in this.$data) {Object.defineProperty(this, key, {get() {return this.$data[key]},set(v) {this.$data[key] = v}})}}compile(node) {node.childNodes.forEach((item, index) => {if (item.nodeType == 1) {if (item.hasAttribute('@click')) {let vmKey = item.getAttribute('@click').trim();item.addEventListener('click', (event) => {this.eventFn = this.$optinos.methods[vmKey].bind(this);this.eventFn(event)})}if (item.childNodes.length > 0) {this.compile(item)}}if (item.nodeType == 3) {let reg = /\{\{(.*?)\}\}/g;let text = item.textContent;item.textContent = text.replace(reg, (match,vmKey) => {vmKey = vmKey.trim()return this.$data[vmKey]})}})}
}

13 手撕源码-更新视图

在这里插入图片描述
对以上代码的理解为:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

15 手撕源码-v-model 的双向绑定

在这里插入图片描述

相关内容