Instrucciones y ciclo de vida
Referencia: Cursos de la serie de análisis de código fuente de Vue
Código fuente de este capítulo: https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study
Notas de la serie:
- [Análisis de código fuente front-end] El principio básico del motor de plantilla de bigote
- [Análisis de código fuente front-end] El principio básico del DOM virtual
- [Análisis de código fuente front-end] Principio de respuesta de datos
- [Análisis de código fuente front-end] Árbol de sintaxis abstracta AST
- [Análisis de código fuente front-end] Instrucciones y ciclo de vida
Reserva de conocimientos de JavaScript
fragmento de documento
Document.createDocumentFragment() - Referencia de la interfaz API web | MDN (mozilla.org)
document.createDocumentFragment()
Se utiliza para crear un objeto de nodo virtual (objeto de fragmento de documento)
Fragment se considera como un contenedor para los nodos dom. Originalmente, la cantidad de nodos que se van a insertar en dom requiere que el navegador se refluya tantas veces como sea posible. El navegador solo se refluye una vez.
Use esto para simular el árbol de sintaxis abstracta de AST (versión simplificada de AST) en Vue
Tipo de nodo DOM
La propiedad nodeType devuelve el tipo de nodo (solo lectura)
- Nodo de elemento , el atributo nodeType devuelve 1
- atributo nodo , el atributo nodeType devuelve 2
- Nodo de texto , la propiedad nodeType devuelve 3
- Tenga en cuenta el nodo , el atributo nodeType devuelve 8
tipo matriz
situación
Un objeto similar a una matriz es un objeto que tiene la misma forma que una matriz, pero no tiene un método de matriz (con un atributo de longitud)
// 这是个类数组
let arrayLike = {
0: "java",
1: "C++",
2: "javascript",
length: 3
}
Los objetos de JavaScript son como una matriz :
- Los argumentos del objeto de parámetro en la función.
getElementsByName
HTMLCollection obtenido usando / TagName / ClassNamequerySelector
La NodeList obtenida con
argumentos :
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)
Colección HTML :
<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>
Lista de nodos :
<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>
Escenario de aplicación
Operaciones de parámetros transversales:
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
Defina la función de cadena de concatenación:
La matriz de clase no tiene un método de combinación, solo se puede convertir primero en una matriz y luego usar el método de combinación
function myConcat(separa){
// 类数组转数组
var args = Array.prototype.slice.call(arguments, 1)
return args.join(separa)
}
console.log(myConcat(",", "张三", "李四", "王五")) // 张三,李四,王五
Pasa argumentos de una función a otra:
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
clase matriz a matriz
Método 1: Usar ES6Array.from()
function fun () {
let arr = Array.from(arguments)
console.log(Array.isArray(arr)) // true
}
Método 2: Usar ES6...
function fun () {
let arr = [...arguments]
console.log(Array.isArray(arr)) // true
}
Método 3: tomar prestado el método de matriz
Al []
crear una matriz vacía, usar slice
el método devuelve una nueva matriz
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))
}
Escriba una versión simple de Vue a mano
Código fuente de este capítulo: https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study
Una gran cantidad de contenido aquí se basa en la base anterior
El efecto de la versión simple de 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>
La clase Vue debe montarse en el objeto de Windows:
window.Vue = Vue
Vue.js
: Una versión simple de la clase Vue
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
: compilación de plantillas y análisis de directivas
/**
* 模板编译类,指令解析
*/
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
}
})
}
}