关于javascript:前端知识体系2vue篇

2次阅读

共计 34868 个字符,预计需要花费 88 分钟才能阅读完成。

1. 简略说下 vue

  • vue 是突变式框架,依据本人的需要增加性能
  • vue 数据驱动采纳 mvvm 模式,m 是数据层,v 是视图层,vm 是调度者
  • SPA 单页面利用,只有一个页面,加载速率快
  • 组件化,复用性强

那么,它有什么毛病?

  1. vue2 底层基于 Object.defineProperty 实现响应式,这个 api 自身不反对 IE8 及以下浏览器
  2. csr 的先天不足,首屏性能问题(白屏)
  3. 因为百度等搜索引擎爬虫无奈爬取 js 中的内容,故 spa 先天就对 seo 优化心有余力有余(谷歌的 puppeteer 就挺牛逼的,实现预渲染底层也是用到了这个工具)

2. vuehereact 的区别

首先你得说说相同点,两个都是 MVVM 框架,数据驱动视图,无争议。如果说不同,那可能分为以下这么几点:

  1. vue 是残缺一套由官网保护的框架,外围库次要有由尤雨溪大神单独保护,而 react 是不要脸的书保护(很多库由社区保护),已经一段时间很多人质疑 vue 的后续维护性,仿佛这并不是问题。
  2. vue 上手简略,进阶式框架,文言说你能够学一点,就能够在你我的项目中去用一点,你不肯定须要一次性学习整个 vue 能力去应用它,而 react,恐怕如果你这样会面对我的项目大刀阔斧。
  3. 语法上 vue 并不限度你必须 es6+ 齐全 js 模式编写页面,能够视图和 js 逻辑尽可能拆散,缩小很多人看不惯 react-jsx 的恶心嵌套,毕竟都是作为前端开发者,还是更习惯于 html 洁净。
  4. 很多人说 react 适宜大型项目,适宜什么什么,vue 轻量级,适宜挪动端中小型我的项目,其实我想说,说这话的人是心里基本没点逼数,vue 齐全能够应答简单的大型利用,甚至于说如果你 react 学的不是很好,写进去的货色或基本不如 vue 写的,毕竟 vue 跟着官网文档撸就行,自有人帮你标准,而 react 比拟懒惰自在,能够自由发挥
  5. vue 在国内人气显著胜过 react,这很大水平上得益于它的很多语法包含编程思维更合乎国人思维

1. 什么是 mvvm

MVVM 的外围是 数据驱动 即 ViewModel,ViewModel是 View 和 Model 的 关系映射

MVVM 实质就是基于操作 数据 来操作 视图 进而操作 DOM,借助于 MVVM 无需间接 操作 DOM,开发者只需编写 ViewModel 中有 业务 ,使得 View 齐全实现 自动化

2. 什么是 SPA 单页面,它的优缺点别离是什么

SPA(single-page application)即 一个 web 我的项目 只有一个页面(即一个 HTML 文件,HTML 内容的变换是利用路由机制实现的。

仅在 Web 页面 初始化 加载 相应的 HTML、JavaScript 和 CSS。一旦页面 加载实现 ,SPA 不会 因为用户的 操作 而进行页面的 从新加载或跳转 ;取而代之的是利用 路由机制 实现 HTML 内容 的变换,UI 与用户的交互,防止页面的从新加载。

长处:

  • 用户体验好、快,内容的扭转不须要从新加载整个页面,防止了不必要的跳转和反复渲染;
  • 基于下面一点,SPA 绝对对 服务器压力小
  • 前后端 职责拆散 架构清晰,前端进行交互逻辑,后端负责数据处理;

毛病

  • 首次加载耗时多:为实现单页 Web 利用性能及显示成果,须要在加载页面的时候将 JavaScript、CSS 对立加载,局部页面按需加载;

后退后退路由治理:因为单页利用在一个页面中显示所有的内容,所以不能应用浏览器的后退后退性能,所有的页面切换须要本人建设堆栈治理;

SEO 难度较大:因为所有的内容都在一个页面中动静替换显示,所以在 SEO 上其有着人造的弱势。

3. 生命周期

3-1 基本概念

什么是 vue 生命周期?Vue 实例 创立 销毁 过程,就是生命周期。

留神 :浏览器有 8 个钩子,然而node 中做服务端渲染的时候只有 beforeCreatecreated

  • beforeCreatenew Vue() 之后触发的 第一个 钩子,在以后阶段 data、methods、computed 以及 watch 上的数据和办法都不能被拜访。能够做页面拦挡。当进一个路由的时候咱们能够判断是否有权限进去,是否平安进去,携带参数是否残缺,参数是否平安。应用这个钩子好函数的时候就防止了让页面去判断,省掉了创立一个组建 Vue 实例。
  • created 产生在 实例创立实现后 ,以后阶段曾经实现了 数据观测 ,也就是能够应用数据,更改数据,在这里 更改 数据 不会 触发 updated 函数。能够做一些初始数据的获取,在以后阶段 无奈 Dom进行 交互 (因为 Dom 还没有创立),如果非要想,能够通过vm.$nextTick 来拜访 Dom。
  • beforeMount产生在 挂载之前 ,在这之前 template 模板已导入渲染函数编译。而以后阶段 虚构 Dom曾经 创立实现,行将开始渲染。在此时也能够对数据进行更改,不会触发 updated。
  • mounted产生在 挂载实现后 ,在以后阶段, 实在 Dom挂载结束,数据实现 双向绑定 ,能够 拜访 到 Dom节点,应用 $refs 属性对 Dom 进行操作。
  • beforeUpdate产生在 更新之前,也就是响应式数据产生更新,虚构 dom 从新渲染之前被触发,你能够在以后阶段进行更改数据,不会造成重渲染。
  • updated产生在 更新实现之后,以后阶段组件 Dom 已实现更新。要留神的是防止在此期间更改数据,因为这可能会导致有限循环的更新。
  • beforeDestroy产生在 实例销毁之前,在以后阶段实例齐全能够被应用,咱们能够在这时进行善后收尾工作,比方革除计时器,销毁父组件对子组件的反复监听。beforeDestroy(){Bus.$off("saveTheme")}
  • destroyed产生在实例销毁之后,这个时候只剩下了 dom 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也通通被销毁。

3-2 生命周期调用程序

  • 组件的调用程序都是先父后子
  • 渲染实现的程序是先子后父
  • 组件的销毁操作是先父后子
  • 销毁实现的程序是先子后父

加载渲染过程 父 beforeCreate-> 父 created-> 父 beforeMount-> 子 beforeCreate-> 子 created-> 子 beforeMount- > 子 mounted-> 父 mounted

子组件更新过程 父 beforeUpdate-> 子 beforeUpdate-> 子 updated-> 父 updated

父组件更新过程 父 beforeUpdate -> 父 updated

销毁过程 父 beforeDestroy-> 子 beforeDestroy-> 子 destroyed-> 父 destroyed

3-3 vue 生命周期的作用是什么

它的生命周期中有多个事件钩子,让咱们 管制 Vue 实例过程更加 清晰

3-4 第一次页面加载会触发哪几个钩子

第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子

3-5 每个周期具体适宜哪些场景

  • beforecreate : 能够在这加个 loading 事件,在加载实例时触发
  • created : 初始化实现时的事件写在这里,如在这完结 loading 事件,异步申请也合适在这里调用
  • mounted : 挂载元素,获取到 DOM 节点
  • updated : 如果对数据对立解决,在这里写上相应函数
  • beforeDestroy : 能够革除定时器
  • nextTick : 更新数据后立刻操作 dom

4.v-show 与 v-if 的区别

v-if

  • 是真正的条件渲染,因为它会确保在切换过程中条件块内的 事件监听器 子组件 适当地被 销毁和重建
  • 也是 惰性 的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show

不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。

所以:

  • v-if 实用于在运行时 很少扭转条件 不须要 频繁切换条件的场景;
  • v-show 则实用于须要 十分频繁 切换条件的场景。

5. Vue 的单向数据流

背景:

所有的 prop 都使得其父子 prop 之间造成了一个 单向上行绑定 :父级 prop 的更新会向下流动到子组件中,然而反过来则不行。这样会避免从子组件 意外扭转 父级组件的状态,从而导致你的利用的数据流向变的凌乱。

每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件外部扭转 prop。如果你这样做了,Vue 会在浏览器的控制台中收回正告。子组件想批改时,只能通过 $emit 派发一个 自定义事件 ,父组件接管到后,由父组件 批改

有两种常见的试图扭转一个 prop 的情景 :

  • 这个 prop 用来传递一个初始值;
  • 这个子组件接下来心愿将其作为一个本地的 prop 数据来应用。

在第 2 状况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:

props: ['initialCounter'],
data: function () {
  return {counter: this.initialCounter// 定义本地的 data 属性接管 prop 初始值}
}

这个 prop 以一种原始的值传入且须要进行转换。

在这种状况下,最好应用这个 prop 的值来定义一个计算属性

props: ['size'],
computed: {normalizedSize: function () {return this.size.trim().toLowerCase()}
}

6. 异步申请适宜在哪个生命周期调用?

官网实例 的异步申请是在 mounted 生命周期中调用的,而实际上也能够在 created 生命周期中调用。

自己举荐在 created 钩子函数中调用异步申请,有以下长处:

  • 更快 获取到 服务端数据 缩小 页面 loading 工夫;
  • ssr 不反对 beforeMount、mounted 钩子函数,所以放在 created 中有助于一致性;

7.Vue2.x 组件通信有哪些形式?

7-1 父子组件通信

  • 父 -> 子 props;子(emit)-> 父(on)
  • 获取父子组件实例 $parent / $children 如:间接在子组件的 methods 办法外面写:this.$parent.show()//show 为父组件中定义的办法
  • Ref (如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例),如在引入的子组件的标签上挂载一个:ref=”comA”,而后在办法中或者子组件的数据,this.$refs.comA.titles
  • Provide、inject 官网不举荐应用,然而写组件库时很罕用,先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量

7-2 兄弟组件通信

  • Event Bus 实现跨组件通信: Vue.prototype.$bus = new Vue
  • Vuex

7-3 跨级组件通信

  • Vuex
  • attrs,listeners
  • Provide、inject

7-4 应用

1. 父子 props,on

// 子组件

<template>
  <header>
    <h1 @click="changeTitle">{{title}}</h1>// 绑定一个点击事件
  </header>
</template>
<script>
export default {data() {
    return {title:"Vue.js Demo"}
  },
  methods:{changeTitle() {this.$emit("titleChanged","子向父组件传值");// 自定义事件  传递值“子向父组件传值”}
  }
}
</script>

// 父组件

<template>
  <div id="app">
    <Header @titleChanged="updateTitle" ></Header>// 与子组件 titleChanged 自定义事件保持一致
    <h2>{{title}}</h2>
  </div>
</template>
<script>
import Header from "./Header"
export default {data(){
    return{title:"传递的是一个值"}
  },
  methods:{updateTitle(e){   // 申明这个函数
      this.title = e;
    }
  },
  components:{Header}
}
</script>

2. parent / $children 与 ref

// A 子组件

export default {data () {
    return {title: 'a 组件'}
  },
  methods: {sayHello () {alert('Hello');
    }
  }
}

// 父组件

<template>
  <A ref="comA"></A>
</template>
<script>
  export default {mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // a 组件
      comA.sayHello();  // 弹窗}
  }
</script>

3.attrs,listeners

attrs:蕴含了 父作用域 不被 prop 辨认 (且获取) 的个性绑定 (class 和 style 除外)。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 (class 和 style 除外),并且能够通过 v-bind=”$attrs” 传入外部组件。通常配合 inheritAttrs 选项一起应用。

listeners::蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on=”$listeners” 传入外部组件

// index.vue

<template>
  <div>
    <h2> 浪里行舟 </h2>
    <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠"></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {components: { childCom1},
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>

// childCom1.vue

<template class="border">
  <div>
    <p>foo: {{foo}}</p>
    <p>childCom1 的 $attrs: {{$attrs}}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {childCom2},
  inheritAttrs: false, // 能够敞开主动挂载到组件根元素上的没有在 props 申明的属性
  props: {foo: String // foo 作为 props 属性绑定},
  created() {console.log(this.$attrs); // 父组件中的属性,且不在以后组件 props 中的属性。{"boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠"}
  }
};
</script>

// childCom2.vue

<template>
  <div class="border">
    <p>boo: {{boo}}</p>
    <p>childCom2: {{$attrs}}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {childCom3},
  inheritAttrs: false,
  props: {boo: String},
  created() {console.log(this.$attrs); // / 父组件中的属性,且不在以后组件 props 中的属性。{"coo": "CSS", "doo": "Vue", "title": "前端工匠"}
  }
};
</script>

// childCom3.vue

<template>
  <div class="border">
    <p>childCom3: {{$attrs}}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>

4. Provide、inject 的应用:

父组件

<template>
    <div id="app">
    </div>
</template>
    <script>
        export default {data () {
                    return {
                        datas: [
                            {
                                id: 1,
                                label: '产品一'
                            }
                        ]
                    }
            },
            provide {
                return {datas: this.datas}
            }
        }
    </script>

子组件

<template>
    <div>
        <ul>
        <li v-for="(item, index) in datas" :key="index">
            {{item.label}}
        </li>
        </ul>
    </div>
</template>
    <script>
        export default {inject: ['datas']
        }
    </script>

8. 什么是 SSR

SSR 也就是 服务端渲染 ,也就是将 Vue 在客户端把 标签 渲染成 HTML 的工作放在服务端实现,而后再把 html 间接返回 给客户端。

服务端渲染 SSR 的优缺点如下:

(1)服务端渲染的长处:

  • 更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会期待 Ajax 异步实现后再抓取页面内容,所以在 SPA 中是 抓取不到 页面通过 Ajax 获取到的 内容 ;而 SSR 是间接由 服务端 返回 曾经渲染好 的页面(数据曾经蕴含在页面中),所以搜索引擎爬取工具能够抓取渲染好的页面;
  • 更快的内容达到工夫 (首屏加载更快):SPA 期待 所有 Vue 编译后的 js 文件 都下载实现后 才开始 进行页面的 渲染,文件下载等须要肯定的工夫等,所以首屏渲染须要肯定的工夫;SSR 间接由服务端渲染好页面间接返回显示,无需期待下载 js 文件及再去渲染等,所以 SSR 有更快的内容达到工夫;

(2) 服务端渲染的毛病:

  • 更多的开发条件限度:例如服务端渲染只反对 beforCreatecreated 两个钩子函数,这会导致一些内部扩大库须要非凡解决,能力在服务端渲染应用程序中运行;并且与能够部署在任何动态文件服务器上的齐全动态单页面应用程序 SPA 不同,服务端渲染应用程序,须要处于 Node.js server 运行环境;
  • 更多的服务器负载:在 Node.js 中渲染残缺的应用程序,显然会比仅仅提供动态文件的 server 更加大量占用 CPU 资源 (CPU-intensive – CPU 密集),因而如果你意料在高流量环境 (high traffic) 下应用,请筹备相应的服务器负载,并明智地采纳缓存策略。

9.Vue 路由

9-1 vue-router 路由模式有几种?

vue-router 有 3 种路由模式:hashhistoryabstract,对应的源码如下所示:

switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {assert(false, `invalid mode: ${mode}`)
    }
}

路由模式的阐明如下:

  • hash: 应用 URL hash 值来作路由。反对 所有浏览器,包含不反对 HTML5 History Api 的浏览器;
  • history : 依赖 HTML5 History API 和服务器配置。具体能够查看 HTML5 History 模式;
  • abstract : 反对所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会主动强制进入这个模式.

9-2 hash 路由和 history 路由实现原理

(1)hash 模式的实现原理

晚期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简略,location.hash 的值就是 URL 中 # 前面的内容。

比方上面这个网站,它的 location.hash 的值为 '#search'

https://www.word.com#search

hash 路由模式的实现次要是基于上面几个个性:

  • URL 中 hash 值只是 客户端 的一种 状态 ,也就是说当向服务器端 发出请求时 ,hash 局部 不会被发送
  • hash 值的 扭转 ,都会在浏览器的 拜访历史 减少 一个 记录。因而咱们能通过浏览器的回退、后退按钮管制 hash 的切换;
  • 能够通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会产生扭转;或者应用 JavaScript 来对 loaction.hash 进行赋值,扭转 URL 的 hash 值;
  • 咱们能够应用 hashchange 事件来监听 hash 值的变动,从而对页面进行跳转(渲染)。

(2)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 事件,这时咱们须要手动触发页面跳转(渲染)。

10.Vue 中的 key 有什么作用?

key 是为 Vue 中 vnode 惟一标记 ,通过这个 key,咱们的 diff 操作能够 更精确、更疾速

Vue 的 diff 过程能够概括为:

oldChnewCh 各有 两个头尾 的变量 oldStartIndex、oldEndIndexnewStartIndex、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
    }

