前端开发
前端架构
Vue.js

前端面试官问 开发vue项目都遇到过哪些问题怎么解决的?

关注者
30
被浏览
76,118

6 个回答

打包优化的目的

1、优化项目启动速度,和性能

2、必要的清理数据

3、性能优化的主要方向

cdn加载

-压缩js

减少项目在首次加载的时长(首屏加载优化)

4、目前的解决方向

cdn加载不比多说,就是改为引入外部js路径

首屏加载优化方面主要其实就两点

第一: 尽可能的减少首次加载的文件体积,和进行分布加载

第二: 首屏加载最好的解决方案就是ssr(服务端渲染),还利于seo

但是一般情况下没太多人选择ssr,因为只要不需要seo,ssr更多的是增加了项目开销和技术难度的。

1、路由懒加载

在 Webpack 中,我们可以使用动态 import语法来定义代码分块点 (split point): import(’./Fee.vue’) // 返回 Promise如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。

结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。

const Fee = () => import('./Fee.vue')

在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:

const router = new VueRouter({ 
  routes: [ 
    { path: '/fee', component: Fee } 
  ] 
}) 

2、服务器和webpack打包同时配置Gzip

Gzip是GNU zip的缩写,顾名思义是一种压缩技术。它将浏览器请求的文件先在服务器端进行压缩,然后传递给浏览器,浏览器解压之后再进行页面的解析工作。在服务端开启Gzip支持后,我们前端需要提供资源压缩包,通过Compression-Webpack-Plugin插件build提供压缩

需要后端配置,这里提供nginx方式:

http:{  
      gzip on; #开启或关闭gzip on off 
      gzip_disable "msie6"; #不使用gzip IE6 
      gzip_min_length 100k; #gzip压缩最小文件大小,超出进行压缩(自行调节) 
      gzip_buffers 4 16k; #buffer 不用修改 
      gzip_comp_level 8; #压缩级别:1-10,数字越大压缩的越好,时间也越长 
      gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; #  压缩文件类型  
} 
 
  • // 安装插件
  $ cnpm i --save-dev compression-webpack-plugin 

// 在vue-config.js 中加入

  const CompressionWebpackPlugin = require('compression-webpack-plugin'); 
  const productionGzipExtensions = [ 
    "js", 
    "css", 
    "svg", 
    "woff", 
    "ttf", 
    "json", 
    "html" 
  ]; 
  const isProduction = process.env.NODE_ENV === 'production'; 
  ..... 
  module.exports = { 
  .... 
   // 配置webpack 
   configureWebpack: config => { 
    if (isProduction) { 
     // 开启gzip压缩 
     config.plugins.push(new CompressionWebpackPlugin({ 
      algorithm: 'gzip', 
      test: /\.js$|\.html$|\.json$|\.css/, 
      threshold: 10240, 
      minRatio: 0.8 
     })) 
    } 
   } 
  } 
 

3、优化打包chunk-vendor.js文件体积过大

当我们运行项目并且打包的时候,会发现chunk-vendors.js这个文件非常大,那是因为webpack将所有的依赖全都压缩到了这个文件里面,这时我们可以将其拆分,将所有的依赖都打包成单独的js。

  // 在vue-config.js 中加入 
 
  ..... 
  module.exports = { 
  .... 
   // 配置webpack 
   configureWebpack: config => { 
    if (isProduction) { 
      // 开启分离js 
      config.optimization = { 
        runtimeChunk: 'single', 
        splitChunks: { 
          chunks: 'all', 
          maxInitialRequests: Infinity, 
          minSize: 20000, 
          cacheGroups: { 
            vendor: { 
              test: /[\\/]node_modules[\\/]/, 
              name (module) { 
                // get the name. E.g. node_modules/packageName/not/this/part.js 
                // or node_modules/packageName 
                const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1] 
                // npm package names are URL-safe, but some servers don't like @ symbols 
                return `npm.${packageName.replace('@', '')}` 
              } 
            } 
          } 
        } 
      }; 
    } 
   } 
  } 
 

