Skip to content

Introduction ​

This is a book with modern front-end technology ideas and theories as its main content.Front-end technology develops rapidly and involves many technical points. We often need to read many books to understand the knowledge system of front-end technology. 《现代前端技术解析》 It has a good summary and sorting out of the front-end knowledge system, covering most of the knowledge content of modern front-end technology. It serves as an enlightenment and can help readers quickly grasp the entire context of front-end technology, cultivate more complete systematic thinking, master more flexible front-end code architecture methods, and enable readers to acquire the thinking and abilities necessary to become senior front-end engineers or architects.

Chapter 1 ​

The evolution of front-end and back-end development models

Static Yellow Pages ==》 Server assembles dynamic web page data ==》 Back-end-based MVC ==》 Separation of front-end and back-end ==》 Pure front-end MV* mainly, middle layer straight out ==》 Front-end Virtual DOM, MNV*, front-end and back-end isomorphism

javascript
// 解释
// MV* 分为:MVC、MVP、MVVM
// 1. MVC(Model-View-Controller)
// 2. MVP (Model-View-Presenter)
// 3. MVVM

1.1、MVC ​

Summary:

  1. Use the controller to call different Models based on front-end conditions to render different data content to the View.
  2. The Controller only distributes modification operation instructions, and the rendering of data is generally completed in the View layer.

MVC can be considered a development and design pattern. Its basic idea is to divide the content of DOM interaction into three parts: data model, view and event control function, and manage them uniformly.Model is used to store requested data results and data objects, View is used to update and modify the page DOM, and Controller is used to call different Models to render different data content to View based on front-end routing conditions (such as different HASH routes).The implementation of common page routing is also very simple. The code is as follows. The main idea is to match the URL address content to the corresponding string and then perform the corresponding operation.

javascript
const router = {
    get (match, fn) {
        let url = location.href,
            routeReg = new RegExp(match, 'g');
        if (routeReg.test(rul)) {
           fn(); 
        }
        return this;
    }
}

router.get('#index', function(){
    _loadIndex(); // 注册hash含有#index 的路由执行对应的操作
}).get('#detail', function(){
    _loadDetail(); // 注册hash含有#detail的路由执行对应的操作
})

In addition, we can also use HTML5's pushState to implement routing. history.pushState(state,title,url) The method can change the URL of the current page without jumping, and associate different state data with the corresponding URL.If the content displayed on the page is automatically completed based on different data states, it is useful to load different components based on the content of the state.

javascript
history.pushState({page:'A'}, 'page A', 'a.html');
console.log(history.state); // {page: 'A'}对象

history.pushState({page: 'B'}, 'page B', 'b.html');
console.log(history.state); // {page: 'B'}

history.pushState({page: 'C'}, 'page C', 'c.html')
console.log(history.state); // {page: 'C'}

Visit different URL addresses here a.html、b.html、c.html , the page will not jump to refresh, but will change the current history.state Content we use hisotry.state It is very convenient to implement SPA by dynamically changing the content of the page DOM based on changes in data.

After using routing, if the page content loaded by each route or user operation in SPA is regarded as a component, then the previous approach was that each component independently completes its own data request operation, rendering and data binding. Once there are too many components, each component handles it by itself, which will cause logical confusion.In MVC, all component data requests, rendering, and page logic operations are performed separately. Model、View、Controller to register the call.In layman's terms, it is like a component handing over its control to a unified control object to call.

// 放图

As shown in the figure above, after the front-end application page is loaded, the page will perform different responses according to different user operations.For example, when the user operates or the URL hash changes, the corresponding ControllerA method will be called. The ControllerA method will request to obtain the corresponding data ModelA. ModelA is likely to be an interface data returned by the front-end AJAX request. ModelA will then be passed to the view template ViewA and the final content will be rendered to the browser. This completes the user's interaction and the process for the user's next operation is the same.At the same time, if the user's operation requires modification of the DOM structure, it will be passed to the Controller, and the data will be obtained through the Controller and the update of the View will be controlled.The implementation operation code for one of the components A is as follows.

