12 Vue 고주파 원리 면접 질문(분석 포함)

青灯夜游
풀어 주다: 2021-09-18 10:02:23
앞으로
51819명이 탐색했습니다.

12 Vue 고주파 원리 면접 질문(분석 포함)

이 기사에서는 Vue의 핵심 구현 원칙을 다루는 12개의 고빈도 Vue 원칙 인터뷰 질문을 공유합니다. 실제로 프레임워크의 구현 원칙을 하나의 기사로 마무리하는 것은 불가능합니다. 독자는 자신의 단점을 보완하고 Vue를 더 잘 마스터할 수 있도록 Vue 숙달(B 번호)에 대해 어느 정도 이해해야 합니다.

[관련 권장사항:vue 인터뷰 질문(2021)]

1. Vue 응답성 원칙

12 Vue 고주파 원리 면접 질문(분석 포함)

핵심 구현 수업:

관찰자: 그 기능 종속성 수집 및 업데이트 전달을 위해 개체의 속성에 getter 및 setter를 추가하는 것입니다.

Dep: 현재 반응형 개체의 종속성을 수집하는 데 사용됩니다. 하위 개체를 포함한 각 반응형 개체에는 Dep 인스턴스가 있습니다(내부 하위는 Watcher 인스턴스입니다). array), 데이터가 변경되면 dep.notify()를 통해 각 watcher에게 알림이 전달됩니다.

Watcher: Observer 객체, 인스턴스는 렌더링 감시자(render watcher), 계산된 속성 감시자(computed watcher), 청취자 감시자(user watcher)의 세 가지 유형으로 나뉩니다.

Watcher와 Dep의 관계

watcher는 dep를 인스턴스화합니다. dep.subs에 구독자를 추가했고, dep는 노티파이를 통해 dep.subs를 순회하여 각 감시자 업데이트를 알렸습니다.

종속성 컬렉션

  1. InitState, 계산된 속성이 초기화되면 계산된 감시자 종속성 컬렉션이 트리거됩니다.
  2. InitState, 수신 속성이 초기화되면 사용자 감시자 종속성 컬렉션이 트리거됩니다.
  3. render() 프로세스가 트리거됩니다. 렌더링 감시자 종속성
  4. re-render를 수집하면 vm.render()가 다시 실행되고 모든 하위에서 감시자의 구독이 제거되고 다시 할당됩니다.

업데이트 배포

  1. 응답 데이터는 구성 요소에서 수정되어 setter 로직을 트리거합니다.
  2. dep.notify()를 호출
  3. 모든 하위 항목(Watcher 인스턴스)을 탐색하고 각 감시자의 업데이트 메서드를 호출합니다.

Principle

