zone.js - Beauty of Violence

Keywords: angular Javascript github Java

From: http://www.cnblogs.com/whitewolf/p/zone-js.html


During the development of ng2, the Angular team brought us a new library, zone. js. zone.js was inspired by Dart language, which describes the context of JavaScript execution and allows persistent transfer between asynchronous tasks, similar to TLS in Java.( thread-local storage: Thread Local storage ) Technology, zone.js is the implementation framework of introducing TLS into JavaScript language.

So what problems can zone.js solve for us? Before answering this question, bloggers would like to review what problems we have encountered in JavaScript development.

Problem introduction

Let's start with a regular synchronous JavaScript code:

var foo = function(){ ... },
    bar = function(){ ... },
    baz = function(){ ... };

foo();
bar();
baz();

There's nothing special about this code, and there's nothing special about its execution sequence. It's entirely within our predictions: foo -> bar -> baz. It's also easy to monitor its performance. We just need to record execution time before and after execution context.

var start, 
    timer = performance ? performance.now.bind(performance) : Date.now.bind(Date);

start = timer();

foo(); 
bar(); 
baz(); 

console.log(Math.floor((timer() - start) * 100) / 100 + 'ms');

But in the world of JavaScript, it's not all that simple. It's well known that JavaScript executes in a single thread. Therefore, in order not to block the user experience of UI interface, many time-consuming operations performed in JavaScript are encapsulated as asynchronous operations, such as setTimeout, XMLHttpRequest, DOM events, etc. Because of the browser's boarding restrictions, asynchronous operations in JavaScript are inherent features that are deeply imprinted in the bone marrow. This is one of the reasons why Dr. Ryan Dahl chose JavaScript to develop Node.js platform. Another blog post on JavaScript single-threaded execution can be consulted by the blogger: A Brief Introduction to JavaScript Single Thread and Browser Event Loop.

So how do we monitor the performance of the following asynchronous code?

var foo = function(){ setTimeout(..., 2000); },
    bar = function(){ $.get(...).success(...); },
    baz = function(){ ... };

foo();
bar();
baz();

In this code, setTimeout and AJAX asynchronous calls are introduced. The timing of AJAX callbacks and setTimeout callbacks is difficult to determine, so introducing performance detection code into this code is not as simple as executing the code in the order above. If we need to force performance testing, we insert relevant hook code into setTimeout and $. get callbacks and record execution time, so that our business code can become very confusing, just like a bunch of "What the fuck!".

Introduction to zone.js

In the beginning of this article, zone. JS provides execution context for JavaScript, which allows persistent transfer between asynchronous tasks. It's time zone. JS came on. zone.js uses Monkey-patched violence to wrap asynchronous tasks in JavaScript so that they will run in the context of zones. Each asynchronous task is treated as a Task in zone. js, and on the basis of Task, zone. JS provides developers with hooks before and after execution. These hook functions include:

  • onZoneCreated: The hook function when a new zone object is generated. zone.fork also generates a new zone inherited from the base class zone, forming a separate zone context.
  • beforeTask: zone Task hook function before execution;
  • After Task: zone Task completes the hook function;
  • onError: The exception hook function when zone runs Task.

And zone.js wraps most of the asynchronous events in JavaScript, including:

  • zone.alert;
  • zone.prompt;
  • zone.requestAnimationFrame,zone.webkitRequestAnimationFrame,zone.mozRequestAnimationFrame;
  • zone.addEventListener;
  • zone.addEventListener,zone.removeEventListener;
  • zone.setTimeout,zone.clearTimeout,zone.setImmediate;
  • zone.setInterval,zone.clearInterval

It also encapsulates promise, geolocation information, websocket and so on. You can find them here. https://github.com/angular/zone.js/tree/master/lib/patch.

Let's start with a simple zone.js example:

var log = function(phase){
    return function(){
        console.log("I am in zone.js " + phase + "!");
    };
};

zone.fork({
    onZoneCreated: log("onZoneCreated"),
    beforeTask: log("beforeTask"),
    afterTask: log("afterTask"),
}).run(function(){
    var methodLog = function(func){
        return function(){
            console.log("I am from " + func + " function!");
        };
    },
    foo = methodLog("foo"),
    bar = methodLog("bar"),
    baz = function(){
        setTimeout(methodLog('baz in setTimeout'), 0);
    };

    foo();
    baz();
    bar();
});

