Vue

Vue 是什么?

  • Vue 是一套用于构建用户界面的渐进式框架
  • vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合

vue面试题:

  • v-model的底层原理是什么?

    (首先通过属性绑定的方式,v-bind:value="",然后通过input事件动态的获取输入框的值。修改data内的数据,一旦data内的数据发生改变,通过属性绑定的值也会改变。)

  • computed和watch的区别是什么?

    (computed:

    1.要设置一个新的值,比如value(){}

    2.value是依赖于data里的某个数据进行操作的,只要data里的数据发生改变,value函数就会自动执行

    3.而且computed里的值是有缓存的,多次渲染value,函数并不会重新计算,它会从缓存中拿第一次value方法执行的结果

    4.value方法必须要有return

    watch:

    1.方法名必须和data里的某个数据(比如value:123)名字一致,比如value(new,old){},new是函数执行后新的值,old值是函数执行前的值

    2.只要data里的数据方式改变,watch里监听的函数就会立即执行

    3.它和计算属性很像,但是它一般用来操作一些异步请求,因为在computed里无法发送异步请求)

  • 页面出现白屏怎么办?

    (数据没拿到就会出现白屏,

    解决方案:给加载动画,拿到数据让动画消失)

  • data数据不更新是什么原因?

    (出现的场景,修改数组,对象直接添加属性和值不会造成页面更新

    this.$set(数组/对象名,索引/key,要更新的数据))

  • 如何保证我发送的多次后台请求,返回的值也是按照发送的额顺序返回的?

    (Promise.all)

mvvm

MVVM

M代表数据层,V代表视图层,VM view-model 数据层数据发生改变通过数据绑定的方式让视图层也发生变化,视图层发生变化,通过数据绑定的方式让数据层也发生变化。

  • MVC 是后端的分层开发概念; MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel
  • m model
    • 数据层 Vue 中 数据层 都放在 data 里面
  • v view 视图
    • Vue 中 view 即 我们的HTML页面
  • vm (view-model) 控制器 将数据和视图层建立联系
    • vm 即 Vue 的实例 就是 vm

使用Vue将helloworld 渲染到页面上

<body>
    <div id="app">
        <div>{{msg}}</div>
        <div>{{1 + 2}}</div>
        <div>{{msg + '----' + 123}}</div>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        /*
              Vue的基本使用步骤
              1、需要提供标签用于填充数据
              2、引入vue.js库文件
              3、可以使用vue的语法做功能了
              4、把vue提供的数据填充到标签里面
            */
        var vm = new Vue({
            el: '#app',
            data: {
                msg: 'Hello Vue'
            }
        });
    </script>
</body>

1. 指令

  • 本质就是自定义属性
  • Vue中指定都是以 v- 开头

1.0 v-cloak

  • 防止页面加载时出现闪烁问题

     <style type="text/css">
      /* 
        1、通过属性选择器 选择到 带有属性 v-cloak的标签  让他隐藏
     */
      [v-cloak]{
        /* 元素隐藏 */
        display: none;
      }
      </style>
    <body>
      <div id="app">
        <!-- 2、 让带有插值 语法的   添加 v-cloak 属性 
             在 数据渲染完场之后,v-cloak 属性会被自动去除,
             v-cloak一旦移除也就是没有这个属性了  属性选择器就选择不到该标签
    		 也就是对应的标签会变为可见
        -->
        <div v-cloak>{{msg}}</div>
      </div>
      <script type="text/javascript" src="js/vue.js"></script>
      <script type="text/javascript">
        var vm = new Vue({
          //  el   指定元素 id 是 app 的元素  
          el: '#app',
          //  data  里面存储的是数据
          data: {
            msg: 'Hello Vue'
          }
        });
    </script>
    </body>
    </html>
    

1.1 v-text

  • v-text指令用于将数据填充到标签中,作用于插值表达式类似,但是没有闪动问题
  • 如果数据中有HTML标签会将html标签一并输出
  • 注意:此处为单向绑定,数据对象上的值改变,插值会发生变化;但是当插值发生变化并不会影响数据对象的值
<div id="app">
    <!--  
		注意:在指令中不要写插值语法  直接写对应的变量名称 
        在 v-text 中 赋值的时候不要在写 插值语法
		一般属性中不加 {{}}  直接写 对应 的数据名 
	-->
    <p v-text="msg"></p>
    <p>
        <!-- Vue  中只有在标签的 内容中 才用插值语法 -->
        {{msg}}
    </p>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });

</script>

1.2 v-html

  • 用法和v-text 相似 但是他可以将HTML片段填充到标签中

  • 可能有安全问题, 一般只在可信任内容上使用 v-html永不用在用户提交的内容上

  • 它与v-text区别在于v-text输出的是纯文本,浏览器不会对其再进行html解析,但v-html会将其当html标签解析后输出。

    <div id="app">
      <p v-html="html"></p> <!-- 输出:html标签在渲染的时候被解析 -->
        
        <p>{{message}}</p> <!-- 输出:<span>通过双括号绑定</span> -->
        
      <p v-text="text"></p> <!-- 输出:<span>html标签在渲染的时候被源码输出</span> -->
    </div>
    <script>
      let app = new Vue({
      el: "#app",
      data: {
        message: "<span>通过双括号绑定</span>",
        html: "<span>html标签在渲染的时候被解析</span>",
        text: "<span>html标签在渲染的时候被源码输出</span>",
      }
     });
    </script>
    

1.3 v-pre

  • 显示原始信息跳过编译过程
  • 跳过这个元素和它的子元素的编译过程。
  • 一些静态的内容不需要编译加这个指令可以加快渲染
    <span v-pre>{{ this will not be compiled }}</span>    
	<!--  显示的是{{ this will not be compiled }}  -->
	<span v-pre>{{msg}}</span>  
     <!--   即使data里面定义了msg这里仍然是显示的{{msg}}  -->
<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });

</script>

1.4 v-once

  • 执行一次性的插值【当数据改变时,插值处的内容不会继续更新】
  <!-- 即使data里面定义了msg 后期我们修改了 仍然显示的是第一次data里面存储的数据即 Hello Vue.js  -->
     <span v-once>{{ msg}}</span>    
<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });
</script>

1.5 v-model 双向数据绑定

  • 当数据发生变化的时候,视图也就发生变化

  • 当视图发生变化的时候,数据也会跟着同步变化

  • v-model是一个指令,限制在 <input>、<select>、<textarea>、components中使用

 <div id="app">
      <div>{{msg}}</div>
      <div>
          当输入框中内容改变的时候,  页面上的msg  会自动更新
        <input type="text" v-model='msg'>
      </div>
  </div>

v-model的底层原理是什么?

首先通过属性绑定的方式,v-bind:value="",然后通过input事件动态的获取输入框的值。修改data内的数据,一旦data内的数据发生改变,通过属性绑定的值也会改变。

<div id="app">
    <div>{{msg}}</div>
    <input type="text" v-bind:value="msg" v-on:input='handle'>
    <input type="text" v-bind:value="msg" v-on:input='msg=$event.target.value'>
    <input type="text" v-model='msg'>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      v-model指令的本质

    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: 'hello'
      },
      methods: {
        handle: function(event){
          // 使用输入域中的最新的数据覆盖原来的数据
          this.msg = event.target.value;
        }
      }
    });
  </script>

1.6 v-on

  • 用来绑定事件的
  • 形式如:v-on:click 缩写为 @click;

1.6.1 v-on事件函数中传入参数

<body>
    <div id="app">
        <div>{{num}}</div>
        <div>
            <!-- 如果事件直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数 -->
            <button v-on:click='handle1'>点击1</button>
            <!-- 2、如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,
                 并且事件对象的名称必须是$event,@click='handle()'不写不会会自动传递参数 
            -->
            <button v-on:click='handle2(123, 456, $event)'>点击2</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: {
                handle1: function(event) {
                    console.log(event.target.innerHTML)
                },
                handle2: function(p, p1, event) {
                    console.log(p, p1)
                    console.log(event.target.innerHTML)
                    this.num++;
                }
            }
        });
    </script>

1.6.2 事件修饰符

  • 在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。
  • Vue 不推荐我们操作DOM 为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符
  • 修饰符是由点开头的指令后缀来表示的
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联   即阻止冒泡也阻止默认事件 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

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

1.6.3 按键修饰符

  • 在做项目中有时会用到键盘事件,在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">

<!-- -当点击enter 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

<!--当点击enter或者space时  时调用 `vm.alertMe()`   -->
<input type="text" v-on:keyup.enter.space="alertMe" >

