关于前端:滴滴前端常考vue面试题

40次阅读

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

Computed 和 Methods 的区别

能够将同一函数定义为一个 method 或者一个计算属性。对于最终的后果,两种形式是雷同的

不同点:

  • computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相干依赖产生扭转时才会从新求值;
  • method 调用总会执行该函数。

如何定义动静路由?如何获取传过来的动静参数?

(1)param 形式

  • 配置路由格局:/router/:id
  • 传递的形式:在 path 前面跟上对应的值
  • 传递后造成的门路:/router/123

1)路由定义

// 在 APP.vue 中
<router-link :to="'/user/'+userId" replace> 用户 </router-link>    

// 在 index.js
{
   path: '/user/:userid',
   component: User,
},

2)路由跳转

// 办法 1:<router-link :to="{name:'users', params: { uname: wade}}"> 按钮 </router-link

// 办法 2:this.$router.push({name:'users',params:{uname:wade}})

// 办法 3:this.$router.push('/user/' + wade)

3)参数获取
通过 $route.params.userid 获取传递的值

(2)query 形式

  • 配置路由格局:/router,也就是一般配置
  • 传递的形式:对象中应用 query 的 key 作为传递形式
  • 传递后造成的门路:/route?id=123

1)路由定义

// 形式 1:间接在 router-link 标签上以对象的模式
<router-link :to="{path:'/profile',query:{name:'why',age:28,height:188}}"> 档案 </router-link>

// 形式 2:写成按钮以点击事件模式
<button @click='profileClick'> 我的 </button>    

profileClick(){
  this.$router.push({
    path: "/profile",
    query: {
        name: "kobi",
        age: "28",
        height: 198
    }
  });
}

2)跳转办法

// 办法 1:<router-link :to="{name:'users', query: { uname: james}}"> 按钮 </router-link>

// 办法 2:this.$router.push({name: 'users', query:{ uname:james}})

// 办法 3:<router-link :to="{path:'/user', query: { uname:james}}"> 按钮 </router-link>

// 办法 4:this.$router.push({path: '/user', query:{ uname:james}})

// 办法 5:this.$router.push('/user?uname=' + jsmes)

3)获取参数

通过 $route.query 获取传递的值

Class 与 Style 如何动静绑定

Class 能够通过对象语法和数组语法进行动静绑定

对象语法:

<div v-bind:class="{active: isActive,'text-danger': hasError}"></div>

data: {
  isActive: true,
  hasError: false
}

数组语法:

<div v-bind:class="[isActive ? activeClass :'', errorClass]"></div>

data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

Style 也能够通过对象语法和数组语法进行动静绑定

对象语法:

<div v-bind:style="{color: activeColor, fontSize: fontSize +'px'}"></div>

data: {
  activeColor: 'red',
  fontSize: 30
}

数组语法:

<div v-bind:style="[styleColor, styleSize]"></div>

data: {
  styleColor: {color: 'red'},
  styleSize:{fontSize:'23px'}
}

ref 和 reactive 异同

这是 Vue3 数据响应式中十分重要的两个概念,跟咱们写代码关系也很大

const count = ref(0)
console.log(count.value) // 0
​
count.value++
console.log(count.value) // 1

const obj = reactive({count: 0})
obj.count++
  • ref接管外部值(inner value)返回响应式 Ref 对象,reactive返回响应式代理对象
  • 从定义上看 ref 通常用于解决单值的响应式,reactive用于解决对象类型的数据响应式
  • 两者均是用于结构响应式数据,然而 ref 次要解决原始值的响应式问题
  • ref返回的响应式数据在 JS 中应用须要加上 .value 能力拜访其值,在视图中应用会主动脱 ref,不须要.valueref 能够接管对象或数组等非原始值,但外部仍然是 reactive 实现响应式;reactive外部如果接管 Re f 对象会主动脱ref;应用开展运算符(...) 开展 reactive 返回的响应式对象会使其失去响应性,能够联合 toRefs() 将值转换为 Ref 对象之后再开展。
  • reactive外部应用 Proxy 代理传入对象并拦挡该对象各种操作,从而实现响应式。ref外部封装一个 RefImpl 类,并设置get value/set value,拦挡用户对值的拜访,从而实现响应式

