zoukankan      html  css  js  c++  java
  • 轻量级前端MVVM框架avalon

    废话说了大几篇,我们开始来点干货了~ 

    ViewModel的内部机制

    • 在MVVM中,数据是核心。而jQuery则以DOM为核心。
    • 而DOM只是HTML在JS的世界的抽象,是一个很易变的东西。因此如果业务代码遍历选择器表达式会非常难维护。但不可否认,jQuery是操作DOM的王者,让我们操作DOM顺手拈来。但如果不让你操作DOM,不是更好吗?就像jQuery不让你用getElementById,getElementsByTagName, querySelecterAll,大家都不知道里面有多少坑,短短几个字母$(expr)是背后sizzle选择器引擎1700行的实现!!!!jQuery其实是在用户代码与原生API中提供一层厚厚的粘合层,因此摸起来光溜溜。在MVVM中,DOM操作基本是水下运作了。由于VM与V之间的双向绑定,操作了VM中的数据(当然只能是监控属性),就会同步到DOM,我们透过DOM事件监控用户对DOM的改动,也会同步到VM。DOM隐形了,就像软件公司,到处跑出来活动的是业务员与不写代码的经理老总,程序员全部关起来加班!虽然这比喻有点残酷,但这正体现了各司其职的威力。能说会道去拉风投接单子没什么不妥,喜欢呆在电脑前的就让他呆吧。jQuery的世界就是一个混乱的公司,全能的程序员什么都做。

    定义一个ViemMode

    <fieldset ms-controller="simple">
        <legend>例子</legend>
        <p>First name: <input ms-model="firstName" /></p>
        <p>Last name: <input ms-model="lastName"  /></p>
        <p>Hello,    <input ms-model="fullName"></p>
        <div>{{firstName +" | "+ lastName }}</div>
        <p>nick name: <input ms-model="nick.name"  /></p>
        <p>{{nick.name}}</p>
    </fieldset>
    
                
    avalon.define("simple", function(vm) {
        vm.firstName = "司徒"
        vm.lastName = "正美"
        vm.fullName = {//一个包含set或get的对象会被当成PropertyDescriptor,    
            set: function(val) {//set, get里面的this不能改成vm
                    var array = (val || "").split(" ");
                        this.firstName = array[0] || "";
                        this.lastName = array[1] || "";
            },
            get: function() {
              returnthis.firstName + " " + this.lastName;
            }
        },
        vm.nick = {
              name: "暗黑之民"
        }
    });
        

    这是官方给出的DEMO,我们看看对应的操作定义

      HTML中:

    1. ms-controller是用于指定ViewModel的作用范围, ms-controller的值等于avalon.define的第一个参数,并且这个值必须是一个命法的变量名, 如aaa, $aaa, aaaSSS, aaa_bbb,不能写成23432, sdfs-A  
    2. ms-model="firstName" 此类绑定只能用于表单中,框架会在上面绑定一些事件,如input, change, click以进行同步
    3. {{firstName +" | "+ lastName }} 模版机制,插值表达式,用于替换值

        

         Javascript中:

    1. ViewModel的定义,它是通过avalon.define来创建,在函数内我们定义它的属性与方法
    2. vm.firstName 监控属性:定义时为一个简单的数据类型,如undefined, string, number, boolean。
    3. vm.fullName 计算属性:定义时为一个最多拥有get,set方法的对象(get方法是必需的),注意,get, set里面的this不能改为vm,框架内部会帮你调整好指向
    4. 监控数组:定义时为一个数组
    5. 普通属性或方法:我们可以在vm里面设置一个$skipArray数组,里面装着你不想处理的方法与属性名

       

    因为在ViewModel的转化中会用到defineProperty的定义,有必要先预先提出来 

    要了解详细,见我的一篇译文 (译)ECMAScript 5 Objects and Properties

       

    JavaScript中有三种不同类型的属性:

    命名数据属性(named data properties

         命名数据属性,就是我们在IE8碰到的绝对大多数属性,可以随意删除添加,设置什么返回什么,不会在内部做多余的事。

    var obj = {
        prop: 123
    };
    console.log(obj.prop); // 123
                    
    console.log(obj["prop"]); // 123
                
    obj.prop = "abc";
    obj["prop"] = "abc";

    命名访问器属性(named accessor properties)

    • 命名访问器属性,就是设置或读取时内部调用一些函数做事情的函数,著名的代表是元素的innerHTML,给它一个字符串会创建一大堆节点,读它时返回的值与我们给它的值可能不一样。 又如数组的length,可能通过它来添加或删除元素。IE8添加了set get关键字,不过没什么人用。不过它又添加了著名的Object.defineProperty方法, 里面可指定读取时或写入时的处理函数。标准浏览器老早就支持__defineGetter__,__defineSetter__。
    var obj = {}
    var _a = 1;
    Object.defineProperty(obj, "a", {
        get: function() {
        return _a
        },
        set: function(a) {
            _a = a + 10
        }
    });
    
    console.log(obj.a) //1;
                    
    obj.a = 20;
    
    console.log(obj.a) //30;
                
    • 计算属性的set, get函数其实就是对应它们俩。
    • avalon, emberjs的ViewModel就是基于访问器实现的,不过emberjs只兼容到IE8。

     内部属性就是无法通过JavaScript直接访问的属性

       

    走进vm的幕后:

    源码:

     1    avalon.define = function(name, deps, factory) {
     2
                    var args = [].slice.call(arguments);
     3
                    if (typeof name !== "string") {
     4             name = generateID();
     5
                                args.unshift(name);
     6
                            }
     7
                    if (!Array.isArray(args[1])) {
     8             args.splice(1, 0, []);
     9
                            }
    10         deps = args[1];
    11
                    if (typeof args[2] !== "function") {
    12             avalon.error("factory必须是函数");
    13
                            }
    14         factory = args[2];
    15
                    var scope = {
    16
                                $watch: noop
    17
                            };
    18
                            deps.unshift(scope);
    19         factory(scope); //得到所有定义
                
    20
                    var model = modelFactory(scope); //转为一个ViewModel
                
    21         stopRepeatAssign = true;
    22         deps[0] = model;
    23         factory.apply(0, deps); //重置它的上下文
                
    24
                            deps.shift();
    25         stopRepeatAssign = false;
    26         model.$id = name;
    27
                    return avalon.models[name] = model;
    28     };

    我们一行行分析:

    •  avalon.define 的定义能接受3个实参
    •  var args = [].slice.call(arguments); 转换数组,arguments是伪数组
    •  保证传参数满足3个定义 如果第二个参数不是数组,转换 avalon.define("on",fn); -> avalon.define("on",[],fn); 
    var scope = {
            $watch: noop
     };

    定义一个作用域,是一个对象,这个东东其实就是暴露给用户的一个接口,也就是vm了,其实VM是后台先创建的

       

      factory(scope); //得到所有定义

    对象嘛是引用,执行后就会把用户定义的方法给挂到scope上了,这样就达到收集用户在外面的处理方法了

       

    var model = modelFactory(scope); //

    这个就是核心的东东了,把scpoe转为一个ViewModel,只有转化之后,才能让我们的东东具有实际的处理能力了

       

    factory.apply(0, deps);

    这是个非常巧妙的设计,用户定义的函数内部的作用域其实还是在普通的对象,我们可以强制转化vm

       

    return avalon.models[name] = model;

    很明显转化后的模型对象挂在到了全局中,方便在扫描节点绑定中获取

    所以整个VM的创建过程,

    核心点就是

    modelFactory方法了

    下篇继续着中分析~
  • 相关阅读:
    Acrobat dose not allow connection to:
    如何备份sqlite数据库
    Linux下Perl的安装
    Sqlserver取分组后的第一条数据
    JS根据占比计算名次范围
    eltable单元格换行显示,超出部分省略号
    二 前端框架引入、结构分配和路由定义
    扩展运算符(...)
    eltable动态合并行列
    解决table中换行符<br>被字符化得问题
  • 原文地址:https://www.cnblogs.com/aaronjs/p/3145392.html
Copyright © 2011-2022 走看看