vue原理理解记录
背景
我们在学习vue的时候,刚开始看到的是vue是一个渐进式的JavaScript框架。这里是我们很多同学能够很快上手vue开的一个原因。但是随着我们开发过程中遇到各种的问题,如果我们想要快速的解决问题和了解其原理的时候,那么就需要我们对vue原理进行学习和深入的研究。有时候我们使用v-for的时候要使用key,但是你知道为什么要使用key吗,我们知道data的数据发生变化时,页面上就会发生相应的变化这个又是因为什么呢,带着这样的疑问,我们学习一下,是什么能够帮助我们实现响应式和vue 的diff算法、双向绑定、mvvm设计模式。
一、vue响应式的原理:
在vue2中我们是使用Object.defineProperty,我们在MDN上查找其定义。
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法:
Object.defineProperty(obj, prop, descriptor)参数 obj 要定义属性的对象。
prop
要定义或修改的属性的名称或 Symbol 。
descriptor
要定义或修改的属性描述符。
返回值
被传递给函数的对象。
示例:
var o = {}; // 创建一个新对象// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {value : 37,writable : true,enumerable : true,configurable : true
});// 对象 o 拥有了属性 a,值为 37// 在对象中添加一个设置了存取描述符属性的示例
var bValue = 38;
Object.defineProperty(o, "b", {// 使用了方法名称缩写(ES2015 特性)// 下面两个缩写等价于:// get : function() { return bValue; },// set : function(newValue) { bValue = newValue; },get() { console.log("调用了set方法") return bValue;},set(newValue) { console.log("调用了set方法") bValue = newValue; },enumerable : true,configurable : true
});o.b; // 38 打印调用了get方法
o.b = 14 //打印调用了set方法
下面我们通过一个例子来看看是如何进行页面更新的。
我们先定义一个视图更新的函数;
function updateView(){
console.log('视图更新')
}
考虑到defineProperty方法没有办法监听数组,因此我们需要重新定义数组原型,不能影响到原先的原型。
const arrProperty = Array.prototype
//创建新的对象原型指向我们新定义的arrProperty,再扩展新的方法不会影响原型
const arrProtoTypy = Object.create(arrProperty);
['push','shift','unshift','pop','splice'].forEach(methodName=>{
updateView()//触发视图更新
arrProtoType.apply(this,...arguments)
})
//定义一个属性并监听起来
function defineReactive(target,key,value){
//为了深度监听
Object.defineProperty(target,key,{
get(){
return value
}
set (newValue){
if(newValue !== value){
//不确定newValue是不是对象我们就要继续去深度的监听
observer(newValue)
value = newValue
updateView()
}
}
})
}
//监听对象属性
function observer(target){
if(typeof target ! =='object' || target === null ){
//不是对象或者数组,直接返回
return target
}
if (Array.isArray(target)){
target.__proto__ = arrPrototype
}
for( let key in target) {
defineReactive(target,key,target[key])
}
}
验证:
定义一个对象
const data = {
name:'zhangsan',
age:30,
children:[
son:{ name:'zhangsi',age:4} ,
],
wife:{
name:"xiaotiantian",
age:28
}
}
//监听数据
observer(data)
// 测试
data.name = 'zhangsanfeng'
data.age = 35
// // console.log('age', data.age)
data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
delete wife.name // 删除属性,监听不到 —— 所有已 Vue.delet
data.wife.age= 30 // 深度监听
data.children.push({name:"xiaokeai",age:1}) // 监听数组
二、上面看到的是vue2的,在vue3版本中实现响应式的是Proxy
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
术语
handler (en-US)
包含捕捉器(trap)的占位符对象,可译为处理器对象。
traps
提供属性访问的方法。这类似于操作系统中捕获器的概念。
target
被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。
语法
const p = new Proxy(target, handler)
Copy to Clipboard
参数
target
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
我们看一下是如何实现响应式的;
Reflect是一个映射的方法,将逐步代替Object
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。
描述
与大多数全局对象不同 Reflect 并非一个构造函数,所以不能通过 new 运算符对其进行调用,或者将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)
// const data = {
// name: 'zhangsan',
// age: 20,
// }
const data = ['a', 'b', 'c']
const proxyData = new Proxy(data, {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 监听
}
const result = Reflect.get(target, key, receiver)
return result // 返回结果
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否删除成功
}
})
这篇文章中我们学习一个vue2和vue3事项响应式的核心API,下一章我们学习一个vue的diff算法