// 至此,你会发现原先的vender文件没有了,同时多了好几个依赖的js文件

4、启用CDN加速

用Gzip已把文件的大小减少了三分之二了,但这个还是得不到满足。那我们就把那些不太可能改动的代码或者库分离出来,继续减小单个chunk-vendors,然后通过CDN加载进行加速加载资源。

  // 修改vue.config.js 分离不常用代码库 
  // 如果不配置webpack也可直接在index.html引入 
 
  module.exports = { 
   configureWebpack: config => { 
    if (isProduction) { 
     config.externals = { 
      'vue': 'Vue', 
      'vue-router': 'VueRouter', 
      'moment': 'moment' 
     } 
    } 
   } 
  } 
 

// 在public文件夹的index.html 加载

  <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.runtime.min.js"></script> 
  <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script> 
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script> 

5、完整vue.config.js代码

  const path = require('path') 
 
  // 在vue-config.js 中加入 
  // 开启gzip压缩 
  const CompressionWebpackPlugin = require('compression-webpack-plugin'); 
  // 判断开发环境 
  const isProduction = process.env.NODE_ENV === 'production'; 
 
  const resolve = dir => { 
    return path.join(__dirname, dir) 
  } 
 
  // 项目部署基础 
  // 默认情况下,我们假设你的应用将被部署在域的根目录下, 
  // 例如:https://www.my-app.com/ 
  // 默认:'/' 
  // 如果您的应用程序部署在子路径中,则需要在这指定子路径 
  // 例如:https://www.foobar.com/my-app/ 
  // 需要将它改为'/my-app/' 
  // iview-admin线上演示打包路径: https://file.iviewui.com/admin-dist/ 
  const BASE_URL = process.env.NODE_ENV === 'production' 
    ? '/' 
    : '/' 
 
  module.exports = { 
    //webpack配置 
    configureWebpack:config => { 
      // 开启gzip压缩 
      if (isProduction) { 
        config.plugins.push(new CompressionWebpackPlugin({ 
          algorithm: 'gzip', 
          test: /\.js$|\.html$|\.json$|\.css/, 
          threshold: 10240, 
          minRatio: 0.8 
        })); 
        // 开启分离js 
        config.optimization = { 
          runtimeChunk: 'single', 
          splitChunks: { 
            chunks: 'all', 
            maxInitialRequests: Infinity, 
            minSize: 20000, 
            cacheGroups: { 
              vendor: { 
                test: /[\\/]node_modules[\\/]/, 
                name (module) { 
                  // get the name. E.g. node_modules/packageName/not/this/part.js 
                  // or node_modules/packageName 
                  const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1] 
                  // npm package names are URL-safe, but some servers don't like @ symbols 
                  return `npm.${packageName.replace('@', '')}` 
                } 
              } 
            } 
          } 
        }; 
        // 取消webpack警告的性能提示 
        config.performance = { 
          hints:'warning', 
              //入口起点的最大体积 
              maxEntrypointSize: 50000000, 
              //生成文件的最大体积 
              maxAssetSize: 30000000, 
              //只给出 js 文件的性能提示 
              assetFilter: function(assetFilename) { 
            return assetFilename.endsWith('.js'); 
          } 
        } 
      } 
    }, 
    // Project deployment base 
    // By default we assume your app will be deployed at the root of a domain, 
    // e.g. https://www.my-app.com/ 
    // If your app is deployed at a sub-path, you will need to specify that 
    // sub-path here. For example, if your app is deployed at 
    // https://www.foobar.com/my-app/ 
    // then change this to '/my-app/' 
    publicPath: BASE_URL, 
    // tweak internal webpack configuration. 
    // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 
    devServer: { 
      host: 'localhost', 
      port: 8080, // 端口号 
      hotOnly: false, 
      https: false, // https:{type:Boolean} 
      open: true, //配置自动启动浏览器 
      proxy:null // 配置跨域处理,只有一个代理 
 
    }, 
    // 如果你不需要使用eslint,把lintOnSave设为false即可 
    lintOnSave: true, 
    css:{ 
      loaderOptions:{ 
        less:{ 
          javascriptEnabled:true 
        } 
      }, 
      extract: true,// 是否使用css分离插件 ExtractTextPlugin 
      sourceMap: false,// 开启 CSS source maps 
      modules: false// 启用 CSS modules for all css / pre-processor files. 
    }, 
    chainWebpack: config => { 
      config.resolve.alias 
        .set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components')) 
        .set('@c', resolve('src/components')) 
    }, 
    // 打包时不生成.map文件 
    productionSourceMap: false 
    // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串 
    // devServer: { 
    //   proxy: 'localhost:3000' 
    // } 
  } 