html
<div id ="A" onclick="Controller.A.event.change"></div>
javascript
let Controller = {}, Model = {}, View = {};

View['A'] = function(data){
    let tpl = '<input id="input" type="text" value="{{text}}"><span id="showText">{{text}}</span>';
    
    //调用模板渲染数据获取HTML片段    
    let html = render(tpl, data);
    
    document.getElementById('A').innerHTML = html;
};

Model['A'] = {
    text: 'ViewA 渲染完成'
}

Controller['A'] = function(){
    View['A'](model['A']);
    
    // 用户操作一般通过改变Hash完成,并触发Controller来改变Model和View
    $('window').on('hashChange', function(){
        model['A'].text = location.hash;
        View['A'](model['A']);
    })
    
    // 点击事件可以直接触发Controller改变Model并重新渲染view
    self.event['change'] = function(){
        model['A'].text = '新的ViewA渲染完成';
        View['A'](model['A']);
    }
    
}

In order to facilitate everyone's understanding, the most direct implementation method is adopted here, and there is no A、B、C The component is managed separately, but its calling content is divided into Controller、Model、View They are put together for unified management, and calls between them are made directly.Mature MVC frameworks are generally implemented through event listening or observer patterns. This is just to let readers better understand the principles of MVC.Of course, if there are too many such components, it will be confusing.Therefore, by improving Song Shi to divide the page into different small modules for processing, and taking into account code reusability, these three components can be defined using inheritance. Taking the A component as an example, the component definition code of the mainstream MVC framework we generally see is as follows.

html
<div id="A" onclick="A.event.change"></div>
javascript
//可能有一个公用的Component基类
let component = new Component();

let A = component.extend({
    $el: document.getElementById('A'),
    model: {
        text: 'viewA 渲染完成'
    },
    View (data) {
        let tpl = '<input id="input" type="text" value="{{text}}"><span id="showText">{{text}}</span>';
        
        // 调用模板渲染数据获取HTML片段
        let html = render(tpl, data);
        this.$el.innerHTML = html;
    },
    controller () {
        let self = this;
        
        // 调用model数据传入view中渲染内容
        self.view(self.model);
        
        // 用户操作一般通过Hash来出发Controller改变Model和View
        $('window').on('hashchange', function() {
          self.model.text = localtion.hash;
          self.view(self.model);
        })
        
        // 点击事件可以直接触发Model改变并重新渲染View
        self.event['change'] = function() {
          self.model.text = '新的ViewA渲染完成';
          self.view(self.model);
        }
    }
})

Although the writing method here is different, the function implemented is the same as the above code.When the Model or View is complex, we can consider Model、View、Controller Split into different files to import references.This code is the design and implementation of the MVC basic prototype. It should be noted that the DOM modification operations caused by user operations are mainly directly controlled through the Controller, but the Controller only distributes modification operation instructions, and the rendering of data is generally completed in the View layer.

1.2 MVP ​

Summary:

  1. The Presenter in MVP is somewhat similar to the controller, but the difference is that when the user performs a DOM modification operation, the behavior on the View triggers modification notifications to the Presenter. After the Model is modified and other Views are updated, the operation binding between the Presenter and the View is usually two-way. Changes in the View will generally trigger the Presenter's action, and the Presenter's action will also change the View.
  2. View and Model are only used to provide view templates and data without performing any logical processing. Presenter is responsible for logical operations and has clear responsibilities.However, the content of the Presenter layer may become very heavy. In addition, the user's operations on the View will be fed back to the Presenter to modify the Model and update other corresponding parts of the View content.

MVP is the same as MVC. M is Model, V is View, and p represents Presenter. It is somewhat similar to Controller, but the difference is that when the user performs DOM modification operations, he will start through the behavior on View, and then notify the Presenter of the modification to complete the subsequent Model.Modifications and other View updates. In MVC mode, user operations are controlled directly through the Controller. The operation binding between Presenter and View is usually two-way. Changes in View will generally trigger Presenter actions, and Presenter actions will also change View.

