Vue从入门到精通

1,269

第一部分 Vue知识框架

image.png

0.邂逅Vue

Vue读音类似于view

Vue的渐进式

Vue特点

1.如何安装

  • 方式一 直接CDN引入
  • 方式二 下载和引入
  • NPM 安装

2.HelloWorld

编程范式:声明式编程

 <div id="app">{{message}}</div>
    <script>
        //let 变量   const 常量
        const app=new Vue({
            el:"#app",  //用来挂载要管理的元素
            data:{  //用来管理数据
                message:"你好呀"

            }
        })
    </script>

对比以前的原生JS开发(命令式编程)

  • 1.创建div元素,设置id属性
  • 2.定义一个变量叫message
  • 3.将message变量放到前面的div元素中显示
  • 4.修改message的数据:今天天气真好
  • 5.将修改后的数据再次替换到div中

3.什么是MVVM

Model ViewModel View

image.png

 * View 依然是我们的DOM
 * Model 就是我们抽离出来的obj
 * ViewModel 就是我们创建的view实例

他们之间如何工作

  首先ViewModel通过Data Binding 让obj中的数据实时的在DOM中显示
  其次ViewModel通过DOM Linstener来监听DOM事件,并且通过Methods中的操作,来改变obj的数据
  
  
    

4.目前掌握的选项(options)

  • el

      类型:string|HTMLElement
      作用:决定之后的Vue实例会管理哪一个DOM
      
    
  • data

      类型: Object|Function
      作用:Vue实例对应的数据
    
  • methods

      类型 {[key:string]:function}
    

开发中什么时候称为方法?什么时候称为函数

本质区别:方法都是和实例对象相挂钩的

5.基本语法

(1) 插值语法 Mustache(胡子)

{{}}---->体验vue的响应式

插值相关的指令

  • v-once

    v-once只渲染一次,后期message数据变化以后就不会再改变
         <h2 v-once>
            {{message}}
        </h2>
    
  • v-html

    <h2 v-html="url"></h2>
    data: {
      message:"你好呀",
      url:'<a href="http://www.baidu.com">百度一下</a>'
    }
    
  • v-text和mustache一样

  • v-pre

     <h2 v-pre>{{message}}</h2>原封不动展示不解析
     
    
  • v-cloak v-cloak 作用,一旦vue做了解析会删除 v-cloak 在vue解析之前,div有一个v-cloak 所以我们可以利用v-cloak写一些样式

       <div id="app" v-cloak>
        <!-- -->
        {{message}}
      </div>
      
       [v-cloak]{
          display: none;
        }
    

(2)v-bind

绑定属性,动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。

  • 绑定基本属性:

    v-bind:src
    :href
    
  • 缩写:

<img v-bind:src="imgUrl" alt="">

<img :src="imgUrl" alt="">

  • v-bind动态绑定class属性

两种

  * 对象语法
  * 数组语法

对象语法

我们可以传给 v-bind:class 一个对象,以动态地切换 class:

用法1 :直接通过{}绑定一个类
   
   <div v-bind:class="{ active: isactive}">哈哈哈哈</div>
   
用法2: 也可以通判断传入多个值
   
    <div v-bind:class="{ active: isactive ,line:isLine}">哈哈哈哈</div>

用法3:和普通类同时存在
  
   <div class="title" v-bind:class="{ active: isactive ,line:isLine}">哈哈哈哈</div>

用法4:如果过于复杂,可以放入一个methods或者computed中

  <div v-bind:class="getClasses()">呵呵呵呵呵</div>
  getClasses:function(){
      return { active: this.isactive ,line:this.isLine}
  }
  

数组语法

  <div class="title" :class="['active','line']">哈哈哈哈哈</div>
  • v-bind动态绑定style

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。

两种

  * 对象语法
  * 数组语法    

对象语法

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

数组语法

 多个值用逗号分隔即可
  <div v-bind:style="[baseStyles, overridingStyles]">hhahahhah</div>
  
data: {
  baseStyles:{backgroundColor:'red'},
  overridingStyles:{fontSize:'100px'}
}

(3)计算属性

当我们需要对数据进行转换或者多个数据合并再来显示,直接写在{{}}里面代码看上去可读性不高

