Build Your Own Angularjs 读书笔记(AngularJS牛逼的地方在于它内嵌了一个表达式到Function对象的编译器。。。当然还有DI框架)

来源:互联网 发布:数据库接口怎么写 编辑:程序博客网 时间:2024/05/14 08:08

Build Your Own Angularjs 读书笔记

目录

 [隐藏] 
  • 1 项目配置
  • 2 作用域
  • 3 表达式与过滤器
  • 4 模块与依赖注入
  • 5 辅助函数
  • 6 指令

项目配置[编辑]

  1. npm package.json
  2. Lo-Dash, jQuery:不依赖,如果有就代理使用
    1. _.template("Hello, <%= name %>!")({name: to}); //var _ = require('lodash');
    2. _.forEach
    3. _.bind //等价于ES5 Function.prototype.bind
    4. _.isArray _.isObject
    5. _.forOwn //遍历object的属性
  3. 用jasmine写测试(describe-it-expect)

作用域[编辑]

  1. 看到$,就会想到PHP、Perl这些脚本语言,程序员手里敲着代码眼里看的是美元符号 :-D
  2. digest cycle与脏检查:$watch, $digest, $apply
    1. watch表达式(内部编译为一个函数,这个看起来有点意思!)
      1. scope.$watch( watchFn: scope -> value, listener: (old, new, scope) -> void) //var watchFn = jasmine.createSpy();
        1. 将old value缓存到watcher对象上?这边有一个小问题:watcher.last要不要初始化为当前value呢?
          1. => 初始化为function initWatchVal() {},用于表示app级别“不存在的”对象取值,绝妙~ 但这里js变量的类型前后是不一样的(也许不需要这么严格要求?)
          2. 简单实现不能捕获listener对scope的每次修改!(除非像Vue那样重载js object的setter函数)--> 一个digest周期内listener改变了属性值,则补充再迭代,可能多次
            1. 防止无限迭代:TTL
        2. $digest迭代watcher,而不是scope的所有属性(不过假如多个watcher监视相同的属性变化?)跟react的virtual dom diff原则不同
    2. $digest:迭代执行一遍所有的$watch
    3. $$前缀代表angular内部的私有变量
    4. 基于value的脏检查(而不是仅仅基于引用):数组和对象的元素diff算法
      1. 为了提供value的old、new,需要deep copying...
      2. NaN != NaN,特殊处理
    5. $eval
    6. $apply:集成外部代码到digest cycle
    7. $evalAsync(推迟,但仍然在当前digest cycle)
      1. vs $timeout(后者是setTimeout(,0)的封装)
      2. []用作defer队列:var asyncTask = this.$$asyncQueue.shift();
    8. scope phases(见鬼,从这个地方有点不太好懂了:不都是在一个digest cycle里面吗?)
      1. => 为了确保从外部调用$evalAsync后,会调度一次digest执行(setTimeout)
    9. $applyAsync(原始动机:处理HTTP response,但不触发完整的digest)
      1. var self = this; //当注册callback时,引用当前的this,以防止JS的动态作用域特性引用错误的this(实际上相当于一个独立的函数,而非对象方法了)
      2. async callbacks的batch执行:用一个新的lambda将queue里的所有callback对象包起来一起执行
      3. 惯用法:
        1. self.$ $applyAsyncId = setTimeout(function(){... self.$ $applyAsyncId = null;},0) //跟踪setTimeout是否已经被浏览器调度执行
    10. $$postDigest(略)
    11. 异常处理
    12. 删除watch
      1. var destroyWatch = scope.$watch(...)
      2. destroyWatch(); //调用数组的splice(index, n)方法删除元素
    13. $watchGroup
  3. 作用域继承
    1. $rootScope
    2. $new:创建新的子scope
    3. child直接看得到parent的属性,但同名的情况下发生JS Prototype链上的shadowing(看到这里,会想起SICP里实现一个解释器的内容)
    4. digest过程现在从rootScope触发,递归执行; $$children
      1. 注意:Angular.js本身用“prev/next-Sibling、$head/tail-Child”来组织一棵scope层次树
    5. scope隔离:仍然挂在scope树上,但是断开到parent的原型链(这样属性查找只在当前child中?)
      1. directive scope linking:有选择地引用parent的属性
      2. 隔离情况下仍然想让scope共用root的属性,则在$new创建时执行一个引用copy操作...
    6. 替换parent(略)
      1. 这里的隐含约束是:当scope层次树的结构动态修改后,相应的watch、apply机制仍然能够properly起作用,相当于代码灵活性的要求
    7. $destroy:从$parent断开引用
  4. 用于集合对象(array、object)的高效脏检查
    1. $watchCollection:每次!$areEqual的时候counter++ ?
    2. 注意,之前oldValue是deep copy出来的,所以这里的核心算法就是针对array或object的diff操作而已...(但是不如React的virtual dom那么激进)
    3. array-like(typeof .length === "number")
      1. arguments
      2. DOM NodeList
      3. ?String不是Object
    4. Trick:如何检测包含了length作为key的object(与array-like区分)
    5. Function对象的.length属性保存了形参的个数?//作用:需不需要记住oldValue传给listenerFn
  5. (Scope Events)事件系统:$on, $emit, $broadcast
    1. Pub:emitting:当前的及其祖先scopes得到通知;往下则为广播(注意:跟DOM事件无关)
    2. $on:注册事件listener
    3. $emit, $broadcast
      1. var listeners = this.$$listeners[eventName] || [];
      2. var additionalArgs = _.rest(arguments);
      3. var listenerArgs = [event].concat(additionalArgs); listener.apply(null, listenerArgs);
    4. 仿照DOM事件的target与currentTarget属性,有targetScope与currentScope(分别代表触发和处理事件的scope)
    5. 实现event.stopPropagation()
    6. 实现preventDefault //?这有意义吗?
    7. this.$broadcast('$destroy'); //通知directives其所在scope已被移除

