Object.defineProperty
一.侵入式和非侵入式
非侵入式 this.++ //vue数据变化
侵入式 this.setdData({a:1})//小程序
二.Object.defineProperty()
Object.defineProperty(obj, prop, descriptor)
developer.mozilla.org/zh-CN/docs/…
var obj = {}
Object.defineProperty(obj, 'a', {
value: 5,
enumerable: true , //可被枚举
writable: true,// 可被改变
get () {
},
set () {
}
})
三.defineReactive
function defineReactive (data,key,val) {
const dep = new Dep()
if(arguments.length>0) {
val = obj[key];
}
// 子元素递归
let childOb = observe(val)
Object.defineProperty(data, key, {
enumerable: true,
writable: true,
get () {
console.log('正在访问obj的'+ key + '属性')
if(Dep.target) {
dep.depend();
if(childOb) {
childOb.dep.depend()
}
}
return val
},
set (newValue) {
console.log('正在改变obj的'+ key + '属性' + newValue)
if(newValue == val) return;
val = newValue
// 设置新值的observe
childOb = observe(newValue)
// 发布订阅模式
dep.notify()
},
})
}
四.循环递归(Observer)
将一个正常的object转化为每个层级的属性都是响应式,可以被侦测
export const def = function (obj,key,value,enumerable) {
Object.defineProperty(obj, key , {
value,
enumerable,
writable: true,
configurable: true
})
}
export default Class Observer {
constructor (value) {
// 每个Observer的实例上都有一个dep
this.dep = new Dep()
//this不是表示类本身,而是表示实例
def(value, '_obj_', this, false)
console.log('Observer构造器', value)
// 检查是否为数组还是对象
if(Array.isArray(value)) {
Object.setPrototypeOf(value, arrayMethods)
this.observeArray(value)
} else {
}
this.walk(value)
}
// 遍历
walk(value) {
for(let k in value) {
defineReactive(value, k)
}
}
// 数组的特殊遍历
observeArray(arr) {
for(let i=0;i=arr.length;i++) {
observe(arr[i])
}
}
}
function observe(value) {
if(typeof value != 'object') return;
var obj;
if(typeof value._ob_ != 'undefined') {
obj = value._obj_
} else {
obj = new observe(value)
}
return obj
}
五.数组的响应式
改写的7个方法:
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
const arrayPrototype = Array.prototype
// 以Array.prototype为原型创建arrayMethods对象
const arrayMethods = Object.create(arrayPrototype)
// 要被改写的七个方法
const methodsMeedChange = [
'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
]
methodsMeedChange.forEach(methodName=>{
// 备份原来的方法
const original = arrayMethods[methodName]
def(arrayMethods, methodName, function(){
const result = original.apply(this, arguments)
// 把类数组变成数组
const args = [...arguments]
// 恢复原来的功能
const ob = this._ob_
let insterted = []
switch(methodName) {
case 'push':
case 'unshift':
insterted = args
break
case 'splice':
insterted = args.slice(2)
break
}
// 新数据变为相应
if(insterted) {
ob.observeArray(insterted)
}
ob.dep.notify()
return result
}, false)
})
六.收集依赖
vue2.x 中等粒度依赖, 用到数据的组件是依赖
在getter种收集依赖, 在setter种触发依赖
把依赖收集的代码封装成一个Dep类,它专门用来管理依赖,每个Observer的实例,成员中都有一个Dep的实例;
Watcher是一个中介, 数据发生变化时通过Watcher中专,通知组件
依赖就是Watcher. 只有Watcher触发的getter才会收集依赖,那个Watcher触发了getter, 就把那个Watcher收集到Dep中
Dep使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍.
Watcher把自己设置到全局的一个指定位置, 然后读取数据, 所以会触发这个数据的getter.在getter中就能得到当前正在读取数据的Watcher,并把这个Watcher收集到Dep中.
var uid = 0
export default const Dep {
constructor () {
this.uid = uid++
// 用数组存储自己的订阅者,放的是Watcher的实例
this.subs = []
}
// 添加订阅
addSub(sub) {
this.subs.push(sub)
}
//添加依赖
depend() {
// Dep.target就是设置一个自己制定的全局的位置.用window.target也行,只要是全局唯一
if(Dep.target){
this.addSub(Dep.target)
}
}
notify() {
const subs = this.subs.slice()
for(let i=0, i - subs.length;i<1;i++) {
subs[i].update()
}
}
}
var uid = 0
export default const Watcher {
constructor (target,expression,callback) {
this.id = uid++
this.target = target
this.getter = parsePath(expression)
this.callback = callback()
this.value = this.get()
}
update () {
this.run()
}
get() {
Dep.target = this;
const obj = this.target;
var value;
try{
value = this.getter(obj)
} finally {
Dep.target = null;
}
return value
}
run () {
this.getAndInvoke(this.callback)
}
getAndInvoke (cb) {
if(value!==this.value || typeof value == 'object') {
const oldValue = this.value
this.value = value
cb.callback(this.target,value,oldValue)
}
}
}
function parsrPath (str) {
var segments = str.split('.')
return (obj) => {
for(let i =0;i<segments.length;i++){
if(!obj) return
obj = obj[segments[i]]
}
}
}