Angular Scope

作者: 菲汐 | 来源:发表于2016-11-11 15:32 被阅读66次

    摘要


    在AngularJS中, ChildScope通常以原型链的方式继承自ParentScope. 但有一个特殊的情况, 当你在指令中指定了scope:{}, 这会创建一个IsolateScope, 它不从DOM元素所在的Scope上继承属性, 这对于需要创建一个可复用的指令很有帮助.

    Scope的继承通常很简单, 以至于大多数情况下你不会意识到它的存在, 但当你尝试在ChildScope里双向数据绑定(例如ng-model)一个ParentScope上的原生变量的时候(例如number, string, boolean), 它会在ChildScope上创建一个同名的属性, 并使你无法再访问到ParentScope上的该属性, 这并不是Angular施了什么魔法, 这是由Javascrip原型继承决定的. 刚入门的AngularJS 开发者通常不会意识到ng-repeat, ng-switch, ng-view, ng-include, ng-if这些指令都会创建一个新的ChildScope, 所以当牵扯到这些指令的时候, 经常会出现一些奇怪的问题. 看看这个例子.

    一个最佳实践可以轻松的避免上面提到的原生变量的问题, 就是永远在ng-model中加上一个".", 这样可以确保原型链继承正常工作. 所以

    //use
    <input type="text" ng-model="someObj.prop1">
    //rather than
    <input type="text" ng-model="prop1">.
    

    如果你确实需要绑定原生变量, 有两个解决办法:

    1. 在ChildScope里绑定$parent.parentScopeProperty, 这样ChildScope不会自己创建一个同名的变量.
    2. 在ParentScope里定义一个函数, 在ChildScope里调用, 把原生变量作为参数传入

    JavaScript 原型继承


    充分理解JavaScript原型继承很重要, 尤其如果你是后端开发人员, 更熟悉类(Class)继承. 下面我们回顾一下原型继承.

    假设ParentScope有如下属性, aString, aNumber, anArray, anObject, aFunction. ChildScope继承自它.

    Image-1.png

    当我们试图从ChildScope中去访问一个ParentScope上的属性, JavaScript会从ChildScope开始一级一级沿着原型链往上找, 直到找到该属性为止. 所以下面这些表达式都为TRUE.

    ChildScope.aString === 'parent string'
    ChildScope.anArray[1] === 20
    ChildScope.anObject.property1 === 'parent prop1'
    ChildScope.aFunction() === 'parent output'
    

    假设接下类我们执行如下语句

    ChildScope.aString = 'child string'
    

    这句代码会在ChildScope创建一个叫aString的属性, 而ParentScope上的aString不会受到任何影响. 明白这点对接下来讨论ng-repeatng-include很重要.

    Image-2.png

    接下来我们执行

    ChildScope.anArray[1] = 22
    ChildScope.anObject.property1 = 'child prop1'
    

    JavaScript会先在ChildScope上寻找anArrayanObject属性, 找寻无果后, 会继续往原型链上一级也就是ParentScope寻找, 所以ParentScope上的这两个属性就被修改了.

    Image-3.png

    继续执行

    ChildScope.anArray = [100, 555]
    ChildScope.anObject = { name: 'Mark', country: 'USA' }
    

    仔细比较和上一步的区别, 这次是直接对anArrayanObject赋值, 会在ChildScope新增两个对象属性.

    Image-3.png

    这个jsfiddle 是对上述例子的演示.

    Angular Scope 继承


    ng-include
    假设有如下代码

    //Controller
    $scope.myPrimitive = 50;
    $scope.myObject    = {aNumber: 11};
    
    //HTML
    <script type="text/ng-template" id="/tpl1.html">
        <input ng-model="myPrimitive">
    </script>
    <div ng-include src="'/tpl1.html'"></div>
    
    <script type="text/ng-template" id="/tpl2.html">
        <input ng-model="myObject.aNumber">
    </script>
    <div ng-include src="'/tpl2.html'"></div>
    

    每个ng-include都会创建一个ChildScope, 如下图关系:

    在第一个输入框中输入"77", 会得到图4结果, 因为这相当于执行了

    ChildScope.myPrimitive = 77
    
    Image-4.png

    在第二个输入框中输入"99", 会得到图5结果, 因为这相当于执行了

    ChildScope.myObject.aNumber = 99
    
    Image-5.png

    参考上面说道的JavaScript原型继承, 相信这不难理解.

    如摘要中提到的, 如果你确实需要用原生变量, 你可以修改tp11.html

    <input ng-model="$parent.myPrimitive">
    

    输入"22", 得到图6结果, 因为我们直接引用$parent(对ParentScope的引用)上的属性. 所以不会在ChildScope上创建属性.

    Image-6.png
    或者你可以使用绑定函数的形式, 参考jsfiddle
    ng-switch
    ng-include类似, 不再赘述.
    ng-repeat
    假设有如下代码
    //Controller
    $scope.myArrayOfPrimitives = [ 11, 22 ];
    $scope.myArrayOfObjects    = [{num: 101}, {num: 202}]
    
    //HTML
    <ul>
        <li ng-repeat="num in myArrayOfPrimitives">
           <input ng-model="num"></input>
        </li>
    </ul>
    <ul>
        <li ng-repeat="obj in myArrayOfObjects">
           <input ng-model="obj.num"></input>
        </li>
    </ul>
    

    对每一次循环, 每一个ng-repeat会创建一个继承当前Scope的ChildScope. 然后在ChildScope上创建一个属性并赋值(属性的名字就是循环的变量名). 上述代码的运行结果请看图7和8.

    ChildScope = scope.$new(); // child scope prototypically inherits from parent scope ...     
    ChildScope[valueIdent] = value; // creates a new childScope property
    
    Image-7.png Image-8.png
    他们的区别在于数组中每一项的值是原生类型还是对象. 如果是原生类型, 则会复制一份然后赋值给ChildScope上的新属性(num), 如果是对象, 则把对象的引用赋值给ChildScope上的新属性(obj).
    ng-view
    TBD, but I think it acts just like ng-include.
    ng-controller
    互相嵌套的Controller, 就像ng-include, ng-switch一样, 会产生继承关机的Scope. 但是通过$scope在Controller之间共享数据是不好的做法, 应该用Service替代.
    directive
    • scope: false default
      不会创建ChildScope, 所以也存在什么继承, 很简单但也很危险, 指令可以直接操作Scope
    • scope: true
      创建一个ChildScope继承自ParentScope, 如果一个DOM元素上同时有多个指令需要新的Scope, 只有一个会被创建.
    • scope: {}
      创建一个IsolateScope, 如果需要访问ParentScope上的属性, 可以使用@, =, <, &, 详情参考Directives
    • transclude: true
      创建一个TranscludeChildScope继承自指令外部的ParentScope(不是指令的Scope), Angular1.3版本以前, TranscludeChildScope的$parent属性指向的是ParentScope. Angular1.3版本以后, TranscludeChildScope的$parent属性指向的是指令的Scope. jsfiddle

    https://github.com/angular/angular.js/wiki/Understanding-Scopes

    相关文章

      网友评论

        本文标题:Angular Scope

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