目录
在以往的经验中,我们主技术栈为vue的前端开发,在面试的时候被问到响应式原理的概率是非常大的。而现在市场上,基本上老项目还是vue2,新项目大部分都会选择vue3来开发。
所以,我们有必要来总结对应的源码和面试题,come on ~
简述:在组件初始化时,通过Object.defineProperty来进行对数据进行劫持,构造get set方法。get来进行依赖的收集dep,set用于通知dep.notify。
- export function observe (value: any, asRootData: ?boolean): Observer | void {
- // 观察者
- let ob: Observer | void
- if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
-
- ob = value.__ob__
- } else if (
- shouldObserve &&
- !isServerRendering() &&
- (Array.isArray(value) || isPlainObject(value)) &&
- Object.isExtensible(value) &&
- !value._isVue
- ) {// 创建观察者
- ob = new Observer(value)
- }
- if (asRootData && ob) {
- ob.vmCount++
- }
- return ob
- }
2. ob对象根据不同的数据类型进行不同的响应化操作
- export class Observer {
- value: any;
- dep: Dep; // 保存数组类型数据的依赖
-
- constructor (value: any) {
- this.value = value
- this.dep = new Dep()
- def(value, '__ob__', this) // 在getter中可以通过__ob__可获取ob实例
- if (Array.isArray(value)) { // 数组响应化
- protoAugment(value, arrayMethods)
- this.observeArray(value)
- } else { // 对象响应化
- this.walk(value)
- }
- }
-
- /**
- * 遍历对象所有属性定义其响应化
- */
- walk (obj: Object) {
- const keys = Object.keys(obj)
- for (let i = 0; i < keys.length; i++) {
- defineReactive(obj, keys[i])
- }
- }
-
- /**
- * 对数组每一项执行observe
- */
- observeArray (items: Array
) { - for (let i = 0, l = items.length; i < l; i++) {
- observe(items[i])
- }
- }
- }
3. defineReactive()函数定义get、set方法,并收集依赖。
- export function defineReactive (
- obj: Object,
- key: string,
- val: any,
- customSetter?: ?Function,
- shallow?: boolean
- ) {
- const dep = new Dep() // 一个key一个Dep实例
-
- // 递归执行子对象响应化
- let childOb = !shallow && observe(val)
-
- // 定义当前对象getter/setter
- Object.defineProperty(obj, key, {
- enumerable: true,
- configurable: true,
- get: function reactiveGetter () {
- // getter负责依赖收集
- if (Dep.target) {
- dep.depend()
- // 若存在子observer,则依赖也追加到子ob
- if (childOb) {
- childOb.dep.depend()
- if (Array.isArray(value)) {
- dependArray(value) // 数组需特殊处理
- }
- }
- }
- return value
- },
- set: function reactiveSetter (newVal) {
- if (newVal === value || (newVal !== newVal && value !== value)) {
- return
- }
- val = newVal // 更新值
- childOb = !shallow && observe(newVal) // childOb更新
- dep.notify() // 通知更新
- }
- })
- }
4. Dep管理一组watcher, dep管理的值更新时,通知对应的watcher进行更新
- export default class Dep {
- static target: ?Watcher; // 依赖收集时的wacher引用
- subs: Array<Watcher>; // watcher数组
-
- constructor () {
- this.subs = []
- }
- //添加watcher实例
- addSub (sub: Watcher) {
- this.subs.push(sub)
- }
- //删除watcher实例
- removeSub (sub: Watcher) {
- remove(this.subs, sub)
- }
- //watcher和dep相互保存引用
- depend () {
- if (Dep.target) {
- Dep.target.addDep(this)
- }
- }
-
- notify () {
- // stabilize the subscriber list first
- const subs = this.subs.slice()
-
- for (let i = 0, l = subs.length; i < l; i++) {
- subs[i].update()
- }
- }
- }
5. watcher监控数据或者关联组件的一个更新函数,数据更新则执行回调或者更新函数背调用。
- export default class Watcher {
- constructor (
- vm: Component,
- expOrFn: string | Function,
- cb: Function,
- options?: ?Object,
- isRenderWatcher?: boolean
- ) {
- this.vm = vm
- // 组件保存render watcher
- if (isRenderWatcher) {
- vm._watcher = this
- }
- // 组件保存非render watcher
- vm._watchers.push(this)
-
- // options...
-
- // 将表达式解析为getter函数
- // 如果是函数则直接指定为getter,那什么时候是函数?
- // 答案是那些和组件实例对应的Watcher创建时会传递组件更新函数updateComponent
- if (typeof expOrFn === 'function') {
- this.getter = expOrFn
- } else {
- // 这种是$watch传递进来的表达式,它们需要解析为函数
- this.getter = parsePath(expOrFn)
- if (!this.getter) {
- this.getter = noop
- }
- }
- // 若非延迟watcher,立即调用getter
- this.value = this.lazy ? undefined : this.get()
- }
-
- /**
- * 模拟getter, 重新收集依赖re-collect dependencies.
- */
- get () {
- // Dep.target = this
- pushTarget(this)
- let value
- const vm = this.vm
- try {
- // 从组件中获取到value同时触发依赖收集
- value = this.getter.call(vm, vm)
- }
- catch (e) {}
- finally {
- // deep watching,递归触发深层属性
- if (this.deep) {
- traverse(value)
- }
- popTarget()
- this.cleanupDeps()
- }
- return value
- }
-
- addDep (dep: Dep) {
- const id = dep.id
- if (!this.newDepIds.has(id)) {
- // watcher保存dep引用
- this.newDepIds.add(id)
- this.newDeps.push(dep)
- // dep添加watcher
- if (!this.depIds.has(id)) {
- dep.addSub(this)
- }
- }
- }
-
- update () {
- // 更新逻辑
- if (this.lazy) {
- this.dirty = true
- } else if (this.sync) {
- this.run()
- } else {
- //默认lazy和sync都是false,所以会走该逻辑
- queueWatcher(this)
- }
- }
- }
- // 数组原型
- const arrayProto = Array.prototype
- // 修改后的原型
- export const arrayMethods = Object.create(arrayProto)
- // 七个待修改方法
- const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
-
- /**
- * 拦截这些方法,额外发送变更通知
- */
- methodsToPatch.forEach(function (method) {
- // 原始数组方法
- const original = arrayProto[method]
- // 修改这些方法的descriptor
- def(arrayMethods, method, function mutator (...args) {
- // 原始操作
- const result = original.apply(this, args)
- // 获取ob实例用于发送通知
- 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)
- // 通知更新
- ob.dep.notify()
- return result
- })
- })
- if (Array.isArray(value)) {
- // 替换数组原型
- protoAugment(value, arrayMethods) // value.__proto__ = arrayMethods
- this.observeArray(value)
- }
2. 数组响应式的循环o'berve
- observeArray (items: Array
) { - for (let i = 0, l = items.length; i < l; i++) {
- observe(items[i])
- }
- }
3. 依赖收集时做特殊处理,如果数组项中还是数组需要deepArray
- //getter中
- if (Array.isArray(value)) {
- dependArray(value)
- }
-
- // 数组中每一项也需要收集依赖
- function dependArray (value: Array
) { - for (let e, i = 0, l = value.length; i < l; i++) {
- e = value[i]
- e && e.__ob__ && e.__ob__.dep.depend()
- if (Array.isArray(e)) {
- dependArray(e)
- }
- }
- }
持续更新中~ 如有不足,敬请指出,共同进步~