Instructions and Lifecycle
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:
- [Front-end source code analysis] The core principle of mustache template engine
- [Front-end source code analysis] The core principle of virtual DOM
- [Front-end source code analysis] Data responsive principle
- [Front-end source code analysis] AST abstract syntax tree
- [Front-end source code analysis] Instructions and life cycle
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
getElementsByName
HTMLCollection obtained by using / TagName / ClassNamequerySelector
The 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 slice
the 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
}
})
}
}