avalonJS-源码阅读(三) VMODEL

 avalon的重头戏。终于要到我最期待的vmodel了。 ps:这篇博文想做的全一点,错误少一点,所以会有后续的更新在这篇文章中。 状态:一稿

目录[-]
avalon dom小结
数据结构
观察者模式
依赖收集与触发
avalon Observable
avalon modelFactory
loopModel
函数介绍
isEqual
小记
avalon dom小结

看过前面三篇文章后,应该会对avalon关于dom的处理有个大体的理念。这里再理一遍:avalon通过手动触发scan函数来遍历dom。然后根据ms-import ms-container ms-include ms-skip确

定VMODELS的作用域,接下来便是处理用户代码并生成相应的函数,通过registerSubscriber函数,将dom生成的函数以及相应的关联数据进行注册。
剩下的,就是其他模块的事情了。
数据结构

如常

//VMODULE 严格意义上,这个不算数据结构,而且,这是object一层的结构,如果object是嵌套式的
// {
//     a:{
//         b:"嵌套"
//     }
// }
// 那么这个比较复杂了
{
    $id:$string,//define函数的第一个参数,如果是嵌套层的,则随机
    $model:$obj,//define定义中的第二个参数中的vm。如果是嵌套层的,则$model为当前嵌套层及以下的值
    $watch:$fn,
    $unwatch:$fn,
    $fire:$fn,//以上三个是Observable对象下的三个方法,不过他们的上下文被修改了。
    $skipArray:true|$array,//要忽略的参数
    hasOwnProperty:$fn,//重写的方法,将其作用域定义到$model
    $accessors:$obj,下文将详细讲解。
    $event:$obj,//记录用户定义的$watch
    get ...:$fn,
    set ...:$fn,
    ...:...//经过defineProperty处理的model值
}
//$accessors,
{
    $modelName:$fn //$fn[subscribers] =["记录订阅者"]
    $vmodel:$obj// 嵌套层时,出现
}
观察者模式

在讲解 avalon vmodel之前,一定要对观察者模式有个清晰的了解,观察者模式又叫订阅发布模式(Subscribe/Publish),具体讲解参见观察者模型。
在avalon中,作者用了两遍观察者模型,分别解决了model和view的交互和扩展用户监控model值的问题。具体实现模块为依赖收集与触发和Observable。和java代码中实现的观察者不同的

是,被观察者会创建一个数组,数组内存放着所有的观察者,而观察者则都是函数。
当被观察者产生改变时,观察者函数则会被附加上下文环境后,依次执行。
依赖收集与触发

这里先上下源码,用来做辅助讲解。

    function registerSubscriber(data) {
        Registry[expose] = data //暴光此函数,方便collectSubscribers收集
        avalon.openComputedCollect = true
        var fn = data.evaluator
        if (fn) { //如果是求值函数
            if (data.type === "duplex") {
                data.handler()
            } else {
                data.handler(fn.apply(0, data.args), data.element, data)
            }
        } else { //如果是计算属性的accessor
            data()
        }
        avalon.openComputedCollect = false
        delete Registry[expose]
    }

    function collectSubscribers(accessor) { //收集依赖于这个访问器的订阅者
        if (Registry[expose]) {
            var list = accessor[subscribers]
            list && avalon.Array.ensure(list, Registry[expose])//只有数组不存在此元素才push进去
        }
    }

上面代码中,accessor函数的[subscribes]属性,做了被观察者存储观察者的事情。
为什么registerSubscriber函数就敢肯定在 Registry[expose] = data和delete Registry[expose]之间,collectSubscribers会被执行?
秘密就在于descriptorFactory函数对Object.defineProperty函数的应用。我们先写一点Object.defineProperty的简单例子,具体理解可参考数据属性和访问器属性。

