vue3的ref,reactive的使用和原理解析

news2024/11/20 7:12:07

目录

1.前言

2.比较

3.ref源码解析

4.reactive源码解析

createReactiveObject

handles的组成

get陷阱

set陷阱

5.总结


1.前言

vue3新增了ref,reactive两个api用于响应式数据,Ref 系列毫无疑问是使用频率最高的 api 之一,响应式意味着数据变动,页面局部自动更新。数据类型有基本数据类型(string,number,boolean,undfined,null,symbol),引用数据类型(object,array,set,map等)。如何精准检测跟踪js中所有的数据类型变动,并且能够达到vnode的对比后真实dom的渲染?vue中是如何做到的呢?简单实例如下:

import { reactive, ref } from "vue"; 

import type { Ref } from "vue";

// 定义响应式数据

const count: Ref<number> = ref(0);

function countClick() {

        count.value++; // 更新数据

}

// 定义引用类型数据标注

interface TypeForm {

        name: string;

        num: number;

        list?: Array<[]>;

}

const formInline: TypeForm = reactive({

        name: "",

        num: 0,

});

formInline.name = 'KinHKin'

formInline.num = 100

formInline.list = [1,2,3,4]

效果图:

 在线地址:

KinHKinhttps://rondsjinhuajin.github.io/DemoVue/#/但是,这只是简单的使用,配合了ts的类型标注,但是背后的原理是什么呢?🤔

2.比较

先来做个ref,reactive的比较:

比较维度(kin注)refreactive
是否响应式对象 JavaScript Proxy
创建的数据类型任何值类型的响应式响应式对象或数组
是否需要.value 属性
复杂的类型标注Ref 这个类型interface自定义
隐式地从它的参数中推导类型
dom的更新异步异步
是否深层次响应默认深层次默认深层次

不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。

ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性:

const obj = {
  foo: ref(1),
  bar: ref(2)
}

// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)

// 仍然是响应式的
const { foo, bar } = obj

简言之,ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中。 

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。下面是之前的计数器例子,用 ref() 代替:

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }} <!-- 无需 .value -->
  </button>
</template>

请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 

3.ref源码解析

对于vue3.2.2x版本的源码位于node_moudles/@vue/reactivity/dist/reactivity.cjs.js文件中

执行顺序是ref ->createRef ->new RefImpl 生成实例对象,提供get,set方法

源码中我们可以看到:入口有两个函数默认深层次响应ref,浅层次使用shallowRef,参数一个false,一个是true。

function ref(value) {
    return createRef(value, false);
}
function shallowRef(value) {
    return createRef(value, true);
}

 接下来就是走createRef这个方法:

function createRef(rawValue, shallow) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}

 这个createRef方法接受两个参数,一个是传入的基本类型的默认数值,一个是否是深层次响应的boolean值。

function isRef(r) {
    return !!(r && r.__v_isRef === true);
}

如果rawValue本就是ref类型的会立即返回rawValue,否则返回一个RefImpl实例。

 RefImpl类:

class RefImpl {
    constructor(value, __v_isShallow) {
        this.__v_isShallow = __v_isShallow;
        this.dep = undefined;
        this.__v_isRef = true;
        this._rawValue = __v_isShallow ? value : toRaw(value);
        this._value = __v_isShallow ? value : toReactive(value);
    }
    get value() {
        trackRefValue(this);
        return this._value;
    }
    set value(newVal) {
        const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
        newVal = useDirectValue ? newVal : toRaw(newVal);
        if (shared.hasChanged(newVal, this._rawValue)) {
            this._rawValue = newVal;
            this._value = useDirectValue ? newVal : toReactive(newVal);
            triggerRefValue(this, newVal);
        }
    }
}

RefImpl类在构造函数中,__v_isShallow表示是否是浅层次响应的属性, 私有的 _rawValue 变量,存放 ref 的旧值,_value是ref接受的最新的值。公共的只读变量 __v_isRef 是用来标识该对象是一个 ref 响应式对象的标记与在讲述 reactive api 时的 ReactiveFlag 相同。

在const toReactive = (value) => shared.isObject(value) ? reactive(value) : value;这个函数的内部判断是否传入的是一个对象,如果是一个对象就返回reactive返回代理对象,否则直接返回原参数。

