从Vue源码来看数组操作
Posted 一腔诗意醉了酒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从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>
学习总结
如果在开发过程中,遇到了数组操作
- 更新的是数组中间的值,使用
深拷贝
,或许性能最好 - 如果是尾插或者头插一个新的值,那就
push
,shift
- 如果是去掉第一个或者最后一个就用
unshift
,pop
以上是关于从Vue源码来看数组操作的主要内容,如果未能解决你的问题,请参考以下文章
浅读vue-router源码,了解vue-router基本原理