准备

什么是Vue.js

  • Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架(React除了开发网站,还可以开发手机App, Vue语法也是可以用于进行手机App开发的,需要借助于Weex)

  • Vue.js 是前端的主流框架之一,和Angular.js、React.js 一起,并成为前端三大主流框架!

  • Vue.js 是一套构建用户界面的框架,只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。(Vue有配套的第三方类库,可以整合起来做大型项目的开发)

  • 前端的主要工作?主要负责MVC中的V这一层;主要工作就是和界面打交道,来制作前端页面效果;

为什么要学习流行框架

  • 企业为了提高开发效率:在企业中,时间就是效率,效率就是金钱;
  • 企业中,使用框架,能够提高开发的效率;
  • 提高开发效率的发展历程:原生JS -> Jquery之类的类库 -> 前端模板引擎 -> Angular.js / Vue.js(能够帮助我们减少不必要的DOM操作;提高渲染效率;双向数据绑定的概念【通过框架提供的指令,我们前端程序员只需要关心数据的业务逻辑,不再关心DOM是如何渲染的了】)

  • 在Vue中,一个核心的概念,就是让用户不再操作DOM元素,解放了用户的双手,让程序员可以更多的时间去关注业务逻辑;

  • 增强自己就业时候的竞争力, 人无我有,人有我优

框架和库的区别

  • 框架:是一套完整的解决方案;对项目的侵入性较大,项目如果需要更换框架,则需要重新架构整个项目。

    • node 中的 express;
  • 库(插件):提供某一个小功能,对项目的侵入性较小,如果某个库无法完成某些需求,可以很容易切换到其它库实现需求。

    • 从Jquery 切换到 Zepto
    • 从 EJS 切换到 art-template

MVC 与MV VM

  • MVC 是后端的分层开发概念;

  • MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel

  • 为什么有了MVC还要有MVVM

  • 通过vm对象来代理data对象中所有属性的操作

    • 好处: 更方便的操作data中的数据
  • 基本实现流程

    • 通过Object.defineProperty()给vm添加与data对象的属性对应的属性描述符
    • 所有添加的属性都包含getter/setter
    • 在getter/setter内部去操作data中对应的属性数据

基本使用

代码结构

  • 基本结构如下

    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
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 1. 导入Vue的包 -->
    <script src="./lib/vue-2.6.10.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js"></script>
    </head>

    <body>
    <!-- 将来 new 的Vue实例,会控制这个 元素中的所有内容 -->
    <!-- Vue 实例所控制的这个元素区域,就是我们的 V -->
    <div id="app">
    <p>{{ msg }}</p>
    </div>

    <script>
    // 2. 创建一个Vue的实例
    // 当我们导入包之后,在浏览器的内存中,就多了一个 Vue 构造函数
    // 注意:我们 new 出来的这个 vm 对象,就是我们 MVVM中的 VM调度者
    var vm = new Vue({
    el: "#app", // 表示,当前我们 new 的这个 Vue 实例,要控制页面上的哪个区域
    // 这里的 data 就是 MVVM中的 M,专门用来保存 每个页面的数据的
    data: {
    // data 属性中,存放的是 el 中要用到的数据
    msg: "欢迎学习Vue", // 通过 Vue 提供的指令,很方便的就能把数据渲染到页面上,程序员不再手动操作DOM元素了【前端的Vue之类的框架,不提倡我们去手动操作DOM元素了】
    },
    });
    </script>
    </body>
    </html>

文本展示

  • 插值表达式: 刷新页面过程中会有表达式闪烁的问题

    1
    2
    <!-- {{msg2}}会先被渲染出来后替换掉 -->
    <div>{{msg2}}</div>
  • v-cloak + 插值表达式解决闪烁问题

    1
    <p v-cloak>++++++++ {{ msg }} ----------</p>
  • v-text:默认 v-text 是没有闪烁问题的

    1
    2
    <!-- v-text会覆盖元素中原本的内容,但是 插值表达式  只会替换自己的这个占位符,不会把整个元素的内容清空 -->
    <h4 v-text="msg">==================</h4>
  • v-html: 会将数据中的标签进行渲染

    1
    <div v-html="msg2">1212112</div>

属性/事件绑定

  • v-bind 是 Vue中,提供的用于绑定属性的指令。 v-bind: 指令可以被简写为 :要绑定的属性

  • v-bind 中,可以写合法的JS表达式

  • Vue 中提供了 v-on: 事件绑定机制 ,可以简写为@事件名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <input type="button" value="按钮" v-bind:title="mytitle + '123'" />
    <!-- v-bind Vue提供的属性绑定机制 缩写是 : -->
    <input
    type="button"
    value="按钮"
    :title="mytitle + '123'"
    v-on:click="alert('hello')"
    />
    <!-- v-on Vue提供的事件绑定机制 缩写是 @ -->
    <input *type*="button" *value*="按钮" @click="show" />
  • 跑马灯效果

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 1. 导入Vue包 -->
    <script src="./lib/vue-2.4.0.js"></script>
    </head>

    <body>
    <!-- 2. 创建一个要控制的区域 -->
    <div id="app">
    <input type="button" value="浪起来" @click="lang" />
    <input type="button" value="低调" @click="stop" />

    <h4>{{ msg }}</h4>
    </div>

    <script>
    // 注意:在 VM实例中,如果想要获取 data 上的数据,或者 想要调用 methods 中的 方法,必须通过 this.数据属性名 或 this.方法名 来进行访问,这里的this,就表示 我们 new 出来的 VM 实例对象
    var vm = new Vue({
    el: "#app",
    data: {
    msg: "猥琐发育,别浪~~!",
    intervalId: null, // 在data上定义 定时器Id
    },
    methods: {
    lang() {
    // console.log(this.msg)
    // 获取到头的第一个字符
    // this

    if (this.intervalId != null) return;

    this.intervalId = setInterval(() => {
    var start = this.msg.substring(0, 1);
    // 获取到 后面的所有字符
    var end = this.msg.substring(1);
    // 重新拼接得到新的字符串,并赋值给 this.msg
    this.msg = end + start;
    }, 400);

    // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去;【好处:程序员只需要关心数据,不需要考虑如何重新渲染DOM页面】
    },
    stop() {
    // 停止定时器
    clearInterval(this.intervalId);
    // 每当清除了定时器之后,需要重新把 intervalId 置为 null
    this.intervalId = null;
    },
    },
    });

    // 分析:
    // 1. 给 【浪起来】 按钮,绑定一个点击事件 v-on @
    // 2. 在按钮的事件处理函数中,写相关的业务逻辑代码:拿到 msg 字符串,然后 调用 字符串的 substring 来进行字符串的截取操作,把 第一个字符截取出来,放到最后一个位置即可;
    // 3. 为了实现点击下按钮,自动截取的功能,需要把 2 步骤中的代码,放到一个定时器中去;
    </script>
    </body>
    </html>

事件修饰符:

  • .stop 阻止冒泡

  • .prevent 阻止默认事件

  • .capture 添加事件侦听器时使用事件捕获模式

  • .self 只当事件在该元素本身(比如不是子元素)触发时触发回调

  • .once 事件只触发一次

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    <body>
    <div id="app">
    <!-- 使用 .stop 阻止冒泡 -->
    <div class="inner" @click="div1Handler">
    <input type="button" value="戳他" @click.stop="btnHandler" />
    </div>

    <!-- 使用 .prevent 阻止默认行为 -->
    <a href="http://www.baidu.com" @click.prevent="linkClick"
    >有问题,先去百度</a
    >

    <!-- 使用 .capture 实现捕获触发事件的机制 -->
    <div class="inner" @click.capture="div1Handler">
    <input type="button" value="戳他" @click="btnHandler" />
    </div>

    <!-- 使用 .self 实现只有点击当前元素时候,才会触发事件处理函数 -->
    <div class="inner" @click="div1Handler">
    <input type="button" value="戳他" @click="btnHandler" />
    </div>

    <!-- 使用 .once 只触发一次事件处理函数 -->
    <a href="http://www.baidu.com" @click.prevent.once="linkClick"
    >有问题,先去百度</a
    >

    <!-- 演示: .stop 和 .self 的区别 -->
    <div class="outer" @click="div2Handler">
    <div class="inner" @click="div1Handler">
    <input type="button" value="戳他" @click.stop="btnHandler" />
    </div>
    </div>

    <!-- .self 只会阻止自己身上冒泡行为的触发,并不会真正阻止 冒泡的行为 -->
    <div class="outer" @click="div2Handler">
    <div class="inner" @click.self="div1Handler">
    <input type="button" value="戳他" @click="btnHandler" />
    </div>
    </div>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {},
    methods: {
    div1Handler() {
    console.log("这是触发了 inner div 的点击事件");
    },
    btnHandler() {
    console.log("这是触发了 btn 按钮 的点击事件");
    },
    linkClick() {
    console.log("触发了连接的点击事件");
    },
    div2Handler() {
    console.log("这是触发了 outer div 的点击事件");
    },
    },
    });
    </script>
    </body>

双向数据绑定

  • v-bind 只能实现数据的单向绑定,从 M 自动绑定到 V, 无法实现数据的双向绑定

    1
    <input type="text" v-bind:value="msg" style="width:100%;" />
  • 使用 v-model 指令,可以实现 表单元素和 Model 中数据的双向数据绑定

    1
    2
    <!-- 注意: v-model 只能运用在 表单元素中 -->
    <!-- input(radio, text, address, email....) select checkbox textarea -->
  • 简易计算器案例

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script src="./lib/vue-2.4.0.js"></script>
    </head>

    <body>
    <div id="app">
    <input type="text" v-model="n1" />
    <select v-model="opt">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="*">*</option>
    <option value="/">/</option>
    </select>
    <input type="text" v-model="n2" />
    <input type="button" value="=" @click="calc" />
    <input type="text" v-model="result" />
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    n1: 0,
    n2: 0,
    result: 0,
    opt: "+",
    },
    methods: {
    calc() {
    // 计算器算数的方法
    // 逻辑:
    /* switch (this.opt) {
    case '+':
    this.result = parseInt(this.n1) + parseInt(this.n2)
    break;
    case '-':
    this.result = parseInt(this.n1) - parseInt(this.n2)
    break;
    case '*':
    this.result = parseInt(this.n1) * parseInt(this.n2)
    break;
    case '/':
    this.result = parseInt(this.n1) / parseInt(this.n2)
    break;
    } */

    // 注意:这是投机取巧的方式,正式开发中,尽量少用
    var codeStr =
    "parseInt(this.n1) " + this.opt + " parseInt(this.n2)";
    this.result = eval(codeStr);
    },
    },
    });
    </script>
    </body>
    </html>

使用class样式

  • 示例如下:

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <head>
    <script src="./lib/vue-2.4.0.js"></script>
    <style>
    .red {
    color: red;
    }

    .thin {
    font-weight: 200;
    }

    .italic {
    font-style: italic;
    }

    .active {
    letter-spacing: 0.5em;
    }
    </style>
    </head>

    <div id="app">
    <h1 class="red thin">这是一个很大很大的H1,大到你无法想象!!!</h1>
    <!-- 第一种使用方式,直接传递一个数组,注意: 这里的 class 需要使用 v-bind 做数据绑定 -->
    <h1 :class="['thin', 'italic']">
    这是一个很大很大的H1,大到你无法想象!!!
    </h1>
    <!-- 在数组中使用三元表达式 -->
    <h1 :class="['thin', 'italic', flag?'active':'']">
    这是一个很大很大的H1,大到你无法想象!!!
    </h1>
    <!-- 在数组中使用 对象来代替三元表达式,提高代码的可读性 -->
    <h1 :class="['thin', 'italic', {'active':flag} ]">
    这是一个很大很大的H1,大到你无法想象!!!
    </h1>
    <!-- 在为 class 使用 v-bind 绑定 对象的时候,对象的属性是类名,由于 对象的属性可带引号,也可不带引号,所以 这里我没写引号; 属性的值 是一个标识符 -->
    <h1 :class="classObj">这是一个很大很大的H1,大到你无法想象!!!</h1>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    flag: true,
    classObj: { red: true, thin: true, italic: false, active: false },
    },
    methods: {},
    });
    </script>
  1. 数组

    1
    <h1 :class="['red', 'thin']">这是一个邪恶的H1</h1>
  2. 数组中使用三元表达式

    1
    <h1 :class="['red', 'thin', isactive?'active':'']">这是一个邪恶的H1</h1>
  3. 数组中嵌套对象class:boolean

    1
    <h1 :class="['red', 'thin', {'active': isactive}]">这是一个邪恶的H1</h1>
  4. 直接使用对象

    1
    2
    3
    <h1 :class="{red:true, italic:true, active:true, thin:true}">
    这是一个邪恶的H1
    </h1>