当我们通过 ref.value 的形式读取该 ref 的值时,就会触发 value 的 getter 方法,在 getter 中会先通过 trackRefValue 收集该 ref 对象的 value 的依赖,收集完毕后返回该 ref 的值。

function trackRefValue(ref) {
    if (shouldTrack && activeEffect) {
        ref = toRaw(ref);
        {
            trackEffects(ref.dep || (ref.dep = createDep()), {
                target: ref,
                type: "get" /* TrackOpTypes.GET */,
                key: 'value'
            });
        }
    }
}

当我们对 ref.value 进行修改时,又会触发 value 的 setter 方法,会将新旧 value 进行比较,如果值不同需要更新,则先更新新旧 value,之后通过 triggerRefValue 派发该 ref 对象的 value 属性的更新,让依赖该 ref 的副作用函数执行更新。

function triggerRefValue(ref, newVal) {
    ref = toRaw(ref);
    if (ref.dep) {
        {
            triggerEffects(ref.dep, {
                target: ref,
                type: "set" /* TriggerOpTypes.SET */,
                key: 'value',
                newValue: newVal
            });
        }
    }
}

4.reactive源码解析

对于vue3.2.2x版本的源码位于node_moudles/@vue/reactivity/dist/reactivity.cjs.js文件中

整体描述vue3的更新机制:

在 Vue3 中,通过 track 的处理器函数来收集依赖,通过 trigger 的处理器函数来派发更新,每个依赖的使用都会被包裹到一个副作用(effect)函数中,而派发更新后就会执行副作用函数,这样依赖处的值就被更新了。

Proxy 对象能够利用 handler 陷阱在 get、set 时捕获到任何变动,也能监听对数组索引的改动以及 数组 length 的改动。

执行顺序是:reactive -> createReactiveObject ->

function reactive(target) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (isReadonly(target)) {
        return target;
    }
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}

第三行 isReadonly 函数 确定对象是否为只读对象,IS_READONLY key 确定对象是否为只读对象。ReactiveFlags 枚举会在源码中不断的与我们见面,所以有必要提前介绍一下 ReactiveFlags:

function isReadonly(value) {

return !!(value && value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */]);

}

export const enum ReactiveFlags {
  SKIP = '__v_skip', // 是否跳过响应式 返回原始对象
  IS_REACTIVE = '__v_isReactive', // 标记一个响应式对象
  IS_READONLY = '__v_isReadonly', // 标记一个只读对象
  RAW = '__v_raw' // 标记获取原始值
  IS_SHALLOW  = '__v_isShallow' // 是否浅层次拷贝
}

在 ReactiveFlags 枚举中有 5 个枚举值,这五个枚举值的含义都在注释里。对于 ReactiveFlags 的使用是代理对象对 handler 中的 trap 陷阱非常好的应用,对象中并不存在这些 key,而通过 get 访问这些 key 时,返回值都是通过 get 陷阱的函数内处理的。介绍完 ReactiveFlags 后我们继续往下看。

createReactiveObject

入参部分:

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {}

先看 createReactiveObject 函数的签名,该函数接受 5 个参数:

  • target:目标对象,想要生成响应式的原始对象。
  • isReadonly:生成的代理对象是否只读。
  • baseHandlers:生成代理对象的 handler 参数。当 target 类型是 Array 或 Object 时使用该 handler。
  • collectionHandlers:当 target 类型是 Map、Set、WeakMap、WeakSet 时使用该 handler。
  • proxyMap:存储生成代理对象后的 Map 对象。

这里需要注意的是 baseHandlers 和 collectionHandlers 的区别,这两个参数会根据 target 的类型进行判断,最终选择将哪个参数传入 Proxy 的构造函数,当做 handler 参数使用。

逻辑部分:

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    // 如何不是对象 曝出警告 返回其原始值
    if (!shared.isObject(target)) {
        {
            console.warn(`value cannot be made reactive: ${String(target)}`);
        }
        return target;
    }
    // target is already a Proxy, return it.
    // exception: calling readonly() on a reactive object

     // 如果目标已经是一个代理,直接返回  KinHKin译
    // 除非对一个响应式对象执行 readonly

    if (target["__v_raw" /* ReactiveFlags.RAW */] &&
        !(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
        return target;
    }
    // target already has corresponding Proxy
    // 目标已经存在对应的代理对象 KinHKin译
    const existingProxy = proxyMap.get(target);
    if (existingProxy) {
        return existingProxy;
    }
    // only specific value types can be observed.
    // 只有白名单里的类型才能被创建响应式对象  KinHKin译
    const targetType = getTargetType(target);
    if (targetType === 0 /* TargetType.INVALID */) {
        return target;
    }
    const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
    proxyMap.set(target, proxy);
    return proxy;
}

