vue 主要原理知识点

vue数据处理

vue在初始化的时候会调用initState(vm),这个函数接受vue的实例作为参数,所有的数据操作都和由这个函数处理

vue2响应式数据实现原理

  1. 首先调用observe(data)来观察数据,判断vue实例传递的数据是不是对象,不是对象返回,是对象在进行处理(函数也是对象)
export function observe(data) {
    // 这里的data 已经是data()函数的返回值了,是对象
    if (typeof data !== 'object' || data === null) return
    if (data.__ob__) return
    let ob = new Observe(data)
    return ob
}
复制代码
  1. 创建Observe实例

Observe实例为数据新增增加了一个__ob__属性,这个属性可以标识,当前数据对象,是否已经设置过双向数据绑定,如果已经设置,在数据开始观察的地方(observe(data)),就可以直接返回。

在这个类中,分别有walk,observeArray来处理对象和数组。

class Observe {
    constructor(value) {
        this.dep = new Dep()  // 为每对象,或者数组都绑定一个dep
        Object.defineProperty(value, '__ob__', {
            value: this,
            enumerable: false,
            configurable: false
        })
        if (Array.isArray(value)) {
            // 重写数组方法
            Object.setPrototypeOf(value, arrayMethods)
            // 监测数组属性
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }
    walk(obj) {
        const keys = Object.keys(obj)
        // 处理数据对象的每一个属性
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i])
        }
    }
    observeArray(value) {
        for (let i = 0; i < value.length; i++) {
            observe(value[i])
        }
    }
}
复制代码
  1. 对象:defineReactive就是对数据对象的每一个key做处理的

利用Object.defineProperty 对对象的添加额外的操作,当用户修改对象的属性值的时候,触发set,获取的时候出发get,Object.defineProperty已经能及时的获取数据的最新值了,但是还不能实现数据变化,ui界面同时更新。所以又对get做了特别的处理。

export function defineReactive(obj, key) {
    let val = obj[key]
    let childOb = observe(val) // 对结果递归拦截
    let dep = new Dep()  // 为每个属性创建一个dep
    Object.defineProperty(obj, key, {
        get() {
            // 取值的时候,把watcher 存入dep
            if (Dep.target) {
                dep.depend()
                if (childOb) {
                    // 主要用来搜集数组的依赖,当然,对象也是可以搜集的,但是对象,没有这样的触发
                    childOb.dep.depend()
                    if (Array.isArray(val)) {
                        dependArray(val)
                    }
                }
            }
            return val
        },
        set(newVal) {
            if (val === newVal) return
            observe(newVal)  // 把属性设置新对象的时候,也能有set,get
            val = newVal
            dep.notify()
        }
    })
}
复制代码
  1. 数组 数组的处理是循环数组的每一项

数组的对象元素,就会调用处理对象的方式,非对象元素,直接返回

 observeArray(value) {
    for (let i = 0; i < value.length; i++) {
        observe(value[i])
    }
}
复制代码
  1. 数据变化通知ui视图 (dep/watcher)
  • dep

dep的主要在数据更新触发dep.notify()通知更新,数据获取的时候触发dep.depend()搜集数据,同时dep里会搜集所有的wather,每一个属性都有一个watcher

let id = 0
export default class Dep {
    constructor() {
        this.id = id++
        this.subs = [] // 存放watcher
    }
    depend() {
        // 实现watcher也记住dep
        // Dep.target 是watcher
        if (Dep.target) {
            Dep.target.addDep(this)
        }
    }
    notify() {
        // 更新
        this.subs.forEach((watcher) => {
            watcher.update()
        })
    }
    addSub(watcher) {
        // 实现dep 记住watcher
        this.subs.push(watcher)
    }
}
// 保存watcher
Dep.target = null
export function pushTarget(watcher) {
    Dep.target = watcher
}
export function popTarget() {
    Dep.target = null
}
复制代码
  • watcher

真正的更新操作在queueWatcher里面,并且了,防止用户不挺的更改同一个属性,还设置了防抖操作

import Dep, { popTarget, pushTarget } from "./dep"
import { queueWatcher } from "./scheduler"

