Vue相关

251 阅读12分钟

响应式原理

1. 原理:

  • 监听数据的变化,数据劫持
  • 收集数据的依赖
  • 当数据发生变化的时候更新渲染视图

2. vue2的响应式原理

  • 主要使用的是Object.defineProperty( ) ,里面需要传入三个参数,分别是:(源数据的对象,源数据中的需要读写的属性,相对应的对象方法(包含了get和set方法))

3. vue3的响应式原理

  • 主要依靠的是ES6新增的 Proxy 以及相配合的 Reflect实现的,需要在Proxy的实例对象中传入两个参数 (源数据对象,处理对象的方法(get,set,deleteProperty…等))

4. vue2和vue3响应式原理对比

  • vue3的响应式相对于vue2来说简洁了很多,主要体现的地方就是我们用了Proxy的实例对象之后,不需要在单独的想vue2之中那样(使用的defineProperty)需要特意去知名监控某个对象的变化(name、age属性)。这个是很重要的一个变化。

双向数据绑定

1. 原理

  • vue是通过数据劫持和发布订阅者模式来实现双向数据绑定的 就是利用oberver来监听module数据的变化,通过compile来解析模板,最终通过watcher来建立oberver和module之间的通信桥梁 创建一个vue对象,分为el模板和data数据 使用Object.definePropertype来劫持data中的每个属性的getter和setter,在数据发生变化的时候发布消息给订阅者,触发相应的回调。(vue3使用proxy劫持数据)也就是observer的功能 compile 解析模板,将模板中的变量替换成数据,然后动态渲染页面, 将双向绑定的指令对应的节点添加更新函数,添加监听数据的订阅者,一旦数据有变化,就更新视图 Watcher订阅者是Observer和Compile之间通信的桥梁,他负责往dep里添加订阅者,当收到数据变化的通知时,触发compile的回调,更新视图 (vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep中增加一个订阅者)

2. Vue实现数据双向绑定的原理:Object.defineProperty()

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

3. js实现简单的双向绑定

```
<body>
    <div id="app">
    <input type="text" id="txt">
    <p id="show"></p>
</div>
</body>
<script type="text/javascript">
    var obj = {}
    Object.defineProperty(obj, 'txt', {
        get: function () {
            return obj
        },
        set: function (newValue) {
            document.getElementById('txt').value = newValue
            document.getElementById('show').innerHTML = newValue
        }
    })
    document.addEventListener('keyup', function (e) {
        obj.txt = e.target.value
    })
</script>
```

vue2和vue3的区别

区别

  • 变得更轻,更快,对上⼀个版本的升级优化,⽤户体验都是不断地在越来越⽅便,
  • 在数据双向绑定原理方面
    • Vue2使⽤的是Object.defineProperty()进⾏数据劫持,结合发布订阅的⽅式实现。
    • Vue3使⽤的是Proxy代理,使⽤ref或者reactive将数据转化为响应式数据。
    • Object.defineProperty监听对象属性。而Proxy监听的是整个对象
  • vue3新增了⼀些内置组件和⽅法
    • ⽐如vue3可以默认进⾏懒观察,使⽤Function-based API,setup函数,对于插件或对象的⼀ 个按需引⼊,Computed Value ,新加⼊了 TypeScript 以及 PWA 的⽀持等
  • 在生命周期方面
    • vue3相比vue2,生命周期名字大部分需要+on,功能类似,使用vue3API需要先引入,vue2选项API这直接调用即可。 image.png
  • 虚拟DOM
    • Vue3 相比于 Vue2 虚拟DOM 上增加patchFlag字段。
  • 自定义渲染API
    • Vue3 提供的createApp默认是将 template 映射成 html。但若想生成canvas时,就需要使用custom renderer api自定义render生成函数。
    //自定义runtime-render函数 import{createApp}from'./runtime-render' 
    import App from './src/App' createApp(App).mount('#app')
    
  • 多根节点
    • vue在模板中如果使用多个根节点时会报错,需要去将组件包裹在< div >中。Vue3 支持多个根节点,使用fragment,可以少写一层。