常用的按键修饰符
.enter =>    enter键
.tab => tab键
.delete (捕获“删除”和“退格”按键) =>  删除键
.esc => 取消键
.space =>  空格键
.up =>  上
.down =>  下
.left =>  左
.right =>  右

<script>
	var vm = new Vue({
        el:"#app",
        methods: {
              submit:function(){},
              alertMe:function(){},
        }
    })

</script>

1.6.4 自定义按键修饰符别名

  • 在Vue中可以通过config.keyCodes自定义按键修饰符别名
<div id="app">
    预先定义了keycode 116(即F5)的别名为f5,因此在文字输入框中按下F5,会触发prompt方法
    <input type="text" v-on:keydown.f5="prompt()">
</div>

<script>
	
    Vue.config.keyCodes.f5 = 116;

    let app = new Vue({
        el: '#app',
        methods: {
            prompt: function() {
                alert('我是 F5!');
            }
        }
    });
</script>

1.7 v-bind

  • v-bind 指令被用来响应地更新 HTML 属性
  • v-bind:href 可以缩写为 :href;
<!-- 绑定一个属性 -->
<img v-bind:src="imageSrc">

<!-- 缩写 -->
<img :src="imageSrc">

1.7.1 绑定对象

  • 我们可以给v-bind:class 一个对象,以动态地切换class。
  • 注意:v-bind:class指令可以与普通的class特性共存
1、 v-bind 中支持绑定一个对象 
	如果绑定的是一个对象 则 键为 对应的类名  值 为对应data中的数据 
<!-- 
	HTML最终渲染为 <ul class="box textColor textSize"></ul>
	注意:
		textColor,textSize  对应的渲染到页面上的CSS类名	
		isColor,isSize  对应vue data中的数据  如果为true 则对应的类名 渲染到页面上 


		当 isColor 和 isSize 变化时,class列表将相应的更新,
		例如,将isSize改成false,
		class列表将变为 <ul class="box textColor"></ul>
-->

<ul class="box" v-bind:class="{textColor:isColor, textSize:isSize}">
    <li>学习Vue</li>
    <li>学习Node</li>
    <li>学习React</li>
</ul>
  <div v-bind:style="{color:activeColor,fontSize:activeSize}">对象语法</div>
<sript>
var vm= new Vue({
    el:'.box',
    data:{
        isColor:true,
        isSize:true,
    	activeColor:"red",
        activeSize:"25px",
    }
})
</sript>
<style>
    .box{
        border:1px dashed #f0f;
    }
    .textColor{
        color:#f00;
        background-color:#eef;
    }
    .textSize{
        font-size:30px;
        font-weight:bold;
    }
</style>

1.7.2 绑定数组

2、  v-bind 中支持绑定一个数组    数组中classA和 classB 对应为data中的数据

这里的classA  对用data 中的  classA
这里的classB  对用data 中的  classB
<ul class="box" :class="[classA, classB]">
    <li>学习Vue</li>
    <li>学习Node</li>
    <li>学习React</li>
</ul>
<script>
var vm= new Vue({
    el:'.box',
    data:{
        classA:‘textColor‘,
        classB:‘textSize‘
    }
})
</script>
<style>
    .box{
        border:1px dashed #f0f;
    }
    .textColor{
        color:#f00;
        background-color:#eef;
    }
    .textSize{
        font-size:30px;
        font-weight:bold;
    }
</style>

1.7.3 绑定对象和数组的区别

  • 绑定对象的时候 对象的属性 即要渲染的类名 对象的属性值对应的是 data 中的数据
  • 绑定数组的时候数组里面存的是data 中的数据

1.7.4 绑定style

 <div v-bind:style="styleObject">绑定样式对象</div>'
 
<!-- CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来)    -->
 <div v-bind:style="{ color: activeColor, fontSize: fontSize,background:'red' }">内联样式</div>

<!--组语法可以将多个样式对象应用到同一个元素 -->
<div v-bind:style="[styleObj1, styleObj2]"></div>
<script>
	var vm = new Vue({
      el: '#app',
      data: {
        styleObject: {
          color: 'green',
          fontSize: '30px',
          background:'red'
        },
        activeColor: 'green',
   		fontSize: "30px",
       styleObj1: {
             color: 'red'
       },
       styleObj2: {
            fontSize: '30px'
       }
      },
</script>
<style>
      
</style>

1.8 分支结构

1.8.1 v-if 使用场景

  • 1- 多个元素 通过条件判断展示或者隐藏某个元素。或者多个元素
  • 2- 进行两个视图之间的切换

1.8.2 v-show

  • 控制元素样式是否显示 display:none
<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>
$nextTick()

在数据更新之后,立刻获取到更新之后的dom的状态

1.8.3 v-show 和 v-if的区别

  • v-show本质就是标签display设置为none,控制隐藏。渲染所有盒子,只是让符合条件的盒子会block,频繁切换显示的的地方适合使用,开销小
    • v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。
  • v-if是动态的向DOM树内添加或者删除DOM元素,性能开销比较大,适合页面不是频繁切换显示的地方
    • v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

1.9 循环结构

v-for

  • 用于循环的数组里面的值可以是对象,也可以是普通元素

    <ul id="example-1">
       <!-- 循环结构-遍历数组  
    	item 是我们自己定义的一个名字  代表数组里面的每一项  
    	items对应的是 data中的数组-->
      <li v-for="item in items">
        {{ item.message }}
      </li> 
    
    </ul>
    <script>
     new Vue({
      el: '#example-1',
      data: {
        items: [
          { message: 'Foo' },
          { message: 'Bar' }
        ]}
    })
    </script>
    
  • 不推荐同时使用 v-ifv-for

    v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级。所以有时候为了渲染某一项符合条件的数据时,v-for也会渲染所有数据后再做v-if判断,耗费性能,v-for可以配合计算属性使用

  • 循环对象

   <!--  循环结构-遍历对象
		v 代表   对象的value
		k  代表对象的 键 
		i  代表索引	
	---> 
     <div v-if='v==13' v-for='(v,k,i) in obj'>{{v + '---' + k + '---' + i}}</div>

<script>
 new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ],
    obj: {
        uname: 'zhangsan',
        age: 13,
        gender: 'female'
    }
  }
})
</script>
  • key 的作用
    • key来给每个节点做一个唯一标识
    • key的作用主要是为了高效的更新虚拟DOM
<ul>
  <li v-for="item in items" :key="item.id">...</li>
</ul>

案例选项卡

    <div id="app">
        <div class="tab">
            <ul>
                <!-- 通过v-on 添加点击事件   需要把当前li 的索引传过去 
				-->
                <li v-on:click='change(index)'		           			
                    :class='currentIndex==index?"active":""'                   
                    :key='item.id' 
                    v-for='(item,index) in list'>{{item.title}}</li>
            </ul>
            <div :class='currentIndex==index?"current":""' 
                 :key='item.id' v-for='(item, index) in list'>
                <img :src="item.path">
            </div>
        </div>
    </div>

<script>
    new  Vue({
        el: '#app',
            data: {
                currentIndex: 0, // 选项卡当前的索引  默认为 0  
                list: [{
                    id: 1,
                    title: 'apple',
                    path: 'img/apple.png'
                }, {
                    id: 2,
                    title: 'orange',
                    path: 'img/orange.png'
                }, {
                    id: 3,
                    title: 'lemon',
                    path: 'img/lemon.png'
                }]
            },
            methods: {
                change: function(index) {
                    // 通过传入过来的索引来让当前的  currentIndex  和点击的index 值 相等 
                    //  从而实现 控制类名    
                    this.currentIndex = index;
                }
            }
    })
</script>

2. 表单基本操作

2.1获取单选框中的值

  • 通过v-model
 	<!-- 
		1、 两个单选框需要同时通过v-model 双向绑定 一个值 
        2、 每一个单选框必须要有value属性  且value 值不能一样 
		3、 当某一个单选框选中的时候 v-model  会将当前的 value值 改变 data 中的 数据

		gender 的值就是选中的值,我们只需要实时监控他的值就可以了
	-->
   <input type="radio" id="male" value="1" v-model='gender'>
   <label for="male"></label>

   <input type="radio" id="female" value="2" v-model='gender'>
   <label for="female"></label>

<script>
    new Vue({
         data: {
             // 默认会让当前的 value 值为 2 的单选框选中
                gender: 2,  
            },
    })
</script>
  • 获取复选框中的值

    • 通过v-model
    • 和获取单选框中的值一样
    • 复选框 checkbox 这种的组合时 data 中的 hobby 我们要定义成数组 否则无法实现多选
    	<!-- 
    		1、 复选框需要同时通过v-model 双向绑定 一个值 
            2、 每一个复选框必须要有value属性  且value 值不能一样 
    		3、 当某一个单选框选中的时候 v-model  会将当前的 value值 改变 data 中的 数据
    
    		hobby 的值就是选中的值,我们只需要实时监控他的值就可以了
    	-->
    
    <div>
       <span>爱好:</span>
       <input type="checkbox" id="ball" value="1" v-model='hobby'>
       <label for="ball">篮球</label>
       <input type="checkbox" id="sing" value="2" v-model='hobby'>
       <label for="sing">唱歌</label>
       <input type="checkbox" id="code" value="3" v-model='hobby'>
       <label for="code">写代码</label>
     </div>
    <script>
        new Vue({
             data: {
                    // 默认会让当前的 value 值为 2 和 3 的复选框选中
                    hobby: ['2', '3'],
                },
        })
    </script>
    
  • 获取下拉框和文本框中的值

    • 通过v-model
       <div>
          <span>职业:</span>
           <!--
    			1、 需要给select  通过v-model 双向绑定 一个值 
                2、 每一个option  必须要有value属性  且value 值不能一样 
    		    3、 当某一个option选中的时候 v-model  会将当前的 value值 改变 data 中的 数据
    		     occupation 的值就是选中的值,我们只需要实时监控他的值就可以了
    		-->
           <!-- multiple  多选 -->
          <select v-model='occupation' multiple>
              <option value="0">请选择职业...</option>
              <option value="1">教师</option>
              <option value="2">软件工程师</option>
              <option value="3">律师</option>
          </select>
             <!-- textarea 是 一个双标签   不需要绑定value 属性的  -->
            <textarea v-model='desc'></textarea>
      </div>
    <script>
        new Vue({
             data: {
                    // 默认会让当前的 value 值为 2 和 3 的下拉框选中
                     occupation: ['2', '3'],
                 	 desc: 'nihao'
                },
        })
    </script>
    

2.2 表单修饰符

  • .number 转换为数值

    • 注意点:
    • 当开始输入非数字的字符串时,因为Vue无法将字符串转换成数值
    • 所以属性值将实时更新成相同的字符串。即使后面输入数字,也将被视作字符串。
  • .trim 自动过滤用户输入的首尾空白字符

    • 只能去掉首尾的 不能去除中间的空格
  • .lazy 将input事件切换成change事件

    • .lazy 修饰符延迟了同步更新属性值的时机。即将原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上
  • 在失去焦点 或者 按下回车键时才更新

    <!-- 自动将用户的输入值转为数值类型 -->
    <input v-model.number="age" type="number">
    
    <!--自动过滤用户输入的首尾空白字符   -->
    <input v-model.trim="msg">
    
    <!-- 在“change”时而非“input”时更新 -->
    <input v-model.lazy="msg" >
    

2.3 自定义指令

  • 内置指令不能满足我们特殊的需求
  • Vue允许我们自定义指令

Vue.directive 注册全局指令

<!-- 
  使用自定义的指令,只需在对用的元素中,加上'v-'的前缀形成类似于内部指令'v-if','v-text'的形式。 
-->
<input type="text" v-focus>
<script>
// 注意点: 
//   1、 在自定义指令中  如果以驼峰命名的方式定义 如  Vue.directive('focusA',function(){}) 
//   2、 在HTML中使用的时候 只能通过 v-focus-a 来使用     
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
  	// 当绑定元素插入到 DOM 中。 其中 el为dom元素
  	inserted: function (el) {
    		// 聚焦元素
    		el.focus();
 	}
});
new Vue({
  el:'#app'
});
</script>

