标签: VUE

1.vuex是什么?怎么使用?哪种功能场景使用它?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。简单来说就是:应用遇到多个组件共享状态时,使用vuex。

文档:https://vuex.vuejs.org/zh/

场景:多个组件共享数据或者是跨组件传递数据时

原理:实际上是通过实例化Vue来实现

流程(工作原理):页面通过mapAction异步提交事件到action。action通过commit把对应参数同步提交到mutation,mutation会修改state中对应的值。最后通过getter把对应值跑出去,在页面的计算属性中,通过,mapGetter来动态获取state中的值。(单向数据流,便于管控)

2.vuex有哪几种属性

有五种,分别是State , Getter , Mutation , Action , Module

  1. state:vuex的基本数据,用来存储变量
  2. geeter:从基本数据(state)派生的数据,相当于state的计算属性
  3. mutation:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,提交载荷作为第二个参数。
  4. action:和mutation的功能大致相同,不同之处在于 ==》1. Action 提交的是 mutation,而不是直接变更状态。 2. Action 可以包含任意异步操作。
  5. modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

4.异步请求代码应该写在组件的methods中还是vuex的actions中?

一、如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。

二、如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用,并包装成promise返回,在调用处用async await处理返回的数据。

5.mutation中能放异步操作吗?为什么?

最好不要。但不会报错。会造成状态改变的不可追踪。异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

这个只是规范,而不是逻辑的不允许,是为了让devtool工具能够追踪数据变化,另外方便我们更好的处理数据逻辑。

6.Vuex中actions和mutations的区别

Mutation 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

const store = createStore({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

Action 类似于 mutation,不同在于:

  1. Action 需要提交 mutation去变更状态,而不是直接变更状态。
  2. Action 可以包含任意异步操作。
  3. mutation是同步更新数据(内部会进行是否异步检测,严格模式会报错,实现检测方法是通过$watch)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

7. commit和dispatch,这两个有什么作用呢?

类似与在vue中通过click事件,触发methods中的方法。

当存在异步时,在vuex中需要dispatch来触发actions中的方法,actions中的commit可以触发mutations中的方法。

同步,则直接在组件中commit触发vuex中mutations中的方法。

通过this.$store.state.属性 的⽅法来访问状态。

通过this.$store.commit(‘mutation中的⽅法’) 来修改状态。

8.双向绑定和vuex是否冲突?

当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model 会比较棘手。因为双向绑定修改状态时没有通过 mutation 函数, 这里会抛出一个错误。

9.Vuex可以直接修改state的值吗?

可以直接修改,但是极其不推荐。

当我们将vuex的模式改成严格模式的时候,我们在通过直接修改state的方式修改状态的时候,控制台会报错。

state的修改必须在mutation来修改,否则⽆法被devtool所监测,⽆法监测数据的来源,⽆法保存状态快照,也就⽆法实现时间漫游、回滚之类的操作。

简单来说,就是使用commit来更改状态主要是为了状态能够很好的跟踪,开发者工具更好的处理状态变化,更好的调试体验。

10.什么情况下使⽤ Vuex?

如果应⽤够简单,最好不要使⽤Vuex ,⼀个简单的 Event Bus 就可实现跨组件通信(公共的vue实例)。

//使用中间vue实例实现子-子通信

//mid
Vue.prototype.$bus = new Vue()

//某子组件发消息
//....
methods: {
    tellname () {
       // 发出事件,传递数据,givename自定义事件
     this.$bus.$emit('givename', this.mybfname) 
  } 
}

//某子组件收消息

// 组件一加载就进行兄弟组件所发出的事件的监听
created () {
    // vm.$on(vue自定义的事件, 处理函数)
    // 处理函数有一个默认参数,就是其它组件所传递的数据
    this.$bus.$on('givename', (data) => {
      console.log(data);
      this.mysbfname = data
    })
}

需要构建⼀个中⼤型单页应⽤时,使⽤Vuex能更好地在组件外部管理状态。

11.页面刷新后Vuex的state数据丢失怎么办?

Vuex只是在页面内存中保存状态,页面刷新后是必然会丢失状态。

当然可以使用localStoragesessionStoragecookie进行存储。我们一般使用localStorage做持久化(Cookie、sessionStorage、localStorage的区别

初始化state数据时可以直接从localStorage中获取备用值,但是需要每次在修改状态时使用localStorage.setItem(key,val)保存最新值,这样感觉不太优雅。

// 初始化
const store = createStore({
    state(){
        return {
            count: localStorage.getItem('count')
        }
    },
    mutations: {
        // 更新值
        updateCount(state, value){
            state.count++
            localStorage.setItem('count',state.count)
        }

    }
})

// 或者在调用时手动更新值
store.commit('updateCount')
localStorage.setItem('count', store.state.count)

存在问题:

  1. 不是所有的状态都需要持久化,所以需要区别对待,有一定心智负担。
  2. 如果需要保存的状态很多,每次手动重设缓存就比较繁琐,不够优雅。

解决方式:

1) 使用Vuex提供的Api --> subscribe做统一处理。(订阅 store 的 mutation。handler 会在每个 mutation 完成后调用,接收 mutation 和经过 mutation 后的状态作为参数),可以将处理函数封装为插件。

const unsubscribe = store.subscribe((mutation, state) => {
  console.log(mutation.type)
  console.log(mutation.payload)
  if(mutation.type==='updateCount'){
    localStorage.setItem('count', state.count)
  }
})

// 你可以调用 unsubscribe 来停止订阅。
unsubscribe()

// 封装为插件
const myPlugin = (store) => {
  // 当 store 初始化后调用
  store.subscribe((mutation, state) => {
    // 每次 mutation 之后调用
    // mutation 的格式为 { type, payload }
  })
}
// 使用插件
const store = createStore({
  // ...
  plugins: [myPlugin]
})

2) 可以使用开源的插件进行持久化,原理同上。插件如 vuex-persist、vuex-persistedstate等。

12.Vuex的缺点

Vuex 可以集中组件的公共状态,利用响应式,很方便我们做开发。但模块化这一块做得比较复杂,使用不是很方便,需要经常看文档去回顾。

另外,如果不打算开发⼤型应⽤,使⽤Vuex可能是繁琐冗余的。Vuex是重量级的,对性能有一些影响。换句话说,Vuex其实不适合中小型应用。

1)模块使用比较繁琐,获取状态模式不统一,复杂度较高,增加心智负担,容易出错

// 使用插件
const store = createStore({
  modules: {
    a: moduleA,
    b: moduleB
  }
})
// 使用
store.state.b // -> moduleB 的状态
store.state.a  // 要带上 moduleA 的 key(.a) ,内嵌模块的话会很长,代码阅读性不佳,不得不配合 mapState 使用
store.getters.c  // moduleA 里的 getters ,没有 namespaced: true 时又变成了全局的
store.getters['a/c']  // namespaced: true 时需要加 path
store.commit('d')  // 没有 namespaced 时又是全局的
store.commit('a/d')  // namespaced: true 时也需要加 path,要使用mapMutations 的话也比较麻烦

2)对TS的支持不太友好,没有友好的类型支持和提示

虽然Vuex在后期版本通过定义类型化的 InjectionKey 使得类型支持有所改善,但使用会很不优雅。

3)体积较大,重量级,小型项目对性能有一定影响。

如今团队又推出了新一代的状态管理插件:Pinia,它使用 Vue 3 中的新反应系统来构建一个直观且完全类型化的状态管理库。其库容量和性能要优于Vuex。而且完全支持Ts。

