100行代码说明白vue_like框架

https://github.com/william-xue/iwe7-design/tree/mvvc_like.

<!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>
        My name is {{firstName + ' ' + lastName}}, I am {{age + ss}} years old.
    </p>
    <button id="button" onclick="la()">点击更新,验证监听是成功的</button>
</div>
<script type="text/javascript">
    const observe = (data) => {
        if (!data || typeof data !== 'object') {
            return;
        }
        // 取出所有属性遍历,监听所有属性
        for (let key in data) {
            defineReactive(data, key, data[key]);
        }
    };


    //利用Object.defineProperty监听数据变化
    //一个defineReactive对应对象一个属性
    const defineReactive = (data, key, val) => {
        //一个dep管理一个对象的属性
        //对象属性跟节点是一对多的关系
        var dep = new Dep();
        
        Object.defineProperty(data, key, {
            get: function () {
                if (target) {
                    //收集依赖
                    //为什么在这里收集?不在别处?
                    //数据与dom是一对多的关系。你数据遍历,跟踪完了。拿到dom上渲染。(暂且搁置虚拟dom,虚拟dom也就是加一层。只要渲染dom最好也免不了这步)
                    //那我怎么知道dom上你密密麻麻写那么多指令对应的说哪个数据。跟踪了js数据结构,怎么跟踪dom结构。并且保持一直上下文。?
                    //解决方法是:也就是在跟踪数据的时候,判断target....存在与否。target又是个什么鬼东西?
                    //见下文定义watch类。他是个什么鬼?他的构造器里,三句话。 target = this; this.node = node; this.data = data;这三句
                    //将此时此刻的this,装进去了data和node。
                    dep.addNode(target)
                }
                return val
            },
            set: function (newVal) {
                val = newVal;
              //触发变更
                dep.notify();
            }
        });
    }




    //以上是循环遍历.实现observe相对简单。下边是实现观察者。watch是连接observer和compile的中间人,订阅发布模式。异常重要。
    //Dep是对象属性对节点的管理器.Dep类的作用是,一个容器,一个装满了所有监听的数据节点。或者是绑定了v-***属性的容器。add是收集依赖,notify是接到observe通知后进行更新。

    function Dep() {
        this.nodes = []
    }


    Dep.prototype = {
        addNode: function (node) {
            this.nodes.push(node)
        },
        notify: function () {
            //异步更新节点.为什么是settimeout?更新dom?这个要说的话 也是半天。先空着。简单的说 js 是一层,拿到数据放到dom又是一层。浏览器的处理机制,eventloop
            setTimeout(() => {
                this.nodes.map((node) => {
                    node.update()
                })
            })
        }
    }


    //观察者 观察者观察者 观察者观察者 观察者观察者 观察者观察者 观察者观察者 观察者观察者 观察者
    function Watcher(data, node) {
        //target的作用是只在new Watcher的时候建立关系
        //不然每次get都将节点压入dep
        target = this;
        console.dir(target);
        //保存节点
        this.node = node;
        this.data = data;
        //保存text节点的值
        this.nodeValue = node.nodeValue;
        //将对象的每一个属性都访问一遍,建立对象属性跟节点的关系
        //异常重要。删掉无法运行。

        console.log('..........get........')
        for (let key in data) {
            data[key]
        }
        //以上这句话看着像废话???
        //是get.属性访问。是observe的get.目的是收集监听。dep.addNode(target).没有这句话,就没有了桥梁,整个项目就是0⃣️。
        //下文还有一个赋值语句。看着也像是废话。他是set.没有他,整个项目也是0.
        // for (let key in data) { data[key] = data[key] return }

        //console.log('..........555.........')
        //target = null;
   }
    // 模板解析模板解析模板解析模板解析模板解析
    Watcher.prototype = {
        //模版解析.init
        execute: function (exp) {
            return new Function(...Object.keys(this.data), `return ${exp}`)(...Object.values(this.data))
        },
 // new Function.(参数一,参数二,参数三四五六,最后一个参数是执行的方法体。) new Function(...Object.keys(this.data), `return ${exp}`)这步仅仅是实例化一个函数,
// 后,传入(...Object.values(this.data))作为参数,为调用。
// `return ${exp}` es6语法。标签字符串。一种新的调用函数的方式.http://es6.ruanyifeng.com/#docs/string#%E6%A8%A1%E6%9D%BF%E5%AD%97%E7%AC%A6%E4%B8%B2
//这也是为什么{{里边可以进行任何js表达式的原因}}
        //更新view.此处与依赖收集的notify中的update()相关联。
        update: function () {
            //匹配到{{}}
            const newValue = this.nodeValue.replace(/{{(.*?)}}/g, (exp) => {
                //去除{{,}}
                exp = exp.replace(/{{|}}/g, '')
                return this.execute(exp)
                //整个vue的模版语法各种正则,大致思路一致。在作用域里找key对应的value。







            })
            this.node.nodeValue = newValue;
        }
    }


    //监听所有text节点
    const watchTextNode = (el, data) => {
        [...el.childNodes].forEach(child => {
            // 递归直至都是text.也就是说只监听text。别的类型那么多,这里只是个最简单的也说明大体。
            if (!(child instanceof Text)) {
                watchTextNode(child, data)
            }
            //这里监听了所有的text节点.从根节点往下,有几个节点就有几个watch.而每一个watch 实例,参看定义,有node,有data,有init方法,有update方法,。他们都定义这里。那他们又在哪里调用尼?
            //转到dep实例里。每个watch都push进里dep数组里。在什么时间点呢?在init数据,对所有数据进行observer的时候。
            //为什么在彼时彼地进行调用?数据跟踪在进行的过程中,要确定好,我的这个数据是在view层的哪个dom节点使用。与更新。进而,在此时此地的上下文中进行调用,来更新js数据和dom.
            else new Watcher(data, child)
        })
    }


    const bindViewToData = (root, data) => {
        //监听数据
        observe(data);
        //数据变化则更新对应的节点
        watchTextNode(root, data);
 console.log('..............s e t .....')
        //先触发数据变化,更新view。赋值操作很重要。要不然不会触发行为
        for (let key in data) {
            data[key] = data[key]
            return
        }
    }


    let appData = {
        firstName: 'Lucy',
        lastName: 'Green',
        age: 12222,
        ss: 22
    }

function la(){

    appData.firstName='大头儿子';
    setInterval(() => {
        appData.age = +(new Date())
      
    }, 500)
    

    
    
    }


    bindViewToData(document.getElementById('app'), appData)
</script>




</body>
</html>
复制代码

总共这么点代码。参照vue的实现方式,试图对vue的运行原理进行剖析。 三个类和两句html.每句都有注释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值