使用内联样式

  1. 直接在元素上通过 :style 的形式,书写样式对象

    1
    <h1 :style="{color: 'red', 'font-size': '40px'}">这是一个善良的H1</h1>
  2. 将样式对象,定义到 data 中,并直接引用到 :style

    1
    2
    3
    4
    5
    data: { h1StyleObj: { color: 'red', 'font-size': '40px', 'font-weight': '200'
    } }

    <!-- 在元素中,通过属性绑定的形式,将样式对象应用到元素中: -->
    <h1 :style="h1StyleObj">这是一个善良的H1</h1>
  3. :style 中通过数组,引用多个 data 上的样式对象

    1
    2
    3
    4
    5
    data: { h1StyleObj: { color: 'red', 'font-size': '40px', 'font-weight': '200'
    }, h1StyleObj2: { fontStyle: 'italic' } }

    <!-- 在元素中,通过属性绑定的形式,将样式对象应用到元素中: -->
    <h1 :style="[h1StyleObj, h1StyleObj2]">这是一个善良的H1</h1>

迭代指令v-for

  1. 迭代普通数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <ul>
    <li v-for="(item, i) in list">索引值:{{i}} --- 每一项:{{item}}></li>
    </ul>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    list: [1, 2, 3, 4, 5, 6],
    },
    methods: {},
    });
    </script>
  2. 迭代对象数组,索引从0开始

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!-- 循环遍历对象身上的属性 -->
    <div id="app">
    <p v-for="(user, i) in list">
    Id:{{ user.id }} --- 名字:{{ user.name }} --- 索引:{{i}}
    </p>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    list: [
    { id: 1, name: "zs1" },
    { id: 2, name: "zs2" },
    { id: 3, name: "zs3" },
    { id: 4, name: "zs4" },
    ],
    },
    methods: {},
    });
    </script>
  3. 迭代对象,索引从0开始

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <div id="app">
    <!-- 注意:在遍历对象身上的键值对的时候, 除了 有 val key ,在第三个位置还有 一个 索引 -->
    <p v-for="(val, key, i) in user">
    值是: {{ val }} --- 键是: {{key}} -- 索引: {{i}}
    </p>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    user: {
    id: 1,
    name: "托尼·屎大颗",
    gender: "男",
    },
    },
    methods: {},
    });
    </script>
  4. 迭代数字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <div id="app">
    <!-- in 后面我们放过 普通数组,对象数组,对象, 还可以放数字 -->
    <!-- 注意:如果使用 v-for 迭代数字的话,前面的 count 值从 1 开始 -->
    <p v-for="count in 10">这是第 {{ count }} 次循环</p>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {},
    methods: {},
    });
    </script>

v-for与 key

  • 2.2.0+ 的版本里,当在组件中使用 v-for 时,key 现在是必须的。

  • 当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

  • 为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。

  • 注意: v-for 循环的时候,key 属性只能使用 number获取string

  • 注意: key 在使用的时候,必须使用 v-bind 属性绑定的形式,指定 key 的值

    1
    2
    3
    <p v-for="item in list" :key="item.id">
    <input type="checkbox" />{{item.id}} --- {{item.name}}
    </p>

v-if和v-show

  • 一般来说,v-if 有更高的切换消耗,v-show 有更高的初始渲染消耗。

  • 因此,如果需要频繁切换 v-show 较好,如果在运行时条件不大可能改变 v-if 较好。

    1
    2
    3
    4
    5
    6
    7
    <!-- v-if 的特点:每次都会重新删除或创建元素 -->
    <!-- v-show 的特点: 每次不会重新进行DOM的删除和创建操作,只是切换了元素的 display:none 样式 -->

    <!-- 如果元素涉及到频繁的切换,最好不要使用 v-if, 而是推荐使用 v-show -->
    <!-- 如果元素可能永远也不会被显示出来被用户看到,则推荐使用 v-if -->
    <h3 v-if="flag">这是用v-if控制的元素</h3>
    <h3 v-show="flag">这是用v-show控制的元素</h3>

CRUD案例

添加操作

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <div class="panel-body form-inline">
    <label>
    Id:
    <input type="text" class="form-control" v-model="id" />
    </label>

    <label>
    Name:
    <input type="text" class="form-control" v-model="name" />
    </label>

    <!-- 在Vue中,使用事件绑定机制,为元素指定处理函数的时候,如果加了小括号,就可以给函数传参了 -->
    <input type="button" value="添加" class="btn btn-primary" @click="add()" />

    <label>
    搜索名称关键字:
    <input type="text" class="form-control" v-model="keywords" />
    </label>
    </div>
  • 脚本如下

    1
    2
    3
    4
    5
    add() {
    var car = { id: this.id, name: this.name, ctime: new Date() }
    this.list.push(car)
    this.id = this.name = ''
    }

删除操作

  • 脚本如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    del(id) {
    /* this.list.some((item, i) => {
    if (item.id == id) {
    this.list.splice(i, 1)
    // 在 数组的 some 方法中,如果 return true,就会立即终止这个数组的后续循环
    return true;
    }
    }) */

    var index = this.list.findIndex(item => {
    if (item.id == id) {
    return true;
    }
    })

    // console.log(index)
    this.list.splice(index, 1)
    }

根据条件查找

  • 1.x 版本中的filterBy指令,在2.x中已经被废除:filterBy - 指令

    1
    2
    3
    4
    5
    6
    7
    8
    <tr v-for="item in list | filterBy searchName in 'name'">
    <td>{{item.id}}</td>
    <td>{{item.name}}</td>
    <td>{{item.ctime}}</td>
    <td>
    <a href="#" @click.prevent="del(item.id)">删除</a>
    </td>
    </tr>
  • 在2.x版本中手动实现筛选的方式

    • 筛选框绑定到 VM 实例中的 searchName 属性:

      1
      2
      <hr />
      输入筛选名称: <input type="text" v-model="searchName" />
    • 在使用 v-for 指令循环每一行数据的时候,不再直接 item in list,而是 in 一个 过滤的methods 方法,同时,把过滤条件searchName传递进去:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <tbody>
      <tr v-for="item in search(searchName)">
      <td>{{item.id}}</td>
      <td>{{item.name}}</td>
      <td>{{item.ctime}}</td>
      <td>
      <a href="#" @click.prevent="del(item.id)">删除</a>
      </td>
      </tr>
      </tbody>
    • search 过滤方法中,使用 数组的 filter 方法进行过滤:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      search(name) {
      this.list.forEach(item => {
      if (item.name.indexOf(keywords) != -1) {
      newList.push(item)
      }
      })
      return newList

      // 注意: forEach some filter findIndex 这些都属于数组的新方法,
      // 都会对数组中的每一项,进行遍历,执行相关的操作;
      return this.list.filter(item => {
      // 注意 : ES6中,为字符串提供了一个新方法,叫做 String.prototype.includes('要包含的字符串')
      // 如果包含,则返回 true ,否则返回 false
      // contain
      if (item.name.includes(keywords)) {
      return item
      }
      })
      }

文本格式化

  • 概念:Vue.js 允许你自定义过滤器,可被用作一些常见的文本格式化。过滤器可以用在两个地方:mustache 插值和 v-bind 表达式。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示;

  • 注意:当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用

  • Vue.filter('过滤器的名称', function(){}):过滤器中的 function

    • 第一个参数,已经被规定死了,永远都是 过滤器 管道符前面 传递过来的数据

      1
      2
      3
      Vue.filter('过滤器的名称', function (data) {
      return data + '123'
      })

私有过滤器

  • HTML元素:

    1
    <td>{{item.ctime | dataFormat('yyyy-mm-dd')}}</td>
  • 私有 filters 定义方式:

    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
    var vm2 = new Vue({
    el: '#app2',
    data: {
    dt: new Date()
    },
    methods: {},
    filters: {
    // 定义私有过滤器 过滤器有两个 条件 【过滤器名称 和 处理函数】
    // 过滤器调用的时候,采用的是就近原则,如果私有过滤器和全局过滤器名称一致了,这时候 优先调用私有过滤器
    dateFormat: function (dateStr, pattern = '') {
    // 根据给定的时间字符串,得到特定的时间
    var dt = new Date(dateStr)

    // yyyy-mm-dd
    var y = dt.getFullYear()
    var m = (dt.getMonth() + 1).toString().padStart(2, '0')
    var d = dt.getDate().toString().padStart(2, '0')

    if (pattern.toLowerCase() === 'yyyy-mm-dd') {
    return `${y}-${m}-${d}`
    } else {
    //使用ES6中的字符串新方法 String.prototype.padStart(maxLength, fillString='')
    //或 String.prototype.padEnd(maxLength, fillString='')来填充字符串进行补0;
    var hh = dt.getHours().toString().padStart(2, '0')
    var mm = dt.getMinutes().toString().padStart(2, '0')
    var ss = dt.getSeconds().toString().padStart(2, '0')
    return `${y}-${m}-${d} ${hh}:${mm}:${ss} ~~~~~~~`
    }
    }
    }
    })

全局过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
// 全局的过滤器, 进行时间的格式化
// 所谓的全局过滤器,就是所有的VM实例都共享的
Vue.filter('dateFormat', function (dateStr, pattern = "") {
// 根据给定的时间字符串,得到特定的时间
var dt = new Date(dateStr)

// yyyy-mm-dd
var y = dt.getFullYear()
var m = dt.getMonth() + 1
var d = dt.getDate()
if (pattern.toLowerCase() === 'yyyy-mm-dd') {
return `${y}-${m}-${d}`
} else {
var hh = dt.getHours()
var mm = dt.getMinutes()
var ss = dt.getSeconds()
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
})
</script>

键盘修饰符

1.x中自定义键盘修饰符【了解即可】

1
Vue.directive('on').keyCodes.f2 = 113;

2.x中自定义键盘修饰符

  1. 通过Vue.config.keyCodes.名称 = 按键值来自定义案件修饰符的别名:

    1
    Vue.config.keyCodes.f2 = 113;
  2. 使用自定义的按键修饰符:

    1
    <input type="text" v-model="name" @keyup.f2="add">

自定义指令

  • 使用 Vue.directive() 定义全局的指令
    • 参数1 : 指令的名称,注意,在定义的时候,指令的名称前面,不需要加 v- 前缀, 在调用的时候,必须 在指令名称前 加上v- 前缀来进行调用
    • 参数2: 是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
  1. 自定义全局和局部的 自定义指令:

    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
    // 定义全局的指令  v-focus
    Vue.directive("focus", {
    bind: function (el) {
    // 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,只执行一次
    // 注意: 在每个 函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象
    // 在元素 刚绑定了指令的时候,还没有 插入到 DOM中去,这时候,调用 focus 方法没有作用
    // 因为,一个元素,只有插入DOM之后,才能获取焦点
    // el.focus()
    },
    inserted: function (el) {
    // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
    el.focus();
    // 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效
    },
    updated: function (el) {
    // 当VNode更新的时候,会执行 updated, 可能会触发多次
    },
    });

    // 自定义一个 设置字体颜色的 指令
    Vue.directive("color", {
    // 样式,只要通过指令绑定给了元素,不管这个元素有没有被插入到页面中去,这个元素肯定有了一个内联的样式
    // 将来元素肯定会显示到页面中,这时候,浏览器的渲染引擎必然会解析样式,应用给这个元素
    bind: function (el, binding) {
    // el.style.color = 'red'
    // console.log(binding.name)
    // 和样式相关的操作,一般都可以在 bind 执行

    // console.log(binding.value)
    // console.log(binding.expression)

    el.style.color = binding.value;
    },
    });
  2. 自定义指令的使用方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 注意: Vue中所有的指令,在调用的时候,都以 v- 开头 -->
    <input
    type="text"
    class="form-control"
    v-model="keywords"
    id="search"
    v-focus
    v-color="'green'"
    />

Vue 1.x 中 自定义元素指令

  • 【已废弃,了解即可】

    1
    2
    3
    4
    5
    Vue.elementDirective("red-color", {
    bind: function () {
    this.el.style.color = "red";
    },
    });
  • 使用方式:

    1
    <red-color>1232</red-color>

vue实例的生命周期

  • 什么是生命周期:从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期!

  • 生命周期钩子:就是生命周期事件的别名而已;

  • 生命周期钩子 = 生命周期函数 = 生命周期事件

  • 主要的生命周期函数分类:

  • 创建期间的生命周期函数:

    • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
    • created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
    • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
    • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
  • 运行期间的生命周期函数:

    • beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
    • updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
  • 销毁期间的生命周期函数:

    • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
    • destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
  • 代码体验

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="./lib/vue-2.4.0.js"></script>
    </head>

    <body>
    <div id="app">
    <input type="button" value="修改msg" @click="msg='No'">
    <h3 id="h3">{{ msg }}</h3>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
    msg: 'ok'
    },
    methods: {
    show() {
    console.log('执行了show方法')
    }
    },
    beforeCreate() { // 这是我们遇到的第一个生命周期函数,表示实例完全被创建出来之前,会执行它
    // console.log(this.msg)
    // this.show()
    // 注意: 在 beforeCreate 生命周期函数执行的时候,data 和 methods 中的 数据都还没有没初始化
    },
    created() { // 这是遇到的第二个生命周期函数
    // console.log(this.msg)
    // this.show()
    // 在 created 中,data 和 methods 都已经被初始化好了!
    // 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
    },
    beforeMount() { // 这是遇到的第3个生命周期函数,表示 模板已经在内存中编辑完成了,但是尚未把 模板渲染到 页面中
    // console.log(document.getElementById('h3').innerText)
    // 在 beforeMount 执行的时候,页面中的元素,还没有被真正替换过来,只是之前写的一些模板字符串
    },
    mounted() { // 这是遇到的第4个生命周期函数,表示,内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了
    // console.log(document.getElementById('h3').innerText)
    // 注意: mounted 是 实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,此时,如果没有其它操作的话,这个实例,就静静的 躺在我们的内存中,一动不动
    },


    // 接下来的是运行中的两个事件
    beforeUpdate() { // 这时候,表示 我们的界面还没有被更新【数据被更新了吗? 数据肯定被更新了】
    /* console.log('界面上元素的内容:' + document.getElementById('h3').innerText)
    console.log('data 中的 msg 数据是:' + this.msg) */
    // 得出结论: 当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时 data 数据是最新的,页面尚未和 最新的数据保持同步
    },
    updated() {
    console.log('界面上元素的内容:' + document.getElementById('h3').innerText)
    console.log('data 中的 msg 数据是:' + this.msg)
    // updated 事件执行的时候,页面和 data 数据已经保持同步了,都是最新的
    }
    });
    </script>
    </body>

    </html>

vue-resource发请求

  • 实现 get, post, jsonp请求

  • 除了 vue-resource 之外,还可以使用 axios 的第三方包实现实现数据的请求

  • 常见的数据请求类型? get post jsonp

  • JSONP的实现原理

    • 由于浏览器的安全性限制,不允许AJAX访问 协议不同、域名不同、端口号不同的 数据接口,浏览器认为这种访问不安全;
    • 可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP(注意:根据JSONP的实现原理,知晓,JSONP只支持Get请求);
  • 具体实现过程:

    • 先在客户端定义一个回调方法,预定义对数据的操作;
    • 再把这个回调方法的名称,通过URL传参的形式,提交到服务器的数据接口;
    • 服务器数据接口组织好要发送给客户端的数据,再拿着客户端传递过来的回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端去解析执行;
    • 客户端拿到服务器返回的字符串之后,当作Script脚本去解析执行,这样就能够拿到JSONP的数据了;
  • 带大家通过 Node.js ,来手动实现一个JSONP的请求例子;

    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
    35
    const http = require("http");
    // 导入解析 URL 地址的核心模块
    const urlModule = require("url");

    const server = http.createServer();
    // 监听 服务器的 request 请求事件,处理每个请求
    server.on("request", (req, res) => {
    const url = req.url;

    // 解析客户端请求的URL地址
    var info = urlModule.parse(url, true);

    // 如果请求的 URL 地址是 /getjsonp ,则表示要获取JSONP类型的数据
    if (info.pathname === "/getjsonp") {
    // 获取客户端指定的回调函数的名称
    var cbName = info.query.callback;
    // 手动拼接要返回给客户端的数据对象
    var data = {
    name: "zs",
    age: 22,
    gender: "男",
    hobby: ["吃饭", "睡觉", "运动"],
    };
    // 拼接出一个方法的调用,在调用这个方法的时候,把要发送给客户端的数据,序列化为字符串,作为参数传递给这个调用的方法:
    var result = `${cbName}(${JSON.stringify(data)})`;
    // 将拼接好的方法的调用,返回给客户端去解析执行
    res.end(result);
    } else {
    res.end("404");
    }
    });

    server.listen(3000, () => {
    console.log("server running at http://127.0.0.1:3000");
    });

配置步骤

  • 直接在页面中,通过script标签,引入 vue-resource 的脚本文件;

  • 注意:引用的先后顺序是:先引用 Vue 的脚本文件,再引用 vue-resource 的脚本文件;

    1
    2
    3
    4
    <script src="./lib/vue-2.4.0.js"></script>
    <!-- 注意:vue-resource 依赖于 Vue,所以先后顺序要注意 -->
    <!-- this.$http.jsonp -->
    <script src="./lib/vue-resource-1.3.4.js"></script>

发送请求

  • html

    1
    2
    3
    4
    5
    <div id="app">
    <input type="button" value="get请求" @click="getInfo">
    <input type="button" value="post请求" @click="postInfo">
    <input type="button" value="jsonp请求" @click="jsonpInfo">
    </div
  • 脚本

    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
    35
    36
    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {
    // 发起get请求
    getInfo() {
    // 当发起get请求之后, 通过 .then 来设置成功的回调函数
    this.$http.get('http://127.0.0.1:8899/api/get').then(function (result) {
    // 通过 result.body 拿到服务器返回的成功的数据
    // console.log(result.body)
    })
    },
    // 发起post请求
    postInfo() {
    var url = 'http://127.0.0.1:8899/api/post';
    // post 方法接收三个参数:
    // 参数1: 要请求的URL地址
    // 参数2: 要发送的数据对象
    // 参数3: 指定post提交的编码类型为 application/x-www-form-urlencoded
    // { emulateJSON: true } 设置 提交的内容类型 为 普通表单数据格式
    this.$http.post(url, { name: 'zs' }, { emulateJSON: true }).then(res => {
    console.log(res.body);
    });
    },
    // 发送JSONP请求
    jsonpInfo() { // JSONP形式从服务器获取数据
    var url = 'http://127.0.0.1:8899/api/jsonp';
    this.$http.jsonp(url).then(res => {
    console.log(res.body);
    });
    }
    }
    });
    </script>

jsonp示例

  • 使用Node建立服务

    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
    // 导入 http 内置模块
    const http = require("http");
    // 这个核心模块,能够帮我们解析 URL地址,从而拿到 pathname query
    const urlModule = require("url");

    // 创建一个 http 服务器
    const server = http.createServer();
    // 监听 http 服务器的 request 请求
    server.on("request", function (req, res) {
    // const url = req.url
    const { pathname: url, query } = urlModule.parse(req.url, true);

    if (url === "/getscript") {
    // 拼接一个合法的JS脚本,这里拼接的是一个方法的调用
    // var scriptStr = 'show()'

    var data = {
    name: "xjj",
    age: 18,
    gender: "女孩子",
    };

    var scriptStr = `${query.callback}(${JSON.stringify(data)})`;
    // res.end 发送给 客户端, 客户端去把 这个 字符串,当作JS代码去解析执行
    res.end(scriptStr);
    } else {
    res.end("404");
    }
    });

    // 指定端口号并启动服务器监听
    server.listen(3000, function () {
    console.log("server listen at http://127.0.0.1:3000");
    });
  • 调用Jsonp接口,打印输出的结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <body>
    <script>
    function showInfo123(data) {
    console.log(data);
    }
    </script>

    <script src="http://127.0.0.1:3000/getscript?callback=showInfo123"></script>
    </body>

Vue中的动画

  • 为什么要有动画:动画能够提高用户的体验,帮助用户更好的理解页面中的功能;

动画示例

  • 点击按钮,让 h3 显示,再点击,让 h3 隐藏 ,非常直观

    1
    2
    3
    4
    5
    <div id="app">
    <input type="button" value="toggle" @click="flag=!flag" />
    <!-- 需求: 点击按钮,让 h3 显示,再点击,让 h3 隐藏 -->
    <h3 v-if="flag">这是一个H3</h3>
    </div>
  1. 使用过渡类名

    • html代码

      1
      2
      3
      4
      5
      6
      7
      8
      <div id="app">
      <input type="button" value="动起来" @click="myAnimate" />
      <!-- 使用 transition 将需要过渡的元素包裹起来 -->
      <!-- transition 元素,是 Vue 官方提供的 -->
      <transition>
      <div v-if="flag">动画哦</div>
      </transition>
      </div>
    • VM 实例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <script>
      // 创建 Vue 实例,得到 ViewModel
      var vm = new Vue({
      el: '#app',
      data: {
      flag: false
      },
      methods: {}
      });
      </script>
  2. 定义两组类样式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <style>
    /* v-enter 【这是一个时间点】 是进入之前,元素的起始状态,此时还没有开始进入 */
    /* v-leave-to 【这是一个时间点】 是动画离开之后,离开的终止状态,此时,元素 动画已经结束了 */
    .v-enter,
    .v-leave-to {
    opacity: 0;
    transform: translateX(150px);
    }

    /* v-enter-active 【入场动画的时间段】 */
    /* v-leave-active 【离场动画的时间段】 */
    .v-enter-active,
    .v-leave-active{
    transition: all 0.8s ease;
    }
    </style>

使用自定义的名字

  • 默认使用的是 v-enter,v-leave二组标签

  • 在transition上可以指定name属性添加替换前缀v

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <style>
    .my-enter,
    .my-leave-to {
    opacity: 0;
    transform: translateY(70px);
    }

    .my-enter-active,
    .my-leave-active {
    transition: all 0.8s ease;
    }
    </style>
    <transition name="my">
    <h6 v-if="flag2">这是一个H6</h6>
    </transition>

使用第三方 CSS 动画库

  1. 导入动画类库:

    1
    <link rel="stylesheet" type="text/css" href="./lib/animate.css" />
  2. 定义 transition 及属性:

    1
    2
    3
    4
    5
    6
    7
    <transition
    enter-active-class="fadeInRight"
    leave-active-class="fadeOutRight"
    :duration="{ enter: 500, leave: 800 }"
    >
    <div class="animated" v-show="isshow">动画哦</div>
    </transition>

使用动画钩子函数

  1. 定义 transition 组件以及三个钩子函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div id="app">
    <input type="button" value="切换动画" @click="isshow = !isshow" />
    <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    >
    <div v-if="isshow" class="show">OK</div>
    </transition>
    </div>
  2. 定义三个 methods 钩子方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    methods: {
    beforeEnter(el) { // 动画进入之前的回调
    el.style.transform = 'translateX(500px)';
    },
    enter(el, done) { // 动画进入完成时候的回调
    el.offsetWidth;
    el.style.transform = 'translateX(0px)';
    done();
    },
    afterEnter(el) { // 动画进入完成之后的回调
    this.isshow = !this.isshow;
    }
    }
  3. 定义动画过渡时长和样式:

    1
    2
    3
    .show{
    transition: all 0.4s ease;
    }

