深入聊一聊vue3中的reactive()

news2024/11/19 20:38:09

 

在vue3的开发中,reactive是提供实现响应式数据的方法。日常开发这个是使用频率很高的api。这篇文章笔者就来探索其内部运行机制。小白一枚,写得不好请多多见谅。

调试版本为3.2.45

  • 什么是reactive?

    reactive是Vue3中提供实现响应式数据的方法.

    在Vue2中响应式数据是通过defineProperty来实现的.

    而在Vue3响应式数据是通过ES6的Proxy来实现的

  • reactive注意点

    reactive参数必须是对象(json/arr)

    如果给reactive传递了其他对象,默认情况下修改对象,界面不会自动更新,如果想更新,可以通过重新赋值的方式。

<script setup>
import {reactive} from 'vue'
const data = reactive({ //定义对象
  name:'测试',
  age:10
})
const num = reactive(1)//定义基本数据类型
console.log(data)//便于定位到调试位置
</script>
<template>
<div>
<h1>{{ data.name }}</h1>
</div>
</template>
<style scoped></style>

 设置断点

 

 

开始调试

接下来我们可以开始调试了,设置好断点后,只要重新刷新页面就可以进入调试界面。

复杂数据类型

我们先调试简单的基本数据类型

 

/*1.初始进来函数,判断目标对象target是否为只读对象,如果是直接返回*/
function reactive(target) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (isReadonly(target)) {
        return target;
    }
    //创建一个reactive对象,五个参数后续会讲解
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
/*2.判断是来判断target是否为只读。*/
function isReadonly(value) {
    return !!(value && value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */]);
}
/*3.创建一个reactive对象*/
/*createReactiveObject接收五个参数:
target被代理的对象,
isReadonl是不是只读的,
baseHandlers proxy的捕获器,
collectionHandlers针对集合的proxy捕获器,
proxyMap一个用于缓存proxy的`WeakMap`对象*/
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
//如果target不是对象则提示并返回
/*这里会跳转到如下方法
判断是否原始值是否为object类型 
const isObject = (val) => val !== null && typeof val === 'object';
*/
    if (!isObject(target)) {
        if ((process.env.NODE_ENV !== 'production')) {
            console.warn(`value cannot be made reactive: ${String(target)}`);
        }
        return target;
    }
    // 如果target已经是proxy是代理对象则直接返回.
    if (target["__v_raw" /* ReactiveFlags.RAW */] &&
        !(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
        return target;
    }
    // 从proxyMap中获取缓存的proxy对象,如果存在的话,直接返回proxyMap中对应的proxy。否则创建proxy。
    const existingProxy = proxyMap.get(target);
    if (existingProxy) {
        return existingProxy;
    }
    // 并不是任何对象都可以被proxy所代理。这里会通过getTargetType方法来进行判断。
    const targetType = getTargetType(target);
    //当类型值判断出是不能代理的类型则直接返回
    if (targetType === 0 /* TargetType.INVALID */) {
        return target;
    }
    //通过使用Proxy函数劫持target对象,返回的结果即为响应式对象了。这里的处理函数会根据target对象不同而不同(这两个函数都是参数传入的):
    //Object或者Array的处理函数是collectionHandlers;
    //Map,Set,WeakMap,WeakSet的处理函数是baseHandlers;
    const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
    proxyMap.set(target, proxy);
    return proxy;
}

getTargetType方法调用流程

//1.进入判断如果value有__v_skip属性且为true或对象是可拓展则返回0,否则走类型判断函数
function getTargetType(value) {
//Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
    return value["__v_skip" /* ReactiveFlags.SKIP */] || !Object.isExtensible(value)
        ? 0 /* TargetType.INVALID */
        : targetTypeMap(toRawType(value));
}
//2.这里通过Object.prototype.toString.call(obj)来判断数据类型
const toRawType = (value) => {
    // extract "RawType" from strings like "[object RawType]"
    return toTypeString(value).slice(8, -1);
};
const toTypeString = (value) => objectToString.call(value);
//3.这里rawType是为'Object'所以会返回1
function targetTypeMap(rawType) {
    switch (rawType) {
        case 'Object':
        case 'Array':
            return 1 /* TargetType.COMMON */;
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return 2 /* TargetType.COLLECTION */;
        default:
            return 0 /* TargetType.INVALID */;//返回0说明除前面的类型外其他都不能被代理,如Date,RegExp,Promise等
    }
}

 

