vue3.0API详解

Vue 3.0 于 2020-09-18 发布了,使用了 Typescript 进行了大规模的重构,带来了 Composition API RFC 版本,类似 React Hook 一样的写 Vue,可以自定义自己的 hook ,让使用者更加的灵活。
为什么推出vue3.x?

一、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>

参数说明

  1. props: 组件传入的属性
  2. 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

  1. reactive() 函数接收一个普通对象,返回一个响应式的数据对象
  2. setup 中 return 出去,
  3. 直接在 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区别

  1. reactive用于处理对象的双向绑定,ref则处理js基础类型的双向绑定,
  2. reactive不能代理基本类型,例如字符串、数字、boolean等。

三、isRef() 、toRefs()、unref()

1、ref访问dom

通过 ref 来获取真实 dom 元素, 这个和 react 的用法一样,为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup()中声明一个 ref 并返回它

  1. 还是跟往常一样,在 html 中写入 ref 的名称
  2. 在steup 中定义一个 ref
  3. steup 中返回 ref的实例
  4. 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 函数用来侦听特定的数据源,并在回调函数中执行副作用。
  • 默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。
    1. 需要一个明确的数据资源
    2. 需要有一个回调函数
    3. 等到改变才会执行函数

监听用 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之前,监听的是所有数据的变化。

  1. 首次加载会立即执行
  2. 响应的最终所有依赖监听变化(数据改变)
  3. 在卸载onUnmounte时自动停止
  4. 执行stop就会停止监听,否则一直监听
  5. 异步函数先执行再去监听改变
<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中。
我们可以用包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。

使用

  1. index.html
    Dialog渲染的dom和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:
<div id="app"></div>
    <div id="dialog"></div>
</body>
  1. 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
  1. 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>

数据响应对比

响应方法

  1. vue2.x:object.defineProperty数据劫持
  2. vue3.x:使用ES6的proxy映射

vue2.x响应实现原理

  1. vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
  2. 关于VUE双向数据绑定核心: Object.defineProperty()
    1. 三个参数:
      • obj:要定义其上属性的对象
      • prop:要定义或修改的属性
      • descriptor:具体的改变方法
    2. 简单地说,就是用这个方法来定义一个值。当调用时我们使用了它里面的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 操作的方法
    1. 作为函数的delete操作符,相当于执行 delete target[name]。

      Reflect.deleteProperty(target, propertyKey)
        

    2. 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。

      Reflect.set(target, propertyKey, value[, receiver])
        

    3. 获取对象身上某个属性的值,类似于 target[name]。

      Reflect.get(target, propertyKey[, receiver])

    let p = Vue.reactive({name:'youxuan'});
    Vue.effect(()=>{ // effect方法会立即被触发
        console.log(p.name);
    })
    p.name = 'webyouxuan';; // 修改属性后会再次触发effect方法
    
  • 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?

  1. vue2.x问题:
    1. 存在更新对象类型时无法即时渲染。
    2. 需要用到$set或$forceUpdata强制刷新。
    3. vm.items[indexOfItem] = newValue这种是无法检测的
  2. 事实上,Object.defineProperty 本身是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能 / 体验的性价比考虑,放弃了这个特性。

那么Object.defineProperty 和 Proxy 对比存在哪些优缺点呢?

  1. 只能劫持对象的属性,而 Proxy 是直接代理对象。

    • Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。
    • Proxy 直接代理对象,不需要遍历操作。
  2. 对新增属性需要手动进行 Observe。

    • Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。
    • 也正是因为这个原因,使用 Vue 给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。

总结

  1. Object.defineProperty 并非不能监控数组下标的变化,Vue2.x 中无法通过数组索引来实现响应式数据的自动更新是 Vue 本身的设计导致的,不是 defineProperty 的锅。
  2. Object.defineProperty 和 Proxy 本质差别是,defineProperty 只能对属性进行劫持,所以出现了需要递归遍历,新增属性需要手动 Observe 的问题。
  3. Proxy 作为新标准,浏览器厂商势必会对其进行持续优化,但它的兼容性也是块硬伤,并且目前还没有完整的 polyfill 方案。
posted @   久宇诗  阅读(1557)  评论(2编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· 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工具
点击右上角即可分享
微信分享提示