守望者--AIR技术交流

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

搜索
热搜: ANE FlasCC 炼金术
查看: 1338|回复: 1
打印 上一主题 下一主题

[Angular UI] AngularJS沉思录(二) 视图模型的层次

[复制链接]
  • TA的每日心情
    擦汗
    2018-4-10 15:18
  • 签到天数: 447 天

    [LV.9]以坛为家II

    1742

    主题

    2094

    帖子

    13万

    积分

    超级版主

    Rank: 18Rank: 18Rank: 18Rank: 18Rank: 18

    威望
    562
    贡献
    29
    金币
    52695
    钢镚
    1422

    开源英雄守望者

    跳转到指定楼层
    楼主
    发表于 2016-8-11 11:24:25 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

    视图模型的层次

    嵌套作用域的数据继承

    在Angular中,存在作用域的继承。所谓作用域的继承,是指:如果两个视图有包含关系,内层视图对应的作用域可以共享外层视图作用域的数据。比如说:

    <body ng-app="test">
        <div ng-controller="OuterCtrl">
            <span ng-bind="a"></span>
            <div ng-controller="InnerCtrl">
                <span ng-bind="a"></span>
                <span ng-bind="b"></span>
            </div>
        </div>
    </body>
    var app = angular.module("test", []);
    
    app.controller("OuterCtrl", function ($scope) {
        $scope.a = 1;
    });
    
    app.controller("InnerCtrl", function ($scope) {
        $scope.b = 100;
    });

    内层的这个div上,一样也可以绑定变量a,因为在Angular内部,InnerCtrl的实例的原型会被设置为OuterCtrl的实例。

    我们改变一下这个示例,如果在内层作用域上,对a进行赋值会怎样?

    <body ng-app="test">
        <div ng-controller="OuterCtrl">
            <span ng-bind="a"></span>
            <div ng-controller="InnerCtrl">
                <span ng-bind="a"></span>
                <span ng-bind="b"></span>
                <button ng-click="increasea()">increase a</button>
            </div>
        </div>
    </body>
    var app = angular.module("test", []);
    
    app.controller("OuterCtrl", function ($scope) {
        $scope.a = 1;
    });
    
    app.controller("InnerCtrl", function ($scope) {
        $scope.b = 100;
    
        $scope.increasea = function() {
            $scope.a++;
        };
    });

    点击这个按钮的时候,发现了一个问题,内层有了a,而且值在增加,外层的不变了。这是为什么呢?

    因为它其实是通过原型集成来做到这样的。像上面这样的包含关系,内层scope的prototype被自动设置为外层的scope了,所以,才可以在内层使用这个a。这时候在内层给a赋值,当然就赋到它自己上了,不会赋值到原型的那个对象上。

    同理,如果内外两层作用域上存在同名变量,在内层界面赋值的时候只会赋到内层作用域上的那个变量,不会影响到外层的。

    那么,除了显式的ng-controller,Angular还会在什么地方引入视图模型的继承呢,主要是这些:

    数组和对象属性的迭代

    在Angular里面,有ng-repeat指令,可以用于遍历数组元素、对象属性。

    <ul>
        <li ng-repeat="member in members">{{member.name}}</li>
    </ul>

    单从这个片段看,看不出视图继承的意义。我们把这个例子再拓展一下:

    <ul>
        <li ng-repeat="member in members">{{member.name}} in {{teamname}}</li>
    </ul>

    它对应的视图模型是这么个结构:

    function TeamCtrl($scope) {
        $scope.teamname = "Disney";
    
        $scope.members = [
            {name: "Tom Cat"},
            {name: "Jerry Mouse"},
            {name: "Donald Duck"},
            {name: "Micky Mouse"}
        ];
    }

    好了,注意到这里,teamname跟members里面的成员其实不在一层作用域,因为它给循环的每个元素都建立了单独的作用域,如果不允许视图模型的继承,在li里面是没法访问到teamname的。为了让这段话更容易理解,我作个转换:

    var teamname = "Disney";
    var members = [
        {name: "Tom Cat"},
        {name: "Jerry Mouse"},
        {name: "Donald Duck"},
        {name: "Micky Mouse"}
    ];
    
    for (var i=0; i<members.length; i++) {
        var member = members[i];
        console.log(member.name + " in " + teamname);
    }

    ng-repeat内部给每个循环造了个作用域,如果不这么做,各个member就无法区分开了。在这种情况下,如果没有作用域的继承关系,在循环内,就访问不到这个teamname。

    在这里,我觉得不一定非要造子作用域,它搞子作用域的原因无非是为了区分每个循环变量,但其实可以换一种写法,比如,avalon框架里的repeat写法就很好,在属性上指定循环元素变量名,然后给每个元素生成ObjectProxy,包装每个元素的数据,附带$index等有可能在循环过程中访问的东西。

    因此,这里其实不必出现Scope的新实例,而是用一个ObjectProxy返回元素数据即可。

    很可能我们的场景还有些简单,再来个复杂的:

    <div ng-controller="TestCtrl">
        <div ng-repeat="boy in boys">
            <span style="color:red" ng-bind="boy.name"></span>
            <span style="color:green" ng-bind="boy.age"></span>
            <button ng-click="boy.growUP()">grow up</button>
        </div>
    </div>
    function TestCtrl($scope){
        $scope.boys = [{
            name: "Tom",
            age: 5,
            growUP: function() {
                this.age ++;
            }
        }, {
            name: "Jerry",
            age: 2,
            growUP: function() {
                this.age ++;
            }
        }];
    }

    这里,每个boy都能自增自己的年龄,原理与上面相同,这里面growUp方法的调用,用ObjectProxy应当也能处理。

    动态包含

    另外一个造成视图继承的原因是动态引入界面模板,比如说ng-include和ng-view等。

    inner.html

    <div>
        <span ng-bind="name"></span>
    </div>

    outer.html

    <div ng-controller="OuterCtrl">
        <span ng-bind="name"></span>
        <div ng-include="'inner.html'"></div>
    </div>
    function OuterCtrl($scope) {
        $scope.name = "outer name";
    }

    对上面这个例子来说,ng-include会创建一层作用域,如果不允许作用域继承,那么内层的HTML中就拿不到name属性。那么,为什么ng-include一定要创建子作用域呢?在这个例子里,创建子作用域并不一定必要,直接让两层HTML模板对应同一个视图模型的实例,不就可以了?

    我感觉他可能是为了省事,否则要判断动态include进来的这个HTML片段中,是否还指定了别的控制器,如果不管三七二十一就创建子作用域,这事就省了。ng-view跟ng-include的情况还不一样,因为ng-view可能会在路由里面指定新的控制器,所以判断起来就更复杂了,基本上只能创建新作用域。

    视图模型的继承好不好?

    视图模型的继承在很多情况下是很方便,但造成问题的可能性也会非常多。真的需要这样的共享机制吗?

    大家都知道,组件化是解决开发效率不高的银弹,但具体如何做组件化,人们的看法是五花八门的。Angular提供的控制器,服务,指令等概念,把不同的东西隔离到各自的地方,这是一种很好的组件化思路,但与此同时,界面模板层非常乱。

    我们可以理解它的用意:只把界面模板层当作配置文件来使用,压根就不考虑它的可复用性。是啊,反正只用一次,就算我写得乱,又怎样呢?可是在Angular中,界面模板是跟控制器密切相关的。我很怀疑控制器的可重用性,注意,它虽然叫控制器,但其实更应该算视图模型。

    从可重用性角度来看,如果满分5分的话,整个应用的这些部分的得分应当是这样:

    • 服务,比如说,对后端RESTful接口的AJAX调用,对本地存储的访问等,5分
    • 控制器(也就是视图模型),2-3分
    • 指令,这个要看情况,有的指令是当作对HTML元素体系的扩展来用的,有些是其他事情的
      • 纯UI类型的指令,也可以算是控件,比如DatetimePicker,5分
      • 有些用于沟通DOM跟视图模型的指令,2分
    • 界面模板,这个基本就没有重用性了,1分

    从这里我们可以看到,以可重用度来排序,最有价值的是服务和控件,服务代表着业务逻辑的基本单元,控件代表了UI层的最小单元,所以它们是最值得重用的。

    现在来看看中间层:视图模型值得重用吗?还是值得的。比如说,同一视图模型以不同的界面模板来展现,这就是一种很好的方式。如果说,同一个视图模型要支持多个界面模板,这些界面模板使用的模型字段或者方法有差异,也可以考虑在视图模型中取并集。例如:

    function TestCtrl($scope) {
        $scope.counter = 0;
    
        $scope.increase = function() {
            $scope.counter++;
        };
    
        $scope.decrease = function() {
            $scope.counter--;
        };
    }

    1.html

    <div ng-controller="TestCtrl">
        <span ng-bind="counter"></span>
        <button ng-click="increase()">increase</button>
    </div>

    2.html

    <div ng-controller="TestCtrl">
        <span ng-bind="counter"></span>
        <button ng-click="decrease()">decrease</button>
    </div>

    3.html

    <div ng-controller="TestCtrl">
        <span ng-bind="counter"></span>
        <button ng-click="increase()">increase</button>
        <button ng-click="decrease()">decrease</button>
    </div>

    三个视图的内容是有差异的,但它们仍然共用了同一个视图模型,这个视图模型的内容包含三个视图所能用到的所有属性和方法,每个视图各取所需,互不影响。

    这时候,我们再来看视图模型的继承会造成什么影响。如果是我们有了视图模型的继承关系,就意味着界面模板的包含关系必须跟视图模型的继承关系完全一致,这个很大程度上是增加了管理成本的,也造成了视图模型的非通用性。

    刚开始提到的例子,如果内外层有同名变量,要在内层作用域中显式变更外层的变量,需要从scope.$parent里面去赋值。而一旦在代码中写了$parent这样的东西,就意味着视图模型只能以这样的方式包含了,甚至说,如果不想变更它们的包含关系,只想变更包含层级,也是不可能的,那说不定就要变成$parent.$parent了。

    我们看个场景:

    <body ng-app="test">
        <div ng-controller="OuterCtrl">
            <span ng-bind="a"></span>
            <div ng-controller="InnerCtrl">
                <span ng-bind="a"></span>
                <button ng-click="increaseOuterA()">increase outer a</button>
            </div>
        </div>
    </body>
    var app = angular.module("test", []);
    
    app.controller("OuterCtrl", function ($scope) {
        $scope.a = 1;
    });
    
    app.controller("InnerCtrl", function ($scope) {
        $scope.a = 100;
    
        $scope.increaseOuterA = function() {
            $scope.$parent.a++;
        };
    });

    这里,因为在InnerCtrl中显式调用了$parent,所以它跟OuterCtrl的视图关系就只能非常固定了。如果说,我们这时候把里面这个div提取出来,放在单独的HTML文件中,然后使用ng-view或者ng-include引入它,因为它们本来就要创建一级作用域,所以会导致这个中间又隔了一级,$parent变成了$parent.$parent,非常不好。

    代码如下:

    <body ng-app="test">
        <div ng-controller="OuterCtrl">
            <span ng-bind="a"></span>
            <div ng-include="'inner.html'"></div>
        </div>
    </body>

    inner.html

    <div ng-controller="InnerCtrl">
        <span ng-bind="a"></span>
        <button ng-click="increaseOuterA()">increase outer a</button>
    </div>

    个人认为,在AngularJS中,视图模型的继承虽然使得很多时候代码写起来比较方便,但有些时候会造成很多麻烦。当编写视图模型代码的时候,应当尽量避免父子作用域存在同名变量的情况,以防止造成隐含的问题。不了解AngularJS实现原理的朋友很可能在这里踩很多坑。


    来源:http://div.io/topic/583

    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
    收藏收藏 分享分享 支持支持 反对反对 微信
    守望者AIR技术交流社区(www.airmyth.com)
    回复

    使用道具 举报

  • TA的每日心情
    郁闷
    2016-8-19 16:08
  • 签到天数: 7 天

    [LV.3]偶尔看看II

    4

    主题

    31

    帖子

    2628

    积分

    少尉

    Rank: 6Rank: 6

    威望
    0
    贡献
    8
    金币
    320
    钢镚
    25
    沙发
    发表于 2016-8-16 14:06:27 | 只看该作者
    学习学习。。。。。。。。。。
    我是一个兵
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    
    关闭

    站长推荐上一条 /4 下一条

    QQ|手机版|Archiver|网站地图|小黑屋|守望者 ( 京ICP备14061876号

    GMT+8, 2024-4-20 15:14 , Processed in 0.065148 second(s), 35 queries .

    守望者AIR

    守望者AIR技术交流社区

    本站成立于 2014年12月31日

    快速回复 返回顶部 返回列表