Vue Ref 的作用

  • 获取 dom 元素this.$refs.box
  • 获取子组件中的datathis.$refs.box.msg
  • 调用子组件中的办法this.$refs.box.open()

router-link 和 router-view 是如何起作用的

剖析

vue-router中两个重要组件 router-linkrouter-view,别离起到导航作用和内容渲染作用,然而答复如何失效还真有肯定难度

答复范例

  1. vue-router中两个重要组件 router-linkrouter-view,别离起到路由导航作用和组件内容渲染作用
  2. 应用中 router-link 默认生成一个 a 标签,设置 to 属性定义跳转 path。实际上也能够通过custom 和插槽自定义最终的展示模式。router-view是要显示组件的占位组件,能够嵌套,对应路由配置的嵌套关系,配合 name 能够显示具名组件,起到更强的布局作用。
  3. router-link组件外部依据 custom 属性判断如何渲染最终生成节点,外部提供导航办法 navigate,用户点击之后理论调用的是该办法,此办法最终会批改响应式的路由变量,而后从新去routes 匹配出数组后果,router-view则依据其所处深度 deep 在匹配数组后果中找到对应的路由并获取组件,最终将其渲染进去。

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

Vue 中封装的数组办法有哪些,其如何实现页面更新

在 Vue 中,对响应式解决利用的是 Object.defineProperty 对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行 hack,让 Vue 能监听到其中的变动。那 Vue 是如何实现让这些数组办法实现元素的实时更新的呢,上面是 Vue 中对这些办法的封装:

// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === 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) {
  // 缓存原生数组办法
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    // 执行并缓存原生数组性能
    const result = original.apply(this, args);
    // 响应式解决
    const ob = this.__ob__;
    let inserted;
    switch (method) {
    // push、unshift 会新增索引,所以要手动 observer
      case "push":
      case "unshift":
        inserted = args;
        break;
      // splice 办法,如果传入了第三个参数,也会有索引退出,也要手动 observer。case "splice":
        inserted = args.slice(2);
        break;
    }
    // 
    if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听
    // notify change
    ob.dep.notify();// 告诉依赖更新
    // 返回原生数组办法的执行后果
    return result;
  });
});

简略来说就是,重写了数组中的那些原生办法,首先获取到这个数组的__ob__,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 持续对新的值察看变动(也就是通过 target__proto__ == arrayMethods 来扭转了数组实例的型),而后手动调用 notify,告诉渲染 watcher,执行 update。

Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期?

考点: Vue 的变动侦测原理

前置常识: 依赖收集、虚构 DOM、响应式零碎

根本原因是 Vue 与 React 的变动侦测形式有所不同

React 是 pull 的形式侦测变动, 当 React 晓得发生变化后, 会应用 Virtual Dom Diff 进行差别检测, 然而很多组件实际上是必定不会发生变化的, 这个时候须要用 shouldComponentUpdate 进行手动操作来缩小 diff, 从而进步程序整体的性能.

Vue 是 pull+push 的形式侦测变动的, 在一开始就晓得那个组件产生了变动, 因而在 push 的阶段并不需要手动管制 diff, 而组件外部采纳的 diff 形式实际上是能够引入相似于 shouldComponentUpdate 相干生命周期的, 然而通常正当大小的组件不会有适量的 diff, 手动优化的价值无限, 因而目前 Vue 并没有思考引入 shouldComponentUpdate 这种手动优化的生命周期.

简述 mixin、extends 的笼罩逻辑

(1)mixin 和 extends mixin 和 extends 均是用于合并、拓展组件的,两者均通过 mergeOptions 办法实现合并。

  • mixins 接管一个混入对象的数组,其中混入对象能够像失常的实例对象一样蕴含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子依照传入程序顺次调用,并在调用组件本身的钩子之前被调用。
  • extends 次要是为了便于扩大单文件组件,接管一个对象或构造函数。

    (2)mergeOptions 的执行过程

  • 规范化选项(normalizeProps、normalizelnject、normalizeDirectives)
  • 对未合并的选项,进行判断
