关于前端:滴滴前端一面常考vue面试题持续更新中

4次阅读

共计 11942 个字符,预计需要花费 30 分钟才能阅读完成。

为什么要应用异步组件

  1. 节俭打包出的后果,异步组件离开打包,采纳 jsonp 的形式进行加载,无效解决文件过大的问题。
  2. 外围就是包组件定义变成一个函数,依赖import() 语法,能够实现文件的宰割加载。
components:{AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([]) 
}

原理

export function (Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void { 
    // async component 
    let asyncFactory 
    if (isUndef(Ctor.cid)) { 
        asyncFactory = Ctor 
        Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend 
        // 第二次渲染时 Ctor 不为 undefined 
        if (Ctor === undefined) { 
            return createAsyncPlaceholder( // 渲染占位符 空虚构节点 
                asyncFactory, 
                data, 
                context, 
                children, 
                tag 
            ) 
        } 
    } 
}
function resolveAsyncComponent (factory: Function, baseCtor: Class<Component>): Class<Component> | void {if (isDef(factory.resolved)) { 
        // 3. 在次渲染时能够拿到获取的最新组件 
        return factory.resolved 
    }
    const resolve = once((res: Object | Class<Component>) => {factory.resolved = ensureCtor(res, baseCtor) 
        if (!sync) {forceRender(true) //2. 强制更新视图从新渲染 
        } else {owners.length = 0} 
    })
    const reject = once(reason => {if (isDef(factory.errorComp)) {factory.error = true forceRender(true) 
        } 
    })
    const res = factory(resolve, reject)// 1. 将 resolve 办法和 reject 办法传入,用户调用 resolve 办法后 
    sync = false 
    return factory.resolved 
}

Vue.js 的 template 编译

简而言之,就是先转化成 AST 树,再失去的 render 函数返回 VNode(Vue 的虚构 DOM 节点),具体步骤如下:

首先,通过 compile 编译器把 template 编译成 AST 语法树(abstract syntax tree 即 源代码的形象语法结构的树状表现形式),compile 是 createCompiler 的返回值,createCompiler 是用以创立编译器的。另外 compile 还负责合并 option。

而后,AST 会通过 generate(将 AST 语法树转化成 render funtion 字符串的过程)失去 render 函数,render 的返回值是 VNode,VNode 是 Vue 的虚构 DOM 节点,外面有(标签名、子节点、文本等等)

nextTick 在哪里应用?原理是?

  • nextTick 中的回调是在下次 DOM 更新循环完结之后执行提早回调,用于取得更新后的 DOM
  • 在批改数据之后立刻应用这个办法,获取更新后的 DOM
  • 次要思路就是采纳 微工作优先 的形式调用异步办法去执行 nextTick 包装的办法

nextTick 办法次要是应用了宏工作和微工作, 定义了一个异步办法. 屡次调用 nextTick 会将办法存入队列中,通过这个异步办法清空以后队列。所以这个 nextTick 办法就是异步办法

依据执行环境别离尝试采纳

  • 先采纳Promise
  • Promise不反对,再采纳MutationObserver
  • MutationObserver不反对,再采纳setImmediate
  • 如果以上都不行则采纳setTimeout
  • 最初执行 flushCallbacks,把callbacks 外面的数据顺次执行

答复范例

  1. nextTick 中的回调是在下次 DOM 更新循环完结之后执行提早回调,用于取得更新后的 DOM
  2. Vue有个异步更新策略,意思是如果数据变动,Vue不会立即更新 DOM,而是开启一个队列,把组件更新函数保留在队列中,在同一事件循环中产生的所有数据变更会异步的批量更新。这一策略导致咱们对数据的批改不会立即体现在 DOM 上,此时如果想要获取更新后的 DOM 状态,就须要应用nextTick
  3. 开发时,有两个场景咱们会用到nextTick
  4. created中想要获取 DOM
  • 响应式数据变动后获取 DOM 更新后的状态,比方心愿获取列表更新后的高度
  • nextTick签名如下:function nextTick(callback?: () => void): Promise<void>

所以咱们只须要在传入的回调函数中拜访最新 DOM 状态即可,或者咱们能够 await nextTick() 办法返回的 Promise 之后做这件事

  1. Vue 外部,nextTick之所以可能让咱们看到 DOM 更新后的后果,是因为咱们传入的 callback 会被增加到队列刷新函数 (flushSchedulerQueue) 的前面,这样等队列外部的更新函数都执行结束,所有 DOM 操作也就完结了,callback天然可能获取到最新的 DOM 值

根本应用

const vm = new Vue({
    el: '#app',
    data() {return { a: 1}
    }
}); 

// vm.$nextTick(() => {// [nextTick 回调函数 fn, 外部更新 flushSchedulerQueue]
//     console.log(vm.$el.innerHTML)
// })

// 是将内容保护到一个数组里,最终依照程序程序。第一次会开启一个异步工作

vm.a = 'test'; // 批改了数据后并不会马上更新视图
vm.$nextTick(() => {// [nextTick 回调函数 fn, 外部更新 flushSchedulerQueue]
    console.log(vm.$el.innerHTML)
})

// nextTick 中的办法会被放到 更新页面 watcher 的前面去

相干代码如下

// src/core/utils/nextTick
let callbacks = [];
let pending = false;
function flushCallbacks() {
  pending = false; // 把标记还原为 false
  // 顺次执行回调
  for (let i = 0; i < callbacks.length; i++) {callbacks[i]();}
}
let timerFunc; // 定义异步办法  采纳优雅降级
if (typeof Promise !== "undefined") {
  // 如果反对 promise
  const p = Promise.resolve();
  timerFunc = () => {p.then(flushCallbacks);
  };
} else if (typeof MutationObserver !== "undefined") {
  // MutationObserver 次要是监听 dom 变动 也是一个异步办法
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {characterData: true,});
  timerFunc = () => {counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
} else if (typeof setImmediate !== "undefined") {
  // 如果后面都不反对 判断 setImmediate
  timerFunc = () => {setImmediate(flushCallbacks);
  };
} else {
  // 最初降级采纳 setTimeout
  timerFunc = () => {setTimeout(flushCallbacks, 0);
  };
}

export function nextTick(cb) {
  // 除了渲染 watcher  还有用户本人手动调用的 nextTick 一起被收集到数组
  callbacks.push(cb);
  if (!pending) {
    // 如果屡次调用 nextTick  只会执行一次异步 等异步队列清空之后再把标记变为 false
    pending = true;
    timerFunc();}
}

数据更新的时候外部会调用nextTick

// src/core/observer/scheduler.js

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {has[id] = true
    if (!flushing) {queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {i--}
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) {flushSchedulerQueue()
        return
      }
      // 把更新办法放到数组中保护[nextTick 回调函数, 更新函数 flushSchedulerQueue]
      /**
       * vm.a = 'test'; // 批改了数据后并不会马上更新视图
        vm.$nextTick(() => {// [fn, 更新]
            console.log(vm.$el.innerHTML)
        })
       */
      nextTick(flushSchedulerQueue)
    }
  }
}

