Reprinted from CSDN

The principle of

Vue. Js has two core functions: one is a responsive data binding system and the other is a component system. This article only explores how bidirectional binding is implemented. Let’s start with the basics and implement a simple Hello World example with incredibly simplified code.

Accessor properties

The accessor property is a special property in an object that cannot be set directly in the object and must be defined separately through the defineProperty() method.

var obj = {};
Object.defineProperty(obj, "hello", {
  get: function() {
    console.log('Get method called');
  },
  set: function(val) {
    console.log(The 'set method is called with the argument${val}`)
  }
})

obj.hello; // The get method is called
obj.hello = 'abc'; // The set method is called with the argument ABC
Copy the code

The this inside the get and set methods both refer to obj, which means that the get and set functions can manipulate values inside objects. In addition, accessor properties are “overridden” by common properties with the same name, because accessor properties are preferentially accessed and common properties with the same name are ignored.

Two, the implementation of minimalist bidirectional binding

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>
<body>
    <input type="text" id="a">
    <span id="b"></span>
    <script>
        const obj = {};
        Object.defineProperty(obj, "hello", {
            set: function(newVal) {
                document.getElementById('a').value = newVal;
                document.getElementById('b').innerHTML = newVal; }})// Perform data binding

        // Add bubbling events
        document.addEventListener('keyup'.e= > {
            obj.hello = e.target.value;
        })
    </script>
</body>
</html>
Copy the code

In this example, the same text content will be displayed synchronously in SPAN as the input text changes in the text box. Explicitly change the value of obj. Hello in JS or console, and the view will update accordingly. This implements model => View and view => Model bidirectional binding.

This is the basic principle of Vue’s bidirectional binding.

Break down tasks

The examples above are just to illustrate the principle. What we ultimately want to achieve is:

<div id="app">
  <input type="text" v-model="text">
  {{ text }}
</div>
Copy the code
var vm = new Vue({
  el: '#app'.data: {
    text: 'hello world'}})Copy the code

First, the task is divided into several sub-tasks:

  1. Input fields and text nodes bind to data in data
  2. When the content of the input box changes, the data in data changes synchronously. The view => model change.
  3. As the data in data changes, the content of the text node changes synchronously. The model => view changes.

DocumentFragment task 1 requires DOM compilation.

Four, DocumentFragment

A DocumentFragment can be thought of as a node container. It can contain multiple child nodes. When we insert it into the DOM, only its child nodes will insert the target node, so think of it as a container for a group of nodes. Using DocumentFragment to process nodes is much faster and performs better than manipulating DOM directly. When Vue is compiled, it hijacks all the children of the mounted target (really hijacks; nodes in the DOM are automatically removed via appendChild) into the DocumentFragment. After some processing, The DocumentFragment is then returned as a whole and inserted into the mount target.

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <input type="text" id="a">
        <span id="b"></span>
    </div>

    <script>
        const dom = nodeToFragment(document.getElementById('app'));
        console.log(dom)

        function nodeToFragment(node) {
            let flag = document.createDocumentFragment();
            let child;
            while (child = node.firstChild) {
                flag.appendChild(child); // Hijack all child nodes of a node
            }
            return flag;
        }

        document.getElementById('app').appendChild(dom); // Return to app
    </script>
</body>
</html>
Copy the code

5. Data binding initialization

// initialize data binding
function compile(node, vm) {
    const reg = / \ {\ {(. *) \} \} /;
    // The node type is element
    if (node.nodeType === 1) {
        const attr = node.attributes;
        // Parse attributes
        for (let i = 0; i < attr.length; i++) {
            if (attr[i].nodeName == 'v-model') {
                const name = attr[i].nodeValue; // Get the attribute name of the V-Demo binding
                node.value = vm.data[name]; // Assign the value of data to the node
                node.removeAttribute('v-model')}}}// The node type is text
    if (node.nodeType === 3) {
        if (reg.test(node.nodeValue)) {
            let name = RegExp. $1;// Get the matched string
            name = name.trim();
            node.nodeValue = vm.data[name]; // Assign the value of data to the node}}}Copy the code
function nodeToFragment(node, vm) {
    let flag = document.createDocumentFragment();
    let child;
    while (child = node.firstChild) {
        compile(child, vm);
        flag.appendChild(child); // Attach the child node to the document fragment
    }
    return flag;
}

function Vue(options) {
    this.data = options.data;
    const id = options.el;
    const dom = nodeToFragment(document.getElementById(id), this);
    // After compiling, hijack child nodes into document fragments
    document.getElementById('app').appendChild(dom);
}

const vms = new Vue({
    el: 'app'.data: {
        text: 'hello world'}})Copy the code