当前位置: 首页 > news >正文

Vue2+Vue3知识补充

defineProperty

一个对象默认的配置规则参数如下,通常都是为true。通过getOwnPropertyDescriptor方法查看

      let obj = {x: 10,};console.log(Object.getOwnPropertyDescriptor(obj, "x"));

在这里插入图片描述
当使用defineProperty定义一个对象中已经存在属性的配置项时。如果没有重新定义配置,那么还是以之前的配置参数为主。

      let obj = {x: 10,};Object.defineProperty(obj, "x", {writable: false,});console.log(Object.getOwnPropertyDescriptor(obj, "x"));

在这里插入图片描述
但是,如果往一个对象身上,使用defineProperty定义一个原本不存在的属性,那么默认的配置信息通常都是false

      Object.defineProperty(obj, "y", {value: 300,});

在这里插入图片描述
如果是直接往一个对象身上添加一个属性,那么该属性默认的配置通常都是true

      let o = {};o.x = 2;console.log(Object.getOwnPropertyDescriptor(o, "x"));

在这里插入图片描述

如果存在一个数组,这时候需要写一个公共的方法,那么会写到数组的原型身上。但是for/in循环会将自身的和原型身上可枚举的属性都处理。

      let arr = [1, 2, 3];for (let key in arr) {console.log(key, arr[key]);}
	  Array.prototype.myfun = () => {}; // 这样子新增一个元素,默认配置通常都是为true,如可枚举性

这就会造成了循环的时候将函数打印出来。会造成别人使用forin循环的时候出现不可预测的问题
在这里插入图片描述
正确做法应该是这样子,

      Object.defineProperty(Array.prototype, "myfun", {value: () => {},enumerable: false,});

数据劫持

定义一个obj对象,并对其进行数据劫持操作,在defineProperty中可以使用get,set两个函数,get函数用于读取的时候返回,set函数用于设置新参数。

      let obj = {x: 1,};

如下这种写法会造成死循环栈溢出。因此需要设置一个代理对象。一旦设置了set和get函数,则不能设置value和writable属性,否则报错

      Object.defineProperty(obj, "x", {get() {return obj.x;},set(val) {obj.x = val;},});
      let proxy = { ...obj };Object.defineProperty(obj, "x", {get() {return proxy.x;},set(val) {proxy.x = val;},});

在这里插入图片描述
凡是被代理的对象属性,都会在原对象身上添加get和set函数
在这里插入图片描述

new Vue针对数据做了哪些事情

Vue构造函数在实例化对象的时候会去执行initData方法进行初始化数据,会针对data中的数据进行响应式处理。凡是经过数据劫持操作的数据。每当set函数被触发,不仅会修改值,还会通知视图更新。实现数据驱动视图。如下是一些细节

      const fn = function () {};fn.num = 20;const reg = /\d+/;reg.xxx = 20;let vm = new Vue({data: {msg: "哈哈哈",num: 10,arr: [10, 20, [30, 40], { a: "A", b: "B" }],obj: {x: 10,y: {z: 20,},m: [100, 200],},forz: Object.freeze({ c: 100, d: "200" }),fn: fn,reg: reg,},});vm.$mount("#app");console.log(vm);

首先针对外围的这些属性,vue都会进行数据劫持
在这里插入图片描述
数组内部数据没有进行数据劫持操作,但是如果内部数据本身是一个普通对象,则会进行数据劫持
在这里插入图片描述

展开数据查看,发现对于普通数值类型,如msg等,其值不进行数据劫持操作,但是如普通对象类型,会针对内部数据进行深度递归监听和劫持操作,但是对象内部的数组本身还是会进行处理操作,数组内部数据会针对性的进行数据劫持
在这里插入图片描述
但是像一些函数,正则则不会进行数据监听和劫持
在这里插入图片描述
在这里插入图片描述
被冻结的对象内部数据则无法通过defineProperty无法实现数据劫持操作
在这里插入图片描述
凡是可以被枚举的非Symbol私有属性均可以被数据劫持
在这里插入图片描述
在底层中针对data中的数据,是通过Object.keys方法获取key值,那么非symbol类型,可枚举的私有属性。