Vue.directive 注册全局指令带参数

  <input type="text" v-color='msg'>
 <script type="text/javascript">
    /*
      自定义指令-带参数
      bind - 只调用一次,在指令第一次绑定到元素上时候调用
    */
    Vue.directive('color', {
      // bind声明周期, 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
      // el 为当前自定义指令的DOM元素  
      // binding 为自定义的函数形参   通过自定义属性传递过来的值 存在 binding.value 里面
      bind: function(el, binding){
        // 根据指令的参数设置背景色
        // console.log(binding.value.color)
        el.style.backgroundColor = binding.value.color;
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {
        msg: {
          color: 'blue'
        }
      }
    });
  </script>

自定义指令局部指令

  • 局部指令,需要定义在 directives 的选项 用法和全局用法一样
  • 局部指令只能在当前组件里面使用
  • 当全局指令和局部指令同名时以局部指令为准
<input type="text" v-color='msg'>
 <input type="text" v-focus>
 <script type="text/javascript">
    /*
      自定义指令-局部指令
    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: {
          color: 'red'
        }
      },
   	  //局部指令,需要定义在  directives 的选项
      directives: {
        color: {
          bind: function(el, binding){
            el.style.backgroundColor = binding.value.color;
          }
        },
        focus: {
          inserted: function(el) {
            el.focus();
          }
        }
      }
    });
  </script>

3. 属性

3.1 计算属性 computed

computed里的方法名可以直接作为参数在页面上使用

computed里的方法名对应的是一个函数,函数返回值是基于data内的某一个数据进行计算后的值

  • 模板中放入太多的逻辑会让模板过重且难以维护 使用计算属性可以让模板更加的简洁
  • 计算属性是基于它们的响应式依赖进行缓存的
  • computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化
 <div id="app">
     <!--  
        当多次调用 reverseString  的时候 
        只要里面的 num 值不改变 他会把第一次计算的结果直接返回
		直到data 中的num值改变 计算属性才会重新发生计算
     -->
    <div>{{reverseString}}</div>
    <div>{{reverseString}}</div>
     <!-- 调用methods中的方法的时候  他每次会重新调用 -->
    <div>{{reverseMessage()}}</div>
    <div>{{reverseMessage()}}</div>
  </div>
  <script type="text/javascript">
    /*
      计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: 'Nihao',
        num: 100
      },
      methods: {
        reverseMessage: function(){
          console.log('methods')
          return this.msg.split('').reverse().join('');
        }
      },
      //computed  属性 定义 和 data 已经 methods 平级 
      computed: {
        //  reverseString   这个是我们自己定义的名字 
        reverseString: function(){
          console.log('computed')
          var total = 0;
          //  当data 中的 num 的值改变的时候  reverseString  会自动发生计算  
          for(var i=0;i<=this.num;i++){
            total += i;
          }
          // 这里一定要有return 否则 调用 reverseString 的 时候无法拿到结果    
          return total;
        }
      }
    });
  </script>

3.2 侦听器 watch

  • 使用watch来响应数据的变化
  • 一般用于异步或者开销较大的操作
  • watch 中的属性 一定是data 中 已经存在的数据
  • 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听
 <div id="app">
	<input type="text" v-model="userName"/>
    <input type="text" v-model="userAge"/>
    <input type="text" v-model="cityName.name" />
 </div>

  <script type="text/javascript">
        /*侦听器*/
        var vm = new Vue({
            el: '#app',
            data: {
                userName: 'Jim',
                userAge: '18',
                cityName: {name:'北京'}
            },
            watch: {
                //注意:  这里userName  对应着data 中的 userName 
                //1.当userName值发生变化时触发
                //缺点 就是当值第一次绑定的时候 不会执行监听函数,只有当值改变的时候 才会执行
                userName (newName, oldName) {
				console.log(newName)
			   },
                //2.在第一次绑定的时候执行此监听函数 则需要 设置 immediate为true
                userName: {
				handler (newName, oldName) {
					console.log(newName)
				},
				immediate: true
			   },
                //3.当需要监听对象的改变时,此时就需要设置deep为true,
                //此时会给cityName的所有属性都加上监听函数
                cityName: {
				handler(newName, oldName) {
					console.log(newName)
				},
			   	immediate: true,
                 deep: true
			  }
                //3.1如果属性较多时 可以使用如下
                'cityName.name': {
					handler(newName, oldName) {
						console.log(newName)
					},
					immediate: true,
					deep: true
				}
            }
        });
    </script>

面试题:computed和watch的区别是什么?

computed:

1.要设置一个新的值,比如value(){}

2.value是依赖于data里的某个数据进行操作的,只要data里的数据发生改变,value函数就会自动执行

3.而且computed里的值是有缓存的,多次渲染value,函数并不会重新计算,它会从缓存中拿第一次value方法执行的结果

4.value方法必须要有return

watch:

1.方法名必须和data里的某个数据(比如value:123)名字一致,比如value(new,old){},new是函数执行后新的值,old值是函数执行前的值

2.只要data里的数据方式改变,watch里监听的函数就会立即执行

3.它和计算属性很像,但是它一般用来操作一些异步请求,因为在computed里无法发送异步请求

3.3 过滤器

  • Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。
  • 过滤器可以用在两个地方:双花括号插值和v-bind表达式。
  • 过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示
  • 支持级联操作
  • 过滤器不改变真正的data,而只是改变渲染的结果,并返回过滤后的版本
  • 全局注册时是filter,没有s的。而局部过滤器是filters,是有s的
  <div id="app">
    <input type="text" v-model='msg'>
      <!-- upper 被定义为接收单个参数的过滤器函数,表达式  msg  的值将作为参数传入到函数中 -->
    <div>{{msg | upper}}</div>
    <!--  
      支持级联操作
      upper  被定义为接收单个参数的过滤器函数,表达式msg 的值将作为参数传入到函数中。
	  然后继续调用同样被定义为接收单个参数的过滤器 lower ,将upper 的结果传递到lower中
 	-->
    <div>{{msg | upper | lower}}</div>
    <div :abc='msg | upper'>测试数据</div>
  </div>

<script type="text/javascript">
   //  lower  为全局过滤器     
   Vue.filter('lower', function(val) {
      return val.charAt(0).toLowerCase() + val.slice(1);
    });
    var vm = new Vue({
      el: '#app',
      data: {
        msg: ''
      },
       //filters  属性 定义 和 data 已经 methods 平级 
       //  定义filters 中的过滤器为局部过滤器 
      filters: {
        //   upper  自定义的过滤器名字 
        //    upper 被定义为接收单个参数的过滤器函数,表达式  msg  的值将作为参数传入到函数中
        upper: function(val) {
         //  过滤器中一定要有返回值 这样外界使用过滤器的时候才能拿到结果
          return val.charAt(0).toUpperCase() + val.slice(1);
        }
      }
    });
  </script>

3.3.1过滤器中传递参数

    <div id="box">
        <!--
			filterA 被定义为接收三个参数的过滤器函数。
  			其中 message 的值作为第一个参数,
			普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
		-->
        {{ message | filterA('arg1', 'arg2') }}
    </div>
    <script>
        // 在过滤器中 第一个参数 对应的是  管道符前面的数据   n  此时对应 message
        // 第2个参数  a 对应 实参  arg1 字符串
        // 第3个参数  b 对应 实参  arg2 字符串
        Vue.filter('filterA',function(n,a,b){
            if(n<10){
                return n+a;
            }else{
                return n+b;
            }
        }); 
        new Vue({
            el:"#box",
            data:{
                message: "哈哈哈"
            }
        })
    </script>

4. 生命周期

  • 事物从出生到死亡的过程
  • Vue实例从创建 到销毁的过程 ,这些过程中会伴随着一些函数的自调用。我们称这些函数为钩子函数

常用的钩子函数

beforeCreate在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了
created在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是页面还没有渲染出来(发送后台请求)
beforeMount在挂载开始之前被调用 此时页面上还看不到真实数据 只是一个模板页面而已
mountedel被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上 在这个钩子函数里面我们可以使用一些第三方的插件(渲染页面)
beforeUpdate数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的
updated由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的
beforeDestroy实例销毁之前调用
destroyed实例销毁后调用

面试题:页面出现白屏怎么办?

数据没拿到就会出现白屏,

解决方案:给加载动画,拿到数据让动画消失

5. 动态数组响应式数据

面试题:data数据不更新是什么原因?

出现的场景,修改数组,对象直接添加属性和值不会造成页面更新

this.$set(数组/对象名,索引/key,要更新的数据)

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

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

<body>
  <div id="app">
    <ul>
      <li v-for="(item, index) in list" :key="item.id">
        {{item.name}}
      </li>
      <li v-for="(v, k, i) in obj" :key="k">{{v}}</li>
    </ul>
    <!-- <button @click="handle1">更改数组</button> -->
    <button @click="handle2">更改对象</button>
    <!-- <input type="text" v-model="msg"> -->
  </div>
</body>
<script type="text/javascript" src="js/vue.js"></script>
<script>
  //面试题:修改data数据,页面不更新是什么原因?
  //出现的场景:修改数组,对象直接添加属性和值也不会造成页面更新  this.$set(数组/对象名,索引/key,要更新的数据)
  var vm = new Vue({
    el: '#app',
    data: {
      msg: 'ls',
      list: [
        { id: 1, name: '李斯' },
        { id: 2, name: '吕不韦' },
        { id: 3, name: '嬴政' }
      ],
      obj: {
          uname: 'zhangsan',
          age: 13,
          gender: 'female',
      }
    },
    methods: {
      handle1() {
        // this.list.push({ id: 4, name: '源氏' });
        //直接修改数组长度, 页面不能更新
        // this.list.length++;
        // this.list[this.list.length - 1] = {id: 4, name: '源氏'}
        //直接更改数组内容, 页面不能更新
        // this.list[0] = {id: 1, name: '源氏'}
        //调用数组既有一些方法,可以让页面动态更新
        //解决方案 this.$set(数组名,索引,更新的值),给数据手动进行双向绑定
        this.$set(this.list, 3, { id: 4, name: '源氏' })
      },
      handle2() {
        // this.obj.uname = '123';
        //给对象直接添加属性和值,页面不更新
        // this.obj.money = 10000;
        //解决方案 this.$set(对象名,key值,更新的值),给数据手动进行双向绑定
        this.$set(this.obj, 'money', 10000) 
      }
    }
  });
</script>
</html>

6. 组件

  • 组件 (Component) 是 Vue.js 最强大的功能之一
  • 组件可以扩展 HTML 元素,封装可重用的代

6.1 组件注册

6.2.1 全局注册

  • Vue.component(‘组件名称’, { }) 第1个参数是标签名称,第2个参数是一个选项对象
  • 全局组件注册后,任何vue实例都可以用

组件基础用法

<div id="example">
  <!-- 2、 组件使用 组件名称 是以HTML标签的形式使用  -->  
  <my-component></my-component>
</div>
<script>
    //   注册组件 
    // 1、 my-component 就是组件中自定义的标签名
	Vue.component('my-component', {
      template: '<div>A custom component!</div>'
    })
    // 创建根实例
    new Vue({
      el: '#example'
    })
</script>

组件注意事项

  • 组件参数的data值必须是函数同时这个函数要求返回一个对象
  • 组件模板必须是单个根元素
  • 组件模板的内容可以是模板字符串
  <div id="app">
     <!-- 
		4、  组件可以重复使用多次 
	      因为data中返回的是一个对象所以每个组件中的数据是私有的
		  即每个实例可以维护一份被返回对象的独立的拷贝   
	--> 
    <button-counter></button-counter>
    <button-counter></button-counter>
    <button-counter></button-counter>
      <!-- 8、必须使用短横线的方式使用组件 -->
     <hello-world></hello-world>
  </div>
<script type="text/javascript">
	//5  如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,
    // 7、但是在普通的标签模板中,必须使用短横线的方式使用组件
     Vue.component('HelloWorld', {
      data: function(){
        return {
          msg: 'HelloWorld'
        }
      },
      template: '<div>{{msg}}</div>'
    });
    Vue.component('button-counter', {
      // 1、组件参数的data值必须是函数 
      // 同时这个函数要求返回一个对象  
      data: function(){
        return {
          count: 0
        }
      },
      //  2、组件模板必须是单个根元素
      //  3、组件模板的内容可以是模板字符串  
      template: `
        <div>
          <button @click="handle">点击了{{count}}次</button>
          <button>测试123</button>
			#  6 在字符串模板中可以使用驼峰的方式使用组件	
		   <HelloWorld></HelloWorld>
        </div>
      `,
      methods: {
        handle: function(){
          this.count += 2;
        }
      }
    })
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>

6.2.2 局部注册

  • 只能在当前注册它的vue实例中使用
  <div id="app">
      <my-component></my-component>
  </div>
<script>
    // 定义组件的模板
    var Child = {
      template: '<div>A custom component!</div>'
    }
    new Vue({
      //局部注册组件  
      components: {
        // <my-component> 将只在父模板可用  一定要在实例上注册了才能在html文件中使用
        'my-component': Child
      }
    })
 </script>

6.3 Vue组件之间传值

6.3.1 父组件向子组件传值

  • 父组件发送的形式是以属性的形式绑定值到子组件身上。
  • 然后子组件用属性props接收
  • 在props中使用驼峰形式,模板中需要使用短横线的形式字符串形式的模板中没有这个限制
  <div id="app">
    <div>{{pmsg}}</div>
     <!--1、menu-item  在 APP中嵌套着 故 menu-item   为  子组件      -->
     <!-- 给子组件传入一个静态的值 -->
    <menu-item title='来自父组件的值'></menu-item>
    <!-- 2、 需要动态的数据的时候 需要属性绑定的形式设置 此时 ptitle  来自父组件data 中的数据 . 
		  传的值可以是数字、对象、数组等等
	-->
    <menu-item :title='ptitle' content='hello'></menu-item>
  </div>

  <script type="text/javascript">
    Vue.component('menu-item', {
      // 3、 子组件用属性props接收父组件传递过来的数据  
      props: ['title', 'content'],
      data: function() {
        return {
          msg: '子组件本身的数据'
        }
      },
      template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        ptitle: '动态绑定属性'
      }
    });
  </script>

