[Front-end source code analysis] Instructions and life cycle

Reference: Vue Source Code Analysis Series Courses

Source code of this chapter: https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study

Series notes:

JavaScript knowledge reserve

documentFragment

Document.createDocumentFragment() - Web API interface reference | MDN (mozilla.org)

document.createDocumentFragment()Used to create a virtual node object (document fragment object)

Fragment is considered as a container for dom nodes. Originally, as many nodes as you want to insert into dom, you need the browser to reflow as many times as possible. The browser only reflows once.

Use this to simulate the AST abstract syntax tree (simplified version of AST) in Vue

DOM nodeType

nodeType property returns the node type (read-only)

  • Element node , the nodeType attribute returns 1
  • attribute node , the nodeType attribute returns 2
  • Text node , the nodeType property returns 3
  • Note node , nodeType attribute returns 8

array-like

situation

An array-like object is an object that has the same form as an array, but does not have an array method (with a length attribute)

// 这是个类数组
let arrayLike = {
    
    
   0: "java",
   1: "C++",
   2: "javascript",
   length: 3
}

JavaScript objects are array :

  • The parameter object arguments in the function
  • getElementsByNameHTMLCollection obtained by using / TagName / ClassName
  • querySelectorThe NodeList obtained with

arguments

function sum(a, b, c) {
    
    
    console.log(arguments)
    console.log(typeof arguments) // object
	console.log(Object.prototype.toString.call(arguments)) // [object Arguments]
	console.log(arguments.callee)
}
sum(1, 2, 3)

HTMLCollection

<div class="son">
   张三
</div>
<div class="son">
   李四
</div>
<div class="son">
   王五
</div>

<script>
	var arrayList = document.getElementsByClassName("son")
	console.log(arrayList)
	console.log(Array.isArray(arrayList)) // false
	console.log(typeof arrayList) // object
	console.log(Object.prototype.toString.call(arrayList)) // [object HTMLCollection]
</script>

NodeList

<div class="son">
   张三
</div>
<div class="son">
   李四
</div>
<div class="son">
   王五
</div>

<script>
    var arrayList = document.querySelector(".son")
	console.log(Array.isArray(arrayList)) // fasle
    console.log(arrayList) // object
    console.log(typeof arrayList) // [object HTMLDivElement]
    console.log(Object.prototype.toString.call(arrayList))
</script>

Application Scenario

Traverse parameter operation:

function sum() {
    
    
      var sum = 0, len = arguments.length
      for (var i = 0; i < len; i++) {
    
    
        sum += arguments[i]
      }
	return sum
}

console.log(sum(1,2,3)) // 6

Define the concatenation string function:

The class array does not have a join method, it can only be converted into an array first, and then use the join method

function myConcat(separa){
    
    
	// 类数组转数组
	var args = Array.prototype.slice.call(arguments, 1)
	return args.join(separa)
}

console.log(myConcat(",", "张三", "李四", "王五")) // 张三,李四,王五

Pass arguments from one function to another:

function bar(a, b, c) {
    
    
   console.log(a, b, c)
}

function foo() {
    
    
   bar.apply(this, arguments)
}

bar(1, 2, 3) // 1 2 3
foo(1, 2, 3) // 1 2 3

class array to array

Method 1: Using ES6'sArray.from()

function fun () {
    
    
	let arr = Array.from(arguments)
	console.log(Array.isArray(arr)) // true
}

Method 2: Using ES6's...

function fun () {
    
    
	let arr = [...arguments]
	console.log(Array.isArray(arr)) // true
}

Method 3: Borrow the array method

By []creating an empty array, using slicethe method returns a new array

function fun () {
    
    
    let arr = [].slice.call(arguments)
    console.log(Array.isArray(arr))
}
function fun () {
    
    
    let arr = Array.prototype.slice.call(arguments)
    console.log(Array.isArray(arr))
}

Write a simple version of Vue by hand

Source code of this chapter: https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study

A lot of content here is based on the previous basis

The effect of the simple version of Vue:

<div id="app">
	{
    
    {
    
    a}}
	<button onclick="add()">增加数字</button>
	<input type="text" v-model="a">
</div>

<script src="/xuni/bundle.js"></script>    

<script>
	let vm = new Vue({
    
    
		el: '#app',
		data: {
    
    
			a: 10,
		},
		watch: {
    
    
			a() {
    
    
				console.log('a 变化了');
			}
		}
	})
	console.log(vm);

	function add() {
    
    
		vm.a++
	}
</script>

The Vue class should be mounted on the windows object:

window.Vue = Vue

Vue.js: A simple version of the Vue class