createReactiveObject方法中const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);这一条语句中,第二个参数判断target是否为Map或者Set类型。从而使用不同的handler来进行依赖收集。

在调试的文件node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js中,我们从reactive函数的createReactiveObject函数调用的其中两个参数mutableHandlersmutableCollectionHandlers开始往上查询

mutableHandlers的实现

const mutableHandlers = {
    get,// 获取值的拦截,访问对象时会触发
    set,// 更新值的拦截,设置对象属性会触发
    deleteProperty,// 删除拦截,删除对象属性会触发
    has,// 绑定访问对象时会拦截,in操作符会触发
    ownKeys// 获取属性key列表
};
function deleteProperty(target, key) {
    // key是否是target自身的属性
    const hadKey = hasOwn(target, key);
    // 旧值
    const oldValue = target[key];
    // 调用Reflect.deleteProperty从target上删除属性
    const result = Reflect.deleteProperty(target, key);
    // 如果删除成功并且target自身有key,则触发依赖
    if (result && hadKey) {
        trigger(target, "delete" /* TriggerOpTypes.DELETE */, key, undefined, oldValue);
    }
    return result;
}
//
function has(target, key) {
    //检查目标对象是否存在此属性。
    const result = Reflect.has(target, key);
    // key不是symbol类型或不是symbol的内置属性,进行依赖收集
    if (!isSymbol(key) || !builtInSymbols.has(key)) {
        track(target, "has" /* TrackOpTypes.HAS */, key);
    }
    return result;
}
/*ownKeys可以拦截以下操作:
1.Object.keys()
2.Object.getOwnPropertyNames()
3.Object.getOwnPropertySymbols()
4.Reflect.ownKeys()操作*/
function ownKeys(target) {
    track(target, "iterate" /* TrackOpTypes.ITERATE */, isArray(target) ? 'length' : ITERATE_KEY);
    return Reflect.ownKeys(target);
}

 get方法实现

const get = /*#__PURE__*/ createGetter();
/*传递两个参数默认都为false
isReadonly是否为只读
shallow是否转换为浅层响应,即Reactive---> shallowReactive,shallowReactive监听了第一层属性的值,一旦发生改变,则更新视图;其他层,虽然值发生了改变,但是视图不会进行更新
*/
function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
    //1.是否已被reactive相关api处理过;
        if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
            return !isReadonly;
        }
     //2.是否被readonly相关api处理过
        else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
            return isReadonly;
        }
        else if (key === "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */) {
            return shallow;
        }
      //3.检测__v_raw属性
        else if (key === "__v_raw" /* ReactiveFlags.RAW */ &&
            receiver ===
                (isReadonly
                    ? shallow
                        ? shallowReadonlyMap
                        : readonlyMap
                    : shallow
                        ? shallowReactiveMap
                        : reactiveMap).get(target)) {
            return target;
        }
      //4.如果target是数组,且命中了一些属性,则执行函数方法
        const targetIsArray = isArray(target);
        if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
            return Reflect.get(arrayInstrumentations, key, receiver);
        }
      //5.Reflect获取值
        const res = Reflect.get(target, key, receiver);
      //6.判断是否为特殊的属性值
        if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
            return res;
        }
        if (!isReadonly) {
            track(target, "get" /* TrackOpTypes.GET */, key);
        }
        if (shallow) {
            return res;
        }
      //7.判断是否为ref对象
        if (isRef(res)) {
            // ref unwrapping - skip unwrap for Array + integer key.
            return targetIsArray && isIntegerKey(key) ? res : res.value;
        }
      //8.判断是否为对象
        if (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.
            return isReadonly ? readonly(res) : reactive(res);
        }
        return res;
    };
}

 

  • 检测__v_isReactive属性,如果为true,表示target已经是一个响应式对象了。

  • 依次检测__v_isReadonly__v_isShallow属性,判断是否为只读和浅层响应,如果是则返回对应包装过的target。

  • 检测__v_raw属性,这里是三元的嵌套,主要判断原始数据是否为只读或者浅层响应,然后在对应的Map里面寻找是否有该目标对象,如果都为true则说明target已经为响应式对象。

  • 如果target是数组,需要对一些方法(针对includesindexOflastIndexOfpushpopshiftunshiftsplice)进行特殊处理。并对数组的每个元素执行收集依赖,然后通过Reflect获取数组函数的值。

  • Reflect获取值。

  • 判断是否为特殊的属性值,symbol, __proto____v_isRef__isVue, 如果是直接返回前面得到的res,不做后续处理;

  • 如果为ref对象,target不是数组的情况下,会自动解包。

  • 如果resObject,进行深层响应式处理。从这里就能看出,Proxy是懒惰式的创建响应式对象,只有访问对应的key,才会继续创建响应式对象,否则不用创建。

