您的位置:首页 > Web前端 > Vue.js

Vue源码分析(虚拟DOM与优化)

2020-08-26 16:00 731 查看

概念

使用步骤
1.编写 页面 模板
1.直接在HTML标签中写
2.使用template
3.使用单文件(<template>)
2.创建Vue实例
1.在Vue 的构造函数中:data,methods,computer,watcher,props,...
3.将Vue挂载到页面中(mount)

数据驱动模型
Vue执行流程
1.获得模板:模板中有‘坑’
2.利用Vue构造函数提供的数据来‘填坑’,就可以得到页面显示的'标签'了
3.替换原来有坑的标签
Vue 利用 我们提供的数据 和 页面的 模板 生成了一个新的HTML标签(node元素),替换到了 页面中 放置模板的位置

虚拟DOM
目标:
1. 怎么将真正的DOM转换为虚拟DOM
2.怎么将虚拟DOM转换为真正的DOM

思路与深拷贝类似

概念
1.柯里化: 一个函数原本有多个参数 只传入一个参数生成一个新函数 ,由新函数接受到新的参数运行得到的结构
2.偏函数: 参考柯里化, 传入一部分参数
3.高阶函数: 一个函数参数是一个函数,该函数对参数这个函数进行加工,得到一个函数,这个加工用的函数就是高阶函数

为什么要使用柯里化
为了提升性能  使用柯里化可以缓存一部分能力

使用两个例子说明

1.判断元素

Vue 本质上是使用HTML的字符串作为模板,将字符串的 模板 转换为AST 再转换为VNode
1.模板-AST
2.AST-VNode
3.VNode-DOM

最消耗性能的
是模板-AST
例子 字符串 1 + 2 * ( 3 + 4 )  解析该表达式,得到结果
一般将此转换为 ‘波兰式’ 表达式 然后用栈进行运算

在Vue中每一个标签可以是真正的HTML标签,也可以是自定义组件,怎么区分
在vue源码中,将所有可用的HTML标签 已经存起来,
假设这里只考虑 几个标签
```js
let tags = 'div,p,a,img'.split(',')
```
一个函数,判断标签名是否为 内置标签
```js
function isHTML( tagName ){
tagName = tagName.toLowerCase()
//tags.indexOf(tagName) >-1 return true
for(let i=0;i<...){
if(tagName === tags[i])return true
}
return false
}//也可以用indexOf判断
```
模板是任意编写的,可以写的很简单,也可以写的很复杂,indexOf内部也要循环

如果有6个内置标签 模板有10个,就得循环60次

使用柯里化
```js
let tags = 'div,p,a,img'.split(',')
function makeMap( keys ) {
let set = {}
keys.forEach(key => {
set[key] = true
});
return function ( tagName ) {
//!!改为boolean
return !!set[ tagName.toLowerCase() ]
}
}

let isHTML = makeMap( tags )
//不用再做循环
```

2.虚拟DOM 的render

vue 项目 模板 转换为AST 转换几次?
1.页面加载渲染 一次
2.每一个属性(响应式)数据发生变化 要渲染
3.watch computed 等等

render的作用是将 虚拟DOM 转换为 真正的DOM 加载到页面中

//虚拟DOM 可以降级为AST
一个项目运行时 模板不会变  抽象语法树不会变

我们可以将代码优化  将虚拟DOM 缓存起来 生成函数  函数只需要传入数据  得到真正的DOM

源码模拟与分析

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<div>
<div>h1</div>
<div>h2</div>
<div>h3</div>
</div>
<div>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
</div>
</body>
<script>
//为什么使用虚拟DOM ? 性能
//<div> => {tag: 'div' }
//文本节点  => {tag : undefined , value: '文本节点' }
//<div title='1'  class='c'>  => {tag : 'div'  data: {title: 1, class : 'c'}}
//<div><div></div></div> {tag: 'div', children: [ {{tag: 'div'}]}

class VNode{
constructor(tag, data, value, type,){
this.tag = tag && tag.toLowerCase()
this.data = data
this.value = value
this.type = type
this.children = []
}

appendChild( vnode ){
this.children.push(vnode)
}

}

//递归  来遍历DOM 生成虚拟DOM
//Vue 中的源码使用 栈结构 ,使用栈存储 父元素 来实现递归生成
function getVNode( node ) {
let nodeType = node.nodeType
let _vnode = null
if(nodeType === 1 ){
//元素
let nodeName = node.nodeName
let attrs =  node.attributes
let _attrObj = {}
//遍历属性节点 nodeType=2
for (let i = 0; i < attrs.length; i++) {
_attrObj[ attrs[i].nodeName ] = attrs[i].nodeValue
}
_vnode = new VNode(nodeName, _attrObj, undefined, nodeType)

//考虑 node的子元素
//递归
let childNodes = node.childNodes
for (let i = 0; i < childNodes.length; i++) {
_vnode.appendChild( getVNode(childNodes[i]  ) )
}
}else if(nodeType === 3 ){
_vnode = new VNode(undefined, undefined, node.nodeValue, nodeType)
}
return _vnode
}

let app = document.querySelector('#app')

let vapp = getVNode(app)

console.log( vapp )

//将VNode 转换为真正的dom
function parseVNode( vnode) {
let type = vnode.type
let _node = null
if( type === 3){
return document.createTextNode( vnode.value )
}else if( type === 1 ){

_node = document.createElement( vnode.tag )

//属性
//data 键值对
let data = vnode.data
Object.keys(data).forEach( (key) => {
let attrName = key
let attrValue = data[key]
_node.setAttribute( attrName, attrValue)
})

//子元素
//递归转换子元素 (虚拟dom)
let children = vnode.children
children.forEach( subvnode => {
_node.appendChild(parseVNode(subvnode))
})
}
return _node
}

//在真正的vue也是使用 递归+加栈
let dom = parseVNode(vapp)

console.log(dom)

</script>
</html>

vue源码 虚拟dom对象

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: