前端知识总结之VUE基础

1.Vue3 的一些新特性。
Proxy 的拦截器里有个 receiver 参数,在本文中为了简化没有体现出来,它是用来做什么的?国内的网站比较少能找到这个资料:

new Proxy(raw, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  }
})

可以看 StackOverflow 上的问答:what-is-a-receiver-in-javascript
2.vue中 key 值的作用
key 的特殊属性主要用在 Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用key,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。
有相同父元素的子元素必须有独特的key。重复的key会造成渲染错误。
最常见的用例是结合 v-for:
1
2
3

    • ...
    • 它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景时它可能会很有用: 完整地触发组件的生命周期钩子 触发过渡 1 2 3 {{ text }} 当 text 发生改变时, 会随时被更新,因此会触发过渡。 3.vue中子组件调用父组件的方法 子组件调用父组件的方法可以使用this.$emit() 4.vue等单页面应用及其优缺点 优点: 1、具有桌面应用的即时性、网站的可移植性和可访问性。 2、用户体验好、快,内容的改变不需要重新加载整个页面。 3、基于上面一点,SPA相对对服务器压力小。 4、良好的前后端分离。SPA和RESTful架构一起使用,后端不再负责模板渲染、输出页面工作,web前端和各种移动终端地位对等,后端API通用化。 5、同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端;

      缺点:
      1、不利于SEO。(如果你看中SEO,那就不应该在页面上使用JavaScript,你应该使用网站而不是Web应用)
      2、初次加载耗时相对增多。
      3、导航不可用,如果一定要导航需要自行实现前进、后退。
      5. v-show和v-if指令的共同点和不同点?
      v-show指令是通过修改元素的displayCSS属性让其显示或者隐藏。v-show 则是不管值为 true 还是 false ,html 元素都会存在,只是 CSS 中的 display 显示或隐藏
      v-if指令是直接销毁和重建DOM达到让元素显示和隐藏的效果(注意:v-if 可以实现组件的重新渲染)使用了 v-if 的时候,如果值为 false ,那么页面将不会有这个 html 标签生成。
      6. 如何让CSS只在当前组件中起作用?
      将当前组件的

      1. 的作用是什么?
        包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。

      大白话: 比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用进行缓存,
      这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染
      8. Vue中引入组件的步骤?

      1.采用ES6的import … from …语法


      CommonJS的require()方法引入组件

      2.对组件进行注册,代码如下
      注册

      Vue.component(‘my-component’,
      { template: ‘

      A custom component!
      ’})
      3.使用组件
      9. 指令v-el的作用是什么?
      提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标.可以是 CSS 选择器,也可以是一个 HTMLElement 实例,
      10. 在Vue中使用插件的步骤

      1. 采用ES6的import … from …语法
      2. 使用全局方法Vue.use( plugin )使用插件,可以传入一个选项对象

      Vue.use(MyPlugin, { someOption: true })

      1. 请列举出3个Vue中常用的生命周期钩子函数?
      2. created: 实例已经创建完成之后调用,在这一步,实例已经完成数据观测, 属性和方法的运算, watch/event事件回调. 然而, 挂载阶段还没有开始, $el属性目前还不可见
      3. mounted: el被新创建的 vm. e l 替 换 , 并 挂 载 到 实 例 上 去 之 后 调 用 该 钩 子 。 如 果 r o o t 实 例 挂 载 了 一 个 文 档 内 元 素 , 当 m o u n t e d 被 调 用 时 v m . el 替换,并挂载到实例上去之后调用该钩子。如果 root实例挂载了一个文档内元素,当 mounted 被调用时 vm. elrootmountedvm.el 也在文档内。
      4. activated::keep-alive组件激活时调用
      5. 请简述下Vuex的原理和使用方法

      数据单向流动
      一个应用可以看作是由上面三部分组成: View, Actions,State,数据的流动也是从View => Actions => State =>View 以此达到数据的单向流动.

      但是项目较大的, 组件嵌套过多的时候, 多组件共享同一个State会在数据传递时出现很多问题.Vuex就是为了解决这些问题而产生的.

      Vuex可以被看作项目中所有组件的数据中心,我们将所有组件中共享的State抽离出来,任何组件都可以访问和操作我们的数据中心

      Vuex的组成:一个实例化的Vuex.Store由state, mutations和actions三个属性组成:
      • state中保存着共有数据
      • 改变state中的数据有且只有通过mutations中的方法,且mutations中的方法必须是同步的
      • 如果要写异步的方法,需要些在actions中, 并通过commit到mutations中进行state中数据的更改.

      1. vue watch的高级用法–监听对象的属性变化
        1.监听对象需要深度监听 ,如下代码可以监听整个msg对象的变化
        1
        2
        3
        4
        5
        6
        7
        8 watch: {
          msg: {
            handler(newValue, oldValue) {
              console.log(newValue)
            },
            deep: true
          }
        }
        2.监听对象里面某个属性的变化,通过computed做中间层实现
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11 computed: {
          channel() {
            return this.msg.channel
          }
        },
        watch:{
        channel(newValue, oldValue) {
            console.log(‘new: %s, old: %s’, newval, oldVal)
            //这里面可以执行一旦监听的值发生变化你想做的操作
          }
        }
          
        14、active-class是哪个组件的属性?嵌套路由怎么定义?
        vue-router模块的router-link组件。

      15.怎么定义vue-router的动态路由?怎么获取传过来的动态参数?
      答:在router目录下的index.js文件中,对path属性加上/:id。 使用router对象的params.id 例如 : this.$route.params.id;
      16. Vue路由跳转方式有哪几种?
      1.router-link
      不带参数

      //name,path都行, 建议用name
      // 注意:router-link中链接如果是’/‘开始就是从根路由开始,如果开始不带’/’,则从当前路由开始。
      带参数

      // params传参数 (类似post)
      // 路由配置 path: “/home/:id” 或者 path: “/home:id”
      // 不配置path ,第一次可请求,刷新页面id会消失
      // 配置path,刷新页面id会保留
      // html 取参 r o u t e . p a r a m s . i d / / s c r i p t 取 参 t h i s . route.params.id // script 取参 this. route.params.id//scriptthis.route.params.id

      // query传参数 (类似get,url后面会显示参数)
      // 路由可不配置
      // html 取参 r o u t e . q u e r y . i d / / s c r i p t 取 参 t h i s . route.query.id // script 取参 this. route.query.id//scriptthis.route.query.id
      2.this. r o u t e r . p u s h ( ) ( 函 数 里 面 调 用 ) 不 带 参 数 t h i s . router.push() (函数里面调用) 不带参数 this. router.push()()this.router.push(’/home’)
      this. r o u t e r . p u s h ( n a m e : ′ h o m e ′ ) t h i s . router.push({name:'home'}) this. router.push(name:home)this.router.push({path:’/home’})
      query传参
      this. r o u t e r . p u s h ( n a m e : ′ h o m e ′ , q u e r y : i d : ′ 1 ′ ) t h i s . router.push({name:'home',query: {id:'1'}}) this. router.push(name:home,query:id:1)this.router.push({path:’/home’,query: {id:‘1’}})
      // html 取参 r o u t e . q u e r y . i d / / s c r i p t 取 参 t h i s . route.query.id // script 取参 this. route.query.id//scriptthis.route.query.id
      2.1 params传参
      this.$router.push({name:‘home’,params: {id:‘1’}}) // 只能用 name
      // 路由配置 path: “/home/:id” 或者 path: “/home:id” ,
      // 不配置path ,第一次可请求,刷新页面id会消失
      // 配置path,刷新页面id会保留
      // html 取参 r o u t e . p a r a m s . i d / / s c r i p t 取 参 t h i s . route.params.id // script 取参 this. route.params.id//scriptthis.route.params.id
      2.2 query和params区别
      query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用params刷新页面id还在
      params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失
      3.this. r o u t e r . r e p l a c e ( ) ( 用 法 同 上 , p u s h ) 4. t h i s . router.replace() (用法同上,push) 4. this. router.replace()(,push)4.this.router.go(n) ()

      17.scss是什么?在vue.cli中的安装使用步骤是?有哪几大特性?
      答:css的预编译,把css当成函数编写,定义变量,嵌套。
      使用步骤:
      第一步:用npm 下三个loader(sass-loader、css-loader、node-sass)
      第二步:在build目录找到webpack.base.config.js,在那个extends属性中加一个拓展.scss
      第三步:还是在同一个文件,配置一个module属性
      第四步:然后在组件的style标签加上lang属性 ,例如:lang=”scss”
      有哪几大特性:
      1、可以用变量,例如( 变 量 名 称 = 值 ) ; 2 、 可 以 用 混 合 器 , 例 如 : 定 义 了 字 体 的 混 合 器 @ m i x i n f o n t − d p r ( 变量名称=值); 2、可以用混合器,例如: 定义了字体的混合器 @mixin font-dpr( =2:@mixinfontdpr(font-size){
      f o n t : font: font:font-size/2;
      font-size: $font;
      [data-dpr=“2”] & { font-size: $font+2px;}
      [data-dpr=“3”] & { font-size: $font+4px;}
      }
      使用方法如下
      .div{
      @include font-dpr(24px);
      }
      3、可以嵌套

      18、mint-ui是什么?怎么使用?说出至少三个组件使用方法?
      答:基于vue的前端组件库。npm安装,然后import样式和js,vue.use(mintUi)全局引入。在单个组件局部引入:import {Toast} from ‘mint-ui’。组件一:Toast(‘登录成功’);组件二:mint-header;组件三:mint-swiper

      19、v-model是什么?怎么使用? vue中标签怎么绑定事件?
      答:可以实现双向绑定,vue 实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调

      指令(v-class、v-for、v-if、v-show、v-on)。vue的model层的data属性。绑定事件:

      20、axios是什么?怎么使用?描述使用它实现登录功能的流程?
      答:请求后台资源的模块。npm install axios -S装好,然后发送的是跨域,需在配置文件中config/index.js进行设置。后台如果是Tp5则定义一个资源路由。js中使用import进来,然后.get或.post。返回在.then函数中如果成功,失败则是在.catch函数中
      21、axios+tp5进阶中,调用axios.post(‘api/user’)是进行的什么操作?axios.put(‘api/user/8′)呢?
      答:跨域,添加用户操作,更新操作。
      22、什么是RESTful API?怎么使用?
      答:是一个api的标准,无状态请求。请求的路由地址是固定的,如果是tp5则先路由配置中把资源路由配置好。标准有:.post .put .delete

      23、vuex是什么?怎么使用?哪种功能场景使用它?
      答:vue框架中状态管理。在main.js引入store,注入。新建了一个目录store,…… export 。场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车

      24、mvvm框架是什么?它和其它框架(jquery)的区别是什么?哪些场景适合?
      答:一个model+view+viewModel框架,数据模型model,viewModel连接两个
      区别:vue数据驱动,通过数据来显示视图层而不是节点操作。
      场景:数据操作比较多的场景,更加便捷

      25、自定义指令(v-check、v-focus)的方法有哪些?它有哪些钩子函数?还有哪些钩子函数参数?
      答:全局定义指令:在vue对象的directive方法里面有两个参数,一个是指令名称,另外一个是函数。组件内定义指令:directives
      钩子函数:bind(绑定事件触发)、inserted(节点插入的时候触发)、update(组件内相关更新)
      钩子函数参数:el、binding

      26、说出至少4种vue当中的指令和它的用法?
      答:v-if:判断是否隐藏;v-for:数据循环出来;v-bind:class:绑定一个属性;v-model:实现双向绑定

      27、vue-router是什么?它有哪些组件?
      答:vue用来写路由一个插件。router-link、router-view

      28、导航钩子有哪些?它们有哪些参数?
      答:导航钩子有:a/全局钩子和组件内独享的钩子。b/beforeRouteEnter、afterEnter、beforeRouterUpdate、beforeRouteLeave
      参数:有to(去的那个路由)、from(离开的路由)、next(一定要用这个函数才能去到下一个路由,如果不用就拦截)最常用就这几种

      29、Vue的双向数据绑定原理是什么?
      答:vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。]
      详细请参考: http://www.cnblogs.com/libin-1/p/6893712.html
      具体步骤:
      第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
      这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
      第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
      第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
      1、在自身实例化时往属性订阅器(dep)里面添加自己
      2、自身必须有一个update()方法
      3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
      第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
      30、请详细说下你对vue生命周期的理解?
      答:总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
      创建前/后: 在beforeCreated阶段,vue实例的挂载元素 e l 和 数 据 对 象 d a t a 都 为 u n d e f i n e d , 还 未 初 始 化 。 在 c r e a t e d 阶 段 , v u e 实 例 的 数 据 对 象 d a t a 有 了 , el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了, eldataundefinedcreatedvuedatael还没有。
      载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
      更新前/后:当data变化时,会触发beforeUpdate和updated方法。
      销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
      31、请说下封装 vue 组件的过程?
      答:首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。
      然后,使用Vue.extend方法创建一个组件,然后使用Vue.component方法注册组件。子组件需要数据,可以在props中接受定义。而子组件修改好数据后,想把数据传递给父组件。可以采用emit方法。
      32、你是怎么认识vuex的?
      答:vuex可以理解为一种开发模式或框架。比如PHP有thinkphp,java有spring等。
      通过状态(数据源)集中管理驱动组件的变化(好比spring的IOC容器对bean进行集中管理)。
      应用级的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。

      33、vue-loader是什么?使用它的用途有哪些?
      答:解析.vue文件的一个加载器,跟template/js/style转换成js模块。
      用途:js可以写es6、style样式可以scss或less、template可以加jade等

      34、请说出vue.cli项目中src目录每个文件夹和文件的用法?
      答:assets文件夹是放静态资源;components是放组件;router是定义路由相关的配置;view视图;app.vue是一个应用主组件;main.js是入口文件

      35、聊聊你对Vue.js的template编译的理解?
      答:简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点)
      详情步骤:
      首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创建编译器的。另外compile还负责合并option。
      然后,AST会经过generate(将AST语法树转化成render funtion字符串的过程)得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)
      36、vuejs与angularjs以及react的区别?

      37、了解过哪些vue源码结构
      38、vue如何实现父子组件通信,以及非父子组件通信?
      父组件向子组件通信
      子组件通过 props 属性,绑定父组件数据,实现双方通信
      子组件向父组件通信
      将父组件的事件在子组件中通过 $emit 触发
      非父子组件、兄弟组件之间的数据传递
      /新建一个Vue实例作为中央事件总嫌/
      let event = new Vue();

      /监听事件/
      event.$on(‘eventName’, (val) => {
      //…do something
      });

      /触发事件/
      event.$emit(‘eventName’, ‘this is a message.’)
      Vuex 数据管理

      39、vue响应式原理?Proxy?
      Vue3 不同于 Vue2 也体现在源码结构上,Vue3 把耦合性比较低的包分散在 packages 目录下单独发布成 npm 包。 这也是目前很流行的一种大型项目管理方式 Monorepo。
      其中负责响应式部分的仓库就是 @vue/rectivity,它不涉及 Vue 的其他的任何部分,是非常非常 「正交」 的一种实现方式。
      甚至可以轻松的集成进 React。
      这也使得本篇的分析可以更加聚焦的分析这一个仓库,排除其他无关部分。
      区别
      Proxy 和 Object.defineProperty 的使用方法看似很相似,其实 Proxy 是在 「更高维度」 上去拦截属性的修改的,怎么理解呢?
      Vue2 中,对于给定的 data,如 { count: 1 },是需要根据具体的 key 也就是 count,去对「修改 data.count 」 和 「读取 data.count」进行拦截,也就是
      Object.defineProperty(data, ‘count’, {
      get() {},
      set() {},
      })
      复制代码
      必须预先知道要拦截的 key 是什么,这也就是为什么 Vue2 里对于对象上的新增属性无能为力。
      而 Vue3 所使用的 Proxy,则是这样拦截的:
      new Proxy(data, {
      get(key) { },
      set(key, value) { },
      })
      复制代码
      可以看到,根本不需要关心具体的 key,它去拦截的是 「修改 data 上的任意 key」 和 「读取 data 上的任意 key」。
      所以,不管是已有的 key 还是新增的 key,都逃不过它的魔爪。
      但是 Proxy 更加强大的地方还在于 Proxy 除了 get 和 set,还可以拦截更多的操作符。
      简单的例子
      先写一个 Vue3 响应式的最小案例,本文的相关案例都只会用 reactive 和 effect 这两个 api。如果你了解过 React 中的 useEffect,相信你会对这个概念秒懂,Vue3 的 effect 不过就是去掉了手动声明依赖的「进化版」的 useEffect。
      React 中手动声明 [data.count] 这个依赖的步骤被 Vue3 内部直接做掉了,在 effect 函数内部读取到 data.count 的时候,它就已经被收集作为依赖了。
      Vue3:
      // 响应式数据
      const data = reactive({
      count: 1
      })

      // 观测变化
      effect(() => console.log(‘count changed’, data.count))

      // 触发 console.log(‘count changed’, data.count) 重新执行
      data.count = 2
      复制代码
      React:
      // 数据
      const [data, setData] = useState({
      count: 1
      })

      // 观测变化 需要手动声明依赖
      useEffect(() => {
      console.log(‘count changed’, data.count)
      }, [data.count])

      // 触发 console.log(‘count changed’, data.count) 重新执行
      setData({
      count: 2
      })
      复制代码
      其实看到这个案例,聪明的你也可以把 effect 中的回调函数联想到视图的重新渲染、 watch 的回调函数等等…… 它们是同样基于这套响应式机制的。
      而本文的核心目的,就是探究这个基于 Proxy 的 reactive api,到底能强大到什么程度,能监听到用户对于什么程度的修改。
      先讲讲原理
      先最小化的讲解一下响应式的原理,其实就是在 Proxy 第二个参数 handler 也就是陷阱操作符中,拦截各种取值、赋值操作,依托 track 和 trigger 两个函数进行依赖收集和派发更新。
      track 用来在读取时收集依赖。
      trigger 用来在更新时触发依赖。
      track
      function track(target: object, type: TrackOpTypes, key: unknown) {
      const depsMap = targetMap.get(target);
      // 收集依赖时 通过 key 建立一个 set
      let dep = new Set()
      targetMap.set(ITERATE_KEY, dep)
      // 这个 effect 可以先理解为更新函数 存放在 dep 里
      dep.add(effect)
      }
      复制代码
      target 是原对象。
      type 是本次收集的类型,也就是收集依赖的时候用来标识是什么类型的操作,比如上文依赖中的类型就是 get,这个后续会详细讲解。
      key 是指本次访问的是数据中的哪个 key,比如上文例子中收集依赖的 key 就是 count
      首先全局会存在一个 targetMap,它用来建立 数据 -> 依赖 的映射,它是一个 WeakMap 数据结构。
      而 targetMap 通过数据 target,可以获取到 depsMap,它用来存放这个数据对应的所有响应式依赖。
      depsMap 的每一项则是一个 Set 数据结构,而这个 Set 就存放着对应 key 的更新函数。
      是不是有点绕?我们用一个具体的例子来举例吧。
      const target = { count: 1}
      const data = reactive(target)

      const effection = effect(() => {
      console.log(data.count)
      })
      复制代码
      对于这个例子的依赖关系,

      1. 全局的 targetMap 是:
        targetMap: {
        { count: 1 }: dep
        }
        复制代码
      2. dep 则是
        dep: {
        count: Set { effection }
        }
        这样一层层的下去,就可以通过 target 找到 count 对应的更新函数 effection 了。
        trigger
        这里是最小化的实现,仅仅为了便于理解原理,实际上要复杂很多,
        其实 type 的作用很关键,先记住,后面会详细讲。
        export function trigger(
        target: object,
        type: TriggerOpTypes,
        key?: unknown,
        ) {
        // 简化来说 就是通过 key 找到所有更新函数 依次执行
        const dep = targetMap.get(target)
        dep.get(key).forEach(effect => effect())
        }
        复制代码
        新增属性
        这个上文已经讲了,由于 Proxy 完全不关心具体的 key,所以没问题。
        // 响应式数据
        const data = reactive({
        count: 1
        })

      // 观测变化
      effect(() => console.log(‘newCount changed’, data.newCount))

      // ✅ 触发响应
      data.newCount = 2
      复制代码
      数组新增索引:
      // 响应式数据
      const data = reactive([])

      // 观测变化
      effect(() => console.log(‘data[1] changed’, data[1]))

      // ✅ 触发响应
      data[1] = 5
      复制代码
      数组调用原生方法:
      const data = reactive([])
      effect(() => console.log(‘c’, data[1]))

      // 没反应
      data.push(1)

      // ✅ 触发响应 因为修改了下标为 1 的值
      data.push(2)
      复制代码
      其实这一个案例就比较有意思了,我们仅仅是在调用 push,但是等到数组的第二项被 push的时候,我们之前关注 data[1] 为依赖的回调函数也执行了,这是什么原理呢?写个简单的 Proxy 就知道了。
      const raw = []
      const arr = new Proxy(raw, {
      get(target, key) {
      console.log(‘get’, key)
      return Reflect.get(target, key)
      },
      set(target, key, value) {
      console.log(‘set’, key)
      return Reflect.set(target, key, value)
      }
      })

      arr.push(1)
      在这个案例中,我们只是打印出了对于 raw 这个数组上的所有 get、set 操作,并且调用 Reflect这个 api 原样处理取值和赋值操作后返回。看看 arr.push(1) 后控制台打印出了什么?
      get push
      get length
      set 0
      set length
      原来一个小小的 push,会触发两对 get 和 set,我们来想象一下流程:

      1. 读取 push 方法
      2. 读取 arr 原有的 length 属性
      3. 对于数组第 0 项赋值
      4. 对于 length 属性赋值
        这里的重点是第三步,对于第 index 项的赋值,那么下次再 push,可以想象也就是对于第 1 项触发 set 操作。
        而我们在例子中读取 data[1],是一定会把对于 1 这个下标的依赖收集起来的,这也就清楚的解释了为什么 push 的时候也能精准的触发响应式依赖的执行。
        对了,记住这个对于 length 的 set 操作,后面也会用到,很重要。
        遍历后新增
        // 响应式数据
        const data = reactive([])

      // 观测变化
      effect(() => console.log(‘data map +1’, data.map(item => item + 1))

      // ✅ 触发响应 打印出 [2]
      data.push(1)
      复制代码
      这个拦截很神奇,但是也很合理,转化成现实里的一个例子来看,
      假设我们要根据学生 id 的集合 ids, 去请求学生详细信息,那么仅仅是需要这样写即可:
      const state = reactive({})
      const ids = reactive([1])

      effect(async () => {
      state.students = await axios.get(‘students/batch’, ids.map(id => ({ id })))
      })

      // ✅ 触发响应
      ids.push(2)
      复制代码
      这样,每次调用各种 api 改变 ids 数组,都会重新发送请求获取最新的学生列表。
      如果我在监听函数中调用了 map、forEach 等 api,
      说明我关心这个数组的长度变化,那么 push 的时候触发响应是完全正确的。
      但是它是如何实现的呢?感觉似乎很复杂啊。
      因为 effect 第一次执行的时候, data 还是个空数组,怎么会 push 的时候能触发更新呢?
      还是用刚刚的小测试,看看 map 的时候会发生什么事情。
      const raw = [1, 2]
      const arr = new Proxy(raw, {
      get(target, key) {
      console.log(‘get’, key)
      return Reflect.get(target, key)
      },
      set(target, key, value) {
      console.log(‘set’, key)
      return Reflect.set(target, key, value)
      }
      })

      arr.map(v => v + 1)
      get map
      get length
      get constructor
      get 0
      get 1
      和 push 的部分有什么相同的?找一下线索,我们发现 map 的时候会触发 get length,而在触发更新的时候, Vue3 内部会对 「新增 key」 的操作进行特殊处理,这里是新增了 0 这个下标的值,会走到 trigger 中这样的一段逻辑里去:
      源码地址
      // 简化版
      if (isAddOrDelete) {
      add(depsMap.get(‘length’))
      }
      复制代码
      把之前读取 length 时收集到的依赖拿到,然后触发函数。
      这就一目了然了,我们在 effect 里 map 操作读取了 length,收集了 length 的依赖。
      在新增 key 的时候, 触发 length 收集到的依赖,触发回调函数即可。
      对了,对于 for of 操作,也一样可行:
      // 响应式数据
      const data = reactive([])

      // 观测变化
      effect(() => {
      for (const val of data) {
      console.log(‘val’, val)
      }
      })

      // ✅ 触发响应 打印出 val 1
      data.push(1)
      复制代码
      可以按我们刚刚的小试验自己跑一下拦截, for of 也会触发 length 的读取。
      length 真是个好同志…… 帮了大忙了。
      遍历后删除或者清空
      注意上面的源码里的判断条件是 isAddOrDelete,所以删除的时候也是同理,借助了 length 上收集到的依赖。
      // 简化版
      if (isAddOrDelete) {
      add(depsMap.get(‘length’))
      }
      复制代码
      const arr = reactive([1])

      effect(() => {
      console.log(‘arr’, arr.map(v => v))
      })

      // ✅ 触发响应
      arr.length = 0

      // ✅ 触发响应
      arr.splice(0, 1)
      复制代码
      真的是什么操作都能响应,爱了爱了。
      获取 keys
      const obj = reactive({ a: 1 })

      effect(() => {
      console.log(‘keys’, Reflect.ownKeys(obj))
      })

      effect(() => {
      console.log(‘keys’, Object.keys(obj))
      })

      effect(() => {
      for (let key in obj) {
      console.log(key)
      }
      })

      // ✅ 触发所有响应
      obj.b = 2
      这几种获取 key 的方式都能成功的拦截,其实这是因为 Vue 内部拦截了 ownKeys 操作符。
      const ITERATE_KEY = Symbol( ‘iterate’ );

      function ownKeys(target) {
      track(target, “iterate”, ITERATE_KEY);
      return Reflect.ownKeys(target);
      }
      复制代码
      ITERATE_KEY 就作为一个特殊的标识符,表示这是读取 key 的时候收集到的依赖。它会被作为依赖收集的 key。
      那么在触发更新时,其实就对应这段源码:
      if (isAddOrDelete) {
      add(depsMap.get(isArray(target) ? ‘length’ : ITERATE_KEY));
      }
      复制代码
      其实就是我们聊数组的时候,代码简化掉的那部分。判断非数组,则触发 ITERATE_KEY 对应的依赖。
      小彩蛋:
      Reflect.ownKeys、 Object.keys 和 for in 其实行为是不同的,
      Reflect.ownKeys 可以收集到 Symbol 类型的 key,不可枚举的 key。
      举例来说:
      var a = {
      [Symbol(2)]: 2,
      }

      Object.defineProperty(a, ‘b’, {
      enumerable: false,
      })

      Reflect.ownKeys(a) // [Symbol(2), ‘b’]
      Object.keys(a) // []
      复制代码
      回看刚刚提到的 ownKeys 拦截,
      function ownKeys(target) {
      track(target, “iterate”, ITERATE_KEY);
      // 这里直接返回 Reflect.ownKeys(target)
      return Reflect.ownKeys(target);
      }
      复制代码
      内部直接之间返回了 Reflect.ownKeys(target),按理来说这个时候 Object.keys 的操作经过了这个拦截,也会按照 Reflect.ownKeys 的行为去返回值。
      然而最后返回的结果却还是 Object.keys 的结果,这是比较神奇的一点。
      删除对象属性
      有了上面 ownKeys 的基础,我们再来看看这个例子
      const obj = reactive({ a: 1, b: 2})

      effect(() => {
      console.log(Object.keys(obj))
      })

      // ✅ 触发响应
      delete obj[‘b’]
      复制代码
      这也是个神奇的操作,原理在于对于 deleteProperty 操作符的拦截:
      function deleteProperty(target: object, key: string | symbol): boolean {
      const result = Reflect.deleteProperty(target, key)
      trigger(target, TriggerOpTypes.DELETE, key)
      return result
      }
      复制代码
      这里又用到了 TriggerOpTypes.DELETE 的类型,根据上面的经验,一定对它有一些特殊的处理。
      其实还是 trigger 中的那段逻辑:
      const isAddOrDelete = type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE
      if (isAddOrDelete) {
      add(depsMap.get(isArray(target) ? ‘length’ : ITERATE_KEY))
      }
      复制代码
      这里的 target 不是数组,所以还是会去触发 ITERATE_KEY 收集的依赖,也就是上面例子中刚提到的对于 key 的读取收集到的依赖。
      判断属性是否存在
      const obj = reactive({})

      effect(() => {
      console.log(‘has’, Reflect.has(obj, ‘a’))
      })

      effect(() => {
      console.log(‘has’, ‘a’ in obj)
      })

      // ✅ 触发两次响应
      obj.a = 1
      复制代码
      这个就很简单了,就是利用了 has 操作符的拦截。
      function has(target, key) {
      const result = Reflect.has(target, key);
      track(target, “has”, key);
      return result;
      }
      复制代码
      性能

      1. 首先 Proxy 作为浏览器的新标准,性能上是一定会得到厂商的大力优化的,拭目以待。
      2. Vue3 对于响应式数据,不再像 Vue2 中那样递归对所有的子数据进行响应式定义了,而是再获取到深层数据的时候再去利用 reactive 进一步定义响应式,这对于大量数据的初始化场景来说收益会非常大。
        比如,对于
        const obj = reactive({
        foo: {
        bar: 1
        }
        })
        复制代码
        初始化定义 reactive 的时候,只会对 obj 浅层定义响应式,而真正读取到 obj.foo 的时候,才会对 foo 这一层对象定义响应式,简化源码如下:
        function get(target: object, key: string | symbol, receiver: object) {
        const res = Reflect.get(target, key, receiver)
        // 这段就是惰性定义
        return isObject(res)
        ? reactive(res)
        : res
        }
        复制代码
        推荐阅读
        其实 Vue3 对于 Map 和 Set 这两种数据类型也是完全支持响应式的,对于它们的原型方法也都做了完善的拦截,限于篇幅原因本文不再赘述。
        说实话 Vue3 的响应式部分代码逻辑分支还是有点过多,对于代码理解不是很友好,因为它还会涉及到 readonly 等只读化的操作,如果看完这篇文章你对于 Vue3 的响应式原理非常感兴趣的话,建议从简化版的库入手去读源码。
        这里我推荐 observer-util,我解读过这个库的源码,和 Vue3 的实现原理基本上是一模一样!但是简单了很多。麻雀虽小,五脏俱全。里面的注释也很齐全。
        当然,如果你的英文不是很熟练,也可以看我精心用 TypeScript + 中文注释基于 observer-util重写的这套代码: typescript-proxy-reactive
        对于这个库的解读,可以看我之前的两篇文章:
        带你彻底搞懂Vue3的Proxy响应式原理!TypeScript从零实现基于Proxy的响应式库。
        带你彻底搞懂Vue3的Proxy响应式原理!基于函数劫持实现Map和Set的响应式
        在第二篇文章里,你也可以对于 Map 和 Set 可以做什么拦截操作,获得源码级别的理解。
      3. 解释单向数据流和双向数据绑定
        单向数据流: 顾名思义,数据流是单向的。数据流动方向可以跟踪,流动单一,追查问题的时候可以更快捷。缺点就是写起来不太方便。要使UI发生变更就必须创建各种 action 来维护对应的 state
        双向数据绑定:数据之间是相通的,将数据变更的操作隐藏在框架内部。优点是在表单交互较多的场景下,会简化大量与业务无关的代码。缺点就是无法追踪局部状态的变化,增加了出错时 debug 的难度
      4. Vue 如何去除url中的 #
        vue-router 默认使用 hash 模式,所以在路由加载的时候,项目中的 url 会自带 #。如果不想使用 #, 可以使用 vue-router 的另一种模式 history
        new Router({
        mode: ‘history’,
        routes: [ ]
        })
        需要注意的是,当我们启用 history 模式的时候,由于我们的项目是一个单页面应用,所以在路由跳转的时候,就会出现访问不到静态资源而出现 404 的情况,这时候就需要服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面
      5. 对 MVC、MVVM的理解
        MVC

      特点:
      View 传送指令到 Controller
      Controller 完成业务逻辑后,要求 Model 改变状态
      Model 将新的数据发送到 View,用户得到反馈
      所有通信都是单向的
      MVVM

      特点:
      各部分之间的通信,都是双向的
      采用双向绑定:View 的变动,自动反映在 ViewModel,反之亦然。
      MVVM 是 Model-View-ViewModel 的缩写。
      Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
      View 代表UI 组件,它负责将数据模型转化成UI 展现出来。
      ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
      在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
      ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
      MVVM的优缺点?
      优点:
      分离视图(View)和模型(Model),降低代码耦合,提高视图或者逻辑的重用性: 比如视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定不同的"View"上,当View变化的时候Model不可以不变,当Model变化的时候View也可以不变。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑
      提高可测试性: ViewModel的存在可以帮助开发者更好地编写测试代码
      自动更新dom: 利用双向绑定,数据更新后视图自动更新,让开发者从繁琐的手动dom中解放
      缺点:
      Bug很难被调试: 因为使用双向绑定的模式,当你看到界面异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得一个位置的Bug被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。另外,数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的
      一个大的模块中model也会很大,虽然使用方便了也很容易保证了数据的一致性,当时长期持有,不释放内存就造成了花费更多的内存
      对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。

      1. vue生命周期的理解
        beforeCreate(创建前) 在数据观测和初始化事件还未开始
        created(创建后) 完成数据观测,属性和方法的运算,初始化事件, e l 属 性 还 没 有 显 示 出 来 b e f o r e M o u n t ( 载 入 前 ) 在 挂 载 开 始 之 前 被 调 用 , 相 关 的 r e n d e r 函 数 首 次 被 调 用 。 实 例 已 完 成 以 下 的 配 置 : 编 译 模 板 , 把 d a t a 里 面 的 数 据 和 模 板 生 成 h t m l 。 注 意 此 时 还 没 有 挂 载 h t m l 到 页 面 上 。 m o u n t e d ( 载 入 后 ) 在 e l 被 新 创 建 的 v m . el属性还没有显示出来 beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。 mounted(载入后) 在el 被新创建的 vm. elbeforeMountrenderdatahtmlhtmlmountedelvm.el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
        beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
        updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
        beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
      2. 什么是vue生命周期?
        Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
      3. vue生命周期的作用是什么?
        答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
      4. vue生命周期总共有几个阶段?
        答:它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。
      5. 第一次页面加载会触发哪几个钩子?
        答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。
      6. DOM 渲染在 哪个周期中就已经完成?
        答:DOM 渲染在 mounted 中就已经完成了。
      7. v-if 和 v-show 区别
        使用了 v-if 的时候,如果值为 false ,那么页面将不会有这个 html 标签生成。
        v-show 则是不管值为 true 还是 false ,html 元素都会存在,只是 CSS 中的 display 显示或隐藏
      8. r o u t e 和 route和 routerouter的区别
        $router 为 VueRouter 实例,想要导航到不同 URL,则使用 $router.push 方法
        $route 为当前 router 跳转对象里面可以获取 name 、 path 、 query 、 params 等
      9. NextTick 是做什么的
        $nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM
      10. Vue 组件 data 为什么必须是函数
        因为js本身的特性带来的,如果 data 是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有Vue实例的数据。如果将 data 作为一个函数返回一个对象,那么每一个实例的 data 属性都是独立的,不会相互影响了
      11. 计算属性computed 和事件 methods 有什么区别
        我们可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的
        不同点:
        computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值
        对于 method ,只要发生重新渲染,method 调用总会执行该函数
      12. 对比 jQuery ,Vue 有什么不同
        jQuery 专注视图层,通过操作 DOM 去实现页面的一些逻辑渲染; Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作
        Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发
      13. Vue 中怎么自定义指令
        全局注册
        // 注册一个全局自定义指令 v-focus
        Vue.directive(‘focus’, {
        // 当被绑定的元素插入到 DOM 中时……
        inserted: function (el) {
        // 聚焦元素
        el.focus()
        }
        })
        局部注册
        directives: {
        focus: {
        // 指令的定义
        inserted: function (el) {
        el.focus()
        }
        }
        }
      14. Vue 中怎么自定义过滤器
        可以用全局方法 Vue.filter() 注册一个自定义过滤器,它接收两个参数:过滤器 ID 和过滤器函数。过滤器函数以值为参数,返回转换后的值
        Vue.filter(‘reverse’, function (value) {
        return value.split(’’).reverse().join(’’)
        })


      过滤器也同样接受全局注册和局部注册
      50. 对 keep-alive 的了解
      keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染





      可以使用API提供的props,实现组件的动态缓存
      51. Vue 的核心是什么
      数据驱动 组件系统
      52. Vue实现数据双向绑定的原理:Object.defineProperty()
      vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
      vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
      js实现简单的双向绑定

      53.Vue组件间的参数传递 Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信 (1)props / $emit 适用 父子组件通信 这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。 (2)ref 与 children 适用 父子组件通信 ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例 children:访问父 / 子实例 (3)EventBus (on) 适用于 父子、隔代、兄弟组件通信 这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。 (4)listeners 适用于 隔代组件通信 :包含了父作用域中不被所识别且获取的特性绑定和除外。当一个组件没有声明任何时,这里会包含所有父作用域的绑定和除外,并且可以通过attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。 :包含了父作用域中的不含修饰器的事件监听器。它可以通过listeners" 传入内部组件 (5)provide / inject 适用于 隔代组件通信 祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。 (6)Vuex 适用于 父子、隔代、兄弟组件通信 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。 Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化。 54、Vue的路由实现:hash模式 、 history模式、abstract模式 hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取; 特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。 hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。 hash 模式的实现原理 早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search': https://www.abc.com#search hash 路由模式的实现主要是基于下面几个特性: URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送; hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换; 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值; 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。 history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。 history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。” history 模式的实现原理 HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示: window.history.pushState(null, null, path); window.history.replaceState(null, null, path); history 路由模式的实现主要基于存在下面几个特性: pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ; 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染); history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。 abstract模式 : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式. (后续补上) 55.vue路由的钩子函数 首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。 beforeEach主要有3个参数to,from,next: to:route即将进入的目标路由对象, from:route当前导航正要离开的路由 next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。 56、vuex是什么?怎么使用?哪种功能场景使用它? 只用来读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。 在main.js引入store,注入。新建了一个目录store,….. export 。 场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车

      state
      Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
      mutations
      mutations定义的方法动态修改Vuex 的 store 中的状态或数据。
      getters
      类似vue的计算属性,主要用来过滤一些数据。
      action
      actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。
      const store = new Vuex.Store({ //store实例
      state: {
      count: 0
      },
      mutations: {
      increment (state) {
      state.count++
      }
      },
      actions: {
      increment (context) {
      context.commit(‘increment’)
      }
      }
      })
      modules
      项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
      const moduleA = {
      state: { … },
      mutations: { … },
      actions: { … },
      getters: { … }
      }
      const moduleB = {
      state: { … },
      mutations: { … },
      actions: { … }
      }

      const store = new Vuex.Store({
      modules: {
      a: moduleA,
      b: moduleB
      })
      57、vue-cli如何新增自定义指令?
      1.创建局部指令
      var app = new Vue({
      el: ‘#app’,
      data: {
      },
      // 创建指令(可以多个)
      directives: {
      // 指令名称
      dir1: {
      inserted(el) {
      // 指令中第一个参数是当前使用指令的DOM
      console.log(el);
      console.log(arguments);
      // 对DOM进行操作
      el.style.width = ‘200px’;
      el.style.height = ‘200px’;
      el.style.background = ‘#000’;
      }
      }
      }
      })
      2.全局指令
      Vue.directive(‘dir2’, {
      inserted(el) {
      console.log(el);
      }
      })
      3.指令的使用

      58、vue如何自定义一个过滤器? html代码:
      {{msg| capitalize }}
      JS代码: var vm=new Vue({ el:"#app", data:{ msg:'' }, filters: { capitalize: function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } } }) 全局定义过滤器 Vue.filter('capitalize', function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) }) 过滤器接收表达式的值 (msg) 作为第一个参数。capitalize 过滤器将会收到 msg的值作为第一个参数。 59.对keep-alive 的了解? keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。 在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。 使用方法 参数解释 include - 字符串或正则表达式,只有名称匹配的组件会被缓存 exclude - 字符串或正则表达式,任何名称匹配的组件都不会被缓存 include 和 exclude 的属性允许组件有条件地缓存。二者都可以用“,”分隔字符串、正则表达式、数组。当使用正则或者是数组时,要记得使用v-bind 。 使用示例 60、Class 与 Style 如何动态绑定? Class 可以通过对象语法和数组语法进行动态绑定: 对象语法:

      data: {
      isActive: true,
      hasError: false
      }
      数组语法:

      data: {
      activeClass: ‘active’,
      errorClass: ‘text-danger’
      }
      Style 也可以通过对象语法和数组语法进行动态绑定:
      对象语法:

      data: {
      activeColor: ‘red’,
      fontSize: 30
      }
      数组语法:

      data: {
      styleColor: {
      color: ‘red’
      },
      styleSize:{
      fontSize:‘23px’
      }
      }
      61、理解 Vue 的单向数据流?
      所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
      额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 e m i t 派 发 一 个 自 定 义 事 件 , 父 组 件 接 收 到 后 , 由 父 组 件 修 改 。 有 两 种 常 见 的 试 图 改 变 一 个 p r o p 的 情 形 : 这 个 p r o p 用 来 传 递 一 个 初 始 值 ; 这 个 子 组 件 接 下 来 希 望 将 其 作 为 一 个 本 地 的 p r o p 数 据 来 使 用 。 在 这 种 情 况 下 , 最 好 定 义 一 个 本 地 的 d a t a 属 性 并 将 这 个 p r o p 用 作 其 初 始 值 : p r o p s : [ ′ i n i t i a l C o u n t e r ′ ] , d a t a : f u n c t i o n ( ) r e t u r n c o u n t e r : t h i s . i n i t i a l C o u n t e r 这 个 p r o p 以 一 种 原 始 的 值 传 入 且 需 要 进 行 转 换 。 在 这 种 情 况 下 , 最 好 使 用 这 个 p r o p 的 值 来 定 义 一 个 计 算 属 性 p r o p s : [ ′ s i z e ′ ] , c o m p u t e d : n o r m a l i z e d S i z e : f u n c t i o n ( ) r e t u r n t h i s . s i z e . t r i m ( ) . t o L o w e r C a s e ( ) 62 、 c o m p u t e d 和 w a t c h 的 区 别 和 运 用 的 场 景 ? c o m p u t e d : 是 计 算 属 性 , 依 赖 其 它 属 性 值 , 并 且 c o m p u t e d 的 值 有 缓 存 , 只 有 它 依 赖 的 属 性 值 发 生 改 变 , 下 一 次 获 取 c o m p u t e d 的 值 时 才 会 重 新 计 算 c o m p u t e d 的 值 ; w a t c h : 更 多 的 是 「 观 察 」 的 作 用 , 类 似 于 某 些 数 据 的 监 听 回 调 , 每 当 监 听 的 数 据 变 化 时 都 会 执 行 回 调 进 行 后 续 操 作 ; 运 用 场 景 : 当 我 们 需 要 进 行 数 值 计 算 , 并 且 依 赖 于 其 它 数 据 时 , 应 该 使 用 c o m p u t e d , 因 为 可 以 利 用 c o m p u t e d 的 缓 存 特 性 , 避 免 每 次 获 取 值 时 , 都 要 重 新 计 算 ; 当 我 们 需 要 在 数 据 变 化 时 执 行 异 步 或 开 销 较 大 的 操 作 时 , 应 该 使 用 w a t c h , 使 用 w a t c h 选 项 允 许 我 们 执 行 异 步 操 作 ( 访 问 一 个 A P I ) , 限 制 我 们 执 行 该 操 作 的 频 率 , 并 在 我 们 得 到 最 终 结 果 前 , 设 置 中 间 状 态 。 这 些 都 是 计 算 属 性 无 法 做 到 的 。 63 、 直 接 给 一 个 数 组 项 赋 值 , V u e 能 检 测 到 变 化 吗 ? 由 于 J a v a S c r i p t 的 限 制 , V u e 不 能 检 测 到 以 下 数 组 的 变 动 : 当 你 利 用 索 引 直 接 设 置 一 个 数 组 项 时 , 例 如 : v m . i t e m s [ i n d e x O f I t e m ] = n e w V a l u e 当 你 修 改 数 组 的 长 度 时 , 例 如 : v m . i t e m s . l e n g t h = n e w L e n g t h 为 了 解 决 第 一 个 问 题 , V u e 提 供 了 以 下 操 作 方 法 : / / V u e . s e t V u e . s e t ( v m . i t e m s , i n d e x O f I t e m , n e w V a l u e ) / / v m . emit 派发一个自定义事件,父组件接收到后,由父组件修改。 有两种常见的试图改变一个 prop 的情形 : 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值: props: ['initialCounter'], data: function () { return { counter: this.initialCounter } } 这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性 props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } } 62、computed 和 watch 的区别和运用的场景? computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值; watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作; 运用场景: 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算; 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。 63、直接给一个数组项赋值,Vue 能检测到变化吗? 由于 JavaScript 的限制,Vue 不能检测到以下数组的变动: 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue 当你修改数组的长度时,例如:vm.items.length = newLength 为了解决第一个问题,Vue 提供了以下操作方法: // Vue.set Vue.set(vm.items, indexOfItem, newValue) // vm. emitprop:propprop使datapropprops:[initialCounter],data:function()returncounter:this.initialCounterprop使propprops:[size],computed:normalizedSize:function()returnthis.size.trim().toLowerCase()62computedwatchcomputedcomputedcomputedcomputedwatch使computedcomputed使watch使watch(访API)63VueJavaScriptVuevm.items[indexOfItem]=newValuevm.items.length=newLengthVue//Vue.setVue.set(vm.items,indexOfItem,newValue)//vm.set,Vue.set的一个别名
      vm.$set(vm.items, indexOfItem, newValue)
      // Array.prototype.splice
      vm.items.splice(indexOfItem, 1, newValue)
      为了解决第二个问题,Vue 提供了以下操作方法:
      // Array.prototype.splice
      vm.items.splice(newLength)
      64、在哪个生命周期内调用异步请求?
      可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是本人推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
      能更快获取到服务端数据,减少页面 loading 时间;
      ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
      65、父组件可以监听到子组件的生命周期吗?
      比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
      // Parent.vue

      // Child.vue
      mounted() {
      this.$emit(“mounted”);
      }
      以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
      // Parent.vue

      doSomething() {
      console.log(‘父组件监听到 mounted 钩子函数 …’);
      },

      // Child.vue
      mounted(){
      console.log(‘子组件触发 mounted 钩子函数 …’);
      },
      // 以上输出顺序为:
      // 子组件触发 mounted 钩子函数 …
      // 父组件监听到 mounted 钩子函数 …
      当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听
      66、组件中 data 为什么是一个函数?
      因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

      67、Vue 怎么用 vm. s e t ( ) 解 决 对 象 新 增 属 性 不 能 响 应 的 问 题 ? 受 现 代 J a v a S c r i p t 的 限 制 , V u e 无 法 检 测 到 对 象 属 性 的 添 加 或 删 除 。 由 于 V u e 会 在 初 始 化 实 例 时 对 属 性 执 行 g e t t e r / s e t t e r 转 化 , 所 以 属 性 必 须 在 d a t a 对 象 上 存 在 才 能 让 V u e 将 它 转 换 为 响 应 式 的 。 但 是 V u e 提 供 了 V u e . s e t ( o b j e c t , p r o p e r t y N a m e , v a l u e ) / v m . set() 解决对象新增属性不能响应的问题 ? 受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。但是 Vue 提供了 Vue.set (object, propertyName, value) / vm. set()JavaScriptVueVuegetter/setterdataVueVueVue.set(object,propertyName,value)/vm.set (object, propertyName, value) 来实现为对象添加响应式属性,那框架本身是如何实现的呢?
      Vue 源码
      export function set (target: Array | Object, key: any, val: any): any {
      // target 为数组
      if (Array.isArray(target) && isValidArrayIndex(key)) {
      // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
      target.length = Math.max(target.length, key)
      // 利用数组的splice变异方法触发响应式
      target.splice(key, 1, val)
      return val
      }
      // key 已经存在,直接修改属性值
      if (key in target && !(key in Object.prototype)) {
      target[key] = val
      return val
      }
      const ob = (target: any).ob
      // target 本身就不是响应式数据, 直接赋值
      if (!ob) {
      target[key] = val
      return val
      }
      // 对属性进行响应式处理
      defineReactive(ob.value, key, val)
      ob.dep.notify()
      return val
      }
      阅读以上源码可知,vm.$set 的实现原理是:
      如果目标是数组,直接使用数组的 splice 方法触发相应式;
      如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)
      68、Vue 中的 key 有什么作用?
      key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。
      Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。
      所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。
      更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
      更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:
      function createKeyToOldIdx (children, beginIdx, endIdx) {
      let i, key
      const map = {}
      for (i = beginIdx; i <= endIdx; ++i) {
      key = children[i].key
      if (isDef(key)) map[key] = i
      }
      return map
      }
      69、说说对于 SSR了解?有没有使用过?
      Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
      即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。
      优点:
      更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
      更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
      缺点:
      更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
      更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源。
      70、服务端渲染 SSR or 预渲染
      服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。
      1.服务端渲染的优点:
      更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
      更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
      2.服务端渲染的缺点:
      更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
      更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。
      如果你的项目的 SEO 和 首屏渲染是评价项目的关键指标,那么你的项目就需要服务端渲染来帮助你实现最佳的初始加载性能和 SEO。如果你的 Vue 项目只需改善少数营销页面(例如 /products, /about, /contact 等)的 SEO,那么你可能需要预渲染,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点,具体你可以使用 prerender-spa-plugin 就可以轻松地添加预渲染 。
      71、虚拟 DOM 的优缺点?
      优点:
      保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
      无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
      跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
      缺点:
      无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
      72、虚拟 DOM 实现原理?
      虚拟 DOM 的实现原理主要包括以下 3 部分:
      ①用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
      ②diff 算法 — 比较两棵虚拟 DOM 树的差异;
      ③pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
      73、Vue 项目优化
      1.代码层面的优化
      v-if 和 v-show 区分使用场景
      computed 和 watch 区分使用场景
      v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
      长列表性能优化
      事件的销毁
      图片资源懒加载
      路由懒加载
      第三方插件的按需引入
      优化无限列表性能
      服务端渲染 SSR or 预渲染
      2.Webpack 层面的优化
      Webpack 对图片进行压缩
      减少 ES6 转为 ES5 的冗余代码
      提取公共代码
      模板预编译
      提取组件的 CSS
      优化 SourceMap
      构建结果输出分析
      Vue 项目的编译优化
      3.基础的 Web 技术的优化
      开启 gzip 压缩
      浏览器缓存
      CDN 的使用
      使用 Chrome Performance 查找性能瓶颈
      74、vue3.0 特性的了解
      1.监测机制的改变
      Vue3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:①只能监测属性,不能监测对象;②检测属性的添加和删除;③检测数组索引和长度的变更;④支持 Map、Set、WeakMap 和 WeakSet。
      新的 observer 还提供了以下特性:
      用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。
      默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。
      更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。
      不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。
      更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染。
      2.模板
      模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。
      同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。
      3.对象式的组件声明方式
      vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。
      此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。
      4.其它方面的更改
      vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其它的更改:
      支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
      支持 Fragment(多个根节点)和 Protal(在 dom 其它部分渲染组建内容)组件,针对一些特殊的场景做了处理。
      基于 treeshaking 优化,提供了更多的内置功能。
      75、css只在当前组件起作用
      答:在style标签中写入scoped即可 例如:
      76.vue常用的修饰符?
      答:.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;.capture: 事件侦听,事件发生的时候会调用
      77.v-on 可以绑定多个方法吗?
      答:可以
      78.什么是vue的计算属性?
      答:在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。好处:①使得数据处理结构清晰;②依赖于数据,数据更新,处理结果自动更新;③计算属性内部this指向vm实例;④在template调用时,直接写计算属性名即可;⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;⑥相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。
      79.Render 函数

      1. $route 和 $router 的区别
        $router 为 VueRouter 实例,想要导航到不同 URL,则使用 $router.push 方法。
        $route 为当前 router 跳转对象里面可以获取 name 、 path 、 query 、 params 等。
      2. NextTick 是做什么的
        $nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM。

    你可能感兴趣的:(前端知识总结,javascript,es6,vue.js)