let id = 0
export default class Watcher {
    constructor(vm, expOrFn, cb, options = {}, isRenderWatcher) {
        this.vm = vm
        this.cb = cb
        this.options = options;
        this.lazy = (options && options.lazy) || false;
        this.dirty = this.lazy
        this.user = (options && options.user) || false;
        this.isRenderWatcher = isRenderWatcher
        this.id = id++
        this.deps = [] // 存放dep
        this.depsId = new Set()  // 防止存放多个相同的属性的dep

        // 因为用户wacther 的传入, expOrFn 是属性字符,而不是函数
        if (typeof expOrFn === "function") {
            this.getter = expOrFn
        } else {
            this.getter = function () {
                // 这里的方式是为了,用户深层取值a.b.c.d
                let path = expOrFn.split('.')
                let val = vm
                for (let i = 0; i < path.length; i++) {
                    val = val[path[i]]
                }
                return val
            }
        }

        this.value = this.lazy ? undefined : this.get()
    }
    get() {
        // 还是执行更新函数,只是更加语义化
        // 不是模版取值的操作,根本不会进入watcher
        pushTarget(this)
        let value = this.getter.call(this.vm)
        popTarget()
        return value

    }
    evaluate() {
        this.value = this.get()
        this.dirty = false
    }
    depend() {
        // 为了调用compuetd的渲染组件,能在页面属性更新的时候,页面模版中的computed 也更新
        let i = this.deps.length
        while (i--) {
            this.deps[i].depend()
        }
    }
    addDep(dep) {
        let id = dep.id
        if (!this.depsId.has(id)) {
            this.depsId.add(id)
            this.deps.push(dep)
            dep.addSub(this)
        }
    }
    run() {
        // oldValue, newValue 使用用户使用watch监听属性的时候会使用
        let oldValue = this.value
        let newValue = this.get()
        this.value = newValue
        if (this.user) {
            this.cb.call(this.vm, newValue, oldValue)
        }
    }
    update() {
        // 这种更新太粗暴,用户不停的修改同一个属性,就会调用多次,所以这里需要一个防抖的操作
        // this.get()
        if (this.options && this.options.sync) {
            this.run()
        } else if (this.lazy) {
            this.dirty = true
        } else {
            queueWatcher(this)
        }
    }
    // 模版数据变化之后,程序主动调用watcher,就可以更新数据了,每个组件中的数据,都因该有自己的watcher,所以这里watcher 要保存起来,vue把watcher 保存在了dep 类中

}
复制代码

总结: observe 遍历数据属性,为每个属性添加get,set操作,在数据set时同过dep.notify()通知watcher更新试图,在数据get的时候,触发dep.depend()进行数据收集,让wather把当前的dep收集起来

vue3响应式原理

以reactive为例,处理对象目标的利用new Proxy包对象,免去了vue2循环处理对象对象属性的过程。

// 存放target的对象, 分只读的和可变的
export const readonlyMap = new WeakMap()
export const reactiveMap = new WeakMap()

function createReactiveObject(targrt, isReadonly, baseHandlers) {
    const proxyMap = isReadonly ? readonlyMap : reactiveMap
    const existProxy = proxyMap.get(targrt)
    if (existProxy) {
        return existProxy
    }
    const proxy = new Proxy(targrt, baseHandlers)
    proxyMap.set(targrt, proxy)
    return proxy
}
复制代码

数据的依赖收集和触发更新

可以看到get时触发track收集数据,set时trigger通知更新

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        const res = Reflect.get(target, key, receiver)
        if (!isReadonly) {
            //依赖搜集
            track(target, TrackOpTypes.GET, key)
        }
        if (shallow) {
            return res
        }
        if (isObject(res)) {
            return isReadonly ? readonly(res) : reactive(res)
        }
        return res
    }
}

function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        const oldValue = target[key]
        // 这里要区分新增属性和修改属性
        // 新增肯定要更新,修改属性要判断新旧属性的值是否相等,不想等才修改
        // 修改又涉及到array 是数组,并且下表小于数组长度,那就是修改
        // 如果数组的下表大于数组长度,那就是新增
        const res = Reflect.set(target, key, value, receiver)
        const hasKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key) 
        if (!hasKey) {
            // 新增
            trigger(target, TriggerOpTypes.ADD, key, value)
        } else {
            // 修改
            trigger(target, TriggerOpTypes.SET, key, value, oldValue)
        }
        return res
    }
}
复制代码

track

对象都有一个打的depsMap,当获取对象上的属性的时候,又把属性对应的dep存放到当前对象的depsMap里

let targetMap = new WeakMap()
// 搜集用户的更新,方便以后调用,在get时使用
export function track(target, type, key) {
    if (activeEffect === undefined) return
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect)
    }
}
复制代码

