MVVM模式
在vue使用中,我们只要修改了数据,所见视图便会进行相应更新。
为什么?原理是什么?
在这期间做三件事(发布订阅者模式)
- 数据劫持
- 依赖收集
- 派发更新
Object.defineProperty()详解
详见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...
该方法有三个入参obj
要定义属性的对象。prop
一个字符串或 Symbol
,指定了要定义或修改的属性键。descriptor
要定义或修改的属性的描述符。
通过该方法进行赋值(无需关注隐藏属性)
// 定义一个对象 var obj = {} // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { value: '张三' }) console.log(obj) // {name: '张三'} console.log(obj.name) // 张三
引入get
// 定义一个对象 var obj = {} // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { get() { console.log('正在访问name') // 当访问时,会执行该代码 } }) Object.defineProperty(obj, 'age', { value: 18 }) console.log(obj.name) // undefined 因为没有被赋值、所以认为不存在 console.log(obj.age) // 18 console.log(obj) // {age: 18}
引入set
// 定义一个对象 var obj = {} // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { get() { console.log('正在访问name') // 当访问时,会执行该代码 }, set() { console.log('尝试改变name') // 当对name进行更改时,执行该行代码 } }) Object.defineProperty(obj, 'age', { value: 18 }) console.log(obj.name) // undefined obj.name = '李四' console.log(obj.name) // undefined 虽然改变了name, // 但并没有进行具体的赋值,此时会触发set内代码
如何赋值?
// 定义一个对象 var obj = {} // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { // getter return出的值为name值, get() { console.log('正在访问name') // 当访问时,会执行该代码 return 'TEST' // 便于理解我们写死 }, set(newValue) { console.log('尝试改变name', newValue) // 当对name进行更改时,执行该行代码 } }) console.log(obj.name) // 输出TEST, obj.name = '李四' // setter内输出 '李四' console.log(obj.name) // 输出TEST、因为在getter中赋值为TEST,setter未成功赋值
引入defineReactive()
//为什么引入该函数? //因为get和set不好用,并且不希望引入变量进行周转,例子: // 定义一个对象 var obj = {} // 定义一个变量 var temp // 通过Object.defineProperty对obj进行赋值 Object.defineProperty(obj, 'name', { // getter return出的值为name值, get() { console.log('正在访问name') // 当访问时,会执行该代码 return temp // 返回temp,此时已被赋值 }, set(newValue) { temp = newValue // 赋值给全局变量 console.log('尝试改变name', newValue) // 当对name进行更改时,执行该行代码 } }) console.log(obj.name) // undefined,因为temp未赋值,且setter未触发 obj.name = '李四' // 赋值进行setter触发,将 '李四'赋值给temp console.log(obj.name) // 李四
defineReactive提供一个闭包环境,利用闭包特性进行周转
var obj = {} function defineReactive(data, key, val) { console.log(data, key, val) Object.defineProperty(data, key, { get() { return val }, set(newValue) { // 如果你要设置的值和原值相等,直接返回 if(newValue === val) return val = newValue } }) } // 进行赋值操作 defineReactive(obj, 'name', '张三') console.log(obj.name) // 张三 obj.name = '李四' console.log(obj.name) // 李四
引入递归侦测对象(observer)处理对象
// 不进行递归操作的话,如果一个对象有好几层,那么我们是检测不到深层变量的 // eg: var obj = { a: { b: { c: 'i am c' } }, b: 'i am b' }
实现递归
定义observer
把一个正常的object转换为每个层级都是响应式的//defineReactive function defineReactive(data, key, val) { console.log(data, key, val) if (arguments.length == 2) { val = data[key] } // 子元素要进行递归 let childOb = observe(val) Object.defineProperty(data, key, { get() { return val }, set(newValue) { // 如果你要设置的值和原值相等,直接返回 if(newValue === val) return val = newValue // 设置了新值,也要被observe childOb = observe(newValue) } }) } // Observer class Observer { constructor(value) { // 给实例添加__ob__属性,数值是这次new的实例 def(value, '__ob__', this, false) this.walk(value) } // 遍历 walk(value) { for (let k in value) { defineReactive(value, k) } } } // 不可遍历的方法 function def(obj, key, value, enumerable) { Object.defineProperty(obj, key, { value, enumerable, writable: true, configurable: true }) } // 创建observe函数用于辅助 function observe(value) { // 只为对象服务 if(typeof value !== 'object') return // 定义ob if(typeof value.__ob__ !== 'undefined'){ ob = value.__ob__ } else { ob = new Observer(value) } } // 循环监测 observe(obj) // 此时obj每个属性都被挂载了__ob__标识,且为响应式 obj.a.b.c.e = 10 console.log(obj.a.b.c.e) // 10 // 简单来说就是通过__ob__这个标识来确认你这个obj是不是响应式的,如果不是就递归遍历 // observe(obj) ==》 是否有__ob__ ==》 没有就new Observer(obj) ==》逐个响应式defineReactive
- 处理数组
vue
对数组的方法进行了改写(push
、pop
、shift
、unshift
、splice
、sort
、reverse
)
以Array.prototype
为原型创建了一个arrayMethods
的对象,用es6
的Object.setPrototypeOf()
,强制指向arrayMethods
// 拿到Array.prototype
const arrayPrototype = Array.prototype
// 以Array.prototype为原型创建arrayMethods对象
const arrayMethods = Object.create(arrayPrototype)
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 遍历
methodsNeedChange.foreach(methodName => {
// 备份原来的方法, 因为数组的功能不能被剥夺
const original = arrayPrototype[methodName]
// 定义新的方法
def(arrayMethods, methodName, functio(this){
// 恢复原来的功能
const result = original.apply(this, arguments)
// 把类数组对象变为数组
const args = [...arguments]
// 把数组身上的__ob__取出来,__ob__已经被添加了,因为数组不是最高层,在遍历obj对象第一层的时候,已经添加了__ob__属性
const ob = this.__ob__
//push, unshift, splice可以插入新项,因此也要把插入的新项也变为observe
let inserted = []
switch(methodName){
case 'push':
case 'unshift':
inserted = arguments;
break;
case 'splice':
// splice格式是splice(下标、数量、插入的新项)
inserted = args.slice(2);
break;
}
// 判断有没有要插入的新项, 如果有让新项也变为响应的
if (inserted) {
ob.observeArray(inserted)
}
return result
}, false)
})
// 在observer 中处理数组
// Observer
class Observer {
constructor(value) {
// 给实例添加__ob__属性,数值是这次new的实例
def(value, '__ob__', this, false)
// 如果是数组,需要把数组的原型,指向arrayMethods
if(Array.isArray(value)) {
Object.setPrototypeOf(value, arrayMethods)
// 让数组可以响应式、既observe
this.observeArray(value)
} else {
this.walk(value)
}
}
// 遍历
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
// 数组的特殊遍历
observeArray(arr) {
for(let i = 0, l = arr.length; i < l; i++) {
// 逐项进行observe
observe(arr[i])
}
}
}
依赖收集
- 需要用到数据的地方,称为依赖
- vue1.X, 细粒度依赖、用到数据的DOM依赖
- vue2.X, 中等粒度依赖、用到数据的组件是依赖
- 在getter收集依赖、在setter触发依赖
dep类
- 把依赖收集的代码封装,专门用来管理依赖,每个Observer的实例,成员中都有一个Dep的实例
- dep使用发布订阅模式、当数据发生变化时,会循环依赖列表,把所有的watcher都通知一遍
watcher类
- 中介、数据发生变化时通过Watcher进行中转,通知组件
- 依赖就是watcher、只有watcher触发的getter才会收集依赖、哪个watcher触发了getter、就把哪个watcher收集到dep中
- whatcher把自己设置到一个全局指定的位置,然后读取数据,由于读取了数据,会触发这个数据的getter。
- 在getter中就能得到当前正在读取数据的watcher,并把这个watcher收集到dep中
- dep类
class Dep{
constructor(){
// dep类的构造器
// 用数组存储自己订阅者, 存储的是watcher的实例
this.subs = []
}
// 添加订阅
addSub(){
this.subs.push(sub)
}
// 添加依赖
depend() {
// Dep.target是我们自己指定的全局的位置,主要是全局唯一,没歧义
if(Dep.target){
this.addSub(Dep.target)
}
}
// 通知更新
notify() {
// 浅克隆一份
const subs = this.subs.slice()
// 遍历
for ()let i = 0, l = subs.length; i < 1; i++){
subs[i].update()
}
}
}
- watcher类
class Watcher{
constructor(target, expression, callback){
// watcher类的构造器
this.target = target
// parsePath是一个高阶函数、用于拿到obj最底层的值
// getter被赋值的为函数
this.getter = parsePath(expression)
this.callback = callback
this.value = this.get()
}
update() {
this.run()
}
get() {
// 进入依赖收集阶段,让全局的Dep.target设置为watcher本身,那么就是进入依赖收集阶段
Dep.target = this
const obj = this.traget
// 取出最底层的值
try {
this.getter(obj)
} finally {
Dep.target = null
}
return value
}
run() {
this.getAndInvoke(this.callback)
}
getAndInvoke(cb) {
const value = this.get()
if (value !== this.value || typeof value == 'object') {
const oldValue = this.value
this.value = value
cb.call(this.target, value, oldValue)
}
}
}
// parsePath函数
function parsePath(str) {
var segments = str.split('.')
// 返回一个函数
return (obj) => {
for (let i = 0; i < segments.length; i++) {
obj = obj[segments[i]]
}
return obj
}
}
var fb = parsePath('a.b.c.d.e')
var v =fb({a:{b:{c:{d:{e:12}}}}})
console.log(v) // 12
//
dep和watcher实例化的位置
dep: defineReactive的闭包中(创建响应式中、闭包内可以得到)、observer构造器内//defineReactive function defineReactive(data, key, val) { const dep = new Dep() console.log(data, key, val) if (arguments.length == 2) { val = data[key] } // 子元素要进行递归 let childOb = observe(val) Object.defineProperty(data, key, { get() { // 如果处于依赖收集阶段 if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() } } return val }, set(newValue) { // 如果你要设置的值和原值相等,直接返回 if(newValue === val) return val = newValue // 设置了新值,也要被observe childOb = observe(newValue) // 发布订阅模式,通知dep } }) } // Observer class Observer { constructor(value) { // 每一个observer实例身上,都有一个dep this.dep = new Dep() // 给实例添加__ob__属性,数值是这次new的实例 def(value, '__ob__', this, false) // 如果是数组,需要把数组的原型,指向arrayMethods if(Array.isArray(value)) { Object.setPrototypeOf(value, arrayMethods) // 让数组可以响应式、既observe this.observeArray(value) } else { this.walk(value) } } // 遍历 walk(value) { for (let k in value) { defineReactive(value, k) } } // 数组的特殊遍历 observeArray(arr) { for(let i = 0, l = arr.length; i < l; i++) { // 逐项进行observe observe(arr[i]) } } } methodsNeedChange.foreach(methodName => { // 备份原来的方法, 因为数组的功能不能被剥夺 const original = arrayPrototype[methodName] // 定义新的方法 def(arrayMethods, methodName, functio(this){ // 恢复原来的功能 const result = original.apply(this, arguments) // 把类数组对象变为数组 const args = [...arguments] // 把数组身上的__ob__取出来,__ob__已经被添加了,因为数组不是最高层,在遍历obj对象第一层的时候,已经添加了__ob__属性 const ob = this.__ob__ //push, unshift, splice可以插入新项,因此也要把插入的新项也变为observe let inserted = [] switch(methodName){ case 'push': case 'unshift': inserted = arguments; break; case 'splice': // splice格式是splice(下标、数量、插入的新项) inserted = args.slice(2); break; } // 判断有没有要插入的新项, 如果有让新项也变为响应的 if (inserted) { ob.observeArray(inserted) } ob.dep.notify() return result }, false) })
dep类和watcher类 没太懂,后续研究后详细说明下
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。