Vue 인스턴스를 생성할 때 Vue는 데이터 옵션의 속성을 순회하고 Object.defineProperty를 사용하여 속성에 getter 및 setter를 추가하여 데이터 읽기를 하이재킹합니다(getter는 컬렉션에 의존하는 데 사용됩니다. 업데이트를 전달하는 데 사용되며 내부적으로 종속성을 추적하여 속성에 액세스하고 수정될 때 변경 사항을 알립니다.

각 구성 요소 인스턴스에는 구성 요소 렌더링 프로세스 중 종속성의 모든 데이터 속성(종속성 수집, 계산된 감시자 및 사용자 감시자 인스턴스)을 기록하는 해당 감시자 인스턴스가 있습니다. setter 메소드 이 데이터에 의존하는 감시자 인스턴스는 다시 계산(업데이트 디스패치)하라는 알림을 받고 관련 구성 요소를 다시 렌더링합니다.

한 문장으로 요약:

vue.js는 게시-구독 모드와 결합된 데이터 하이재킹을 사용하고, Object.defineproperty를 통해 각 속성의 setter 및 getter를 하이재킹하고, 데이터가 변경되면 구독자에게 메시지를 게시하고, 응답 모니터링 콜백을 트리거합니다

2.computed

computed의 구현 원리는 본질적으로 게으른 평가 관찰자입니다.

computed는 게으른 감시자, 즉 계산된 감시자를 내부적으로 구현합니다. 계산된 감시자는 즉시 평가하지 않으며 dep 인스턴스도 보유합니다.

내부적으로 this.dirty 속성을 사용하여 계산된 속성을 재평가해야 하는지 여부를 표시합니다.

계산된 종속성 상태가 변경되면 이 게으른 감시자가 알림을 받게 됩니다.

계산된 감시자는 this.dep.subs.length를 통해 구독자가 있는지 여부를 판단합니다.

있는 경우 새 항목과 구독자를 다시 계산하여 비교합니다. 값이 변경되면 다시 렌더링됩니다. (Vue는 계산된 속성이 변경에 따라 달라지는 값뿐만 아니라 계산된 속성의 최종 계산된 값이 변경되면 렌더링 감시자가 다시 렌더링되도록 트리거되는 것을 보장하고 싶어하는데, 이는 본질적으로 최적화입니다.)

그렇지 않다면 그냥 this.dirty = true라고 입력하세요. (계산된 속성이 다른 데이터에 의존하는 경우 해당 속성은 즉시 다시 계산되지 않습니다. 나중에 다른 곳에서 해당 속성을 읽어야 하는 경우에만 실제로 계산됩니다. 즉, 게으른(lazy Calculation) 특성이 있습니다.)

3. 계산된 값과 시계의 차이점은 무엇인가요? 계산된 값은 다음에 얻어집니다. 계산된 값은 해당 값에 도달한 경우에만 다시 계산됩니다.감시 리스너: 이는 모니터링되는 데이터가 변경될 때마다 후속 작업을 위해 콜백이 실행되는 특정 데이터의 모니터링 콜백과 유사한

캐시 없는

"관찰" 기능에 가깝습니다.

애플리케이션 시나리오

애플리케이션 시나리오:수치 계산을 수행해야 하고 다른 데이터에 의존해야 하는 경우 계산을 사용해야 합니다. 왜냐하면 계산의 캐시 기능을 사용하면 값을 얻을 때마다 재계산을 피할 수 있기 때문입니다.

Watch는 데이터가 변경될 때 비동기식 또는 비용이 많이 드는 작업을 수행해야 할 때 사용해야 합니다. watch 옵션을 사용하면 비동기 작업(API 액세스)을 수행하고 작업 수행 빈도를 제한하며 최종 결과를 얻을 때까지 기다릴 수 있습니다. 결과 이전에 중간 상태를 설정합니다. 이는 계산된 속성이 수행할 수 없는 작업입니다.

4. Vue3.0 및 Object.defineProperty에서 Proxy가 채택되는 이유는 무엇입니까?

Object.defineProperty 자체에는 배열 첨자 변경 사항을 모니터링하는 특정 기능이 있지만 Vue에서는 성능/경험 비용 효율성 측면에서 Youda가 이 기능을 포기했습니다. ( Vue에서 배열 변경 사항을 감지할 수 없는 이유) . 이 문제를 해결하기 위해 Vue 내부 처리 후 다음 방법을 사용하여 배열을 모니터링할 수 있습니다
push(); pop(); shift(); unshift(); splice(); sort(); reverse();
로그인 후 복사

위 7가지 방법만 해킹되었기 때문에 다른 배열의 속성을 감지할 수 없으며 여전히 일정한 제한이 있습니다. .

Object.defineProperty는 객체의 속성만 하이재킹할 수 있으므로 각 객체의 각 속성을 순회해야 합니다. Vue 2.x에서는 데이터 객체의 재귀 + 순회를 통해 데이터 모니터링이 이루어집니다. 속성 값도 객체라면 심층 탐색이 필요합니다. 물론 완전한 객체를 탈취할 수 있다면 더 나은 선택입니다.

프록시는 전체 개체를 하이재킹하고 새 개체를 반환할 수 있습니다. 프록시는 객체뿐만 아니라 프록시 배열도 프록시할 수 있습니다. 동적으로 추가된 속성을 프록시할 수도 있습니다.

5. Vue에서 키의 용도는 무엇인가요?

key는 각 vnode에 부여되는 고유 ID입니다. 키에 따라 diff 작업이 더 정확하고 빨라질 수 있습니다. diff 노드는 간단한 목록 페이지 렌더링의 경우에도 더 빠르지만 몇 가지 숨겨진 부작용이 발생합니다. 등 전환 효과가 없거나 일부 노드에 바인딩된 데이터(형태) 상태가 있을 수 있으며 상태 불일치가 발생할 수 있습니다.)

diff 알고리즘 과정에서 이전과의 정면 교차 비교가 수행됩니다. 새 노드가 먼저 수행되고 일치하는 항목이 없으면 새 노드의 키를 이전 노드와 비교하여 해당 이전 노드를 찾습니다.

더 정확합니다. 키가 제자리에서 재사용되지 않기 때문입니다. sameNode 함수에서 비교할 수 있습니다. a.key === b.key 내부 재사용을 피하세요. 따라서 키를 추가하지 않으면 이전 노드의 상태가 유지되므로 일련의 버그가 발생합니다.

빠름: Map 데이터 구조를 통해 키의 고유성을 최대한 활용할 수 있습니다. 순회 검색의 시간 복잡도 O(n)에 비해 Map의 시간 복잡도는 O(1)에 불과합니다.

function createKeyToOldIdx(children, beginIdx, endIdx) { let i, key; const map = {}; for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key; if (isDef(key)) map[key] = i; } return map; }
로그인 후 복사

6.nextTick

JS 실행 메커니즘