vue3使⽤proxy的优势

  • defineProperty只能监听某个属性,不能对全对象监听 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
  • 可以监听数组,不⽤再去单独的对数组做特异性操作,通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。

Vue、Angular、React的区别?

  • 1.Vue与AngularJS的区别
    • 相同点: 都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。
    • 不同点: AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
  • 2.与React的区别
    • 相同点: React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。
    • 不同点: React采用的Virtual DOM会对渲染出来的结果做出检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。

MVVM 和 MVC

MVVM

  • Model(模型)、View(视图)、ViewModel(视图模型))
  • 概念:MVVM框架下视图和模型是不能直接通信的,只能通过ViewModel进行交互,它能够监听到数据的变化,然后通知视图进行自动更新,而当用户操作视图时,VM也能监听到视图的变化,然后通知数据做相应改动,这实际上就实现了数据的双向绑定。并且VVM可以进行通信。
  • 优点: 分离视图和模型,减少了代码的耦合度,自动更新DOM和数据
  • 缺点: 当一个视图的状态很多的时候,viewmodule的构建和维护成本比较高

MVC

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。 View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。 Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
  • 概念: VC是单向通信。也就是ViewModel,必须通过Controller来进行联系。
  • 优点: 低耦合、生命周期成本低、可维护性高、部署快、有利软件工程化管理。

区别

  • MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
    • MVC中Controller演变成MVVM中的ViewModel
    • MVVM通过数据来显示视图层而不是节点操作
    • MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验

Vue的生命周期

  • beforeCreate(创建前) 在数据观测和初始化事件还未开始
  • created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来
  • beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
  • mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
  • beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
  • updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  • beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
  • destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

1. 什么是vue生命周期?

  • Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

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

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

3. vue生命周期总共有几个阶段?

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

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

  • 会触发 下面这几个beforeCreate, created, beforeMount, mounted 。

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

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

Vue组件通信

1. 父组件向子组件传递

  • 父组件通过props向下传递数据给子组件,组件中的数据共有三种形式:data、props、computed。

2. 子组件传给父组件

  • 通过事件形式,子组件通过events给父组件发送消息,也就是子组件把自己的数据发送到父组件。通过 events($emit)。
  • 通过父链 / 子链也可以通信($parent / $children)。
  • ref 也可以访问组件实例;provide / inject API;$attrs/$listeners

3. 兄弟组件之间

  • eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。(虽然也有不少人推荐直接用VUEX,具体来说看需求。)
  • VueX

4. 跨级通信

  • us;Vuex;provide / inject API、$attrs/$listeners

Vue的路由实现

1. hash、history、abstract

  • hash:默认值 路由从浏览器地址栏中的hash部分获取路径,改变路径也是改变hash的部分,兼容性最好,不刷新页面;

  • history: 路由从浏览器地址栏中的location.pathname部分获取路径,改变路径使用H5的history api(history.pushState(null,null,"/blog")),该模式可以让地址栏友好,看起来舒服,也需要浏览器支持history api;

  • abstract: 路由从内存中获取路径,改变路径也只是改动内存中的值,这种模式通常应用到非浏览器环境中。

  • hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取; 特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。 hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

  • history模式:history采用HTML5的新特性;且提供了两个新方法:pushState()replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。

2. route和router的区别

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

3. vue路由的钩子函数具体参数

  • 作用
    • 首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。
  • 具体参数
    • beforeEach主要有3个参数to,from,next:

    • to:route即将进入的目标路由对象,

    • from:route当前导航正要离开的路由

    • next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。

导航守卫

  • vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
  • vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
  • 导航钩子的三个参数解析:
    • to: 即将要进入的目标的路由对象.
    • from: 当前导航即将要离开的路由对象.
    • next: 调用该方法后, 才能进入下一个钩子

Vuex

1.出现原因:

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理 应用的所有组件的状态
  • 简单来说就是把需要共享的变量全部存储在一个对象里面。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。并且这个对象里的数据都是响应式的,当组件从store中获取了数据,如果store里的数据发生改变,组件里的数据也会发生改变

