vue3.0API详解
Vue 3.0 于 2020-09-18 发布了,使用了 Typescript 进行了大规模的重构,带来了 Composition API RFC 版本,类似 React Hook 一样的写 Vue,可以自定义自己的 hook ,让使用者更加的灵活。
为什么推出vue3.x?
-
2.x对ts支持不够友好(所有的属性都放在了this对象上,很难推断组件的数据类型)
-
2.x大量的API挂在到了Vue对象原型上,难以实现TreeShaking
-
2.x架构层面对跨平台 dom 渲染开发支持不友好
-
3.x采用CompositionAPI,受React Hook启发
-
3.x支持的template多个跟标签
-
3.x对虚拟DOM进行了从写,对模板编译进行了优化
一、setup
- setup 是Vue3.x新增的一个选项, 他是组件内使用 Composition API的入口。
- setup 函数会在 beforeCreate 之后、created 之前执行。
- vue3 取消了beforeCreate,created这两个钩子,统一用 setup 代替
- 该函数相当于一个生命周期函数,vue 中过去的 data,methods,watch 等全部都用对应的新增 api 写在 setup()函数中
<script>
export default {
components: {},
setup(props,context){
context.attrs
context.slots
context.parent
context.root
context.emit
context.refs
return {
}
}
}
</script>
参数说明
- props: 组件传入的属性
- context:attrs,emit,slots...
props
- setup中接受的props是响应式的, 当传入新的props 时,会及时被更新。
- 由于是响应式的, 所以不可以使用ES6解构,解构会消除它的响应式。需要结构可以用toRefs()
<template>
<div>
{{data}}
</div>
</template>
<script>
export default {
props:{
data:Number
},
components: {},
setup(props,con){
console.log(props.data)
return {}
}
}
</script>
context
- setup中不能访问Vue2中最常用的this对象
- 所以context中就提供了this中最常用的三个属性:attrs、slot 和emit,
- 分别对应Vue2.x中的:$attr属性、slot插槽 和$emit发射事件。
二、reactive、ref
- vue2.x中, 定义数据都是在data中,
- Vue3.x中, 使用reactive/ref来进行数据定义。
1、reactive
- reactive() 函数接收一个普通对象,返回一个响应式的数据对象
- setup 中 return 出去,
- 直接在 template 中调用即可
<template>
<div>
<!-- 3、调用 -->
<span>{{ user.name }}</span>
<span>{{ user.label }}</span>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
components: {},
setup(props, con) {
//1、定义
const user = reactive({
name: "夏利",
label: "",
});
//2、导出
return {
user,
};
},
};
</script>
不能直接对user进行结构, 这样会消除它的响应式, 这里就和上面我们说props不能使用ES6直接解构就呼应上了。
那我们就想使用解构后的数据怎么办?
解决办法就是使用toRefs。
return {
// 使用reRefs
...toRefs(user)
}
2、ref() 函数
ref() 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 value 属性, 只在 setup 函数内部访问 ref 函数需要加.value
<template>
<div class="mine">
{{count}} // 10
</div>
</template>
<script >
import { ref } from 'vue';
export default ({
setup() {
const count = ref(10)
// 在js 中获取ref 中定义的值, 需要通过value属性
console.log(count.value);
/这里ref取值需要加value
if (count.value > 5) {
console.log('牛逼')
} else {
console.log( "一般")
}
return {
count
}
}
});
</script>
reactive中访问ref
<template>
<div class="mine">{{ count }} -{{ t }}-{{ tD }} </div>
</template>
<script >
import { reactive, ref, toRefs } from "vue";
export default {
setup() {
const count = ref(10);
const obj = reactive({
t: 100,
count,
});
const tD = ref(obj.t);
return {
tD,
...toRefs(obj),
};
},
};
</script>
reactive与ref区别
- reactive用于处理对象的双向绑定,ref则处理js基础类型的双向绑定,
- reactive不能代理基本类型,例如字符串、数字、boolean等。
三、isRef() 、toRefs()、unref()
1、ref访问dom
通过 ref 来获取真实 dom 元素, 这个和 react 的用法一样,为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup()中声明一个 ref 并返回它
- 还是跟往常一样,在 html 中写入 ref 的名称
- 在steup 中定义一个 ref
- steup 中返回 ref的实例
- onMounted 中可以得到 ref的RefImpl的对象, 通过.value 获取真实dom
<template>
<!--第一步:还是跟往常一样,在 html 中写入 ref 的名称-->
<div class="mine" ref="elmRefs">
<span>1111</span>
</div>
</template>
<script >
import { set } from 'lodash';
import { onMounted, ref } from 'vue';
export default{
setup(props, context) {
// 获取真实dom
const elmRefs = ref(null);
onMounted (() => {
console.log(elmRefs.value); // 得到一个 RefImpl 的对象, 通过 .value 访问到数据
})
return {
elmRefs
}
}
}
</script>
2、isRef()
isRef() 用来判断某个值是否为 ref() 创建出来的对象
<script>
import { , isRef, ref } from 'vue';
export default ({
setup(props, context) {
const name= 'vue'
const age = ref(18)
console.log(isRef(age)); // true
console.log(isRef(name)); // false
return {
age,
name
}
}
});
</script>
3、toRefs()
- toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象
- 不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据
<template>
<div class="mine">
{{name}} // test
{{age}} // 18
</div>
</template>
<script >
import { reactive, ref, toRefs } from 'vue';
export default ({
setup(props, context) {
let state = reactive({
name: 'test'
});
const age = ref(18)
return {
...toRefs(state),
age
}
}
});
</script>
4、unref()
unRef() 用来判断某个值是否为 ref() 创建出来的对象有抛出该对象
setup(props, context) {
const name: string = 'vue'
const age = ref(18)
console.log(unRef(age)); // 18
console.log(unRef(name)); // vue
return {
age,
name
}
}
四、computed()
- 该函数用来创造计算属性,和过去一样,它返回的值是一个 ref 对象。
- 里面可以传方法,或者一个对象,对象中包含 set()、get()方法。
创建只读的计算属性
复制代码
import { computed, ref } from 'vue';
export default ({
setup(props, context) {
const age = ref(18)
// 根据 age 的值,创建一个响应式的计算属性 readOnlyAge,它会根据依赖的 ref 自动计算并返回一个新的 ref
const readOnlyAge = computed(() => age.value++) // 19
return {
age,
readOnlyAge
}
}
});
</script>
通过 set()、get()方法创建一个可读可写的计算属性
<template>
<div>
<p>refCount: {{refCount}}</p>
<p>计算属性的值computedCount : {{computedCount}}</p>
<button @click="refCount++">refCount + 1</button>
</div>
</template>
<script>
import { computed, ref } from '@vue/composition-api'
export default {
setup() {
const refCount = ref(1)
// 可读可写
let computedCount = computed({
// 取值函数
get: () => refCount.value + 1,
// 赋值函数
set: val => {
refCount.value = refCount.value -5
}
})
//触发get函数
console.log(computedCount.value)
// 为计算属性赋值的操作,会触发 set 函数
computedCount.value = 10
console.log(computedCount.value)
// 触发 set 函数后,count 的值会被更新
console.log(refCount.value)
return {
refCount,
computedCount
}
}
};
</script>
五、watch()与 watchEffect
1、watch
- watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。
- 默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。
- 需要一个明确的数据资源
- 需要有一个回调函数
- 等到改变才会执行函数
监听用 ref 声明的数据源
<script >
import { ref, watch } from 'vue';
export default ({
setup(props, context) {
const age = ref(10);
watch(age,
(nowValue,oldValue) =>{
console.log(age.value)
}
); // 100
// 4、ref简写
watch(age, () => {});
// 修改age 时会触发watch 的回调, 打印变更后的值
age.value = 100
return {
age
}
}
});
</script>
监听用 reactive 声明的数据源
监听对象获数组
- 第一个参数传入的 state 对象,
- 第二个参数是回调函数,
- 只要 state 中任意的属性发生改变都会执行回调函数,和 vue2 的区别是不要写 deep 属性,默认就是深度监听了。
<script >
import { reactive, toRefs, watch } from 'vue';
export default ({
setup(props, context) {
const state = reactive({ name: 'vue', age: 10 })
watch(
state,
(nowAge, oldAge) => {
}
)
// 修改age 时会触发watch 的回调, 打印变更前后的值
state.age = 100
return {
...toRefs(state)
}
}
});
</script>
监听对象或数组中某个值
现在是监听整个对象,当然我们也可以监听对象上的某个属性,注意下面代码的写法:第一个是回调函数,第二个参数也是回调函数。
<script >
import { reactive, toRefs, watch } from 'vue';
export default ({
setup(props, context) {
const state = reactive({ name: 'vue', age: 10 })
watch(
() => state.age,
(age, preAge) => {
console.log(age); // 100
console.log(preAge); // 10
}
)
// 修改age 时会触发watch 的回调, 打印变更前后的值
state.age = 100
return {
...toRefs(state)
}
}
});
</script>
同时监听多个值
setup(props, context) {
const state = reactive({ name: "vue", age: 10 });
const count = ref(87);
watch(
[() => state.name, () => state.age, count],
([newName, newAge, count], [oldName, oldAge, oldCount]) => {
console.log("111111111111111111111111");
console.log(newName, "names");
console.log("111111111111111111111111", oldCount);
console.log(newAge);
console.log(oldName);
console.log(oldAge);
}
);
// 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调
state.age = 100;
state.name = "vue3";
count.value += 1;
return {
...toRefs(state),
};
},
};
stop 停止监听
- 在 setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。
- 如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可(stop()),语法如下:
setup(props, context) {
const state = reactive({ name: 'vue', age: 10 })
const stop = watch(
[() => state.age, () => state.name],
([newName, newAge], [oldName, oldAge]) => {
console.log(newName);
console.log(newAge);
console.log(oldName);
console.log(oldAge);
}
)
// 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调
state.age = 100
state.name = 'vue3'
setTimeout(()=> {
stop()
// 此时修改时, 不会触发watch 回调
state.age = 1000
state.name = 'vue3-'
}, 1000) // 1秒之后讲取消watch的监听
return {
...toRefs(state)
}
}
2、watchEffect
该函数有点像update函数,但他执行在update与beforeuodate之前,监听的是所有数据的变化。
- 首次加载会立即执行
- 响应的最终所有依赖监听变化(数据改变)
- 在卸载onUnmounte时自动停止
- 执行stop就会停止监听,否则一直监听
- 异步函数先执行再去监听改变
<script>
import { watchEffect, ref, onMounted } from "vue";
export default {
components: {},
setup(props, con) {
const count = ref(0);
setTimeout(() => {
count.value = 1;
}, 2000);
const stop = watchEffect(
() => {
/*
* 1、首次加载会立即执行
* 2、响应的最终所有依赖监听变化(数据改变)
* 3、在卸载onUnmounte时自动停止
* 4、执行stop就会停止监听,否则一直监听
* 5、异步函数先执行再去监听改变
*/
},
{
// 6、在update之后执行
flush: "post",
// 同步执行
flush: "async",
}
);
setTimeout(() => {
stop();
}, 4000);
// 7、组件挂在ref
const myRef = ref(null);
// 避免监听时先见听到null 在监听到h1
onMounted(() => {
watchEffect(() => {
console.log(myRef.value);
});
});
// 8、debugging 开发模式使用
watchEffect(
() => {
console.log(count.value);
},
{
onTrack(e) {
// 监听到count和改变count
},
onTrigger(e) {
// count改变了会触发
},
}
);
return {
myRef,
count,
};
},
};
</script>
六、生命周期
-
新版的生命周期函数,可以按需导入到组件中,且只能在 setup() 函数中使用, 但是也可以在 setup 外定义, 在 setup 中使用
-
我们可以看到beforeCreate和created被setup替换了(但是Vue3中你仍然可以使用, 因为Vue3是向下兼容的, 也就是你实际使用的是vue2的)。
-
钩子命名都增加了on;
-
Vue3.x还新增用于调试的钩子函数onRenderTriggered和onRenderTricked
<script>
import {
defineComponent,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onRenderTracked,
onRenderTriggered,
} from "vue";
export default defineComponent({
// beforeCreate和created是vue2的
beforeCreate() {
console.log("------beforeCreate-----");
},
created() {
console.log("------created-----");
},
setup() {
onBeforeMount(()=> {
console.log('beformounted!')
})
onMounted(() => {
console.log('mounted!')
})
onBeforeUpdate(()=> {
console.log('beforupdated!')
})
onUpdated(() => {
console.log('updated!')
})
onBeforeUnmount(()=> {
console.log('beforunmounted!')
})
onUnmounted(() => {
console.log('unmounted!')
})
onErrorCaptured(()=> {
console.log('errorCaptured!')
})
},
});
</script>
七、vue 的全局配置
vue的config配置
通过 vue 实例上 config 来配置,包含 Vue 应用程序全局配置的对象。
您可以在挂载应用程序之前修改下面列出的属性:
const app = Vue.createApp({})
app.config = {...}
为组件渲染功能和观察程序期间的未捕获错误分配处理程序。
错误和应用程序实例将调用处理程序
app.config.errorHandler = (err, vm, info) => {}
全局定义属性 globalProperties
在项目中往往会全局定义公共属性或方法,方便我们组件间调用。
import { createApp } from 'vue'
import App from './App.vue'
import utils from './utils'
// vue2.0写法
let vue=new Vue()
vue.prototype.utils=utils
// vue3.0写法
const app=createApp(App)
app.config.globalProperties.utils=utils
app.mount('#app')
vue3.0中使用全局定义的属性
- 可以在组件用通过 getCurrentInstance() 来获取全局 globalProperties 中配置的信息,getCurrentInstance 方法获取当前组件的实例,
- 然后通过 ctx 属性获得当前上下文,这样我们就能在 setup 中使用 router 和 vuex, 通过这个属性我们就可以操作变量、全局属性、组件属性等等
<script>
import { getCurrentInstance } from "vue";
export default {
components: {},
setup(props, con) {
const { ctx } = getCurrentInstance();
console.log(ctx.utils);
},
};
</script>
八、自定义Hooks
一个实现加减的例子, 这里可以将其封装成一个hook, 我们约定这些「自定义 Hook」以 use 作为前缀,和普通的函数加以区分。
useCount.js
import {ref,computed} from 'vue'
export default function useCount(initValue=1) {
const count=ref(initValue)
const inCrearse=(delta)=>{
if(delta){
count.value+=delta
}else{
count.value+=1
}
}
const multiple=computed(()=>count.value*2)
return{
count,multiple,inCrearse
}
}
接下来看一下在组件中使用useCount这个 hook:
<template>
<div>
<p>count: {{ count }}</p>
<p>倍数: {{ multiple }}</p>
<div>
<button @click="increase()">加1</button>
<button @click="decrease()">减一</button>
</div>
</div>
</template>
<script>
import useCount from "../hooks/useCount";
export default {
components: {},
//con==context(attrs,emit,slots)
setup(props, con) {
const { count, multiple, increase, decrease } = useCount(10);
return {
count,
multiple,
increase,
decrease,
};
},
};
</script>
<style lang='scss' scoped>
</style>
九、Teleport自定义传送门
举例:我们希望继续在组件内部使用Dialog,又希望渲染的DOM结构不嵌套在组件的DOM中。
我们可以用
使用
- index.html
Dialog渲染的dom和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:
<div id="app"></div>
<div id="dialog"></div>
</body>
- Dialog.vue,
定义一个Dialog组件Dialog.vue, 留意 to 属性, 与上面的id选择器一致:
<template>
<teleport to="#dialog">
<div class="dialog">
<div class="dialog_wrapper">
<div class="dialog_header" v-if="title">
<slot name="header">
<span>{{title}}</span>
</slot>
</div>
</div>
<div class="dialog_content">
<slot></slot>
</div>
<div class="dialog_footer">
<slot name="footer"></slot>
</div>
</div>
</teleport>
</template
- Header.vue
最后在一个子组件Header.vue中使用Dialog组件,这里主要演示 Teleport的使用,不相关的代码就省略了。header组件
<div class="header">
...
<navbar />
+ <Dialog v-if="dialogVisible"></Dialog>
</div>
...
可以看到,我们使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与
同级,也就是在 body 下,但是 Dialog 的状态 dialogVisible 又是完全由内部 Vue 组件控制
十、Suspense 异步组件
在vue2.0前后端交互获取数据时, 是一个异步过程,一般我们都会提供一个加载中的动画,当数据返回时配合v-if来控制数据显示。
<div>
<div v-if="!loading">
...
</div>
<div v-if="loading">
加载中...
</div>
</div>
如果你使用过vue-async-manager这个插件来完成上面的需求, 你对Suspense可能不会陌生,Vue3.x感觉就是参考了vue-async-manager.
Suspense, 它提供两个template slot, 刚开始会渲染一个fallback状态下的内容, 直到到达某个条件后才会渲染default状态的正式内容, 通过使用Suspense组件进行展示异步渲染就更加的简单。如果使用 Suspense, 要返回一个promise 组件的使用:
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
其他
1、readonly 只读属性
在使用readonly后重新进行复制是不允许修改的,这个api不常用
setup(props,con){
const reactiveObj=reactive({
a:1,b:2,
c:{
d:3,e:4
}
})
const newReactiveObj=readonly(reactiveObj)
reactiveObj.a=10
console.log(reactiveObj.a)//10
newReactiveObj.a=30
console.log(newReactiveObj.a)//10
}
2、片段(Fragment)
- 在 Vue2.x 中, template中只允许有一个根节点:
- 但是在 Vue3.x 中,你可以直接写多个根节点, 是不是很爽:
<template>
<span></span>
<span></span>
</template>
3、vue3与vue2相比的一些变动
slot 具名插槽语法
在Vue3.0中将slot和slot-scope进行了合并。
// 子组件
<slot name="content" :data="data"></slot>
export default {
data(){
return{
data:["1234","2234","3234"]
}
}
}
<!-- 父组件中使用 -->
<template v-slot:content="scoped">
<div v-for="item in scoped.data">{{item}}</div>
</template>
<!-- 也可以简写成: -->
<template #content="{data}">
<div v-for="item in data">{{item}}</div>
</template>
数据响应对比
响应方法
- vue2.x:object.defineProperty数据劫持
- vue3.x:使用ES6的proxy映射
vue2.x响应实现原理
- vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
- 关于VUE双向数据绑定核心: Object.defineProperty()
- 三个参数:
- obj:要定义其上属性的对象
- prop:要定义或修改的属性
- descriptor:具体的改变方法
- 简单地说,就是用这个方法来定义一个值。当调用时我们使用了它里面的get方法,当我们给这个属性赋值时,又用到了它里面的set方法;
- 三个参数:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>双向数据绑定原理</title>
</head>
<body>
<h1>极简版的双向数据绑定</h1>
<input type="text" id="txt_id">
<p id="p_id"></p>
<script>
var obj = {}
Object.defineProperty(obj, 'newKey', { //这里的newKey相当于data里的属性
get: function () {
console.log('触发get操作')
return 'abd'
},
set: function (value) {
console.log('触发set操作')
document.getElementById('p_id').innerText = value
},
// value: 'pcm',
// writable: true
// 注意:get、set不能和value、writable等属性共同存在,原因如下:
// 如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为
// 是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将
// 会产生一个异常。 ---------取自MDN原话
})
console.log(obj.newKey)
document.addEventListener('keyup', function (e) {
let ev = e || event
obj.newKey = ev.target.value
})
</script>
</body>
</html>
发布订阅模式的方式来实现
class Subject {
constructor () {
this.state = 0
this.observes = []
}
getState () {
return this.state
}
setState (state) {
this.state = state
this.notifyAllObservers()
}
attach (observer) {
this.observes.push(observer)
}
notifyAllObservers () {
this.observes.forEach(observe => {
observe.update()
})
}
}
class Observer {
constructor (name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this)
}
update () {
console.log(`${this.name} update, state: ${this.subject.getState()}`)
}
}
let subject = new Subject()
let o1 = new Observer('o1', subject)
let o2 = new Observer('o2', subject)
subject.setState(2)
vue3.x响应实现原理
vue3.x响应原理详解
proxy
Reflect
map
set
首先熟练一下ES6中的 Proxy、Reflect 及 ES6中为我们提供的 Map、Set两种数据结构。
- Proxy
用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)语法:const p = new Proxy(target, handler)
参数:- target:目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
const handler = { get: function(obj, prop) { return prop in obj ? obj[prop] : 37; } }; const p = new Proxy({}, handler); p.a = 1; p.b = undefined; console.log(p.a, p.b); // 1, undefined console.log('c' in p, p.c); // false, 37
- Reflect
是一个内置的对象,它提供拦截 JavaScript 操作的方法- 作为函数的delete操作符,相当于执行 delete target[name]。
Reflect.deleteProperty(target, propertyKey)
- 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
Reflect.set(target, propertyKey, value[, receiver])
- 获取对象身上某个属性的值,类似于 target[name]。
Reflect.get(target, propertyKey[, receiver])
let p = Vue.reactive({name:'youxuan'}); Vue.effect(()=>{ // effect方法会立即被触发 console.log(p.name); }) p.name = 'webyouxuan';; // 修改属性后会再次触发effect方法
- 作为函数的delete操作符,相当于执行 delete target[name]。
- reactive方法实现
通过proxy 自定义获取、增加、删除等行为// 1、声明响应式对象 function reactive(target) { return createReactiveObject(target); } // 是否是对象类型 function isObject(target) { return typeof target === 'object' && target !== null; } // 2、创建 function createReactiveObject(target) { // 判断target是不是对象,不是对象不必继续 if (!isObject(target)) { return target; } const handlers = { get(target, key, receiver) { // 取值 console.log('获取') let res = Reflect.get(target, key, receiver); return res; }, set(target, key, value, receiver) { // 更改 、 新增属性 console.log('设置') let result = Reflect.set(target, key, value, receiver); return result; }, deleteProperty(target, key) { // 删除属性 console.log('删除') const result = Reflect.deleteProperty(target, key); return result; } } // 开始代理 observed = new Proxy(target, handlers); return observed; } let p = reactive({ name: 'youxuan' }); console.log(p.name); // 获取 p.name = 'webyouxuan'; // 设置 delete p.name; // 删除 ```
对比
为何替换掉Object.defineProperty?
- vue2.x问题:
- 存在更新对象类型时无法即时渲染。
- 需要用到$set或$forceUpdata强制刷新。
- vm.items[indexOfItem] = newValue这种是无法检测的
- 事实上,Object.defineProperty 本身是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能 / 体验的性价比考虑,放弃了这个特性。
那么Object.defineProperty 和 Proxy 对比存在哪些优缺点呢?
-
只能劫持对象的属性,而 Proxy 是直接代理对象。
- Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。
- Proxy 直接代理对象,不需要遍历操作。
-
对新增属性需要手动进行 Observe。
- Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。
- 也正是因为这个原因,使用 Vue 给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。
总结
- Object.defineProperty 并非不能监控数组下标的变化,Vue2.x 中无法通过数组索引来实现响应式数据的自动更新是 Vue 本身的设计导致的,不是 defineProperty 的锅。
- Object.defineProperty 和 Proxy 本质差别是,defineProperty 只能对属性进行劫持,所以出现了需要递归遍历,新增属性需要手动 Observe 的问题。
- Proxy 作为新标准,浏览器厂商势必会对其进行持续优化,但它的兼容性也是块硬伤,并且目前还没有完整的 polyfill 方案。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具