欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

常见的前端面试问题-vue

最编程 2024-03-13 09:55:12
...

*1. 关于Vue的⽣命周期,下列哪项是不正确的?[单选题]

A、Vue 实例从创建到销毁的过程,就是⽣命周期。

B、⻚⾯⾸次加载会触发beforeCreate, created, beforeMount, mounted, beforeUpdate,

updated。

C、created表示完成数据观测,属性和⽅法的运算,初始化事件,$el属性还没有显示出来。

D、DOM 渲染在 mounted 中就已经完成了。

*2. 对于Vue中数据响应式原理的说法,下列哪项是不正确的?[多选题]

A、采⽤数据劫持⽅式,即Object.defifineProperty()劫持data中各属性来实现数据响应式

B、视图中的变化通过Watcher更新data中数据

C、若data中某属性多次发⽣变化,Watcher仅会进⼊更新队列⼀次

D、通过编译过程进⾏依赖收集

*3. 关于Vue组件间的参数传递,下列哪项是不正确的?[单选题]

A、⼦组件给⽗组件传值,使⽤$emit⽅法

B、⼦组件使⽤$emit('someEvent')派发事件,⽗组件使⽤@someEvent监听

C、祖孙组件间可以使⽤provide和inject⽅式跨层级相互传值

D、⽗组件给⼦组件传值,⼦组件通过props接受数据

4. 下列关于v-model的说法,哪项是不正确的?[单选题]

A、v-model能够实现双向绑定

B、v-model本质上是语法糖。它负责监听⽤户的输⼊事件以更新数据

C、v-model是内置指令,不能⽤在⾃定义组件上

D、对input使⽤v-model实际上是指定其:value和:input

5. 下列说法不正确的是哪项?[单选题]

A、key的作⽤主要是为了⾼效的更新虚拟DOM

B、若指定了组件的template选项,render函数不会执⾏

C、使⽤vm.$nextTick可以确保获得DOM异步更新的结果

D、若没有el选项,vm.$mount(dom)可将Vue实例挂载于指定元素上

6. 下列说法不正确的是哪项?[单选题]

A、使⽤ this.$parent查找当前组件的⽗组件。

B、使⽤ this.$children按顺序查找当前组件的直接⼦组件。

C、使⽤ this. 查 找 根 组 件 , 并 可 以 配 合 children遍历全部组件。

D、使⽤ this.$refs查找命名⼦组件。

7. 下列关于vuex描述,不正确的是哪项?[单选题]

A、Vuex 是⼀个状态管理模式

B、Vuex主要⽤于多视图间状态全局共享与管理

C、在Vuex中改变状态可以通过mutations和actions

D、Vuex通过Vue实现状态响应式,因此只能⽤于Vue

*8. 下列关于vue-router的描述,不正确的是哪项?[单选题]

A、vue-router常⽤模式有hash和history两种

B、可以通过addRoutes⽅法动态添加路由

C、可以通过beforeEnter对单个组件进⾏路由守卫

D、vue-router借助Vue实现路由信息响应式,因此只能⽤于Vue

*9. 关于vue服务端渲染,下列哪项说法是不正确的?[单选题]

A、通过服务端渲染,可以优化SEO抓取,提升⾸⻚加载速度

B、某些声明周期钩⼦函数(如beforeCreate、created)能同时运⾏在服务端和客户端

C、服务端渲染的vue.js是同构开发,因此vue扩展库可以在服务端应⽤中正常运⾏

D、组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后在客户端上"激活"为可

交互的应⽤

*10. 关于typescript在vue中的应⽤,哪项说法是不正确的?[单选题]

A、使⽤TypeScript可获得静态类型检查以及最新的ECMAScript特性

B、TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。意味着你完全可以⽤JS语法

编写TS代码

C、使⽤Vue.extend({})⽅式声明组件不能获得TypeScript类型推断能⼒

D、基于类的Vue组件中如果要声明初始数据可以直接声明为实例的属性,如: message: string

