vue2源码学习-响应式原理
Posted ~往无前
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2源码学习-响应式原理相关的知识,希望对你有一定的参考价值。
observer类
/src/core/observer/index.js
将一个正常的object转换为每个层级的属性都是响应式(可以被侦测的)的object;
// Observe.js
import defineReactive from './defineReactive.js
import arrayMethods from './rewriteArray.js'
class Observer
constructor(value)
def(value,'__ob__',this,flase). //给value对象绑定ob属性,且不可枚举
if(Array.isArray(value)). //数组对象的处理
value.__proto__ = arrayMethods; //修改数组的隐式原型对象为更改后的数组原型对象
else
const keys = Object.keys(value);
for (let i = 0; i < keys.length; i++)
const key = keys[i];
defineReactive(value, key);
observeArray(value)
for (let i = 0, l = value.length; i < l; i++)
observe(value[i], false);
var obj=
a:
m:
n:5
,
b:[1,2,3]
observe方法是最外层级的,上来先看observe(obj). —>>> obj身上有没有 __obj__ —>>> new Observer() ---->>> 遍历下一层属性,逐个defineReactive
defineReactive方法
/src/core/observer/index.js
//defineReactive.js文件
import observe from './observe.js
export default function defineReactive(data,key,val)
if(arguments.length===2)
val=data[key]
//递归调用子元素,至此形成了递归,这个递归不是函数自己调用自己,而是多个函数,类循环调用;
let childOb = observe(val) //返回val的ob对象
Object.defineProperty(data,key,
//可枚举
enumrable:true,
//可被配置,比如可以被删除delete
configurable:true,
//getter
get()
console.log('你试图访问obj的'+key+ '属性')
return val
,
set(newValue)
console.log('你试图改变obj的'+key+'属性‘,newValue)
if(val===newValue) return
val=newValue
//当设置了新值,这个新值也要被observer
childOb=observe(newValue)
//index.js
import defineReactive from './defineReactive'
observe方法
// 创建observe函数,注意函数的名字没有r,value其实是obj[key]
function observe(value)
if(typeof value != 'object') return;
//定义ob
var ob;
if(typeof value.__ob__ !== 'undefined')
ob = value.__ob__;
else
ob = new Observer(value)
return ob
在这里插入图片描述
1.obj对象会先通过observe处理,看obj身上有没有__ob__属性;
2.没有则在Observe构造器中给obj对象添加__ob__属性; (精:def (obj,__ob__属性,this),这里的this指向的是实例化出来的observe对象,然后赋值给__ob__属性)
3.添加__ob__属性需要调用一个def方法:原因:__ob__属性不可枚举,所以单独使用一个def方法;
4.在Obsever类的构造器中遍历obj每一个属性,调用definReactive方法,进行数据劫持;
5.在遍历中如果该obj[key]是一个object对象,
数组响应式实现
src/core/observer/array.js
// rewriteArray.js
//得到Array
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto); //将数组的原型对象绑定在一个对象的原型上
//对数组的7个方法进行改造
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
//重写方法
methodsToPatch.forEach(function (method)
// cache original method
const original = arrayProto[method];
// mutator是给数组方法重新定义的一个方法 def(obj,key,value)
def(arrayMethods, method, function mutator(...args)
const result = original.apply(this, args); //this是当前调用数组方法的对象,args是传进来的参数
const ob = this.__ob__;
let inserted; // 给数组对象添加的元素
switch (method)
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
if (inserted)
//给添加的所有元素绑定响应式
ob.observeArray(inserted);
return result;
);
);
export arrayMethods
def
/src/core/util/lang.js
export function def(obj: Object,key: string, val: any, enumerable?: boolean)
Object.definepProperty(obj,key,
value: val,
enumerable: !!enumberable,
writable: true,
configurable: true,
protoAugment
/src/core/observer/index.js
/**
*设置 target._proto_的原型对象为src
*比如数组对象,arr.__proto__arrayMethods
*/
function protoAugment(target,src: Object)
target.__proto__ = src
copyAugment
/src/core/observer/index.js
/**
*在目标对象上定义指定属性
*比如数组:为数组对象定义那七个方法
*/
function copyAugment(target: Object, src:Object, keys: Array<string>)
for(let i=0; i=keys.length; i<l; i++)
const key = keys[i]
def(target, key, src[key])
Dep
/src/core/observer/dep.js
import type Watcher from './watcher'
import remove from '../util/index'
import config from '../config'
let uid=0
/**
*一个dep对应一个obj.key
*在读取响应式数据时,负责收集依赖,每个dep(或者说obj.key)依赖的watcher有哪些
*在响应式数据更新时,负责通知dep中哪些watcher去执行update方法
*/
export default class Dep
static target: ?Watcher;
id: number;
subs: Array<Watcher>
construcotr()
this.id=uid++
this.subs=[]
//在dep中添加watcher
addSub(sub: Watcher)
this.subs.push(sub)
removeSub(sub:Watcher)
remove(this.subs,sub
//向watcher中添加dep
depend()
if(Dep.target)
Dep.target.addDep(this)
/**
*通知dep中所有watcher,执行watcher.update()方法
*/
notify()
const subs = this.subs.slice()
if(process.env.NODE_ENV !== 'production' && !config.async)
subs.sort((a,b)=> a.id - b.id);
for(let i=0, l=subs.length;i<l;i++)
subs[i].update()
/**
*当前正在执行的watcher,同一时间只会有一个watcher执行
*Dep.target = 当前正在执行的watcher
*通过调用pushTarget方法完成赋值,调用popTarget方法完成重置(null)
*/
Dep.target = null
const targetStack = []
//在需要进行依赖收集的时候调用,设置Dep.target=watcher
export function pushTarget (target: ?Watcher)
targetStack.push(target)
Dep.target = target
//依赖收集结束调用,设置Dep.target = null
export function popTarget()
targetStack.pop()
Dep.target = targetStack[targetStack.length-1]
Watcher
/src/core/observer/watcher.js
/**
* 一个组件一个watcher(渲染watcher)或者一个表达式watcher(用户watcher)
* 当数据更新时watcher会被触发,访问this.computedProperty时也会触发watcher
*/
export function class Watcher
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
acive: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor(
vm: Component,
exOrFn: string | Function,
cb: Function,
options? : ?Object,
isRenderWatcher? : boolean
)
this.vm = vm
if(isRenderWatcher)
vm._watcher = this
vm._watchers.push(this)
if(options)
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
else
this.deep = this.user = this.lazy = this.sync =false
this.cb = cb
this.id = ++uid
this.active = true
this.dirty = this.lazy
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn,toString()
: ''
if(typeof expOrFn === 'function')
this.getter = expOrfn
else
// this.getter = function() return this.xx
//在this.get中执行this.getter 时会触发依赖收集
//待后续this.xx 更新时就会触发响应式
this.getter = parsePath(exOrfn)
if(!this.getter)
this.getter = noop
process.env.NODE_ENV !== 'production' && warn$2("Failed watching path: \\"".concat(expOrFn, "\\" ") +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.', vm);
this.value = this.lazy
? undefined
: this.get()
/**
* 执行this.getter,并重新收集依赖
* this.getter 是实例化watcher时传递的第二个参数,一个函数或者字符串,比如updateComponent或者parsePath返回的读取this.xx属性值的函数
* 为什么要重新收集依赖?
* 因为触发更新说明有响应式数据被更新了,但是被更新的数据虽然已经经过observe观察了,但是却没有进行依赖收集,
* 所以,在更新页面时,会重新执行一次render函数,执行期间会触发读取操作,这时候进行依赖收集
*/
get()
//打开Dep.target, Dep.target = this
pushTarget(this)
//value为回掉函数执行的结果
let value
const vm = this.vm
try
//执行回掉函数,比如updateComponent ,进入patch阶段
value = this.getter.call(vm,vm)
catch(e)
if(this.user)
handleError(e,vm,`getter for watcher "$this.expression"`
else
throw e
finally
if(this.deep)
traverse(value)
//关闭Dep.target, Dep.target=null
popTarget()
this.cleanupDeps()
return value
/**
* Add a dependency to this directive
* 1.添加dep给自己(watcher)
* 2.添加自己(watcher)到dep
*/
addDep(dep: Dep)
//判重,如果dep已经存在则不重复添加
const id = dep.id
if(!this.newDepIds.has(id)
//缓存dep.id,用于判重
this.newDepIds.add(id)
//添加
this.newDeps.push(dep)
// 避免在dep中重复添加watcher,this.depIds 的设置在cleanDeps 方法中
if(!this.depIds.has(id)
//添加watcher自己到dep
dep.addSub(this)
/**
* Clean up for dependency collection
*/
cleanupDeps ()
let i = this.deps.length;
while(i--)
const dep = this.deps[i]
if(!this.newDepsIds.has(dep.id))
dep.removeSub(this)
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length =
/**
*根据watcher配置项,决定接下来怎么走,一般是queueWatcher
*/
update()
if(this.lazy)
//懒执行走这里,比如computed
//将dirty 置为true,可以让computedGetter执行重新计算computed回掉函数的执行结果
this.dirty = true
else if(this.sync)
//同步执行,在使用vm.$watch或者watch选项时可以传一个sync选项
//当为true时在数据更新时该watcher就不走异步更新队列,直接执行this.run
//方法进行更新
//这个属性在官方文档中没有出现
this.run()
else
//更新时一般走这里,将watcher放入wacher队列
queueWatcher(this)
/**
* 由刷新队列函数flushSchedulerQueue调用,完成以下几件事:
* 1.执行实例化watcher传递的第二个参数,updateComponent或者获取this.xx的的一个函数(parsePath 返回的函数)
* 2.更新旧值为新值
* 3.执行实例化watcher时传递的第三个参数,比如用户watcher的回掉函数
*/
run()
if(this.active)
//调用this.get方法
const value = this.get()
if(
value !== this.value ||
isObject(value) ||
this.deep
)
// 更新旧值为新值
const oldValue = this.value
this.value = value
if(this.user)
//如果是用户watcher,则执行用户传递的第三个参数 ---回掉函数,参数为val和oldVal
try
this.cb.call(this.vm,value,oldValue)
catch(e)
handleError(e,this.vm,`callback for watcher "$this.expression")
else
//渲染watcher,this.cb=noop,一个空函数
this.cb.call(this.vm,value,oldValue)
/**
* 懒执行watcher 会调用该方法
* 比如:computed,在获取以上是关于vue2源码学习-响应式原理的主要内容,如果未能解决你的问题,请参考以下文章