菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
0
0

Vue全家桶 - Vue基础

原创
05/13 14:22
阅读数 437

1 Vue概述

1.1 vue简介

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。
渐进式: Vue 可以自底向上逐层应用。在简单应用只需一个轻量小巧的核心库,而在复杂应用就渐进的引入各式各样的的vue插件。
Vue借鉴angular的模板和数据绑定技术以及react的组件化和虚拟DOM技术。
图片.png

1.2 vue特点

● 遵循 MVVM 模式
● 编码简洁,体积小,运行效率高,适合移动/PC 端开发
● 它本身只关注视图层,所有的 DOM 操作都由 Vue 来处理,可以轻松引入 vue 插件或其它第三方库开发项目
● 采用组件化模式,提高代码复用率、且让代码更好维护
● 声明式编程,让编码人员无需直接操作DOM,提高开发效率
● 使用虚拟DOM和Diff算法,尽量复用DOM节点

注:
命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。
声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

1.3 Vue 扩展插件

● vue-cli:vue 脚手架
● vue-resource(axios):ajax 请求
● vue-router:路由
● vuex:状态管理(它是 vue 的插件但是没有用 vue-xxx 的命名规则)
● vue-lazyload:图片懒加载
● vue-scroller:页面滑动相关
● mint-ui:基于 vue 的 UI 组件库(移动端)
● element-ui:基于 vue 的 UI 组件库(PC 端)

2 Vue的基本使用#

2.1 传统开发模式对比

 //原生JS
 <div id="msg"></div>
  <script type="text/javascript">
    var msg = 'Hello World'
    var div = document.querySelector('#msg');
    div.innerHTML = msg
  </script>
//jQuery
<div id="msg"></div>
  <script type="text/javascript" src="js/jquery.js"></script>
  <script type="text/javascript">
    var msg = 'Hello World';
    $('#msg').html(msg);
  </script>

2.2 引入Vue.js的方法

1.直接<script> 引入,在Vue.js官网上下载,放在自己正在开发项目的js文件夹里

<script src="js/vue.js"></script>

图片.png
2.使用CDN引入
对于制作原型或学习,你可以这样使用最新版本:

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>

对于生产环境,我们推荐链接到一个明确的版本号和构建文件,以避免新版本造成的不可预期的破坏。

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>

2.3 Vue.js案例分析

Vue的基本使用步骤:
​ 1、需要提供标签用于填充数据
​ 2、引入Vue.js库文件
​ 3、创建Vue实例,并配置对象
​ 4、把vue提供的数据填充到标签里面

案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title></title>
  </head>

  <body>
    <div id="app">
      <!-- 插值表达式{{}} -->
      <div>{{num}}</div>
      <!-- 事件绑定v-on -->
      <div><button v-on:click="handle">点击</button></div>
    </div>
    <!-- //引入vue -->
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
      //创建vue实例  vm (ViewModel) 
      var vm = new Vue({
        el: '#app',
        data: {
          num: 0,
        },
        methods: {
          // ES6 的对象字面量方法简写允许我们省略对象方法之后的冒号及function关键字
          // handle:function(){
          // this.num++;
          // }
          handle() {
            this.num++;
          },
        },
      });
    </script>
  </body>
</html>

图片.png

2.3.1 实例参数el、data、methods的写法

el:指定当前Vue实例为哪个标签服务(值可以是CSS选择器或者DOM元素),el有2种写法:
(1) new Vue时候配置el属性。

const vm = new Vue({
	el:'#root', //第一种写法
	data:{
		msg:' '
	}
})

