Vue2剖析源码
打包、入口分析
目录结构
- .circleci 持续集成
- benchmarks 性能评测
- dist 输出目录
- examples 案例
- flow flow声明文件
- packages vue中的包
- scripts 工程化
- src 源码目录
- test 测试相关
- types ts声明文件
├─compiler # 编译的相关逻辑
│ ├─codegen
│ ├─directives
│ └─parser
├─core # vue核心代码
│ ├─components # vue中的内置组件 keep-alive
│ ├─global-api # vue中的全局api
│ ├─instance # vue中的核心逻辑
│ ├─observer # vue中的响应式原理
│ ├─util
│ └─vdom # vue中的虚拟dom模块
├─platforms # 平台代码
│ ├─web # web逻辑 - vue
│ │ ├─compiler
│ │ ├─runtime
│ │ ├─server
│ │ └─util
│ └─weex # weex逻辑 - app
│ ├─compiler
│ ├─runtime
│ └─util
├─server # 服务端渲染模块
├─sfc # 用于编译.vue文件
└─shared # 共享的方法和常量
打包流程
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
核心是使用node执行
scripts/build.js
,通过传递参数来实现不同的打包结果,这里的--
代表后面的内容是参数。
build.js
既然是打包,那我们肯定要找到打包的入口点,所以这里的关键就是查找打包的入口!
// 1.获取不同的打包的配置
let builds = require('./config').getAllBuilds()
// 2.根据执行打包时的参数进行过滤
if (process.argv[2]) {
const filters = process.argv[2].split(',')
builds = builds.filter(b => {
return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
})
} else {
// 默认不打包weex相关代码
builds = builds.filter(b => {
return b.output.file.indexOf('weex') === -1
})
}
// 3.进行打包
build(builds)
不同的打包配置指的是:
-
web
/weex
不同的平台这里我们不关注
weex
,web
指代的就是我们常用的vue
-
Runtime only
/Runtime + compiler
是否带编译模块带有
compiler
的会将模板转化成render
函数 -
CommonJS
/es
/umd
打包出不同模块规范umd
模块是整合了CommonJS和AMD
两个模块定义规范的方法,当不支持时两种模块时会将其添加到全局变量中
打包入口
src/platforms/web/entry-runtime.js
src/platforms/web/entry-runtime-with-compiler.js
我们可以通过打包的配置找到我们需要的入口,这两个区别在于是否涵盖
compiler
逻辑,我们在开发时一般使用的是entry-runtime
,可以减小vue
的体积,但是同样在开发时也不能再使用template
,.vue
文件中的template
是通过vue-loader
来进行编译的,和我们所说的compiler
无关哈。
new Vue({
template:`<div></div>`
})
这样的template必须要使用带compiler的入口才能进行模板的解析
入口分析
这里为了剖析vue
完整的代码,我们就来分析带有compiler
的文件。
我们观察这两个入口的文件不难发现他们都引入了
runtime/index.js
entry-runtime-with-compiler.js
import Vue from './runtime/index' // 1.引入运行时代码
const mount = Vue.prototype.$mount; // 2.获取runtime中的$mount方法
Vue.prototype.$mount = function (el,hydrating) { // 3. 重写$mount方法
el = el && query(el)
const options = this.$options
if (!options.render) { // 4.没有render方法就进行编译操作
let template = options.template
if(template){ // 5.将模板编译成函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render // 6.将render函数放到options中
}
// todo...
}
return mount.call(this, el, hydrating) // 7.进行挂载操作
}
export default Vue
带有
compiler
的文件仅仅是对$mount
方法进行了重写,增添了将template
变成render
函数的功能
Vue的构造函数
instance/index.js
真正的Vue的构造函数,并在Vue的原型上扩展方法core/index.js
增加全局API
方法runtime/index.js
扩展$mount
方法及平台对应的代码
全局API & 执行流程
全局API
Vue.util
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
暴露的工具方法。这些方法不被视为公共API的一部分,除非你知道里面的风险,否则避免使用。(这个util是Vue内部的工具方法,可能会发生变动),例如:在Vue.router中就使用了这个工具方法
Vue.set / Vue.delete
set方法新增响应式数据
export function set (target: Array<any> | Object, key: any, val: any): any {
// 1.是开发环境 target 没定义或者是基础类型则报错
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 2.如果是数组 Vue.set(array,1,100); 调用我们重写的splice方法 (这样可以更新视图)
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 3.如果是对象本身的属性,则直接添加即可
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// 4.如果是Vue实例 或 根数据data时 报错
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 5.如果不是响应式的也不需要将其定义成响应式属性
if (!ob) {
target[key] = val
return val
}
// 6.将属性定义成响应式的
defineReactive(ob.value, key, val)
ob.dep.notify() // 7.通知视图更新
return val
}
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 1.如果是数组依旧调用splice方法
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
// 2.如果本身就没有这个属性什么都不做
if (!hasOwn(target, key)) {
return
}
// 3.删除这个属性
delete target[key]
if (!ob) {
return
}
// 4.通知更新
ob.dep.notify()
}
Vue的缺陷:新增之前不存在的属性不会发生视图更新,修改数组索引不会发生视图更新 (解决方案就是通过$set方法,数组通过splice进行更新视图,对象则手动通知)
Vue.nextTick
const callbacks = []; // 存放nextTick回调
let pending = false;
function flushCallbacks () { // 清空队列
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let timerFunc
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx) // 1.将回调函数存入到callbacks中
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc(); // 2.异步刷新队列
}
// 3.支持promise写法
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
不难看出nextTick原理就是将回调函数存入到一个队列中,最后异步的清空这个队列
timerFunc
// 1.默认先使用Promise 因为mutationObserver有bug可能不工作
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// 解决队列不刷新问题
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
// 2.使用MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
// 3.使用 setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
// 4.使用setTimeout
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
采用EventLoop中的微任务和宏任务,先采用微任务并按照优先级优雅降级的方式实现异步刷新
Vue.observable
2.6新增的方法,将对象进行观测,并返回观测后的对象。可以用来做全局变量,实现数据共享
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options
存放全局的组件、指令、过滤器的一个对象,及拥有_base
属性保存Vue的构造函数
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents) // 内置了 keep-alive
Vue.use
Vue.use
主要的作用就是调用插件的install
方法,并将Vue
作为第一个参数传入,这样做的好处是可以避免我们编写插件时需要依赖Vue导致版本问题。
initUse(Vue)
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 1.如果安装过这个插件直接跳出
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// 2.获取参数并在参数中增加Vue的构造函数
const args = toArray(arguments, 1)
args.unshift(this)
// 3.执行install方法
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
// 4.记录安装的插件
installedPlugins.push(plugin)
return this
}
Vue.mixin
全局混合方法,可以用来提取公共方法及状态等.
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
Vue对不同的属性做了不同的合并策略
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (!child._base) { // 1.组件先将自己的extends和mixin与父属性合并
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
// 2.再用之前合并后的结果,与自身的属性进行合并
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat; // 3.采用不同的合并策略
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
你可以通过查看
strats
这个对象来了解不同的合并策略
Vue.extend
Vue中非常核心的一个方法,可以通过传入的对象获取这个对象的构造函数,后续在组件初始化过程中会用到此方法
Vue.extend = function (extendOptions: Object): Function {
// ...
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.options = mergeOptions( // 子组件的选项和Vue.options进行合并
Super.options,
extendOptions
)
// ...
return Sub;
}
extend
创建的是 Vue 构造器,我们可以自己实例化并且将其挂载在任意的元素上
组件、指令、过滤器
initAssetRegisters(Vue)
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition; // 将指令、过滤器、组件 绑定在Vue.options上
// 备注:全局组件、指令过滤器其实就是定义在 Vue.options中,这样创建子组件时都会和Vue.options进行合并,所以子组件可以拿到全局的定义
return definition
}
}
})
初始化全局的api,Vue.component、Vue.directive、Vue.filter,这里仅仅是格式化用户传入的内容,将其绑定在Vue.options选项上
Vue的初始化过程
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) // 当new Vue时会调用 _init方法
}
initMixin(Vue) // 初始化_init方法
stateMixin(Vue) // $set / $delete / $watch
eventsMixin(Vue) // $on $once $off $emit
lifecycleMixin(Vue) // _update
renderMixin(Vue) // _render $nextTick
Vue的初始化
通过Vue的_init
方法,我们可以看到内部又包含了很多初始化的过程
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// 1.每个vue的实例上都有一个唯一的属性_uid
vm._uid = uid++
// 2.表示是Vue的实例
vm._isVue = true
// 3.选项合并策略
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._self = vm
// 4.进行初始化操作
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
// 5.如果有el就开始进行挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
_init方法中的初始化
这个代码写的真是一目了然,我们先看看每个方法"大概"干了什么事,切记不要死追到底!
initLifecycle(vm) // 初始化组件间的父子关系
initEvents(vm) // 更新组件的事件
initRender(vm) // 初始化_c方法
initInjections(vm)// 初始化inject
initState(vm) // 初始化状态
initProvide(vm) // 初始化provide
挂载流程
// 1.如果有el就开始挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
// 2.组件的挂载
Vue.prototype.$mount = function (el,hydrating){
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating);
}
// 3.创建渲染watcher进行渲染
export function mountComponent (vm,el,hydrating) {
vm.$el = el
let updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, { // 创建渲染Watcher
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
return vm
}
响应式变化原理
数据劫持
进行了细化和拆分,对不同的属性做了不同的初始化操作
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
数据的初始化
这里我们先关心数据是如何进行初始化操作的
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 1.数据不是对象则发生异常
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 2.校验数据是否在method中已经声明过
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 3.校验数据是否在属性中已经声明过
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
// 4.将_data代理到实例上
proxy(vm, `_data`, key)
}
}
// 5.观测数据
observe(data, true /* asRootData */)
}
这里主要是检测属性是否被重复声明,并对属性进行观测
观测数据
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 1.如果不是对象直接return
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 2.如果已经观测过则直接返回上次观测的实例
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
) {
// 3.如果可以观测就进行观测
ob = new Observer(value)
}
// 4.如果是根数据 vmCount标注为1
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
只观测对象数据类型,已经观测的不在进行观测,不能扩展的属性不进行观测。
export class Observer {
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
// 1.数组的话重写数组原型方法
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 2.观测数组中是对象类型的数据
this.observeArray(value)
} else {
// 3.对象的话使用defineProperty重新定义属性
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
这里要区分对象和数组,如果是数组不能使用Object.defineProperty会造成性能浪费,所以采用重写可以更改数组本身的方法的方式
对象的观测
对象的观测就是将所有属性使用defineProperty
进行重新定义
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 1.如果对象不可配置则直接退出
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 2.获取getter和setter
const getter = property && property.get
const setter = property && property.set
// 3.重新定义set和get方法
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
}
})
}
对象的属性劫持已经烂大街了,非常简单就是通过
defineProperty
来实现的,如果你还不会那得好好反思一下了。这里提一下:想减少观测可以使用Object.freeze
冻结对象
数组的观测
数组的观测就是通过重写原型方法来实现的
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(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
})
})
所谓的数据观测就是当数据变化时我们可以知道,像对象更改时可以出发
set
方法,像数组调用push
方法可以触发我们自己写的push
依赖收集
vue
的渲染过程是通过渲染watcher
来实现的
let updateComponent = updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {}, true /* isRenderWatcher */)
在我们创建
watcher
时,会对变量进行取值
对象依赖收集
对于对象而言,取值就会触发get方法,我们可以在defineProperty的get中进行依赖收集,在set中通知watcher
进行更新操作
class Watcher {
constructor (
vm: Component,
expOrFn: string | Function, // updateComponent
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// 将updateComponent 放到this.getter上
this.getter = expOrFn
this.value = this.lazy
? undefined
: this.get() // 执行get方法
}
get () {
pushTarget(this) // Dep.target = 渲染watcher
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) // 开始取值 那么在get方法中就可以获取到这个全局变量Dep.target
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget() // 结束后进行清理操作
this.cleanupDeps()
}
return value
}
}
渲染watcher,默认会调用get方法也就是我们传入的updateComponent方法,在调用此方法前先将watcher存到全局中,这样再取值时可以获取到这个watcher。
const dep = new Dep()
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) { // 如果有watcher 将dep和watcher对应起来
dep.depend()
}
return value
}
set: function reactiveSetter (newVal) {
dep.notify(); // 当属性更新时通知dep中的所有watcher进行更新操作
}
异步更新
为了防止多次更改同一个属性或者多次修改不同属性(他们依赖的watcher相同) 会导致频繁更新渲染
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// 1.判断watcher是否已经存放了
if (has[id] == null) {
has[id] = true
// 2.将watcher存放到队列中
queue.push(watcher)
// queue the flush
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue) // 在下一队列中清空queue
}
}
}
对相同watcher进行过滤操作,当同步的更改状态完毕时再去更新watcher
Vue2核心原理
使用Rollup搭建开发环境
安装rollup环境
npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D
rollup.config.js
文件
// rollup.config.js
import babel from 'rollup-plugin-babel';
import serve from 'rollup-plugin-serve';
export default {
input: './src/index.js',
output: {
format: 'umd', // 模块化类型
file: 'dist/umd/vue.js',
name: 'Vue', // 打包后的全局变量的名字
sourcemap: true
},
plugins: [
babel({
exclude: 'node_modules/**'
}),
process.env.ENV === 'development'?serve({
open: true,
openPage: '/public/index.html',
port: 3000,
contentBase: ''
}):null
]
}
配置.babelrc文件
// .babelrc
{
"presets": [
"@babel/preset-env"
]
}
执行脚本配置
"scripts": {
"build:dev": "rollup -c",
"serve": "cross-env ENV=development rollup -c -w"
}
Vue的渲染流程
- 先初始化数据
- 将模板进行编译
- 调用render函数
- 生成虚拟节点
- 生成真实的dom
- 扔到页面上
Vue响应式原理
相关文章:https://blog.csdn.net/qq_40588441/article/details/140337629?spm=1001.2014.3001.5501
Vue并不是一个MVVM框架,只是借鉴了MVVM思想,所谓MVVM是指数据变化视图会更新,视图变化数据会被影响,不能跳动数据去更新视图
但是Vue可以通过$ref跳过数据去更新视图
导出vue
构造函数
import {initMixin} from './init';
function Vue(options) {
this._init(options);
}
initMixin(Vue); // 给原型上新增_init方法
export default Vue;
init
方法中初始化vue
状态
import {initState} from './state';
export function initMixin(Vue){
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options
// 初始化状态
initState(vm)
}
}
根据不同属性进行初始化操作
export function initState(vm){
const opts = vm.$options;
if(opts.props){
initProps(vm);
}
if(opts.methods){
initMethod(vm);
}
if(opts.data){
// 初始化data
initData(vm);
}
if(opts.computed){
initComputed(vm);
}
if(opts.watch){
initWatch(vm);
}
}
function initProps(){}
function initMethod(){}
function initData(){}
function initComputed(){}
function initWatch(){}
初始化数据
这里又进行了细化和拆分,对不同的属性做了不同的初始化操作,原来我们常用的api都在这里做的初始化
import {observe} from './observer/index.js'
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 1.数据不是对象则发生异常
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 2.校验数据是否在method中已经声明过
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 3.校验数据是否在属性中已经声明过
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
// 4.将_data代理到实例上
proxy(vm, `_data`, key)
}
}
// 5.观测数据
observe(data, true /* asRootData */)
}
递归属性劫持
消耗大性能差,不存在的属性,如果新增的话不能重新渲染视图
所以在Vue3中使用Proxy来解决,不需要以上来就进行递归(性能好,但是兼容性差)
class Observer { // 观测值
constructor(value){
this.walk(value);
}
walk(data){ // 让对象上的所有属性依次进行观测
let keys = Object.keys(data);
for(let i = 0; i < keys.length; i++){
let key = keys[i];
let value = data[key];
defineReactive(data,key,value);
}
}
}
function defineReactive(data,key,value){
observe(value);
Object.defineProperty(data,key,{
get(){
return value
},
set(newValue){
if(newValue == value) return;
observe(newValue);
value = newValue
}
})
}
export function observe(data) {
if(typeof data !== 'object' || data == null){
return;
}
return new Observer(data);
}
数组方法的劫持
函数劫持之后,调用数组的方法,则触发更新视图
数组中如果是对象数据类型也会进行递归劫持
数组的索引和长度变化是无法监控到的(可以通过$set解决[本质是splice方法])
import {arrayMethods} from './array';
class Observer { // 观测值
constructor(value){
if(Array.isArray(value)){
value.__proto__ = arrayMethods; // 重写数组原型方法
this.observeArray(value);
}else{
this.walk(value);
}
}
observeArray(value){
for(let i = 0 ; i < value.length ;i ++){
observe(value[i]);
}
}
}
重写数组原型方法
let oldArrayProtoMethods = Array.prototype;
export let arrayMethods = Object.create(oldArrayProtoMethods);
let methods = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
];
methods.forEach(method => {
arrayMethods[method] = function (...args) {
const result = oldArrayProtoMethods[method].apply(this, args);
const ob = this.__ob__;
let inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2)
default:
break;
}
if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测
return result
}
})
增加__ob__属性
class Observer {
constructor(value){
Object.defineProperty(value,'__ob__',{
enumerable:false,
configurable:false,
value:this
});
// ...
}
}
数据代理
function proxy(vm,source,key){
Object.defineProperty(vm,key,{
get(){
return vm[source][key];
},
set(newValue){
vm[source][key] = newValue;
}
});
}
function initData(vm){
let data = vm.$options.data;
data = vm._data = typeof data === 'function' ? data.call(vm) : data;
for(let key in data){ // 将_data上的属性全部代理给vm实例
proxy(vm,'_data',key)
}
observe(data);
}
模板编译
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
// 初始化状态
initState(vm);
// 页面挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
// 如果没有render方法
if (!options.render) {
let template = options.template;
// 如果没有模板但是有el
if (!template && el) {
template = el.outerHTML;
}
const render= compileToFunctions(template);
options.render = render;
}
}
将template
编译成render函数
解析标签和内容
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
function start(tagName,attrs){
console.log(tagName,attrs)
}
function end(tagName){
console.log(tagName)
}
function chars(text){
console.log(text);
}
function parseHTML(html){
while(html){
let textEnd = html.indexOf('<');
if(textEnd == 0){
const startTagMatch = parseStartTag();
if(startTagMatch){
start(startTagMatch.tagName,startTagMatch.attrs);
continue;
}
const endTagMatch = html.match(endTag);
if(endTagMatch){
advance(endTagMatch[0].length);
end(endTagMatch[1]);
continue;
}
}
let text;
if(textEnd >= 0){
text = html.substring(0,textEnd);
}
if(text){
advance(text.length);
chars(text);
}
}
function advance(n){
html = html.substring(n);
}
function parseStartTag(){
const start = html.match(startTagOpen);
if(start){
const match = {
tagName:start[1],
attrs:[]
}
advance(start[0].length);
let attr,end;
while(!(end = html.match(startTagClose)) && (attr = html.match(attribute))){
advance(attr[0].length);
match.attrs.push({name:attr[1],value:attr[3]});
}
if(end){
advance(end[0].length);
return match
}
}
}
}
export function compileToFunctions(template){
parseHTML(template);
return function(){}
}
生成ast语法树
语法树就是用对象描述js
语法
{
tag:'div',
type:1,
children:[{tag:'span',type:1,attrs:[],parent:'div对象'}],
attrs:[{name:'hs',age:10}],
parent:null
}
let root;
let currentParent;
let stack = [];
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
function createASTElement(tagName,attrs){
return {
tag:tagName,
type:ELEMENT_TYPE,
children:[],
attrs,
parent:null
}
}
function start(tagName, attrs) {
let element = createASTElement(tagName,attrs);
if(!root){
root = element;
}
currentParent = element;
stack.push(element);
}
function end(tagName) {
let element = stack.pop();
currentParent = stack[stack.length-1];
if(currentParent){
element.parent = currentParent;
currentParent.children.push(element);
}
}
function chars(text) {
text = text.replace(/\s/g,'');
if(text){
currentParent.children.push({
type:TEXT_TYPE,
text
})
}
}
生成代码
template
转化成render函数的结果
<div style="color:red">hello {{name}} <span></span></div>
render(){
return _c('div',{style:{color:'red'}},_v('hello'+_s(name)),_c('span',undefined,''))
}
实现代码生成
function gen(node) {
if (node.type == 1) {
return generate(node);
} else {
let text = node.text
if(!defaultTagRE.test(text)){
return `_v(${JSON.stringify(text)})`
}
let lastIndex = defaultTagRE.lastIndex = 0
let tokens = [];
let match,index;
while (match = defaultTagRE.exec(text)) {
index = match.index;
if(index > lastIndex){
tokens.push(JSON.stringify(text.slice(lastIndex,index)));
}
tokens.push(`_s(${match[1].trim()})`)
lastIndex = index + match[0].length;
}
if(lastIndex < text.length){
tokens.push(JSON.stringify(text.slice(lastIndex)))
}
return `_v(${tokens.join('+')})`;
}
}
function getChildren(el) { // 生成儿子节点
const children = el.children;
if (children) {
return `${children.map(c=>gen(c)).join(',')}`
} else {
return false;
}
}
function genProps(attrs){ // 生成属性
let str = '';
for(let i = 0; i<attrs.length; i++){
let attr = attrs[i];
if(attr.name === 'style'){
let obj = {}
attr.value.split(';').forEach(item=>{
let [key,value] = item.split(':');
obj[key] = value;
})
attr.value = obj;
}
str += `${attr.name}:${JSON.stringify(attr.value)},`;
}
return `{${str.slice(0,-1)}}`;
}
function generate(el) {
let children = getChildren(el);
let code = `_c('${el.tag}',${
el.attrs.length?`${genProps(el.attrs)}`:'undefined'
}${
children? `,${children}`:''
})`;
return code;
}
let code = generate(root);
生成render函数
export function compileToFunctions(template) {
parseHTML(template);
let code = generate(root);
let render = `with(this){return ${code}}`;
let renderFn = new Function(render);
return renderFn
}
创建渲染watcher
初始化渲染Watcher
import {mountComponent} from './lifecycle'
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
// 如果没有render方法
if (!options.render) {
let template = options.template;
// 如果没有模板但是有el
if (!template && el) {
template = el.outerHTML;
}
const render= compileToFunctions(template);
options.render = render;
}
mountComponent(vm,el);
}
lifecycle.js
export function lifecycleMixin() {
Vue.prototype._update = function (vnode) {}
}
export function mountComponent(vm, el) {
vm.$el = el;
let updateComponent = () => {
// 将虚拟节点 渲染到页面上
vm._update(vm._render());
}
new Watcher(vm, updateComponent, () => {}, true);
}
render.js
export function renderMixin(Vue){
Vue.prototype._render = function () {}
}
watcher.js
let id = 0;
class Watcher {
constructor(vm, exprOrFn, cb, options) {
this.vm = vm;
this.exprOrFn = exprOrFn;
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn;
}
this.cb = cb;
this.options = options;
this.id = id++;
this.get();
}
get() {
this.getter();
}
}
export default Watcher;
先调用_render
方法生成虚拟dom
,通过_update
方法将虚拟dom
创建成真实的dom
生成虚拟dom
import {createTextNode,createElement} from './vdom/create-element'
export function renderMixin(Vue){
Vue.prototype._v = function (text) { // 创建文本
return createTextNode(text);
}
Vue.prototype._c = function () { // 创建元素
return createElement(...arguments);
}
Vue.prototype._s = function (val) {
return val == null? '' : (typeof val === 'object'?JSON.stringify(val):val);
}
Vue.prototype._render = function () {
const vm = this;
const {render} = vm.$options;
let vnode = render.call(vm);
return vnode;
}
}
创建虚拟节点
export function createTextNode(text) {
return vnode(undefined,undefined,undefined,undefined,text)
}
export function createElement(tag,data={},...children){
let key = data.key;
if(key){
delete data.key;
}
return vnode(tag,data,key,children);
}
function vnode(tag,data,key,children,text){
return {
tag,
data,
key,
children,
text
}
}
生成真实DOM元素
将虚拟节点渲染成真实节点
import {patch} './observer/patch'
export function lifecycleMixin(Vue){
Vue.prototype._update = function (vnode) {
const vm = this;
vm.$el = patch(vm.$el,vnode);
}
}
export function patch(oldVnode,vnode){
const isRealElement = oldVnode.nodeType;
if(isRealElement){
const oldElm = oldVnode;
const parentElm = oldElm.parentNode;
let el = createElm(vnode);
parentElm.insertBefore(el,oldElm.nextSibling);
parentElm.removeChild(oldVnode)
return el;
}
}
function createElm(vnode){
let {tag,children,key,data,text} = vnode;
if(typeof tag === 'string'){
vnode.el = document.createElement(tag);
updateProperties(vnode);
children.forEach(child => {
return vnode.el.appendChild(createElm(child));
});
}else{
vnode.el = document.createTextNode(text);
}
return vnode.el
}
function updateProperties(vnode){
let newProps = vnode.data || {}; // 获取当前老节点中的属性
let el = vnode.el; // 当前的真实节点
for(let key in newProps){
if(key === 'style'){
for(let styleName in newProps.style){
el.style[styleName] = newProps.style[styleName]
}
}else if(key === 'class'){
el.className= newProps.class
}else{ // 给这个元素添加属性 值就是对应的值
el.setAttribute(key,newProps[key]);
}
}
}
生命周期的合并
Mixin原理
import {mergeOptions} from '../util/index.js'
export function initGlobalAPI(Vue){
Vue.options = {};
Vue.mixin = function (mixin) {
// 将属性合并到Vue.options上
this.options = mergeOptions(this.options,mixin);
return this;
}
}
合并生命周期
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
]
const strats = {};
function mergeHook(parentVal, childValue) {
if (childValue) {
if (parentVal) {
return parentVal.concat(childValue);
} else {
return [childValue]
}
} else {
return parentVal;
}
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
export function mergeOptions(parent, child) {
const options = {}
for (let key in parent) {
mergeField(key)
}
for (let key in child) {
if (!parent.hasOwnProperty(key)) {
mergeField(key);
}
}
function mergeField(key) {
if (strats[key]) {
options[key] = strats[key](parent[key], child[key]);
} else {
if (typeof parent[key] == 'object' && typeof child[key] == 'object') {
options[key] = {
...parent[key],
...child[key]
}
}else{
options[key] = child[key];
}
}
}
return options
}
调用生命周期
export function callHook(vm, hook) {
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0; i < handlers.length; i++) {
handlers[i].call(vm);
}
}
}
初始化流程中调用生命周期
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = mergeOptions(vm.constructor.options,options);
// 初始化状态
callHook(vm,'beforeCreate');
initState(vm);
callHook(vm,'created');
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
依赖收集
每个属性都要有一个dep
,每个dep
中存放着watcher
,同一个watcher
会被多个dep
所记录。
在渲染时存储watcher
class Watcher{
// ...
get(){
pushTarget(this);
this.getter();
popTarget();
}
}
let id = 0;
class Dep{
constructor(){
this.id = id++;
}
}
let stack = [];
export function pushTarget(watcher){
Dep.target = watcher;
stack.push(watcher);
}
export function popTarget(){
stack.pop();
Dep.target = stack[stack.length-1];
}
export default Dep;
对象依赖收集
let dep = new Dep();
Object.defineProperty(data, key, {
get() {
if(Dep.target){ // 如果取值时有watcher
dep.depend(); // 让watcher保存dep,并且让dep 保存watcher
}
return value
},
set(newValue) {
if (newValue == value) return;
observe(newValue);
value = newValue;
dep.notify(); // 通知渲染watcher去更新
}
});
Dep实现
class Dep{
constructor(){
this.id = id++;
this.subs = [];
}
depend(){
if(Dep.target){
Dep.target.addDep(this);// 让watcher,去存放dep
}
}
notify(){
this.subs.forEach(watcher=>watcher.update());
}
addSub(watcher){
this.subs.push(watcher);
}
}
watcher
constructor(){
this.deps = [];
this.depsId = new Set();
}
addDep(dep){
let id = dep.id;
if(!this.depsId.has(id)){
this.depsId.add(id);
this.deps.push(dep);
dep.addSub(this);
}
}
update(){
this.get();
}
数组的依赖收集
this.dep = new Dep(); // 专门为数组设计的
if (Array.isArray(value)) {
value.__proto__ = arrayMethods;
this.observeArray(value);
} else {
this.walk(value);
}
function defineReactive(data, key, value) {
let childOb = observe(value);
let dep = new Dep();
Object.defineProperty(data, key, {
get() {
if(Dep.target){
dep.depend();
if(childOb){
childOb.dep.depend(); // 收集数组依赖
}
}
return value
},
set(newValue) {
if (newValue == value) return;
observe(newValue);
value = newValue;
dep.notify();
}
})
}
arrayMethods[method] = function (...args) {
// ...
ob.dep.notify()
return result;
}
递归收集数组依赖
if(Dep.target){
dep.depend();
if(childOb){
childOb.dep.depend(); // 收集数组依赖
if(Array.isArray(value)){ // 如果内部还是数组
dependArray(value);// 不停的进行依赖收集
}
}
}
function dependArray(value) {
for (let i = 0; i < value.length; i++) {
let current = value[i];
current.__ob__ && current.__ob__.dep.depend();
if (Array.isArray(current)) {
dependArray(current)
}
}
}
实现Vue异步更新之nextTick
实现队列机制
update(){
queueWatcher(this);
}
scheduler
import {
nextTick
} from '../util/next-tick'
let has = {};
let queue = [];
function flushSchedulerQueue() {
for (let i = 0; i < queue.length; i++) {
let watcher = queue[i];
watcher.run()
}
queue = [];
has = {}
}
let pending = false
export function queueWatcher(watcher) {
const id = watcher.id;
if (has[id] == null) {
has[id] = true;
queue.push(watcher);
if(!pending){
nextTick(flushSchedulerQueue)
pending = true;
}
}
}
nextTick实现原理
util/next-tick.js
let callbacks = [];
function flushCallbacks() {
callbacks.forEach(cb => cb());
}
let timerFunc;
if (Promise) { // then方法是异步的
timerFunc = () => {
Promise.resolve().then(flushCallbacks)
}
}else if (MutationObserver) { // MutationObserver 也是一个异步方法
let observe = new MutationObserver(flushCallbacks); // H5的api
let textNode = document.createTextNode(1);
observe.observe(textNode, {
characterData: true
});
timerFunc = () => {
textNode.textContent = 2;
}
}else if (setImmediate) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
}else{
timerFunc = () => {
setTimeout(flushCallbacks, 0);
}
}
export function nextTick(cb) {
callbacks.push(cb);
timerFunc();
}
Watch & Computed
Watch实现原理
let vm = new Vue({
el: '#app',
data(){
return {name:'hs'}
},
watch:{
name(newValue,oldValue){
console.log(newValue,oldValue);
}
}
});
watch用于监控用户的data变化,数据变化后会触发对应的watch的回调方法
if (opts.watch) {
initWatch(vm,opts.watch);
}
选项中如果有watch则对watch进行初始化
function initWatch(vm, watch) {
for (const key in watch) {
const handler = watch[key];
// 如果结果值是数组循环创建watcher
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm,key,handler[i]);
}
}else{
createWatcher(vm,key,handler)
}
}
}
function createWatcher(vm,exprOrFn,handler,options){
// 如果是对象则提取函数 和配置
if(isObject(handler)){
options = handler;
handler = handler.handler;
}
// 如果是字符串就是实例上的函数
if(typeof handler == 'string'){
handler = vm[handler];
}
return vm.$watch(exprOrFn,handler,options);
}
这里涉及了watch的三种写法,1.值是对象、2.值是数组、3.值是字符串 (如果是对象可以传入一些watch参数),最终会调用vm.$watch来实现
扩展Vue原型上的方法,都通过mixin的方式来进行添加的。
stateMixin(Vue);
export function stateMixin(Vue) {
Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
options.user = true; // 标记为用户watcher
// 核心就是创建个watcher
const watcher = new Watcher(this, exprOrFn, cb, options);
if(options.immediate){
cb.call(vm,watcher.value)
}
}
}
class Watcher {
constructor(vm, exprOrFn, callback, options) {
// ...
this.user = !! options.user
if(typeof exprOrFn === 'function'){
this.getter = exprOrFn;
}else{
this.getter = function (){ // 将表达式转换成函数
let path = exprOrFn.split('.');
let obj = vm;
for(let i = 0; i < path.length;i++){
obj = obj[path[i]];
}
return obj;
}
}
this.value = this.get(); // 将初始值记录到value属性上
}
get() {
pushTarget(this); // 把用户定义的watcher存起来
const value = this.getter.call(this.vm); // 执行函数 (依赖收集)
popTarget(); // 移除watcher
return value;
}
run(){
let value = this.get(); // 获取新值
let oldValue = this.value; // 获取老值
this.value = value;
if(this.user){ // 如果是用户watcher 则调用用户传入的callback
this.callback.call(this.vm,value,oldValue)
}
}
}
还是借助vue响应式原理,默认在取值时将watcher存放到对应属性的dep中,当数据发生变化时通知对应的watcher重新执行
Computed实现原理
if (opts.computed) {
initComputed(vm,opts.computed);
}
function initComputed(vm, computed) {
// 存放计算属性的watcher
const watchers = vm._computedWatchers = {};
for (const key in computed) {
const userDef = computed[key];
// 获取get方法
const getter = typeof userDef === 'function' ? userDef : userDef.get;
// 创建计算属性watcher
watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
defineComputed(vm, key, userDef)
}
}
每个计算属性也都是一个
watcher
,计算属性需要表示lazy:true,这样在初始化watcher时不会立即调用计算属性方法
class Watcher {
constructor(vm, exprOrFn, callback, options) {
this.vm = vm;
this.dirty = this.lazy
// ...
this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
}
}
默认计算属性需要保存一个dirty属性,用来实现缓存功能
function defineComputed(target, key, userDef) {
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
} else {
sharedPropertyDefinition.get = createComputedGetter(userDef.get);
sharedPropertyDefinition.set = userDef.set;
}
// 使用defineProperty定义
Object.defineProperty(target, key, sharedPropertyDefinition)
}
创建缓存getter
function createComputedGetter(key) {
return function computedGetter() {
const watcher = this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) { // 如果dirty为true
watcher.evaluate();// 计算出新值,并将dirty 更新为false
}
// 如果依赖的值不发生变化,则返回上次计算的结果
return watcher.value
}
}
}
watcher.evaluate
evaluate() {
this.value = this.get()
this.dirty = false
}
update() {
if (this.lazy) {
this.dirty = true;
} else {
queueWatcher(this);
}
}
当依赖的属性变化时,会通知watcher调用update方法,此时我们将dirty置换为true。这样再取值时会重新进行计算。
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) { // 计算属性在模板中使用 则存在Dep.target
watcher.depend()
}
return watcher.value
}
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
如果计算属性在模板中使用,就让计算属性中依赖的数据也记录渲染watcher,这样依赖的属性发生变化也可以让视图进行刷新
Vue组件原理解析
组件的渲染流程
- 调用Vue.component
- 内部用的Vue.extend就是产生一个子类来继承父类
- 等会创建子类实例时,会调用父类的_init方法,再$mount即可
- 组件的初始化就是new 这个组件的构造函数并且调用$mount方法
- 创建虚拟节点,根据表情筛出组件对应,生成组件的虚拟节点,componentOPtions里面包含Ctor,children
- 组件创建真实don时(先渲染的是父组件) 遇到的是组件的虚拟节点时,去调用init方法,让组件初始化挂载,组件的 m o u n t 无参数会把渲染后的 d o m 放到 mount无参数会把渲染后的dom放到 mount无参数会把渲染后的dom放到vm.el上(vnode.conponentInstance中,这样渲染时就会获取这个对象的$el属性来渲染)
全局组件的解析
<div id="app">
<my-component></my-component>
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<button>点我</button>',
});
let vm = new Vue({
el:'#app'
});
</script>
我们可以通过
Vue.component
注册全局组件,之后可以在模板中进行使用
export function initGlobalAPI(Vue){
// 整合了所有的全局相关的内容
Vue.options ={}
initMixin(Vue);
// _base 就是Vue的构造函数
Vue.options._base = Vue;
Vue.options.components = {}
// 注册API方法
initAssetRegisters(Vue);
}
Vue.component方法
export default function initAssetRegisters(Vue) {
Vue.component = function (id, definition) {
definition.name = definition.name || id;
definition = this.options._base.extend(definition);
this.options['components'][id] = definition;
}
}
Vue.component
内部会调用Vue.extend
方法,将定义挂载到Vue.options.components
上。这也说明所有的全局组件最终都会挂载到这个变量上
export function initGlobalAPI(Vue){
// 整合了所有的全局相关的内容
Vue.options ={}
initMixin(Vue);
// _base 就是Vue的构造函数
Vue.options._base = Vue;
Vue.options.components = {}
// initExtend
+ initExtend(Vue);
// 注册API方法
initAssetRegisters(Vue);
}
Vue.extend方法
import {mergeOptions} from '../util/index'
export default function initExtend(Vue) {
let cid = 0;
Vue.extend = function (extendOptions) {
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.options = mergeOptions(
Super.options,
extendOptions
);
return Sub
}
}
extend
方法就是创建出一个子类,继承于Vue
,并返回这个类
属性合并
function mergeAssets(parentVal,childVal){
const res = Object.create(parentVal);
if(childVal){
for(let key in childVal){
res[key] = childVal[key];
}
}
return res;
}
strats.components = mergeAssets;
初始化合并
vm.$options = mergeOptions(vm.constructor.options,options);
组件的渲染
function makeMap(str) {
const map = {};
const list = str.split(',');
for (let i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return (key)=>map[key];
}
export const isReservedTag = makeMap(
'a,div,img,image,text,span,input,p,button'
)
在创建虚拟节点时我们要判断当前这个标签是否是组件,普通标签的虚拟节点和组件的虚拟节点有所不同
创建组件虚拟节点
export function createElement(vm,tag, data = {}, ...children) {
let key = data.key;
if (key) {
delete data.key;
}
if (typeof tag === 'string') {
if (isReservedTag(tag)) {
return vnode(tag, data, key, children, undefined);
} else {
// 如果是组件需要拿到组件的定义,通过组件的定义创造虚拟节点
let Ctor = vm.$options.components[tag];
return createComponent(vm,tag,data,key,children,Ctor)
}
}
}
function createComponent(vm,tag,data,key,children,Ctor){
// 获取父类构造函数t
const baseCtor = vm.$options._base;
if(isObject(Ctor)){
Ctor = baseCtor.extend(Ctor);
}
data.hook = { // 组件的生命周期钩子
init(){}
}
return vnode(`vue-component-${Ctor.cid}-${tag}`,data,key,undefined,{Ctor,children});
}
function vnode(tag, data, key, children, text, componentOptions) {
return {tag, data, key, children, text, componentOptions}
}
创建组件的真实节点
export function patch(oldVnode,vnode){
// 1.判断是更新还是要渲染
if(!oldVnode){
return createElm(vnode);
}else{
// ...
}
}
function createElm(vnode){ // 根据虚拟节点创建真实的节点
let {tag,children,key,data,text} = vnode;
// 是标签就创建标签
if(typeof tag === 'string'){
// createElm需要返回真实节点
if(createComponent(vnode)){
return vnode.componentInstance.$el;
}
vnode.el = document.createElement(tag);
updateProperties(vnode);
children.forEach(child=>{ // 递归创建儿子节点,将儿子节点扔到父节点中
return vnode.el.appendChild(createElm(child))
})
}else{
// 虚拟dom上映射着真实dom 方便后续更新操作
vnode.el = document.createTextNode(text)
}
// 如果不是标签就是文本
return vnode.el;
}
function createComponent(vnode) {
let i = vnode.data;
if((i = i.hook) && (i = i.init)){
i(vnode);
}
if(vnode.componentInstance){
return true;
}
}
调用
init
方法,进行组件的初始化
data.hook = {
init(vnode){
let child = vnode.componentInstance = new Ctor({});
child.$mount(); // 组件的挂载
}
}