image.png

  • 1、计算属性的setter和getter

      这种写法其实是简写
      // fullName:function(){
      //     return this.firstName+ '  '+ this.lastName
      // }
    
     完整实现,一般我们只需要实现get方法,我们不希望别人随便给我们计算属性设置值
     没有set属性,这是只读属性
      
      fullName:{
          set:function(){
              
          },
          get:function(){
              return this.firstName+ '  '+ this.lastName
          }
    
      }
    
    一旦有了set方法就可以设置值了
    
       fullName:{
          set:function(newValue){
              console.log("-----",newValue)
              const name=newValue.split(" ")
              this.firstName=name[0]
              this.lastName=name[1]
          },
          get:function(){
              return this.firstName+ '  '+ this.lastName
          }
    
      }
    
  • 2、计算属性和Methods的对比

        <!-- 第一种方案:直接拼接,语法过于繁琐,我们希望在HTML里面代码越简洁越好 -->
       <h2>{{firstName}} {{lastName}}</h2>
       
       
       <!-- 第二种方案:通过定义methods函数,然后调用函数 -->
       <h2>{{getFullName()}}</h2>
         methods:{
             getFullName(){
                 return this.firstName+" "+this.lastName
             }
       },
       
       
       <!-- 第三种方案:计算属性 -->
       <h2>{{fullName}}</h2>
        computed:{
          //计算属性,我们尽量按照属性名起名字
          fullName:function(){
             return this.firstName+" "+this.lastName
          }
       }
       
    对比:
       
       计算属性内部是做了缓存的,如果每次调用发现firstName和lastName没有发生变化,就直接返回上次结果
       
       方法是调用几次执行几次
      
    

(4)补充ES6知识点

  • let/var

    ES5中的var是没有块级作用域的

    ES6中的let是有块级作用域的

    变量作用域:变量在什么范围内可以使用
    
    没有块级作用域影响:
    
    if的块级没有作用域,引起问题
    for的块级没有作用域
    函数有作用域
    
      var btn=document.getElementsByTagName("button")
       // for(var i=0;i<btn.length;i++){
       //      btn[i].addEventListener("click",function(){
       //          console.log("第"+i+"个按钮被点击")  //这里打印的都是5,因为for没有块级作用域,所有的i引用的都是同一个变量
       //      })
       // }
    
       // 解决办法(利用闭包可以解决问题:函数是一个作用域)
       for(var i=0;i<btn.length;i++){
           (function(i){
               btn[i].addEventListener("click",function(){
                console.log("第"+i+"个按钮被点击") 
            })
           })(i)
       }
    
    
    Brendan EichJS的设计者,当初在涉及var的时候,没有考虑很多,是一种缺陷,于是他添加了关键词:let
    
    let有块级作用域
    

总结:

ES5之前因为我们的iffor都没有块级作用域的概念,所以在很多时候我们必须借助function的作用域来解决应用外面变量的问题
  
  在ES6中,加入了letlet是有块级作用域了
  
    //ES6来解决上面的问题就变得非常简单了
    
     for(let i=0;i<btn.length;i++){
         btn[i].addEventListener("click",function(){
             console.log("第"+i+"个按钮被点击")  //这里打印的都是5,因为for没有块级作用域,所有的i引用的都是同一个变量
         })
    }
     
 
  • const

    常量

    const a=30
    a=40;//错误,不可以修改
    
    const name;//错误,const标识符必须赋值
    

    常量的含义是指向的对象不能修改,但是可以改变对象内部的属性

  • ES6对象字面量增强语法

    什么叫对象字面量?

          const obj={}
          
    

    属性的增强写法

        const name='vina';
        const age=18,
        const obj={
           name:name,
           age:age
        }
        
        增强写法
        
         const obj={
           name,
           age
        }
        
    

    函数的增强写法

         // ES5写法
          const obj={
              run:function(){
              },
              eat:function(){
              }
          }
          //增强写法
          const obj={
              run(){
    
              },
              eat(){
    
              }
          }
    

(5) v-on

  • 作用 :绑定事件监听

  • 缩写 :@

  • 预期 :Function|Inline Statement|Object

  • 参数 : event

       <button v-on:click="counter++">+</button> 
       <button v-on:click="counter--">-</button>
       <button v-on:click="increment()">+</button>
       //语法题(推荐写法)
       <button @click="decrement">+</button>
    
  • v-on 参数问题

    事件调用的方法没有参数

          分析:当事件方法没有参数的时候,括号是可以省略的
          
          <button @click="btn1Click">
               按钮1
          </button>
          <button @click="btn1Click()">
              按钮2
         </button>
         
          btn1Click(){
              console.log("btnClick")
          },
      
    

    在函数事件定义时,写函数时省略了小括号,但是方法本身需要一个参数这时候

         分析:如果方法本身需要一个参数,但是方法调用的时候没有传递参数且没有小括号,那么会打印event事件,如果有小括号会打印undefined
         
         
          <button @click="btn2Click">
              按钮3
          </button>
    
          <button @click="btn2Click()">
              按钮4
          </button>
          
          btn2Click(a){
          console.log(a)
      }
     
    

    方法定义时,我们需要event事件,同事还需要多余的参数,怎么办?

        分析:必须给event前面加$
        
        $event是浏览器产生的event对象
        
       <button @click="btn3Click($event,123)">
          按钮5
      </button>
      
    
  • v-on 修饰符

.stop

 <div @click="divClick">
   <!-- .stop就可以阻止冒泡 -->
       <button @click.stop="btnClick">按钮</button>
 </div>
 

