vue学习篇5之函数柯里化与渲染模型

概念
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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容