Build Your Own Angularjs Reading notes
Directory
[Hide] 1 project configuration 2 scope 3 expression with filter 4 module with Dependency Injection 5 auxiliary function 6 instruction
project configuration [edit]
NPM Package.json Lo-dash, jQuery: No dependencies, if any, on proxies using _.template ("Hello, <%= name%>!") ({name:to}); var _ = require (' Lodash '); _.foreach _.bind//equivalent to ES5 Function.prototype.bind _.isarray _.isobject _.forown//Traversal Object properties Write Tests with Jasmine (describe-it-expect)
scope [Edit]
When you see $, you'll think of PHP and Perl scripting languages, and the programmer is looking at the dollar sign :-D digest cycle and Dirty Check in the code: $watch, $digest, $apply watch expression (internally compiled into a function, This looks kind of interesting. ) scope. $watch (watchfn:scope-> value, listener: (Old, new, scope)-> void)//var WATCHFN = Jasmine.createspy (); The old value is cached on the Watcher object. Here's a little question: Watcher.last do you want to initialize the current value? => initialized to function Initwatchval () {} to indicate the value of an app-level "nonexistent" object, but here the JS variable's type is not the same (perhaps not required.) A simple implementation does not capture every modification of listener to scope. (Unless you overload the setter function of JS object like Vue)--> a digest period listener changed the attribute value, then add iterations, possibly preventing infinite iterations multiple times: TTL $digest Iteration Watcher, Rather than all of the properties of scope (although multiple watcher monitor the same property changes.) Differs from React's virtual Dom diff principle $digest: Iterate through all the $watch $$ prefixes represent a dirty check based on the value of the private variable within angular (rather than simply based on the reference): element diff algorithm for arrays and objects In order to provide the value of old, new, need deep copying ... nan != NaN, special handling $eval $apply: Integrating external code into digest cycle $evalAsync (deferred, but still at current digest cycle) vs $timeout (the latter is settimeout (, 0) ) [] is used as the defer queue: var asynctask = this.$ $asyncQueue. Shift (); Scope Phases (heck, it's a little bit understood from this place: not all in a digest cycle.) => to ensure that the $evalasync is called from outside, a digest execution (settimeout) is scheduled once $apPlyasync (Original motivation: Handling HTTP response, but not triggering complete digest) var self = this; When registering callback, reference the current this to prevent JS's dynamic scope attribute from referencing the wrong this (actually equivalent to a separate function rather than an object method) async Callbacks Batch execution: Wrap all callback objects in a queue with a new lambda execute idioms: self.$ $applyAsyncId = settimeout (function () {... self. $ $applyAsyncId = null;},0)//tracking settimeout whether the browser has been scheduled to execute $ $postDigest (slightly) exception handling delete watch var Destroywatch = scope. $watch (...) d Estroywatch (); Call the splice (index, N) method of the array to delete the element $watchGroup scope inheritance $rootScope $new: Create a new child scope children directly to see the properties of parent, but with the same name occurs JS Prototype chain shadowing (see here, will think of the SICP to implement an interpreter content) digest process is now triggered from the rootscope, recursive execution; $ $children Note: Angular.js itself organizes a scope hierarchy tree scope isolation with "prev/next-sibling, $head/tail-child": Still Hanging on the scope tree, But disconnect to parent's prototype chain (so that the property lookup is only in the current child.) Directive scope linking: Optionally referencing parent's attribute isolation, the property that still wants scope to share root, performs a referential copy operation when $new is created ... Replace parent (abbreviated) The implied constraint here is that when the structure of the scope hierarchy tree is dynamically modified, the corresponding watch and apply mechanism can still properly function, which is equivalent to the requirement of code flexibility $destroy: Disconnect the reference from the $parent An efficient dirty check $watchCollection for collection objects (array, object): each time. When $areEqual, counter++. Note that before oldvalue was deep copy out of theThe kernel algorithm here is for the diff operation of Array or object ... (but not as radical as react's virtual DOM) Array-like (typeof. Length = = "number") arguments DOM nodelist. String is not object trick: how to detect the. Length property that contains the object with length as the key (Array-like) holds the number of formal parameters. Role: Need not remember oldvalue passed to LISTENERFN Event System: $on, $emit, $broadcast pub:emitting: Current and ancestor Scopes notified , down to broadcast (note: not related to DOM events) $on: Registering event listener $emit, $broadcast var listeners = this.$ $listeners [EventName] | | []; var Additionalargs = _.rest (arguments); var Listenerargs = [Event].concat (Additionalargs); Listener.apply (null, Listenerargs); Modeled on the target and Currenttarget properties of the DOM event, Targetscope and CurrentScope (representing the scope of the triggering and handling events, respectively) are implemented Event.stoppropagation () Implement Preventdefault//. Does it make sense. this. $broadcast (' $destroy '); Notifies directives that its scope has been removed
Expressions and filters [edit]
Expr parser supports CSPs (switching from compilation mode to interpretation mode). The constant expression "A+b" is compiled to function (scope) {return scope.a+scope.b} Lexer--> Parser {AST Builder--> Compiler} AST Node: {t Ype:ast. Literal, value:42}//directly calculates the value string type escape:\r \ n Unicode escape:\uxxxx return ' \\u ' + (' 0000 ' + c.charcodeat (0). toString ( ). Slice (-4); Peek/consume primary function. The subexpression of the parse value type. var key = Property.key.type = AST. identifier ? PROPERTY.KEY.NAME&NBSP: This.escape (Property.key.value); The JSON key may be an identifier or a string compiler there is no need to consider the problem of generating temporary variables (especially shadowing under nested scopes). Watch expression directly to the translation. But there's an advanced | pipeline filter operator. NextID () lookup and function call this (= current scope) var fn = parse (' AKey.secondKey.thirdKey.fourthKey '); Expect (FN ({akey: {}})). tobeundefined (); Here's a mistake where you should throw TypeError exceptions instead of returning to the undefined AST. The Memberexpression:fourthkey property name is at the top level of the AST tree. Well, in line with the principle of "calculated first at the AST bottom" locals computed (a["B"))--What difference does it make with A.B. Method * (a.m () does not mean that M must be a method. M can also be a function) called the bind to this assignment * Secure member access Afunction.constructor ("return window;") () __proto__//nonstandard has passed away. __DEFINEGETTER__, __lookupgetter__, __definesetter__, and __lookupsetter__/non-standard ensuresafemembername: Shielding access to these unsafe member properties, although it solves the problem, But the general feeling is not flexible enough. Instead of throwing an exception, it's better to have a quiet failure. Secure objects (do not associate Windows to scope): Obj.window = = obj Dom object. Check that window is not on the right side of the assignment statement, or function (obj.constructor = = obj), object pair function object in the argument of the functions, call/apply (this rebind to other obj) is not allowed. How to pass these ensuresafexxx functions into runtime. operator multiplicative how to handle Operators priority. Additive Operators Here The parser code implementation is strictly recursive descent, feeling unable to handle the "a+b+c-d" situation. Relationship comparison (lowest priority.) and Logic && | | ) ternary operator: Test. T:f Filter A | f | G corresponds to the additional filter parameters of G (f (a)) (generates a partial function.) Filter (is actually a higher order function) arr | Filter:isodd Arr | Filter: "!o" arr | Filter:{name: "O"}//allow depth nesting, pattern matching ... ' Arr | Filter:{user: {name: "Bob"}} ' arr | filter:{$: "O"} '//Custom comparison function for any property position: Arr | filter:{$: "O"}:mycomparator//. Expressions and watches literal vs constant ast.constant property grammars. One expression: {{User.firstname}} {{user.lastname}}//final variable, single assignment one-time Binding:{{::user.firstname}} {{:: User.lastname}}} The input trace (which triggers recalculation only if the input variable is changed) inputswatchdelegate:watch each variable in the inputs. Traversing the AST, determines the inputs of the expression: theThere are very important elements to be careful not to watch the left and right sides of the logical expression separately, to prevent damage | | Short circuit characteristics of &&. this.state.computing = ' fn '; Stateful filter ast.towatch = stateless ? argstowatch : [AST]; External assignment var EXPRFN = Parse (' a.b '); var scope = {A: {b:42}}; Exprfn.assign (scope, 43); Used to implement 2-path binding, a bit like the ES6 in the watch, but if the compiled function return value cannot be traced to the variable reference in scope. Fn.assign = function (scope, value, locals) {...} Ast. Ngvalueparameter.
module and Dependency Injection [edit]
The module and the injector modules are local variables, so make sure that the function is defined only once. f=f| | function () {...} is not allowed to register the ' hasownproperty ' name module injector loading module, through "Invoke queue" Note: When the whole webapp cured, defensive code can be removed. Because the user of the angular library is the WebApp JS code module.requires DI: Expect injector to automatically find the module-dependent parameters and pass it in at construction time explicitly: FN. $inject binding This:invoke: ... Return fn.apply (self, args); Give invoke an extra locals local variable bound array style dependency annotation annotate:fn.slice (0, fn.length-1) or FN. $inspect dependent annotations from formal parameters (this is the most interesting feature of the angular 1.x, I was surprised to see it. Principle: (function (A, b) {}). toString ()//=> "function (A, b) {}" by. However, in order to get the name of the formal parameter to perform a ToString serialization is a little too exaggerated.-_-Removal Note: var strip_comments =/\/\*.*?\*\//g; Further improvements: var strip_comments =/(\/\/.*$) | (\/\*.*?\*\/)/mg; When the JS code is confused and compressed, this mechanism will fail. => Strict mode instantiates object Type with di. $inject = [' A ', ' B ']; var instance = injector.instantiate (Type); The key is to obtain the actual value of the a,b. here; var instance =object.create(Unwrappedtype.prototype); ES 5.1 Invoke (Type, instance); Bind the current scope to this to invoke Providers any object that provides a $get function with the return value as a dependency: supply the di parameter to $get: Cache[key] = Invoke (provider. $get, provider); Direct call =>invoke () lazy instantiation of dependency (A's dependency B can be registered after a) cache split into 2: var providercache = {}; var instancecache = {}; Providercache[key + ' Provider '] = Provider; Dirty trick "everything in angular is a singleton" var instance = Instancecache[name] = Invoke (provider. $get); Before detection loops rely on initializing di, place a marker object: if (instancecache[name] = = instantiating) {throw ...} shows the path of the loop dependency: var path = []; Use a name stack ... Provider constructors Angular doesn ' t care. What really needs to be done is to do one more type check: if (_.isfunction (provider)) {Provider = instantiate (provider);} Two injectors:the Provider Injector and the Instance Injector. Cannot inject an instance to another provider ' s constructor. Similarly, the reverse is not possible: this. $get = function (aprovider) {return aprovider. $get (); X cannot obtain a provider reference through Injector.get (Recall:provider exists only to invoke $get to obtain its return value) constant: Always push (unshift) to the invoke queue to the front Invokelater:invokequEue[arraymethod | | ' Push '] ([method, arguments]); Constant:invokelater (' constant ', ' unshift '),//constant: Push changed to Unshift Advanced di feature injecting the $injectors (provided in $injector) The most useful is the dynamic get method: var aprovider = $injector. Get (' Aprovider '); Inject the $provide * configuration block (execute any ' configuration function ') $routeProvider when the module is loaded. When ('/someurl ', {templateurl: '/my/view.html ', Controller: ' Mycontroller '}); Run BLOCK: Injector constructor Call (module Loader/injector--> module instance--> run blocks) module.provider (' a ', {$get: _. Constant (42)}); Module.run (function (a) {...}) => Moduleinstance._runblocks.push (FN); Call time: Need to wait for all modules to be loaded after the execution (once), trick: first collected into an array, the last execution function module HashKey: Returns a string key expect from the JS object (HashKey (null)). Toequal (' Object:null '); Format: TYPE:VALUESTR, not based on value semantics, 2 reference value is the same, but hash key is different. value.$ $hashKey = _.uniqueid (); Hashmap:key can be any JS object (normal JS object key can only be string) Function Modules redux var loadedmodules = new HashMap (); Modules directly as key. It feels more like a hashset (used to determine if the specified module is loaded) The factory can inject instance dependencies: Module.factory (' A ', function () {return 1;}); Module.factory (' B ', function (a) {return a + 2;}); Factory:function (key, FACTORYFN) {This.provider (key, {$get: Factoryfn});//Factory is a provider Enforcereturnvalue ( FACTORYFN): Can return null, but cannot be undefined values values are not available to providers or config blocks (for instances only) implementation: Valu E:function (key, value) {this.factory (Key, _.constant (value));} Services (constructor function) module.service (' Aservice ', function MyService (thevalue) {this.getvalue = function () {return thevalue;} ; }); Decorators module.decorator (' Avalue ', function ($delegate) {$delegate. Decoratedkey = 43;}); Here Avalue is a factory, $delegate refers to the object returned by multiple adorners: order (encapsulated from inside to outside) application: var instance = Instanceinjector.invoke (Original$get, Provider); Modify the instance ... instanceinjector.invoke (DECORATORFN, NULL, {$delegate: instance}); Integrated scopes, expression, filter, & injection controller. This.register (' Filter ', require ('./filter_filter '));. When registering a my filter, it also provides setupmoduleloader (window) as a normal factory in Myfilter's name; DI Injector is well configured here. var ngmodule = angular.module (' ng ', []); Ngmodule.provider (' $filter ', reQuire ('./filter ')); NG Basic Services Ngmodule.provider (' $parse ', require ('./parse ')); Ngmodule.provider (' $rootScope ', require ('./scope ')); $rootScopeProvider: Configuring $rootscope TTL
Accessibility Functions