那 vue 中是如何检测数组变动的呢?

数组就是应用 object.defineProperty 从新定义数组的每一项,那能引起数组变动的办法咱们都是晓得的, pop push shift unshift splice sort reverse 这七种,只有这些办法执行改了数组内容,我就更新内容就好了,是不是很好了解。

  1. 是用来函数劫持的形式,重写了数组办法,具体呢就是更改了数组的原型,更改成本人的,用户调数组的一些办法的时候,走的就是本人的办法,而后告诉视图去更新。
  2. 数组里每一项可能是对象,那么我就是会对数组的每一项进行观测,(且只有数组里的对象能力进行观测,观测过的也不会进行观测)

vue3:改用 proxy,可间接监听对象数组的变动。

如何从实在 DOM 到虚构 DOM

波及到 Vue 中的模板编译原理,次要过程:

  1. 将模板转换成 ast 树, ast 用对象来形容实在的 JS 语法(将实在 DOM 转换成虚构 DOM)
  2. 优化树
  3. ast 树生成代码

虚构 DOM 实现原理?

  • 虚构 DOM 实质上是 JavaScript 对象, 是对实在 DOM 的形象
  • 状态变更时,记录新树和旧树的差别
  • 最初把差别更新到真正的 dom 中

参考 前端进阶面试题具体解答

为什么 Vue 采纳异步渲染呢?

Vue 是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图。核心思想 nextTick

dep.notify() 告诉 watcher 进行更新, subs[i].update 顺次调用 watcher 的 update queueWatcher 将 watcher 去重放入队列,nextTick( flushSchedulerQueue)在下一 tick 中刷新 watcher 队列(异步)。

Vue3.0 和 2.0 的响应式原理区别

Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够间接监听对象和数组的变动,并且有多达 13 种拦挡办法。

相干代码如下

import {mutableHandlers} from "./baseHandlers"; // 代理相干逻辑
import {isObject} from "./util"; // 工具办法