The output to execute this sample code is:

I am in zone.js beforeTask!
I am from foo function!
I am from bar function!
I am in zone.js afterTask!

I am in zone.js onZoneCreated!
I am in zone.js beforeTask!
I am from baz in setTimeout function!
I am in zone.js afterTask!

From the above output, we can see that in zone.js, the run method block is divided into two Tasks, which are the Task of the method body running and the Task of the asynchronous setTimeout. And we can create these Tasks, intercept them before and after execution, and do something meaningful.

In zone.js, the fork method generates a subclass inherited to zone, and the fork function can configure a specific hook method to form a separate zone context. The run method is to start the external interface to execute business code.

zone also supports father-son inheritance, and it also defines a DSL grammar that supports prefixes of $, +, - and so on.

  • $passes the hook function of the parent zone to facilitate control over the execution of the zone hook function.
  • - Represents running this hook function before the hook function of the parent zone.
  • + On the contrary, this hook function is run after the hook function of the parent zone.

For more syntax use, refer to the zone.js github home page documentation https://github.com/angular/zone.js.

Introduce zone.js

With the above basic knowledge of zone. js, we can solve the remaining problems at the beginning of this article. The following code is a sample code from the zone.js project: https://github.com/angular/zone.js/blob/master/example/profiling.html

var profilingZone = (function () {
    var time = 0,
        timer = performance ?
                    performance.now.bind(performance) :
                    Date.now.bind(Date);
    return {
      beforeTask: function () {
        this.start = timer();
      },
      afterTask: function () {
        time += timer() - this.start;
      },
      time: function () {
        return Math.floor(time*100) / 100 + 'ms';
      },
      reset: function () {
        time = 0;
      }
    };
  }());

  zone.fork(profilingZone).run(function(){

     //Business logic code

  });

Here time calculation is started in beforeTask and the current accumulated time spent is calculated in afterTask. So we can always use zone.time() in the logic of business code to get the current time-consuming.

Implementation of zone.js

When you know zone.js, you may feel as amazing as I do. How does it work?

