Vue知识点1

Vue生命周期

  • beforeCreate在实例初始化后, 数据观测(data observer)之前被调用

  • created 实例已经创建完成之后被调用, 在这一步, 实例已经完成以下配置: 数据观测(data observer), 属性和方法的运算, watch和event回调, 这里没有$el

  • beforeMounted: 在挂载开始之前被调用, 相关render函数被首次调用

  • mounted: el被新创建的vm.$el替换, 并挂载到实例上后首次调用该钩子函数

  • beforeUpdate: 数据更新时调用, 发生在虚拟dom打补丁之前这里适合访问现有的dom, 比如手动移除已添加的事件监听. 该钩子在服务器端渲染期间不被调用, 因为只有初次渲染会在服务端进行
  • updated: 由于数据更改导致虚拟DOM重新渲染和打补丁, 在这之后会调用该钩子
  • beforeDestroy: 实例销毁前被调用, 在这一步, 实例仍然完全可用, 该钩子在服务器渲染端不能被调用
  • destroyed: Vue实例销毁后调用, 调用后Vue实例指示的所有东西会解绑, 所有的事件监听被移除, 子实例被销毁, 服务端渲染期间不会被调用

Vue的通讯的方式

  1. props, emit(最常用的父子通讯方式)

    父组件传入属性, 子组件通过props接受, 就可以在内部thisXXX的方式使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // section父组件
    <template>
    <div class="section">
    <com-article :articles="articleList"></com-article>
    </div>
    </template>

    <script>
    import comArticle from './test/article.vue'
    export default {
    name: 'HelloWorld',
    components: { comArticle },
    data() {
    return {
    articleList: ['红楼梦', '西游记', '三国演义']
    }
    }
    }
    </script>


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 子组件 article.vue
    <template>
    <div>
    <span v-for="(item, index) in articles" :key="index">{{item}}</span>
    </div>
    </template>

    <script>
    export default {
    props: ['articles']
    }
    </script>

    总结: prop只可以从上一级组件传递到下一级组件(父子组件), 即所谓的单向数据流. 而且prop只读, 不可被修改, 修改会失效并警告

    子组件向父组件传值

    对于$emit, $emit绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件, 父组件通过v-on监听并接受参数. 通过一个例子, 点击页面渲染出来到的ariticle的item, 父组件中显示数组的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 父组件中
<template>
<div class="section">
<com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
<p>{{currentIndex}}</p>
</div>
</template>

<script>
import comArticle from './test/article.vue'
export default {
name: 'HelloWorld',
components: { comArticle },
data() {
return {
currentIndex: -1,
articleList: ['红楼梦', '西游记', '三国演义']
}
},
methods: {
onEmitIndex(idx) {
this.currentIndex = idx
}
}
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
</div>
</template>

<script>
export default {
props: ['articles'],
methods: {
emitIndex(index) {
this.$emit('onEmitIndex', index)
}
}
}
</script>

ref和refs

如果在普通的DOM元素上使用, 引用指向的就是DOM元素, 如果引用在子组件上, 就指向子组件实例, 可以通过实例直接调用组件的方法或数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 子组件 A.vue

export default {
data () {
return {
name: 'Vue.js'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 父组件 app.vue

<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.name); // Vue.js
comA.sayHello(); // hello
}
}
</script>

eventBus

eventbus又称事件总线, 在vue中可以使用它来作为沟通桥梁的概念, 组件共用相同的事件中心, 可以向该中心注册发送事件或接收事件, 所有组件都可以通知其他组件

缺点是项目较大难以维护

  1. 初始化

    1
    2
    3
    // event-bus.js
    import Vue from 'vue'
    export const EventBus = new Vue()
  2. 发送事件

    加入有两个组件: additionNum, showNum, 这两个组件可以是兄弟组件, 也可以是父子组件, 这里以兄弟组件为例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <template>
    <div>
    <show-num-com></show-num-com>
    <addition-num-com></addition-num-com>
    </div>
    </template>

    <script>
    import showNumCom from './showNum.vue'
    import additionNumCom from './additionNum.vue'
    export default {
    components: { showNumCom, additionNumCom }
    }
    </script>

    发送事件组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // addtionNum.vue 中发送事件
    <template>
    <div>
    <button @click="additionHandle">+加法器
    </button>
    </div>
    </template>
    <script>
    import {EventBus} from './event-bus.js'
    console.log(EventBus)
    export default{
    data(){
    return {
    num: 1
    }
    },
    methods: {
    additionHandle(){
    EventBus.$emit('addition', {
    num: this.num ++
    })
    }
    }
    }
    </script>

    接受事件组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // showNum.vue 组件
    <template>
    <div>
    计算和: {{count}}
    </div>
    </template>
    <script>
    import {EventBus} from './event-bus.js'
    export default{
    data(){
    return {
    count: 0
    }
    },

    methods(){
    EventBus.$on('addition', param => {
    this.count = this.count + param.num;
    })
    }
    }
    </script>
    Vuex

    Vuex是一个专门为Vue.js应用程序开发的状态管理模式. 它采用集中存储管理应用的所有组件和方法, 并以相应的规则保证状态以一种可以预测的方式发生改变. Vuex解决了多个视图依赖同一状态来自不同视图的行为需要变更同一状态的问题, 将开发者的经历聚集在数据的更新而不是数据在组件间的传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <template>
    <div id="app">
    <ChildA/>
    <ChildB/>
    </div>
    </template>

    <script>
    import ChildA from './components/ChildA' // 导入A组件
    import ChildB from './components/ChildB' //
    </script>

vue懒加载

为什么要懒加载

Vue是单页面应用, 如果没有应用懒加载, 运用webpack打包的文件很大, 造成进入首页时, 需要加载的内容过多, 白屏时间很长, 懒加载有利于页面划分, 需要的时候加载页面, 减少首页加载压力, 减少加载用时

一句话: 进入首页不用一次加载过多资源, 造成用时过长

懒加载

也叫延迟加载, 需要的时候再去加载, 随用随加载

根据功能可以划分为图片懒加载和组件懒加载

图片懒加载

一张图片就是一个img标签, 浏览器是否发起图片请求是根据的src属性, 实现懒加载的关键是, 在图片没有进入可视区域时, 先不给的src值, 这样浏览区就不会发送请求了, 等待图片进入可视区域再给src赋值

实现懒加载有四个步骤:

  1. 加载loading图片
  2. 判断哪些图片要加载
  3. 隐形加载图片
  4. 替换真图片

加载loading图片

加载loading图片在html就实现的

1
2
3
4
<div>
// src存放的是伪图片 等待的图片 data-src是自定义属性, 存放的是真实的图片
<img class="lazy" src="img/loading.gif" data-src="img/pic1.jpg"></img>
</div>

判断图片进入可视区域

用到了onload事件, onload事件会在页面和图像加载完成后立即发生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 懒加载实现原理
function lazyload(imgs){
// 可视区域高度
var h = window.innerHeight;
//滚动区域高度
var s = document.documentElement.scrollTop || document.body.scrollTop;
for(var i=0;i<imgs.length;i++){
//图片距离顶部的距离大于可视区域和滚动区域之和时懒加载
if ((h+s) > getTop(imgs[i])) {
// 真实情况是页面开始有2秒空白,所以使用setTimeout定时2s
(function(i){
setTimeout(function(){
// 不加立即执行函数i会等于9
// 隐形加载图片或其他资源,
//创建一个临时图片,这个图片在内存中不会到页面上去。实现隐形加载
var temp = new Image();
temp.src = imgs[i].getAttribute('data-src');//只会请求一次
// onload判断图片加载完毕,真图片加载完毕,再赋值给dom节点
temp.onload = function(){
// 获取自定义属性data-src,用真图片替换假图片
imgs[i].src = imgs[i].getAttribute('data-src')
}
},2000)
})(i)
}
}
}

隐形加载替换真图片

  • 当图片的offsetTop 小于 scrollTop + window.innerHeight时, 就会开始加载
  • 隐形加载: 首先创建一个临时图片, 图片不会到页面上, 而是保存在内存中, 然后发起请求, 请求完成后, 把它赋值给src属性, 这样就完成了图片替换

路由懒加载 和 组件懒加载

路由懒加载

vue异步实现懒加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: resolve => (require(['@/components/HelloWorld', resolve]))
}
]
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)
const HelloWorld = () => import('@/components/HelloWorld')
export default new Router({
routers: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
  1. 原来的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="hello">
<One-com></One-com>
1234
</div>
</template>
<script>
import One from './one'
export default{
components: {
'One-com': One
},
data() {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
  1. const方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="hello">
<One-com></One-com>
1234
</div>
</template>
<script>
const One = () => import('./One')
export default{
components: {
'One-com': One
},
data() {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
  1. 异步方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="hello">
<One-com></One-com>
1234
</div>
</template>
<script>
const One = () => import('./One')
export default{
components: {
'One-com': resolve => (['./one'], resolve);
},
data() {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>

Vue响应式原理

整体思路 是数据劫持 + 观察者模式

对象内部通过defineReactive方法, 使用Object.defineProperty将属性进行劫持(只会劫持已经存在的属性), 数组则通过数组重写方式实现的. 当页面使用对应属性时, 每个属性都有自己的dep属性, 存放它所收集的watcher(依赖收集), 当属性变化后通知自己对应的watcher去派发更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Observer{
constructor(value){
this.walk(value);
}
walk(data){
// 对象上所有属性一次进行观测
let keys = Object.keys(data);
for(let i = 0; i < keys.length; i++){
let key = keys[i];
let value = data[key];
defineReactive(data, key, value);
}
}
}
// 数据劫持核心
function defineReactive(data, key, value){
observe(value); // 递归
Object.defineProperty(data, key, {
get() {
console.log('获取值');
return value;
},
set(newValue){
console.log('设置值');
value = newValue;
},
});
}
export function observe(value){
// 如果传过来是对象或数组, 进行属性劫持
if(Object.prototype.toString.call(value) === 'object Object' || Array.isArray(value)) {
return new Oberver(value);
}
}