哪些vue面试题是经常会被问到的

本文详细介绍了Vue.js面试中经常出现的题目,涵盖Vuex、v-html安全、vue-loader作用、Vue.observable、Vue-Router懒加载、Watch深入、过滤器、Vue computed、插件使用、SPA首屏优化、v-model原理、组件缓存与更新、事件绑定机制、Vue采用异步渲染的原因、组件化理解、slot工作原理、Vue生命周期、数组变化检测、双向数据绑定原理等多个方面,旨在帮助开发者全面理解和掌握Vue.js核心技术。
摘要由CSDN通过智能技术生成

Vuex和单纯的全局对象有什么区别?

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。

Vue中v-html会导致哪些问题

  • 可能会导致 xss 攻击
  • v-html 会替换掉标签内部的子元素
let template = require('vue-template-compiler'); 
let r = template.compile(`<div v-html="'<span>hello</span>'"></div>`) 

// with(this){return _c('div',{domProps: {"innerHTML":_s('<span>hello</span>')}})} 
console.log(r.render);

// _c 定义在core/instance/render.js 
// _s 定义在core/instance/render-helpers/index,js
if (key === 'textContent' || key === 'innerHTML') {
    
    if (vnode.children) vnode.children.length = 0 
    if (cur === oldProps[key]) continue // #6601 work around Chrome version <= 55 bug where single textNode // replaced by innerHTML/textContent retains its parentNode property 
    if (elm.childNodes.length === 1) {
    
        elm.removeChild(elm.childNodes[0]) 
    } 
}

前端vue面试题详细解答

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.observable你有了解过吗?说说看

一、Observable 是什么

Observable 翻译过来我们可以理解成可观察的

我们先来看一下其在Vue中的定义

Vue.observable,让一个对象变成响应式数据。Vue 内部会用它来处理 data 函数返回的对象

返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器

Vue.observable({
    count : 1})

其作用等同于

new vue({
    count : 1})

Vue 2.x 中,被传入的对象会直接被 Vue.observable 变更,它和被返回的对象是同一个对象

Vue 3.x 中,则会返回一个可响应的代理,而对源对象直接进行变更仍然是不可响应的

二、使用场景

在非父子组件通信时,可以使用通常的bus或者使用vuex,但是实现的功能不是太复杂,而使用上面两个又有点繁琐。这时,observable就是一个很好的选择

创建一个js文件

// 引入vue
import Vue from 'vue
// 创建state对象,使用observable让state对象可响应
export let state = Vue.observable({
   
  name: '张三',
  'age': 38
})
// 创建对应的方法
export let mutations = {
   
  changeName(name) {
   
    state.name = name
  },
  setAge(age) {
   
    state.age = age
  }
}

.vue文件中直接使用即可

<template>
  <div>
    姓名:{
   {
    name }}
    年龄:{
   {
    age }}
    <button @click="changeName('李四')">改变姓名</button>
    <button @click="setAge(18)">改变年龄</button>
  </div>
</template>
import {
    state, mutations } from '@/store
export default {
   
  // 在计算属性中拿到值
  computed: {
   
    name() {
   
      return state.name
    },
    age() {
   
      return state.age
    }
  },
  // 调用mutations里面的方法,更新数据
  methods: {
   
    changeName: mutations.changeName,
    setAge: mutations.setAge
  }
}
三、原理分析

源码位置:src\core\observer\index.js

export function observe (value: any, asRootData: ?boolean): Observer | void {
   
  if (!isObject(value) || value instanceof VNode) {
   
    return
  }
  let ob: Observer | void
  // 判断是否存在__ob__响应式属性
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
   
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
   
    // 实例化Observer响应式对象
    ob = new Observer(value)
  }
  if (asRootData && ob) {
   
    ob.vmCount++
  }
  return ob
}

Observer

export class Observer {
   
    value: any;
    dep: Dep;
    vmCount: number; // number of vms that have this object as root $data

    constructor (value: any) {
   
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
        if (Array.isArray(value)) {
   
            if (hasProto) {
   
                protoAugment(value, arrayMethods)
            } else {
   
                copyAugment(value, arrayMethods, arrayKeys)
            }
            this.observeArray(value)
        } else {
   
            // 实例化对象是一个对象,进入walk方法
            this.walk(value)
        }
}

walk函数

walk (obj: Object) {
   
    const keys = Object.keys(obj)
    // 遍历key,通过defineReactive创建响应式对象
    for (let i = 0; i < keys.length; i++) {
   
        defineReactive(obj, keys[i])
    }
}

defineReactive方法

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
   
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
   
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
   
    val = obj[key]
  }

  let childOb = !shallow && observe(val)
  // 接下来调用Object.defineProperty()给对象定义响应式属性
  Object.defineProperty(obj, key, {
   
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
   
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
   
        dep.depend()
        if (childOb) {
   
          childOb.dep.depend()
          if (Array.isArray(value)) {
   
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
   
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
   
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
   
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
   
        setter.call(obj, newVal)
      } else {
   
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      // 对观察者watchers进行通知,state就成了全局响应式对象
      dep.notify()
    }
  })
}

Vue-Router 的懒加载如何实现

非懒加载:

import List from '@/components/list.vue'
const router = new VueRouter({
   
  routes: [
    {
    path: '/list', component: List }
  ]
})

(1)方案一(常用):使用箭头函数+import动态加载

const List = () => import('@/components/list.vue')
const router = new VueRouter({
   
  routes: [
    {
    path: '/list', component: List }
  ]
})

(2)方案二:使用箭头函数+require动态加载

const router = new Router({
   
  routes: [
   {
   
     path: '/list',
     component: resolve => require(['@/components/list'], resolve)
   }
  ]
})

(3)方案三:使用webpack的require.ensure技术,也可以实现按需加载。 这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。

// r就是resolve
const List = r => require.ensure([], () => r(require('@/components/list')), 'list');
// 路由也是正常的写法  这种是官方推荐的写的 按模块划分懒加载 
const router = new Router({
   
  routes: [
  {
   
    path: '/list',
    component: List,
    name: 'list'
  }
 ]
}))

Watch中的deep:true是如何实现的

当用户指定了 watch 中的deep属性为 true 时,如果当前监控的值是数组类型。会对对象中的每一项进行求值,此时会将当前 watcher存入到对应属性的依赖中,这样数组中对象发生变化时也会通知数据更新

源码相关

get () {
    
    pushTarget(this) // 先将当前依赖放到 Dep.target上 
    let value 
    const vm = this.vm 
    try {
    
        value = this.getter.call(vm, vm) 
    } catch (e) {
    
        if (this.user) {
    
            handleError(e, vm, `getter for watcher "${
     this.expression}"`) 
        } else {
   <
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值