export default class Vue {
    
    
    constructor(options) {
    
    
        this.$options = options || {
    
    }
        this._data = options?.data || undefined 
        // 将数据变成响应式
        observe(this._data)
        // 将数据添加到实例中
        this._initData()
        // 调用默认的 watch
        this._initWatch()
        // 模板编译
        new Compile(options.el, this)
    }

    /**
     * 将数据添加到实例中
     */
    _initData() {
    
    
        let self = this
        Object.keys(this._data).forEach(key => {
    
    
            Object.defineProperty(self, key, {
    
    
                get() {
    
    
                    return self._data[key]
                },
                set (newVal) {
    
    
                    self._data[key] = newVal
                }
            })
        })
    }

    /**
     * 初始化 watch
     */
    _initWatch() {
    
    
        let self = this
        let watch = this.$options.watch 
        Object.keys(watch).forEach(key => {
    
    
            new Watcher(self, key, watch[key])
        })
    }
}

Compile.js: template compilation and directive parsing

/**
 * 模板编译类,指令解析
 */
export default class Compile {
    
    
    constructor(el, vue) {
    
    
        // vue 实例
        this.$vue = vue
        // 挂载点
        this.$el = document.querySelector(el)
        if (this.$el) {
    
    
            // fragment 是 JS 提供的虚拟节点(弱化版 AST)
            let $fragment = this.node2Fragment(this.$el)
            // 编译
            this.compile($fragment)
            // 编译后内容,上树
            this.$el.appendChild($fragment)
        }
    }

    /**
     * DOM 节点转换成 Fragment
     */
    node2Fragment(el) {
    
    
        let fragment = document.createDocumentFragment()
        let child
        while (child = el.firstChild)
            fragment.append(child)
        return fragment
    }
    /**
     * 模板编译
     */
    compile(el) {
    
    
        let self = this
        let childNodes = el.childNodes

        // 匹配 {
    
    {val}} 中 val 
        let reg = /\{\{(.*)\}\}/ 

        childNodes.forEach(node => {
    
    
            let text = node.textContent

            if (node.nodeType === 1) {
    
    
                // console.log('是元素节点');
                self.compileElement(node)
            } else if (node.nodeType === 3 && reg.test(text)) {
    
    
                // console.log('是文本节点');
                let name = text.match(reg)[1] // 获取 {
    
    {val}} 中 val
                // console.log(`文本节点:${name}`);
                self.compileText(node, name)
            }
        })
    }
    /**
     * 编译元素
     */
    compileElement(node) {
    
    
        let self = this
        // 获取到节点的属性
        let nodeAttrs = node.attributes;
        // console.log(nodeAttrs)

        // nodeAttrs 是类数组,转成数组
        [].slice.call(nodeAttrs).forEach(attr => {
    
    
            // 例如:class="title"
            let attrName = attr.name // class
            let value = attr.value // title

            // 分析指令,都是 v- 开头的,如:v-if、v-model
            if (attrName.startsWith('v-')) {
    
     // 判断是指令
                let dir = attrName.substring(2)
                if (dir == 'model') {
    
    
                    // console.log(`v-model 指令:${value}`);
                    // 实现 v-model 的双向绑定功能
                    new Watcher(self.$vue, value, newVal => {
    
    
                        node.value = newVal
                    })

                    let v = self.getVueVal(self.$vue, value)
                    node.value = v
                    node.addEventListener('input', e => {
    
    
                        let newVal = e.target.value
                        self.setVueVal(self.$vue, value, newVal)
                        v = newVal
                    })
                } else if (dir == 'if') {
    
    
                    // console.log(`v-if 指令:${value}`);
                }
            }
        })
    }
    /**
     * 编译文本
     */
    compileText(node, name) {
    
    
        // console.log('compileText: ' + name);
        console.log(`getVueVal: ${
      
      this.getVueVal(this.$vue, name)}`);

        node.textContent = this.getVueVal(this.$vue, name)
        // 创建一个观察者,监听数据变化
        new Watcher(this.$vue, name, value => {
    
    
            node.textContent = value
        })
    }
    /**
     * 根据 a.b.c 形式的表达式从 vue 实例中获取值 
     */
    getVueVal(vue, exp) {
    
    
        let val = vue
        exp.split('.').forEach(key => 
            val = val[key]
        )
        return val
    }
    /**
     * 根据 a.b.c 形式的表达式给 vue 实例设置值
     */
    setVueVal(vue, exp, value) {
    
    
        let val = vue
        exp.split('.').forEach((key, index) => {
    
    
            if (index < key.length - 1) {
    
    
                val = val[key]
            } else {
    
    
                val[key] = value
            }
        })
    }
}

Guess you like

Origin blog.csdn.net/weixin_43734095/article/details/125530676