概念
1旭贬、柯里化:一個函數(shù)原本有多個參數(shù)怔接,只傳入一個參數(shù),生成一個新函數(shù)稀轨,由新函數(shù)接收剩下的參數(shù)來運行得到結(jié)構(gòu)蜕提。
2、偏函數(shù):一個函數(shù)原本有多個參數(shù)靶端,只傳入一部分參數(shù),生成一個新函數(shù)凛膏,由新函數(shù)接收剩下的參數(shù)來運行得到結(jié)構(gòu)杨名,
3、高階函數(shù):一個函數(shù)參數(shù)是一個函數(shù)猖毫,該函數(shù)對參數(shù)這個函數(shù)進行加工台谍,得到一個函數(shù),這個加工函數(shù)就是高階函數(shù)吁断。
為什么要使用柯里化趁蕊?
為了提升性能,使用柯里化可以緩存一部分能力。
使用兩個案例來說明:
例1.判斷元素標簽
Vue本質(zhì)上是使用HTML的字符串作為模板的间护,將字符串的模板轉(zhuǎn)換為抽象語法樹(AST)雳攘,在轉(zhuǎn)換為VNode
第一階段:模板-----AST
第二階段:AST----VNode
第三階段:VNode----真實DOM
最耗性能的是字符串解析(模板----AST)
在Vue中每一個標簽可以是真正的HTML標簽,也可以是自定義組件任柜,是怎么區(qū)分的卒废?
在Vue源碼中其實將所有可以用的HTML標簽已經(jīng)存起來了
//假設(shè)這里只考慮幾個標簽
let tags='div,p,a,img,ul,li'.split(',')
//需要一個函數(shù)判斷一個標簽是否為內(nèi)置標簽
function isHTMLTag(tagName){
tagName = tagName.toLowerCase()
for(let i = 0; i < tags.length; i++){
if (tagName === tags[i]) return true
}
return false
}
//如果有6種內(nèi)置標簽,而模板中有十個標簽需要判斷宙地,那么就需要執(zhí)行60次循環(huán)摔认。是很耗性能的
//vue中使用函數(shù)柯里化解決
let tags='div,p,a,img,ul,li'.split(',')
function makeMap(keys) {
let set = {}
tags.forEach(key => { //一共就需要遍歷一次 不需要每次判斷時都調(diào)用
set[key] = true
})
return function (tagName) {
return !!set[tagName.toLowerCase()] //!!當沒有找到返回undefined的時候 !!undefined轉(zhuǎn)為布爾值false
}
}
let isHTMLTag = makeMap(tags) //返回函數(shù)
//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 () {
//執(zhí)行mountComponent()函數(shù)
let mount = () => {
this.update(this.render())
}
mount.call(this) //本質(zhì)應(yīng)該交給watcher來調(diào)用,但是還沒講到watcher
}
/*
在真正的Vue中使用了二次提交的設(shè)計結(jié)構(gòu)
1.在頁面中的DOM和虛擬DOM是一一一對應(yīng)的關(guān)系
2.當數(shù)據(jù)改變先由AST和數(shù)據(jù)生成新的VNode
3.將舊的VNode和新的VNode比較(diff算法)宅粥,更新
*/
//這里是生成render函數(shù)参袱,目的是緩存抽象語法樹(這里使用虛擬DOM來模擬)
JGVue.prototype.createRenderFn = function () {
let ast = getVNode(this._template) //拿到虛擬DOM
//Vue中:將AST和數(shù)據(jù)data合成生成VNode
//這里:帶有{{}}的VNode + date生成含有數(shù)據(jù)的VNode
return function render () {
//將帶有{{}}的VNode轉(zhuǎn)換為帶有數(shù)據(jù)的VNode
let _tmp = combine(ast, this._data)
return _tmp
}
}
//將虛擬DOM渲染到頁面中,diff算法就在這里秽梅,將數(shù)據(jù)改動生成的VNode和之前的VNode作比較
JGVue.prototype.update = function (vnode) {
//簡化抹蚀,直接生把虛擬DOM轉(zhuǎn)換成真正的DOM 替換到頁面中去,不用diff算法
//父元素.replaceChild(新元素, 舊元素)
let realDOM = parseVNode(vnode)
this._parent.replaceChild(realDOM, document.querySelector('#root'))
//這是算法是不負責任的风纠,每次會將頁面中的DOM全部替換
}
/*虛擬DOM構(gòu)造函數(shù)*/
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 將這個函數(shù)當做compiler函數(shù)*/
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]屬性節(jié)點(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轉(zhuǎn)換成真正的DOM*/
function parseVNode (vnode) {
let type = vnode.type
let _node = null
if (type === 3) {
_node = document.createTextNode(vnode.value) //創(chuàng)建文本節(jié)點
} 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)) //遞歸轉(zhuǎn)換子元素
})
}
return _node
}
let kuohaoReg = /\{\{(.+?)\}\}/g //匹配大括號表達式{{}}正則
/*根據(jù)路徑訪問對象成員 {{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與數(shù)據(jù)結(jié)合况鸣,得到填充數(shù)據(jù)的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) { //文本節(jié)點
//對文本處理
_value = _value.replace(kuohaoReg, function(_, g) { //第一個參數(shù)返回{{name}}竹观, 第二個參數(shù)表示正則的n個組 這個/\{\{(.+?)\}\}/g只有一個組(.+?)镐捧,顯示大括號里的內(nèi)容name
return getValueByPath(data, g.trim())
})
_vnode = new VNode(_tag, _data, _value, _type)
} else if (_type === 1) { //元素節(jié)點
_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>