响应式原理实现(2)vue2和vue3

响应式2

vue2响应式实现

提供shallow,决定是否需要深度响应

/*******************新增 shallow*******************/
export function defineReactive(obj, key, val, shallow) {
/****************************************************/const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcher/*******************新增****************************/// 将新的val也收集响应 observe(val)!shallow && observe(val);/******************************************************/Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;if (Dep.target) {dep.depend();}return value;},set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal);} else {val = newVal;}childOb = !shallow && observe(newVal);dep.notify();},});
}
/*
util.js
export function isObject(obj) {return obj !== null && typeof obj === "object";
}
*/
export function observe(value) {if (!isObject(value)) {return;}let ob = new Observer(value);return ob;
}

代理模式

import { def } from "./util";const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);const methodsToPatch = ["push","pop","shift","unshift","splice","sort","reverse",
];/*** Intercept mutating methods and emit events*/
methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method];def(arrayMethods, method, function mutator(...args) {const result = original.apply(this, args);/*****************这里相当于调用了对象 set 需要通知 watcher ************************/// 待补充/**************************************************************************** */return result;});
});
export class Observer {constructor(value) {/******新增 *************************/this.dep = new Dep();/************************************/this.walk(value);}/*** 遍历对象所有的属性,调用 defineReactive* 拦截对象属性的 get 和 set 方法*/walk(obj) {const keys = Object.keys(obj);for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]);}}
}
export function defineReactive(obj, key, val, shallow) {const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcherlet childOb = !shallow && observe(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;if (Dep.target) {dep.depend();/******新增 *************************/if (childOb) {// 当前 value 是数组,去收集依赖if (Array.isArray(value)) {childOb.dep.depend();}}/************************************/}return value;},set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal);} else {val = newVal;}dep.notify();},});
}

上面已经重写了array方法,不可以直接覆盖全局的array方法,如果当前value是数组,在observer中拦截array方法。

import { arrayMethods } from './array' // 上边重写的所有数组方法
/* export const hasProto = "__proto__" in {}; */
export class Observer {constructor(value) {this.dep = new Dep();/******新增 *************************/if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods);} else {copyAugment(value, arrayMethods, arrayKeys);}/************************************/} else {this.walk(value);}}/*** 遍历对象所有的属性,调用 defineReactive* 拦截对象属性的 get 和 set 方法*/walk(obj) {const keys = Object.keys(obj);for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]);}}
}
/*** Augment a target Object or Array by intercepting* the prototype chain using __proto__*/
function protoAugment(target, src) {/* eslint-disable no-proto */target.__proto__ = src;/* eslint-enable no-proto */
}/*** Augment a target Object or Array by defining* hidden properties.*/
/* istanbul ignore next */
function copyAugment(target, src, keys) {for (let i = 0, l = keys.length; i < l; i++) {const key = keys[i];def(target, key, src[key]);}
}

上面代码中,实现了数组的操作方法,但是并没有给数组中的元素添加响应式,所以我们需要给数组中的元素添加响应式。

export class Observer {constructor(value) {this.dep = new Dep();def(value, "__ob__", this);if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods);} else {copyAugment(value, arrayMethods, arrayKeys);}/******新增 *************************/this.observeArray(value);/************************************/} else {this.walk(value);}}/*** 遍历对象所有的属性,调用 defineReactive* 拦截对象属性的 get 和 set 方法*/walk(obj) {const keys = Object.keys(obj);for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]);}}observeArray(items) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i]);}}
}

如果是多维数组,则需要对多维数组中的元素进行依赖

