vue中数据响应式的原理是什么?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
响应式的理解
响应式顾名思义就是数据变化,会引起视图的更新。这篇文章主要分析 vue2.0 中对象和数组响应式原理的实现,依赖收集和视图更新我们留在下一篇文章分析。
在 vue 中,我们所说的响应式数据,一般指的是数组类型和对象类型的数据。vue 内部通过 Object.defineProperty 方法对对象的属性进行劫持,数组则是通过重写数组的方法实现的。下面我们就简单实现一下。
首先我们定义一个需要被拦截的数据
const vm = new Vue({
data () {
return {
count: 0,
person: { name: 'xxx' },
arr: [1, 2, 3]
}
}
})
let arrayMethods
function Vue (options) { // 这里只考虑对 data 数据的操作
let data = options.data
if (data) {
data = this._data = typeof data === 'function' ? data.call(this) : data
}
observer (data)
}
function observer(data) {
if (typeof data !== 'object' || data === null) {
return data
}
if (data.__ob__) { // 存在 __ob__ 属性,说明已经被拦截过了
return data
}
new Observer(data)
}
这里的 arrayMethods、Observer 、 __ob__的实现和作用请继续往下看
class Observer {
constructor (data) {
Object.defineProperty(data, '__ob__', { // 在 data 上定义 __ob__ 属性,在数组劫持里需要用到
enumerable: false, // 不可枚举
configurable: false, // 不可配置
value: this // 值是 Observer 实例
})
if (Array.isArray(data)) { // 对数组进行拦截
data.__proto__ = arrayMethods // 原型继承
this.observerArray(data)
} else { // 对象进行拦截
this.walk(data)
}
}
walk (data) {
const keys = Object.keys(data)
for(let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(data, key, data[key])
}
}
observerArray (data) { // 拦截数组中的每一项
data.forEach(value => observer(value))
}
}
对象的劫持需要注意的几点:
遍历对象,如果值还是对象类型,需要重新调用 observer 观测方法
如果设置的新值是对象类型,也需要被拦截
// 处理对象的拦截
function defineReactive(data, key, value) {
observer(value) // 如果 value 值仍是对象类型,需要递归劫持
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue){
if (newValue === value) return
value = newValue
observer(newValue) // 如果设置 newValue 值也是对象类型,需要被劫持
}
})
}
数组的劫持需要注意的几点:
数组是使用函数劫持(切片编程)的思想,对数据进行拦截的
数组里新增加的值,如果是对象类型,也需要被重新拦截
const oldArrayPrototype = Array.prototype
arrayMethods = Object.create(oldArrayPrototype)
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] // 能够改变原数组的方法
methods.forEach(method => {
arrayMethods[methods] = function (...args) {
const result = oldArrayPrototype[methods].call(this, ...args)
const ob = this.__ob__ // this 就是调用改方法的数组
let inserted; // 数组新增的项的集合,需要再对其进行拦截
switch(methods) {
case 'push':
case 'unshift':
inserted = args
case 'splice':
inserted = args.slice(2) // 因为 splice 第二个参数后面的才是新增的
}
if (inserted) {
ob.observerArray(inserted)
}
return result
}
})
在面试中,如果我们需要手写 vue 的响应式原理,上面的代码足矣。但是我们通过学习 vue 的源码,如果在面试中能够给出以下加以总结性的回答更能得到面试官的青睐。
vue 2.0 源码的响应式原理:
因为使用了递归的方式对对象进行拦截,所以数据层级越深,性能越差
数组不使用 Object.defineProperty 的方式进行拦截,是因为如果数组项太多,性能会很差
只有定义在 data 里的数据才会被拦截,后期我们通过 vm.newObj = 'xxx' 这种在实例上新增的方式新增的属性是不会被拦截的
改变数组的索引和长度,不会被拦截,因此不会引起视图的更新
如果在 data 上新增的属性和更改数组的索引、长度,需要被拦截到,可以使用 $set 方法
可以使用 Object.freeze 方法来优化数据,提高性能,使用了此方法的数据不会被重写 set 和 get 方法
vue 3.0 源码响应式原理:
3.0 版本中使用了 proxy 代替了 Object.defineProperty ,其有13中拦截方式,不需要对对象和数组分别进行处理,也无需递归进行拦截,这也是其提升性能最大的地方
vue 3.0 版本响应式原理的简单实现
const handler = {
get (target, key) {
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key)
},
set (target, key, value) {
if(key === 'length') return true
console.log('update')
return Reflect.set(target, key, value)
}
}
const obj = {
arr: [1, 2, 3],
count: { num: 1 }
}
// obj 是代理的目标对象, handler 是配置对象
const proxy = new Proxy(obj, handler)
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。