.prevent

 form自身提交了,如果我们想要自己写提交方法,那么就要阻止默认的提交事件, 通过prevent阻止默认事件
   <form action="baidu">
     <input type="text">
     <input type="submit" value="提交" @click.prevent="submitClick">
 </form>

.keyUp 监听所有事件

     <!-- 监听某个键盘(所有)的键帽 -->
   <input type="text" @keyup="keyUp">
    <!-- 监听某个键盘(enter)的键帽 -->
   <input type="text" @keyup.enter="keyUp">
   

.once 只触发一次

  <button @click.once="onceClick">once</button>
  

(6)条件判断

  • v-if
   
  <h2 v-if="score>=90">优秀</h2>
   <h2 v-else-if="score>=80">良好</h2>
   <h2 v-else-if="score>=60">及格</h2>
   <h2 v-else>不及格</h2>
 分析:当条件很多的时候,不建议这么写,我们可以写在计算属性中

为什么我输入了内容,但是切换了输入框,数据还存在?

分析: 这是因为Vue在进行DOM渲染的时候,出于性能的考虑,会复用,而不是重新创建

<span v-if="isUser">
    <label for="username">用户账号 </label>
    <input type="text" id="username" placeholder="用户账号">
</span>
 <span v-else>
     <label for="email">用户邮箱</label>
    <input type="text" id="email" placeholder="用户邮箱">
 </span>
 <button @click="checkStatus">切换类型</button>

解决:如果我不希望切换输入框了原先的数据还存在,应该怎么做

我们给每个输入框加个key
  • v-show和v-if

    v-if

     当条件为false的时候,包含v-if指令的元素,根本就不会存在在dom中
     
    

    v-show

     当条件为false的时候,v-show指令只会在我们的元素添加一个行内样式 display:none
     
    

    开发时怎么选择?

     当需要在我们显示和隐藏之间切片很频繁,使用v-show
     当只有一次切换的时候,通常选择 v-if,开发中v-if用的多,我们一般获取服务器传过来的数据判断是否渲染。
    

(7) 循环遍历

  • v-for遍历数组

      //在遍历的过程中,没有使用索引值
      <li v-for="item in names">{{item}}</li>
      
      //在遍历的过程中,获取索引值
      <li v-for="(item,index) in names">{{index+1}}:{{item}}</li>
    
    
  • v-for遍历对象

    <li v-for="item in info">{{item}}</li>
    
    <!-- 获取key和value -->
    <!-- (value,key) -->
    <li v-for="(value,key) in info">{{value}}------{{key}}</li>
    
    <!-- h获取key、value、index -->
    <li v-for="(value,key,index) in info">{{index}}:{{value}}------{{key}}</li>
    
     info:{
      name:"why",
      age:19,
      height:1.88
    }
    
  • 组件的key属性

    官方推荐我们使用v-for的时候,给对应的元素添加key属性,目的是为了让我们更好的复用

    我们需要使用key来给每个节点做一个唯一标识

         diff算法就可以正确的识别次节点
         找到正确的位置区插入新的节点
         
    

    所以,key的主要最用就是为了高效的更新虚拟DOM

(8)数组中哪些方法是响应式的

 Vue内部会监听数据变化,会根据新的数据重新渲染DOM
 
 
 push、pop、unshift、shift、reverse、splice
 
 
  splice作用,删除元素、插入元素、修改元素
  
        如果你要删除元素:第二个参数传入你要删除几个元素
        splice(start)
        this.letters.splice(1,2)
        如果不传则删除所有
        this.letters.splice(2)
        
        如果我想要替换三个元素
        this.letters.splice(1,3,'1','2','3')
        
        新增元素,只要第二个元素为0
        this.letters.splice(1,0,'1','2','3')
        

(9)高阶函数

我们先看没学高阶函数之前,完成某些需求需要的代码

 // 需求:取出所有小于100的数字

const nums=[1,2,3,4,5]
let newNums=[]

for(let n of nums){
    if(n<100){
        newNums.push(n)
    }
}
console.log(newNums)

//将取出的数据*2
let new2Nums=[]
for(let n of newNums){
    new2Nums.push(n*2)
}
console.log(new2Nums)

//将所有数据相加
let total=0
for(let n of new2Nums){
    total+=n
}
console.log("循环方法",total)

学了高阶函数以后,我们的需求实现代码如下

    let Total=0
    Total =nums.filter(function(n){
        return n<100
    }).map(function(n){
        return n*2
    }).reduce(function(pre,value){
        return pre+value
    })
    console.log("高阶函数",Total)

分析:filter、map、reduce三种方法

 filter中的回调函数有个要求,必须返回布尔值,当返回true时,函数内部会自动把n加入到新的数组值,当为false时,函数内部会自动过滤这次的n

 map中的回调函数,返回的是当前这个的n,相当于遍历

箭头函数 result=nums.filter(n=>n<100).map(n=>n*2).reduce((pre,val)=>pre+val)

