仿VUE写个框架_VUE学习笔记_05

仿VUE写个框架

仅仅是我个人的Vue学习笔记,用来记录我Vue的学习成果,只是个仿写,功能肯定不全

代码

Html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="./js/my/Compile.js"></script>
<script type="text/javascript" src="./js/my/Sub.js"></script>
<script type="text/javascript" src="./js/my/Pub.js"></script>
<script type="text/javascript" src="./js/my/Observer.js"></script>
<script type="text/javascript" src="./js/my/Lei.js"></script>
<style>
    .cssTest{
        color:red
    }
    .cssFont{
        font-size: 100px;
    }
    .cssBlue{
        color: blue;
    }
</style>
<body>
    <div id="app" class="aa bb cc">
        <button v-on:click="clickTest">测试</button>
        <p v-text="text"></p>
        <p v-html="html"></p>
        <p v-class="myClass">类测试</p>
        <p>
            姓名{{name}}性别{{user.sex}}
        </p>
        <p>
            姓名{{user.name}}
        </p>
    </div>
</body>
<script>
    var lei = new Lei({
        el:'#app',
        data:{
            name:'爱学习的小萌新',
            user:{
                name:'海贼王',
                sex:'男'
            },
            text:'测试text',
            html:'<button>测试</button>',
            myClass:['cssTest','cssFont']
        },
        methods:{
            getJob(){
                return 'program'
            },
            clickTest(){
                console.log('clickTest');
                this.name = 'nakura';
                this.user.name = 'adventure';
                this.user.sex = '女';
                this.text = 'text内容改变';
                this.myClass = 'cssBlue';
            }
        }
    })
</script>

</html>

Lei.js

/**
 * 仿vue实现
  */

function Lei(options) {
    // 保存lei对象
    this.$options = options;
    // 保存data,_data用于保存
    this.data = this._data = options.data || {};
    this.$methods = options.methods;
    // 循环设置代理
    Object.keys(this.data).forEach(key => {
        this._proxy(key);
    });
    // 循环添加方法
    Object.keys(this.$methods).forEach(key => {
        Lei.prototype[key] = this.$methods[key];
    });
    // 初始化数据绑定(只有定义)
    observe(this.data);
    new Compile(options.el,this)
}

Lei.prototype = {
    _proxy(key){
        Object.defineProperty(this,key,{
            configurable:false,
            enumerable:true,
            get(){
                return this._data[key]
            },
            set(newVal){
                this._data[key] = newVal;
            }
        })
    }
}

Compile.js

/**
 * 编译器
 */

function Compile(el, lei) {

    this.$lei = lei;
    let srcNode = document.querySelector(el) || document.getElementsByName('body')[0];
    // 1 取出所有的子节点
    this.getAllNodes(el);
    // 2 编译所有子节点
    this.init();
    // 3 放入页面展示
    srcNode.appendChild(this.$fragment);
}

Compile.prototype = {
    getAllNodes(el) {
        // 取出所有子节点
        let ele = document.querySelector(el);
        let fragment = document.createDocumentFragment();
        while (child = ele.firstChild) {
            fragment.appendChild(child);
        }
        this.$fragment = fragment;
    },
    init() {
        this._compileNodes(this.$fragment);
    },
    _compileNodes(fragment) {
        let childNodes = fragment.childNodes
        // 遍历所有子节点
        Array.prototype.slice.call(childNodes).forEach(node => {
            // 判断节点类型
            if (compileUtil.isElement(node)) {
                // 是标签
                // 解析事件指令等
                this.compileElement(node);
            } else if (compileUtil.isText(node)) {
                // 是文本
                this.compileText(node);
            }
            if (node.childNodes && node.childNodes.length) {
                this._compileNodes(node);
            }
        })
    },

    // 元素编译
    compileElement(node) {
        // 遍历所有属性
        const nodeAttrs = node.attributes
        Array.prototype.slice.call(nodeAttrs).forEach(attr => {
            const attrName = attr.name;
            const exp = attr.nodeValue;//表达式
            const dir = attrName.substring(2);//v-on v-text
            // 判断是否是指令
            if (compileUtil.isDirective(attrName)) {
                // 判断指令类型
                if (compileUtil.isEventAttr(attrName)) {
                    // 事件指令类型
                    compileUtil.eventHandler(node, this.$lei, dir, exp);
                } else {
                    // 一般指令类型
                    compileUtil.bind(node, this.$lei, dir, exp);
                }
            }
        })
    },
    // 双花括号编译
    compileText(node) {
        var reg = /{{(.*?)}}/g;
        let textContent = node.textContent.trim();
        node._template = textContent;
        compileUtil.updater.innerTextUpdater(node,this.$lei);
        while (temp = reg.exec(textContent)) {
            // 创建属性监听
            new Sub(this.$lei, temp[1], (newVal, oldVal) => {
                compileUtil.updater.innerTextUpdater(node,this.$lei,newVal, oldVal);
            });
        }
    }
}