定义如下数据添加到data中。验证不可枚举,发现内部y属性不可枚举,所以无法进行数据劫持操作

      let oo = { x: 1 };Object.defineProperty(oo, "y", {value: 20,enumerable: false,});

在这里插入图片描述

数组响应式处理

输出发现,数组中,并没有针对每一个索引进行响应式处理。展开其原型发现,vue帮助实现了一个原型,包含各种方法,用来实现数组响应式。而在该原型之上才是数组的原型。
在这里插入图片描述
下面给出部分测试案例

  • 直接根据索引修改数组的某一项无法实现响应式

在这里插入图片描述
在这里插入图片描述

  • 修改冻结的参数
    在这里插入图片描述

数据的更新处理

当实例化一个vm后,再次像对象中添加一个属性,默认是不进行数据劫持的 数据劫持发生在new Vue阶段,后序添加的数据无法实现劫持操作

      let vm = new Vue({data: {msg: "哈哈哈",num: 10,arr: [10, 20, [30, 40], { a: "A", b: "B" }],obj: {x: 10,y: {z: 20,},m: [100, 200],},forz: Object.freeze({ c: 100, d: "200" }),},});vm.obj.name = "12131231232"; // 没有被劫持,无法修改后页面渲染
  • 如果在new Vue的时候,data中obj对象没有name属性,那么可以使用vm.$set()设置新属性,并且进行了数据劫持操作
    在这里插入图片描述
    在这里插入图片描述

  • 如果是原obj中没有的数据,但是后来使用vm.obj.name添加的数据,之后使用$set再次设置,那么是无法对该属性进行数据拦截处理,但是页面会更新。 但是测试发现,在控制台写$set页面不会进行更新渲染
    在这里插入图片描述
    在这里插入图片描述

  • 如果是数组,采用$set设置对应索引的内容,vm.$set(vm.arr,0,100000)

  • vue中提供了$forceupdate强制更新vm.obj.name = "测试一下";vm.$forceUpdate()

数据响应式代码实现

如下是针对数组的主体部分处理

