Build Your Own Angularjs 讀書筆記(AngularJS牛逼的地方在於它內嵌了一個運算式到Function對象的編譯器。。。當然還有DI架構)__js

來源:互聯網
上載者:User

Build Your Own Angularjs 讀書筆記 目錄  [隱藏]  1 項目配置 2 範圍 3 運算式與過濾器 4 模組與依賴注入 5 輔助函數 6 指令 項目配置[編輯] npm package.json Lo-Dash, jQuery:不依賴,如果有就代理使用 _.template("Hello, <%= name %>!")({name: to}); //var _ = require('lodash'); _.forEach _.bind //等價於ES5 Function.prototype.bind _.isArray _.isObject _.forOwn //遍曆object的屬性 用jasmine寫測試(describe-it-expect) 範圍[編輯] 看到$,就會想到PHP、Perl這些指令碼語言,程式員手裡敲著代碼眼裡看的是貨幣符號 :-D digest cycle與髒檢查:$watch, $digest, $apply watch運算式(內部編譯為一個函數,這個看起來有點意思。) scope.$watch( watchFn: scope -> value, listener: (old, new, scope) -> void) //var watchFn = jasmine.createSpy(); 將old value緩衝到watcher對象上。這邊有一個小問題:watcher.last要不要初始化為當前value呢。 => 初始化為function initWatchVal() {},用於表示app層級“不存在的”對象取值,絕妙~ 但這裡js變數的類型前後是不一樣的(也許不需要這麼嚴格要求。) 簡單實現不能捕獲listener對scope的每次修改。(除非像Vue那樣重載js object的setter函數)--> 一個digest周期內listener改變了屬性值,則補充再迭代,可能多次 防止無限迭代:TTL $digest迭代watcher,而不是scope的所有屬性(不過假如多個watcher監視相同的屬性變化。)跟react的virtual dom diff原則不同 $digest:迭代執行一遍所有的$watch $$首碼代表angular內部的私人變數 基於value的髒檢查(而不是僅僅基於引用):數組和對象的元素diff演算法 為了提供value的old、new,需要deep copying... NaN != NaN,特殊處理 $eval $apply:整合外部代碼到digest cycle $evalAsync(延遲,但仍然在當前digest cycle) vs $timeout(後者是setTimeout(,0)的封裝) []用作defer隊列:var asyncTask = this.$$asyncQueue.shift(); scope phases(見鬼,從這個地方有點不太好懂了:不都是在一個digest cycle裡面嗎。) => 為了確保從外部調用$evalAsync後,會調度一次digest執行(setTimeout) $applyAsync(原始動機:處理HTTP response,但不觸發完整的digest) var self = this; //當註冊callback時,引用當前的this,以防止JS的動態範圍特性引用錯誤的this(實際上相當於一個獨立的函數,而非對象方法了) async callbacks的batch執行:用一個新的lambda將queue裡的所有callback對象包起來一起執行 慣用法: self.$ $applyAsyncId = setTimeout(function(){... self.$ $applyAsyncId = null;},0) //跟蹤setTimeout是否已經被瀏覽器調度執行 $$postDigest(略) 異常處理 刪除watch var destroyWatch = scope.$watch(...) destroyWatch(); //調用數組的splice(index, n)方法刪除元素 $watchGroup 範圍繼承 $rootScope $new:建立新的子scope child直接看得到parent的屬性,但同名的情況下發生JS Prototype鏈上的shadowing(看到這裡,會想起SICP裡實現一個解譯器的內容) digest過程現在從rootScope觸發,遞迴執行; $$children 注意:Angular.js本身用“prev/next-Sibling、$head/tail-Child”來組織一棵scope層次樹 scope隔離:仍然掛在scope樹上,但是斷開到parent的原型鏈(這樣屬性尋找只在當前child中。) directive scope linking:有選擇地引用parent的屬性 隔離情況下仍然想讓scope共用root的屬性,則在$new建立時執行一個引用copy操作... 替換parent(略) 這裡的隱含約束是:當scope層次樹的結構動態修改後,相應的watch、apply機制仍然能夠properly起作用,相當於代碼靈活性的要求 $destroy:從$parent斷開引用 用於集合對象(array、object)的高效髒檢查 $watchCollection:每次。$areEqual的時候counter++ 。 注意,之前oldValue是deep copy出來的,所以這裡的核心演算法就是針對array或object的diff操作而已...(但是不如React的virtual dom那麼激進) array-like(typeof .length === "number") arguments DOM NodeList 。String不是Object Trick:如何檢測包含了length作為key的object(與array-like區分) Function對象的.length屬性儲存了形參的個數。//作用:需不需要記住oldValue傳給listenerFn (Scope Events)事件系統:$on, $emit, $broadcast Pub:emitting:當前的及其祖先scopes得到通知;往下則為廣播(注意:跟DOM事件無關) $on:註冊事件listener $emit, $broadcast var listeners = this.$$listeners[eventName] || []; var additionalArgs = _.rest(arguments); var listenerArgs = [event].concat(additionalArgs); listener.apply(null, listenerArgs); 仿照DOM事件的target與currentTarget屬性,有targetScope與currentScope(分別代表觸發和處理事件的scope) 實現event.stopPropagation() 實現preventDefault //。這有意義嗎。 this.$broadcast('$destroy'); //通知directives其所在scope已被移除 運算式與過濾器[編輯] expr parser支援CSP(從編譯模式切換到解釋模式。) 常量運算式 “a+b" 編譯為 function(scope){return scope.a+scope.b} Lexer --> Parser { AST Builder --> Compiler } AST Node: {type: AST.Literal, value: 42} //直接算出值 String類型 escape:\r \n Unicode escape:\uXXXX return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); peek / consume primary函數。parse value類型的子運算式。 var key = property.key.type === AST.Identifier ? property.key.name : this.escape(property.key.value); //JSON key可能是標識符或字串 這裡compiler不需要考慮產生臨時變數(特別是嵌套範圍下的shadowing)的問題。watch運算式直接對著翻譯。但是後面還有一個進階的|管道過濾操作符呢 nextId() Lookup與函數調用 this(=當前的scope) var fn = parse('aKey.secondKey.thirdKey.fourthKey'); expect(fn({aKey: {}})).toBeUndefined(); 這裡有個錯誤,這裡應該會拋TypeError異常,而不是返回undefined AST.MemberExpression:fourthKey屬性名稱在AST樹的上一級。嗯,符合“先計算的在AST底層”的原則 locals Computed Attribute Lookup(a["b"])——這與a.b有什麼區別。。 方法*(a.m()也不代表m一定是個方法吧。m也有可能是個函數) call context(bind to this) 賦值* 安全的成員訪問 aFunction.constructor("return window;")() __proto__ //非標準已過世。 __defineGetter__, __lookupGetter__, __defineSetter__, and __lookupSetter__ //非標準 ensureSafeMemberName:屏蔽對這些不安全成員屬性的訪問,雖然是解決了問題,但總感覺不夠靈活。與其拋出異常,不如來個靜悄悄的失敗。 確保安全的對象(不要把window關聯到Scope):obj.window === obj DOM對象呢。 檢查window不在指派陳述式的右邊,或函數的實參中 Function(obj.constructor === obj)、Object 對函數對象,不允許call/apply(this rebind to other obj) 。怎麼把這些ensureSafeXxx函數傳遞進runtime。 操作符 Multiplicative Operators 優先順序怎麼處理。 Additive Operators 這裡的parser代碼實現是嚴格遞迴下降的,感覺無法處理“a+b+c-d”這種情況。 關係比較(最低優先順序。還有邏輯&& ||) 三元運算子:test。t:f 過濾器 a | f | g 相當於g(f(a)) 附加的過濾器參數(產生一個partial函數。) filter(實際上是個高階函數) arr | filter:isOdd arr | filter:"!o" arr | filter:{name: "o"} //允許深度嵌套,模式比對。。 'arr | filter:{user: {name: "Bob"}}' 'arr | filter:{$: "o"}' //任意屬性位置 定製的比較函數:arr | filter:{$: "o"}:myComparator //。 運算式與watches literal vs constant ast.constant 屬性文法。 一次運算式: { {user.firstName}} { {user.lastName}}//final變數,單賦值 one-time binding:{{::user.firstName}} {{::user.lastName}} 輸入跟蹤(只有輸入變數發生變化時才觸發重新計算) inputsWatchDelegate:watch inputs中的每個變數。 遍曆AST,決定運算式的inputs:即所有非常量的元素 注意不要分別watch邏輯運算式的左右兩邊,以防破壞|| &&的短路特性 。this.state.computing = 'fn'; 有狀態的過濾器 ast.toWatch = stateless ? argsToWatch : [ast]; 外部賦值 var exprFn = parse('a.b'); var scope = {a: {b: 42}}; exprFn.assign(scope, 43); //用於實現2-路綁定,有點像是ES6裡的解構賦值 但假如watch編譯後的函數傳回值無法追蹤到scope中的變數引用呢。 fn.assign = function(scope, value, locals) { ... } AST.NGValueParameter。 模組與依賴注入[編輯] 模組與注入器 modules是局部變數,所以確保函數只定義一次。f=f||function(){...} 不允許註冊'hasOwnProperty'名稱的模組 injector載入模組,通過“invoke queue” 備忘:當整個webapp固化之後,防禦性的代碼是不是可以去掉了。因為angular庫的使用者就是webapp的js代碼 module.requires DI: 期望injector自動找到模組依賴的參數並在構造時傳遞進來 顯式指出:fn.$inject 綁定this:invoke:... return fn.apply(self, args); 給invoke提供額外的locals局部變數綁定 數組風格的依賴標註 annotate:fn.slice(0, fn.length - 1) 或 fn.$inspect 依賴標註從形參(這是Angular 1.x最有意思的特性了,我當初看到簡直就是大吃一驚啊。) 原理:(function(a, b) { }).toString() // => "function (a, b) { }" 靠。不過,為了取得形參的名字執行一個toString序列化有點太誇張了 -_- 去除注釋:var STRIP_COMMENTS = /\/\*.*?\*\//g; 進一步改進:var STRIP_COMMENTS = /(\/\/.*$)|(\/\*.*?\*\/)/mg; 當JS代碼被混淆壓縮後,此機制會失效。 => strict 模式 用DI執行個體化對象 Type.$inject = ['a', 'b']; var instance = injector.instantiate(Type); //關鍵是取得a,b的實際取值。這裡; var instance = Object.create(UnwrappedType.prototype); //ES 5.1 invoke(Type, instance); //將當前scope綁定到this進行調用 Providers 任何提供了$get函數的對象,其傳回值作為依賴: 給$get提供DI參數:cache[key] = invoke(provider.$get, provider); //直接調用=>invoke() 依賴的懶惰執行個體化(a的依賴b可以在a後面註冊) cache拆成2個:var providerCache = {}; var instanceCache = {}; providerCache[key + 'Provider'] = provider; //dirty trick “everything in Angular is a singleton” var instance = instanceCache[name] = invoke(provider.$get); 檢測循環相依性 初始化DI之前,放置一個marker對象:if (instanceCache[name] === INSTANTIATING) { throw ... } 顯示循環相依性的路徑:var path = []; //使用一個name棧... Provider Constructors Angular doesn't care. 真正需要做的是多做一個類型檢查:if (_.isFunction(provider)) { provider = instantiate(provider); } Two Injectors: The Provider Injector and The Instance Injector 。不能inject an instance to another provider’s constructor 。類似的,反之也不行:this.$get = function(aProvider) { return aProvider.$get(); }; X 不能通過injector.get獲得provider的引用(recall:provider的存在只是為了調用$get獲得其返回value) 常量:總是push(unshift)到invoke隊列到前面 invokeLater:invokeQueue[arrayMethod || 'push']([method, arguments]); constant: invokeLater('constant', 'unshift'), //常量:push改成unshift 進階DI特性 Injecting The $injectors(以$injector提供) 最有用的是動態get方法:var aProvider = $injector.get('aProvider'); 注入$provide * 配置塊(在模組載入時執行任意‘配置函數’) $routeProvider.when('/someUrl', { templateUrl: '/my/view.html', controller: 'MyController' }); 運行塊:injector構造時調用(module loader/injector --> module instance --> run blocks) module.provider('a', {$get: _.constant(42)}); module.run(function(a) { ... }) => moduleInstance._runBlocks.push(fn); 調用時機:需要等所有模組都載入完畢後執行(一次),trick:先收集到一個數組裡,最後執行 函數模組 hashKey:從JS對象返回一個string key expect(hashKey(null)).toEqual('object:null'); //格式:type:valueStr, 不基於value語義,2個引用value相同,但是hash key不同。 value.$$hashKey = _.uniqueId(); HashMap:key可以是任意JS對象(普通js object key只能是string) Function Modules Redux var loadedModules = new HashMap(); //modules直接作為key。感覺更像是一個HashSet(用於判斷指定module有沒有被載入) 工廠 可以注入執行個體依賴: module.factory('a', function() { return 1; }); module.factory('b', function(a) { return a + 2; }); factory: function(key, factoryFn) { this.provider(key, {$get: factoryFn}); } //工廠就是一個provider enforceReturnValue(factoryFn):可以return null,但不能是undefined Values values are not available to providers or config blocks(只用於instances) 實現:value: function(key, value) { this.factory(key, _.constant(value)); } Services(構造器函數) module.service('aService', function MyService(theValue) { this.getValue = function() { return theValue; }; }); Decorators module.decorator('aValue', function($delegate) { $delegate.decoratedKey = 43; }); //這裡aValue是一個工廠,$delegate指代它返回的對象 多個裝飾器:順序(從內往外依次封裝)應用: var instance = instanceInjector.invoke(original$get, provider); //對instance進行修改... instanceInjector.invoke(decoratorFn, null, {$delegate: instance}); 整合Scopes、運算式、過濾器、&注入控制器 。this.register('filter', require('./filter_filter')); 。當註冊一個my過濾器時,它還以myFilter的名字作為一個正常工廠提供 setupModuleLoader(window); //DI injector在這裡配置好。 var ngModule = angular.module('ng', []); ngModule.provider('$filter', require('./filter')); //ng基礎服務 ngModule.provider('$parse', require('./parse')); ngModule.provider('$rootScope', require('./scope')); $rootScopeProvider: 配置$rootScope TTL 輔助函數

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.