(10) 表单绑定v-model

  • 基本操作:

       <input type="text" v-model="message">
    
  • 本质:

v-model其实是一个语法糖,他的背后本质上是包含两个操作

1) v-bind 绑定一个value
 (2) v-on 指令给当前元素绑定input事件

   <input type="text"  v-bind:value="message"  v-on:input="valueChange">
   
valueChange(event){
      this.message=event.target.value
  }
  
  • 简写:

    <input type="text"  v-bind:value="message"  v-on:input="message=$event.target.value">
    
  • 语法糖

    <input type="text" :value="message"  @input="message=$event.target.value">
    

v-model:radio

  • 值绑定

    值绑定就是动态的给value赋值,因为在实际的开发中,这些input的值可能是网络获取的或者定义在data中

      <label v-for="item in originHobby" :for="item">
           <input type="checkbox" :id="item" :value="item" v-model:value='hobby'>{{item}}
       </label>
       
        originHobby:['羽毛球','篮球','蹦床','跳水','弹钢琴']
    
  • v-model 修饰符

    lazy

    number

    trim

      <!-- 修饰符lazy 
     
    双向绑定缺点:一旦数据发生改变对应的data中的数据就会改变
    .lazy的以后,回车或者失去焦点才会进行实时绑定
     -->
    <input type="text" v-model.lazy="message">
    {{message}}
    
    
    <!--   有时候我们希望用户输入数字,我们可以修改input的type为number,但是数据还是string类型 -->
    <!-- <input type="number" v-modal="num">
    {{typeof num}} -->
    
    <!-- 当我们希望数据是number类型我们可以添加修饰符number,就不需要进行类型转化 -->
    <input type="text" v-model.number="num">
    {{typeof num}}
    
    <input type="text" v-model="name">
    {{name}}
    
    <input type="text" v-model.trim="name">
    {{name}}
    

6.组件化

 我们可以将土匪完整的页面分成多个组件,每个组件可以实现自己对应的模块的功能

(1) Vue组件化思想

Vue组件化思想

    它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
    任何的应用都会被抽象成一颗组件树

组件化思想的应用

    有了组件化的思想,我们在之后的开发中要充分的利用它
    尽可能的将页面拆成一个个小的、可复用的组件
    这样让我们的代码更加方便组织和管理,并且扩展性更强
 
    所以,组件是Vue开发中,非常重要的篇章,要认真学习
    

组件的使用步骤

  • 创建组件构造器

  • 注册组件

  • 使用组件

     Vue.extend()方法创建组件构造器
     Vue.component()方法注册组件
     在Vue 实例的作用范围内使用组件
     
     
        const cpnC= Vue.extend({
             template:` 
                <div>
                <h2>我是标题</h2>
                <h2>我是内容,哈哈哈哈哈</h2>
                <h2>我是内容,呵呵呵呵</h2> 
                </div>
                `
         })
         Vue.component('my-cpn',
            cpnC
         )
    

注册组件步骤解析

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

2、Vue.component()
   
   调用Vue.component()时将刚才的组件构造器注册为一个组件,并且给他起一个组件的标签名称
   所哟需要传递两个参数:1、注册组件的标签名 2、组件构造器
   
3、组件必须挂载在某个Vue实例下,否则他不会生效

(2)全局组件和局部组件

//  全局组件
 Vue.component('my-cpn',
    cpnC
 )

//局部组件直接挂载在vue实例下
components:{
  cpn:cpnC
}

(3)父组件和子组件

组件和组件之间存在层级关系

其中最重要的关系就是父子组件

image.png

(4) 注册组件语法糖

之前创建组件、注册组件

  const cpnC= Vue.extend({
     template:` 
        <div>
        <h2>我是标题</h2>
        <h2>我是内容,哈哈哈哈哈</h2>
        <h2>我是内容,呵呵呵呵</h2> 
        </div>
        `
 })
 Vue.component('my-cpn',
    cpnC
 )

创建组件语法糖模式

 实际上内部还是调用了extend
 
 //全局组件
  Vue.component('my-cpn',
      {
            template:` 
                <div>
                <h2>我是标题</h2>
                <h2>我是内容,哈哈哈哈哈</h2>
                <h2>我是内容,呵呵呵呵</h2> 
                </div>
                `
        }
    )
    
 //注册局部组件
   const app=new Vue({
    el:"#app",
    data: {

    },
    components:{
      'cnp':{
        template:` 
                <div>
                <h2>我是标题</h2>
                <h2>我是内容,哈哈哈哈哈</h2>
                <h2>我是内容,呵呵呵呵</h2> 
                </div>
                `
      }
    }
  })
  

(5) 如何将组件模板抽取出来

(1)第一种:通过script标签,类型必须是 text/x-template ,给模板加一个id,这样可以绑定到实例上

image.png

(2)第二种:写在template标签中,同样也给template加一个id

image.png

