Conceptual understanding

  • What is a Vue?

Officially, it is a progressive framework for building user interfaces

  • The difference between a framework and a library?

The library itself is a collection of functions that implement a specific function each time a function is called (e.g., jQuery)

Framework, is a complete solution, when using the framework, only need to write code according to its rules

Personally, it is a complete solution for building a user interface, with its own complete process, and its core is to focus only on the view layer, without requiring developers to care about the processing of data.

Core: bidirectional binding and diff algorithm.

Principle of bidirectional binding

A simple example:

<input id="input" type="text" />
<div id="text"></div>
Copy the code
let input = document.getElementById("input");
let text = document.getElementById("text");
let data = { value: "" };
Object.defineProperty(data, "value", {
  set: function(val) {
    text.innerHTML = val;
    input.value = val;
  },
  get: function() {
    returninput.value; }}); input.onkeyup =function(e) {
  data.value = e.target.value;
};
Copy the code
  • Principle realization:

Vue adopts data hijacking combined with publisher subscriber mode. It uses defineProperty to hijack setter of each attribute. When data changes, it publishes messages to subscribers and triggers corresponding listener callback.

To implement bidirectional binding, you need to implement the following:

First, implement a listener observer. Observe recursively iterates through all data objects and adds setters and getters to all properties of the data object. If the data changes, it triggers the setter, gets the latest value and notifies the subscriber.

Second, implement a command parser compile, compile the main do is to parse the template instruction, will replace the data in the template variables, and then apply colours to a drawing page view, and the corresponding node for each command binding update function, at the same time, at the time of processing instruction, will instantiate the subscriber, once the data changes, subscribers receive notification, Update the view.

Because dom nodes will be operated for many times during parsing, in order to improve performance and efficiency, the root node EL will be converted into document fragment for operation. After parsing, the fragment will be added to the original DOM node.

The declarative specification of the directive is marked by a node attribute with a specific prefix.

Dep is an array that collects subscribers. When the data changes, it will trigger its notify function. In notify, it will call the update method of subscribers. This is to implement a simple subscriber.

The subscriber, as a bridge between the Observer and compile, mainly does three things: first, it adds itself to the subscriber when it instantiates itself; second, it must have an update function; third, it can call its own update method when it receives deP notification of data changes. And triggers the callback bound in compile.

Fourth, instantiate a Vue object. Vue is a data binding entry that integrates observer,compile, and Watcher.

This is my understanding of VUE bidirectional binding.


      
<head>
  <meta charset="utf-8">
  <meta name="viewport">
  <title>66666</title>
  <script src='js/jquery.min.js'></script>
</head>
<body>
<div id="app">
 <input id="input" type="text" v-model="name"/>
 <div id="viewShow">{{name}}</div>
</div>
<script type="text/javascript">


  // Implement an observer, recursive loop that hijacks data
  function observe(data){
  	// Check whether data has child attributes. Only the child attributes of observed objects are supported
  	if(! data ||typeofdata ! = ='object') {return;
  	}
  	for(let key in data){
  		defineReactive(data,key,data[key])
  	}
  }

  // Hijacking data
  function defineReactive(data,key,val){
  	var dep = new Dep();
  	observe(val);
  	Object.defineProperty(data,key,{
  		enumberable:true./ / can be enumerated
  		configurable:false.// cannot define again
  		get:function(){
  			if(Dep.target){
  				// Determine whether there are currently subscribers, if so, join the subscriber
  				dep.addSub(Dep.target)
  			}
  			console.log("you get it")
  			return val;
  		},
  		set:function(newVal){
  			console.log("you are updating it")
  			if(newVal === val){
  				return;
  			}
  			val = newVal;
  			// Notify all subscribers of data updates
  			dep.notify()
  		}
  	})
  }