就分享到这里了。欢迎关注陆神,会持续分享前端技术干货!

发布于 2022-10-28 09:51

前端面试系列:

  • 前端面试系列初章——HTML & CSS 篇
  • 前端面试系列之最重要的基础知识——JavaScript篇

Vue 基础

Computed 和 Watch 的区别

  • computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
  • watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
  • 运用场景
    • 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
    • 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

常见的事件修饰符及其作用

  • .stop:等同于 JavaScript 中的 event.stopPropagation() ,防止事件冒泡;
  • .prevent :等同于 JavaScript 中的 event.preventDefault() ,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
  • .capture :与事件冒泡的方向相反,事件捕获由外到内;
  • .self :只会触发自己范围内的事件,不包含子元素;
  • .once :只会触发一次

v-if 和 v-show 的区别

  • 手段:v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐;
  • 编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;
  • 编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留;
  • 性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
  • 使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换。

data 为什么是一个函数而不是对象

  • JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。
  • 而在 Vue 中,更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。
  • 所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。

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

  • "push","pop", "shift", "unshift", "splice", "sort", "reverse"
  • 在Vue中,对响应式处理利用的是Object.defineProperty对数据进行拦截,而这个方法并不能监听到数组内部变化,数组长度变化,数组的截取变化等,所以需要对这些操作进行hack,让Vue能监听到其中的变化。

谈谈MVVM

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

使用 Object.defineProperty() 来进行数据劫持有什么缺点?

  • 在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。
  • 在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。

