Vue简介
Vue.js 是一个开源的渐进式 JavaScript 前端框架,主要用于构建用户界面和单页应用程序(SPA)。Vue.js 可以轻松地与其他库或现有项目集成使用,并被认为是开发响应式数据驱动的现代 Web 应用的一种有效方式。
Vue.js 的核心特点:
- 响应式数据绑定:Vue.js 可以通过对数据进行双向绑定来响应用户输入和页面变化。
- 组件化:Vue.js 使得开发者可以将单个组件变成一个独立的模块,组件性能优良且可以重复利用。
- 基于模板的语法:Vue.js 提供了一套模板语法,使得开发者可以简单地编写 HTML 模板,并将其与 Vue.js 组件绑定。
Vue2的响应式原理
Vue2 的响应式原理建立在 ES5 的 Object.defineProperty()
上,该方法可以定义对象的属性,并对其进行劫持,当属性值发生变化时,Vue 可以检测到该变化并重新渲染相应的页面内容。
具体实现原理如下:
- 在 Vue 的初始化阶段,Vue 会对传入的
data
对象进行递归劫持,将data
对象的所有属性都转换成getter/setter
形式。 - 当页面中使用数据时,Vue 会通过访问属性的方式触发
getter
函数,从而将当前的Watcher
(观察者)对象加入到当前属性的依赖中。 - 当数据发生变化时,Vue 会通过监听器检测到变化,并触发对应属性的
setter
函数,从而通知该属性下所有的依赖Watcher
更新; Watcher
对象被通知后,会向对应的组件发送消息通知需要重新渲染视图,从而实现整个页面的更新。
需要注意,Vue2 只能监听对象属性的变化,并不能监听到添加/删除对象属性、数组方法的变化,因此我们可以使用 Vue.set()
或者 Vue.delete()
方法来更新对象属性,但是只能使用原生 JavaScript 数组的 push()
、 pop()
、splice()
、 shift()
、 unshift()
方法等来操作数组。
Vue2数据劫持的示例代码如下:
代码语言:javascript复制const data = { msg: 'Hello Vue' }
Object.defineProperty(data, 'msg', {
get() {
console.log('get');
return val;
},
set(newValue) {
console.log('set', newValue);
val = newValue;
}
})
Object.defineProperty()详解
Object.defineProperty() 是在 ES5 中新增的一个方法,用于为对象定义新的属性或修改对象的属性,其语法如下:
代码语言:javascript复制Object.defineProperty(obj, prop, descriptor)
其中的参数含义:
- obj:要定义属性的对象。
- prop:要定义或修改的属性的名称。
- descriptor:需要定义或修改的属性描述符对象。
属性描述符对象中包含以下可选属性:
- value:属性的值,默认为 undefined。
- writable:如果为 true,则该属性的值可以被赋值运算符改变,默认为 false。
- enumerable:如果为 true,则该属性可以在枚举对象属性时被枚举,默认为 false。
- configurable:如果为 true,则可以使用 Object.defineProperty() 方法修改该属性的描述符,默认为 false。
- get:属性读取方法。
- set:属性赋值方法。
Object.defineProperty()的缺点
- 无法监听数组的变化
Vue2
把会修改原来数组的方法定义为变异方法。 变异方法例如push、pop、shift、unshift、splice、sort、reverse
等,是无法触发set
的。 非变异方法,例如filter,concat,slice
等,它们都不会修改原始数组,而会返回一个新的数组。Vue2
的做法是把这些变异方法重写来实现监听数组变化。 - 必须遍历对象的每个属性
使用
Object.defineProperty
多数情况下要配合Object.keys
和遍历,于是就多了一层嵌套。 并且由于遍历的原因,假如对象上的某个属性并不需要“劫持”,但此时依然会对其添加“劫持”。 - 必须深层遍历嵌套的对象
当一个对象为深层嵌套的时候,必须进行逐层遍历,直到把每个对象的每个属性都调用
Object.defineProperty()
为止。
Vue3的响应式原理
Vue3 的响应式原理主要使用了 ES6 的 Proxy
代替了 Vue2 中的 Object.defineProperty()
,从而实现了更加高效和强大的数据劫持和响应式。
Proxy 对象可以通过对访问和修改数据的拦截来实现数据劫持。而 Reflect 对象则提供了更加灵活和易用的数据操作方法,比如可以使用 Reflect.has() 来检查对象是否有某个属性,使用 Reflect.defineProperty() 来代替 Object.defineProperty()。
具体实现原理如下:
- 在 Vue3 的初始化阶段,Vue3 会对传入的
data
对象通过使用Proxy
对象进行代理,即使用new Proxy(target, handler)
,其中target
是被代理的对象,handler
是一个对象,用来定义代理target
中的操作。 - 当页面中使用数据时,Vue3 会触发
get
操作,代理对象handler.get()
会被调用,进而让handler
捕获该操作,并将当前的Watcher
(观察者)对象加入到当前属性的依赖中。 - 当数据发生变化时,Vue3 会通过监听器检测到变化,并触发对应数据的
set
操作,代理对象handler.set()
会被调用,从而通知该属性下所有依赖的 ‘Watcher’ 对象更新; -
Watcher
对象被通知后,会向对应的组件发送消息通知需要重新渲染视图,从而实现整个页面的更新。
Vue3 中使用 Proxy 对象实现数据响应式的代码如下:
代码语言:javascript复制const data = { msg: 'Hello Vue' }
const reactiveData = new Proxy(data, {
get(target, key) {
console.log('get');
return target[key];
},
set(target, key, value) {
console.log('set');
target[key] = value;
return true;
}
})
上述代码中,data
对象通过 Proxy 对象 reactiveData
进行代理,当访问 reactiveData
对象的属性时,Proxy 对象内部的 get() 函数被调用;当设置属性时,Proxy 对象内部的 set() 函数被调用。在 get() 和 set() 函数中,可以对属性的读取和赋值进行拦截,从而实现数据的响应式。
Proxy详解
Proxy 是在 ES6 中新增的一个对象,用于代理另一个对象并拦截该对象的读取、赋值、属性定义等一系列操作,其语法如下:
代码语言:javascript复制new Proxy(target, handler)
Proxy中的参数:
- target:被代理的目标对象。
- handler:一个对象,其属性是钩子函数(trap),用于拦截代理对象的操作。
handler 包含以下可选钩子函数(trap):
- get(target, prop):用于拦截对象的读取操作。
- set(target, prop, value):用于拦截对象的赋值操作。
- has(target, prop):用于拦截 in 操作。
- deleteProperty(target, prop):用于拦截 delete 操作。
- apply(target, thisArg, args):用于拦截函数调用。
- construct(target, args):用于拦截 new 操作。
Proxy的优点
Proxy
可以直接监听对象而非属性;Proxy
可以直接监听数组的变化;Proxy
返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty
只能遍历对象属性直接修改;