html
<div id="A" onclick="A.event.change"></div>
javascript
// 可能有一个公用的Component基类
let component = new Component();

let A = component.extend({
$el:document.getElementById('A'),
model:{
    text: 'ViewA 渲染完成'
},
view: '<input id="input" type="text" value="{{text}}"><span id="showText">{{text}}<span>',
presenter () {
    let self = this;
    
    // 调用模板渲染数据获取HTML片段
    let html = render(self.view,self.model);
    
    // View上的改变将通知Presenter改变Model和其它的View
    $('#input').on('change', function() {
      self.model.text = this.value;
      html = render('{{text}}',self.model);
      $('#showText').html(html);
    });
    
    // Controller上的操作处理和MVC的方式类似
    self.event['change'] = function(){
        self.model.text = '新的ViewA渲染完成';
        html = render('{{text}}', self.model);
        $('#showText').html(html);
    }    
}
});

It can be seen that at this time, View and Model are mainly used to provide view templates and data without any logical processing. This is beneficial, because now we only need to pay attention to the logical operations on the Presenter. Its responsibilities are very clear. The Presenter serves as the communication and interaction between the Model and View to complete all logical operations, but in this way, the crane of the Presenter layer may become very heavy.In addition, the user's operations on the View will be fed back to the Presenter for Model modification, and other corresponding parts of the View content will be updated.

1.3 MVVM pattern ​

MVVM can be considered an automated MVP framework, and uses ViewModel instead of Presenter. That is, the calling of data Model and the rendering of template content do not require our active operation, but ViewModel automatically triggers the completion, and any user operations are also driven by changes in ViewModel.

A great benefit of MVVM design is that the work of the Presenter in MVP is divided into multiple small instruction steps, and then bound to the corresponding elements, driven based on the corresponding data changes, automatically managing interactive operations, and also eliminating the need to view the event list in the Presenter. In addition, generally the ViewModel will automatically perform data binding when initialized, and reuse all similar operations in the page, which greatly saves our own code for content rendering and event binding.The code implemented using the MVVM pattern is as follows:

html
<div id="A" q-on="click: change">
    <input type="text" q-value="text"><span q-html="text"></span>
</div>
javascript
let viewModel = new VM({
    $el: document.getElementById('A'),
    data: {
        text: 'View 渲染完成'
    },
    method: {
        change(){
            this.text = '新的ViewA渲染完成';
        }
    }
})

It is much simpler overall. The rendering and data binding of template data can be controlled through special attributes such as q-html or q-click. These special element tag attributes are what we call Directive. Of course, different MVVM frameworks use different Directive prefixes, but the functions are similar.

html
<form action="#" id="form">
    <label for="text" q-html="label"></label>
    <input type="text" q-value="value" q-model="value" q-mydo="number | getValue">
    <button q-on="click: submit"></button>
</form>
javascript
let viewModel = new VM({
    $el: document.getElementById('form'),
    data: {
        label: '用户名',
        value: '输入初始值',
        number: 0
    },
    method: {
        submit () {
            // doSubmit
        }
    },
    directive: {
        mydo (value) {
            console.log(value);
        }
    },
    filter: {
        getValue () {
            return ++ value;
        }
    }
})

In this code, the ViewModel automatically performs data filling, two-way data binding and event binding when it is initialized.Let's take a look at what is done by initializing ViewModel when executing new VM().

First, javaScript will find the element document.getElementById('form') and start scanning the element node, and facilitate the attribute node attributes of this element and the attributes in all child nodes. Once it is facilitated to an attribute starting with q- in the name, it is considered to be a Directive defined by the MVVM framework, and the corresponding operation will be performed.For example, when the convenience reaches q-html="label", the label value in the default data object data when the ViewModel is initialized is assigned to the innerHTML of this element; when the traversal reaches q-on="click:submit", the click event is bound to this element, and the event function is named submit; You can also customize the q-mydo command to facilitate calling the mydo method in Directive when accessing the node attribute. The input parameter is the return value of the getValue method in data. Here getValue() will automatically add 1 to the input number value and return it. The getValue function is generally called a filter.When the user operates on the View layer, the data of the ViewModel will be automatically changed. Then the ViewModel will detect the data changes, re-traverse the scan node attributes, execute the corresponding Directive, render the HTML view or do event binding.

What you also need to know here is that the tag attributes starting with q- are called instructions, which are agreed by the framework. Different frameworks usually agree differently, for example ng-、v- 、ms- , I believe you have seen or even used it.The process of creating ViewModel and performing view rendering and event binding here is very simple. By expanding according to this idea, we can implement a simple MVVM framework by ourselves.Of course, there are far more things in the complete framework than this, such as rich directive、filter、表达式、ViewModel It has a complete API and even includes some browser compatibility processing.

directive、filter What exactly is it?Let’s take a closer look at the relevant content of the MVVM framework design.

Directive.Translated into instructions, it is simply a custom execution function, for example: q-html、q-calss、q-on、q-show、q-attr etc. Encapsulate some basic reusable operation function APIs of DOM.

Filter.Also called filter, such as bool、upperCase、lowerCase etc. means that the user wants to process the incoming initial data and then hand the processing result to the Directive or the next Filter. For example, when the ViewModel is initialized, a Filter function is passed in that converts the timestamp time into a time format.

Expression design.Such as if...else, etc., which are similar to ordinary page template expressions on the front end. Their function is also to control the page content to be displayed according to specific conditions.

ViewModel design.It is the link where the incoming Model data is stored internally. Usually ViewModel also provides some basic operation APIs to facilitate developers to read or modify the data.

Data change detection, as we mentioned above, MVVM is usually driven by data changes, which requires two-way binding of data.Generally, if you want to change the Model according to changes in the View layer, you need to use some special elements (such as <input>、<select>、<textarea> elements) to trigger the modification of the content of the ViewModel object data in JavaScript. This is easier to understand.On the other hand, the ViewModel is modified. How to trigger the automatic update or new rendering of the View.This mechanism that automatically triggers other operations based on changes in data is what we call data change detection. The main methods to implement data change detection include manual trigger binding, dirty data detection, object hijacking, proxy, etc.Let’s analyze it in detail below

1.3.1 Data change detection example ​

Manually trigger binding ​

Manually triggering instruction binding is a relatively direct implementation method. The main idea is to define the get() method and set() method on the data object (of course other naming methods can also be used), and when calling, manually initiate the get() or set() function to obtain and modify the data. After the data is changed, the re-rendering function of the View layer in the get() and get() functions will be actively triggered.As mentioned earlier, the common main application of driving ViewModel changes based on the view View <input>、<select>、<textarea> In other elements, when the content changes, the operation is triggered to change the data of the ViewModel by listening to the DOM's change, select, keyup and other events.Let’s look at a simple example of two-way data binding

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data-binding-method-set</title>
</head>
<body>
<input q-value="value" type="text" id="input">
<span q-text="value" id="el"></span>
<script >
let elems = [document.getElementById('el'), document.getElementById('input')];
let data = {
    value: 'hello'
};

// 定义Directive
let directive = {
    text: function(text) {
      this.innerHTML = text;
    },
    value: function(value) {
      this.setAttribute('value', value);
    }
};

// 数据绑定监听
if(document.addEventListener){
    elems[1].addEventListener('keyup',function(e) {
        ViewModelSet('value', e.target.value);      
    }, false);
} else {
    elems[1].attachEvent('onkeyup', function(e) {
      ViewModelSet('value', e.target.value);
    }, false);
}

// 事件监听的第三个值,是可选的,默认为false,且只有在部分浏览器中支持,true表明该事件监听器绑定在捕获阶段(和目标阶段),false则表明绑定在冒泡阶段(和目标阶段)。

// 开始扫描节点
scan();

//设置页面2秒后自动改变数据更新视图
setTimeout(function() {
  ViewModelSet('value', 'hello world');
}, 1000)

