虽然没有完全遵循 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模板中都可以直接使用。
通过遍历对象可以发现 - age 是不可以被枚举的
Object.defineProperty(person, 'age', {value: 18,enumerable:true, //控制属性是否可以枚举,默认值为false})
可以被枚举,但是不可以被修改
Object.defineProperty(person, 'age', {value: 18,enumerable:true, //控制属性是否可以枚举,默认值为falsewritable:true, //控制属性是否可以被修改,默认值为falseconfigurable:true //控制属性是否可以被删除,默认值为false})
Title
数据代理:通过一个对象代理对另一个对象中属性的操作 (读/写)
Title
将鼠标放到实例中的数据上,可以看到也提示了“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中对应的属性。
Title
- {{ item.name }} - {{ item.age }}
以上代码是起作用的,这是为什么呢?原因如下所示【getter setter】
但是,我们修改为如下代码就不起作用了:
结果如下所示:这是为什么呢?
这是因为,如下所示:
Title
学校名称:{{ name }}
学校地址:{{ address }}
Document
Vue监测数据的原理,就是靠setter。
学校信息
学校名称:{{ name }}
学校地址:{{ address }}
学生信息
姓名:{{ student.name }}
性别:{{ student.sex }}
年龄:真实{{ student.age.rAge }}, 对外{{ student.age.sAge }}
朋友们:
- {{ f.name }}--{{ f.age }}
原生Javascript数组使用的方法,例如push,就是从Array原型中找到的。可用 arr.push === Array.prototype.push
验证。 而Vue中的push却不等于 Array.prototype.push ,因为Vue中的push是经过包装的。
学生信息
爱好:
- {{ item }}
针对于上面的数组,有了解决方案:如下
以后对于数组的修改,使用如下方法:
Vue监视数据的原理:
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1)对象中后追加的属性,Vue默认不做响应式处理。
(2)如需给后添加的属性做响应式,请使用如下API:vm.set(target,propertyName/index,value) 或vm.$set(target,propertyName/index,value)
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1)调用原生对应的方法对数组进行更新。
(2)重新解析模板,进而更新页面。
(1)使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2)Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm的根数据对象添加属性!
定义:vue中双向绑定就是指v-model指令,可以绑定一个响应式数据到视图,同时视图中变化能同步改变该值。
通过Object.defineProperty( )
对属性设置一个set
函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了。
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。
如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。
因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。
接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
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]})}})}
}
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]})}})}
}
引出一个问题:
我们在绑定事件的方法 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]})}})}
}
对以上代码的理解为: