前端面试题:VUE

1. vue的双向数据绑定实现原理?
2. vue如何在组件之间进行传值?
3. vuex和vue的双向数据绑定有什么冲突?
4. vue-router中的history模式和hash模式的区别?
5. 路由懒加载的作用
6. 对虚拟dom的了解,说说实现原理

6. 对虚拟dom的了解,说说实现原理

什么是虚拟DOM树?(Virtual DOM)

virtual,指的是对真实DOM的一种模拟。相对于直接操作真实的DOM结构,我们构建一棵虚拟的树,将各种数据和操作直接应用在这棵虚拟的树上,然后再将对虚拟的树的修改应用到真实的DOM结构上。
虚拟DOM树其实就是一个普通的js对象,它是用来描述一段HTML片段的

  • 当页面渲染的时候Vue会创建一颗虚拟DOM树
  • 当页面发生改变Vue会再创建一颗新的虚拟DOM树
  • 前后两颗新旧虚拟DOM树进行对比,Vue通过diff算法,去记录差异的地方
  • 将有差异的地方更新到真实的DOM树中
虚拟DOM树有什么用?

vue中的虚拟DOM树只会重新渲染页面修改的地方,大大减少了对真实DOM树的操作。 -------虚拟DOM树是占内容的,但是可以帮我们提高DOM的性能。

可以这样理解,虚拟DOM树是用空间(虚拟DOM树占空间)换时间(虚拟DOM树可以提高DOM效率)。

Vue v-for 中 :key 到底有什么用?

vue不直接操作真实的DOM树,通过虚拟DOM树就可以重新渲染修改的地方,影藏在背后的原理其实就是 diff 算法。
key的作用是为了高效的更新虚拟DOM树,提高查找的效率,一次性定位到要修改的元


5. 路由懒加载的作用

在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,
造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,
运用懒加载可以将页面进行划分,按需加载页面,可以分担首页所承担的加载压力,减少加载用时。

  • 常用的懒加载方式有两种:即使用vue异步组件ES中的import
// 原加载方式
 export default new Router({
    routes: [
       {
           path: '/',
           name: 'HelloWorld',
          component:HelloWorld
       }
     ]
 })
// vue异步组件实现懒加载
// 方法如下:component:resolve=>(require(['需要加载的路由的地址']),resolve)
export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: resolve=>(require(["@/components/HelloWorld"],resolve))
    }
  ]
})
// ES 提出的import方法,(------最常用------)
const HelloWorld = ()=>import("@/components/HelloWorld")
export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component:HelloWorld
    }
  ]
})

**扩展知识**:组件懒加载

// 原加载方式
import One from './one'
export default {
  components:{
    "One-com":One
  },
...
// const方法
const One = ()=>import("./one");
export default {
  components:{
    "One-com":One
  },
...
// 异步方法
export default {
  components:{
    "One-com":resolve=>(['./one'],resolve)
  },

路由和组件的常用两种懒加载方式:
1、vue异步组件实现路由懒加载
  component:resolve=>(['需要加载的路由的地址',resolve])
2、es提出的import(推荐使用这种方式)
  const HelloWorld = ()=>import('需要加载的模块地址')
参考连接:
https://www.cnblogs.com/xiaoxiaoxun/p/11001884.html


4. vue-router中的history模式和hash模式的区别?

Vue-router 路由模式有三种 "hash" | "history" | "abstract",一般人只知道两种"hash" | "history"

// 源码:

switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `invalid mode: ${mode}`)
    }
}
mode
  • 类型: string
  • 默认值: "hash" (浏览器环境) | "abstract" (Node.js 环境)
  • 可选值: "hash" | "history" | "abstract"

配置路由模式:

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。
  • history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。
  • abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

对于Vue 这类渐进式前端开发框架,为了构建SPA(单页面应用),需要引入前端路由系统,这也就是Vue-router存在的意义.

前端路由的核心,就在于——— 改变视图的同时不会向后端发出请求
浏览器对页面的访问是无状态的,所以我们在切换不同的页面时都会重新进行请求。 实际运用vue和vue-router开发就会发现,在切换页面时是没有重新请求的,使用起来就好像页面是有状态的。 其实是借助浏览器的History API来实现的,可以使页面跳转而不刷新,页面的状态就维持在浏览器中了。

  • vue-router: hash

hash模式中url带有#号,修改成history模式,url中的#自动就去除了。

hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件:

window.onhashchange = function(event){
    console.log(event.oldURL, event.newURL);
    let hash = location.hash.slice(1)
}

hash模式下,仅hash符号之前的内容会被包含在请求中,如 http://www.abc.com, 因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回404错误,因此改变hash不会重新加载页面.

  • vue-router: history
    随着history api的到来,前端路由开始进化了,前面的hashchange,你只能改变#后面的url片段,而history api则给了前端完全的自由。
     history api可以分为两大部分:切换和修改
  1. 切换历史状态

包括back、forward、go三个方法,对应浏览器的前进,后退,跳转操作,有同学说了,(谷歌)浏览器只有前进和后退,没有跳转,嗯,在前进后退上长按鼠标,会出来所有当前窗口的历史记录,从而可以跳转:

history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进

