实现入口文件
//index.js
import observe from "./observe.js";
var data = {
a: {
m: {
n: 5
}
},
c: {
d: {
e: {
f: 6666
}
}
},
hh:10,
g:[1,2,3,4,5]
};
observe(data)
data.hh = 100
首先是需要把对象变为响应式的函数。
实现响应式的函数
observe()
import Observer from "./Observer.js";
export default function(value){
//该方法使用__ob__属性来判断数据是否被响应式
if(typeof value != 'object') return
var ob;
if(typeof value.__ob__ != 'undefined'){
ob = value.__ob__
}else{
ob = new Observer(value)
}
return ob
}
一个对象是否被响应式了,是通过该对象上是否有__ob__属性来判断。这时候我们已经成功为对象添加响应式了。
Observer类做了什么?
export default class Observer {
constructor(value){
// 每一个Observer的实例身上,都有一个dep
this.dep = new Dep()//用于收集依赖
def(value,'__ob__',this,false)
if(Array.isArray(value)){
//改变原型
Object.setPrototypeOf(value, arrayMethods);
this.observeArray(value)
}else{
this.walk(value)
}
}
walk(value){
for(let key in value){
defineReactive(value,key)
}
}
observeArray(arr){
for(let i=0,l=arr.length;i<l;i++){
observe(arr[i])
}
}
}
function def(data,key,value,enumerable){
Object.defineProperty(data,key,{
value,
enumerable,
configurable:true,
writable:true
})
}
其实就是为对象添加__ob__属性。判断传入的数据是数组还是对象。
1.对象:继续遍历对象,利用Object.defineProperty劫持数据。
2.是数组,遍历每一项,利用递归继续判断。
对象还是数组?
- 对象:劫持数据。
export default function (data,key,val){
if (arguments.length == 2) {
val = data[key];
}
observe(val);
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){
console.log("你视图访问"+key+"属性",val)
return val
},
set(newval){
console.log("你视图改变"+key+"属性",val,newval)
if(val == newval){
return
}
val = newval
}
})
}
- 数组:改写数组的方法
const arrayPrototype = Array.prototype
//创建一个新对象指向数组原型
export const arrayMethods = Object.create(arrayPrototype)
//需要改写的方法
const arrayNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
arrayNeedChange.forEach(methodName=>{
// 备份原来的方法,因为push、pop等7个函数的功能不能被剥夺
const original = arrayPrototype[methodName];
//给arrayMethods添加方法
def(arrayMethods,methodName,function(){
//调用函数
const result = original.apply(this,arguments)
const args = [...arguments]
const ob = this.__ob__
let inserted = []
switch(methodName){
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break
}
if(inserted){
//递归调用判断数组里面的项
ob.observeArray(inserted)//添加的项再次变为响应式
}
console.log('啦啦啦')
return result
},false)
})
单单劫持数据是没有什么意义的,最重要的还是依赖的收集。vue中有三种依赖。视图依赖,computed依赖,watch依赖。
这里实现的是watch依赖
依赖收集器(订阅者)
var uid = 0
export default class Dep{
constructor(){
console.log('我是dep构造器')
this.id = uid++
this.sub = []
}
//添加订阅
addsub(sub){
this.sub.push(sub)
}
//在此收集依赖
depend(){
if(Dep.target){
this.addsub(Dep.target)
}
}
//通知更新
notify(){
let subs = this.sub.slice()
console.log('通知',subs)
for(let i=0,l=subs.length;i<l;i++){
subs[i].update()
}
}
}
依赖(观察者)
import Dep from "./Dep"
var uid = 0
export default class Watcher{
constructor(target, expression, callback){
this.id = uid++
this.target = target;
this.getter = parsePath(expression); //1.expression=>a.b.c | 2.此时getter为一个函数,参数为一个对象
this.callback = callback //回调函数
this.value = this.get() //当调用this.get()时,其实就会读取obj上的属性,就会触发get方法,触发添加依赖方法,等同于自动添加依赖。
}
get(){
//进入依赖收集
Dep.target = this //当读取obj的属性时,此时Dep.target就为真
const obj = this.target
var value
try{
value = this.getter(obj)
}finally{
Dep.target = null
}
return value
}
update(){
this.run()
}
run(){
this.getAndInvoke(this.callback);
}
getAndInvoke(cb){
const value = this.get()
if(value!==this.value || typeof value == 'object'){
const oldvalue = this.value
cb.call(this.target,value,oldvalue) //触发回调函数
}
}
}
function parsePath(str) { //此方法用于取出obj.a.b.c这种的值
var segments = str.split('.');
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]]
}
return obj;
};
}
调整defineReactive.js
export default function (data,key,val){
const dep = new Dep()
if (arguments.length == 2) {
val = data[key];
}
let childOb = observe(val);
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){
if(Dep.target){
dep.depend()//读取属性就会自动添加依赖
if(childOb){
childOb.dep.depend() //子也要和当前watcher建立联系
}
}
return val
},
set(newval){
console.log("你视图改变"+key+"属性",val,newval)
if(val == newval){
return
}
val = newval
childOb = observe(newval);//新数据继续响应式
dep.notify()//触发依赖更新
}
})
}
调整数组方法
import {def} from "./utils"
const arrayPrototype = Array.prototype
//创建一个新对象指向数组原型
export const arrayMethods = Object.create(arrayPrototype)
//需要改写的方法
const arrayNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
arrayNeedChange.forEach(methodName=>{
// 备份原来的方法,因为push、pop等7个函数的功能不能被剥夺
const original = arrayPrototype[methodName];
//给arrayMethods添加方法
def(arrayMethods,methodName,function(){
const result = original.apply(this,arguments)
const args = [...arguments]
const ob = this.__ob__
ob.dep.notify() //**只修改了该行触发依赖**
let inserted = []
switch(methodName){
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break
}
if(inserted){
//递归调用判断数组里面的项
ob.observeArray(inserted)
}
console.log('啦啦啦')
return result
},false)
})
最后
new Watcher(obj,'hh.value',function(val){
console.log('★我是watcher,我在监控a.m.n', val);
})
通过watcher依赖来监听数据,当数据改变时,就会触发依赖修改,输出回调函数。
疑问:不是很明白给数组添加依赖那步的操作?