trigger

tirgger里面收集了所有的需要被修改的属性,放在effects里面等待统一调用effect进行修改

// 通知依赖更新
export function trigger (target, type, key?, value?, oldValue?) {
    let depsMap = targetMap.get(target)
    // !depsMap 代表当前的属性根本没有被追踪,即,没有被使用过
    if (!depsMap) return 
    // 要一块通知
    const effects = new Set()
    function add(effectsToAdd) {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => effects.add(effect))
        }
    }
    // 修改数组的长度
    if (key === 'length' && isArray(target)) {
        depsMap.forEach((dep: any, key: any) => {
            // 都代表为数组新增了元素
            if (key === 'length' || (isIntegerKey(key) && Number(key) >= value )) {
                add(dep)
            }
        })
    } else {
        // 对象
        if (key !== undefined) {
            add(depsMap.get(key))
        }
        switch(type) {
            case TriggerOpTypes.ADD: 
                if (isArray(target) && isIntegerKey(key)) {
                    add(depsMap.get('length'))
                }
        }
    }
    // 搜集了,这次更新的所有的回调函数,然后统一更新
    effects.forEach((effect: any) => {
        return effect()
    })
}
复制代码

总结: vue3的数据响应主要用new Proxy取代Object.defineProperty,减少了循环操作,把数据修改的dep.dependdep.notify变成了tracktrigger但是主要的思想是不变的

vue的渲染流程

  • vue是实例话的时候调用_init方法,调用$mount options参数渲染优先级render>template>el,确定渲染对象之后调用compileToFunctions
 Vue.prototype.$mount = function (el) {
    el = el && document.querySelector(el)
    const vm = this
    const options = vm.$options
    // 把真实的el 挂载到vm, 方便以后使用
    vm.$el = el
    if (!options.render) {
        let template = options.template
        if (!template && el) {
            template = el.outerHTML
        }
        // 将模版编译成函数
        const render = compileToFunctions(template)
        options.render = render
    }

    mountComponent(vm)
}
复制代码
  • compileToFunctions

将模版转换成ast语法树,再由ast转换成可渲染的字符串

export function compileToFunctions(template) {
    // 解析模版成ast
    const ast = parseHTML(template)
    // 转换ast 成 render
    const code = generate(ast)
    let render = `with(this){return ${code}}`

    let fn = new Function(render) // 让code字符串变成函数

    return fn
}
复制代码
  • mountComponent

然后调用mountComponent,先使用vue原型上的_render产生虚拟节点,通过_update调用patch把虚拟节点转换成真实节点,再调用watcher执行渲染函数。

export function mountComponent(vm) {
    // 这里可以表示更新是组件级别的更新,渲染模版,数据显示,都放在watcher   _render() 返回虚拟dom, _update() 执行比对和渲染过程
    let updateComponent = () => {
        let res = vm._update(vm._render())
    }
    // 实际是执行updateComponent 函数
    new Watcher(vm, updateComponent, () => { }, {}, true /* 标志是渲染wacher */)
}
复制代码

总结:vue的渲染流程就是通过优先级,选择正确的模版,然后把模版转换成ast语法树,再把ast转换成可渲染的render,然后产生虚拟节点,通过_update中的patch转变成真实节点,渲染到页面上

vue的组件渲染原理

render可以创建元素虚拟节点和文本虚拟节点

export function renderMixin(Vue) {
    Vue.prototype._c = function (...args) { // 元素虚拟节点
        const vm = this;
        return createElement(vm, ...args)
    }
    Vue.prototype._v = function (text) { // 文本虚拟节点
        const vm = this;
        return createTextVnode(vm, text)
    }
    Vue.prototype._s = function (val) { // 转化字符串
        // 触发this.XXX 直接把变量的值取了出来
        return val === null ? '' : (typeof val === 'object') ? JSON.stringify(val) : val;
    }
    Vue.prototype._render = function () {
        const vm = this;
        let render = vm.$options.render
        let vnode = render.call(vm)
        return vnode
    }
}
复制代码

render创建虚拟节点的时候会判断,当前是原始标签还是组件标签(vue内部有搜集所有的原始标签),如果不是原始标签就走createComponent创建组件