v-for 的列表过渡

  1. 定义过渡样式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <style>
    .list-enter,
    .list-leave-to {
    opacity: 0;
    transform: translateY(10px);
    }

    .list-enter-active,
    .list-leave-active {
    transition: all 0.3s ease;
    }
    </style>
  2. 定义DOM结构,其中,需要使用 transition-group 组件把v-for循环的列表包裹起来:

    • 在实现列表过渡的时候,如果需要过渡的元素,是通过 v-for 循环渲染出来的,不能使用 transition 包裹,需要使用 transitionGroup
    • 如果要为 v-for 循环创建的元素设置动画,必须为每一个 元素 设置 :key属性
    • 给 ransition-group 添加 appear 属性,实现页面刚展示出来时候,入场时候的效果
    • 通过 为 transition-group 元素,设置 tag 属性,指定 transition-group 渲染为指定的元素,如果不指定 tag 属性,默认,渲染为 span 标签
    1
    2
    3
    4
    5
    6
    7
    <div id="app">
    <input type="text" v-model="txt" @keyup.enter="add" />

    <transition-group appear tag="ul" name="list">
    <li v-for="(item, i) in list" :key="i">{{item}}</li>
    </transition-group>
    </div>
  3. 定义 VM中的结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    txt: "",
    list: [1, 2, 3, 4],
    },
    methods: {
    add() {
    this.list.push(this.txt);
    this.txt = "";
    },
    },
    });

列表的排序过渡

  • <transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move 特性,它会在元素的改变定位的过程中应用
  • v-movev-leave-active 结合使用,能够让列表的过渡更加平缓柔和:

    1
    2
    3
    4
    5
    6
    7
    /* 下面的 .v-move 和 .v-leave-active 配合使用,能够实现列表后续的元素,渐渐地漂上来的效果 */
    .v-move{
    transition: all 0.8s ease;
    }
    .v-leave-active{
    position: absolute;
    }

定义Vue组件

  • 什么是组件: 组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可;
    组件化和模块化的不同:
  • 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
  • 组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;

全局组件定义的三种方式

