笔记 | Vue基础

240 阅读5分钟

Vue 基础

概念

  • vue.js 是一套构建用户界面的渐进式框架
  • vue.js 是自底向上增量开发设计的
  • vue.js 是一个构建数据驱动的 web 界面的库。
  • vue 核心库只关注视图层。
  • vue.js 是一个MVVM库。

问题

  • 什么是渐进式框架?

    • 框架是分层的,每层都有不同功能,可以根据需求选择,一步一步,不用一下把所有东西都用上
  • 什么是自底向上增量开发?

    • 由基础程序逐渐扩大规模,升级功能
  • 什么是数据驱动?

    • 所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。

响应式数据原理

vue2.x 响应原理

参考:Vue 源码阅读-依赖收集原理 - SHERlocked93

  • 通过数据劫持配合发布-订阅的设计模式利用 Object.defineProperty将 data 中的 property 转为 getter/setter(通过 observer)。这些 getter 和 setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。【每个组件实例都对应一个watcher实例,它会在组件渲染过程中把接触过的 property 记录为依赖(通过 dep 进行依赖收集),之后依赖项的 setter 触发时会通知 watcher 重新计算,从而使它关联的组件重新渲染】。

    • 数据劫持:指在访问或者修改对象的某个属性时,被一段代码拦截这个行为,进行额外的操作或者修改返回结果。

    • 发布-订阅模式:定义了一种一对多的依赖关系,发生改变时,所有依赖它的对象都会得到通知。

    • 在 google 的时候看到的一个新问题,观察者和发布-订阅模式有什么区别?

      • 发布-订阅与观察者模式,在广义上时是一个意思。
      • 观察者模式,观察者需要直接订阅目标,在目标发出事件后,直接接收事件做出响应。
      • 发布订阅模式,中间存在一个信息中介,发布不知道有没有人订阅及订阅的人是谁,解耦能力更强

Vue3.x 响应式数据原理

  • 使用Proxy取代 Object.defineProperty()实现数据劫持

vue3出来还没了解,这段后续待更

对比

  • object.defineProperty 只能劫持对象的属性,需要遍历对象的每个属性。
  • object.defineProperty 新增需要重新遍历对象,再对新增对象进行劫持
  • proxy 直接遍历对象。
  • proxy 可以直接监听数组的变化.
  • proxy 性能高,支持 13 种拦截方式

特性

  • 轻量级的框架
  • 双向数据绑定
  • 指令
  • 插件化

MVVM 框架

Model view viewmodel

参考:MVC,MVVM,MVP 的图示-阮一峰

  • model 代表数据模型,可以在 model 中定义数据修改和操作的业务逻辑。
  • view 代表 UI 组件,它负责将数据模型转换成 UI 展现出来。
  • viewmodel 同步 view 和 model。
  • view 和 model 通过 viewmodel 进行交互,交互是双向的。
  • view 的变化会同步到 model 中,model 数据的变化会立即反映到 view 上。 MVVM

使用 MVVM 模式的优点

  • 低耦合: 视图可以独立于模型变化和修改,一个 viewmodel 可以绑定在不同的视图上,当视图变化的时候模型可以不变,模型变化时视图也可以不变。
  • 可重用性:可以把一些视图的逻辑放在 viewmodel 中,让很多视图复用这段逻辑。
  • 独立开发
  • 可测试性

MVC

  • MVC(model-view-controller)

    • MVC 强调职责分离

    • 所有通信都是单向的

      • view 将指令传送到 controller
      • controller 完成业务逻辑要求 model 改变状态
      • model 将新的数据传递给 view

MVC

思考二者区别

  • MVVM 通过数据来显示视图,解决了 MVC 中大量 DOM 操作的问题,

Vue 实例

创建 vue 实例

当一个 vue 实例被创建时,它的 data 对象的所有 property 加入到 vue 响应式系统中,当 property 值发生变化时,视图也会产生响应

var vm = new Vue({
<!--选项-->
})

举例

  <div id="app">
    {{message}}
  </div>
  <script>
  var vm = new Vue({
    el: "#app",
    data: {
      message: 'hello vue!'
    }
  })
  </script>

el

提供一个页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。

  • 实例挂载后,可以通过 vm.$el 访问

data

vue 将 data 的 property 转换为 getter/setter,让 data 的 property 能够响应数据变化。

  • 对于已经存在的实例,不允许动态添加根级别的响应式 property

    • 无法检测property的添加或移除。
    • 因为 Vue 在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式。
    • 可以通过 Vue.set()方法添加响应式 property。
  • 可以通过 vm.$data 访问原始数据对象

  • 通过 vm.a 可以获取 data 的 a 属性

  • vm.a = vm.$data.a