set方法实现

例子:data.name='2'

const set = /*#__PURE__*/ createSetter();
  //shallow是否转换为浅层响应,默认为false
function createSetter(shallow = false) {
    //1.传递四个参数
    return function set(target, key, value, receiver) {
        let oldValue = target[key];
        //首先获取旧值,如果旧值是ref类型,且新值不是ref类型,则不允许修改
        if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
            return false;
        }
     //2.根据传递的shallow参数,来执行之后的操作
        if (!shallow) {
            if (!isShallow(value) && !isReadonly(value)) {
                oldValue = toRaw(oldValue);
                value = toRaw(value);
            }
            if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
                oldValue.value = value;
                return true;
            }
        }
      //3.检测key是不是target本身的属性
        const hadKey = isArray(target) && isIntegerKey(key)
            ? Number(key) < target.length
            : hasOwn(target, key);
        //利用Reflect.set()来修改值,返回一个Boolean值表明是否成功设置属性
        //Reflect.set(设置属性的目标对象, 设置的属性的名称, 设置的值, 如果遇到 `setter`,`receiver`则为`setter`调用时的`this`值)
        const result = Reflect.set(target, key, value, receiver);
        // 如果目标是原始原型链中的某个元素,则不要触发
        if (target === toRaw(receiver)) {
        //如果不是target本身的属性那么说明执行的是'add'操作,增加属性
            if (!hadKey) {
                trigger(target, "add" /* TriggerOpTypes.ADD */, key, value);
            }
      //4.比较新旧值,是否触发依赖
            else if (hasChanged(value, oldValue)) {
      //5.触发依赖
                trigger(target, "set" /* TriggerOpTypes.SET */, key, value, oldValue);
            }
        }
        return result;
    };
}

 

1、以data.name='2'这段代码为例,四个参数分别为:

target:目标对象,即target={"name": "测试","age": 10}(此处为普通对象)

key:修改的对应key,即key: "name"

value:修改的值,即value: "2"

receiver:目标对象的代理。即receiver=Proxy {"name": "测试","age": 10}

2、shallow为false的时候。

第一个判断:如果新值不是浅层响应式并且不是readonly,新旧值取其对应的原始值。

第二个判断:如果target不是数组并且旧值是ref类型,新值不是ref类型,直接修改oldValue.value为value

3.检测key是不是target本身的属性。这里的hadKey有两个方法,isArray就不解释,就是判断是否为数组

isIntegerKey:判断是不是数字型的字符串key值

 

//判断参数是否为string类型,是则返回true
const isString = (val) => typeof val === 'string';
//如果参数是string类型并且不是'NaN',且排除-值(排除负数),然后将 key 转换成数字再隐式转换为字符串,与原 key 对比
const isIntegerKey = (key) => isString(key) &&
    key !== 'NaN' &&
    key[0] !== '-' &&
    '' + parseInt(key, 10) === key;