Pinia是Vuex的良好替代品吗?

1.vue性能优化

1.编码优化(代码层面)

  1. 不要将所有数据都放到data中,因为data中数据会遍历添加gettersetter,会收集对应的watcher
  2. 若在方法或计算属性中不对组件数据进行修改,最好在刚开始时就将数据赋值给变量,之后使用直接使用变量效率较高(let {user,list} = this)。
  3. 在v-for时需要给每项元素绑定事件时用外层事件代理
  4. v-for遍历时避免同时使用v-if,使用计算属性提前把数组中要显示的进行过滤,然后v-for直接使用过滤后的数组
  5. SPA页面采用keep-alive缓存组件,可以添加include和exclude来配置需要缓存的组件。
  6. 尽量细地拆分组件,提高复用性、可维护性,减少不必要的渲染
  7. v-ifv-show 区分使⽤场景:v-iffalse时内部指令不会执行,有阻断功能,不频繁的显隐功能或简单的组件尽量用v-if代替v-show
  8. 较复杂的组件渲染比较吃力,显隐功能可以使用v-show进行缓存组件
  9. computedwatch 区分使⽤场景:computed 的值有缓存,只有它依赖的属性值发⽣改变,下⼀次获取computed 的值时才会重新计算。watch每当监听的数据变化时都会执⾏回调进⾏后续操作;当我们需要在数据变化时执⾏异步或开销较⼤的操作时,应该使⽤watch。当一个属性受多个属性影响的时候需要computed,当一个属性影响多个属性的时候用watch
  10. v-for 遍历必须为 item 添加 keykey保证唯一性(vue的dom更新采用就地复用策略)
  11. Object.freeze冻结数据,这些数据只是单纯展示,避免不必要的响应式(this.userInfo = Object.freeze(userInfo)
  12. 使用v-once指令,处理只需要渲染一次的节点或组件(低开销的静态组件)。
  13. 无状态(没有自己的数据,只是展示父组件的传值)的组件标记为函数式组件(<template functional></template>)。
  14. 合理使用路由懒加载、异步组件(缩减初始包体积,路由会按需加载)
  15. 数据持久化(做缓存)
  16. 防抖、节流
  17. 组件在销毁时注意销毁事件监听和定时器,防止内存泄漏。(vue组件在销毁时会自动销毁组件本身和组件节点上的的事件和指令)
  18. 通过对css样式的合并和js逻辑的封装:如在两不同组件中,拥有相同的样式,可通过全局css文件中设置。在js文件上,将相同的方法封装合并成一个方法,如API请求。

2.页面加载性能优化

  1. 第三方模块按需导入,比如使用elementUI时,不需要的组件无需引入,避免体积过大。(使用babel-plugin-component插件)
  2. 滚动到可视区动态加载。比如长列表使用虚拟滚动,滚动到一定位置时加载下一页。
  3. 图片懒加载,动态加载图片。比如vue-lazyload插件,通过v-lazy指令来取代img的src属性。

3.用户体验

  1. 骨架屏(app-skeleton)首屏加载Loading
  2. app壳(app-shell)默认先渲染一个导航,或者静态视图

4.SEO优化

  1. 预渲染插件(prerender-spa-plugin)
  2. 服务端渲染SSR

5.打包优化

  1. 使用CDN加载第三方资源,就可以缓解我们服务器的压⼒。什么是CDN
  2. 多线程打包(happypack)
  3. Webpack 、Vite 编译时对图⽚进⾏压缩
  4. Webpack可使用相关插件如useless-files-webpack-plugin检查及删除无用文件

6.首屏加载优化

  1. 减少入口文件体积,压缩文件
  2. 合理使用路由组件按需加载、懒加载、异步组件。缩减初始包体积,在调用某个组件时再加载对应的js文件;
  3. 静态资源本地缓存,后端返回的资源:采用http缓存,前端合理利用localStorage,CDN静态资源缓存
  4. UI框架、自定义组件按需加载
  5. 图片压缩,开启gzip压缩(webpack中配置)
  6. 骨架屏、首屏加载Loading
  7. 图标可通过font-icon或矢量图(如SVG)来代替,也可将小图片转为Base64格式进行展示。
  8. 通过精灵图来减少小图标的总请求数
  9. 图片懒加载,对未可见的图片进行延迟加载。比如通过安装vue-lazyload模块实现懒加载。图片懒加载实现
  10. 将公用的JS库通过script标签外部CDN引入,减小打包的js大小,让浏览器并行下载资源文件,提高下载速度;
  11. 加一个首屏 loading 图,提升用户体验;

7.计算首屏加载时间

首屏加载时间,指的是浏览器从相应用户输入网址,到首屏内容渲染完成的时间。可以使用window.performance提供的API进行计算。可以在window.onload事件中读取performance提供的各种数据。

计算首屏加载时间公式:times = (performance.timing.domComplete - performance.timing.navigationStart) / 1000

若首屏的DOM会持续变化的话,可以使用 MutationObserver 方法监听DOM变动,根据变动的时间计算DOM趋于稳定的时间节点。

8. 服务器访问速度优化

  1. 开启服务器 Gzip 压缩,需要前端提供压缩包,然后在服务器开启压缩,⽂件在服务器压缩后传给浏览器,浏览器解压后进⾏再进⾏解析。(通过webpack或Vite对项目体积进行压缩)
  2. 全站 CDN 加速,使用一些第三方 CDN 云加速服务。什么是CDN

2.浏览器性能检测(F12控制台)

Performance(性能)

Performance 是 Chrome 开发者工具中的一个功能,用于记录网页从初始化到运行时的所有性能指标。

使用 Performance 前,我们最好打开 Chrome 的无痕模式。因为 Chrome 上一般有着大量的插件,会或多或少的影响页面的性能,所以我们关掉这个来避免对页面性能的影响。

点击左上角的 Record(小圆点)按钮,Performance 进入 Record 阶段,从此刻开始,它会记录用户的交互以及这些交互对页面性能数据的影响。

生成的 Performance 性能报告,我们先看顶部的三个数据:FPSCPU 以及 NET

  • FPS:主要和动画性能有关,代表每秒帧数。图表中的绿色长条越高,说明FPS越高,用户体验越好。如果其中有红色长条,代表着这部分帧数有卡顿,需要优化
  • CPU:和底部的 Summary 对应,显示了页面加载过程中,各阶段对 CPU 的占用时间,占用时间越多,代表该阶段越需要优化。在 Performance 中,该部分是最需要关注的指标之一。
  • NET:每条彩色横杠表示一种资源。横杠越长,检索资源所需的时间越长。 每个横杠的浅色部分表示等待时间(从请求资源到第一个字节下载完成的时间) 深色部分表示传输时间(下载第一个和最后一个字节之间的时间)
  • Main:火焰图。它展现了主线程在 Record 过程中做的所有事情,包括:Loading、Scripting、Rendering、Painting 等等。火焰图的横轴代表着时间,纵轴代表着调用堆栈。每一个长条代表执行了一个事件或函数,长条的长度代表着耗时的长短,如果某个长条右上角是红色的则表示该函数存在性能问题,需要重点关注。
  • DOMContentLoaded :就是 dom 内容加载完毕。 那什么是 dom 内容加载完毕呢?打开一个网页当输入一个 URL,页面的展示首先是空白的,然后过一会,页面会展示出内容,但是页面的有些资源比如说图片资源还无法看到,此时页面是可以正常的交互,过一段时间后,图片才完成显示在页面。从页面空白到展示出页面内容,会触发 DOMContentLoaded 事件。而这段时间就是 HTML 文档被加载和解析完成。
  • load: 页面上所有的资源(图片,音频,视频等)被加载以后才会触发 load 事件,简单来说,页面的 load 事件会在 DOMContentLoaded 被触发之后才触发。

Performance 提供的性能监测功能已经较为完备,但是,它有两个问题:

  • 数据缺少实时性
  • 数据面板过于复杂,不够直观

为此,Performance monitor 功能可以实时直观的数据展示页面性能。

Lighthouse面板

Lighthouse 是一个开源的自动化工具,是 Chrome 的一个扩展程序。为 Lighthouse 提供一个您要审查的网址,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告,会对页面的加载进行分析,然后给出提高页面性能的建议。可以对以下分类做报告:

  • 性能
  • 无障碍使用
  • 用户体验
  • SEO 优化
  • 移动设备和桌面设备兼容性

3.webpack 性能检测工具

webpack-bundle-analyzer 分析

vue-cli3的项目直接 vue-cli-service build --report 就会生成一个report.html,打开这个html就能看到。非vue-cli3需要自行安装插件。

这个报告可以可以直观分析打包结果,查看各个依赖的体积分布。通过查看,我们可以将首屏未使用到的库去除,进行按需引入。较大的库我们可以换成更轻量级的库。

作为前端开发中现行最火的框架之一,基于此,总结了一些 Vue 方面经常出现的问题,留给自己查看消化,也分享给有需要的小伙伴。

由于篇幅较长,不能将所有知识点放到一篇文章内。这是Vue重点知识梳理理论篇的第二篇。前端茫茫,学无止境。

1.模板引擎原理(指令和插槽表达式如何生效)

使用with改变作用域,渲染数据。并将其包到字符串中。`var render = with(vm){return ${data}}`,使用new Function(render)执行字符串语句。

2.描述组件渲染和更新过程

渲染组件时,会通过Vue.extend()方法构建子组件的构造函数并实例化为vueComponent。extend方法会合并一些父类如Vue的属性。最终手动调用$mount()进行挂载。更新组件时会进行patchVnode流程,也就是diff流程。

3.为什么要使用异步组件加载方式?

如果组件功能较多,打包出的文件会过大。导致页面加载过慢。这时候可以使用import("url")函数异步加载组件的方式,可以实现文件的分隔加载。

  1. 会将组件分开打包。减小体积。
  2. 会采用jsonp的方式加载,有效解决一个文件过大问题。
  3. import语法是webpack提供的。
  4. 异步组件一定是一个函数。
components:{
    mycomp:()=>import("../components/mycomp.vue")
}

4. Vue-loader 是什么

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。本质上,webpack loader 将所有类型的文件,转换为应用程序可以直接引用的模块。所以 loader 就是个搞预处理工作的。

Vue-loader 可以解析和转换 .vue ⽂件,提取出其中的逻辑代码 script 、样式代码 style 、以及模版 template ,再分别把它们交给对应的 Loader 去处理。

5.Vue 中 key 的作用

key主要作用是为了高效得更新虚拟DOM。原理是VUE在patch过程中会通过key精准判断两个节点是否是同一个。从而避免频繁更新不同元素,使得整个patch过程更加高效,减少dom操作量,提高性能。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

准确: 如果不加key,那么vue会选择复⽤节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产⽣⼀系列的bug.。

快速: key的唯⼀性可以被Map数据结构充分利⽤,相⽐于遍历查找的时间复杂度O(n),Map的时间复杂度仅仅为O(1)

  1. 若不设置key,会在列表的渲染过程中发生一些隐蔽的bug,比如新增数据乱序。
  2. 重复的 key 会造成渲染错误。
  3. key是为Vue中的vnode标记的唯⼀id,通过这个key,我们的diff操作可以更准确、更快速
  4. diff算法的过程中,先会进⾏新旧节点的⾸尾交叉对⽐,当⽆法匹配的时候会⽤新节点的key与旧节点进⾏⽐对,然后超出差异

没有key情况还会导致节点的删除和重建,影响性能。若有key,在进行更新时会直接保留原有节点,只插入新节点,无需删除重建。

  1. v-for循环的时候,key属性只能使用number/string
  2. key属性值必须是唯一标识符。
  3. key属性不要使用索引作为标识。

用引索index作为key可能会引发的问题:

  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
  2. 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
  3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

作为前端开发中现行最火的框架之一,Vue 在面试中出现的频率不断增加。基于此,总结了一些 Vue 方面经常出现的面试题,留给自己查看消化,也分享给有需要的小伙伴。

就算工作时间再久,不刷题,是不能那么容易过面试的。我相信就算你是面试官,也不会百分百了解你所问的问题,也不能做到面面俱到。我一直相信,成功的面试都是双方的缘分到了。缘分到了,自然水到渠成。

1.vue解决了什么问题

  1. 不用大量地操作DOM,把重点放到处理数据和业务逻辑上(数据和视图分离,数据驱动视图),可能提高了编码效率,更快的实现业务。
  2. 解决了工程化,模块化的问题,更方便的组织和构建复杂应用。
  3. 提供统一ui库,提升开发效率。
  4. 更加可维护。

2.Vue设计原则理解

  1. 渐进式js框架
  2. 易用、灵活、高效

渐进式

与其他大型框架不同,自底向上逐层应用。核心库只关注视图层,易于上手,还便于与第三方库和自有项目整合。

渐进式在于,如果只是单页应用,完全可以直接引入Vue来开发;如果应用程序有了一定的规模,我们可以考虑引入vue-router路由;如果需要维护很多公共的状态,那么可以引入vuex;如果要做大型应用,我们可以引入Vue-cli脚手架等等。vue的全家桶就体现了渐进式的思想。

易用性

响应式和申明式的模板语法便于快速上手,不需要管底层原理,只要会html、js、css直接就可以开发应用。

灵活性

渐进式的思想最大优点就是灵活。不管是构建大型应用、还是只开发单页、还是与既有项目整合。

高效性

超快的虚拟DOM和diff算法使得我们的应用拥有较高的性能表现。Vue后续版本也得以体现,比如使用Proxy,比如优化diff算法。

3.Vue数据为什么是异步更新?

vue是组件级更新,也就是说,每一次的更新都是渲染整个组件。Vue组件中的每个data数据都会收集依赖,如果是同步的话,一旦修改了data属性,便会触发对应的 watcher,然后调用对应 watcher 下的 update方法更新视图,那么会导致视图频繁更新,性能堪忧。

如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图。这也是nextTick 产生的原因。异步渲染核心思想是 nextTick 。

作用:

nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行,减少操作DOM的次数

使用场景:想要操作 基于最新数据生成的DOM 时,就将这个操作放在 nextTick 的回调中;

实现原理

将传入的回调函数包装成异步任务,异步任务又分微任务和宏任务(setTimeout、promise那些),定义了一个异步方法,多次调用nextTick会将方法存入队列,通过异步方法清空当前队列。

异步更新是Vue会在本轮数据更新后,再去异步更新视图。这也是为了提高性能的考虑。具体实现是:只要侦听到数据变化,Vue 将开启一个队列,将在同一事件循环中发生的所有数据变更进行缓存。如果同一个 watcher 被多次触发,只会被推入到队列中一次。最终执行队列中的run方法更新视图,这里涉及到nextTick。所以相同的watcher最终只会更新一次。

nextTick相当于是一个防抖操作,但类似于setTimeout(flushCallbacks, 0),它的防抖并不是说某一个时间段内的任务只执行一次。而是等到所有主线程的宏任务都执行完才去执行任务队列中的任务。而任务队列的执行顺序是优先微任务的,所以首选promise。不支持promise的话再用MutationObserver,如果都不支持,再考虑异步宏任务setImmediate(高版本IE),再不济只能用setTimeout 0

4. 解释单向数据流和双向数据绑定

单向数据流: 顾名思义,数据流是单向的。数据流动方向可以跟踪,流动单一,追查问题的时候可以更快捷。数据只能从⼀个⽅向来修改状态。

vue 组件间传递数据是单向的,即数据总是由父组件传递到子组件,子组件在其内部可以有自己维护的数据,子组件无权修改父组件传递给它的数据。这样做是为了组件间更好的解耦,在开发中可能有多个子组件依赖于父组件的某个数据,假如子组件可以修改父组件数据的话,一个子组件变化会引发所有依赖这个数据的子组件发生变化。而单向数据流会防止从子组件意外改变父级组件的状态而导致的数据混乱,让开发变得复杂。

缺点就是写起来不太方便。如在Vuex中要使UI发生变更就必须创建各种 action 来维护对应的 state。子组件要改变父组件状态时只能通过$emit方法触发父组件相应的事件来实现。

双向数据绑定:即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。双向数据绑定就是在单向绑定的基础上给输入框元素(input、textare等)添加了 change(input) 事件,来动态修改数据和视图。优点是在表单交互较多的场景下,会简化大量与业务无关的代码。

5.对 MVC、MVP、MVVM的理解

两者都是框架模式,设计目标为了解决view和model的耦合问题,解决代码分层,解决维护性问题。

MVC:Model代表数据模型,数据Model层中定义;View代表UI视图,负责数据的展示;Controller负责业务逻辑处理。

早起专注于服务端(后端),当时ajax还没有火起来,这时前后端虽然有一些分离,但依然绑定在一起。如java的springMVC、ASP.net、PHP的mvc等,前端的backBonejs。优点是分层清晰。缺点是数据流混乱、控制层臃肿,代码不易读,不易维护,灵活性依然有问题。

  1. View 传送指令到 Controller
  2. Controller 完成业务逻辑后,要求 Model 改变状态
  3. Model 将新的数据发送到 View,用户得到反馈
  4. 所有通信都是单向的,最后controller会很臃肿,不易维护。

MVP

MVP的全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。

MVVM:分为Model、View、ViewModel三者

这种模式不仅解决了耦合性问题,而且通过Viewmodel层数据和视图的映射关系,减少了很多繁琐的代码,尤其是DOM操作的代码。在提高开发效率,可读性的同时,还保持了优越的性能。

  1. Model 代表数据模型,数据和业务逻辑都在Model层中定义;
  2. View 代表UI视图,负责数据的展示;
  3. ViewModel-数据响应层,负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
  4. Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
  5. 这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。

Vue中的MVVM模式理解

MVVM是由MVC发展而来的。两者都以数据为核心,MVC最初的用意是为了将视图和逻辑层分离。MVVM比MVC的的最大优势在于MVVM把大量的与页面视图相关的逻辑单独抽离出来(viewModel)构成了一个库。而Vue就是一个vm(viewModel)库。最简单的就是v-model指令,把数据与输入框做一个关联,不需要自己再去监听,Vue内部帮我们搞定。viewModel最多体现在Vue的各种指令上。

MVVM模式在react中的对应关系

  •  M(odel):对应组件的方法或生命周期函数中实现的业务逻辑和this.state中保存的本地数据,如果React集成了redux +react-redux,那么组件中的业务逻辑和本地数据可以完全被解耦出来单独存放当做M层,如业务逻辑放在Reducer和Action中。
  •  V(iew)-M(odel):对应组件中的JSX,它实质上是Virtual DOM的语法糖。React负责维护 Virtual DOM以及对其进行diff运算,而React-dom 会把Virtual DOM渲染成浏览器中的真实DOM
  •  View:对应框架在浏览器中基于虚拟DOM生成的真实DOM(并不需要我们自己书写)以及我们书写的CSS
  • 绑定器:对应JSX中的命令以及绑定的数据,如className={ this.props.xxx }、{this.props.xxx}等等

MVVM的双绑和单绑区别

  • 一般只有UI表单控件才存在双向数据绑定,非UI表单控件只有单向数据绑定。
  • 单向数据绑定是指:M的变化可以自动更新到ViewModel,但ViewModel的变化需要手动更新到M(通过给表单控件设置事件监听)
  • 双向数据绑定是指念:M的变化可以自动更新到ViewModel,ViewModel的变化也可以自动更新到M
  • 双向绑定 = 单向绑定 + UI事件监听。双向和单向只不过是框架封装程度上的差异,本质上两者是可以相互转换的。

优缺点:在表单交互较多的情况下,单向数据绑定的优点是数据更易于跟踪管理和维护,缺点是代码量较多比较啰嗦,双向数据绑定的优缺点和单向绑定正好相反。

MVVM的优点

MVVM比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作DOM元素。因为在MVVM中,View不知道Model的存在,Model和ViewModel也观察不到View,这种低耦合模式提高代码的可重用性。

6.Vue组件化理解

组件是独立的可复用的代码组织单元。组件化是便于复用、测试和维护。而且可以实现分而治之,大幅提高开发效率。

组件系统是Vue的核心特性之一。是一种规范,这种规范使得开发变得更可控。他使得开发者可以使用小型的可复用的组件来构建大型应用。

这种规范,使得组件之间相互影响制约,更好的进行团队协作。包括布局和样式可以独立、解耦。

Vue的更新粒度是组件级别的,组件的数据变化只会影响当前组件的更新,但是在组件更新的过程中,也会对子组件做一定的检查,判断子组件是否也要更新,并通过某种机制避免子组件重复更新。

组件分为:页面组件、业务组件(如登录框)、通用组件(如按钮、弹窗等UI组件)。

组件开发应该是高内聚(更加独立)、低耦合(组件之间不应有太多依赖关系)的。

Vue组件是基于配置的。我们通常写的组件是配置选项而非组件实例。框架后期会生成对应的构造函数。他们基于VueComponent,是Vue的扩展。

vue组件的构造函数在内部通过extend来创建,创建构造函数时会继承Vue且传入当前vue实例,Vue每一个组件都是一个Vue的实例。组件实例是继承自Vue的。

全局组件会立刻调用extend生成构造函数,在当前Vue实例化之前就已经创建了。而局部组件在运行时某个时刻才会生成。

合理使用组件能提升性能:每一个组件在实例化时(mountComponent)都会实例化自己的Watcher,每一个watchar中都有其更新函数。这样每次更新时只更新当前组件本身。所以应该尽量地细粒化组件,提高渲染的性能(这样每次更新视图时打补丁的范围就比较小了)。

组件有父子组件、根实例,组件或路由切换避免丢失状态。

单文件Vue的组件不仅仅是DOM层面的封装。也包含了组件的方法、逻辑、数据、样式。

单文件组件并不是实例的直接导出,而是经过webpack的vue-loader,将vue后缀的文件进行编译,最终导出的是js对象(组件配置对象而非构造函数)。

Vue组件常用的功能有:属性prop、插槽slot、自定义事件等,通常用于组件通信和扩展。

Vue组件遵循单向数据流原则。

7. 对比 原生或jQuery与Vue 有什么不同

原生专注视图层,通过操作 DOM 去实现页面的一些逻辑渲染;

Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作。更直白来说就是不操作DOM,用数据来渲染,通过虚拟的抽象数据层来直接更新页面。当数据发生变化的时候,用户界面发生相应的变化,开发者不需要手动的去操作dom。相当于数据与DOM之间做了一个映射。更简单,更方便,程序员只需要关注核心的业务逻辑和数据变化,不需要关注诸如DOM操作等其他层面。

Vue 使用了组件化思想,提高了开发效率,方便复用,便于协同开发。

Vue的核心是数据驱动,组件系统。

jquery是以程序逻辑为重,事件驱动。所谓的事件驱动简单来说就是用户通过点击,修改,删除,输入等等,来操作DOM,并触发对应的事件,然后通过后台响应处理,随之更新UI。

具体:

  1. jquery是使用选择器()选取DOM对象,对其进行赋值、取值、事件绑定等操作,和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。jquery侧重样式操作,动画效果等;可以应用于一些html5的动画页面,一些需要js来操作页面样式的应用。
  2. Vue 则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的 DOM 对象,他们通过 Vue 对象这个 vm 实现相互的绑定。这就是传说中的 MVVM。vue侧重数据绑定,可以应用于复杂数据操作的后台页面。

8.Vue组件化优势

每一个组件都是一个实例,每一个实例都可以作为一个组件挂载到一个实例上。每个组件是一个vueComponent的实例。方便我们进行单文件组件开发。

  1. 提高开发效率
  2. 方便重复使用
  3. 简化调试步骤,更快聚焦问题
  4. 提升项目的可维护性
  5. 便于多人协同开发

9.Vue 等单页面应用的优缺点

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

  • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染。良好的交互体验。
  • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;便于提高开发效率。后端不再负责模板渲染、输出页⾯⼯作,后端API通⽤化,即同⼀套后端程序代码,不⽤修改就可以⽤于Web界⾯、⼿机、平板等多种客户端。
  • 单页应⽤没有页⾯之间的切换,就不会出现“⽩屏现象”,也不会出现假死并有“闪烁”现象,用户体验及交互比较流畅。
  • 组件化开发,有利于复用和维护。
  • 只有一个Html,前端js承担了页面所有切换和渲染,相对服务器压⼒⼩,服务器只⽤出数据就可以,不⽤管展⽰逻辑和页⾯合成,吞吐能⼒会提⾼⼏倍。

缺点:

  • 首屏加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
  • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理
  • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
  • 容易造成Css命名冲突。
  • 页⾯的复杂度很⾼,需要⼀定的技能⽔平,开发成本⾼

10. 单页⾯(SPA)和多页⾯区别

  • 单页应⽤ 页⾯跳转---->js渲染,整个项目只有一个Html文件,优点:页⾯切换快 缺点:⾸屏加载稍慢,seo差
  • 多页应⽤ 页⾯跳转---->返回html 优点:⾸屏时间快,seo效果好 缺点:页⾯切换慢

11.理解虚拟DOM

浏览器工作流程大致分5步:构建DOM树 –> 创建样式规则Style Rules -> 构建渲染树Render tree -> 布局Layout –> 绘制页面。

源生操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍。比如当你在一次操作时,需要更新10个DOM节点,理想状态是一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,所以最终执行10次流程。

虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。例如前面的例子,假如一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性附到DOM树上,通知浏览器去执行绘制工作,这样可以避免大量的无谓的计算量。

真实的DOM节点,包含着很多属性。虚拟DOM是用js对象模拟DOM节点,好处是:页面的更新可以先全部反映在js对象上,操作js对象的速度显然要快多了。等对象操作完成再一次性渲染。

12.Vue虚拟DOM和diff算法

虚拟dom

使用JS对象来模拟DOM结构,就是所谓的虚拟dom。由于dom操作非常昂贵(吃性能),所以把DOM变化的对比,放在JS层来做,页面的更新可以先全部反映在js对象上,操作js对象的速度显然要快多了。等对象操作完成再一次性渲染,提高dom渲染效率。

由于渲染真实DOM的开销很大,有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,如果想要只更新我们修改的那一小块dom而不要更新整个dom,就用到了虚拟dom和diff算法。

diff算法

diff算法即差异查找算法。对于DOM结构即为tree(dom树)的差异查找算法。

diff算法是虚拟DOM的必然产物。通过新旧虚拟DOM的对比,将最终的变化反映到真实DOM上。

diff算法是⼀种通过同层的树节点进⾏⽐较的⾼效算法,避免了对树进⾏逐层搜索遍历,所以时间复杂度只有 O(n)。

而vue采用虚拟dom形式模拟真实dom,故其差异查找实质是对两个JavaScript对象的差异查找。

Vue为什么使用diff

因为vue中Watcher粒度的降低,一个组件对应一个watcher,只有引入diff才能精准地找到发生变化的位置。

diff算法有两个⽐较显著的特点

  1. ⽐较只会在同层级进⾏, 不会跨层级⽐较。
  2. 在diff⽐较的过程中,循环从两边向中间收拢。

Vue的diff过程(阐述1)

当数据发生改变时,set方法会让调用Dep.notify通知所有订阅者Watcher,内部会尝试将Watcher添加到异步更新队列,在每一次事件循环结束时会清空这些队列。

这时所有的watcher尝试执行其对应的更新函数,更新函数调用了组件的渲染函数和更新函数。这时会重新生成最新的虚拟DOM,然后执行更新函数比较新旧虚拟DOM,这时是真正的diff执行时刻,这个过程称为patch(打补丁),更新相应的视图。

diff调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。(patch意为补丁)。

diff算法采用:深度优先,同层比较的策略。

  1. 两个节点先判断是否都拥有子节点或文本节点然后做不同的操作
  2. 比较两组子节点的算法是diff的核心。首先假设子节点的头尾节点可能相同,做比对尝试,减少循环次数。
  3. 如果没有相同节点则按照通用方式遍历查找。借助key的方式可以高效得找到相同节点。
  4. 当找不到相同节点时,再按情况处理剩余节点。剩余的节点要么是批量删除,要么是批量新增。

比较只会在同层级进行, 不会跨层级比较。

Vue的diff过程(阐述2)

⾸先定义 oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 分别是新⽼两个 VNode 的两边的索引。

接下来是⼀个 while 循环,在这过程中,oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 会逐渐向中间靠拢。while 循环的退出条件是直到⽼节点或者新节点的开始位置⼤于结束位置。

while 循环中会遇到四种情况:

  1. 情形⼀:当新⽼ VNode 节点的 start 是同⼀节点时,直接 patchVnode 即可,同时新⽼ VNode 节点的开始索引都加 1。
  2. 情形⼆:当新⽼ VNode 节点的 end 是同⼀节点时,直接 patchVnode 即可,同时新⽼ VNode 节点的结束索引都减 1。
  3. 情形三:当⽼ VNode 节点的 start 和新 VNode 节点的 end 是同⼀节点时,这说明这次数据更新后 oldStartVnode 已经跑到了oldEndVnode 后⾯去了。这时候在 patchVnode 后,还需要将当前真实 dom 节点移动到 oldEndVnode 的后⾯,同时⽼ VNode 节点开始索引加 1,新 VNode 节点的结束索引减 1。
  4. 情形四:当⽼ VNode 节点的 end 和新 VNode 节点的 start 是同⼀节点时,这说明这次数据更新后 oldEndVnode 跑到了oldStartVnode 的前⾯去了。这时候在 patchVnode 后,还需要将当前真实 dom 节点移动到 oldStartVnode 的前⾯,同时⽼ VNode节点结束索引减 1,新 VNode 节点的开始索引加 1。

while 循环的退出条件:直到⽼节点或者新节点的开始位置⼤于结束位置。

  1. 情形⼀:如果在循环中,oldStartIdx⼤于oldEndIdx了,那就表⽰oldChildren⽐newChildren先循环完毕,那么newChildren⾥⾯剩余的
    节点都是需要新增的节点,把[newStartIdx, newEndIdx]之间的所有节点都插⼊到DOM中
  2. 情形⼆:如果在循环中,newStartIdx⼤于newEndIdx了,那就表⽰newChildren⽐oldChildren先循环完毕,那么oldChildren⾥⾯剩余
    的节点都是需要删除的节点,把[oldStartIdx, oldEndIdx]之间的所有节点都删除

13.虚拟dom的作用(目的)

什么是虚拟DOM?

虚拟DOM其实就是⼀个JavaScript对象。通过这个JavaScript对象来描述真实DOM。

虽然虚拟dom通过diff算法,再利用vue数据异步更新机制, 能够尽可能地减少整个dom的重绘和频繁的渲染。除了将dom转为vdom,然后还要执行diff算法,最终的渲染还是要操作真实dom,所以效率并没有很高。而vue官方也从来没说过虚拟DOM效率有多高。

虚拟dom和diff算法只有在dom元素剧烈变化的时候才会体现他的好处:可以局部替换HTML标签(替换 vnode)。

虚拟dom的作用

  1. 由于虚拟dom和真实dom很相似。而虚拟dom是js对象,这就可以实现组件的高度抽象化
  2. 可以适配 DOM 以外的渲染目标。
  3. 不用再依赖html解析器进行模板解析。为框架跨平台提供了可能。比如React Native、weex、服务端渲染(ssr)。
  4. 提高渲染性能。

虚拟 dom 为什么会提⾼性能?

真实DOM的操作,⼀般都会对某块元素的整体重新渲染,采⽤虚拟DOM的话,当数据变化的时候,只需要局部刷新变化的位置就好了,虚拟dom相当于在js和真实dom中间加了⼀个缓存,利⽤dom diff算法避免了没有必要的dom操作,减少了整个dom的重绘和频繁的渲染,从⽽提⾼性能。

  1. 局部更新节点数据
  2. 新旧js对象进行diff,找到差异节点局部刷新。避免了没有必要的dom操作

虚拟 dom 具体实现步骤

  1. ⽤ JavaScript对象结构表⽰ DOM树的结构;然后⽤这个树构建⼀个真正的 DOM树,插到⽂档当中。
  2. 当状态变更的时候,重新构造⼀棵新的对象树。然后⽤新的树和旧的树进⾏⽐较,记录两棵树差异。
  3. 把记录的差异应⽤到先前(步骤1)所构建的真正的DOM树上,视图实现更新。

14. defineProperty数据劫持后如何通知数据和视图更新的

vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进⾏数据劫持的?说⽩了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情。

我们已经知道实现数据的双向绑定,⾸先要对数据进⾏劫持监听,所以我们需要设置⼀个监听器Observer,⽤来监听所有属性。如果属性发⽣变化了,就需要告诉订阅者Watcher看是否需要更新。

因为订阅者是有很多个,所以我们需要有⼀个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进⾏统⼀管理的。

接着,我们还需要有⼀个指令解析器Compile,对每个节点元素进⾏扫描和解析,将相关指令(如v-model,v-on)对应初始化成⼀个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执⾏对应的更新函数,从⽽更新视图。因此接下去我们执⾏以下3个步骤,实现数据的双向绑定:

  1. 实现⼀个监听器Observer,⽤来劫持并监听所有属性,如果有变动的,就通知订阅者。
  2. 实现⼀个订阅者Watcher,每⼀个Watcher都绑定⼀个更新函数,watcher可以收到属性的变化通知并执⾏相应的函数,从⽽更新视图。
  3. 实现⼀个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显⽰在视图上,然后初始化相应的订阅者(Watcher)。

1.Vue 中怎么自定义过滤器

可以用全局方法 Vue.filter() 注册一个自定义过滤器,它接收两个参数:过滤器 ID 和过滤器函数。过滤器函数以值为参数,返回转换后的值

Vue.filter('reverse', function (value) { 
    return value.split('').reverse().join('') 
})
<!-- 'abc' => 'cba' --> 
<span v-text="message | reverse"></span>

2.Vue 中怎么自定义指令

全局注册

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

局部注册

directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

3.$route和$router的区别

$router 为 VueRouter 实例,想要导航到不同 URL,则使用 $router.push 方法$route 为当前 router 跳转对象里面可以获取 name 、 path 、 query 、 params 等

4.vue-router 使用params与query传参有什么区别

vue-router 可以通过 params 与 query 进行传参

// 传递
this.$router.push({path: './xxx', params: {xx:xxx}})
this.$router.push({path: './xxx', query: {xx:xxx}})

// 接收
this.$route.params
this.$route.query

params 是路由的一部分,必须要有。query 是拼接在 url 后面的参数,没有也可以。
params 不设置的时候,刷新页面或者返回参数会丢,query 则不会有这个问题。

5.vm.$mount()作用,$mount 和 el的区别

$mount和实例化Vue的el参数作用相同,是用来手动执行挂载到相应的dom元素。底层用的是querySelector()方法。若$mount不传参,则模板将被渲染为文档之外的的元素,并且你必须使用原生DOM API把实例的$el插入文档中(注意:当前vue实例或组件实例必须有template模板,否则$el为空),这样也可以渲染模板中的数据。

若使用script引入vue这种方式进行开发,非组件化开发,一般每个页面都会实例化一个新的vue实例。这时就不需要在实例中添加template模板,因为针对的是整个DOM。这时实例化时必须有el标签或调用$mount("dom")方法,且$mount方法必须传参,此时Vue会通过 el选项或$mount指定的挂载元素中提取出innerHtml作为其template模板编译渲染函数。否则数据或方法将无法挂载(vue不知道挂载到哪个元素上,使用appendChild挂载将无效,因为没有template)。

el和$mount区别

两者在使用效果上没有任何区别,都是为了将实例化后的vue挂载到指定的dom元素中。

如果在实例化vue的时候指定el,则该vue将会渲染在此el对应的dom中,反之,若没有指定el,则vue实例会处于一种“未挂载”的状态,此时可以通过$mount手动执行挂载

// 扩展组件
var MyComponent = Vue.extend({
  template: '<div>Hello!</div>'
})

// 创建并挂载到 #app (会替换 #app)
new MyComponent().$mount('#app')

// 同上
new MyComponent({ el: '#app' })

// 实例化Vue
new Vue({ el: '#app' })

// 同上
new Vue({}).$mount('#app')

// 或者,在文档之外渲染并且随后挂载
// 注意:挂载必须使用appendChild方法,若使用innerHtml是无法挂载的,因为$el是一个dom对象,不是标签字符串
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)

6.实例化Vue中的render参数作用(render:h=>h(App))

若使用script引入vue这种方式进行开发,实例化时并不需要render参数。因为没有组件template,此时Vue会通过 el选项或$mount指定的挂载元素中提取出innerHtml作为其template模板编译渲染函数。

若使用组件化开发,实例化vue时实际上需要一个外层template(比如app.vue),vue需要把这个模板先放到页面的某个标签内,再将数据挂载到这个标签。所以render方法就相当于组件中的templatecompoments两个参数的组合。只不过在组件实例中templatecompoments方式比render优先级高一些。而在vue实例中render优先级更高。

import Vue from 'vue';
import VueRouter from "vue-router";
import App from './App';
const app=new Vue({
    el:'#app',
    router,
    render:h => h(App) //App实际上就是一个组件
});

// 上述代码作用等同于
const app=new Vue({
    el:'#app',
    router,
    compoments:{
        App
    },
    template:"<App/>"
});

// 组件
var A = Vue.component("A",{
	template:"<div>{{notice}}</div>",
	data(){
		return{
			notice:"21212112"
		}
	}
})
const app=new Vue({
    el:'#app',
    render:h => h(A)
});

Vue 在创建 Vue 实例时,通过调用 render 方法来渲染实例的 DOM 树。Vue 在调用 render 方法时,会传入一个 createElement 函数作为参数,也就是这里的 h 的实参是 createElement 函数,然后 createElement 以App为参数进行调用,生成一个 VNode节点。render 函数得到这个VNode节点之后,返回给 Vue.jsmount 函数,渲染成真实 DOM 节点,并挂载到根节点上。

vue将createElement简化为h,因为它来自单词 hyperscript,这个单词通常用在虚拟dom的实现中。Hyperscript 本身是指生成HTML 结构的 script 脚本。

render:function(createElement){
    return creatElemnt(App);
}

7.Ajax请求放在哪个生命周期?

Ajax可以放到created及其之后的生命周期。

  1. created时候,DOM未挂载,这里请求的数据可以存到data中。但是无法操作dom,没办法将数据渲染到页面。
  2. mounted时DOM已挂载完成,这里可以操作DOM节点。一般情况下都放到mounted中。
  3. 生命周期时同步进行的,Ajax是异步进行的。为保证逻辑统一性,尽量将Ajax都放到同一个生命周期中。
  4. 服务端渲染时(SSR)不支持mounted(无DOM),所以放到created中。

8.何时需要使用beforeDestroy?

实例销毁之前钩子。在这里实例仍然可用。

  1. 可以解绑$on方法。使用$off方法。(在非父子组件通信时会借助中间Vue实例,然后使用vm.$emit()vm.$on()方法进行通信)
  2. 可以清除定时器。
  3. 可以移除绑定的事件监听。使用removeEventListener

9.Vue父子组件的生命周期调用顺序

渲染调用顺序:先父后子;渲染完成顺序:先子后父

父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 若还有嵌套子组件,则将子mounted缓存,继续下级子组件渲染 -> 父mounted

更新操作:先父后子;更新完成顺序:先子后父

父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

销毁操作:先父后子;销毁完成顺序:先子后父

父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

10.vue组件如何通信

父子通信:子组件通过 props 属性,绑定父组件数据,实现双方通信

子父通信:由于Vue数据传递是单向数据流(儿子不能直接改父亲)

  1. 事件监听方法:父组件通过$on监听,子组件 $emit 触发。(简易的发布-订阅)
  2. 父组件通过props给子组件传递一个方法,子组件调用这个方法。

其他通信方式:

  1. 通过$children $parent获取父子组件的实例来拿到父子相应的数据。
  2. 通过provideinject在父组件中提供数据,子组件进行注入(写插件使用,把provide挂到$options上,内部会挂到实例上vm._provided,子组件会直接将其挂到数据中,而且是响应式的)。
  3. 通过ref获取组件的实例,从而拿到组件的数据。(普通标签获取的事dom元素,组件标签获取的是组件实例)

非兄弟组件通信:

  1. 在组件结构相对简单情况下,可以通过兄弟组件中的父组件, 子组件先把数据传递给父组件,  父组件再传递给另外的子组件。
  2. Event Bus 实现跨组件通信(公共的vue实例进行通信),如 Vue.prototype.$bus = new Vue()
  3. 使用状态管理插件vuex