不论是哪种方式创建出来的组件,组件的 template 属性指向的模板内容,必须有且只能有唯一的一个根元素

  1. Vue.extend 来创建全局的Vue组件,配合 Vue.component 方法:

    • 如果使用 Vue.component 定义全局组件的时候,组件名称使用了 驼峰命名,则在引用组件的时候,需要把 大写的驼峰改为小写的字母,同时,两个单词之前,使用 - 链接;
    • 如果不使用驼峰,则直接拿名称来使用即可;
    1
    2
    3
    4
    5
    var login = Vue.extend({
    template: '<h1>登录</h1>' // 通过 template 属性,指定了组件要展示的HTML结构
    });
    // Vue.component('组件的名称', 创建出来的组件模板对象)
    Vue.component('login', login);
  2. 直接使用 Vue.component 方法:

    • `Vue.component 第一个参数:组件的名称,将来在引用组件的时候,就是一个 标签形式 来引入 它的
    • 第二个参数: Vue.extend 创建的组件 ,其中 template 就是组件将来要展示的HTML内容
    1
    2
    3
    Vue.component('register', {
    template: '<h1>注册</h1>'
    });
  3. 将模板字符串,定义到script标签种:

    1
    2
    3
    4
    5
    6
    7
    8
    <script id="tmpl" type="x-template">
    <div><a href="#">登录</a> | <a href="#">注册</a></div>
    </script>

    // 或者使用<template>标签
    <template id="tmpl2">
    <h1>这是私有的 login 组件</h1>
    </template>
    • 同时,需要使用 Vue.component 来定义组件:

      1
      2
      3
      Vue.component('account', {
      template: '#tmpl'
      });
    • 定义内部的私有组件

      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
      var vm2 = new Vue({
      el: '#app2',
      data: {},
      methods: {},
      filters: {},
      directives: {},
      components: { // 定义实例内部私有组件的
      login: {
      template: '#tmpl2'
      }
      },

      beforeCreate() { },
      created() { },
      beforeMount() { },
      mounted() { },
      beforeUpdate() { },
      updated() { },
      beforeDestroy() { },
      destroyed() { }
      })
      </script>

      // 使用
      <login></login>

组件的数据和事件

  • 组件可以有自己的 data 数据,
  • 组件的 data 和 实例的 data 有点不一样,实例中的 data 可以为一个对象,但是 组件中的 data 必须是一个方法,方法内部,还必须返回一个对象才行;
  • 组件中 的data 数据,使用方式,和实例中的 data 使用方式完全一样
  1. 在组件中,data需要被定义为一个方法,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Vue.component("mycom1", {
    template: "<h1>这是全局组件 --- {{msg}}</h1>",
    data: function () {
    return {
    msg: "这是组件的中data定义的数据",
    };
    },
    });

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {},
    methods: {},
    });

计数器案例

  • 为什么组件中的data属性必须定义为一个方法并返回一个对象

    • 对于多个组件,可以保证数据互相独立
    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
    35
    36
    37
    38
    39
    <body>
    <div id="app">
    <counter></counter>
    <hr />
    <counter></counter>
    </div>

    <template id="tmpl">
    <div>
    <input type="button" value="+1" @click="increment" />
    <h3>{{count}}</h3>
    </div>
    </template>

    <script>
    var dataObj = { count: 0 };

    // 这是一个计数器的组件, 身上有个按钮,每当点击按钮,让 data 中的 count 值 +1
    Vue.component("counter", {
    template: "#tmpl",
    data: function () {
    // return dataObj
    return { count: 0 };
    },
    methods: {
    increment() {
    this.count++;
    },
    },
    });

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {},
    methods: {},
    });
    </script>
    </body>

局部子组件components

  1. 组件实例定义方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {},
    components: { // 定义子组件
    account: { // account 组件
    template: '<div><h1>这是Account组件{{name}}</h1><login></login></div>', // 定义的子组件
    components: { // 定义子组件的子组件
    login: { // login 组件
    template: "<h3>这是登录组件</h3>"
    }
    }
    }
    }
    });
    </script>
  2. 引用组件:

    1
    2
    3
    <div id="app">
    <account></account>
    </div>

组件切换

  1. 使用flag标识符结合v-ifv-else切换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <div id="app">
    <input type="button" value="toggle" @click="flag=!flag" />
    <my-com1 v-if="flag"></my-com1>
    <my-com2 v-else="flag"></my-com2>
    </div>

    <script>
    Vue.component("myCom1", {
    template: "<h3>奔波霸</h3>",
    });

    Vue.component("myCom2", {
    template: "<h3>霸波奔</h3>",
    });

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    flag: true,
    },
    methods: {},
    });
    </script>
  2. 使用:is属性来切换不同的子组件,并添加切换动画

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 登录组件 const login = Vue.extend({ template: `
    <div>
    <h3>登录组件</h3>
    </div>
    ` }); Vue.component('login', login); // 注册组件 const register =
    Vue.extend({ template: `
    <div>
    <h3>注册组件</h3>
    </div>
    ` }); Vue.component('register', register); // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({ el: '#app', data: { comName: 'login' }, methods: {} });
    • 使用component标签,来引用组件,并通过:is属性来指定要加载的组件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      <style>
      .v-enter,
      .v-leave-to {
      opacity: 0;
      transform: translateX(30px);
      }

      .v-enter-active,
      .v-leave-active {
      position: absolute;
      transition: all 0.3s ease;
      }
      </style>

      <div id="app">
      <a href="#" @click.prevent="comName='login'">登录</a>
      <a href="#" @click.prevent="comName='register'">注册</a>
      <hr />
      <!-- 通过 mode 属性,设置组件切换时候的 模式 -->
      <transition mode="out-in">
      <!-- component 是一个占位符, :is 属性,可以用来指定要展示的组件的名称 -->
      <component :is="comName"></component>
      </transition>
      </div>

组件渲染

  • 页面中渲染

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <div id="app">
    <login></login>
    </div>

    <script>
    var login = {
    template: "<h1>这是登录组件</h1>",
    };

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {},
    methods: {},
    components: {
    login,
    },
    });
    </script>
  • 使用render 函数渲染

    • createElements 是一个 方法,调用它能够把指定的组件模板,渲染为 html 结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <div id="app"></div>

    <script>
    var login = {
    template: "<h1>这是登录组件</h1>",
    };

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {},
    methods: {},
    render: function (createElements) {
    return createElements(login);
    // 注意:这里 return 的结果,会替换页面中 el 指定的那个容器
    },
    });
    </script>

组件传值

父组件向子组件传值

  1. 组件实例定义方式,注意:一定要使用props属性来定义父组件传递过来的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
    msg: '这是父组件中的消息'
    },
    components: {
    son: {
    template: '<h1>这是子组件 --- {{finfo}}</h1>',
    // 注意: 组件中的 所有 props 中的数据,都是通过 父组件传递给子组件的
    // props 中的数据,都是只读的,无法重新赋值
    props: ['finfo'] // 在 props 数组中定义一下,才能使用这个数据
    }
    }
    });
    </script>
  2. 使用v-bind或简化指令,将数据传递到子组件中:

    1
    2
    3
    <div id="app">
    <son :finfo="msg"></son>
    </div>

子组件向父组件传值

  1. 原理:

    • 父组件将方法的引用,传递到子组件内部
    • 子组件在内部调用父组件传递过来的方法引用
    • 同时把要发送给父组件的数据,在调用方法的时候当作参数传递进去
  2. 父组件将方法的引用传递给子组件,其中,getMsg是父组件中methods中定义的方法名称,func是子组件调用传递过来方法时候的方法名称

    1
    2
    // 在子组件中定义方法的引用 func,指向父组件中method中getMsg()
    <son @func="getMsg"></son>
  3. 子组件内部通过this.$emit('方法名', 要传递的数据)方式,来调用父组件中的方法,同时把数据传递给父组件使用

    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
    35
    36
    37
    38
    39
    40
    <div id="app">
    <!-- 引用父组件 -->
    <son @func="getMsg"></son>

    <!-- 组件模板定义 -->
    <templateid="son">
    <div>
    <input type="button" value="向父组件传值" @click="sendMsg" />
    </div>
    </template>
    </div>

    <script>
    // 子组件的定义方式
    Vue.component('son', {
    template: '#son', // 组件模板Id
    data() {
    return {
    sonmsg: { name: '小头儿子', age: 6 }
    }
    },
    methods: {
    sendMsg() { // 按钮的点击事件
    // emit 英文原意: 是触发,调用、发射的意思
    this.$emit('func', this.sonmsg); // 调用父组件传递过来的方法,同时把数据传递出去
    }
    }
    });

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {
    getMsg(val){ // 子组件中,通过 this.$emit() 实际调用的方法,在此进行定义
    alert(val.name);
    }
    }
    });
    </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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
<link rel="stylesheet" href="./lib/bootstrap-3.3.7.css" />
</head>

<body>
<div id="app">
<cmt-box @func="loadComments"></cmt-box>

<ul class="list-group">
<li class="list-group-item" v-for="item in list" :key="item.id">
<span class="badge">评论人: {{ item.user }}</span>
{{ item.content }}
</li>
</ul>
</div>

<template id="tmpl">
<div>
<div class="form-group">
<label>评论人:</label>
<input type="text" class="form-control" v-model="user" />
</div>

<div class="form-group">
<label>评论内容:</label>
<textarea class="form-control" v-model="content"></textarea>
</div>

<div class="form-group">
<input
type="button"
value="发表评论"
class="btn btn-primary"
@click="postComment"
/>
</div>
</div>
</template>

<script>
var commentBox = {
data() {
return {
user: "",
content: "",
};
},
template: "#tmpl",
methods: {
postComment() {
// 发表评论的方法
// 分析:发表评论的业务逻辑
// 1. 评论数据存到哪里去??? 存放到了 localStorage 中 localStorage.setItem('cmts', '')
// 2. 先组织出一个最新的评论数据对象
// 3. 想办法,把 第二步中,得到的评论对象,保存到 localStorage 中:
// 3.1 localStorage 只支持存放字符串数据, 要先调用 JSON.stringify
// 3.2 在保存 最新的 评论数据之前,要先从 localStorage 获取到之前的评论数据(string), 转换为 一个 数组对象, 然后,把最新的评论, push 到这个数组
// 3.3 如果获取到的 localStorage 中的 评论字符串,为空不存在, 则 可以 返回一个 '[]' 让 JSON.parse 去转换
// 3.4 把 最新的 评论列表数组,再次调用 JSON.stringify 转为 数组字符串,然后调用 localStorage.setItem()

var comment = {
id: Date.now(),
user: this.user,
content: this.content,
};

// 从 localStorage 中获取所有的评论
var list = JSON.parse(localStorage.getItem("cmts") || "[]");
list.unshift(comment);
// 重新保存最新的 评论数据
localStorage.setItem("cmts", JSON.stringify(list));

this.user = this.content = "";

// this.loadComments() // ?????
this.$emit("func");
},
},
};

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {
list: [
{ id: Date.now(), user: "李白", content: "天生我材必有用" },
{ id: Date.now(), user: "江小白", content: "劝君更尽一杯酒" },
{
id: Date.now(),
user: "小马",
content: "我姓马, 风吹草低见牛羊的马",
},
],
},
beforeCreate() {
// 注意:这里不能调用 loadComments 方法,因为在执行这个钩子函数的时候,data 和 methods 都还没有被初始化好
},
created() {
this.loadComments();
},
methods: {
loadComments() {
// 从本地的 localStorage 中,加载评论列表
var list = JSON.parse(localStorage.getItem("cmts") || "[]");
this.list = list;
},
},
components: {
"cmt-box": commentBox,
},
});
</script>
</body>
</html>

this.$refs 来获取元素和组件

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
35
36
37
38
39
40
41
42
<div id="app">
<div>
<input type="button" value="获取元素内容" @click="getElement" />
<!-- 使用 ref 获取元素 -->
<h1 ref="myh1">这是一个大大的H1</h1>

<hr />
<!-- 使用 ref 获取子组件 -->
<my-com ref="mycom"></my-com>
</div>
</div>

<script>
Vue.component("my-com", {
template: "<h5>这是一个子组件</h5>",
data() {
return {
name: "子组件",
};
},
methods: {
show() {
console.log("调用了子组件的方法");
},
},
});

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {
getElement() {
// 通过 this.$refs 来获取元素
console.log(this.$refs.myh1.innerText);
// 通过 this.$refs 来获取组件
console.log(this.$refs.mycom.name);
this.$refs.mycom.show();
},
},
});
</script>

路由

  1. 对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;

  2. 对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;

  3. 在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由(区别于后端路由);

在 vue 中使用 vue-router

  1. 导入 vue-router 组件类库:

    1
    2
    3
    <script src="./lib/vue-2.4.0.js"></script>
    <!-- 1. 导入 vue-router 组件类库 -->
    <script src="./lib/vue-router-2.7.0.js"></script>
  2. 使用 router-link 组件来导航

    1
    2
    3
    4
    5
    6
    <!-- <a href="#/login">登录</a> -->
    <!-- <a href="#/register">注册</a> -->
    <!-- router-link 默认渲染为一个a 标签 -->
    <!-- 2. 使用 router-link 组件来导航 -->
    <router-link to="/login">登录</router-link>
    <router-link to="/register">注册</router-link>
  3. 使用 router-view 组件来显示匹配到的组件

    • 这是 vue-router 提供的元素,专门用来 当作占位符的,将来,路由规则,匹配到的组件,就会展示到这个 router-view 中去
    • 我们可以把 router-view 认为是一个占位符
    1
    2
    <!-- 3. 使用 router-view 组件来显示匹配到的组件 -->
    <router-view></router-view>
  4. 创建使用Vue.extend创建组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 4.1 使用 Vue.extend 来创建登录组件
    var login = Vue.extend({
    template: "<h1>登录组件</h1>",
    });

    // 4.2 使用 Vue.extend 来创建注册组件
    var register = Vue.extend({
    template: "<h1>注册组件</h1>",
    });
  5. 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则

    • 每个路由规则,都是一个对象,这个规则对象,身上,有两个必须的属性:
      • 属性1 是 path, 表示监听 哪个路由链接地址;
      • 属性2 是 component, 表示,如果 路由是前面匹配到的 path ,则展示 component 属性对应的那个组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 5. 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则
    var router = new VueRouter({
    routes: [
    // 这个配置对象中的 route 表示 【路由匹配规则】 的意思
    { path: "/", redirect: "/login" }, // 这里的 redirect 和 Node 中的 redirect 完全是两码事
    { path: "/login", component: login },
    { path: "/register", component: register },
    ],
    });
  6. 使用 router 属性来使用路由规则

    1
    2
    3
    4
    5
    // 6. 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    router: router, // 使用 router 属性来使用路由规则
    });

设置路由高亮,切换动效

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
var routerObj = new VueRouter({
routes: [ // 路由匹配规则
{ path: '/', redirect: '/login' }, // 这里的 redirect 和 Node 中的 redirect 完全是两码事
],
linkActiveClass: 'myactive'
})


style>
.router-link-active,
.myactive {
color: red;
font-weight: 800;
font-style: italic;
text-decoration: underline;
background-color: green;
}

.v-enter,
.v-leave-to {
opacity: 0;
transform: translateX(140px);
}

.v-enter-active,
.v-leave-active {
transition: all 0.5s ease;
}
</style>

路由规则中定义参数

  1. 在规则中定义参数:

    1
    { path: '/login/:id/:name', component: register }
  2. 通过 this.$route.params来获取路由中的参数:

    1
    2
    3
    4
    5
    var register = Vue.extend({
    template: '<h1>注册组件 --- {{this.$route.params.id}}</h1>'
    });

    <router-link to="/login/12/ls">登录</router-link>

路由嵌套

  • 使用 children 属性,实现子路由,同时子路由的 path 前面,不要带 /
  • 否则永远以根路径开始请求,这样不方便我们用户去理解URL地址
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<div id="app">
<router-link to="/account">Account</router-link>
<router-view></router-view>
</div>

<script>
// 父路由中的组件
const account = Vue.extend({
template: `<div>
这是account组件
<router-link to="/account/login">login</router-link> |
<router-link to="/account/register">register</router-link>
<router-view></router-view>
</div>`,
});

// 子路由中的 login 组件
const login = Vue.extend({
template: "<div>登录组件</div>",
});

// 子路由中的 register 组件
const register = Vue.extend({
template: "<div>注册组件</div>",
});

// 路由实例
var router = new VueRouter({
routes: [
{ path: "/", redirect: "/account/login" }, // 使用 redirect 实现路由重定向
{
path: "/account",
component: account,
children: [
// 通过 children 数组属性,来实现路由的嵌套
{ path: "login", component: login }, // 注意,子路由的开头位置,不要加 / 路径符
{ path: "register", component: register },
],
},
],
});

// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: "#app",
data: {},
methods: {},
components: {
account,
},
router: router,
});
</script>

命名视图实现经典布局

  1. 标签代码结构:

    1
    2
    3
    4
    5
    6
    7
    <div id="app">
    <router-view></router-view>
    <div class="container">
    <router-view name="left"></router-view>
    <router-view name="main"></router-view>
    </div>
    </div>
  2. JS代码:

    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
    35
    36
    37
    38
    39
    40
    <script>

    var header = {
    template: '<h1 class="header">Header头部区域</h1>'
    }

    var leftBox = {
    template: '<h1 class="left">Left侧边栏区域</h1>'
    }

    var mainBox = {
    template: '<h1 class="main">mainBox主体区域</h1>'
    }

    // 创建路由对象
    var router = new VueRouter({
    routes: [
    /* { path: '/', component: header },
    { path: '/left', component: leftBox },
    { path: '/main', component: mainBox } */


    {
    path: '/', components: {
    'default': header,
    'left': leftBox,
    'main': mainBox
    }
    }
    ]
    })

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {},
    methods: {},
    router
    });
    </script>
  3. CSS 样式:

    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
    <style>
    html,
    body {
    margin: 0;
    padding: 0;
    }

    .header {
    background-color: orange;
    height: 80px;
    }

    h1 {
    margin: 0;
    padding: 0;
    font-size: 16px;
    }

    .container {
    display: flex;
    height: 600px;
    }

    .left {
    background-color: lightgreen;
    flex: 2;
    }

    .main {
    background-color: lightpink;
    flex: 8;
    }
    </style>

watch、computed和methods

  1. computed属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
  2. methods方法表示一个具体的操作,主要书写业务逻辑;
  3. watch一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computedmethods的结合体;

watch属性的使用

  • 考虑一个问题:想要实现 两个文本框的内容改变,则全名的文本框中的值也跟着改变;
  • 特点:第一次初始化页面的时候,是不会对定义的属性进行监听操作
  1. 监听data中属性的改变:

    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
    <div id="app">
    <input type="text" v-model="firstName" /> +
    <input type="text" v-model="lastName" /> =
    <span>{{fullName}}</span>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    firstName: "jack",
    lastName: "chen",
    fullName: "jack - chen",
    },
    methods: {},
    watch: {
    firstName: function (newVal, oldVal) {
    // 第一个参数是新数据,第二个参数是旧数据
    this.fullName = newVal + " - " + this.lastName;
    },
    lastName: function (newVal, oldVal) {
    this.fullName = this.firstName + " - " + newVal;
    },
    },
    });
    </script>
  2. 监听路由对象的改变:

    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
    35
    36
    37
    38
    <div id="app">
    <router-link to="/login">登录</router-link>
    <router-link to="/register">注册</router-link>

    <router-view></router-view>
    </div>

    <script>
    var login = Vue.extend({
    template: "<h1>登录组件</h1>",
    });

    var register = Vue.extend({
    template: "<h1>注册组件</h1>",
    });

    var router = new VueRouter({
    routes: [
    { path: "/login", component: login },
    { path: "/register", component: register },
    ],
    });

    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {},
    methods: {},
    router: router,
    watch: {
    $route: function (newVal, oldVal) {
    if (newVal.path === "/login") {
    console.log("这是登录组件");
    }
    },
    },
    });
    </script>

handler

  • 第一次初始化时候是不会进行监听操作,如果希望监听,需要引入handler方法和Immediate属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    watch: {
    firstName: {
    handler(newVal, oldVal) {
    // 第一个参数是新数据,第二个参数是旧数据
    this.fullName = newVal + ' - ' + this.lastName;
    },
    immediate: true
    }
    }
  • immediate: true 的含义就是在watch声明了firstName的话,会立即执行里面的handler方法

deep属性

  • watch中有一个属性deep,含义:是否深度监听某个对象的值,该值默认为false。

    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
    <template>
    <div>
    <input v-model="obj.age" />
    <p>{{ obj.msg }}</p>
    </div>
    </template>
    <script>
    export default {
    data() {
    return {
    obj: {
    msg: '',
    age: 0
    }
    };
    },

    watch: {
    obj: {
    handler(newVal, oldVal) {
    // 第一个参数是新数据,第二个参数是旧数据
    this.obj.msg = '输入的年龄为:' + newVal.age;
    },
    immediate: true
    // deep: true
    }
    }
    </script>
    • 不添加deep: true 属性,在输入框修改了obj.age后,handler函数不会被执行到
    • 受JS的限制,Vue不能检测到对象的添加或删除,指定将听到这个obj对象的变化(赋值操作)

computed计算属性的使用

  1. 默认只有getter的计算属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <div id="app">
    <input type="text" v-model="firstName" /> +
    <input type="text" v-model="lastName" /> =
    <span>{{fullName}}</span>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    firstName: "jack",
    lastName: "chen",
    },
    methods: {},
    computed: {
    // 计算属性; 特点:当计算属性中所以来的任何一个 data 属性改变之后,都会重新触发属性的重新计算
    // 从而更新 fullName 的值
    fullName() {
    return this.firstName + " - " + this.lastName;
    },
    },
    });
    </script>
  2. 定义有gettersetter的计算属性:

    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
    35
    36
    <div id="app">
    <input type="text" v-model="firstName" />
    <input type="text" v-model="lastName" />
    <!-- 点击按钮重新为 计算属性 fullName 赋值 -->
    <input type="button" value="修改fullName" @click="changeName" />
    <span>{{fullName}}</span>
    </div>

    <script>
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: "#app",
    data: {
    firstName: "jack",
    lastName: "chen",
    },
    methods: {
    changeName() {
    this.fullName = "TOM - chen2";
    },
    },
    computed: {
    fullName: {
    get: function () {
    return this.firstName + " - " + this.lastName;
    },
    // 反向操作: 给出姓名,修改对应的姓&名
    set: function (newVal) {
    var parts = newVal.split(" - ");
    this.firstName = parts[0];
    this.lastName = parts[1];
    },
    },
    },
    });
    </script>

区别

  • computed: 是基于响应性依赖来进行缓存的。只有在响应依赖发生改变的时候才会重新计算,就是说当多次访问 <span>{{fullName}}</span>的时候,不会再次执行computed的就算操作(methods比较)。

webpack

webpack 是前端的一个项目构建工具,它是基于 Node.js 开发出来的一个前端工具

借助于webpack这个前端自动化构建工具,可以完美实现资源的合并、打包、压缩、混淆等诸多功能。

webpack官网

安装方式

  1. 运行npm i webpack -g全局安装webpack,这样就能在全局使用webpack的命令
  2. 在项目根目录中运行npm i webpack --save-dev安装到项目依赖中

初步使用

  1. 创建一个基于 webpack 模板的新项目:$ vue init webpack my-project

  2. cd my-project安装依赖, 运行npm init初始化项目,使用npm管理项目中的依赖包

  3. 创建项目基本的目录结构

  4. 使用cnpm i jquery --save安装jquery类库

  5. 创建main.js并书写各行变色的代码逻辑:

    1
    2
    3
    4
    5
    6
    7
    8
    // 导入jquery类库
    import $ from "jquery";
    // const $ = require('jquery')

    // 设置偶数行背景色,索引从0开始,0是偶数
    $("#list li:even").css("backgroundColor", "lightblue");
    // 设置奇数行背景色
    $("#list li:odd").css("backgroundColor", "pink");
  6. 直接在页面上引用main.js会报错,因为浏览器不认识import这种高级的JS语法,需要使用webpack进行处理,webpack默认会把这种高级的语法转换为低级的浏览器能识别的语法;

  7. 运行webpack 入口文件路径 输出文件路径main.js进行处理:

    1
    webpack src/js/main.js dist/bundle.js
  8. 当我们在 控制台,直接输入 webpack 命令执行的时候,webpack 做了以下几步:

    • 首先,webpack 发现,我们并没有通过命令的形式,给它指定入口和出口
    • webpack 就会去 项目的 根目录中,查找一个叫做 webpack.config.js 的配置文件
    • 当找到配置文件后,webpack 会去解析执行这个 配置文件,当解析执行完配置文件后,就得到了 配置文件中,导出的配置对象
    • 当 webpack 拿到 配置对象后,就拿到了 配置对象中,指定的 入口 和 出口,然后进行打包构建;

Webpack下的Vuejs项目文件结构

  • 全局的文件结构:

    1
    2
    3
    4
    5
    6
    7
    8
    ▸ build/                // 编译用到的脚本
    ▸ config/ // 各种配置
    ▸ dist/ // 打包后的文件夹
    ▸ node_modules/ // node第三方包
    ▸ src/ // 源代码
    static/ // 静态文件, 暂时无用
    index.html // 最外层文件
    package.json // node项目配置文件
  • build: 保留各种打包脚本。不可或缺,不要随意修改。

    展开后如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ▾ build/
    build.js
    check-versions.js
    dev-client.js
    dev-server.js
    utils.js
    vue-loader.conf.js
    webpack.base.conf.js
    webpack.dev.conf.js
    webpack.prod.conf.js
    • build.js:打包使用, 不要修改。

    • check-versions.js: 检查npm的版本, 不要修改。

    • dev-client.js 和 dev-server.js:是在开发时使用的服务器脚本。不要修改。(借助于node这个后端语言,我们 在做vuejs开发时,可以通过 $npm run dev这个命令,打开一个小的server, 运行vuejs. )

    • utils.js 不要修改。 做一些css/sass 等文件的生成。

    • vue-loader.conf.js 非常重要的配置文件,不要修改。内容是用来辅助加载vuejs用到的css source map等内容。

    • webpack.base.conf.js, webpack.dev.conf.js,webpack.prod.conf.js 这三个都是基本的配置文件。不要修改。

  • config:跟部署和配置相关。

    1
    2
    3
    4
    ▾ config/
    dev.env.js
    index.js
    prod.env.js
    • dev.env.js 开发模式下的配置文件,一般不用修改。
    • prod.env.js 生产模式下的配置文件,一般不用修改。
    • index.js 很重要的文件, 定义了 开发时的端口(默认是8080),定义了图片文件夹(默认static), 定义了开发模式下的 代理服务器. 我们修改的还是比较多的。
  • dist 打包之后的文件所在目录,如下。

    • 可以看到,对应的css, js, map, 都在这里。这个文件夹不要放到git中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    ▾ dist/
    ▾ static/
    ▾ css/
    app.d41d8cd98f00b204e9800998ecf8427e.css
    app.d41d8cd98f00b204e9800998ecf8427e.css.map
    ▾ js/
    app.c482246388114c3b9cf0.js
    app.c482246388114c3b9cf0.js.map
    manifest.577e472792d533aaaf04.js
    manifest.577e472792d533aaaf04.js.map
    vendor.5f34d51c868c93d3fb31.js
    vendor.5f34d51c868c93d3fb31.js.map
    index.html
  • node_modules: node项目所用到的第三方包,特别多,特别大。 $ npm install 所产生。这个文件夹不要放到git中。

  • src: 最最核心的源代码所在的目录。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ▾ src/
    ▾ assets/
    logo.png
    ▾ components/
    Book.vue
    BookList.vue
    Hello.vue
    ▾ router/
    index.js
    App.vue
    main.js
    • assets: 用到的图片
    • components: 用到的”视图”和”组件”所在的文件夹。(最最核心)
    • router/index.js 路由文件。 定义了各个页面对应的url.
    • App.vue 如果index.html 是一级页面模板的话,这个App.vue就是二级页面模板。 所有的其他vuejs页面,都作为该模板的 一部分被渲染出来。
    • main.js 废代码。没有实际意义,但是为了支撑整个vuejs框架,存在很必要。

webpack配置文件

  1. 简化打包时候的命令

    • 在项目根目录中创建webpack.config.js

    • 由于运行webpack命令的时候,webpack需要指定入口文件和输出文件的路径,所以,我们需要在webpack.config.js中配置这两个路径:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 导入处理路径的模块
      var path = require("path");

      // 导出一个配置对象,将来webpack在启动的时候,会默认来查找webpack.config.js,并读取这个文件中导出的配置对象,来进行打包处理
      module.exports = {
      entry: path.join(__dirname, "./src/js/main.js"), // 项目入口文件,要使用 webpack 打包哪个文件
      output: {
      // 配置输出选项
      path: path.join(__dirname, "./dist"), // 配置输出的路径
      filename: "bundle.js", // 配置输出的文件名
      },
      };

实时打包构建

  1. 由于每次重新修改代码之后,都需要手动运行webpack打包的命令,比较麻烦,所以使用webpack-dev-server来实现代码实时打包编译,当修改代码之后,会自动进行打包构建。
  2. 运行cnpm i webpack-dev-server --save-dev安装到开发依赖
  3. 安装完成之后,在命令行直接运行webpack-dev-server来进行打包,发现报错,此时需要借助于package.json文件中的指令,来进行运行webpack-dev-server命令,在scripts节点下新增"dev": "webpack-dev-server"指令,发现可以进行实时打包,但是dist目录下并没有生成bundle.js文件,这是因为webpack-dev-server将打包好的文件放在了内存中
  • bundle.js放在内存中的好处是:由于需要实时打包编译,所以放在内存中速度会非常快

  • 这个时候访问webpack-dev-server启动的http://localhost:8080/网站,发现是一个文件夹的面板,需要点击到src目录下,才能打开我们的index首页,此时引用不到bundle.js文件,需要修改index.html中script的src属性为:<script src="../bundle.js"></script>

  • 为了能在访问http://localhost:8080/的时候直接访问到index首页,可以使用--contentBase src指令来修改dev指令,指定启动的根目录:

    1
    "dev": "webpack-dev-server --contentBase src"
  • 同时修改index页面中script的src属性为<script src="bundle.js"></script>

使用html-webpack-plugin插件配置启动页面

由于使用--contentBase指令的过程比较繁琐,需要指定启动的目录,同时还需要修改index.html中script标签的src属性,所以推荐大家使用html-webpack-plugin插件配置启动页面.

  1. 运行cnpm i html-webpack-plugin --save-dev安装到开发依赖

  2. 两个作用:自动在内存中根据指定页面生成一个内存的页面,自动把打包好的 bundle.js 追加到页面中去

  3. 修改webpack.config.js配置文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 导入处理路径的模块
    var path = require("path");
    // 导入自动生成HTMl文件的插件
    var htmlWebpackPlugin = require("html-webpack-plugin");

    module.exports = {
    entry: path.resolve(__dirname, "src/js/main.js"), // 项目入口文件
    output: {
    // 配置输出选项
    path: path.resolve(__dirname, "dist"), // 配置输出的路径
    filename: "bundle.js", // 配置输出的文件名
    },
    plugins: [
    // 添加plugins节点配置插件
    new htmlWebpackPlugin({
    template: path.resolve(__dirname, "src/index.html"), //模板路径
    filename: "index.html", //自动生成的HTML文件的名称
    }),
    ],
    };
  4. 修改package.jsonscript节点中的dev指令如下:

    1
    "dev": "webpack-dev-server"
  5. 将index.html中script标签注释掉,因为html-webpack-plugin插件会自动把bundle.js注入到index.html页面中!

实现自动打开浏览器、热更新和配置浏览器的默认端口号

  • 注意:热更新在JS中表现的不明显,可以从一会儿要讲到的CSS身上进行介绍说明!

  • 方式1: 修改package.json的script节点如下,其中--open表示自动打开浏览器,--port 4321表示打开的端口号为4321,--hot表示启用浏览器热更新:

    1
    "dev": "webpack-dev-server --hot --port 4321 --open"
  • 方式2:修改webpack.config.js文件,

    1. 新增devServer节点如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      module.exports = {
      devServer: { // 这是配置 dev-server 命令参数的第二种形式,相对来说,这种方式麻烦一些
      // --open --port 3000 --contentBase src --hot
      open: true, // 自动打开浏览器
      port: 3000, // 设置启动时候的运行端口
      contentBase: 'src', // 指定托管的根目录
      hot: true // 启用热更新 的 第1步
      }
      ...
      }
    2. 在头部引入webpack模块:

      1
      var webpack = require("webpack");
    3. plugins节点下新增:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      module.exports = {
      devServer: { // 这是配置 dev-server 命令参数的第二种形式,相对来说,这种方式麻烦一些
      // --open --port 3000 --contentBase src --hot
      open: true, // 自动打开浏览器
      port: 3000, // 设置启动时候的运行端口
      contentBase: 'src', // 指定托管的根目录
      hot: true // 启用热更新 的 第1步
      },
      plugins: [ // 配置插件的节点
      new webpack.HotModuleReplacementPlugin(), // new 一个热更新的 模块对象
      ],
      module: {
      }
      }

使用webpack打包css文件

  1. 运行cnpm i style-loader css-loader --save-dev

  2. 修改webpack.config.js这个配置文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    module.exports = {
    devServer: {
    // 这是配置 dev-server 命令参数的第二种形式,相对来说,这种方式麻烦一些
    },
    plugins: [
    // 配置插件的节点
    ],
    module: {
    // 用来配置第三方loader模块的
    rules: [
    // 文件的匹配规则
    { test: /\.css$/, use: ["style-loader", "css-loader"] }, //处理css文件的规则
    ],
    },
    };
  3. 注意:use表示使用哪些模块来处理test所匹配到的文件;use中相关loader模块的调用顺序是从后向前调用的

使用webpack打包less文件

  1. 运行cnpm i less-loader less -D

  2. 修改webpack.config.js这个配置文件:

    1
    { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },

使用webpack打包sass文件

  1. 运行cnpm i sass-loader node-sass --save-dev

  2. webpack.config.js中添加处理sass文件的loader模块:

    1
    { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }

使用webpack处理css中的路径

  1. 运行cnpm i url-loader file-loader --save-dev

  2. webpack.config.js中添加处理url路径的loader模块:

    1
    { test: /\.(png|jpg|gif)$/, use: 'url-loader' }
  3. 可以通过limit指定进行base64编码的图片大小;只有小于指定字节(byte)的图片才会进行base64编码:

    1
    { test: /\.(png|jpg|gif)$/, use: 'url-loader?limit=43960' },

使用babel处理高级JS语法

  1. 运行cnpm i babel-core babel-loader babel-plugin-transform-runtime --save-dev安装babel的相关loader包

  2. 运行cnpm i babel-preset-es2015 babel-preset-stage-0 --save-dev安装babel转换的语法

  3. webpack.config.js中添加相关loader模块,其中需要注意的是,一定要把node_modules文件夹添加到排除项:

    1
    { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
  4. 在项目根目录中添加.babelrc文件,并修改这个配置文件如下:

    1
    2
    3
    4
    {
    "presets":["env", "es2015", "stage-0"],
    "plugins":["transform-runtime"]
    }
  5. 注意:语法插件babel-preset-es2015可以更新为babel-preset-env,它包含了所有的ES相关的语法;

配置.vue组件页面的解析

  1. 运行cnpm i vue -S将vue安装为运行依赖;

  2. 运行cnpm i vue-loader vue-template-compiler -D将解析转换vue的包安装为开发依赖;

  3. 运行cnpm i style-loader css-loader -D将解析转换CSS的包安装为开发依赖,因为.vue文件中会写CSS样式;

  4. webpack.config.js中,添加如下module规则:

    1
    2
    3
    4
    5
    6
    module: {
    rules: [
    { test: /\.css$/, use: ["style-loader", "css-loader"] },
    { test: /\.vue$/, use: "vue-loader" },
    ];
    }
  5. 创建App.js组件页面:

    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
    <template>
    <!-- 注意:在 .vue 的组件中,template 中必须有且只有唯一的根元素进行包裹,一般都用 div 当作唯一的根元素 -->
    <div>
    <h1>这是APP组件 - {{ msg }}</h1>
    <h3>我是h3</h3>
    </div>
    </template>

    <script>
    // 注意:在 .vue 的组件中,通过 script 标签来定义组件的行为
    // 需要使用 ES6 中提供的 export default 方式,导出一个vue实例对象
    export default {
    data() {
    return {
    msg: "OK",
    };
    },
    };
    </script>

    <style scoped>
    h1 {
    color: red;
    }
    </style>
  6. 创建main.js入口文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 导入 Vue 组件
    import Vue from "vue";

    // 导入 App组件
    import App from "./components/App.vue";

    // 创建一个 Vue 实例,使用 render 函数,渲染指定的组件
    var vm = new Vue({
    el: "#app",
    render: (c) => c(App),
    });

构建的Vue项目中使用模板对象

  1. webpack.config.js中添加resolve属性:

    1
    2
    3
    4
    5
    6
    7
    8
    module.exports = {
    resolve: {
    alias: {
    // 修改 Vue 被导入时候的包的路径
    // "vue$": "vue/dist/vue.js"
    },
    },
    };

webpack 中如何使用 vue

  1. 安装vue的包:cnpm i vue -S

  2. 由于 在 webpack 中,推荐使用 .vue 这个组件模板文件定义组件,所以,需要安装 能解析这种文件的 loader cnpm i vue-loader vue-template-complier -D

  3. 在 main.js 中,导入 vue 模块 import Vue from 'vue'

    • 在 webpack 中, 使用 import Vue from 'vue' 导入的 Vue 构造函数,功能不完整,只提供了 runtime-only 的方式,并没有提供 像网页中那样的使用方式
    • 完整import Vue from '../node_modules/vue/dist/vue.js'
    • 包的查找规则:
    1. 找 项目根目录中有没有 node_modules 的文件夹
    2. 在 node_modules 中 根据包名,找对应的 vue 文件夹
    3. 在 vue 文件夹中,找 一个叫做 package.json 的包配置文件
    4. 在 package.json 文件中,查找 一个 main 属性【main属性指定了这个包在被加载时候,的入口文件】
  4. 定义一个 .vue 结尾的组件,其中,组件有三部分组成: template ,script,style

  5. 使用 import login from './login.vue' 导入这个组件

  6. 创建 vm 的实例 var vm = new Vue({ el: '#app', render: c => c(login) })

  7. 在index页面中创建一个 id 为 app 的 div 元素,作为我们 vm 实例要控制的区域;

    1
    2
    <!-- 这是容器 -->
    <div id="app"></div>

vue组件集成路由模块

  1. 导入路由模块:

    1
    2
    3
    import VueRouter from "vue-router";
    // 安装路由模块:
    Vue.use(VueRouter);
  2. 导入需要展示的组件:

    1
    2
    import login from "./components/account/login.vue";
    import register from "./components/account/register.vue";
  3. 创建路由对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var router = new VueRouter({
    routes: [
    { path: "/", redirect: "/login" },

    { path: "/login", component: login },

    { path: "/register", component: register },
    ],
    });
  4. 将路由对象,挂载到 Vue 实例上:

    1
    2
    3
    4
    5
    6
    7
    8
    var vm = new Vue({
    el: "#app",
    // render: c => { return c(App) }
    render(c) {
    return c(App);
    },
    router, // 将路由对象,挂载到 Vue 实例上
    });
  5. 改造App.vue组件,在 template 中,添加router-linkrouter-view

    1
    2
    3
    <router-link to="/login">登录</router-link>
    <router-link to="/register">注册</router-link>
    <router-view></router-view>

ES6中语法

  1. 使用 export defaultexport 导出模块中的成员; 对应ES5中的 module.exportsexport
  2. 使用 import fromimport '路径' 还有 import {a, b} from '模块标识' 导入其他模块
  3. 使用箭头函数:(a, b)=> { return a-b; }

import

  • import 用来引入 第三方程序, 是es6的语法标准

    1
    2
    import Vue from "vue";
    import Router from "vue-router";
  • 上面两个,是引入 package.json 中的第三方包,所以可以直接 import ... from <包名>.

    1
    import SayHi from "@/components/SayHi";
  • 上面这个,在from后面,有 @符号, 表示是在本地文件系统中,引入文件. @代表 源代码目录,一般是 src. 在 @出现之前,我们在编码的时候也会这样写:

    1
    2
    3
    4
    5
    6
    import Swiper from '../components/swiper'
    import SwiperItem from '../components/swiper-item'
    import XHeader from '../components/header/x-header'
    import store from '../vuex/store'
    // 重命名
    import {store as store 1 } from '../vuex/store'
  • 大量使用了 ../.. 这样的代码,会引起代码的混乱. 所以推荐使用 @ 方法.

  • import * as obj from 'XXXX'的作用: 将若干个 export 导出的内容组合成一个对象返回

    • 假设 xxx 模块里的内容如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      export function test(){
      return 'test';
      }

      export function login(){
      return 'login';
      }

      export defautl de;
    • 调用函数: obj.test() obj.login()

    • 如果是不带 * as, 即: import xxx from xxxx, 只会到处默认的对象作为一个对象。

export default {..}

  • 我们会看到,在每个 vue文件中的 <script>代码端中,都会存在这个代码.它的作用是方便其他代码对这个代码 的引用.

  • 对于vue程序员,我们就记住这个写法就好了. 没它不行, 有它也没太大意义. 鸡肋代码. 参考: http://www.jianshu.com/p/710e66547bbb

  • 在ES6之前js没有一个统一的模块定义方式,流行的定义方式有AMD,CommonJS等,而ES6从语言层面对定义模块的方式进行了统一。

  • 假设有: lib/math.js 文件内容如下:

    1
    2
    3
    4
    export function sum(x, y) {
    return x + y;
    }
    export var pi = 3.141593;
  • app.js 文件内容如下:

    1
    2
    import * as math from "lib/math";
    alert("2π = " + math.sum(math.pi, math.pi));
  • other_app.js 文件内容如下:

    1
    2
    import { sum, pi } from "lib/math";
    alert("2π = " + sum(pi, pi));
  • export default { ... } 则是暴露出一段没有名字的代码, (不像 export function sum(a,b){ .. } 这样有个名字(叫sum) . )

一些简写

  • 我们会发现,这样的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script>
    export default {
    data () {
    return { }
    }
    }
    </script>

    let title = '...';

    return {
    title
    }
  • 实际上,上面的代码是一种简写形式,它等同于下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script>
    export default {
    data: function() {
    return { }
    }
    }
    </script>


    let title = '...';

    return {
    title: title;
    }
  • 函数简写:addName: function() {} => addName() {}

  • 属性的简写: data:data =>data .也就是说,可以直接在对象中直接写入变量,当函数的返回值为对象时候,使用简写方式更加简洁直观:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function getPerson() {
    let name = "Jack";
    let age = 10;

    return { name, age };
    // 等价于
    // return {
    // name : name,
    // age : age
    // }
    }
    getPerson();
    • v-on:click 等于 @click
    • v-bind:xx 等于 :xx

let, var, 常量 与全局变量

  • 声明本地变量,使用 let 或者 var .两者的区别是:

    • var: 有可能引起 变量提升,或者块级作用域的问题.
    • let: 就是为了解决这两个问题存在的.
    • 最佳实践: 多用let, 少用var. 遇到诡异变量问题的时候,就查查是不是var的问题.
  • es6 之前,JavaScript 并没有块级作用域,所谓的块,就是大括号里面的语句所组成的代码块,比如

    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
    function fire(bool) {
    if (bool) {
    var foo = "bar";
    }
    console.log(foo);
    }

    fire(true); //=> bar

    // 虽然变量 foo 位于 if 语句的代码块中,但是 JavaScript 并没有块级作用域的概念,因此被添加到了当前的执行环境
    // 即函数中,在函数内都可以访问到。

    function fire(bool) {
    if (bool) {
    var foo = "bar";
    } else {
    console.log(foo);
    }
    }
    fire(false); //=> undefined

    // 直接访问一个未定义的变量,会报错:
    console.log(nope); //=> Uncaught ReferenceError: nope is not defined
    // 上述的例子中,会返回 undefined。也就是说,变量的定义被提升到了作用域的顶部 等价于:
    function fire(bool) {
    // 在 JavaScript 中,声明但是未赋值的变量会被赋值为 undefined,因此,结果输出 undefined。
    var foo;
    if (bool) {
    foo = "bar";
    } else {
    console.log(foo);
    }
    }
    fire(false);
  • let解决var的问题

    • 定义的变量只在代码块内有效
    • 变量不存在变量提升:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function fire(bool) {
    if (bool) {
    let foo = "bar";
    }
    console.log(foo);
    }

    fire(true); //=> Uncaught ReferenceError: foo is not defined

    function fire(bool) {
    if (bool) {
    let foo = "bar";
    } else {
    console.log(foo);
    }
    }
    fire(false); //=> Uncaught ReferenceError: foo is not defined
  • 常量:const 与 let 的基本用法相同,定义的变量都具有块级作用域,也不会发生变量提升。不同的地方在于,const 定义的变量,只能赋值一次

    • 在一些不需要重复赋值的场合可以使用 const
    1
    2
    3
    4
    5
    6
    7
    8
    const a = 1;
    a = 2; // Uncaught TypeError: Assignment to constant variable.
    ++a; // Uncaught TypeError: Assignment to constant variable.

    // 对于数组和对象来说,值是可以改变的:
    const arr = ["a", "b", "c"];
    arr.push("d");
    arr.pop();
  • 对于全局变量, 直接在 index.html 中声明即可. 例如:

    1
    window.title = "我的博客列表";

箭头函数 =>

  • 跟coffeescript一样, es script也可以通过箭头来表示函数.

    1
    2
    3
    4
    5
    6
    7
    .then(response => ... );

    // 等同于:

    .then(function (response) {
    // ...
    })
  • 在 Vue 中,使用箭头函数的最大好处就是可以让 this 指向 Vue 实例:

    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
    35
    36
    var vm = new Vue({
    el:'#root',
    data:{
    tasks:[]
    },
    mounted(){
    axios.get('/tasks')
    .then(function (response) {
    // 不能使用this.tasks 回调函数的 this 指向全局对象 window
    // 需要通过 vm 来访问实例的方
    vm.tasks = response.data;
    })
    }
    });

    // 使用箭头函数
    new Vue({
    el:'#root',
    data:{
    tasks:[]
    },
    mounted(){
    axios.get('/tasks')
    // 箭头函数的 this 对象始终指向定义函数时所在的对象
    .then(response => this.tasks = response.data);
    }
    });

    // 相当于:
    mounted(){
    var that = this;
    axios.get('/tasks')
    .then(function (response) {
    that.tasks = response.data;
    })
    }

模板字符串

  • 模板字符串为 Vue 的组件模板定义带来了巨大的便利,在此之前,需要这样定义一个模板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let template = '<div class="container"><p>Foo</p></div>';

    // 如果要写成多行,可以用反斜杠:
    let template =
    '<div class="container">\
    <p>Foo</p>\
    </div>';

    // 或者使用数组形式:
    let template = ['<div class="container">', "<p>Foo</p>", "</div>"].join("");

    // 如果要嵌入变量,可以写成:
    let name = "jack";
    let template = '<div class="container"><p>' + name + "</p></div>";
  • 使用模板字符串,则可以方便的在多行里面编写模板:

    • 模板字符串的空格和换行会被保留,可以使用 trim() 方法从字符串中移除 前导 空格、尾随空格和行终止符。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    let template = `
    <div class="container">
    <p>Foo</p>
    </div>
    `;

    // 移除 前导 空格、尾随空格和行终止符。
    let template = `
    <div class="container">
    <p>Foo</p>
    </div>
    `.trim();

    // 嵌入变量或者表达式
    let name = "jack";
    let template = `
    <div class="container">
    <p>${name} is {100 + 100}</p>
    </div>
    `.trim();

默认参数

  • 在 es6 之前,JavaScript 不能像 PHP 那样支持默认参数,因此需要自己手动定义:

    1
    2
    3
    4
    5
    function takeDiscount(price, discount) {
    discount = discount || 0.9;
    return price * discount;
    }
    takeDiscount(100);
  • es6 则允许定义默认参数

    1
    2
    3
    4
    function takeDiscount(price, discount = 0.9) {
    return price * discount;
    }
    takeDiscount(100);
  • 甚至可以以函数形式传递参数:

    1
    2
    3
    4
    5
    6
    7
    8
    function getDiscount() {
    return 0.9;
    }

    function takeDiscount(price, discount = getDiscount()) {
    return price * discount;
    }
    takeDiscount(100);

rest 参数

  • 先从函数的参数传递说起:在 JavaScript 中,函数参数实际上以数组的方式进行传递,参数会被保存在 arguments 数组中

    1
    2
    3
    4
    5
    function sum(a, b, c) {
    let total = a + b + c;
    return total;
    }
    sum(1, 2, 3);
  • 上例等价于:

    1
    2
    3
    4
    5
    function sum() {
    let total = arguments[0] + arguments[1] + arguments[2];
    return total;
    }
    sum(1, 2, 3);
  • 不过 arguments 不单单包括参数,也包括了其他东西,因此没法直接用数组函数来操作 arguments。如果要扩展成任意多个数值相加,可以使用循环:

    1
    2
    3
    4
    5
    6
    7
    8
    function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
    total = total + arguments[i];
    }
    return total;
    }
    sum(1, 2, 3, 4, 6);
  • es6 则提供了 rest 参数来访问多余变量,上例等价于:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function sum(...num) {
    let total = 0;
    for (let i = 0; i < num.length; i++) {
    total = total + num[i];
    }
    return total;
    }
    sum(1, 2, 3, 4, 6);

    // 可以以变量形式进行传递:
    function sum(...num) {
    let total = 0;
    for (let i = 0; i < num.length; i++) {
    total = total + num[i];
    }
    return total;
    }
    let nums = [1, 2, 3, 4, 6];
    sum(...nums);
  • 在函数中体内,num 就是单纯由参数构成的数组,因此可以用数组函数 reduce 来实现同样的功能:

    1
    2
    3
    4
    5
    6
    function sum(...num) {
    return num.reduce((preval, curval) => {
    return preval + curval;
    });
    }
    sum(1, 2, 3, 4, 6);
  • ... 还可以与其他参数结合使用,只需要将其他参数放在前面即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function sum(total = 0, ...num) {
    return (
    total +
    num.reduce((preval, curval) => {
    return preval + curval;
    })
    );
    }

    let nums = [1, 2, 3, 4];
    sum(100, ...nums);

解构赋值

  • 解构赋值可以方便的取到对象的可遍历属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let person = {
    firstname: "steve",
    lastname: "curry",
    age: 29,
    sex: "man",
    };

    let { firstname, lastname } = person;
    console.log(firstname, lastname);

    // 等价于
    // let firstname = person.firstname;
    // let lastname = person.lastname;
  • 可以将其用于函数传参中:但是不建议大家这样使用. 有一些奇淫技巧的感觉. 另外,浏览器和一些第三方支持的不是太好, 我们在实际项目中,曾经遇到过与之相关的很奇葩的问题.

    1
    2
    3
    4
    5
    6
    7
    8
    function greet({ firstname, lastname }) {
    console.log(`hello,${firstname}.${lastname}!`);
    }

    greet({
    firstname: "steve",
    lastname: "curry",
    });

require

  • require的基本语法:

    • 在导出的文件中定义module.export,导出的对象的类型不予限定(可以是任何类型,字符串,变量,对象,方法),
    • 在引入的文件中调用require()方法引入对象即可
  • 换一种说法就是require相当于module.exports的传送门,module.exports后面的内容是什么,require的结果就是什么(对象、数字、字符串、函数……),再把require的结果赋值给某个变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //a.js中
    module.export = {
    a: function () {
    console.log(666);
    },
    };

    //b.js中
    var obj = require("../a.js");
    obj.a(); //666

import

  • 核心概念:导出的对象必须与模块中的值一一对应,换一种说法就是导出的对象与整个模块进行解构赋值。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //tt.js中
    export default{
    //(这种方法是最常使用的方法,加入default关键字代表在import时可以使用任意变量名并且不需要花括号{})
    b: function(){
    console.log("这是一个函数")
    }
    }

    export function(){ //导出函数

    }

    // 解构赋值语法(as关键字在这里表示将newF作为aa的数据接口暴露给外部,外部不能直接访问aa)
    export {newF as aa ,bb,cc}
    1
    2
    3
    4
    5
    6
    7
    8
    //bb.js中
    import aa from '...' //import常用语法(需要export中带有default关键字)可以任意指定import的名称

    import {...} from '...' // 基本方式,导入的对象需要与export对象进行解构赋值。

    import aa as As from '...' //使用as关键字,这里表示将aa代表As引入(当变量名称有冲突时可以使用这种方式解决冲突)

    import {a as Aa,b,c} //as关键字的其他使用方法

二者区别

  • require和import相互转换使用:

    1
    2
    3
    import list from "./list";
    //等价于
    var list = require("./list");
  • require和import分别使用在:

    • require 是赋值过程并且是运行时才执行,也就是异步加载。
    • require可以理解为一个全局方法,因为它是一个方法所以意味着可以在任何地方执行。
    • import 是解构过程并且是编译时执行。
    • import必须写在文件的顶部。
  • require和import的优缺点比较:

    • require的性能相对于import稍低,因为require是在运行时才引入模块并且还赋值给某个变量,而import只需要依据import中的接口在编译时引入指定模块所以性能稍高。

获取对象属性

  • js对象属性 通过点(.) 和 方括号([]) 的不同之处

  • 点操作符: 静态的。右侧必须是一个以属性名称命名的简单标识符

    • 属性名用一个标识符来表示。标识符必须直接出现再js程序中,它们不是数据类型,因此程序无法修改它们。
  • 中括号操作符: 动态的。方括号里必须是一个计算结果为字符串的表达式

    • 属性名通过字符串表示。字符串是js的数据类型
  • 主要有以下区别:

    • 中括号法可以用变量作为属性名,而点方法不可以

      1
      2
      3
      4
      5
      var obj = {};
      obj.name = "张三";
      var myName = "name";
      console.log(obj.myName); //undefined,访问不到对应的属性
      console.log(obj[myName]); //张三
  • 中括号法可以用数字作为属性名,而点语法不可以

    1
    2
    3
    4
    5
    6
    7
    var obj1 = {};
    // obj1.1=1;//Unexpected number
    obj1[2] = 2;
    // console.log(obj1.1)
    console.log(obj1[2]); //2
    // console.log(obj1.2)
    console.log(obj1); //{2: 2}
  • 中括号法可以动态访问的属性名,可以在程序运行时创建和修改属性,点操作符就不行!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var customer = {};
    var addr = ["北京", "上海", "广州", "深圳"];
    for (i = 0; i < 4; i++) {
    customer["address" + i] = addr[i];
    }
    console.log(addr);
    console.log(customer);
    var str = "";
    for (i = 0; i < 4; i++) {
    str += customer["address" + i] + "\t";
    }
    console.log(str);
  • 中括号法可以使用js的关键字和保留字作为属性名,而点语法不可以(尽量避免在变量或者属性中使用关键字或保留字)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    person["first name"] = "gogo2"; //first name包含一个空格
    console.log(person["first name"]);
    // console.log(person.first name)//书写都通不过
    person["for"] = "gogo_for"; //first name包含一个空格
    person.if = "gogo_if"; //first name包含一个空格
    console.log(person["for"]); //gogo_for
    console.log(person.for); //gogo_for
    console.log(person["if"]); //gogo_if
    console.log(person.if); //gogo_if
  • 简单利用:在数组原型链上增加一个去重得的方法,并能实现链式写法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    Array.prototype.myDistinct = function () {
    var obj = {};
    for (var i = 0; i < this.length; i++) {
    var cur = this[i];
    if (obj[cur] == cur) {
    //对象的属性名不能重复,重复就是修改;让对象的属性名和属性值相同,借以保存不重复的数组元素
    //--中括号法可以用数字作为属性名,而点语法不可以;
    this[i] = this[this.length - 1];
    this.length--;
    i--;
    continue;
    }
    obj[cur] = cur;
    }
    // console.log(obj);//{2: 2, 3: 3, 4: 4, 5: 5}
    obj = null;
    return this;
    };

    var arr = [5, 3, 3, 4, 5, 4, 2];
    arr.myDistinct().sort().pop();
    console.log(arr); //[2, 3, 4]
    var arr1 = [3, "a", 4, 5, 4, "b", "a"];
    console.log(arr1.myDistinct()); //[3, "a", 4, 5, "b"]

相关链接

  1. vue.js 1.x 文档
  2. vue.js 2.x 文档
  3. String.prototype.padStart(maxLength, fillString)
  4. js 里面的键盘事件对应的键码
  5. Vue.js双向绑定的实现原理
  6. URL中的hash(井号)
  7. babel-preset-env:你需要的唯一Babel插件
  8. Runtime transform 运行时编译es6