说一下Vue的生命周期

  • 概念:Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。
  • beforeCreate(创建前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
  • created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。
  • beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
  • mounted(挂载后):在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。
  • beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
  • updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  • beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。
  • destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
  • 另外还有 keep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。

Vue 子组件和父组件执行顺序

  • 加载渲染过程:
    • 父组件 beforeCreate
    • 父组件 created
    • 父组件 beforeMount
    • 子组件 beforeCreate
    • 子组件 created
    • 子组件 beforeMount
    • 子组件 mounted
    • 父组件 mounted
  • 更新过程:
    • 父组件 beforeUpdate
    • 子组件 beforeUpdate
    • 子组件 updated
    • 父组件 updated
  • 销毁过程:
    • 父组件 beforeDestroy
    • 子组件 beforeDestroy
    • 子组件 destroyed
    • 父组件 destoryed

一般在哪个生命周期请求异步数据

  • 我们可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
  • 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
    • 能更快获取到服务端数据,减少页面加载时间,用户体验更好;
    • SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。

Vue 组件通信方式

(1)父子组件间通信

    • 子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 $emit 触发事件来向父组件发送数据。
    • 通过 ref 属性给子组件设置一个名字。父组件通过 $refs 组件名来获得子组件,子组件通过 $parent 获得父组件,这样也可以实现通信。
    • 使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。

(2)兄弟组件间通信

    • 使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。
    • 通过 $parent/$refs 来获取到兄弟组件,也可以进行通信。

(3)任意组件之间

    • 使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。
    • Vuex:如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

Vue 进阶

Vue 路由模式有哪些?有什么区别?

  • vue的路由模式⼀共有两种,分别是hash和history
  • 区别
    • 它们的区别是 hash 模式不会包含在 http 请求当中,并且 hash 不会重新加载⻚⾯
    • 使⽤history模式的话,如果前端的url和后端发起请求的url不⼀致的话,会 报404错误,所以使⽤history模块的话我们需要和后端进⾏配合.
  • 原理
    • history的原理就是利⽤html5新增的两个特性⽅法,分别是psuhState和replaceState来完成的,这就是我对vue路由模式的理解

说说 Vue Router 的 hash模式

  • 简介: hash模式是开发中默认的模式,它的URL带着一个#,例如:www.abc.com/#/vue,它的hash值就是#/vue
  • 特点:hash 值会出现在 URL 里面,但是不会出现在 HTTP 请求中,对后端完全没有影响。所以改变 hash 值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
  • 原理: hash 模式的主要原理就是 onhashchange() 事件。
  • 使用 onhashchange() 事件的好处就是,在页面的 hash 值发生变化时,无需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的hash值和对应的URL关联起来了。

$route$router 的区别

  • $route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数
  • $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。

Vuex 是什么?核心流程?

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化。
  • 核心流程中的主要功能:
  • Vue Components 是 vue 组件,组件会触发(dispatch)一些事件或动作,也就是图中的 Actions;
  • 在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;
  • 然后 Mutations 就去改变(Mutate)State 中的数据;
  • 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。

Vuex中action和mutation的区别

  • Mutation专注于修改State,理论上是修改State的唯一途径;Action业务代码、异步请求。
  • Mutation:必须同步执行;Action:可以异步,但不能直接操作State。
  • 在视图更新时,先触发actions,actions再触发mutation
  • mutation的参数是state,它包含store中的数据;store的参数是context,它是 state 的父级,包含 state、getters

Redux 和 Vuex 有什么区别,它们的共同思想

(1)Redux 和 Vuex区别

    • Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可
    • Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
    • Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)

(2)共同思想

    • 单—的数据源
    • 变化可以预测
    • 本质上:redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案; 形式上:vuex借鉴了redux,将store作为全局的数据中心,进行mode管理;

Vuex 和 localStorage 的区别

(1)最重要的区别

    • vuex存储在内存中
    • localstorage 则以文件的方式存储在本地,只能存储字符串类型的数据,存储对象需要 JSON的stringify和parse方法进行处理。 读取内存比读取硬盘速度要快

(2)应用场景

    • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值。
    • localstorage是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。
    • Vuex能做到数据的响应式,localstorage不能

(3)永久性

    • 刷新页面时vuex存储的值会丢失,localstorage不会。
    • 注意: 对于不变的数据确实可以用localstorage可以代替vuex,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage无法做到,原因就是区别1。

Vue3.0有什么更新

(1)监测机制的改变

    • 3.0 将带来基于代理 Proxy的 observer 实现,提供全语言覆盖的反应性跟踪。
    • 消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:

(2)只能监测属性,不能监测对象

    • 检测属性的添加和删除;
    • 检测数组索引和长度的变更;
    • 支持 Map、Set、WeakMap 和 WeakSet。

(3)模板

    • 作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。
    • 同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。

(4)对象式的组件声明方式

    • vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。
    • 3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易

(5)其它方面的更改

    • 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
    • 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。
    • 基于 tree shaking 优化,提供了更多的内置功能。

defineProperty和proxy的区别

  • Vue 在实例初始化时遍历 data 中的所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。这样当追踪数据发生变化时,setter 会被自动调用。
  • Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
  • 但是这样做有以下问题:
    • 添加或删除对象的属性时,Vue 检测不到。因为添加或删除的对象没有在初始化进行响应式处理,只能通过$set 来调用Object.defineProperty()处理。
    • 无法监控到数组下标和长度的变化。
  • Vue3 使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。相对于Object.defineProperty(),其有以下特点:
    • Proxy 直接代理整个对象而非对象属性,这样只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。
    • Proxy 可以监听数组的变化。