// 解析工具
const compileUtil = {
    isElement(node) {
        return node.nodeType === 1
    },
    isText(node) {
        return node.nodeType === 3
    },
    // 判断指令类型
    isEventAttr(name) {
        return name.indexOf('v-on') == 0
    },
    isDirective(name) {
        return name.indexOf('v-') == 0
    },
    // 事件指令处理
    eventHandler(node, lei, dir, exp) {
        const fn = lei[exp]
        //v-on:click = "click"
        const eventType = dir.split(':')[1];
        if (fn && eventType) {
            node.addEventListener(eventType, fn.bind(lei), false);
        }
    },
    // 一般指令处理
    bind(node, lei, dir, exp) {
        // v-bind:style=""
        const dirs = dir.split(':');
        const dirType = dirs[0];
        const attrType = dirs[1];//有些情况用到,这里不展开写了
        const updater = compileUtil.updater[dirType + 'Updater'];
        const newVal = lei[exp]
        updater && updater(node, newVal);

        // 创建属性监听
        new Sub(lei, exp, (newVal, oldVal) => {
            updater && updater(node, newVal, oldVal);
        });
    },
    // 更新节点
    updater: {
        innerTextUpdater(node,lei) {
            const textContent = node._template;
            var reg = /{{(.*?)}}/g;
            let tempStr = textContent;
            while (temp = reg.exec(textContent)) {
                tempStr = tempStr.replace(temp[0], compileUtil._getVal(lei, temp[1]));
            }
            node.textContent = tempStr;
        },
        textUpdater(node, newVal) {
            node.textContent = newVal ? newVal : '';
        },
        htmlUpdater(node, newVal) {
            node.innerHTML = newVal ? newVal : '';
        },
        classUpdater(node, newVal) {
            if (!newVal) return;
            if (Object.prototype.toString.call(newVal) == '[object Array]') {
                // 是数组
                newVal.forEach(item => {
                    if (Object.prototype.toString.call(item) == '[object String]') {
                        node.classList.add(item);
                    }
                })
            } else if (Object.prototype.toString.call(newVal) == '[object String]') {
                node.classList.add(newVal);
            }
        }
    },
    // 根据表达式取得数据
    _getVal(lei, exp) {
        var exp = exp.split('.');
        var val = lei._data;
        exp.forEach(k => {
            val = val[k];
        });
        return val
    }
}

// 指令分支
const directives = {
    'text': function (node, lei, exp) {
        compileUtil.bind(node, lei, 'text', exp);
    },
    'html': function () {
        // compileUtil.bind(node,lei,'text',exp);
    },
    'class': function () {

    },
    'model': function () {

    },
}

Observer.js

function Observer(data){
    // 保存data对象
    this.data = data;
    this.init(data);
}

function observe(data){
    if(!data || typeof data != 'object')return;
    new Observer(data);
}

Observer.prototype = {
    init(){
        // 给data初始化内容
        Object.keys(this.data).forEach( key => {
            this.defineReactive(key,this.data[key]);
        });
    },
    /**
     * 数据绑定的基本原理:
     * 1 它是基于defineProperty实现的,当我们在method中使用this.name = xxx时,触发vm的Set方法,在vm的set方法中又触发data的set方法,进而触发更新页面的方法
     * 2 分析数据变动与页面渲染,考虑到一个变量可能在页面上多处用到,那么数据变动和页面渲染是一对多的关系
     * 3 要想实现页面的局部刷新,页面渲染的动作必须是异步的,
     * 4 于是问题变成了怎么保存一个变量与相应的多个回调函数,暂且定义为pub和sub,即一个发布者对应多个订阅者(但不是真的发布-订阅模式)
     * 5 初始化顺序问题,要何时进行数据绑定,因为在对页面初始化时,必定用到变量,因此可以将数据绑定的行为定义在data的get方法中
     * 6 get方法肯定会重复调用,那么就一定要有条件去判断是否数据绑定,显然数据绑定只需要在页面初始化,相应的页面编译的时候,也就是Pub先定义好,在定义Sub的时候再定义Pub与Sub的关系
     * 7 那么Pub与Sub之间必须有个东西作为关联,同时这个也可以作为判断是否要数据绑定的条件
     * @param {*} key 
     * @param {*} value 
     */
    defineReactive(key,value){
        let pub = new Pub();//一个变量对应一个Pub
        let ob = observe(value);//递归去绑定每层的属性
        Object.defineProperty(this.data,key,{
            configurable:false,
            enumerable:true,
            get(){
                // 需要定义数据绑定
                if(Pub.target){
                    pub.addSub()
                }
                return value;
            },
            set(newVal){
                if(newVal == value)return;

                value = newVal;
                // 如果新值是个对象则再绑定一次
                ob = observe(newVal);

                // 触发数据更新
                pub.update();
            }
        });
    }
}

Pub.js

var uid = 0;
function Pub(){
    this.id = uid++;//考虑到如果使用变量时xxx.xxx那么在一次数据绑定中就有可能一个sub用到多个pub
    this.subs = [];
}

Pub.prototype = {
    pushSub(sub){
        this.subs.push(sub);
    },
    addSub(){
        Pub.target.addSub(this);
    },
    update(){
        this.subs.forEach(sub => {
            sub.update();
        });
    }
}

Pub.target = undefined;

Sub.js

function Sub(lei,exp,callback){
    this.callback = callback;
    this.lei = lei;
    this.exp = exp;
    this.pubIds = {};
    this.value = this.bind();
}
Sub.prototype = {
    update(){
        // 旧值
        const oldVal = this.value;
        // 新值
        const newVal = this._getVal();
        if(oldVal != newVal){
            this.value = newVal;
            // 调用回调函数,更新页面
            this.callback.call(this.lei,newVal,oldVal);
        }
    },
    bind(){
        Pub.target = this;
        // 当target不为空时进行数据绑定
        const val = this._getVal();
        Pub.target = undefined;
        return val;
    },
    addSub(pub){
        if(!this.pubIds.hasOwnProperty(pub.id)){
            // 可以绑定
            pub.pushSub(this);//保存sub
            this.pubIds[pub.id] = pub;
        }
    },
    /**
     * 根据表达式获取值
     */
    _getVal() {
        var exp = this.exp.split('.');
        var val = this.lei._data;
        exp.forEach(k => {
            val = val[k];
        });
        return val
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值