学习 AngularJS (五) scope

继续看 kityminder-editor 部分代码, 一层层进入到 undoRedo.directive.js, 以及对应的模板文件 undoRedo.html,
又看不懂里面的 angularjs 相关代码了, 怎么办?? --- 只能是回学校重新学啊.

幸好买的书到了, 《精通AngularJS》. 但为了弄懂 kityminder 就得学这么多东西, 是不是很复杂? 
万一以后领导/客户说还要改点什么, 一定会更复杂吧...?

将粗看一下部分开始章节, 期望重点了解 scope, directive 概念.

AngularJS 的 MVC 模式

MVC 太知名了, 招聘时年轻的学子们有问你们用不用 MVC 模式, 我都不知道该怎么回答. 看他们表情仿佛是若不用 MVC,
就很落伍很低级 -> 不来. 所以, 还是赶紧学一下什么是 MVC 吧!

MVC (model-view-controller), 是一种架构(architect)模式, 相当抽象. 当前有很多变种和派生 (如 MVP,
MVVM 模式), 开发者有自己对 MVC 的理解并有不同的设计架构. 导致同样的 MVC 名字有不同的架构...

AngularJS 采用注重实效的方式 -> MVW(model-view-whatever) 模式.

书上例子: hello-world 使用 ng-controller:

<div ng-controller='HelloCtrl'>
  <h1>Hello, {{name}}!</h1>
</div>

<script>
function HelloCtrl($scope) {
  $scope.name = 'angular';
}
</script>

要亲自实验一下才放心, 于是写简单网页吧, 看看到底能行不? --- 测试结果: 没通过...

换一下写法, 下面这种才能通过:

<div ng-app='demo' ng-controller='HelloCtrl'>
  <h1>Hello {{ name }}!</h1>
</div>

<script>
angular.module('demo', []) // 必须有一个 module 吗?
  .controller('HelloCtrl', ['$scope', function($scope) { // 必须注册到 module 吗?
    console.log('HelloCtrl() is called, $scope is: ', $scope);
    $scope.name = 'World';
  }]);
</script>

(这样, 看这书是不是前景堪忧啊...) 还是先继续看 scope (作用域)概念吧.

 

作用域 (scope)

AngularJS 中的 $scope 对象是模板的域模型(domain model), 也称为作用域实例 (instance).
通过为其属性赋值, 可以传递数据给模板渲染.

上面的 HelloCtrl 例子中, 输出的 $scope 如下图样子:

学习 AngularJS (五) scope_第1张图片

 

初看应是一个 Scope 类的实例, 以 $$, $ 开头的属性和方法应该是 angularjs 内部使用的. name 属性是我们添加的.
还注意到有 $id, $parent, $root 属性, 应是构成了 scope 的树结构(层次结构).

书上写向 scope 赋值, 可以传递数据给模板渲染, 那我们来做实验, 手动在 console 中输入语句为 $scope.name 赋新值,
看看能发生什么...?  --- 答案是: 没变化. 这意味着什么?

既然 $scope 对象是模板 (对应 view 吗?) 的域模型 (对应 model 吗?), ... 那么就有了 M&V of MVC 了吗?

作用域 (scope) 可以加入与模板相关的数据(data)和提供相关的功能(function). 例子, 用函数 getName() 替代
name 属性 (实验通过):

angular... .controller(..., function($scope) {
  $scope.getName = function() {
    return 'World';
  }
});
// 对应模板写作: Hello, {{ getName() }}

证明了啥? 证明 AngularJS expression 能支持调用 $scope 上的函数......

$scope 对象可以精准地控制领域模型 (domain model) 的哪些数据和操作在视图上 (view) 上是有效的.
概念上来说, AngularJS 的作用域 (scopes) 与 MVVM 模式的视图模型 (view-model) 非常相似.

控制器

控制器(controller) 的主要职责是初始化作用域 (scope) 实例 (instance).