对虚拟DOM的理解?

  • 从本质上来说,Virtual Dom是一个JavaScript对象,通过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能。
  • 虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对 DOM的描述。它设计的最初目的,就是更好的跨平台,比如Node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟DOM,因为虚拟DOM本身是js对象。 在代码渲染到页面之前,vue会把代码转换成一个对象(虚拟 DOM)。以对象的形式来描述真实DOM结构,最终渲染到页面。在每次数据发生变化前,虚拟DOM都会缓存一份,变化之时,现在的虚拟DOM会与缓存的虚拟DOM进行比较。在vue内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。
  • 另外现代前端框架的一个基本要求就是无须手动操作DOM,一方面是因为手动操作DOM无法保证程序性能,多人协作的项目中如果review不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作可以大大提高开发效率。

虚拟DOM的解析过程

  • 首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
  • 当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
  • 最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。

双向数据绑定的原理

  • Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
  • 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
  • compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  • Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
    • ①在自身实例化时往属性订阅器(dep)里面添加自己
    • ②自身必须有一个update()方法
    • ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
  • MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

Diff 算法

  • 在新老虚拟DOM对比时:
  • 首先,对比节点本身
    • 没有新节点,直接触发旧节点的destory钩子
    • 没有旧节点,说明是页面刚开始初始化的时候,此时,根本不需要比较了,直接全是新建,所以只调用 createElm
    • 旧节点和新节点自身一样,通过 sameVnode 判断节点是否一样,一样时,直接调用 patchVnode 去处理这两个节点
    • 旧节点和新节点自身不一样,当两个节点不一样的时候,直接创建新节点,删除旧节点
  • 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
    • 新节点是否是文本节点,如果是,则直接更新dom的文本内容为新节点的文本内容
    • 新节点和旧节点如果都有子节点,则处理比较更新子节点
    • 只有新节点有子节点,旧节点没有,那么不用比较了,所有节点都是全新的,所以直接全部新建就好了,新建是指创建出所有新DOM,并且添加进父节点
    • 只有旧节点有子节点而新节点没有,说明更新后的页面,旧节点全部都不见了,那么要做的,就是把所有的旧节点删除,也就是直接把DOM 删除
  • 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。
    • 设置新旧VNode的头尾指针
    • 新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找 key一致的VNode 节点再分情况操作
  • 匹配时,找到相同的子节点,递归比较子节点
  • 在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n3)降低值O(n),也就是说,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。

Vue 为什么要设置 key?

  • vue 中 key 值的作用可以分为两种情况来考虑:
    • 第一种情况是 v-if 中使用 key。由于 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因此当使用 v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input 元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。
    • 第二种情况是 v-for 中使用 key。用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM。


  • key 是为 Vue 中 vnode 的唯一标记,通过这个 key,diff 操作可以更准确、更快速
    • 更准确:因为带 key 就不是就地复用了,在 sameNode 函数a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。
    • 更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快


为什么不建议用index作为key?

  • 使用index 作为 key和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2...这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作。

编译的过程

  • 将模板解析为 AST
    • 最主要的事情还是通过各种各样的正则表达式去匹配模板中的内容,然后将内容提取出来做各种逻辑操作,接下来会生成一个最基本的 AST 对象
  • 优化 AST
    • 对静态节点做优化
  • 将 AST 转换为 render 函数