export function createElement(vm, tag, data = {}, children) {
    // 这里要对标签做区分,区分组件标签和原始标签
    debugger
    if (isReservedTag(tag)) {
        return vnode(vm, tag, data, data.key, children, undefined)
    } else {
        // 组件渲染 获取组件
        const Ctor = vm.$options.components[tag]
        return createComponent(vm, tag, data, data.key, children, Ctor)
    }
}
复制代码

createComponent

创建组件的时候,不管组件是不死函数最终都会利用Vue.extend把组件转变成函数,并且为组件增加hook,在hook上绑定init,在由虚拟节点变成真实节点的时候,手动的调用$mount挂载组件

function createComponent(vm, tag, data, key, children, Ctor) {
    debugger
    if (isObject(Ctor)) {
        // 如果组件是对象,要转换成函数 Vue.extend
        Ctor = vm.$options._base.extend(Ctor)
    }
    // 为组件增加生命周期
    data.hook = {
        init(vnode) {
            // 这里是因为vue.extend 的sub 类中,有this._init 方法
            const child = vnode.componentInstance = new vnode.componentOptions.Ctor({})
            child.$mount()
        } // 初始化钩子函数
    }
    return vnode(tag, `vue-component-${Ctor.cid}-${tag}`, data, key, undefined, undefined, {
        Ctor
    })
}
复制代码

总结:在render创建虚拟dom的时候会注意当前是不是html的原始标签,不是原始标签的话,就在创建的虚拟节点,调用vue.extend生成组件的实例。组件虚拟节点上上绑定hook,存放_init方法,并且在之后的patch过程中,通过componentInstance来判断是不是组件虚拟节点,调用_init函数,并且手动调用$mount,组件的渲染顺序是先父后子

vue.component

initGlobalAPI

 Vue.component = function (id, definition) {
        definition.name = definition.name || id
        definition = this.options._base.extend(definition)  // 这里保证不管怎么调用extend,都指向vue
        this.options.components[id] = definition
  }
复制代码

vue.component内部调用vue.extend

export function initExtend(Vue) {
    Vue.extend = function (options) {
        // extend 里面的this,指向的也是this 因为 Vue.options._base 已经这样设定过了
        const Super = this
        const Sub = function VueComponent(options) {
            this._init(options)
        }
        Sub.cid = cid++
        Sub.prototype = Object.create(Super.prototype)
        Sub.prototype.constructor = Sub
        Sub.component = Super.component

        Sub.options = mergeOptions(Super.options, options)
        return Sub
    }
}
复制代码

通过mergeOptionsvue.component的属性合并到vue.components

watch 原理

initWatch把watch的值都转变成单个的元素,传递给createWatcher

function initWatch(vm, watch) {
    for (let key in watch) {
        const handler = watch[key];
        if (Array.isArray(handler)) {
            for (let i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i])
            }
        } else {
            createWatcher(vm, key, handler)
        }
    }
}
复制代码

createWatcher 获取watch监听的元素是对象的时候,获取handler,调用具体响应的方法,

function createWatcher(vm, key, handler, options) {
    if (isObject(handler)) {
        options = handler
        handler = handler.handler
    }

    if (typeof handler === 'string') {
        handler = vm.$options.methods[handler]
    }
    // watch 最终走到$watch
    vm.$watch(key, handler, options)
}
复制代码

$watch 最终还是要使用watcher

export function stateMixin(Vue) {
    Vue.prototype.$watch = function (expOrFn, cb, options) {
        // 这里调用Watcher类
        const vm = this
        options.user = true // 用来区分不是渲染watcher,而是用户watcher
        new Watcher(vm, expOrFn, cb, options)
    }
}
复制代码

总结:vue收集用户传入的watch选项,根据数组和对象作不同处理,最终进入watcher更新数据

computed 原理

分函数获取和对象获取,为了防止computed主动取值,特别设置了一个lazy传入watcher

function initComputed(vm, computed) {
    // 用__computedWatchers 存放每个属性对应的watcher
    const watchers = vm._computedWatchers = {}
    for (let key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        // lazy 是用来表示computed, 不要重复更新数据的
        watchers[key] = new Watcher(vm, getter, () => {}, {
            lazy: true
        })
        // 取值操作,把computed的属性值变成实例中可以取到的属性
        defineComputed(vm, key, userDef)
    }
}
复制代码

defineComputed 把不同的computed的取值操作都挂载到,对象的get上Object.defineProperty实现属性值的获取与设置