参考 1:Vue2.0 v-for 中 :key 到底有什么用?

11. 虚构 DOM 实现原理

虚构 DOM 的实现原理次要包含以下 3 局部:

  • 用 JavaScript 对象模仿实在 DOM 树,对实在 DOM 进行形象;
  • diff 算法 — 比拟两棵虚构 DOM 树的差别;
  • pach 算法 — 将两个虚构 DOM 对象的差别利用到真正的 DOM 树。

详情点击这里

13. 虚构 DOM 的优缺点

长处:

  • 保障性能上限 :框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作 的实现必须是 普适 的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你 不须要手动优化 的状况下,仍然能够 提供还不错 性能,即保障性能的上限;
  • 无需手动操作 DOM:咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
  • 跨平台:虚构 DOM 实质上是 JavaScript 对象, 而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、weex 开发等等。

毛病:

  • 无奈进行极致优化:尽管虚构 DOM + 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化。

14.Proxy 与 Object.defineProperty 优劣比照

Proxy 的劣势如下:

  • Proxy 能够间接监听对象而非属性;
  • Proxy 能够间接监听数组的变动;
  • Proxy 有多达 13 种拦挡办法, 不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
  • Proxy 返回的是一个新对象, 咱们能够只操作新的对象达到目标, 而 Object.defineProperty 只能遍历对象属性间接批改;
  • Proxy 作为新规范将受到浏览器厂商重点继续的性能优化,也就是传说中的新规范的性能红利;

Object.defineProperty 的劣势如下:

  • 兼容性好,反对 IE9,而 Proxy 的存在浏览器兼容性问题,而且无奈用 polyfill 磨平

14-1 那么,Proxy 能够实现什么性能?

Proxy 是 ES6 中新增的性能,它能够用来自定义对象中的操作。

let p = new Proxy(target, handler)
  • target 代表须要 增加代理 的对象
  • handler 用来自定义 对象中 操作,比方能够用来自定义 set 或者 get 函数。

