前端笔记--VUE--20200603

96 阅读9分钟

[VUE篇]

一. vue的优点

  • 轻量级框架,大小只有几十kb,中文文档,简单易学
  • 组件化,数据双向绑定,数据操作更加方便,可以封装组件,重复使用。
  • 虚拟dom: dom的操作本身是很耗时的,vue不在使用原生dom操作,代码更加简洁易读。

二. 组件传值

1. 父子组件传值:

父组件向子组件传值一般实在父组件上通过属性绑定,然后有子组件通过==props==接收父组件传过来的值

子组件向父组件传值:由子组件通过$==emit==()向父组件发送数据,然后由都组件通过事件绑定方式接收子组件的数据

2. 兄弟组件传值:

==事件总线方式==:创建一个事件总线,由一个组件A向组件B发射数据,再有B组件监听得到A组件传递过来的数据。

  1. 创建一个事件总线:
import Vue from 'vue'
export const EventBus = new Vue()
  1. A组件向B组件发送数据
emit() {
      //表单发生变化,就像兄弟组件发射变化后的表单
      EventBus.$emit('searchform', {
        searchform: this.searchform,  // data中定义的属性
        searchInput: this.value   // data中定义的属性
      })
    },
  1. B组件接受A组件数据
 EventBus.$on('searchform', data => {
      this.searchform = data.searchform
      this.searchInput = data.searchInput
    })

三. VUE基础用法

1. v-show和v-if指令的共同点和不同点

共同点: 都是控制元素的显示隐藏

不同点: 实现的方式不一样,v-show是通过控制css样式display:none来隐藏元素,v-if是通过动态添加删除元素来实现显示隐藏

总结: 频繁的操作显示和隐藏应该使用v-show,因为v-if频繁操作会消耗性能。

2. 如何让CSS只在当前组件中起作用?

在组件中的style前面加上scoped

3. keep-alive的作用是什么?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。

4. 说出几种vue当中的指令和它的用法?

  • v-model双向数据绑定;
  • v-for循环;
  • v-if/ v-show 显示与隐藏;
  • v-on事件;v-once: 只绑定一次。

5. 为什么使用key?

vue的核心思想是数据驱动视图,避免直接操作dom元素来改变视图中的数据,所有vue中就有了虚拟dom作为数据和视图之间的桥梁,==key值就是给dom元素添加一个唯一标识==,方便diff算法有效快速的找到对应的节点,从而达到==更新虚拟dom的效果==

6. 分别简述computed和watch的使用场景

  • computed计算属性: 当一个属性受到多个属性影响的时候就可以使用computed将其作为计算属性,比如: 购物车商品结算的时候
  • watch监听属性: 当一个属性可以影响多个属性的时候可使用watch做为监听,当这个属性发生变化的时候可以做一些动作。比如:监听输入框中的内容

7. vue组件中data为什么必须是一个函数?

因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。

组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。

8. 为什么不建议v-if和v-for同时使用

因为v-for的优先级对于v-if,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐v-if和v-for同时使用。

如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去。

9. 如何获取dom

在组件上添加ref="domname"属性,然后通过this.$refs.domname获取dom

四. MVC, MVP, MVVM

  • 在web发展早期阶段,前端代码和后端代码柔和在一起,维护起来十分不便,前端代码也不好书写和调试,于是逐渐形成了mvc,mvp,mvvm等框架设计规范。

  • 三者都是一种框架设计规范,目标都是为了解决model层和view层的耦合问题。

  • MVC模式:出现较早应用于后端,在前端也有所应用,优点是分层清晰,缺点: 数据混乱,controller层臃肿,灵活性低。

  • MVP模式:是由mvc模式进化而来,由p--->presenter作为中间层,解决了model和view之间耦合的问题,负责传递model和view之间的通信,但是由于presenter层过于臃肿,导致维护困难

  • MVVM模式:是在mvc模式基础上进行了再次划分层次,增加了VM-->viewmodel视图模型层,将数据解析,封装的工作交由vm去执行,controller层只需要劫持vm层解析和封装好的数据交给model层保存或者给view层进行渲染。

1. MVC模式

