惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
Microsoft Azure Blog
Microsoft Azure Blog
云风的 BLOG
云风的 BLOG
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LangChain Blog
P
Privacy & Cybersecurity Law Blog
Hugging Face - Blog
Hugging Face - Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
大猫的无限游戏
大猫的无限游戏
Cyberwarzone
Cyberwarzone
The Register - Security
The Register - Security
Stack Overflow Blog
Stack Overflow Blog
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Threatpost
The GitHub Blog
The GitHub Blog
P
Privacy International News Feed
WordPress大学
WordPress大学
U
Unit 42
S
Securelist
T
The Exploit Database - CXSecurity.com
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Proofpoint News Feed
Latest news
Latest news
Hacker News: Ask HN
Hacker News: Ask HN
小众软件
小众软件
Know Your Adversary
Know Your Adversary
The Cloudflare Blog
V
Vulnerabilities – Threatpost
The Hacker News
The Hacker News
Scott Helme
Scott Helme
有赞技术团队
有赞技术团队
Security Latest
Security Latest
Google DeepMind News
Google DeepMind News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - Franky
Y
Y Combinator Blog
博客园 - 叶小钗
Security Archives - TechRepublic
Security Archives - TechRepublic
Google DeepMind News
Google DeepMind News
N
Netflix TechBlog - Medium
S
Secure Thoughts
T
Threat Research - Cisco Blogs
aimingoo的专栏
aimingoo的专栏
S
SegmentFault 最新的问题
Microsoft Security Blog
Microsoft Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
M
MIT News - Artificial intelligence

蚊子的前端博客

