Vue学习

一、Vue 简介

1、官网

英文官网: https://vuejs.org/

中文官网: https://cn.vuejs.org/

2、作者和版本

在这里插入图片描述

3、定义

Vue(读⾳ /vjuː/,类似于 view),不要读错。是一套用于构建用户界面的渐进式 MVVM 模型框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

Vue 是⼀个渐进式的前端框架,什么是渐进式的呢?
简单来说可以理解为轻量级,Vue.js 核⼼库只提供了基础功能,让你在使用 Vue 作为开发框架时,不需要⼀次性引入过重的依赖,可以先使用核⼼功能,如果应用再进⼀步升级变得更复杂以后,可以逐渐的引入 Vue 生态的其他组件,例如 vue-router、vuex、 Axios 等,而不是⼀股脑的⼀次性全塞进来。

单页 Web 应用(single page web application,SPA),就是只有一张 Web 页面的应用。单页应用程序(SPA)是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的 Web 应用程序。浏览器一开始会加载必需的 HTML、CSS 和 JavaScript,所有的操作都在这张页面上完成,都由 JavaScript 来控制。

4、特点

  • 解耦视图和数据;
  • 双向数据绑定;
  • 可复用的组件;
  • 前端路由技术;
  • 状态管理;
  • 虚拟 DOM。