4.比较新旧值,如果新旧值不同,则触发依赖进行更新

hasChanged方法

//Object.is()方法判断两个值是否是相同的值。
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);

 5.触发依赖,这里太过复杂,笔者也没搞懂,如果有兴趣的读者可自行去调试

<script setup>
import { reactive } from "vue";
const data = reactive({
  name: "测试",
  age: 10,
});
data.name='1'//这里并未收集依赖,在处理完 createSetupContext 的上下文后,组件会停止依赖收集,并且开始执行 setup 函数。具体原因有兴趣的读者可以自行去了解
const testClick = ()=>{
  data.name='test'
}
</script>
<template>
  <div>
    <h1>{{ data.name }}</h1>
    <el-button @click="testClick">Click</el-button>
  </div>
</template>
<style scoped></style>

基本数据类型

const num = reactive(2)

这里比较简单,在createReactiveObject函数方法里面:

if (!isObject(target)) {
        if ((process.env.NODE_ENV !== 'production')) {
            console.warn(`value cannot be made reactive: ${String(target)}`);
        }
        return target;
    }

 

因为判断类型不是对象,所以会在控制台打印出警告,并且直接返回原数据

proxy对象

<script>
const data = reactive({
  name: "测试",
  age: 10,
});
const num = reactive(data)//定义一个已经是响应式对象
</script>

1.调试开始进来reactive函数,然后会经过isReadonly函数,这里跟前面不同的是,target是一个proxy对象,它已经被代理过有set,get等handler。所以在isReadonly函数读取target的时候,target会进行get函数的读取操作。

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);
}

2.可以看到get传入的参数有个key="__v_isReadonly",这里的isReadonly返回是false,接下来进入createReactiveObject函数

这里说明下,在本次调试中常见的vue里面定义的私有属性有:

  • __v_skip:是否无效标识,用于跳过监听
  • __v_isReactive:是否已被reactive相关api处理过
  • __v_isReadonly:是否被readonly相关api处理过
  • __v_isShallow:是否为浅层响应式对象
  • __v_raw:当前代理对象的源对象,即target

 3.在createReactiveObject函数中,经过target["__v_isReactive"]的时候会触发target的get函数,这时候get函数传入的参数中key='__v_raw'

if (target["__v_raw" /* ReactiveFlags.RAW */] &&
    !(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
            return target;
}

 

由上图可知我们检测target即已定义过的proxy对象,被reactiveapi处理过就会有__v_raw私有属性,然后再进行receiver的判断,判断target是否为只读或浅层响应。如果都不是则从缓存proxy的WeakMap对象中获取该元素。最后直接返回target的原始数据(未被proxy代理过)。

最后回到之前的判断,由下图可知,target__v_raw属性存在,isReadonly为false,__v_isReactive的值为true,可以说明reactive函数需要处理的对象是一个被reactiveAPI处理过的对象,然后直接返回该对象的原始数据。

 

ref类型

经过ref函数处理,其本质也是一个对象,所以使用reactive函数处理ref类型就跟处理复杂数据类型一样过程。有些内容跟这里差不多,也有对此补充,如果觉得不错请各位帮忙点个赞

(开发中应该不会有这种嵌套行为吧,这里只是为了测试多样化)。

<script setup>
import { reactive,ref } from "vue";
const data = reactive({
  name: "测试",
  age: 10,
});
const numRef = ref(1)
const dataRef = ref({
  name: "测试2",
  age: 20,
})
const num = reactive(numRef)
const dataReactive = reactive(dataRef)
console.log('data',data)
console.log('numRef',numRef)
console.log('num',num)
console.log('dataRef',dataRef)
console.log('dataReactive',dataReactive)
</script>

 

Map类型和Set类型

  • Map 类型是键值对的有序列表,而键和值都可以是任意类型。
  • SetMap类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
  • <script setup>
    import { reactive } from "vue";
    const mapData = new Map();
    mapData.set('name','张三')
    const setData = new Set([1,2,3,1,1])
    console.log(mapData)
    console.log(setData)
    const mapReactive = reactive(mapData)
    console.log(mapReactive)
    </script>

     

    由上图可知Map结构和Set结构使用typeof判断是object,所有流程前面会跟复杂数据类型一样,知道在createReactiveObject函数的getTargetType()函数开始不同。

    getTargetType函数里面toRawType()判断数据类型所用方法为Object.prototype.toString.call()

const targetType = getTargetType(target);
function getTargetType(value) {
    return value["__v_skip" /* ReactiveFlags.SKIP */] || !Object.isExtensible(value)
        ? 0 /* TargetType.INVALID */
        : targetTypeMap(toRawType(value));
}
function targetTypeMap(rawType) {//rawType="Map",这里返回值为2
    switch (rawType) {
        case 'Object':
        case 'Array':
            return 1 /* TargetType.COMMON */;
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return 2 /* TargetType.COLLECTION */;
        default:
            return 0 /* TargetType.INVALID */;
    }
}

 

这时候targetType=2,在createReactiveObject的函数中const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);的三元表达式中可得知,这里的handlercollectionHandlers