(6)组件可以访问Vue实例数据嘛?

不能访问

Vue组件内部应该有自己保存数据的地方!!!!

  组件对象也有一个data属性(也可以有methods属性)
  只是这个data属性必须是一个函数
  而且这个函数返回一个对象,对象内部保存数据
  

image.png

(7)为什么组件data必须函数

创建组件的时候,会调用data函数,每次调用的时候我会创建一个新的函数data

(8)父子组件通信

  • props->properties(缩写) 父---->子
  • 通过事件向父组件发送消息 子----->父

*(1)父传子通过props

第一种写法:

 props:['cmovies','cmessage']

第二种写法:类型限制

props:{
  cmovies:Array,
  cmessage:String
}

第三种写法:有默认值、必传属性

props:{
  cmovies:Array,
  cmessage:String
  cmessage:{
    type:String,
    default:'HHHHHH',
    required:true   //必传属性
  }
}

*(2)子传父

如果子组件传递事件或者数据到父组件中,我们需要通过自定义时间来完成

什么时候需要自定义时间呢?

    当子组件需要想父组件传递数据是,就要用到自定义事件了
    我们之前学习的v-on不仅仅可以用于监听DOM时间,也可以用于组件间的自定义事件
    

自定义事件的流程

  在子组件中,通过$emit()来触发事件
  在父组件中,通过v-on来监听子组件事件
  
  子组件
  
    <template id="cnp">
       <div>
         <button
           v-for="item in categories" 
           @click="btnclick(item)"
         >
           {{item.name}}
        </button>
       </div>
    </template>
     
  
      methods:{
        btnclick(item){
          console.log(item)
          this.$emit('itemclick',item)
        }
  }
  

(9)父子组件通信-结合双向绑定事件

(10) 父子组件的访问方式:$children$refs(reference:引用)

有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者子组件访问根组件。

  • 父组件访问子组件:使用$children$refs(reference:引用)
  • 子组件访问父组件使用:$parent

父访问子

`this.$children`开发中不常用,因为没办法判断第几个children,而且可能this.$children的长度会发生变化

只有我们需要拿到所有的子组件的时候,才会通过$children,但是大多数想要拿到特定子组件,通过设置ref
`<cpn  ref="aaa"></cpn>`

子访问父

 this.$parent 访问父组件
 this.$root 访问根组件

(11)插槽slot

为什么使用slot?

让我们原来的设备具有扩展性,类似于电脑的USB,可以当做电源插槽、U盘、键盘、鼠标、音响等不通设备

组件的插槽

也是为了让我们封装的组件更加具有扩展性

<template id="cpn">
    <div>
        <h2>我是组件</h2>
        <p>我是组件哈哈哈哈哈哈哈</p>
        <!-- 预留空间 -->
        <slot></slot>
     </div>
</template>

如何封装合适呢?

抽取共性,保留不同

具名插槽

<template id="cpn">
<div>
    <h2>我是组件</h2>
    <p>我是组件哈哈哈哈哈哈哈</p>
   <!-- 具名插槽 -->
    <slot name="left"><h2></h2> </slot>
    <slot name="center"><h2></h2></slot>
     <slot name="right"><h2></h2></slot>
</div>
</template>

 <cpn>
    <button slot="left">左边</button>
    <span slot="center">我想替换中间</span>
    <button slot="right">右边</button>
</cpn>

编译作用域

官方准则:父组件模板中的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译

<div id="app">
    <!-- 这里isShow使用实例isShow还是组件呢?答案:实例 -->
    <!-- 说明在实例中组件使用变量的时候,还是会去实例中寻找的,而不是组件中,因为实例把组件看做一个简单的html -->
    <cpn v-show="isShow">
    </cpn>
</div>

作用域插槽

总结:父组件替换插槽的标签,但是内容由子组件来提供

其实就是在父组件中拿到子组件的数据

image.png

7.前端模块化