问题

实例

 <div id="app">
    <span class="span-a">
      {{obj.a}}
    </span>
    <span class="span-b">
      {{obj.b}}
    </span>
  </div>
  1. 问最终 span-a 和 span-b 分别展示什么字符串?
var app = new Vue({
  el: "#app",
  data: {
    obj: {
      a: 'a'
    }
  }
})
app.obj.a = 'a2'

答:span-a 中显示 a2,span-b 不显示(因为 obj.b 在实例被创建时不存在)

只有当实例被创建时就已经存在于 data 中的 property 才是响应式的

  1. 问最终 span-a 和 span-b 分别展示什么字符串?
var app = new Vue({
  el: "#app",
  data: {
    obj: {
      a: 'a'
    }
  }

})
app.obj.b = 'b'

答:span-a 中显示 a,span-b 中不显示(因为 obj.b 在实例被创建时不存在,对 b 的改动不会触发任何视图的更新)

解决方法:

  • 方法一 通过 Vue.set(object, propertyName, value)方法向嵌套对象添加响应式 property
 Vue.set(app.obj, 'b', 'b')
  • 方法二 通过 vm.$set()添加响应式 property
var app = new Vue({
  el: "#app",
  data: {
    obj: {
      a: 'a'
    }
  },
  methods: {
    add: function(){
      this.$set(this.obj,'b','b')
    }
  }
})
app.obj.a = 'a2'
  • 方法三 利用 Object.assign({},this.obj)
 var app = new Vue({
   el: "#app",
   data: {
     obj: {
       a: 'a'
     }
   },
   methods: {
     add: function(){
       this.obj = Object.assign({},this.obj,{b:'b'})
     }
   }
 })
  1. 问最终 span-a 和 span-b 分别展示什么字符串?
var app = new Vue({
  el: "#app",
  data: {
    obj: {
      a: 'a'
    }
  }

})
app.obj.a = 'a2'
app.obj.b = 'b'

答案:span-a 中显示 a2,span-b 中显示 b

Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更

  • 优点

    • 提高性能,在本轮数据更新后再去异步更新视图,若不采用异步更新,则每次更新都会重新渲染
  1. 页面显示结果是什么
    <div id="app">
        {{foo}}
    <button @click="foo = 'baz'">点击一下</button>
    </div>
    <script>
        var obj = {
            foo: 'bar'
        }
        Object.freeze(obj)
        var app = new Vue({
            el: '#app',
            data: obj
        })
    </script>

答:bar

Object.freeze()会阻止修改现有的 property,响应系统无法在追踪变化

  1. Vue 不能检测数组和对象变化(对象见第 2 题)
  • 利用索引直接设置一个数组项
    • 通过 Vue.set(vm.items, indexOfItem, newValue)方法
    • vm.$set()
    • 通过 vm.splice()方法增删改
  • 修改数组的长度
    • 通过 vm.splice()方法_
    <div id="app">
        {{items}}
    </div>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                items: ['a','b','c']
            }
        })
        // 下面两种方法不响应
        // app.items[1] = 'z';
        // app.items.length = 2;

        Vue.set(app.items,'1','z') //[ "a", "z", "c" ]
        app.items.splice(3,1,'y') //[ "a", "z", "c", "y" ]
      
      	methods: {
            add: function(){
              this.$set(this.items,'4','d')
            }
          }
      
        app.items.splice(2)//[ "a", "z" ]修改数组长度,只能往短了修改
    </script>
  1. 删除属性值
 var app = new Vue({
 el: "#app",
 data: {
   obj: {
     a: 'a',
     b: 'b'
   }
 },
 methods: {
   del: function(){
     this.$delete(this.obj,'a')
   }
 }
})
Vue.delete(app.obj,'a')
Vue.set(app.obj,'b')

常用的部分全局 API

Vue.set(target,propertyName/index,value)

  • target {Object | Array}
  • propertyName/index {String | number}
//对象
Vue.set(app.obj,b,'b')
//数组
Vue.set(app.items,'1'.'c')

Vue.delete(target,propertyName/index)

Vue.deletet(app.obj,'b')

Vue.delete(app.items,'1')

Vue.filter(id,[definition])

  • id {string}
  • [definition] {Function}

生命周期

从对象的创建、使用到消亡就是对象的生命周期

vue 生命周期的阶段

图来自 vue.js 官方文档

  • 总体

    • 初始化
    • 运行中
    • 销毁
  • 详细

    • 创建
    • 初始化
    • 编译
    • 挂载 DOM
    • 渲染-更新-渲染
    • 销毁

生命周期钩子函数

生命周期钩子的 this 上下文指向调用它的 Vue 实例。

  1. beforeCreate

