浅谈Vue为什么不能检测数组变动

问题来源:https://segmentfault.com/q/1010000015780995

问题描述:Vue检测数据的变动是通过Object.defineProperty实现的,所以无法监听数组的添加操作是可以理解的,因为是在构造函数中就已经为所有属性做了这个检测绑定操作。

但是官方的原文:由于 JavaScript 的限制, Vue 不能检测以下变动的数组:

当你利用索引直接设置一个项时,例如: vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如: vm.items.length = newLength

这句话是什么意思?我测试了下Object.defineProperty是可以通过索引属性来设置属性的访问器属性的,那为何做不了监听?

有些论坛上的人说因为数组长度是可变的,即使长度为5,但是未必有索引4,我就想问问这个答案哪里来的,修改length,新增的元素会被添加到最后,它的值为undefined,通过索引一样可以获取他们的值,怎么就叫做“未必有索引4”了呢?

既然知道数组的长度为何不能遍历所有元素并通过索引这个属性全部添加set和get不就可以同时更新视图了吗?

如果非要说的话,考虑到性能的问题,假设元素内容只有4个有意义的值,但是长度确实1000,我们不可能为1000个元素做检测操作。但是官方说的由于JS限制,我想知道这个限制是什么内容?各位大大帮我解决下这个问题,感谢万分

面对这个问题,我想说的是,首先,长度为1000,但只有4个元素的数组并不一定会影响性能,因为js中对数据的遍历除了for循环还有forEach、map、filter、some等,除了for循环外(for,for...of),其他的遍历都是对键值的遍历,也就是除了那四个元素外的空位并不会进行遍历(执行回调),所以也就不会造成性能损耗,因为循环体中没有操作的话,所带来的性能影响可以忽略不计,下面是长度为10000,但只有两个元素的数组分别使用for及forEach遍历的结果:

var arr = [1]; arr[10000] = 1
function a(){
  console.time()
  for(var i = 0;i<arr.length;i++)console.log(1)
  console.timeEnd()
}
a(); //default: 567.1669921875ms
a(); //default: 566.2451171875ms

function b(){
  console.time()
  arr.forEach(item=>{console.log(2)})
  console.timeEnd()
}
b(); //default: 0.81982421875ms
b(); //default: 0.434814453125ms

可以看到结果非常明显,不过,如果for循环中不做操作的话两者速度差不多

其次,我要说的是,我也不知道这个限制是什么 (⇀‸↼‶) ╮( •́ω•̀ )╭

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。数组的索引也是属性,所以我们是可以监听到数组元素的变化的

var arr = [1,2,3,4]
arr.forEach((item,index)=>{
  Object.defineProperty(arr,index,{
    set:function(val){
      console.log('set')
      item = val
    },
    get:function(val){
      console.log('get')
      return item
    }
  })
})
arr[1]; // get 2
arr[1] = 1; // set 1

但是我们新增一个元素,就不会触发监听事件,因为这个新属性我们并没有监听,删除一个属性也是。

再回到题主的问题,既然数组是可以被监听的,那为什么vue不能检测vm.items[indexOfItem] = newValue导致的数组元素改变呢,哪怕这个下标所对应的元素是存在的,且被监听了的?

为了搞清楚这个问题,我用vue的源码测试了下,下面是vue对数据监测的源码:

可以看到,当数据是数组时,会停止对数据属性的监测,我们修改一下源码:

使数据为数组时,依然监测其属性,然后在defineReactive函数中的get,set打印一些东西,方便我们知道调用了get以及set。这里加了个简单判断,只看数组元素的get,set

然后写了一个简单案例,主要测试使用vm.items[indexOfItem] = newValue改变数组元素能不能被监测到,并响应式的渲染页面

运行页面

可以看到,运行了6次get,我们数组长度为3,也就是说数组被遍历了两遍。两遍不多,页面渲染一次,可能多次触发一个数据的监听事件,哪怕这个数据只用了一次,具体的需要看尤大代码怎么写的。就拿这个来说,当监听的数据为数组时,会运行dependArray函数(代码在上面图中get的实现里),这个函数里对数组进行了遍历取值操作,所以会多3遍get,这里主要是vue对data中arr数组的监听触发了dependArray函数。

当我们点击其中一个元素的时候,比如我点击的是3

可以看到会先运行一次set,然后数据更新,重新渲染页面,数组又是被遍历了两遍。

但是!!!数组确实变成响应式的了,也就是说js语法功能并不会限制数组的监测。

这里我们是用长度为3的数组测试的,当我把数组长度增加到9时

可以看到,运行了18次get,数组还是被遍历了两遍,点击某个元素同理,渲染的时候也是被遍历两次。

有了上面的实验,我的结论是数组在vue中是可以实现响应式更新的,但是不明白尤大是出于什么考虑,没有加入这一功能,希望有知道的大佬们不吝赐教

2018-07-27补充