let classtype = {},toString = classtype.toString,hasOwnProperty = classtype.hasOwnProperty;function isPlainObject(obj) {let proto, ctor;if (!obj || toString.call(obj) !== "[object Object]") return false;proto = Object.getPrototypeOf(obj);//函数自身原型上存放构造器函数if (!proto) return true;ctor = hasOwnProperty.call(proto, "constructor") && proto.constructor;return typeof ctor === "function" && ctor === Object;
}//处理视图更新
function compiler() {console.log("视图更新");
}//给对象新增不可枚举的属性
function def(obj, key, value, enumerable) {Object.defineProperty(obj, key, {value,writable: true,configurable: true,enumerable: !!enumerable, //为假});
}let proto = {};
Object.setPrototypeOf(proto, Array.prototype);
["pop", "push", "reverse", "shift", "sort", "splice", "unshift"].forEach((name) => {def(proto, name, function mutator(...args) {//当前this就是指向调用者arr// 通过Array原型的push方法进行新增const res = Array.prototype[name].apply(this, args);compiler(); //通知视图编译return res;});}
);//数组处理
function observeArray(arr) {//需要实现vue提供的原型,该原型在指向Array原型Object.setPrototypeOf(arr, proto);
}//对象处理
function defineReactive(obj, key, val) {}function Observe(obj) {let isObject = isPlainObject(obj);let isArray = Array.isArray(obj);if (!isObject && !isArray) return obj;if (isArray) {//重写原型指向observeArray(obj);obj.forEach((item) => Observe(item));return obj;}if (isObject) {}
}

如下是实现一个基础的数组响应式处理。调用修改后的方法触发视图更新

let arr = [1, 2, 3, [4, 5, 6]];
Observe(arr);
console.log(arr);

在这里插入图片描述
在这里插入图片描述
但是这种情况并没有处理数组中新增数据的劫持,比如数组arr = [1,2]这时候新增一个{name:‘123’}作为第三个参数,那么该对象内部属性是需要做劫持操作。

["pop", "push", "reverse", "shift", "sort", "splice", "unshift"].forEach((name) => {def(proto, name, function mutator(...args) {//当前this就是指向调用者arr// 通过Array原型的push方法进行新增const res = Array.prototype[name].apply(this, args);//如果是数组中新增某个元素,如果比如将数组的第一个元素从0修改为{name:'123'}那么新对象中的数据需要劫持let inserted;switch (name) {case "push":case "unshift"://收集新数据inserted = args;break;case "splice":inserted = args.slice(2); //splice(0,0,2)第三个参数才是数据break;default:}if (Array.isArray) Observe(inserted);compiler(); //通知视图编译return res;});}
);

处理对象数据的劫持操作

function Observe(obj) {let isObject = isPlainObject(obj);let isArray = Array.isArray(obj);if (!isObject && !isArray) return obj;if (isArray) {//重写原型指向observeArray(obj);obj.forEach((item) => Observe(item));return obj;}//对象处理let keys = Object.keys(obj);let proxy = { ...obj };keys.forEach((key) => {defineReactive(obj, key, obj[key], proxy);//递归处理key所对应的元素值Observe(obj[key]);});return obj;
}
//对象处理
function defineReactive(obj, key, val, proxy) {//冻结的对象不需要处理(冻结的对象特性判断)let property = Object.getOwnPropertyDescriptor(obj, key);if (property && property.configurable === false) return;Object.defineProperty(obj, key, {get() {return proxy[key];},set(newVal) {if (newVal === proxy[key]) return;proxy[key] = Observe(newVal); //如果是修改的新值,如从10设置到{name:'123'}那么新值中的属性也应该是被数据劫持compiler();},});
}

v-on指令原理

对象中函数的两种绑定方法区别,省略写法是没有原型的
在这里插入图片描述
在new Vue阶段,会进行数据的初始化操作,除了针对data中的数据进行initdata操作,还针对methods中的方法进行initMethods操作。在该函数内部,会遍历methods配置项中的数据往vm实例身上绑定,同时还会再bind$1方法中修改this指向。
在这里插入图片描述
在这里插入图片描述
如下这段代码,都是给methods方法中添加方法,那么经过initmethods方法处理都会绑定到vm实例身上,但是在new阶段执行完毕初始化操作,直接往当前vm实例挂载方法,两者有什么区别。

      let vm = new Vue({data: {msg: "哈哈哈",},methods: {handle(e) {console.log("12312", this, e);},},});vm.handle2 = function (e) {console.log("12312", this, e);};vm.$mount("#app");

在这里插入图片描述
分别绑定输出查看。在vue配置项methods中写的函数,其this都指向了vm实例,但是初始化完毕后添加的方法,其this没有经过vue处理,还是指向window,那都是vm.handle2,为什么其this没指向vm实例,这涉及到事件绑定底层实现。’

      <button v-on:click="handle">{{msg}}</button><button v-on:click="handle2">{{msg}}</button>

在这里插入图片描述
vue底层对v-on是基于addEventListener处理,会将将v-on:click='fn'进行处理解析大致如下格式。然后会根据该格式对元素进行事件添加。

{name:'v-on',value:fn,args:['click']
}buttonDOM.addEventListener('click',function(){//处理额外操作,如修饰符,别名等//最后调用方法fn() //未进过vue处理的this指向window
})

v-pre

该指令用来优化渲染。控制vue在编译视图的时候,跳过该元素以及后代的编译,视图中怎么写,渲染结果就是什么,通常针对静态数据进行v-pre处理。在vue3中不需要手动处理,底层默认实现通过静态节点的编译

计算属性

计算属性是会被缓存使用的,其值的改变根据依赖项决定。在new Vue初始化的时候,执行initComputed$1函数进行computed计算属性配置项的处理。在该函数中,会先遍历计算属性的配置项并绑定到userDef属性身上。那么userDef可能是函数或者对象。根据情况获取其getter。
在这里插入图片描述

        computed: {x() {return 1231;},y: {get() {return "xixii1";},set(v) {console.log("我设置了啊", v);},},},

不是服务端渲染的话就会创建Watcher实例。同时将新的值添加到当前vm实例身上。
在这里插入图片描述

然后进入defineComputed函数中处理,优先判断是否服务端渲染。如果getter是函数则并且是浏览器端渲染,则会设置get属性,同时set函数就不应该支持修改,调用createComputedGetter函数,同时将set设置为一个noop,即对应的报错函数用于修改时候触发。如果是对象,则分别设置get和set对应的内容。
执行createComputedGetter函数会返回computedGetter函数,该函数是对计算属性的get进行劫持处理

计算属性内部的值是基于自身创建的新值,不像watch中监视的属性必须存在当vm实例中,如下代码,计算属性最终通过defineProperty将新属性添加到vm实例中。
在这里插入图片描述

watch监视属性

watch监视属性中,监视的属性必须是存在于当前vm实例中的属性。在new Vue的阶段,会去初始化initWatch函数,内部迭代配置项,若当前监视的属性是函数,则handler是函数,否则是对象

        watch: {n: {handler(n, o) {console.log(n, o);},immediate: true,},x(n, o) {console.log(n, o);},},

通过源码发现,针对监视的属性,可以将其值写成一个数组格式,如下

        watch: {n: [{handler(n, o) {console.log(n, o);},immediate: true,},],}

在这里插入图片描述
进入createWatcher函数执行,在该函数中处理相关逻辑,通过代码发现,监视的属性其所对应的值还可以是一个字符串,该字符串就是vm实力身上绑定的方法。
在这里插入图片描述

        watch: {n: "upd",},methods: {upd() {console.log("upd");},},

无论是计算属性或者是监视属性,其底层都是经过Watch实例的处理。

函数扁平化

如下这段代码是需要将多个函数的返回值依次作为结果传递给下一个函数,但是下面这种写法的话过于笨重,并且如果有多个函数需要处理,则需要手动将前一个函数的返回值包裹起来传递作为参数。

const a = (n) => n + 10;
const b = (n) => n - 10;
const c = (n) => n * 10;
const d = (n) => n / 10;
console.log(d(c(b(a(10)))));

如下两种写法

function compose(...args) {const funcs = args;return function handler(param) {let res = 0;funcs.forEach((func, index) => {if (index === 0) {res = func(param);} else {res = func(res);}});return res;};
}console.log(compose(a, b, c, d)(10));
function compose(...args) {const funcs = args;return function handler(param) {if (funcs.length === 0) return param; //reduce遍历空数组会报错return funcs.reduce((prev, item, index) => {return item(prev);}, param);};
}

如下是redux中都处理

function compose(...funcs) {if (funcs.length === 0) return (par) => par;if (funcs.length === 1) return funcs[0];//省略默认值的情况,a默认为第一个参数,b默认为第二个参数,这里是针对传入的函数进行逆序处理/* 第一次 a = a ,b = b , 返回函数地址 0X001 =...args=> a(b(args)), c(d(10))作为args参数,b替换为b,a在这里作为实际是第一个函数,a(b(c(d(10))))第二次 a=0x001 ,b=c,返回函数地址0x002 = ...args=> a(b(args)),  d(10)作为args参数。b替换为c,a(c(d(10)))返回上层作用域第三次 a=0x002 ,b=d,返回函数地址0x003 = ...args=> a(b(args)),  a(d(10)) //b替换d,返回上层作用域*/return funcs.reduce((a, b) =>(...args) =>a(b(args)));
}

生命周期函数

new Vue的时候会去执行Vue构造函数。在构造函数内部会优先去执行其原型上的_init初始化函数。
在这里插入图片描述
_init函数内部会按照vue的创建流程执行对应了函数,这些步骤大致对应vue的生命周期创建过程。

  1. 首先就是初始化initLifecycle函数,进行生命周期的初始化操作
  2. 其次执行initEvents函数,执行事件的初始化操作
  3. 然后就是执行initRender函数,在该函数中会初始化vm身上一些属性:如$slots,$attrs
  4. 往下就是到了 callHook$1执行生命周期函数beforeCreate。所以到这一步还没有进行到initState函数初始化数据等等,所以在beforeCreate生命周期函数中无法访问当vm实例中数据劫持的数据。
  5. 然后就是进入initInjections函数,类似react的创建上下文
  6. 执行initState函数,在该函数中创建数据的劫持操作,如初始化 initProps$1,initMethods,initData,initComputed$1,initWatch这些内容
  7. initProvide类似react的创建上下文
  8. 然后就是执行callHook$1函数执行created生命周期函数。在此期间可以访问vm实例身上的所有内容,
  9. 然后进入$mount函数,该函数在Vue原型上,主要是针对el或者template进行判断处理。$mount在源码中有两次地方进行了处理,分别赋予不同的函数。第一次进入该函数,会进入mountComponent函数中处理
  10. mountComponent函数中会 去调用beforeMount生命周期方法,做完后去执行mounted生命周期函数处理。但是在该函数内部会完成对beforeUpdate函数的监听处理,每次更新的时候触发都会进行判断是否为非初次挂载情况和销毁情况。
  11. 在一个名为queueWatcher的函数中创建一个监视更新的队列。更新的时候会进入flushSchedulerQueue函数,在该函数内部会去调用callUpdatedHooks更新的方法,并实现updated生命周期函数的触发处理。
  12. 最终queueWatcher函数中更新处理完毕后会,会触发nextTick,将其中的事件依次触发。
  13. 组件销毁可以通过vm.$destroy()或者组件失或销毁。都会去触发原型上的$destroy方法。在该方法内部会触发beforeDestroydestroyed生命周期函数,同时将_isDestroyed属性设置为true,同时将部分数据进行清空处理,但是数据的劫持还存在,所以为了防止数据更新,下次尝试视图更新的时候则会根据判断不会进入视图更新阶段。视图不会销毁,并且会进行事件解绑操作,但是部分初始化的数据依旧保留
  14. callHook$1在触发生命周期的函数内部,都会去调用setCurrentInstance方法将当前生命周期函数的this修改为当前vm实例
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

nextTick

有如下一段代码,了解vue的更新机制,首先需要知道,在vue中更新一个状态值后,可以立即获取到最新的内容。但是通知视图这件事情是放到异步队列中等待。待到该上下文中的代码执行完毕后,进行一次批处理操作。所以在下面的代码中,修改x了状态值后可以立即获取最新的内容,同时将本次状态更新通知视图的操作放到队列中,执行y的修改,把通知视图更新的操作放到队列中,最后进行一次批处理更新,updated生命周期函数执行一次。

      <button @click="handle1">{{x}}</button>
      let vm = new Vue({data: {x: 1,y: 1,},methods: {handle1() {this.x++;console.log(this.x);this.y++;},},updated() {console.log("update");},});

在这里插入图片描述
defineReactive定义响应式数据的时候,在set中函数中会执行如下notify函数,本质就是同步将数据修改,但是会将更新视图的操作放入一个更新队列sub.update();

                  dep.notify({type: "set" /* TriggerOpTypes.SET */,target: obj,key: key,newValue: newVal,oldValue: value});

在这里插入图片描述
即使使用$forceUpdate函数进行强制更新,视图也只会渲染一次,因为该函数底层也是去调用.update()函数将视图更新的操作放入队列中。

          handle1() {this.x++;console.log(this.x);this.$forceUpdate();this.y++;},

区分react的更新原理,react是当前将更新状态的操作和更新视图的操作都放到了异步队列中,因此在后续上下文中无法获取最新的状态值。

如果需要视图更新两次,就需要放入异步操作中。当vue执行的时候首先遇见x同步更新,然后将当前视图更新操作放入异步队列,然后当前上下文中没有同步代码后执行一次批处理操作,视图更新一次。然后定时器执行,y的值同步修改,然后更新操作放入队列中,上下文中没有内容后执行视图更新。setTimeout是放到宏队列,queueMicrotask是放到微队列

          handle1() {this.x++;console.log(this.x);setTimeout(() => {this.y++;});},
          handle1() {this.x++;console.log(this.x);queueMicrotask(() => {this.y++;});},

在这里插入图片描述
所以这种异步的方式可以使用$nextTick函数完成相应个功能,$nextTick第二个参数用来指定第一个函数的this指向。首先会把传入的函数放入一个callbacks队列中。会由flushCallbacks函数内部处理。将对应的callbacks回调放入如下优先级中处理形成异步任务。Promise > MutationObserver > setImmediate > setTimeout并且$nextTick操作是在updated生命周期执行完毕后执行其回调队列,因此在这里可以获取最新的DOM内容。这种模式就是发布者订阅模式

如下代码中,x的值同步修改后将视图更新的操作放到异步队列中,同时执行$nextTick函数,将内部函数放入callbacks异步队列中。然后视图执行一次更新操作。完成updated生命周期后,执行callbacks中的函数。

          handle1() {this.x++;this.$nextTick(() => {console.log(this);});},

在这里插入图片描述

Vue3

新特性

性能提升

  • 重写了虚拟DOM的实现,vue2是基于vue-template-compiler实现视图编译。该编译器无论是动态节点或是静态节点都需要重新编译处理。所以通常需要基于v-pre/v-once进行优化
  • vue3中基于@vue/compiler-sfc实现对template视图的编译,该编译器会跳过静态节点,只处理动态节点。因此性能提升1.3-2倍,服务器端性能提升2-3倍。
  • Tree shaking:vue3采用按需打包,基于vite实现,只针对使用到的内容打包,降低打包后的体积。
  • vue3基于proxy实现响应式劫持,性能更高

功能提升

  • 提供了Teleport组件实现传送门效果,可以将编译的内容放到除app容器以外的地方
    在vue中,默认编写的内容都是存放到app容器中,但是有一些需求如全局消息提示等,容器应该放到body中。这个时候可以使用Teleport组件完成。
<template><h1>我是放到app容器中的</h1><Message />
</template>Message组件
<template><Teleport to="body"><div>我是消息组件放到body中</div></Teleport>
</template>

在这里插入图片描述

  • 提供了Suspense异步组件,等待一个组件异步处理完毕后,再渲染组件,可以实现组件一次渲染呈现数据,可以在组件异步加载期间,使用插槽fallback显示默认信息。
    如下代码,在list中进行了异步处理,但是这种List视图中的代码会被渲染两次,第一次进入if判断显示骨架屏,然后经过异步完成后,进入else判断显示数据
<template><h1>我是放到app容器中的</h1><List />
</template>
<template><div v-if="list.length === 0">默认骨架屏</div><ul v-else><li v-for="item in list" :key="item">{{ item }}</li></ul>
</template>
<script setup>
import { onMounted, ref } from "vue";
const list = ref([]);
function delay() {return new Promise((resolve, reject) => {setTimeout(() => {resolve([1, 2, 3]);}, 2000);});
}onMounted(async () => {list.value = await delay();
});
</script>

但是有的时候可能会出现这样子的代码,这个时候就发现,组件在视图中并没有渲染成功。这个时候就需要异步组件Suspense包裹处理。

  <ul><li v-for="item in list" :key="item">{{ item }}</li></ul><script setup>
import { ref } from "vue";
const list = ref([]);
function delay() {。。。}
list.value = await delay();
</script>

实际组件只会渲染一次,等待异步处理完成后,进行视图渲染

  <Suspense><List /><template #fallback><div>默认显示内容</div></template></Suspense>
  • 更好的支持Typescript
  • 支持custom render api可以自定义渲染api,用户可以尝试webgl自定义渲染

语法变革

  • vue3支持vue2语法,并且视图支持多个根节点
  • 采用组合式api替换配置式api
  • 采用函数式编程,去除类的概念,所以函数都是解构出来的,没有this指向问题。

Vue3 响应式

vue3中基于Proxy实现响应式处理,使用Proxy可以针对数组或者对象直接进行劫持,并且不需要对内部的数据进行遍历劫持操作,可以直接数据劫持一个对象,针对代理对象的所有操作,如新增,修改,读取,都可以做一个劫持操作,而defienProperty却只有get/set两步劫持操作。

      let obj = {name: "12312",age: 12,};let proxy = new Proxy(obj, {get(target, val) {console.log("get", target, val);return target.val;},set(target, key, val) {console.log("set", target, key, val);target[key] = val;},deleteProperty(target, key) {console.log("deleteproperty", target, key);return Reflect.deleteProperty(target, key);},});

reactive源码

reactive函数执行的时候传入一个对象,那么进入该函数的时候首先会进行只读判断,接着就是调用createReactiveObject函数,第二参数是处理非只读的情况。mutableHandlers是需要做劫持数据的配置对象。这里可以发现,vue3会比vue2多做几处劫持处理。

let proxy = reactive(data);
const mutableHandlers = {get: function get2(target, key) {...},set: function set2(target, key, value) {...};deleteProperty: function deleteProperty(target, key) {...},has: function has$1(target, key) {...},ownKeys: function ownKeys(target) {...},
};function reactive(target) {if (isReadonly(target)) {return target;}return createReactiveObject(target, false, mutableHandlers);
}
function createReactiveObject(target, isReadonly2, baseHandlers) {....
}

createReactiveObject函数内部上来就会针对传递进来的对象进行处理判断是否为普通对象或数组

const isObject = (val) => val !== null && typeof val === "object";function createReactiveObject(target, baseHandlers) {if (!isObject(target)) {{console.warn(`value cannot be made reactive: ${String(target)}`);}return target;}//判断是否是代理过的属性const existingProxy = proxyMap.get(target);if (existingProxy) {return existingProxy;}//根据targetTypeMap获取传递参数的类型const targetType = getTargetType(target);if (targetType === 0 /* INVALID */) {return target;}//如果是数组或者对象类型则进入我们传入的proxy代理类型处理const proxy = new Proxy(target,targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);proxyMap.set(target, proxy);return proxy;
}

在这里插入图片描述
然后就是get劫持函数,在get劫持中,会先针对数组进行处理,判断是否使用到数组的方法,如push等

const isArray = Array.isArray;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
const arrayInstrumentations = createArrayInstrumentations();
function createArrayInstrumentations() {const instrumentations = {};["includes", "indexOf", "lastIndexOf"].forEach((key) => {instrumentations[key] = function (...args) {const arr = toRaw(this);for (let i = 0, l = this.length; i < l; i++) {track(arr, "get", i + "");}const res = arr[key](...args); //执行方法传入参数if (res === -1 || res === false) {return arr[key](...args.map(toRaw)); //不存在的话进行特殊处理} else {return res; //返回方法执行的结果}};});["push", "pop", "shift", "unshift", "splice"].forEach((key) => {instrumentations[key] = function (...args) {pauseTracking();const res = toRaw(this)[key].apply(this, args);resetTracking(); //触发视图更新return res;};});return instrumentations;
}
  get: function get2(target, key) {const targetIsArray = isArray(target);if (!isReadonly2) {//判断读取,修改数据是否是采用数组方法完成的。if (targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key); }if (key === "hasOwnProperty") {return hasOwnProperty;}}const res = Reflect.get(target, key);if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res;}if (!isReadonly2) {track(target, "get", key);}if (shallow) {return res;}if (isRef(res)) {return targetIsArray && shared.isIntegerKey(key) ? res : res.value;}if (isObject(res)) { //如果在get读取监视对象的某一个值,发现该值所对应的value是一个对象,那么会递归进去进行劫持//vue2是上来直接将所有的数据直接做响应式处理,而vue3初始情况是只对数据最外层进行响应式处理,数据内部的数据//是当被读取到的时候才进行递归响应式处理。所以在reactive函数内部会判断当前数据是否被代理过return isReadonly2 ? readonly(res) : reactive(res);}return res;},

在set函数中代码如下,会设置一个值,并进行新旧值判断,然后会判断是添加还是修改一个值

  set: function set2(target, key, value) {const result = Reflect.set(target, key, value);let oldValue = target[key];if (target === toRaw(receiver)) {if (!hadKey) {trigger(target, "add", key, value);} else if (shared.hasChanged(value, oldValue)) {trigger(target, "set", key, value, oldValue);}}return result;},

v-model

现有一个父组件使用子组件信息如下,使用v-model绑定arr数据传递。在子组件内部接受数据并渲染。但是使用v-model进行数据绑定的时候,可以在子组件内部使用update:变量的方法触发事件更新。从而直接触发父组件数据更新,从而重新传递给子组件。

const arr = ref([1, 2, 3]);
<List v-model:arr="arr" />
<template><div v-for="item in props.arr" :key="item">{{ item }}</div><button @click="add">修改</button>
</template><script setup>
import { ref } from "vue";
let props = defineProps(["arr"]);
let emits = defineEmits();
function add() {emits("update:arr", [1, 2, 3, 4, 5, 6, 7]);
}

在这里插入图片描述
在这里插入图片描述
但是这种情况如果是reactive定义的响应式数据则不行

相关文章:

  • Python爬取城市空气质量数据
  • CMake的学习之路
  • nmap工具使用
  • 如何成为嵌入式系统工程师?
  • 解决生产问题的万能接口(Java编译器API的使用)
  • OA协同办公系统 iWebPDF插件安装
  • 字符串循环遍历抵消、队列的应用-649. Dota2 参议院
  • (delphi11最新学习资料) Object Pascal 学习笔记---第14章泛型第2节(泛型类的类构造函数)
  • 「C系列」C enum(枚举)
  • go-zero整合Excelize并实现Excel导入导出
  • pytest+requests+allure自动化测试接入Jenkins学习
  • uniapp地图选择位置
  • docker部署redis实践
  • `kubectl get pod -oyaml` 和 `kubectl describe pod`
  • C#面:什么是 Windows 服务,它的生命周期与标准的 EXE 程序有什么不同
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • HashMap ConcurrentHashMap
  • Javascript 原型链
  • JavaScript工作原理(五):深入了解WebSockets,HTTP/2和SSE,以及如何选择
  • javascript数组去重/查找/插入/删除
  • leetcode388. Longest Absolute File Path
  • Material Design
  • PHP 的 SAPI 是个什么东西
  • TypeScript迭代器
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 从零开始的无人驾驶 1
  • 大快搜索数据爬虫技术实例安装教学篇
  • 基于游标的分页接口实现
  • 技术发展面试
  • 警报:线上事故之CountDownLatch的威力
  • 每天10道Java面试题,跟我走,offer有!
  • 入门到放弃node系列之Hello Word篇
  • 删除表内多余的重复数据
  • 数据科学 第 3 章 11 字符串处理
  • 网页视频流m3u8/ts视频下载
  • 翻译 | The Principles of OOD 面向对象设计原则
  • 进程与线程(三)——进程/线程间通信
  • 昨天1024程序员节,我故意写了个死循环~
  • ​Java基础复习笔记 第16章:网络编程
  • # linux 中使用 visudo 命令,怎么保存退出?
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (C++二叉树05) 合并二叉树 二叉搜索树中的搜索 验证二叉搜索树
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (转)程序员疫苗:代码注入
  • ****** 二 ******、软设笔记【数据结构】-KMP算法、树、二叉树
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .dwp和.webpart的区别
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET 8.0 中有哪些新的变化?
  • .NET Core 网络数据采集 -- 使用AngleSharp做html解析
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置