vue数据处理
vue
在初始化的时候会调用initState(vm)
,这个函数接受vue
的实例作为参数,所有的数据操作都和由这个函数处理
vue2响应式数据实现原理
- 首先调用
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
}
复制代码
- 创建
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])
}
}
}
复制代码
- 对象:
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()
}
})
}
复制代码
- 数组 数组的处理是循环数组的每一项
数组的对象元素,就会调用处理对象的方式,非对象元素,直接返回
observeArray(value) {
for (let i = 0; i < value.length; i++) {
observe(value[i])
}
}
复制代码
- 数据变化通知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.depend
和dep.notify
变成了track
,trigger
但是主要的思想是不变的
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
}
}
复制代码
通过mergeOptions
把vue.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
来触发get
和set
,同时在get
的时候,根据watcher
的dirty
设置缓存,防止不停的取同一个属性,然后在依赖的属性值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
- listeners
- $refs
- 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增加$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插件
- 发布订阅: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产生实例,达到响应式数据的目的