= 'Hello!'

11. 下列关于vue说法哪些是不正确的?[单选题]

A、vue简单易上⼿,性能⾼效,还便于与第三⽅库或既有项⽬整合

B、vue构建的项⽬复杂度增加较快,仅适合中⼩型项⽬

C、vue基于组件构建应⽤,代码组织简洁、易理解、易维护

D、vue借助虚拟DOM实现跨平台,服务端渲染,以及性能良好的DOM更新策略

*12. 下列关于vue原理哪些是正确的?[多选题]

A、Vue中数组变更通知通过拦截数组操作⽅法实现

B、编译器⽬标是创建渲染函数,渲染函数执⾏将得到VNode树

C、组件内data发⽣变化时会通知其对应Watcher执⾏异步更新

D、patching算法⾸先进⾏同层级⽐较,可能执⾏的操作是节点的增加、删除和更新

答案:
1B
2BD
3C
4C
5B
6B
7C
8C
9C
10C
11B
12ABCD

1. vue的生命周期

beforeCreated
created
beforeMount
mounted
beforeUpdate
updated
beforeDestory
destoryed

2. 第一个进入组件调用的生命周期

beforeCreated
created
beforeMount
mounted
如果有keep-alive ,后面只会重复触发activated

3.v-if v-show区别

v-if 是动态插入dom,重新触发渲染,页面也会触发重排重绘,切换时比较慢。
v-show只是样式的隐藏 和显示,占用空间但是,初次显示比较慢,但切换速度快。

4.v-if v-for优先级

v-for优先级更高,所以会执行所有for的内容里面在判断if

5. keep-alive了解

keep-alive是vue内置的组件,可以保留组件状态。
结合路由使用,activated 和 deactivated两个钩子函数,通过include和 exclude匹配过滤组件。

6.ref是什么

dom的引用,用于直接操作dom

7.nextTick是什么

vue改变dom元素结构后,会触发nextTick方法来实现dom数据更新后,执行的代码逻辑。
vue是异步执行dom更新,一旦发现数据变化,会把watcher加入到事件队列里。如果同一个watcher被多次触发,也只会推送一次,最终只执行一次dom更新,在下一次事件循环的时候,清空队列并执行dom更新。nextTick就是在执行dom更新后执行的钩子。

8.路由导航守卫有那些?

全局
router.beforeEach
router.beforeResolve
router.afterEach
路由
beforeEnter
组件内
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave

9.vue 如何做样式穿透

sass和less 使用/deep/
stylus 使用 >>>
通用 ::v-deep

10.scoped原理

样式只有在当前组件生效,vue是通过postcss 转译实现
在对应的组件生成别名

<template>
<div class="example">111</div>
</template>
<style scoped>
.example{ color:red}
</style>

//生成...
<template>
<div class="example" data-v-212122>111</div>
</template>
<style>
.example[data-v-212122]{ color:red}
</style>

11.mvvm的理解

model 数据模型层
view 视图层
vm 连接mode和view的中间层 ,负责数据绑定 和 视图监听。

12.mvvm双向数据绑定原理

image.png

13.vuex是单向数据流,还是双向

单向

14.什么是虚拟dom

由于频繁直接操作dom,都需要遍历整个dom树,效率很低。通过一个js对象,记录变更的信息,由于只是修改对象属性,所以很高效,最后一次性 比对老的dom 只更新变化的内容。

15.组件间传值

props,emit , eventBus 也可以直接是Vue

16.什么是spa,有什么优点缺点

spa是单页面应用
优点:一次加载后,后面都是本地浏览器缓存,有点像客户端软件,交互快。
通过异步请求方式交互数据,有效的前后端分离,后端只关心接口处理,
减轻服务端的压力,展示和渲染都在前端浏览器完成。
后端的接口可以复用,通过json格式,可以在pc移动端都通用
缺点:首屏加载慢,很多资源都要第一次一次性加载完,容易白屏
不利于SEO优化