if (!child._base) {if (child.extends) {parent = mergeOptions(parent, child.extends, vm);
  }
  if (child.mixins) {for (let i = 0, l = child.mixins.length; i < l; i++) {parent = mergeOptions(parent, child.mixins[i], vm);
    }
  }
}
  • 合并解决。依据一个通用 Vue 实例所蕴含的选项进行分类逐个判断合并,如 props、data、methods、watch、computed、生命周期等,将合并后果存储在新定义的 options 对象里。
  • 返回合并后果 options。

MVVM的优缺点?

长处:

  • 拆散视图(View)和模型(Model),升高代码耦合,提⾼视图或者逻辑的重⽤性: ⽐如视图(View)能够独⽴于 Model 变动和批改,⼀个 ViewModel 能够绑定不同的 ”View” 上,当 View 变动的时候 Model 不能够不变,当 Model 变动的时候 View 也能够不变。你能够把⼀些视图逻辑放在⼀个 ViewModel ⾥⾯,让很多 view 重⽤这段视图逻辑
  • 提⾼可测试性: ViewModel 的存在能够帮忙开发者更好地编写测试代码
  • ⾃动更新 dom: 利⽤双向绑定, 数据更新后视图⾃动更新, 让开发者从繁琐的⼿动 dom 中解放

毛病:

  • Bug 很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异样了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得⼀个地位的 Bug 被疾速传递到别的地位,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的申明是指令式地写在 View 的模版当中的,这些内容是没方法去打断点 debug 的
  • ⼀个⼤的模块中 model 也会很⼤,尽管使⽤⽅便了也很容易保障了数据的⼀致性,过后⻓期持有,不开释内存就造成了破费更多的内存
  • 对于⼤型的图形应⽤程序,视图状态较多,ViewModel 的构建和保护的老本都会⽐较⾼。

应用 Object.defineProperty() 来进行数据劫持有什么毛病?

在对一些属性进行操作时,应用这种办法无奈拦挡,比方通过下标形式批改数组数据或者给对象新增属性,这都不能触发组件的从新渲染,因为 Object.defineProperty 不能拦挡到这些操作。更准确的来说,对于数组而言,大部分操作都是拦挡不到的,只是 Vue 外部通过重写函数的形式解决了这个问题。

在 Vue3.0 中曾经不应用这种形式了,而是通过应用 Proxy 对对象进行代理,从而实现数据劫持。应用 Proxy 的益处是它能够完满的监听到任何形式的数据扭转,惟一的毛病是兼容性的问题,因为 Proxy 是 ES6 的语法。

Vue 修饰符有哪些

事件修饰符

  • .stop 阻止事件持续流传
  • .prevent 阻止标签默认行为
  • .capture 应用事件捕捉模式, 即元素本身触发的事件先在此处解决,而后才交由外部元素进行解决
  • .self 只当在 event.target 是以后元素本身时触发处理函数
  • .once 事件将只会触发一次
  • .passive 通知浏览器你不想阻止事件的默认行为

v-model 的修饰符

  • .lazy 通过这个修饰符,转变为在 change 事件再同步
  • .number 主动将用户的输出值转化为数值类型
  • .trim 主动过滤用户输出的首尾空格

键盘事件的修饰符

  • .enter
  • .tab
  • .delete (捕捉“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

零碎润饰键

  • .ctrl
  • .alt
  • .shift
  • .meta

鼠标按钮修饰符

  • .left
  • .right
  • .middle

对 SSR 的了解

SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端实现,而后再把 html 间接返回给客户端

SSR 的劣势:

  • 更好的 SEO
  • 首屏加载速度更快

SSR 的毛病:

  • 开发条件会受到限制,服务器端渲染只反对 beforeCreate 和 created 两个钩子;
  • 当须要一些内部扩大库时须要非凡解决,服务端渲染应用程序也须要处于 Node.js 的运行环境;
  • 更多的服务端负载。

vue-loader 是什么?它有什么作用?

答复范例

  1. vue-loader是用于解决单文件组件(SFCSingle-File Component)的webpack loader
  2. 因为有了 vue-loader,咱们就能够在我的项目中编写SFC 格局的 Vue 组件,咱们能够把代码宰割为 <template><script><style>,代码会异样清晰。联合其余 loader 咱们还能够用 Pug 编写 <template>,用SASS 编写 <style>,用TS 编写 <script>。咱们的<style> 还能够独自作用以后组件
  3. webpack打包时,会以 loader 的形式调用vue-loader
  4. vue-loader被执行时,它会对 SFC 中的每个语言块用独自的 loader 链解决。最初将这些独自的块装配成最终的组件模块

原理

vue-loader会调用 @vue/compiler-sfc 模块解析 SFC 源码为一个描述符(Descriptor),而后为每个语言块生成 import 代码,返回的代码相似上面

// source.vue 被 vue-loader 解决之后返回的代码
​
// import the <template> block
import render from 'source.vue?vue&type=template'
// import the <script> block
import script from 'source.vue?vue&type=script'
export * from 'source.vue?vue&type=script'
// import <style> blocks
import 'source.vue?vue&type=style&index=1'
​
script.render = render
export default script

咱们想要 script 块中的内容被作为 js 解决(当然如果是 <script lang="ts"> 被作为 ts 理),这样咱们想要 webpack 把配置中跟 .js 匹配的规定都利用到形如 source.vue?vue&type=script 的这个申请上。例如咱们对所有 *.js 配置了babel-loader,这个规定将被克隆并利用到所在Vue SFC

import script from 'source.vue?vue&type=script

将被开展为:

import script from 'babel-loader!vue-loader!source.vue?vue&type=script'

相似的,如果咱们对 .sass 文件配置了style-loader + css-loader + sass-loader,对上面的代码

<style scoped lang="scss">

vue-loader将会返回给咱们上面后果:

import 'source.vue?vue&type=style&index=1&scoped&lang=scss'

而后 webpack 会开展如下:

import 'style-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
  • 当解决开展申请时,vue-loader将被再次调用。这次,loader将会关注那些有查问串的申请,且仅针对特定块,它会选中特定块外部的内容并传递给前面匹配的loader
  • 对于 <script> 块,解决到这就能够了,然而 <template><style> 还有一些额定工作要做,比方

    • 须要用 Vue 模板编译器编译 template,从而失去render 函数
    • 须要对 <style scoped>中的 CSS 做后处理(post-process),该操作在 css-loader 之后但在 style-loader 之前

实现上这些附加的 loader 须要被注入到曾经开展的 loader 链上,最终的申请会像上面这样:

// <template lang="pug">
import 'vue-loader/template-loader!pug-loader!source.vue?vue&type=template'
​
// <style scoped lang="scss">
import 'style-loader!vue-loader/style-post-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'

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

Vuex 有哪几种属性?

有五种,别离是 State、Getter、Mutation、Action、Module

  • state => 根本数据(数据源寄存地)
  • getters => 从根本数据派生进去的数据
  • mutations => 提交更改数据的办法,同步
  • actions => 像一个装璜器,包裹 mutations,使之能够异步。
  • modules => 模块化 Vuex

什么是 mixin?

  • Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。
  • 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、办法等,则能够将其编写为 mixin,并在组件中简略的援用它。
  • 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

Vue 模版编译原理

vue 中的模板 template 无奈被浏览器解析并渲染,因为这不属于浏览器的规范,不是正确的 HTML 语法,所有须要将 template 转化成一个 JavaScript 函数,这样浏览器就能够执行这一个函数并渲染出对应的 HTML 元素,就能够让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。

  • 解析阶段:应用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为形象语法树 AST。
  • 优化阶段:遍历 AST,找到其中的一些动态节点并进行标记,不便在页面重渲染的时候进行 diff 比拟时,间接跳过这一些动态节点,优化 runtime 的性能。
  • 生成阶段:将最终的 AST 转化为 render 函数字符串。

为什么在 Vue3.0 采纳了 Proxy, 摈弃了 Object.defineProperty?

Object.defineProperty 自身有肯定的监控到数组下标变动的能力, 然而在 Vue 中, 从性能 / 体验的性价比思考, 尤大大就弃用了这个个性。为了解决这个问题, 通过 vue 外部解决后能够应用以下几种办法来监听数组

push();
pop();
shift();
unshift();
splice();
sort();
reverse();

因为只针对了以上 7 种办法进行了 hack 解决, 所以其余数组的属性也是检测不到的, 还是具备肯定的局限性。

Object.defineProperty 只能劫持对象的属性, 因而咱们须要对每个对象的每个属性进行遍历。Vue 2.x 里, 是通过 递归 + 遍历 data 对象来实现对数据的监控的, 如果属性值也是对象那么须要深度遍历, 显然如果能劫持一个残缺的对象是才是更好的抉择。

Proxy 能够劫持整个对象, 并返回一个新的对象。Proxy 不仅能够代理对象, 还能够代理数组。还能够代理动静减少的属性。

二、如何解决

解决跨域的办法有很多,上面列举了三种:

  • JSONP
  • CORS
  • Proxy

而在 vue 我的项目中,咱们次要针对 CORSProxy这两种计划进行开展

CORS

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一个零碎,它由一系列传输的 HTTP 头组成,这些 HTTP 头决定浏览器是否阻止前端 JavaScript 代码获取跨域申请的响应

CORS 实现起来十分不便,只须要减少一些 HTTP 头,让服务器能申明容许的拜访起源

只有后端实现了 CORS,就实现了跨域

!

koa 框架举例

增加中间件,间接设置 Access-Control-Allow-Origin 响应头

app.use(async (ctx, next)=> {ctx.set('Access-Control-Allow-Origin', '*');
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
  ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  if (ctx.method == 'OPTIONS') {ctx.body = 200;} else {await next();
  }
})

ps: Access-Control-Allow-Origin 设置为 * 其实意义不大,能够说是形同虚设,理论利用中,上线前咱们会将Access-Control-Allow-Origin 值设为咱们指标host

Proxy

代理(Proxy)也称网络代理,是一种非凡的网络服务,容许一个(个别为客户端)通过这个服务与另一个网络终端(个别为服务器)进行非间接的连贯。一些网关、路由器等网络设备具备网络代理性能。个别认为代理服务有利于保障网络终端的隐衷或平安,避免攻打

计划一

如果是通过 vue-cli 脚手架工具搭建我的项目,咱们能够通过 webpack 为咱们起一个本地服务器作为申请的代理对象

通过该服务器转发申请至指标服务器,失去后果再转发给前端,然而最终公布上线时如果 web 利用和接口服务器不在一起仍会跨域

vue.config.js 文件,新增以下代码

amodule.exports = {
    devServer: {
        host: '127.0.0.1',
        port: 8084,
        open: true,// vue 我的项目启动时主动关上浏览器
        proxy: {
            '/api': { // '/api' 是代理标识,用于通知 node,url 后面是 /api 的就是应用代理的
                target: "http://xxx.xxx.xx.xx:8080", // 指标地址,个别是指后盾服务器地址
                changeOrigin: true, // 是否跨域
                pathRewrite: {// pathRewrite 的作用是把理论 Request Url 中的 '/api' 用 ""代替'^/api':""}
            }
        }
    }
}

通过 axios 发送申请中,配置申请的根门路

axios.defaults.baseURL = '/api'

计划二

此外,还可通过服务端实现代理申请转发

express 框架为例

var express = require('express');
const proxy = require('http-proxy-middleware')
const app = express()
app.use(express.static(__dirname + '/'))
app.use('/api', proxy({ target: 'http://localhost:4000', changeOrigin: false}));
module.exports = app

计划三

通过配置 nginx 实现代理

server {
    listen    80;
       location / {
        root  /var/www/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    location /api {
        proxy_pass  http://127.0.0.1:3000;
        proxy_redirect   off;
        proxy_set_header  Host       $host;
        proxy_set_header  X-Real-IP     $remote_addr;
        proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

正文完
 0