在该函数的逻辑部分,可以看到基础数据类型并不会被转换成代理对象,而是直接返回原始值。

并且会将已经生成的代理对象缓存进传入的 proxyMap,当这个代理对象已存在时不会重复生成,会直接返回已有对象。

也会通过 TargetType 来判断 target 目标对象的类型,Vue3 仅会对 Array、Object、Map、Set、WeakMap、WeakSet 生成代理,其他对象会被标记为 INVALID,并返回原始值。

当目标对象通过类型校验后,会通过 new Proxy() 生成一个代理对象 proxy,handler 参数的传入也是与 targetType 相关,并最终返回已生成的 proxy 对象。

所以回顾 reactive api,我们可能会得到一个代理对象,也可能只是获得传入的 target 目标对象的原始值。

handles的组成

在 @vue/reactive 库中有 baseHandlers 和 collectionHandlers 两个模块,分别生成 Proxy 代理的 handlers 中的 trap 陷阱。

例如在上面生成 reactive 的 api 中 baseHandlers 的参数传入了一个 mutableHandlers 对象,这个对象是这样的:

const mutableHandlers = {
    get,
    set,
    deleteProperty,
    has,
    ownKeys
};

通过变量名我们能知道 mutableHandlers 中存在 5 个 trap 陷阱。而在 baseHandlers 中,get 和 set 都是通过工厂函数生成的,以便于适配除 reactive 外的其他 api,例如 readonly、shallowReactive、shallowReadonly 等。

baseHandlers 是处理 Array、Object 的数据类型的,这也是我们绝大部分时间使用 Vue3 时使用的类型,所以笔者接下来着重的讲一下baseHandlers 中的 get 和 set 陷阱。

get陷阱

上一段提到 get 是由一个工厂函数生成的,先来看一下 get 陷阱的种类。

const get = /*#__PURE__*/ createGetter();
const shallowGet = /*#__PURE__*/ createGetter(false, true);
const readonlyGet = /*#__PURE__*/ createGetter(true);
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true);

get 陷阱有 4 个类型,分别对应不同的响应式 API,从名称中就可以知道对应的 API 名称,非常一目了然。而所有的 get 都是由 createGetter 函数生成的。所以接下来我们着重看一下 createGetter 的逻辑。

从函数的签名看起,入参有2个,一个是isReadonly,另一个是shallow,让使用 get 陷阱的 api 按需使用。

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) { }
}

函数内部返回一个get函数,使用了闭包的方式,将get函数中的参数传到handlers中。