(1)模块化基础

  • 为什么要模块化

    JavaScript原始功能

    在网页开发早期,js作为一种脚本语言,做一些简单的表单验证或者动画实现等,那时候代码还是很少的
    那时候代码是写在script标签中的
    
    随着ajax异步请求的出现,慢慢形成了前后端分离
       
        客户端要完成的事情越来越多,代码量与日俱增
        为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护
        但是这种维护方式,依然不能避免一些灾难性的问题
        
        比如全局变量同名问题
    

    我们使用匿名函数来实现,虽然解决了命名冲突问题,但是没有解决代码复用性问题

    所以使用模块作为出口,我们可以将需要暴露在外面的变量,作为一个模块的出口,意思如下:

     在匿名函数内部,定义一个对象
     给对象添加各种需要暴露带外面的属性和方法(不需要暴露的直接定义即可)
     最后将这个对象返回,并且在外面使用ModualeA接收
     
     这就是模块化的最基本的封装
    

    随着前端越来越复杂,我们已经不需要自己去使用匿名函数模块化来解决了,很多人已经写好了一些规范化的模块化

    常见的模块化规范

        CommonJs、AMD、CMD、也有ES6的Modules
    
  • CommonJS(了解)

    模块化核心:导入和导出

    CommonJS的导出

         module.exports={
             flag:true,
             test(a,b){
                return a+b
             },
             demo(a,b){
                return a*b
             }
        }
    

    CommonJS的导入

       let {test,demo,flag}=require('moduleA')
       
       等同于
         
         let mA=require('moduleA')
         let test=mA.test
         let demo=mA.demo
         let flag=mA.flag
    
  • ES6的模块化

    export指令是导出我们模块对外提供的接口,下面我们就可以通过import命令来加载这个模块

    首先我们需要再HTML代码中引入两个js文件,并且需要设置type为module

      <script src="aaa.js" type="module"></script>
      <script src="ccc.js" type="module"></script>
      
    

    import指令用于导入模块中的内容,比如main.js代码

       import {flag,num1,height,mul,sum,Person} from './aaa.js'
    
      // 这种导入表示导入aaa.js里面默认导出的内容,有且只有一个
      import addr from './aaa.js'
    
     // 统一全部导入
      import * as aaa from './aaa.js'
      console.log("aaa",aaa)
    

    export指令用于导出

      // 导出方式1
      export {name,age,flag}
    
      // 导出方式2
      export var num1=1000;
      export var height=200.
      // 导出函数
      export {sum};
      export function mul(num1,num2){
          return num1+num2
      }
      // 导出es6
      export class Person{
         run(){
             console.log('在奔跑')
         }
      }
      //某些情况下,一个某块包含某个功能,我们并不希望给这个功能命名,而让导入者自己命名 export default
      const address='北京市'
      // 在开发中默认的导出只能有一个
      export default address 
    

    注意: export default在同一个模块中,不允许存在多个

8.webpack

内容概述

 * 认识webpack
 * webpack的安装
 * webpack的起步
 * webpack的配置
 * webpack的使用
 * webpack中配置Vue
 * Plugin的实验
 * 搭建本地服务器

(1)什么是webpack

从官方解释看,webpack是一个现代的JavaScript应用的静态模块打包工具

image.png

  • 前端模块化

      前面我们学习了几种前端模块化的方案:AMD、CMD、CommonJS、ES6规范,目前只能用ES6,原因是浏览器只支持ES6的规范,浏览器做了ES6的底层的支撑
    
      但是在webpack中就能用AMD、CMD、CommonJS,他会在打包的的时候自动将代码进行转换,转换成大部分浏览器都能识别的模块化,所以打包的里面没有AMD、CMD、CommonJS的代码,我们在开发的时候这么使用。webpack做了底层的支撑!
    
    
      在ES6之前,我们想要进行模块化开发,就必须借助其他的工具,让我们可以进行模块化开发
    
      并且通过模块化开发完成项目后,还需要处理各个模块之间的依赖
    
      而webpack其中一个核心就是让我们可以进行模块化开发,并且他会帮助我们处理模块之间的依赖关系
    
      而且不仅仅是js文件,我们的css、图片、json文件等等再webpack中都可以被当做模块来使用
    
      这就是webpack中模块化的思想
    
  • 打包

    理解了webpack可以帮助我们进行模块化,并且处理好了模块间复杂的关系后,打包的概念就非常好理解了
    就是将webpack中各种资源进行打包合成一个或多个包
    并且在打包过程中,还可以对资源进行处理,比如压缩图片,将scss转换成ES5语法,将ts转换为js等等操作
    
  • webpack和grunt/gulp对比

    但是打包的操作似乎grunt/gulp也可以帮助我们完成,他们有什么不同呢?

    gulp的核心是Task

       我们会配置一些列的task,并且定义task要处理的事务(例如ES6、ts转化、图片压缩、scss转换成css)
       之后grunt/gulp来一次执行这些task,而且让整个流程自动化
       所以grunt/gulp也称为前端自动化任务管理工具
    

    什么时候用到grunt/glup呢?

      如果你的工程依赖非常简单,甚至没有用到模块化的概念
      只需要进行简单的合并、压缩、就使用grunt/glup
      但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就需要用到最强大的工具webpack
      
    

    所以,grunt/glup和webpack有什么不同呢?

      grunt/glup更加强调前端流程的自动化,模块化不是他的核心
      webpack更加强调模块化开发管理,而文件压缩合并、预处理功能,是他附带的功能
      
    

(2)webpack和node和npm关系

webpack为了可以正常运行,必须依赖node环境

node环境为了可以正常执行很多代码,必须其中依赖各种包,手动管理太麻烦,所以安装node的时候会自动安装npm工具,npm工具只是为了方便管理node各种包,npm(node packages manager)。

(3)webpack安装

  • 安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm

  • 查看自己的node版本