export function reactive(target) {
  // 依据不同参数创立不同响应式对象
  return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {if (!isObject(target)) {return target;}
  const observed = new Proxy(target, baseHandler);
  return observed;
}

const get = createGetter();
const set = createSetter();

function createGetter() {return function get(target, key, receiver) {
    // 对获取的值进行喷射
    const res = Reflect.get(target, key, receiver);
    console.log("属性获取", key);
    if (isObject(res)) {
      // 如果获取的值是对象类型,则返回以后对象的代理对象
      return reactive(res);
    }
    return res;
  };
}
function createSetter() {return function set(target, key, value, receiver) {const oldValue = target[key];
    const hadKey = hasOwn(target, key);
    const result = Reflect.set(target, key, value, receiver);
    if (!hadKey) {console.log("属性新增", key, value);
    } else if (hasChanged(value, oldValue)) {console.log("属性值被批改", key, value);
    }
    return result;
  };
}
export const mutableHandlers = {
  get, // 当获取属性时调用此办法
  set, // 当批改属性时调用此办法
};

Vue 的生命周期办法有哪些 个别在哪一步发申请

beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在以后阶段 data、methods、computed 以及 watch 上的数据和办法都不能被拜访

created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算,watch/event 事件回调。这里没有 $el, 如果非要想与 Dom 进行交互,能够通过 vm.$nextTick 来拜访 Dom

beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用。

mounted 在挂载实现后产生,在以后阶段,实在的 Dom 挂载结束,数据实现双向绑定,能够拜访到 Dom 节点

beforeUpdate 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁(patch)之前。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程

updated 产生在更新实现之后,以后阶段组件 Dom 已实现更新。要留神的是防止在此期间更改数据,因为这可能会导致有限循环的更新,该钩子在服务器端渲染期间不被调用。

beforeDestroy 实例销毁之前调用。在这一步,实例依然齐全可用。咱们能够在这时进行善后收尾工作,比方革除计时器。

destroyed Vue 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

activated keep-alive 专属,组件被激活时调用

deactivated keep-alive 专属,组件被销毁时调用

异步申请在哪一步发动?

能够在钩子函数 created、beforeMount、mounted 中进行异步申请,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。

如果异步申请不须要依赖 Dom 举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:

  • 能更快获取到服务端数据,缩小页面 loading 工夫;
  • ssr 不反对 beforeMount、mounted 钩子函数,所以放在 created 中有助于一致性;

Vue-router 路由有哪些模式?

个别有两种模式:

(1)**hash 模式 **:前面的 hash 值的变动,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变动会触发 hashchange 事件。(2)**history 模式 **:利用了 HTML5 中新增的 pushState() 和 replaceState() 办法。这两个办法利用于浏览器的历史记录栈,在以后已有的 back、forward、go 的根底之上,它们提供了对历史记录进行批改的性能。只是当它们执行批改时,尽管扭转了以后的 URL,但浏览器不会立刻向后端发送申请。

虚构 DOM 的优缺点?

长处:

  • 保障性能上限: 框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限;
  • 无需手动操作 DOM: 咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
  • 跨平台: 虚构 DOM 实质上是 JavaScript 对象, 而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、weex 开发等等。

毛病:

  • 无奈进行极致优化: 尽管虚构 DOM + 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化。

vue 和 react 的区别

=> 相同点:

1. 数据驱动页面,提供响应式的试图组件
2. 都有 virtual DOM, 组件化的开发,通过 props 参数进行父子之间组件传递数据,都实现了 webComponents 标准
3. 数据流动单向,都反对服务器的渲染 SSR
4. 都有反对 native 的办法,react 有 React native,vue 有 wexx

=> 不同点:

 1. 数据绑定:Vue 实现了双向的数据绑定,react 数据流动是单向的
 2. 数据渲染:大规模的数据渲染,react 更快
 3. 应用场景:React 配合 Redux 架构适宜大规模多人合作简单我的项目,Vue 适宜小快的我的项目
 4. 开发格调:react 举荐做法 jsx + inline style 把 html 和 css 都写在 js 了
      vue 是采纳 webpack + vue-loader 单文件组件格局,html, js, css 同一个文件

v-model 的原理?

咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:

  • text 和 textarea 元素应用 value 属性和 input 事件;
  • checkbox 和 radio 应用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例:

<input v-model='something'>

相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>

子组件:<div>{{value}}</div>

props:{value: String},
methods: {test1(){this.$emit('input', '小红')
  },
},

什么是 MVVM?

Model–View–ViewModel(MVVM)是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程形式。由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于 2005 年在他的博客上发表

MVVM 源自于经典的 Model–View–Controller(MVC)模式,MVVM 的呈现促成了前端开发与后端业务逻辑的拆散,极大地提高了前端开发效率,MVVM 的外围是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易治理和应用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口申请进行数据交互,起呈上启下作用

(1)View 层

View 是视图层,也就是用户界面。前端次要由 HTML 和 CSS 来构建。

(2)Model 层

Model 是指数据模型,泛指后端进行的各种业务逻辑解决和数据操控,对于前端来说就是后端提供的 api 接口。

(3)ViewModel 层

ViewModel 是由前端开发人员组织生成和保护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换解决,做二次封装,以生成合乎 View 层应用预期的视图数据模型。须要留神的是 ViewModel 所封装进去的数据模型包含视图的状态和行为两局部,而 Model 层的数据模型是只蕴含状态的,比方页面的这一块展现什么,而页面加载进来时产生什么,点击这一块产生什么,这一块滚动时产生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 能够残缺地去形容 View 层。

MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展示在 View 层,前端开发者再也不用低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架曾经把最脏最累的一块做好了,咱们开发者只须要解决和保护 ViewModel,更新数据视图就会主动失去相应更新。这样 View 层展示的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就齐全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端拆散计划施行的重要一环。

咱们以下通过一个 Vue 实例来阐明 MVVM 的具体实现,有 Vue 开发教训的同学应该高深莫测:

(1)View 层

<div id="app">
    <p>{{message}}</p>
    <button v-on:click="showMessage()">Click me</button>
</div>

(2)ViewModel 层

var app = new Vue({
    el: '#app',
    data: {  // 用于形容视图状态   
        message: 'Hello Vue!', 
    },
    methods: {  // 用于形容视图行为  
        showMessage(){
            let vm = this;
            alert(vm.message);
        }
    },
    created(){
        let vm = this;
        // Ajax 获取 Model 层的数据
        ajax({
            url: '/your/server/data/api',
            success(res){vm.message = res;}
        });
    }
})

(3)Model 层

{
    "url": "/your/server/data/api",
    "res": {
        "success": true,
        "name": "IoveC",
        "domain": "www.cnblogs.com"
    }
}

diff 算法

工夫复杂度: 个树的齐全 diff 算法是一个工夫复杂度为 O(n*3),vue 进行优化转化成 O(n)

了解:

  • 最小量更新, key 很重要。这个能够是这个节点的惟一标识,通知 diff 算法,在更改前后它们是同一个 DOM 节点

    • 扩大 v-for 为什么要有 key,没有 key 会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加 key 只会挪动缩小操作 DOM。
  • 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。
  • 只进行同层比拟,不会进行跨层比拟。

diff 算法的优化策略:四种命中查找,四个指针

  1. 旧前与新前(先比结尾,后插入和删除节点的这种状况)
  2. 旧后与新后(比结尾,前插入或删除的状况)
  3. 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)
  4. 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)