model(模型):在程序中负责存放数据的部分 view(视图): 在程序中负责显示数据给用户的部分 controller(控制层): 在程序中负责处理数据,和业务逻辑部分,向model发送数据,获取用户输入的数据,或者向view层提供数据部分

斯坦福大学公开课上的这幅图来说明,这可以说是最经典和最规范的MVC标准

image
上图中,将程序框架划分为三个部分,分别是:model,view和controller,当用户在view层上触发某个事件,view会将事件传递给controller,view不会关心这个事件会干嘛,只负责将事件传递给controller。controller在接到事件后会通过过方法(函数)告诉model层,model层在接受到请求后,将数据再交给controller,controller在接收到数据后,会对数据进行解析,封装等工作,最后将数据交给view渲染。这样的过程,最大限度的将前后端分离了出来,每个模块只是执行负责做好自己事情。

再上图中,model层和view层中间是双黄线隔离,但是这部意味着,model和view层是不能交互的,而是不应该,没人能阻止你在写代码的时候在一个M里面去写V,但是一旦你这样做了,那么你就违背了MVC的规范,你就不是在使用MVC了。

总结:M和V之间的关系应该是一种同步的关系,也就是,不管任何时刻,只要M的值发生改变,V的显示就应该发生改变(显示最新的M的内容)。所以我们可以关注M的值改变,而不用关心M的网络请求是否结束了。实际上C根本不知道M从哪去拿的数据,C的责任是负责把M最新的数据赋值给V。所以C应该关注的事件是:M的值是否发生了变化或者view中是否有事件传递过来。

2. mvp模式:

MVP架构模式是Model(模型)、View(视图)、Presenter(表示器)组成。 image

MVP架构模式最主要是针对Android的MVC架构模式进行改进的,MVP与MVC最不同的一点是M与V是不直接关联的也是就Model与View不存在直接关系,这两者之间间隔着的是Presenter层,其负责调控View与Model之间的间接交互。

3. mvvm模式:

MVVM是Model(数据层)、ViewController/View(视图层)、ViewModel(数据模型)组成。 image MVVM架构模式最主要是针对前端和iOS的MVC架构模式进行改进的,减轻Controller层或者View层的压力,实现更加清晰化代码。通过对ViewModel层的封装:封装业务逻辑处理,封装网络处理、封装数据缓存等,让逻辑处理分离出来,并且不需要处理Model数据,使得Controller层或者View层结构简单,条理清晰。

五. vue如何实现数据双向绑定

vue实现数据双向绑定的原理使用过数据劫持和发布-订阅者模式来实现的

