Vue.js 是一个开源的渐进式 JavaScript 前端框架,主要用于构建用户界面和单页应用程序(SPA)。Vue.js 可以轻松地与其他库或现有项目集成使用,并被认为是开发响应式数据驱动的现代 Web 应用的一种有效方式。
Vue.js 的核心特点:
Vue2 的响应式原理建立在 ES5 的 Object.defineProperty()
上,该方法可以定义对象的属性,并对其进行劫持,当属性值发生变化时,Vue 可以检测到该变化并重新渲染相应的页面内容。
具体实现原理如下:
data
对象进行递归劫持,将 data
对象的所有属性都转换成 getter/setter
形式。getter
函数,从而将当前的 Watcher
(观察者)对象加入到当前属性的依赖中。setter
函数,从而通知该属性下所有的依赖 Watcher
更新;Watcher
对象被通知后,会向对应的组件发送消息通知需要重新渲染视图,从而实现整个页面的更新。需要注意,Vue2 只能监听对象属性的变化,并不能监听到添加/删除对象属性、数组方法的变化,因此我们可以使用 Vue.set()
或者 Vue.delete()
方法来更新对象属性,但是只能使用原生 JavaScript 数组的 push()
、 pop()
、splice()
、 shift()
、 unshift()
方法等来操作数组。
Vue2数据劫持的示例代码如下:
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() 是在 ES5 中新增的一个方法,用于为对象定义新的属性或修改对象的属性,其语法如下:
Object.defineProperty(obj, prop, descriptor)
其中的参数含义:
属性描述符对象中包含以下可选属性:
Vue2
把会修改原来数组的方法定义为变异方法。
变异方法例如 push、pop、shift、unshift、splice、sort、reverse
等,是无法触发 set
的。
非变异方法,例如 filter,concat,slice
等,它们都不会修改原始数组,而会返回一个新的数组。
Vue2
的做法是把这些变异方法重写来实现监听数组变化。Object.defineProperty
多数情况下要配合 Object.keys
和遍历,于是就多了一层嵌套。
并且由于遍历的原因,假如对象上的某个属性并不需要“劫持”,但此时依然会对其添加“劫持”。Object.defineProperty()
为止。Vue3 的响应式原理主要使用了 ES6 的 Proxy
代替了 Vue2 中的 Object.defineProperty()
,从而实现了更加高效和强大的数据劫持和响应式。
Proxy 对象可以通过对访问和修改数据的拦截来实现数据劫持。而 Reflect 对象则提供了更加灵活和易用的数据操作方法,比如可以使用 Reflect.has() 来检查对象是否有某个属性,使用 Reflect.defineProperty() 来代替 Object.defineProperty()。
具体实现原理如下:
data
对象通过使用 Proxy
对象进行代理,即使用 new Proxy(target, handler)
,其中 target
是被代理的对象,handler
是一个对象,用来定义代理 target
中的操作。
get
操作,代理对象 handler.get()
会被调用,进而让 handler
捕获该操作,并将当前的 Watcher
(观察者)对象加入到当前属性的依赖中。
set
操作,代理对象 handler.set()
会被调用,从而通知该属性下所有依赖的 ‘Watcher’ 对象更新;
Watcher
对象被通知后,会向对应的组件发送消息通知需要重新渲染视图,从而实现整个页面的更新。
Vue3 中使用 Proxy 对象实现数据响应式的代码如下:
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 是在 ES6 中新增的一个对象,用于代理另一个对象并拦截该对象的读取、赋值、属性定义等一系列操作,其语法如下:
new Proxy(target, handler)
Proxy中的参数:
handler 包含以下可选钩子函数(trap):
Proxy
可以直接监听对象而非属性;Proxy
可以直接监听数组的变化;Proxy
返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty
只能遍历对象属性直接修改;