createGetter 的逻辑:

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        // 如果key是响应式的对象  就返回不是只读  *KinHKin注释*
        if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
            return !isReadonly;
        }
        // 如果key是只读对象  就返回只读是true  *KinHKin注释*
        else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
            return isReadonly;
        }
        // 如果key是浅层次响应对象  就返回浅层次是true  *KinHKin注释*
        else if (key === "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */) {
            return shallow;
        }
        // 如果key是原始值对象并且改变的值和原始标记一致  就返回原始值  *KinHKin注释*
        else if (key === "__v_raw" /* ReactiveFlags.RAW */ &&
            receiver ===
                (isReadonly
                    ? shallow
                        ? shallowReadonlyMap
                        : readonlyMap
                    : shallow
                        ? shallowReactiveMap
                        : reactiveMap).get(target)) {
            return target;
        }
        // 判断传入的值是不是数组
        const targetIsArray = shared.isArray(target);
        // 如果不是只读 并且是数组
        // arrayInstrumentations 是一个对象,对象内保存了若干个被特殊处理的数组方法,并以键值对的形式存储。 *KinHKin注释*
        if (!isReadonly && targetIsArray && shared.hasOwn(arrayInstrumentations, key)) {
            // 特殊处理数组返回结果 
            return Reflect.get(arrayInstrumentations, key, receiver);
        }
        // 获取 Reflect 执行的 get 默认结果
        const res = Reflect.get(target, key, receiver);
        // 如果是 key 是 Symbol,并且 key 是 Symbol 对象中的 Symbol 类型的 key
        // 或者 key 是不需要追踪的 key: __proto__,__v_isRef,__isVue
        // 直接返回 get 结果 *KinHKin注释*
        if (shared.isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
            return res;
        }
        // 不是只读对象 执行 track 收集依赖 *KinHKin注释*
        if (!isReadonly) {
            track(target, "get" /* TrackOpTypes.GET */, key);
        }
        // 是浅层次响应 直接返回 get 结果 *KinHKin注释*
        if (shallow) {
            return res;
        }
        if (isRef(res)) {
            // ref unwrapping - skip unwrap for Array + integer key.
            // 如果是 ref ,则返回解包后的值 - 当 target 是数组,key 是 int 类型时,不需要解包 *KinHKin注释*
            return targetIsArray && shared.isIntegerKey(key) ? res : res.value;
        }
        if (shared.isObject(res)) {
            // Convert returned value into a proxy as well. we do the isObject check
            // here to avoid invalid value warning. Also need to lazy access readonly
            // and reactive here to avoid circular dependency.
            // 将返回的值也转换成代理,我们在这里做 isObject 的检查以避免无效值警告。
            // 也需要在这里惰性访问只读和星影视对象,以避免循环依赖。*KinHKin注释*
            return isReadonly ? readonly(res) : reactive(res);
        }
        // 不是 object 类型则直接返回 get 结果 *KinHKin注释*
        return res;
    };
}

从这段 createGetter 逻辑中,之前专门介绍过的 ReactiveFlags 枚举在这就取得了妙用。其实目标对象中并没有这些 key,但是在 get 中Vue3 就对这些 key 做了特殊处理,当我们在对象上访问这几个特殊的枚举值时,就会返回特定意义的结果。而可以关注一下 ReactiveFlags.IS_REACTIVE 这个 key 的判断方式,为什么是只读标识的取反呢?因为当一个对象的访问能触发这个 get 陷阱时,说明这个对象必然已经是一个 Proxy 对象了,所以只要不是只读的,那么就可以认为是响应式对象了。

get 的后续逻辑:

继续判断 target 是否是一个数组,如果代理对象不是只读的,并且 target 是一个数组,并且访问的 key 在数组需要特殊处理的方法里,就会直接调用特殊处理的数组函数执行结果,并返回。

arrayInstrumentations 是一个对象,对象内保存了若干个被特殊处理的数组方法,并以键值对的形式存储。

我们之前说过 Vue2 以原型链的方式劫持了数组,而在这里也有类似地作用,下面是需要特殊处理的数组。

  • 对索引敏感的数组方法
    • includes、indexOf、lastIndexOf
  • 会改变自身长度的数组方法,需要避免 length 被依赖收集,因为这样可能会造成循环引用
    • push、pop、shift、unshift、splice

下面的几个key是不需要被依赖收集或者是返回响应式结果的:

  • __proto__
  • _v_isRef
  • __isVue

在处理完数组后,我们对 target 执行 Reflect.get 方法,获得默认行为的 get 返回值。

之后判断 当前 key 是否是 Symbol,或者是否是不需要追踪的 key,如果是的话直接返回 get 的结果 res。

接着判断当前代理对象是否是只读对象,如果不是只读的话,则运行笔者上文提及的 tarck 处理器函数收集依赖。

如果是 shallow 的浅层响应式,则不需要将内部的属性转换成代理,直接返回 res。

如果 res 是一个 Ref 类型的对象,就会自动解包返回,这里就能解释官方文档中提及的 ref 在 reactive 中会自动解包的特性了。而需要注意的是,当 target 是一个数组类型,并且 key 是 int 类型时,即使用索引访问数组元素时,不会被自动解包。

如果 res 是一个对象,就会将该对象转成响应式的 Proxy 代理对象返回,再结合我们之前分析的缓存已生成的 proxy 对象,可以知道这里的逻辑并不会重复生成相同的 res,也可以理解文档中提及的当我们访问 reactive 对象中的 key 是一个对象时,它也会自动的转换成响应式对象,而且由于在此处生成 reactive 或者 readonly 对象是一个延迟行为,不需要在第一时间就遍历 reactive 传入的对象中的所有 key,也对性能的提升是一个帮助。