(2) 先创建Vue实例,随后再通过vm.$mount(’#root’)指定el的值。
除了数据 property,Vue 实例还暴露了一些有用的实例 property 与方法。它们都有前缀 $,以便与用户定义的 property 区分开来

const vm = new Vue({
	data:{
		msg:' '
	}
})
vm.$mount('#root') //第二种写法 */

data:用于存储数据(值是一个对象或函数),数据供el所指定的标签使用,data有2种写法:
(1) 对象式

data:{
	msg:' '
}

(2) 函数式

data(){
    //console.log(this)//此处的this是Vue实例对象
	return{
		msg:' '
	}
}

如何选择:目前data的哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。

methods : 该属性用于在Vue对象中定义方法。
● 事件的回调需要配置在methods对象中,最终会在vm上;
● methods中配置的函数,不要用箭头函数!否则this就不是vm
● methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象;

两个重要的小原则:
● 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
● 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,又因为箭头函数没有this,需要往外层作用域寻找父级,这样this的指向还是是vm或组件实例对象。

2.3.2 Vue代码运行原理分析

概述编译过程:Vue语法经过Vue框架编译成原生JS语法,才能够被浏览器执行
图片.png

2.3.3 前端渲染

前端渲染:把数据填充到HTML标签中
前端渲染方式:
● 原生js拼接字符串
● 使用前端模板引擎
● 使用vue特有的模板语法(推荐)

app标签里的代码被称为vue模板
图片.png

2.3.4 Vue基本使用总结

● 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
● app标签里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
● app标签里的代码被称为【Vue模板】;
● Vue实例和标签是一一对应的; 真实开发中只有一个Vue实例,并且会配合着组件一起使用;
● {{xxx}}中的xxx要写js表达式`,且xxx可以自动读取到data中的所有属性;
● 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;

2.3.5 js表达式 和 js语句的区别

js表达式:是由运算元和运算符(可选)构成,功能是执行计算,并返回一个值。比如:
(1). a
(2). a+b
(3). demo(1)
(4). x === y ? ‘a’ : ‘b’

js代码(语句):语句可以理解为一个行为,循环语句和判断语句就是典型的语句。一个程序有很多个语句组成,比如:
(1). if(){}
(2). for(){}

3 Vue 模板语法

Vue模板语法包括两大类:
1.插值语法:双大括号表达式{{ }} (“Mustache”语法)【一个】
2.指令语法:指令(以v-开头的自定义标签属性)【很多】

3.1 插值语法{{}}

● 功能:用于解析标签体内容(如

{{xxx}}
起始标签和结束标签之间的内容{{xxx}}就是标签体)
● 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性,且可以直接读取到data中的所有属性,也可以调用对象的方法和计算属性
● 里面写js表达式(有返回值的js代码,而不是js语句)

   <div id="app">
      <p> {{ msg }} </p>
   </div>
   <script>
	var vm = new Vue({
    	el: ‘#app’,
  		data: {
  			msg: 'hello vue.js'
  		}
  	})
	</script>

3.2 指令语法

功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,href为参数,xxx同样要写js表达式,且可以直接读取到data中的所有属性
备注:Vue中有很多的指令,且形式都是:v-???

3.3 属性绑定v-bind

属性绑定v-bind,数据只能从 data 流向页面
功能:指定变化的属性值
语法:

v-bind:href ="xxx" 或简写为 :href ="xxx"  //xx会作为表达式解析执行

3.3.1 class样式绑定

:class='xxx' // xxx可以是字符串、对象、数组。

● 对象语法

<div :class="{ active:isActive,error:isError }"></div>

● 数组语法

<div :class="[activeClass, errorClass]"></div>

样式绑定相关语法细节:
1、对象绑定和数组绑定可以结合使用
2、class绑定的值可以简化操作
3、默认的class会保留

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type='text/css'>
    .active {
      border: 1px solid red;
      width: 100px;
      height: 100px;
    }
    .error {
      background-color: pink;
    }
  </style>
</head>

<body>
  <div id="app">
    <div :class="{active:isActive,error:isError}">测试样式</div>
    <!-- <div :class='[activeClass, errorClass]'>测试样式</div> -->
    <button @click='handle'>切换</button>
  </div>
</body>
<script type='text/javascript' src='js/vue.js'></script>
<script type='text/javascript'>
  const app = new Vue({
    el: "#app",
    data: {
      isActive: true,
      isError: true,
      //activeClass: 'active',
      //errorClass: 'error'
    },
    methods: {
      handle() {
        //控制isActive在true和false之间切换
        this.isActive = !this.isActive
        this.isError = !this.isError
        //this.activeClass = '';
        //this.errorClass = '';
      }
    }
  })
</script>

</html>

图片.png

3.3.2 style样式绑定

对象语法

<div :style="{ color: activeColor, fontSize: fontSize }"></div>

数组语法

<div :style="[baseStyles, overridingStyles]"></div>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>

</head>

<body>
  <div id="app">
    <!-- 盒子1 -->
    <div :style='{border: borderStyle, width: widthStyle, height: heightStyle}'></div>
    <!-- 盒子2 -->
    <div :style='objStyles'></div>
    <!-- 盒子3 -->
    <div :style='[objStyles, overrideStyles]'></div>
    <button @click='handle'>切换</button>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    var vm = new Vue({
      el: '#app',
      data: {
        borderStyle: '1px solid blue',
        widthStyle: '100px',
        heightStyle: '200px',
        objStyles: {
          border: '1px solid green',
          width: '200px',
          height: '100px'
        },
        overrideStyles: {
          border: '5px solid orange',
          backgroundColor: 'blue'
        }
      },
      methods: {
        handle() {
          this.heightStyle = '100px';
          this.objStyles.width = '100px';
        }
      }
    });
  </script>
</body>

</html>

图片.png

3.4 双向数据绑定v-model

Vue中双向数据绑定使用v-model指令来实现标签内容的绑定,它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。如表单元素和数据的双向绑定。
语法: v-model=“xxx”
特点: 数据不仅能从 data 流向视图,还能从视图流向 data
备注:
● v-model只能用于表单类元素(Input单行文本、textarea多行文本
、select 下拉多选、 radio 单选框、checkbox多选框),其余类型可用v-bind
● v-model指令的原始写法为.v-model:value ,因为v-model默认收集的就是value值。
● v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

    text 和 textarea 元素使用 value 属性 和 input 事件;
    checkbox 和 radio 使用 checked 属性 和 change 事件;
    select 字段将 value 作为 prop 并将 change 作为事件。
<!DOCTYPE html>
 <html lang="en">
 <head>
   <meta charset="UTF-8">
   <title>Document</title>
 </head>
 <body>
   <div id="app">
     <div>{{msg}}</div>
     <div>
       <input type="text" v-model='msg'>
     </div>
   </div>
   <script type="text/javascript" src="js/vue.js"></script>
   <script type="text/javascript">
     /* 双向数据绑定
       1、从页面(用户)到数据
       2、从数据到页面  */
     var vm = new Vue({
       el: '#app',
       data: {
         msg: 'Hello Vue'
       }
     });
   </script>
 </body>
 </html>

图片.png

3.5 双向数据绑定原理

1.什么是单向数据绑定和双向数据绑定?
单向数据绑定(v-bind):数据只能从 data 流向页面
双向数据绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
2.双向数据绑定原理
双向数据绑定原理:vue是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。

双向数据绑定主要是指:数据变化更新视图,视图变化更新数据.

输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。
Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。

其中,View 变化更新 Data ,可以通过事件监听的方式来实现,比如input标签监听 ‘input’ 事件就可以实现了,所以主要讨论如何根据 Data 变化更新 View。

根据 Data 变化更新 View实现过程:
首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听数据的所有属性。如果属性发生变化,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下4个步骤,实现数据的双向绑定:
1、实现一个监听器 Observer ,用来劫持并监听所有属性,如果属性发生变化,就通知订阅者;
2、实现一个订阅器 Dep,用来收集订阅者,对监听器 Observer 和 订阅者 Watcher 进行统一管理;
3、实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的方法,从而更新视图;
4、实现一个指令解析器 Compile,可以解析每个节点的相关指令,对模板数据和订阅器进行初始化。
图片.png

3.6 事件的基本使用

3.6.1 事件绑定v-on

v-on指令用法:用于绑定HTML事件,如鼠标事件、键盘事件
● 写法:v-on:click=‘xxx’ 其中xxx是事件名;
● 简写形式:@click=‘xxx’
● 功能:绑定指定事件名的回调函数
● 事件的回调需要配置在methods对象中,最终会在vm上;
● methods中配置的函数,不要用箭头函数!否则this就不是vm
● methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象;
● 事件函数的调用方式: @click="demo”和@click="demo($event)”效果一致,但后者可以传参

<!-- 1.如果事件直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数 -->
    <button @click='handle1'>点击1</button>
<!-- 2.如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,并且事件对象的名称必须是$event -->
    <button @click='handle2(123, 456, $event)'>点击
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <div>{{num}}</div>
    <div>
        <!-- 4种都可以实现-->
      <button v-on:click='num++'>点击</button>
      <button @click='num++'>点击1</button>
      <button @click='handle'>点击2</button>
      <button @click='handle()'>点击3</button>//和上面效果一致,但可传参
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    var vm = new Vue({
      el: '#app',
      data: {
        num: 0
      },
      methods: {
        handle () {
          // 这里的this是Vue的实例对象
          console.log(this === vm)
          // 在函数中 想要使用data里面的数据 一定要加this 
          this.num++;
        }
      }
    });
  </script>
</body>

</html>

3.6.2 事件修饰符

修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定,例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()
stop:阻止冒泡

<a @click.stop="handle">跳转</a> 

js语法实现

event.stopPropagation();

prevent:阻止默认行为,下面两种都可以

<a @click.prevent="handle">跳转</a>

js语法实现

event.preventDefault();
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>{{num}}</div>
    <div @click='handle0'>
      <button @click.stop='handle1'>点击1</button>
    </div>
    <div>
      <a href="http://www.baidu.com" @click.prevent='handle2'>百度</a>
    </div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    var vm = new Vue({
      el: '#app',
      data: {
        num: 0
      },
      methods: {
        handle0: function () {
          this.num++;
        },
        handle1: function (event) {
       //event.stopPropagation()
        },
        handle2: function (event) {
      //event.preventDefault();
        }
      }
    });
  </script>
</body>

</html>

● once:事件只触发一次
● capture:使用事件的捕获模式;
● self:只有event.target是当前操作的元素时才触发事件;
● passive:滚动事件的默认行为 (即滚动行为) 将会立即触发

注: 修饰符可以连续写,使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

未加事件修饰符:
图片.png

加事件修饰符:
图片.png

3.6.3 按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符。
Vue中常用的按键别名:

回车 => enter
删除 => delete (捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab (特殊,必须配合keydown去使用)
上 => up
下 => down
左 => left
右 => right

系统修饰键(用法特殊):ctrl、alt、shift、meta
(1). 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。比如ctrl+s 然后释放s
(2). 配合keydown使用:正常触发事件。
也可以使用keyCode去指定具体的按键(不推荐),但注意要转为kebab-case(短横线命名)
Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

实例
1、enter 回车键

//keyup 按键松开触发
<input @keyup.enter='submit'>

2、delete删除键

<input @keyup.delete='handle'>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <form action="">
      <div>
        用户名: <input type="text" @keyup.delete='clearContent' v-model='uname'>
      </div>
      <div>
        密码:<input type="text" @keyup.enter='handleSubmit' v-model='pwd'>
      </div>
      <div>
        <input type="button" @click='handleSubmit' value="提交">
      </div>
    </form>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    var vm = new Vue({
      el: '#app',
      data: {
        uname: '',
        pwd: '',
        age: 0
      },
      methods: {
        clearContent(){
          // 按delete键的时候,清空用户名
          this.uname = '';
        },
        handleSubmit(){
          console.log(this.uname,this.pwd)
        }
      }
    });
  </script>
</body>
</html>

图片.png

3、自定义按键修饰符全局
规则:自定义按键修饰符名字是自定义的,但是对应的值必须是按键对应event.keyCode值

Vue.config.keyCodes.f1 = 65
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title></title>
  <script type='text/javascript' src='js/vue.js'></script>
</head>

<body>
  <div id="app">
    <input type="text" @keyup.f1='handle' v-model='msg'>
  </div>
  <script type='text/javascript'>
  //a的ACSII值为65
    Vue.config.keyCodes.f1 = 65
    const app = new Vue({
      el: "#app",
      data: {
        msg: ''
      },
      methods: {
        handle: function (event) {
          console.log(event.keyCode);
        }
      }
    })
  </script>
</body>

</html>

图片.png

3.7 MVVM设计思想

图片.png
MVVM是Model-View-ViewModel的简写,是一种基于前端开发的架构模式。它本质上就是MVC 的改进版。Model代表数据,View代表视图,ViewModel是View和Model的桥梁,ViewModel会通过Data Binding(数据绑定)将model的改变渲染到视图中,会通过DOM Listener(DOM监听)监听view的变化更新数据。

● Model(数据):data中的数据
数据可能是我们data中的固定数据,更多的是来自我们服务器,从网络上请求下来的数据。
● View(视图):vue模板
在我们前端开发中,通常就是DOM层。主要的作用是给用户展示各种信息。
● ViewModel(视图模型):Vue实例
视图模型层是View和Model沟通的桥梁。一方面它通过Data Binding(数据绑定),将Model的改变实时的反应到View中,另一方面它通过DOM Listener(DOM监听),当DOM发生一些事件(点击、滚动、touch等)时,可以监听到,并在需要的情况下改变对应的Data。这两个方向都实现的,我们称之为数据的双向绑定。
图片.png

注意:
● data中所有的属性,最后都出现在了vm身上。
● vm身上所有的属性及Vue原型上所有属性,在Vue模板中都可以直接使用(谨记!!!)。如下图中options可以通过

{{options}}
在模板中获得
图片.png
MVC是Model-View- Controller的简写,即数据-视图-控制器
实现: 在Controller里面把数据赋值给视图
缺点:不适合小型项目的开发,视图与控制器间的连接过于紧密,妨碍了他们的独立重用

3.8 Objcet.defineProperty()和Vue数据代理

3.8.1 Objcet.defineProperty()

Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。
通过Objcet.defineproperty()添加的参数不参与枚举(遍历)、不可修改、不可删除,除非设置enumerable:true(控制属性是否可以枚举)、writable:true(控制属性是否可以被修改)、configurable:true(控制属性是否可以被删除)
语法:

Object.defineProperty(obj, prop, descriptor)

● obj 需要定义属性的对象。
● prop 需被定义或修改的属性名。
● descriptor 需被定义或修改的属性的描述符。

<!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></title>
  </head>
  <body>
    <script>
      let number = 18;
      let person = {
        name: '张三',
        sex: '男',
      };
      Object.defineProperty(person, 'age', {
        //value:18,
        //enumerable:true //控制属性是否可以枚举,默认值为false
        //writable:true //控制属性是否可以被修改,默认值为false
        //configurable:true //控制属性是否可以被删除,默认值为false
        //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
        get: function () {
          console.log('有人读取age属性了');
          return number;
        },
        //当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
        set(value) {
          console.log('有人修改了age属性,且值是', value);
          number = value;
        },
      });
      console.log(person);
    </script>
  </body>
</html>

图片.png

3.8.2 Vue数据代理

数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)

<!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></title>
  </head>
  <body>
    <script>
      let obj = { x: 100 };
      let obj2 = { y: 200 };
     
     //为obj2添加x属性
      Object.defineProperty(obj2, 'x', {
        get() {
         //obj2代理了obj中x的数据
          return obj.x;
        },
        set(value) {
          obj.x = value;
        },
      });
    </script>
  </body>
</html>

图片.png
Vue数据代理:通过vm对象来代理data对象(以data对象为例)中属性的操作
Vue中数据代理的好处:更加方便的操作data中的数据
基本原理:通过Object.defineproperty()把data对象中所有属性都添加到vm上。为每一个添加到vm上的属性,都指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性。

<!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></title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h2>学校名称:{{name}}</h2>
      <h2>学校地址:{{address}}</h2>
    </div>
    <script>
      const vm = new Vue({
        el: '#app',
        data: {
          name: '学习猿地',
          address: '科技园',
        },
      });
    </script>
  </body>
</html>

3.9 条件渲染v-if、v-show

作用:控制元素是否在页面中显示
v-if、v-else、v-else-if
写法:
● v-if=“表达式”
● v-else-if=“表达式”
● v-else=“表达式”

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else>
  Not A/B/C
</div>

适用:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和v-else-if、v-else一起使用,但要求结构不能被“打断”。
若想切换多个元素,可以把一个 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 元素。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

v-show
写法:v-show=“表达式”
适用:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式display:none隐藏掉

v-if和v-show的区别
v-if指令是直接销毁和重建DOM达到让元素显示和隐藏的效果
v-show指令是通过修改元素的display属性让其显示或者隐藏

v-if只有在为true的时候才会显示数据,执行if,否则,执行else。v-show当条件为false时,将元素的display属性设置为none
当需要在显示与隐藏之间切换很频繁时,使用v-show.当只有一次切换时,通过使用v-if
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  
</head>
<body>
  <div id="app">
    <div v-if='score>=90'>优秀</div>
    <div v-else-if='score<90&&score>=80'>良好</div>
    <div v-else-if='score<80&&score>60'>一般</div>
    <div v-else>比较差</div>
    <div v-show='flag'>测试v-show</div>
    <button v-on:click='handle'>点击</button>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*v-show的原理:控制元素样式是否显示 display:none*/
    var vm = new Vue({
      el: '#app',
      data: {
        score: 10,
        flag: false
      },
      methods: {
        handle: function(){
          this.flag = !this.flag;
        }
      }
    });
  </script>
</body>
</html>

图片.png

3.10 列表渲染v-for

3.10.1 基本列表

v-for作用:用于遍历展示列表数据
语法:v-for=“(item, index) in xxx” :key=“item.id” (:key 保证key是唯一的即可,:为v-bind:的简写)
key的作用:帮助vue区分不同的元素,可以提高vue的渲染效率,确保key的唯一性,,尽可能在使用 v-for 时提供 key ,提高渲染效率
可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
● 数组: (item, index),item每一项的数据,index索引
● 对象: (value, key,index) , value属性值,key属性名,index索引
● 字符串:(char, index) , char字符串中的每一个字符,index每一个字符的索引
● 指定次数:(number, index) ,number字符串中的每一个字符,index每一个字符的索引

<!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></title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <ul>
        <h2>1.遍历数组</h2>
        <li v-for="(item,index) in persons">
          {{item.name}}--{{item.age}}--{{index}}
        </li>
        <h2>2.遍历对象</h2>
        <li v-for="(value,key) in car">{{key}}--{{value}}</li>
        <h2>3.遍历字符串</h2>
        <li v-for="(char, index) in str">{{char}}--{{index}}</li>
        <h2>4.遍历指定次数</h2>
        <li v-for="(number, index) in 5">{{number}}--{{index}}</li>
      </ul>
    </div>
    <script>
      const vm = new Vue({
        el: '#app',
        data: {
          persons: [
            { id: '001', name: '张三', age: 18 },
            { id: '002', name: '李四', age: 19 },
            { id: '003', name: '王五', age: 20 },
          ],
          car: {
            name: '奥迪',
            price: '30万',
          },
          str: 'hello',
        },
        methods: {},
      });
    </script>
  </body>
</html>

图片.png

注意:类似于 v-if,你也可以利用带有 v-for 的 来循环渲染一段包含多个元素的内容。比如:

<ul>
  <template v-for="item in items"f>
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

3.10.2 虚拟DOM、Diff算法、Key

原始JS实现功能:数据 => 真实DOM
Vue实现功能:数据 => 虚拟DOM(内存中的数据)=> 真实DOM
虚拟DOM可以理解为虚拟节点,是一个用来描述真实DOM结构的js对象。
● 优点:减少了DOM操作,减少了回流与重绘,保证性能的下限,比正常的DOM性能更好
● 缺点:首次渲染DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。

Diff算法是用于比较新旧虚拟节点之间差异的一种算法,每个虚拟节点都有一个唯一标识key,通过对比新旧节点的key来判断节点是否改变,将两个节点不同的地方存储在patch对象中,最后利用patch记录的消息局部更新DOM。
Key主要用在虚拟Dom算法中,每个虚拟节点都有一个唯一标识Key,通过对比新旧节点的key来判断节点是否改变,可以大大提高渲染效率。

对比规则:
(1)旧节点中找到了与新节点相同的key:
①若新节点中内容没变(和旧节点一样), 直接复用之前的旧节点生成真实DOM
②若新节点中内容变了(和旧节点不一样), 则生成新的真实DOM,随后替换掉页面中之前旧节点生成的真实DOM
(2) 旧节点中未找到与新节点相同的key,则创建新的真实DOM,随后渲染到到页面。

用index作为key可能会引发的问题:
(1).若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ,这时界面效果没问题, 但渲染效率低。
(2).如果结构中还包含输入类的DOM,会产生错误DOM更新,使界面有问题

开发中如何选择key:
(1).最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
(2).如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

由下图可知,第一次有三项数据渲染为真实DOM,第二次渲染中的数据比第一次多了一项,此时Vue使用Diff算法按照规则在虚拟DOM中依次对比每一项数据,发现前三项数据key和内容都相同,则复用第一次的真实DOM,最后一项数据没有则需渲染新数据为真实DOM,提高了渲染效率。
图片.png

3.10.3 列表过滤与排序

列表过滤:可以使用watch也可以使用计算属性,使用计算属性更加简单方便一点(见4.3 和4.4 )

案例:输入框输入值,搜索想要的结果

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>列表过滤</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			<h2>人员列表</h2>
			<input type="text" placeholder="请输入名字" v-model="keyWord">
			<ul>
				<li v-for="(p,index) of filPerons" :key="index">
					{{p.name}}-{{p.age}}-{{p.sex}}
				</li>
			</ul>
		</div>

		<script type="text/javascript">
			Vue.config.productionTip = false
			
			//用watch实现
			//#region 
			/* new Vue({
				el:'#root',
				data:{
					keyWord:'',
					persons:[
						{id:'001',name:'马冬梅',age:19,sex:'女'},
						{id:'002',name:'周冬雨',age:20,sex:'女'},
						{id:'003',name:'周杰伦',age:21,sex:'男'},
						{id:'004',name:'温兆伦',age:22,sex:'男'}
					],
					filPerons:[]
				},
				watch:{
					keyWord:{
					    //初始化自动调用handler,输入框为空,handler的val为空字符串
						immediate:true,
						//'abcd'.indexOf('') =0 一个字符串indexOf空字符串结果为0,filPerons就获取到了persons所有值
						handler(val){
							this.filPerons = this.persons.filter((p)=>{
								return p.name.indexOf(val) !== -1
							})
						}
					}
				}
			}) */
			//#endregion
			
			//用computed实现
			new Vue({
				el:'#root',
				data:{
					keyWord:'',
					persons:[
						{id:'001',name:'马冬梅',age:19,sex:'女'},
						{id:'002',name:'周冬雨',age:20,sex:'女'},
						{id:'003',name:'周杰伦',age:21,sex:'男'},
						{id:'004',name:'温兆伦',age:22,sex:'男'}
					]
				},
				computed:{
					filPerons(){
						return this.persons.filter((p)=>{
							return p.name.indexOf(this.keyWord) !== -1
						})
					}
				}
			}) 
		</script>
</html>

图片.png

列表排序

<body>
<div id="test">
    <input type="text" v-model="searchName">
    <ul>
        <li v-for="(p, index) in filterPersons" :key="index">
            {{index}}---{{p.name}}---{{p.age}}
        </li>
    </ul>
    <button @click="setOrderType(1)">年龄升序</button>
    <button @click="setOrderType(2)">年龄降序</button>
    <button @click="setOrderType(0)">还原顺序</button>
</div>

    <script src="../js/vue.js"></script>

    <script>
        new Vue({
            el: '#test',
            data: {
                searchName: '',
                orderType: 0, //0代表原本, 1代表升序, 2代表降序
                persons: [
                    {name: 'Tom', age: 10},
                    {name: 'Jack', age: 16},
                    {name: 'Rose', age: 12},
                    {name: 'Aka', age: 18}
                ]
            },
            computed: {
                filterPersons(){
                    // 1. 取出相关数据
                    const {searchName, persons, orderType} = this; // 解构赋值
                    
                    let fPersons;
               
                    // 2. 对persons进行过滤
                    fPersons = persons.filter(p => p.name.indexOf(searchName) !== -1);

                    // 3. 排序
                    if(orderType !== 0){
                        fPersons.sort(function(p1, p2){ // 返回负数p1在前,返回正数p2在前
                            if(orderType === 2){ 
                                return p2.age - p1.age; // 降序
                            }else{ 
                                return p1.age - p2.age; // 升序
                            }
                        })
                    }
                    return fPersons;
                }
            },
            methods: {
                setOrderType(orderType){
                    this.orderType = orderType;
                }
            }
        })
            //数组排序
            //sort排序允许接受一个参数(函数),这个函数接受2个形参a、b(数组中的数字),并且通过冒泡的方式比较

			// let arr =[2,3,4,6,65,7]
			// arr.sort((a,b)=>{  
			// 	//return a-b//升序
			// 	return b-a//降序
			// })
    </script>
</body>

3.10.4 Vue监测数据的原理

Vue监测数据:vue会监视data中所有层次的数据。
如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:

//向响应式对象中添加一个property,并确保这个新property同样是响应式的,且触发视图更新。
//target目标对象(target不能是vue实例或实例中的data第一层(跟)数据对象),propertyName属性名,value属性值
Vue.set(target,propertyName/index,value)  
vm.$set(target,propertyName/index,value)

变更数组的方法
Vue将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。变更方法包括:push()、pop()、shift()、unshift()、splice()、sort()、reverse()。这些方法会变更原数组

变更方法详细介绍:
● push / pop: 末尾添加、删除,改变原数组, 返回添加之后新数组的长度或删除的这个值
● unshift / shift: 头部添加、删除,改变原数组,返回添加之后新数组的长度或删除的这个值
● sort/ reverse: 排序、反转,改变原数组
● splice(start开始的位置, number删除/更改的个数, 替换的值): 一般用于删除或更改数组中的元素,返回删除或更改元素组成的数组,改变原数组

替换数组的方法
非变更方法:不会变更原始数组,而总是返回一个新数组。非变更方法包括: filter()、concat() 和 slice()。当使用非变更方法时,用一个含有相同元素的数组去替换原来的数组是非常高效的操作,例如:

//h !== '抽烟'的元素组成一个新数组
this.student.hobby = this.student.hobby.filter((h) => {
    return h !== '抽烟';
 });

非变更方法详细介绍:
● filter(item => true(满足条件为true))返回的是满足条件的一个新数
● concat: 连接数组,不影响原数组, 浅拷贝
● slice(start开始的索引, end结束的索引): 返回截断后的新数组,不改变原数组

<!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></title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h1>学生信息</h1>
      <button @click="student.age++">年龄+1岁</button> <br />
      <button @click="addSex">添加性别属性,默认值:男</button> <br />
      <button @click=" student.sex='女' ">修改性别为女</button> <br />
      <button @click="addFriend">在列表首位添加一个朋友</button> <br />
      <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button><br />
      <button @click="addHobby">添加一个爱好</button> <br />
      <button @click="updateFirstHobby">修改第一个爱好为:开车</button> <br />
      <button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br />
      <h3>姓名:{{student.name}}</h3>
      <h3>年龄:{{student.age}}</h3>
      <h3>性别:{{student.sex}}</h3>
      <h3>爱好:</h3>
      <ul>
        <li v-for="(h,index) in student.hobby" :key="index">{{h}}</li>
      </ul>
      <h3>朋友们:</h3>
      <ul>
        <li v-for="(f,index) in student.friends " :key="index">
          {{f.name}}--{{f.age}}
        </li>
      </ul>
    </div>
    <script>
      const vm = new Vue({
        el: '#app',
        data: {
          student: {
            name: 'tom',
            age: 18,
            hobby: ['抽烟', '喝酒', '烫头'],
            friends: [
              { name: 'jerry', age: 35 },
              { name: 'tony', age: 36 },
            ],
          },
        },
        methods: {
          addSex() {
            //Vue.set(this.student, 'sex', '男');
            this.$set(this.student, 'sex', '男');
          },
          addFriend() {
            this.student.friends.unshift({ name: 'tom', age: 34 });
          },
          updateFirstFriendName() {
            this.student.friends[0].name = '张三';
          },
          addHobby() {
            this.student.hobby.push('打游戏');
          },
          updateFirstHobby() {
            //this.student.hobby.splice(0, 1, '开车');
            this.$set(this.student.hobby, 0, '开车');
          },
          removeSmoke() {
            //arr.filter(item => true(满足条件为true));//返回的是满足条件的一个新数组
            this.student.hobby = this.student.hobby.filter((h) => {
              return h !== '抽烟';
            });
          },
        },
      });
    </script>
  </body>
</html>

图片.png

3.11 其他指令语法

3.11.1 v-cloak指令

插值表达式存在的问题:“闪动”
如何解决该问题:使用v-cloak指令
ref : 为某个元素注册一个唯一标识, vue对象通过$refs属性访问这个元素对象
v-cloak指令的用法:
1、提供样式

[v-cloak]{
  display: none;
}

2、在插值表达式所在的标签中添加v-cloak指令
背后的原理:先通过样式隐藏内容,然后在内存中进行值的替换,替换好之后再显示最终的结果

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
  [v-cloak]{
    display: none;
  }
  </style>
</head>
<body>
  <div id="app">
    <div v-cloak>{{msg}}</div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    var app = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue'
      }
    });
  </script>
</body>
</html>

3.11.2 数据绑定指令语法v-text、v-html、v-pre

如何理解响应式?
html5中的响应式(尺寸数据的变化导致屏幕样式的变化)
数据的响应式(数据的变化导致页面内容的变化)
数据绑定:将数据填充到标签中,默认是响应式的,即data中属性发生改变,插值内容也会随之变化。

指令语法
● v-text更新元素的 textContent

语法:v-text=‘msg’
作用:向其所在的节点中渲染文本内容。
与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

● v-html输出真正的 HTML

语法: `v-html=‘msg’
作用:向指定节点中渲染包含html结构的内容。
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

图片.png

与插值语法的区别:
(1). v-html会替换掉节点中所有的内容,{{xxx}}则不会。
(2). v-html可以识别html结构。
严重注意:v-html有安全性问题!!!!
(1). 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2). 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

v-pre指令:
● v-pre填充原始信息 跳过其所在节点的编译过程。

显示原始信息,跳过分析编译过程,可利用它跳过没有使用指令语法、没有使用插值语法的节点,会加快编译。
语法:
{{msg}} 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>{{msg}}</div>
    <div v-text='msg'></div>
    <div v-html='msg1'></div>
    <div v-pre>{{msg}}</div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    var vm = new Vue({
      el: '#app',
      data: {
        msg: 'Hello Vue',
        msg1: '<h1>HTML</h1>'
      }
    });
  </script>
</body>
</html>

图片.png

3.11.3 v-once指令

● v-once指令只编译一次,显示内容之后不再具有响应式功能
● v-once的应用场景:以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
● 语法:

  <h2 v-once>{{msg}}</h2>

4 Vue常用特性

4.1 表单操作

input中的type类型
● text单行文本
● textarea多行文本
● select 下拉多选
● radio 单选框
● checkbox多选框

收集表单数据:
● 若<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
● 若<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值
● 若<input type="checkbox"/>

    1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
    2.配置input的value属性:
    (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
    (2)v-model的初始值是数组,那么收集的的就是value组成的数组

v-model的三个修饰符:
● number:表单输入的字符串转化为数值
● trim:去掉开始和结尾的空格
● lazy:失去焦点再收集数据

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>收集表单数据</title>
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>

  <body>
    <div id="root">
      <form @submit.prevent="demo">
        账号:
        <input type="text" v-model.trim="userInfo.account" />
        <br />
        密码:
        <input type="password" v-model="userInfo.password" />
        <br />
        性别: 男
        <input type="radio" name="sex" v-model="userInfo.sex" value="male" />
        女
        <input type="radio" name="sex" v-model="userInfo.sex" value="female" />
        <br />
        爱好: 学习
        <input type="checkbox" v-model="userInfo.hobby" value="study" />
        打游戏
        <input type="checkbox" v-model="userInfo.hobby" value="game" />
        <br />
        所属校区
        <select v-model="userInfo.city">
          <option value="">请选择校区</option>
          <option value="beijing">北京</option>
          <option value="shanghai">上海</option>
          <option value="wuhan">武汉</option>
        </select>
        <br />
        其他信息:
        <textarea v-model.lazy="userInfo.other"></textarea>
        <br />
        <input type="checkbox" v-model="userInfo.agree" />
        阅读并接受
        <a href="http://www.atguigu.com">《用户协议》</a>
        <button>提交</button>
      </form>
    </div>
  </body>

  <script type="text/javascript">
    new Vue({
      el: '#root',
      data: {
        userInfo: {
          account: '',
          password: '',
          sex: 'female',
          hobby: [],
          city: 'beijing',
          other: '',
          agree: '',
        },
      },
      methods: {
        demo() {
          console.log(JSON.stringify(this.userInfo));
        },
      },
    });
  </script>
</html>

图片.png

4.2 自定义指令

自定义指令:内置指令不满足需求,需要自己定义指令使用

需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。

定义语法:
(1) 局部指令

//写法一:
new Vue({															
	directives:{指令名:回调函数}	
}) 	
//写法二:		
new Vue({
	directives{指令名:配置对象}
})

例子

    directives: {
         //写法一:回调函数写法
         //el,指令所绑定的元素. binding,一个对象,包含以下属性:name:指令名,不包括 v- 前缀。value:指令的绑定值 arg:传给指令的参数,
          big(el, binding) {
            el.innerText = binding.value * 10;
          },
          
        //写法二:配置对象写法  在不使用inserted钩子函数时可以使用方法一简写
        
          big: {
            //指令与元素成功绑定时(一上来)
            bind(el, binding) {
              el.innerText = binding.value * 10;
            },
            //指令所在元素被插入页面时
            inserted(el, binding) {},
            //指令所在的模板被重新解析时
            update(el, binding) {
              el.innerText = binding.value * 10;
            },
          },
      
        },

(2) 全局指令

//方法一
Vue.directive(指令名,配置对象) 
//方法二
Vue.directive(指令名,回调函数)

配置对象中常用的3个钩子函数:

bind:指令与元素成功绑定时调用。
inserted:指令所在元素被插入页面时调用
update:指令所在模板结构被重新解析时调用。

备注:
指令定义时不加v-,但使用时要加v-;
指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>自定义指令</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<h2>{{name}}</h2>
			<h2>当前的n值是:<span v-text="n"></span> </h2>
			<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
			<button @click="n++">点我n+1</button>
			<hr/>
			<input type="text" v-fbind:value="n">
		</div>
	</body>
	
	<script type="text/javascript">
		//定义全局指令
		/* Vue.directive('fbind',{
			//指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value
			},
			//指令所在元素被插入页面时
			inserted(element,binding){
				element.focus()
			},
			//指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value
			}
		}) */

		new Vue({
			el:'#root',
			data:{
				name:'学习猿地',
				n:1
			},
			directives:{
			
               //big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
				big(el,binding){
					console.log('big',this) //注意此处的this是window
					el.innerText = binding.value * 10
				},
				fbind:{
					//指令与元素成功绑定时(一上来)
					bind(element,binding){
						element.value = binding.value
					},
					//指令所在元素被插入页面时
					inserted(element,binding){
						element.focus()
					},
					//指令所在的模板被重新解析时
					update(element,binding){
						element.value = binding.value
					}
				}
			}
		})
		
	</script>
</html>

4.3 计算属性computed

计算属性computed:所需要的属性不存在,要通过已有属性(data中的属性或外部传入的prop)计算得来

使用:在computed属性对象中定义计算属性的方法,在页面中使用{{方法名}}来显示计算的结果
原理:底层借助了Objcet.defineproperty()方法提供的getter和setter。
get有什么作用?
当读取computed中的方法时,get就会被调用,且返回值就作为该方法的值
get函数什么时候执行?
(1) 初次读取时会执行一次。
(2) 当依赖的数据发生改变时会被再次调用。

优势:与methods实现相比,computed内部有缓存机制(复用),效率更高,调试方便,方法methods不缓存。
备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

计算属性完整写法

<div>{{fullName}}</div>

 computed: {
 //我们提供的函数fullName将用作vm.fullName的getter函数上,计算属性默认只有 getter
       fullName: {
        get(){//get可以省略
        //当fullName所依赖的firstNameh和lastName发生改变时会被再次调用
          return this.firstName + '-' + this.lastName;
        },
         set(value){
         //当fullName被修改时调用set
          console.log(value)
        }
     }
 }

常用简写(只考虑读取,不考虑修改的情况下)

<div>{{fullName}}</div>

 computed: {
       fullName()  {
          return this.firstName + '-' + this.lastName;
        }
 }


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <div> 名:<input type="text" v-model="firstName" /> </div>
      <div> 姓:<input type="text" v-model="lastName" /> </div>   
      <div>{{fullName}}</div>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
      var vm = new Vue({
        el: '#app',
        data: {
          firstName: 'Jim',
          lastName: 'Green',
        },
        computed: {
          fullName() {
            return this.firstName + ' ' + this.lastName;
          },
        },
      });
    </script>
  </body>
</html>

图片.png

4.4 监听属性watch

监听属性watch:监听具体数据变化,当数据属性变化时, 回调函数handler自动调用, 在函数内部进行计算
写法:
(1)在vue实例vm中传入watch配置来监视指定的属性
(2)通过vm对象的$watch()
应用场景:数据变化时执行异步或开销较大(比较耗时)的操作
注意:监听的属性必须在vm中存在,才能进行监听

监听属性watch的写法

   var vm = new Vue({
        el: '#app',
        data: {
          firstName: 'Jim',
          lastName: 'Green',
          fullName: 'Jin Green',
        },
        //侦听器监听输入信息的变化
        watch: {
          //xxx为vm实例中存在且被监听的属性
          //   xxx: {
          //     immediate: false, //初始化时让handler调用一下
          //     //handler(固定函数) 什么时候调用?当函数中的数据发生改变时
          //     handler() {
          //       ....
          //     },
          //   },
          
          //简写 (当只需要handler属性时)
          firstName(val) {
            this.fullName = val + ' ' + this.lastName;
          },
          lastName(val) {
            this.fullName = this.firstName + ' ' + val;
          },
        },
      });
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>名:</span>
      <span>
        <input type="text" v-model='firstName'>
      </span>
    </div>
    <div>
      <span>姓:</span>
      <span>
        <input type="text" v-model='lastName'>
      </span>
    </div>
    <div>{{fullName}}</div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    var vm = new Vue({
      el: '#app',
      data: {
        firstName: 'Jim',
        lastName: 'Green',
        fullName: 'Jin Green'
      },
      watch: {
        firstName(val) {
          this.fullName = val + ' ' + this.lastName;
        },
        lastName(val) {
          this.fullName = this.firstName + ' ' + val;
        },
      }
    });
  </script>
</body>

</html>

深度监听:
(1) Vue中的watch默认不监测对象内部值的改变,只检测第一层。
(2) 配置deep:true可以监测对象内部值改变,可以检测多层。
备注:
(1). Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2). 使用watch时根据数据的具体结构,决定是否采用深度监视。

