Vue源码分析之数据驱动


前言

本篇并不是直接去学习源码,Vue源码也没有这么简单,而是去慢慢的靠拢源码。什么叫数据驱动?数据驱动就是用我们的数据渲染页面,用页面呈现的结果。为什么要学习源码?当然是为了提高薪资。。。。当然我们不是为了提高薪资而去学习源码,也不是为了掌握Vue而去学习源码,而是单纯的想在学习源码的过程中去学习里边的算法和一些设计模式等等一些东西


提示:以下是本篇文章正文内容,下面案例可供参考

一、Vue与模板

代码如下(示例):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue 与模板</title>
</head>
<body>
    <div id="root">
        <div>
            <p>{{name}} -- {{message}}</p>
        </div>
        <div>{{name}}</div>
        <div>{{message}}</div>
    </div>
</body>
<script>
    /* Vue 与模板 */
    let tmpNode = document.getElementById('root');

    let data = {
        name: '张珊',
        message: '消息'
    }

    //正则匹配括号内的内容
    let rBrackets = /\{\{(.+?)\}\}/g;

    function compiler(template, data) {
        //获取子元素
        let childNodes = template.childNodes;
        //遍历子元素
        for(let i = 0; i < childNodes.length; i++) {
            //获取元素类型
            let type = childNodes[i].nodeType;
            //元素类型等于3是文本节点
            if (type === 3){
                //获取文本节点内容
                let txt = childNodes[i].nodeValue;
                // 替换正则匹配的内容
                txt = txt.replace(rBrackets, function(_, g){
                    let key = g.trim();
                    return data[key];
                });
                // 替换原文本内容
                childNodes[i].nodeValue = txt;
            }
            //元素类型1是元素节点,此时进行递归
            else if(type === 1){
                compiler(childNodes[i], data); //递归
            }
        }
    }

    // 利用模板生成一个需要被渲染的HTML标签,这样也保留了原来的模板
    let generateNode = tmpNode.cloneNode(true);
    
    

    compiler(generateNode, data);
    // compiler(tmpNode, data);

    // console.log(generateNode)

    // 渲染好的HTML放到页面中
    root.parentNode.replaceChild( generateNode, root);
</script>
</html>

总结:
上面的思路有很大的问题:
1.Vue使用的虚拟 DOM
2.只考虑了单属性 {{ name }}, 而 Vue 中大量的使用层级 {{ data.result.name }}
3.代码没有整合

二、抽取到controller中

代码如下(示例):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="root">
        <div>
            <p>{{name}} -- {{message}}</p>
        </div>
        <div>{{name}}</div>
        <div>{{message}}</div>
    </div>
</body>
<script>
    /* 抽取到构造函数中 */
    
    //正则匹配括号内的内容
    let rBrackets = /\{\{(.+?)\}\}/g;

    function compiler(template, data) {
        //省略...
        //跟 ”Vue与模板“ 中的一样
    }

    function Vue(options) {
        this._data = options.data;
        this._el = options.el;

        // 准备模板
        this.$el = this._templateDOM = document.querySelector(this._el);
        this._parent = this._templateDOM.parentNode;

        //渲染工作
        this.render();
    }

    Vue.prototype.render = function(){
        this.compiler()
    }

    //编译将 模板与数据结合,得到真正的 DOM
    Vue.prototype.compiler = function(){
        let realHTMLDOM = this._templateDOM.cloneNode(true);
        compiler(realHTMLDOM, this._data);
        this.update(realHTMLDOM);
    }

    //将 DOM 元素放到页面中
    Vue.prototype.update = function(real){
        console.log(this._parent);
        this._parent.replaceChild(real, document.querySelector(this._el))
    }

    let app = new Vue({
        el: '#root',
        data: {
            name: 'tom',
            message: 'info'
        }
    });

</script>
</html>

三、解决 xxx.xxx.xxx 访问某个数据

代码如下(示例):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>

<script>

function createGetValueByPath( obj, path ){
    let paths = path.split('.');
    
    let res = obj;
    
    let prop;

    while( prop = paths.shift() ){
        res = res[prop]
    }

    return res;
}

let getValueByPath = createGetValueByPath('a.b.c.d');

var o = {
    a: {
        b: {
            c: {
                d: {
                    e: 'e里面的数据'
                }
            }
        }
    }
}

var res = getValueByPath(o)

console.log(res)

//函数柯力化
/*function createGetValueByPath( path )
{
    let paths = path.split('.');
    return function ( obj )
    {
        let res = obj;
        let prop;
        while (prop = paths.shift())
        {
            res = res[prop];
        }
        return res;
    }
}
*/


</script>
</html>

结合到 compiler 中使用


function getValueByPath( obj, path ){
    let paths= path.split('.');
    
    let res = obj;
    
    let prop;

    while( prop = paths.shift() ){
        res = res[prop]
    }

    return res;
}

/**
compiler()函数 只在这里有变化,其他代码没有变动
 txt = txt.replace(rBrackets, function(_, g){
    return getValueByPath(data, g.trim());
});
**/

function compiler(template, data) {
        //获取子元素
    let childNodes = template.childNodes;
    //遍历子元素
    for(let i = 0; i < childNodes.length; i++) {
        //获取元素类型
        let type = childNodes[i].nodeType;
        //元素类型等于3是文本节点
        if (type === 3){
            //获取文本节点内容
            let txt = childNodes[i].nodeValue;
            // 替换正则匹配的内容
            
            txt = txt.replace(rBrackets, function(_, g){
                // debugger
                // let key = g.trim();
                // return data[key];
                return getValueByPath(data, g.trim());
            });
            // 替换原文本内容
            childNodes[i].nodeValue = txt;
        }
        //元素类型1是元素节点,此时进行递归
        else if(type === 1){
            compiler(childNodes[i], data);
        }
    }
}

四、真实DOM和虚拟DOM之间的转换

代码如下(示例):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="root">
        <div>
            <div class="c1" title="t1">hello1</div>
            <div class="c2" title="t2">hello2</div>
            <div>hello3</div>
            <ul class="ul">
                <li>1</li>
                <li>2</li> 
                <li>3</li>
            </ul>
        </div>
    </div>
</body>
<script>
   
    //为什么要用虚拟 DOM? 当然是性能

    //<div /> => {tag = 'div'}
    //文本节点 => {tag: undefined, value: '文本节点'}
    //<div title="1" class="c" /> => {tag: 'div', data:{title: 1, class: 'c'}} 
    //<div><div /></div> => {tag: 'div', children: [{tag: 'div'}]}


class VNode{
    constructor(tag, data, value, type/* , elm */){
        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 attrsObj = {};

        for( let i = 0; i < attrs.length; i++ ) {
            attrsObj[ attrs[ i ].nodeName ] = attrs[ i ].nodeValue;
        }
        
        _vnode = new VNode( nodeName, attrsObj, 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;
}

let vroot = getVNode( document.querySelector( '#root' ) );
console.log(vroot);

function parseVNode( vnode ) {
    let nodeType = vnode.type;
    let _node = null;
    if ( nodeType === 3 ) {
        return document.createTextNode( vnode.value );
    }
    else if ( nodeType === 1) {
        _node = document.createElement(vnode.tag);
        let data = vnode.data;
        Object.keys(data).forEach( function (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 node = parseVNode( vroot );	//放入虚拟DOM
console.log(node) 	//得到真实DOM
</script>
</html>

总结

需要掌握的知识点:函数柯力化、递归、DOM API、正则表达式、构造函数等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值