// Instruction parsing
  function Compile(el,vm){
  	// Get the VM object
  	this.$vm = vm
  	// Get the node
  	this.$el = this.isElementNode(el) ? el : document.querySelector(el);
  	if(this.$el){
  		// Create a virtual DOM
  	this.$fragment = this.node2Fragment(this.$el)
  	/ / initialization
  	this.init();
  	// Add shards to the real DOM
  	this.$el.appendChild(this.$fragment)
  	}
  }

  Compile.prototype = {
  	init:function(){
  		// Parse the virtual DOM
  		this.compileElement(this.$fragment)
  	},
  	node2Fragment:function(el){
  		// Create a virtual node object
  		var fragment = document.createDocumentFragment()
  		var firstChild;
  		// First assign el.frstChild to firstChild
  		// The append method is movable, and firstChild is null after execution
  		// The loop puts nodes in the real DOM one by one
  		// Until el.firstChild = null
  		// Exit the loop and return to the virtual DOM
  		while(firstChild = el.firstChild){
  			fragment.appendChild(firstChild)
  		}
  		return fragment;
  	},
  	compileElement:function(el){
  		// The method to compileElement will pass through all nodes and their children, scan and parse, call the instruction render function for rendering and call the instruction update function for binding.
        var childNodes = el.childNodes,me = this
        // [].slice.call converts arguments to pseudo-arrays, which can be used with various methods of arrays
        for(let node of [].slice.call(childNodes)){
        	var text = node.textContent;// You can get HTML elements, blank lines, and text within elements
        	 var reg = /\{\{\s*\w+\s*\}\}/
        	if(me.isElementNode(node)){
        	    // Element compiles
        		me.compile(node)
        	} else if(me.isTextNode(node) && reg.test(text)){
        		// Text compiled and reG compliant
        		// text.match(reg)[0] = "{{name}}"
        		{{name}} {{name}} {{name}}
        		me.compileText(node,text.match(reg)[0])}if(node.childNodes && node.childNodes.length){
        		// The child element continues to be fetched
        		me.compileElement(node)
        	}
        }
  	},
  	compile:function(node){
  		// Get the attributes of the current element and iterate over them
  		var nodeAttrs = node.attributes,me = this
  		// v-text="name"
  		for(let attr of [].slice.call(nodeAttrs)){
  			var attrName = attr.name; / / such as v - text
  			// Determine whether the current attribute is a directive
  			if(me.isDirective(attrName)){
  				// Attribute value
  				var exp = attr.value; // name
  				// Attribute suffix
  				var dir = attrName.substring(2) // text
  				if(me.isEventDirective(dir)){
  					// Whether the event directive v-on: click
  				} else {
  					if(compileUtil[dir]){
  						if(dir === 'model') {// Is the model directive
  							compileUtil[dir](node,me.$vm,exp,attr)
  						} else {
  							compileUtil[dir](node,me.$vm,exp)
  						}
  					}
  				}
  			}
  		}
  	},
  	compileText(node,matchs){
  		compileUtil.compileTextNode(node,this.$vm,matchs);
  	},
  	isElementNode(node){
  		return node.nodeType == 1;
  	},
  	isTextNode(node){
  		return node.nodeType == 3;
  	},
  	isDirective(attr){
  		return attr.indexOf('v-') = =0;
  	},
  	isEventDirective(dir){
  		return dir.indexOf('on') = =0; }}// Instruction processing
  var compileUtil = {
  	reg: /\{\{\s*(\w+)\s*\}\}/.// Matches key in {{key}}
  	compileTextNode:function(node,vm,matchs){
  		// "{{name}}"
  		const rawTextContent = node.textContent;
  		const key = rawTextContent.match(this.reg)[1] // name in {{name}}
  		
  		// First update
  		this.updateTextNode(vm, node, key,rawTextContent)
  		// Instantiate the subscriber
  		new Watcher(vm,key,(newVal,oldVal)=>{
  			// The callback updates the text
  			this.updateTextNode(vm, node, key, rawTextContent)
  		})
  	},
  	updateTextNode:function(vm,node,key,rawTextContent){
  	   let newTextContent = rawTextContent;
  	   // Get the value of name
  	   const val = this.getModelValue(vm, key);
  	   // Replace the text content
       node.textContent = val;
  	},
  	model:function(node,vm,exp,attr){
  		// model
  		const { value: keys, name } = attr;
  		node.value = this.getModelValue(vm,keys);
  		node.removeAttribute(name)
  		/ / input to monitor
  		node.addEventListener('input', (e) => {
  			this.setModelValue(vm, keys, e.target.value);
  		});
  		// Instantiate the subscriber
  		new Watcher(vm, keys, (oldVal, newVal) => {
  			// Update data
  			node.value = newVal;
        });
  	},
  	getModelValue(vm,keys){
  		return vm[keys];
  	},
  	setModelValue(vm,keys,val){
  		vm[keys] = val
  	}
  }

  function Dep(){
  	this.subs = []
  }

  Dep.prototype = {
  	addSub:function(sub){
  		// Join the subscriber
  		this.subs.push(sub)
  	},
  	notify:function(){
  		// Notify subscribers
  		for(let sub of this.subs){ sub.update(); }}}function Watcher(vm,exp,cb){
  	this.cb = cb;
  	this.vm = vm;
  	this.exp = exp;
  	// Add yourself to the DEP when instantiated
  	this.value = this.get();
  }

  Watcher.prototype = {
  	update:function(){
  		this.run();
  	},
  	run:function(){
  		var value = this.vm[this.exp];
  		var oldValue = this.value;
  		if(value ! == oldValue){this.value = value
  			/ / the compile callback
  			this.cb(this.vm,value,oldValue)
  		}
  	},
  	get:function(){
  		// Point target at yourself
  		Dep.target = this;
  		var value = this.vm[this.exp];// Trigger the getter for data
  		Dep.target = null;// Delete when used up
  		returnvalue; }}function MVVM(options){
  	// The argument passed in
  	this.$options = options;
  	var data = this._data = this.$options.data
  	var me = this
  	for(let key of Object.keys(data)){
  		// Data broker
  		me._proxy(key)
  	}
  	// console.log(me.name)
  	observe(data,this)
  	this.$compile = new Compile(options.el || document.body,this)}Var vm = new MVVM({data:{name: 'kindeng'}}); vm._data.name = 'dmq'; That's how you change the data.
  MVVM.prototype = {
  	_proxy:function(key){
  		var me = this;
  		Object.defineProperty(me,key,{
  			configurable:false.enumberable:true.get:function proxyGetter(){
  				return me._data[key];
  			},
  			set:function proxySetter(newVal){
  				me._data[key] = newVal
  			}
  		})
  	}
  }

  new MVVM({
  	el:'#app'.data: {name:'yang'}})</script>
