从Vue源码来看数组操作

简介: 从Vue源码来看数组操作

文章目录


文章风格

基于研发者心理talk is cheap, show me the code原则,文章可能会涉及大量代码。

学习目标

相信很多人写Vue的时候,如果碰到了这样的情况(本意想要视图更新,但是视图却没有更新的时候),往往会不管三七二十一,直接一波$forceupdate完事,但是再读源码的时候,你就会发现为什么视图没有自动更新了。

学习过程

不用forceUpdate

比如说下面一段代码:

<!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 src='../dist/vue.js'></script>
</head>
<body>
    <div id = 'demo'>
        <p v-for="item in arr" :key='item'>{{item}}</p>
    </div>
    <script>
        const app = new Vue({
            el:'#demo',
            data:{
                arr: [1,2,3,4]
            },
            mounted(){
                /*
                需要强迫更新
                */
                setTimeout(()=>{
                    this.arr[2] = 99;
                    console.log(this.arr)
                   // this.$forceUpdate()
                 },1000)
            }
        })
    </script>
</body>
</html>

如果你把代码粘贴下来自己跑一次的话,你会发现,在打开this.$forceUpdate()之前,虽然在控制台,会打印出变化之后的数组,但是视图却没有更新。如图

那这到底是为什么呢?

  • 如果你读过源码的话,你应该会发现会,在vue2的源码中(src\core\observer\array.js),数组的数据响应式,是这样的。
/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */
import { def } from '../util/index'
// 获取数组原型,备份
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 method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    // 执行原来的动作
    const result = original.apply(this, args)
    const ob = this.__ob__
    // 如果是插入的操作,还需要额外的响应化处理
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 怀疑插入的是一个新数组,对他进行响应式
    if (inserted) ob.observeArray(inserted)
    // notify change  通知watcher更新
    ob.dep.notify()
    return result
  })
})

我们发现,对数组的响应式处理,其实是通过覆盖会改变原数组的值的七个方法进行覆盖,添加新的操作。

简单地来说,就是只要对数组进行进行

'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'

操作就会导致视图更新,或者直接更新整个数组,因为在vue2里面,属性名跟属性值是分离的(个人理解), 为什么这么说呢? 因为在defineReactive里面主要是通过Object.defineProperty的getter以及setter进行属性拦截的,如果你改变数组的其中一个值arr[i],并不会影响到arr对他的值([1,2,3,4])的引用,类似于C系语言的指针,没有发生改变,内存地址不没有发生变化,进一步的寻址不受影响。

简而言之,就是arr[i]的改变,不会触发set,所以视图没有更新(如果你就是想要他更新的话,就得调用forceUpdate进行强制重新渲染,但是,这会影响性能,是毋庸置疑的)。

  • 除此之外,你可能会想到的办法是对原数组就行拷贝,然后重新赋值引用。比如:
