star:2020.6.27
和公司前端大佬聊一些关于vue技术的时候,有几个问题记忆尤深,今天开始总结
- vue的精髓是什么吗?
- 我当时回答的是响应式系统,他的理解则是模板编译。
- 如果有一天vue消失了,你能手写一个vue或者与vue类似的框架吗?
- 我的回答是不可以。
知其然,知其所以然,加油
github
# clone the project
git clone [email protected]:FBmm/my-vue.git
# enter the project directory
cd my-vue
# install dependency
npm install
# develop
npm run serve # rollup内置环境启动项目
# build
npm run build # 打包源码
├── public # 静态资源
│ │── index.html # html模板文件
├── src # vue源码
│ │── index.js # vue入口函数
│ │── init.js # vue初始化函数init
│ │── state.js # 初始化props,methods,data,computed,watch
├── .babelrc # babel-loader配置
├── .gitignore # git版本管理忽略配置
├── package-lock.json # 项目配置、脚本命令、模块依赖管理
├── package.json # 应用
├── rollup.config.js # rollup.js配置
├── readme.md # readme
初始化相关代码
...
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)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
...
疑问:this instanceof Vue 如何判断函数是new关键字调用?
疑问:_init是何时挂载到Vue构造函数上的?
疑问:_init方法做了什么事?
_init方法的入参是Vue初始化的options
…
…
虚拟dom(Virtual DOM):真实dom的虚拟dom节点,是VNode实例
Vue中的虚拟DOM的作用
总结:虚拟DOM的最大作用和用途是通过DOM-Diff算法对比数据变化,减少DOM操作,提升视图更新性能
VNode:是Vue中描述dom节点的类,包含描述真实DOM节点的一系列属性。
VNode作用
export default class VNode {
tag: string | void; // 标签名
data: VNodeData | void; // 标签属性
children: ?Array<VNode>; // 子节点
text: string | void; // 当前节点的文本
elm: Node | void; // 当前VNode对应的真实dom节点
ns: string | void; // 命名空间
context: Component | void; // rendered in this component's scope
key: string | number | void; // 节点的key 值是data.key 也就是Vue模板声明的key
componentOptions: VNodeComponentOptions | void; // 组件options
componentInstance: Component | void; // component instance - 当前节点对应组件的实例
parent: VNode | void; // component placeholder node - 父节点
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder? - 是否是注释节点
isCloned: boolean; // is a cloned node? - 是否是克隆节点
isOnce: boolean; // is a v-once node? - 是否有v-once指令
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
注释节点
// 创建注释节点
export const createCommentNode = (text: string = '') => {
const node = new VNode()
ndoe.text = text
node.isComment = true
return node
}
文本节点
// 创建注释节点
export const createTextVNode(val: string | number) {
return new VNode(undefined, undefined, undefined, val)
}
元素节点
新VNode与缓存VNode找出差异的过程,这个过程称为patch,指对旧VNode的修补。
patch
创建节点
只有注释节点、文本节点、元素节点能被创建并插入到DOM中
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
if (process.env.NODE_ENV !== 'production') {
if (data && data.pre) {
creatingElmInVPre++
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
)
}
}
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// in Weex, the default insertion order is parent-first.
// List items can be optimized to use children-first insertion
// with append="tree".
const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
if (!appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
createChildren(vnode, children, insertedVnodeQueue)
if (appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
} else {
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
nodeOps:跨平台兼容,封装节点操作
更新节点
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
// 克隆节点或者是有v-once指令的静态节点 并且key没有改变 则不更新
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
// 旧VNode子节点
const oldCh = oldVnode.children
// 新VNode子节点
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
// 新VNode不是文本节点
if (isUndef(vnode.text)) {
// 新旧VNode都有子节点
if (isDef(oldCh) && isDef(ch)) {
// 新VNode不是旧VNode 更新旧VNode子节点
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) { // 只有新VNode有子节点 并且旧Vnode没有子节点
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
// 旧VNode是文本节点 并且有内容
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') // 清空真实dom元素的内容
// 把新vnode子节点新增到真实dom中
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) { // 旧Vnode有子节点 新Vnode没有子节点
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) { // 新旧VNode都没有子节点 旧VNode是文本节点
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) { // 新VNode是文本节点 并且与旧vnode文本内容不同
nodeOps.setTextContent(elm, vnode.text) // 替换真实dom文本内容
}
// 新VNode有元素属性
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
由上面代码知道,更新节点的核心步骤为:
updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
nodeOps.setTextContent(elm, '')
nodeOps.setTextContent(elm, vnode.text)
总结:上面过程有一个增加节点操作,一个删除节点操作,一个更新节点操作,一个替换文本操作,两个清空文本操作
…