微说 | 有的人觉得只要不考XX,一定能考好!-蚊子的前端博客 微说 | 春天来了-蚊子的前端博客 明天和意外不知道哪个先来-蚊子的前端博客 微说 | 既然错过了路口,就及时止损,重新规划路线上路-蚊子的前端博客 微说 | 2025年的出生人口数是792万-蚊子的前端博客 微说 | 工作年终总结,让写对接的接口的数量?-蚊子的前端博客 微说 | 温家宝:没有政治体制改革的成功 经济体制改革不可能进行到底-蚊子的前端博客 微说 | 违法犯罪了该不该被禁言?-蚊子的前端博客 微说 | 不明白为什么在推荐去俄罗斯旅游-蚊子的前端博客 微说 | 易中天论骗子-蚊子的前端博客 微说 | 一场大风吹散了秋天-蚊子的前端博客 微说 | 既要、又要、还要、更要!-蚊子的前端博客 又是一年的国庆雨季-蚊子的前端博客 微说 | 预估下2025年的出生人口数据-蚊子的前端博客 微说 | 可惜了我那些小时候的书本-蚊子的前端博客 微说 | 牛马有的是,驴不够了!-蚊子的前端博客 微说 | 能否有一条非户口也能高考的路-蚊子的前端博客 微说 | 小聊中医-蚊子的前端博客 微说 | 将要制作的一款新产品-蚊子的前端博客 微说 | 做人要有信-蚊子的前端博客 微说 | 好一个正义联盟-蚊子的前端博客 微说 | 都是见过吃过的主儿-蚊子的前端博客 微说 | 追求8小时工作制有错吗?-蚊子的前端博客 微说 | 如果尖锐的批评完全消失-蚊子的前端博客 微说 | 很好!-蚊子的前端博客 微说 | 封禁用户可以,但要告知具体原因-蚊子的前端博客 微说 | 面朝大海,春暖花开-蚊子的前端博客 微说 | 程序员的悲哀是什么?-蚊子的前端博客 微说 | 2025年出生人口的预测-蚊子的前端博客 前端在 LiveKit 中如何获取所有的参与者-蚊子的前端博客 在 Electron 中使用 LiveKit 实现屏幕共享-蚊子的前端博客 在 Electron 应用中,如何获取 Mac 的摄像头和麦克风权限-蚊子的前端博客 微说 | 不要总让台湾艺人表态-蚊子的前端博客 微说 | 他们总说要更开放-蚊子的前端博客 自己封装 uuid 踩的一个小坑-蚊子的前端博客 CKEditor5 光标在粘贴的图片下一行-蚊子的前端博客 软考出成绩了,没过-蚊子的前端博客 微说 | 也许有一天我们这个社会会进步-蚊子的前端博客 好久没有更新文章了-蚊子的前端博客 微说 | 中共中央关于进一步全面深化改革,推进中国式现代化的决定-蚊子的前端博客 微说 | 关于惩罚性赔偿-蚊子的前端博客 微说 | 生孩子是谁的事儿!-蚊子的前端博客 微说 | 给群众一些选择电视剧的空间-蚊子的前端博客 微说 | 活着-蚊子的前端博客 微说 | 借用睡前消息里面的一段话-蚊子的前端博客 微说 | 曾经梦寐以求的,现在却没了追求-蚊子的前端博客 微说 | 我小时候的那些书本-蚊子的前端博客 给博客添加了一个微说的功能-蚊子的前端博客 今年的双 11 很平静-蚊子的前端博客 微说 | 贪就是贪-蚊子的前端博客 微说 | 总有人说穿越小说为什么不发展工业和科技!-蚊子的前端博客 微说 | 花了很多时间和精力,-蚊子的前端博客 2025 年终于是增加了 2 天的假期-蚊子的前端博客 微说 | 这是我的第一条微说,-蚊子的前端博客 如何手写 Array 的 forEach 方法-蚊子的前端博客 看到聚美优品的没落,真是唏嘘-蚊子的前端博客 如何手写 Array 的 map 方法-蚊子的前端博客 秋意正浓时-蚊子的前端博客 收藏几个常用但访问比较慢的组件的官网网址-蚊子的前端博客 从 Axios 源码分析如何支持 fetch 方法的-蚊子的前端博客 1024,给博客评论区所有的头像戴上一顶可爱的帽子-蚊子的前端博客 使用 GitHub 搭建了一个静态博客-蚊子的前端博客 在 JavaScript 如何判断变量是否为空-蚊子的前端博客 我为什么还在坚持我的个人博客-蚊子的前端博客 不要再手动拼接 URL 参数,请使用 URLSearchParams-蚊子的前端博客 使用 NodeJs 向百度资源推送链接-蚊子的前端博客 工具链极度内卷,留给开发者的性能优化手段已经不多了-蚊子的前端博客 在 React 中显示多个标签,超出省略并可以 hover 显示更多-蚊子的前端博客 蚊子的前端博客:探索与分享前端技术的奇妙之旅-蚊子的前端博客 互联网程序员前景真的一片黑暗吗?-蚊子的前端博客 Deno2.0 正式发布,向 Node.js 兼容-蚊子的前端博客 React 请求数据别再使用 useEffect 和 useState,试试 SWR 吧!-蚊子的前端博客 React 的 useEffect 的一些使用场景和技巧-蚊子的前端博客 前端趣闻之 JavaScript 语言的诞生和发展-蚊子的前端博客 如何合并同一接口的相同参数的请求-蚊子的前端博客 百度搜索中关于蚊子前端博客的奇怪的检索数据-蚊子的前端博客 使用 React 实现 todo list 的 curd 操作-蚊子的前端博客 React 模板中为什么可以用逻辑与运算符-蚊子的前端博客 评论回复在一张表里的评论系统如何进行分页-蚊子的前端博客 React 组件多次调用时如何区分不同的 div 容器-蚊子的前端博客 安全赋值运算符,再也不用写 try-catch 了-蚊子的前端博客 使用 nextjs 重构我的个人博客-蚊子的前端博客 基于 gitlab 的 webhook 向飞书发通知-蚊子的前端博客 数字转换为更高量级单位的工具方法-蚊子的前端博客 如何避免旧请求的数据覆盖掉最新请求-蚊子的前端博客 JavaScript 中 toString 的冷知识-蚊子的前端博客 React 中 useState 和 useRef 与全局变量的区别-蚊子的前端博客 从面试官的角度分析下简历中存在的问题-蚊子的前端博客 使用 React 实现 6 个输入框的短信验证码功能-蚊子的前端博客 反驳那些要实时刷新页面的前端部署方案-蚊子的前端博客 JSON stringify 的一些不常见使用-蚊子的前端博客 JavaScript 中数组 Array 的常见操作-蚊子的前端博客 前端如何提升用户的交互体验-蚊子的前端博客 没有日的日期在iOS中报 Invalid Date 的探究-蚊子的前端博客 飞书很好,但还是没抗住-蚊子的前端博客 给 Antd 的 DatePicker 组件实现带有至今的功能-蚊子的前端博客 antd 的 InputNumber 输入框添加左右的加减按钮-蚊子的前端博客 给想进国央企的同学介绍一款招聘软件「国聘」-蚊子的前端博客 基于 React 和 antd 实现的图片裁剪压缩功能-蚊子的前端博客 uniapp 中 checkbox 中的 checked 不生效的方案-蚊子的前端博客
Vue中对数组特殊的操作-蚊子的前端博客
author · 2018-06-20 · via 蚊子的前端博客