<!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 src='../dist/vue.js'></script>
</head>
<body>
    <div id = 'demo'>
        <p v-for="item in arr" :key='item'>{{item}}</p>
    </div>
    <script>
        const app = new Vue({
            el:'#demo',
            data:{
                arr: [1,2,3,4]
            },
            mounted(){
                /*
                需要强迫更新
                */
                setTimeout(()=>{
                    let temp = this.arr;
                    temp[2] = 99
                    this.arr = temp
                    console.log(this.arr)
                },1000)
    </script>
</body>
</html>

很遗憾地告诉你,结果是这样的:

因为js对数组就行=是进行的浅拷贝。

调用forceUpdate

<!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 src='../dist/vue.js'></script>
</head>
<body>
    <div id = 'demo'>
        <p v-for="item in arr" :key='item'>{{item}}</p>
    </div>
    <script>
        const app = new Vue({
            el:'#demo',
            data:{
                arr: [1,2,3,4]
            },
            mounted(){
                /*
                需要强迫更新
                */
                setTimeout(()=>{
                    this.arr[2] = 99;
                    console.log(this.arr)
                    this.$forceUpdate()
                },1000)
            }
        })
    </script>
</body>
</html>

一些会自动触发视图更新的办法

  • 七个方法之一
<!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 src='../dist/vue.js'></script>
</head>
<body>
    <div id = 'demo'>
        <p v-for="item in arr" :key='item'>{{item}}</p>
    </div>
    <script>
        const app = new Vue({
            el:'#demo',
            data:{
                arr: [1,2,3,4]
            },
            mounted(){
                setTimeout(()=>{
                    this.arr.push(100)
                },1000)
        })
    </script>
</body>
</html>

  • 深拷贝数组
<!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 src='../dist/vue.js'></script>
</head>
<body>
    <div id = 'demo'>
        <p v-for="item in arr" :key='item'>{{item}}</p>
    </div>
    <script>
        const app = new Vue({
            el:'#demo',
            data:{
                arr: [1,2,3,4]
            },
            mounted(){
                setTimeout(()=>{
                    // 深拷贝
                    let temp = [...this.arr]
                    temp[2] = 9999
                    this.arr = temp
                    console.log(this.arr)
                },3000)
            }
        })
    </script>
</body>
</html>


学习总结

如果在开发过程中,遇到了数组操作

  1. 更新的是数组中间的值,使用深拷贝,或许性能最好
  2. 如果是尾插或者头插一个新的值,那就push,shift
  3. 如果是去掉第一个或者最后一个就用unshift,pop
相关文章
|
4天前
|
人工智能 自然语言处理 Serverless
阿里云函数计算 x NVIDIA 加速企业 AI 应用落地
阿里云函数计算与 NVIDIA TensorRT/TensorRT-LLM 展开合作,通过结合阿里云的无缝计算体验和 NVIDIA 的高性能推理库,开发者能够以更低的成本、更高的效率完成复杂的 AI 任务,加速技术落地和应用创新。
|
5天前
|
人工智能 数据可视化 API
10 分钟构建 AI 客服并应用到网站、钉钉或微信中测试评
10 分钟构建 AI 客服并应用到网站、钉钉或微信中测试评
28 2
|
2天前
|
数据采集 机器学习/深度学习 人工智能
AI在医疗诊断中的应用
【9月更文挑战第15天】本文将探讨人工智能(AI)在医疗诊断领域的应用。我们将首先介绍AI的基本概念和其在医疗领域的潜力,然后通过一些具体的案例来展示AI如何帮助医生进行更准确的诊断。最后,我们将讨论AI在医疗诊断中面临的挑战和未来的可能性。
|
2天前
|
机器学习/深度学习 人工智能 搜索推荐
AI在医疗诊断中的应用:精准医疗的加速发展
【9月更文挑战第16天】随着人工智能(AI)技术的不断进步,医疗领域正经历前所未有的变革。本文探讨了AI在医学影像分析、病历数据分析和病症诊断预测等方面的应用,展示了其在提高诊断准确性、推动个性化治疗和促进医疗资源均衡分布方面的巨大潜力。AI正加速精准医疗的发展,有望在未来实现更智能、个性化的医疗服务,全面提升医疗质量和效率。
29 11
|
4天前
|
消息中间件 人工智能 运维
|
5天前
|
机器学习/深度学习 数据采集 人工智能
AI技术在自然语言处理中的应用与挑战
【9月更文挑战第12天】本文将探讨AI技术在自然语言处理(NLP)领域的应用及其面临的挑战。我们将介绍NLP的基本概念、主要任务和应用场景,并分析当前AI技术在NLP中的局限性和未来发展趋势。通过实际案例和代码示例,我们将展示AI技术如何帮助解决NLP问题,并探讨如何克服现有挑战以实现更高效的自然语言处理系统。
|
4天前
|
机器学习/深度学习 人工智能 算法
AI在医疗诊断中的应用与挑战
人工智能技术在医疗领域的应用日益广泛,尤其在医疗诊断中显示出巨大的潜力和优势。本文将探讨AI在医疗诊断中的应用,包括影像识别、病理分析、个性化治疗方案等,同时分析当前面临的挑战,如数据隐私、算法偏见和法规制约。通过对具体案例和技术原理的分析,我们希望能为读者提供一个全面而深入的视角,理解AI如何在医疗诊断中发挥作用,以及未来可能的发展方向。
|
4天前
|
机器学习/深度学习 人工智能 监控
探索AI技术在医疗健康中的应用与前景
本文深入探讨了人工智能(AI)技术在医疗健康领域的多样化应用及其未来发展潜力。通过分析当前AI技术的具体应用案例,如智能诊断、个性化治疗方案制定、患者监护与管理等,文章揭示了AI如何助力提升医疗服务质量、增强疾病预防能力并优化医疗资源配置。同时,针对AI技术发展中面临的伦理、隐私保护及技术准确性等挑战,文章提出了相应的解决策略和建议,旨在为读者提供一个全面而深入的视角,理解AI技术在医疗健康领域的现状与未来趋势。
11 0
|
2月前
|
存储 自然语言处理 API
通义万相AIGC技术Web服务体验评测
随着人工智能技术的不断进步,图像生成技术已成为创意产业的一大助力。通义万相AIGC技术,作为阿里云推出的一项先进技术,旨在通过文本到图像、涂鸦转换、人像风格重塑及人物写真创建等功能,加速艺术家和设计师的创作流程。本文将详细评测这一技术的实际应用体验。
126 4
|
26天前
|
机器学习/深度学习 数据采集 人工智能
作为AIGC技术的一种应用-bard
8月更文挑战第22天
33 15