引入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>
複製代碼