Vue 初始化页面闪动问题如何解决?

呈现该问题是因为在 Vue 代码尚未被解析之前,尚无法控制页面中 DOM 的显示,所以会看见模板字符串等代码。
解决方案是,在 css 代码中增加 v-cloak 规定,同时在待编译的标签上增加 v-cloak 属性:

[v-cloak] {display: none;}

<div v-cloak>
  {{message}}
</div>

Vue 子组件和父组件执行程序

加载渲染过程:

  1. 父组件 beforeCreate
  2. 父组件 created
  3. 父组件 beforeMount
  4. 子组件 beforeCreate
  5. 子组件 created
  6. 子组件 beforeMount
  7. 子组件 mounted
  8. 父组件 mounted

更新过程:

  1. 父组件 beforeUpdate
  2. 子组件 beforeUpdate
  3. 子组件 updated
  4. 父组件 updated

销毁过程:

  1. 父组件 beforeDestroy
  2. 子组件 beforeDestroy
  3. 子组件 destroyed
  4. 父组件 destoryed

Vue 模版编译原理晓得吗,能简略说一下吗?

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST 树
  • 优化
  • codegen

首先解析模版,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是 将优化后的 AST 树转换为可执行的代码

vue 中应用了哪些设计模式

1. 工厂模式 – 传入参数即可创立实例

虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode

2. 单例模式 – 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉

3. 公布 - 订阅模式 (vue 事件机制)

4. 观察者模式 (响应式数据原理)

5. 装璜模式: (@装璜器的用法)

6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略

… 其余模式欢送补充

父组件能够监听到子组件的生命周期吗

比方有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑解决,能够通过以下写法实现:

// Parent.vue
<Child @mounted="doSomething"/>

// Child.vue
mounted() {this.$emit("mounted");
}

以上须要手动通过 $emit 触发父组件的事件,更简略的形式能够在父组件援用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {console.log('父组件监听到 mounted 钩子函数 ...');
},

//  Child.vue
mounted(){console.log('子组件触发 mounted 钩子函数 ...');
},    

// 以上输入程序为:// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...     

当然 @hook 办法不仅仅是能够监听 mounted,其它的生命周期事件,例如:createdupdated 等都能够监听

正文完
 0