image.png

  • 全局安装webpack(这里指定版本3.6.0,因为vue cli2依赖该版本 )

image.png

image.png

  • 局部安装webpack(后续才需要)

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

  • 为什么全局安装后,还需要局部安装呢?

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

webpack如何打包?

   webpack ./src/main.js ./dist/bundle.js
   

image.png

bundle.js是webpack处理了项目直接文件依赖后生成的一个js文件,我们只需要将这个js文件在index.html

webpack配置 webpack.config.js

const path =require('path')

// 将入口文件,出口文件放在配置,执行webpack即可
module.exports={
    entry:'./src/main.js',
    output:{
        // path.resolve()拼接路径,当前路径拼接__dirname+dist
        path:path.resolve(__dirname,'dist'),
        filename:'bundle.js'
    }
}
   

(4) 局部安装webpack

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

npm install webpack@3.6.0 --save-dev

 webpack作用是打包出去包,只有在开发阶段才需要,打包完了webpack就没有用了
 

在这里定义脚本,优先执行本地的webpack

image.png

只要在终端输入的webpack用的都是全局的webpack

image.png

(5)什么是loader

loader是webpack中非常核心的概念

webpack最主要用来做什么?

 在我们之前的实例中,我们主要是用webpack来处理我们的js代码,并且webpack会自动处理js之间相关的依赖。
 
 但是,在开发中,我们不仅仅有基本的js代码处理。我们也需要加载css、图片也包括一些高级的将es6转换成ES5代码,将scss、less转换成css,将jsx、.vue文件转换成js文件等等。
 对于webpack本身来说,对于这些转移是不支持的
 
 那么怎么办呢?
 webpack扩展对应的loader就可以了

loader使用过程

步骤一:通过npm安装需要使用的loader
步骤二:在webpack.config.js的modules关键字下进行配置

大部分的loader我们可以在webpack官网可以看到

(6)处理CSS的loader

文件加载必须依赖css-loader、style-loader。css-loader只负责将css文件进行加载,style-loader负责将样式添加到DOM,依赖这两个loader的时候,先使用css-loader,然后再使用style-loader。但是因为webpack在读取use顺序的时候,是从右向左读取的,所以书写的时候要写成use: ["style-loader","css-loader"]

image.png

image.png

image.png

(7)webpack-less文件处理

如果我们希望在项目中使用less\scss\stylus来写样式,webpack怎么处理呢?

image.png npm install less less-loader@4.1.0 --save-dev

(8)webpack-图片文件处理

npm install url-loader --save-dev

当加载的图片,小于limit时,会将图片编译成base64字符串形式。

大于limit,必须加载file-loader。但是大于图片的时候运行的时候还是会报错,我们主要到,再次打包的时候,dist文件夹下多了一个图片文件。有了这个图片的时候,我们发现图片没有显示出来,这是因为图片的路径没有写对。

  • 默认情况下webpack会将生成的路径直接返回给使用者

  • 但是,我们整个程序是打包在dist文件夹下的,所以我们需要再路径下添加一个dist/ ,则我们在output中添加 publicPath:'dist/'

  • 我们发觉dist打包中的图片命名是32位的hash值,目的是为了防止重名

image.png

image.png

image.png

如果我们相对图片进行命名,我们可以设置name如下:

image.png

(8)webpack-ES6语法处理

如果你仔细阅读webpack打包的js文件,会发现写的ES6语法并没有转成ES5,部分浏览器是不能认识的。

babel就是将ES6转换成ES5

babel也是一个loader

(9) webpack配置vue

因为我们后续需要再实际项目中使用vue,所以,并不是开发时依赖

  • 第一步:安装vue

npm i vue --save

  • 第二步:导入vue,

import Vue from 'vue'

创建vue实例对象

image.png

如果页面什么都没显示也没报错,可以设置,然后npm run dev

image.png

打包运行的时候发现报错,所以我们指定alias指定我们当前要使用的到底是哪一个vue

image.png

(10) el和template区别

我们以后的项目可能是只有一个index.html,一般我们不改index.html。 image.png 如果我们想要在index中展示内容,我们可以写在template中

vue内部会把template替换到之前el挂载的位置

image.png

同时有el和template,template会吧el替换掉 这样的好处就是我们并不需要改动index.html中的内容

(11)vue终极解决方案

改造(一)

image.png

改造(二)

image.png

改造(三)

image.png

报错了,因为没有对应的loader和vue-template-compiler

安装vue-loader和vue-template-compiler都是开发时依赖

npm install vue-loader@15.4.2 vue-template-compiler@2.5.21 --save-dev

image.png

image.png

详情见 vue-loader.vuejs.org/zh/guide/#v…

到此.vue已经正常编译了。

注:resolve主要就是用来解决路径问题!

image.png

(12) 认识plugin

* plugin是插件的意思,通常是对某个现有的架构进行扩展
* webpack中的插件,就是对webpack现有功能进行扩展,比如打包优化,文件压缩

