1.svelte的优势,没有劫持、没有虚拟DOM等,纯编译响应式,它可能是未来的趋势(是编译器,已经不是框架了)
2.vue2,关于响应式的原理大概如下:
// 单一职责,解耦
export class Vue {
constructor(options = {}) {
this.$options = options
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
this.$data = options.data
this.$methods = options.methods
this.proxy(this.$data)
// Observe 拦截this.$data
new Observer(this.$data)
// 编译html
new Compiler(this)
}
// 代理,方便使用,直接this.name
proxy(data) {
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return data[key]
},
set(newValue) {
if(data[key] === newValue || (Number.isNaN(data[key] && Number.isNaN(newValue)))) {
return
}
data[key] = newValue;
}
})
})
}
}
// 发布订阅模式
class Dep {
constructor() {
this.deps = new Set()
}
// 收集副作用代码
add() {
if (dep && dep.update) {
this.deps.add(dep);
}
}
notify() {
// udpate就是执行副作用 Watcher
this.deps.forEach(dep => dep.udpate())
}
}
// watcher会在编译的时候触发,编译html中的变量->触发当前变量的getter->封装一个watcher
class Watcher {
constructor(vm, key, cb) {
// vm是vue实例
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;
this.__old = vm[key] // 存下初始值,触发getter
// 防止我们在别处触发getter,仅在编译的时候
Dep.target = null;
}
update() {
let newValue = this.vm[this.key]
if (this.__old === newValue) {
return;
}
this.cb(newValue); // 操作DOM的函数
}
}
class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
if(!data || typeof data !== 'object') return
Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]))
}
// { obj: }
defineReactive(obj, key, value) {
let that = this;
let dep = new Dep();
this.walk(value);// 这个放在后面可以吗?
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get() {
Dep.target && dep.add(Dep.target)
return value;
},
set(newValue) {
value = newValue;
// 赋值的这个也可能是一个对象
that.walk(newValue);
dep.notify()
}
})
}
}
class Compiler {
constructor(vm) {
this.el = vm.$el
this.vm = vm
this.methods = vm.$methods
this.compiler(vm.$el)
}
compiler(el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
this.compileElement(node)
}
// 递归编译子节点
if (node.childNodes && node.childNodes.length) {
this.compiler(node)
}
})
}
compileElement(node) {
if(node.attributes.length) {
Array.from(node.attributes).forEach(attr => {
let attrName = attr.name
// v-on:click and v-model
if(this.isDirective(attrName)) {
attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2)
let key = attr.value
this.update(node, key, attrName, this.vm[key])
}
})
}
}
udpate(node, key, attrName, value) {
if (attrName === 'text') {
node.textContent = value
// 如果加上虚拟DOM diff算法的话,此处的回调函数就不应该直接操作DOM了,应该传入新老树进行对比,最后再渲染
new Watcher(this.vm, key, val => {
node.textContent = val
})
} else if (attrName === 'model') {
node.value = value
new Watcher(this.vm, key, val => {
node.textContent = val
})
node.addEventListenner('input', () => {
this.vm[key] = node.value
})
} else if (attrName === 'click') {
node.addEventListenner('click', this.methods[key].bind(this.vm))
}
}
isDirective(str) {
return str.startsWith('v-')
}
// 元素节点 <div v-model="msg"></div>
isElementNode(node) {
return node.nodeType === 1
}
// {{ count}} 文本节点
compileText(node) {
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if(reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, thiis.vm[key])
new Watcher(this.vm, key, val => {
node.textContent = val
})
}
}
isTextNode(node) {
return node.nodeType === 3
}
// 缺少调度啊???
// 没有考虑作用域?????
}
3.vue3.0相关响应式如下:
最大的优势: 考虑到内存的问题,不用每次都像2.0那样,new Dep()
// vue3.0
function isObject(data) {
return data && typeof data === 'object'
}
export function reactive(data) {
if(!isObject(data)) return
return new Proxy(data, {
get(target, key, receiver) {
const ret = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key)
return isObject(ret) ? reactive(ret) : ret
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
deleteProperty(target, key, receiver) {
const ret = Reflect.deleteProperty(target, keu, receiver)
// todo 通知
return ret
}
})
}
// proxy无法监听基本类型, 采用一个对象包起来
export function ref(target) {
let value = target
const obj = {
get value() {
track(obj, 'value')
return value
},
set value(newValue) {
if (value === newValue) return
value = newValue
trigger(obj, 'value')
}
}
return obj
}