function scan() {
  // 扫描带指令的节点属性
  for(let elem of elems){
      elem.directive = [];
      for (let attr of elem.attributes) {
          if (attr.nodeName.indexOf('q-') >= 0){
              // 调用属性指令
              directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
              elem.directive.push(attr.nodeName.slice(2));
          }
      }
  }
}

// 设置数据改变后扫描节点
function ViewModelSet(key, value) {
  data[key] = value;
  scan();
}
</script>
</body>
</html>

By loading and executing this page structure through the browser, changes in the ViewModel will automatically change the content of the input box. Similarly, changes in the input content will also drive changes in the ViewModel data.After we change the iewModel data through the ViewModelSet() method, we need to actively call the scan() method to re-scan the nodes on the HTML page and re-render the HTML structure where needed.

1.3.2 Dirty detection mechanism ​

Summary: Different from manually triggering binding, dirty detection only scans elements that may be modified, which improves the efficiency of scanning view rendering after the ViewModel content changes.

Take Angularjs, a typical MVVM framework, as an example. Angularjs updates View layer operations by checking dirty data. However, we do not analyze the mechanism of dirty data detection for a certain framework, because the implementation of mature frameworks considers many comprehensive things and is relatively complicated. What we need is to demonstrate the implementation principle of dirty data detection through concise and clear code.The implementation principle of dirty detection is to detect all elements related to a certain attribute value of the ViewModel object when the attribute value changes, and then compare the data changes. If the data changes, the Directive instruction is called to rescan and render the element.Use this method to implement the above example, the code is as follows

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>data-binding-dirty-check</title>
</head>
<body>
<input qg-event="value" qg-bind="value" type="text" id="input">
<span qg-event="text" qg-bind="value" id="el"></span>
<script >
    let elems = [document.getElementById('el'), document.getElementById('input')];
    let data = {
        value: 'hello'
    };

    // 定义Directive
    let directive = {
        text: function(str) {
            this.innerHTML = str;
        },
        value: function(str) {
            this.setAttribute('value', str);
        }
    };

    // 初始化扫描节点

    scan(elems);
    $digest('value');

    /**
     * 输入框数据绑定监听
     */
    if (document.addEventListener) {
        elems[1].addEventListener('keyup', function(e) {
            data.value = e.target.value;
            $digest(e.target.getAttribute('qg-bind'));
        }, false);
    } else {
        elems[1].attachEvent('onkeyup', function(e) {
            data.value = e.target.value;
            $digest(e.target.getAttribute('qg-bind'));
        }, false);
    }

    setTimeout(function() {
        data.value = 'hello world';
        $digest('value');
    }, 2000);

    function scan(elems) {
        // 扫描带指令的节点属性
        for ( let elem of elems){
            elem.directive = [];
        }
    }

    // 可以理解为数据劫持监听
    function $digest(value) {
        let list = document.querySelectorAll('[qg-bind='+ value + ']');
        digest(list);
    }

    // 脏数据循环检测
    function digest(elems) {
        for (let i = 0, len = elems.length; i < len; i ++){
            let elem = elems[i];
            for (let j = 0 ,len1= elem.attributes.length; j < len1; j++){
                let attr = elem.attributes[j];
                if (attr.nodeName.indexOf('qg-event') >= 0){
                    // 调用属性指令
                    let dataKey = elem.getAttribute('qg-bind') || undefined;
                    // 进行脏数据检测,如果数据改变,则重新执行指令,否则跳过
                    if ( elem.directive[attr.nodeValue] !== data[dataKey]){
                        directive[attr.nodeValue].call(elem,data[dataKey]); // 给DOM赋值
                        elem.directive[attr.nodeValue] == data[dataKey];
                    }
                }
            }
        }
    }
</script>
</body>
</html>

In fact, the method here is similar to the manual binding of scanning nodes. The difference is that dirty detection only scans elements that may be modified, which improves the efficiency of scanning view rendering after the ViewModel content changes.

1.3.3 Front-end data object hijacking (Hijacking) ​