loader和plugin区别

* loader主要用于转换某些类型的模块,他是一个转换器
* plugin是插件,他是对webpack本身的扩展,是一个扩展器

plugin的使用过程:

步骤一:通过npm安装需要使用的plugins(某些插件不需要安装,内置了)
步骤二:在webpack.config.js中的plugin中配置插件

1.添加版权的Plugin

该插件名字叫做BannerPlugin,属于webpack自带插件
   

image.png

(12) webpack-HtmlWebpackPlugin的使用

目前,我们的index.html文件是存放在项目的根目录下的

   * 我们知道在正式发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也没有意义了
   * 所以,我们需要将index.html文件打包到dist中,这时候就需要用到HtmlWebpackPlugin插件了

HtmlWebpackPlugin可以为我们做什么?

   * 自动生成一个index.html文件(可以指定模板来生成)
   * 将打包的js文件,自动通过script标签插入到body中

安装HtmlWebpackPlugin插件

  npm install html-webpack-plugin@3 --save-dev
  
  const HtmlWebpackPlugin=require('html-webpack-plugin')
  
  plugins: [
     new HtmlWebpackPlugin()
  ],
  
  

image.png

此时,打包路径就多余了

image.png

(12) js的压缩Plugin的使用

在我们项目发布之前,我们必然需要对js等文件进行压缩处理

  * 这里,我们就对打包的js文件夹进行压缩
  * 我们使用一个第三方的插件 uglifyjs-webpack-plugin,并且版本号指定1.1.1,和cli2保持一致

    npm install uglifyjs-webpack-plugin@1.1.1 --save-dev

修改webpack.config.js配置文件

   const UglifyjswebpackPlugin=require('uglifyjs-webpack-plugin')
   
    plugins: [ new UglifyjswebpackPlugin()],
   

(13)webpack-dev-server搭建本地服务器

webpack可以搭建一个本地的可选服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果

不过它是一个单独的模块,在webpack中使用之前需要先安装它 npm install --save-dev webpack-dev-server@2.9.1

image.png

9.Vue-Router

(1)导航守卫

我们通过修改网页标题案例引入导航守卫。

  • 普通的修改方式:

我们可以在每个路由对应的vue组件中,通过mounted声明周期函数,执行对应的代码进行修改,但是当页面特别多的时候,维护不方便。

  • 导航守卫解决: 当我们从一个路由跳转到另一个路由的时候,beforeEach是一个函数,本身要求传入函数作为参数,传入的的函数有3个参数,from、to、next,next是必须调用的,当你从一个路由跳转到另一个路由的时候,就会执行beforeEach。之后可以在路由中定义meta,元数据(描述数据的数据),我们可以动态的通过ro,拿到meta,但是如果路由存在嵌套的话,就拿不到meta,所以我们直接去拿matched的第一个元素。

第一个home有路由嵌套,拿到meta里面没有title,我们可以去matched里面找

image.png

router.beforeEach(function(to,from,next){
   console.log("to",to)
   // 从from跳转到to
   document.title=to.meta.title
   next()
})

改造

router.beforeEach(function(to,from,next){
   console.log("to",to)
   // 从from跳转到to
   // document.title=to.meta.title
   document.title=to.matched[0].meta.title
   next()
})

(2)导航守卫补充

beforeEach()是前置钩子,必须调用next() afterEach()是后置钩子,不需要主动调用next()函数

router.beforeEach(function(to,from,next){
   console.log("to",to)
   // 从from跳转到to
   // document.title=to.meta.title
   document.title=to.matched[0].meta.title
   next()
})
// afterEach()是后置钩子,不需要主动调用next()函数
router.afterEach((to,from)=>{
   console.log("+++++",to,from)
})

上面是我们使用导航守卫,我们称之为全局守卫

  • 路由独享的守卫
  • 组件内的守卫

(3)路由独享守卫

你可以在路由配置上直接定义beforeEnter守卫

{
        path:'/about',
        component:()=>import('../components/About'),
         meta:{
             title:'关于'
         },
         beforeEnter:((to,from,next)=>{
           console.log("路由独享守卫————————",to,from)
           next()
         })
     },

(4)vue生命周期

image.png

(5) keep-alive

keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或者避免重新渲染

router-view也是一个组件,如果直接被包在keep-alive里面,所有路径匹配到的组件都会被缓存

使用keep-alive的时候,组件不会被频繁的创建和销毁(可以排除一些组件),被排除的组件还是会被频繁创建和销毁

<keep-alive exclude="Profile,User">
    <router-view></router-view>
</keep-alive>
 created(){
    console.log("created")
  },
  destroyed(){
    console.log("destroyed")
  },
5.1补充
//这两个函数只有该组件被保存了状态,使用keep-alive才是有效的
activated(){
     console.log("aaa")
     this.$router.push(this.path)
 },
deactivated(){
     console.log('deactivated')
},