2.分类:

  • 有五种,分别是 State、 Getter、Mutation 、Action、 Module state => 基本数据(数据源存放地) getters => 从基本数据派生出来的数据 mutations => 提交更改数据的方法,同步 actions => 像一个装饰器,包裹mutations,使之可以异步。 modules => 模块化Vuex

3.使用场景

  • 比如用户的登录状态、用户名称、头像、地理位置信息等等。
  • 比如商品的收藏、购物车中的物品等等。
  • 这些状态信息,都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
  • 核心流程
  • 组件触发一些动作或事件,也就是actions,想要改变状态或获取数据,但是在vuex中状态是集中管理的,不能直接修改数据,所以就会把这个动作或事件(actions)提交给mutation,然后mutatiion去修改state中的数据,当state数据改变后,就会重新渲染到vue组件去,组件展示新数据。

vue中 key 值的作用

1. 概念:

  • key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。
  • vue中的key的作用,在vue中我们可能在两种情况下使用key,第一种情况下就是在v-if中,第二种情况下就是在v-for中使用key。下面我们来看一下key在其中起到的作用。

2.在v-if中使用key

  • 首先我们先看在vue中出现的一种情况,我们在vue中如果使用v-if进行切换时,此时Vue为了更加高效的渲染,此时会进行前后比较,如果切换前后都存在的元素,则直接复用。如果我们在模板中切换前后都存在input框,此时我们在input框中写入一些数据,并且进行页面切换,则原有的数据就会保存。 此时我们就可以使用key,给每一个input框,添加一个唯一的标识key,来表示元素的唯一性。

3.在v-for中使用key

  • 对于用v-for渲染的列表数据来说,数据量可能一般很庞大,而且我们经常还要对这个数据进行一些增删改操作。那么整个列表都要重新进行渲染一遍,那样就会很费事。而key的出现就尽可能的回避这个问题,提高效率。v-for默认使用就地复用的策略,列表数据修改的时候,他会根据key值去判断某一个值是否修改,如果修改则重新渲染该项,否则复用之前的元素。在v-for中我们的key一般为id,也就是唯一的值,但是一般不要使用index作为key。
  • Computed 和 Methods 的区别

  • 可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的
  • 不同点:
    • computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值;
    • method 调用总会执行该函数。

Computed 和 Watch 的区别

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

v-if和v-show的区别?

  • 首先,它们两个的作用效果是相同的,都能控制元素在页面是否显示。
  • 内部控制方式不一样:
    • v-if显示隐藏是将dom元素整个添加或删除。
    • v-show隐藏则是为该元素添加css--display:nonedom元素依旧还在。
  • v-if可以拥有更多的分支,v-else等,会删除和添加元素,控制dom元素的有和没有,有效的减少树的结点和渲染量,但是导致树不稳定
  • 而v-show没有分支,只是切换属性样式,不能改动元素,dom元素始终都在
  • 树的渲染效率取决于(树的节点,渲染量和树的稳定性)
  • 使用场景:v-if 相比 v-show 开销更大的,直接操作dom节点增加与删除,如果需要非常频繁地切换,则使用 v-show 较好,如果在运行时条件很少改变,则使用 v-if 较好。

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

js中的对象是引用类型,多个实例引用一个对象时,当一个实例的对象修改时,别的实例都会改变。在Vue中是想要更多的复用组件,每个组件中的数据都要是独立的,所以组件的data值不能是一个对象,使用函数的形式,在复用组件的时候每次都会返回一个新的data,这样每个组件都有自己的数据空间,各自维护,互不影响