Data hijacking is currently a widely used method.The basic idea is to use Object.defineProperty and Object.defineProperies Monitor the properties get() and set() of the ViewModel data object. When there are data reading and assignment operations, scan the element node and run the Directive instruction specifying the corresponding node. In this way, the ViewModel can use the common equal sign assignment.Specific examples are as follows.

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>data-binding-hijacking</title>
</head>
<body>
<input q-value="value" type="text" id="input">
<span q-text="value" id="el"></span>
<script >
    let elems = [document.getElementById('el'), document.getElementById('input')];
    let data = {
        value: 'hello'
    };

    // 定义Directive
    let directive = {
        text: function(text) {
            this.innerHTML = text;
        },
        value: function(text) {
            this.setAttribute('value', text);
        }
    };

    // 初始化扫描节点

    let bValue;
    scan(elems);

    // 可以理解为数据劫持监听
    defineGetAndSet(data, 'value');

    /**
     * 输入框数据绑定监听
     */
    if (document.addEventListener) {
        elems[1].addEventListener('keyup', function(e) {
            data.value = e.target.value;
        }, false);
    } else {
        elems[1].attachEvent('onkeyup', function(e) {
            data.value = e.target.value;
        }, false);
    }

    setTimeout(function() {
        data.value = 'hello world';
    }, 2000);

    function scan() {
        // 扫描带指令的节点属性
        for ( let elem of elems) {
            elem.directive = [];
            for ( let attr of elem.attributes) {
                if (attr.nodeName.indexOf('q-') >=0 ){
                    // 调用属性指令
                    directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
                    elem.directive.push(attr.nodeName.slice(2));
                }
            }
        }
    }

    // 定义对象属性调协劫持
    function defineGetAndSet(obj,propName){
        Object.defineProperty(obj,propName, {
            get: function () {
                return bValue;
            },
            set: function (newValue) {
                bValue = newValue;
                scan();
            },
            enumerable: true,
            configurable: true
        });
    }
</script>
</body>
</html>

It should be noted that defineProperty only supports standard browsers such as Internet Explorer 8 or above and Chrome, and the Internet Explorer 8 browser needs to use es5-shim to provide support.Firefox browser does not support this method. You need to use __defineGetter__ and __defineSetter__ instead. In order to let everyone understand the principle, defineProperty is directly used to implement it.

1.3.4 ECMAScript 6 Proxy ​

Proxy can be used to define an object based on an existing object and redefine the methods on the object prototype, including the get() method and set() method. At the same time, we can also compare it with defineProperty to learn by ourselves. Let's take a look at how to use Proxy to detect data changes.

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>data-binding-proxy</title>
</head>
<body>
<input q-value="value" type="text" id="input">
<span q-text="value" id="el"></span>
<script >

    'use strict';
    let elems = [document.getElementById('el'), document.getElementById('input')];

    // 定义Directive
    let directive = {
        text: function(text) {
            this.innerHTML = text;
        },
        value: function(text) {
            this.setAttribute('value', text);
        }
    };
    
    // 设置data的访问Proxy
    let data = new Proxy({}, {
        get: function(target, key, receiver) {
          return target.value;
        },
        set: function(target, key, value, receiver) {
          target.value= value;
          scan();
          return target.value;
        }
    });
    
    data['value'] = 'hello';
    scan();
    /**
     * 输入框数据绑定监听
     */
    if (document.addEventListener) {
        elems[1].addEventListener('keyup', function(e) {
            data.value = e.target.value;
        }, false);
    } else {
        elems[1].attachEvent('onkeyup', function(e) {
            data.value = e.target.value;
        }, false);
    }

    setTimeout(function() {
        data.value = 'hello world';
    }, 2000);

    function scan() {
        // 扫描带指令的节点属性
        for ( let elem of elems){
            elem.directive = [];
            for ( let attr of elem.attributes) {
                if (attr.nodeName.indexOf('q-') >= 0 ){
                    // 调用属性指令
                    directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
                    elem.directive.push(attr.nodeName.slice(2));
                }
            }
        }
    }
</script>
</body>
</html>