//使用中间vue实例实现子-子通信

//mid
Vue.prototype.$bus = new Vue()

//某子组件发消息
//....
methods: {
    tellname () {
       // 发出事件,传递数据,givename自定义事件
     this.$bus.$emit('givename', this.mybfname) 
  } 
}

//某子组件收消息

// 组件一加载就进行兄弟组件所发出的事件的监听
created () {
    // vm.$on(vue自定义的事件, 处理函数)
    // 处理函数有一个默认参数,就是其它组件所传递的数据
    this.$bus.$on('givename', (data) => {
      console.log(data);
      this.mysbfname = data
    })
}

11.Vue相同逻辑如何抽离

Vue.mixin方法。可以给组件每个生命周期和函数混入一些公共逻辑。

12. Vue中如何实现⼦组件内的css样式名在项⽬中绝对唯⼀性

style标签上加上scoped属性

13. 说出⾄少4种Vue当中的指令和它的⽤法?

  1. v-if(判断是否隐藏,⽤来判断元素是否创建)
  2. v-show(元素的显⽰隐藏,类似css中的display的block和hidden)
  3. v-for(把数据遍历出来)
  4. v-bind(绑定属性、响应式数据)
  5. v-model(实现双向绑定、语法糖)

1.Vue 组件 data 为什么必须是函数

