@frank-shaw
2019-11-30T02:18:38.000000Z
字数 2837
阅读 1310
vue 数据响应式 数组响应式回答这个问题,实际上回答的是:Vue的数据响应式原理有什么限制?理解了Vue的数据响应式原理的限制,就可以很好回答这个问题。
在实际使用Vue的过程中,对于响应式Data的声明,必须将需要响应的各个属性都逐一在声明阶段写清楚。如果对于一个响应式的Object,动态增加了某一个属性,那么Vue是无法为该新增属性做响应式处理的。举个栗子:
export default {data(){return {foo : 1,bar : {tau : "test"}}}//给data动态增加属性newFoo, 页面无法响应式this.data.newFoo = "123"//给bar动态增加属性tau2, 页面无法响应this.data.bar.tau2 = "tell me"
理解了这个限制,我们再来看看数组的情况。我们会经常对数组进行的操作是怎样的呢?
var array1 = ['t1','t2','t3']//动态修改元素array1[0] = 'tt1'//动态增加元素array1[4] = 't5'//push方法增加数组元素array1.push("1234")//动态改变元素的长度array1.length = 100
这些常规操作,如果只是通过常规的拦截key、value的方式来进行数据响应式,那么明显无法完全覆盖。并且成本太高,设想一下:如果用户直接写一个array1[1000] = 'haha',Vue是否要为其1000个子元素(极有可能是都为null)都响应化处理呢?
另外,值得注意的是,之所以对象可以在data声明的时候写死,是因为页面操作中的对象属性基本上可以在编写的时候确定。但页面中数组的变化就不一样,其变化基本上无法预测。而JS语言中的数组对其子元素的属性不限制,于是更加凌乱。
那么,Vue是采用了什么方式来进行数组的数据响应化实现呢?
我们来接着上节课的内容,看源码。
查看/src/observer/array.js,可以知道Vue对数组的七个方法进行了拦截处理:
const arrayProto = Array.prototype//先克隆一份数组原型export const arrayMethods = Object.create(arrayProto)//七个变异方法const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']/*** Intercept mutating methods and emit events*/methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method]def(arrayMethods, method, function mutator (...args) {//执行原始方法const result = original.apply(this, args)//额外通知变更,当然,只有这7个方法才会有这个待遇const ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}//对新加入对象进行响应化处理if (inserted) ob.observeArray(inserted)// notify change//此处通知,可以知道数组更新行为ob.dep.notify()return result})})
我们来看,在定义Observer的时候,如何处理数组:
import { arrayMethods } from './array'const arrayKeys = Object.getOwnPropertyNames(arrayMethods)export class Observer {value: any;dep: Dep;vmCount: number; // number of vms that have this object as root $data//值得留意的是:Observer对象在一个Vue实例中是存在多个的,取决于data数据中嵌套了几个Object对象或数组对象constructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(value, '__ob__', this)//如果是数组if (Array.isArray(value)) {//如果能够使用原型特性,直接将变异方法赋予响应化数组的原型链上if (hasProto) {protoAugment(value, arrayMethods)} else {//如果无法使用原型,那么通过defineProperty的方式将变异方法赋予响应化数组copyAugment(value, arrayMethods, arrayKeys)}//接着,对数组子元素,进行新一轮的observe数据响应化的过程this.observeArray(value)} else {//如果是对象this.walk(value)}observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}}function protoAugment (target, src: Object) {target.__proto__ = src}function copyAugment (target: Object, src: Object, keys: Array<string>) {for (let i = 0, l = keys.length; i < l; i++) {const key = keys[i]def(target, key, src[key])}}
这个过程并不难看懂。至此,我们可以回答这个问题:
data: {obj: {foo: 'foo'}bar: ['tua', 'tea']}//下面的这个操作,是否会触发数据响应化过程呢?this.bar[0] = 'testNew';
我们了解了Vue数据响应化的这些限制,但我们如果又想要动态更新数组的属性,或者动态增加已有对象的新属性,该怎么办呢? Vue给我们提供了可用的api: Vue.set()与Vue.delete()方法。问题不是如何使用该api,而是想要了解其内部源码是怎样的?
想了解更多,可以通过