The basic principles of MVVM two-way data binding are reflected here through simple code. There is a lot of introduction about MVVM data change detection, because it can be considered the most critical part of MVVM design and implementation. If handled well, it will greatly improve the efficiency of the MVVM framework. Otherwise, there will be more repeated element scanning processes that will slow down the code execution speed.I believe everyone has a deeper understanding of the design principles of MVVM and can implement a similar framework by themselves.

To sum up, the front-end framework has evolved from direct DOM operation to MVC design pattern, then to MVP, and then to MVVM framework. The improvement principles of front-end design patterns have been developing in the direction of high efficiency, easy implementation, easy maintenance, and easy expansion. Although various front-end frameworks have matured and begun to iterate to higher versions, they are not over yet. Our current objects are still not deviated from the basic routine of DOM programming. The improvement of the framework has greatly improved the development efficiency, but the efficiency of DOM element operation has not changed.To solve this problem, you must understand the front-end Virtual DOM introduced in everything.

1.4 Virtual DOM interactive mode ​

1.4.1 Virtual DOM design concept ​

The front-end interaction mode of MVVM greatly improves the efficiency of programming. Automatic two-way data binding allows us to transfer the core of page logic implementation to the modification operation of the data layer, instead of directly operating the DOM in the page.But in fact, as can be seen from all the above content, although MVVM has changed the logic of front-end development, the final rendering and changes of the data layer to the View layer on the page are still completed through corresponding instructions to perform DOM operations, and usually a change in ViewModel may trigger changes in multiple instructions on the page to operate the DOM, resulting in a large number of page structure layer DOM operations or renderings.Let’s first look at the following application scenario

html
<ul id="root">
    <li q-repeat="list">
        <span q-text="value"></span>
        <span>固定文本</span>
    </li>
</ul>
javascript
let viewModel = new VM({
    $el: document.getElementById('root'),
    data: {
        list: [{value: 1}, {value: 2}, {value: 3}]
    }
})

When using the MVVM framework, a list of numbers is generated. At this time, if the content to be displayed becomes [{value:0},{value:1},{value:2},{value:3}], the entire list will generally be re-rendered in the MVVM framework, including the parts of the list that do not need to be changed.But in fact, if you directly change the DOM, you only need to insert a new <li> element before the <ul> child element.But in the general MVVM framework, we usually don't do this.There is no doubt that in this case, the View layer update mode of MVVM consumes more unnecessary performance.

So how to improve the ViewModel so that the browser knows that only one element has been added?By comparing the two arrays, we found that there is only one {value:0}, so how to reflect this data to the View layer?We can think of it this way, comparing the new Model data with the old Model data, and then recording the mode and position of the ViewModel, we will know how the View layer should be updated this time, which is much more efficient than re-rendering the entire list directly.

It can actually be understood here that the data in the ViewModel is another data structure identifier that describes the content of the page View, but it needs to be compiled with a specific MVVM description syntax to generate a complete DOM structure.According to the differentially described objects, the changed DOM structure can be created on the spot to complete the modification of the HTML DOM structure instead of directly re-rendering the entire updated table.

Virtual DOM is a JavaScript object that can directly describe a piece of HTML DOM. The browser can create a unique HTML DOM based on its structure and certain rules.Overall, Virtual DOM's interactive mode reduces the number of DOM scans or operations in MVVM or other frameworks, and only minimizes page DOM operations based on JavaScript objects in appropriate places after data changes, avoiding a large number of re-renderings.

1.4.2 Core implementation of Virtual DOM ​

The core operations can be abstracted into three steps: creating a Virtual DOM; comparing two Virtual DOMs to generate a differentiated Virtual DOM; and placing the differentiated Virtual DOM on the page.

1.创建Virtual DOM
    把一段HTML字符串文本解析成一个能够描述它的javascript 对象
    
    <ul id="root">
        <li>
            <span>1</span>
            <span>固定文本</span>
        </li>
    </ul>
    
    let ulElement = {
        tagName: 'ul',
        attribute:[{
            id: 'root'
        }],
        children:[{
            tagName:'li',children:[{
                tagName: 'span',
                nodeText: 1
            },{
                tagName: 'span',
                nodeText: '固定文本'
           }]
        }]
    }
 2.对比Virtual DOM
 3.渲染差异化Virtual DOM