因为js本身的特性带来的,Vue组件可能会有多个实例,如果 data 是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到当前组件所有实例的数据。如果将 data 作为一个工厂函数,那么每一个组件实例的 data 属性都是全新独立的,不会相互影响了,有效规避了很多组件状态污染问题。

通过查看源码发现,在state.js的initData方法中,对data的类型做了判断,如果是函数类型,则执行getData(data.call()直接执行)方法获取其值,如果是普通对象则返回原对象。

在组件中使用对象作为data的值,在编译时会直接报错。

原理:

  1. data是被挂到组件类的原型中,如果不是函数而直接是一个对象,会被其他任何组件继承,任何组件都可以对其进行修改。
  2. 如果data是一个工厂对象,每个组件进行调用时,执行函数都会返回一个全新的对象,就可以避免组件之间的数据互相影响。
  3. Vue实例化根组件时data是一个对象,是因为Vue根实例只会实例化一次,是单例的,实例不会被公用。不用考虑组件之间干扰问题。

2.计算属性computed 和 methods 有什么区别

我们可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的。

区别:

  1. computed计算属性是一个watcher有缓存,计算属性是基于依赖(依赖就是data中的数据)进行缓存的。只要依赖的数据不发生改变,计算属性中的方法是不需要执行的,一直使用缓存中的数据。当它的相关依赖发生改变时会重新赋值。当多次调用的时候只要依赖项不改变,就不会重新执行,从而节省了内存。(缓存机制在源码中通过dirty状态去判定)
  2. method 就是普通的函数。不依赖data中的数据,也没有缓存,每次调用都重新执行一遍。
  3. 函数的调用时需要加()的,而计算属性调用不需要加括号。
  4. 计算属性是必须有返回值的。

3.侦听器 watch和计算属性computed区别