6.3.2 子组件向父组件传值

  • 子组件用$emit()触发事件
  • $emit() 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据
  • 父组件用v-on 监听子组件的事件
 <div id="app">
    <div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
     <!-- 2 父组件用v-on 监听子组件的事件
		这里 enlarge-text  是从 $emit 中的第一个参数对应   handle 为对应的事件处理函数	
	-->	
    <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      子组件向父组件传值-携带参数
    */
    
    Vue.component('menu-item', {
      props: ['parr'],
      template: `
        <div>
          <ul>
            <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
          </ul>
			###  1、子组件用$emit()触发事件
			### 第一个参数为 自定义的事件名称   第二个参数为需要传递的数据  
          <button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
          <button @click='$emit("enlarge-text", -5)'>减小父组件中字体大小</button>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        parr: ['apple','orange','banana'],
        fontSize: 10
      },
      methods: {
        handle: function(val){
          // 扩大字体大小
          this.fontSize += val;
        }
      }
    });
  </script>

6.3.3 兄弟之间的传递

  • 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
    • 提供事件中心 var hub = new Vue()
  • 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
  • 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
  • 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
 <div id="app">
    <div>父组件</div>
    <div>
      <button @click='handle'>销毁事件</button>
    </div>
    <test-tom></test-tom>
    <test-jerry></test-jerry>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      兄弟组件之间数据传递
    */
    //1、 提供事件中心
    var hub = new Vue();

    Vue.component('test-tom', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>TOM:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
          hub.$emit('jerry-event', 2);
        }
      },
      mounted: function() {
       // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on(方法名
        hub.$on('tom-event', (val) => {
          this.num += val;
        });
      }
    });
    Vue.component('test-jerry', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>JERRY:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
          hub.$emit('tom-event', 1);
        }
      },
      mounted: function() {
        // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on()方法名
        hub.$on('jerry-event', (val) => {
          this.num += val;
        });
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      },
      methods: {
        handle: function(){
          //4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据  
          hub.$off('tom-event');
          hub.$off('jerry-event');
        }
      }
    });
  </script>

6.4组件插槽

  • 组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力

6.4.1 匿名插槽

  <div id="app">
    <!-- 这里的所有组件标签中嵌套的内容会替换掉slot  如果不传值 则使用 slot 中的默认值  -->  
    <alert-box>有bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
    <alert-box></alert-box>
  </div>
  <script type="text/javascript">
    /*
      组件插槽:父组件向子组件传递内容
    */
    Vue.component('alert-box', {
      template: `
        <div>
          <strong>ERROR:</strong>
		# 当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。
		# 插槽内可以包含任何模板代码,包括 HTML
          <slot>默认内容</slot>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>
</body>
</html>

6.4.2 具名插槽

  • 具有名字的插槽
  • 使用 中的 “name” 属性绑定元素
  <div id="app">
    <base-layout>
       <!-- 2、 通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上
				如果没有匹配到 则放到匿名的插槽中   --> 
      <p slot='header'>标题信息</p>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <p slot='footer'>底部信息信息</p>
    </base-layout>

    <base-layout>
      <!-- 注意点:template临时的包裹标签最终不会渲染到页面上     -->  
      <template slot='header'>
        <p>标题信息1</p>
        <p>标题信息2</p>
      </template>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <template slot='footer'>
        <p>底部信息信息1</p>
        <p>底部信息信息2</p>
      </template>
    </base-layout>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      具名插槽
    */
    Vue.component('base-layout', {
      template: `
        <div>
          <header>
			###	1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字
            <slot name='header'></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
			###  注意点: 
			###  具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序
            <slot name='footer'></slot>
          </footer>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>

6.4.3 作用域插槽

  • 父组件对子组件加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致
<body>
  <div id="app">
    <!-- 
		1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件,
		但样式希望不一样 这个时候我们需要使用作用域插槽 	
	-->  
    <fruit-list :list='list'>
       <!-- 2、 父组件中使用了<template>元素,而且包含scope="slotProps",
			slotProps在这里只是临时变量   
		---> 	
      <template slot-scope='slotProps'>
        <strong v-if='slotProps.info.id==3' class="current">
            {{slotProps.info.name}}		         
         </strong>
        <span v-else>{{slotProps.info.name}}</span>
      </template>
    </fruit-list>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      作用域插槽
    */
    Vue.component('fruit-list', {
      props: ['list'],
      template: `
        <div>
          <li :key='item.id' v-for='item in list'>
			###  3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg="xxx",
			###   插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。
					如果父组件为这个插槽提供了内容,则默认的内容会被替换掉
            <slot :info='item'>{{item.name}}</slot>
          </li>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        list: [{
          id: 1,
          name: 'apple'
        },{
          id: 2,
          name: 'orange'
        },{
          id: 3,
          name: 'banana'
        }]
      }
    });
  </script>
</body>

7. 接口调用方式

  • 原生ajax
  • 基于jQuery的ajax
  • fetch
  • axios

异步

  • JavaScript的执行环境是「单线程」
  • 所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会「阻塞」其他任务。这个任务可称为主线程
  • 异步模式可以一起执行多个任务
  • JS中常见的异步调用
    • 定时任何
    • ajax
    • 事件函数

7.1 promise

  • 主要解决异步深层嵌套的问题
  • promise 提供了简洁的API 使得异步操作更加容易
  <script type="text/javascript">
    /*
     1. Promise基本使用
           我们使用new来构建一个Promise  Promise的构造函数接收一个参数,是函数,并且传入两个参数:		   resolve,reject, 分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数
    */
    var p = new Promise(function(resolve, reject){
      //2. 这里用于实现异步任务  setTimeout
      setTimeout(function(){
        var flag = false;
        if(flag) {
          //3. 正常情况
          resolve('hello');
        }else{
          //4. 异常情况
          reject('出错了');
        }
      }, 100);
    });
    //  5 Promise实例生成以后,可以用then方法指定resolved状态和reject状态的回调函数 
    //  在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了  
    p.then(function(data){
      console.log(data)
    },function(info){
      console.log(info)
    });
  </script>

1. 基于Promise发送Ajax请求

 
  <script type="text/javascript">
    /*
      基于Promise发送Ajax请求
    */
    function queryData(url) {
     #   1.1 创建一个Promise实例
      var p = new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            # 1.2 处理正常的情况
            resolve(xhr.responseText);
          }else{
            # 1.3 处理异常情况
            reject('服务器错误');
          }
        };
        xhr.open('get', url);
        xhr.send(null);
      });
      return p;
    }
	# 注意:  这里需要开启一个服务 
    # 在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了
    queryData('http://localhost:3000/data')
      .then(function(data){
        console.log(data)
        #  1.4 想要继续链式编程下去 需要 return  
        return queryData('http://localhost:3000/data1');
      })
      .then(function(data){
        console.log(data);
        return queryData('http://localhost:3000/data2');
      })
      .then(function(data){
        console.log(data)
      });
  </script>

2. Promise 基本API

2.1 实例方法

***.then()***:得到异步任务正确的结果

***.catch()***:获取异常信息

***.finally()***:成功与否都会执行(不是正式标准)

  
  <script type="text/javascript">
    /*
      Promise常用API-实例方法
    */
    // console.dir(Promise);
    function foo() {
      return new Promise(function(resolve, reject){
        setTimeout(function(){
          // resolve(123);
          reject('error');
        }, 100);
      })
    }
    // foo()
    //   .then(function(data){
    //     console.log(data)
    //   })
    //   .catch(function(data){
    //     console.log(data)
    //   })
    //   .finally(function(){
    //     console.log('finished')
    //   });

    // --------------------------
    // 两种写法是等效的
    foo()
      .then(function(data){
        # 得到异步任务正确的结果
        console.log(data)
      },function(data){
        # 获取异常信息
        console.log(data)
      })
      # 成功与否都会执行(不是正式标准) 
      .finally(function(){
        console.log('finished')
      });
  </script>
2.2 静态方法

***.all()***:

  • Promise.all方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定

面试题:如何保证我发送的多次后台请求,返回的值也是按照发送的额顺序返回的?

Promise.all