</body>
</html>

Copy the code

My own sketch:

Extended questions

  • Why do you need dependency collection?

If data is not used in the template, but is defined in the data, when the data changes, setter events are triggered to re-render the page, which obviously affects performance. Therefore, dependency collection is required. Data is collected when it is used in the template, and data is not collected when it is not used.

  • How are dependencies collected?

When the template directive is parsed, it instantiates the subscriber watcher, and when the watcher instantiates it fires the getter for defineProType and marks a target, and then in the getter for defineProType, it makes a call, If target exists, the current target is added to the subscriber.

  • Based on VUE data binding, if the data in data is changed 1000 times per second, will every change be displayed on the page?

Don’t. Because dom updates asynchronously.

  • In new vue(), data can be an object directly. Why must data be a function in a Vue component?

Each vue component is instantiated via new vue () and refers to the same object. If the component’s data is directly an object, then the same data of other components will be changed once the component’s data is modified. If data is a function, then the component’s data will have its own scope. Do not interfere with each other.

Implementation principle of V-Model

The V-model is an instruction of vUE that implements bidirectional data binding on input, Textarea, SELECT, radio, checkbox, and child components.

The v-model is implemented in elements:

1. Instruction parsing: When the template is compiled, the instructions in the element are parsed.

2. The V-model will be divided into two pieces of code. The first is to bind the value of the element to the data data such as Message, and at the same time add input event to the element, trigger input event, and dynamically assign the current input value to message.

  • Text and Textarea elements use value attributes and input events;

  • Checkbox and radio use the Checked attribute and the change event;

  • The SELECT field takes value as prop and change as an event.

<input
  v-bind:value="message"
  v-on:input="message=$event.target.value">
Copy the code

The V-Model is implemented in a component: by default, it passes in a prop named value and defines a trigger event.

V – model modifier

.lazy

Data synchronization occurs when the change event is triggered

.number

Automatically converts the input value to a numeric type

The parseFloat() function processes the input value and returns the original value if the input value is something that the parseFloat() function cannot parse, such as a string that begins with a non-number.

.trim

Remove closing whitespace characters.

V – bind analysis

V-bind Types that can be bound:

  • v-bind:key

  • v-bind:title

  • v-bind:class

  • v-bind:style

vue-cli

First we need to know, vue-CLI generated project, for us to configure what functions?

  • ES6 code is converted to ES5 code
  • Turn the CSS SCSS/sass/less/stylus
  • Convert.vue files to JS files
  • Use JPG, PNG, font and other resource files
  • Automatically adds prefixes for CSS browser manufacturers
  • Code hot update
  • Resource preloading
  • Each build cleans up the previously generated code
  • Defining environment variables
  • Distinguish between development environment packaging and production environment packaging