当 res 都不满足上述条件时,直接返回 res 结果。例如基础数据类型就会直接返回结果,而不做特殊处理。最后,get 陷阱的逻辑全部结束了。

set陷阱

set 也有一个 createSetter 的工厂函数,也是通过柯里化的方式返回一个 set 函数。

set 的函数比较简短,所以这次一次性把写好注释的代码放上来,先看代码再讲逻辑。

// 纯函数 默认深层次响应 函数不入参 *KinHKin*
const set = /*#__PURE__*/ createSetter();
// 纯函数 浅层次响应 函数入参是true *KinHKin*
const shallowSet = /*#__PURE__*/ createSetter(true);
function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        let oldValue = target[key];
        // 如果原始值是只读and是ref类型and新的value属性不是ref类型  直接返回
        if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
            return false;
        }
        if (!shallow) {
        // 如果新的值不是浅层次响应对象,也不是只读  更新旧值 新值为普通对象 *KinHKin*
            if (!isShallow(value) && !isReadonly(value)) {
                oldValue = toRaw(oldValue);
                value = toRaw(value);
            }
        // 当不是 只读 模式时,判断旧值是否是 Ref,如果是则直接更新旧值的 value
        // 因为 ref 有自己的 setter *KinHKin*
            if (!shared.isArray(target) && isRef(oldValue) && !isRef(value)) {
                oldValue.value = value;
                return true;
            }
        }
        // 判断 target 中是否存在 key *KinHKin*
        const hadKey = shared.isArray(target) && shared.isIntegerKey(key)
            ? Number(key) < target.length
            : shared.hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        // don't trigger if target is something up in the prototype chain of original
        // 如果目标是原始对象原型链上的属性,则不会触发 trigger 派发更新  *KinHKin*
        if (target === toRaw(receiver)) {
            // 使用 trigger 派发更新,根据 hadKey 区别调用事件
            if (!hadKey) {
                trigger(target, "add" /* TriggerOpTypes.ADD */, key, value);
            }
            else if (shared.hasChanged(value, oldValue)) {
                trigger(target, "set" /* TriggerOpTypes.SET */, key, value, oldValue);
            }
        }
        return result;
    };
}

在 set 的过程中会首先获取新旧与旧值,当目前的代理对象不是浅层比较时,会判断旧值是否是一个 Ref,如果旧值不是数组且是一个 ref类型的对象,并且新值不是 ref 对象时,会直接修改旧值的 value。

看到这里可能会有疑问,为什么要更新旧值的 value?如果你使用过 ref 这个 api 就会知道,每个 ref 对象的值都是放在 value 里的,而 ref 与 reactive 的实现是有区别的,ref 其实是一个 class 实例,它的 value 有自己的 set ,所以就不会在这里继续进行 set 了。

在处理完 ref 类型的值后,会声明一个变量 hadKey,判断当前要 set 的 key 是否是对象中已有的属性。

接下来调用 Reflect.set 获取默认行为的 set 返回值 result。

然后会开始派发更新的过程,在派发更新前,需要保证 target 和原始的 receiver 相等,target 不能是一个原型链上的属性。

之后开始使用 trigger 处理器函数派发更新,如果 hadKey 不存在,则是一个新增属性,通过 TriggerOpTypes.ADD 枚举来标记。这里可以看到开篇分析 Proxy 强于 Object.defineProperty 的地方,会监测到任何一个新增的 key,让响应式系统更强大。

如果 key 是当前 target 上已经存在的属性,则比较一下新旧值,如果新旧值不一样,则代表属性被更新,通过 TriggerOpTypes.SET 来标记派发更新。

在更新派发完后,返回 set 的结果 result,至此 set 结束。

5.总结

开始部分讲解了ref,reactive的使用实例,如何进行类型的标注,配合ts这么使用,接着讲解了两者的区别,分别需要注意的点,还有ref的顶层自动解包。

后面讲解了ref的底层实现的原理,源码的分析,ref是一个类,它有自己的get,set方法去更新。不需要依赖底层的所以使用于所有的数据类型做响应式。