表达式与过滤器[编辑]

  1. expr parser支持CSP(从编译模式切换到解释模式?)
  2. 常量表达式
    1. “a+b" 编译为 function(scope){return scope.a+scope.b}
    2. Lexer --> Parser { AST Builder --> Compiler }
    3. AST Node: {type: AST.Literal, value: 42} //直接算出值
    4. String类型
      1. escape:\r \n
      2. Unicode escape:\uXXXX
        1. return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
    5. peek / consume
    6. primary函数?parse value类型的子表达式?
    7. var key = property.key.type === AST.Identifier ? property.key.name : this.escape(property.key.value); //JSON key可能是标识符或字符串
    8. 这里compiler不需要考虑生成临时变量(特别是嵌套作用域下的shadowing)的问题?watch表达式直接对着翻译?但是后面还有一个高级的|管道过滤操作符呢
      1. nextId()
  3. Lookup与函数调用
    1. this(=当前的scope)
    2. var fn = parse('aKey.secondKey.thirdKey.fourthKey'); expect(fn({aKey: {}})).toBeUndefined();
      1. 这里有个错误,这里应该会抛TypeError异常,而不是返回undefined
      2. AST.MemberExpression:fourthKey属性名称在AST树的上一级?嗯,符合“先计算的在AST底层”的原则
    3. locals
    4. Computed Attribute Lookup(a["b"])——这与a.b有什么区别??
    5. 方法*(a.m()也不代表m一定是个方法吧?m也有可能是个函数)
      1. call context(bind to this)
    6. 赋值*
    7. 安全的成员访问
      1. aFunction.constructor("return window;")()
      2. __proto__ //非标准已过世?
      3. __defineGetter__, __lookupGetter__, __defineSetter__, and __lookupSetter__ //非标准
      4. ensureSafeMemberName:屏蔽对这些不安全成员属性的访问,虽然是解决了问题,但总感觉不够灵活?与其抛出异常,不如来个静悄悄的失败?
    8. 确保安全的对象(不要把window关联到Scope):obj.window === obj
      1. DOM对象呢?
      2. 检查window不在赋值语句的右边,或函数的实参中
      3. Function(obj.constructor === obj)、Object
      4. 对函数对象,不允许call/apply(this rebind to other obj)
    9. ?怎么把这些ensureSafeXxx函数传递进runtime?
  4. 操作符
    1. Multiplicative Operators
      1. 优先级怎么处理?
    2. Additive Operators
      1. 这里的parser代码实现是严格递归下降的,感觉无法处理“a+b+c-d”这种情况?
    3. 关系比较(最低优先级?还有逻辑&& ||)
    4. 三元运算符:test?t:f
  5. 过滤器
    1. a | f | g 相当于g(f(a))
    2. 附加的过滤器参数(生成一个partial函数?)
    3. filter(实际上是个高阶函数)
      1. arr | filter:isOdd
      2. arr | filter:"!o"
      3. arr | filter:{name: "o"} //允许深度嵌套,模式匹配??
        1. 'arr | filter:{user: {name: "Bob"}}'
        2. 'arr | filter:{$: "o"}' //任意属性位置
      4. 定制的比较函数:arr | filter:{$: "o"}:myComparator //?
  6. 表达式与watches
    1. literal vs constant
      1. ast.constant 属性文法?
    2. 一次表达式:
    3. { {user.firstName}} { {user.lastName}}//final变量,单赋值
      1. one-time binding:{{::user.firstName}} {{::user.lastName}}
    4. 输入跟踪(只有输入变量发生变化时才触发重新计算)
      1. inputsWatchDelegate:watch inputs中的每个变量?
      2. 遍历AST,决定表达式的inputs:即所有非常量的元素
      3. 注意不要分别watch逻辑表达式的左右两边,以防破坏|| &&的短路特性
    5. ?this.state.computing = 'fn';
    6. 有状态的过滤器
      1. ast.toWatch = stateless ? argsToWatch : [ast];
    7. 外部赋值
      1. var exprFn = parse('a.b'); var scope = {a: {b: 42}}; exprFn.assign(scope, 43); //用于实现2-路绑定,有点像是ES6里的解构赋值
        1. 但假如watch编译后的函数返回值无法追踪到scope中的变量引用呢?
      2. fn.assign = function(scope, value, locals) { ... }
        1. AST.NGValueParameter?

