Vue.js学习笔记(四)

Posted by Ivens on November 24, 2019


前言

从今天开始学习课程《Vue2.5开发去哪儿网App 从零基础入门到实战项目》.

课程流程

正文

设计模式

MVP 设计模式

  • M:Model层,数据服务层,负责数据的增删改查。
  • V:View层,视图界面层,负责UI的渲染、子视图的组织、UI事件、用户交互等。
  • P:Presenter层在View和Model之间起到桥梁的作用,又封装了业务的复杂度,使UI和业务都可以独立的进行变化。
MVP 设计模式制作 TODO 页面
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
<body>

  <div>
    <!-- V层: 输入框 + 提交按钮 + 展示的无序数列 -->
    <input id="input" type="text">
    <button id="btn">提交</button>
    <ul id="ul"></ul>
  </div>

  <script>
    // P层 
    // 面向对象思想 : 将整个页面当成对象
    function Page(){ }

    $.extend(Page.prototype,{
      bindEvents : function () {
        $('#btn').on('click',$.proxy(this.handleBtnClick,this))
        // 将btn的click事件绑定到handleBtnClick方法上.
      },
      handleBtnClick : function () {
        var input = $('#input');
        var inputValue = input.val(); 
        var ulElem = $('#ul');
        ulElem.append('<li>'+inputValue+'</li>');
        input.val('');
      }
    })

    // 实例化页面类
    var page = new Page();
    // 执行
    page.bindEvents();
  </script>
</body>

可以看出,如果使用 MVP 设计模式,有大量的代码是在操作DOM的,这必将造成时间上的浪费.

MVVM 设计模式

使用 MVVM 设计模式, 我们可以只关注V端与M端.

中间数据与显示层的连接,可以交给Vue去做,可以省下大量人力物力.

Vue 双向绑定的原理
  • ES5中 Object.defineProperty 方法
  • 虚拟DOM

组件化思想

全局组件

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
<body>
  <div id="app">
    <ul>
      <todo-item :conten="item" 
                 v-for="(item, index) in listA">
                 {{item}}
      </todo-item>
    </ul>
  </div>

  <script>
    // 全局组件
    Vue.component('TodoItem',{
      props: ['conten'],
      template: "<li>{{conten}}</li>"
    });
    var app = new Vue({
      el: '#app',
      data() {
        return {
          newadd: '',
          listA: ['a','b','c']
        }
      }
    });
  </script>
</body>

局部组件

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
<body>
  <div id="app">
    <ul>
      <show-list :conten="item" 
                 v-for="(item, index) in listA">
                 {{item}}
      </show-list>
    </ul>
  </div>
  <script>
    var TodoItem = {
      props: ['conten'],
      template: "<li>{{conten}}</li>"
    }
    var app = new Vue({
      el: '#app',
      // 在这里为组件在全局中注册
      components: {
        // 给定一个名称,在HTML中使用.
        ShowList: TodoItem
      },
      data() {
        return {
          newadd: '',
          listA: ['a','b','c']
        }
    });
  </script>
</body>

父子组件间相互传值

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
<body>
  <div id="app">
    <ul>
      <!-- 监听delete事件,如果触发则执行 handleItemDelete 方法,进入new Vue() 中, 其实这里可以直接在handleItemDelete传入参数,不需要再通过子组件一遍-->
      <show-list :conten="item" :index="index"
                 @delete="handleItemDelete(index)"
                 v-for="(item, index) in listA">
                 {{item}}
      </show-list>
    </ul>
  </div>

  <script>
    var TodoItem = {
      props: ['conten','index'],//为li标签绑定click事件
      template: '<li @click="handleItemClick()">{{conten}}</li>',
      methods: {
        handleItemClick () {
          //当触发click事件后,会向父组件触发delete事件,并且传入参数
          this.$emit('delete',this.index);
        }
      }
    }
    var app = new Vue({
      el: '#app',
      components: {
        ShowList: TodoItem
      },
      data() {
        return {
          newadd: '',
          listA: ['a','b','c']
        }
      },
      methods: {
        handleItemDelete (index) {
          // show-list标签触发delete事件进入这里
          this.listA.splice(index,1);
        }
      }
    });
  </script>
</body>

生命周期

模板语法

v-textv-html 的区别?

先看一段代码:

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
<div id="app">
    <p>1.nameA</p>
    <div v-text="nameA"></div>
    <div v-html="nameA"></div>
    
    <p>2.字符串拼接</p>
    <div>{{nameA + 'Lee'}}</div>
    <div v-text="nameA + 'Lee'"></div>
    <div v-html="nameA + 'Lee'"></div>
    <div v-html="nameB + 'Lee'"></div>

    <p>3.nameB</p>
    <div v-text="nameB"></div>
    <div v-html="nameB"></div>
  </div>  
  <script>
    var vm = new Vue({
      el: '#app',
      data() {
        return {
          nameA: 'dell',
          nameB: '<li>dell</li>'
        }
      }
    })
  </script>

  • v-html 可以输出的是HTML元素, v-text 是什么输出什么
  • 都可以拼接字符串

计算属性与侦听器

计算属性是内置缓存的

当我们使用Chrome的调试模式,将Vue对象中的元素进行重新赋值.

只有计算属性中使用到的变量发生变化时,计算属性才会重新执行,不然元素不会发生变化.

同样的computed也可以同时计算多个变量,而且对Vue对象外的变量还是没法计算,只有当对象内变量发生变化,页面才会连同一起更新 —— 《Vue.js学习笔记(一) · 侦听器与计算属性》

computed相比watch代码实现更简洁.

计算属性的settergetter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 页面打印fullName -->
<script>
  var vm = new Vue({
    el: '#app',
    data() {
      return {
        firstName: 'Zhang',
        lastName: 'Yi'
      }
    },
    computed: {
      fullName: {
        get () {
          return this.firstName + ' ' + this.lastName ; 
        },
        set (value) {
          var arr = value.split(' ');
          this.firstName = arr[0];
          this.lastName = arr[1]; 
        }
      }
    }
  })
</script>

Vue内置的函数,当fullName发生变化时,调用set方法,当外部引用fullName时,调用get方法.

条件渲染、列表渲染

v-if与v-else中Vue会尝试复用已经显示的元素

原因:虚拟DOM中的即时算法.

如果想解决这个问题,给元素加上key值,这样就不会复用了.

列表渲染中的key值

在每个循环项下添加一个唯一的key值,用以提高性能.

这个值如果列表中有附带的id属性则用表中属性,如果没有则用index.

响应式对数组进行修改

1.使用以下的方法,如果只用vm.list[0]= 2 数组值会发生变化,但是页面不会刷新.

  • push()
  • pop()
  • shift() — 删除第一项
  • unshift() — 在第一项添加
  • splice() — 截取
  • sort() — 排序
  • reverse() — 取反

2.或直接改变list数组,如vm.list={1,2,3,4}.

3.使用Vue.set(数组名,索引值,值)实例化Vue对象名.$set(数组名,索引值,值)

响应式对对象进行修改

1.直接重新定义对象

2.使用Vue.set(对象名,属性名,值)实例化Vue对象名.$set(对象名,属性名,值)

拾遗

JQuery.extend()用法

  1. 合并多个对象
1
2
3
4
5
6
7
8
//用法: jQuery.extend(obj1,obj2,obj3,..)

var Css1={size: "10px",style: "oblique"}
var Css2={size: "12px",style: "oblique",weight: "bolder"}
$.jQuery.extend(Css1,Css2)

//结果:Css1的size属性被覆盖,而且继承了Css2的weight属性
// Css1 = {size: "12px",style: "oblique",weight: "bolder"}
  1. 深度嵌套对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$.extend(
  { name: John, location: { city: Boston } },
  { last: Resig, location: { state: MA } }
);

// 结果:
// => { name: “John”, last: “Resig”, location: { state: “MA” } }
// 新的更深入的 .extend()


jQuery.extend( true,
  { name: John, location: { city: Boston } },
  { last: Resig, location: { state: MA } }
);

// 结果
// => { name: “John”, last: “Resig”,
// location: { city: “Boston”, state: “MA” } }
  1. 给JQuery添加静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
  <head>
    <title></title>
    <script type="text/javascript" src="jquery.2.0.3.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      $.extend({
        add:function(a,b){return a+b;},
        minus:function(a,b){return a-b},
        multiply:function(a,b){return a*b;},
        divide:function(a,b){return Math.floor(a/b);}
      });
      
      var sum = $.add(3,5)+$.minus(3,5)+$.multiply(3,5)+$.divide(5,7);
      console.log(sum);
    </script>
  </body>
</html>

JS中的prototype属性

在 JavaScript 中,每个函数对象都有一个默认的属性 prototype,称为函数对象的原型成员,这个属性指向一个对象,称为函数的原型对象.

当我们每定义了一个函数的时候,JavaScript 就创建了一个对应的原型对象,也就是说,当我们定义一个函数的时候,实际上得到了两个对象,一个函数对象,一个原型对象。

原型对象是一个特殊的对象,函数的 prototype 成员指向它的原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="text/javascript">

function employee(name,job,born)
{
  this.name=name;
  this.job=job;
  this.born=born;
}

var bill=new employee("Bill Gates","Engineer",1985);

employee.prototype.salary=null;
bill.salary=20000;

document.write(bill.salary);    // 输出结果:20000
</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
function Page(){

}

var page1 = new Page();
var page2 = new Page();


// 1.prototype只能对类使用

page1.prototype.f1 = function () {
  alert('f1');    //Uncaught TypeError: Cannot set property 'f1' of undefined
}


// 2.如果类不适用prototype添加方法,则实例化的对象无法使用

Page.f3 = function () {
  alert('f3');
}
// Page.f3() --- 打印f3
// page.f3() Uncaught TypeError: page.f3 is not a function 


// 3.如果是对象添加的方法,其他同类实例化的对象无法使用该方法

page.f2 = function () {
  alert('f2');
};
// page2.f2()  Uncaught TypeError: page2.f2 is not a function