上面来通过 Proxy 来实现一个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {get(target, property, receiver) {getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = {a: 1}
let p = onWatch(
  obj,
  (v, property) => {console.log(` 监听到属性 ${property}扭转为 ${v}`)
  },
  (target, property) => {console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性 a 扭转为 2
p.a // 'a' = 2

在上述代码中,通过自定义 set 和 get 函数的形式,在本来的逻辑中插入了咱们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简略版的响应式实现,如果须要实现一个 Vue 中的响应式,须要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要应用 Proxy 替换本来的 API 起因在于 Proxy 无需一层层递归为每个属性增加代理,一次即可实现以上操作,性能上更好,并且本来的实现有一些数据更新不能监听到,然而 Proxy 能够完满监听到任何形式的数据扭转,惟一缺点就是浏览器的兼容性不好。

15.Vue 框架怎么实现对象和数组的监听?

Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(局部办法的操作)进行监听。

vue2:

数组 就是应用 object.defineProperty 从新定义数组的 每一项,能引起数组变动的办法为 pop、push、shift、unshift、splice、sort、reverse 这七种,只有这些办法执行改了数组内容,就更新内容

  • 是用来 函数劫持 的形式,重写 了数组办法,具体就是更改了数组的原型,更改成本人的,用户调数组的一些办法的时候,走的就是本人的办法,而后告诉视图去更新(实质就是在原有的办法上又调用了更新数据的办法)。
  • 数组项可能是 对象,那么就对数组的每一项进行观测

vue3:

改用 proxy,可间接监听对象数组的变动。

参考

16.Vue 是如何实现数据双向绑定的

Vue 数据双向绑定次要是指:数据变动更新视图,视图变动更新数据

输入框内容变动时,Data 中的数据同步变动。即 View => Data 的变动。Data 中的数据变动时,文本节点的内容同步变动。即 Data => View 的变动。

其中,View 变动更新 Data,能够通过事件监听的形式来实现,所以 Vue 的数据双向绑定的工作次要是如何依据 Data 变动更新 View。

Vue 次要通过以下 4 个步骤来实现数据双向绑定的

  • 实现一个监听器 Observer 对数据对象进行遍历,包含子属性对象的属性,利用 Object.defineProperty() 对属性都加上 settergetter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变动。
  • 实现一个解析器 Compile 解析 Vue 模板指令,将模板中的 变量 都替换成 数据 ,而后初始化渲染页面视图,并将每个 指令 对应的节点 绑定更新函数,增加监听数据的订阅者,一旦数据有变动,收到告诉,调用更新函数进行数据更新。
  • 实现一个订阅者 Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,次要的工作是订阅 Observer 中的属性值变动的音讯,当收到属性值变动的音讯时,触发解析器 Compile 中对应的更新函数。
  • 实现一个订阅器 Dep 订阅器采纳 公布 - 订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行对立治理。

17.v-model 的原理

v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,v-model 实质上是 语法糖,会在外部为不同的输出元素应用不同的属性并抛出不同的事件:

  • text 和 textarea 元素应用value 属性和 input 事件;
  • checkbox 和 radio 应用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

以 input 表单元素为例

<input v-model='something'>

相当于

<input :value="something" @input="something = $event.target.value">

18. 组件中 data 为什么是一个函数?

为什么组件中的 data 必须是一个函数,而后 return 一个对象,而 new Vue 实例里,data 能够间接是一个对象?

// data

data() {
  return {
    message: "子组件",
    childName:this.name
  }
}

// new Vue

new Vue({
el: '#app',
router,
template: '<App/>',
components: {App}
})

一个组件被复用屡次的话,也就会创立多个实例,实质上,这些实例用的都是同一个构造函数。

如果 data 是对象的话,对象属于援用类型,会影响到所有的实例,所以为了保障组件不同的实例之间 data 不抵触,data 必须是一个函数。

而 new Vue 的实例,是不会被复用的,因而不存在援用对象的问题。

19. 谈谈你对 keep-alive

keep-alive 是 Vue 内置 的一个 组件 ,能够使被 蕴含 的组件 保留状态 防止从新渲染,其有以下个性:

  • 个别联合路由和动静组件一起应用,用于缓存组件;
  • 提供 includeexclude 属性,两者都反对字符串或正则表达式,include 示意只有名称 匹配 的组件会被 缓存 ,exclude 示意任何名称 匹配 的组件都 不会被缓存 ,其中 exclude 的 优先级 比 include
  • 对应两个钩子函数 activateddeactivated,当组件被 激活 时,触发钩子函数 activated,当组件被 移除 时,触发钩子函数 deactivated。

keep-alive 的生命周期

  • activated:页面第一次进入的时候,钩子触发的程序是 created->mounted->activated
  • deactivated: 页面退出的时候会触发 deactivated,当再次后退或者后退的时候只触发 activated

20. 父组件能够监听到子组件的生命周期吗?

比方有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑解决,能够通过以下写法实现:

// Parent.vue

<Child @mounted="doSomething"/>

// Child.vue

mounted() {this.$emit("mounted");
}

以上须要手动通过 $emit 触发父组件的事件,更简略的形式能够在父组件援用子组件时通过 @hook 来监听即可,如下所示:

// Parent.vue

<Child @hook:mounted="doSomething" ></Child>
doSomething() {console.log('父组件监听到 mounted 钩子函数 ...');
},

// Child.vue

mounted(){console.log('子组件触发 mounted 钩子函数 ...');
}, 

// 以上输入程序为:
// 子组件触发 mounted 钩子函数 …
// 父组件监听到 mounted 钩子函数 …
当然 @hook 办法不仅仅是能够监听 mounted,其它的生命周期事件,例如:created,updated 等都能够监听。

21. 间接给一个数组项赋值,Vue 能检测到变动吗?

因为 JavaScript 的限度,Vue 不能检测到以下数组的变动:

  • 当你利用索引间接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  • 当你批改数组的长度时,例如:vm.items.length = newLength

为了解决第一个问题,Vue 提供了以下操作方法:

// Vue.set

Vue.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)

22.vue2.x 中如何监测数组变动

应用了 函数劫持 的形式,重写 了数组的办法,Vue 将 data 中的 数组 进行了 原型链重写 ,指向了 本人定义 数组原型办法。这样当调用数组 api 时,能够告诉依赖更新。如果数组中蕴含着援用类型,会对数组中的援用类型再次递归遍历进行监控。这样就实现了监测数组变动。

22.Vue2.x 和 Vue3.x 渲染器的 diff 算法别离说一下

简略来说,diff 算法有以下过程

同级比拟,再比拟子节点,先判断一方有子节点一方没有子节点的状况(如果新的 children 没有子节点,将旧的子节点移除)

比拟都有子节点的状况 (外围 diff) 递归比拟子节点

失常 Diff 两个树 的工夫复杂度是 O(n^3),但理论状况下咱们很少会进行跨层级的挪动 DOM,所以 Vue 将 Diff 进行了优化,从 O(n^3) -> O(n),只有当新旧 children 都为多个子节点时才须要用外围的 Diff 算法进行同层级比拟。

Vue2的外围 Diff 算法采纳了 双端比拟 的算法,同时从新旧 children 的两端开始进行比拟,借助 key 值找到可复用的节点,再进行相干操作。相比 React 的 Diff 算法,同样状况下能够缩小挪动节点次数,缩小不必要的性能损耗,更加的优雅。

Vue3.x借鉴了 ivi 算法和 inferno 算法 在创立 VNode 时就确定其类型,以及在 mount/patch 的过程中采纳位运算来判断一个 VNode 的类型,在这个根底之上再配合外围的 Diff 算法,使得性能上较 Vue2.x 有了晋升。该算法中还使用了动静布局的思维求解最长递归子序列。

23.Vue 模版编译原理

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST
  • 树优化
  • codegen

首先 解析模版 ,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。

应用大量的 正则 表达式对 模板 进行 解析 ,遇到 标签 文本 的时候都会 执行 对应的 钩子 进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据 首次渲染 后就 不会 变动 ,对应的DOM 也不会变动。那么优化过程就是 深度遍历 AST 树,依照相干条件对 树节点 进行 标记 。这些被标记的 节点 (动态节点) 咱们就能够 跳过 对它们的 比对,对运行时的模板起到很大的优化作用。

编译的最初一步是将优化后的 AST 树 转换 可执行 代码

24.Computed 和 Watch

computed:

  • computed 是 计算属性, 也就是计算值, 它更多用于计算值的场景
  • computed 具备 缓存性,computed 的值在 getter 执行后是会缓存的,只有在它依赖的属性值扭转之后,下一次获取 computed 的值时才会从新调用对应的 getter 来计算
  • computed 实用于计算比拟耗费性能的计算场景

watch:

  • 更多的是「察看」的作用, 相似于某些数据的监听回调, 用于察看 props $emit 或者 本组件 的值, 当数据变动时来执行回调进行后续操作
  • 无缓存性,页面从新渲染时值 不变动 也会 执行

小结:

  • 当咱们要进行数值计算, 而且依赖于其余数据,那么把这个数据设计为 computed
  • 如果你须要在某个数据变动时做一些事件,应用 watch 来察看这个数据变动

25.nextTick

下次 DOM 更新循环完结之后 执行 提早回调 。在这外面的代码会等到dom 更新当前 再执行。

<template>
  <section>
    <div ref="hello">
      <h1>Hello World ~</h1>
    </div>
    <el-button type="danger" @click="get"> 点击 </el-button>
  </section>
</template>
<script>
  export default {
    methods: {get() {}},
    mounted() {console.log(333);
      console.log(this.$refs['hello']);
      this.$nextTick(() => {console.log(444);
        console.log(this.$refs['hello']);
      });
    },
    created() {console.log(111);
      console.log(this.$refs['hello']);
      this.$nextTick(() => {console.log(222);
        console.log(this.$refs['hello']);
      });
    }
  }
</script>

具体点击这路

26.vue 响应式原理

26-1 Vue2.x 响应式数据原理

Vue 在初始化数据时,会应用 Object.defineProperty 从新定义 data 中的 所有属性 ,当页面 应用 对应属性时,首先会进行 依赖收集 (收集以后组件的 watcher),如果属性产生 变动 告诉 相干依赖进行 更新操作(公布订阅)

具体的过程

  • 首先 Vue 应用 initData 初始化用户传入的 参数
  • 而后应用 new Observer 对数据进行 观测
  • 如果数据是一个 对象类型 就会调用 this.walk(value) 对对象进行解决,外部应用 defineeReactive 循环对象属性定义响应式变动,外围就是应用 Object.defineProperty 从新定义数据。

26-2 Vue3.x 响应式数据原理

Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够 间接监听 对象和数组的变动,并且有多达 13 种拦挡办法。并且作为新规范将受到浏览器厂商重点继续的性能优化。

Proxy 只会代理对象的第一层,那么 Vue3 又是怎么解决这个问题的呢?

判断以后 Reflect.get 的返回值是否为 Object,如果是则再通过reactive 办法做代理,这样就实现了深度观测。

监测数组的时候可能触发屡次 get/set,那么如何避免触发屡次呢?

咱们能够判断 key 是否为以后 被代理对象 target本身属性,也能够判断 旧值 新值 是否相等,只有满足以上两个条件之一时,才有可能执行trigger

27. 在应用计算属性的时,函数名和 data 数据源中的数据能够同名吗?

不能同名 因为不论是计算属性还是 data 还是 props 都会被挂载在 vm 实例 上,因而 这三个都不能同名

28. 怎么解决 vue 打包后动态资源图片生效的问题

找到 config/index.js 配置文件,找build 打包对象里的 assetsPublicPath 属性 默认值为 /,更改为./ 就好了

29. 怎么解决 vue 动静设置 img 的 src 不失效的问题?

因为动静增加 src 被当 做动态资源 解决了,没有进行 编译,所以要加上 require。

<img :src="require('../../../assets/images/xxx.png')" />

30. 应用 vue 渲染大量数据时应该怎么优化?说下你的思路!

Object.freeze

适宜一些 big data的业务场景。尤其是做治理后盾的时候,常常会有一些 超大数据量 table,或者一个含有 n 多数据的图表,这种数据量很大的货色应用起来最显著的感触就是卡。但其实很多时候其实这些数据其实并不需要响应式变动,这时候你就能够应用 Object.freeze 办法了,它能够 解冻一个对象(留神它不并是 vue 特有的 api)。

当你把一个一般的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并应用 Object.defineProperty 把这些属性全副转为 getter/setter,它们让 Vue 能进行追踪依赖,在属性被拜访和批改时告诉变动。

应用了 Object.freeze 之后,不仅能够缩小 observer 的开销,还能缩小不少 内存开销

应用形式:

this.item = Object.freeze(Object.assign({}, this.item))

30. vue 自定义指令

先理解一下, 在 vue 中, 有很多内置的指令.

比方:

  • v-bind: 属性绑定, 把数据绑定在 HTML 元素的属性上.
  • v-html & v-text 把数据绑定在 HTML 元素的属性上, 作用同 innerHTML & innerText
  • v-on: 绑定 HTML 元素事件

所以, 对于指令, 咱们能够总结上面几点:

  • 指令是写在 HTML 属性 中央的,<input v-model='name' type='text' />
  • 指令都是以 v- 结尾的.
  • 指令表达式的左边个别也能够跟值 v-if = false

Vue 自定义指令案例 1

例如:咱们须要一个指令, 写在某个 HTML 表单元素上, 而后让它在被加载到 DOM 中时, 主动获取焦点.

// 和自定义过滤器一样, 咱们这里定义的是全局指令
Vue.directive('focus',{inserted(el) {el.focus()
    }
})

<div id='app'>
    <input type="text">
    <input type="text" v-focus placeholder="我有 v -focus, 所以, 我获取了焦点">
  </div>

先总结几个点:

  • 应用 Vue.directive() 来新建一个 全局指令,(指令应用在 HTML 元素属性上的)
  • Vue.directive(‘focus’) 第一个参数 focus 是 指令名, 指令名在申明的时候, 不须要加 v-
  • 在应用指令的 HTML 元素上,<input type="text" v-focus placeholder="我有 v -focus, 所以, 我获取了焦点"/> 咱们须要加上 v-.
  • Vue.directive(‘focus’,{}) 第二个参数 是一个 对象 , 对象 外部 有个 inserted() 的函数, 函数有 el 这个参数.
  • el 这个参数示意了绑定这个指令的 DOM 元素, 在这里就是前面那个有 placeholderinput,el 就等价于 document.getElementById('el.id')
  • 能够利用 $(el) 无缝连贯 jQuery

指令的生命周期

用指令咱们须要:

  • 新增一个指令
  • 定义指令的第二个参数里的 inserted 函数
  • 在须要获取焦点的元素上, 应用这个指令.

当一个指令绑定到一个元素上时, 其实指令的外部会有五个生命周期事件函数.

  • bind(){} 当指令 绑定 到 HTML 元素 上时触发. 只调用一次.
  • inserted() 当绑定了指令的这个 HTML 元素 插入到 父元素 上时触发(在这里父元素是 div#app). 但不保障, 父元素曾经插入了 DOM 文档.
  • updated() 所在组件的 VNode 更新 时调用.
  • componentUpdate 指令所在的组件的 VNode 以及其子 VNode 全副更新后 调用.
  • unbind: 指令和元素 解绑 的时候调用, 只调用一次

Vue 指令的申明周期函数

Vue.directive('gqs',{bind() {
      // 当指令绑定到 HTML 元素上时触发.** 只调用一次 **
      console.log('bind triggerd')
    },
    inserted() {// 当绑定了指令的这个 HTML 元素插入到父元素上时触发(在这里父元素是 `div#app`)**. 但不保障, 父元素曾经插入了 DOM 文档.**
      console.log('inserted triggerd')
    },
    updated() {
      // 所在组件的 `VNode` 更新时调用.
      console.log('updated triggerd')
    },
    componentUpdated() {
      // 指令所在组件的 VNode 及其子 VNode 全副更新后调用。console.log('componentUpdated triggerd')
      
    },
    unbind() {
      // 只调用一次,指令与元素解绑时调用.
      console.log('unbind triggerd')
    }
  })

HTML

<div id='app' v-gqs></div>

后果:

bind triggerd
inserted triggerd

发现默认状况下只有 bind 和 inserted 申明周期函数触发了.

那么剩下的三个什么时候触发呢?

 <div id='app' >
    <p v-gqs v-if="show">v-if 是删除或者新建 dom 元素, 它会触发 unbind 指令申明周期吗?</p>
    <button @click="show=!show">toggle</button>
  </div>

当指令绑定的元素被销毁时, 会触发指令的 unbind 事件.
(新建并显示, 依然是触发 bind & inserted)

unbind 触发.gif

 <p v-gqs v-show="show2">v-show 设置元素的 display:block|none, 会触发 componentUpdated 事件 </p>
 <button @click="show2=!show2">toggle-v-show</button>
  • 一个把元素从 DOM 删除触发 unbind().—> 仅仅是删除.
  • 一个显示设置元素的暗藏和显示的时候触发 componentUpdated() —> block | none 都触发.

31. vue 实例挂载的过程是什么

32. 组件和插件有什么区别

  • 组件 (Component) 是用来形成你的 App 的业务模块,它的指标是 App.vue。
  • 插件 (Plugin) 是用来加强你的技术栈的功能模块,它的指标是 Vue 自身。

33. 应用 vue 过程中可能会遇到的问题(坑)有哪些

34. 动静给 vue 的 data 增加一个新的属性时会产生什么

依据官网文档定义:

如果在 实例创立之后 增加新的 属性 到实例上,它不会触发视图更新。

Vue 不容许在 曾经创立 实例 动静增加 新的 根级响应式属性 (root-level reactive property)。

然而它能够应用 Vue.set(object, key, value) 办法将响应属性增加到嵌套的对象上。

35. SPA 首屏加载速度慢的怎么解决

  1. 通过 Gzip 压缩
  2. 应用路由懒加载
  3. 利用 webpack 中的 externals 这个属性把不须要打包的库文件都拆散进来,减小我的项目打包后的大小
  4. 应用 SSR 渲染

36. mixin

多个实例 援用了雷同或类似的 办法或属性 等,可将这些反复的内容抽取进去作为 mixins 的 js,export 进来,在须要援用的 vue 文件通过 mixins 属性注入,与 以后实例 其余内容 进行merge

一个混入对象能够蕴含 任意组件选项 。同一个生命周期, 混入对象 会比 组件 先执行

// 裸露两个 mixins 对象

export const mixinsTest1 = {
    methods: {hello1() {console.log("hello1");
        }
    },
    created() {this.hello1();
    },
}


export const mixinsTest2 = {
    methods:{hello2(){console.log("hello2");
        }
    },
    created() {this.hello2();
    },
}
<template>
<div>
    home
</div>
</template>

<script>
import {mixinsTest1,mixinsTest2} from '../util/test.js'
export default {
  name: "Home",
  data () {return {};
  },
  created(){console.log("1212");
  },
  mixins:[mixinsTest2,mixinsTest1] // 先调用哪个 mixins 对象,就先执行哪个
}
</script>

hello2
hello1
1212

37. vue 的外围是什么

  1. 数据驱动 专一于 View 层。它让开发者省去了操作 DOM 的过程,只须要扭转数据。
  2. 组件响应原理 数据(model)扭转驱动视图(view)自动更新
  3. 组件化 扩大 HTML 元素,封装可重用的代码。

38. vue 罕用的修饰符有哪些

  • v-model: .trim .number
  • v-on: .stop .prevent

    39. v-on 能够绑定多个办法吗?

<input v-model="msg" type="text" v-on="{input:a, focus:b}"/>

40. template 编译的了解

41.axios 是什么,如何中断 axios 的申请

42. 如何引入 scss?

装置 scss 依赖包:

npm install sass-loader --save-dev npm install node-sass --save-dev

在 build 文件夹下批改 webpack.base.conf.js 文件, 在 module 下的 rules 里增加配置,如下:

{test: /\.scss$/, loaders: ['style', 'css', 'sass'] }

利用:

在 vue 文件中利用 scss 时,须要在 style 款式标签上增加 lang="scss",即 <style lang="scss">。

43. 在 vue 中 watch 和 created 哪个先执行

watch 中的 immediate 会让监听在初始值申明的时候去执行监听计算,否则就是 created 先执行

44. 在 vue 中 created 与 activated 有什么区别

created(): 在实例创立实现后被立刻调用。在这一步,实例已实现以下的配置:数据观测 (data observer),property 和办法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。

activated():是在路由设置 <keep-alive></keep-alive> 时,才会有这个生命周期。在被 keep-alive 缓存的组件激活时调用。

45. 为什么在 v -for 中的 key 不举荐应用随机数或者 index 呢

因为在插入数据或者删除数据的时候,会导致前面的数据的 key 绑定的 index 变动,进而导致从新渲染,效率会升高

46. 如何批量引入组件

动静组件应用办法

<keep-alive>
    <component :is="isWhich"></component>
</keep-alive>
应用标签保留状态,即切换组件再次回来仍然是原来的样子,页面不会刷新,若不须要能够去掉。通过事件扭转 is 绑定的 isWhich 值即可切换成不同的组件,isWhich 的值为组件名称。

47. vue 中怎么重置 data

应用场景:

比方,有一个表单,表单提交胜利后,心愿组件复原到初始状态,重置 data 数据。

应用 Object.assign()vm.$data 能够获取以后状态下的 data,vm.$options.data能够获取到组件初始化状态下的 data

初始状态下设置 data 数据的默认值,重置时间接bject.assign(this.$data, this.$options.data())

阐明:

  • this.$data 获取以后状态下的 data
  • this.$options.data()获取该组件初始状态下的 data(即初始默认值)
  • 如果只想批改 data 的某个属性值,能够this[属性名] = this.$options.data()[属性名],如this.message = this.$options.data().message

48. vue 渲染模板时怎么保留模板中的 HTML 正文呢

<template comments>
  ...
</template>

49. style 加 scoped 属性的用处和原理

  • 用处:避免全局同名 CSS 净化
  • 原理 :在标签加上v-data-something 属性,再在选择器时加上对应[v-data-something],即 CSS 属性选择器,以此实现相似作用域的抉择形式

50. 在 vue 我的项目中如何配置 favicon

  • 将 favicon 图片放到 static 文件夹下
  • 而后在 index.html 中增加:

    <link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico">

51. babel-polyfill 模块

Babel默认只转换 新的 JavaScript 句法 (syntax),而不转换 新的 API,比方 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在 全局对象 上的办法(比方Object.assign)都不会转码。

举例来说,ES6 在 Array 对象上新增了 Array.from 办法。Babel 就不会转码这个办法。如果想让这个办法运行,必须应用babel-polyfill,为以后环境提供一个垫片。

52. 在 vue 事件中传入 $event,应用 e.target 和 e.currentTarget 有什么区别

  • currentTarget:事件绑定的元素
  • target:鼠标触发的元素

53. vue 怎么实现强制刷新组件

强制从新渲染

this.$forceUpdate()

强制从新刷新某组件

// 模版上绑定 key
<SomeComponent :key="theKey"/>
// 选项里绑定 data
data(){
  return{theKey:0}
}

// 刷新 key 达到刷新组件的目标

theKey++;

54. vue 给组件绑定自定义事件有效怎么解决

退出.native 修饰符

55. vue 的属性名与 method 的办法名一样时会产生什么问题

报错 “Method ‘xxx’ has already been defined as a data property”

键名优先级:props > data > methods

56. vue 变量名如果以_、$ 结尾的属性会产生什么问题

实例创立之后,能够通过 vm.$data 拜访 原始数据对象。Vue 实例也代理了 data 对象上所有的属性,因而拜访 vm.a 等价于拜访 vm.$data.a

_$ 结尾的属性 不会 被 Vue 实例 代理 ,因为它们可能和 Vue 内置 的属性、API 办法 抵触 。能够应用 vm.$data._property 的形式 拜访 这些属性。

57. vue 我的项目本地开发实现后部署到服务器后报 404

应用了 history 模式,而后端又没有进行相干资源配置。

58. vue 的表单修饰符.lazy

v-model 默认的触发条件是 input 事件, 加了 .lazy 修饰符之后,v-model 会在 change 事件触发的时候去监听

59. vue 为什么要求组件模板只能有一个根元素

diff 算法要求,源码中 patch.js 中的 patchVnode 也是依据树状构造进行遍历

60. 在 vue 中应用 this 应该留神哪些问题

生命周期的钩子函数不能应用箭头函数,否者 this 不能指向 vue 实例

61. <template></template>有什么用

包裹嵌套其它元素,使元素具备区域性,本身具备三个特点:

  • 暗藏性:不会显示在页面中
  • 任意性:能够写在页面的任意中央
  • 有效性:没有一个根元素包裹,任何 HTML 内容都是有效的

62. 组件中写 name 选项有什么作用

  • 我的项目应用 keep-alive 时,可搭配组件 name 进行 缓存过滤
  • DOM 做递归组件时须要调用本身 name
  • vue-devtools 调试工具里显示的 组见名称 是由 vue 中组件 name 决定的

63. prop 是怎么做验证的

  • 单个类型就用 Number 等根底类型
  • 多个类型用数组
  • 必填的话设置 require 为 true
  • 默认值的话设置 default
  • 对象和数组设置默认用工厂函数
  • 自定义验证函数 validator。

64. vue 权限治理,按钮和路由

65. 大型项目你该怎么划分构造和划分组件

  • views 目录寄存一级路由的组件,即视图组件
  • Components 目录寄存组件
  • Store 寄存 vuex 相干文件
  • Router 目录寄存路由相干文件
  • Untils 目录寄存工具 js 文件
  • API 目录寄存封装好的与后端交互的逻辑
  • Assets 寄存动态文件

66. vue3.0 的新个性

67. 高阶组件

68. vue-loader 是什么

解析和转换 .vue 文件, 提取 出其中的逻辑代码 script、款式代码 style、以及 HTML 模版 template,再别离把它们交给对应的 Loader 去解决。

69. 怎么捕捉组件 vue 的错误信息

70. 怎么配置跨域

71. vue-router 怎么配置 404 页面

设置 path: '*' , 并且放在最初一个

72. vue-router 如何响应路由参数的变动

为什么要响应参数变动?

  • 切换路由,路由参数 产生了 变动 ,然而 页面数据 及时更新,须要强制刷新后才会变动。
  • 不同路由渲染雷同的组件时(组件复用比销毁从新创立效率要高),在切换路由后,以后组件下的生命周期函数不会再被调用。

解决方案:

应用 watch 监听

watch: {$route(to, from){if(to != from) {console.log("监听到路由变动,做出相应的解决");
        }
    }
}

向 router-view 组件中增加 key

<router-view :key="$route.fullPath"></router-view>

$route.fullPath 是实现后解析的 URL,蕴含其查问参数信息和 hash 残缺门路

73. 切换到新路由时,面要滚动到顶部或放弃原先的滚动地位

在路由实例中配置
scrollBehavior(ro,form,savedPosition){
// 滚动到顶部
return {x:0,y:0}
// 放弃原先的滚动地位
return {selector:falsy}
}

74. 路由懒加载

75. MVVM

全称:Model-View-ViewModel,Model 示意 数据模型层 ,view 示意 视图层 ,ViewModel 是 View 和 Model 层的 桥梁 数据绑定 viewModel 层并主动渲染到页面中,视图变动 告诉 viewModel 层更新数据。

76. vue 中的事件绑定原理

事件绑定有几种?

  1. 原生的事件绑定,原生 dom 事件的绑定, 采纳的是 addEventListener 实现。
  2. 组件的事件绑定,组件绑定事件采纳的是 $on 办法。
  • 一般元素 原生 事件绑定在上是通过 @click 进行绑定的
  • 组件 原生 事件绑定是通过 @click.native 进行绑定的,组件中的 nativeOn 是等价于 on 的。
  • 组件 自定义 事件是通过 @click 绑定的,是通过 $on 办法来实现的,必须有 $emit 才能够触发。

解释下这 2 种的区别:

  • 原生:比方咱们在原生便签上写一个事件<div @click="getData"></div>, 间接触发的就是原生的点击事件
  • 组件:当初咱们自定义了个组件,想要组件下面写事件,<BtnGroup @click="getName" @click.native="getData"></BtnGroup>,这时候,要触发原生的点击事件 getData,就须要应用修饰符 .native,因为间接应用@click 是接管来自子组件 emit 过去的事件 getName,这样才不会抵触。
let compiler = require('vue-template-compiler'); // vue loader 中的包
let r1 = compiler.compile('<div @click="fn()"></div>'); // 给一般标签绑定 click 事件
// 给组件绑定一个事件,有两种绑定办法
// 一种 @click.native,这个绑定的就是原生事件
// 另一种 @click,这个绑定的就是组件自定义事件
let r2 = compiler.compile('<my-component @click.native="fn"@click="fn1"></mycomponent>');
console.log(r1.render); // {on:{click}} 
console.log(r2.render); // {nativeOn:{click},on:{click}}
// 为什么组件要加 native?因为组件最终会把 nativeOn 属性放到 on 的属性中去,这个 on 会独自解决
// 组件中的 nativeOn 等价于 一般元素 on,组件 on 会独自解决

具体连贯

77. Vue 为何采纳异步渲染

Vue 在 更新 DOM时是 异步执行 的,只有 侦听 到数据 变动 ,将 开启 一个 队列 ,并 缓冲 在同一事件循环中产生的所有 数据变更 ,如果同一个watcher屡次触发 ,只会被 推入 队列 一次 ,这种在 缓冲 时去除 反复数据 对于缩小 不必要 计算和 DOM 操作 是十分重要的.

而后,在下一个的事件循环 tick 中,Vue 刷新队列并执行理论 (已去重的) 工作,Vue 在外部对异步队列尝试应用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不反对,则会采纳setTimeout(fn, 0) 代替。

形容

对于 Vue 为何采纳异步渲染,简略来说就是为了 晋升性能 ,因为不采纳异步更新,在 每次更新 数据都会对以后组件进行 从新渲染 ,为了性能思考,Vue 会在 本轮数据 更新后,再去异步 更新视图,举个例子,让咱们在一个办法内反复更新一个值。

this.msg = 1;
this.msg = 2;
this.msg = 3;

事实上,咱们真正想要的其实只是最初一次更新而已,也就是说前三次 DOM 更新都是能够省略的,咱们只须要等所有状态都批改好了之后再进行渲染就能够缩小一些性能损耗。

对于渲染方面的问题是很明确的,最终只渲染一次必定比批改之后即渲染所消耗的性能少,在这里咱们还须要考虑一下异步更新队列的相干问题,假如咱们当初是进行了相干解决使得每次更新数据只进行一次实在 DOM 渲染,来让咱们思考异步更新队列的性能优化。

假如这里是同步更新队列,this.msg=1,大抵会产生这些事

msg 值更新 -> 触发 setter -> 触发 Watcher 的 update -> 从新调用 render -> 生成新的 vdom -> dom-diff -> dom 更新

这里的 dom 更新并不是 渲染 (即布局、绘制、合成等一系列步骤),而是更新 内存 中的DOM 树结构,之后再运行 this.msg=2,再反复上述步骤,之后的第 3 次更新同样会触发雷同的流程,等开始渲染的时候,最新的 DOM 树中的确只会存在更新实现 3,从这里来看,前 2 次对 msg 的操作以及 Vue 外部对它的解决都是无用的操作,能够进行优化解决。

如果是异步更新队列,会是上面的状况

运行 this.msg=1,并不是立刻进行下面的流程,而是将对 msg 有依赖的 Watcher 都保留在队列中,该队列可能这样 [Watcher1, Watcher2...],当运行 this.msg= 2 后,同样是将对 msg 有依赖的 Watcher 保留到队列中,Vue 外部 会做 去重判断,这次操作后,能够认为队列数据没有发生变化,第 3 次更新也是下面的过程。

当然,你不可能只对 msg 有操作,你可能对该组件中的另一个属性也有操作,比方 this.otherMsg=othermessage,同样会把对 otherMsg 有依赖的 Watcher 增加到异步更新队列中,因为有反复判断操作,这个 Watcher 也只会在队列中存在一次,本次异步工作执行完结后,会进入下一个工作执行流程,其实就是遍历异步更新队列中的每一个 Watcher,触发其 update,而后进行从新调用 render -> new vdom -> dom-diff -> dom 更新 等流程,然而这种形式和同步更新队列相比,不论操作多少次 msg,Vue 在外部只会进行一次从新调用实在更新流程。

所以,对于异步更新队列不是节俭了 渲染老本,而是节俭了Vue 外部计算及 DOM 树操作的老本,不论采纳哪种形式,渲染的确只有一次。

此外,组件外部理论应用 VirtualDOM 进行渲染,也就是说,组件外部其实是不关怀哪个状态产生了变动,它只须要计算一次就能够得悉哪些节点须要更新,也就是说,如果更改了 N 个状态,其实只须要发送一个信号就能够将 DOM 更新到最新,如果咱们更新多个值。

this.msg = 1;
this.age = 2;
this.name = 3;

此处咱们分三次批改了三种状态,但其实 Vue 只会渲染一次,因为 VIrtualDOM 只须要一次就能够将整个组件的 DOM 更新到最新,它基本不会关怀这个更新的信号到底是从哪个具体的状态收回来的。

而为了达到这个目标,咱们须要将 渲染操作 推延到 所有 的状态 都批改实现 ,为了做到这一点只须要将渲染操作推延到本轮事件循环的最初或者下一轮事件循环,也就是说,只须要在 本轮事件循环 最初 ,等 后面更新状态 的语句都 执行完之后 执行一次 渲染操作,它就能够忽视后面各种更新状态的语法,无论后面写了多少条更新状态的语句,只在最初渲染一次就能够了。

将渲染推延到本轮事件循环的最初执行渲染的时机会比推延到下一轮快很多,所以 Vue 优先将渲染操作推延到本轮事件循环的最初,如果执行环境不反对会降级到下一轮,Vue 的变动侦测机制 (setter) 决定了它必然会在每次状态发生变化时都会收回渲染的信号,但 Vue 会在收到信号之后查看队列中是否曾经存在这个工作,保障队列中不会有反复,如果队列中不存在则将渲染操作增加到队列中,之后通过异步的形式提早执行队列中的所有渲染的操作并清空队列,当同一轮事件循环中重复批改状态时,并不会重复向队列中增加雷同的渲染操作,所以咱们在应用 Vue 时,批改状态后更新 DOM 都是异步的。

当数据 变动 后会调用 notify 办法,将 watcher 遍历,调用 update 办法告诉 watcher 进行更新,这时候 watcher 并不会立刻去执行,在 update 中会调用 queueWatcher 办法将 watcher 放到了一个队列里,在 queueWatcher 会依据 watcher 的进行去重,若多个属性依赖一个 watcher,则如果队列中没有该 watcher 就会将该 watcher 增加到队列中,而后便会在 $nextTick 办法的执行队列中退出一个 flushSchedulerQueue 办法 (这个办法将会触发在缓冲队列的所有回调的执行),而后将$nextTick 办法的回调退出 $nextTick 办法中保护的执行队列,flushSchedulerQueue 中开始会触发一个 before 的办法,其实就是 beforeUpdate,而后 watcher.run()才开始真正执行 watcher,执行完页面就渲染实现,更新实现后会调用 updated 钩子。

$nextTick

在上文中谈到了对于 Vue 为何采纳异步渲染,如果此时咱们有一个需要,须要在页面渲染实现后获得页面的 DOM 元素,而因为渲染是异步的,咱们不能间接在定义的办法中同步获得这个值的,于是就有了 vm.$nextTick 办法,Vue 中 $nextTick 办法将回调提早到下次 DOM 更新循环之后执行,也就是在下次 DOM 更新循环完结之后执行提早回调,在批改数据之后立刻应用这个办法,可能获取更新后的 DOM。简略来说就是当数据更新时,在 DOM 中渲染实现后,执行回调函数。

通过一个简略的例子来演示 $nextTick 办法的作用,首先须要晓得 Vue 在更新 DOM 时是异步执行的,也就是说在更新数据时其不会阻塞代码的执行,直到执行栈中代码执行完结之后,才开始执行异步工作队列的代码,所以在数据更新时,组件不会立刻渲染,此时在获取到 DOM 构造后获得的值仍然是旧的值,而在 $nextTick 办法中设定的回调函数会在组件渲染实现之后执行,获得 DOM 构造后获得的值便是新的值。

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {msg: 'Vue'},
        template:`
            <div>
                <div ref="msgElement">{{msg}}</div>
                <button @click="updateMsg">updateMsg</button>
            </div>
        `,
        methods:{updateMsg: function(){
                this.msg = "Update";
                console.log("DOM 未更新:", this.$refs.msgElement.innerHTML)
                this.$nextTick(() => {console.log("DOM 已更新:", this.$refs.msgElement.innerHTML)
                })
            }
        },
        
    })
</script>
</html>

异步机制 #

Js 是单线程的,其引入了同步阻塞与异步非阻塞的执行模式,在 Js 异步模式中保护了一个 Event Loop,Event Loop 是一个 执行模型 ,在不同的中央有不同的实现,浏览器和 NodeJS 基于不同的技术实现了各自的 Event Loop。浏览器的 Event Loop 是在HTML5 的标准中明确定义,NodeJS 的 Event Loop 是基于 libuv 实现的。

在浏览器中的 Event Loop执行栈 Execution Stack、 后盾线程 Background Threads、 宏队列 Macrotask Queue、 微队列Microtask Queue 组成。

  • 执行栈 就是在 主线程 执行 同步工作 数据结构,函数调用造成了一个由若干帧组成的栈。
  • 后盾线程 就是浏览器实现对于 setTimeout、setInterval、XMLHttpRequest 等等的执行线程。
  • 宏队列 ,一些 异步工作 的回调会顺次进入宏队列,期待后续被调用,包含 setTimeout、setInterval、setImmediate(Node)、requestAnimationFrame、UI rendering、I/ O 等操作。
  • 微队列 ,另一些 异步工作 的回调会顺次进入微队列,期待后续调用,包含 Promise、process.nextTick(Node)、Object.observe、MutationObserver 等操作。

当 Js 执行时,进行如下流程:

  • 首先将执行栈中代码同步执行,将这些代码中异步工作退出后盾线程中。
  • 执行栈中的同步代码执行结束后,执行栈清空,并开始扫描微队列。
  • 取出微队列队首工作,放入执行栈中执行,此时微队列是进行了出队操作。
  • 当执行栈执行实现后,持续出队微队列工作并执行,直到微队列工作全副执行结束。
  • 最初一个微队列工作出队并进入执行栈后微队列中工作为空,当执行栈工作实现后,开始扫面微队列为空,持续扫描宏队列工作,宏队列出队,放入执行栈中执行,执行结束后持续扫描微队列为空则扫描宏队列,出队执行。一直往返 …。

实例 #

// Step 1
console.log(1);

// Step 2
setTimeout(() => {console.log(2);
  Promise.resolve().then(() => {console.log(3);
  });
}, 0);

// Step 3
new Promise((resolve, reject) => {console.log(4);
  resolve();}).then(() => {console.log(5);
})

// Step 4
setTimeout(() => {console.log(6);
}, 0);

// Step 5
console.log(7);

// Step N
// ...

// Result
/*
  1
  4
  7
  5
  2
  3
  6
*/

剖析 #

在理解异步工作的执行队列后,回到中 $nextTick 办法,当用户数据更新时,Vue 将会保护一个 缓冲队列 ,对于所有的更新数据将要进行的组件渲染与 DOM 操作进行肯定的策略解决后退出缓冲队列,而后便会在$nextTick 办法的执行队列中退出一个 flushSchedulerQueue 办法 (这个办法将会触发在缓冲队列的所有回调的执行),而后将$nextTick 办法的回调退出 $nextTick 办法中保护的执行队列,在异步挂载的执行队列触发时就会首先会首先执行 flushSchedulerQueue 办法来解决 DOM 渲染的工作,而后再去执行 $nextTick 办法构建的工作,这样就能够实现在 $nextTick 办法中获得已渲染实现的 DOM 构造。

在测试的过程中发现了一个很有意思的景象,在上述例子中的退出两个按钮,在点击 updateMsg 按钮的后果是 3 2 1,点击 updateMsgTest 按钮的运行后果是 2 3 1。

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {msg: 'Vue'},
        template:`
            <div>
                <div ref="msgElement">{{msg}}</div>
                <button @click="updateMsg">updateMsg</button>
                <button @click="updateMsgTest">updateMsgTest</button>
            </div>
        `,
        methods:{updateMsg: function(){
                this.msg = "Update";
                setTimeout(() => console.log(1))
                Promise.resolve().then(() => console.log(2))
                this.$nextTick(() => {console.log(3)
                })
            },
            updateMsgTest: function(){setTimeout(() => console.log(1))
                Promise.resolve().then(() => console.log(2))
                this.$nextTick(() => {console.log(3)
                })
            }
        },
        
    })