<!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></title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h3>a的值是:{{numbers.a}}</h3>
      <button @click="numbers.a++">点我让a++</button>
    </div>
    <script>
      const vm = new Vue({
        el: '#app',
        data: {
          numbers: {
            a: 1,
            b: 2,
          },
        },
        watch: {
          numbers: {
          //深度监听
            deep: true,
            handler() {
              console.log('numbers改变了');
            },
          },
        },
      });
    </script>
  </body>
</html>

4.5 computed与watch、methods的区别

computed:计算属性,依赖其他属性,当其他属性改变的时候,下一次获取computed值时也会改变,computed的值会有缓存
watch:监听属性,监听具体数据变化,当数据属性变化时, 回调函数handler自动调用, 在函数内部进行计算
methods : 该属性用于在Vue对象中定义方法。
computed与watch区别:
● computed能完成的功能,watch都可以完成。watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
● 当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
● 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
● 计算属性computed在大多数情况下更合适,但当需要在数据变化时执行异步或开销较大的操作时,使用watch更适用。
computed与methods区别:
● 计算属性是基于它们的依赖进行缓存,如果多次使用时,计算属性只会调用一次,性能上计算属性明显比methods好,如果依赖改变则重新缓存,而方法不缓存

4.6 过滤器filter

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
全局过滤器:

Vue.filter(filterName, function(value[,arg1,arg2,...]){
  // 进行一定的数据处理
  return newValue
})

局部过滤器:

new Vue{
	filters:{
		filterName(value){
			return newValue
		}
	}
}
//使用方法
<div>{{myData | filterName}}</div>
<div>{{myData | filterName(arg)}}</div>

备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据

示例

<body>
    <div id="demo">
        <h2>显示格式化的日期时间</h2>
        <p>{{date}}</p> 
        <p>完整版:{{date | dateString}}</p>
        <p>年月日:{{date | dateString('YYYY-MM-DD')}}</p>
        <p>时分秒:{{date | dateString('HH:mm:ss')}}</p>
    </div>
    <script src="../js/vue.js"></script>
    //引入时间格式化插件
    <script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/moment.js"></script>
    <script>
        Vue.filter('dateString', function(value, format='YYYY-MM-DD HH:mm:ss'){
            return moment(value).format(format);
        });

        new Vue({
            el: '#demo',
            data: {
                date: new Date()
            }
        })
    </script>
</body>

图片.png

使用dayjs

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>过滤器</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.0/dayjs.min.js"></script>
  </head>
  <body>
    <div id="root">
      <h2>显示格式化后的时间</h2>
      <!-- 计算属性实现 -->
      <h3>现在是:{{fmtTime}}</h3>
      <!-- methods实现 -->
      <h3>现在是:{{getFmtTime()}}</h3>
      <!-- 过滤器实现 -->
      <h3>现在是:{{time | timeFormater}}</h3>
      <!-- 过滤器实现(传参) -->
      <h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
      <h3 :x="msg | mySlice">学习猿地</h3>
    </div>

    <div id="root2">
      <h2>{{msg | mySlice}}</h2>
    </div>
  </body>

  <script type="text/javascript">
    Vue.config.productionTip = false;
    //全局过滤器
    Vue.filter('mySlice', function (value) {
      return value.slice(0, 4);
    });

    new Vue({
      el: '#root',
      data: {
        time: 1621561377603, //时间戳
        msg: '你好',
      },
      computed: {
        fmtTime() {
          return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss');
        },
      },
      methods: {
        getFmtTime() {
          return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss');
        },
      },
      //局部过滤器
      filters: {
        timeFormater(value, str = 'YYYY年MM月DD日 HH:mm:ss') {
          // console.log('@',value)
          return dayjs(value).format(str);
        },
      },
    });

    new Vue({
      el: '#root2',
      data: {
        msg: 'hello!',
      },
    });
  </script>
</html>

图片.png

4.7 Vue生命周期

生命周期:事物从诞生到消亡的整个过程
vue生命周期:Vue 实例从创建到销毁的过程,vue生命周期钩子:在达到某一阶段时去触发的函数
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的,生命周期函数中的this指向是vm 或 组件实例对象。
它可以总共分为8个阶段:创建前/后, 挂载前/后,更新前/后,销毁前/销毁后

create阶段:vue实例被创建
mount阶段: vue实例被挂载到真实DOM节点
update阶段:当vue实例里面的data数据变化时,触发组件重新渲染
destroy阶段:vue实例被销毁

1、beforeCreate(创建前)
表示实例完全被创建出来之前,vue 实例的挂载元素$el和数据对象 data 都为 undefined,还未初始化。

2、created(创建后)
数据对象 data 已存在,可以调用 methods 中的方法,操作 data 中的数据,但 dom 未生成,$el 未存在 。

3、beforeMount(挂载前)
vue 实例的 $el 和 data 都已初始化,挂载之前为虚拟的 dom节点,模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中。data.message 未替换。

4、mounted(挂载后)
vue 实例挂载完成,data.message 成功渲染。内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了。实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,DOM 渲染在 mounted 中就已经完成了。

5、beforeUpdate(更新前)
当 data 变化时,会触发beforeUpdate方法 。data 数据尚未和最新的数据保持同步。

6、updated(更新后)
当 data 变化时,会触发 updated 方法。页面和 data 数据已经保持同步了。

7、beforeDestory(销毁前)
组件销毁之前调用 ,在这一步,实例仍然完全可用。

8、destoryed(销毁后)
组件销毁之后调用,对 data 的改变不会再触发周期函数,vue 实例已解除事件监听和 dom绑定,但 dom 结构依然存在。

常用的生命周期方法:
mounted():初始化操作,发送ajax请求, 启动定时器、绑定自定义事件、订阅消息等异步任务
beforeDestroy(): 做收尾工作, 清除定时器、解绑自定义事件、取消订阅消息等

关于销毁Vue实例:
销毁后借助Vue开发者工具看不到任何信息
销毁后自定义事件会失效,但原生DOM事件依然有效
一般不会在beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了。
图片.png
vue生命周期在真实场景下的业务应用
created:vue实例被创建
mounted: 挂载元素,获取dom节点
nextTick: 针对单一事件更新数据后立即操作dom
updated: 任何数据的更新,做统一的业务逻辑处理
watch: 监听具体数据变化,并做相应的处理

发表评论

0/200
0 点赞
0 评论
收藏