***.race()***:

  • Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilledrejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数
  <script type="text/javascript">
    /*
      Promise常用API-对象方法
    */
    // console.dir(Promise)
    function queryData(url) {
      return new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            // 处理正常的情况
            resolve(xhr.responseText);
          }else{
            // 处理异常情况
            reject('服务器错误');
          }
        };
        xhr.open('get', url);
        xhr.send(null);
      });
    }
    var p1 = queryData('http://localhost:3000/a1');
    var p2 = queryData('http://localhost:3000/a2');
    var p3 = queryData('http://localhost:3000/a3');
     Promise.all([p1,p2,p3]).then(function(result){
         //result保存的是根据你发送后台请求顺序的值
       //all 中的参数  [p1,p2,p3]   和 返回的结果一 一对应["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
       console.log(result) //["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
     })
    Promise.race([p1,p2,p3]).then(function(result){
         //race方法返回最先拿到的数据保存在result里
      // 由于p1执行较快,Promise的then()将获得结果'P1'。p2,p3仍在继续执行,但执行结果将被丢弃。
      console.log(result) // "HELLO TOM"
    })
  </script>

7.2 fetch

  • Fetch API是新的ajax解决方案 Fetch会返回Promise
  • fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
  • fetch(url, options).then()
  <script type="text/javascript">
    /*
      Fetch API 基本用法
      	fetch(url).then()
     	第一个参数请求的路径   Fetch会返回Promise   所以我们可以使用then 拿到请求成功的结果 
    */
    fetch('http://localhost:3000/fdata').then(function(data){
      // text()方法属于fetchAPI的一部分,它返回一个Promise实例对象,用于获取后台返回的数据
      return data.text();
    }).then(function(data){
      //   在这个then里面我们能拿到最终的数据  
      console.log(data);
    })
  </script>

fetch API中的HTTP请求

  • fetch(url, options).then()
  • HTTP协议,它给我们提供了很多的方法,如POST,GET,DELETE,UPDATE,PATCH和PUT
    • 默认的是 GET 请求
    • 需要在 options 对象中 指定对应的 method method:请求使用的方法
    • post 和 普通 请求的时候 需要在options 中 设置 请求头 headers 和 body
   <script type="text/javascript">
        /*
              Fetch API 调用接口传递参数
        */
       #1.1 GET参数传递 - 传统URL  通过url  ? 的形式传参 
        fetch('http://localhost:3000/books?id=123', {
            	# get 请求可以省略不写 默认的是GET 
                method: 'get'
            })
            .then(function(data) {
            	# 它返回一个Promise实例对象,用于获取后台返回的数据
                return data.text();
            }).then(function(data) {
            	# 在这个then里面我们能拿到最终的数据  
                console.log(data)
            });

      #1.2  GET参数传递  restful形式的URL  通过/ 的形式传递参数  即  id = 456 和id后台的配置有关   
        fetch('http://localhost:3000/books/456', {
            	# get 请求可以省略不写 默认的是GET 
                method: 'get'
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });

       #2.1  DELETE请求方式参数传递      删除id  是  id=789
        fetch('http://localhost:3000/books/789', {
                method: 'delete'
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });

       #3 POST请求传参
        fetch('http://localhost:3000/books', {
                method: 'post',
            	# 3.1  传递数据 
                body: 'uname=lisi&pwd=123',
            	#  3.2  设置请求头 
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });

       # POST请求传参
        fetch('http://localhost:3000/books', {
                method: 'post',
                body: JSON.stringify({
                    uname: '张三',
                    pwd: '456'
                }),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });

        # PUT请求传参     修改id 是 123fetch('http://localhost:3000/books/123', {
                method: 'put',
                body: JSON.stringify({
                    uname: '张三',
                    pwd: '789'
                }),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then(function(data) {
                return data.text();
            }).then(function(data) {
                console.log(data)
            });
    </script>

fetch API中响应格式

  • 用fetch来获取数据,如果响应正常返回,我们首先看到的是一个response对象,其中包括返回的一堆原始字节,这些字节需要在收到后,需要我们通过调用方法将其转换为相应格式的数据,比如JSONBLOB或者TEXT等等
    /*
      Fetch响应结果的数据格式
    */
    fetch('http://localhost:3000/json').then(function(data){
      // return data.json();   //  将获取到的数据使用 json 转换对象
      return data.text(); //  //  将获取到的数据 转换成字符串 
    }).then(function(data){
      // console.log(data.uname)
      // console.log(typeof data)
      var obj = JSON.parse(data);
      console.log(obj.uname,obj.age,obj.gender)
    })

7.3 axios

  • 基于promise用于浏览器和node.js的http客户端
  • 支持浏览器和node.js
  • 支持promise
  • 能拦截请求和响应
  • 自动转换JSON数据
  • 能转换请求和响应数据

axios基础用法

  • get和 delete请求传递参数
    • 通过传统的url 以 ? 的形式传递参数
    • restful 形式传递参数
    • 通过params 形式传递参数
  • post 和 put 请求传递参数
    • 通过选项传递参数
    • 通过 URLSearchParams 传递参数
    # 1. 发送get 请求 
	axios.get('http://localhost:3000/adata').then(function(ret){ 
      #  拿到 ret 是一个对象      所有的对象都存在 ret 的data 属性里面
      // 注意data属性是固定的用法,用于获取后台的实际数据
      // console.log(ret.data)
      console.log(ret)
    })
	# 2.  get 请求传递参数
    # 2.1  通过传统的url  以 ? 的形式传递参数
	axios.get('http://localhost:3000/axios?id=123').then(function(ret){
      console.log(ret.data)
    })
    # 2.2  restful 形式传递参数 
    axios.get('http://localhost:3000/axios/123').then(function(ret){
      console.log(ret.data)
    })
	# 2.3  通过params  形式传递参数 
    axios.get('http://localhost:3000/axios', {
      params: {
        id: 789
      }
    }).then(function(ret){
      console.log(ret.data)
    })
	#3 axios delete 请求传参     传参的形式和 get 请求一样
    axios.delete('http://localhost:3000/axios', {
      params: {
        id: 111
      }
    }).then(function(ret){
      console.log(ret.data)
    })

	# 4  axios 的 post 请求
    # 4.1  通过选项传递参数
    axios.post('http://localhost:3000/axios', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })
	# 4.2  通过 URLSearchParams  传递参数 
    var params = new URLSearchParams();
    params.append('uname', 'zhangsan');
    params.append('pwd', '111');
    axios.post('http://localhost:3000/axios', params).then(function(ret){
      console.log(ret.data)
    })

 	#5  axios put 请求传参   和 post 请求一样 
    axios.put('http://localhost:3000/axios/123', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })

axios 全局配置

#  配置公共的请求头 
axios.defaults.baseURL = 'https://api.example.com';
#  配置 超时时间
axios.defaults.timeout = 2500;
#  配置公共的请求头
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
# 配置公共的 post 的 Content-Type
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

axios 拦截器

  • 请求拦截器
    • 请求拦截器的作用是在请求发送前进行一些操作
      • 例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易
  • 响应拦截器
    • 响应拦截器的作用是在接收到响应后进行一些操作
      • 例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页
	# 1. 请求拦截器 
	axios.interceptors.request.use(function(config) {
      console.log(config.url)
      # 1.1  任何请求都会经过这一步   在发送请求之前做些什么   
      config.headers.mytoken = 'nihao';
      # 1.2  这里一定要return   否则配置不成功  
      return config;
    }, function(err){
       #1.3 对请求错误做点什么    
      console.log(err)
    })
	#2. 响应拦截器 
    axios.interceptors.response.use(function(res) {
      #2.1  在接收响应做些什么  
      var data = res.data;
      return data;
    }, function(err){
      #2.2 对响应错误做点什么  
      console.log(err)
    })

7.4 async 和 await

  • async作为一个关键字放到函数前面
    • 任何一个async函数都会隐式返回一个promise
  • await关键字只能在使用async定义的函数中使用
    • ​ await后面可以直接跟一个 Promise实例对象
    • ​ await函数不能单独使用
  • async/await 让异步代码看起来、表现起来更像同步代码
 	# 1.  async 基础用法
    # 1.1 async作为一个关键字放到函数前面
	async function queryData() {
      # 1.2 await关键字只能在使用async定义的函数中使用      await后面可以直接跟一个 Promise实例对象
      var ret = await new Promise(function(resolve, reject){
        setTimeout(function(){
          resolve('nihao')
        },1000);
      })
      // console.log(ret.data)
      return ret;
    }
	# 1.3 任何一个async函数都会隐式返回一个promise   我们可以使用then 进行链式编程
    queryData().then(function(data){
      console.log(data)
    })

	#2.  async    函数处理多个异步函数
    axios.defaults.baseURL = 'http://localhost:3000';

    async function queryData() {
      # 2.1  添加await之后 当前的await 返回结果之后才会执行后面的代码   
      
      var info = await axios.get('async1');
      #2.2  让异步代码看起来、表现起来更像同步代码
      var ret = await axios.get('async2?info=' + info.data);
      return ret.data;
    }

    queryData().then(function(data){
      console.log(data)
    })