github上提问了尤大

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Vue.js中数组变动的检测详解

    前言 最近在尝试用Vue.js重构公司的现有业务代码,组件化的设计思路和MVVM的思想让我深深沉迷于其中.但是是踩到了不少坑,就比如这篇文章介绍的数组绑定后的更新检测. 相信大家都知道Observer,Watcher,vm 可谓 Vue 中比较重要的部分,检测数据变动后视图更新的重要环节.在 vue.js中$watch的用法示例 中,我们讨论了如何实现基本的 watch . 接下来,我们来看看如何实现数组变动检测. 例子: // 创建 vm let vm = new Vue({ data: {

  • 解决Vue不能检测数组或对象变动的问题

    来看一个实例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>vue</title> <script src="https://unpkg.com/vue@2.3.3/dist/vue.js"></script> <style> li:hover

  • 浅谈Vue为什么不能检测数组变动

    问题来源:https://segmentfault.com/q/1010000015780995 问题描述:Vue检测数据的变动是通过Object.defineProperty实现的,所以无法监听数组的添加操作是可以理解的,因为是在构造函数中就已经为所有属性做了这个检测绑定操作. 但是官方的原文:由于 JavaScript 的限制, Vue 不能检测以下变动的数组: 当你利用索引直接设置一个项时,例如: vm.items[indexOfItem] = newValue 当你修改数组的长度时,例如

  • 浅谈vue中get请求解决传输数据是数组格式的问题

    qs的stringify接收2个参数,第一个参数是需要序列化的对象,第二个参数是转化格式,一般默认格式是给出明确的索引,如:arr[0]=1&arr[1]=2 //indices是index的复数格式,因此indices是索引的意思 //bracket是括号的意思,因此arrayFormat:'brackets'代表数组下标为空[] qs.stringify({ arr: [1,2,3] }, { indices: false }) //arr=1&arr=2&arr=3 qs.s

  • 浅谈js数据类型判断与数组判断

    写在开篇: 昨天面试发现一个十分非常简单的问题竟然没有回答上来,可能也确实是因为太紧张了,感觉被自己蠢哭了.后来想想还是应该认真记录一下,这样才能印象深刻.革命尚未成功,壮实仍需努力! 1. js六大数据类型 number:数字,整数.浮点数等等, string:单引号或者双引号来说明, Boolean:返回true和false,这两个值不一定对应1和0 object:对象,可以执行new操作符后跟要创建的对象类型的名称来创建. null:只有一个值得数据类型,逻辑上讲,null值表示一个空对象

  • 浅谈vue首屏加载优化

    本文介绍了浅谈vue首屏加载优化,分享给大家,具体如下: 库使用情况 vue vue-router axios muse-ui material-icons vue-baidu-map 未优化前 首先我们在正常情况下build 优化 1. 按需加载 当前流行的UI框架如iview,muse-ui,Element UI都支持按需加载,只需稍微改动一下代码. 修改前: import MuseUI from 'muse-ui' import 'muse-ui/dist/muse-ui.css' imp

  • 浅谈vue权限管理实现及流程

    一.整体思路 后端返回用户权限,前端根据用户权限处理得到左侧菜单:所有路由在前端定义好,根据后端返回的用户权限筛选出需要挂载的路由,然后使用 addRoutes 动态挂载路由. 二.实现要点 (1)路由定义,分为初始路由和动态路由,一般来说初始路由只有 login,其他路由都挂载在 home 路由之下需要动态挂载. (2)用户登录,登录成功之后得到 token,保存在 sessionStorage,跳转到 home,此时会进入路由拦截根据 token 获取用户权限列表. (3)全局路由拦截,根据

  • 浅谈vue的第一个commit分析

    为什么写这篇vue的分析文章? 对于天资愚钝的前端(我)来说,阅读源码是件不容易的事情,毕竟有时候看源码分析的文章都看不懂.每次看到大佬们用了1-2年的vue就能掌握原理,甚至精通源码,再看看自己用了好几年都还在基本的使用阶段,心中总是羞愧不已.如果一直满足于基本的业务开发,怕是得在初级水平一直待下去了吧.所以希望在学习源码的同时记录知识点,可以让自己的理解和记忆更加深刻,也方便将来查阅. 目录结构 本文以vue的第一次 commit a879ec06 作为分析版本 ├── build │ └─

  • 浅谈vue 二级路由嵌套和二级路由高亮问题

    第一层路由我写在app.vue里面.如图所示: footer.vue: 二级路由是这样: index.js里面的配置: 效果图: 效果出来了,又出现新的问题,就是点击二级路由的时候,默认的二级路由高亮不会去掉,如图所示: 在网上看到别人用exact方法,即在默认的二级路由里面加上exact,如图所示: 补充知识:vue - 子路由-路由嵌套 描述:子路由,也叫路由嵌套,采用在children后跟路由数组来实现,数组里和其他配置路由基本相同,需要配置path和component,然后在相应部分添加

  • 浅谈Vue 函数式组件的使用技巧

    什么是函数式组件 没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法,它只是一个接受一些 prop 的函数.简单来说是 一个无状态和无实例的组件 基本写法: Vue.component('my-component', { functional: true, // Props 是可选的 props: { // ... }, // 为了弥补缺少的实例 // 提供第二个参数作为上下文 render: function(createElement, context) { // ... }

  • 浅谈Vue使用Cascader级联选择器数据回显中的坑

    业务场景 由于项目需求,需要对相关类目进行多选,类目数据量又特别大,业务逻辑是使用懒加载方式加载各级类目数据,编辑时回显用户选择的类目. 问题描述 使用Cascader级联选择器过程中主要存在的应用问题如下: 1.由于在未渲染节点数据的情况下编辑时无法找到对应的类目数据导致无法回显,如何自动全部加载已选择类目的相关节点数据: 2.提前加载数据后,点击相应父级节点出现数据重复等: 3.使用多个数据源相同的级联选择器,产生只能成功响应一个加载子级节点数据: 4.Vue中级联选择器相应数据完成加载,依

  • 浅谈vue.watch的触发条件是什么

    很多人习惯用watch,但是却很少有人知道watch的真正触发条件.如果不是对vue原理了如指掌,请谨慎使用watch. 示例1,下面会触发watch 吗? <script> new Vue({ data() { return { city: {id: 1, name: 'Beijing'} } }, watch: { city() { console.log('city changed') } }, created() { this.city = {id: 1, name: 'Beijing

随机推荐