首先建议了解 Object.defineproperty ,它是有get与set方法的,分别在获取与设置时触发。
我们先来进行对象的响应式,它是经历了一下的流程:
从上可以看出,它是经历循环去把所有的属性都响应式的,我们现在来把这几个基数方法写下来:
对象的响应式
observe判断类型
import Observer from './Observer.js'
export default function observe(value){
if(typeof(value) != 'object' ) return;
var ob;
if(typeof(value.__ob__) != 'undefined'){
ob = value.__ob__;
}else{
ob = new Observer(value);
};
return ob;
}
Observer
import defineReactive from "./defineReactive.js"
import def from './utils.js'
export default class Observer {
constructor(value) {
//给对象加上__ob__属性,不可枚举,这里的this是Observer的实例
def(value, '__ob__', this, false);
//遍历对象,将每个对象中的属性都去defineReactive,响应式一样
this.walk(value)
}
walk(value) {
for (let key in value) {
defineReactive(value, key, value[key]);
}
}
}
def添加__ob__
export default function def(data, key, value, enumerable) {
Object.defineProperty(data, key, {
configurable: true,
writable: true,
enumerable: enumerable,
value
})
}
/**
*
*
* let obj = {
a: 10,
b: 11
}
def(obj, 'a', 10, false);
for (let key in obj) {
console.log(key) //b a将不可枚举
}
*
*/
defineReactive将数据响应式
import observe from "./observe.js";
export default function defineReactive(data, key, value) {
//继续将value响应式,如果value是基础类型,直接返回,如果是对象,继续Observer也会走到这里,直到所有的属性都是响应式
observe(value);
Object.defineProperty(data, key, {
//可枚举
enumerable: true,
//可删除
configurable: true,
get(){
console.log(`${key}被读取了,${value},get`);
return value;
},
set(newVal){
console.log(`${key}被更改了,${newVal},set `);
value = newVal;
//设置新值,也需要observe变为响应式的
observe(value);
}
})
}
测试对象响应式
import observe from './observe.js'
let obj = {
a:888,
b:{
c:{
d:999
}
}
}
observe(obj);
obj.b.c.d
数组的响应式
首先object.defineProperty是可以get与set数组但无法监听数组的操作, 源码中是对7个数组的方法进行了重写,分别是 'pop', 'push', 'unshift', 'shift', 'splice', 'sort', 'reverse' 我们只是想对数组的方法进行一部分的操作,添加的数据响应式,所以我们应该保留数组原有的方法。 所以需要将原有的方法设为新方法的原型。
当我们在一个对象(数组)上查询属性或方法时,本身没有会去原型链上去查找,原型链上也没有,会去原型链的原型链上去查找,这里当数组 [1,2,3]上想使用push方法时,本身会去 Array.prototype上去查找,但是我们加了一层 arrayMethods,就会去使用 arrayMethods上的 push等 7个方法。 这样我们就相当于加了一层拦截。
Observer判断对象还是数组
import defineReactive from "./defineReactive.js"
import def from './utils.js'
import { arrayMethods } from './array.js'
import observe from "./observe.js";
export default class Observer {
constructor(value) {
//给对象加上__ob__属性,不可枚举,这里的this是Observer的实例
def(value, '__ob__', this, false);
//判读是数组还是对象
if (Array.isArray(value)) {
//arrayMethods是重写的7个数组方法,arrayMethods.__proto__是Array.prototype, Array.prototype上有原始的数组方法
Object.setPrototypeOf(value, arrayMethods)
this.observeArray(value)
//value.__proto__ = arrayMethods
// console.log(value)
} else {
this.walk(value);
}
}
walk(value) {
//遍历对象,将每个对象中的属性都去defineReactive,响应式一样
for (let key in value) {
defineReactive(value, key, value[key]);
}
}
observeArray(value) {
for (let i = 0; i < value.length; i++) {
observe(value[i]);
}
}
}
array.js
import def from './utils.js';
//arrayMethods可以使用数组方法 等于arrayMethods.__proto__ = Array.prototype
export let arrayMethods = Object.create(Array.prototype);
let methodNeedChange = [
'pop', 'push', 'unshift', 'shift', 'splice', 'sort', 'reverse'
]
methodNeedChange.forEach(methodName => {
//备份初始的方法
const original = Array.prototype[methodName]
def(arrayMethods, methodName, function () {
const result = original.apply(this, arguments)
//参数
const args = [...arguments];
//vue中data为什么要是对象,因为第一次必须是对象,才能监听data 里的属性
const ob = this.__ob__;
//要插入的数据
let insertList = [];
//当时这3个方法时,是需要添加数据的
switch(methodName){
case 'push':
case 'unshift':
//push(55,66)
insertList = args;
break;
case 'splice':
//splice( index ,howmany , 55,66 ) 下标为2以后的数据是添加的
insertList = args.slice(2);
break;
}
//将添加的数据响应式 observeArray是ob上的属性
ob.observeArray(insertList)
console.log(ob)
console.log('我是被重写的,此时会调用!')
return result;
}, false)
})
测试数组响应式
import observe from './observe.js'
let obj = {
a:888,
b:{
c:{
d:999
}
},
e:[11,22,33,44]
}
observe(obj);
obj.e.push(55)
会触发我们def设置的新方法, ob就是Observer的实例,上面有observeArray,walk等方法。
Vue的watch,相信大家一定都用过,watch可以监听数据,返回新旧值,下面为vue.js官网上的一样图:
可以看出,Data中有getter 与setter,getter 中 collect as Dependency (收集依赖) , setter 中有 Notify(通知 )
Dep 收集依赖与通知
export default class Dep{
constructor(){
//存放依赖 subs是所有的依赖,什么是依赖,就是我们去监听的数据,可能会很多个,都存放在subs中
this.subs = [];
}
//订阅添加
addSubs(sub){
this.subs.push(sub)
}
//依赖添加
depend(){
//这里的Dep.target很难理解,到底是什么呢,Dep就是class Dep{} , Dep.target是Watcher{}
this.addSubs(Dep.target)
}
//通知Watch更新
notice(){
let subs = this.subs;
//这个地方不定义l , i<subs.length 会死循环。
let l = subs.length;
for(let i=0;i<l;i++){
//subs[i]就是Watcher{}
//循环通知Watch去更新,只有变了才会更新,这个是在Watch判断的
subs[i].update();
}
}
}
Watch
import Dep from "./Dep.js";
export default class Watch{
constructor(target,exp,callBack){
//对象
this.target = target;
//getter 这个是根据key获取val的function
this.getter = this.parseExp(exp);
//回调函数
this.cb = callBack;
//值
this.value = this.get();
}
parseExp(exp){
var segments = exp.split('.');
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]]
}
return obj;
};
}
get(){
//Dep.target是全局的属性 是Watch{}
Dep.target = this;
const obj = this.target;
let value;
try{
value = this.getter(obj);
}finally{
Dep.target = null;
}
return value;
}
//set时触发
update(){
this.getAndInvoke();
}
//只有改变了 才会触发cb,需要判断一下新旧值
getAndInvoke(){
//获取Object.defineProperty set之后的值
const value = this.get();
if( value != this.value || typeof value == 'object' ){
//在vue中 $watch() 会返回新旧值
const oldVal = this.value;
//将新增更新
this.value = value;
this.cb.call(this.target,oldVal,value);
}
}
}
Observer中给实例添加上dep
class Observer {
constructor(){
//给对象加上__ob__属性,不可枚举,这里的this是Observer的实例
def(value, '__ob__', this, false);
//每个实例上都有dep
this.dep = new Dep()
...
}
}
get中 depend 和 set 中 notify
import observe from "./observe.js";
import Dep from "./Dep.js";
export default function defineReactive(data, key, value) {
//继续将value响应式,如果value是基础类型,直接返回,如果是对象,继续Observer也会走到这里,直到所有的属性都是响应式
let child = observe(value);
let dep = new Dep()
Object.defineProperty(data, key, {
//可枚举
enumerable: true,
//可删除
configurable: true,
get(){
console.log(`${key}被读取了,${value},get`);
if(Dep.target){
//收集依赖
dep.depend();
if(child){
//实例上都有dep属相在observer上添加的
child.dep.depend();
}
}
return value;
},
set(newVal){
console.log(`${key}被更改了,${newVal},set `);
value = newVal;
//设置新值,也需要observe变为响应式的
child = observe(value);
//发布订阅
dep.notice();
}
})
}
到这就完整的写完了数据响应式原理了,我们来测试一下
import observe from './observe.js'
import Watch from './Watch.js';
let obj = {
a:888,
b:{
c:{
d:999
}
},
e:[11,22,33,44]
}
observe(obj);
new Watch(obj,'e',(oldVal,newVal)=>{
console.log(`${oldVal}改变为${newVal}`)
})
obj.e = [55,66];
END。