Below is the code snippet for browser.ts in zone.js( https://github.com/angular/zone.js/blob/master/lib/patch/browser.ts):

export function apply() {
  fnPatch.patchSetClearFunction(global, global.Zone, [
    ['setTimeout', 'clearTimeout', false, false],
    ['setInterval', 'clearInterval', true, false],
    ['setImmediate', 'clearImmediate', false, false],
    ['requestAnimationFrame', 'cancelAnimationFrame', false, true],
    ['mozRequestAnimationFrame', 'mozCancelAnimationFrame', false, true],
    ['webkitRequestAnimationFrame', 'webkitCancelAnimationFrame', false, true]
  ]);

  fnPatch.patchFunction(global, [
    'alert',
    'prompt'
  ]);

  eventTargetPatch.apply();

  propertyDescriptorPatch.apply();

  promisePatch.apply();

  mutationObserverPatch.patchClass('MutationObserver');
  mutationObserverPatch.patchClass('WebKitMutationObserver');

  definePropertyPatch.apply();

  registerElementPatch.apply();

  geolocationPatch.apply();

  fileReaderPatch.apply();
}

From here, we can see that zone.js has made special handling of setTimeout, setInterval, setImmediate, events, promise, geolocation in browsers. So how are these treatments handled? Here's the implementation code for fnPatch.patchSetClearFunction from functions.ts in zone.js( https://github.com/angular/zone.js/blob/master/lib/patch/functions.ts ) Code snippet:

export function patchSetClearFunction(window, Zone, fnNames) {
  function patchMacroTaskMethod(setName, clearName, repeating, isRaf) {
    //Browser Native Method Retention
    var setNative = window[setName];
    var clearNative = window[clearName];
    var ids = {};

    if (setNative) {
      var wtfSetEventFn = wtf.createEvent('Zone#' + setName + '(uint32 zone, uint32 id, uint32 delay)');
      var wtfClearEventFn = wtf.createEvent('Zone#' + clearName + '(uint32 zone, uint32 id)');
      var wtfCallbackFn = wtf.createScope('Zone#cb:' + setName + '(uint32 zone, uint32 id, uint32 delay)');

      // Packaging of native browser methods
      window[setName] = function () {
        return global.zone[setName].apply(global.zone, arguments);
      };

      // Packaging of native browser methods
      window[clearName] = function () {
        return global.zone[clearName].apply(global.zone, arguments);
      };


      // Create your own wrapping method and move it from the wind[setName] above to execute here.
      Zone.prototype[setName] = function (fn, delay) {

        var callbackFn = fn;
        if (typeof callbackFn !== 'function') {
          // force the error by calling the method with wrong args
          setNative.apply(window, arguments);
        }
        var zone = this;
        var setId = null;
        // wrap the callback function into the zone.
        arguments[0] = function() {
          var callbackZone = zone.isRootZone() || isRaf ? zone : zone.fork();
          var callbackThis = this;
          var callbackArgs = arguments;
          return wtf.leaveScope(
              wtfCallbackFn(callbackZone.$id, setId, delay),
              callbackZone.run(function() {
                if (!repeating) {
                  delete ids[setId];
                  callbackZone.removeTask(callbackFn);
                }
                return callbackFn.apply(callbackThis, callbackArgs);
              })
          );
        };
        if (repeating) {
          zone.addRepeatingTask(callbackFn);
        } else {
          zone.addTask(callbackFn);
        }
        setId = setNative.apply(window, arguments);
        ids[setId] = callbackFn;
        wtfSetEventFn(zone.$id, setId, delay);
        return setId;
      };
      ......

    }
  }
  fnNames.forEach(function(args) {
    patchMacroTaskMethod.apply(null, args);
  });
};

In the above code, you first save the browser's native methods in setNative for reuse. Then zone.js started its violence, covering window[setName] and window[clearName] and then transferring the call to its own zone[setName], which is how zone.js violently intercepts and transfers native browser objects. It then calls its own addRepeatingTask, addTask, and wtf events before and after Task execution to apply all hook functions on the registry.

I believe that as a reader, you have already understood the implementation mechanism of zone.js. Is it as simple and crude as the author? But it's really powerful. It enables us to track and analyze asynchronous Task s.

zone.js application scenario

Zone.js can implement asynchronous Task tracking, analysis, error recording, development and debugging tracking, which are all application scenarios of zone.js scenarios. You can also https://github.com/angular/zone.js/tree/master/example See more sample code, and Brian in ng-conf 2014 Speech Video on zone.js: https://www.youtube.com/watch?v=3IqtmUscE_U.

Of course, for some specific business analysis zone.js also has its good application scenarios. If you've used Angular 1 development, you might remember a scenario where you used third-party events or ajax and forgot $scope.$apply. In Angular 1, if you change the data Model in a non-Angular context, Angular is unpredictable and therefore does not trigger an update of the interface. So we have to display calls of $scope.$apply or $timeout to trigger the interface updates. The Angular framework has to encapsulate a set of built-in services and instructions, such as ngClick, ngChange, $http,$timeout, etc., in order to learn more about the changing events, which also increases the learning cost of Angular 1.

In order to solve these column problems of Angular 1, the Angular 2 team introduced zone.js, abandoning customization of such services and instructions, instead embracing the browser's native objects and methods. So any browser event can be used in Angular 2, just need bracketed template syntax tags: (eventName), equivalent to on-eventName; you can also directly use the browser's native objects, such as setTimeout, addEventListener, promise, fetch, etc.

Of course, zone.js can also be applied to Angular1 projects. The sample code is as follows( http://jsbin.com/kenilivuvi/edit?html,js,output):

angular.module("com.ngbook.demo", [])
    .controller("DemoController", ['$scope', function($scope){

        zone.fork({
            afterTask: function(){
                var phase = $scope.$root.$$phase;
                if(['$apply', '$digest'].indexOf(phase) === -1) {
                    $scope.$apply();
                 }
            }
        }).run(function(){

            setTimeout(function(){
                $scope.fromZone = "I am from zone with setTimeout!";
            }, 2000);
        });

    }]);

In the sample code, $scope.$apply is attempted after each Task is completed, forcing changes to the Model data to be updated to the UI interface. More places to use zone.js in Angular 1 should be in Directive, while encapsulating the zone creation process as a service (factory method, returning a brand new zone object each time). The same zone encapsulation is also available in Angular 2, which is called ngZone.( https://github.com/angular/angular/blob/master/modules/angular2/src/core/zone/ng_zone.ts).

Posted by geroido on Wed, 27 Mar 2019 22:09:29 -0700