手順とライフサイクル
この章のソースコード: https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study
シリーズノート:
- 【フロントエンドソースコード解析】口ひげテンプレートエンジンの核心原理
- 【フロントエンドのソースコード解析】仮想DOMの核心原理
- 【フロントエンドソースコード解析】データレスポンシブの原則
- 【フロントエンドソースコード解析】AST抽象構文木
- 【フロントエンドソースコード解析】命令とライフサイクル
JavaScript ナレッジ リザーブ
ドキュメントフラグメント
Document.createDocumentFragment() - Web API インターフェイス リファレンス | MDN (mozilla.org)
document.createDocumentFragment()
仮想ノード オブジェクト (ドキュメント フラグメント オブジェクト) の作成に使用されます。
Fragment は dom ノードのコンテナーと見なされます. 本来、dom にいくつのノードを挿入するかは、ブラウザーのリフローを可能な限り何度も行う必要があります. ブラウザーのリフローは 1 回だけです.
これを使用して、Vue で AST 抽象構文ツリー (AST の簡易版) をシミュレートします。
DOM ノード タイプ
nodeType プロパティはノード タイプを返します (読み取り専用)
- 要素 node、 nodeType 属性は 1 を返します
- 属性 node、 nodeType 属性は 2 を返します
- テキストノード、nodeType プロパティは 3 を返します
- node、 nodeType 属性が 8 を返すことに注意してください
配列のような
シチュエーション
配列のようなオブジェクトは、配列と同じ形式を持つが、配列メソッドを持たない (長さ属性を持つ) オブジェクトです。
// 这是个类数组
let arrayLike = {
0: "java",
1: "C++",
2: "javascript",
length: 3
}
次の場合、JavaScript オブジェクトは配列。
- 関数内のパラメーター オブジェクトの引数
- / TagName / ClassNameを使用して
getElementsByName
取得した HTMLCollection - で
querySelector
取得したNodeList
引数:
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)
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>
ノードリスト:
<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>
適用シナリオ
トラバース パラメータ操作:
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
連結文字列関数を定義します。
クラス配列には結合メソッドがありません。最初に配列に変換してから結合メソッドを使用することしかできません
function myConcat(separa){
// 类数组转数组
var args = Array.prototype.slice.call(arguments, 1)
return args.join(separa)
}
console.log(myConcat(",", "张三", "李四", "王五")) // 张三,李四,王五
ある関数から別の関数に引数を渡します。
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
クラス配列から配列へ
方法 1: ES6 を使用するArray.from()
function fun () {
let arr = Array.from(arguments)
console.log(Array.isArray(arr)) // true
}
方法 2: ES6 を使用する...
function fun () {
let arr = [...arguments]
console.log(Array.isArray(arr)) // true
}
方法 3: 配列メソッドを借りる
[]
空の配列を作成することにより、slice
メソッドを使用すると新しい配列が返されます
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))
}
単純なバージョンの Vue を手で書く
この章のソースコード: https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study
ここのコンテンツの多くは、以前の基準に基づいています
シンプルなバージョンの 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>
Vue クラスは windows オブジェクトにマウントする必要があります。
window.Vue = Vue
Vue.js
: 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
: テンプレートのコンパイルとディレクティブの解析
/**
* 模板编译类,指令解析
*/
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
}
})
}
}