最后
面试一面会问很多基础问题,而这些基础问题基本上在网上搜索,面试题都会很多很多。最好把准备一下常见的面试问题,毕竟面试也相当与一次考试,所以找工作面试的准备千万别偷懒。面试就跟考试一样的,时间长了不复习,现场表现肯定不会太好。表现的不好面试官不可能说,我猜他没发挥好,我录用他吧。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
96道前端面试题:
常用算法面试题:
前端基础面试题:
内容主要包括HTML,CSS,JavaScript,浏览器,性能优化
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler,
collectionHandlers: ProxyHandler
) {
if (!isObject(target)) {
if (DEV) {
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
if (
target[ReactiveFlags.raw] &&
!(isReadonly && target[ReactiveFlags.isReactive])
) {
return target
}
// target already has corresponding Proxy
if (
hasOwn(target, isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive)
) {
return isReadonly
-
? target[ReactiveFlags.readonly]
- target[ReactiveFlags.reactive]
}
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target
}
const observed = new Proxy(
target,
// 根据是否 Set, Map, WeakMap, WeakSet 来决定 proxy 的 handler 参数
collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
)
// 在原始对象上定义一个属性(只读则为 “__v_readonly”,否则为 “__v_reactive”),这个属性的值就是根据原始对象生成的 proxy 实例。
def(
target,
isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive,
observed
)
return observed
}
这个函数的处理逻辑如下:
1.如果 target 不是一个对象,返回 target。2.如果 target 已经是 proxy 实例,返回 target。3.如果 target 不是一个可观察的对象,返回 target。4.生成 proxy 实例,并在原始对象 target 上添加一个属性(只读则为 __v_readonly
,否则为 __v_reactive
),指向这个 proxy 实例,最后返回这个实例。添加这个属性就是为了在第 2 步做判断用的,防止对同一对象重复监听。
其中第 3、4 点需要单独拎出来讲一讲。
什么是可观察的对象
const canObserve = (value: Target): boolean => {
return (
!value[ReactiveFlags.skip] &&
isObservableType(toRawType(value)) &&
!Object.isFrozen(value)
)
}
canObserve()
函数就是用来判断 value 是否是可观察的对象,满足以下条件才是可观察的对象:
1.ReactiveFlags.skip 的值不能为 __v_skip
,__v_skip
是用来定义这个对象是否可跳过,即不监听。2.target 的类型必须为下列值之一 Object,Array,Map,Set,WeakMap,WeakSet
才可被监听。3.不能是冻结的对象。
传递给 proxy 的处理器对象是什么
根据上面的代码可以看出来,在生成 proxy 实例时,处理器对象是根据一个三元表达式产生的:
// collectionTypes 的值为 Set, Map, WeakMap, WeakSet
collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
这个三元表达式非常简单,如果是普通的对象 Object
或 Array
,处理器对象就使用 baseHandlers;如果是 Set, Map, WeakMap, WeakSet
中的一个,就使用 collectionHandlers。
collectionHandlers 和 baseHandlers 是从 collectionHandlers.ts
和 baseHandlers.ts
处引入的,这里先放一放,接下来再讲。
有多少种 proxy 实例
createReactiveObject()
根据不同的参数,可以创建多种不同的 proxy 实例:
1.完全响应式的 proxy 实例,如果有嵌套对象,会递归调用 reactive()
。2.只读的 proxy 实例。3.浅层响应的 proxy 实例,即一个对象只有第一层的属性是响应式的。4.只读的浅层响应的 proxy 实例。
浅层响应的 proxy 实例是什么?
之所以有浅层响应的 proxy 实例,是因为 proxy 只代理对象的第一层属性,更深层的属性是不会代理的。如果确实需要生成完全响应式的 proxy 实例,就得递归调用 reactive()
。不过这个过程是内部自动执行的,用户感知不到。
其他一些函数介绍
// 判断 value 是否是响应式的
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
return isReactive((value as Target)[ReactiveFlags.raw])
}
return !!(value && (value as Target)[ReactiveFlags.isReactive])
}
// 判断 value 是否是只读的
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.isReadonly])
}
// 判断 value 是否是 proxy 实例
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
// 将响应式数据转为原始数据,如果不是响应数据,则返回源数据
export function toRaw(observed: T): T {
return (
(observed && toRaw((observed as Target)[ReactiveFlags.raw])) || observed
)
}
// 给 value 设置 skip 属性,跳过代理,让数据不可被代理
export function markRaw(value: T): T {
def(value, ReactiveFlags.skip, true)
return value
}
baseHandlers.ts 文件
在 baseHandlers.ts
文件中针对 4 种 proxy 实例定义了不对的处理器。由于它们之间差别不大,所以在这只讲解完全响应式的处理器对象:
export const mutableHandlers: ProxyHandler = {
get,
set,
deleteProperty,
has,
ownKeys
}
处理器对五种操作进行了拦截,分别是:
1.get 属性读取2.set 属性设置3.deleteProperty 删除属性4.has 是否拥有某个属性5.ownKeys
其中 ownKeys 可拦截以下操作:
1.Object.getOwnPropertyNames()
2.Object.getOwnPropertySymbols()
3.Object.keys()
4.Reflect.ownKeys()
其中 get、has、ownKeys 操作会收集依赖,set、deleteProperty 操作会触发依赖。
get
get 属性的处理器是用 createGetter()
函数创建的:
// /#PURE/ 标识此为纯函数 不会有副作用 方便做 tree-shaking
const get = /#PURE/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
// target 是否是响应式对象
if (key === ReactiveFlags.isReactive) {
return !isReadonly
// target 是否是只读对象
} else if (key === ReactiveFlags.isReadonly) {
return isReadonly
} else if (
// 如果访问的 key 是 __v_raw,并且 receiver == target.__v_readonly || receiver == target.__v_reactive
// 则直接返回 target
key === ReactiveFlags.raw &&
receiver ===
(isReadonly
-
? (target as any).__v_readonly
- (target as any).__v_reactive)
) {
return target
}
const targetIsArray = isArray(target)
// 如果 target 是数组并且 key 属于三个方法之一 [‘includes’, ‘indexOf’, ‘lastIndexOf’],即触发了这三个操作之一
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
// 不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
// 如果不用 Reflect 来获取,在监听数组时可以会有某些地方会出错
// 具体请看文章《Vue3 中的数据侦测》——https://juejin.im/post/6844903957807169549#heading-10
const res = Reflect.get(target, key, receiver)
// 如果 key 是 symbol 并且属于 symbol 的内置方法之一,或者访问的是原型对象,直接返回结果,不收集依赖。
if ((isSymbol(key) && builtInSymbols.has(key)) || key === ‘proto’) {
return res
}
// 只读对象不收集依赖
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 浅层响应立即返回,不递归调用 reactive()
if (shallow) {
return res
}
// 如果是 ref 对象,则返回真正的值,即 ref.value,数组除外。
if (isRef(res)) {
// ref unwrapping, only for Objects, not for Arrays.
return targetIsArray ? res : res.value
}
if (isObject(res)) {
// 由于 proxy 只能代理一层,所以 target[key] 的值如果是对象,就继续对其进行代理
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
这个函数的处理逻辑看代码注释应该就能明白,其中有几个点需要单独说一下:
1.Reflect.get()
2.数组的处理3.builtInSymbols.has(key)
为 true 或原型对象不收集依赖
Reflect.get()
Reflect.get()
方法与从对象 (target[key])
中读取属性类似,但它是通过一个函数执行来操作的。
为什么直接用 target[key]
就能得到值,却还要用 Reflect.get(target, key, receiver)
来多倒一手呢?
先来看个简单的示例:
const p = new Proxy([1, 2, 3], {
get(target, key, receiver) {
return target[key]
},
set(target, key, value, receiver) {
target[key] = value
}
})
p.push(100)
运行这段代码会报错:
Uncaught TypeError: ‘set’ on proxy: trap returned falsish for property ‘3’
但做一些小改动就能够正常运行:
const p = new Proxy([1, 2, 3], {
get(target, key, receiver) {
return target[key]
},
set(target, key, value, receiver) {
target[key] = value
return true // 新增一行 return true
}
})
p.push(100)
这段代码可以正常运行。为什么呢?
区别在于新的这段代码在 set()
方法上多了一个 return true
。我在 MDN 上查找到的解释是这样的:
set()
方法应当返回一个布尔值。
•返回 true
代表属性设置成功。•在严格模式下,如果 set()
方法返回 false
,那么会抛出一个 TypeError
异常。
这时我又试了一下直接执行 p[3] = 100
,发现能正常运行,只有执行 push
方法才报错。到这一步,我心中已经有答案了。为了验证我的猜想,我在代码上加了 console.log()
,把代码执行过程的一些属性打印出来。
const p = new Proxy([1, 2, 3], {
get(target, key, receiver) {
console.log('get: ', key)
return target[key]
},
set(target, key, value, receiver) {
console.log('set: ', key, value)
target[key] = value
return true
}
})
p.push(100)
// get: push
// get: length
// set: 3 100
// set: length 4
从上面的代码可以发现执行 push
操作时,还会访问 length
属性。推测执行过程如下:根据 length
的值,得出最后的索引,再设置新的值,最后再改变 length
。
结合 MDN 的解释,我的推测是数组的原生方法应该是运行在严格模式下的(如果有网友知道真相,请在评论区留言)。因为在 JS 中很多代码在非严格模式和严格模式下都能正常运行,只是严格模式会给你报个错。就跟这次情况一样,最后设置 length
属性的时候报错,但结果还是正常的。如果不想报错,就得每次都返回 true
。
然后再看一下 Reflect.set()
的返回值说明:
返回一个 Boolean 值表明是否成功设置属性。
所以上面代码可以改成这样:
const p = new Proxy([1, 2, 3], {
get(target, key, receiver) {
console.log('get: ', key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set: ', key, value)
return Reflect.set(target, key, value, receiver)
}
})
p.push(100)
另外,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为。
通过上面的示例,不难理解为什么要通过 Reflect.set()
来代替 Proxy 完成默认操作了。同理,Reflect.get()
也一样。
数组的处理
// 如果 target 是数组并且 key 属于三个方法之一 [‘includes’, ‘indexOf’, ‘lastIndexOf’],即触发了这三个操作之一
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
在执行数组的 includes
, indexOf
, lastIndexOf
方法时,会把目标对象转为 arrayInstrumentations
再执行。
const arrayInstrumentations: Record<string, Function> = {}
;[‘includes’, ‘indexOf’, ‘lastIndexOf’].forEach(key => {
arrayInstrumentations[key] = function(…args: any[]): any {
// 如果 target 对象中指定了 getter,receiver 则为 getter 调用时的 this 值。
// 所以这里的 this 指向 receiver,即 proxy 实例,toRaw 为了取得原始数据
const arr = toRaw(this) as any
// 对数组的每个值进行 track 操作,收集依赖
for (let i = 0, l = (this as any).length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + ‘’)
}
// we run the method using the original args first (which may be reactive)
// 参数有可能是响应式的,函数执行后返回值为 -1 或 false,那就用参数的原始值再试一遍
const res = arrkey
if (res === -1 || res === false) {
// if that didn’t work, run it again using raw values.
return arrkey
} else {
return res
}
}
})
从上述代码可以看出,Vue3.0 对 includes
, indexOf
, lastIndexOf
进行了封装,除了返回原有方法的结果外,还会对数组的每个值进行依赖收集。
builtInSymbols.has(key)
为 true 或原型对象不收集依赖
const p = new Proxy({}, {
get(target, key, receiver) {
console.log('get: ', key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set: ', key, value)
return Reflect.set(target, key, value, receiver)
}
})
p.toString() // get: toString
// get: Symbol(Symbol.toStringTag)
p.proto // get: proto
从 p.toString()
的执行结果来看,它会触发两次 get,一次是我们想要的,一次是我们不想要的(我还没搞明白为什么会有 Symbol(Symbol.toStringTag)
,如果有网友知道,请在评论区留言)。所以就有了这个判断: builtInSymbols.has(key)
为 true
就直接返回,防止重复收集依赖。
再看 p.__proto__
的执行结果,也触发了一次 get 操作。一般来说,没有场景需要单独访问原型,访问原型都是为了访问原型上的方法,例如 p.__proto__.toString()
这样使用,所以 key 为 __proto__
的时候也要跳过,不收集依赖。
set
const set = /#PURE/ createSetter()
// 参考文档《Vue3 中的数据侦测》——https://juejin.im/post/6844903957807169549#heading-10
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
// 如果原来的值是 ref,但新的值不是,将新的值赋给 ref.value 即可。
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey = 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
if (target === toRaw(receiver)) {
if (!hadKey) {
// 如果 target 没有 key,就代表是新增操作,需要触发依赖
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果新旧值不相等,才触发依赖
// 什么时候会有新旧值相等的情况?例如监听一个数组,执行 push 操作,会触发多次 setter
// 第一次 setter 是新加的值 第二次是由于新加的值导致 length 改变
// 但由于 length 也是自身属性,所以 value === oldValue
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
set()
的函数处理逻辑反而没那么难,看注释即可。track()
和 trigger()
将放在下面和 effect.ts 文件一起讲解。
deleteProperty、has、ownKeys
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)
// 如果删除结果为 true 并且 target 拥有这个 key 就触发依赖
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
function has(target: object, key: string | symbol): boolean {
const result = Reflect.has(target, key)
track(target, TrackOpTypes.HAS, key)
return result
}
function ownKeys(target: object): (string | number | symbol)[] {
track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
return Reflect.ownKeys(target)
}
这三个函数比较简单,看代码即可。
effect.ts 文件
等把 effect.ts 文件讲解完,响应式模块基本上差不多结束了。
effect()
effect()
主要和响应式的对象结合使用。
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
// 如果已经是 effect 函数,取得原来的 fn
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
// 如果 lazy 为 false,马上执行一次
// 计算属性的 lazy 为 true
if (!options.lazy) {
effect()
}
return effect
}
真正创建 effect 的是 createReactiveEffect()
函数。
let uid = 0
function createReactiveEffect<T = any>(
fn: (…args: any[]) => T,
options: ReactiveEffectOptions
): ReactiveEffect {
// reactiveEffect() 返回一个新的 effect,这个新的 effect 执行后
// 会将自己设为 activeEffect,然后再执行 fn 函数,如果在 fn 函数里对响应式属性进行读取
// 会触发响应式属性 get 操作,从而收集依赖,而收集的这个依赖函数就是 activeEffect
const effect = function reactiveEffect(…args: unknown[]): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn(…args)
}
// 为了避免递归循环,所以要检测一下
if (!effectStack.includes(effect)) {
// 清空依赖
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn(…args)
} finally {
// track 将依赖函数 activeEffect 添加到对应的 dep 中,然后在 finally 中将 activeEffect
// 重置为上一个 effect 的值
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect._isEffect = true
effect.active = true // 用于判断当前 effect 是否激活,有一个 stop() 来将它设为 false
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
其中 cleanup(effect)
的作用是让 effect 关联下的所有 dep 实例清空 effect,即清除这个依赖函数。
function cleanup(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
从代码中可以看出来,真正的依赖函数是 activeEffect。执行 track()
收集的依赖就是 activeEffect。趁热打铁,现在我们再来看一下 track()
和 trigger()
函数。
track()
// 依赖收集
export function track(target: object, type: TrackOpTypes, key: unknown) {
// activeEffect 为空,代表没有依赖,直接返回
if (!shouldTrack || activeEffect === undefined) {
return
}
// targetMap 依赖管理中心,用于收集依赖和触发依赖
let depsMap = targetMap.get(target)
// targetMap 为每个 target 建立一个 map
// 每个 target 的 key 对应着一个 dep
// 然后用 dep 来收集依赖函数,当监听的 key 值发生变化时,触发 dep 中的依赖函数
// 类似于这样
// targetMap(weakmap) = {
// target1(map): {
// key1(dep): (fn1,fn2,fn3…)
// key2(dep): (fn1,fn2,fn3…)
// },
// target2(map): {
// key1(dep): (fn1,fn2,fn3…)
// key2(dep): (fn1,fn2,fn3…)
// },
// }
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
// 开发环境下会触发 onTrack 事件
if (DEV && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
targetMap 是一个 WeakMap
实例。
WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
弱引用是什么意思呢?
let obj = { a: 1 }
const map = new WeakMap()
map.set(obj, ‘测试’)
obj = null
当 obj 置为空后,对于 { a: 1 }
的引用已经为零了,下一次垃圾回收时就会把 weakmap 中的对象回收。
但如果把 weakmap 换成 map 数据结构,即使把 obj 置空,{ a: 1 }
依然不会被回收,因为 map 数据结构是强引用,它现在还被 map 引用着。
trigger()
// 触发依赖
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set
) {
const depsMap = targetMap.get(target)
// 如果没有收集过依赖,直接返回
if (!depsMap) {
// never been tracked
return
}
// 对收集的依赖进行分类,分为普通的依赖或计算属性依赖
// effects 收集的是普通的依赖 computedRunners 收集的是计算属性的依赖
// 两个队列都是 set 结构,为了避免重复收集依赖
const effects = new Set()
const computedRunners = new Set()
const add = (effectsToAdd: Set | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
// effect !== activeEffect 避免重复收集依赖
if (effect !== activeEffect || !shouldTrack) {
// 计算属性
if (effect.options.computed) {
computedRunners.add(effect)
} else {
effects.add(effect)
}
} else {
// the effect mutated its own dependency during its execution.
// this can be caused by operations like foo.value++
// do not trigger or we end in an infinite loop
}
})
}
}
// 在值被清空前,往相应的队列添加 target 所有的依赖
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === ‘length’ && isArray(target)) { // 当数组的 length 属性变化时触发
depsMap.forEach((dep, key) => {
if (key === ‘length’ || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
// 如果不符合以上两个 if 条件,并且 key !== undefined,往相应的队列添加依赖
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
const isAddOrDelete =
type === TriggerOpTypes.ADD ||
(type === TriggerOpTypes.DELETE && !isArray(target))
if (
isAddOrDelete ||
(type === TriggerOpTypes.SET && target instanceof Map)
) {
add(depsMap.get(isArray(target) ? ‘length’ : ITERATE_KEY))
}
if (isAddOrDelete && target instanceof Map) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
const run = (effect: ReactiveEffect) => {
if (DEV && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
// 如果 scheduler 存在则调用 scheduler,计算属性拥有 scheduler
effect.options.scheduler(effect)
} else {
effect()
}
}
// Important: computed effects must be run first so that computed getters
// can be invalidated before any normal effects that depend on them are run.
computedRunners.forEach(run)
// 触发依赖函数
effects.forEach(run)
}
对依赖函数进行分类后,需要先运行计算属性的依赖,因为其他普通的依赖函数可能包含了计算属性。先执行计算属性的依赖能保证普通依赖执行时能得到最新的计算属性的值。
track() 和 trigger() 中的 type 有什么用?
这个 type 取值范围就定义在 operations.ts
文件中:
// track 的类型
export const enum TrackOpTypes {
GET = ‘get’, // get 操作
HAS = ‘has’, // has 操作
ITERATE = ‘iterate’ // ownKeys 操作
}
// trigger 的类型
export const enum TriggerOpTypes {
最后
我可以将最近整理的前端面试题分享出来,其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器、数据结构与算法等等,还在持续整理更新中,希望大家都能找到心仪的工作。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
篇幅有限,仅展示部分截图:
h’ : ITERATE_KEY))
}
if (isAddOrDelete && target instanceof Map) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
const run = (effect: ReactiveEffect) => {
if (DEV && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
// 如果 scheduler 存在则调用 scheduler,计算属性拥有 scheduler
effect.options.scheduler(effect)
} else {
effect()
}
}
// Important: computed effects must be run first so that computed getters
// can be invalidated before any normal effects that depend on them are run.
computedRunners.forEach(run)
// 触发依赖函数
effects.forEach(run)
}
对依赖函数进行分类后,需要先运行计算属性的依赖,因为其他普通的依赖函数可能包含了计算属性。先执行计算属性的依赖能保证普通依赖执行时能得到最新的计算属性的值。
track() 和 trigger() 中的 type 有什么用?
这个 type 取值范围就定义在 operations.ts
文件中:
// track 的类型
export const enum TrackOpTypes {
GET = ‘get’, // get 操作
HAS = ‘has’, // has 操作
ITERATE = ‘iterate’ // ownKeys 操作
}
// trigger 的类型
export const enum TriggerOpTypes {
最后
我可以将最近整理的前端面试题分享出来,其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器、数据结构与算法等等,还在持续整理更新中,希望大家都能找到心仪的工作。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
篇幅有限,仅展示部分截图: