2. 初始化vue数据劫持
vue2中对象的数据劫持:使用到
Object.defineProperty方法,使用observe来监听属性的变化
缺点:
- 只能对对象中的一个属性进行劫持
- 需要遍历
- 需要递归
vue2中对数组的数据劫持:使用到函数劫持(重写数组的方法)
所新增代码以及新建目录:
- src
- index.js
- init.js
- initState.js
- observe // 监听器
- index.js
- arr.js
src/index.js:
import { initMixin } from "./init"
/**
* @author xwya
* @since 2023-12-11
* @description Vue 构造函数
* @param {Object} options - Vue 的初始化选项。
* @returns {void} - 没有返回值。
*/
function Vue(options) {
// 初始化
this._init(options);
}
initMixin(Vue)
export default Vue;
src/init.js:
import { initState } from "./initState";
/**
* @description 初始化vue
* @param {Object} Vue
* @returns {void}
*/
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
let vm = this
vm.$options = options
// 初始化状态
initState(vm)
}
}
src/initState.js:
import { observe } from "./observe/index";
/**
* 初始化所有状态
* @param {Object} vm
* @returns {void}
*/
export function initState(vm) {
let ops = vm.$options
console.log(ops);
// 判断
if (ops.props) {
initProps(vm)
}
if (ops.data) {
initData(vm)
}
if (ops.watch) {
initWatch(vm)
}
if (ops.computed) {
initComputed(vm)
}
if (ops.methods) {
initMethods(vm)
}
}
function initProps(vm) {
}
function initData(vm) {
let data = vm.$options.data
// 判断data是函数还是对象
// 是函数的话要解决data指向问题 指向vue实例上去 没有call(vm)的话指向的是window
data = vm._data=typeof data=== "function" ? data.call(vm) : data
// data数据进行劫持
// 将data上的所有属性代理到vm 上
for (let key in data) {
proxy(vm, "_data", key)
}
observe(data)
}
function initWatch(vm) {
}
function initComputed(vm) {
}
function initMethods(vm) {
}
function proxy(vm, souce, key) {
Object.defineProperty(vm, key, {
get() {
return vm[souce][key]
},
set(newVal) {
vm[souce][key]= newVal
}
})
}
src/observe/index.js:
import { arrayProto } from "./arr";
/**
* @description 用于监听属性的变化通知订阅者
* @param {Object} data
* @returns {Object}
*/
export function observe(data) {
// 判断是否是对象
if (typeof data !== 'object' || data === null) {
return data;
}
return new Observe(data);
}
class Observe {
constructor(data) {
Object.defineProperty(data, '__ob__', {
enumerable: false, // 不能进行枚举
value:this
})
// 判断对象是否是数组
if(Array.isArray(data)) {
data.__proto__ = arrayProto;
// 如果数组是个对象
this.observeArray(data); // 处理数组对象
} else {
// 遍历
this.walk(data);
}
}
walk(data) {
// 遍历data数据,对每个属性进行监听
let keys = Object.keys(data);
for (let i = 0, len = keys.length; i < len; i++) {
// 对每个属性进行劫持
let key = keys[i];
let val = data[key];
// 监听
defineReactive(data, key, val);
}
}
// 对数组对象进行遍历监听
observeArray(data) {
for (let i = 0,len=data.length; i < len; i++) {
observe(data[i])
}
}
}
// 对对象中的属性进行劫持
function defineReactive(data, key, value) {
observe(value) // 深度监听
// 代理数据
Object.defineProperty(data,key,{
enumerable:true, // 可以枚举
configurable:true, // 可以删除
get() {
return value;
},
set(newValue) {
if (newValue !== value) {
observe(newValue) // 如果新的值也是对象也需要进行监听
value = newValue;
}
}
})
}
src/observe/arr.js:
/*
重写数组函数方法
(1) 获取原来数组方法
(2) 继承
(3) 劫持数组方法
*/
// (1) 获取原来数组
let oldArrayprotoMethods = Array.prototype;
// (2) 继承
export let arrayProto = Object.create(oldArrayprotoMethods);
// 需要劫持的数组方法
let arrayProtoMethods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
// (3) 劫持数组方法
arrayProtoMethods.forEach(item => {
arrayProto[item] = function (...args) {
let arr = oldArrayprotoMethods[item].apply(this, args);
let inserted =null
switch (item) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
let ob = this.__ob__
if (inserted) {
ob.observeArray(inserted) // 对我添加的对象进行劫持
}
return arr
}
})
总结:
问题1: 在后续添加直接添加的属性不会是响应式的
答: vue2是用过
Object.defineProperty实现数据响应式, 组件初始化时,对 data 中的 item 进行递归遍历,对 item 的每一个属性进行劫持,添加 set , get 方法。我们后来 新加的属性 ,并没有通过Object.defineProperty设置成响应式数据,修改后不会视图更新
问题2:数组/对象的响应式 ,vue 里面是怎么处理的?
答:
对象:使用了
Object.defineProperty中的 get 和 set 实现响应式数组:
Vue重写了数组的原型,更准确的表达是拦截了数组的原型实现响应式
问题3:如需给后添加的属性做响应式
答:
Vue.set(target,propertyName/index,value)或vm.$set(target,propertyName/index,value)如果是数组的话还可以直接使用新数组覆盖原始数组
问题4: 为什么对象和数组要分开来处理
答: 因为对象和数组的特性不同,处理它们的响应式需要不同的机制,数组使用重写方法是确保在调用这些方法时能够通知 Vue 进行响应式更新,因为直接修改数组的元素或长度 Vue 是无法追踪的。
对象的特性:
- 属性访问和设置: 对象使用键值对存储数据,可以通过点语法或中括号语法访问和设置属性。
- 可扩展性: 对象是动态的,可以随时添加或删除属性。
- 属性的定义性: 可以使用
Object.defineProperty定义属性,并设置属性的特性(比如可枚举性、可配置性、可写性)。 - 枚举性: 对象的属性可以是可枚举的(可以通过
for...in循环遍历到)或不可枚举的。
数组的特性:
- 有序集合: 数组是一种有序集合,它的元素是按照索引顺序存储的。
- 长度: 数组有一个
length属性,可以动态改变数组的长度。 - 变异方法: 数组具有可以改变自身的变异方法(如
push,pop,shift,unshift等),这些方法会改变数组的内容。 - 迭代方法: 数组提供了一些迭代方法(如
map,filter,reduce等),用于对数组进行操作或遍历。