8. 路由

1.路由的概念

路由的本质就是一种对应关系,比如说我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源。
那么url地址和真实的资源之间就有一种对应的关系,就是路由。

路由分为前端路由和后端路由
1).后端路由是由服务器端进行实现,并完成资源的分发
2).前端路由是依靠hash值(锚链接)的变化进行实现

后端路由性能相对前端路由来说较低,所以,我们接下来主要学习的是前端路由
前端路由的基本概念:根据不同的事件来显示不同的页面内容,即事件与事件处理函数之间的对应关系
前端路由主要做的事情就是监听事件并分发执行事件处理函数

2.前端路由的初体验

前端路由是基于hash值的变化进行实现的(比如点击页面中的菜单或者按钮改变URL的hash值,根据hash值的变化来控制组件的切换)
核心实现依靠一个事件,即监听hash值变化的事件

window.onhashchange = function(){
    //location.hash可以获取到最新的hash值
    location.hash
}

前端路由实现tab栏切换:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
        <!-- 导入 vue 文件 -->
        <script src="./lib/vue_2.5.22.js"></script>
    </head>
    <body>
        <!-- 被 vue 实例控制的 div 区域 -->
        <div id="app">
        <!-- 切换组件的超链接 -->
        <a href="#/zhuye">主页</a> 
        <a href="#/keji">科技</a> 
        <a href="#/caijing">财经</a>
        <a href="#/yule">娱乐</a>

        <!-- 根据 :is 属性指定的组件名称,把对应的组件渲染到 component 标签所在的位置 -->
        <!-- 可以把 component 标签当做是【组件的占位符】 -->
        <component :is="comName"></component>
        </div>

        <script>
        // #region 定义需要被切换的 4 个组件
        // 主页组件
        const zhuye = {
            template: '<h1>主页信息</h1>'
        }

        // 科技组件
        const keji = {
            template: '<h1>科技信息</h1>'
        }

        // 财经组件
        const caijing = {
            template: '<h1>财经信息</h1>'
        }

        // 娱乐组件
        const yule = {
            template: '<h1>娱乐信息</h1>'
        }
        // #endregion

        // #region vue 实例对象
        const vm = new Vue({
            el: '#app',
            data: {
            comName: 'zhuye'
            },
            // 注册私有组件
            components: {
            zhuye,
            keji,
            caijing,
            yule
            }
        })
        // #endregion

        // 监听 window 的 onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称
        window.onhashchange = function() {
            // 通过 location.hash 获取到最新的 hash 值
            console.log(location.hash);
            switch(location.hash.slice(1)){
            case '/zhuye':
                vm.comName = 'zhuye'
            break
            case '/keji':
                vm.comName = 'keji'
            break
            case '/caijing':
                vm.comName = 'caijing'
            break
            case '/yule':
                vm.comName = 'yule'
            break
            }
        }
        </script>
    </body>
    </html>

案例效果图:

在这里插入图片描述

点击每个超链接之后,会进行相应的内容切换,如下:

在这里插入图片描述

3.Vue Router简介

它是一个Vue.js官方提供的路由管理器。是一个功能更加强大的前端路由器,推荐使用。
Vue Router和Vue.js非常契合,可以一起方便的实现SPA(single page web application,单页应用程序)应用程序的开发。
Vue Router依赖于Vue,所以需要先引入Vue,再引入Vue Router

Vue Router的特性:
支持H5历史模式或者hash模式
支持嵌套路由
支持路由参数
支持编程式路由
支持命名路由
支持路由导航守卫
支持路由过渡动画特效
支持路由懒加载
支持路由滚动行为

4.Vue Router的使用步骤(★★★)

A.导入js文件

B.添加路由链接:是路由中提供的标签,默认会被渲染为a标签,to属性默认被渲染为href属性,
to属性的值会被渲染为#开头的hash地址
User
Login
C.添加路由填充位(路由占位符)

D.定义路由组件
var User = { template:“

This is User
” }
var Login = { template:“
This is Login
” }
E.配置路由规则并创建路由实例
var myRouter = new VueRouter({

//routes是路由规则数组
routes:[
    //每一个路由规则都是一个对象,对象中至少包含path和component两个属性
    //path表示  路由匹配的hash地址,component表示路由规则对应要展示的组件对象
    {path:"/user",component:User},
    {path:"/login",component:Login}
]
})

F.将路由挂载到Vue实例中

new Vue({
el:"#app",
//通过router属性挂载路由对象
router:myRouter
})

小结:
Vue Router的使用步骤还是比较清晰的,按照步骤一步一步就能完成路由操作
A.导入js文件
B.添加路由链接
C.添加路由占位符(最后路由展示的组件就会在占位符的位置显示)
D.定义路由组件
E.配置路由规则并创建路由实例
F.将路由挂载到Vue实例中

补充:
路由重定向:可以通过路由重定向为页面设置默认展示的组件
在路由规则中添加一条路由规则即可,如下:
var myRouter = new VueRouter({

//routes是路由规则数组
routes: [
    //path设置为/表示页面最初始的地址 / ,redirect表示要被重定向的新地址,设置为一个路由即可
    { path:"/",redirect:"/user"},
    { path: "/user", component: User },
    { path: "/login", component: Login }
]
})

5.嵌套路由,动态路由的实现方式

A.嵌套路由的概念(★★★)

当我们进行路由的时候显示的组件中还有新的子级路由链接以及内容。

嵌套路由最关键的代码在于理解子级路由的概念:
比如我们有一个/login的路由
那么/login下面还可以添加子级路由,如:
/login/account
/login/phone

参考代码如下:

var User = { template: "<div>This is User</div>" }
//Login组件中的模板代码里面包含了子级路由链接以及子级路由的占位符
    var Login = { template: `<div>
        <h1>This is Login</h1>
        <hr>
        <router-link to="/login/account">账号密码登录</router-link>
        <router-link to="/login/phone">扫码登录</router-link>
        <!-- 子路由组件将会在router-view中显示 -->
        <router-view></router-view>
        </div>` }

    //定义两个子级路由组件
    var account = { template:"<div>账号:<input><br>密码:<input></div>"};
    var phone = { template:"<h1>扫我二维码</h1>"};
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            { path:"/",redirect:"/user"},
            { path: "/user", component: User },
            { 
                path: "/login", 
                component: Login,
                //通过children属性为/login添加子路由规则
                children:[
                    { path: "/login/account", component: account },
                    { path: "/login/phone", component: phone },
                ]
            }
        ]
    })

    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router:myRouter
    });

页面效果大致如下:

在这里插入图片描述

B.动态路由匹配

<body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
      <router-link to="/user/3">User3</router-link>
      <router-link to="/register">Register</router-link>

      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>

    <script>
      const User = {
        props: ['id'],
        template: '<h1>User 组件 -- 用户id为: {{id}}</h1>'
      }

      const Register = {
        template: '<h1>Register 组件</h1>'
      }

      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user'},
          { path: '/user/:id', component: User, props: true },
          { path: '/register', component: Register }
        ]
      })

      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
<body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
      <router-link to="/user/3">User3</router-link>
      <router-link to="/register">Register</router-link>

      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>

    <script>
      const User = {
        props: ['id', 'uname', 'age'],
        template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
      }

      const Register = {
        template: '<h1>Register 组件</h1>'
      }

      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user'},
          { path: '/user/:id', component: User, props: { uname: 'lisi', age: 20, id: route.params.id } },
          { path: '/register', component: Register }
        ]
      })

      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
<body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
      <router-link to="/user/3">User3</router-link>
      <router-link to="/register">Register</router-link>

      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>

    <script>
      const User = {
        props: ['id', 'uname', 'age'],
        template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
      }

      const Register = {
        template: '<h1>Register 组件</h1>'
      }

      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user' },
          {
            path: '/user/:id',
            component: User,
            props: route => ({ uname: 'zs', age: 20, id: route.params.id })
          },
          { path: '/register', component: Register }
        ]
      })

      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>

6. 命名路由以及编程式导航

A.命名路由:给路由取别名

案例:

var myRouter = new VueRouter({
	//routes是路由规则数组
    routes: [
        //通过name属性为路由添加一个别名
        { path: "/user/:id", component: User, name:"user"},

    ]
})
//添加了别名之后,可以使用别名进行跳转

<router-link to="/user">User</router-link>

<router-link :to="{ name:'user' , params: {id:123} }">User</router-link>

//还可以编程式导航

myRouter.push( { name:'user' , params: {id:123} } )

B.编程式导航(★★★)

页面导航的两种方式:
A.声明式导航:通过点击链接的方式实现的导航
B.编程式导航:调用js的api方法实现导航

Vue-Router中常见的导航方式:
this.$router.push("hash地址");
this.$router.push("/login");
this.$router.push({ name:'user' , params: {id:123} });
this.$router.push({ path:"/login" });
this.$router.push({ path:"/login",query:{username:"jack"} });//(/login?username=jack)

this.$router.go( n );//n为数字,参考history.go
this.$router.go( -1 );

7. route和router的区别

route是“路由信息对象”,包括path,params,hash,query,fullPath, matched,name等路由信息参数。而router是“路由实例”对象包括了路由的跳转 方法,钩子函数等。

8. 路由导航守卫

  1. 全局守卫:beforeEach, beforeResolve, afterEach【没有next】
  2. 路由独享守卫:beforeEnter
  3. 组件内守卫:beforeRouteEnter【唯一next有回调】, Update, Leave

应用:

  1. 全局守卫:beforeEach(用户登录以及权限判定)
  2. 组件内守卫:beforeRouteEnter(根据用户从何而来,修改当前组件标题)

9. vuex

1. Vuex概述

Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据共享

使用Vuex管理数据的好处:
A.能够在vuex中集中管理共享的数据,便于开发和后期进行维护
B.能够高效的实现组件之间的数据共享,提高开发效率
C.存储在vuex中的数据是响应式的,当数据发生改变时,页面中的数据也会同步更新

2. 使用Vuex完成计数器案例

打开刚刚创建的vuex项目,找到src目录中的App.vue组件,将代码重新编写如下:

<template>
  <div>
    <my-addition></my-addition>

    <p>----------------------------------------</p>

    <my-subtraction></my-subtraction>
  </div>
</template>

<script>
import Addition from './components/Addition.vue'
import Subtraction from './components/Subtraction.vue'

export default {
  data() {
    return {}
  },
  components: {
    'my-subtraction': Subtraction,
    'my-addition': Addition
  }
}
</script>

<style>
</style>

在components文件夹中创建Addition.vue组件,代码如下:

<template>
    <div>
        <h3>当前最新的count值为:</h3>
        <button>+1</button>
    </div>
</template>

<script>
export default {
  data() {
    return {}
  }
}
</script>

<style>
</style>

在components文件夹中创建Subtraction.vue组件,代码如下:

<template>
    <div>
        <h3>当前最新的count值为:</h3>
        <button>-1</button>
    </div>
</template>

<script>
export default {
  data() {
    return {}
  }
}
</script>

<style>
</style>

最后在项目根目录(与src平级)中创建 .prettierrc 文件,编写代码如下:

{
    "semi":false,
    "singleQuote":true
}

3. Vuex中的核心特性

A. state

State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储
例如,打开项目中的store.js文件,在State对象中可以添加我们要共享的数据,如:count:0

在组件中访问State的方式:
1).this.$store.state.全局数据名称  如:this.$store.state.count
2).先按需导入mapState函数: import { mapState } from 'vuex'
然后数据映射为计算属性: computed:{ ...mapState(['全局数据名称']) }

B. mutations

Mutation用于修改变更$store中的数据
使用方式:
打开store.js文件,在mutations中添加代码如下

mutations: {
    add(state,step){
      //第一个形参永远都是state也就是$state对象
      //第二个形参是调用add时传递的参数
      state.count+=step;
    }
  }

然后在Addition.vue中给按钮添加事件代码如下:

<button @click="Add">+1</button>

methods:{
  Add(){
    //使用commit函数调用mutations中的对应函数,
    //第一个参数就是我们要调用的mutations中的函数名
    //第二个参数就是传递给add函数的参数
    this.$store.commit('add',10)
  }
}

使用mutations的第二种方式:

import { mapMutations } from 'vuex'

methods:{
  ...mapMutations(['add'])
}

如下:

import { mapState,mapMutations } from 'vuex'

export default {
  data() {
    return {}
  },
  methods:{
      //获得mapMutations映射的sub函数
      ...mapMutations(['sub']),
      //当点击按钮时触发Sub函数
      Sub(){
          //调用sub函数完成对数据的操作
          this.sub(10);
      }
  },
  computed:{
      ...mapState(['count'])
      
  }
}

C. actions

在mutations中不能编写异步的代码,会导致vue调试器的显示出错。
在vuex中我们可以使用Action来执行异步操作。
操作步骤如下:
打开store.js文件,修改Action,如下:

actions: {
  addAsync(context,step){
    setTimeout(()=>{
      context.commit('add',step);
    },2000)
  }
}

然后在Addition.vue中给按钮添加事件代码如下:

<button @click="AddAsync">...+1</button>

methods:{
  AddAsync(){
    this.$store.dispatch('addAsync',5)
  }
}

第二种方式:

import { mapActions } from 'vuex'

methods:{
  ...mapActions(['addAsync'])
}

如下:

import { mapState,mapMutations,mapActions } from 'vuex'

export default {
  data() {
    return {}
  },
  methods:{
      //获得mapMutations映射的sub函数
      ...mapMutations(['sub']),
      //当点击按钮时触发Sub函数
      Sub(){
          //调用sub函数完成对数据的操作
          this.sub(10);
      },
      //获得mapActions映射的addAsync函数
      ...mapActions(['addAsync']),
      asyncSub(){
          this.addAsync(5);
      }
  },
  computed:{
      ...mapState(['count'])
      
  }
}

D. getters

Getter用于对Store中的数据进行加工处理形成新的数据
它只会包装Store中保存的数据,并不会修改Store中保存的数据,当Store中的数据发生变化时,Getter生成的内容也会随之变化
打开store.js文件,添加getters,如下:

export default new Vuex.Store({
  .......
  getters:{
    //添加了一个showNum的属性
    showNum : state =>{
      return '最新的count值为:'+state.count;
    }
  }
})

然后打开Addition.vue中,添加插值表达式使用getters

<h3>{{$store.getters.showNum}}</h3>

或者也可以在Addition.vue中,导入mapGetters,并将之映射为计算属性

import { mapGetters } from 'vuex'
computed:{
  ...mapGetters(['showNum'])
 }

E. module

允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

10.extends和mixins混入

一个混入对象可以包含任意组件选项。

同一个生命周期,混入对象会比组件的先执行。
extends会比mixins先执行。执行顺序:extends > mixins > 组件
extends只能暴露一个extends对象,暴露多个extends不会执行

1.创建一个test.js,用export暴露出mixins对象

 export const mixinsTest = {
    methods:{
        hello(){
            console.log("hello");
        }
    },
    created(){
        this.hello()
    }
}

2.在组件中引入这个mixins对象,通过mixins:[xxx],使用mixins对象

<template>
<div>
    home
</div>
</template>
<script>
import {mixinsTest} from '../util/test.js'
export default {
  name: "Home",
  data () {
    return {
    };
  },
  created(){
      console.log("home");
  },
  //mixins的created会先被调用,然后再执行组件的created 
  //打印的顺序
  // hello
  // home
  mixins:[mixinsTest]
}
</script>

3.可以混入多个mixins对象

//暴露两个mixins对象
export const mixinsTest = {
    methods: {
        hello() {
            console.log("hello mixins");
        }
    },
    created() {
        this.hello();
    },
}

export const mixinsTest2 = {
    methods:{
        hello2(){
            console.log("hello2");
        }
    },
    created() {
        this.hello2();
    },
}

//只能使用一个extends对象,多个无效,extends会先于mixins执行
export const extendsTest = {
    methods: {
        hello3() {
            console.log("hello extends");
        }
    },
    beforeCreate(){
        console.log("extends的beforeCreated");
        
    },
    created() {
        this.hello3();
    },
}
<template>
<div>
    home
</div>
</template>
<script>
import {mixinsTest,mixinsTest2,extendsTest} from '../util/test.js'
export default {
  name: "Home",
  data () {
    return {
    };
  },
  created(){
      console.log("1212");
  },
  mixins:[mixinsTest2,mixinsTest], // 先调用那个mixins对象,就先执行哪个
  extends:extendsTest  // 使用extends
  //打印的顺序
  //extends的beforeCreated
  //hello extends
  //hello2
  //hello mixins
  //1212
}
</script>
<style lang="css" scoped>
</style>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值