参考:Vue 源码解析系列课程
本章源码:https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study
系列笔记:
JavaScript 知识储备
documentFragment
Document.createDocumentFragment() - Web API 接口参考 | MDN (mozilla.org)
document.createDocumentFragment() 用来创建一个虚拟的节点对象(文档片段对象)
fragment 认为是一个 dom 节点的收容器,原本要向 dom 中插入多少个节点就需要浏览器回流多少次,fragment 之后把要插入的节点先放入这个收容器中,这样再一次性插入,可以使浏览器只回流一次。
使用这个来模拟 Vue 中的 AST 抽象语法树(简化版的 AST)
DOM nodeType
nodeType 属性返回节点类型(只读)
- 元素节点,nodeType 属性返回 1
- 属性节点, nodeType 属性返回 2
- 文本节点,nodeType 属性返回 3
- 注释节点,nodeType 属性返回 8
类数组
产生情况
类数组就是形式像数组一样,但是没有数组的方法(有 length 属性)的对象
let arrayLike = {
0: "java",
1: "C++",
2: "javascript",
length: 3
}
JavaScript 在以下情况中的对象是 类数组:
- 函数里面的参数对象 arguments
- 使用
getElementsByName / TagName / ClassName 获得到的 HTMLCollection - 用
querySelector 获得的 NodeList
arguments:
function sum(a, b, c) {
console.log(arguments)
console.log(typeof arguments)
console.log(Object.prototype.toString.call(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))
console.log(typeof arrayList)
console.log(Object.prototype.toString.call(arrayList))
</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))
console.log(arrayList)
console.log(typeof arrayList)
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))
定义连接字符串函数:
类数组是没有 join 方法的,只能将其先转换为数组,再使用 join 方法
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)
foo(1, 2, 3)
类数组 转 数组
方法1:使用 ES6 中的 Array.from()
function fun () {
let arr = Array.from(arguments)
console.log(Array.isArray(arr))
}
方法2:使用 ES6 中的 ...
function fun () {
let arr = [...arguments]
console.log(Array.isArray(arr))
}
方法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()
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
}
})
})
}
_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) {
this.$vue = vue
this.$el = document.querySelector(el)
if (this.$el) {
let $fragment = this.node2Fragment(this.$el)
this.compile($fragment)
this.$el.appendChild($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
let reg = /\{\{(.*)\}\}/
childNodes.forEach(node => {
let text = node.textContent
if (node.nodeType === 1) {
self.compileElement(node)
} else if (node.nodeType === 3 && reg.test(text)) {
let name = text.match(reg)[1]
self.compileText(node, name)
}
})
}
compileElement(node) {
let self = this
let nodeAttrs = node.attributes;
[].slice.call(nodeAttrs).forEach(attr => {
let attrName = attr.name
let value = attr.value
if (attrName.startsWith('v-')) {
let dir = attrName.substring(2)
if (dir == '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') {
}
}
})
}
compileText(node, 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
})
}
getVueVal(vue, exp) {
let val = vue
exp.split('.').forEach(key =>
val = val[key]
)
return val
}
setVueVal(vue, exp, value) {
let val = vue
exp.split('.').forEach((key, index) => {
if (index < key.length - 1) {
val = val[key]
} else {
val[key] = value
}
})
}
}
|