NextTick 原理分析

  • 对于实现 macrotasks ,会先判断是否能使用 setImmediate ,不能的话降级为 MessageChannel ,以上都不行的话就使用 setTimeout
  • Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。
  • nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。
  • nextTick 不仅是 Vue 内部的异步队列的调用方法,同时也允许开发者在实际项目中使用这个方法来满足实际应用中对 DOM 更新数据时机的后续逻辑处理
  • 问题
    • 如果是同步更新,则多次对一个或多个属性赋值,会频繁触发 UI/DOM 的渲染,可以减少一些无用渲染
    • 同时由于 VirtualDOM 的引入,每一次状态发生变化后,状态变化的信号会发送给组件,组件内部使用 VirtualDOM 进行计算得出需要更新的具体的 DOM 节点,然后对 DOM 进行更新操作,每次更新状态后的渲染过程需要更多的计算,而这种无用功也将浪费更多的性能,所以异步渲染变得更加至关重要

往期干货

【前端行业发展】:

  • Web 前端分为哪几个大方向,工资待遇如何,辛苦吗?
  • 找前端工作会不会很难?
  • 现在web前端的工资怎样?
  • 前端开发就业情况如何?

【前端工作内容】:

  • 前端工程师主要工作内容是什么?
  • 前端开发是做什么的?工作职责有哪些?

【前端学习路线】:

  • 0基础入门学前端,2023年前端学习路线总结,前端开发需要学什么?

【前端自学网站】:

  • 【建议收藏】91个前端开发必备学习国内外免费网站和个人博客
  • 13 个有趣且实用的 CSS 框架 / 组件
  • 有哪些好的前端社区?

【前端书籍推荐(32本)】:

  • 【建议收藏】2023前端书籍,前端开发入门与前端学习路线

【前端面试题】:

  • 【建议收藏】4500字前端面试题及答案汇总,前端八股文
  • 关于前端Vue框架的面试题,面试官可能会问到哪些?
  • 【一定要收藏】32000字的前端面试题总结!!!
  • (建议收藏)Vue面试热点问题

【前端简历模板】:

  • web前端简历个人技能该怎么写?
  • 前端简历中项目描述怎么写能够更加的优雅?
  • 一个优秀的前端工程师简历应该是怎样的?
  • 前端简历怎么写?前端尽力模板,4个动作帮你搞定心仪Offer
  • 面试前端工程师简历应该怎么写才容易通过?
  • 自学 web 前端人怎么找工作?
  • 自学前端简历怎么写啊?

【HTML教程】:

  • HTML5入门教程(含新特性),从入门到精通
  • HTML图文教程(表单域/文本框与密码框/单选按钮与复选框)
  • HTML图文教程(选按钮与复选框/input标签/提交按钮与重置按钮)
  • HTML图文教程(通按钮/文件域/label/下拉表单)
  • HTML零基础入门教程, 零基础学习HTML网页制作(HTML基本结构)
  • HTML零基础入门教程, 零基础学习HTML网页制作(HTML 标签)

【JavaScript教程】:

  • JavaScript 是什么?
  • JavaScript基础入门教程(引入方式/注释/变量/常量/数据类型/类型转换)
  • JavaScript基础入门教程(引入方式/注释/变量/数据类型/类型转换)
  • JavaScript基础入门教程(for循环/数组)
  • JavaScript基础入门教程(函数/返回值/作用域)
  • JavaScript基础入门教程(对象/内置对象)
  • JavaScript进阶教程(作用域/函数/解构赋值)
  • JavaScript进阶教程(构造函数/内置函数/继承/封装)
  • JavaScript进阶教程(深浅拷贝/异常/this/防抖节流)
  • JavaScript函数(函数创建和使用、参数传递)
  • JavaScript函数(函数返回值)
  • JavaScript数函(作用域和局部变量)
  • JavaScript函数(模态框插件的封装)

【VUE教程】:

  • Vue基础入门教程(vue-cli+vue指令)
  • Vue基础入门教程(vue指令大全)
  • Vue基础入门教程(vue组件化开发)
  • 最全的Vuex学习教程(Vuex基础、模块化、案例)

【Koa2教程】:

  • Koa2框架是什么?Koa框架教程快速入门Koa中间件
  • Koa2框架路由应用,Koa2前景、Koa2中间件
  • Koa2异常处理
