petite-vue源码剖析-逐行解读@vue/reactivity之reactive
在petite-vue中我们通过reactive
构建上下文对象,并将根据状态渲染UI的逻辑作为入参传递给effect
,然后神奇的事情发生了,当状态发生变化时将自动触发UI重新渲染。那么到底这是怎么做到的呢?
@vue/reactivity功能十分丰富,而petite-vue仅使用到reactive
和effect
两个最基本的API,作为入门本文将仅仅对这两个API进行源码解读。
一切源于Proxy
我们知道Vue2是基于Object.defineProperty
拦截对象属性的读写操作,从而实现依赖收集和响应式UI渲染。而@vue/reactivity作为Vue3的子项目,采用的是ES6的Proxy接口实现这一功能。
const state = {
count: 1
}
const proxyState = new Proxy(state, {
get(target: T, property: string, receiver?: T | Proxy): any {
// 拦截读操作
console.log('get')
return Reflect.get(target, property, receiver)
},
set(target: T, property: string, value: any, receiver?: T | Proxy): boolean {
// 拦截写操作
console.log('set')
return Reflect.set(target, property, value, receiver)
},
deleteProperty(target, prop) {
// 拦截属性删除操作
console.log('delete')
delete target[prop]
return true
}
})
相对Object.defineProperty
,Proxy的特点:
- 通过
new Proxy
构建的对象进行操作才能拦截对象属性的读写操作,而被代理的对象则没有任何变化; - 可以监听数组元素的变化和增减;
- 可以监听对象属性的增减;
- Proxy可以逐层代理对象属性,而
Object.defineProperty
则需要一次性代理对象所有层级的属性。
响应式编程
// 定义响应式对象
const state = reactive({
num1: 1,
num2: 2
})
// 在副作用函数中访问响应式对象属性,当这些属性发生变化时副作用函数将被自动调用
effect(() => {
console.log('outer', state.num1)
effect(() => {
console.log('inner', state.num2)
})
})
// 回显 outer 1
// 回显 inner 2
state.num2 += 1
// 回显 inner 3
state.num1 += 1
// 回显 outer 2
// 回显 inner 3
state.num2 += 1
// 回显 inner 4
// 回显 inner 4
本篇我们将从reactive入手,解读Vue3到底如何构造一个响应式对象。
深入reactive
的工作原理
@vue/reactivity的源码位于vue-next项目的packages/reactivity下,而reactive
函数则位于其下的src/reactive.ts文件中。该文件中除了包含reactive
函数外,还包含如shallowReactive
、readonly
、shallowReadonly
和其它帮助函数。
而reactive
核心工作则是通过Proxy将一个普通的JavaScript对象转换为监控对象,拦截对象属性的读写删操作,并收集依赖该对象(属性)的副作用函数。大致流程如下:
- 通过
reactive
构造的响应式对象都会将被代理对象和响应式对象的映射关系保存在reactiveMap
,防止重复生成响应式对象,优化性能; - 当调用
reactive
后会对被代理对象进行检查,若不是只读对象、响应式对象、primitive value和reactiveMap
中不存在则根据被代理对象的类型构造响应式对象 - 拦截读操作(
get
,has
和ownKeys
)时调用effect.ts中的track收集依赖 - 拦截写操作(
set
,deleteProperty
)时调用effect.ts中的trigger触发副作用函数执行
下面我们一起逐行理解源码吧!
源码解读——reactive
入口
// Vue3内部定义的对象特性标识
export const enum ReactiveFlags {
SKIP = '__v_skip', // 标识该对象不被代理
IS_REACTIVE = '__v_isReactive', // 标识该对象是响应式对象
IS_READONLY = '__v_isReadonly', // 标识该对象为只读对象
RAW = '__v_raw' // 指向被代理的JavaScript对象
}
// 响应式对象的接口
export interface Target {
[ReactiveFlags.SKIP]?: boolean
[ReactiveFlags.IS_REACTIVE]?: boolean
[ReactiveFlags.IS_READONLY]?: boolean
[ReactiveFlags.RAW]?: any // 用于指向被代理的JavaScript对象
}
// 用于缓存被代理对象和代理对象的关系,防止重复代理
export const reactiveMap = new WeakMap<Target, any>()
// 将被代理对象的处理方式分为不代理(INVALID)、普通对象和数组(COMMON)和Map、Set(COLLECTION)
const enum TargetType {
INVALID = 0,
COMMON = 1,
COLLECTION = 2,
}
function targetTypeMap(rawType: string) {
switch(rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
function getTargetType(value: Target) {
// 若对象标记为跳过,或不可扩展则不代理该对象
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
// 根据类型决定处理方式
: targetTypeMap(toRawType(value))
}
export function reative(target: object) {
// 不拦截只读对象的读写删操作
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
function createReactiveObject (
target: Target,
isReadonly: boolean,
beaseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// reactive函数入参必须是JavaScript对象或数组,若是primitive value则会直接返回primitive value
if (!isObject(target)) {
return target
}
/**
* 1. 仅能对非响应式和非只读对象构造响应式对象
* 2. 可以对非只读对象构造响应式对象
*/
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 若对象已被代理过,则直接返回对应的代理对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 根据被代理的对象类型决定采用哪种代理方式
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy (
target,
targetType == TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
可以看到reactive
方法中会对被代理对象进行各种检查,从而减少不必要的操作提高性能。最后若被代理对象的类型为Object
或Array
则采用baseHandlers
生成代理,否则使用collectionHandlers
生成代理。
源码解读-代理Object
和Array
的baseHandlers
//文件 ./baseHandlers.ts
// /*#__PURE__*/用于告诉webpack等bundler工具后面紧跟的函数是纯函数,若没被调用过则可以采用tree-shaking移除掉该函数
const get = /*#__PURE__*/ createGetter()
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys,
}
我们首先看看是如何拦截读操作吧
拦截读操作
拦截读操作核心是收集依赖所读属性的辅作用函数的信息,具体流程逻辑是
- 对于Vue3内部属性的读操作,即返回对应的值而不用收集依赖
- 对于数组内置方法的读操作,需要改写这些内置方法用于在调用该方法前对数组元素进行依赖收集,或解决一些边界问题
- 对于内置Symbol属性和其它Vue3内部属性的读操作,直接返回原始值且不用收集依赖
- 对于非只读对象的除上述外的其余属性的读操作,执行依赖收集(核心逻辑)
- 若浅层响应式对象则直接返回属性值,否则若属性值为对象,则将其构造为响应式对象(
reactive
)或只读对象(readonly
)
//文件 ./baseHandlers.ts
/**
* isNonTrackableKeys = {'__proto__': true, '__v_isRef': true, '__isVue': true}
*/
const isNonTrackableKeys = /*#__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`)
// 内置的Symbol实例包含:hasInstance, isConcatSpreadable, iterator, asyncIterator, match, matchAll, replace, search, split, toPrimitive, toStringTag, species, unscopables
const builtInSymbols = new Set(
Object.getOwnPropertyNames(Symbol)
.map(key => (Symbol as any)[key])
.filter(isSymbol)
)
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 处理Vue3内部属性名(`__v_isReactive`, `__v_isReadonly`, `__v_raw`)的值
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
}
// TODO
else if (
key === ReactiveFlags.RAW &&
receiver === reactiveMap
) {
return target
}
// 如果key是includes,indexOf,lastIndexOf,push,pop,shift,unshift,splice时则返回能跟踪依赖变化的版本
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
// 不拦截内置Symbol属性和__proto__,__v_isRef和__isVue属性
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 收集依赖该属性的副作用函数
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是构建ShallowReactive则不会基于属性值构造响应式式对象
if (shallow) {
return res
}
/* 对于属性值为@vue/reactivity的Ref实例时,如果不是执行[1,2,3][0]的操作则返回Ref实例包含的primitive value,否则返回Ref实例
* 因此我们在effect updator中可以通过如下方式直接获取Ref实例属性的primitive value
* const age = ref(0), state = reactive({ age })
* console.log(age.value) // 回显0
* effect(() => { console.log(state.age) }) // 回显0
*/
if (isRef(res)) {
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 若属性值不是primitive value或BOM,则基于属性值构造响应式对象
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
}
}
这里可以看到当读取属性时才根据属性值类型来为属性值构造响应式对象,而不是当我们调用reactive
时就一股脑的遍历对象所有属性,并为各个属性构建响应式对象。
另外,针对includes
等数组操作会返回对应的能跟踪依赖变化的版本,到底什么是能跟踪依赖变化的版本呢?
// 文件 ./baseHandlers.ts
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
function createArrayInstrumentations() {
const instrumentations: Record<string, Function> = {}
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
const arr = toRaw(this) as any
/* 提前遍历数组所有元素,跟踪每个元素的变化。若其中一个元素发生变化都会触发调用includes,indexOf或lastIndexOf副作用函数的执行。
* 假如执行`[2,1,2].includes(1)`,那么当匹配到第二个元素1时就会返回匹配结果,后续的元素不会被读取到,因此也就不会被跟踪收集到,那么当我们执行`[2,1,2][2] = 1`时就不会触发副作用执行。
*/
//
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// 调用数组原生的includes,indexOf和lastIndexOf方法
const res = arr[key](...args)
if (res === -1 // indexOf和lastIndexOf
|| res === false // includes
) {
// 由于数组元素有可能为响应式对象而入参也有可能是响应式对象,因此当匹配失败,则将尝试获取数组元素的被代理对象重新匹配
return arr[key](...args.map(toRaw))
} else {
return res
}
}
})
// 下面的操作会修改数组的长度,这里避免触发依赖长度的副作用函数执行
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
instrumentations[key] = function(this: unknown[], ...args: unknown[]) {
pauseTracking()
const res = (toRaw(this) as any)[key].apply(this, args)
resetTracking()
return res
}
})
return instrumentations
}
// 文件 ./reactive.ts
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
TypeScript小课堂1:['includes', 'indexOf', 'lastIndexOf'] as const
在TypeScript中用于标识对象或数组为不可修改对象。即
let a = ['includes', 'indexOf', 'lastIndexOf'] as const
a[0] = 'hi' // 编译时报错
const b = ['includes', 'indexOf', 'lastIndexOf']
b[0] = 'hi' // 修改成功
console.log(b[0]) // 回显 hi
TypeScript小课堂2:instrumentations[key] = function(this: unknown[], ...args: unknown[]) {...}
采用的是TypeScript的this
参数,用于限制调用函数时的this
类型。
转换为JavaScript就是
instrumentations[key] = function(...args){
pauseTracking()
const res = (toRaw(this) as any)[key].apply(this, args)
resetTracking()
return res
}
拦截写操作
既然拦截读操作是为了收集依赖,那么拦截写操作自然就是用于触发副作用函数了。流程逻辑如下:
- 若属性值为Ref对象,而新值取原始值后不是Ref对象,则更新Ref对象的value,由Ref内部触发副作用函数
- 判断是否为新增属性,还是更新属性值,并触发副作用函数
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
return function set(
target: Object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// Proxy的set拦截器返回true表示赋值成功,false表示赋值失败
let oldValue = (target as any)[key]
if (!shallow) {
/* 若旧属性值为Ref,而新值不是Ref,则直接将新值赋值给旧属性的value属性
* 一眼看上去貌似没有触发依赖该属性的副作用函数执行任务压入调度器,但Ref对象也是响应式对象,赋值给它的value属性,会触发依赖该Ref对象的辅佐用函数压入调度器
*/
value = toRaw(value)
oldValue = toRaw(oldValue)
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
}
// 用于判断是新增属性还是修改属性
const 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) {
// 触发依赖该属性的副作用函数执行任务压入调度器
trigger(target, TriggerOpTypes.ADD, key, value)
}
else if (hasChange(value, oldValue)) {
// 触发依赖该属性的副作用函数执行任务压入调度器
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
// 文件 @vue/shared
export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue)
为什么不用===
而要使用Object.is
来比较两个值是否相等呢?
对于-0===0
返回true
,NaN === NaN
返回false
,而Object.is(-0, 0)
返回false
,Object.is(NaN, NaN)
返回true
。
更多信息请查看《Source Code Reading for Vue 3: How does hasChanged
work? 》
拦截删除操作
删除操作会修改属性自然也会触发依赖该属性的副作用函数啦
function 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
}
拦截检查存在与否操作('name' in state
)
检查存在与否属于读操作,因此我们可以用于依赖收集。
function has(target: object, key: string | symbol): boolean {
const result = Reflect.has(target, key)
// Symbol内置属性不收集
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}
拦截键遍历操作
以下操作都会执行ownKeys
Proxy trap方法
Object.getOwnPropertyNames
Object.getOwnPropertySymbols
Object.keys
Object.names
for..in
流程逻辑是:对于数组则跟踪数组长度,否则跟踪由effect模块提供的ITERATE_KEY
,这个是什么东东呢?继续往下看就知道了:)
function ownKeys(target: object): (string | symbol)[] {
track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
Proxy中的receiver到底是什么?
在上述代码中我们发现会使用到Proxy拦截函数入参receiver
,如:
在写入拦截时,如果
target === toRaw(receiver)
成立则触发副作用函数执行在读取拦截时,若
key === ReactiveFlags.RAW && receiver === reactiveMap
则不以入参会基础构建响应式对象另外,在开篇《petite-vue源码剖析-从静态视图开始》中创建作用域链
createScopedContext
有如下代码const reactiveProxy = reactive(
new Proxy(mergeScope, {
set(target, key, val, receiver) {
// 若当设置的属性不存在于当前作用域则将值设置到父作用域上,由于父作用域以同样方式创建,因此递归找到拥有该属性的祖先作用域并赋值
if (receiver === reactiveProxy && !target.hasOwnProperty(key)) {
return Reflect.set(parentScope, key, val)
}
return Reflect.set(target, key, val, receiver)
}
})
)
那么到底receiver
是什么呢?
对于数据属性(data properties)的拦截,
receiver
指向当前构建的Proxy
实例本身// `receiver`指向当前构建的`Proxy`实例本身
const state = {
name: 'john'
}
let pState = new Proxy(state, {
get(target, key, receiver) {
console.log("receiver === pState:", receiver === pState)
return Reflect.get(target, key, receiver)
}
}) pState.name
// 回显 receiver === pState: true
对于访问器属性(accessor properties)的拦截,
receiver
指向this
或者继承Proxy
实例的对象const state = {
_name: 'john',
name() {
return this._name
}
} let pState = new Proxy(state, {
get(target, key, receiver) {
console.log("target[key]():", target[key])
console.log("receiver !== pState:", receiver !== pState)
return Reflect.get(target, key, receiver)
}
}) const son = {
__proto__: pState,
_name: 'son'
} console.log(son.name)
// 回显 target[key](): john
// 回显 receiver !== pState: true
// 回显 son
虽然了解了receiver
的作用,但对如下问题已经无法作出完整的解答:
- 在写入拦截时,如果
target === toRaw(receiver)
成立则触发副作用函数执行
首先receiver
是Proxy实例一定不会等于target
,而toRaw(receiver)
则是获取其代理的对象,仅当被代理的对象和当前target相同时才触发副作用函数执行。(至于什么场景会出现,求高人指导?) - 在读取拦截时,若
key === ReactiveFlags.RAW && receiver === reactiveMap
则不以入参会基础构建响应式对象
为何reactiveMap
会进行Proxy呢? - 另外,在开篇《petite-vue源码剖析-从静态视图开始》中创建作用域链
createScopedContext
如下代码
receiver === reactiveProxy && !target.hasOwnProperty(key)
即对当前作用域(receiver === reactiveProxy
)进行写操作时,若属性不存在于该作用域对象,则往父作用域上递归执行写操作。
总结
下一篇我们来看看代理Map/WeakMap/Set/WeakSet
的mutableCollectionHandlers
的实现吧!
尊重原创,转载请注明来自:https://www.cnblogs.com/fsjohnhuang/p/16037690.html肥仔John
petite-vue源码剖析-逐行解读@vue/reactivity之reactive的更多相关文章
- petite-vue源码剖析-逐行解读@vue-reactivity之effect
当我们通过effect将副函数向响应上下文注册后,副作用函数内访问响应式对象时即会自动收集依赖,并在相应的响应式属性发生变化后,自动触发副作用函数的执行. // ./effect.ts export ...
- petite-vue源码剖析-逐行解读@vue-reactivity之Map和Set的reactive
本篇我们会继续探索reactive函数中对Map/WeakMap/Set/WeakSet对象的代理实现. Map/WeakMap/Set/WeakSet的操作 由于WeakMap和WeakSet分别是 ...
- Vue源码分析(一) : new Vue() 做了什么
Vue源码分析(一) : new Vue() 做了什么 author: @TiffanysBear 在了解new Vue做了什么之前,我们先对Vue源码做一些基础的了解,如果你已经对基础的源码目录设计 ...
- [Vue源码]一起来学Vue模板编译原理(一)-Template生成AST
本文我们一起通过学习Vue模板编译原理(一)-Template生成AST来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vu ...
- [Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串
本文我们一起通过学习Vue模板编译原理(二)-AST生成Render字符串来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学V ...
- [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅
有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...
- vue源码分析之new Vue过程
实例化构造函数 从这里可以看出new Vue实际上是使vue构造函数实例化,然后调用_init方法 _init方法,该方法在 src/core/instance/init.js 中定义 Vue.pro ...
- Vue源码思维导图------------Vue选项的合并之$options
本节将看下初始化中的$options: Vue.prototype._init = function (options?: Object) { const vm: Component = this / ...
- 大白话Vue源码系列(01):万事开头难
阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的 ...
随机推荐
- Shell之sed编辑器
Shell之sed编辑器 目录 Shell之sed编辑器 一.sed编辑器 1. sed编辑器概述 2. sed编辑器的工作流程 二.sed命令 1. 命令格式 2. 常用选项 3. 常用操作 三.操 ...
- 通过示例学习PYTORCH
注意:这是旧版本的PyTorch教程的一部分.你可以在Learn the Basics查看最新的开始目录. 该教程通过几个独立的例子较少了PyTorch的基本概念. 核心是:PyTorch提供了两个主 ...
- 【CF457D】Bingo!(数学 期望)
题目链接 大意 给定\(N,M,K\),表示有一个\(N*N\)的空矩阵,\(M\)个不同的数. 随机地把\(M\)个数中的\(N^2\)个数丢进这个空矩阵中(\(M\ge N^2\)) 再从\(M\ ...
- 稳过!华为微认证ModelArts实现智能花卉识别稳过!
华为微认证ModelArts实现智能花卉识别稳过! 目录 华为微认证ModelArts实现智能花卉识别稳过! ModelArts实现智能花卉识别的概述 ModelArts实现智能花卉识别的解决方案 M ...
- fiddler模拟弱网1
第一步: 首先你得将你的fiddle配置好了,并链接上了移动端. 参考 这篇文章:http://www.cnblogs.com/lijiageng/p/6214162.html 第二步: 使用 ...
- springboot自动扫描添加的BeanDefinition源码解析
1. springboot启动过程中,首先会收集需要加载的bean的定义,作为BeanDefinition对象,添加到BeanFactory中去. 由于BeanFactory中只有getBean之类获 ...
- Solution -「LOCAL」过河
\(\mathcal{Description}\) 一段坐标轴 \([0,L]\),从 \(0\) 出发,每次可以 \(+a\) 或 \(-b\),但不能越出 \([0,L]\).求可达的整点数. ...
- 如何在3D场景中在模型上面绘制摄取点
有些时候,我们在屏幕上面绘制一个摄取点,在单屏玩游戏的模式下,我们并不能觉得有什么不妥.但是最近VR的热火朝天,我们带上眼镜看双屏的时候,总觉得这个摄取点看着很不舒服. 这个问题该怎么解决?在这里我首 ...
- C# Event 内核构造 |EventWaitHandle、AutoResetEvent、 ManualResetEvent
EventWaitHandle 继承:Object->WaitHandle-> EventWaitHandle派生:System.Threading.AutoResetEvent\Syst ...
- 一个C#程序的执行过程
可能很多人都知道我们把程序打包成dll就丢出去了,但是里面的具体的执行过程是怎么样的呢. 程序集是由元数据和IL组成的.IL是和CPU无关的语言,是微软的几个专家请教了外面的编译器的作则,开发出来的. ...