网上查找可在reactive函数中return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);这条语句找到,当rawType=1handler是用mutableHandlers,rawType=1时是用mutableCollectionHandlers

mutableCollectionHandlers方法:

const mutableCollectionHandlers = {
    get: /*#__PURE__*/ createInstrumentationGetter(false, false)
};
//解构createInstrumentations
const [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations, shallowReadonlyInstrumentations] = /* #__PURE__*/ createInstrumentations();
//传入两个参数,是否为可读,是否为浅层响应
function createInstrumentationGetter(isReadonly, shallow) {
    const instrumentations = shallow
        ? isReadonly
            ? shallowReadonlyInstrumentations
            : shallowInstrumentations
        : isReadonly
            ? readonlyInstrumentations
            : mutableInstrumentations;
    return (target, key, receiver) => {
        if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
            return !isReadonly;
        }
        else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
            return isReadonly;
        }
        else if (key === "__v_raw" /* ReactiveFlags.RAW */) {
            return target;
        }
        return Reflect.get(hasOwn(instrumentations, key) && key in target
            ? instrumentations
            : target, key, receiver);
    };
}
//篇幅问题以及这方面笔者并未深入,所以就大概带过
function createInstrumentations() {
//创建了四个对象,对象内部有很多方法,其他去掉了,完整可自行去调试查看
    const mutableInstrumentations = {
        get(key) {
            return get$1(this, key);
        },
        get size() {
            return size(this);
        },
        has: has$1,
        add,
        set: set$1,
        delete: deleteEntry,
        clear,
        forEach: createForEach(false, false)
    };
    .................
    //通过createIterableMethod方法操作keys、values、entries、Symbol.iterator迭代器方法
    const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator];
    iteratorMethods.forEach(method => {
        mutableInstrumentations[method] = createIterableMethod(method, false, false);
        readonlyInstrumentations[method] = createIterableMethod(method, true, false);
        shallowInstrumentations[method] = createIterableMethod(method, false, true);
        shallowReadonlyInstrumentations[method] = createIterableMethod(method, true, true);
    });
    return [
        mutableInstrumentations,
        readonlyInstrumentations,
        shallowInstrumentations,
        shallowReadonlyInstrumentations
    ];
}

后续比较复杂,加上笔者技术力还不够,暂时先到这里吧

总结:关于reactive的源码调试就到这了,这只是其中一小部分的源码,希望有兴趣的读者可以以此深入,输出文章,共同进步成长。最后,如果这篇文章对你有所收获,请点个赞,如果有写的不对的地方,请大神们指出。

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

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

相关文章

【Dash搭建可视化网站】项目9:智能选股器(结合行业、换手率、量比与60日k线图选择目标股票)制作步骤详解

【Dash搭建可视化网站】项目9&#xff1a;智能选股器&#xff08;结合行业、换手率、量比与60日k线图选择目标股票&#xff09;1 项目效果图2 项目架构3 文件介绍和功能完善3.1 assets文件夹介绍3.2 app.py和index.py文件完善3.3 layoutleft.py文件完善3.4 api.py和api.ipynb文…