var a={};
Object.defineProperty(a,"a",{

    get:function(){
        console.log("get a")
        return "默认值"
    },
    set:function(val){
        console.log("set a")
        value=val;
    }
});
console.log(a.a);
console.log(a.a="new value")
console.log(a.a);
// get a
// 默认值
// set a
// new value
// get a
// 默认值 

var memeryValue="默认值";//we need to save the value in some where
Object.defineProperty(a,"b",{
    get:function(){
        console.log("get b")
        return memeryValue
    },
    set:function(val){
        console.log("set b")
        memeryValue=val;
    }
});
console.log(a.b);
console.log(a.b="new value")
console.log(a.b);
// get b
// 默认值
// set b
// new value
// get b
// new value

通过上面的例子,结合观察者模型我们可知道,通过对get/set的设定,我们可以观察用户什么时候对vmodel里绑定的值进行赋值或读取,并做相应的处理(对avalon来讲,就是调用

accessor函数)。
至于notifySubscribers函数,则是调出绑定在accessor函数上的观察者集合,并执行。
avalon Observable

Observable是一个对象。它下面定义了三个方法,分别为$watch、$unwacth、$fire。这三个方法会和$events会被注入到每一个用户定义的vm中(如果用户定义的vm是嵌套的,方法会在每

个嵌套层都注入一下)。用户只需要了解$watch和$unwacth用法即可,fire交给avalon自行处理就好了。
$events用来记录观察者和被观察者关系。

注意:"$events"下面的属性会多出类似这样的值{"modelValue":undefined},而这个值的来源计算属性
accessor的真么一段代码。

//name=‘abc‘
var backup = vmodel.$events[name]//当vmodel.$events[name]不存在时,backup会被赋值为undefined
vmodel.$events[name] = []
setter.call(vmodel, newValue)
vmodel.$events[name] = backup//这时,{abc:undefined}

这个地方好生纠结,自己稍微改了一下:

var backup = vmodel.$events[name]
vmodel.$events[name] = []
setter.call(vmodel, newValue)
if(backup===undefined)
    delete vmodel.$events[name]
else
    vmodel.$events[name] = backup

avalon modelFactory

modelFactory由两个重要的函数构成 loopModel和descriptorFactory。
loopModel

在说loopModel之前,我们要先了解下avalon的5种属性。

    model属性,存放着未被avalon处理用户定义的属性集合。你可以把它当成java的entity,和后台进行数据交互时,直接和它进行交互,avalon会自动触发订阅。

    normalProperties 普通属性,不需要双向绑定的,例如放在$skipArray里的属性,用户自定义以$开头的,函数以及avalon自定义的一些函数($event, $watch 等)。

    accessingProperties 监控属性,要进行双向绑定的属性。

    watchProperties 强制要监听的属性,以$开头的,但又想强制监听它,例如avalon内部定义的$event等。这个属性是normalProperties的补充,属于内部属性,用户不会使用到它。

    computedProperties 计算属性,用户自己自定义的set和get方法。是accessingProperties的补充。

loopModel函数的主要作用是将用户定义的vm object进行属性归类,并生成被观察者函数accessor。
作为被观察者accessor,他需要绑定来自dom的观察者到自身上以及将值大的改变通知到来自dom的观察(notifySubscribers)和来自用户自定义的观察(safeFire)。依据

accessingProperties、computedProperties的嵌套的的不同特点构造了三种类型的accessor。
我们捡一个对object嵌套的accessor实现来看看。他除了实现上面的功能外,还需要调用modelFactory对嵌套的每一层生成VMODULE结构,在这个实现上,为了尽可能的复用现有代码,牺牲

了数据结构,avalon.vmodels关于$model的记录有些重复。至于updateWithProxy、updateVModel函数以及关于的详细讲解,会在以后补充上。
函数介绍
isEqual

var isEqual = Object.is || function(v1, v2) {
        if (v1 === 0 && v2 === 0) {
            return 1 / v1 === 1 / v2
        } else if (v1 !== v1) {
            return v2 !== v2
        } else {
            return v1 === v2
        }
    }//虽然看不懂,但不妨碍使用
