引入MyVue文件,New一个 Vue对象,挂载元素,处理数据。响应化设置,模板编译。javascript
这个很少说,主文件html
管理/控制者,管理/控制数据的更新vue
观察者,观察数据的变化,写数据更新的方法java
只有这一个,负责编译node
class Myvue { // 核心文件
}
// 管理 若干watcher 的实例,和key 是一对一
class Dep {
}
// 保存依赖(和key的依赖) 实现update更新
class Watcher {
}
复制代码
// 遍历模板 将里面的插值表达式作处理
// 若是发现 k-bind 等指令 特殊处理
class Compile {
}
复制代码
所有代码展现闭包
class myvue {
constructor (options) {
// 初始化 加$做用 区分开
this.$options = options
this.$data = options.data
// 响应式
this.observe(this.$data)
new Compile(options.el, this)
options.created && options.created.call(this)
}
// 递归遍历,是数据相应化
observe (value) {
if (!value || typeof value !== 'object') {
return
}
// 遍历
Object.keys(value).forEach(key => {
// 定义响应式
this.defineReactive(value, key, value[key])
this.proxyData(key)
})
}
// 座一层代理
proxyData (key) {
// 这里的this 指的是app 实例
Object.defineProperty(this, key, {
get () {
return this.$data[key]
},
set (newValue) {
this.$data[key] = newValue
}
})
}
// 函数外面访问了内部遍历 造成了闭包 定义响应式
defineReactive (obj, key, val) {
// 递归 遍历深对象
this.observe(val)
// 建立Dep Dep和key 一一对应
const dep = new Dep() // Vue依赖收集的代码
Object.defineProperty(obj, key, {
get () {
// 将Dep 指向的watcher 放到Deo中
Dep.target && dep.addDep(Dep.target) // Vue依赖收集的代码
return val
},
set (newValue) {
if (newValue !== val) {
val = newValue
// console.log(`${key}属性更新了`) // Vue数据响应的代码
dep.notify()
}
}
})
}
}
复制代码
constructor (options) {
// 初始化 加$做用 区分开
this.$options = options
this.$data = options.data
// 响应式
this.observe(this.$data)
// 编译相关 后面会再说
new Compile(options.el, this)
options.created && options.created.call(this)
}
复制代码
咱们首先要接收一个 配置项options
,而后保存一下并拿出来数据。app
拿出data
数据后要进行 数据的响应式 也就是observe
,并把data
传入。最后编译模板框架
注,$符号仅仅做为区分,并没有他用dom
// 递归遍历,是数据相应化
observe (value) {
if (!value || typeof value !== 'object') {
return
}
Object.keys(value).forEach(key => {
// 定义响应式
this.defineReactive(value, key, value[key])
this.proxyData(key)
})
}
复制代码
众所周知,咱们须要的data
是一个函数或者对象。这里咱们 只考虑对象,若是不是对象直接返回。而后遍历数据,拿到key
。给value
对象里面对应的key
设置响应式。最后 设置一层代理函数
代理的做用: app是myvue实例
不设置代理的化,咱们须要这样访问数据
app.$data.test = '123456'
设置代理以后
能够直接访问app.test = '123456'
defineReactive (obj, key, val) {
// 递归 遍历深对象
this.observe(val)
// 建立Dep Dep和key 一一对应
const dep = new Dep() // Vue依赖收集的代码
Object.defineProperty(obj, key, { // 给这个对象添加 访问器属性
get () {
// 将Dep 指向的watcher 放到Deo中
Dep.target && dep.addDep(Dep.target) // Vue依赖收集的代码
return val
},
set (newValue) {
if (newValue !== val) {
val = newValue
// console.log(`${key}属性更新了`) // Vue数据响应的代码
dep.notify()
}
}
})
}
复制代码
做用:管理 若干watcher 的实例
class Dep {
constructor () {
this.deps = []
}
addDep (Watcher) {
this.deps.push(Watcher)
}
// 通知 即执行watcher 里面的 update 函数
notify () {
this.deps.forEach(dep => dep.update())
}
}
复制代码
这个类比较简单。就不详细说了
// 保存依赖(和key的依赖) 实现update更新
class Watcher {
constructor (vm, key, cb) {
this.vm = vm
this.key = key
this.cb = cb // 后来加上的 动态改变的时候加上的
// 把当前实例指定给Dep.target
Dep.target = this
this.vm[this.key] // 触发get 动态改变的时候加上的
Dep.target = null
}
update () {
this.cb.call(this.vm, this.vm[this.key]) // 发生嵌套得不到值 保证this指向正确
}
}
复制代码
这个引用是再 编译代码的时候建立的,感受最主要的是this.vm[this.key]
这句可能不太理解,就是这样访问这个属性,这个就触发了get函数,使得 添加到Dep.deps 里面,这样就能够监听到他的变化
class Compile {
constructor (el, vm) { // 接收一个vue 实例 和绑定元素
this.$vm = vm
this.$el = document.querySelector(el) // 得到元素
if (this.$el) {
// 把 el里面的内容放到另外一个fragment里面去,也就是另外一个空白DOM树,提升操做效率
this.$fragment = this.node2Fragment(this.$el)
console.log(this.$fragment)
// 编译 fragment
this.compile(this.$fragment)
// 将编译结果追加到宿主中 有则删除从新添加
this.$el.appendChild(this.$fragment)
}
}
// 遍历el 将里面的内容 搬到新建立的fragment
node2Fragment (el) {
const fragment = document.createDocumentFragment() // 建立空白DOM树
let child
while ((child = el.firstChild)) { // 每次取出一个
// console.log(el.firstChild.nodeName) // 文本也算一个节点
// appendChild移动操做 即全部孩子全到了 fragment下
fragment.appendChild(child)
}
// console.log(el.firstChild) 输出结果为空
return fragment
}
// 编译 把指令和事件作处理
compile (el) {
// 遍历el
const childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
if (this.isElement(node)) {
// console.log(`编译元素:${node.nodeName}`)
// 若是是元素节点,就要处理指令等
this.compileElement(node)
} else if (this.isInterpolation(node)) { // 是否是插值表达式
// console.log(`编译文本:${node.textContent}`)
// 处理文本
this.compileText(node)
}
// 递归子元素
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
// 是否是 元素节点 参考网址 https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType
isElement (node) {
return node.nodeType === 1
}
// 插值表达式的判断 须要知足 {{xx}}
isInterpolation (node) {
// test 测试方法 参考网址以下
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent) // textContent返回一个节点后代和文本内容
}
// 编译文本
compileText (node) {
// console.log(RegExp.$1) // 这个就是匹配出来的值 {{xxx}} 这个就是xxx
const exp = RegExp.$1
this.update(node, this.$vm, exp, 'text')
// node.textContent = this.$vm[exp]
}
// update函数 可复用 exp表达式 dir具体操做
update (node, vm, exp, dir) {
const fn = this[dir + 'Update']
fn && fn(node, this.$vm[exp])
new Watcher(vm, exp, function () { // 添加响应式
fn && fn(node, vm[exp])
})
// 建立watcher
}
textUpdate (node, exp) {
node.textContent = exp
}
modelUpdate (node, value) {
node.value = value
}
htmlUpdate (node, value) {
node.innerHTML = value
}
// 编译元素节点
compileElement (node) {
// 查看 node特性中 是否有 k-xx这样的指令
const nodeAttrs = node.attributes // attribute属性返回该元素全部属性节点的一个实时集合
// console.log(nodeAttrs)
Array.from(nodeAttrs).forEach(attr => {
const attrName = attr.name // k-xxx
const exp = attr.value // k-xxx = 'abc' 这是abc
if (attrName.indexOf('k-') === 0) {
const dir = attrName.substring(2) // 拿到xxx
// console.log(this)
this[dir] && this[dir](node, this.$vm, exp)
} else if (attrName.indexOf('@') === 0) { // 这就是事件
const eventName = attrName.substring(1)
this.eventHandle(node, this.$vm, exp, eventName)
}
})
}
// text指令实现
text (node, vm, exp) {
this.update(node, vm, exp, 'text')
}
// 双向绑定实现
model (node, vm, exp) {
this.update(node, vm, exp, 'model')
node.addEventListener('input', e => {
vm[exp] = e.target.value
})
}
html (node, vm, exp) {
this.update(node, vm, exp, 'html')
}
eventHandle (node, vm, exp, eventName) {
const fn = vm.$options.methods && vm.$options.methods[exp]
if (eventName && fn) {
node.addEventListener(eventName, fn.bind(vm))
}
}
}
复制代码
// 遍历el 将里面的内容 搬到新建立的fragment
node2Fragment (el) {
const fragment = document.createDocumentFragment() // 建立空白DOM树
let child
while ((child = el.firstChild)) { // 每次取出一个
// console.log(el.firstChild.nodeName) // 文本也算一个节点
// appendChild移动操做 即全部孩子全到了 fragment下
fragment.appendChild(child)
}
// console.log(el.firstChild) 输出结果为空
return fragment
}
复制代码
document.createDocumentFragment()
这个方法是建立了新的 空dom树(通常来讲,不直接修改数据),而后进行遍历.el.firstChild
,属于 移动操做,会自动向下走
// 编译元素节点
compileElement (node) {
// 查看 node特性中 是否有 k-xx这样的指令
const nodeAttrs = node.attributes // attribute属性返回该元素全部属性节点的一个实时集合
// console.log(nodeAttrs)
Array.from(nodeAttrs).forEach(attr => { // 获取到每个属性 进行判断
const attrName = attr.name // k-xxx
const exp = attr.value // k-xxx = 'abc' 这是abc
if (attrName.indexOf('k-') === 0) {
const dir = attrName.substring(2) // 拿到xxx
// console.log(this)
this[dir] && this[dir](node, this.$vm, exp) // 若是存在就执行这个函数
} else if (attrName.indexOf('@') === 0) { // 这是事件
const eventName = attrName.substring(1)
this.eventHandle(node, this.$vm, exp, eventName) // 执行事件函数
}
})
}
复制代码
// 编译 把指令和事件作处理
compile (el) {
// 遍历el
const childNodes = el.childNodes // 返回全部节点的集合
Array.from(childNodes).forEach(node => {
if (this.isElement(node)) {
// console.log(`编译元素:${node.nodeName}`)
// 若是是元素节点,就要处理指令等
this.compileElement(node) // 处理执行之类的操做
} else if (this.isInterpolation(node)) { // 是否是插值表达式
// console.log(`编译文本:${node.textContent}`)
// 处理文本
this.compileText(node)
}
// 递归子元素
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
复制代码
<!DOCTYPE html>
<html lang="en" xmlns="">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<p>{{name}}</p>
<p k-text="name"></p>
<p>{{age}}</p>
<!-- <p>{{doubleAge}}</p>-->
<input type="text" k-model="name">
<button @click="changeName">呵呵</button>
<div k-html="html"></div>
</div>
<script src='./Myvue.js'></script>
<script src='./Compile.js'></script>
<script> const app = new Kvue({ el: '#app', data: { name: 'I am test.', age: 12, html: '<button>这是一个按钮</button>' }, created () { console.log('开始啦') setTimeout(() => { this.name = '我是测试' }, 1500) }, methods: { changeName () { this.name = '哈喽,我是xxx' this.age = 1 } } }) </script>
</body>
</html>
复制代码