export function defineReactive(obj, key, val, shallow) {const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcherlet childOb = !shallow && observe(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;if (Dep.target) {dep.depend();if (childOb) {if (Array.isArray(value)) {childOb.dep.depend(); // [["hello", "wind"],["hello", "liang"]]  这个整体进行了依赖的收集/******新增 *************************/dependArray(value); // 循环数组中的元素,如果是数组的话进行依赖收集。/************************************/}}}return value;},...}
function dependArray(value) {for (let e, i = 0, l = value.length; i < l; i++) {e = value[i];if (Array.isArray(e)) {e && e.__ob__ && e.__ob__.dep.depend();dependArray(e); // 递归进行}}
}      

同时也需要对插入的数据进行依赖收集,如果是数组,进行数组的依赖收集。
如果是数组,需要将数组中的元素进行响应式处理,对于新添加的元素也进行响应式处理。收集依赖的时候,需要对数组中的数组进行依赖收集。
数组的set和delete方法
数组set
list[0]不会触发watcher收集。数组只能通过重写的push,splice方法去触发

/*** Set a property on an object. Adds the new property and* triggers change notification if the property doesn't* already exist.*/
export function set(target, key, val) {if (Array.isArray(target)) {target.length = Math.max(target.length, key);target.splice(key, 1, val);return val;}// targe 是对象的情况// ...
}

数组del

/*** Delete a property and trigger change if necessary.*/
export function del(target, key) {if (Array.isArray(target) && isValidArrayIndex(key)) {target.splice(key, 1);return;}// targe 是对象的情况// ...
}

对象set和delete方法

  • 对象set方法
// 例子
import { observe, set, del } from "./reactive";
import Watcher from "./watcher";
const data = {obj: {a: 1,b: 2,},
};
observe(data);
const updateComponent = () => {const c = data.obj.c ? data.obj.c : 0;console.log(data.obj.a + data.obj.b + c);
};new Watcher(updateComponent);data.obj.c = 3;
/*** Set a property on an object. Adds the new property and* triggers change notification if the property doesn't* already exist.*/
export function set(target, key, val) {if (Array.isArray(target)) {target.length = Math.max(target.length, key);target.splice(key, 1, val);return val;}// targe 是对象的情况// key 在 target 中已经存在if (key in target && !(key in Object.prototype)) { target[key] = val;return val;}const ob = target.__ob__;// target 不是响应式数据if (!ob) {target[key] = val;return val;}// 将当前 key 变为响应式的defineReactive(target, key, val);return val;
}

上面的代码虽然设置了set,但是不会新收集watcher, 需要手动调用。
这里将代码中的对象的dep进行收集对象元素中dep收集的watcher

export function defineReactive(obj, key, val, shallow) {const property = Object.getOwnPropertyDescriptor(obj, key);// 读取用户可能自己定义了的 get、setconst getter = property && property.get;const setter = property && property.set;// val 没有传进来话进行手动赋值if ((!getter || setter) && arguments.length === 2) {val = obj[key];}const dep = new Dep(); // 持有一个 Dep 对象,用来保存所有依赖于该变量的 Watcherlet childOb = !shallow && observe(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {const value = getter ? getter.call(obj) : val;if (Dep.target) {dep.depend();if (childOb) {/******新位置 *************************/childOb.dep.depend();/**********************************/if (Array.isArray(value)) {// childOb.dep.depend(); //原来的位置dependArray(value);}}}return value;},set: function reactiveSetter(newVal) {if (setter) {setter.call(obj, newVal);} else {val = newVal;}childOb = !shallow && observe(newVal);dep.notify();},});
}function dependArray(value) {for (let e, i = 0, l = value.length; i < l; i++) {e = value[i];/******新位置 *************************/e && e.__ob__ && e.__ob__.dep.depend();/**********************************/if (Array.isArray(e)) {//  e && e.__ob__ && e.__ob__.dep.depend(); // 原位置dependArray(e);}}
}

并不知道 c 被哪些 Watcher 依赖,我们只知道和 c 同属于一个对象的 a 和 b 被哪些 Watcher 依赖,但大概率 c 也会被其中的 Watcher 依赖。所以我们可以在set中手动执行一下obj的Dep,依赖c的Watcher大概率会被执行,相应的c也会成功收集到依赖。
c中的watcher不会在收集,这种情况下,只能通过父级收集依赖,触发依赖,来更新后续步骤。

  • 对象delete方法
    如果要是删除a属性,删除后执行他相应的dep就行。
    同上诉的思想一样,这里也需要更新父级。这样就可以完成delete。
/*** Delete a property and trigger change if necessary.*/
export function del(target, key) {if (Array.isArray(target)) {target.splice(key, 1);return;}// targe 是对象的情况const ob = target.__ob__;if (!hasOwn(target, key)) {return;}delete target[key];if (!ob) {return;}ob.dep.notify();
}

vue3响应式实现

reactive

在vue2中需要重写数组的方法,来达到对数组响应式。
但是在vue3中,却是不需要,因为vue3中使用的是proxy来做数据拦截,可以原生支持数组的响应式。
这里来解释一下object.definePropertyProxy
object.defineproperty实际上是通过定义或者修改对象属性的描述符来实现数据劫持,缺点是只能拦截get和set操作,无法拦截delete,in,方法调用等属性。动态添加新属性,保证后续使用的属性要在初始化生命data的时候定义,通过this. s e t 设置新属性。通过 d e l e t e 删除属性,响应式丢失,通过 t h i s . set设置新属性。通过delete删除属性,响应式丢失,通过this. set设置新属性。通过delete删除属性,响应式丢失,通过this.delete()删除属性。通过数组索引替换/新增元素,响应式丢失,使用this.$set来设置新元素。使用数组push,pop,shift,unshif,splice,sort,reverse等原生方法来改变原数组的时候,会响应式丢失,需要使用重写/增强后的push,pop,shift,unshift,splice,sort,reverse方法。一次只能对一个属性实现数据劫持,需要遍历对所有的属性进行劫持。数据结构复杂的时候,属性值为引用类型数据,需要通过递归进行处理。
其实object.defineProperty也能拦截Array。但是,还是有如下原因。1. 数组和普通对象在使用场景下会有区别,在项目中使用数组的目的是为了遍历,比较少会使用array[index]=xxx的形式。2. 数组长度是多变的,不可能和普通对象一样在data选项中提前声明好的所有元素。通过array[index]=xxx方式赋值的时候,一旦index超过了现有最大的索引值,那么当前添加的新元素也不会具有响应式。3. 数组存储的元素比较多,不可能为每个数组元素都这是getter/setter。4.无法拦截数组原生方法,比如push,pop,shift,unshift等的调用,最终任然需要重写和增强方法。
proxy主要是用来创建一个对象的代理,从而实现基本操作的拦截和自定义(比如属性查找, 赋值, 枚举, 函数调用等),本质上是通过拦截对象内部方法的执行实现代理,而对象本身根据规范定义的不同又会分为常规对象和异质对象。

  1. get()属性读取操作捕获的捕获器。
  2. set()属性设置操作的捕获器。
  3. deleteProperty()是delete操作符的捕捉器。
  4. ownkeys()是object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕获器。
  5. has()是in操作符的捕获器。
export function reactive(target: object) {is(isReadonly(target)) {return target}return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)
}

target为传进来的对象

function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlerrs: ProxyHandler<any>,proxyMap: WeakMap<Target>
) {
function createReactiveObject(target: Target,isReadonly: boolean,baseHandlers: ProxyHandler<any>,collectionHandlers: ProxyHandler<any>,proxyMap: WeakMap<Target, any>
) {// 非对象类型直接返回if (!isObject(target)) {if (__DEV__) {console.warn(`value cannot be made reactive: ${String(target)}`)}return target}// 目标数据的 __v_raw 属性若为 true,且是【非响应式数据】或 不是通过调用 readonly() 方法,则直接返回if (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {return target}// 目标对象已存在相应的 proxy 代理对象,则直接返回const existingProxy = proxyMap.get(target)if (existingProxy) {return existingProxy}// 只有在白名单中的值类型才可以被代理监测,否则直接返回const targetType = getTargetType(target)if (targetType === TargetType.INVALID) {return target     }// 创建代理对象const proxy = new Proxy(target,// 若目标对象是集合类型(Set、Map)则使用集合类型对应的捕获器,否则使用基础捕获器targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers )
}

createReactiveObject()函数需要做一些前置判断处理。

  • 目标数据是原始值类型,直接返回原数据。
  • __v_raw属性为true,而且非响应式数据或不是通过调用readonly()方法。直接返回原数据。
  • 若目标数据中已经存在响应的proxy代理对象,则直接返回对应的代理对象。
  • 若目标数据中不存在对应的白名单数据类型中,则直接返回原数据。
    支持响应式的数据类型为
    • 可拓展的对象,即他的上面上可以添加新的属性
    • __v_skip属性不存在或者是值为false的对象
    • 数据类型为object, Array, Map, Set, WeakMap, WeakSet对象。
    • 其他数据都被认为无效的响应式对象。
// 白名单
function targetTypeMap(rawType: string) {switch (rawType) {case 'Object':case 'Array':return TargetType.COMMONcase 'Map':case 'Set':case 'WeakMap':case 'WeakSet':return TargetType.COLLECTIONdefault:return TargetType.INVALID}
}

Handlers捕获器

export const mutableHandlers: ProxyHandler<object> = {get,set,deleteProperty,has,ownKeys
}

这些对应的就是读取,设置,删除,判断是否存在对应的属性,获取对象自身的属性值。

get捕获器
class BaseReactiveHandler implements ProxyHandler<Target> {constructor(protected readonly _isReadonly = false,protected readonly _isShallow = false,) {}get(target: Target, key: string | symbol, receiver: object) {const isReadonly = this._isReadonly,isShallow = this._isShallow// 当直接通过指定 key 访问 vue 内置自定义的对象属性时,返回其对应的值if (key === ReactiveFlags.IS_REACTIVE) {return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {return isReadonly} else if (key === ReactiveFlags.IS_SHALLOW) {return isShallow} else if (key === ReactiveFlags.RAW) {if (receiver ===(isReadonly? isShallow? shallowReadonlyMap: readonlyMap: isShallow? shallowReactiveMap: reactiveMap).get(target) ||// receiver is not the reactive proxy, but has the same prototype// this means the reciever is a user proxy of the reactive proxyObject.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {return target}// early return undefinedreturn}// 判断是否为数组类型const targetIsArray = isArray(target)// 数组对象if (!isReadonly) {if (targetIsArray && hasOwn(arrayInstrumentations, key)) {// 重写/增强数组的方法// - 查找方法: includes, indexof, lastIndexOf// - 修改原数组的方法: push, pop, unshift, shift, splicereturn Reflect.get(arrayInstrumentations, key, receiver)}if (key === 'hasOwnProperty') {return hasOwnProperty}}// 获取对应属性值const res = Reflect.get(target, key, receiver)if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res}if (!isReadonly) {track(target, TrackOpTypes.GET, key)}if (isShallow) {return res}if (isRef(res)) {// ref unwrapping - skip unwrap for Array + integer key.return targetIsArray && isIntegerKey(key) ? res : res.value}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}
}
  • 若当前数据对象是数组,则重写/增强数组对应的方法。
    • 数组的查找方法: includes, indexOf, lastIndexOf
    • 修改原数组的方法: push, pop, unshift, shift, splice
  • 若当前数据对象是普通对象,且非只读的则通过track(target, TrackOpTypes.GET, key)进行依赖收集
    • 若当前数据对象是浅层响应的,则直接返回其对应属性值。
    • 当前对象是ref类型的,则会自动脱ref
  • 若当前数据对象的属性值是对象类型
    • 若当前属性值属于是只读的,则通过readonly(res)向外返回其结果
    • 否则会将当前的属性值以reactie(res)向外返回proxy代理对象
    • 否则直接想外返回对应的属性值
数组类型捕获

数组的查找方法中包含includes, indexof, lastIndexOf,这些方法通常情况下是能够按照预期工作的,但是还需要对某些情况进行特殊处理。

  • 查找的目标数据是响应式数据本身。
const obj = {}
const proxy = reactive([obj])
console.log(proxy.includes(proxy[0])) // false
- 产生原因: 这里涉及到了两次读取操作,第一次是proxy[0],这个时候会触发get捕获器并为obj生成对应代理对象并返回。第二次是proxy.includes()的调用,会遍历数组的每个元素,就是触发get捕获其,生成一个新的代理对象并返回,这两次生成的代理独享不是同一个,因此返回false
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()function createArrayInstrumentations() {const instrumentations: Record<string, Function> = {}// instrument identity-sensitive Array methods to account for possible reactive// values;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {instrumentations[key] = function (this: unknown[], ...args: unknown[]) {const arr = toRaw(this) as anyfor (let i = 0, l = this.length; i < l; i++) {track(arr, TrackOpTypes.GET, i + '')}// we run the method using the original args first (which may be reactive)const res = arr[key](...args)if (res === -1 || res === false) {// if that didn't work, run it again using raw values.return arr[key](...args.map(toRaw))} else {return res}}})// instrument length-altering mutation methods to avoid length being tracked// which leads to infinite loops in some cases (#2137);(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {instrumentations[key] = function (this: unknown[], ...args: unknown[]) {pauseTracking()pauseScheduling()const res = (toRaw(this) as any)[key].apply(this, args)resetScheduling()resetTracking()return res}})return instrumentations
}
set捕获器

处理数组索引index和length
数组的index和length是会相互影响的。

  • 当Number(key)<target.length => 证明是修改操作。对应的是TriggerOpTypes.SET类型,即当前操作不会改变length的值,不需要触发和length相关副作用的执行
  • 当Number(key)>=target.length => 证明是新增操作,TriggerOpTypes.ADD类型,当前操作会改变length的值,需要触发和length相关副作用函数的执行。
  set(target: object,key: string | symbol,value: unknown,receiver: object,): boolean {// 保存旧的数据let oldValue = (target as any)[key]// 若原数据值属于只读且ref类型,并且新数据值不属于ref类型,则意味着修改失败if (!this._isShallow) {const isOldValueReadonly = isReadonly(oldValue)if (!isShallow(value) && !isReadonly(value)) {oldValue = toRaw(oldValue)value = toRaw(value)}if (!isArray(target) && isRef(oldValue) && !isRef(value)) {if (isOldValueReadonly) {return false} else {oldValue.value = valuereturn true}}} else {// in shallow mode, objects are set as-is regardless of reactive or not}// 是否存在对应的keyconst hadKey =isArray(target) && isIntegerKey(key)? Number(key) < target.length: hasOwn(target, key)// 设置对应值const result = Reflect.set(target, key, value, receiver)// 若目标对象是原始原型链上的内容(非自定义添加),则不触发依赖更新if (target === toRaw(receiver)) {if (!hadKey) {// 若目标对象不在对应的key上,则为新增操作。trigger(target, TriggerOpTypes.ADD, key, value)} else if (hasChanged(value, oldValue)) {// 目标对象存在对应的值,则为修改操作。trigger(target, TriggerOpTypes.SET, key, value, oldValue)}}return result}
deleteProperty has ownKeys捕获器

这三个捕获器内容非常简洁,其中has和ownKeys本质上也属于读取操作,因此需要通过track()进行依赖搜集,而deleteProperty相当于修改操作。因此需要trigger()触发更新

  deleteProperty(target: object, key: string | symbol): boolean {const hadKey = hasOwn(target, key)const oldValue = (target as any)[key]const result = Reflect.deleteProperty(target, key)if (result && hadKey) {trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)}return result}
  has(target: object, key: string | symbol): boolean {const result = Reflect.has(target, key)if (!isSymbol(key) || !builtInSymbols.has(key)) {track(target, TrackOpTypes.HAS, key)}return result}
  ownKeys(target: object): (string | symbol)[] {track(target,TrackOpTypes.ITERATE,isArray(target) ? 'length' : ITERATE_KEY,)return Reflect.ownKeys(target)}

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

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

相关文章

【React】useState:状态管理的基石

文章目录 一、什么是 useState&#xff1f;二、useState 的基本用法三、useState 的工作原理四、高级用法五、最佳实践 在现代前端开发中&#xff0c;React 是一个非常流行的库&#xff0c;而 useState 是 React 中最重要的 Hook 之一。useState 使得函数组件能够拥有自己的状态…

【Nodejs基础06】Node.js常用命令总结

执行JS文件&#xff1a;node xx 初始化package.json: npm init -y&#xff08;所在文件夹不能有中文或特殊符号&#xff09; 下载本地软件包&#xff1a;npm i 软件包名&#xff08;软件包源码全部集成在node_modules文件夹中&#xff09; 下载全局软件包&#xff1a;npm i …

深入理解synchronized(简记)

深入理解synchronized 管程synchronized对象的内存布局锁状态记录锁对象状态转换偏向锁轻量级锁锁对象转换总结 管程synchronized Java 参考了 MESA 模型&#xff0c;语言内置的管程&#xff08;synchronized&#xff09;对 MESA 模型进行了精简。 对象的内存布局 对象头 Mar…

Nginx笔记(一)

一、Nginx简介 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器 [13]&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔赛索耶夫为俄罗斯访问量第二的Rambler.ru站点&#xff08;俄文&#xff1a;Рамблер&#xff09;开发的&#xff0c;公开版本1.…

如何在宝塔面板给域名配置 SSL 证书

首先需要有证书 这里以阿里云为例 1. 首先进入到 SSL 证书管理控制台 选择个人测试证书&#xff0c;并点击购买 免费的可以使用三个月。 购买完成之后回到控制台。 点击创建证书&#xff0c;将标红的地方填写&#xff0c;其他默认就好。 然后提交审核就行。 这里需要对域名…

岁月未央,深情永驻,暮春的脚步渐行渐远,夏日的微风轻拂而来,时光的车轮从未停歇,岁月的篇章仍在续写

《岁月未央,深情永驻》暮春的脚步渐行渐远,夏日的微风轻拂而来,时光的车轮从未停歇,岁月的篇章仍在续写。在这流转的光阴中,我们追寻着,思索着,感悟着生命中那些最真挚、最动人的情感。 回首暮春,那是一场花事的落幕,却也是诗意的延续。岁月的舞台上,每一个远去的季…

【React】组件:全面解析现代前端开发的基石

文章目录 一、什么是组件&#xff1f;二、组件的类型三、组件的生命周期四、状态管理五、属性传递六、组合与继承七、最佳实践 在现代前端开发中&#xff0c;React 已成为开发者构建用户界面的首选框架之一。React 的强大之处在于其组件化设计&#xff0c;允许开发者将 UI 拆分…

day09-linux系统优化

01.知识点回顾 常用命令 1.pwd 2.cd-~...../../ 3.ls-l 详细信息-a 查看隐藏的文件 4.touch 5.mkdir-p 递归创建目录 6.cat 查看文件内容-n 显示行号 7.cp 复制文件-r 复制目录 8.mv 移动文件/目录 9.rm 删除文件-r 删除目录-f 强制删除不提示 10.tree 树形结构 11…

Wireshark自定义协议解析器插件C语言开发二

接着上一篇安装文章&#xff0c;在wireshark插件开发完成后&#xff0c;仿真的dll并不能直接分享使用&#xff0c;当另外电脑缺少必要的c环境或依赖项时候&#xff0c;在打开wireshark软件时候即会报错。 上图是仿真得到的dll文件路径&#xff0c;但是并不能在其他没有安装了v…

java——final关键字

final关键字是最终的意思&#xff0c;可以修饰&#xff08;类、方法、变量&#xff09; 修饰类&#xff1a;该类被称为最终类&#xff0c;特点是不能被继承了修饰方法&#xff1a;该方法被称为最终方法&#xff0c;特点是不能被重写了修饰变量&#xff1a;该变量只能被赋值一次…

linux驱动--中断

中断号和中断的申请 中断号的添加-----定义设备节点&#xff0c;描述当前设备 通过设备树文件获取 /dts/xxxx.dts文件中进行设备的设置 在dts设备树文件中进行设备的定义&#xff0c;包括继承的设备&#xff0c;中断号的设置 需要对我们的dts设备树文件进行编译&#xff0…

LeetCode 191, 173, 210

文章目录 191. 位1的个数题目链接标签思路代码Integer.bitCount() 173. 二叉搜索树迭代器题目链接标签思路递归迭代 210. 课程表 II题目链接标签思路代码 191. 位1的个数 题目链接 191. 位1的个数 标签 位运算 分治 思路 这里可以使用一个结论&#xff1a;n & (n - 1…

天机学堂第二天项目 添加我的课表 项目总结

目录 根据产品原型得到数据库表结构 RabbitMq监听 构造器注入 幂等 mybatisplus 分页查询的多种写法 在new page里面添加排序 查询条件中 用orderBydESC指定排序 ​编辑 链式编程中使用page指定排序 stream流 ​编辑 在网关中解析token 根据产品原型得到数据库表结构 根…

基于物联网的区块链算力网络,IGP/BGP协议

目录 基于物联网的区块链算力网络 IGP/BGP协议 IGP(内部网关协议) BGP(边界网关协议) 内部使用ISP的外部使用BGP的原因 一、网络规模和复杂性 二、路由协议的特性 三、满足业务需求 四、结论 基于物联网的区块链算力网络 通 过 多个物联网传感器将本地计算…

Node服务器开发和部署

Node服务器开发和部署 第一步&#xff1a;写一个Node服务 方法1&#xff1a;Express编写 创建一个项目&#xff1a;node_server mkdir node_server && cd node_server && npm init -y安装express&#xff1a; npm install express至此&#xff0c;项目创建…

使用在UE5中使用AirSim插件Eigen库头文件引用报错,出现报错的解决方式

一、概述 如图所示&#xff0c;用红线圈出的两条头文件引用会报错&#xff0c;提示无法找到他们&#xff0c;但是可以发现的是&#xff0c;他们的路径书写是没有问题的。 // #include <Source/Airlib/deps/eigen3/Eigen/Core> // #include <Source/Airlib/deps/eigen…

Android 线程并发:线程通信:Handler机制

文章目录 API源码分析操作总结 API Handler相关 Handler对象.sendMessage(Message) 发送消息 Handler对象.handleMessage()空方法 自定义Handler重写handleMessage方法&#xff0c;处理Message Looper相关 Looper.getMainLooper() 获取App的UI线程的Looper对象 Looper…

【网络爬虫技术】(1·绪论)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;网络爬虫开发技术入门_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 …

日拱一卒 | JVM

文章目录 什么是JVM&#xff1f;JVM的组成JVM的大致工作流程JVM的内存模型 什么是JVM&#xff1f; 我们知道Java面试&#xff0c;只要你的简历上写了了解JVM&#xff0c;那么你就必然会被问到以下问题&#xff1a; 什么是JVM&#xff1f;简单说一下JVM的内存模型&#xff1f;…

梯度下降算法,gradient descent algorithm

定义&#xff1a;是一个优化算法&#xff0c;也成最速下降算法&#xff0c;主要的部的士通过迭代找到目标函数的最小值&#xff0c;或者收敛到最小值。 说人话就是求一个函数的极值点&#xff0c;极大值或者极小值 算法过程中有几个超参数&#xff1a; 学习率n&#xff0c;又称…