概念
1、柯里化:一个函数原本有多个参数,只传入一个参数,生成一个新函数,由新函数接收剩下的参数来运行得到结构。
2、偏函数:一个函数原本有多个参数,只传入一部分参数,生成一个新函数,由新函数接收剩下的参数来运行得到结构,
3、高阶函数:一个函数参数是一个函数,该函数对参数这个函数进行加工,得到一个函数,这个加工函数就是高阶函数。
为什么要使用柯里化?
为了提升性能,使用柯里化可以缓存一部分能力。
使用两个案例来说明:
例1.判断元素标签
Vue本质上是使用HTML的字符串作为模板的,将字符串的模板转换为抽象语法树(AST),在转换为VNode
第一阶段:模板-----AST
第二阶段:AST----VNode
第三阶段:VNode----真实DOM
最耗性能的是字符串解析(模板----AST)
在Vue中每一个标签可以是真正的HTML标签,也可以是自定义组件,是怎么区分的?
在Vue源码中其实将所有可以用的HTML标签已经存起来了
//假设这里只考虑几个标签
let tags='div,p,a,img,ul,li'.split(',')
//需要一个函数判断一个标签是否为内置标签
function isHTMLTag(tagName){
tagName = tagName.toLowerCase()
for(let i = 0; i < tags.length; i++){
if (tagName === tags[i]) return true
}
return false
}
//如果有6种内置标签,而模板中有十个标签需要判断,那么就需要执行60次循环。是很耗性能的
//vue中使用函数柯里化解决
let tags='div,p,a,img,ul,li'.split(',')
function makeMap(keys) {
let set = {}
tags.forEach(key => { //一共就需要遍历一次 不需要每次判断时都调用
set[key] = true
})
return function (tagName) {
return !!set[tagName.toLowerCase()] //!!当没有找到返回undefined的时候 !!undefined转为布尔值false
}
}
let isHTMLTag = makeMap(tags) //返回函数
//isHTMLTag = function (tagName) {
// return !!set[tagName.toLowerCase()]
//}
console.log(isHTMLTag('div')) //true
console.log(isHTMLTag('ol')) //false
2.虚拟DOM的render方法
简化模拟Vue初始化页面模板渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewpoort" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>text</title>
</head>
<body>
<div id="root">
<div>
<div>{{name}}</div>
<div>{{age}}</div>
<div>hello3</div>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
</div>
<script>
function JGVue (options) {
this._data = options.data
let elm =document.querySelector(options.el) //vue中是字符串 这里是DOM(简化的)
this._template = elm
this._parent = elm.parentNode //拿到父元素
this.mount() //挂载
}
JGVue.prototype.mount = function () {
//需要提供一个render方法生成虚拟DOM
this.render = this.createRenderFn() //带有缓存
this.mountComponent()
}
JGVue.prototype.mountComponent = function () {
//执行mountComponent()函数
let mount = () => {
this.update(this.render())
}
mount.call(this) //本质应该交给watcher来调用,但是还没讲到watcher
}
/*
在真正的Vue中使用了二次提交的设计结构
1.在页面中的DOM和虚拟DOM是一一一对应的关系
2.当数据改变先由AST和数据生成新的VNode
3.将旧的VNode和新的VNode比较(diff算法),更新
*/
//这里是生成render函数,目的是缓存抽象语法树(这里使用虚拟DOM来模拟)
JGVue.prototype.createRenderFn = function () {
let ast = getVNode(this._template) //拿到虚拟DOM
//Vue中:将AST和数据data合成生成VNode
//这里:带有{{}}的VNode + date生成含有数据的VNode
return function render () {
//将带有{{}}的VNode转换为带有数据的VNode
let _tmp = combine(ast, this._data)
return _tmp
}
}
//将虚拟DOM渲染到页面中,diff算法就在这里,将数据改动生成的VNode和之前的VNode作比较
JGVue.prototype.update = function (vnode) {
//简化,直接生把虚拟DOM转换成真正的DOM 替换到页面中去,不用diff算法
//父元素.replaceChild(新元素, 旧元素)
let realDOM = parseVNode(vnode)
this._parent.replaceChild(realDOM, document.querySelector('#root'))
//这是算法是不负责任的,每次会将页面中的DOM全部替换
}
/*虚拟DOM构造函数*/
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)
}
}
/*由HTMLDOM生成虚拟DOM 将这个函数当做compiler函数*/
function getVNode(node) {
let nodeType = node.nodeType
let _vnode = null
if (nodeType === 1) { //元素
let nodeName = node.nodeName
let attrs = node.attributes
let _attrsObj = {}
for (let i = 0; i < attrs.length; i++) { //attrs[i]属性节点(nodeType === 2)
_attrsObj[attrs[i].nodeName] = attrs[i].nodeValue
}
_vnode = new VNode(nodeName, _attrsObj, 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
}
/*由虚拟DOM转换成真正的DOM*/
function parseVNode (vnode) {
let type = vnode.type
let _node = null
if (type === 3) {
_node = 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
}
let kuohaoReg = /\{\{(.+?)\}\}/g //匹配大括号表达式{{}}正则
/*根据路径访问对象成员 {{a.b.c}}*/
function getValueByPath (obj, path) {
let paths = path.split('.') //[a,b,c]
let res = obj
let prop
while (prop = paths.shift()) {
res = res[prop]
}
return res
}
/*将带有{{}}的VNode与数据结合,得到填充数据的VNode。 模拟由抽象语法树去生成VNode*/
function combine (vnode, data) {
let _tag = vnode.tag
let _data = vnode.data
let _value = vnode.value
let _type = vnode.type
let _children = vnode.children
let _vnode = null
if (_type === 3) { //文本节点
//对文本处理
_value = _value.replace(kuohaoReg, function(_, g) { //第一个参数返回{{name}}, 第二个参数表示正则的n个组 这个/\{\{(.+?)\}\}/g只有一个组(.+?),显示大括号里的内容name
return getValueByPath(data, g.trim())
})
_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
}
let app = new JGVue({
el: '#root',
data: {
name: '张三',
age: 18
}
})
</script>
</body>
</html>