模块与依赖注入[编辑]

  1. 模块与注入器
    1. modules是局部变量,所以确保函数只定义一次?f=f||function(){...}
    2. 不允许注册'hasOwnProperty'名称的模块
    3. injector加载模块,通过“invoke queue”
      1. 备注:当整个webapp固化之后,防御性的代码是不是可以去掉了?因为angular库的使用者就是webapp的js代码
    4. module.requires
    5. DI: 期望injector自动找到模块依赖的参数并在构造时传递进来
      1. 显式指出:fn.$inject
      2. 绑定this:invoke:... return fn.apply(self, args);
      3. 给invoke提供额外的locals局部变量绑定
    6. 数组风格的依赖标注
      1. annotate:fn.slice(0, fn.length - 1) 或 fn.$inspect
    7. 依赖标注从形参(这是Angular 1.x最有意思的特性了,我当初看到简直就是大吃一惊啊!)
      1. 原理:(function(a, b) { }).toString() // => "function (a, b) { }" 靠!不过,为了取得形参的名字执行一个toString序列化有点太夸张了 -_-
      2. 去除注释:var STRIP_COMMENTS = /\/\*.*?\*\//g;
        1. 进一步改进:var STRIP_COMMENTS = /(\/\/.*$)|(\/\*.*?\*\/)/mg;
      3. 当JS代码被混淆压缩后,此机制会失效! => 严格模式
    8. 用DI实例化对象
      1. Type.$inject = ['a', 'b']; var instance = injector.instantiate(Type); //关键是取得a,b的实际取值!这里;
      2. var instance = Object.create(UnwrappedType.prototype); //ES 5.1
        1. invoke(Type, instance); //将当前scope绑定到this进行调用
  2. Providers
    1. 任何提供了$get函数的对象,其返回值作为依赖:
      1. 给$get提供DI参数:cache[key] = invoke(provider.$get, provider); //直接调用=>invoke()
    2. 依赖的懒惰实例化(a的依赖b可以在a后面注册)
      1. cache拆成2个:var providerCache = {}; var instanceCache = {};
      2. providerCache[key + 'Provider'] = provider; //dirty trick
    3. “everything in Angular is a singleton”
      1. var instance = instanceCache[name] = invoke(provider.$get);
    4. 检测循环依赖
      1. 初始化DI之前,放置一个marker对象:if (instanceCache[name] === INSTANTIATING) { throw ... }
      2. 显示循环依赖的路径:var path = []; //使用一个name栈...
    5. Provider Constructors
      1. Angular doesn't care.
      2. 真正需要做的是多做一个类型检查:if (_.isFunction(provider)) { provider = instantiate(provider); }
    6. Two Injectors: The Provider Injector and The Instance Injector
      1. ?不能inject an instance to another provider’s constructor
      2. ?类似的,反之也不行:this.$get = function(aProvider) { return aProvider.$get(); }; X
      3. 不能通过injector.get获得provider的引用(recall:provider的存在只是为了调用$get获得其返回value)
    7. 常量:总是push(unshift)到invoke队列到前面
      1. invokeLater:invokeQueue[arrayMethod || 'push']([method, arguments]);
      2. constant: invokeLater('constant', 'unshift'), //常量:push改成unshift
  3. 高级DI特性
    1. Injecting The $injectors(以$injector提供)
      1. 最有用的是动态get方法:var aProvider = $injector.get('aProvider');
    2. 注入$provide *
    3. 配置块(在模块加载时执行任意‘配置函数’)
      1. $routeProvider.when('/someUrl', {
        templateUrl: '/my/view.html',
        controller: 'MyController'
        });
    4. 运行块:injector构造时调用(module loader/injector --> module instance --> run blocks)
      1. module.provider('a', {$get: _.constant(42)});
      2. module.run(function(a) { ... })
      3. => moduleInstance._runBlocks.push(fn);
      4. 调用时机:需要等所有模块都加载完毕后执行(一次),trick:先收集到一个数组里,最后执行
    5. 函数模块
      1. hashKey:从JS对象返回一个string key
        1. expect(hashKey(null)).toEqual('object:null'); //格式:type:valueStr, 不基于value语义,2个引用value相同,但是hash key不同?
        2. value.$$hashKey = _.uniqueId();
      2. HashMap:key可以是任意JS对象(普通js object key只能是string)
    6. Function Modules Redux
      1. var loadedModules = new HashMap(); //modules直接作为key?感觉更像是一个HashSet(用于判断指定module有没有被加载)
    7. 工厂
      1. 可以注入实例依赖:
        1. module.factory('a', function() { return 1; });
        2. module.factory('b', function(a) { return a + 2; });
      2. factory: function(key, factoryFn) { this.provider(key, {$get: factoryFn}); } //工厂就是一个provider
      3. enforceReturnValue(factoryFn):可以return null,但不能是undefined
    8. Values
      1. values are not available to providers or config blocks(只用于instances)
      2. 实现:value: function(key, value) { this.factory(key, _.constant(value)); }
    9. Services(构造器函数)
      1. module.service('aService', function MyService(theValue) {
        this.getValue = function() { return theValue; }; });
    10. Decorators
      1. module.decorator('aValue', function($delegate) { $delegate.decoratedKey = 43; }); //这里aValue是一个工厂,$delegate指代它返回的对象
        1. 多个装饰器:顺序(从内往外依次封装)应用:
      2. var instance = instanceInjector.invoke(original$get, provider);
      3. //对instance进行修改...
      4. instanceInjector.invoke(decoratorFn, null, {$delegate: instance});
    11. 集成Scopes、表达式、过滤器、&注入控制器
      1. ?this.register('filter', require('./filter_filter'));
      2. ?当注册一个my过滤器时,它还以myFilter的名字作为一个正常工厂提供
      3. setupModuleLoader(window); //DI injector在这里配置好?
      4. var ngModule = angular.module('ng', []);
        1. ngModule.provider('$filter', require('./filter')); //ng基础服务
        2. ngModule.provider('$parse', require('./parse'));
        3. ngModule.provider('$rootScope', require('./scope'));
      5. $rootScopeProvider: 配置$rootScope TTL

