概念
使用步骤
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 缓存起来 生成函数 函数只需要传入数据 得到真正的DOM
响应式原理
vue 赋值属性获得属性都是直接使用的Vue实例
我们在设置属性时,页面的数据要更新
对于对象可以响应式化 递归
数组
push
pop
shift
unshift
reverse
sort
splice
1.在改变数组的数据时,需要发出通知
1.vue2中的缺陷 数据发生变化 ,设置length 没法通知 (在vue3中使用proxy 解决了问题)
2.加入的元素应该变成响应式的
技巧:如果一个函数已经定义了,我们要扩展其功能
1.使用一个临时的函数名存储函数
2.重新定义原来的函数
3.定义扩展的功能
4.调用临时的函数
扩展数组方法 push 和 pop
直接修改prototype 不行 所有数组变化
修改要进行响应式的数组的原型(__proto__)
已经将对象改成响应式的了,但是如果直接给对象赋值另外一个对象,那么就不是响应式的
<!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>{{name}}</div>
<div>{{age}}</div>
<div>{{sex}}</div>
</div>
<div>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
</div>
<script>
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)
}
}
function getVNode(node) {
let nodeType = node.nodeType
let _vnode = null
if (nodeType === 1) {
let nodeName = node.nodeName
let attrs = node.attributes
let _attrObj = {}
for (let i = 0; i < attrs.length; i++) {
_attrObj[attrs[i].nodeName] = attrs[i].nodeValue
}
_vnode = new VNode(nodeName, _attrObj, undefined, nodeType)
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
}
function getValueByPath(obj, path) {
let paths = path.split('.')
let res = obj
let prop
while (prop = paths.shift()) {
res = res[prop]
}
return res
}
function combine(vNode, data) {
let _type = vNode.type
let _data = vNode.data
let _value = vNode.value
let _tag = vNode.tag
let _children = vNode.children
let _vnode = null
let rex = /\{\{(.+?)\}\}/g
if (_type === 3) {
_value = _value.replace(rex, function (_, g) {
let path = g.trim()
let value = getValueByPath(data, path)
return value
})
_vnode = new VNode(_tag, _data, _value, _type)
} else if (_type === 1) {
_vnode = new VNode(_tag, _data, _value, _type)
_children.forEach((_subVNode) => {
_vnode.appendChild(combine(_subVNode, data))
});
}
return _vnode
}
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)
let data = vnode.data
Object.keys(data).forEach((key) => {
let attrName = key
let attrValue = data[key]
_node.setAttribute(attrName, attrValue)
})
let children = vnode.children
children.forEach(subvnode => {
_node.appendChild(parseVNode(subvnode))
})
}
return _node
}
function MVue(options) {
this._options = options
this._data = options.data
let elm = document.querySelector(options.el)
this._template = elm
this._parent = elm.parentNode
reactify(this._data,this)
this.mount()
}
let ARRAY_METHOD = [
'push',
'shift',
'unshif',
'revers',
'sort',
'splice',
]
let array_methods = Object.create(Array.prototype)
ARRAY_METHOD.forEach(method => {
array_methods[method] = function () {
for (let i = 0; i < arguments.length; i++) {
reactify(arguments[i])
}
let res = Array.prototype[method].apply(this, arguments)
return res
}
})
function defineReactive(target, key, value, enumerable) {
let that = this
if (typeof value === 'object' && value != null && !Array.isArray(value)) {
reactify(value)
}
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get() {
return value
},
set(newValue) {
value = newValue
that.mountComponent()
}
})
}
function reactify(o, vm) {
let keys = Object.keys(o)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
let value = o[key]
if (Array.isArray(value)) {
value.__proto__ = array_methods
for (let j = 0; j < value.length; j++) {
reactify(value[j], vm)
}
} else {
defineReactive.call(vm, o, key, value, true)
}
}
}
MVue.prototype.mount = function () {
this.render = this.createRenderFn()
this.mountComponent()
}
MVue.prototype.createRenderFn = function () {
let ast = getVNode(this._template)
return function render() {
let _temp = combine(ast, this._data)
return _temp
}
}
MVue.prototype.mountComponent = function () {
let mount = () => {
this.update(this.render())
}
mount.call(this)
}
MVue.prototype.update = function (VNode) {
let realDom = parseVNode(VNode)
this._parent.replaceChild(realDom, document.querySelector('#app'))
}
let app = new MVue({
el: '#app',
data: {
name: 'xa',
age: 12,
sex: 'man',
dates:[
{info:'111'},
{info:'2222'},
{info:'333'}
]
}
})
</script>
</body>
</html>