Vue

头像
喆喆
    阅读 6 分钟
    1

    基础知识点(一)

    原始类型

    原始类型有哪几种?null是对象嘛?

    在JS中,存在着6种原始值,分别是:

    • number
    • boolean
    • string
    • null
    • undefined
    • symbol

    首先原始类型存储的都是值,是没有函数可以调用的,比如undefined.toString()。
    image.png
    虽然typeof null会输出object,但是这只是JS存在的一个悠久Bug。在JS的最初版本中使用的是32位系统,为了性能考虑使用低位存储变量的类型消息,000开头代表是对象,然而null表示为全零,所以将它错误的判断为object。虽然现在的内部类型判断代码已经改变了,但是对于这个Bug却是一直流传下来。

    Vue框架

    MVVM

    什么是MVVM?比之MVC有什么区别?

    首先申明一点,不管是React还是Vue,它们都不是MVVM框架,只有借鉴MVVM的思路。
    接下来先说下View和Model:

    • View很简单,就是用户看到的试图
    • Model同样很简单,一般就是本地数据和数据库中的数据

    基本上,我们写的产品就是通过接口从数据库中读取数据,然后将数据经过处理展现到用户看到的试图上。当然我们还可以从视图上读取用户的输入,然后又将用户的输入通过接口写入到数据库中。但是,如何将数据展示到试图上,然后又如何将用户的输入写入到数据中,不同的就产生了不同的看法,从此出现了很多架构设计。
    传统的MVC架构通常是使用控制器更新模板,视图从模型中获取数据去渲染。当用户有输入时,会通过控制器去更新模板,并且通知视图进行更新。
    image.png
    但是MVC有一个巨大的缺陷就是控制器承担的责任太大了,随着项目愈加复杂,控制器中的代码会越来越臃肿,导致出现不利于维护的情况。
    在MVVM架构中,引入了ViewModel的概念。ViewModel只关心数据的业务的处理,不关心View如何处理数据,在这种情况系下,View和model都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在ViewModel中,让多个View复用这个ViewModel。
    image.png
    以Vue框架来举例,ViewModel就是组件的实例。View就是模板,Model的话在引入Vuex的情况下是完全可以和组件分离的。
    除了以上三个部分,其实在MVVM中还引入了一个隐式的Binder层,实现了View和ViewModel的绑定。
    image.png
    同样以Vue框架来举例,这个隐式的Binder层就是Vue通过解析模板中的插值和指令从而实现View与ViewModel的绑定。
    对于MVVM来说,其实最重要的并不是通过双向绑定或者其他的方式将View与VieModel绑定起来,而是通过ViewModel将视图中的状态和用户的行为分离出一个抽象,这才是MVVM的精髓。

    Virtual DOM

    什么是Virtual DOM?为什么Virtual DOM比原生DOM快?

    为什么操作DOM性能很差?因为DOM是属于渲染引擎中的东西,而JS又是JS引擎中的东西。当我们通过JS操作DOM的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作DOM次数一多,也就等同于一直在进行线程之前的通信,并且操作DOM可能还会带来重绘回流的情况,所以也就导致了性能上的问题。
    首先DOM是一个多叉树的结构,如果需要完整的对比两棵树的差异,那么需要的时间复杂度会是O(n^3),这个复杂度肯定是不能接受的,于是React团队优化了算法,实现了O(n)的复杂度来对比差异。实现O(n)复杂度的关键就是只对比同层的节点,而不是跨层对比,这也是考虑到在实际业务中很少会去跨层的移动DOM元素。所以判断差异的算法就分为了两步:

    • 首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后渲染差异。
    • 一旦节点会有子元素,就去判断子元素是否有不同。

    在第一步算法中我们需要判断新旧节点的tagName是否相同,如果不相同的话就代表节点被替换了。如果没有更改tagName的话,就需要判断是否有子元素,有的话就进行第二步算法。
    在第二步算法中,我们需要判断原本的列表中是否有节点被移除,在新的列表中需要判断是否有新的节点加入,还需要判断节点是否有移动。
    当然了Virtual DOM提高性能是其中一个优势,其实最大的优势还是在于:

    • 1、将Virtual DOM作为一个兼容层,让我们还能对接非Web端的系统,实现跨端开发。
    • 2、同样的,通过Virtual DOM我们可以渲染到其他的平台,比如实现SSR、同构渲染等等。
    • 3、实现组件的高度抽象化。

    路由原理

    前端路由原理?两种实现方式有什么区别?
    前端路由实现起来其实很简单,本质就是监听URL的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面。目前前端使用的路由就只有两种实现方式

    • Hash模式
    • History模式

    Hash模式

    www.test.com/#/就是Hash URL,当#后面的哈希值发生变化时,可以通过hashchange时间来监听到URL的变化,从而进行跳转页面,并且无论哈希值如何为变化,服务器接收到URL请求永远是www.test.com。

    window.addEventListener('hashchange', () => {
        // ...具体逻辑
    })

    Hash模式相对来说更简单,并且兼容性也更好。

    History模式

    History模式是HTML5新推出的功能,主要使用history.pushState和history.replaceState改变URL。
    通过History模式改变URL同样不会引起页面的刷新,只会更新浏览器的历史记录。

    // 新增历史记录
    history.pushState(stateObject, title, URL)
    // 替换当前历史记录
    history.replaceState(stateObject, title, URL)

    当用户做出浏览器动作时,比如点击后退按钮时会触发popState事件

    window.addEventListener('popstate', e => {
        // e.state就是pushState(stateObject)中的stateObject
        console.log(e.state)
    })

    两种模式对比

    • Hash模式只可以更改#后面的内容,History模式可以通过API设置任意的同源URL
    • History模式可以通过API添加任意类型的数据到历史记录中,Hash模式只能更改哈希值,也就是字符串
    • Hash模式无需后端配置,并且兼容性好。History模式在用户手动输入地址或者刷新页面的时候会发起URL请求,后端需要配置index.html页面用于匹配不到静态资源的时候。

    Vue基础知识点

    生命周期钩子函数

    在beforeCreate钩子函数调用的时候,是获取不到props或者data中的数据的,因为这些数据的初始化都在initState中。
    然后会执行created钩子函数,在这一步的时候已经访问之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。
    接下来会先执行beforeMount钩子函数,开始创建VDOM,最后执行mounted钩子,并将VDOM渲染为真实DOM并且渲染数据。组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。
    接下来是数据更新时会调用的钩子函数beforeUpdate和updated,这两个钩子函数,分别在数据更新前和更新后会调用。
    另外还有keep-alive独有的生命周期,分别为activated和deactivated。用keep-alive包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行deactivated钩子函数,命中缓存渲染后会执行actived钩子函数。
    最后就是销毁组件的钩子函数beforeDestroy和destroyed。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的destroyed钩子函数。

    组件通信

    组件通信一般分为以下几种情况:

    • 父子组件通信
    • 兄弟组件通信
    • 跨多层级组件通信
    • 任意组件

    父子通信

    父组件通过props传递数据给子组件,子组件通过emit发送事件传递数据给父组件,这两种方式是最常用的父子通信实现方法。
    这种父子通信方式也就是典型的单项数据流,父组件通过props传递数据,子组件不能直接修改props,而是必须通过发送事件的方式告知父组件修改数据。
    另外这两种方式还可以使用语法糖v-model来直接实现,因为v-model默认会解析成名为value的prop和名为input的事件。这种语法糖的方式是典型的双向绑定,常用于UI控件上,但是究其根本,还是通过事件的方法让父组件修改数据。
    当然我们还可以通过访问$parent或者$children对象来访问组件实例中的方法和数据。
    另外如果你使用Vue 2.3及以上版本的话还可以使用$listeners和.sync这两个属性。
    $listeners属性会将父组件中的(不含.native修饰器的)v-on事件监听器传递给子组件,子组件可以通过访问$listeners来自定义监听器。
    .sync属性是个语法糖,可以很简单的实现子组件与父组件通信

    <!-- 父组件中 -->
    <input :value.sync="value">
    <!-- 以上写法等同于 -->
    <input :value="value" @update:value="v => value = v"></comp>
    <!-- 子组件中 -->
    <script>
        this.$emit('update:value', 1)
    </script>

    兄弟组件通信

    对于这种情况可以通过查找父组件中的子组件实现,也就是this.$parent.$children,在$children中可以通过组件name查询到需要的组件实例,然后进行通信。

    跨多层次组件通信

    对于这种情况可以使用Vue 2.2新增的API provide/inject,虽然文档中不推荐直接使用在业务中,但是如果用得好的话还是很有用的。
    假设有父组件A,然后有一个跨多层级的子组件B

    // 父组件A
    export default {
        provide: {
            data: 1
        }
    }
    // 子组件
    export default {
        inject: ['data'],
        mounted() {
            // 无论跨几层都能获得父组件的data属性
            console.log(this.data) // => 1
        }
    }

    任意组件

    这种方式可以通过Vuex或者Event Bus解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况。

    extend能做什么

    这个API很少用到,作用是扩展组件生成一个构造器,通常会与$mount一起使用。
    image.png

    mixin和mixins区别

    mixin用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。

    Vue.mixin({
        beforeCreate() {
            // ...逻辑
            // 这种方式会影响到每个组件的beforeCreate钩子函数
        }
    })

    虽然文档不建议我们在应用中直接使用mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的ajax或者一些工具函数等等。
    mixins应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过mixins混入代码,比如上拉下拉加载数据这种逻辑等等。
    另外需要注意的是mixins混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。

    computed和watch区别

    computed是计算属性,依赖其他属性计算值,并且computed的值有缓存,只有当计算值变化才会返回内容。
    watch监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
    所以一般来说需要依赖别的属性来动态获得值的时候可以使用computed,对于坚挺到值的变化需要做一些复业务逻辑的情况可以使用watch。
    另外computed和watch还都支持对象的写法。

    vm.$watch('obj', {
        // 深度遍历
        deep: true,
        // 立即触发
        immediate: true,
        // 执行的函数
        handler: function(val, oldVal) {}
    })
    var vm = new Vue({
        data: { a: 1 },
        computed: {
            aPlus: {
                // this.aPlus时触发
                get: fucntion() {
                    return this.a + 1
                },
                // this.aPlus = 1时触发
                set: function(v) {
                    this.a = v - 1
                }
            }
        }
    })

    keep-alive组件有什么作用

    如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用keep-alive组价包裹需要保存的组件。
    对于keep-alive组件来说,它拥有两个独有的生命周期钩子函数,分别微activated和deactivated。用keep-alive包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行deactivated钩子函数,命中缓存渲染后会执行actived钩子函数。

    v-show与v-if区别

    v-show只是在display:none和display:block之间切换。
    v-if,当属性初始为false时,组件就不会被渲染,直到条件为true。
    总的来说,v-show在初始渲染时有更高的开销,但是切换开销很小,更适用于频繁切换的场景;v-if在切换时开销更高,更适合不经常切换的场景。

    组件中data什么时候可以使用对象

    组件复用时所有组件实例都会共享data,如果data是对象的话,就会造成一个组件修改data以后会影响到其他所有组件,所以需要将data写成函数,每次用到就调用一次函数获得新的数据。
    当我们使用new Vuw()的方式的时候,无论我们将data设置为对象还是函数都是可以的,因为new Vue()的方式是生成一个根组件,该组件不会复用,也就不存在共享data的情况了。


    喆喆
    74 声望7 粉丝

    小白一枚!!!