The difference between Hash and History modes
Either way, it is routed by the client, not by the server.
Differences between forms of expression:
The history mode requires server configuration support.
Principle difference:
The difference between history.push() and pushState is that the push method changes its path and sends a request to the server. Instead of sending a request to the server, pushState simply changes the address in the address bar and records the address in the history.
Use of History mode
Node.js server configuration
Nginx server configuration:
Place the packaged front-end projects into the HTML folder of nginx
Nginx start:
To configure history mode in Nginx, you need to modify the configuration file
Add a line of code to location in server in the file:
VueRouter implementation principle
First, review the core code:
VueRouter’s class diagram:
let _Vue = null export default class VueRouter { static install(Vue) { // 1. Judge whether the current plug-in has been installed the if (VueRouter. Install. Installed) {return} VueRouter. The installed = true / / 2. Log the Vue constructor to the global variable _Vue = Vue // 3. Mixin ({// Mixin this points to Vue beforeCreate() {// Mixin this points to Vue beforeCreate() {// Mixin this points to Vue beforeCreate() { $forecreate () {$forecreate (); $forecreate () {$forecreate (); If (this.$options.router) {_Vue. Prototype.$router = this.$options This.$options.router.init()}}})} constructor(options) {this RouteMap // is a key-value pair, where the key is the routing address and the value is the routing component. // Router-view is a component that uses the current routing address to find the routing component in the routeMap and render it in the browser. This.routemap = {} // data is a responsive object with a current property that records the current routing address // By default the routing address is /, This.data = _vue.Observable ({current:}) {this.data = _vue.Observable ({current:}) {this.data = _vue.Observable ({current:}); '/')} init() {this.createrOutemap () this.initComponents(_Vue) this.initEvent()} // The createRouteMap method is the argument passed in the constructor CreateRouteMap () {// Go through all the routing rules, parse the routing rules into key-value pairs, Stored in the routeMap enclosing options. Routes. ForEach (route = > {enclosing routeMap [route. The path] = route.com ponent})} / / initComponents Router-view /** * Note: The build of Vue is divided into runtime version and full version * Runtime version: The template template is not supported and should be compiled in advance when packaged. Use the Render function to create the virtual DOM and then render it to the view. * Full version: contains the runtime and the compiler, about 10K larger than the runtime version, the application runs the template into the render function, performance is not as good as the runtime version. * VUe-CLI-created projects default to the runtime version * use two methods: * 1. Module.exports = {* runtimeCompiler: / / module. Exports = {* runtimeCompiler: / / module. Exports = {* module. // initComponents(Vue) {// vue.component_props ('router-link', {// props: {// to: String // }, // template: '<a :href="to"><slot></slot></a>' // }) // } /** * 2. */ initComponents(vue) {const self = this vue.component ('router-link', {props: {to: String}, render(h) {// function h is used to create the virtual DOM, render function h('a', {attrs: props: {to: String}, render(h) {function h('a', {attrs: {href: self.options.mode === 'history'? This. to: '/#${this.to}'}, on: { this.clickHandler } }, [this.$slots.default]) }, methods: { clickHandler(e) { const to = self.options.mode === 'history' ? this.to : '/#${this.to}' if (self.routemap [this.to]) {// Change the browser path and do not send a request to the server history.pushState({}, ", to) // Load the current routing component: // This. data is a responsive object. When the current value changes, $router.data.current = this.to} else {history.pushState({}, "", '/#/*') this.$router.data.current = '*' } e.preventDefault() } } // template: '<a :href="to"><slot></slot></a>'}) Vue.component('router-view', {render(h) {const Component = self.routemap [self.data.current] // h converts the component to a virtual DOM and returns h(Component)}})} // When you click your browser's forward and back buttons, Render the component that corresponds to the address in the current address bar initEvent() {if (this.options.mode === 'history') {window.addeventListener (' popState ', () => { this.data.current = this.routeMap[window.location.pathname] ? window.location.pathname : '*' }) } else { window.addEventListener('hashchange', () => { const hash = window.location.hash this.data.current = hash ? (this.routeMap[hash.substr(1)] ? hash.substr(1) : '*') : '/'})}}Copy the code
Data response
Core principles of data responsiveness – Vue 2.x
In Vue 2.x, when passing an ordinary JavaScript Object to a Vue instance as the data option, Vue iterates through all of the Object’s attributes and converts them all into getters/setters using Object.defineProperty. Object.defineproperty is a non-degrade handling (SHIm) feature in ES5, so it is not supported in IE8 and earlier browsers.
Core principles of data responsiveness – Vue 3.x
- Using the Proxy
- Listen directly on objects, not properties
- New in ES6, not supported by IE, performance is optimized by browser
Simulate the Vue responsive principle
Vue – Is responsible for injecting data from Vue into Vue instances and calling observers and Compiler.
Observer – Responsible for data hijacking, that is, listening for changes in data and converting properties in data into getters and setters.
Compiler – Is responsible for parsing differential expressions and instructions.
In the responsive mechanism of Vue, the observer mode is used to listen for changes in data, so you can see Dep and Watcher.
Dep – Publisher (target) in observer pattern, short for Dependency, collects dependencies in getter methods. For each responsive property, a Dep object is created that collects all dependencies on that property, and all dependencies on that property create a Watcher object, so a dependency collection is a Watcher object that depends on that property. In setter methods, dependencies are notified, and when a value in a property changes, the notify of the Dep is called, and the Update method of the Watcher object is called
Watcher – Responsible for updating the corresponding view
Vue
- Responsible for receiving initialization parameters (options)
- Is responsible for injecting properties from Data into Vue instances and converting them into getters/setters
- Responsible for calling the Observer to listen for changes to all properties in data
- Responsible for calling compiler parse instructions/differential expressions
Class diagram:
class Vue { constructor (options) { // 1. Through property preservation option data enclosing $options = options | | {} this. $data = options. The data | | {} / / options. El can be dom object also can be a string, namely a selector, $this.$el = typeof options. El === 'string'? document.querySelector(options.el) : options.el // 2. Getters and setters for data. this._proxyData(this.$data) // 3. New Observer(this.$data) // 4. Calling the Compiler object, New Compiler(this)} _proxyData(data) {// Iterate over all attributes in data object.keys (data).foreach (key => {// inject the attributes of data into DefineProperty (this, key, {enumerable: true, signals: true, get() { return data[key] }, set(newValue) { if (newValue === data[key]) return data[key] = newValue } }) }) } }Copy the code
Observer
- Is responsible for converting properties in the Data option into responsive data
- A property in data is also an object, and that property is converted into responsive data
- Send notifications of data changes
Class diagram:
class Observer { constructor(data) { this.walk(data) } walk(data) { // 1. Check whether data is an object if (! data || typeof data ! Keys (data). ForEach (key => {this.definereactive (data, key, key); Data [key])})} defineReactive(obj, key, val) {let dep = new dep () DefineProperty (obj, key, {enumerable: true, different: 64x) {this.walk(val) Object. True, get() {// Collect dependency dep.target && dep.addSub(dep.target) // cannot return obj[key], Obj [key] = obj[key] = obj[key]; Return val}, Set (newValue) {if (newValue === val) return val = newValue that.walk(newValue) // Send notification dep.notify()}})}}Copy the code
Compiler
- Responsible for compiling templates and parsing instruction/differential expressions
- Responsible for the first rendering of the page
- Re-render the view when the data changes
Class diagram:
// Compiler: // Compile the template, Class Compiler {constructor(vm) {this.el = vm.$el this.vm = vm This.compile (this.el)} Compile (el) {let childNodes = el.childnodes array. from(childNodes).foreach (node => {// Process text nodes if(this.isTextNode(node)) { this.compileText(node) } else if(this.isElementNode(node)) { this.compileElement(node) } // Check whether the node has child nodes. If so, To compile if(node.childnodes && node.childnodes.length) {this.compile(node)}})} // Compile element nodes, Handler command compileElement(node) {// console.log(node.attributes) // Traverses all attribute nodes array.from (node.attributes).foreach (attr => { Let attrName = attr.name if(this.isdirective (attrName)) {// v-text --> text attrName = attrname.substr (2) let key = attr.value this.update(node,key,attrName) } }) } update(node,key,attrName) { let updateFn = this[attrName + 'Updater'] updatefn. call(this,node,this.vm[key],key) { node.textContent = value new Watcher(this.vm,key,(newValue) => { node.textContent = newValue }) } // v-model modelUpdater(node,value,key) { node.value = value new Watcher(this.vm,key,(newValue) => { node.value = newValue }) // Node.addeventlistener ('input',() => {this.vm[key] = node.value})} CompileText (node) {// console.dir(node) // {{MSG}} let reg = /\{\{(.+?)\}\}/ let value = node.textContent If (reg.test(value)) {let key = RegExp.$1.trim() node.textContent = value.replace(reg,this.vm[key]) // Create a watcher object, Update view new Watcher(this.vm,key,(newValue) => {node.textContent = newValue})}} // determine if element attributes are directives - if they start with v- IsDirective (attrName) {return attrname.startswith ('v-')} isTextNode(node) {return node.nodeType === 3 IsElementNode (node) {return node.nodeType === 1}}Copy the code
Dep
- Collect dependencies, add watcher
- Notify all observers
Class diagram:
Constructor () {constructor () {this.subs = []} // Add observer addSub(sub) {if (sub && sub.update) { ForEach (sub => {sub.update()}) {this.subs.push(sub)}}Copy the code
Watcher
- When data changes trigger dependencies, DEP notifies all Watcher instances to update the view
- Add yourself to the DEP object when instantiating itself
Class diagram:
class Watcher { constructor (vm, key, Cb) {this.vm = vm // Data attribute name this.key = key // the callback is responsible for updating the view this.cb = cb // Log the watcher object to the static attribute target of the Dep class Dep.target = this // Trigger the get method, AddSub this.oldValue = vm[key] dep.target = null} // Update the view when data changes update() {let newValue = this.vm[this.key] if (newValue === this.oldValue) return this.cb(newValue) } }Copy the code
Virtual DOM (Virtual DOM)
What is the Virtual DOM
- Virtual DOM (Virtual DOM) is a common JS object to describe the DOM object
As shown in the figure above, we iterate over and print the real DOM member, and the printed result is shown below. As you can see, there are a lot of printed members, which means the cost of creating a DOM is very high.
The virtual DOM (below) is a JS object. Creating a virtual DOM has very few members, which means the cost of creating a virtual DOM is much lower than creating a real DOM.
Why use the virtual DOM
The front end is evolving rapidly and now requires a lot of dom manipulation. Virtual DOM can track state changes. When the state changes, there is no need to update the DOM immediately. You only need to create a virtual DOM tree to describe the real DOM tree.
The role of the virtual DOM
- Maintain the relationship between views and states
- Improved rendering performance in complex view situations
- cross-platform
-
Browser platforms render the DOM
-
Server render SSR (nuxt.js/Next-js)
-
Weex /React Native
-
Small programs (MPvue /uni-app), etc
-
Virtual DOM library
- Snabbdom
- The virtual DOM used internally in vue.js 2.x is a modified Snabbdom
- Approximately 200 SLOC (single line of code)
- Extensible through modules
- Source code is developed using TypeScript
- One of the fastest Virtual DOM
- virtual-dom
Basic use of Snabbdom
Create a project
Install Snabbdom. My version is:
Directory structure:
Index.html:
Create the 01-basicusage.js file under SRC to replace div#app in index.html with what we want to display. In 01-basicUsage.js we will show a div that is replaced with a string.
The basic use
The core of Snabbdom:
- Init () sets up the module and creates the patch() function
- Use the h() function to create JavaScript objects (vNodes) that describe the real DOM
- Patch () compares the old and new vNodes
- Update the changed content to the real DOM tree
01-basicusage.js:
Import {init} from 'snabbdom/build/init' import {h} from 'snabbdom/build/h' const patch = init([]) This is a virtual node for VNode, which is used to describe the real DOM. Let vnode = h('div#container.cls', 'Hello world') let app = document.querySelector('#app') // Patch converts virtual DOM to real DOM to mount to the DOM tree The old VNode can be a DOM element. If it is a DOM element, the DOM element is converted to a VNode. New VNode // returns a new VNode // Patch compares the two vnodes and updates the differences to the real DOM, Let oldVnode = patch(app, vnode) vnode = h('div#container. XXX ', 'hello snabbdom') patch(oldVnode, vnode)Copy the code
Run NPM run dev to see the result, and you can see the Hello SnabbDOM shown on the page.
Change the js introduced in index.html to 02-basicUsage.js to update div#app to a div with other elements inside.
02 – basicusage. Js:
Import {init} from 'snabbdom/build/init' import {h} from 'snabbdom/build/h' const patch = init([]) The second element is an array, each of which is an element, Let vnode = h('div#container', [h('h1', 'hello snabbdom'), h('p', 'This is ap ')]) let app = document.querySelector('#app') let oldVnode = patch(app, vnode) setTimeout(() => { // vnode = h('div#container', [ // h('h1', 'hello world'), // h('p', 'hello p') //]) // patch(oldVnode, vnode) // Clear the contents of the div. Patch (oldVnode, h('! '))}, 2000)Copy the code
Use of modules
Change the js file introduced in index.html to 03-modules.js.
03 – modules. Js:
import { init } from 'snabbdom/build/init' import { h } from 'snabbdom/build/h' // 1. The import module import {styleModule} from 'snabbdom/build/modules/style' import from {eventListenersModule} 'snabbdom/build/modules/eventlisteners' // 2. Const patch = init([styleModule, eventListenersModule]) // 3. Let vnode = h('div', [h('h1', {style: {backgroundColor: 'red' } }, 'hello world'), h('p', { on: { click: EventHandler}}, 'hello p')]) function eventHandler() {console.log(' Don't call me, } let app = document.querySelector('#app') patch(app, vnode)Copy the code
The core of Snabbdom
As can be seen from the above, the core of Snabbdom is: