# 起步

# hello Vue.js

<div id="app">
    <h2>{{message}}</h2>
    <h1>{{name}}</h1>
</div>

<div>{{message}}</div>
<!-- 这里不绑定元素id 显示不出来 -->
1
2
3
4
5
6
7
<script src="/js/vue.js"></script>
    // let(变量)/const(常量)
    // 编程范式: 声明式编程
    const app = new Vue({
        el: '#app', // 用于挂载要管理的元素
        data: { // 定义数据
            message: 'hello js!',
            name: 'love you'
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11

代码是可以做到响应式的

# 命令式编程

原生js的做法 1.创建div元素,设置id属性 2.定义一个变量叫message 3.将message变量放在前面的div元素中显示 4.修改message的数据: 今天天气不错! 5.将修改后的数据再次替换到div元素

# Vue列表展示

    <ul>
        <!-- v-for遍历 -->
        <li v-for="item in movies">{{item}}</li>
    </ul>
1
2
3
4
const app = new Vue({
        el: '#app',
        data: {
        movies: ['星际穿越', '大话西游', '少年派', '盗梦空间']
    }
})
1
2
3
4
5
6

# 计数器小案例

<div id="app">
    <h2>当前计数: {{counter}}</h2>
    <!--<button v-on:click="counter++">+</button>-->
    <!--<button v-on:click="counter--;">-</button>-->
    <button v-on:click="add">+</button>
    <button v-on:click="sub">-</button>
    <!--下面是语法糖写法-->
    <!--<button @click="sub">-</button>-->
</div>
1
2
3
4
5
6
7
8
9
const obj = {
        counter: 0,
        message: 'abc'
    }
const app = new Vue({
        el: '#app',
        data: obj,
        methods: {
        add: function () {
            console.log('add被执行');
            this.counter++
            },
        sub: function () {
            console.log('sub被执行');
            this.counter--
            }
    },
        beforeCreate: function () {

        },
        created: function () {
            console.log('created');
        },
        mounted: function () {
            console.log('mounted');
        }
})
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

# MVVM

Model View View Model

`Model层(数据层)`

数据可能是固定的死数据,也肯呢个是来自服务器,从网络上请求下来的数据。在计数器的案例中,就是后面抽取出来的obj,当然,里面的数据可能没有这么简单。

`View层(视图层)`

在前端开发中,通常就是DOM层。主要的作用是给用户展示各种信息。

`VueModel层(视图模型层)`

视图模型层是View和Model沟通的桥梁。

一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时的反应到View中

另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data。

计数器的MVVM

# Vue的生命周期

# 插值操作

# Mustache语法

Mustache语法

两个花括号, 里面写message

花括号message花括号, Vue.js

mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式

花括号firstName + lastName花括号

花括号firstName + ' ' + lastName花括号

花括号firstName花括号 花括号lastName花括号

花括号counter * 2花括号

# v-once

禁止实时改变数据

<div id="app">
  <h2>{{message}}</h2>
  <h2 v-once>{{message}}</h2>
</div>
1
2
3
4
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
1
2
3
4
5
6

# v-html

将url链接插入到页面中

<div id="app">
  <h2>{{url}}</h2>
  <h2 v-html="url"></h2>
</div>
1
2
3
4
const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      url: '<a href="http://www.baidu.com">百度一下</a>'
    }
  })
1
2
3
4
5
6
7

# v-text

将文本插入到页面中

<h2>{{message}}, Vue</h2>
<h2 v-text="message">, Vue</h2>
<!--渲染  你好啊,覆盖掉了上面的你好啊,Vue,不常用-->
1
2
3
    data: {
      message: '你好啊'
    }
1
2
3

# v-pre

阻止渲染mustache语法

<h2 v-pre>{{message}}</h2>
<!--渲染  {{message}}-->
1
2

# v-cloak

在vue解析之前, div中有一个属性v-cloak 在vue解析之后, div中没有一个属性v-cloak

# 动态绑定属性

# v-bind基本使用

<div id="app">
  <!-- 错误的做法: 这里不可以使用mustache语法-->
  <!--<img src="{{imgURL}}" alt="">-->
  
  <!-- 正确的做法: 使用v-bind指令 -->
  <img v-bind:src="imgURL" alt="">
  <a v-bind:href="aHref">百度一下</a>

  <!--语法糖的写法-->
  <img :src="imgURL" alt="">
  <a :href="aHref">百度一下</a>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
data: {
message: '你好啊',
    imgURL: 'https://img11.360buyimg.com/mobilecms/s350x250_jfs/t1/20559/1/1424/73138/5c125595E3cbaa3c8/74fc2f84e53a9c23.jpg!q90!cc_350x250.webp',
    aHref: 'http://www.baidu.com'
}
1
2
3
4
5

# v-bind绑定class(对象)

    .active {
      color: red;
    }
1
2
3
<div id="app">
<!--原始写法-->
<!--<h2 class="active">{{message}}</h2>-->
<!--<h2 :class="active">{{message}}</h2>-->

<!--v-bind写法-->
<!--<h2 v-bind:class="{key1: value1, key2: value2}">{{message}}</h2>-->
<!--<h2 v-bind:class="{类名1: true, 类名2: boolean}">{{message}}</h2>-->
<!--可以控制类名的显示和隐藏-->

<!--v-bind最终写法-->
<h2 class="title" v-bind:class="{active: isActive, line: isLine}">{{message}}</h2>
<!--把isActive和isLine写在这里因为下面定义了变量-->
<!--当然不止定义变量可以加class类,也可以手动些class,现在h2就有三个class-->
<h2 class="title" v-bind:class="getClasses()">{{message}}</h2>
<!--如果后面绑定的属性太长可以封装成一个函数写在methods里面,但是class要写函数的名字()-->
<button v-on:click="btnClick">按钮</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data: {
      message: '你好啊',
      isActive: true,
      isLine: true
    },
    methods: {
      btnClick: function () {
        this.isActive = !this.isActive
      },
      getClasses: function () {
        return {active: this.isActive, line: this.isLine}
      }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# v-bind绑定class(数组)

<h2 class="title" :class="[active, line]">{{message}}</h2>
<h2 class="title" :class="getClasses()">{{message}}</h2>
1
2
data: {
      message: '你好啊',
      active: 'aaaaaa',
      line: 'bbbbbbb'
    },
    methods: {
      getClasses: function () {
        return [this.active, this.line]
      }
    }
1
2
3
4
5
6
7
8
9
10

# v-bind绑定style(对象)

<!--<h2 :style="{key(属性名): value(属性值)}">{{message}}</h2>-->

<!--'50px'必须加上单引号, 否则是当做一个变量去解析-->
<!--<h2 :style="{fontSize: '50px'}">{{message}}</h2>-->

<!--finalSize当成一个变量使用-->
<!--<h2 :style="{fontSize: finalSize}">{{message}}</h2>-->
<h2 :style="{fontSize: finalSize + 'px', backgroundColor: finalColor}">{{message}}</h2>
<!--简写-->
<h2 :style="getStyles()">{{message}}</h2>
1
2
3
4
5
6
7
8
9
10
data: {
    message: '你好啊',
    finalSize: 100,
    finalColor: 'red',
},
methods: {
    getStyles: function () {
    return {fontSize: this.finalSize + 'px', backgroundColor: this.finalColor}
    }
}
1
2
3
4
5
6
7
8
9
10

# v-bind绑定style(数组)

<h2 :style="[baseStyle, baseStyle1]">{{message}}</h2>
1
data: {
    message: '你好啊',
    baseStyle: {backgroundColor: 'red'},
    baseStyle1: {fontSize: '100px'},
}
1
2
3
4
5

# 计算属性

# 基本操作

<h2>{{firstName + ' ' + lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>

<!--methods里面的写法-->
<h2>{{getFullName()}}</h2>

<!--computed里面的写法-->
<h2>{{fullName}}</h2>
1
2
3
4
5
6
7
8
data: {
      firstName: 'Lebron',
      lastName: 'James'
    },
    // computed: 计算属性()
computed: {
      fullName: function () {
        return this.firstName + ' ' + this.lastName
      }
    },
methods: {
      getFullName() {
        return this.firstName + ' ' + this.lastName
      }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

计算属性和方法属性都是可以进行渲染的

# 复杂操作

  <h2>总价格: {{totalPrice}}</h2>
<!--页面渲染总价格:409 改变books里面数据页面会动态改变-->
1
2
data: {
books: [
    {id: 110, name: 'TypeScript', price: 119},
    {id: 111, name: 'python', price: 105},
    {id: 112, name: 'c++', price: 98},
    {id: 113, name: 'java', price: 87},
      ]
},
computed: {
    totalPrice: function () {
    let result = 0
    for (let i=0; i < this.books.length; i++) {
          result += this.books[i].price
        }
    return result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

计算属性绑定和methods属性绑定的区别就是计算属性在{{}}里面直接写计算属性的方法名字,methods属性需要写方法名字+(),下面有说到为什么计算属性只写一个方法名字.

# setter和getter

<h2>{{fullName}}</h2>
1
data: {
      firstName: 'Kobe',
      lastName: 'Bryant'
    },
computed: {
//之前的写法
// fullName: function () {
//   return this.firstName + ' ' + this.lastName
// }

// 计算属性一般是没有set方法, 是只读属性.
//使用的时候一般使用的get方法,所以直接忽略了get:function(){},直接写属性名:function(){}

//完整的计算属性
fullName: {
    set: function(newValue) {
    // console.log('-----', newValue);
    const names = newValue.split(' ');
    this.firstName = names[0];
    this.lastName = names[1];
    },
    
    get: function () {
    return this.firstName + ' ' + this.lastName
    }
}
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

可以手动设置set属性

# 计算属性和methods属性区别

计算属性和methods属性区别

计算属性内部有缓存,可以提高效率

methods属性里面的方法,如果dom页面写了多个重复结构,methods属性里面的方法会调用多次

相反,计算属性里面的方法只会被调用一次,降低了损耗.如果有方法和函数要写,尽量写到计算属性里面,举个例子,如果有for循环的需求,methods属性会调用多次,计算属性只会调用一次.

# 事件监听

# v-on基本使用

<h2>{{counter}}</h2>
<!--旧的绑定函数写法-->
<!--<button v-on:click="counter++">+</button>-->
<!--<button v-on:click="counter--">-</button>-->
<!--<button v-on:click="increment">+</button>-->
<!--<button v-on:click="decrement">-</button>-->

<!--语法糖写法-->
<button @click="increment">+</button>
<button @click="decrement">-</button>
1
2
3
4
5
6
7
8
9
10
data: {
      counter: 0
    },
methods: {
      increment() {
        this.counter++
      },
      decrement() {
        this.counter--
      }
    }
1
2
3
4
5
6
7
8
9
10
11

# v-on的参数

<!--1.事件调用的方法没有参数-->
<button @click="btn1Click()">按钮1</button>
<button @click="btn1Click">按钮1</button>

<!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 这个时候, Vue会默认将浏览器生产的event事件对象作为参数传入到方法-->
<button @click="btn2Click">按钮2</button>

<!--3.方法定义时, 我们需要event对象, 同时又需要其他参数-->
<!-- 在调用方式, 如何手动的获取到浏览器参数的event对象: $event-->
<button @click="btn3Click(abc, $event)">按钮3</button>
1
2
3
4
5
6
7
8
9
10
data: {
      message: '你好啊',
      abc: 123
    },
    
methods: {
      btn1Click() {
        console.log("btn1Click");
      },
      btn2Click(event) {
        console.log('--------', event);
      },
      btn3Click(abc, event) {
        console.log('++++++++', abc, event);
      }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# v-on的修饰符

在某些情况下,我们拿到event的目的可能是进行一些事件处理。 Vue提供了修饰符来帮助我们方便的处理一些事件

v-on的修饰符

.stop - 调用 event.stopPropagation()。

.prevent - 调用 event.preventDefault()。

.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。

.native - 监听组件根元素的原生事件。

.once - 只触发一次回调。

# 条件判断

# v-if基本使用

<!-- v-if一般后面写布尔值 -->
<h2 v-if="isShow">
    <div>abc</div>
    <div>abc</div>
    <div>abc</div>
    <div>abc</div>
    <div>abc</div>
    {{message}}
</h2>
1
2
3
4
5
6
7
8
9
    data: {
      message: '你好啊',
      isShow: true
    }
1
2
3
4

# v-if 和 v-else

<h2 v-if="isShow">
    <div>abc</div>
    <div>abc</div>
    <div>abc</div>
    <div>abc</div>
    <div>abc</div>
    {{message}}
</h2>
  <h1 v-else>isShow为false时, 显示我</h1>
</div>
1
2
3
4
5
6
7
8
9
10
    data: {
      message: '你好啊',
      isShow: true
    }
1
2
3
4

# v-else-if

<h2 v-if="score>=90">优秀</h2>
<h2 v-else-if="score>=80">良好</h2>
<h2 v-else-if="score>=60">及格</h2>
<h2 v-else>不及格</h2>
<!--也可以写在计算属性里同样效果-->
<h1>{{result}}</h1>
1
2
3
4
5
6
data: {
      score: 99
      //只需要改变score值页面就能渲染分数标准
    },
computed: {
    result() {
    let showMessage = '';
    if (this.score >= 90) {
        showMessage = '优秀'
    } else if (this.score >= 80) {
        showMessage = '良好'
    }
    return showMessage
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 登录小案例

<span v-if="isUser">
    <label for="username">用户账号</label>
    <input type="text" id="username" placeholder="用户账号" key="username">
</span>

<span v-else>
    <label for="email">用户邮箱</label>
    <input type="text" id="email" placeholder="用户邮箱" key="email">
</span>

<button @click="isUser = !isUser">切换类型</button>
1
2
3
4
5
6
7
8
9
10
11
data: {
    isUser: true
}
1
2
3

用户点击切换类型就可以从账号登录切换到邮箱登录,可是为什么要给input输入框里面写key呢?其实就是如果在账号登录情况下输入了账号,点击切换类型到邮箱.那么input输入框里面的数据不会清空.key就是可以作为唯一的标识来使用

# v-show

v-show与v-if的区别

<!--v-if: 当条件为false时, 包含v-if指令的元素, 根本就不会存在dom中-->
<h2 v-if="isShow" id="aaa">{{message}}</h2>

<!--v-show: 当条件为false时, v-show只是给我们的元素添加一个行内样式: display: none-->
<h2 v-show="isShow" id="bbb">{{message}}</h2>
1
2
3
4
5

当需要在显示与隐藏之间切换很频繁时,使用v-show 当只有一次切换时,通过使用v-if

data: {
    message: '你好啊',
    isShow: true
}
1
2
3
4

# 循环遍历

# v-for(数组)

<!--1.在遍历的过程中,没有使用索引值(下标值)-->
<ul>
    <li v-for="item in names">{{item}}</li>
</ul>

<!--2.在遍历的过程中, 获取索引值-->
<ul>
    <li v-for="(item, index) in names">
      {{index+1}}.{{item}}
    </li>
</ul>
1
2
3
4
5
6
7
8
9
10
11
    data: {
      names: ['why', 'kobe', 'james', 'curry']
    }
1
2
3

# v-for(对象)

<!--1.在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value-->
  <ul>
    <li v-for="item in info">{{item}}</li>
  </ul>


  <!--2.获取key和value 格式: (value, key) -->
  <ul>
    <li v-for="(value, key) in info">{{value}}-{{key}}</li>
  </ul>


  <!--3.获取key和value和index 格式: (value, key, index) -->
  <ul>
    <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
  </ul>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    data: {
      info: {
        name: 'why',
        age: 18,
        height: 1.88
      }
    }
1
2
3
4
5
6
7

# key

<ul>
    <li v-for="item in letters" :key="item">{{item}}</li>
</ul>
<!--遍历出来ABCDE-->
<!-- key要和关键标识一样 要一一对应 -->
1
2
3
4
5
data: {
    letters: ['A', 'B', 'C', 'D', 'E']
    }
})
1
2
3
4

官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。 为什么需要这个key属性呢,这个其实和Vue的虚拟DOM的Diff算法有关系。

当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点,我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的。即把C更新成F,D更新成C,E更新成D,最后再插入E,很没有效率,所以我们需要使用key来给每个节点做一个唯一标识.Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。 key的作用主要是为了高效的更新虚拟DOM

# 数组的响应式方法

//push方法(同unshift一样可以添加多个)
this.push('aaa')
this.push('aaaa', 'bbbb', 'cccc')
//同理还有pop,shift,onshift,splice,sort,reverse
//都可以进行响应式操作dom

//还有Vue中的一个方法set也可以实现响应式
//set(要修改的对象, 索引值, 修改后的值)
Vue.set(this, 0, 'a')//往数组最前面插入a

//通过索引值修改数组中的元素 不是响应式
this[0] = 'aa';
//可以在数组的第0位插入aa,但是页面不是响应式更新
1
2
3
4
5
6
7
8
9
10
11
12
13

# 小练习

点击数组的每一位,当点击到哪一位哪一位的数据颜色变红

    .active {
      color: red;
    }
1
2
3
<ul>
    <li v-for="(item, index) in movies"
        :class="{active: currentIndex === index}"
        @click="liClick(index)">
      {{index}}.{{item}}
    </li>

<!--<li :class="{active: 0===currentIndex}"></li>-->
<!--<li :class="{active: 1===currentIndex}"></li>-->
<!--<li :class="{active: 2===currentIndex}"></li>-->
<!--<li :class="{active: 3===currentIndex}"></li>-->
  </ul>
1
2
3
4
5
6
7
8
9
10
11
12
    data: {
      movies: ['海王', '海贼王', '加勒比海盗', '海尔兄弟'],
      currentIndex: 0//谁显示红色
    },
    methods: {
      liClick(index) {
        this.currentIndex = index
      }
    }
1
2
3
4
5
6
7
8
9

# 购物车案例

<div v-if="books.length">
    <table>
      <thead>
      <tr>
        <th></th>
        <th>书籍名称</th>
        <th>出版日期</th>
        <th>价格</th>
        <th>购买数量</th>
        <th>操作</th>
      </tr>
      </thead>

      <tbody>
      <tr v-for="(item, index) in books">
        <td>{{item.id}}</td>
        <td>{{item.name}}</td>
        <td>{{item.date}}</td>
        <td>{{item.price | showPrice}}</td>
<!-- |过滤器 是一个函数,要过滤的东西作为参数传递过去-->

<!--左右+-按钮添加区域-->
        <td>
<button @click="decrement(index)" v-bind:disabled="item.count <= 1">-</button>
        {{item.count}}
<button @click="increment(index)">+</button>
        </td>
        
<!--移出书籍区域-->
        <td><button @click="removeHandle(index)">移除</button></td>
      </tr>
      </tbody>
    </table>

<h2>总价格: {{totalPrice | showPrice}}</h2>
</div>

  <h2 v-else>购物车为空</h2>
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
data: {
    books: [
      {
        id: 1,
        name: '《算法导论》',
        date: '2006-9',
        price: 85.00,
        count: 1
      },
      {
        id: 2,
        name: '《UNIX编程艺术》',
        date: '2006-2',
        price: 59.00,
        count: 1
      },
      {
        id: 3,
        name: '《编程珠玑》',
        date: '2008-10',
        price: 39.00,
        count: 1
      },
      {
        id: 4,
        name: '《代码大全》',
        date: '2006-3',
        price: 128.00,
        count: 1
      },
    ]
  },
  methods: {
    // getFinalPrice(price) {
    //   return '¥' + price.toFixed(2)
    // }
    //增加购买数量
    increment(index) {
      this.books[index].count++
    },
    decrement(index) {
      this.books[index].count--
    },
    removeHandle(index) {
      this.books.splice(index, 1)
      //把自己删掉
    }
  },
  computed: {
    totalPrice() {
      let totalPrice = 0
      for (let i = 0; i < this.books.length; i++) {
        totalPrice += this.books[i].price * this.books[i].count
      }
      return totalPrice

      // for (let i in/of this.books)
      // reduce
    }
  },
  filters: {
    showPrice(price) {
      return '¥' + price.toFixed(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
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

# 高阶函数(数组方法)

# 编程范式

编程范式分为命令式编程和声明式编程

编程范式: 面向对象编程(第一公民:对象) 函数式编程(第一公民:函数)

# 数组小例子

1.取出所有小于100的数字

2.将所有小于100的数字进行转化: 全部*2

3.将所有*2后的数字相加,得到最终的结果

普通写法

const nums = [10, 20, 111, 222, 444, 40, 50];
//1: 取出所有小于100的数字
let newNums = []
for (let n of nums) {
  if (n < 100) {
    newNums.push(n)
  }
}

//2:将所有小于100的数字进行转化: 全部*2
let new2Nums = []
for (let n of newNums) {
  new2Nums.push(n * 2)
}

// 3:将所有new2Nums数字相加,得到最终的结果
let total = 0
for (let n of new2Nums) {
  total += n
}
console.log(total);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

高阶写法

//1.filter函数的使用(过滤)
const nums = [10, 20, 111, 222, 444, 40, 50];
let newNums = nums.filter(function (n) {
  return n < 100
})
// console.log(newNums);10, 20, 40, 50

//filter中的回调函数有一个要求
//必须返回一个boolean值
//当返回true时, 函数内部会自动将这次回调的n加入到新的数组中
//当返回false时, 函数内部会过滤掉这次的n

// 2.map函数的使用
let new2Nums = newNums.map(function (n) {
  return n * 2
})
console.log(new2Nums);// 20, 40, 80, 100

// 3.reduce函数的使用
// reduce作用:对数组中所有的内容进行汇总(相加)
// reduce第一个参数.表示上次回调的数值,或者初始值
//第二个参数表示正在处理的数组元素
let total = new2Nums.reduce(function (preValue, n) {
  return preValue + n //0+20
}, 0)
console.log(total);//240

//第一次: preValue 0 n 20
//第二次: preValue 20 n 40
//第三次: preValue 60 n 80
//第四次: preValue 140 n 100
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

简写(连写)

const nums = [10, 20, 111, 222, 444, 40, 50]
let total = nums.filter(function (n) {
  return n < 100//10,20,40,50
}).map(function (n) {
  return n * 2  //20,40,80,100
}).reduce(function (prevValue, n) {
  return prevValue + n  
}, 0)
console.log(total);
1
2
3
4
5
6
7
8
9

箭头函数写法

let total = nums.filter(n => n < 100).map(n => n * 2).reduce((pre, n) => pre + n);
console.log(total);
1
2

# v-model

# v-model基本使用

<input type="text" v-model="message">
<!-- 在v-model里面直接添加变量 绑定 -->
{{message}}
1
2
3
    data: {
      message: '你好啊'
    }
1
2
3

Vue中使用v-model指令来实现表单元素数据的双向绑定。当我们在输入框输入内容时,因为input中的v-model绑定了message,所以会实时将输入的内容传递给message,message发生改变。

当message发生改变时,因为上面我们使用Mustache语法,将message的值插入到DOM中,所以DOM会发生响应的改变.v-model也用于textarea元素

# v-model原理

<!-- v-model其实就是两个属性的拼接 value 和事件对象 -->
<!--v-model => v-bind:value v-on:input-->

<!--<input type="text" :value="message" @input="valueChange">-->

<!--直接写在行间也可以-->
<input type="text" :value="message" @input="message = $event.target.value">
<h2>{{message}}</h2>
1
2
3
4
5
6
7
8

v-model本质上包含了两个操作. 1.v-bind绑定一个value属性 2.v-on指令给当前元素绑定input事件

data: {
    message: '你好啊'
    },
    methods: {
      valueChange(event) {
        this.message = event.target.value;
        //获取实时的input的value值
      }
    }
1
2
3
4
5
6
7
8
9

# v-model(radio)

<label for="male">
    <input type="radio" id="male" value="" v-model="sex"></label>
<!-- 两个都加上name=sex才能互斥(单选),
但是两个v-model也可以实现互斥,所以选择删除掉name属性 -->
<label for="female">
    <input type="radio" id="female" value="" v-model="sex"></label>
  <h2>您选择的性别是: {{sex}}</h2>
1
2
3
4
5
6
7
8
9
    data: {
      message: '你好啊',
      sex: '女'
    }
1
2
3
4

# v-model(checkbox)

<!--checkbox单选框-->
<label for="agree">
    <input type="checkbox" id="agree" v-model="isAgree">同意协议
</label>
    <h2>您选择的是: {{isAgree}}</h2>
    <button :disabled="!isAgree">下一步</button>
<!--checkbox多选框-->
<input type="checkbox" value="篮球" v-model="hobbies">篮球
<input type="checkbox" value="足球" v-model="hobbies">足球
<input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球
<input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球
    <h2>您的爱好是: {{hobbies}}</h2>
  
<!-- 值绑定 -->
<label v-for="item in originHobbies" :for="item">
    <input type="checkbox" :value="item" :id="item" 
    v-model="hobbies">{{item}}
</label>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data: {
    message: '你好啊',
    isAgree: false, // 单选框
    hobbies: [], // 多选框,
    originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', 
    '台球', '高尔夫球']
}
1
2
3
4
5
6
7

# v-model(select)

<!--1.选择一个-->
<select name="abc" v-model="fruit">
    <option value="苹果">苹果</option>
    <option value="香蕉">香蕉</option>
    <option value="榴莲">榴莲</option>
    <option value="葡萄">葡萄</option>
</select>
    <h2>您选择的水果是: {{fruit}}</h2>
    
<!--2.选择多个-->
<select name="abc" v-model="fruits" multiple>
    <option value="苹果">苹果</option>
    <option value="香蕉">香蕉</option>
    <option value="榴莲">榴莲</option>
    <option value="葡萄">葡萄</option>
</select>
    <h2>您选择的水果是: {{fruits}}</h2>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data: {
      message: '你好啊',
      fruit: '香蕉',
      fruits: []
    }
1
2
3
4
5

# 值绑定

就是动态的给value赋值,前面的表单案例value中的值,都是在定义input的时候直接给定的,真实开发中,这些input的值可能是从网络获取或定义在data中的.可以通过v-bind:value动态的给value绑定值,值绑定顾名思义就是v-bind在input中的应用.

# v-model修饰符

<!-- 不会调用的那么频繁 -->
<!--1.修饰符: lazy-->
<input type="text" v-model.lazy="message">
<h2>{{message}}</h2>

<!-- 必须输入数字 默认情况下是string类型-->
<!--2.修饰符: number-->
<input type="number" v-model.number="age">
<h2>{{age}}-{{typeof age}}</h2>

<!-- 去掉空格 -->
<!--3.修饰符: trim-->
<input type="text" v-model.trim="name">
<h2>您输入的名字:{{name}}</h2>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
data: {
      message: '你好啊',
      age: 0,
      name: ''
    }
  })

  var age = 0
  age = '1111'
  age = '222'
1
2
3
4
5
6
7
8
9
10

# 组件化开发

如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

我们将一个完整的页面分成很多个组件。 每个组件都用于实现页面的一个功能块。 而每一个组件又可以进行细分。

# Vue的组件化思想

# 注册组件

组件的使用分成三个步骤:1.创建组件构造器2.注册组件3.使用组件。

// 1.创建组件构造器对象
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>我是标题</h2>
        <p>我是内容, 哈哈哈哈</p>
        <p>我是内容, 呵呵呵呵</p>
      </div>`
  })

  // 2.注册组件 (组件的标签名,组件构造器)
  Vue.component('my-cpn', cpnC)
  
  
  el: '#app',
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--3.使用组件
可以直接在页面上使用,也可以嵌套使用-->
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>

  <div>
    <div>
      <my-cpn></my-cpn>
    </div>
  </div>
  
  <my-cpn></my-cpn>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 注册步骤

注册组件步骤解析

1.Vue.extend() 调用Vue.extend()创建的是一个组件构造器。 通常在创建组件构造器时,传入template代表我们自定义组件的模板。该模板就是在使用到组件的地方,要显示的HTML代码。

2.Vue.component() 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。

所以需要传递两个参数:1、注册组件的标签名 2、组件构造器

3.组件必须挂载在某个Vue实例下,否则它不会生效(这里指写在页面上的app标签外)

# 全局组件局部组件

<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>

<div id="app2">
  <cpn></cpn>
</div>
1
2
3
4
5
6
7
8
9
// 在开发中一般只用到一个Vue实例,一般只用全局组件
// 1.创建组件构造器
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>我是标题</h2>
        <p>我是内容,哈哈哈哈啊</p>
      </div>
    `
  })

// 2.注册组件(全局组件, 意味着可以在多个Vue的实例下面使用)
// Vue.component('cpn', cpnC)

// 局部组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      // cpn组件的标签名:构造器
      cpn: cpnC
    }
  })

  const app2 = new Vue({
    el: '#app2'
  })
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

当我们通过调用Vue.component()注册组件时,组件的注册是全局的 这意味着该组件可以在任意Vue示例下使用。 如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

# 父组件子组件

// 1.创建第一个组件构造器(子组件)
  const cpnC1 = Vue.extend({
    template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容, 哈哈哈哈</p>
      </div>
    `
  })


// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
    template: `
      <div>
        <h2>我是标题2</h2>
        <p>我是内容, 呵呵呵呵</p>
        <cpn1></cpn1>
      </div>
    `,
// 在第二个组件里面注册第一个组件
    components: {
      cpn1: cpnC1
    }
  })


// root 根组件 爷爷组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn2: cpnC2
    }
  })
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
<cpn2></cpn2>
<!--<cpn1></cpn1>-->
<!-- cpn1会报错 -->
1
2
3

# 注册组件语法糖

// 1.旧的创建组件构造器
//const cpn1 = Vue.extend()

// 2.新的注册组件(全局组件)
//主要是省去了调用Vue.extend()的步骤
//可以直接使用一个对象来代替

//第一个参数组件名,第二个组件模板
  Vue.component('cpn1', {
    template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容, 哈哈哈哈</p>
      </div>
    `
  })

  // 2.注册局部组件的语法糖
const app = new Vue({
    el: '#app',
    data: {
    message: '你好啊'
    },
    components: {
      'cpn2': {
        template: `
          <div>
            <h2>我是标题2</h2>
            <p>我是内容, 呵呵呵</p>
          </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
  <cpn1></cpn1> 
  <cpn2></cpn2>
<!--  全局组件和局部组件都在Vue实例上都可以在页面显示-->
1
2
3

# 组件模板分离

1.script标签,注意:类型必须是text/x-template

<script type="text/x-template" id="cpn">
<div>
  <h2>我是标题</h2>
  <p>我是内容,哈哈哈</p>
</div>
</script>
1
2
3
4
5
6

2.template标签

<template id="cpn">
  <div>
    <h2>我是标题</h2>
    <p>我是内容,呵呵呵</p>
  </div>
</template>
1
2
3
4
5
6
  // 1.注册一个全局组件
  Vue.component('cpn', {
    template: '#cpn'//绑定模板id
  })
1
2
3
4
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<!--多次调用组件名也可以渲染多次-->
1
2
3
4

# 组件中的数据存放

组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板,也应该有属性自己的数据data,在组件中,写{{}}语法不能访问Vue实例对象的data

即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变的非常臃肿。所以组件内也有一个自己的data(此处为函数)

Vue.component('cpn', {
    template: '#cpn',
    data() {
      return {
        title: 'abc'
      }
    }
  })
1
2
3
4
5
6
7
8
<template id="cpn">
  <div>
    <h2>{{title}}</h2>
    <p>我是内容,呵呵呵</p>
  </div>
</template>
1
2
3
4
5
6

渲染多次cpn(组件),此时的h2 title已经变成了指定的abc

# 为什么data是函数

const obj = {
    counter: 0
  }
  Vue.component('cpn', {
    template: '#cpn',
    // data() {
    //   return {
    //     counter: 0
    //   }
    // },
    // 相互影响/如果是函数就互不影响
    data() {
      return obj;
      // return counter++;
    },
    methods: {
      increment() {
        this.counter++
      },
      decrement() {
        this.counter--
      }
    }
  })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template id="cpn">
  <div>
    <h2>当前计数: {{counter}}</h2>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13

首先,如果不是一个函数,Vue直接就会报错。 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。

因为对象是一个引用地址,而函数有自己作用域(可以这么理解),此例中模板的data函数的会创建三个相同的函数,各自执行自己的作用(域),对象的话都执行的同一个地址

# 组件的父传子

  // 父传子: props
  //子组件
  const cpn = {
    template: '#cpn',
    // props: ['cmovies', 'cmessage'],
    // covies就相当于一个变量

    props: {
      // 1.类型限制
      // cmovies: Array,
      // cmessage: String,

      // 2.提供一些默认值, 以及必传值
      cmessage: {
        type: String,
        default: 'aaaaaaaa',
        //在没传的情况下cmessage的情况下aaa
        required: true
        //用cpn时cmessage是必传的,不传会报错

      },
      // 类型是对象或者数组时, 默认值必须是一个函数
      cmovies: {
        type: Array,
        default() {
          return []  
        }
      }
    },
    data() {
      return {}
    },
    methods: {}
  }
  
//父组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      movies: ['海王', '海贼王', '海尔兄弟']
    },
    components: {
      cpn
    }
  })
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
<!--父组件-->
<div id="app">
  <!--<cpn v-bind:cmovies="movies"></cpn>-->
  <!-- 已经传过来了 -->

  <cpn :cmessage="message" :cmovies="movies"></cpn>
</div>


<!--子组件-->
<template id="cpn">
  <div>
    <ul>
      <li v-for="item in cmovies">{{item}}</li>
    </ul>
    <h2>{{cmessage}}</h2>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 组件中的props

子组件的props

props: {
      cInfo: {
        type: Object,
        default() {
          return {}
        }
      },
      childMyMessage: {
        type: String,
        default: ''
      }
    }
1
2
3
4
5
6
7
8
9
10
11
12

父组件的data

data: {
      info: {
        name: 'why',
        age: 18,
        height: 1.88
      },
      message: 'aaaaaa'
    },
1
2
3
4
5
6
7
8

在父组件里面的子组件,绑定的时候会不支持驼峰式写法,要用-来拼接 但是在脚手架里面是支持直接写子组件中的props属性里面的属性的

<!-- v-bind不支持驼峰 -->
<cpn :c-info="info" :child-my-message="message" v-bind:class></cpn>
1
2

除了数组之外,props也可以使用对象,当需要对props进行类型等验证时,就需要对象写法,支持验证的数据类型有String Number``Boolean``Array``Object``Date``Function``Symbol

# 组件的子传父

  // 1.子组件
  const cpn = {
    template: '#cpn',
    data() {
      return {
        // 分类
        categories: [
          {id: 'aaa', name: '热门推荐'},
          {id: 'bbb', name: '手机数码'},
          {id: 'ccc', name: '家用家电'},
          {id: 'ddd', name: '电脑办公'},
        ]
      }
    },
    methods: {
      btnClick(item) {
        // 发射事件: 自定义事件
        // 准备往父组件传入item 发送一个事件,父组件监听item-click事件
        // 父组件处理item-click事件(处理事件写在父组件内)
        this.$emit('item-click', item)
      }
    }
  }

  // 2.父组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn
    },
    methods: {
      cpnClick(item) {
        console.log('cpnClick', 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
28
29
30
31
32
33
34
35
36
37
38
39
<!--父组件模板-->
<div id="app">
  <cpn @item-click="cpnClick"></cpn>
</div>

<!--子组件模板-->
<template id="cpn">
  <div>
    <button v-for="item in categories"
            @click="btnClick(item)">
      {{item.name}}
    </button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 父子组件通信案例

data: {
      num1: 1,
      num2: 0
    },
    methods: {
      num1change(value) {
        this.num1 = parseFloat(value)
      },
      num2change(value) {
        this.num2 = parseFloat(value)
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        props: {
          //只对类型进行限制
          number1: Number,
          number2: Number
        },
        data() {
          // 初始化父传递过来的值
          return {
            dnumber1: this.number1,
            dnumber2: this.number2
          }
        },
        methods: {
          num1Input(event) {
            // 1.将input中的value赋值到dnumber中
            this.dnumber1 = event.target.value;
            // 2.为了让父组件可以修改值, 发出一个事件
            this.$emit('num1change', this.dnumber1)
            // 3.同时修饰dnumber2的值
            this.dnumber2 = this.dnumber1 * 100;
            this.$emit('num2change', this.dnumber2);
          },
          
          num2Input(event) {
            this.dnumber2 = event.target.value;
            this.$emit('num2change', this.dnumber2)

            // 同时修饰dnumber2的值
            this.dnumber1 = this.dnumber2 / 100;
            this.$emit('num1change', this.dnumber1);
          }
        }
      }
    }
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

父组件内的子组件

<div id="app">
  <cpn :number1="num1"
       :number2="num2"
       @num1change="num1change"
       @num2change="num2change"/>
       <!-- 如果组件标签中间没有内容可以写成单标签 -->
</div>
1
2
3
4
5
6
7

子组件

<template id="cpn">
  <div>
    <h2>props:{{number1}}</h2>
    <h2>data:{{dnumber1}}</h2>
    <!--<input type="text" v-model="dnumber1">-->
    <input type="text" :value="dnumber1" @input="num1Input">
    <h2>props:{{number2}}</h2>
    <h2>data:{{dnumber2}}</h2>
    <!--<input type="text" v-model="dnumber2">-->
    <input type="text" :value="dnumber2" @input="num2Input">
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12

也可以用watch实现监听,替换掉上部代码的methods里面的所有, watch跟data(){},template,props,methods一样,都是组件对象的option.

watch: {
    dnumber1(newValue) {
        this.dnumber2 = newValue * 100;
        this.$emit('num1change', newValue);
    },
    dnumber2(newValue) {
        this.number1 = newValue / 100;
        this.$emit('num2change', newValue);
    }
}
1
2
3
4
5
6
7
8
9
10

# 父访问子children,refs

methods: {
    btnClick() {
    // 1.$children
    // console.log(this.$children);
    // for (let c of this.$children) {
    //   console.log(c.name);
    //   c.showMessage();
    // }
    // console.log(this.$children[3].name);

// 2.$refs => 对象类型, 默认是一个空的对象 ref='bbb'
        
// 在组件上面添加ref属性,就可以通过$refs来调用该属性
        console.log(this.$refs.aaa.name);
      }
    },
components: {
    cpn: {
        template: '#cpn',
    data() {
        return {
        name: '我是子组件的name'
        }
    },
    methods: {
        showMessage() {
        console.log('showMessage');
            }
        }
    }
}
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

children的缺陷和refs的区别

通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。

有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs

$refs和ref指令通常是一起使用的。 首先,我们通过ref给某一个子组件绑定一个特定的ID。 其次,通过this.$refs.ID就可以访问到该组件了

# 子访问父parent,root

data: {
      message: '你好啊'
    },
components: {
    cpn: {
    template: '#cpn',
    data() {
        return {
        name: '我是cpn组件的name'
          }
    },
    components: {
        ccpn: {
        template: '#ccpn',
        methods: {
            btnClick() {
            // 1.访问父组件$parent
            // console.log(this.$parent);
            // console.log(this.$parent.name);

            // 2.访问根组件$root
            console.log(this.$root);
            //访问Vue实例
            console.log(this.$root.message);
            //访问Vue实例上的message
              }
            }
          }
        }
    }
}
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
<!--爷爷组件-->
<div id="app">
  <cpn></cpn>
</div>

<!--爸爸组件-->
<template id="cpn">
  <div>
    <h2>我是cpn组件</h2>
    <ccpn></ccpn>
  </div>
</template>

<!--儿子组件-->
<template id="ccpn">
  <div>
    <h2>我是子组件</h2>
    <button @click="btnClick">按钮</button>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 组件化高级

# slot 插槽

移动开发中,几乎每个页面都有导航栏。 导航栏我们必然会封装成一个插件,比如nav-bar组件。 一旦有了这个组件,我们就可以在多个页面中复用了。如果,我们每一个单独去封装一个组件,显然不合适:比如每个页面都返回,这部分内容我们就要重复去封装。 但是,如果我们封装成一个,好像也不合理:有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。 是搜索框,还是文字,还是菜单。由调用者自己来决定。

const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
1
2
3
4
5
6
7
8
9
10
11

1.插槽的基本使用

2.插槽的默认值 button

3.如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素

<div id="app">
  <cpn></cpn>
  <cpn><span>哈哈哈</span></cpn>
  <cpn><i>呵呵呵</i></cpn>
  
  <cpn>
    <!-- 会全部替换 -->
    <i>呵呵呵</i>
    <div>我是div元素</div>
    <p>我是p元素</p>
  </cpn>

  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>


<template id="cpn">
  <div>
    <h2>我是组件</h2>
    <p>我是组件, 哈哈哈</p>
    <!-- 给插槽里面给默认值 -->
    <slot><button>按钮</button></slot>
    <!--<button>按钮</button>-->
  </div>
</template>
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

# 具名插槽的使用

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
1
2
3
4
5
6
7
8
9
10
11
<!-- 导航类 -->
<div id="app">
  <!-- 此处替换掉了中间 -->
  <cpn><span slot="center">标题</span></cpn>
  
  <cpn><button slot="left">返回</button></cpn>
</div>

<template id="cpn">
  <div>
    <slot name="left"><span>左边</span></slot>
    <slot name="center"><span>中间</span></slot>
    <slot name="right"><span>右边</span></slot>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 组件编译作用域

const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      isShow: true
      //父组件内可以展示
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            isShow: false
            //子组件内不能展示
          }
        }
      },
    }
  })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
  <cpn v-show="isShow"></cpn>
<!--子组件在父组件内调用,子组件原本不能展示,在此可以展示
因为子组件在父组件的域-->
</div>

<template id="cpn">
  <div>
    <h2>我是子组件</h2>
    <p>我是内容, 哈哈哈</p>
    <button v-show="isShow">按钮</button>
<!--不能展示button,因为在自己的域已经写死了button false-->
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 插槽案例

const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
          }
        }
      }
    }
  })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<!--列表展示-->
  <cpn></cpn>

<!-- 不想以列表展示 -->
  <cpn>
<!--目的是获取子组件中的pLanguages-->
<!-- 固定写法 slot引用下面模板的插槽 .data就是传过来的data,data就是pleanguages-->
<template slot-scope="slot">
<!--<span v-for="item in slot.data"> - {{item}}</span>-->
      <!-- 把数组转换为字符串并且以-来拼接 -->
      <span>{{slot.data.join(' - ')}}</span>
    </template>
  </cpn>

  <cpn>
    <!--目的是获取子组件中的pLanguages-->
    <template slot-scope="slot">
      <span>{{slot.data.join(' * ')}}</span>
    </template>
  </cpn>
</div>

<template id="cpn">
  <div>
    <slot :data="pLanguages">
      <ul>
        <li v-for="item in pLanguages">{{item}}</li>
      </ul>
    </slot>
  </div>
</template>
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

# 前端模块化

# 实现雏形

项目组长给小红和小明分配了一个任务,小明和小红都在写代码.小明写了aaa.js,小红写了bbb.js, aaa.js模块化代码

//小明
var moduleA = (function () {
  // 导出的对象
  var obj = {}
  
  var name = '小明'
  var age = 22

  function sum(num1, num2) {
    return num1 + num2
  }

  var flag = true

  if (flag) {
    console.log(sum(10, 20));
  }


  obj.flag = flag;//变量
  obj.sum = sum;//函数

  return obj
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//小红bbb.js模块化代码
var moduleB = (function () {
  var obj = {}

  var name = '小红'
  var flag = false

  console.log(name);

  obj.flag = flag

  return obj
})()
1
2
3
4
5
6
7
8
9
10
11
12
13

之所以会这样写因为如果有一天,如果小明再写了一个mmm.js文件,小红也写了一个nnn.js文件,他们有都有两个js文件.

结果项目组长想拿来小明和小红的代码,自己写一个main.js文件将他们进行归纳展示,结果发现出现了文件多导致的变量命名冲突的问题. 所以小明和小红将他的aaa/bbb.js文件通过模块化的方式去写,而他们的第二个js文件想使用第一个js文件中的变量或者函数,小明的js文件可以这样写

;(function () {
  // 1.想使用flag
  if (moduleA.flag) {
    console.log('小明是天才, 哈哈哈');
  }

  // 2.使用sum函数
  console.log(moduleA.sum(40, 50));
})()
1
2
3
4
5
6
7
8
9

小红的可以这样写

(function () {
  console.log(moduleB.flag);
})()
1
2
3

这样使用匿名函数一来是不会存在命名冲突的问题,二来是通过模块化的方式,将自己的第一个js文件封装成一个moduleA和moduleB对象,把变量和函数都赋给obj.xxx返回,用moduleA和moduleB来接收,在自己的第二个js文件中使用moduleA/B.变量/函数 来进行调用,从而实现模块化的思想,如果项目组长想归纳代码在主页index展示,就可以在主要这样引入

<script src="main.js"></script>//归纳的代码
<script src="aaa.js"></script>//小明1
<script src="mmm.js"></script>//小明2
<script src="bbb.js"></script>//小红1
<script src="nnn.js"></script>//小红2
1
2
3
4
5

文件目录结构

按照引入顺序打印

# CommonJS

常见的模块化规范: CommonJS、AMD、CMD,也有ES6的Modules,AMD和CMD不常用,常用的就Conmmon.JS和ES6的模块化.

使用Common.JS 导出module.exports,导入require

//在小明的aaa.js里面使用
//commonjs写法 webpack会自动编译
 module.exports = {
   flag: flag,
   sum: sum
   //将小明的flag变量和sum函数暴露出来
 }
1
2
3
4
5
6
7
//在小明的第二个js文件中使用
var aaa = require('./aaa.js')//导入第一个js文件
var flag = aaa.flag;//将aaa的flag变量赋值给第二个js的flag
var sum = aaa.sum;//函数

//对象解构写法
//var {flag, sum} = require('./aaa.js')

sum(20, 30)//调用sum函数
1
2
3
4
5
6
7
8
9

# ES6的模块化

跟上面的案例类似,小明写了aaa.js文件,小红写了bbb.js文件,小明最后还写了自己的第二个js文件,最终小红的bbb.js文件想使用aaa.js文件中的变量函数等,小红的bbb.js也想使用自己写的aaa.js文件的变量方法...

小明的第一个js文件(aaa.js)

var name = '小明'
var age = 18
var flag = true

function sum(num1, num2) {
  return num1 + num2
}

if (flag) {
  console.log(sum(20, 30));
}

// 1.导出方式一:
export {
  flag, sum
}

// 2.导出方式二:
export var num1 = 1000;
export var height = 1.88


// 3.导出函数/类
export function mul(num1, num2) {
  return num1 * num2
}

export class Person {
  run() {
    console.log('在奔跑');
  }
}

// 另一个js文件中不想用该js中定义的函数变量名,
//可以进行自定义

// 5.export default
// const address = '北京市'
// export {
//   address
// }

// export const address = '北京市'
// const address = '北京市'
//
export default
address
// 开发中只能使用一个 export default

export default function (argument) {
  console.log(argument);
}
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

此时小红想用小明aaa.js里面的sum函数,运用上面讲到的方法(模块化雏形,commonJS),会显得特别麻烦,所以衍生了一种新的写法(ES6模块化)

import {sum} from './aaa.js'

var name = '小红'
var flag = false

console.log(sum(100, 200));
1
2
3
4
5
6

此时小明写的第二个js文件(mmm.js)也需要用到第一个文件中的方法

// 1.导入的{}中定义的变量
import {flag, sum} from "./aaa.js";

if (flag) {
  console.log('小明是天才, 哈哈哈');
  console.log(sum(20, 30));
}

// 2.直接导入export定义的变量
import {num1, height} from "./aaa.js";
console.log(num1);
console.log(height);

// 3.导入 export的function/class
import {mul, Person} from "./aaa.js";

console.log(mul(30, 50));

const p = new Person();
p.run()

// 4.导入 export default中的内容
import addr from "./aaa.js";

addr('你好啊');

// 5.统一全部导入
// import {flag, num, num1, height, Person, mul, sum} from "./aaa.js";

import * as aaa from './aaa.js'

console.log(aaa.flag);
console.log(aaa.height);
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

首页index展示

<script src="aaa.js" type="module"></script>
<script src="bbb.js" type="module"></script>
<script src="mmm.js" type="module"></script>
1
2
3

给script标签写上类型module后,它会识别成一个模板,上面的例子显然易见就是三个模块,因为三个模块都指向不同的房间,所以写了类型module就不会引起命名冲突问题.

扩展: 上面的代码也可以像下面这样写

左边的代码也可以像右边这样写

export default,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名

# webpack

# 安装

全局安装

npm install [email protected] -g
1

局部安装(后续才需要)

npm install [email protected] --save-dev
1

--save-dev`是开发时依赖,项目打包后不需要继续使用的。

为什么全局安装后,还需要局部安装呢? 在终端直接执行webpack命令,使用的全局安装的webpack 当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack

# 起步

dist文件夹:用于存放之后打包的文件

src文件夹:用于存放我们写的源文件

main.js:项目的入口文件。

mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。具体内容查看下面的详情。

index.html:浏览器打开展示的首页html

现在要把info.js的文件和mathUtils.js的文件通过模块化的方式引入到main.js文件,,将main.js文件会打包到dist文件夹下,通过首页index.html引入打包后的js文件 mathUtils.js文件中的代码

function add(num1, num2) {
  return num1 + num2
}

function mul(num1, num2) {
  return num1 * num2
}

module.exports = {
  add,
  mul
}
1
2
3
4
5
6
7
8
9
10
11
12

info.js文件中的代码

export const name = 'why';
export const age = 18;
export const height = 1.88;
1
2
3

main.js文件中的代码

// 1.使用commonjs的模块化规范
const {add, mul} = require('./mathUtils.js')

console.log(add(20, 30));
console.log(mul(20, 30));

// 2.使用ES6的模块化的规范
import {name, age, height} from "./info";

console.log(name);
console.log(age);
console.log(height);
1
2
3
4
5
6
7
8
9
10
11
12

使用webpack讲main.js打包到指定的文件夹/dist下,重命名为bundle.js,此时由于main.js包含了info.js和mathUtils.js,所以会逐一把main.js进行打包,从而打包到/dist文件夹下.在首页index.html引入bundle.js就等于引入了main.js

<script src="./dist/bundle.js"></script>
1

# 入口和出口

在项目文件夹下局部安装webpack,因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题。 所以通常一个项目,都有自己局部的webpack。

npm install [email protected] --save-dev
1

安装后项目文件夹会多出来一个node-modules,里面存放了node的各种依赖包,其中就包括了webpack, 在项目下一般会用npm初始化一下文件,这个时候会生成package.json文件,里面存放的webpack的版本号,开发时依赖的版本号

新建一个webpack.config.js文件,配置出口和入口文件

const path = require('path')
//导入node里面全局的包

module.exports = {
  //入口
  entry: './src/main.js',
  //出口
  output: {
    //路径 动态获取 拼接 __dirname就是当前文件路径
    path: path.resolve(__dirname, 'dist'),
    //打包后的名字
    filename: 'bundle.js'
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

自定义webpack打包命令(package.json)

# css文件

css,需要安装两个依赖,css-loder,style-loder

npm install --save-dev css-loader
npm install --save-dev style-loader
1
2

如果想使用的话就必须建立相应的css文件js文件主页index文件等 文件夹目录

使用的话将css文件引导到main.js文件就可以使用

//导入依赖css文件
require('./css/normal.css')
1
2

在webpack.config.js的文件下(刚才配置的入口文件后)编辑

# less文件

npm install --save-dev less-loader
1

同理,安装less-loader. 在css文件下写less文件,在main.js里面引入,在webpack.config.js文件下配置

{
        test: /\.less$/,
        use: [{
          loader: "style-loader", 
        }, {
          loader: "css-loader" 
        }, {
          loader: "less-loader", 
        }]
      },
      {
        test: /\.(png|jpg|gif|jpeg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
        // 当加载的图片, 小于limit时, 
        //会将图片编译成base64字符串形式.

        // 当加载的图片, 大于limit时, 
        //需要使用file-loader模块进行加载.
              limit: 13000,
              name: 'img/[name].[hash:8].[ext]'
            },
          }
        ]
      },
      {
        test: /\.js$/,
        // exclude: 排除
        // include: 包含
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015']
          }
        }
      }
    ]
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

less同理css文件还有图片文件,babel转es5文件一样,都是以上的基本配置,建立对应文件夹(参考上部文件夹格式)下载相应的依赖包,匹配文件名字,使用loader依赖包.最后项目终端下npm run build进行打包构建,webpack的学习还有很多,如果想深入请移步官方中文文档webpack中文文档 (opens new window)

# webpack配置Vue

在项目文件夹下安装Vue

npm install vue --save
1

此时会在项目文件下node_modules多出vue的配置文件,如果想要在页面使用vue,需要导入引用,在main.js下导入

import Vue from 'vue'

const app = new Vue({
    el:'#app',
    data: {
    message:'hello webpack'
    }
})
1
2
3
4
5
6
7
8

在index.html下创建#app的div,这样在main.js下才能成功挂载,

<div id = "app">
    <h2>{{message}}</h2>
</div>
1
2
3

打包后发现在页面并没有显示出hello webpack,出现报错.

Vue构建发布的时候构建了两个版本,runtime-only(不可以有任何的template),runtime-complier(可以有template,因为有compiler可以用于编译template)

此例子中div就相当于app的模板,这个错误说的是我们使用的是runtime-only版本的Vue,所以会报错,解决此问题的话可以在webpack.config.js下加一条命令,跟入口出口module一个级别

import Vue from 'vue',具体指向文件夹路径去找,找到vue,dist,里面就是发布的所有版本,找到vue.esm.js,里面就包含了runtime-complier,默认的话指向的vue.runtime.js,就是runtime-only版本的Vue.

在webpack.config.js中加了配置后,hello webpack会在页面显示出来

# template和el的关系

可以删掉const app,直接new一个实例.new Vue({}),同时有el,也有template的话,Vue内部会自动把template里面写的模板内容会复制到el挂载的app上面(覆盖),也就是说,在index.html里面可以只写一个

,,,在new Vue实例对象当中写一个template:里面写想在div里显示的内容,,之前的写法就是把所有的文字内容都写在页面上的#app元素里面,现在不需要写在#app元素里面,只需要在实例对象里写template,会自动替换掉el的app挂载的dom(#app)

index.html打包后页面最终效果

# Vue究极使用方案

一般情况下,真实开发中,在index.html中一点代码都不改,就只写一个组件的名字

,同上面一样.好处就是再也不用更改html代码,如果new Vue实例中的代码多了,可以再次进行抽取. 在实例对象上面新建一个组件,把实例对象里的所有都复制到组件里面去

此时App组件就是一个对象,就可以在src下创建一个vue文件夹,新建一个app.js文件,把const App(组件)的东西都写在app.js文件里,

目录结构

app.js文件

main.js文件

这样的话main.js文件看起来更简洁.但是app.js文件中的内容显得也有点冗余,没有把模板和方法等进行分离,所以此时给src下创建一个App.vue文件,将app.js文件中的代码复制粘贴到vue组件文件当中 app.js的template粘贴到template下,data和methods粘贴到script下,最下面还有个style样式,可以在template里面定义元素的class类,在style里面进行更改样式

最后结尾就用不到app.js文件了,要引入App.vue文件

.vue文件在打包的时候会报错,因为babel-loader没有识别,所以同样也需要安装依赖.(vue文件加载和vue文件编译)

npm install --save-dev vue-loader vue-template-compiler
1

安装完毕后配置webpack的loader,在rules:[]里面新增一条

      {
        test: /\.vue$/,
        use:['vue-loader']
      }
1
2
3
4

这样就可以打包编译成功了.

如果还想在页面使用(嵌套)其他组件,就可以在vue文件夹下新建一个Cpn组件,Cpn.vue

这样就形成了一个组件树,其实在实际开发中也是这样,可能只有一个App.vue组件是大的一个组件(页面),然后在App.vue文件里面嵌套这很多很多其他的组件,最后组成一个组件树(组件化开发)

# Vue CLI

如果你只是简单写几个Vue的demo程序, 那么你不需要Vue CLI. 如果你在开发大型项目, 那么你需要, 并且必然需要使用Vue CLI

使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。 如果每个项目都要手动完成这些工作,那无以效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。

# CLI2

CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架. Vue CLI是一个官方发布 vue.js 项目脚手架 使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.

脚手架的使用安装前提是必须要安装node,安装webpack

安装:

npm install -g @vue/cli
1

Vue CLI2初始化项目

vue init webpack my-project
1

Vue CLI3初始化项目

vue create my-project
1

Vue-CLI2安装过程

目录解构详解

# Eslint

搭建脚手架的时候可以选择Eslint进行安装,他是一个进行代码规范的工具 不想使用的话可以在脚手架目录里的config/index.js的useEslint:false,进行关闭

# Vue-CLI3

vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3 vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录 vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化 移除了static文件夹,新增了public文件夹,并且index.html移动到public中

目录结构

图形化界面

启动:项目文件夹下vue ui,配置等文件都可以在图形化界面修改