vue-----手写实习vue基本功能

现在用来手写实现vue的简单功能,主要就是可以和Vue一样渲染页面,这个是很简单的一个,没有实现指令操作,想看指令操作的可以看一下我的github:github地址

这是html代码

 <div id="app">
        <p>姓名:{{name}}</p>
        <p>年龄:{{age}}</p><button onclick="vm.age++">增加年龄</button>
        <dl>
            <dt>住址</dt>
            <dd>
                省份:{{addr.province}}
            </dd>
            <dd>
                城市:{{addr.city}}
            </dd>
        </dl>
    </div>

页面上的展示效果:
在这里插入图片描述
很简单,但是挺锻炼思想的,所以就写了下来。

模块划分

总体分为五个模块:编译模块、虚拟节点模块、渲染模块、数据响应模块以及vue构造函数模块
html页面中使用js的时候,要使用type=module,防止变量污染
在这里插入图片描述

编译模块

编译模块提供了一个compile函数,将一个模板文本和环境编译成一个结果。getTmeplate函数负责提取
{{}}模板,用正则表达式来匹配。getValue函数负责读取环境变量里的值。最后在compile中进行替换。
compile模块是比较简单的

//获取模板的函数
function getTmeplate(template){
    //匹配{{}里面的内容}
    var matches = template.match(/{{[^}]+}}/g);
    return matches ||[] //如果一个都没匹配到就返回[]
}

//获取环境变量中的值
function getValue(frag, envObj){
    //去掉模板两边的括号
    let exp = frag.replace("{{", "").replace("}}", "");
    let props = exp.split('.');//将表达式分解成数组
    let obj = envObj;//将envObj赋值给obj
    for(let i = 0; i < props.length; i++){
        obj = obj[props[i]];
    }
    return obj;
}
export default function compile(template, envObj){
    let frags = getTmeplate(template);
    let result = template
    for(let i = 0; i < frags.length; i++){
        let frag = frags[i];
        result = result.replace(frag, getValue(frag, envObj));
    }
    return result;
}

虚拟节点模块

虚拟节点模块提供一个createVNode函数,根据真实的DOM,构建一个虚拟DOM树。
VNode是一个构造函数,负责创建节点。

function VNode(realDom, template){
    this.realDom = realDom;
    this.template = template;
    //真实dom的子节点
    this.children = [];
}

export default function createVNode(realDom){
    let root = new VNode(realDom, "");
    //判断真实dom是否为文本节点
    //记录本节点的值到虚拟节点
    if(realDom.nodeType === 3){
        root.template = realDom.nodeValue
    }else{ //不是文本节点
    	//不能使用realDom.children,因为这样只能得到标签元素
        for(let i = 0; i < realDom.childNodes.length; i++){
            let childRealNode = realDom.childNodes[i]; //拿到真实节点的真实子节点
            let vnode = createVNode(childRealNode); //进行递归
            //将虚拟节点添加到虚拟dom树中
            root.children.push(vnode);
        }
    }
    return root;
}

渲染模块

编译模块和虚拟节点模块都创建好了,接下来就是渲染模块了。用于提取虚拟节点中的文本节点,将其模板编译结果设置到真实dom中,对虚拟节点的子节点同样的操作。

import compile from './compile.js'
//渲染文本节点
export default function render(vnode, envObj){
    //判断是否为文本节点
    if(vnode.realDom.nodeType === 3){
        //是文本节点,进行编译 
        //将编译结果设置在真实dom中
        let result = compile(vnode.template, envObj);
        if(result !== vnode.realDom.nodeValue){ //每次改变的值与原本的值不同再进行赋值
            vnode.realDom.nodeValue = result;
        }
    }else{ //如果不是文本节点
        for(let i = 0; i < vnode.children.length; i++){
            //拿到子节点在进行渲染
            let childNode = vnode.children[i];
            render(childNode, envObj);
        }
    }
}

数据响应模块

主要负责两原始对象的数据附加到代理对象上,代理对象可以监听到数据的更改,当数据更改时,执行某个回调函数。

vue2.x版本中使用的是Object.defineProperty来进行数据响应的处理,因此这里我们也是这样来实现
稍微复杂一点的地方就是在对属性的判断为对象,然后进行的操作。如果属性值是一个对象,那么就要新创建一个对象来对他代理,targetObj再代理newTarget。

import render from "./render.js";
//将原始对象添加到代理对象中
function proxyProp(originalObj, targetObj, prop, callback){
    if(typeof originalObj[prop] === 'object'){
        //要代理的属性是一个对象
        let newTarget = {};//新的要代理该属性的对象
        createResponse(originalObj[prop], newTarget, callback);
        Object.defineProperty(targetObj, prop, {
            get:function(){
                return newTarget;
            },
            set:function(value){
                originalObj[prop] = value;
                newTarget = value;
                callback && callback(prop);
            }
        })
    }else{
        //要代理的属性不是一个对象
        Object.defineProperty(targetObj, prop, {
            get:function(){
                return originalObj[prop]
            },
            set:function(value){
                originalObj[prop] = value;
                callback && callback(prop);
            }
        })
    }
}
//将原始对象的所有属性,提取到代理对象中
export default function createResponse(originalObj, targetObj, callback){
    for(let prop in originalObj){
        proxyProp(originalObj, targetObj, prop, callback)
    }
}

vue构造函数模块

通过Vue构造函数可以创建一个vue实例,在创建的过程中,需要完成以下操作:
1. 保存el和data配置
2. 根据el创建虚拟节点
3. 将data中的数据附加到代理对象—vue实例中

import createVNode from "./vnode.js";
import createResponse from "./dataResponse.js";
import render from './render.js'

export default function Vue(options){
    //保存el和data
    this.$el = options && options.el;
    this.$data = options && options.data;
    //根绝el创建虚拟节点
    this.$vnode = createVNode(document.querySelector(this.$el));
    //将data中的数据附加到代理对象--vue实例中
    let that = this;
    createResponse(this.$data, this, function (){
        that.render();
    })
    //初始加载时渲染一次
    this.render();
}

Vue.prototype.render = function(){
    render(this.$vnode, this);
}

最后再创建一个index.js

这个文件负责接收和导出,当然也可以不用这样写,但是文件多了以后,这样写还是比较方便的,一目了然

import Vue from './Vue.js'
export default Vue;

这样就完成了基本功能,在控制台修改数据也是可以渲染到页面,如果想添加一些功能,也可以继续写。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值