简单vue如何实现数组的依赖收集 ?
<!DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
function proxy(vm) {
Object.keys(vm.$data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key];
},
set(newVal) {
vm.$data[key] = newVal;
},
});
})
}
class Vue1 {
constructor(options) {
this.$options = options;
this.$data = options.data;
observe(this.$data);
proxy(this);
// 用数据和元素进行编译
new Compiler(options.el, this)
}
}
class Compiler {
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el);
if (this.$el) {
this.compile(this.$el);
}
}
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if (this.isElement(node)) {
// 编译元素
this.compileElement(node)
} else if (this.isInterpolation(node)) {
// 编译插值文本
this.compileText(node);
}
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node);
}
})
}
compileElement(node) {
let nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
let attrName = attr.name;
let exp = attr.value;
if (this.isDirective(attrName)) {
let dir = attrName.substring(2);
this[dir] && this[dir](node, exp);
} else if (this.isEvent(attrName)) {
const dir = attrName.substring(1)
this.eventHandler(node, exp, dir)
}
});
}
eventHandler(node, exp, dir) {
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp]
node.addEventListener(dir, fn.bind(this.$vm))
}
compileText(node) {
this.update(node, RegExp.$1, 'text')
}
isElement(node) {
return node.nodeType == 1;
}
// 判断插值:文本且{{}}
isInterpolation(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
isDirective(attr) {
return attr.indexOf("k-") == 0;
}
// 判断是不是@开头的方法
isEvent(attr) {
return attr.startsWith('@')
}
text(node, exp) {
node.textContent = this.$vm[exp];
}
html(node, exp) {
node.innerHTML = this.$vm[exp];
}
update(node, exp, dir) {
const fn = this[dir + 'Updater'];
fn && fn(node, this.$vm[exp]);
new Watcher(this.$vm, exp, function (val) {
fn && fn(node, val);
});
}
textUpdater(node, val) {
node.textContent = val;
}
htmlUpdater(node, val) {
node.innerHTML = val;
}
model(node, exp) {
this.update(node, exp, 'model')
node.addEventListener('input', (e) => {
this.$vm[exp] = e.target.value
})
}
modelUpdater(node, val) {
node.value = val
}
}
function defineReactive(obj, key, val) {
observe(val);
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addDep(Dep.target);
return val
},
set(newVal) {
if (newVal !== val) {
observe(newVal);
val = newVal
dep.notify();
}
}
})
}
// 对象响应化:遍历每个key,定义getter、setter
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
if (Array.isArray(obj)) {
const dep = new Dep();
// 如果是数组, 重写原型
obj.__proto__ = arrayProto
obj.ob = dep;
// 传入的数据可能是多维度的,也需要执行响应式
for (let i = 0; i < obj.length; i++) {
observe(obj[i])
}
} else {
for (const key in obj) {
// 给对象中的每一个方法都设置响应式
defineReactive(obj, key, obj[key])
}
}
}
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm;
this.key = key;
this.updateFn = updateFn;
Dep.target = this;
// 触发一下getter
this.vm[this.key];
Dep.target = null;
}
update() {
this.updateFn.call(this.vm, this.vm[this.key]);
}
}
class Dep {
constructor() {
this.deps = [];
}
addDep(dep) {
this.deps.push(dep);
}
notify() {
this.deps.forEach(dep => dep.update());
}
}
const orginalProto = Array.prototype;
const arrayProto = Object.create(orginalProto); // 先克隆一份Array的原型出来
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(method => {
arrayProto[method] = function () {
// 执行原始操作
orginalProto[method].apply(this, arguments);
}
})
</script>
</head>
<body>
<div id="app">
<div>
<p>{{name1}}</p>
<p>{{name2}}</p>
<p k-text="name1"></p>
<button @click="click">click</button>
<p>{{name3}}</p>
<input k-model="name4" />
<p>{{name4}}</p>
<button @click="clickarr">clickarr</button>
<p>{{arr}}</p>
</div>
</div>
<script>
new Vue1({
el: '#app',
data: {
name1: 'name1',
name2: 'name2',
name3: 1,
name4: 'name4',
arr: [],
},
methods: {
click: function () {
this.name3 = this.name3 + 1;
},
clickarr: function () {
console.log('clickarr', this.arr);
this.arr.push(this.arr.length);
}
},
});
</script>
</body>
</html>
这是一个简单的vue,这里是完整的代码,页面上点击操作数组,数组一直在增加,但是页面上却不展示新数组,需要怎么改才能实现数组的响应式显示在页面上?
回答:
其实实现不难,两个改造点
watcher 中只通过触发 getter 为对象收集了依赖,那么需要添加 数组 的场景
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm;
this.key = key;
this.updateFn = updateFn;
Dep.target = this;
// 触发一下getter
const target = this.vm[this.key];
// add: 为 数组 收集依赖 -----------------
if (Array.isArray(target)) {
target.ob.addDep(this);
}
// add: end
Dep.target = null;
}
update() {
this.updateFn.call(this.vm, this.vm[this.key]);
}
}
在拦截数组的 api 逻辑中触发通知即可
methodsToPatch.forEach((method) => {
arrayProto[method] = function () {
// 执行原始操作
orginalProto[method].apply(this, arguments);
// add: 触发通知
this.ob.notify();
// add: end
};
});
你这个版本的实现有点老了,可以去看下 vue3 的响应式系统的实现,和这版差距很大了已经
回答:
这个就是vue的数据更新,直接使用$set更新你的数组即可。如果需要再页面打印页可以给相应的html标签加key, 每次set的时候counter++即可
#set 更新对象或者数组 this.$set(this.Object, 'b', 2)
#给打印的元素加key就可以随时跟新内容
<div :key="counter" v-for="(v,k) in Object">
{{ v, k }}
</div>
以上是 简单vue如何实现数组的依赖收集 ? 的全部内容, 来源链接: utcz.com/p/933179.html