function defineComputed(target, key, userDef) {
    // 要设置缓存的,防止每次获取computed里的值,都从新执行computed函数,要数据变化,才触发函数的执行
    if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = createComputedGetter(key)
    } else {
        sharedPropertyDefinition.get = createComputedGetter(key)
        sharedPropertyDefinition.set = userDef.set || (() => {})
    }

    Object.defineProperty(target, key, sharedPropertyDefinition)
}
复制代码

computed 获取值的时候,方式不停的调用相同的属性值,在取值的时候设置dirty缓存,同时也对comouted的值进行了依赖收集,当computed的依赖的值变化的时候,会在watcher的内部,改变dirty的值,让computed获取到最新的值

function createComputedGetter(key) {
    return function () {
        let watcher = this._computedWatchers[key] // 计算属性的watcher

        if (watcher.dirty) {
            // 这里这样写是应为,在watcher 中有 this.value = this.lazy ? undefined : this.get() 判断,当前value = undefined
            watcher.evaluate()
        }

        if (Dep.target) {
            watcher.depend()
        }
        return watcher.value
    }
}
复制代码

总结:computed绑定了watcher,利用lazy实现在watcher内部不会主动的获取值,然后利用 Object.defineProperty来触发getset,同时在get的时候,根据watcherdirty设置缓存,防止不停的取同一个属性,然后在依赖的属性值update的时候,改变dirty,让computed的值在get时候,从新变成新的值

watch和computed的区别

  • watch内部会主动的获取值,computed不会,除非被调用
  • watch 没有缓存,computed有缓存设置
  • watch不需要依赖,computed需要依赖

v-for/v-if/v-model

  • vue2 v-for的优先级大于v-if

  • vue3 v-if的优先级大于v-for

  • v-for 会解析成函数表达式

  • v-if 会被解析成三元运算符

  • v-show 指令,会先存一下原来的样式,显示的时候,就把原来的样式复原

  • v-model 是一个真实的指令

    • 放在input上会给input绑定value和input事件
    • 放在组件上会形成一个model属性,变成input和value的语法糖
     if (el.model) {
        data += `model:{value:${
          el.model.value
        },callback:${
          el.model.callback
        },expression:${
          el.model.expression
        }},`
      }
    复制代码

vue组件名字的好处

  • 可以根据名字查找组件vm.$options[name](递归组件)
  • 缓存(keep-alive)
  • name识别组件尤其是跨级通信

vue组件传值方式

  • props/$emit
  • provide/ inject
  • v-model
  • attrs/attrs/listeners
  • $refs
  • parent/parent/children
  • eventBus
  • vuex

$on/$emit

  • 组件上的data.on,最终在虚拟节点上产生一个listeners
  • init的时候opts._parentListeners = vnodeComponentOptions.listeners
  • 然后在初始化事件时
export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
复制代码

add就是$on 收集组件上的所有方法,是一个数组

Vue.prototype.$on = function (event, fn){
    const vm = this
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }
复制代码

$emit触发找到对应的事件触发里面的函数

Vue.prototype.$emit = function (event) {
    const vm = this
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
复制代码

$parent/$children

组件在调用init的时候会保留当前的虚拟节点,绑定在当前的组件上_parent,然后在leftCirlce的时候,绑定到parnet,parnet,为parnet增加$children保存当前的组件

export function initLifecycle(vm: Component) {
  debugger
  const options = vm.$options
  // locate first non-abstract parent
  let parent = options.parent
  // 判断父组件是不是抽象组件
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
  // 父级挂在
  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm
  vm.$children = []
  vm.$refs = {}
  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}
复制代码

provide/ inject

在父组件的实例上添加provide之后,会存放在实例的_provided中,子组件遇到inject通过$parent一直向上查找到具有provide的父组件,获取值

export function initProvide (vm) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

export function initInjections (vm) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      defineReactive(vm, key, result[key])
    })
    toggleObserving(true)
  }
}

export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    const result = Object.create(null)
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      if (key === '__ob__') continue
      const provideKey = inject[key].from
      let source = vm
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      if (!source) {
        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    return result
  }
}
复制代码

vue.set 原理

  • 传入的属性是数组的话,直接调用数组的splice方法,因为数组的splice方法已经被改写了
  • 如果这个属性以前就被监听过,直接返回
  • 如果属性值数据响应,就直接返回
  • 如果是个新值,就重新执行一遍数据的依赖收集,且触发一下更新操作
export function set(target, key val) {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}
复制代码

