美文网首页
手动实现一个自己的Vue

手动实现一个自己的Vue

作者: key君 | 来源:发表于2019-10-08 10:55 被阅读0次

    不使用任何vue的文件
    目前做到的程度是,
    胡子语法的数据绑定
    k-text指令的数据绑定
    created的生命周期函数
    k-model的双向绑定
    k-html的显示
    @click指令的执行

    index.html

    初始化KVue实例,传入对象

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    
    <body>
        <div id="app">
            <p>{{name}}</p>
            <p k-text="name"></p>
            <p>{{age}}</p>
            <p>
                {{doubleAge}}
            </p>
            <input type="text" k-model="name">
            <button @click="changeName">呵呵</button>
            <div k-html="html"></div>
        </div>
        <script src='./compile.js'></script>
        <script src='./kvue.js'></script>
    
        <script>
            const kaikeba = new KVue({
                el: '#app',
                data: {
                    name: "I am test.",
                    age: 12,
                    html: '<button>这是一个按钮</button>'
                },
                created() {
                    console.log('开始啦')
                    setTimeout(() => {
                        this.name = '我是测试'
                    }, 1500)
                },
                methods: {
                    changeName() {
                        this.name = '哈喽,开课吧'
                        this.age = 1
                    }
                }
            })
        </script>
        <!-- <script src="kvue.js"></script> -->
        <!-- <script>
            const app = new KVue({
                data: {
                    test: "I am test",
                    foo: {
                        bar: "bar"
                    },
                }
            });
    
            // app.$data.test = "hello, kaikeba!";
            // app.$data.foo.bar = "oh my bar";
            app.test = "hello, kaikeba!";
    
        </script> -->
    </body>
    
    </html>
    
    Kvue.js

    实现KVue的类

    1,保存了传入的对象和对象中的data
    2,遍历data里面的key值,给每一个key值用defineProperty重写get和set方法
    创建dep实例,get方法给dep里面的数组添加watcher,set方法调用dep的函数,遍历watcher数组执行里面的watcher实例的update方法。如果key是个对象的话,就递归调用拦截方法。
    3,创建编译器Compile实例传入元素id和当前kVue实例,编译器保存了vm实例和根据id创建了dom元素,获取dom元素的子节点数组。
    如果是元素节点,获取元素的html属性数组,遍历找出带k-开头的,后面截取除k-后面的执行,执行对应的更新函数做首次更新html内容,并创建一个watcher实例,传入vm实例,属性的key,和更新文本的函数,让watcher保存,watcher把Dep.target指向自己,调用了一次属性get方法,把当前watcher加到dep里面的数组中。
    如果是文本节点,根据正则判断是否带有{{}},更新文本并添加watcher,同上,如果当前节点还有子节点,就递归执行编译方法。
    4,改变created函数this指向,并执行生命周期created函数。

    //vue实例
    class KVue{
        constructor(options){
            //保存选项
            this.$options = options;
            //保存data
            this.$data = options.data;
            //对传入data对象执行响应化处理
            this.observe(this.$data);
            
            // //测试
            // new Watcher(this,'test');
            // this.test;//读取属性 触发依赖收集
            new Compile(options.el,this);
            if(options.created){
                options.created.call(this)
            }
        }
    
        observe(value){
            //参数必须是对象
            if(!value || typeof value !== 'object'){
                return;
            }
            //拿到data对象的key数组去遍历
            Object.keys(value).forEach(key => {
                //执行响应化
                this.defineReactive(value,key,value[key]);
                //执行代理
                this.proxyData(key);
            })
        }
        //obj:data对象 遍历给每一个data里面的对象增加监听
        defineReactive(obj,key,val){
            //对象里面可能还有对象 递归判断
            this.observe(val);
    
    
            //创建dep 每个属性对应着一个dep
            const dep = new Dep();
    
            //定义属性
            Object.defineProperty(obj,key,{
                get(){
                    //依赖收集
                    Dep.target && dep.addDep(Dep.target)
                    return val;
                },
                set(newVal){
                    if(newVal === val){
                        return
                    }
                    val = newVal;
                    console.log(key+'属性更新了');
                    dep.notify();
                }
            })
        }
    
        proxyData(key){
            //给自定义vue实例添加属性
            Object.defineProperty(this,key,{
                get(){
                    return this.$data[key]
                },
                set(newVal){
                    this.$data[key] = newVal;
                }
            })
        }
    }
    
    //Dep: 管理若干Watcher实例,通知它们更新
    class Dep{
        constructor(){
            this.deps = [];
        }
    
        addDep(dep){
            this.deps.push(dep);
        }
    
        notify(){
            //set函数调用
            this.deps.forEach(dep => dep.update())
        }
    }
    
    //watcher 执行具体更新操作
    class Watcher{
        constructor(vm,key,updater){
            
            this.vm = vm;
            this.key = key;
            this.updater = updater;
    
            Dep.target = this;//依赖收集
            this.vm[this.key];
            Dep.target = null;
        }
        update(){
            // console.log('属性'+this.key+'更新了'); 
            this.updater.call(this.vm,this.vm[this.key]);
        }
    }
    
    compile.js
    //new Compile('#app',vm)
    class Compile{
        constructor(el,vm){
            this.$vm = vm;
            this.$el = document.querySelector(el);
            
            if(this.$el){
                //执行编译
                this.compile(this.$el);
            }
        }
        compile(el){
            //遍历el el就是真实dom
            //拿当前元素的子元素加文本节点 不包括自身
            const childNodes = el.childNodes;
            //每次拿出一个dom节点
            Array.from(childNodes).forEach(node => {
                if(this.isElement(node)){
                    //元素节点
                    this.compileElement(node);
                    
                }else if(this.isInter(node)){
                    //文本节点 编译文本
                    this.compileText(node);
                    
                }
                //递归 遍历树 记得加有条件的递归 看有没有子节点
                if(node.childNodes && node.childNodes.length > 0){
                    this.compile(node);
                }
            })
        }
        //判断元素
        isElement(node){
            //判断元素 1就是元素
            return node.nodeType === 1;
        }
        //判断文本节点
        isInter(node){
            //判断文本 3是文本节点+xxx{{xxx}}
            return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
        }
        //替换文本节点的值
        compileText(node){
            //获取表达式 RegExp.$1是{{}}里面的值 然后从vm属性里面取到该key的值 替换给文本节点
        //    node.textContent = this.$vm[RegExp.$1];
            const exp = RegExp.$1;
            this.update(node,exp,'text');
        }
        //节点  data里面属性的key  命令text
        update(node,exp,dir){
            let updaterFn = this[dir + 'Updater']
            //首次更新值 this.$vm[exp] 就是data里面当前key的值
            updaterFn && updaterFn(node,this.$vm[exp]);
            //后续更新 额外传一个更新函数
            new Watcher(this.$vm,exp,function(value){
                updaterFn && updaterFn(node,value);
            });
        }
    
        textUpdater(node,value){
            node.textContent = value;
        }
    
        //元素节点更新
        compileElement(node){
            //获取html属性
            const nodeAttrs = node.attributes;
            //k-text="test"
            Array.from(nodeAttrs).forEach(attr => {
                const attrName = attr.name;//k-text
                const exp = attr.value;//test
                //截取指定后面的值
                if(attrName.indexOf('k-') === 0){
                    //指令k-text k-model
                    const dir = attrName.substring(2);//拿到text
                    this[dir] && this[dir](node,exp);//执行text函数
                }
                //拦截事件
                if(attrName.indexOf('@' === 0)){
                    const dir = attrName.substring(1);
                    this.eventHandler(node,exp,dir);
                }
            });
        }
        text(node,exp){
            this.update(node,exp,'text')
        }
    
        //k-model 两个处理 1是显示新值 2是重新赋值
        model(node,exp){
            //执行更新
            this.update(node,exp,"model");
    
            //事件监听 exp是data里面的key
            node.addEventListener('input',e => {
                //data里面的key赋值
                this.$vm[exp] = e.target.value;
            })
        }
    
        modelUpdater(node,value){
            node.value = value;
        }
    
        html(node,exp){
            //执行更新
            this.update(node,exp,"html");
        }
    
        htmlUpdater(node,value){
            node.innerHTML = value;
        }
    
        eventHandler(node,exp,dir){
            //获取回调函数
             const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp];
             if(dir && fn){
                node.addEventListener(dir,fn.bind(this.$vm));
             }
        }
    }
    

    相关文章

      网友评论

          本文标题:手动实现一个自己的Vue

          本文链接:https://www.haomeiwen.com/subject/mryvpctx.html