JS 실행은 단일 스레드이며 이벤트 루프를 기반으로 합니다. 이벤트 루프는 대략 다음 단계로 나뉩니다.

  1. 모든 동기화 작업은 메인 스레드에서 실행되어 실행 컨텍스트 스택을 형성합니다.
  2. 메인 스레드 외에 "작업 대기열"도 있습니다. 비동기 작업에 실행 결과가 있는 한 이벤트는 "작업 대기열"에 배치됩니다.
  3. "실행 스택"의 모든 동기화 작업이 실행되면 시스템은 "작업 대기열"을 읽어 그 안에 어떤 이벤트가 있는지 확인합니다. 해당 비동기 작업은 대기 상태를 종료하고 실행 스택에 들어가 실행을 시작합니다.
  4. 메인 스레드는 위의 세 번째 단계를 계속 반복합니다.

12 Vue 고주파 원리 면접 질문(분석 포함)

메인 스레드의 실행 프로세스는 1틱이며 모든 비동기 결과는 "작업 대기열"을 통해 예약됩니다. 메시지 큐는 작업을 하나씩 저장합니다. 사양에서는 작업을 매크로 작업과 마이크로 작업이라는 두 가지 범주로 나누고 각 매크로 작업이 끝나면 모든 마이크로 작업을 지워야 한다고 규정합니다.

for (macroTask of macroTaskQueue) { // 1. Handle current MACRO-TASK handleMacroTask(); // 2. Handle all MICRO-TASK for (microTask of microTaskQueue) { handleMicroTask(microTask); } }
로그인 후 복사

브라우저 환경:

공통 매크로 작업에는setTimeout, MessageChannel, postMessage, setImmediate

공통 마이크로 작업에는MutationObsever 및 Promise.then

비동기 업데이트 대기열

이 포함됩니다.

어쩌면 당신은 천국일지도 모릅니다 DOM을 업데이트할 때 Vue가비동기적으로실행된다는 사실을 알지 못했습니다. 데이터 변경 사항을 수신하는 한 Vue는 대기열을 열고 동일한 이벤트 루프에서 발생하는 모든 데이터 변경 사항을 버퍼링합니다.

동일한 감시자가 여러 번 실행되면 대기열에 한 번만 푸시됩니다. 버퍼링 중 이러한 중복 제거는 불필요한 계산 및 DOM 작업을 방지하는 데 중요합니다.

그런 다음 다음 이벤트 루프 "tick"에서 Vue는 대기열을 플러시하고 실제(중복 제거된) 작업을 수행합니다.

Vue는 내부적으로 비동기 대기열에 대해 기본 Promise.then, MutationObserver 및 setImmediate를 사용하려고 시도합니다. 실행 환경이 이를 지원하지 않으면 setTimeout(fn, 0)이 대신 사용됩니다.

vue2.5의 소스 코드에서 매크로태스크 다운그레이드 계획은 다음과 같습니다: setImmediate, MessageChannel, setTimeout

vue 的 nextTick 方法的实现原理:

  1. vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行
  2. microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
  3. 考虑兼容问题,vue 做了 microtask 向 macrotask 的降级方案

7. vue 是如何对数组方法进行变异的 ?

我们先来看看源码

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 method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); const ob = this.__ob__; let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; } if (inserted) ob.observeArray(inserted); // notify change ob.dep.notify(); return result; }); }); /** * Observe a list of Array items. */ Observer.prototype.observeArray = function observeArray(items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } };
로그인 후 복사

简单来说,Vue 通过原型拦截的方式重写了数组的 7 个方法,首先获取到这个数组的ob,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 对新的值进行监听,然后手动调用 notify,通知 render watcher,执行 update

8. Vue 组件 data 为什么必须是函数 ?

new Vue()实例中,data 可以直接是一个对象,为什么在 vue 组件中,data 必须是一个函数呢?

因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。

所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。

9. 谈谈 Vue 事件机制,手写$on,$off,$emit,$once

Vue 事件机制 本质上就是 一个 发布-订阅 模式的实现。
class Vue { constructor() { // 事件通道调度中心 this._events = Object.create(null); } $on(event, fn) { if (Array.isArray(event)) { event.map(item => { this.$on(item, fn); }); } else { (this._events[event] || (this._events[event] = [])).push(fn); } return this; } $once(event, fn) { function on() { this.$off(event, on); fn.apply(this, arguments); } on.fn = fn; this.$on(event, on); return this; } $off(event, fn) { if (!arguments.length) { this._events = Object.create(null); return this; } if (Array.isArray(event)) { event.map(item => { this.$off(item, fn); }); return this; } const cbs = this._events[event]; if (!cbs) { return this; } if (!fn) { this._events[event] = null; return this; } let cb; let i = cbs.length; while (i--) { cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break; } } return this; } $emit(event) { let cbs = this._events[event]; if (cbs) { const args = [].slice.call(arguments, 1); cbs.map(item => { args ? item.apply(this, args) : item.call(this); }); } return this; } }
로그인 후 복사

10. 说说 Vue 的渲染过程

12 Vue 고주파 원리 면접 질문(분석 포함)