17.props和data优先级问题

props > methods > data > computed > watch

1. v-if和v-for哪个优先级更高?有哪些优化策略?
<template>
 <p v-for="child in children" v-if="isFolder">{{child.title}}</p>
</template>
  const app = new Vue({... })
  console.log(app.$options.render); 

从打印输出可以看出,_l 循环遍历的for ,里面再执行(isFolder)? 条件判断。

(function anonymous( 
) { 
with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("xxx")]),_v(" "), 
_l((children),function(child){return (isFolder)?_c('p', 
[_v(_s(child.title))]):_e()})],2)} 
}) 

vue会先将template模板 编译成AST抽象语法树。
从源码compiler/codegen/index.js 分析。
在genElement方法

export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
    el.pre = el.pre || el.parent.pre
  } 
  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) { //1.for命令优先执行
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {//2.这里才执行if
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
...}

答:

  1. 在源码中分析我们得出,vue把模板编译成AST抽象语法树时,genElement方法是先执行genFor方法,再执行genIf。所以for优先。
    2.由于for先执行,无论如何判断,循环都会执行,浪费了性能。
    3.优化:
    (1) 把if提取到for的外部,优先判断。
    (2) 如果出现在条件内部,可以提前把整个list 先过滤一遍,再交给template循环。

2. Vue组件data为什么必须是个函数而Vue的根实例则没有此限制?
    <div id="demo"> 
        <h1>test</h1> 
        <comp></comp> 
        <comp></comp> 
    </div> 
    Vue.component('comp', { 
            template:'<div @click="counter++">{{counter}}</div>',            
            data: {counter: 0} 
        })  
 // 创建实例 
        const app = new Vue({ 
            el: '#demo', 
        }); //这里会报错

整体思路

由于局部组件存的动态创建,过程比较复杂,这里使用全局组件来对比。

  1. vue初始化,先调用initAssetRegisters方法,把所有全局组件的构造函数先初始化好,并且全局组件合并到vue的配置信息上。
  2. 这过程中会触发mergeOptions的strats.data 方法判断如果是组件时候,判断data是否为function 如果不是则警告提示。
  3. 根Vue或者组件实例化时,都会统一调用initData(),初始化data。

从源码中分析 src/core/global-api/assets.js vue初始化的时候调用了initAssetRegisters方法

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'
export function initAssetRegisters (Vue: GlobalAPI) {
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
          // this.options._base 等价于访问 Vue根实例
          //这里最终调用Vue.extend方法,传入vue的component配置信息。
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

src/core/global-api/extend.js 定义了Vue.extend方法

  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    const Sub = function VueComponent (options) {
      this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    Sub.options = mergeOptions( //这里把根Vue和当前组件的配置信息进行合并,返回当前组件的构造函数
      Super.options,
      extendOptions
    )
    Sub['super'] = Super
 ...
  }
}

src/core/util/options.js mergeOptions方法 进行合并

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }
  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  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)
      }
    }
  }

  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)//这里通过vm是否有来判断是根实例,还是组件
  }
  return options
}

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )

      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}

最终,无论是根vue实例化,还是组件实例化,都会统一走initData初始化。先实例化根vue,再实例化组件。
src\core\instance\state.js initState -> initData()

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm) //初始化
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm) //当是函数时候,会执行getData函数返回对象data
    : data || {} //直接使用传入的data 
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  ...
  }

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm) //这里只返回对象
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

总结:

1.在根Vue实例或者全局组件实例化时,会先把全局组件的构造函数初始化一次,然后通过initData统一实例化。

  1. 由于全局组件共用一个构造函数,所以在initData 初始化的时候,如果使用对象定义data,所有组件实例都会共用一个data对象,产生数据污染。而采用函数的方式通过工厂函数统一生成新data对象,有效规避的实例间共用污染问题。
  2. vue根实例全局只有一个,所以不会给其他实例修改,