【数据结构趣味多】栈和队列(详细解析)

目录 1.1 栈的定义 1.2 栈的模拟实现&#xff08;顺序栈&#xff09; push()方法 pop()方法 peek()方法 size()方法 栈模拟实现的全代码 1.3顺序栈和链栈的对比 2.队列 2.1队列的定义 2.1队列的模拟实现&#xff08;单链表&#xff09; offer()函数 poll()函数 p…

ABB AC500 PLC 与西门子 S7-300 CPU DP 通讯

硬件连接 将一个 AC500 CPU&#xff08;本例 PM583&#xff09; 左侧插入以 DP 通讯扩展模块 CM572&#xff0c;使用 DP 通讯电缆连接 CM572 与 314-2DP CPU 的 DP 通讯口。连接示意图如下&#xff1a; AC500 DP 主站配置 安装 DP 从站设备描述文件 从西门子官方网站下载 CP…

认识二叉树

hi&#xff0c;代噶候。今天带大家认识一下二叉树&#xff0c;这个二叉树在我看来确实很有难度&#xff0c;但是不要怕&#xff0c;&#xff0c;鲁迅先生曾经说过&#xff0c;真正的勇士敢于面对惨淡的人生&#xff0c;敢于正视淋漓 的鲜血&#xff0c;下面让我们开始吧&#x…

线程池的内部结构与原理解析

线程池的内部结构 使⽤线程池的原因&#xff1a; 1、降低资源消耗 2、控制并发的数量。并发数量过多&#xff0c;可能会导致资源消耗过多&#xff0c;从⽽造成服务器崩溃 3、提高线程的可管理性 1、线程是稀缺资源&#xff0c;如果无限制地创建&#xff0c;不仅会消耗系统资源&…

Global Illumination_SDF Generate Visualize Shadow

Signed Distance Field(有向距离场)&#xff0c;简称SDF&#xff0c;这其实是图形学中非常常用的数学概念。数学上来说&#xff0c;是定义在空间中的一个标量场&#xff0c;标量值为空间一点到曲面的距离。曲面外的点为正值&#xff0c;曲面上的点为0&#xff0c;曲面内的点为负…

通用vue组件化展示列表数据

一、数据的简单展示 1.首先先确定要展示的表格列名以及拿到所需要展示的数组数据 2.然后建立一个专门放el-table遍历的文件 3.在父组件中将数据列表数据存放在listData里面&#xff0c;然后传给子组件&#xff0c;子组件定义一个动态的列&#xff0c;通过遍历propList得到列名…

SpringCloud从入门到精通(八)

config config-概述 • Spring Cloud Config 解决了在分布式场景下多环境配置文件的管理和维护。 • 好处&#xff1a; 集中管理配置文件 不同环境不同配置&#xff0c;动态化的配置更新 配置信息改变时&#xff0c;不需要重启即可更新配置信息到服务config-快速入门 gitee搭…

一文告别结合Nacos后,Springboot的配置文件看不懂的痛苦

一、背景 后端基于SpringCloud项目架构的话&#xff0c;默认会使用Nacos来做配置中心&#xff0c;但是这对从来没接触过Nacos配置中心的小伙伴&#xff0c;肯定就不知道怎么回事了&#xff0c;于是便有了这一篇指引。 二、Nacos配置中心是如何引入SpringCloud项目中的呢&…

操作系统实验8:proc文件的实现

实验目的 掌握虚拟文件系统的实现原理实践文件、目录、索引节点等概念 实验内容 在Linux 0.11上实现procfs&#xff08;proc文件系统&#xff09;内的psinfo结点。当读取此结点的内容时&#xff0c;可得到系统当前所有进程的状态信息。例如&#xff0c;用cat命令显示/proc/p…

有限状态机