如果对你有帮助的话,点个赞收个藏,给作者一个鼓励。也方便你下次能够快速查找。

最后,希望喜欢学习的你们,坚持下去,做一个有知识的前端人,加油~

今天先分享到这里,写了好久的回答,看完了点个赞呀!!!

发布于 2023-07-07 10:25

例如:父子组件传参,组件的合理拆分,路由的配置,权限管理,如果是商城的话,还有购物车的价格计算,优惠券的计算,以及商城首屏的页面加载优化等等。

编辑于 2021-01-16 10:07

作为一个在IT行业数十年经历的人,也是从程序员的一线岗位做到了技术管理,看过数万份简历,主持过公司二面,所以站在前辈的角度就你提的问题做下分析。

首先你提出这个问题,说明已经是过了初面,进行到了技术性面试的步骤了。要知道面试官提出这么一个问题不是单纯的从一个程序员的角度出发,而是站在管理层的角度通过简历里的项目经验去考察你。会根据你说的内容,看你是否真的做过这个项目,考察你的实际专业知识和逻辑能力是否清晰。

所以当面试官给你提出:“开发Vue项目都遇到过哪些问题怎么解决的?”的时候不能单纯的从技术层面出发回答,而是可以采用STAR原则去讲解,它是结构化面试当中非常重要的一个理论,既可以帮助你有效地梳理出答案,也可以回答出面试官想知道同时你也展示的地方。

STAR原则是四个英文单词的首字母组合,分别是:

1、Situation(情景)

描述所从事岗位期间曾经做过的某件重要的且可以当作面试官考评标准的事件的所发生的背景状况。

简单来说就是你的项目是在什么背景下制作的,要在什么情况下应用。

比如:为了用户可以更好的体验xxx,公司研发了xxx,该项目主要针对xx用户,由xxx和xxx系统或架构组成,由xx语言编写等等。

2、Task(目标)

即是要考察应聘者在其背景环境中所执行的任务与角色,从而考察该应聘者是否做过其描述的 职位及其是否具备该岗位的相应能力。

也就是介绍你在该项目中主要负责什么,目标是什么。

比如:我主要负责xxx,它是用来xxx的,并且我负责报告的设计以及最终评审通过等。

3、Action(行动)

考察你在其所描述的任务当中所担任的角色是如何操作与执行任务的。

也就是你在这个目标达成的过程中做了什么。

比如:使用xx管理用例的编写,通过xx管理代码和版本,使用xx工具做了什么,我对该模块的工作使用了xx技术等等。

4、Result(结果)

即该项任务在行动后所达到的效果,通常就是你的求职材料上写的都是一些结果,描述自己做过什么,成绩怎样,比较简单和宽泛。

其实就是你通过行动得出了什么结果。

比如:编写了xx个用例,发现了xx个bug,编写了xx行代码,利用xx工具做的测试结果,开发成果,评审结果等。

你可以事先选择简历中项目经验部分选出一个最熟悉的项目展开去回答。根据下面图形中对应的问题列出每个项目的关键点,在面试前针对这些点预先想好如何回答。

接着,就是针对问题中具体的“开发Vue项目”。其实每个项目都会有不同的问题,你参与的项目也会有遇到独有的问题,需要你遇到、思考、解决,这样才能对你自己有所提升。

但是如果实在是因为经验有限,在这里我也只能以我个人的经历来给出一些案例:

1. SSR 与 SPA 相比有什么优势?

1)更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。

2)更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。

2. 什么场景适合使用 SSR?

使用服务器端渲染 (SSR) 时还需要有一些权衡之处:

在对你的应用程序使用服务器端渲染 (SSR) 之前,你应该问的第一个问题是,是否真的需要它。

这主要取决于内容到达时间 (time-to-content) 对应用程序的重要程度。