对keep-alive 的了解?

  • keep-alive可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
  • 主要是有include、exclude、max三个属性;前两个属性允许keep-alive有条件的进行缓存;max可以定义组件最大的缓存个数,如果超过了这个个数的话,在下一个新实例创建之前,就会将以缓存组件中最久没有被访问到的实例销毁掉。
  • 两个生命周期activated/deactivated,用来得知当前组件是否处于活跃状态。
  • 原理
    • keep-alive中运用了LRU(Least Recently Used)算法。
    • 获取 keep-alive 包裹着的第一个子组件对象及其组件名; 如果 keep-alive 存在多个子元素,keep-alive 要求同时只有一个子元素被渲染。所以在开头会获取插槽内的子元素,调用 getFirstComponentChild 获取到第一个子元素的 VNode
    • 根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则开启缓存策略。
    • 根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键)。
    • 如果不存在,则在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。最后将该组件实例的keepAlive属性值设置为true

$nextTick

1. 出现原因

  • vue为了高效率的更新DOM,Vue不可能对每一个数据变化都做一次渲染,它会把这些变化先放在一个异步的队列当中,同时它还会对这个队列里面的操作进行去重,然后在一次事件循环结束之后更新DOM,nextTick就是在一次DOM更新完毕之后调用的

2. 实现原理:

  • nextTick会将回调函数放在异步任务重中,他是使用Promise.then、MutationObserver和setImmediate或者setTimout来让callback放入异步队列的,这样callbac就会在同步代码执行完了之后调用,此时就可以操作更新好的DOM了

3. 源码是使用三个参数来做的

  • callback:我们要执行的操作,可以放在这个函数当中,我们没执行一次$nextTick就会把回调函数放到一个异步队列当中;
  • pending:标识,用以判断在某个事件循环中是否为第一次加入,第一次加入的时候才触发异步执行的队列挂载
  • timerFunc:用来触发执行回调函数,也就是Promise.then或MutationObserver或setImmediate 或setTimeout的过程

4. 使用场景:

  • 比如说,点击按钮出现输入框,输入框出现的时候自动获得焦点,设计的时候是 点击按钮修改isshow,给输入框加ref,用ref得到输入框,然后this. r e f s . i d . f o c u s ( ) 但是会发现输入框并没有获得焦点,因为执行的时候,虽然改了 i s s h o w ,但是页面还没有渲染出来,就获取 r e f ,是获取不到的,这时就要延时一下,等到页面重新渲染之后再获取,这是就使用 refs.id.focus() 但是会发现输入框并没有获得焦点,因为执行的时候,虽然改了isshow,但是页面还没有渲染出来,就获取ref,是获取不到的,这时就要延时一下,等到页面重新渲染之后再获取,这是就使用 refs.id.focus()但是会发现输入框并没有获得焦点,因为执行的时候,虽然改了isshow,但是页面还没有渲染出来,就获取ref,是获取不到的,这时就要延时一下,等到页面重新渲染之后再获取,这是就使用nextTick 在created()钩子函数中,使用$nextTick,因为created()时,DOM还没有渲染,无法进行DOM操作

vue.js的两个核心是什么?

  • vuejs的两个核心是数据驱动组件系统。数据驱动也就是数据的双向绑定,用于保证数据和视图的一致性。组件系统能够把页面抽象成多个相对独立的模块;可以实现代码重用,提高开发效率和代码质量,便于代码维护。

vue几种常用的指令

  • v-for 、 v-if 、v-bind、v-on、v-show、v-else

什么是vue的计算属性?

  • 在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。 好处:①使得数据处理结构清晰;②依赖于数据,数据更新,处理结果自动更新;③计算属性内部this指向vm实例;④在template调用时,直接写计算属性名即可;⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;⑥相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。

vue等单页面应用及其优缺点

  • 优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。 缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。

动态路由? 怎么获取传过来的值

  • 在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。

父子组件传递时,emit和props实现原理

自定义指令

  • 全局自定义指令

    1. 声明位置:在main.js文件里面通过Vue.directive()进行全局声明。
    2. 定义方式:(例如)
      image.png
    3. 全局引用:
          <div v-color="js表达式"></div>
  • 私有自定义指令

    1. 声明位置:在每个Vue组件中,可以在directives节点下声明私有指令。
    2. 定义方式:
      image.png image.png
    3. 局部引用:
          <div v-color="js表达式"></div>