5、Vue 的周边库

  1. vue-cli: vue 脚手架

  2. vue-resource

  3. axios

  4. vue-router: 路由

  5. vuex: 状态管理

  6. element-ui: 基于 vue 的 UI 组件库(PC 端

  7. 。。。。

二、MVVM

1、MVC 模型

在这里插入图片描述

这种 MVC 架构模式对于简单的应用来看起是 OK 的,也符合软件架构的分层思想。 但实际上,随着 H5 的不断发展,人们更希望使用 H5 开发的应用能和 Native 媲美,或者接近于原生 App 的体验效果,于是前端应用的复杂程度已不同往日,今非昔比。这时前端开发就暴露出了三个痛点问题:

  • 开发者在代码中大量调用相同的 DOM API,处理繁琐,操作冗余,使得代码难以维护。
  • 大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
  • 当 Model 频繁发生变化,开发者需要主动更新到 View ;当用户的操作导致 View 发生变化,开发者同样需要将变化的数据同步到Model 中,这样的工作不仅繁琐,而且很难维护复杂多变的数据状态。

2、MVVM 模型

在这里插入图片描述

  • MVVM 由 Model,View,ViewModel 三部分构成,Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成 UI 展现出来,ViewModel 是一个同步 View 和 Model 的对象。
  • 在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,且是双向的,因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。
  • ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作 DOM,不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

3、Vue 与 MVVM 模型

Vue 可以说是 MVVM 架构的最佳实践,专注于 MVVM 中的 ViewModel,做到了数据双向绑定,实现了 View 和 Model 之间的同步。

在这里插入图片描述

三、Vue 安装使用

与 Java 中,Vue 作为一个第三方框架,使用它的第⼀个步骤就是引入依赖,而前端依赖库都是以 js 文件的形式存在的,因此我们只需要在所需要的页面中引入对应的 js 文件即可。

1、CDN 引入

可以简单将 CDN 理解为⼀个加速服务器,它会在离用户最近的服务器站点上建立用户所需要资源的缓存,帮助用户以更快的速度加载资源。

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺⼨和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

2、下载后引入

利用上方的 CDN 地址,访问后将内容拷贝下来,保存到本地资源文件中,然后再引入页面即可。

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="/static/js/vue-2.7.10/vue.js"></script>
<!-- 生产环境版本,优化了尺⼨和速度 -->
<script src="/static/js/vue-2.7.10/vue.min.js"></script>

3、命令行工具 (CLI)

后续通过 Vue-CLI(脚⼿架)⽅式引入,刚开始学习使用前面两种即可,后续使用这种方式。

四、入门案例

<!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.js -->
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <p>{{message}}</p>       
    </div>
    <script>
        let app = new Vue({              
            el : '#app',                // 针对页面 id 为 app 的元素,支持多种选择器
            data : {                    
                message : '你好美'
            }    
        });
    </script>
</body>
</html>

上面的代码做了什么事情?(了解,重点掌握格式的书写!)

  • 1、先看 js 代码,会发现创建了⼀个 Vue 对象。
  • 2、创建 Vue 对象的时候,传入了⼀个 JS 对象。
    • 2.1、对象中的 el 属性:该属性决定了这个 Vue 对象挂载到哪⼀个元素上,很明显,我们这里挂载到了 id 为 app 的元素上。
    • 2.2、对象中的 data 属性:该属性中通常会存储⼀些数据,上面例子中的 msg 就是直接定义出来的数据
  • 3、Vue 帮我们通过 id 找到了页面上的 app 元素,并且将其中 {{ msg }} 表达式替换为了 data 中的数据
  • 4、执行完上面的代码,数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器控制台,修改 app.message 的值,你将看到上例相应地更新。

五、Vue 基本使用

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式(v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

1、文本插值

给元素中间插入值,有以下几种方式,对比如下。

插值标签数据元素中内容网络延迟插件表示式JavaScript 表达式
插值表达式不支持不会覆盖显示支持
v-text不支持会覆盖不显示支持
v-html支持会覆盖不显示支持

注意:

method Watch filter compute

  • 解决插件表达式网络延迟显示的问题,可以设置 v-cloak 来解决。
  • Vue 都提供了完全的 JavaScript 表达式支持,如下:
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<!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>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <style>
        [v-cloak]{
            display: none;
        }
    </style>
</head>
<body>
    <div id="app">
        <p v-cloak>{{msg1}}</p>       
        <p v-text="msg2"></p>
        <p v-html="msg3"></p>
    </div>
    <script>
        let app = new Vue({              
            el : '#app',                
            data : {                    
                msg1 : '你好美',
                msg2 : '你好高',
                msg3 : '<h1>你好傻</h1>'
            }
        });
    </script>
</body>
</html>

2、属性插值

给元素属性插入值。使用指令 v-bind。值支持 JavaScript 表达式,指令可以简写成冒号。

<div id="app">
    <span v-bind:title="message">
        鼠标悬停几秒钟查看此处动态绑定的提示信息!
    </span>
    <span v-bind:title="message + '吗?'">
        鼠标悬停几秒钟查看此处动态绑定的提示信息!
    </span>
    <span :title="message">
        鼠标悬停几秒钟查看此处动态绑定的提示信息!
    </span>
</div>
    
<script>
    let app = new Vue({             
        el : '#app',                
        data : {                   
            message : '你好美'
        }
    });
</script>

3、Class 与 Style 绑定

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

.active {
    border: 3px solid blue;
    width: 100px;
    height: 100px;
}
.error {
    background-color: red;
}
<div id="app">
    <div :class="{active : isActive, error : isError}">绑定 Class</div>
    <div :style="styleObj">绑定 Style</div>
</div>
let app = new Vue({
    el: '#app',
    data: {
        isActive: true,
        isError: true,
        styleObj: {
            border: '2px solid green',
            width: '150px',
            height: '150px'
        }
    }
})

4、条件渲染

v-ifv-show 都是来控制标签是否显示,但是也有区别,v-show 是对样式层面的控制,v-if 是对 DOM 节点的控制。

<div id="app">
   <div v‐if="score >= 90">优秀</div>
   <div v‐else‐if="score >= 80 && score < 90">良好</div>
   <div v‐else‐if="score >= 60 && score < 80">一般</div>
   <div v‐else>不及格</div>
   <div v‐show="flag">天下第一</div>
</div>
let app = new Vue({
    el: "#app",
    data: {
        scores: 60,
        flag: true
    }
});

大家可以尝试在浏览器控制器修改 app.scoreapp.flag 的值来看效果。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

5、循环渲染

v-for 指令可以对⼀个数组/集合数据进行遍历,遍历的对象可以是⼀个数组,也可以就是⼀个对象。

  • 数组遍历:可以得到数组的元素与索引值;
  • 对象遍历:可以得到对象的属性值与属性名。

语法格式如下:

// 遍历数组,index 可选
v-for="(item, index) in arr"
// 遍历对象,name 和 index 可选
v-for="(value, name, index) in object"
<div id="app">
   <ul>
       <li v-for="(book, index) in books">{{index}} : {{book}}</li>
   </ul>
   <ul>
       <li v-for="(value, name, index) in author">{{index}} : {{name}} : {{value}}</li>
   </ul>
</div>
let app = new Vue({
    el: "#app",
    data: {
        books: ["三国演示", "残唐五代史演义"],
        author: {
            name: "罗贯中",
            age: "70"
        }      
    }
});

6、事件处理

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。可简写成 @ 符号。其语法:

  • 全写:v-on:事件名称="响应处理函数"

  • 简写:@事件名称="响应处理函数"

<div id="app">
  <img :src="img" v-on:click="clickImg"
       @mouseover="changeImg($event, 'b.png')"
       @mouseleave="changeImg($event, 'c.png')" alt="美女">
</div>
 /**
   * 事件的关键对象:
   *  1. 事件源
   *    1.1. 在绑定事件时, 传入 this 参数,只在传统 html 下才可用,在 Vue 中不可以使用
   *    1.2. 通过事件对象.currentTarget 属性获取
   *  2. 事件对象
   *    2.1. 直接在绑定事件处, 传入 $event 对象即可
   *    2.2. 绑定事件时, 直接绑定响应处理函数的引用, 而不是调用该函数
   *  3. 事件响应处理函数
   *    3.1. 事件响应函数编写在 methods 属性中
   *  4. 事件名称
   *    4.1. v-on:事件名称(传统事件名称去掉 on)
   *    4.2. @事件名称(传统事件名称去掉 on)
*/

let app = new Vue({
    el: '#app',
    data: {
        img: 'a.png'
    },
    methods: {
         clickImg: function (event) {
            console.log(event);
            console.log(event.currentTarget);
        },
        changeImg: function (event, name) {
            console.log(event);
            console.log(name);
        }
    }
});

7、双向绑定

数据的双向绑定可以简单理解为就是对 MVVM 思想的一种标准实现了,Vue 通过 Object.defineProperty 对数据与视图分别进行监听,实现了视图的变动会影响到数据,数据的变动同时也会影响到视图。

实际开发中,通常将双向数据绑定应用在表单元素上,通过 v-model 指令将表单元素与对应 data 中的数据进行绑定。

语法:<表单元素 v-model="数据名称">

<div id="app">
  <!-- 对表单元素进行双向绑定 -->
  <input type="text" v-model="msg"> <br>

  <input type="text" v-model="city"> <br>
  
  <select name="cities" id="cities" v-model="city">
    <option value="cd">成都</option>
    <option value="gz">广州</option>
    <option value="sy">沈阳</option>
  </select>
</div>
let app = new Vue({
    el: '#app',
    data: {
        msg: '数据双向绑定',
        city: 'gz'
    }
});

大家可以尝试修改表单元素中的值,再到浏览器控制器看下 app.msgapp.city 的值。

Vue 选项关键属性:

  • el:表示要将 Vue 绑定到哪个标签上,以该标签作为视图;
  • data:保存数据,视图中需要的数据从这⾥取;
  • methods:定义视图所需要使用的⽅法,如事件响应处理函数可以定义在此处。

练习表单数据收集

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="root">
        <form action="#">
            用户名:<input name="username" type="text" v-model="form.username"> <br><br>
            密码:<input name="password" type="text" v-model="form.password"> <br><br>
            性别:
            男:<input type="radio" name="sex" value="man" v-model="form.sex"> 
            女:<input type="radio" name="sex" value="woman" v-model="form.sex"> <br><br>
            爱好:
            篮球:<input type="checkbox" value="ball1"  v-model="form.hobby" >
            羽毛球:<input type="checkbox" value="ball2" v-model="form.hobby" >
            乒乓球:<input type="checkbox" value="ball3" v-model="form.hobby" ><br><br>
            所选校区:
            <select v-model="form.school">
                <option value="">请选择校区</option>
                <option value="0">广州</option>
                <option value="1">成都</option>
                <option value="2">沈阳</option>
            </select><br><br>
            我同意: <input type="checkbox" name="aggree" v-model="form.aggree"><br><br>
           
        </form>
        <button @click="btn">提交</button>
    </div>
</body>
<script>
    var vm = new Vue({
        el: "#root",
        data: {
            form : {
                username: "",
                password: "",
                sex: "",
                hobby: [],
                school: "",
                aggree: ""
            }
        },
        methods: {
            btn(){
                console.log(this.form);
            }
        },
    })
</script>
</html>

8、计算属性

板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<div id="app">
  {{ firtName + lastName }}
</div>

若有的需求是要显示获取的字符串数据进行翻转,那么就会更加难以处理。所以,对于任何复杂逻辑,你都应当使用 Vue 的计算属性 computed

<div id="app">
    <p>姓氏:"{{ firstName }}"</p>
    <p>名字:"{{ lastName }}"</p>
    <p>姓名:"{{ fullName }}"</p>
    <p>姓名:"{{ fullName }}"</p>
</div>
let app = new Vue({
    el: '#app',
    data: {
        firstName: 'Elon',
        lastName: 'Musk',
    },
    computed: {
        fullName: function () {
            // `this` 指向 vm 实例
            console.log("fullName 被执行了")
            return this.firstName + ' ' + this.lastName
        }
    }
})

但是发现把 computed 换成 methods 也是可以的。

<div id="app">
    <p>姓氏:"{{ firstName }}"</p>
    <p>名字:"{{ lastName }}"</p>
    <p>姓名:"{{ fullName() }}"</p>
    <p>姓名:"{{ fullName() }}"</p>
</div>
let app = new Vue({
    el: '#app',
    data: {
        firstName: 'Elon',
        lastName: 'Musk',
    },
    methods: {
        fullName: function () {
            console.log("fullName 被执行了")
            return this.firstName + ' ' + this.lastName
        }
    }
})

computed 支持缓存,只有响应式依赖数据发生改变,即在上面的例子中只要 firstName 和 lastName 还没有发生改变,多次访问 fullName 计算属性会立即返回之前的计算结果,而不必再次执行函数。
methods 不缓存,每当触发重新渲染时,调用的函数(定义在 methods 中的)总会再次执行,即在这里 fullName 函数再次被执行。

9、侦听器

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性 watch。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

<div id="app">
    <p>姓氏:"{{ firstName }}"</p>
    <p>名字:"{{ lastName }}"</p>
    <p>姓名:"{{ fullName }}"</p>
</div>
let app = new Vue({
    el: '#app',
    data: {
        firstName: 'Elon',
        lastName: 'Musk',
        fullName: 'Elon Musk'

    },
    watch: {
        firstName: function (val) {
            this.fullName = val + ' ' + this.lastName
        },
        lastName: function (val) {
            this.fullName = this.firstName + ' ' + val
        }
    }
})

由上可以看出 watch 要监听两个数据,而且代码是同类型的重复的,所以相比用 computed 更简洁。

当依赖的值变化时,在 watch 中,是可以做一些复杂的操作的,而 computed 中的依赖,仅仅是一个值依赖于另一个值,是值上的依赖。
应用场景:
computed:用于处理复杂的逻辑运算;一个数据受一个或多个数据影响;用来处理 watch 和 methods 无法处理的,或处理起来不方便的情况。例如处理模板中的复杂表达式、购物车里面的商品数量和总金额之间的变化关系等。
watch:用来处理当一个属性发生变化时,需要执行某些具体的业务逻辑操作,或要在数据变化时执行异步或开销较大的操作;一个数据改变影响多个数据。例如用来监控路由、input 输入框值的特殊处理等。

10、过滤器

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式(后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由管道符号 | 连接。

<div id="app">
    <ul>
        <li v-for="product in products">
            {{ product.name }} : {{ product.price * 80 / 100 + '元' }}
        </li>
    </ul>
    <ul>
        <li v-for="product in products">
            {{ product.name }} : {{ product.price | discountFn(50) | formatFn() }}
        </li>
    </ul>
</div>
let app = new Vue({
    el: '#app',
    data: {
        products: [
            {
                id: 1,
                name: 'Apple iPhone 14 Pro',
                price: 7999,
            }, 
            {
                id: 2,
                name: 'Model 3',
                price: 279900,
            }
        ]
    },
    filters: {
        discountFn: function(val, d) {
            return val * d / 100

        },
        formatFn: function(val) {
            return val + '元'
        }
    }
})

六、ES6 中的对象简化写法

1、对象简写方式

    var a= 1;
    var b = 1;
    var c = function(){
        console.log(10)
    }
    var obj = {
         a: a,
         b: b,
         c: c
    }
    console.log(obj)

    var obj1 = {
        a,b,c
    }
    console.log(obj1);

2、Ajax 简写方式

$.ajax({
		url: "/api/login",
		type: "post",
		data: {"username":"123","password":"111"},
		success(data){
			console.log(111)
		}
	})

3、Vue 方法的简写

 var vue = new Vue({
        el: "div",
        data: {
            message: 456
        },
        methods: {
            clickMe(){
                
            }
        },
    })

六、Vue 组件化开发

1、组件

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的 HTML 代码,我们可以将组件看作自定义的 HTML 元素(解决页面重复问题,自定义标签, 使用)。

2、组件化

所谓组件化,就是把页面拆分成多个组件,每个组件依赖的 CSS、JS、模板、图片等资源放在一起开发和维护。

在这里插入图片描述

面对复杂问题的处理方式,把问题拆解成很多个能处理的小问题,再将其放在整体中,会发现大的问题也会迎刃而解。
而组件化的思想也类似:

  • 1.如果我们实现⼀个页面结构和逻辑非常复杂的页面时,如果全部一起实现会变得非常复杂,而且也不利于后续的维护和迭代功能。
  • 2.如果我们把页面分成一个个小的功能块,每个功能块能完成属于自己这部分独立的功能,那么整个页面之后的维护和迭代也会变得非常容易。

组件化开发的优势:责任分离、可维护性高、复用性高。

3、组件使用

Vue.js 的组件的使用有 2 个步骤:注册组件和使用组件。根据注册方式不一样,组件分为全局组件和局部组件。

3.1、全局组件

通过 Vue.component('组件名称', {配置对象}) 方法注册的都是全局组件,也就是它们在注册之后可以用在任何新创建的 Vue 实例挂载的区域内使用。

<div id="app1">
<!--
    组件开发是基于在原生的 HTML 之上进行的
    将已经写好的 HTML ⽚段,作为组件的模板,在不同的地⽅使用到时,只需要修改数据,并使用该组件即可
-->
<!-- 
    使用组件 <组件名></组件名> 
-->
    <my-btn></my-btn>
    <div>
        <my-btn></my-btn>
    </div>
</div>

<div id="app2">
  <my-btn></my-btn>
</div>

<my-btn></my-btn>
// 注册组件 Vue.component('组件名', {配置对象});
Vue.component('my-btn', {
    template: '<button style="width: 100px; height: 30px; background-color: aquamarine;">自定按钮</button>'
});

let app1 = new Vue({
    el: '#app1'
});

let app2 = new Vue({
    el: '#app2'
});

上面案例中,最后的 <my-btn> 由于既不在 app1 中也不在 app2 中,因此⽆法被 Vue 实例所渲染,所以⽆法被显示出来。

3.2、局部组件

全局注册往往是不够理想的,只适用于绝大部分视图页面都需要的组件,如果将一个使用较少的组件注册到全局,会导致 在打包时即使没用到它,也会将其打包进去,导致 js 文件变大或变多,请求速度变慢,因此对于特殊场景才会使用到的组件,我们应该将其注册为局部组件。

<template id="mybtn1">
    <button style="width: 100px; height: 30px; background-color: green;">
        自定按钮 1
    </button>
</template>

<template id="mybtn2">
    <button style="width: 100px; height: 30px; background-color: red;">
        自定按钮 2
    </button>
</template>

<div id="app1">
     <my-btn1></my-btn1>
</div>

<div id="app2">
    <my-btn2></my-btn2>
</div>
let app1 = new Vue({
    el: '#app1',
    components: {
        'my-btn1': {
            template: '#mybtn1'
        }
    }
})

let app2 = new Vue({
    el: '#app2',
    components: {
        'my-btn2': {
            template: '#mybtn2'
        }
    }
})

七、Vue 生命周期

从 Vue 创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。

1、生命周期图

在这里插入图片描述

2、生命周期钩子函数

就是生命周期事件响应处理函数的别称,我们可以通过实现对应的钩子函数来完成不同的功能。

2.1、初始化

  • beforeCreate:实例刚刚在内存中被创建出来,此时还没有初始化 data 和 methods 属性。
  • created:实例已经在内存中创建好,此时 data 和 methods 已经创建好,此时还没有开始编译模板。

2.2、挂载

  • beforeMount:data 的数据可以访问和修改,而且此时的模板已经编译好了,还没有更挂载页面中。
  • mounted:这个时候已经把编译好的模板挂载到页面指定的容器里。

2.3、更新

  • beforeUpdate:状态更新之前执行此函数,此时的 data 中的数据是最新,但是界面上显示的还是旧的,因为此时还没有开始重新渲染 DOM 节点。
  • updated:实例更新完毕之后调用此函数,此时 data 中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。

2.4、销毁

  • beforeDestroy:实例销毁之前调用,在这一步,实例仍然完全可用。
  • destroyed:实例销毁后调用,调用后,Vue 实例指向的所以东西会被解绑,所有的事件监听器会被移除,所有的子实列也会被销毁。

3、生命周期示例

<div id="app">
    <p> {{msg}} </p>
</div>
let app = new Vue({
    el: '#app',
    data: {
        msg: 'Hello Vue'
    },
    beforeCreate: function() {
        console.log('beforeCreated');
        console.log(this);
    },
    created: function() {
        console.log('created');
        console.log(this);
    },
    beforeMount: function() {
        console.log('beforeMount');
        console.log(this);
    },
    mounted: function() {
        console.log('mounted');
        console.log(this);
    },
    beforeUpdate: function() {
        console.log('beforeUpdate');
        console.log(this);
    },
    updated: function() {
        console.log('updated');
        console.log(this);
    },
    beforeDestroy: function() {
        console.log('beforeDestory');
        console.log(this);
    },
    destroyed: function() {
        console.log('destroyed');
        console.log(this);
    }
});

大家可以尝试在浏览器控制器运行 app.msg = 'Hi Vue'app.$destroy() 来看效果。

4、使用用场景

生命周期描述
beforeCreate通常用于插件开发中执行一些初始化任务。
created常用于异步数据获取
beforeMount很少用。
mounted可用于获取访问数据和 DOM 元素。
beforeUpdate可用于获取更新前各种状态。
updated可用于获取更新后各种状态。
beforeDestroy可用于一些定时器或订阅的取消。
destroyed作用同上。

八、Node.js

1、定义

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。说白了就是 Node.js 可以让用 JavaScript 语言编写的程序运行在服务端,另外提供操作文件,读取系统信息等等功能。

在这里插入图片描述

针对有 Java 学习经验的同学,为了更好理解,可以拿 Java 中学习到的东西类比,Node.js 类似 JDK。

辅助前端开发开发,用它作为前端开发工具运行环境。

2、安装

2.1、普通安装方式

官方网站

在这里插入图片描述

生成环境使用选择稳定长期支持版本。普通安装方式的缺点是:切换版本比较麻烦。

2.2、多版本安装方式

2.2.1、安装 nvm

nvm 是一个可以在同一台机器上安装和切换不同版本 Node.js 的工具。下载nvm
注意:安装路径不要有中文字符和空格

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

安装之后,会自动配置好环境变量。

2.2.2、nvm 命令
nvm version					# 查看 nvm 的版本
nvm current					# 查看当前使用的版本
nvm list             	 	# 查看当前安装的 Node.js 所有版本
nvm install 版本号    		  # 安装指定版本的 Node.js
nvm uninstall 版本号  		  # 卸载指定版本的 Node.js
nvm use 版本号				  # 选择指定版本的 Node.js
2.2.3、安装 Node.js

安装前,需事先在 nvm 安装根目录中找到 settings.txt 文件配置一下镜像:

node_mirror: https://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/
# 安装指定版本(安装这个)
nvm install 12.10.0

# 安装最新版本
# nvm install latest

在这里插入图片描述

2.2.4、指定或切换 Node.js 版本
nvm use 12.10.0
2.2.5、验证 Node.js 安装是否成功
node -v

3、简单使用

在桌面创建⼀个 hello.js 的文件,在文件内写入如下内容:

console.log('Hello Node.js')

在桌面打开控制台,输入以下命令:

node hello.js

九、NPM

1、概述

NPM(node package manager)是 Node.js 官⽅提供的包管理工具,它已经成了 Node.js 包的标准发布平台,用于 Node.js 包的发布、传播、依赖控制。
NPM 提供了命令行⼯具,使你可以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。

针对有 Java 学习经验的同学,为了更好理解,可以拿 Java 中学习到的东西类比,NPM 类似 Maven。

npm uninstall -g cnpm

npm install cnpm@7.1.0 -g --registry=https://registry.npm.taobao.org

npm config set registry http://registry.cnpmjs.org

2、作用

在程序开发中我们常常需要依赖别⼈提供的框架,写 JS 也不例外。这些可以重复使用的框架代码被称作包(package)或者模块(module),⼀个包可以是⼀个文件夹放着⼏个文件,同时有⼀个叫做 package.json 的文件。常见的场景有以下⼏种:

  • 允许用户从 NPM 服务器下载别⼈编写的第三方包到本地使用。
  • 允许用户从 NPM 服务器下载并安装别⼈编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到 NPM 服务器供别⼈使用。

3、使用

3.1、安装

NPM 是不需要安装的,在你安装 Node.js 的时候就已经安装好了,你可以使用命令来查看你当前的 npm 版本号:

npm -v 

当然,有可能⾃带的 npm 不是最新版本,而你想要将其更新到最新版本的话,可以执行下面的命令:

npm install npm -g 

3.2、初始项目

NPM 管理包的方式有点类似 Maven,同样需要⼀个类似 pom.xml 的配置文件来对项目依赖进行管理,这个文件就是 package.json,其文件主要有以下内容:

  • name :包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格。
  • description :包的简要说明。
  • version :符合语义化版本识别规范的版本字符串。
  • keywords :关键字数组,通常用于搜索。
  • maintainers :维护者数组,每个元素要包含 name、email(可选)、web (可选)字段。
  • contributors :贡献者数组,格式与 maintainers 相同。包的作者应该是贡献者数组的第一个元素。
  • bugs :提交 bug 的地址,可以是网址或者电子邮件地址。
  • licenses :许可证数组,每个元素要包含 type(许可证的名称)和 url(链接到许可证文本的地址)字段。
  • repositories : 仓库托管地址数组,每个元素要包含 type(仓库的类型,如 git )
  • url(仓库的地址)和 path(相对于仓库的路径,可选)字段。
  • dependencies :生成环境包的依赖,一个关联数组,由包名称和版本号组成。
  • devDependencies:开发环境包的依赖,一个关联数组,由包的名称和版本号组成。

我们可以通过 npm 命令创建一个包(或者一个项目),对应 Maven 也有这样的命令。我们在磁盘新建一个包(或者项目)文件夹,打开控制台,控制台路径是刚才所建项目所在包路径,输入以下命令:

npm init        # 初始项目
npm init -y     # 初始项目,对比上面不需要输入项目的一些信息,全用默认

可以为 init 命令设置项目的⼀些初始默认值,比如:

npm set init.author.email "lony@xxx.cn"
npm set init.author.name "lony"
npm set init.license "MIT"

4、依赖管理

TIP:与 Maven 相同的是,NPM 默认也是从国外的仓库下载的,那么在国内的我们下载速度肯定是非常喜⼈的,甚至可能会下载失败,因此可以通过配置淘宝镜像来解决该问题,但是我们安装 Node.js 之前已经配置好了。

4.1、全局模式与本地模式

使用 NPM 下载的第三⽅库的安装路径可以被分为本地与全局两种模式:

  • 本地模式:默认安装⽅式 npm install xxx,下载的依赖会保存在当前包(或者项目)目录下的 node_modules 目录中。
  • 全局模式:执行 npm install -g xxx 时,该模块会被保存到全局路径下,并且该工具可以在命令行直接访问。

总而言之,当我们要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装。

4.2、依赖安装

这里我们通过安装 Vue 给大家演示如何安装依赖。到之前建好的项目下,打开命令行,输入如下命令:

npm install vue

执行完 package.json 加了⼀个 dependencies 属性,这其中就是存放当前项目所依赖的第三方库的信息,并且这个依赖会被下载到当前项目根目录下的 node_modules 目录中。

4.3、生产环境与开发环境

TIP:针对有 Java 学习经验的同学,为了更好理解,类似 Maven 添加依赖配置 scope 的效果。

NPM 管理的依赖会被分为开发环境(devDenpendencies)和生产环境(denpendencies):

  • 开发环境:只在开发时期才会用的一些依赖,如语法检查、包大小分析开发阶段的工具性质的依赖。
  • 生产环境:在项目部署上正式服务器以后还会使用到的依赖。
# 安装的依赖默认生产环境
npm install vue
# 命令等价于上面
npm install --save vue

# 安装的开发环境的依赖
npm install -D eslint
# 命令等价于上面
npm install --save-dev eslint

PS:ESlint 是一个插件化的 JavaScript 代码检测工具。

当你的项目开发完成后,需要将项目交付到对应的服务器或需要切换开发的电脑时,需要重新下载项目所需要的所有依赖,那么此时区分开发环境与生产环境的依赖就有比较大的作用:

  • 开发环境下,你只需要在项目根目录执行 npm install 命令即可直接将开发环境与生产环境的所有依赖都下载到本地。
  • 在服务器上部署运行时只需要下载生产环境的依赖的时候,你只需要执行 npm install --production 命令,或者添加环境变量 NODE_ENV=production 以后再运行 npm install 都可以保证只安装生产环境中的依赖。

十、模块化

1、基本概念

事实上模块化开发最终的目的是将程序划分成一个个小的结构;
这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构;
这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用(导出);
也可以通过某种方式,导入另外结构中的变量、函数、对象等(引入)。

上面说提到的结构,就是模块;按照这种结构划分开发程序的过程,就是模块化开发的过程。

TIP:针对有 Java 学习经验的同学,为了更好理解,模块类似 Java 文件,导出类似权限修饰符,引入类似 Java 中的 import。

2、好处

  • 避免命名空间污染:所有代码都运行在模块作用域,不会污染全局作用域;
  • 代码复用性提高:模块可以多次加载,不同的模块之间可以互相依赖;
  • 责任分离,按需加载:不管模块中暴露了多少功能,都可以只引入自己需要的功能;
  • 无需操心模块间依赖问题:模块的加载顺序完全在模块间的引用中体现了。

基于以上的特性,大大的增加了前端代码的可维护性。

3、模块化规范

基于以上的理论与需求,最终需要落地到实现层面(代码的编写),而这也就涉及到了需要制定相应的代码层面的规范。对书写格式和交互规则的详细描述,就是模块定义规范(Module Definition Specification),市面上主流的模块化有以下规范:

模块化规范实现运行环境
AMD 规范Require.js浏览器
CMD 规范Sea.js浏览器
CommonJS 规范Node.jsNode.js
ES6 规范ECMAscript部分浏览器和 Node.js

这里我们重点了解 CommonJS 规范以及 ES6 模块化规范。

4、CommonJS 模块化规范

模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。

其特点如下:

  • 在服务器端,模块的加载是运行时同步加载的。
  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载。
  • 就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

在 Node.js 中,当每个 JavaScript 文件在执行或被 require 的时候,Node.js 其实创建了一个新的实例 var module = new Module(),这个实例名叫 module。这也就是为什么你并没有定义 module 这个变量,却能 console.log 出来而不会报错的原因。而这个实例中有个 exports 属性默认是空对象,而这个属性的里面值就是该模块被导出的的东西。其他模块要使用这些导出的东西,就得使用 require 导入。

// sum.js
var sum = function(a, b){
    return a + b;
}
// 导出模块 等价于 module.exports.sum = sum
exports.sum = sum;
// main.js
// 导入模块
var module = require('./03.sum.js');
console.log(module.sum(1, 2));

5、ES6 模块化规范

随着 2015 年 6 ⽉ ES6 的发布,JS 终于在语⾔层面上实现了模块化功能,使得在编译时就能确定模块的依赖关系,以及其输入和输出的变量,不像 CommonJS 需要在运行时才能确定(例如 FIS 这样的工具只能预处理依赖关系,本质上还是运行时解析),成为浏览器和服务器通用的模块解决⽅案。

其有以下特点:

  • 静态引入,在 JS 解析到引入指令时,会生成⼀个只读引用,只有在真正使用到的地⽅才会从引用的模块中获取到值。
  • 由于引入的引用是只读的,因此不能在外部修改被引用模块内的数据。
  • ES6 中引入的变量仅仅是引用,因此在运行时引入的值会随着模块中原本值的修改而修改,不会缓存值。

ES6 模块化是通过两个关键字 exportimport 来实现的,从字面意思来理解就是导出和导入:

  • export 有两种使用⽅式,分别是按需导出和默认导出

    • 按需导出:export {a, b, c}
    • 默认导出:export default xxx
  • import 基于两种导出⽅式,可以衍生出三种导入⽅式

    • 单独使用按需导入:import {a, b, c} from 'module'
    • 单独使用默认导入:import xxx from 'module'
    • 两者同时使用:import xxx, {a, b, c} from 'module
let a = 1;
let b = {name : "哈哈"};
let c = function() { console.log("函数") };

export {a};
export {b, c};

export default {a,b,c}
import {a} from './test.mjs'
import d, {b, c} from './test.mjs'
import obj from './test.mjs'

console.log(a);
console.log(b);
console.log(obj.a)
c();
d();

PS:由于 Node.js 默认的模块化⽅式是 CommonJS,如果要使用 ES6 的模块化则需要将 js 文件后缀名修改为 .mjs 才行。

十一、Vue CLI

Vue CLI 是 Vue.js 开发的标准工具或者说脚手架,是 Vue 官方提供的标准化开发工具(开发平台),它提供命令行和 UI 界面,方便创建 Vue 工程、配置第三方依赖、编译 vue 工程。

简单来说,Vue CLI 就是一个帮助我们快捷搭建 Vue 项目的工具。但这个工具需要大家事先安装好 Node.js 和 NPM。可以通过以下命令查看自己电脑上安装版本:

node -v
npm -v

1、安装 Vue ClI

# 安装 Vue ClI
npm install -g @vue/cli

2、创建运行项目

# 创建项目 my-project
vue create my-project
# 创建过程需要进行一些子自定义配置

# 到上面项目 my-project 根目录
cd my-project
# 运行以下命令
npm run serve

3、项目结构

my-project
├─dist                              # 打包后的输出目录,基于 webpack 将 vue 项目打包为普通 html 项目
├─node_modules                      # 当前项目本地包下载存储的文件夹
├─public                            # 静态资源存放目录
├─src                               # 源码目录
│  ├─App.vue                        # **Vue 文件,项目入口文件**
│  ├─main.js                        # **配置入口文件**
│  ├─registerServiceWorker.js       # PWA 应用,web workers 文件,判断应用是否离线
│  ├─assets                         # 存放项目需要编译的静态资源,会被 webpack 处理
│  ├─components                     # 页面间共享组件包
│  ├─router                         # 路由文件
│  ├─store                          # vuex 文件,负责 vue 的组件间通信
│  └─views                          # 视图文件,存放视图页面的 vue 文件
├─.gitignore                        # git 版本控制工具,忽略提交的配置文件
├─babel.config.js                   # babel 配置文件,用来将 ES6 转换为 ES5
├─jsconfig.json                     # JS 配置文件
├─package.json                      # npm 包管理配置文件
├─README.md                         # 项目描述文件
├─vue.config.js                     # vue + webpack 配置文件
└─yarn.lock                         # yarn 包锁定文件,锁定已安装的每个包版本,避免多环境下安装版本不一致问题

以后重点关注的是 src 和 vue.config.js。

4、vue 文件

这种文件可以当场模板,可以作为页面,一般由三个部分构成,包含:

  • template:用来编写结构,注意只能包含一个根标签。
  • style:用来编写样式,可以写普通 CSS,也可以写 LESS,SCCS 等(但需要 Webpack 支持),其还有两个属性:
    • scope,有这个属性,局部样式,反之就是全局样式。
    • lang,只能编写 CSS 语言。
  • script:用来编写数据和逻辑,这里默认使用 ES6 模块化,导入构建 Vue 对象的配置,只是其中数据需要定义名为 data 函数,在函数中返回要展示的数据。

十二 、路由

1、SPA

现代前端项目多为单页 Web 应用(SPA:single page web application 的简称,译为单页 Web 应用),而路由在其中起重要作用。简单的说 SPA 就是一个 Web 项目只有一个 HTML 页面,一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转,取而代之的是利用 JS 动态的变更页面的内容,从而达到模拟以前非单页面应用多个页面之间跳转的效果。

比如这个 Vue.js 就是单页面应用,而这个腾讯网就是非单页面应用。

最开始的 Web 应用是多页面的,比如我们之前 Web 阶段做 Web 应用全是非单页面应用,直到 AJAX 的出现,才慢慢有了 SPA。

SPA 的出现大大提高了 Web 应用的交互体验。在与用户的交互过程中,不再需要重新刷新页面,获取数据也是通过 AJAX 异步获取,页面显示变得更加流畅。

但由于 SPA 中用户的交互是通过 JS 改变 HTML 内容来实现的,页面本身的 url 并没有变化,这导致了两个问题:

  • SPA 无法记住用户的操作记录,无论是刷新、前进还是后退,都无法展示用户真实的期望内容。
  • SPA 中虽然由于业务的不同会有多种页面展示形式,但只有一个 url,对 SEO 不友好,不方便搜索引擎进行收录。

2、前端路由

前端就是为了解决上述 SPA 问题而出现的。那前端路由到底是什么呢?

简单的说,就是在保证只有一个 HTML 页面,且与用户交互时不刷新和跳转页面的同时,为 SPA 中的每次页面变更展示匹配一个特殊的 url。在刷新、前进、后退和 SEO 时均通过这个特殊的 url 来实现。为实现这一目标,我们需要做到以下二点:

  • 改变 url 且不让浏览器向服务器发送同步请求。
  • 可以监听到 url 的变化。

接下来要介绍的 hash 模式和 history 模式,就是实现了上面的功能。

2.1、hash 模式

URL 的 hash 也就是锚点(#),本质上是改变 window.locationhref 属性,我们可以通过直接赋 location.hash 来改变 href,但是页面不发生刷新。

在这里插入图片描述

由于 hash 值的变化不会导致浏览器像服务器发送请求,而且 hash 的改变会触发 hashchange 事件,浏览器的前进后退也能对其进行控制,所以在 H5 的 history 模式出现之前,基本都是使用 hash 模式来实现前端路由。

2.2、history 模式

在 HTML5 之前,浏览器就已经有了 history 对象。但在早期的 history 中只能用于多页面的跳转:

history.go(-1);       	// 后退一页
history.go(2);        	// 前进两页
history.forward();		// 前进一页
history.back();      	// 后退一页

在 HTML5 的规范中,history 新增了以下几个 API:

history.pushState();		// 添加新的状态到历史状态栈
history.replaceState();		// 用新的状态代替当前状态
history.state       		// 返回当前状态对象

HTML5 引入了 history.pushState() 和 history.replaceState() 方法,它们分别可以添加和修改历史记录条目。这些方法通常与 window.onpopstate 配合使用。

十三、vue-router

目前前端流行的三大框架,都有自己的路由实现:

  • Angular 的 ngRouter
  • React 的 ReactRouter
  • Vue 的 vue-router

vue-router 是 Vue 的官方路由插件,它和 Vue 是深度集成的,适合用于构建单页面应用。

vue-router 是基于路由和组件的,路由用于设定访问路径,将路径和组件映射起来;在 vue-router 的单页面应用中,页面的路径的改变就是组件的切换。

1、安装 vue-router

一般项目中建议在 CLI 创建项目时就直接选择需要路由,并搭配 history 模式。如果并未选择,那么安装教程请参照官网:

https://router.vuejs.org/zh/installation.html

2、vue-router 基本使用

在之前使用 Vue CLI 创建的项目的 src 目录下的 views 中,创建 TestView.vue ,随意写入一些信息。

<template>
  <div class="test">
    <h1>测试路由</h1>
  </div>
</template>

找到 router/index.js,并加入下列内容:

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import TestView from '../views/TestView.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  },
  {
    path: '/test',
    name: 'test',
    component: TestView
  }
]

const router = new VueRouter({
  routes
})

export default router

修改 App.vue 文件,内容如下:

<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/test">Test</router-link>
    </nav>
    <router-view/>
  </div>
</template>

router-link 最终会被渲染为 a 标签,当点击其时,会通过 router-view 来显示对应视图内容。

十四、Element UI

Element UI,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。

1、引入

可以参照这里,但我可以用更方便的方式引入,在项目根目录下引入一下命令:

安装

npm i element-ui -S

引入

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

2、使用

这里找表格组件,拷贝其示例代码到我们的视图文件 TestView.vue,启动项目测试访问看下效果。

五、项目实战

1、准备工作

安装 axios

 npm install axios

在主界面配置 axios

Vue.property.$axios=axios

需求,完成数据查询和删除,但注意完成数据需要发送请求到后端,若写后端这里就会遇到一个跨域问题。所以这里暂时不写后端,只是纯前端模拟数据查询和删除。

  <template>
    <el-table
      :data="users"
      style="width: 100%">
      <el-table-column
        prop="date"
        label="日期"
        width="180">
      </el-table-column>
      <el-table-column
        prop="name"
        label="姓名"
        width="180">
      </el-table-column>
      <el-table-column
        prop="address"
        label="地址">
      </el-table-column>
      <el-table-column label="操作">
      <template slot-scope="scope">
        <el-button
          size="mini"
          type="danger"
          @click="handleDelete(scope.$index, scope.row)">删除</el-button>
      </template>
    </el-table-column>
    </el-table>
  </template>

  <script>
    export default {
      created() {
        // 若有后端的话,这里使用 Axios 发送异步请求获取数据,赋值给 this.users
        this.users = [{
            date: '2016-05-02',
            name: '刘备',
            address: '上海市普陀区金沙江路 1518 弄'
          }, {
            date: '2016-05-04',
            name: '关羽',
            address: '上海市普陀区金沙江路 1517 弄'
          }, {
            date: '2016-05-01',
            name: '张飞',
            address: '上海市普陀区金沙江路 1519 弄'
          }, {
            date: '2016-05-03',
            name: '赵云',
            address: '上海市普陀区金沙江路 1516 弄'
          }]
      },
      data() {
        return {
          users: [] // 页面加载完开始是没有数据的
        }
      },
      methods: {
          handleDelete: function(index, row) {
            console.log(index, row);
            // 若有后端的话,这里使用 Axios 发送异步请求删除数据执行下面的操作
            this.users.splice(index, 1)
          }
      }
    }
  </script>
  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值