为了让大家属性 Proxy 对响应式系统的影响,着重介绍了响应式基础 API:reactive。分析了 reactive 的实现,以及 reactive api 返回的 proxy 代理对象使用的 handlers 陷阱。并且对陷阱中我们最常用的 get 和 set 的源码进行分析,相信大家在看完本篇文章以后,对 proxy 这个 ES2015 的新特性的使用又有了新的理解。

本文只是介绍 Vue3 响应式系统的第一篇文章,所以 track 收集依赖,trigger 派发更新的过程没有详细展开,在后续的文章中计划详细讲解副作用函数 effect,以及 track 和 trigger 的过程,如果希望能详细了解响应式系统的源码,麻烦大家点个关注免得迷路。如果想继续追踪后续文章,也可以关注我的账号或 follow 我的github,感谢大家的关注❤️

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/408977.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端get/post等请求后,一直处于pending状态,解决办法

前端发送完请求发现network里的请求一直处于pending状态&#xff08;如图&#xff09; 或者等待过一段事件后会报错&#xff0c;如图 然后我尝试了一些解决办法&#xff0c;分享给大家&#xff0c;建议大家按照顺序来 1.首先排查是不是后端的问题 这个最重要&#xff0c;不然搞…

Vue3-使用axios发起网络请求

即使是小型项目也会涉及到请求后端API&#xff0c;除非你的网站展示的是一些不需要维护的静态数据&#xff0c;第三篇文章我们来给Vue项目搞上axios。 何为Axios &#xff1f;请看官方对Axios的描述&#xff0c;传送门:官方文档 Axios 是一个基于 promise 网络请求库&#xff0…

vue项目中使用md5加密、crypto-js加密、国密sm3、国密sm4

项目中涉及到一些加密解密的需求&#xff0c;了解并尝试了几种加密解密方法&#xff0c;以下&#xff1a; 方法一&#xff1a;md5加密 注意&#xff1a;md5的特性就是只能加密&#xff0c;所以用md5加密的时候&#xff0c;一定要记住你填写的内容&#xff0c;因为它是无法解密…

Vue el-menu-item实现路由跳转

场景&#xff1a;用了element-ui的el-menu 菜单 怎样实现路由跳转呢&#xff1f; 1&#xff0c;在el-menu加上router&#xff0c;添加el-menu的default-active属性&#xff0c;加&#xff1a;动态绑定&#xff0c;值设置为"this.$router.path" &#xff0c; 2&#x…

解决跨域Access to XMLHttpRequest at ‘http://localhost:8080/xxx’ from origin ‘http://localhost:63342

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

web前端-JavaScript中的forEach和map方法

&#x1f41a;作者简介&#xff1a;苏凉&#xff08;专注于网络爬虫&#xff0c;数据分析&#xff0c;正在学习前端的路上&#xff09; &#x1f433;博客主页&#xff1a;苏凉.py的博客 &#x1f310;系列总专栏&#xff1a;web前端基础教程 &#x1f451;名言警句&#xff1a…

vue项目,如何关闭eslint检测?多种解决办法

新版本vue项目&#xff0c;如何关闭eslint检测一、问题描述二、问题解决1、首先是比较旧的vue项目2、创建项目的时候&#xff0c;不要选eslint3、如果你使用的编辑软件是webstorm4、创建的项目没有webpack.base.conf.js文件&#xff0c;但是有 .eslintrc.js5、比较新的vue项目&…

vue3获取元素并修改元素样式

&#x1f525;&#x1f525;&#x1f525;欢迎关注csdn前端领域博主: 前端小王hs &#x1f525;&#x1f525;&#x1f525;email: 337674757qq.com &#x1f525;&#x1f525;&#x1f525;前端交流群&#xff1a; 598778642 需求&#xff1a;获取元素的样式并且修改元素样式…

猿创征文|我的前端学习之旅【来自一名大四老学长的真情流露】

猿创征文 | 我的前端学习之旅自我介绍我浑噩的大一大二&#xff08;是不是另一个你&#xff09;我的大三生活大三上&#xff08;学习过程、学习方法、推荐网站&#xff09;大三下&#xff08;技术提升、荣誉证书、推荐比赛&#xff09;我与 CSDN 的机缘&#xff08;从小白到创作…