1. 为什么 Vue 中不能通过索引来修改数组以更新视图 #

我们知道在 Vue 中的数据是通过Object.defineProperty这种劫持的方式来实现数据更新的,可是数组是一个比较特殊的类型。官网上说:

由于 JavaScript 的限制,Vue 不能检测以下变动的数组: 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue 当你修改数组的长度时,例如:vm.items.length = newLength

我搜寻其他的文章里也说是因为大部分浏览器Object.observe()支持的不友好,不能检测很好的检测到数组的数据变化,因此在 Vue 中则使用了其他的方法来实现数据的更新。

那么我就在想,作者为什么不用 Object.defineProperty 来实现对数组的数据劫持呢,不能实现的原因是什么呢?我们这里来看个 demo:

function defArray(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log('get val: ' + val);
      return val;
    },
    set(newVal) {
      console.log('old value: ' + val);
      console.log('new value: ' + newVal);

      val = newVal;
    },
  });
}
let arr = [0, 1, 2, 3, 4, 5];
arr.forEach((item, index) => {
  defArray(arr, index, item);
});
arr[1] = 123; // 修改索引为1的值
/*
> arr[1] = 1
 old value: 1
 new value: 123
 123
*/

console.log(arr[2]); // 获取索引为1的数据
/*
> console.log(arr[2]);
 get val: 2
 2
*/

arr 数组的长度为 6,修改索引在 0-5 之间任意索引的数据,都能触发 set 方法,获取相应的数据时,就能触发 get 方法。不过当获取额外的数据时,或者先删除原数据再添加时,就不会触发相应的方法了:

arr[6] = 6;
console.log(arr[6]); // 6

delete arr[2];
arr[2] = 2;
console.log(arr[2]);

Vue中对数组的操作

因为上面我们只对已存在的索引的数据进行数据劫持,新添加的数据或者先删除再添加的数据,都是没有 get 和 set 方法的,都不能通过数据劫持的方式来更新视图。但数组是正常更新的,只是无法更新视图了。

所以我在这里大胆的猜测一下作者不用索引来更新视图的原因:防止因新添加或者先删除再添加等的操作,导致后续添加的数据没有 get 和 set 方法,在 Vue 中就统一操作,无论是数组还是 Object 类型的数据,都可以通过Vue.$set(Array|Object, key, val)来操作数据,并更新视图(当然,Object 类型的数据,在 Vue 初始化时就已经存在的 key,是可以直接通过 key 来修改的哈)。

作者尤雨溪在 GitHub 上有专门的回复,为什么 vue 没有提供对数组属性的监听

因为性能问题,性能代价和获得的用户体验收益不成正比。

以下源码均摘自与 github 上的Vue.js v2.5.17-beta.0版本。 commit 链接: Vue.js v2.5.17-beta.0

2. 变异方法更新数组 #

在官方网站中,提供了以下几个方法来检测数组的变化,也会触发视图的更新:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

在 Vue 的源码中,对 Vue 中的数组数据实现了类似的方法,这里官方的叫法是变异方法,比如push()方法,并不是调用 Array 中原生的 push 方法,而是调用自己实现的 push 方法,来实现数据的劫持,再通过 Array 中的 push 方法实现数组数据的更新,可能有点晕,看代码,我这里把几个距离较远的代码都放到一块了,但代码都没动,只是改了位置:

// 获取原生Array中提供的所有方法
var arrayProto = Array.prototype;

// 将原生提供的方法创建一个新的对象,以免修改原生的方法,造成全局污染
var arrayMethods = Object.create(arrayProto);

// 将要实现的几个变异方法
var methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

/**
 * Intercept mutating methods and emit events
 * 截取这些方法,然后实现相应的操作
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  var original = arrayProto[method]; // 缓存原始方法

  // def即Object.defineProperty的包装函数
  def(arrayMethods, method, function mutator() {
    var args = [],
      len = arguments.length;
    while (len--) args[len] = arguments[len];

    var result = original.apply(this, args); // 调用原始方法完成对数组的更新
    var ob = this.__ob__;
    var inserted; // 存储要修改的数据
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice(2);
        break;
    }
    console.log(args, inserted);
    if (inserted) {
      ob.observeArray(inserted);
    } // 如果有修改的数据,则添加observer监听器
    // notify change
    ob.dep.notify(); // 触发更新
    return result;
  });
});

/**
 * Observe a list of Array items.
 * 对数组中的每项添加监听器
 */