小记

avalon的双向绑定的基本内容就这么多了。代码读到现在终于有一种可解脱的感觉了。感谢司徒正美给我们带来如此优秀的代码,另推荐他的一本书《javascript框架设计》,虽然校验的

不怎么样,小bug不断,但内容绝对丰满,适合低中级的Jser反复阅读(至于高级适不适合,我就不知道了)。
时间: 2024-10-10 19:38:13

avalonJS-源码阅读(三) VMODEL的相关文章

SDWebImage源码阅读(三)UIImage+GIF

UIImage+GIF 是UIImage 类的一个GIF 分类,在之前的版本里面这个分类是用了处理GIF 动态图片的但是会有内存暴增的bug.在当前 '4.0.0-beta2' 的版本里GIF 动态图片处理放在了UIImage+MultiFormat  这个分类里面,而当前这个GIF 的分类的功能只是将动态图片作为静态图片来处理,如果是静态图片的NSData 数据则转化为静态UIImage 直接返回,如果是动态图片的NSData 数据,则把图像的第1帧图像转换化为静态UIImage 返回. 首先

SparkConf加载与SparkContext创建(源码阅读三)

sparkContext创建还没完呢,紧接着前两天,我们继续探索..作死... 紧接着前几天我们继续SparkContext的创建: 接下来从这里我们可以看到,spark开始加载hadoop的配置信息,第二张图中 new出来的Configuration正是hadoop的Configuration.同时,将所有sparkConf中所有以spark.hadoop.开头的属性都复制到了Hadoop的Configuration.同时又将spark.buffer.size复制为Hadoop的Configu

SpringMVC源码阅读(三)

先理一下Bean的初始化路线 org.springframework.beans.factory.support.AbstractBeanDefinitionReader public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int c

Struts2源码阅读(一)_Struts2框架流程概述

1. Struts2架构图  当外部的httpservletrequest到来时 ,初始到了servlet容器(所以虽然Servlet和Action是解耦合的,但是Action依旧能够通过httpservletrequest取得请求参数), 然后通过Filter chain,Filter主要包括ActionContextCleanUp,它主要清理当前线程的ActionContext和 Dispatcher:FilterDispatcher主要通过AcionMapper来决定需要调用哪个Actio

《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecut

【原】SDWebImage源码阅读(三)

[原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们来到SDWebImageDownloader.m文件中,找到downloadImageWithURL函数.发现代码不是很长,那就一行行读.毕竟这个函数大概做什么我们是知道的.这个函数大概就是创建了一个SDWebImageSownloader的异步下载器,根据给定的URL下载image. 先映入眼帘的

【 js 基础 】【 源码学习 】backbone 源码阅读(三)

最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 google 一下 backbone 源码,也有很多优秀的文章可以用来学习. 我这里主要记录一些偏设计方向的知识点.这篇文章主要讲 backbone.sync 中用到的 Rest 和 CRUD. 首先我们简单了解一下 REST: REST :

SDWebImage源码阅读-第三篇

这一篇讲讲不常用的一些方法. 1 sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: completed: 取得上次缓存的图片,然后作为占位图的参数再次进行一次图片设置. - (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options

【原】AFNetworking源码阅读(三)

[原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data task.但是对于NSURLSession部分却没有提及.主要是精力有限,准备在这一部分把NSURLSession的知识好好梳理一遍.一切先从上一篇中的addDelegateForDataTask:函数说起,然后再介绍AFURLSessionManagerTaskDelegate,最后结合AFURLSes

JDK源码阅读(三):ArraryList源码解析

今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 一般来讲文章开始应该先介绍一下说下简介.这里就不介绍了 如果你不知道ArrayList是什么的话就没必要在看了.大致讲一下一些常用的方法 2.继承结构 ArrayList源码定义: ArrayList继承结构如下: Serializable 序列化接口 Cloneable 前面我们在看Object源