vue.use 原理

vue.use 接收插件,插件有2种形式,函数和带有install属性的对象,当vue之前已经加载过插件,就会直接返回(单例模式)。如果plugin是函数,就直接调用,如果有install 就调用install

export function initUse (Vue) {
  Vue.use = function (plugin) {
     // 单例模式
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    // 去除插件的第一个参数 vue
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}
复制代码

slot

  • 普通插槽

父组件中渲染,渲染完之后,遇到slot就替换成之前渲染的内容

  • 作用域插槽

子组件中渲染 slot-scope

keep-alive

缓存,使用lru算法(最久未使用法)

vue中的设计模式

  • 工厂模式:根据不同的参数,返回不同的实例
  • 单例模式:vue插件
  • 发布订阅:onon emit
  • 代理模式: vue3 proxy
  • 中介者模式:vuex

vue diff算法

现在懒得写,过几天在写

vue3– ref 原理

ref 内部也是使用类绑定value,在get,set的时候分别使用track tigger收集数据,更新数据,并且在数据是对象的时候,调用reactive

export function ref(value) {
    return createRef(value)
}
export function createRef(value, shallow = false) {
    // 类会在babel 转化为Object.defineProperty()的方式
    return new RefImpl(value, shallow)
}
function convert(value) {
    return isObject(value) ? reactive(value): value
}
class RefImpl {
    public _value
    public readonly __v_isRef = true
    constructor(public _rawValue, public readonly _shallow = false) {
        // 这里的_value 要特别判断,如果是浅层的就直接返出,不是浅层的要针对对象做特别处理
        this._value = _shallow ? _rawValue : convert(_rawValue)
    }

    get value() {
        track(this, TrackOpTypes.GET, 'value')
        return this._value
    }
    set value(newVal) {
        // 先判断数据有没有变化
        if (hasChanged(newVal, this._rawValue)) {
            this._rawValue = newVal
            this._value = newVal
            trigger(this, TriggerOpTypes.SET, 'value', newVal)
        }
    }
}
复制代码

vue3– toRef原理

内部使用类,绑定value,对象的值是proxy包装过的,不用担心数据更新问题

export function toRef(target, key) {
    
    return new ObjectRefImpl(target, key)
}

class ObjectRefImpl {
    public readonly __v_isRef = true
    constructor(public _object, public _key) {

    }
    // 取值和赋值的操作,都操作的object,而且object是proxy的对象,自然可以实现更新
    get value() {
        return this._object[this._key]
    }
    set value(newVal) {
        this._object[this._key] = newVal
    }
}
复制代码

vue3– toRefs

toRefs的实现主要是循环调用toRef

export function toRefs(object) {
    const ret = isArray(object) ? new Array(object.length) : {}
    for(let key in object) {
        ret[key] = toRef(object, key) 
    }
    return ret
}
复制代码

vue3–computed

computed也是通过内部的类来实现获取value,并在get,set的时候出发依赖收集,但是computed也设置了缓存和数据变化的时候才更新

export function computed(getterOrOptions) {
    let getter
    let setter
    if (isFunction(getterOrOptions)) {
        getter = getterOrOptions
        setter = () => {
            console.log('=====函数不能设置set')
        }
    } else {
        getter = getterOrOptions.get
        setter = getterOrOptions.set
    }
    return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions) || !getterOrOptions.get)
}
class ComputedRefImpl {
    private effect
    private _value
    public __v_isReadonly
    public __v_isRef = true
    public _dirty = true
    constructor(getter, public _setter, public isReadonly) {
        this.__v_isReadonly = isReadonly
        this.effect = effect(getter, {
            lazy: true,
            scheduler: () => {
                if (!this._dirty) {
                    this._dirty = true
                    trigger(toRaw(this), TriggerOpTypes.SET, 'value')
                }
            }
        })
    }

    get value() {
        if (this._dirty) {
            this._value = this.effect()
            this._dirty = false
        }
        track(toRaw(this), TrackOpTypes.GET, 'value')
        return this._value
    }

    set value(newVal) {
        this._setter(newVal)
    }
}
复制代码

vue-router模式

  • hash: hashChange 没有兼容问题,但是丑
  • history: popState页面会404,需要后端配置

vuex的理解

  • 单向数据流的概念
  • 状态集中管理,实现多组件状态共享
  • vuex也是通过new vue产生实例,达到响应式数据的目的
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享