可以 1. 属性 在 $scope; 2. 方法 于 $scope, 一般是 UI 相关的行为. 

在初始化 scope instance (View-Model of MVVM) 时, controller 与 ng-init 可以做同样的工作.

实验: 我们即有 ng-init 又有 controller, 会发生什么?

<div ng-controller='HelloCtrl' ng-init='name = "Warlord"' >
  Hello {{ name }}!
</div>
// controller 部分中设置 $scope.name = 'World', 其它不变.

结果显示 Hello Warlord! 这说明了什么?

既然 controller 能用, 则尽量将代码放到 controller 中, 而不是将 js 混在 html 模板中.
书上说控制器就是一个 js 函数, 不需要扩展任何 angularjs 特定的类, 不需要调用特定 angularjs api ...
但我单独写 controller 函数却无法通过实验, 也许我还不太熟悉 angular, 又或者旧版本可以写函数?

模型

AngularJS 的模型 (model) 实际上就是普通的 js 对象. 不需要去扩展任何 angularjs 底层类.

可以使用任何 js 类或对象, 属性也不仅限于原始值 (primitive values).

AngularJS 没有侵略性, 它让模型对象远离框架特定的代码. (请一下此设计)

深入作用域

每个 $scope 都是 Scope 类的实例. (我们上面观察 $scope 对象时可注意到这一点)

作用域层级

问题: controller 函数的参数 $scope 从何而来?
答案是: ng-controller 指令使用 scope 对象的 $new(isolate, parent) 方法创建新的作用域, 传递给 controller 函数.
   angularjs 在新应用启动时自动创建 $rootScope 作为其他所有 scope 的父 scope. (?是否 ng-app 启动一个新应用)

ng-controller 指令是作用域创建 (scope-creating) 指令. 每当 DOM 树中遇到此类指令, 都会创建 Scope 类的
新实例 $scope. 新的 $scope 拥有 $parent 属性, 指向它的父作用域.

由于 DOM 是树结构, 附加在 DOM 上的创建 scope 的指令创建出 scope 树也是不奇怪的.

如重复器(repeater)指令 ng-repeat 会创建子作用域, 看例子:

<div ng-controller='HelloCtrl'>
  <ul>
    <li ng-repeat='i in [2,3,5,7]'>Number is {{ i }}</li>
  </ul>
</div>

页面显示出 2,3,5,7 在一个 ul 中. 查看 html 发现有多个 DOM 元素 class 中含有 ng-scope:

学习 AngularJS (五) scope_第2张图片

 

按照书上指引, 找到一个 Firefox 的扩展 AngScope - AngularJS 作用域查看器 for Firebug, 在 DOM 元素上右键
点击, 出现菜单 'Inspect Angular Scope', 然后可以查看该元素所属作用域. (启发: 也许还有别的 angular 插件...)
(Chrome 类似扩展为 Batarang)

经过查看会发现一些惊人的事实, body 上的 scope.$id = 1, .parent = null, 这应该是为 demo ng-app 创建的根
作用域. div HelloCtrl ng-controller 的 scope.$id = 2, .parent = scope#1;
每个被重复的 li 都有自己的 scope, $id 对应为 3,4,5,6, 其 .parent = scope#2.
也即每个 li 都创建了一个新的 scope...

而且这些 scope 的 __proto__ 属性也比较怪, 似乎就是其 $parent 的值, 也是一层一层向上直到 null.
这种技术似乎很少看到. 也许我应该多花点时间看看其它 js 库? (可是还有事情要忙, 没时间怎么办?) 

用 Chrome + 扩展 Batarang 查看似乎更方便:

学习 AngularJS (五) scope_第3张图片 

从而这里就能明白, 为什么在一个 repeater 里面可以访问到 $index, $first, $last, $even, $odd 等属性的原因了.

(休息一下, 下篇接着学...)

你可能感兴趣的:(学习 AngularJS (五) scope)