vue2源码解析(一)
2021/10/21 20:11:55
本文主要是介绍vue2源码解析(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
src\platforms\web\entry-runtime-with-compiler.js 源码开始位置(引入了Vue构造函数)
扩展$mount,处理可能存在的templete或者el选项,重新编译template为render函数
src\platforms\web\runtime\index.js(按照上面的引入vue往上查找)
一、定义了一个_patch_函数
render函数的目的:获取虚拟dom vdom
patch函数的目的:(diff算法也在里面)1、初始化 2、更新
不管是初始化还是更新,都是将虚拟dom变成真实dom
$mount的目的:生成真实dom($mount里面一定会调用render和patch)
二、实现$mount,这样在第一个路径里才可以扩展mount
$mount最后return了一个mountComponent(vm,el, hydrating:boolean)
src\core\index.js(再往上查找Vue构造函数)
initGlobalAPI(Vue) 初始化全局api 例如:Vue.component/filter/directive/use/mixin/util/extend src\core\instance\index.js(继续往上查找到Vue构造函数) 1、声明构造函数// 1.声明构造函数 function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) //下面的方法initMixin(Vue)执行this._init方法 }
2、实例属性,实例方法
// 2.实例属性,实例方法: initMixin(Vue) // _init() stateMixin(Vue) // $data/$props/$set()/$delete()/$watch() eventsMixin(Vue) // $emit()/$on/$off()/$once() lifecycleMixin(Vue) // _update()/$forceUpdate()/$destroy() renderMixin(Vue) // $nextTick()/_render()
进入initMixin()方法当中(实现_init初始化方法)
问题:new Vue()的时候都发生了什么?
实现_init初始化方法,都做了哪些事情
1、合并选项,代理$data(merge options)(本来有自己写的data,el等,再合并vue提供的一些初始化options,例如filters,component等)
2、初始化核心逻辑
initLifecycle(vm) // $parent/$root/...初始化生命周期 initEvents(vm) // 自定义事件监听(事件的派发和监听都是同个组件) initRender(vm) // $slots 插槽的初始化/$createElement=》 render函数中的h就是createElement/定义$attrs和$listener的响应式(defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
) callHook(vm, 'beforeCreate') //beforeCreate钩子之后才有下面数据的初始化 initInjections(vm) // resolve injections before data/props initState(vm) // props/methods/data/computed/watch initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
3、当设置了el选项时,自动调用了$mount
// 当设置了el选项时,自动调用$mount if (vm.$options.el) { vm.$mount(vm.$options.el) }
$mount把虚拟dom变成真实dom,查看Vue.prototype.$mount,它真正调用的是mountComponent()函数
接下来是mountComponent()函数
1、先是callHook(vm,beforeMount),即在挂载之前先执行beforeMount
2、后面new Watcher(vm,updateComponent(更新函数)) 这说明了一个组件就会有一个watcher
updateComponent = ()=> { vm._update(vm._render(),hydrating)}
所以执行顺序如下:
$mount => _render()渲染函数,获取当前组件的虚拟dom vnode => _update() 将虚拟dom转化为真实dom
下面的截图是_update() 的具体处理逻辑
总结(以上的整体流程捋一捋)
new Vue()==> _init ==> $mount ==> mountComponent ==> new Watcher() ==> updateComponent ==> _render() ==> _update() ==> __patch__ 是否是初始化渲染,是的话将虚拟dom转化为真实dom,不是的话patch走diff算法对比
数据响应式
起始位置:new Vue() => initMixin() => 初始化核心逻辑中 initState() 主要处理props/methods/data/computed/watch的初始化 => 接下来主要看initState中对data的响应式处理
递归响应式处理会触发observe()函数,每个函数都会只要有一个对象,就会产生一个ObServer的实例:
每个对象一个Ob实例,作用是判断对象类型做相对应的响应式处理(数组和对象)
如果这个对象已经是一个响应式的数据,就会有一个标识,这个标识是“__ob__”,所以我们经常在控制台看到的“__ob__”,说明这个对象已经是一个响应式的对象
接下来看Observer构造函数
Observer构造函数判断是数组(Array.isArray())还是对象(this.walk(value)) walk中遍历所有data属性,执行defineReactive()
注意:Observer中也有dep(大管家:负责对象如果有动态新增或删除属性时通知更新。数组有新元素增加和删除,通知更新)
进入defineReactive()函数,作用:给一个对象定义一个响应式属性
const dep = new Dep (小管家:dep和data中的所有key都是一对一的关系)(如果key的值发生变化,通知更新)
defineReactive函数 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() // dep和watcher互相添加映射关系(dep和watcher是多对多的关系) // 子Ob实例也要添加映射关系 if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } })
响应式处理数组的源码
// 1.获取原型 const arrayProto = Array.prototype // 2.克隆副本 export const arrayMethods = Object.create(arrayProto) // 3.定义要覆盖的7个方法 const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ // 4.遍历覆盖 methodsToPatch.forEach(function (method) { // cache original method // 5.获取原始方法 const original = arrayProto[method] // 6.覆盖该方法 def(arrayMethods, method, function mutator (...args) { // 7.先执行原始方法 const result = original.apply(this, args) // 8.扩展逻辑:变更通知 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) // notify change // 变更通知 ob.dep.notify() return result }) })
这篇关于vue2源码解析(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15useCallback教程:React Hook入门与实践
- 2024-11-15React中使用useContext开发:初学者指南
- 2024-11-15拖拽排序js案例详解:新手入门教程
- 2024-11-15React中的自定义Hooks案例详解
- 2024-11-14受控组件项目实战:从零开始打造你的第一个React项目
- 2024-11-14React中useEffect开发入门教程
- 2024-11-14React中的useMemo教程:从入门到实践
- 2024-11-14useReducer开发入门教程:轻松掌握React中的useReducer
- 2024-11-14useRef开发入门教程:轻松掌握React中的useRef用法
- 2024-11-14useState开发:React中的状态管理入门教程