坑1:此处有一个开发的坑:在我们项目中,开发人员会在某个详情页面按钮上绑定history.go(-1)用来进入详情的主页面, 有时测试人员会直接输入url进入详情页,这样点击按钮就会出现问题,跳转的就不是项目中的主页面,就会是浏览器历史记录中的上一页

  1. 修改历史状态
    包括了pushState、replaceState两个方法,这两个方法接收三个参数:stateObj,title,url
history.pushState(stateObj,title,url)
window.onpopstate = function(event){
    console.log(event.state)
    if(event.state && event.state.color === 'red'){
        document.body.style.color = 'red';
    }
}

坑2:通过history api,我们丢掉了丑陋的#,但是它也有个毛病:不怕前进,不怕后退,就怕刷新,f5,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。在hash模式下,前端路由修改的是#中的信息,而浏览器请求时是不带它玩的,所以没有问题。但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,会分分钟刷出一个404来。

  1. popstate实现history路由拦截,监听页面返回事件
    当活动历史记录条目更改时,将触发popstate事件
  • 如果被激活的历史记录条目是通过对 history.pushState() 的调用创建的,或者受到对 history.replaceState() 的调用的影响,popstate事件的state属性包含历史条目的状态对象的副本。

  • 需要注意的是调用 history.pushState() 或 history.replaceState() 用来在浏览历史中添加或修改记录,不会触发popstate事件;

    只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back())

  • vue-router: abstract
    abstract模式是使用一个不依赖于浏览器的浏览历史虚拟管理后端。

根据平台差异可以看出,在 Weex 环境中只支持使用 abstract 模式。 不过,vue-router 自身会对环境做校验,如果发现没有浏览器的 API,vue-router 会自动强制进入 abstract 模式,所以 在使用 vue-router 时只要不写 mode 配置即可,默认会在浏览器环境中使用 hash 模式,在移动端原生环境中使用 abstract 模式


3. vuex和vue的双向数据绑定有什么冲突?

在严格模式下vuex中的state对象中的属性是不能随意更改的,但是在表单处理时使用v-model时绑定的是vuex的数据,用户可以随意更改数据时会抛出一个错误。

解决办法:
1.给 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用一个方法
2.使用带有 setter 的双向绑定计算属性


2. vue如何在组件之间进行传值?

  • 父子组件之间的传值
    1.使用props$emit
    2.在引入的子组件中使用ref,通过this.$refs.xxx.方法/值来获取子组件中的方法或者值
    3.子组件中使用this.$parent来获取父组件中的值或者方法
    4.父组件中使用this.$children来获取子组件中的值或者方法
    ⚠️tip:方法3和方法4有时候会失效,所以建议使用方法2

  • 兄弟组件中的传值
    1.使用eventBus作为中间件,然后使用$emit去抛出事件,使用$on去监听事件
    ⚠️这里要注意一些事项:

a. bus.$emitbeforeDestroy中去触发

b. bus.$oncreated或者mounted中使用,且回调函数使用箭头函数,解决this指向问题

c. 在beforeDestroy中使用bus.$off去销毁事件

d.比较特殊的是如果该组件存在缓存(路由跳转的时候使用了来包裹,此时该组件跳转的时候是不会触发beforeDestroydestroy,该组件只会触发createdmounted,但是它又离去的时候会触发deactivated,返回的时候会触发active,此时就将`bus.$emit 写在 deactivated 中)

tip:我在网上还看到如果是这种情况的话也可以将bus.nextTick里面去触发,尝试过,结果失败。这里只是当做一个参考。

  • 多级组件嵌套传值
  1. 使用 v-bind = "listeners"
    详情参考[点击链接](https://www.jianshu.com/p/26888cb10ce8 "点击链接")
  2. 使用provide和inject
  • 多个组件重复使用,整个项目都需要的
  1. 使用vuex

参考链接:https://www.cnblogs.com/lanhuo666/p/11273128.html


1. vue的双向数据绑定实现原理?

  • vue2:
    在vue2中源码使用的defineProperty来实现双向绑定。
    实际上defineProperty它的主要作用是用来给对象中的属性配置操作权限,
    例如:
  writable:false, // 属性值是否可写
  enumerable: false, // 属性值是否可循环
  configurable: false //属性值是否可以delete

其中getset实现了响应式原理。
数据改变之后触发了set方法->set方法触发更新和通知,同时get方法收集依赖->然后更改对应的虚拟dom->重新render.

缺点:

  1. 在这里defineProperty有一个不优雅的地方是,要使用get&set 方法实现读取的存储,必须将属性值重新赋值给新的变量,来实现存储中转
  2. 而且对数组需要单独处理。
    // 数组的双向绑定就是做了一个设计者模式;
    // 取出数组的原型链并拷贝;
  3. 并且defineProperty只能监听对象的某个属性,不能监听全对象;需要通过for in 循环。
  • vue3:
    而在vue3中源码使用了的proxy来实现双向绑定原理。
    proxy相对于defineProperty.

优点:

  1. proxy的get和set方法中直接可以返回参数target, key, value,可以直接获取到变量,不需要外部变量实现存储和读取。
  2. 而且可以监听数组,不需要单独的对数组进行特异性处理。
  3. proxy对原对象进行代理,生成新的代理对象,不会污染原对象。

你可能感兴趣的:(前端面试题:VUE)