Vue.js设计于实现 - 概览(二)
文章目录
- vue采用运行时+编译时
- 虚拟DOM和innerHTML性能消耗比较
- 打包工具
- 为用户传入的回调函数添加错误处理
- Vnode
- 实现一个只用于创建DOM的render
- 组件Render
- 模板工作原理-编译器
vue采用运行时+编译时
运行时(render):vnode转dom
编译时(compiler):vue模板转vnode
虚拟DOM和innerHTML性能消耗比较
- 创建页面时
虚拟DOM -> vnode(js) + dom
||
命令式的innerHtml -> 字符串(js) + dom
- 更新DOM时
虚拟DOM -> diff + vnode(js) + 部分dom更新
^
命令式的innerHTML -> 字符串(js) + 销毁、新建dom
打包工具
vue采用rollup.js实现打包
- 实现良好的tree-shaking(消除不会执行的代码)
打包工具本来就支持,但是如果该函数产生副作用则无法移出
副作用:该函数对外部产生了影响(例如修改或访问了全局变量)
所以如果想要给副作用函数添加tree-shaking,需要给rollup显式声明这段代码不会产生副作用/*#__PURE__*/
为用户传入的回调函数添加错误处理
当我们封装一个工具模块,接收一个回调函数作为参数并执行时,要添加错误处理
// utils.js
export default {foo(fn) {callWithErrorHandling(fn)}
}
function callWithErrorHandling(fn) {try {fn && fn()} catch (e) {console.log(e)}
}
我们可以进一步封装统一的错误处理函数,将错误传递给用户,让用户来控制处理
// utils.js
let handleError = null
export default {foo(fn) {callWithErrorHandling(fn)},// 用户可以调用该函数注册统一的错误处理函数registerErrorHanlder(fn) {handleError = fn}
}
function callWithErrorHandling(fn) {try {fn && fn()} catch (e) {handleError(e)}
}
import utils form 'utils.js'
utils.registerErrorHanlder((e) => {// 用户可以选择在此处对错误进行更进一步的处理,比如上报日志等console.log(e)
})
utils.foo(() => {})
Vnode
// <h1 @click = 'handler'><span><span></h1>
const title = {tag: 'h1',props: {onClick: handler},children: [{ tag: 'span' }]
}
// 使用h函数更加方便的编写虚拟dom
h('h1', { onClick: handler })
实现一个只用于创建DOM的render
function mountElement(vnode, container) {// 使用vnode.tag作为标签名称创建DOMconst element = document.createElement(vnode.tag);// 遍历vnode.props并设置属性、事件if (vnode.props) {for (const [key, value] of Object.entries(vnode.props)) {if (/^on/.test(key)) {// 如果是事件处理器,添加事件监听const eventType = key.slice(2).toLowerCase(); // 去掉'on'前缀element.addEventListener(eventType, value);} else {element.setAttribute(key, value);}}}// 处理子节点if (vnode.children) {if (typeof vnode.children === 'string') {// 文本节点element.appendChild(document.createTextNode(vnode.children));} else {vnode.children.forEach(child => {renderer(child, element);});}}// 挂载container.appendChild(element);}
组件Render
vue的组件就是一组dom元素的封装
即一个拥有返回vnode对象的函数 的对象(这里给组件包一层-通过render获取vnode而不是直接返回vnode对象是因为MyComponent里还要进行其他操作)
const MyComponent = {render() {return {tag: 'div',props: {},children: '123'}}
}
// 在vnode中用tag属性存储组件函数
const vnode = {tag: MyComponent
}
此时的render要支持组件
function renderer(vnode, container) {if(typeof vnode.tag === 'string') {// 正常标签元素,调用上面的mountElementmountElement(vnode, container)} else if(typeof vnode.tag === 'object') {// 组件mountComponent(vnode, container)}
}function mountComponent() {// 获取组件vnodeconst subtree = vnode.tag.render()// 递归渲染subtreerenderer(subtree, container)
}
模板工作原理-编译器
将模板转为render函数
同时编译器能够识别哪些静态属性,哪些是动态,并在生成的render中进行标识,这样就避免了渲染器花费力气去寻找,例如
<div id="foo" :class="cls"></div>
render(){return {tag: 'div',props: {id: 'foo',class: cls},patchFlags: 1 // 假设数字1代表class是动态的}
}