Because of the actual development needs at work, I started to come into contact with the angular framework. From the initial comparison, tortured and devastated by various problems and concepts, now I have a certain understanding and feel the need to briefly summarize my understanding. Please forgive me for any shortcomings.
1. Two-way data binding
Various MV** frameworks are currently popular in the industry, and related frameworks are constantly emerging, and angular is one of them (MVVM). In fact, the core issue of the MV** framework is to separate the view layer from the model, reduce the coupling of the code, and achieve the separation of data and performance. MVC, MVP, and MVVM all have the same goals, but the differences between them It lies in how to associate the model layer with the view.
How data flows in the model and view layers has become the key to the problem. Angular implements two-way binding of data through dirty-check. The so-called two-way binding means that changes in the view can be reflected in the model layer, and changes in model data can be reflected in the view. So how does Angular achieve two-way binding? Why does it become dirty-check? Let’s start with an original question on the front end:
html:
<input type="button" value="increase 1" id="J-increase" /> <span id="J-count"></span>
js:
<script> var bindDate = { count: 1, appy: function () { document.querySelector('#J-count').innerHTML = this.count; }, increase: function () { var _this = this; document.querySelector('#J-increase').addEventListener('click', function () { _this.count++; appy(); }, true); }, initialize: function () { // 初始化 this.appy(); // this.increase(); } }; bindDate.initialize(); </script>
In the above example, there are two processes:
The view layer affects the model layer: Clicking the button on the page causes the number of data counts to increase by 1
The model layer reflects the view layer: After the count changes, it is reflected on the view layer through the apply function
This is data processing that was previously implemented using libraries such as jquery and YUI. The problems here are obvious:
Let’s take a look at how angular processes data:
The first step. Add a watcher: when the data changes, which objects need to be detected need to be registered first
// 对angular里面的源码进行了精简 $watch: function(watchExp, listener, objectEquality) { var scope = this, array = scope.$$watchers, watcher = { fn: listener, last: initWatchVal, get: get, exp: watchExp, eq: !!objectEquality }; if (!array) { array = scope.$$watchers = []; } array.unshift(watcher); }
The second step. dirty-check: when the data under a certain scope changes, you need to traverse and detect the registered $$watchers = [...]
$digest: function() { while (length--) { watch = watchers[length]; watch.fn(value, lastValue, scope); } }
This achieves two-way binding of data. Is the above implementation similar to a custom event? You can see that the observer design pattern or (publisher-subscriber) is used.
2. Dependency injection
Students who have used the spring framework know that Ioc and AOP are the two most important concepts in spring, and Ioc can be used to inject dependencies (DI). It is obvious that angular has a very strong back-end color.
Similarly, let’s first look at how to solve object interdependence without using DI:
function Car() { ... } Car.prototype = { run: function () {...} } function Benz() { var cat = new Car(); } Benz.prototype = { ... }
In the above example, class Benz depends on class Car, and this dependency is solved directly through internal New. The disadvantages of this are very obvious. The code coupling becomes higher, which is not conducive to maintenance. The back-end framework has been aware of this problem for a long time. In the early days, spring registered the dependencies between objects in xml files. Later, it solved the DI problem more conveniently through anotation. Students on the COS side can take a look at the back-end code.
The js language itself does not have an annotation mechanism, so how does angular implement it?
1. Simulation annotations
// 注解的模拟 function annotate(fn, strictDi, name) { var $inject; if (!($inject = fn.$inject)) { $inject = []; $inject.push(name); }else if (isArray(fn)) { $inject = fn.slice(0, last); } return $inject; } createInjector.$$annotate = annotate;
2. Creation of injection object
function createInjector(modulesToLoad, strictDi) { //通过singleton模式创建对象 var providerCache = { $provide: { provider: supportObject(provider), factory: supportObject(factory), service: supportObject(service), value: supportObject(value), constant: supportObject(constant), decorator: decorator } }, instanceCache = {}, instanceInjector = (instanceCache.$injector = createInternalInjector(instanceCache, function(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); return instanceInjector; }
3. Get the injection object
function invoke(fn, self, locals, serviceName) { var args = [], $inject = annotate(fn, strictDi, serviceName); for (...) { key = $inject[i]; // 替换成依赖的对象 args.push( locals && locals.hasOwnProperty(key) ? locals[key] : getService(key, serviceName) ); } if (isArray(fn)) { fn = fn[length]; } return fn.apply(self, args); }
At this point, have you seen a lot of back-end framework design ideas? Just simulate one without anotation. No wonder PPK says that angular is "a front-end framework by non-front-enders for non-front-enders"
3.controller communication
In actual development, the application system will be very large. It is impossible for an application app to have only one controller, so there is the possibility of communication between different controllers. There are two main ways to solve this common problem:
1. Event mechanism: Register events on $rootScope. The problem with this is that too many events will be registered on $rootScope, which will cause a series of subsequent problems
//controller1 app.controller('controller1', function ($rootScope) { $rootScope.$on('eventType', function (arg) { ...... }) }) // controller2 app.controller('controller2', function ($rootScope) { $rootScope.$emit('eventType',arg); or $rootScope.$broadcast('eventType',arg); })
2. Make full use of the DI features of angular through service: and use the feature that service is a singleton to act as a bridge between different controllers
// 注册service app.service('Message', function () { return { count: void(0); } }) // controller1,修改service的count值 app.controller('controller1', function ($scope, Message) { $scope.count = 1; Message.count = $scope.count; }); // controller2, 获取service的count值 app.controller('controller2', function ($scope, Message) { $scope.num = Message.count; });
4.service的特點
1. 單例(singleton): angular裡面只有service才可以進行DI諸如,controller、directive這些均不具有這些功能,service字面上就是提供一些基本的服務,跟具體的業務沒有關聯,而controller、directive則與具體業務緊密相關聯,所以需要保證service的唯一性。
2. lazy new:angular首先會產生service的provider,但是並沒有立即產生對應的service,只有到需要這些服務的時候才會進行實例化操作。
3. provider)的分類: provider()、factory、service、value、constant,其中provider是最底層的實現,其他方式都是在其基礎上的語法糖(sugar),要注意的是這些服務最終皆要加入$get方法,因為具體service是透過執行$get方法產生的。
5. directive的實作
directive的編譯(compiler)包括兩個階段: compile、link。簡單來講compile階段主要處理template DOM,此時並不涉及作用域問題,也就是沒有進行資料渲染,例如ngRepeate指令就是透過compile進行template修改的,執行compile後會傳回link函數,覆蓋後面定義的link函數;而link主要是進行資料渲染,分為pre-link和post-link兩個環節,這兩個環節解析的順序是相反,post-link是先解析內部,然後才是外部,這樣對directive的解析就是安全的,因為directive內部還可以包括directive,同時link是對真正DOM的處理,會涉及DOM操作的效能問題。
本文涉及的內容還不是很全民,之後還會有相應補充,希望大家也可以對angular框架進行學習探討。