在实例初始化之后,data 和 watcher 等事件配置前调用。

  1. created

在实例创建完成后被立即调用,当前阶段完成了数据观测 data,property,methods,computed,watcher,但是更改数据不会触发 update 函数。挂载还没开始,$el 不可用。

  1. beforeMount

发生在挂载之前,相关的 render 函数首次被调用,将 template 编译到 render 函数中。虚拟 DOM 创建完毕。

  1. mounted

挂载到实例后调用,可以进行 DOM 操作

  1. beforeUpdate

数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前

  1. updated

由于数据更改导致虚拟 DOM 重新渲染和打补丁,避免在此期间更改数据可能会导致无限渲染

  1. beforeDestroy

实例销毁之前调用,实例仍然可能被调用

  1. destroyed

实例销毁后调用,对应 Vue 实例所有指令都被解绑,监听器被移除。(清除计时器,解除事件绑定等)

keep-alive 拥有独立钩子函数

  1. activated

被 keep-alive 缓存的组件激活时调用。

  1. deactivated

被 keep-alive 缓存的组件停用时调用。

模板语法

插值

文本

“Mustache”语法

<div>
{{message}}
</div>

js 表达式

只支持单个表达式,不支持流程控制和多行表达式

{{number + 1}}
{{ ok? 'yes' : 'no'}}
{{ message.split('').reverse().join('') }}
-----------------------------
<!--这是语句不是表达式-->
{{var a = 1}}
<!--流程控制语句不会生效-->
{{ if(){return message}}}

过滤器

{{data | filter1 | filter2}}
{{ data | formatDate(66,99)}}中的两个参数对应的是过滤器中的第二个和第三个参数
filters: {
    formatDate: function(value,a,b){}
}

实例

<div id="app">
        {{date}}
        {{date|formatDate}}
    </div>
    <script>
        var plusDate = function (value) {
            return value < 10 ? '0' + value : value;
        }
        var app = new Vue({
            el: '#app',
            data: {
                date: new Date()
            },
            filters: {
                formatDate: function (value) {
                    var date = new Date(value);
                    var week = plusDate(date.getDay())
                    var year = date.getFullYear()
                    var month = plusDate(date.getMonth() + 1)
                    var day = plusDate(date.getDate())
                    var hour = plusDate(date.getHours())
                    var minute = plusDate(date.getMinutes())
                    var second = plusDate(date.getSeconds())
                    return week + '--' + year + '--' + month + '--' + day + '--' + hour + '--' + minute + '--' + second;
                }
            },
            mounted: function () {
                var _this = this;
                this.timer = setInterval(function () {
                    _this.date = new Date()
                }, 1000)
            },
            // 在实例销毁前清除计时器,否则可能会造成业务逻辑混乱或者页面卡死
            beforeDestroy: function () {
                if (this.timer) {
                    clearInterval(this.timer)
                }
            }
        })
    </script>

指令

带有 v-前缀的特殊 attribute。

  • 指令 attribute 的预期值是单个 JavaScript 的表达式(v-for 除外)
  • 指令的职责:当表达式的值改变时,将响应地作用于 DOM,快速实现 DOM 操作,渲染

v-text

和{{Mustache}}作用一样

<span v-text='messsage'></span>
<!--作用相同-->
<span>
{{message}}
</span>

v-html

解析 html,按普通 Html 插入不会作为 Vue 模板进行编译

注意:在网站动态渲染任意 html,容易导致 XSS 攻击,永不用在用户提交的内容上。

<div id="app">
    <span v-html='html'></span>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            html: '<spanstyle="color:red;"></pan>'
        }
    })
</script>

v-bind

动态绑定一个或多个 attribute,或一个组件 prop 到表达式,缩写:

<div id='app'>
  <div v-bind:class="className">
</div>

<style>
.red {
    background: red;
    height: 10px;
}
</style>

<script>
        var app = new Vue({
            el: '#app',
            data: {
                className: 'red'
            }
        })
</script>

v-on

绑定监听事件,缩写@

  • 在普通元素上只能监听原生 DOM 事件
  • 在自定义元素组件上,可以监听子组件触发的自定义事件
    <div id='app'>

        <button v-on:click="count">{{countNum}}</button>
    </div>

    <script>
        var app = new Vue({
            el: '#app',
            data: {
                countNum: 0
            },
            methods: {
                count: function () {
                    this.countNum = this.countNum + 1
                }
            }
        })
    </script>

动态参数

举例:

<a :[attributeName]='url'></a>


<div class='app'>
<a :href='url'>1234</a>
</div>
<script>
  new Value({
  el: '.app',
  data: {
  url: 'https://baidu.com'
  }
  })
</script>