</script>
</html>

这里假如运行环境中 Promise 对象是齐全反对的,那么应用 setTimeout 是宏队列在最初执行这个是没有异议的,然而应用 $nextTick 办法以及自行定义的 Promise 实例是有执行程序的问题的,尽管都是微队列工作,然而在 Vue 中具体实现的起因导致了执行程序可能会有所不同,首先间接看一下 $nextTick 办法的源码,要害中央增加了正文,请留神这是 Vue2.4.2 版本的源码,在前期 $nextTick 办法可能有所变更。

/**
 * Defer a task to execute it asynchronously.
 */
var nextTick = (function () {
  // 闭包 外部变量
  var callbacks = []; // 执行队列
  var pending = false; // 标识,用以判断在某个事件循环中是否为第一次退出,第一次退出的时候才触发异步执行的队列挂载
  var timerFunc; // 以何种办法执行挂载异步执行队列,这里假如 Promise 是齐全反对的

  function nextTickHandler () { // 异步挂载的执行工作,触发时就曾经正式筹备开始执行异步工作了
    pending = false; // 标识置 false
    var copies = callbacks.slice(0); // 创立正本
    callbacks.length = 0; // 执行队列置空
    for (var i = 0; i < copies.length; i++) {copies[i](); // 执行}
  }
   
   // 如果反对 promise
  if (typeof Promise !== 'undefined' && isNative(Promise)) {var p = Promise.resolve();
    var logError = function (err) {console.error(err); };
    timerFunc = function () {p.then(nextTickHandler).catch(logError); // 挂载异步工作队列
      if (isIOS) {setTimeout(noop); }
    };
  } else if (typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) ||
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    var counter = 1;
    var observer = new MutationObserver(nextTickHandler);
    var textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {characterData: true});
    timerFunc = function () {counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
  } else {
    // fallback to setTimeout
    /* istanbul ignore next */
    timerFunc = function () {setTimeout(nextTickHandler, 0);
    };
  }

  return function queueNextTick (cb, ctx) { // nextTick 办法真正导出的办法
    var _resolve;
    callbacks.push(function () { // 增加到执行队列中 并退出异样解决
      if (cb) {
        try {cb.call(ctx);
        } catch (e) {handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {_resolve(ctx);
      }
    });
    // 判断在以后事件循环中是否为第一次退出,若是第一次退出则置标识为 true 并执行 timerFunc 函数用以挂载执行队列到 Promise
    // 这个标识在执行队列中的工作将要执行时便置为 false 并创立执行队列的正本去运行执行队列中的工作,参见 nextTickHandler 函数的实现
    // 在以后事件循环中置标识 true 并挂载,而后再次调用 nextTick 办法时只是将工作退出到执行队列中,直到挂载的异步工作触发,便置标识为 false 而后执行工作,再次调用 nextTick 办法时就是同样的执行形式而后一直如此往返
    if (!pending) { 
      pending = true;
      timerFunc();}
    if (!cb && typeof Promise !== 'undefined') {return new Promise(function (resolve, reject) {_resolve = resolve;})
    }
  }
})();

回到方才提出的问题上,在更新 DOM 操作时会先触发 $nextTick 办法的回调,解决这个问题的关键在于谁先将异步工作挂载到 Promise 对象上。

首先对有数据更新的 updateMsg 按钮触发的办法进行 debug,断点设置在 Vue.js 的 715 行,版本为 2.4.2,在查看调用栈以及传入的参数时能够察看到第一次执行 $nextTick 办法的其实是因为数据更新而调用的 nextTick(flushSchedulerQueue) 语句,也就是说在执行 this.msg = “Update”; 的时候就曾经触发了第一次的 $nextTick 办法,此时在 $nextTick 办法中的工作队列会首先将 flushSchedulerQueue 办法退出队列并挂载 $nextTick 办法的执行队列到 Promise 对象上,而后才是自行自定义的 Promise.resolve().then(() => console.log(2)) 语句的挂载,当执行微工作队列中的工作时,首先会执行第一个挂载到 Promise 的工作,此时这个工作是运行执行队列,这个队列中有两个办法,首先会运行 flushSchedulerQueue 办法去触发组件的 DOM 渲染操作,而后再执行 console.log(3),而后执行第二个微队列的工作也就是() => console.log(2),此时微工作队列清空,而后再去宏工作队列执行 console.log(1)。

接下来对于没有数据更新的 updateMsgTest 按钮触发的办法进行 debug,断点设置在同样的地位,此时没有数据更新,那么第一次触发 $nextTic k 办法的是自行定义的回调函数,那么此时$nextTick 办法的执行队列才会被挂载到 Promise 对象上,很显然在此之前自行定义的输入 2 的 Promise 回调曾经被挂载,那么对于这个按钮绑定的办法的执行流程便是首先执行 console.log(2),而后执行 $nextTick 办法闭包的执行队列,此时执行队列中只有一个回调函数 console.log(3),此时微工作队列清空,而后再去宏工作队列执行 console.log(1)。

简略来说就是谁先挂载 Promise 对象的问题,在调用 $nextTick 办法时就会将其闭包外部保护的执行队列挂载到 Promise 对象,在数据更新时 Vue 外部首先就会执行 $nextTick 办法,之后便将执行队列挂载到了 Promise 对象上,其实在明确 Js 的 Event Loop 模型后,将数据更新也看做一个 $nextTick 办法的调用,并且明确 $nextTick 办法会一次性执行所有推入的回调,就能够明确其执行程序的问题了,上面是一个对于 $nextTick 办法的最小化的 DEMO。

var nextTick = (function(){

    var pending = false;
    const callback = [];
    var p = Promise.resolve();

    var handler = function(){
        pending = true;
        callback.forEach(fn => fn());
    }

    var timerFunc = function(){p.then(handler);
    }

    return function queueNextTick(fn){callback.push(() => fn());
        if(!pending){
            pending = true;
            timerFunc();}
    }

})();


(function(){nextTick(() => console.log("触发 DOM 渲染队列的办法")); // 正文 / 勾销正文 来查看成果
    setTimeout(() => console.log(1))
    Promise.resolve().then(() => console.log(2))
    nextTick(() => {console.log(3)
    })
})();
正文完
 0