【微信小程序】视图容器和基本内容组件

开发者可以通过运用组件快速搭建出页面结构&#xff0c;上一章也有对组件进行介绍&#xff0c;那么本文牛牛就来带大家学习小程序的组件。 我们可以将组件理解为微信内嵌的标签&#xff0c;它在小程序承担的作用与HTML的标签一致&#xff0c;不过组件的功能更加多样、具体。 事…

如何在UNI-APP内开发微信公众号(H5)JSSDK

参考文章 UNI-APP 开发微信公众号&#xff08;H5&#xff09;JSSDK 的使用方式 微信内H5使用JSSDK分享&#xff01;&#xff01;&#xff01;注意目前Hbuilderx2.3.7版本存在问题&#xff01;&#xff01;&#xff01; vue-router与location.href的用法区别 微信网页开发 JSSDK…

近四十场面试汇聚成的超全Web服务器面经总结

上期写了简历项目链接简历项目烂大街怎么办&#xff1f;教你最有谱的摆烂&#xff0c;有位读者照做以后&#xff0c;拿下了主管面&#xff0c;在群里宣传以后&#xff0c;最近多了不少小伙伴来催我更新服务器项目相关知识点。 这份总结是我之前秋招的时候&#xff0c;根据每次…

JS的事件介绍

JS的事件介绍 JS&#xff08;JavaScript&#xff09;是基于对象&#xff08;Object-based&#xff09;、事件驱动的脚本语言。 JS事件&#xff0c;就是用户或浏览器本身的某种行为&#xff0c;一般是用户对页面的一些动作引起的&#xff0c;例如&#xff0c;单击某个链接或按…

分享5款实用且免费的软件,让你轻松地完成任务

在日常工作和生活中&#xff0c;使用一些实用的小巧工具软件可以提高效率&#xff0c;让你更轻松地完成任务。 1.图像编辑——Paint.NET Paint.NET是一款免费的图像编辑软件&#xff0c;具有多种高级功能和插件支持。它支持多种文件格式、层、魔术棒、文本、画笔、形状、调整…

【C#+JavaScript+SQL Server】实现Web端在线考试系统 五:考试模块设计(附源码和资源)

需要源码请点赞关注收藏后评论区留言私信~~~ 一、考试模块概述 在线考试系统中的考试模块主要包括阅读考试规则&#xff0c;选择考试科目&#xff0c;随机抽取试题&#xff0c;答题计时器以及自动交卷并评分等功能 在开发考试系统过程中&#xff0c;需要考虑的一点是如何将试…

前端工程化详解——理解与实践前端工程化

前言&#xff1a; 前端工程化一直是一个老生常谈的问题&#xff0c;不管是面试还是我们在公司做基建都会经常提到前端工程化&#xff0c;那么为什么经常会说到前端工程化&#xff0c;并没有听过后端工程化、Java工程化或者Python工程化呢&#xff1f;我们理解的前端工程化是不是…

uniappvideo避坑指南(H5、小程序、app)

今天写下这篇文章已是蓄谋已久了&#xff0c;背景是年初接到项目需求开发基于H5运行的视频学习平台&#xff0c;以及后来uniapp转小程序app开发。相信开发过uniapp的video应该或多或少都遇到过一些坑&#xff0c;开场就不多说了&#xff0c;直接上干货。 项目要求用户不得拖动…

vue3引入router

1.添加vue-router依赖 进入项目路径的cmd下&#xff0c;执行命令 npm install vue-router4 或者yarn add vue-router4 推荐使用yarn命令&#xff0c;比npm安装更快 2.执行npm install重新加载依赖 3.在src文件夹下创建一个router文件夹 4.在router文件夹下创建index.js文件&…

JavaScript核心技术之JSON详解

JSON是什么&#xff1f; JSON&#xff08;JavaScript Object Notation, JS对象简谱&#xff09;是一种轻量级的数据交换格式。它基于 ECMAScript&#xff08;European Computer Manufacturers Association, 欧洲计算机协会制定的js规范&#xff09;的一个子集&#xff0c;采用…

undetected_chromedriver的使用

undetected_chromedriver是专门针对浏览器识别做出来的拓展 直接使用undetected_chromedriver第三方库 if __name__ __main__:from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom sel…