• vue源码之数据驱动


    什么是柯里化?为什么要使用柯里化来实现封装?我们通过概念和案例来说明一下:

    概念

    一个函数原本有多个参数,传入一个参数,生成一个新的函数,新的函数接收剩余的参数来运行得到结果。

    柯里化相关学习资源:

    案例

    1.判断元素

    Vue 本质上是使用 HTML 的字符串作为模版的,将字符串的模版转换为 AST(抽象语法树),再转换为 VNode。
    • 模版 -> AST  最消耗性能
    • AST -> VNode
    • VNode -> DOM

    在 Vue 中,每一个标签可以是真正的HTML标签,也可以是自定义标签,那怎么区分?
    在 Vue 源码中其实是将所有可以用的 HTML 标签已经存起来
     
    假设只考虑几个标签
    let tags = 'div,p,a,img,ul,li'.split(',');
    需要一个函数,判断一个标签是否为 内置的标签
    function isHTMLTag (tagName) {
        tagName = tagName.toLowerCase();
        return tags.some(item => item===tagName)
    }
    如果tags有6种,模版中有10的内置标签需要判断,那么就需要60次循环,显然这种方式会消耗大量内存。
     
    下面将通过柯里化来实现该操作,将时间复杂度从O(n)->O(1):
    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()]
        }
    }
    
    let isHTMLTag = makeMap(tags)

    定义 makeMap 方法,该方法在初始时循环一次,使用 set (结构为:{div:true,p:true...})来存储标签便利之后的状态,返回一个新函数,每次在调用新函数的时候,都相当于直接取值,因此,时间复杂度为O(1)。

    2.虚拟DOM都render方法

    思考: Vue 项目模版转换为抽象语法树需要执行几次?
    • 页面一开始加载需要渲染
    • 每一个属性( 响应式 )数据在发生变化的时候需要渲染
    • watch,computed等等
    之前的代码 每次需要渲染的时候,模版就会解析一次

    reder 的作用是将虚拟DOM转换为真正DOM加到页面中
    • 虚拟DOM可以降级理解为 AST
    • 一个项目运行时候,模版是不会变的,就表示 AST 是不会变的

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

    下面我们将上一节(vue源码之数据驱动 - 2.html模拟vue实现数据渲染)用到的代码进行封装:

    // 虚拟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)
        }
    }
    // 由 HTML DOM -> VNode : 将这个函数当做 compiler 函数
    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)
    
            // 考虑 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
    }
    
    // 将 vnode 转换为真正的 DOM
    function parseVNode(vnode) {
        // 创建真实DOM
        let type = vnode.type
        if (type === 3) {
            return document.createTextNode(vnode.value) // 创建文本节点
        } else if (type === 1) {
            let _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)); // 递归转换子元素 ( 虚拟DOM )
    
            })
    
            return _node
        }
    }
    
    function JGVue(options) {
        this._data = options.data || {}
        let elm = document.querySelector(options.el)
        this._template = elm
        this._parent = elm.parentNode
    
        this.mount() // 挂载
    }
    
    // 根据路径访问对象成员
    function getValueByPath(obj, path) {
        let paths = path.split('.')
    
        let res = obj;
        let prop;
        while (prop = paths.shift()) {
            res = res[prop]
        }
        return res
    }
    
    let rkuohao = /{{(.+?)}}/g
    // 将带有坑的Vnode与数据data结合,得到填充数据的VNode
    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
        if (_type === 3) {
            // 文本
            // 对文本处理
            _value = _value.replace(rkuohao, (_, g) => {
                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
    }
    
    JGVue.prototype.mount = function () {
        // 需要提供一个 reder 方法:生成虚拟DOM
        this.render = this.createRederFn()
        this.mountComponent()
    }
    
    JGVue.prototype.mountComponent = function () {
    
        // 执行 mountComponent 函数
        let mount = () => {
            this.update(this.render())
        }
    
        mount.call(this) // 本质上应该交给 watcher 来调用
    }
    
    // 在真正的Vue中,使用了二次提交的设计结构 
    // 1. 在页面中的DOM和虚拟DOM是一一对应关系
    // 2. 先有 AST 和数据生成 VNode(新,reder)
    // 3. 将旧VNode和新VNode比较( diff ) ,更新 ( update )
    
    // 这里是生成reder函数, 目的是缓存抽象语法树( 我们使用虚拟DOM来模拟)
    JGVue.prototype.createRederFn = function () {
        let ast = getVNode(this._template)
        // Vue : 将AST + data => VNode
        // 带有坑的 VNode + data => 含有数据的VNode
    
        return function render() {
            let _tmp = combine(ast, this._data)
            return _tmp
        }
    }
    
    // 将虚拟DOM渲染到页面中:diff算法就在这里
    JGVue.prototype.update = function (vnode) {
    
        // this._parent.replaceChild()
        let realDOM = parseVNode(vnode)
        // this._parent.replaceChild(realDOM, this._template)
        this._parent.replaceChild(realDOM, document.querySelector("#root"))
    }
    
    let app = new JGVue({
        el: '#root',
        data: {
            name: '张三',
            age: '19',
            gender: "男"
        }
    })
    code
  • 相关阅读:
    tracteroute路由追踪
    搭建Weblogic服务器
    Logview_pro破解版
    Spring Boot 如何在类中应用配置文件
    使用Mybatis-Generator自动生成Dao、Model、Mapping相关文件(转)
    springboot 项目中控制台打印日志以及每天生成日志文件
    springboot输出日志到指定目录,简单粗暴,springboot输出mybatis日志
    spring boot 发布成包所需插件
    spring注解
    Multicast注册中心
  • 原文地址:https://www.cnblogs.com/cap-rq/p/14215353.html
Copyright © 2020-2023  润新知