Interviewer Series 5: Why do you use a front-end framework?


The issue of

  • Interviewer Series (1): How to implement deep clone
  • Interviewer Series (2): Implementation of Event Bus
  • Interviewer Series (3): Implementation of front-end routing
  • The advantages of bidirectional binding based on Proxy data hijacking

preface

Since Backbone, the front-end framework has sprung up like bamboo shoots after a spring rain. We have been used to developing with a variety of frameworks, but what is the meaning of the front-end framework? Why do we choose a front-end framework for development?

To be forewarned: we did not judge the incoming parameters in time to avoid errors, but just implemented the core method.


The article directories

  1. The fundamental meaning of a front-end framework
  2. What does a complete front-end framework look like
  3. How to implement responsive system based on Proxy

1. The fundamental meaning of a front-end framework

1.1 Benefits of a front-end framework

When I first learned front-end frameworks (React was my first framework), I didn’t understand what frameworks could bring, just because everyone was using frameworks, and the most practical use was that almost all enterprises were using frameworks, so it would be out without frameworks.

The more I use it, the more I understand the benefits of the framework:

  1. Componentization: React’s componentization is the most thorough, even to functional level atomic components. A high degree of componentization makes our project easy to maintain and combine and expand.
  2. Natural layering: The code of the JQuery era was mostly spaghetti code with heavy coupling. Modern frameworks, whether MVC, MVP, or MVVM patterns, can help us layering. Code decoupling is easier to read and write.
  3. Ecology: Mainstream front-end frameworks now have their own ecology, from data flow management architectures to UI libraries with mature solutions.

1.2 Fundamental significance of front-end framework

In the last section we talked about the benefits of the front-end framework, but didn’t address the fundamental issues until I read this article (In Chinese).

In short, the fundamental point of a front-end framework is to solve the problem of UI and state synchronization.

Todos.push ({text: ‘new project’}). This is because Vue’s built-in responsive system automatically synchronizes UI and state.

<div id="app-4">
  <ol>
    <li v-for="todo in todos">
      {{ todo.text }}
    </li>
  </ol>
</div>
Copy the code
var app4 = new Vue({
  el: '#app-4'.data: {
    todos: [{text: 'learning JavaScript' },
      { text: 'learning Vue' },
      { text: 'The whole cattle Project'}]}})Copy the code

AppendChild, document.createElement, etc. We need a long string of DOM operations to keep the state in sync with the UI BUG, the disadvantages of manual operation are as follows:

  1. Frequent DOM operations degrade performance.
  2. There are too many intermediate steps, which are buggy and difficult to maintain, and the mental demands are not conducive to development efficiency

Vue’s data hijacking, Angular’s dirty detection, and React’s component-level reRender are all great tools to help us solve the UI/state synchronization problem.

This also explains the reason why Backbone, as the ancestor of front-end framework, fell into disuse later. Backbone just introduced the idea of MVC, but failed to solve the problem of View and Modal synchronization. Compared with the three modern frameworks, directly operating Modal can synchronize UI features. Backbone is still tied to JQuery and operates Dom in View to synchronize UI, which obviously does not meet the requirements of modern front-end framework design.


2. How does Vue ensure UI and state synchronization

UI refers to View in MVVM, state refers to Modal in MVVM, and the thing that makes sure that View and Modal are synchronized is view-Modal.

Vue ensures the synchronization of View and Modal through a reactive system. Because of compatibility with IE,Vue selects Object.defineProperty as the reactive system implementation, but if IE is not considered Object. DefineProperty is not a good choice for users, see The advantages of two-way binding based on Proxy data hijacking for more details.

We will implement a responsive system using Proxy.

I suggest you take a look at the rough implementation based on Object.defineProperty in Interviewer series (4): Advantages of Bidirectional Binding based on Proxy Data Hijacking.

2.1 Publishing and Subscription Center

A responsive system requires a published-subscribe model, because we need a Dep to hold the subscribers, notify them of changes in the Observer and update the view so that the view is in sync with the state.

Read the Interviewer series (2): The Implementation of the Event Bus

/** * [subs description] subscriber, store subscribers, notify subscribers * @type {Map} */
export default class Dep {
  constructor() {
    // We use hash to store subscribers
    this.subs = new Map(a); }// Add subscribers
  addSub(key, sub) {
    // Retrieve the subscriber whose key is key
    const currentSub = this.subs.get(key);
    // If a subscriber with the same key already exists, add it directly
    if (currentSub) {
      currentSub.add(sub);
    } else {
      // Use the Set data structure to store unique values
      this.subs.set(key, new Set([sub])); }}/ / notice
  notify(key) {
  // Trigger subscribers with key
    if (this.subs.get(key)) {
      this.subs.get(key).forEach(sub= >{ sub.update(); }); }}}Copy the code

2.2 Implementation of listener

We implemented a notify method in the subscriber Dep to notify the corresponding subscribers. However, when is notify triggered?

Of course notifications are triggered when the state changes, that is, when Modal changes in MVVM. However, the Dep obviously has no way of knowing if Modal has changed, so we need to create a listener Observer to listen for Modal and perform notifications when Modal changes.