  1. 调用 compile 函数,生成 render 函数字符串 ,编译过程如下:
  • parse 函数解析 template,生成 ast(抽象语法树)
  • optimize 函数优化静态节点 (标记不需要每次都更新的内容,diff 算法会直接跳过静态节点,从而减少比较的过程,优化了 patch 的性能)
  • generate 函数生成 render 函数字符串
  1. 调用 new Watcher 函数,监听数据的变化,当数据发生变化时,Render 函数执行生成 vnode 对象
  2. 调用 patch 方法,对比新旧 vnode 对象,通过 DOM diff 算法,添加、修改、删除真正的 DOM 元素

11. 聊聊 keep-alive 的实现原理和缓存策略

export default { name: "keep-alive", abstract: true, // 抽象组件属性 ,它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle 的过程中 props: { include: patternTypes, // 被缓存组件 exclude: patternTypes, // 不被缓存组件 max: [String, Number] // 指定缓存大小 }, created() { this.cache = Object.create(null); // 缓存 this.keys = []; // 缓存的VNode的键 }, destroyed() { for (const key in this.cache) { // 删除所有缓存 pruneCacheEntry(this.cache, key, this.keys); } }, mounted() { // 监听缓存/不缓存组件 this.$watch("include", val => { pruneCache(this, name => matches(val, name)); }); this.$watch("exclude", val => { pruneCache(this, name => !matches(val, name)); }); }, render() { // 获取第一个子元素的 vnode const slot = this.$slots.default; const vnode: VNode = getFirstComponentChild(slot); const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions; if (componentOptions) { // name不在inlcude中或者在exlude中 直接返回vnode // check pattern const name: ?string = getComponentName(componentOptions); const { include, exclude } = this; if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode; } const { cache, keys } = this; // 获取键,优先获取组件的name字段,否则是组件的tag const key: ?string = vnode.key == null ? // same constructor may get registered as different local components // so cid alone is not enough (#3269) componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : "") : vnode.key; // 命中缓存,直接从缓存拿vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance; // make current key freshest remove(keys, key); keys.push(key); } // 不命中缓存,把 vnode 设置进缓存 else { cache[key] = vnode; keys.push(key); // prune oldest entry // 如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个 if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode); } } // keepAlive标记位 vnode.data.keepAlive = true; } return vnode || (slot && slot[0]); } };
로그인 후 복사

原理

  1. 获取 keep-alive 包裹着的第一个子组件对象及其组件名
  2. 根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例
  3. 根据组件 ID 和 tag 生成缓存 Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新 key 的位置是实现 LRU 置换策略的关键)
  4. 在 this.cache 对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据 LRU 置换策略删除最近最久未使用的实例(即是下标为 0 的那个 key)
  5. 最后组件实例的 keepAlive 属性设置为 true,这个在渲染和执行被包裹组件的钩子函数会用到,这里不细说

LRU 缓存淘汰算法

LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

12 Vue 고주파 원리 면접 질문(분석 포함)

keep-alive 的实现正是用到了 LRU 策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0]

12. vm.$set()实现原理是什么?

受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。

由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。

那么 Vue 内部是如何解决对象新增属性不能响应的问题的呢?

export function set(target: Array | Object, key: any, val: any): any { // target 为数组 if (Array.isArray(target) && isValidArrayIndex(key)) { // 修改数组的长度, 避免索引>数组长度导致splice()执行有误 target.length = Math.max(target.length, key); // 利用数组的splice变异方法触发响应式 target.splice(key, 1, val); return val; } // target为对象, key在target或者target.prototype上 且必须不能在 Object.prototype 上,直接赋值 if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } // 以上都不成立, 即开始给target创建一个全新的属性 // 获取Observer实例 const ob = (target: any).__ob__; // target 本身就不是响应式数据, 直接赋值 if (!ob) { target[key] = val; return val; } // 进行响应式处理 defineReactive(ob.value, key, val); ob.dep.notify(); return val; }
로그인 후 복사
  1. 如果目标是数组,使用 vue 实现的变异方法 splice 实现响应式
  2. 如果目标是对象,判断属性存在,即为响应式,直接赋值
  3. 如果 target 本身就不是响应式,直接赋值
  4. 如果属性不是响应式,则调用 defineReactive 方法进行响应式处理

本文转载自:https://segmentfault.com/a/1190000021407782

推荐教程:《JavaScript视频教程

위 내용은 12 Vue 고주파 원리 면접 질문(분석 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:segmentfault.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.