为什么lodash的remove在vuejs中不是响应式的?

栏目: JavaScript · 发布时间: 5年前

内容简介:当我们开发中希望从数组中按照某种筛选条件移除数组的一个元素时,很容易想到使用splice或者filter来操作可以看到,splice方法的可读性并不好,而且还需要考虑val不是arr的元素的情况;filter可读性还不错,但实际上得到了一个新的数组。比较好的办法是循环使用splice,但那样写就太麻烦了。所以就有了lodash这种原生js库来帮助我们。lodash库中的remove方法语义明确,它使用一个循环的splice操作实现元素移除(在后面可以看到源码)。

当我们开发中希望从数组中按照某种筛选条件移除数组的一个元素时,很容易想到使用splice或者filter来操作

/* 从数组arr中移除值为val的元素 */
let index = arr.indexOf(val)
index !== -1 && arr.splice(index, 1)	

/* 从数组arr中移除满足predicate条件的元素 */
arr = arr.filter(predicate)
复制代码

可以看到,splice方法的可读性并不好,而且还需要考虑val不是arr的元素的情况;filter可读性还不错,但实际上得到了一个新的数组。比较好的办法是循环使用splice,但那样写就太麻烦了。

所以就有了lodash这种原生js库来帮助我们。lodash库中的remove方法语义明确,它使用一个循环的splice操作实现元素移除(在后面可以看到源码)。

/* 从数组arr中移除满足predicate条件的元素 */
_.remove(arr, predicate)
复制代码

但美中不足的是,如果在vuejs的开发中使用这个方法,你会发现使用这个方法并不能触发vuejs的DOM更新响应。

这是为什么呢?本篇文章就因此简单探讨一下。首先我们可以简单地认为数组操作后会触发一种机制进行DOM更新。那么题目问题就转化成了两个问题:

  1. 数组操作是怎么触发vuejs响应机制的?
  2. lodash的remove实现和普通的数组操作有什么区别?

数组操作是怎么触发vuejs响应机制的?

简单来说,vuejs用修改后的方法替换了观察的数组本身的原型方法,实现了拦截,增加了触发响应的部分。替换原型方法的代码如下:

// project: vue
// version: 2.5.6
// file: src/core/observer/index.js
if (Array.isArray(value)) {	// value: 观察的对象
    const augment = hasProto
    ? protoAugment
    : copyAugment
    augment(value, arrayMethods, arrayKeys) // 用arrayMethods替换掉value的原型
    this.observeArray(value)
}
复制代码

其中arrayMethods就是vuejs修改后的原型对象,它的实现代码如下:

// project: vue
// version: 2.5.6
// file: src/core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * 修改会改变数组元素的方法,实现拦截
 */
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) { // def类似Object.defineProperty
    const result = original.apply(this, args)	// 先执行原方法
    // 省略部分代码
    ob.dep.notify() // 触发响应更新事件
    return result
  })
})
复制代码

结合两份代码,可以看到如下过程:

  1. vuejs在数组原型的基础上,创建了新的原型对象,
  2. 修改新的原型对象中的'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'方法。
  3. 将观察的数组的原型替换成新的原型对象。

lodash的remove方法的实现

lodash的源码非常易读,其remove的关键实现就是通过筛选条件找到对应值在数组中的index,然后使用 basePullAt 方法将array中对应序号的元素剔除,其中 basePullAt 的方法实现如下

// project: lodash
// version: 4.17.10-npm
// file: _basePullAt.js 
var baseUnset = require('./_baseUnset'),
    isIndex = require('./_isIndex');

var arrayProto = Array.prototype;
var splice = arrayProto.splice;	// 关键:使用Array.prototype中的splice方法

function basePullAt(array, indexes) {
  var length = array ? indexes.length : 0,
      lastIndex = length - 1;

  while (length--) {
    var index = indexes[length];
    if (length == lastIndex || index !== previous) {
      var previous = index;
      if (isIndex(index)) {
        splice.call(array, index, 1);	// splice操作
      } else {
        baseUnset(array, index);
      }
    }
  }
  return array;
}
复制代码

结论

看完lodash中remove方法的实现代码,题目问题的答案就很明朗了:

vue通过改造观察数组的原型方法使它操作对应方法时会触发更新响应,而lodash的remove方法使用Array原型中的splice方法对数组进行操作,因此不会触发响应更新。

我们也得到了一些更多的问题。

更多的问题

1. 为什么lodash要使用Array原型中的splice方法,而不是直接使用数组对象上的splice?

可能是为了兼容一些类数组对象。但还有一个奇怪的地方,lodash的开发分支上早在17年四月就已经修改basePullAt的实现为直接使用splice,而不是用Array的原型。而npm即使是最新的4.17.11-npm,也还是沿用Array原型中的splice方法。可能是因为npm包需要尽量向前兼容吧。

2. 那有什么办法方便地实现响应式地从数组移除元素呢?

建议自己开发 工具 包方法。

3. 如果用户在vue中使用 arr.splice(0, 0) 操作,并不会对原数组产生修改,而同样会触发响应更新,那么是不是会影响效率?

猜测: 使用了key属性与vue的diff算法大概可以让这个效率影响降低,而如果在拦截方法中实现对原数组元素是否变更的需要可能比较影响性能

以上,一点拙见,欢迎指出问题。


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

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

思考的乐趣

思考的乐趣

顾森 / 人民邮电出版社 / 2012-6 / 45.00元

本书是一个疯狂数学爱好者的数学笔记,面向所有喜爱数学的读者。从2005年7月开始,作者已经写了连续六年的博客,积累下来了大量的数学文章。 部分文章内容被广泛关注,在网络上大量分享转载。 这本书有意挑选了初等的话题,让大大小小的读者都能没有障碍地阅读。文章内容新,让有数学背景的人也会发现很多自己没见过的初等问题。 文章是独立的。一篇文章一个话题,文章与文章之间基本不会做参考,读者可以随意跳着看......一起来看看 《思考的乐趣》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试