Vue implements listeners based on Object.defineProperty, and we use proxies to implement listeners.

Unlike Object.defineProperty, The Proxy can listen (actually the Proxy) on the whole object, so there is no need to listen through the properties of the object, but if the properties of the object are still an object, then the Proxy can’t listen, so we implement an observify recursive listening.

/** * [Observer description] * @param {[type]} obj [description] Specifies the object to be monitored */
const Observer = obj= > {
  const dep = new Dep();
  return new Proxy(obj, {
    get: function(target, key, receiver) {
      // If the subscriber exists, add the subscription directly
      if (Dep.target) {
        dep.addSub(key, Dep.target);
      }
      return Reflect.get(target, key, receiver);
    },
    set: function(target, key, value, receiver) {
       // If the value of the object has not changed, the operation below is not triggered
      if (Reflect.get(receiver, key) === value) {
        return;
      }
      const res = Reflect.set(target, key, observify(value), receiver);
      // Trigger the notification method of the Dep when the value is triggered to change
      dep.notify(key);
      returnres; }}); };/** * convert object to listener * @param {*} obj to listener */
export default function observify(obj) {
  if(! isObject(obj)) {return obj;
  }

  // Deep monitor
  Object.keys(obj).forEach(key= > {
    obj[key] = observify(obj[key]);
  });

  return Observer(obj);
}
Copy the code

2.3 Realization of subscriber

We have solved two problems so far, one is how to know that Modal has changed (using listener observers to listen to Modal objects) and the other is how to collect subscribers and notify them of changes (collecting subscribers using subscribers and notifying subscribers with notify).

We are currently one subscriber short (Watcher)

/ / subscriber
export default class Watcher {
  constructor(vm, exp, cb) {
    this.vm = vm; // VM is an instance of vue
    this.exp = exp; // The subscribed data
    this.cb = cb; // Trigger the updated callback
    this.value = this.get(); // Get old data
  }
  get() {
    const exp = this.exp;
    let value;
    Dep.target = this;
    if (typeof exp === 'function') {
      value = exp.call(this.vm);
    } else if (typeof exp === 'string') {
      value = this.vm[exp];
    }
    Dep.target = null;
    return value;
  }
  // Put subscribers into a queue to be updated for batch updates
  update() {
    pushQueue(this);
  }
  // Trigger the actual update operation
  run() {
    const val = this.get(); // Get new data
    this.cb.call(this.vm, val, this.value);
    this.value = val; }}Copy the code

2.4 Implementation of batch update

We implemented Watcher in the previous section, but the update method puts the subscriber in a queue to be updated rather than firing directly, for the following reasons:

Therefore, what this queue needs to do is asynchronous and de-heavy, so we use Set as a data structure to store Watcher to de-heavy, and use Promise to simulate asynchronous update.

// Create an asynchronous update queue
let queue = new Set(a)// Simulate nextTick with Promise
function nextTick(cb) {
    Promise.resolve().then(cb)
}

// Perform the refresh queue
function flushQueue(args) {
    queue.forEach(watcher= > {
            watcher.run()
        })
    / / to empty
    queue = new Set()}// Add to queue
export default function pushQueue(watcher) {
    queue.add(watcher)
    // Next loop call
    nextTick(flushQueue)
}

Copy the code

2.5 summary

So let’s go through the flow, how does a responsive system synchronize UI View with Modal?

We need to listen for Modal first, and in this article we use the Proxy to listen for Modal objects, so our Observer can tell when a Modal object is being modified.

How do we notify the View when we know that Modal has changed? To know, a Modal change may trigger multiple UI updates, such as the user name of a user changes, its personal information component, notification component and other components in the user name need to change, For this situation, we can easily think of using the publish and subscribe model to solve the problem. We need a subscriber (Dep) to store the Watcher (Watcher), and when we listen for Modal changes, we only need to notify the relevant subscribers to update.

So where are the subscribers coming from? In fact, each component instance corresponds to a subscriber (because a component instance corresponds to a subscriber, it is possible to use Dep to notify the corresponding component, otherwise it is confusing, notifies the subscriber is equivalent to indirectly notifies the component).

When the subscriber learns the specific change, it will make corresponding update, and the update is reflected in the UI(View), so the synchronization of UI and Modal is completed.

The complete code is already on Github, so far only a responsive system has been implemented, and a complete mini-version of the MVVM framework will be gradually implemented next, so you can watch or star to follow the progress.


3. Responsive systems are not the whole story

While a responsive system is the core concept of Vue, one responsive system is not enough.

Reactive systems learn about changes in data values, but still need to reRender at the component level when the values do not fully map the UI. This is not efficient, so Vue introduced the virtual DOM in version 2.0. Further diff operations on the virtual DOM allow for more fine-grained operations that keep reReander at the lower limit (and not so slow).

In addition, for the convenience of developers, VUE has a lot of built-in instructions, so we also need a VUE template parser.

Next up

Old problem, virtual DOM, virtual DOM implementation and diff algorithm optimization.