Observer.prototype.observeArray = function observeArray(items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};

这里我们也就知道了,在 Vue 中的数组数据,调用上面的那些方法,都是事先实现的同名方法,然后再通过apply调用原生方法,再添加监听器,触发更新等后续操作。

如果我们自己想单独实现下这样的操作如何实现呢,如果把仅仅把上面的代码拿出来是不行的,这些自定义的变异方法还没有跟数组进行绑定呢,数组调用这些方法时,还是原生的方法,因此这里我们需要把这些变异方法绑定到数组上,我们来看下 Vue 源码中的实现:

// can we use __proto__?
var hasProto = '__proto__' in {}; // __proto__是否可用

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
var Observer = function Observer(value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    // 当前数据为数组,若__protp__可用,则调用protoAugment方法,否则调用copyAugment方法
    var augment = hasProto ? protoAugment : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value); // 监听数组的每一项
  } else {
    this.walk(value);
  }
};

/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 * 把这些方法定义到原型链__proto__上
 */
function protoAugment(target, src, keys) {
  /* eslint-disable no-proto */
  target.__proto__ = src;
  /* eslint-enable no-proto */
}

/**
 * Augment an target Object or Array by defining
 * hidden properties.
 * ie9、ie10 和 opera 没有 __proto__ 属性,为数组添加自定义方法
 */
/* istanbul ignore next */
function copyAugment(target, src, keys) {
  for (var i = 0, l = keys.length; i < l; i++) {
    var key = keys[i];
    def(target, key, src[key]);
  }
}

这样就能给每个数组添加上这些变异方法了。因此 Vue 中也可以使用splice()来更新数组中的内容:

vm.$arr.splice(index, 1, newVal);

我们自己实现这样的操作时,可以把上面的 Observer 简化一下:

function init(value) {
  // can we use __proto__?
  var hasProto = '__proto__' in {};

  var augment = hasProto ? protoAugment : copyAugment;
  augment(value, arrayMethods, arrayKeys);
}

var arr = [1, 2, 3, 4, 5];
init(arr);

arr.splice(2, 1, 2);
arr.push(6, 7, 8);

3. Vue.$set 的实现 #

不多说话,直接看源码怎么实现的,内部有本人的中文注释:

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 * 在一个对象上设置一个属性。如果该属性尚不存在,则添加新属性并触发更改通知。
 */
function set(target, key, val) {
  if ('development' !== 'production' && (isUndef(target) || isPrimitive(target))) {
    warn('Cannot set reactive property on undefined, null, or primitive value: ' + target);
  }

  // 如果当前对象是数组,且key是合法的数字索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 若索引值小于数组的长度,则数组长度不变,只修改
    // 若索引值比当前数组的长度大,则说明是新添加元素,需要更新数组的长度
    // 这里采用变异方法splice来更新数组
    target.length = Math.max(target.length, key);
    target.splice(key, 1, val);
    return val;
  }

  // 以下操作时,target均为object对象类型

  // 若当前key已经存在于对象中,且没有原型链中,直接更新即可
  // 因为在Vue初始化时已经添加了监听器,这里就不用再添加了
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  var ob = target.__ob__;

  // 若当前对象是Vue实例,则不允许使用这种方式添加属性,应当在初始化时预先声明
  if (target._isVue || (ob && ob.vmCount)) {
    'development' !== 'production' &&
      warn(
        'Avoid adding reactive properties to a Vue instance or its root $data ' +
          'at runtime - declare it upfront in the data option.',
      );
    return val;
  }

  // ? 若当前对象没有监听器?则直接赋值?这里不太能理解
  if (!ob) {
    target[key] = val;
    return val;
  }

  // 若当前key不存在,则在ob.value上创建set和get方法,并触发更新
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val;
}

因此使用 Vue.$set 方法更新数组时,内部依然是用的splice()方法来操作的,而更新对象类型的数据则略微复杂点。

本人学艺不精、才疏学全,如果还有什么疏漏的地方,还望批评指正!