例如,如果你正在构建一个内部仪表盘,初始加载时的额外几百毫秒并不重要,这种情况下去使用服务器端渲染 (SSR) 将是一个小题大作之举。

然而,内容到达时间 (time-to-content) 要求是绝对关键的指标,在这种情况下,服务器端渲染 (SSR) 可以帮助你实现最佳的初始加载性能。

3. 什么情况下我应该使用 Vuex?

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果你的应用够简单,最好不要使用 Vuex。一个简单的 store 模式就足够你所需了。

但是,如果需要构建一个中大型单页应用,你很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

引用 Redux 的作者 Dan Abramov 的话说就是:Flux 架构就像眼镜:你自会知道什么时候需要它。

4. Vue 插件 Vuex 是如何进行状态管理的

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

1) Vuex 的状态存储是响应式的。

当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

2) 你不能直接改变 store 中的状态。

改变 store 中的状态的唯一途径就是显式地提交 mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

以上就是作为老前辈的我能给出的答案。

面试只是一时的,技术好不好,工作中就能见真章,所以最好的途径还是多学习,多实操,多做才会有遇到各种问题。这里也推荐几个相关的学习网站:

Vue基础知识学习网站【中文】 cn.vuejs.org/v2/guide/

Vue路由知识学习网站【中文】 router.vuejs.org/zh/gui

Vuex更为复杂的Vue知识学习网站【中文】 vuex.vuejs.org/zh/

Vue脚手架搭建和使用学习网站【中文】 cli.vuejs.org/zh/guide/


过去的实例成功与否不是最重要的,重要的是你从实践中是否总结出经验和教训,并在此基础上继续学习和探索。

最后希望这些能帮助到你,也祝愿你能找到一份合心意的工作!

发布于 2021-02-05 16:02

最烦的就是 问这个的问题的了,然后你说出的问题, 如果在他看来根本不算什么问题的话,就会很尴尬,个人觉得 ,不正面回答比较好 。

技术上可以实现的完全没有问题,技术上实现不了的 经过商讨制定其他的实现方案。

。。。

发布于 2021-01-21 15:02

前端开发工程师是互联网时代软件产品研发中不可缺少的一种专业研发角色。VUE前端开发工程师使用VUE框架专业技能和工具将产品UI设计稿实现成网站产品,涵盖用户PC端、移动端网页,处理视觉和交互问题。 VUE前端开发工程师的工作内容和任职要求有以下几点:

一、工作内容

1、负责证券APP端和中后台系统相关前端设计、开发、维护工作,按时高质量完成任务。

2、按照项目计划进行需求分析、编写开发文档,完成代码编写及自测工作。

3、解决开发中遇到的技术问题,持续分析和优化现有系统,提升系统性能和用户体验。

4、关注业界最新技术发展,并根据业务需求运用到实际项目。

二、任职要求

1、全日制本科及以上学历,计算机相关专业,2年以上工作经验,具有良好的编程习惯。

2、扎实的前端基础,包括JS/CSS3/HTML5/ES5/6等。熟练使用前端开发调试工具,快速定位解决问题。

3、熟练掌握主流前端框架,包括Vue、React,了解业界最佳实践。

4、熟悉前端编译和构建工具,对前端工程化有一定的理解。

5、熟悉HTTP/HTTPS/Websocket等协议,对NodeJs,Typescript有一定的了解。

6、热爱技术,具有良好的责任心和主人翁精神,具有良好的沟通能力和团队合作精神。

7、有丰富的移动端开发经验者优先;有丰富的echarts、d3、g6等图表开发经验者优先;有证券、银行、基金等金融行业平台研发经验优先。

如果你要找金融公司职位或者招聘金融人才,扫描下方二维码或者直接站内联系我们。 51金融圈网站有百万金融人才简历和上万家金融公司合作,更有专业的猎头服务和金融薪酬报告,心动不如行动,还不赶快联系我们!

编辑于 2022-02-23 09:55
( 为什么?)