两者的原理相同,在内部都是watcher,主要区别就是computed是有缓存的。如下:

  1. 侦听器 watch(数据一旦发生变化就通知侦听器所绑定的方法)。watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法;
  2. 计算属性computed是根据一个现有数据去生成一个新数据,并且这两个数据会永久的建立关系,还会建立缓存,当数据没有改变的时候,不会重新计算而是直接使用缓存中的值。
  3. 初始化computed计算属性时会实例化一个watcher,这个watcher有一个lazy:true标识,代表计算属性。还有一个默认dirty:true的标识,为true时才会让计算属性执行。当第一次取值时会执行,执行完毕后会将dirty改为false,也就是说下次再取值不会执行,会直接使用缓存的值。只有当计算属性所依赖的属性发生变化时,会将dirty改为true
  4. computed的底层还是使用Object.defindProperty来实现视图更新。
  5. watch一般用于异步、定时任务、或者开销较大(耗时)的操作。
  6. watch 中的属性 一定是data 中 已经存在的数据。
  7. 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,此时就需要deep属性对对象进行深度监听(深度遍历和递归)。
  8. 使用 watch 选项允许我们执行异步操作,并在我们得到最终结果前,进行中间操作。也可以在数据频繁变化时进行自定义的操作如过滤操作。 而计算属性无法做到的。
  9. 计算属性是必须有返回值的。
watch:{
    obj:{ //监听的对象
        deep:true, //深度监听设置为 true
        handler:function(newV,oldV){
            console.log('watch中:',newV)
        }
    }
}
// deep:true的实现就是深度遍历和递归,很耗性能。
// 如果监听的是数组,deep:true会将数组中的每一项进行遍历取值,会将当前的watcher存到对应属性或数组项的依赖中,实现深层数据变化也会通知。

4.v-if 和 v-show 区别

使用了 v-if 控制页面元素节点是否渲染。v-show 改变 CSS 中的 display 显隐。故想要频繁控制显隐,最好使用v-show

两者不能连用,虽然连用不会报错,但是只有两者同时为true时才会显示节点,无法进行精准的判断。而且两者连用会在渲染函数中做两次判断,影响了性能。

底层原理:

  1. v-if在内部使用with语法,使用三元运算符判断v-if的值的真假。若为真,则渲染节点。若为假,则不渲染。
  2. v-show在内部使用创建指令方式,判断值为假,则改变节点样式display:none,若为真,则使用原有样式。

5.为什么v-if和v-for不能连用?

因为v-forv-if优先级高(vue2.x)。

通过测试得出,如果连用,在渲染函数($options.render)中v-for遍历出的每个元素中都添加v-if指令,性能很差。

所以一般在外层template标签添加v-if,这样如果为false,则不会执行v-for遍历,节省了性能开销。

优化:若非整体显隐,而是在列表中某些字内容有v-if的判断,这时也可以做一个计算属性,通过计算将不需要显示字内容进行过滤,节省了性能。

6.v-for中为什么要有key属性?

在Vue中使用v-for进行列表渲染的时候,它会默认使用“就地更新”的策略。直接对已有的标签就地更新,并不会将所有的标签全部重新删除和重建,只会重新渲染数据,然后再创建新的元素直到数据渲染完为止。

v-for渲染的列表更新时,diff算法会比较新旧节点的标签名key。若进行增加删除列表内节点后,key值会发生变化,那么新旧节点在进行比对时容易错位。最终导致渲染出错。

  1. key主要作用是为了高效得更新虚拟DOM。原理是VUE在patch过程中会通过key精准判断两个节点是否是同一个。从而避免频繁更新不同元素,使得整个patch过程更加高效,减少dom操作量,提高性能。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
  2. 若不设置key,会在列表的渲染过程中发生一些隐蔽的bug
  3. 重复的 key 会造成渲染错误。

没有key情况还会导致节点的删除和重建,影响性能。若有key,在进行更新时会直接保留原有节点,只插入新节点,无需删除重建。

  1. v-for循环的时候,key属性只能使用number/string
  2. key属性值必须是唯一标识符。
  3. key属性不要使用索引作为标识。

用引索index作为key可能会引发的问题:

  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
  2. 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
  3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

7.vue的生命周期介绍

Vue实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM->渲染、更新->渲染、卸载等一系列过程,我们称这是Vue的生命周期。

1.vue生命周期的作用是什么?

它的生命周期中有多个事件钩子,让我们在控制整个vue实例的过程时更容易形成好的逻辑。

在于Vue渲染过程的每个节点给一个通知(事件、回调),便于我们在某一个节点进行一些相关的操作。

2.Vue生命周期总共有几个阶段?

它可以总共分为8个阶段:创建前/后,载入前/后,更新前/后,销毁前/销毁后

3.第一次页面加载会触发那几个钩子?

第一次页面加载时会触发beforeCreate,created,beforeMount,mounted

4.DOM渲染在哪个周期中就已经完成?

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

5.生命周期的调用流程:

  1. beforeCreate:实例初始化完成,数据观测(data observer)之前。在这里拿不到实例中的数据。
  2. created:实例创建完成(数据观测完成、计算属性运算完成、method方法绑定、watch和event事件回调绑定),但dom还未挂载,数据还未渲染到页面,拿不到$el(拿不到dom元素)。<create 和 mount 的区别>
  3. beforeMounted:实例挂载之前执行。会调用render函数(调用template)。这个钩子基本用不到。
  4. mounted:实例挂载完成。创建新的vm.$el,把老的el对象删掉。这里可以拿到dom元素。
  5. beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。
  6. updated:当前生命周期更新之后调用。由于数据更改,虚拟DOM渲染完成之后。
  7. beforeDestroy: 实例销毁之前调用。在这时,实例仍然可用。
  8. destroyed:实例销毁之后调用。调用后,实例所有数据监听都会解绑,所有事件监听都会移除,所有子实例也会被销毁。

6.生命周期钩子的一些使用方法(重点):

  1. beforeCreate:可以加个loading事件,在加载实例时候触发。
  2. created:在这里可以结束loading事件,执行异步请求将数据存如data。
  3. mounted:获取到dom节点,可以进行一些dom操作。由于这里能够取到dom,也可以在这里执行异步请求。
  4. beforeUpdate:在这里可以进一步更新状态,不会导致DOM重新渲染。
  5. updated:这里尽量不要再更新状态,否则可能会导致死循环。这里一般用不到。就算用到也是执行一些基于dom的操作,如动画效果。
  6. beforeDestroy:可以执行一些优化操作。如清空定时器,移除绑定事件等。
  7. nextTick:更新数据后立即操作dom,回调可以做一些在dom渲染完成后做的事。

8.父子组件执行顺序

在组件开始生成到结束生成的过程中,如果该组件还包含子组件,则自己开始生成后,要让所有的子组件也开始生成,然后自己就等着,直到所有的子组件生成完毕,自己再结束。所以上图中。“父亲”先开始自己的created,然后“儿子”开始自己的created和mounted,最后“父亲”再执行自己的mounted。

9.v-html指令会造成哪些问题?

1.可能会导致xss攻击。

2.会将标签内的子元素全部替换。

10.对 keep-alive 的了解

keep-alive 是 Vue 内置的一个组件,可以实现组件缓存。缓存组件数据。组件切换时不会对其进行卸载。可以使被包含的组件保留状态,避免重新渲染。

keep-alive会改变组件的生命周期。当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。

  1. 有两个属性props:include和exclude,想缓存的内容和不想缓存的内容。max属性:最多缓存多少个。
  2. keep-alive内插入多个组件只会缓存第一个。

作用:①提升性能,避免每次重新渲染。(但也不宜太多,太多的话占用内存太大得不偿失)②保留状态(数据)