Vue工作机制
编译compile
核心逻辑获取dom, 遍历dom,获取{{}},v-和@开头的,设置响应式
class Compile {
constructor(el,vm) {
this.$vm = vm
this.$el = document.queryySelector(el)
if (this.$el) {
this.$fragment = this.node2Fragment(this.$el)
this.compileElement(this.$fragment)
this.$el.appendChild(this.$fragment)
}
}
node2Fragment(el) {
let fragment = document.createDocument() // 新建文档碎片 dom接口
let child
while(child = el.firstChild) {
fragment.appendChild(child) // 将原生节点拷贝到fragment
}
return fragment
}
compileElement(el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach((node)=> {
let text = node.textContent
// 表达式文本
// 识别{{}}中的数据
let reg = /\{\{(.*\}\}/
// 按元素节点类型编译
if (this.isElementNode(node)){
this.compile(node)
}else if(this.isTextNode(node) && reg.test(text)){
// 文本 并且有个{{}}
this.compileText(node, RegExp.$1)
}
// 遍历编译子节点
if (node.childNodes && node.childNodes.length){
this.compileElement(node)
}
})
}
compile(node) {
let nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach((attr) => {
// 指令以v-xxx 命名
// 如<span v-text = "content"></span>
let attrName = attr.name // v-text
let exp = attr.value // content
if (this.isDirective(attrName)) {
let dir = attrName.substring(2) // text
// 普通指令
this[dir] && this[dir](node, this.&vm, exp)
}
if (this.isEventDirective(attrName)) {
let dir = attrName.substring(1) // text
this.eventHandle(node, this.&vm, exp, dir)
}
})
}
compileText(node, exp){
this.text(node, this.&vm, exp)
}
isDirective(attr) {
return attr.indexOf("v-") == 0
}
isEventDirective(dir){
return dir.indexOf('@') === 0
}
isElementNode(node) {
return node.nodeType == 1
}
isTextNode(node) {
return node.nodeType == 3
}
text(node, vm, exp) {
this.update(node, vm, exp, 'text')
}
html(node, vm, exp) {
this.update(node, vm, exp, 'html')
}
model(node, vm, exp) {
this.update(node, vm, exp, 'model')
let val = vm.exp
node.addEventListener("input", (e)=>{
let newValue = e.target.value
vm[exp] = newValue
val = newValue
})
}
update(node, vm, exp, dir){
let updateFn = this[dir + 'updater']
updateFn && updaterFn(node, vm[exp])
new Watcher(vm, exp, function(value) {
updaterFn && updaterFn(node, value)
})
}
eventHandler(node, vm, exp, dir){
let fn = vm.$options.methods && vm.$options.methods[exp]
if (dir && fn) node.addEvent(dir, fn.bind(vm), false)
}
textUpdater(node, value){
node.textContent = value
}
htmlUpdater(node, value) {
node.innerHTML = value
}
modelUpdater(node, value){
node.value = value
}
}
Vue响应式的原理defineProperty
class Vue {
constructor(options){
this._data = options.data;
this.observer(this._data);
}
observer (value) {
if (!value || typeof value !== 'object') return
object.keys(value).forEach(key => {
this.defineReactive(value, key, value[key]);
})
}
defineReactive(obj, key, val) {
Object.defineProperty(obj,key,{
enumerable: true // 属性是否可枚举
configurable: true // 属是否可被修改,
get () {
return val
}
set(newVal) {
if (newVal === val) return;
this.cb(newVal)
}
})
}
cb(val) {
console.log('执行更新函数’,val)
}
}
依赖搜集与跟踪
class Dep { // 收集依赖相关并触发更新
constructor () {
this.deps = [] // 依赖数组
}
addDep(dep){ // 向dep添加一个监听对象
this.deps.push(dep)
}
depend() {
Dep.target.addDep(this)
}
notify() { // 通知所有监听去更新视图
this.deps.forEach((dep) => {
dep.update()
})
}
}
class watcher { constructor (vm, key, cb) { // 在new一个监听对象时将该对象赋值给Dep.target, 在get中会用到
// 将 Dep.target 指向自己
// 然后出发属性的 getter 添加监听
// 最后将 Dep.target 清空 this.cb = cb this.vm = vm this.key = key this.value = this.get() Dep.target = this }
get () {
Dep.target = this
let value = this.vm[this.key]
return value
} // 更新视图 update () {
this.value = this.get()
this.cb.call(this.vm, this.value)
console.log('视图更新') } }
我们在增加一个Dep类的对象,用来收集Watcher对象。读数据的时候,会触发reactiveGetter函数把当前的Watcher对象(存放到Dep.target中)收集到Dep类中去。
写数据的时候,则会触发reactiveSetter方法,通知Dep类的调用notify来触发所有watcher对象的update方法更新相应视图
class Vue {
constructor(options) {
this._data = options.data
this.observer(this._data)
this.observer(this._data)
new Watcher() // 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
if (options.created) {
options.created.call(this)
}
this.$compile = new Compile(options.el, this)
}
observer(value){
if (!value || (typeof value !== 'object')) {
return
}
Object.key(value).forEach((key)=>{
this.proxyData(key)
this.defineReactive(value, key, value[key])
})
}
defineReactive(obj, key, val) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enmerable: true,
configurable: true,
get function reactive () {
dep.addDep(Dep.target)
return val
}
set: function reactiveSetter(newVal){
if (newVal === val) return
dep.notify() // 在set的时候触发dep中的notify来通知所有watcher对象更新视图
}
})
}
proxyData(key) {
Object.defineProperty(this, key, {
configurable: false,
enumerable: true,
get() {
return this.$data[key]
}
set (newVal) {
this.$data[key] = newVal
}
})
}
}