文章目录1.概念2.什么是计算3.什么是有限状态机3.1特性3.2为什么要用状态机4.实战4.1字符串转换整数4.2用有限状态机实现4.3源码1.概念 有限状态机&#xff08;英语&#xff1a;finite-state machine&#xff0c;缩写&#xff1a;FSM&#xff09;又称有限状态自动机&#xff0…

【手写 Vue2.x 源码】第十篇 - 数组数据变化的观测情况

一&#xff0c;前言 上篇&#xff0c;主要介绍了对象数据变化的观测情况&#xff0c;涉及以下几个点&#xff1a; 实现了对象老属性值变更为对象、数组时的深层观测处理&#xff1b; 结合实现原理&#xff0c;说明了对象新增属性不能被观测的原因&#xff0c;及如何实现数据…

综合案例二 旅游网【1.项目导入技术选型注册表单校验登录退出表单】注册表空指针异常和其他一些错误

目录 前提&#xff1a;项目导入 在maven中点击travel项目&#xff08;这里我是将项目添加到hello_maven里 &#xff09; 1.启动项目 方式一&#xff1a;maven命令启动 方式二&#xff1a;配置maven快捷启动 2.技术选型 1 Web层 2 Service层 3 Dao层 3.创建数据库…

56/14 shell脚本 后台启动 程序1 + “tail -f log“, ctrl +c 导致程序1中断

前言 接上一篇文章, node 程序后台执行加上 tail 命令, 中断 tail 命令, 同时也中断了 node 程序 我们来详细 参照对比一下 这个问题的各种情况 主要的脚本如下类似, 第一条命令 后台启动 程序1, 然后 第二条命令 tail -f 查看日志 然后 ctrlc 中断 "tail -f" …

实现自己的打印函数

文章目录前言前置知识代码说明实验操作单字符打印字符串打印整形字符串打印前言 本博客记录《操作系统真象还原》第六章实验的操作~ 实验环境&#xff1a;ubuntu18.04VMware &#xff0c; Bochs下载安装 实验内容&#xff1a; 实现 put_char 单字符打印输出函数。实现 put_…

TEE 背景知识

TEE 背景 计算机世界的安全&#xff0c;是保护计算机系统和网络免受攻击者的攻击&#xff0c;这些攻击可能导致未经授权的信息泄露、窃取或损坏硬件、软件或数据&#xff0c;以及它们所提供的服务的中断或误导。更多参考 Computer_security 1 安全是什么 谈安全&#xff0c;…

28.0:Combit LIST LABEL Reporting for Delphi Crack

作为 Delphi 开发人员的优势&#xff1a; 将 VCL 组件直接集成到 Embarcadero IDE 中 免版税报表设计器 交互式预览等 使用综合报表对象 Delphi 和所有其他相关语言和开发环境的报告工具。 将列表和标签集成到 Embarcadero RAD Studio List & Label 的 VCL 组件编译并直接…

HTML与CSS基础(八)—— CSS布局(定位、装饰、选择器拓展)

目标能够说出 定位 的常见应用场景 能够说出 不同定位方式 的特点 能够使用 子绝父相 完成元素水平垂直案例 能够写出三种常见的 光标类型&#xff08;cursor&#xff09; 能够使用 圆角边框 属性完成 正圆 和 胶囊按钮 效果 能够说出 display 和 visibility 让 元素本身隐藏 的…

【java查漏补缺】File类的使用

File类可以用来处理文件数据&#xff0c;比如使用list()方法可以获得某个文件夹下面的所有文件名。 File类的list()方法有一个含有一个参数的重载&#xff0c;在调用该方法的时候需要传入一个FilenameFilter对象&#xff0c;这样便可以进行文件名过滤。 我们先来看一下list()…

GJB 5000B二级-PP项目策划

一、主要变化情况 合并10项(绿色)、修订3项(蓝色)、删除1项(红色) 新增的主要内容 基于A版标准“项目策划”、“集成项目管理”、“定量项目管理”三个过程域中有关项目策划实践的内容来识别,没有新增实践,只增加了以下两个要求: 1、增加软件项目和设备/系统生存周…