1.5 MNV* mode ​

If Virtul DOM reduces the number of DOM interactions, then MNV* One thing I want to do is to completely abandon the use of DOM, but the operation of interactive data can still be used ViewModel、Virtual DOM Or implement it directly with Model.This mode is currently only applicable to mobile Hybrid applications, because it requires native application control call support, and only this special application scenario meets the conditions.

We know javaScriptYou can use the general protocol to call the native application method, and then encapsulate the Model and Directive into the protocol string jsbridge://DOMRender:success/createView?{"text":"hello","element":"TextView"} according to the standard JSBridge protocol, through Prompt (android), IOS uses iframe to send to the client. What is called here is to parse the creatView method of DOMRender to create a TextView. TextView is a base class of a built-in text control on the native Android system. It is similar to the <p> tag of HTML and can be used to create a text field on the mobile terminal to display a piece of text.At this time, the client will parse this protocol string and call the native application to dynamically create the interface content fragment of the text space TextView (similar to the HTML document fragment documentFragment or an XML fragment). Here, the control content description of the TextView in Android passes the XML specification.

On the whole, it is not difficult to design and implement a Native rendering mechanism idea, but it is more difficult to implement the encapsulation of JavaScript side and Native side.The main task of the MNV* framework is to parse the Model, ViewModel or Virtual DOM to form the JSBridge protocol serial connection. The implementation of the Native side will be more complicated and must handle the parsing of different tag elements. For example, when encountering the <TextView> tag, a TextView space will be created. When the <Layout> tag is encountered, a Layout space will be created. It may also need to handle event binding, etc., that is, JavaScript events will be implemented through Native events.On the whole, it is like using the native method of the mobile terminal to parse the functions that need to be implemented in HTML.

MNV* The basic principle is to combine the JSBridge and DOM transformation methods, so that the front end can quickly build and develop applications with native interfaces, thereby breaking away from the DOM interaction mode.Current MNV *The development model is also at the extreme of promotion and application, and I believe it will become more popular in the future.

The first topic is finally finished here. ​

Let’s get down to business ​

2 Basic browser applications ​

Before introducing the structure of the browser, let's first look at a frequently asked question: From the time we open the browser and enter a URL to the page displaying the web content, what happens to the browser and server?Let’s look directly at a relatively brief but clear description of the process.

- 在接收到用户输入的网址后,浏览器会开启一个线程来处理这个请求,对用户输入的URL地址进行分析判断,如果是HTTP协议就按照HTTP方式来处理。
- 调用浏览器引擎中对应方法,比如WebView中的loadUrl方法,分木质并加载这个URL地址
- 通过DNS解析获取该网站地址对应的IP地址,查询完成后边同浏览器的Cookie、userAgent等信息向网站目的IP发出GET请求。
- 进行HTTP协议会话,浏览器客户端向Web服务器发送报文
- 进入网站后台上的Web服务器处理请求,如Apache、Tomcat、Node.js等服务器
- 进入部署好的后端应用,如PHP、java、JavaScript、Python等后端程序,找到对的请求处理逻辑,这期间可能会读取服务器缓存或查询数据库等。
- 服务器处理请求并回响应报文,此时如果浏览器访问过该页面,缓存上有对应资源,会与服务器最后修改记录对比,一致则返回304,否则返回200和对应的内容
- 浏览器开始下载HTML文档(响应报头状态码为200时)或者从本地缓存读取文件内容(浏览器缓存有效或相应报头状态码为304时)
- 浏览器根据下载接收到的HTML文件解析结构建立DOM(Document Object Model, 文档对象模型)文档树,并根据HTML中的标记请求下载指定的MIME类型文件(如CSS、JavaScript脚本等),同时设置缓存等内容
- 页面开始解析渲染DOM,CSS根据规则解析并结合DOM文档树进行网页内容布局和绘制渲染,JavaScript执行引擎、客户端存储等。下面,我们具体来看一个浏览器的主要结构