辅助函数[编辑]

  1. Promises
    1. $q服务,Q库的裁剪版?Promises/A+兼容(jQuery 3.0+支持)
      1. Promises/A+规范是不是先多个then注册successCallback,最后来一个catch注册errorCallback?而ES6 Promise则在一个then里面同时注册2个callback??
    2. 与digest loop深度集成,可以使用$evalAsync,而不是setTimeout,来触发异步回调
    3. ngModule.provider('$q', require('./q'));
    4. 创建Deferreds
      1. Deferred是数据的生产者,而Promise就是消费者
      2. $q:function defer() { return new Deferred(); }
      3. d:this.promise = new Promise();
    5. 解析:
      1. promise.then(promiseSpy);
      2. deferred.resolve('ok'); //当调用resolve时,Promise并不立即触发(只是push到一个异步回调队列里,对于ES6 Promise来说这套机制是原生的)
        1. 注意,Promise也允许先resolve再then注册异步回调
      3. 触发digest:$rootScope.$apply();
    6. 防止多次解析
      1. 实现:维护内部的状态机!
    7. 确保回调被触发
      1. 在then注册异步回调的时候检查一下状态机特殊处理即可
    8. 注册多个异步回调(then链,这里实际上针对的是同一个Promise对象!)
    9. 否决Deferreds及捕获‘否决’
      1. resolve / reject
      2. Promise.prototype.catch = function(onRejected) { return this.then(null, onRejected); }
    10. finally: both then & catch
    11. Promise链
      1. 每个then调用返回一个新的Promise(或句话说,创建了另外一个新的状态机)
      2. value在Promise链中是传递的:one.then(two).catch(three) 这里one、two的异常都会被three捕获
      3. catch的返回值将会当作下一个resolve(!)
    12. 异常处理
      1. 异常不是reject到当前Promise的catch,而是传递到下一个Promise
    13. Promise回调返回Promise的情况
      1. => 将其与下一个Promise连接!
      2. resolve中需要对value特殊检查:if (value && _.isFunction(value.then)) {
        value.then( _.bind(this.resolve, this), _.bind(this.reject, this));
    14. finally的返回值需要被忽略(不在链中进一步传播)
      1. finally中返回一个Promise的情况:需要同步等待此Promise被resolve!**(这里有点不大好懂,需要找时间看第2遍)
    15. 进度通知
      1. defer.promise.then(null, null, progressNotifyCb);
      2. defer.notify('working...'); //哦,每个then都会返回新的Deferred对象及其内部Promise
        1. 但这里调用notify的线索条件来自哪里呢?
      3. 注意,defer.resolve之后,notify不起作用
    16. 立即否决:$q.reject,创建一个状态已经被否决的Promise
    17. 立即解析:$q.when,创建一个状态已经被解析的Promise
    18. Promise集合:$q.all,创建一个新的汇聚Promise,当输入的所有Promise都已经被完成后,解析自己
      1. var promise = $q.all([$q.when(1), $q.when(2), $q.when(3)]);
      2. 注意,$q.all等待的是所有Promise都成功resolve,若有一个被reject,则立即返回
      3. 备注:既然有$q.all,是不是也可以有$q.any/race?
    19. ES6风格的Promises
      1. 没有Deferreds这样的概念;同时,不是注册2个callback分别对应resolve/reject,而是一个callback:function(value, error) {...}
      2. 如何用Q来模仿ES6 Promise API:略
    20. 无$digest集成:$$q
      1. var d = $$q.defer(); //后面一样,这应该是用setTimeout异步调度的了
  2. $http
    1. vs $httpBackend
    2. SinonJS:模仿一个虚假的XMLHttpRequest请求 //注意本书代码的风格是TDD的
      1. xhr = sinon.useFakeXMLHttpRequest(); ==> requests[0].respond(200, {}, 'Hello');
    3. 302(服务器端重定向)被浏览器内部直接处理,不会到达JS代码
    4. 默认请求参数配置
    5. 请求头部
    6. 响应头部
    7. 允许CORS:withCredentials=true
    8. 请求变换:transformRequest:[f1, f2, ...] //data
      1. 设置进默认配置:$http.defaults.transformRequest = ...
    9. 响应变换
      1. transformResponse: function(data, headers) { ... } //根据Content-Type?有必要这么做吗?
    10. JSON序列化与解析
      1. 二进制数据:var bb = new BlobBuilder(); bb.append(...); var data = bb.getBlob('text/plain');
      2. 跳过FormData
      3. if ((contentType && contentType.indexOf('application/json') === 0) || isJsonLike(data)) { return JSON.parse(data); }
        1. isJsonLike: 字符串以{或[开始?
    11. URL参数
      1. url: "...", params: { a:1, ... } //车机webapp代码目前是硬拼字符串
      2. name有多个value的情况:a=1&a=2&a=...
      3. 定制的参数序列化:paramSerializer: serializeParams
        1. $httpParamSerializerJQLike:a: {b: {c: 42}} 被序列化为 a[b][c]=42(方括号被进一步url编码)
    12. Shorthand方法
    13. Interceptors
      1. var interceptors = _.map(interceptorFactories, function(fn) { return fn(); }); //又是工厂模式 -_-
        1. refine: return $injector.invoke(fn);
      2. Interceptors make heavy use of Promises. (靠!)
      3. serverRequest(config):return sendReq(config, reqData).then(transformResponse, transformResponse);
        1. 现在需要$http()之后调用$rootScope.$apply()
      4. Interceptors are objects that have one or more of 4 keys: request, requestError, response, and responseError.
        1. request返回一个修改过的request,也可以返回一个Promise,这意味着它比transform机制强大的多
      5. request请求拦截器核心代码如下://主要是修改config?
        1. var promise = $q.when(config); _.forEach(interceptors, function(interceptor) {
          promise = promise.then(interceptor.request, interceptor.requestError);
        2. });
        3. return promise.then(serverRequest);
        4. _.forEachRight(interceptors, function(interceptor) {
          promise = promise.then(interceptor.response, interceptor.responseError);
    14. Promise扩展
      1. $http.get('http://teropa.info').success/error(...)
    15. 请求超时
      1. timeout之后,request.aborted=true; //xhr.abort();
      2. 在正常的xhr.onload/onerror回调里,clearTimeout(timeoutId);
      3. 注意!angular 1.x实现这里有个缺陷:一旦超时abort之后,无法用方便的方法来执行一个回调通知,必须主动去检查aborted状态
    16. 挂起的请求
      1. $http.pendingRequests: 请求已发送,但响应还未接受到
    17. 集成$http与1.3 $applyAsync
      1. 优化:使得原先的多个$apply发生在一个digest中执行
      2. 启用:$httpProvider.useApplyAsync(true);
      3. 实现:if (useApplyAsync) { $rootScope.$applyAsync(resolvePromise); }

指令[编辑]

  1. DOM编译与基本指令
    1. "DOM编译"?为什么不叫“DOM转换”
    2. directive: invokeLater('$compileProvider', 'directive'), //=> $provide.factory(name + 'Directive', directiveFactory);
    3. $compile
      1. 指令应用:<my-directive> ... </my-directive>
      2. 注意,Angularjs不依赖jQuery实现低级DOM操纵,它有一个自己的实现:jqLite //DOM esoterica is not the focus of this book
    4. 对DOM元素的属性应用指令
    5. 应用指令到class
    6. 应用指令到注释(囧)
    7. 显示指出匹配限制:ECMA
    8. 优先级
    9. 中止编译
      1. terminal:true ==> 如 < div ng-if="condition"> ... </div>,用于对child节点是否继续compile的控制
    10. 跨越多个节点应用指令
      1. 限制:需要multiElement flag,只对属性指令起作用
      2. groupScan:node = node.nextSibling; //开始结束属性所在节点假设在同一层上?但是要考虑相同属性存在嵌套包含的情况
  2. 指令属性
    1. ng-attr- (类似于data-)
    2. 设置属性:attrs.$set(k,v)
      1. var attrs = new Attributes($(node)); ...
    3. 去规范化属性名称
      1. attrs.$set('someAttribute', 43); ==> element.attr('some-attribute')
    4. 持续观察属性
      1. Attributes.prototype.$observe = function(key, fn) {
        this.$$observers = this.$$observers || Object.create(null); //经常看到这种 a = a || b; 的写法(避免a被重复初始化)
      2. 备注:在Angular DI的编码风格下,JS对象的底层内存管理有什么差别吗?
    5. while ((match = /([\d\w\-_]+)(?:\:([^;]+))?;?/.exec(className))) { className = className.substr(match.index + match[0].length); }
    6. Adding Comment Directives As Attributes *
    7. attrs.$addClass/$removeClass/$updateClass
  3. 指令链接与作用域
    1. Linking is the process of combining the compiled DOM tree with scope objects.
    2. var linkFn = $compile(el); //返回一个函数对象的写法其实算很高级了 -_-
      1. linkFn将一个scope对象关联到DOM节点子树:$compileNodes.data('$scope', scope); //调试信息,产品build并不需要?
    3. Directive Link Functions(DLF,每个指令有它自己的link函数?)
      1. wait,这里的指令可以理解为虚拟机里的字节码,前面的DOM编译过程实际上为此指令也生成了一个函数... ?
      2. 而link时,则指令函数进一步看到其DOM子树关联的scope(分别对应view和model??)
        1. compile返回link函数,compileNodes则返回‘组合的link函数’(所谓的link函数指的是将Nodes加入live document的过程吗?)
    4. 普通DLF
      1. compile什么也不做,把工作推迟到link内执行?(真的很绝妙,虽然是源代码级别的转换,但是体现了JIT编译器的思想...)
    5. linking子节点
      1. 最底层的DOM节点上的指令首选链接:childLinkFn = compileNodes(node.childNodes); //就是个递归
    6. Pre- And Post-Linking
      1. child nodes被link之前还是之后:link: { pre: , post: function(scope, element, attrs){...}} //~DOM事件的捕获/冒泡
      2. postlink函数应该以反向优先级顺序调用
      3. 备注:什么样的情况下需要更细致的pre-/post- link的划分呢?
    7. 保持linking节点的稳定性
      1. 即child nodes先用一个var stableNodeList = [];存起来...
    8. 跨越多个nodes的指令linking
      1. groupElementsLinkFnWrapper: 这种情况实际上使得函数的参数不是稳定的一种类型,而可能变成是t或者[t]两种类型
    9. linking与作用域继承
      1. 更common的情况是指令创建它们自己的scope...(a inherited scope?这似乎指的是直接引用$parent的scope?)
        1. => scope = scope.$new(); //创建一个新的scope,但从$parent继承
      2. element将得到一个ng-scope class
    10. 隔离的scopes(不继承的)
      1. 使得指令更模块化(但这意味着外部依赖都需要通过函数传参的方式传递?)
      2. 隔离scope上的scope不会传递给同一/child element上的其他指令
      3. App开发者常见的错误:Multiple directives asking for new/inherited scope(有意思!)
      4. ng-isolate-scope class及jQuery .$isolateScope
    11. 隔离的属性绑定
      1. ‘@’
        1. 可以指定不同的名字(映射):scope: { aScopeAttr: '@anAttr' }, ...
    12. 双向数据绑定(React好像是单向传递?)
      1. ‘=’
      2. 子scope的属性改变自动更新到parent scope,在link后起作用
        1. case '=': var parentGet = $parse(attrs[attrName]); isolateScope[scopeName] = parentGet(scope);
          scope.$watch(parentGet, function(newValue) { isolateScope[scopeName] = newValue; })
      3. var el = $('< div my-directive my-attr="parentAttr"></div>'); //对myAttr的赋值将更新到parent scope的parentAttr属性上去
      4. 一个digest中父子属性同时改变时,父修改优先
        1. 子scope注册自己的watch函数之前,先记下父属性的值,然后子watch实际调用时检查是不是发生过改变(但是无法处理ABA问题?)
        2. 备注:数据双向绑定,我还以为是指MV* view<-->model之间的双向绑定呢
      5. =* 处理my-attr="parentFn()"收到一个集合对象的情况?$watchCollection
      6. =?如果DOM属性不存在,则不创建watcher
    13. 表达式绑定
      1. &
      2. 绑定行为,而不是数据(?)如ngClick
      3. expression在parent scope的context中被调用,而不是当前的isolate scope,原因很简单:表达式中的函数是由当前的指令使用者定义的
      4. case '&': var parentExpr = $parse(attrs[attrName]);
        isolateScope[scopeName] = function() { return parentExpr(scope); };
      5. 允许传参
        1. 例:< div my-expr="parentFunction(a, b)"></div>
        2. 使用命名参数:scope.myExpr({a: 1, b: 2}); //Angular中的参数使用$前缀,如$event
        3. 实现:引入locals参数(<-- recall back!)
  4. 控制器
    1. $controller服务及其provider
    2. var $controller = injector.get('$controller'); //加载ng的内建控制器模块
    3. var controller = $controller(MyController, {aDep: 42}); //初始化特定控制器的实例(function MyController(aDep) {...})
    4. 注册
      1. $controllerProvider上的register方法并不那么常见,通常使用module上的:
        module.controller('MyController', function MyController() { });
    5. 全局的控制器查找(不推荐,略)
    6. 指令控制器
      1. 指令增加一个controller属性,引用控制器的构造函数
        1. if (controllerName === '@') controllerName = attrs[directive.name];
        2. $controller(controllerName);
      2. locals
        1. function MyController($element, $scope, $attrs) {...} //注意!参数是DI注入的,所以其声明顺序无关紧要
      3. 你可以在控制器函数里做任何指令的link函数可以做到的事情
        1. 优点:你不需要实例化指令,就可以对控制器进行单元测试...
    7. 关联到scope *
      1. 在指令上指定一个controllerAs属性(app开发者用不到,compiler内部使用)
    8. Controllers on Isolate Scope Directives
      1. 当指令has isolate scope时,$scope指的是当前isolate scope,而不是surrounding scope(parent)
      2. bindToController
        1. bindings将从$scope转移到controller上
          ... => This means that we need to have the controller object before we actually call the controller constructor. (JS里是可能的)
        2. $controller的第3个参数:later,将使得创建一个“半构造的”控制器
          1. 当调用时,调用真正的控制器构造函数
          2. 有一个instance属性指向控制器对象
        3. ... instance = Object.create(ctrlConstructor.prototype);
      3. 嗯... 同名但使用时类型会改变的变量,可以理解为2个类型变量的一个union组合变量
    9. Requiring Controllers
      1. 当directive.require指定对另一个指令的依赖时,将对应到link函数的第4个参数,并可从中得到依赖指令的controller(其实,对JS来说,不存在私有变量)
    10. Requiring Multiple Controllers:略
    11. Self-Requiring Directives:当一个指令定义了自己的controller,而不是依赖于其他指令的
    12. Requiring Controllers in Multi-Element Directives
    13. Requiring Controllers from Parent Elements
      1. require: '^myDirective'
      2. ^^ 直接从parent寻找,而不是先从siblings开始
    14. 可选的Requiring:找不到controller不报错
      1. require: '?noSuchDirective'
    15. ngController指令
      1. < div ng-controller="TodoController as todoCtrl"> //注意,这里使用正则表达式来提取嵌入的变量名
    16. 从scope中查找控制器的constructor:locals => global(window)
  5. 指令模板
    1. replace: ture //此做法已废弃
    2. template: '< div my-other-directive></div>' //使得可以组件化的风格编写webapp
    3. 模板函数 //实际上,之前的模板字符串就被编译为一个函数
      1. 模板内的指令将接受到一个isolate scope **
    4. 异步模板:templateUrl
      1. 如何暂停-重启编译??
      2. 需要记住当前还没有应用的指令(保存状态):applyDirectivesToNode
    5. templateUrl也可以是一个函数:Template URL Functions(函数的返回值就是一个url?)
    6. 一个元素不允许超过1个templateUrl(技术实现的限制??)
    7. var templateDirective = previousCompileContext.templateDirective; //这是什么意思呢??-_-
    8. 链接异步指令(这将构造异步回调链)
      1. trick 1: 返回一个特殊的“delayed node link function”(?这个时候模板内容可能还没有成功下载下来吧)
        1. afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, attrs, previousCompileContext);
        2. afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes);
      2. trick 2:让link在template接受到之前发生
        1. 引入var linkQueue = []; //所以,对于一般的异步回调链而言,需要检查2种情况?
    9. Linking Directives that Were Compiled Earlier
        1. var preLinkFns = previousCompileContext.preLinkFns || [];
        2. var postLinkFns = previousCompileContext.postLinkFns || []; ...
    10. 保留isolate scope指令 *
    11. 保留控制器指令 *
  6. Directive Transclusion(k, 感觉这部分内容是本书最复杂的...)
    1. 依我的理解,Transclusion指的是指令模板在被嵌套包含的情况下,其binding上下文可以自动对接的意思???
    2. ‘Transclusion’也可以被cloned:transclude: ’element’
    3. 基本Transclusion(即没有scope binding的情况)
      1. transclude: true
      2. var $transcludedNodes = $compileNode.clone().contents(); //注意这里的clone调用
      3. link: function(scope, element, attrs, ctrl, transclude) { element.find('[in-template]').append(transclude()); } //
      4. ... At its core, the transclusion function is really a link function. //Yes, of course
    4. Transclusion And Scopes
      1. 绑定的scope是定义时的(文法作用域),而不是使用时的(动态作用域)
      2. boundTranscludeFn = function() { return linkFn.nodeLinkFn.transclude(scope); } //到处是这种引用定义时上下文的闭包函数...
      3. 修改scope.$new(),指定parentE
        We should create a special transclusion scope that prototypally inherits from the surrounding scope, but whose $parent is set to the Scope of the transclusion directive.
        !the scope-bound transclusion function(靠~ 这个地方真的有点复杂,它涉及到闭包下的对象生命周期控制问题...)
      4. :boundTranscludeFn = function(containingScope) { var transcludedScope = scope.$new(false, containingScope); ...
    5. Transclusion from Descendant Nodes
      1.  ???
    6. Transclusion in Controllers
    7. The Clone Attach Function
      1. link时先clone,并可修改;与Transclusion不同(?)
    8. Transclusion with Template URLs
    9. Transclusion with Multi-Element Directives
    10. ngTransclude指令
      1. 提供一个内容替换掉stub位置(实现非常简单:...)
    11. Full Element Transclusion
      1. 元素本身也包含在内,而不仅仅是其children。~ ngIf, ngRepeat
      2. $compileNode.replaceWith($(document.createComment(' ' + directive.name + ': ' +attrs[directive.name] + ' ')));
      3. 每个指令的compile函数应该只调用一次:_.forEach => _.times($compileNodes.length, function(i) { ... }
    12. Requiring Controllers from Transcluded Directives
      1. $linkNodes.data('$' + name + 'Controller', controller.instance); //部分构造
  7. 插值: { { expr }}
    1. $interpolate服务(app开发人员很少使用,直接集成在$compile里面)
    2. 字符串插值
      1. var interpolateFn = $interpolate('{ {a}} and { {b}}'); interpolateFn({a: 1, b: 2}); // => '1 and 2'
      2. parts.push(text / expFn ); //expFn = $parse(exp);
    3. Value Stringification
      1. 转义:'\\{\\{expr\\}\\}' //注意这里\还有JS字符串转义
    4. 文本节点插值
      1. addTextInterpolateDirective(directives, node.nodeValue); //直接从nodeValue构造插值函数
        1. link:scope.$watch(interpolateFn, function(newValue) { element[0].nodeValue = newValue; }); //就是一个watch
      2. element.parent().addClass('ng-binding'); //用于辅助Batarang工具
        1. also added:$binding数据属性
    5. 属性插值
      1. addAttrInterpolateDirective:基本情况同Text Node的
      2. 与元素上其他指令交互:
        1. 其他指令可能通过Attributes.$observe在观察元素上的属性变化,当属性由于插值变化时,需要触发这些observers:
          不适用jQuery的attr(),改为attrs.$set(name, newValue);
        2. 指令希望收到的是插值完成后的属性值:
          增加一个marker标记:$rootScope.$evalAsync:if(!self.$$observers[key].$$inter)
          触发:link:attrs.$$observers[name].$$inter = true; ...
        3. 当前插值发生在first digest after linking, 但实际上应该是before any digests
          优先级设为100(高)+ 挂载到pre-link时机
        4. isolate scope指令的问题
          case '@': ... destination[scopeName] = $interpolate(attrs[attrName])(scope); ...
        5. 指令应用时可能删除元素属性(或修改其值),此种情况下需要移除插值调用
      3. 需要防止用户在事件处理器里进行插值,如onclick:<button onclick="{ {myFunction()}}">
      4. Optimizing Interpolation Watches With A Watch Delegate
        1. 当给一个watch函数关联一个$$watchDelegate函数属性时,将使用它来检测变化,而不是基于watch函数的返回值
          function(scope, listener) { return scope.$watchGroup(expressionFns, function() { listener(compute(scope)); }); }
      5. Making Interpolation Symbols Configurable(默认是2个大括号对)
        1. $interpolateProvider.startSymbol('FOO').endSymbol('OOF');
        2. 需要动态构造RegExp:new RegExp(startSymbol.replace(/./g, escapeChar), 'g');
  8. 自启
    1. 实现ngClick
      1. Angularjs框架有点像是一个编译器+解释器框架,而编写定制指令则有点像一个编译器后端优化Pass...
      2. var button = $('<button ng-click="doSomething()"></button>');
      3. link: function(scope, element) { element.on('click', function(evt) { scope.$apply(attrs.ngClick); }); }
        1. 注入$event:scope.$eval(attrs.ngClick, {$event: evt});
      4. 注册:ngModule.directive('ngClick', require('./directives/ng_click'));
    2. Bootstrapping Angular Applications Manually
      1. 手工自启
        1. injector.invoke(['$compile', '$rootScope', function($compile,$rootScope) { $compile($element)($rootScope); }]); //DOM不仅仅被compile,而且被link到root scope
        2. 运行初始digest:=> $rootScope.$apply(function() { $compile($element)($rootScope); });
        3. 严格的DI?window.angular.bootstrap(element, ['myModule'], {strictDi: true});
      2. 自动(via ng-app属性)
        1. $(document).ready(function() { ... });
        2. window.angular.bootstrap(foundAppElement, foundModule ? [foundModule] : []);
      3. Building The Production Bundle
        1. ./node_modules/browserify/bin/cmd.js src/bootstrap.js > myangular.js //递归扫描所有的require,打包到一个js文件里面去
        2. npm install --save-dev uglifyjs
        3. package.json: "build:minified": "browserify src/bootstrap.js | uglifyjs -mc > myangular.min.js"
      4. Running An Example App
        1. app.js: angular.module('myExampleApp', []); //?
阅读全文
0 0