简单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,这里是完整的代码,页面上点击操作数组,数组一直在增加,但是页面上却不展示新数组,需要怎么改才能实现数组的响应式显示在页面上?

简单vue如何实现数组的依赖收集 ?


回答:

其实实现不难,两个改造点

  1. 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]);

    }

    }

  2. 在拦截数组的 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

回到顶部