实现步骤:

  1. 实现一个监听者Observer来劫持需要监听的所有属性,一旦有属性发生变化,就通知订阅者watcher

    /*
    	劫持一个属性需要用到  defineProperty(obj,key,val)方法,在JS中每个数据都有两个属性风别是get()和set(),get用来通知属性被读取或者被使用了,
    	set用来通知数据被修改了。
    */
    
    let car = {
        brand: 'BWM',
        price: '5000'
      }
      function defineProperty(obj, key, val) {  // val是指对应键的值
        if (arguments.length === 2) {  // 如果只传了两个参数处理
          val = obj[key]
        }
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            console.log(key + '属性被读取了')
            return val
          },
          set(newVal) {
            console.log(key + '属性被修改了')
            val = newVal
          }
        })
      }
      defineProperty(car, 'price')  // 开始劫持属性
    
    
  2. 实现一个订阅者watcher来接受属性发生变化的通知,并且执行相应的动作达到更新视图的目的

    /*
    	订阅者(watcher): 即被观察者的依赖,使用或者读取了某个属性的集合,当属性发生变化,被修改后,就会通知订阅者发生需要发生变化了,订阅者发生了变化,视图也就更新。
    	1. 如何获取这些订阅者(一个视图下会有多个订阅者)
    	2. 如何通知这些订阅者
    	3. 如何管理这些订阅者
    */
    
    
    //  1. 收集订阅者: 
    //  在劫持属性的时候,我们知道,当属性被读取或者被修改的时候,会触发数据本身携带的get和set属性,订阅者肯定读取了某个属性的值,所以在get中我们可以收集这些订阅者
    //  2. 通知订阅者:
    //  当属性的值被修改的时候,会触发数据的set属性,这时候应该通知订阅者,数据发生了变化。
    function defineReactive (obj,key,val) {
      if (arguments.length === 2) {
        val = obj[key]
      }
      if(typeof val === 'object'){
        new Observer(val)
      }
      const dep = new Dep()  //实例化一个依赖管理器,生成一个依赖管理数组dep
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get(){
          dep.depend()    // 在getter中收集依赖
          return val;
        },
        set(newVal){
          if(val === newVal){
              return
          }
          val = newVal;
          dep.notify()   // 在setter中通知依赖更新
        }
      })
    }
    //  3. 管理订阅者:
    //  应为在一个视图中会有很多的订阅者,将它们集中管理,便于维护,在vue源码中是将它们集中放在一个叫dep的类中: 这个类中会对订阅者执行一些增删改查的动作
    
    export default class Dep {
      constructor () {
        this.subs = []
      }
    
      addSub (sub) {
        this.subs.push(sub)
      }
      // 删除一个依赖
      removeSub (sub) {
        remove(this.subs, sub)
      }
      // 添加一个依赖
      depend () {
        if (window.target) {
          this.addSub(window.target)
        }
      }
      // 通知所有依赖更新
      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    }
    
    /**
     * Remove an item from an array
     */
    export function remove (arr, item) {
      if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
          return arr.splice(index, 1)
        }
      }
    }
    
    // Vue中还实现了一个叫做Watcher的类,在之后数据变化时,我们不直接去通知依赖更新,而是通知依赖对应的Watch实例,由Watcher实例去通知真正的视图
    export default class Watcher {
      constructor (vm,expOrFn,cb) {
        this.vm = vm;
        this.cb = cb;
        this.getter = parsePath(expOrFn)
        this.value = this.get()
      }
      get () {
        window.target = this;
        const vm = this.vm
        let value = this.getter.call(vm, vm)
        window.target = undefined;
        return value
      }
      update () {
        const oldValue = this.value
        this.value = this.get()
        this.cb.call(this.vm, this.value, oldValue)
      }
    }
    
    /**
     * Parse simple path.
     * 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
     * 例如:
     * data = {a:{b:{c:2}}}
     * parsePath('a.b.c')(data)  // 2
     */
    const bailRE = /[^\w.$]/
    export function parsePath (path) {
      if (bailRE.test(path)) {
        return
      }
      const segments = path.split('.')
      return function (obj) {
        for (let i = 0; i < segments.length; i++) {
          if (!obj) return
          obj = obj[segments[i]]
        }
        return obj
      }
    }
    
  3. 实现一个解释器compile,可以扫描和解析每个节点的相关指令,并更具初始化模板数据以及初始化相对应的订阅者

六. 虚拟DOM

1. 什么是虚拟DOM

所谓的虚拟dom就是一个用js对象描述的一个dom节点

<div class="a" id="b">我是内容</div>   // 真实的dom节点
 

//  用js对象描述的dom节点
{    
  tag:'div',        // 元素标签
  attrs:{           // 属性
    class:'a',
    id:'b'
  },
  text:'我是内容',  // 文本内容
  children:[]      // 子元素
}

我们把一个真实的dom节点用js对象描述出来,这个js对象我们就称之为虚拟DOM

2. 为什么需要虚拟DOM

vue的核心是数据驱动视图,当数据发生变化,视图就要随之更新。更新视图必然需要操作dom节点,然而操作dom节点是非常消耗性能的,为了尽量减少操作dom,于是创建的虚拟dom。

当初始化数据的时候,我们创建一个虚拟的dom,当数据发生变化的时候,我们先找到对应的虚拟dom中哪写数据发生了变化,也就是视图哪些地方需要更新,只需要更新需要更新的地方,没有变化的地方则不需要更新,这样就大大减少了dom的操作。也就是用JS的计算性能换取了操作dom消耗的性能

如何计算哪写数据发生了变化,主要通过 DOM-Diff 算法计算出来 (算法具体,不做深究)

七. vue生命周期