美文网首页JavaScriptWeb前端之路让前端飞
自制前端框架Day15 写写scope放松一下

自制前端框架Day15 写写scope放松一下

作者: 蚊子爸爸 | 来源:发表于2017-06-12 21:39 被阅读49次

    写在前面

    之前写博客是为了记录自己的开发过程,没指望有人看,所以写的非常凌乱。没想到的是在这些日子里,竟然会有其他人看我的博客并给我评论留言,我感到受宠若惊。所以我要求自己写博客要写的更容易让别人看懂,把自己的所想所得分享出去,如果有人看了我的博客,我希望的文字能对得起别人花费的时间。

    前端框架是什么

    其实前端框架做的事情都一样,无非是解决了如何用数据来渲染页面的问题:在我看来,无论react还是vue还是angular,其实都是controller+view。
    拿我最熟悉的angular来说,数据与页面的绑定就是scope与compile的绑定。
    之前写表达式解析部分写的头晕,决定换换脑子,写写scope。

    scope的本质

    scope其实就是一个普通对象,只不过上面封装了很多方法而已。

    scope的数据监测机制

    在angular的scope中,有两个部分是数据监测的关键,$watch和$digest。我目前不清楚vue的数据变化监测是什么机制,但是angular的是脏值监测,本质是给scope对象上增加watcher。

    watcher是什么

    watcher监控scope的某一个属性,并且当属性变化的时候执行回调函数。

    $digest()是什么

    digest负责把每一个watcher都检查并且运行一次。

    今天先决定写这个部分。

    在scope的对象上有很多watcher,所以要有一个地方存放这些watcher。

    function Scope(){
        this.$$watchers=[]
    }
    

    scope有$watch函数,这个函数接受两个参数,第一个参数用于指定该watcher监听的属性,第二个是属性变化的时候执行的回调函数。

    Scope.prototype.$watch=function(watchFn,listenFn){
        var watcher = {
            watchFn:watchFn,
            listenFn:listenFn
        }
        this.$$watchers.push(watcher);
    }
    

    $digest是用来执行所有watcher的listen方法,也很简单,遍历一下,执行就可以

    Scope.prototype.$digest=function(){
        for(var i=0;i<this.$$watchers.length;i++){
            this.$$watchers[i].listenFn();
        }
    }
    

    写一个测试案例试试看:

    describe('scope', function() {
        var scope;
        beforeEach(function(){
            scope=new Scope()
        })
        it('scope可以赋值', function() {
            scope.name='wangji'
            expect(scope.name).toBe('wangji');
        });
         it('scope的watch和digest方法执行正常', function() {
            var watchFn=function(){
                return 'name'
            }
            var listenFn=jasmine.createSpy();
            scope.$watch(watchFn,listenFn);
            scope.$digest();
            expect(listenFn).toHaveBeenCalled();
        });
    });
    
    顺利执行

    watcher的实现就是典型的一种观察者模式

    watcher有两个方法,一个是watchFn,一个是listenFn。watchFn用于获取scope上某一个属性的值:

    watchFn=function(scope){
        return scope.id//这个watcher用于监听scope.id的值
    }
    

    listenFn是一个回调函数,这样一来就是很典型的观察者模式:一个方法用来监听某个对象的值,另一个方法是当监听到相应动作时候执行。

    脏值检测的实现

    每一个watcher既然能获取scope上的一个属性值,那么应该也可以保存上次的值。然后运行一次digest,把每一个watcher跑一次,如果这次拿到的值和上次保存的值不同,说明值是脏的,就可以运行listenFn回调函数,典型的观察者模式。

    function Scope(){
        this.$$watchers=[]
    }
    Scope.prototype.$watch=function(watchFn,listenFn){
        var watcher = {
            watchFn:watchFn,
            listenFn:listenFn,
            last:''
        }
        this.$$watchers.push(watcher);
    }
    Scope.prototype.$digest=function(){
        var self = this;
        var oldValue,newValue;
        for(var i=0;i<this.$$watchers.length;i++){
            oldValue = this.$$watchers[i].last;
            newValue = this.$$watchers[i].watchFn(self)
            if(oldValue!=newValue){
                this.$$watchers[i].last = newValue;
                this.$$watchers[i].listenFn();
            }
        }
    }
    

    执行以下案例看看效果

        it('脏值检测',function() {
            scope.id=2;
            var watchFn=function(scope){
                return scope.id;
            }
            var listenFn=function(){
                  console.log('listen!')
            }
            scope.$watch(watchFn,listenFn);
            scope.$digest();
        })
    

    运行$digest方法时,期望中是这样的:
    watcher的last值最初是空的,调用watchFn后拿到id属性的值,是2,进行对比,不相等,然后执行了listenFn,打印出listen!这句话。同时,watcher的last被设置为新值,也就是2.
    来运行一下试试:

    旧值和新值确实不相等 last已经变成最新值 打印出了语句,说明listenFn执行了

    没问题!那么再运行一次$digest的话,应该不会再打印listen了,因为经过第一次digest以后,watcher的last属性被赋值为最新值,所以值不再脏了,也就不再运行listenFn了。

    执行两次digest,只运行了一次listenFn

    完成,这就是脏值检测的核心。

    相关文章

      网友评论

      • 三生石上绛珠草:写的这么好肯定有人看的啊。前段时间没怎么看哈哈,我慢慢补
        我理解的是Angular和Vue都是MVVM中的ViewModel,通俗说是binder,把Model和View绑定到一起:joy: 是吧?
        蚊子爸爸:@三生石上绛珠草 我也是这么理解的,我甚至觉得所有前端框架都在做同样的事情。

      本文标题:自制前端框架Day15 写写scope放松一下

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