更优
)// eventBus.js
// 信号中心
let eventHub = new Vue()
// {"click":[fn1,fn2],"change":[fn]} // new Vue实例中有一个对象
// componentA.vue
// 发布者
addTodo: function () {
// 发布消息(事件)
eventHub.$emit("add-todo", {
text: this.newTodoText })
this.newTodoText = ""
}
// componentB.vue
// 订阅者
cteated: function () {
// 订阅消息(事件)
eventHub.$on("add-todo", this.addTodo)
}
class EventEmitter {
constructor() {
// {"click":[fn1,fn2],"change":[fn]}
this.subs = {
};
}
// 注册事件
$on(eventType, handler) {
this.subs[eventType] = this.subs[eventType] || [];
this.subs[eventType].push(handler);
}
// 触发事件
$emit(eventType) {
this.subs[eventType].forEach((handler) => {
handler();
});
}
}
let em = new EventEmitter();
// 为同一个事件名称添加多个处理函数
em.$on("click", () => {
console.log("click1");
});
em.$on("click", () => {
console.log("click2");
});
em.$emit("click");
// click1
// click2
可以更新视图或者其他的操作
)所有观察者
的update()方法观察者模式没有信号中心,而是将订阅者(观察者)添加到发布者中,让发布者记录下所有的订阅者
// 发布者-目标
class Dep {
constructor() {
// 记录所有的订阅者(观察者)watcher
this.subs = [];
}
addSub(sub) {
// 传递的对象必须有update方法,我们才能将该对象当成观察者
if (sub.update) {
this.subs.push(sub);
}
}
notify() {
this.subs.forEach((sub) => {
sub.update();
});
}
}
// 订阅者-观察者
class Watcher {
update() {
console.log("update");
}
}
let dep = new Dep();
let watcher = new Watcher();
// 将订阅者添加到发布者中,让发布者记录下所有的订阅者
dep.addSub(watcher);
// 通知所有的订阅者,调用订阅者的update()方法
dep.notify(); // update
观察者模式是由具体目标调度,比如当事件触发,Dep就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的
发布/订阅者模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在,减少发布者和订阅者之间的依赖
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>姓名:{
{
name}}</p>
<p>年龄:{
{
age}}</p>
</div>
</body>
<script>
const vm = new Vue({
el: "#app",
data: {
name: "szy",
age: 20,
},
});
console.log(vm);
</script>
</html>
Vue构造函数内部是将data中的成员转换成get/set注入到Vue实例上,
1.1 目的:在其他地方使用的时候我们可以直接通过this.name和this.age来
使用,比较方便
Vue构造函数中的data选项成员记录到了$date中,并且转换成getter/setter
2.1 目的 $data中的setter是真正监视数据变化的地方
2.2 $data和_data指向的是同一个对象,_开头的是私有成员,$开头的是公共成员
$el对应Vue构造函数选项中的el,可以是选择器或者dom对象
class Vue {
constructor(options) {
// 1:通过属性保存选项的数据
this.$options = options || {
}
this.$data = options.data || {
}
this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el
// 2:把data中的成员转换成getter和setter,注入到Vue实例中
this._proxyDate(this.$data)
// 3:调用observer对象,监听数据的变化
// 4:调用compiler对象,解析指令和插值表达式
}
_proxyDate (data) {
// 遍历data中的所有属性,因为真正监控数据变化触发视图更新是$data的职责,这里只要Vue数据发生变化,通过set告诉了$data,就可以,所以Vm上的属性如果是对象,不需要对象的成员也是响应式的
Object.keys(data).forEach(key => {
// 把data的属性注入到Vue实例中
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get () {
return data[key]
},
set (newValue) {
// newValue如果是对象,通知到$data就好,$data内部处理
if (newValue === data[key]) {
return
}
data[key] = newValue
}
})
})
}
}
const vm = new Vue({
el: "#app",
data: {
name: "szy",
age: 20,
},
});
console.log(vm);
功能
结构:两个方法
Observer构造函数核心代码 observer.js
class Observer {
constructor(data) {
this.walk(data)
}
walk (data) {
// 1:判断data是否是对象
if (!data || typeof data !== "object") {
return
}
// 2:遍历data对象的所有属性
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get () {
return val
},
set (newValue) {
if (newValue === val) {
return
}
val = newValue
}
})
}
}
class Vue {
constructor(options) {
// 1:通过属性保存选项的数据
this.$options = options || {
}
this.$data = options.data || {
}
this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el
// 2:把data中的成员转换成getter和setter,注入到Vue实例中
this._proxyDate(this.$data)
// 3:调用observer对象,监听数据的变化
new Observer(this.$data)
// 4:调用compiler对象,解析指令和插值表达式
}
_proxyDate (data) {
// 遍历data中的所有属性
Object.keys(data).forEach(key => {
// 把data的属性注入到Vue实例中
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get () {
return data[key]
},
set (newValue) {
if (newValue === data[key]) {
return
}
data[key] = newValue
}
})
})
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./observer.js"></script>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
name: "szy",
age: 20,
},
});
console.log(vm);
</script>
</body>
</html>
const vm = new Vue({
el: "#app",
data: {
name: "szy",
age: 20,
// person属性是对象
person: {
name: "zjl",
age: 30,
},
},
});
console.log(vm);
defineReactive (obj, key, val) {
// 如果Val是对象,把Val内部的属性转换成响应式数据
this.walk(val)
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get () {
return val
},
set (newValue) {
if (newValue === val) {
return
}
val = newValue
}
})
const vm = new Vue({
el: "#app",
data: {
name: "szy",
age: 20,
person: {
name: "zjl",
age: 30,
},
},
});
// 重新赋值,那么test这个属性是否是响应式的呢?
vm.name= {
test: "hello" };
console.log(vm);
defineReactive (obj, key, val) {
const that = this
// 如果Val是对象,把Val内部的属性转换成响应式数据
this.walk(val)
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get () {
return val
},
set (newValue) {
if (newValue === val) {
return
}
// 新增的属性也要进行处理变成响应式的
that.walk(newValue)
val = newValue
}
})
class Observer {
constructor(data) {
this.walk(data)
}
walk (data) {
// 1:判断data是否是对象
if (!data || typeof data !== "object") {
return
}
// 2:遍历data对象的所有属性
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive (obj, key, val) {
const that = this
// 如果Val是对象,把Val内部的属性转换成响应式数据
this.walk(val)
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get () {
return val
},
set (newValue) {
if (newValue === val) {
return
}
// 新增的属性也要进行处理变成响应式的
that.walk(newValue)
val = newValue
}
})
}
}
功能:操作虚拟dom,为了方便演示,我们下面的实例中直接操作dom
结构
创建Compiler类 => compiler.js
class Compiler {
constructor(vm) {
this.el = vm.$el
this.vm = vm
}
// 编译模板,处理文本节点和元素节点
compile (el) {
}
// 编译元素节点,处理指令
compileElement (node) {
}
// 编译文本节点,处理插值表达式
compileText (node) {
}
// 判断元素属性是否是指令
isDirective (attrName) {
// 因为在vue中,所有的指令都是以v-开头
return attrName.startsWith("v-")
}
// 判断节点是否是文本节点
isTextNode (node) {
return node.nodeType === 3
}
// 判断节点是否是元素节点
isElementNode (node) {
return node.nodeType === 1
}
}
// 编译模板,处理文本节点和元素节点
compile (el) {
// 因为最终el会整体替换,只对它的子节点做处理,不对el本身处理,所以处理的是el.childNodes
const childNodes = el.childNodes // children是元素节点
// childNodes是伪元素
Array.from(childNodes).forEach(node => {
// 处理文本节点
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node)
}
// 判断node节点,是否有子节点,如果有子节点,要递归调用compile
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
constructor(vm) {
this.el = vm.$el
this.vm = vm
// 调用this.compile方法
this.compile(this.el)
}
// 编译文本节点,处理插值表达式
compileText (node) {
// 以对象的形式打印
console.dir(node)
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<h1>插值表达式</h1>
<h3>{
{
name}}</h3>
<h3>{
{
age}}</h3>
<h1>v-text</h1>
<div v-text="name"></div>
<h1>v-model</h1>
<input type="text" v-model="name" />
<input type="text" v-model="age" />
</div>
<script src="./compiler.js"></script>
<script src="./observer.js"></script>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
name: "szy",
age: 20,
person: {
name: "zjl",
age: 30,
},
},
});
// 重新赋值,那么test这个属性是否是响应式的呢?
vm.name = {
test: "hello" };
console.log(vm);
</script>
</body>
</html>
class Vue {
constructor(options) {
// 1:通过属性保存选项的数据
this.$options = options || {
}
this.$data = options.data || {
}
this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el
// 2:把data中的成员转换成getter和setter,注入到Vue实例中
this._proxyDate(this.$data)
// 3:调用observer对象,监听数据的变化
new Observer(this.$data)
// 4:调用compiler对象,解析指令和插值表达式
new Compiler(this)
}
_proxyDate (data) {
// 遍历data中的所有属性
Object.keys(data).forEach(key => {
// 把data的属性注入到Vue实例中
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get () {
return data[key]
},
set (newValue) {
if (newValue === data[key]) {
return
}
data[key] = newValue
}
})
})
}
}
// 编译文本节点,处理插值表达式
compileText (node) {
// console.dir(node)
// 匹配,提取插值 以 {
{ name }} 为例
// node.textContent不仅能获取还能赋值
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim() //去除前后空格,此时的key的值就是 "name"
node.textContent = value.replace(reg, this.vm[key])
// 为什么不能用node.textContent=this.vm[key],因为插值表达式中可能有其他非变量字符
}
}
// 编译元素节点,处理指令
compileElement (node) {
// 获取所有的属性节点
console.log(node.attributes)
}
// 编译元素节点,处理指令
compileElement (node) {
// 获取所有的属性节点
// console.log(node.attributes)
// 遍历所有的属性节点
Array.from(node.attributes).forEach(attr => {
// 判断是否是指令
let attrName = attr.name
if (this.isDirective(attrName)) {
// v-text --> text 方便为每一个指令定义一个处理函数,减少if条件判断
// 属性节点的属性名
attrName = attrName.substr(2)
// 属性节点的值也是vm对象的属性(key)
let key = attr.value
this.update(node, key, attrName)
}
})
}
update (node, key, attrName) {
/*
因为下面的方法中的value是vm中属性对象的值,所以需要key,
不同的指令需要不同的处理函数,所以需要attrName
*/
let updateFn = this[attrName + "Updater"]
//updateFn有值(对应指令的处理函数存在)才调用
updateFn && updateFn(node, this.vm[key])
}
// 处理v-text指令
textUpdater (node, value) {
node.textContent = value
}
// 处理v-model指令
modelUpdater (node, value) {
// 该节点必须是表单元素
node.value = value
}
class Compiler {
constructor(vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// 编译模板,处理文本节点和元素节点
compile (el) {
const childNodes = el.childNodes // children是元素节点
// childNodes是伪元素
Array.from(childNodes).forEach(node => {
// 处理文本节点
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node)
}
// 判断node节点,是否有子节点,如果有子节点,要递归调用compile
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// 编译元素节点,处理指令
compileElement (node) {
// 获取所有的属性节点
// console.log(node.attributes)
// 遍历所有的属性节点
Array.from(node.attributes).forEach(attr => {
// 判断是否是指令
let attrName = attr.name
if (this.isDirective(attrName)) {
// v-text --> text 方便为每一个指令定义一个处理函数,减少if条件判断
// 属性节点的属性名
attrName = attrName.substr(2)
// 属性节点的值也是vm对象的属性(key)
let key = attr.value
this.update(node, key, attrName)
}
})
}
update (node, key, attrName) {
/*
因为下面的方法中的value是vm中属性对象的值,所以需要key,
不同的指令需要不同的处理函数,所以需要attrName
*/
let updateFn = this[attrName + "Updater"]
//updateFn有值(对应指令的处理函数存在)才调用
updateFn && updateFn(node, this.vm[key])
}
// 处理v-text指令
textUpdater (node, value) {
node.textContent = value
}
// 处理v-modle指令
modelUpdater (node, value) {
// 该节点必须是表单元素
node.value = value
}
// 编译文本节点,处理插值表达式
compileText (node) {
// console.dir(node)
// 匹配,提取插值
// node.textContent不仅能获取还能赋值
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
}
}
// 判断元素属性是否是指令
isDirective (attrName) {
// 因为在vue中,所有的指令都是以v-开头
return attrName.startsWith("v-")
}
// 判断节点是否是文本节点
isTextNode (node) {
return node.nodeType === 3
}
// 判断节点是否是元素节点
isElementNode (node) {
return node.nodeType === 1
}
}
功能
结构
创建 Dep类 => dep.js
class Dep {
constructor() {
// 存储所有观察者
this.subs = []
}
// 添加观察者
addSub (sub) {
// 判断sub是否是观察者(具有update方法)
if (sub && sub.update) {
this.subs.push(sub)
}
}
// 发送通知
notify () {
// 遍历subs中所有的观察者,调用没有个观察者的update方法来更新视图
this.subs.forEach(sub => {
sub.update()
})
}
}
defineReactive (obj, key, val) {
/*
为$date中的每一个成员属性创建一个dep对象,在get方法中收集依赖(调用dep的addsub()方法),
在set方法中发送通知(调用dep的notify()方法)
*/
// 负责收集依赖,并发送通知
const dep = new Dep
const that = this
this.walk(val)
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get () {
// Dep.target为观察者对象
Dep.target && dep.addSub(Dep.target)
return val
},
set (newValue) {
if (newValue === val) {
return
}
that.walk(newValue)
val = newValue
// 发送通知
dep.notify()
}
})
}
功能
自身实例化的时候往dep对象中添加自己
结构
两者相等,不更新视图
,有了vm和key,可以得到oldValue创建Watcher类 => watcher.js
class Watcher {
constructor(vm, key, cb) {
this.vm = vm
// data中属性的名称
this.key = key
// 回到函数负责视图更新
this.cb = cb
// 把watcher对象记录到Dep类的静态属性target
Dep.target = this
this.oldValue = vm[key] // 此处因为用到了$date属性触发了该属性的get方法,此时将该wtacher实例添加到了dep的subs依赖数组中,完成了添加watcher(观察者)的功能
// 防止重复添加watcher,将Dep.target的指针指向空
Dep.target = null
}
update () {
const newValue = this.vm[this.key]
if (this.oldValue === newValue) {
return
}
// 将变化的属性传到cb中,局部更新
this.cb(newValue)
}
}
// 编译文本节点,处理插值表达式
compileText (node) {
// console.dir(node)
// 匹配,提取插值
// node.textContent不仅能获取还能赋值
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
// 创建watcher对象,当数据发生变化更新视图
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
}
vm.name = { test: "hello" };
影响代码<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<h1>插值表达式</h1>
<h3>{
{
name}}</h3>
<h3>{
{
age}}</h3>
<h1>v-text</h1>
<div v-text="name"></div>
<h1>v-modle</h1>
<input type="text" v-model="name" />
<input type="text" v-model="age" />
</div>
<script src="./dep.js"></script>
<script src="./watcher.js"></script>
<script src="./compiler.js"></script>
<script src="./observer.js"></script>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
name: "szy",
age: 20,
person: {
name: "zjl",
age: 30,
},
},
});
// 重新赋值,那么test这个属性是否是响应式的呢?
// vm.name = { test: "hello" };
console.log(vm);
</script>
</body>
</html>
// 处理v-text指令
textUpdater (node, value) {
node.textContent = value
new watcher(this.vm, key, (newvalue) => {
node.textContent = newvalue
})
}
update (node, key, attrName) {
/*
因为下面的方法中的value是vm中属性对象的值,所以需要key,
不同的指令需要不同的处理函数,所以需要attrName
*/
let updateFn = this[attrName + "Updater"]
//updateFn有值(对应指令的处理函数存在)才调用
updateFn && updateFn(node, this.vm[key],key) //新增key变量
}
update (node, key, attrName) {
/*
因为下面的方法中的value是vm中属性对象的值,所以需要key,
不同的指令需要不同的处理函数,所以需要attrName
*/
let updateFn = this[attrName + "Updater"]
//updateFn有值(对应指令的处理函数存在)才调用
updateFn && updateFn.call(this, node, this.vm[key], key)
}
// 处理v-modle指令
modelUpdater (node, value, key) {
// 该节点必须是表单元素
node.value = value
new Watcher(this.vm, key, (newvalue) => {
node.value = newvalue
})
}
-概念
数据发生变化,更新视图
视图发生变化,更新数据
原理:当视图发生变化的时候,触发了元素的input事件,给Sdata绑定的元素注册input事件
// 处理v-modle指令
modelUpdater (node, value, key) {
// 该节点必须是表单元素
node.value = value
new Watcher(this.vm, key, (newValue) => {
node.value = newValue
})
// 双向绑定
node.addEventListener("input", () => {
this.vn[key] = node.value
})
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<h1>插值表达式</h1>
<h3>{
{
name}}</h3>
<h3>{
{
age}}</h3>
<h1>v-text</h1>
<div v-text="name"></div>
<h1>v-modle</h1>
<input type="text" v-model="name" />
<input type="text" v-model="age" />
</div>
<script src="./dep.js"></script>
<script src="./watcher.js"></script>
<script src="./compiler.js"></script>
<script src="./observer.js"></script>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
name: "szy",
age: 20,
person: {
name: "zjl",
age: 30,
},
},
});
// 重新赋值,那么test这个属性是否是响应式的呢?
// vm.name = { test: "hello" };
console.log(vm);
</script>
</body>
</html>
class Dep {
constructor() {
// 存储所有观察者
this.subs = []
}
// 添加观察者
addSub (sub) {
// 判断sub是否是观察者(具有update方法)
if (sub && sub.update) {
this.subs.push(sub)
}
}
// 发送通知
notify () {
// 遍历subs中所有的观察者,调用没有个观察者的update方法来更新视图
this.subs.forEach(sub => {
sub.update()
})
}
}
class Watcher {
constructor(vm, key, cb) {
this.vm = vm
// data中属性的名称
this.key = key
// 回到函数负责视图更新
this.cb = cb
// 把watcher对象记录到Dep类的静态属性target
Dep.target = this
this.oldValue = vm[key] // 此处因为用到了$date属性触发了该属性的get方法,此时将该wtacher实例添加到了dep的subs依赖数组中,完成了添加watcher(观察者)的功能
// 防止重复添加watcher
Dep.target = null
}
update () {
const newValue = this.vm[this.key]
if (this.oldValue === newValue) {
return
}
// 将变化的属性传到cb中,局部更新
this.cb(newValue)
}
}
class Compiler {
constructor(vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// 编译模板,处理文本节点和元素节点
compile (el) {
const childNodes = el.childNodes // children是元素节点
// childNodes是伪元素
Array.from(childNodes).forEach(node => {
// 处理文本节点
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node)
}
// 判断node节点,是否有子节点,如果有子节点,要递归调用compile
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// 编译元素节点,处理指令
compileElement (node) {
// 获取所有的属性节点
// console.log(node.attributes)
// 遍历所有的属性节点
Array.from(node.attributes).forEach(attr => {
// 判断是否是指令
let attrName = attr.name
if (this.isDirective(attrName)) {
// v-text --> text 方便为每一个指令定义一个处理函数,减少if条件判断
// 属性节点的属性名
attrName = attrName.substr(2)
// 属性节点的值也是vm对象的属性(key)
let key = attr.value
this.update(node, key, attrName)
}
})
}
update (node, key, attrName) {
/*
因为下面的方法中的value是vm中属性对象的值,所以需要key,
不同的指令需要不同的处理函数,所以需要attrName
*/
let updateFn = this[attrName + "Updater"]
//updateFn有值(对应指令的处理函数存在)才调用
updateFn && updateFn.call(this, node, this.vm[key], key)
}
// 处理v-text指令
textUpdater (node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
// 处理v-modle指令
modelUpdater (node, value, key) {
// 该节点必须是表单元素
node.value = value
new Watcher(this.vm, key, (newValue) => {
node.value = newValue
})
// 双向绑定
node.addEventListener("input", () => {
this.vm[key] = node.value
})
}
// 编译文本节点,处理插值表达式
compileText (node) {
// console.dir(node)
// 匹配,提取插值
// node.textContent不仅能获取还能赋值
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
// 创建watcher对象,当数据发生变化更新视图
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
}
// 判断元素属性是否是指令
isDirective (attrName) {
// 因为在vue中,所有的指令都是以v-开头
return attrName.startsWith("v-")
}
// 判断节点是否是文本节点
isTextNode (node) {
return node.nodeType === 3
}
// 判断节点是否是元素节点
isElementNode (node) {
return node.nodeType === 1
}
}
class Compiler {
constructor(vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// 编译模板,处理文本节点和元素节点
compile (el) {
const childNodes = el.childNodes // children是元素节点
// childNodes是伪元素
Array.from(childNodes).forEach(node => {
// 处理文本节点
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node)
}
// 判断node节点,是否有子节点,如果有子节点,要递归调用compile
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// 编译元素节点,处理指令
compileElement (node) {
// 获取所有的属性节点
// console.log(node.attributes)
// 遍历所有的属性节点
Array.from(node.attributes).forEach(attr => {
// 判断是否是指令
let attrName = attr.name
if (this.isDirective(attrName)) {
// v-text --> text 方便为每一个指令定义一个处理函数,减少if条件判断
// 属性节点的属性名
attrName = attrName.substr(2)
// 属性节点的值也是vm对象的属性(key)
let key = attr.value
this.update(node, key, attrName)
}
})
}
update (node, key, attrName) {
/*
因为下面的方法中的value是vm中属性对象的值,所以需要key,
不同的指令需要不同的处理函数,所以需要attrName
*/
let updateFn = this[attrName + "Updater"]
//updateFn有值(对应指令的处理函数存在)才调用
updateFn && updateFn.call(this, node, this.vm[key], key)
}
// 处理v-text指令
textUpdater (node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
// 处理v-modle指令
modelUpdater (node, value, key) {
// 该节点必须是表单元素
node.value = value
new Watcher(this.vm, key, (newValue) => {
node.value = newValue
})
// 双向绑定
node.addEventListener("input", () => {
this.vm[key] = node.value
})
}
// 编译文本节点,处理插值表达式
compileText (node) {
// console.dir(node)
// 匹配,提取插值
// node.textContent不仅能获取还能赋值
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
// 创建watcher对象,当数据发生变化更新视图
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
}
// 判断元素属性是否是指令
isDirective (attrName) {
// 因为在vue中,所有的指令都是以v-开头
return attrName.startsWith("v-")
}
// 判断节点是否是文本节点
isTextNode (node) {
return node.nodeType === 3
}
// 判断节点是否是元素节点
isElementNode (node) {
return node.nodeType === 1
}
}
class Vue {
constructor(options) {
// 1:通过属性保存选项的数据
this.$options = options || {
}
this.$data = options.data || {
}
this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el
// 2:把data中的成员转换成getter和setter,注入到Vue实例中
this._proxyDate(this.$data)
// 3:调用observer对象,监听数据的变化
new Observer(this.$data)
// 4:调用compiler对象,解析指令和插值表达式
new Compiler(this)
}
_proxyDate (data) {
// 遍历data中的所有属性
Object.keys(data).forEach(key => {
// 把data的属性注入到Vue实例中
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get () {
return data[key]
},
set (newValue) {
if (newValue === data[key]) {
return
}
data[key] = newValue
}
})
})
}
}