前端基础

前端基础

一、ES6

1.1 ES6简介

ECMAScript 6.0(以下简称ES6,ECMAScript是一种有Ecma国际(前身为欧洲计算机制造商协会,英文名是European Computer Manufacturers Association)通过ECMA-2262标准化的脚本程序设计语言)是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了,并且从ECMAScript 6 开始,开始采用年号来做版本,即ECMAScript 2015,就是ECMAScript 6。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,称为企业级开发语言。每年会出一个新版本。

1.2 ES6新特性

1.2.1 声明变量

1.2.1.1 let声明变量

特性1:

var声明的变量往往会跨域

let声明的变量有严格局部作用域

{
    var a = 1;
    let b = 2;
}
console.log(a);  // 在网页控制台能看到1
console.log(b);  // ReferenceError: b is not defined

特性2:

var可以多次声明变量

let只能声明一次

特性3:

var会变量提升

let不存在变量提升

console.log(a);  // undefind (不算报错)
var a = 1;
console.log(b);  // ReferenceError: b is not defined (报错)
let b = 2;

1.2.2.2 const声明常量(只读变量)

  1. const声明后不允许改变
  2. 一旦用const声明必须初始化,否则会报错
const a = 1;
a = 3;  // Uncaught TypeError: Assignment to constant variable

1.2.2 解构表达式

1.2.2.1 数组解构

let arr = [1,2,3];
// 旧方法:
let a = arr[0];
let b = arr[1];
let c = arr[2];
// 解构方法:
let [a, b, c] = arr;
console.log(a, b, c);  // 两种方法都能打印成功

1.2.2.2 对象解构

const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
}
// 旧方法:
const name = person.name;
const age = person.age;
const language = person.language;
// 解构方法:
const {name: abc, age, language} = person;
console.log(abc, age, language);  // 两种方法都能打印成功

1.2.3 字符串扩展

1.2.3.1 几个新的API

ES6为字符串扩展了几个新的API:

  • str.includes("xxx"),返回布尔值,表示是否找到了参数字符串
  • str.startsWith("xxx"),返回布尔值,表示参数字符串是否在原字符串头部
  • str.endsWith("xxx"),返回布尔值,表示参数字符串是否在原字符串尾部
let str = "hello.vue";
console.log(str.includes("e"));  // true
console.log(str.includes("hello"));  // true
console.log(str.startsWith("hello"));  // true
console.log(str.endsWith(".vue"));  // true

1.2.3.2 字符串模板

模板字符串相当于加强版的字符串,用反引号"`",除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量、表达式和函数。

特性1:多行字符串。如果是旧方法只能将多个字符串拼接。

let ss = `<div>
    <span>hello world</span>
</div>`;
console.log(ss);

特性2:字符串插入变量和表达式。变量名写在 {} 中,{} 中可以放入 JavaScript 表达式。

let name = "张三";
let age = 18;
let info = `我是${name}, 今年${age + 5}岁了!!`;
console.log(info);

特性3:字符串中调用函数

function fun() {
    return "这是一个函数";
}
let info = `字符串调用了函数:${fun()}`;
console.log(info);

1.2.4 函数优化

1.2.4.1 函数参数默认值

在 ES6 以前,我们无法给一个函数参数设置默认值,只能采用变通写法

function add(a, b) {
    // 判断b是否为空, 如果为空则给默认值1
    b = b || 1;
    return a + b;
}
console.log(add(10));

在 ES6 中可以直接给参数写上一个默认这,没传就会自动使用默认值

function add(a, b = 1) {
    return a + b;
}
console.log(add(10));

1.2.4.2 不定参数

不定参数用来表示不确定参数个数。形如:...变量名,由 ... 加上一个具名参数标识符组成。具名参数只能放在参数列表的最后,并且有且只有一个不定参数

function fun(...values) {
    console.log(values.length);
}
fun(1, 2);  // 打印2
fun(1, 2, 3, 4);  // 打印4

1.2.4.3 箭头函数

ES6 中定义函数的简写方式

  1. 定义的方法只有一个参数、一行方法体时
<script>
    // 以前声明一个方法:
    var print = function (obj) {
        console.log(obj);
    }
    // 使用箭头函数声明方法:
    var print = obj => console.log(obj);
    print("hello!");
</script>
  1. 定义的方法有多个参数、一行方法体时
// 以前声明:
var sum = function (a, b) {
    return a + b;
}
// 使用箭头函数声明:
var sum = (a, b) => a + b;
console.log(sum(10,20));
  1. 定义的方法有多个参数、多行方法体时
// 以前声明:
var sum = function (a, b) {
    c = a + b;
    return a + c;
}
//使用箭头函数声明:
var sum = (a, b) => {
    c = a + b;
    return a + c;
}
console.log(sum(10, 20));

1.2.4.4 实战:箭头函数结合解构表达式

// 需求: 声明一个对象, hello方法需要对象的个别属性
// 以前的方式:
const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
}
function hello(obj) {
    console.log("hello, " + obj.name);
}
hello(person);

// 箭头函数 + 解构表达式的写法:
var hello = ({name}) => console.log("hello, " + name);  // 在方法入参的时候解构出传入的对象的name属性
hello(person);

1.2.5 对象优化

1.2.5.1 新增的API

ES6给Object拓展了许多新的方法,比如:

  • keys(obj):获取对象的所有key形成的数组
  • values(obj):获取对象的所有value形成的数组
  • entries(obj):获取对象的所有key和value形成的二维数组。格式:[[k1, v1], [k2, v2], ...]
  • assign(dest, ...src):将多个src对象的值拷贝到dest中。(第一层为浅拷贝,第二层为深拷贝:如果果对象的属性值为简单类型(如string, number),通过Object.assign({},srcObj);得到的新对象为深拷贝;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的)

深拷贝和浅拷贝:

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

假设B复制了A,修改A的时候,看B是否发生变化:

如果B跟着**也变了**,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)

如果B**没有改变**,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
const person = {
    name: jack,
    age: 21,
    language: ['java', 'js', 'css']
}

console.log(Object.keys(person));  // ["name", "age", language]
console.log(Object.values(person));  // ["jack", 21, Array(3)]
console.log(Object.entries(person));  // [Array(2), Array(2), Array(2)]

const target = {a: 1}
const source1 = {b: 2}
const source2 = {c: 3}
Object.assign(target, source1, source2);
console.log(target);  // {a: 1, b: 2, c: 3}

1.2.5.2 声明对象简写

const name = "张三";
const age = 23;

// 以前的写法:
const person = {name: name, age: age}  // {name: "张三", age: 23}

// ES6简写的方法: 当对象中属性名与赋值的变量名相同时可以这么写
const person = {name, age}  // {name: "张三", age: 23}

1.2.5.3 对象函数的简写

let person = {
    name: jack,
    
    // 以前的写法:
    eat: function(food) {
        console.log(this.name + "在吃" + food);
    },
    
    // 箭头函数的写法:
    // 注意: 箭头函数不能使用this, 必须用 对象.属性
    eat2: food => console.log(person.name + "在吃" + food),
    
    // 非箭头函数的简写方法:
    eat3(food) {
        console.log(this.name + "在吃" + food);
    }
}

person.eat("香蕉");  // jack在吃香蕉
person.eat2("苹果");  // jack在吃苹果
person.eat3("橘子");  // jack在吃橘子

1.2.5.4 对象的拓展运算符

拓展运算符 “...” 用于取出参数对象所有可遍历的属性然后拷贝到当前对象

  1. 拷贝对象(深拷贝)
let person = {name: "Amy", age: 15}
let someone = {...person}  // 把person这个对象中的属性全部拆分出来
console.log(someone);  // {name: "Amy", age: 15}
  1. 合并对象
let name = {name: "Amy"}
let age = {age :15}
let person = {name: "Zhangsan"}
person = {...name, ...age}  // 如果两个对象的属性名重复, 后面对象的属性值会覆盖前面对象的属性值
console.log(person);  // {name: "Amy", age: 15} "Amy"把person中原来的"Zhangsan"覆盖掉了

1.2.6 类定义优化

1.2.6.1 ES5中的类定义

// 在ES5中使用function来定义类
function Shape() {
    // 私有属性
    var x = 1;
    var y = 2;
    // 公有属性
    this.x_public = 1;
    this.y_public = 2;
    /*
        在Javascript中,函数是Function类的实例,Function间接继承自Object,
        所以,函数也是一个对象,因此,我们可以用赋值的方法创建函数,
        当然,我们也可以将一个函数赋给类的一个属性变量
    */
    // 静态属性、静态方法:是属于类的, 不是属于对象的
    Shape.z = 3;
    Shape.show_static() {
        console.log("静态方法")
    }
    // 同样, 使用var定义的方法为私有方法, 用this.来定义的方法为公有方法
    var show = function() {
        console.log("我是私有show方法");
    }
    this.show_public = function() {
        console.log("我是公有show方法")
    }
    
    // 构造函数, ES5中并不支持构造函数, 我们只能自己模拟构造函数
    var init = function() {
        // 构造函数代码
        this.x_public = 11;
        this.y_public = 22;
    }
    init(); // 直接执行
}

// 创建定义好的Shape类对象
var shape = new Shape();
console.log(shape.x); // 会报错, 因为使用var来定义的属性是私有属性
console.log(shape.x_publick); // 访问对象的公有属性
shape.show_public(); // 调用对象的公有方法


// 带参数的构造函数
function Shape2(ax, ay) {
    this.x_public = 1;
    this.y_public = 2;
    // 模拟构造函数, 把参数传入
    var init = function() {
        this.x_public = ax;
        this.y_public = ay;
    }
    init();
}
// 创建带参数的Shape2类对象
var shape2 = new Shape(20, 30);
console.log(shape2.x_public);

1.2.6.2 ES6中类的定义

// 在ES6中新增了Class关键字可以用来定义类
Class Shape {
    // 同样, 私有属性/方法 用var定义; 公有属性/方法用 this.定义
    this.x_public = 1;
    this.y_public = 2;
    // ES6中提供了constructor专门用来定义构造函数
    constructor(ax, ay) {
        this.x_public = ax;
        this.y_public = ay;
    }
    // ES6中静态属性的定义同ES5, 对于静态方法有static关键字来定义
    Shape.z = 3;
    static show_static(){
        console.log("我是静态方法");
    }
}

1.2.7 map和reduce

数组中新增了map和reduce方法

  1. map方法:接收一个函数,将原数组中的所有元素用这个函数处理放入新数组返回
let arr = ['1', '20', '-5', '3']
arr = arr.map(item => item*2);
console.log(arr);  // [2, 40, -10, 6]
  1. reduce方法:为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素
let arr = [2, 40, -10, 6]
// arr.reduce(callback, [initialValue])
/**
* callback回调函数中可以传4个参数:
* 1. previousValue(上一次调用回调函数返回的值, 或者是提供的初始值initialValue, 如果没有指定初始值就默认从数组中第一个元素开始)
* 2. currentValue(数组中当前正在被处理的元素)
* 3. index(当前元素在数组中的索引, 没有就默认从数组中的第二个元素开始)
* 4. array(调用reduce的数组, 没有就默认为当前数组)
*/
let result = arr.reduce((a, b) => {
    console.log("上一次处理后的元素:" + a);
    console.log("当前正在处理的元素:" + b);
    return a + b;
});
console.log("最终结果为:" + result);

// 打印结果为:
/*
    上一次处理后的元素:2
    当前正在处理的元素:40
    上一次处理后的元素:42
    当前正在处理的元素:-10
    上一次处理后的元素:32
    当前正在处理的元素:6
    最终结果为:38
*/

1.2.8 Promise

在 JavaScript 的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现。一旦有一连串的 ajax 请求 a,b,c,d,... 后面的请求依赖前面的请求结果,就需要层层嵌套。这种缩进和层层嵌套的方式,非常容易造成上下文代码混乱,我们不得不非常小心翼翼处理内层函数与外层函数的数据,一旦内层函数使用了上层函数的变量,这种混乱程度就会加剧......总之,这种“层叠上下文”的层层嵌套方式,着实增加了神经的紧张程度。使用Promise可以封装异步操作。

案例:用户登录,并展示该用户的各科成绩。在页面发送两次请求:

  1. 查询用户,查询成功说明可以登录
  2. 查询用户成功,查询科目
  3. 根据科目查询结果,获取成绩

分析:此时后台应该提供三个接口,一个提供用户查询接口,一个提供科目接口,一个提供各科成绩接口,为了渲染方便,最好响应 json 数据。

// 以前的嵌套写法:
// 第1层: 查询当前用户信息
// 第2层: 按照当前用户的id查询出用户的课程
// 第3层: 按照当前课程id查询出课程的分数
$.ajax({
    url:"mock/user.json",  // 准备json文件, 模拟后台响应回来的数据
    success(data) {
        console.log("查询到的用户:", data);
        $.ajax({
            url: `mock/user_corse_${data.id}.json`,
            success(data) {
                console.log("查询到的课程:", data);
                $.ajax({
                    url: `mock/corse_score_${data.id}.json`,
                    success(data){
                        console.log("查询到的分数:", data)
                    },
                    error(error){...}
                });
            },
            error(error){...}
        });
    },
    error(error){...}
});
    
// 使用Promise封装异步操作
// Promise对象创建的时候可以传入一个函数
// resolve方法: 请求成功后把数据接着往下传
// reject方法: 请求失败后把异常往下传
let p = new Promise((resolve, reject) => {
    $.ajax({
        url: "mock/user.json",
        success: function(data) {
            console.log("查询到的用户:", data);
            resolve(data);  // 把data往下传
        },  
        error: function(err) {
            reject(err);  // 把err往下传
        }
    });
});

// then可以接收resolve方法中传下来的data, 然后写上一步请求成功后接着做的事
// catch可以接收reject方法中传下来的err, 然后写上一步请求失败后接着做的事
p.then((obj) => {  // 这里的obj就是上一步传下来的data
    console.log("上一步的结果为:", obj);
    return new Promise((resolve, reject) => {  // 因为我们还要把data接着往下传, 所以这里的then需要返回一个Promise
        $.ajax({
            url: `mock/user_corse_${obj.id}.json`,
            success: function(data) {  // 这里的data是请求成功得到的数据
                console.log("查询到的课程:", data);
                resolve(data);  // 把data往下传
            },  
            error: function(err) {
                reject(err);
            }
        })
    })
}).then((obj) => {  // 上一步返回的是Promise所以在这还可以接着.then做下一步的事
    console.log("上一步的结果为:", obj);
    $.ajax({
        url: `mock/corse_score_${obj.id}.json`,
        success: function(data) {
            console.log("查询到的分数:", data);
        },  
        error: function(err) {
        }
    })
}).catch((err) => {
    // 失败后做的事
})
// 进一步简化: 将返回Promise封装为一个方法
function get(url) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            success: function(data) {  // 请求成功后返回的数据
                resolve(data);  // 把data往下传
            },  
            error: function(err) {
                reject(err);  // 把err往下传
            }
        });
    })
}

get("mock/user.json")
    .then((obj) => {
        console.log("查询到的用户:", obj);
        return get(`mock/user_corse_${obj.id}.json`);
    })
    .then((obj) => {
        console.log("查询到的课程:", obj);
        return get(`mock/corse_score_${obj.id}.json`);
    })
    .then((obj) => {
        console.log("查询到的课程:", obj);
    })
    .catch((err) => {
    console.log("出现异常!");
    });

1.2.9 模块化

模块化就是把代码进行拆分,方便重复利用,类似 Java 中的导包:要使用一个包必须先导包。而 JS 中没有包的概念,换来的是模块

1.2.9.1 模块化雏形

aaa.js:负责导出

// 匿名函数的写法, 闭包的方式, 可以防止命名冲突, 因为一个函数就是一个单独的作用域
var modelA = (function() { 
    var obj = {}; // 定义一个对象
    
    var name = "小明";
    var age = 22;
    
    function sum(num1, num2) {
        return num1 + num2;
    }
    // 给对象赋值
    obj.name = name;
    obj.age = age;
    obj.sum = sum;
    // 将这个对象作为当前匿名函数的返回值, 返回给modelA
    return obj;
})() // 后面加一个小括号表示立即执行

bbb.js:负责导入

;(function() {
    // 可以直接使用导出的对象
    console.log(modelA.name);
    console.log(modelA.age);
    console.log(modelA.sun(10, 20));
})()

index.html:引入两个js文件看效果

<body>
    <h1>hello world</h1>
    <script src="./aaa.js"></script>
    <script src="./bbb.js"></script>
</body>

1.2.9.2 CommonJS规范的模块化

webpack模块化的实现方式,需要nodejs作为底层支撑,不然无法解析

aaa.js:负责导出

// 不再需要匿名函数, 因为在模块化规范里, 一个js文件就是一个模块  
var name = "小明";
var age = 22;

function sum(num1, num2) {
    return num1 + num2;
}

// 导出, 要导出什么数据就把数据放到module.exports的对象中
// (当对象key和value同名时也可以用ES6的增强写法)
module.exports = {
    name: name,
    age: age,
    sum: sum
}

bbb.js:负责导入

// 因为在模块化规范里, 一个js文件就是一个模块,
// 直接用关键字require("js文件路径")就可以获取到导出的模块对象
var moduleA = require("./aaa.js");
console.log(moduleA.name);
console.log(moduleA.age);
console.log(moduleA.sum(10, 20));

// 也可以用ES6对象解构的写法
var {name, age, sum} = require("./aaa.js");
console.log(name);
console.log(age);
console.log(sum(10, 20));

index.html:引入两个js文件看效果

<body>
    <h1>hello world</h1>
    <script src="./aaa.js"></script>
    <script src="./bbb.js"></script>
</body>

1.2.9.3 ES6规范的模块化(常用)

模块功能主要由两个命令构成:export 和 mport

  • export 命令用于规定模块的对外接口(export不仅能导出对象,一切 JS 变量都可以导出。比如基本类型变量、函数、数组、对象)
  • import 命令用于导入其他模块提供的功能

aaa.js:负责导出

导出方式一:先定义,再导出

var name = "小明";
var age = 22;

export {
    name, age
}

导出方式二:边定义边导出

export var name = "小明";
export var age = 22;

函数和类的导出:

// 函数的先定义后导出
function sum(num1, num2) {
    return num1 + num2;
}
export {
    sum
}

// 函数的边定义边导出
export function sum(num1, num2) {
    return num1 + num2;
}

// 类的先定义后导出
function Person() { // ES5类的写法
    var name = "";
    var age = 0;
    // ES5定义类的构造器
    (function (name, age) {
        this.name = name;
        this.age = age;
    })()
    // 定义类的方法
    run() {
        console.log(this.name + "在奔跑");
    }
}

class Person { // ES6类的写法
    name = "";
    age = 0;
    // ES6直接用constructor关键字就可以定义类的构造器
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    // 定义类的方法
    run() {
        console.log(this.name + "在奔跑");
    }
}

默认导出:导入者在使用默认导出时可以自行给数据命名。注意:一个js文件只能有一个默认导出

// 先定义后导出的默认导出
function sum(num1, num2) {
    return num1 + num2;
}
export default sum;

// 边定义边导出的默认导出
export default function(num1, num2) {
    return num1 + num2;
}

bbb.js:负责导入

// 导入数据
import {name, age} from ",/aaa.js";
console.(name);
console.(age);

// 导入函数/类
import {sum, Person} from ",/aaa.js";
console.(sum(10, 20));
var p = new Person("小明", 18); // 根据导入的对象类型创建对象
console.log(p.run()); // 使用对象的方法

// 导入export default的内容
import moduleA from "./aaa.js"; // 这里的moduleA是在导入时自定义的名
console.log(moduleA(10, 30)); // modelA的类型是根据导出时的类型来决定的, 这里的moduleA就是一个函数

// 统一全部导入
import * as moduleA_All from "./aaa.js"; // 用通配符把所有导出的内容放入一个moduleA_All的对象里
console.(moduleA_All.name);
console.(moduleA_All.age);
console.(moduleA_All.sum(19, 38));

index.html:引入两个js文件看效果

<body>
    <h1>hello world</h1>
    <!-- 引入js文件, 并指定类型为模块 -->
    <script src="./aaa.js" type="module"></script>
    <script src="./bbb.js" type="module"></script>
</body>

二、Vue

2.1 MVVM思想

  • M:即 Model,模型,包括数据和一些基本操作
  • V:即 View,视图,页面渲染效果
  • VM:即 View-Model,模型与视图间的双向操作(无需开发人员干涉)

在 MVVM 之前,开发人员需要从后端获取需要的数据模型,然后要通过 DOM 操作 Model 渲染到 View 中。而后当用户操作视图,我们还需要通过 DOM 获取 View中的数据,然后同步到 Model中。

而在 MVVM 中,VM 要做的事就是把 DOM 操作完全封装起来,开发人员不用再关心 Model 和 View 之间是如何相互影响的。

  • 只要我们 Model 发生了改变,View 上自然就会表现出来
  • 当用户修改了 View,Model 中的数据也会跟着改变

把开发人员从繁琐的 DOM 操作中解放出来,把关注点放在如何操作 Model 上。

2.2 Vue简介

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

官网:http://cn.vuejs.org/

教程参考:http://cn.vuejs.org/v2/guide/

Git地址:http://github.com/vuejs

2.3 入门案例

2.3.1 安装

Step1:创建一个文件夹作为Vue项目,用VSCode打开

Step2:在终端用npm初始化项目,表示该项目是由npm来管理

npm init -y

Step3:用npm安装Vue

npm install vue

Step4:创建一个html文件,在<script>标签中引入vue.js即可使用Vue

<!-- 引入vue.js即可使用vue -->
<script src="./node_modules/vue/dist/vue.js"></script> 

2.3.2 Vue声明式渲染

<body>
    
    <div id="div1">
        <!-- {{name}}表示动态的从Vue的数据区中取出名为name属性的值 -->
        <h1>{{name}}, 非常帅</h1>  
    </div>
    <!-- 引入vue.js即可使用vue -->
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",  // Vue对象管控的元素的id
            data: {  // 存放当前Vue实例的所有数据
                name: "张三"
            }
        });
        vm.name = "李四";  // 改动后页面会实时跟着改变(即Model发生了改变, View也跟着改变)
    </script>
</body>

2.3.3 双向绑定

Model变化,View随之变化,反之亦然。

<body>
    
    <div id="div1">
        <!-- v-model="num"表示这个输入框中的值和Vue的数据区中的num属性进行绑定(双向绑定)
            在输入框中输入数据Vue的数据区中的num属性的值也会随之改变(即View改变, Model随之改变) -->
        <input type="text" v-model="num">
        <h1>{{name}}, 非常帅, 有{{num}}个人为他点赞!</h1>  
    </div>

    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",  // Vue对象管控的元素的id
            data: {
                name: "张三",
                num: 0
            }
        });
    </script>
</body>

2.3.4 事件处理

<body>
    
    <div id="div1">
        <input type="text" v-model="num">
        <!-- v-on用来绑定事件, click表示单击事件, ="表示单击后想要做什么"(当操作复杂时也可以用方法)
            每点击一次botton按钮, Vue数据区中的就会num++, 而input输入框与num绑定,
            所以输入框中的数值也会跟着改变, h1中的{{num}}是从Vue的数据区中取值,
            所以h1中显示的数据也会跟着改变 -->
        <botton v-on:click="num++">点赞</botton>
        <!-- 单击事件绑定Vue中的cancel方法 -->
        <botton v-on:click="cancel">取消点赞</botton>
        <h1>{{name}}, 非常帅, 有{{num}}个人为他点赞!</h1>  
    </div>

    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",  // Vue对象管控的元素的id
            data: {
                name: "张三",
                num: 0
            },
            methods: {  // 用来存放当前Vue实例的所有方法
                cancel() {
                    this.num--;
                }
            }
        });
    </script>
</body>

2.4 Vue指令

2.4.1 插值表达式

2.4.1.1 双花括号

格式:{{表达式}}

  • 表达式支持JS语法,可以调用JS内置函数(必须有返回值)
  • 表达式必须有返回结果,例如:1 + 1。没有返回结果的表达式不允许使用,例如:let a = 1 + 1
  • 可以直接取Vue实例中定义的数据或函数

注意:花括号只能完全显示出属性的值,如果属性是一个html样式的字符串,不会转变为样式。比如name = "<h1>hello</h1>",则在页面上会显示<h1>hello</h1>而不会显示样式。

2.4.1.2 插值闪烁

使用花括号当网速慢时(在浏览器的开发者页面中的Network改为Slow 3G可以模拟网速慢的效果),会直接把{{name}}这个插值表达式给显示在页面上,即页面没有完全渲染之前把插值表达式显示出来,这就是插值闪烁。可以使用v-text指令来解决插值闪烁问题,v-text会等Vue属性的内容完全渲染后再显示到页面上,而不会把插值表达式显示出来。

2.4.1.3 v-text 和 v-html

使用v-text可以解决花括号的插值闪烁的问题,使用v-html可以解决花括号html样式不显示的问题。

<body>
    <div id="div1">
        <!-- 花括号方式取值 显示:<h1>hello</h1> (会有插值闪烁, 不带<h1>样式) -->
        {{name}} {{1 + 1}} {{hello()}}<br/>
        <!-- 使用v-text取值 显示:<h1>hello</h1> (没有插值闪烁, 不带<h1>样式) -->
        <span v-text="name"></span>
        <!-- 使用v-html取值 显示:hello (带<h1>样式)-->
        <span v-html="name"></span>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el:"#div1",
            data: {
                name: "<h1>hello</h1>"
            },
            methods: {
                hello() {
                    return "world";
                }
            }
        })
    </script>
</body>

2.4.2 v-bind

插值表达式是给标签体内绑定值的,如果想在html标签的属性中取到Vue实例的数据,可以用v-bind指令

可以简写为 :属性名

<body>
    <div id="div1">
        <!-- 使用v-bind:标签中的属性名="Vue中的数据名" -->
        <a v-bind:href="link"></a>
        <!-- v-bind对于class和style属性有特殊效果
            可以通过v-bind:class="{active: 绑定Vue实例中的一个boolean类型的属性}"
            (这里花括号的意思是把active和'text-danger'封装成一个对象)
            来决定class属性中是否有active、text-danger等值
            注意: 因为text-danger中有特殊字符-, 所以要用单引号包裹起来 -->
        <span v-bind:class="{active: isActive, 'text-danger': hasError}">你好</span>
        <span :style="{color: colorVue, 'font-size': sizeVue}">你好1</span>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                link: "http://www.baidu.com",
                isActive: true,
                hasError: true,
                colorVue: 'red',
                sizeVue: '36px'
            }
        })
    </script>
</body>

注意: 插值表达式和v-bind都的单向绑定,即数据变化会引起页面的变化,但是页面的变化不会改变数据。(比如直接在页面上修改color: blue把颜色改为blue,页面的颜色会变,但是Vue中的数据不会改变)

2.4.3 v-model

一般用于表单项和自定义组件

<body>
    <div id="div1">
        精通的语言: <br/>
        <!-- 使用v-model绑定Vue数据中的language属性
            这三个input都属于同一组多选框, 绑定同一个language属性即可
            它们选定的value会被保存在Vue中的language数组中 -->
        <input type="checkbox" v-model="language" value="Java">Java<br/>
        <input type="checkbox" v-model="language" value="PHP">PHP<br/>
        <input type="checkbox" v-model="language" value="Python">Python<br/>
        <!-- 数组的join方法, 可以将数组连接成字符串显示 -->
        选中了:{{language.join(",")}}
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                language: []
            }
        })
    </script>
</body>

2.4.4 v-on

2.4.4.1 基本用法

用来绑定事件

可以简写为 @事件名

<body>
    <div id="div1">
        <!-- 事件中直接写JS片段 -->
        <botton v-on:click="num++">点赞</botton>
        <!-- 事件指定一个回调函数, 必须是Vue实例中定义的函数 -->
        <botton @click="cancle">取消点赞</botton>
        <br/>
        获得了{{num}}个赞
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                num: 0
            },
            methods: {
                cancle(){
                    this.num--;
                }
            }
        })
    </script>
</body>

2.4.4.2 事件修饰符

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理DOM事件细节。

为了解决这个问题,Vue为 v-on 提供了事件修饰符。修饰符是由点开头的指令后缀来表示的:

  • .stop:阻止事件冒泡到父元素
  • .prevent:阻止默认事件发生
  • .capture:使用事件捕获模式
  • .self:只有元素自身触发事件才执行(冒泡或捕获的都不执行)
  • .once:只执行一次
<body>
    <div id="div1">

        <!-- 事件修饰符 -->
        
        <!-- 这里有一个事件冒泡, 即点了小div就相当于点了大div, 所以我们会看到两次弹窗(事件冒泡到父元素)
            可以将小div的事件加上.stop即可阻止事件冒泡到父元素 -->
        <!-- 我们想要大div只能被点击1次, 可以给事件加上.once -->
        <div style="border: 1px solid red; padding: 20px" v-on:click.once="hello">
            大div
            <div style="border: 1px solid blue; padding: 20px" @click.stop="hello">
                小div<br/>
                <!-- 小div弹窗完了之后会有一个跳转页面的默认事件,
                    这里我们可以用.prevent来阻止这个默认事件,
                    这样我们就只会看到弹窗而不会看到跳转页面 -->
                <a href="http//www.baidu.com" @click.prevent>去百度</a>
                <!-- 使用prevent阻止默认事件后我们还可以给它指定一个回调函数 -->
                <a href="http//www.baidu.com" @click.prevent="hello">去百度</a>
                <!-- 还可以把prevent和stop连用, 既阻止了默认事件, 又阻止了事件冒泡 -->
                <a href="http//www.baidu.com" @click.prevent.stop="hello">去百度</a>
            </div>
        </div>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                num: 0
            },
            methods: {
                cancle(){
                    this.num--;
                },
                hello(){
                    alert("点击了");
                }
            }
        })
    </script>
</body>

2.4.4.3 按键修饰符

在监听键盘事件时,我们经常需要检查常见的键值(KeyCode)。Vue允许为 v-on 在监听键盘事件的时候添加按键修饰符,即一些常用的按键,让我们不用去记忆常用按键的KeyCode:

  • .enter
  • .tab
  • .delete:可以捕获删除和退格键
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
<body>
    <div id="div1">

        <!-- 按键修饰符 -->
        
        <!-- 给这个输入框绑定监听键盘事件, 按键盘up键表示num+2, 按键盘down键表示num-2 -->
        <input type="text" v-model:"num" @keyup.up="num+=2" @keyup.down="num-=2"><br/>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                num: 0
            }
        })
    </script>
</body>

2.4.4.4 组合按钮

可以用如下修饰符来实现仅在按下相应按键时才出发鼠标或键盘事件的监听器:

  • .ctrl
  • .alt
  • .shift
<body>
    <div id="div1">

        <!-- 组合按钮 -->
        
        <!-- 此时按住ctrl加鼠标单击才能触发事件 -->
        <input type="text" v-model:"num" @click.ctrl="num=10"><br/>
        <!-- 此时按住alt加键盘up键才能触发事件 -->
        <input type="text" v-model:"num" @keyup.alt.up="num=20"><br/>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                num: 0
            }
        })
    </script>
</body>

2.4.5 v-for

2.4.5.1 遍历数组

<body>
    <div id="div1">
        <ul>
            <!-- 当前元素 in 待遍历的数组 (类似Python的 for xx in xxs) -->
            <li v-for="user in users">
                {{user.name}}==>{{user.gender}}==>{{user.age}}
            </li>
        </ul>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                users: [
                    {name: '柳岩', gender: '女', age: 21},
                    {name: '张三', gender: '男', age: 23},
                    {name: '杨幂', gender: '女', age: 25},
                    {name: '古力娜扎', gender: '女', age: 22},
                    {name: '李四', gender: '男', age: 18},
                ]
            }
        })
    </script>
</body>

2.4.5.2 数组索引

<body>
    <div id="div1">
        <ul>
            <!-- (当前元素, 当前元素在数组中的索引) in 待遍历的数组 -->
            <li v-for="(user, index) in users">
                当前索引:{{index}}==>{{user.name}}==>{{user.gender}}==>{{user.age}}
            </li>
        </ul>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                users: [
                    {name: '柳岩', gender: '女', age: 21},
                    {name: '张三', gender: '男', age: 23},
                    {name: '杨幂', gender: '女', age: 25},
                    {name: '古力娜扎', gender: '女', age: 22},
                    {name: '李四', gender: '男', age: 18},
                ]
            }
        })
    </script>
</body>

2.4.5.3 遍历对象

<body>
    <div id="div1">
        <ul>
            
            <li v-for="user in users">
                <!-- 遍历对象可以依次取出对象中的各个属性 -->
                <!-- 当前属性的值 in 待遍历的对象 -->
                对象信息:
                <span v-for="value in user">{{value}}==></span>
                <!-- (当前属性的值, 当前属性的键) in 待遍历的对象 -->
                对象信息:
                <span v-for="(value, key) in user">{{key}}:{{value}}==></span>
                <!-- (当前属性的值, 当前属性的键, 当前属性的索引) in 待遍历的对象 -->
                对象信息:
                <span v-for="(value, key, index) in user">{{index}}:{{key}}:{{value}}</span>
            </li>
        </ul>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                users: [
                    {name: '柳岩', gender: '女', age: 21},
                    {name: '张三', gender: '男', age: 23},
                    {name: '杨幂', gender: '女', age: 25},
                    {name: '古力娜扎', gender: '女', age: 22},
                    {name: '李四', gender: '男', age: 18},
                ]
            }
        })
    </script>
</body>

2.4.5.4 key属性

在使用v-for遍历时最好都带上一个key属性来区分不同的数据,提高Vue的渲染效率

<body>
    <div id="div1">
        <ul>
            <!-- 数组中的每个对象的id都不同, 所以 :key="对象中的id属性"
                :key其实是 v-bind:key的缩写 -->
            <li v-for="user in users" :key="user.id">
                {{user.name}}==>{{user.gender}}==>{{user.age}}
                <!-- 当遍历的是常规数组时, key可以="index" -->
                <span v-for="(value, index) in nums" :key="index"></span>
            </li>
        </ul>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                users: [
                    {id:1, name: '柳岩', gender: '女', age: 21},
                    {id:2, name: '张三', gender: '男', age: 23},
                    {id:3, name: '杨幂', gender: '女', age: 25},
                    {id:4, name: '古力娜扎', gender: '女', age: 22},
                    {id:5, name: '李四', gender: '男', age: 18},
                ],
                nums: [1,2,2,3,3,4,4]
            }
        })
    </script>
</body>

2.4.6 v-if 和 v-show

v-if:条件判断。当判断的结果为true时,所在的元素才会被渲染

v-show:同样为条件判断。当判断的结果为true时,所在的元素才会被显示

区别:v-if 是整个元素都消失了;v-show 是在元素中加上样式:style="display: none" 来隐藏元素,我们在控制台手动去掉style="display: none" 依然可以显示,而 v-if 无法操作

<body>
    <div id="div1">
        <botton @click="isShow = !isShow">点我呀</botton>
        <h1 v-if="isShow">v-if 显示</h1>
        <h1 v-show="isShow">v-show 显示</h1>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                isShow: true
            }
        })
    </script>
</body>

2.4.7 v-else 和 v-else-if

类似Java中的

if (条件判断1) {
    ...
} else if (条件判断2) {
    ...
} else {
    ...
}
<body>
    <div id="div1">
        <botton @click="num = Math.random()">点我呀</botton>
        <span>{{num}}</span>
        <h1 v-if="num >= 0.75">  <!-- &gt;是在页面中显示大于号 > -->
            看到我啦?  &gt;= 0.75
        </h1>
        <h1 v-else-if="num >= 0.5">
            看到我啦?  &gt;= 0.5
        </h1>
        <h1 v-else-if="num >= 0.2">
            看到我啦?  &gt;= 0.2
        </h1>
        <h1 v-else>  <!-- &lt;是在页面中显示小于号 < -->
            看到我啦?  &lt; 0.2
        </h1>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                num: 1
            }
        })
    </script>
</body>

2.4.8 v-for 和 v-if 组合使用

<body>
    <div id="div1">
        <ul>
            <!-- 给v-for的循环加上条件才显示 -->
            <li v-for="user in users" :key="user.id" v-if"user.gender == '女'">
                {{user.name}}==>{{user.gender}}==>{{user.age}}
            </li>
        </ul>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                users: [
                    {id: 1, name: '柳岩', gender: '女', age: 21},
                    {id: 2, name: '张三', gender: '男', age: 23},
                    {id: 3, name: '杨幂', gender: '女', age: 25},
                    {id: 4, name: '古力娜扎', gender: '女', age: 22},
                    {id: 5, name: '李四', gender: '男', age: 18},
                ]
            }
        })
    </script>
</body>

2.5 计算属性和侦听器

2.5.1 计算属性

某些结果是基于数据实时计算出来的,我们可以利用计算属性来完成渲染

<body>
    <div id="div1">
        <ul>
            <!-- 这里是总价需要用到西游记的价格*数量 + 水浒传的价格*数量来实时计算 -->
            <li>西游记,价格:{{xyjPrice}},数量:<input type="number" v-model="xyjNum"></li>
            <li>水浒传,价格:{{shzPrice}},数量:<input type="number" v-model="shzNum"></li>
            <li>总价:{{totalPrice}}</li>
        </ul>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                xyjPrice: 91.12,
                shzPrice: 88.00
                xyjNum: 1,
                shzNum: 1
            },
            // computed专门用来存放计算属性
            computed: {
                totalPrice() {  // 这里将totalPrice声明为一个方法
                    return this.xyjPrice*this.xyjNum + this.shzPrice*this.shzNum;
                }
            }
        })
    </script>
</body>

2.5.2 侦听器

侦听器 watch 可以让我们监控一个值的变化,从而做出相应的操作

<body>
    <div id="div1">
        <ul>
            <!-- 这里是总价需要用到西游记的价格*数量 + 水浒传的价格*数量来实时计算 -->
            <li>西游记,价格:{{xyjPrice}},数量:<input type="number" v-model="xyjNum"></li>
            <li>水浒传,价格:{{shzPrice}},数量:<input type="number" v-model="shzNum"></li>
            <li>总价:{{totalPrice}}</li>
            {{msg}}
        </ul>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                xyjPrice: 91.12,
                shzPrice: 88.00
                xyjNum: 1,
                shzNum: 1,
                msg: ""
            },
            // computed用来存放当前Vue实例的所有计算属性
            computed: {
                totalPrice() {  // 这里将totalPrice声明为一个方法
                    return this.xyjPrice*this.xyjNum + this.shzPrice*this.shzNum;
                }
            }
            // watch用来存放当前Vue实例的所有侦听器
            watch: {
                // 监控的属性(改变后的新值, 以前的旧值)
                xyjNum(newVal, oldVal){
                    // 可以使用弹窗来查看新值和旧值显示是否正确:
                    // alert("newVal: " + newVal + "; oldVal: " + oldVal);
                    if (newVal >= 3) {
                        this.msg = "库存不足";
                        this.xyjNum = 3;
                    } else {
                        this.msg = "";
                    }
                } 
            }
        })
    </script>
</body>

2.6 过滤器

过滤器通常用来处理文本格式化的操作(比如:数据库中将性别“男”存为1,性别“女”存为0,我们要根据数据库中查出来的是1还是0来显示“男”还是“女”)。

过滤器可以用在两个地方:双花括号插值表达式和 v-bind 表达式

<body>
    <div id="div1">
        <ul>
            <li v-for="user in users" :key="user.id">
                <!-- 过滤器的语法:
                    要传入过滤器函数的值 | 过滤器函数, 
                    然后过滤器函数的返回结果就会代替传入过滤器函数的值显示
                    | 称为管道符 -->
                {{user.id}}==>{{user.name}}==>{{user.gender | genderFilter}}==>{{user.age}}
            </li>
        </ul>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        let vm = new Vue({
            el: "div1",
            data: {
                users: [
                    {id: 1, name: '柳岩', gender: 0, age: 21},
                    {id: 2, name: '张三', gender: 1, age: 23},
                    {id: 3, name: '杨幂', gender: 0, age: 25},
                    {id: 4, name: '古力娜扎', gender: 0, age: 22},
                    {id: 5, name: '李四', gender: 1, age: 18},
                ]
            },
            // filters用来存放当前Vue实例的所有过滤器
            // 这里的过滤器是局部的, 只能在当前Vue实例绑定的元素中使用
            // 过滤器函数一定要有返回值
            filters: {
                genderFilter(val) {
                    if (val == 1) {
                        return "男";
                    } else {
                        return "女";
                    }
                }
            }
        })
        
        // 全局过滤器的写法: 写在Vue实例的外面, 调用方法与局部过滤器相同
        Vue.filter("gFilter", function(val) {
            if (val == 1) {
                return "男~~~";
            } else {
                return "女~~~";
            }
        })
    </script>
</body>

2.7 组件化

在大型应用开发的的时候,页面可以划分为很多部分。往往不同的页面也会有相同的部分,比如可能会有相同的头部导航。

但是如果每个页面都独自开发,这无疑增加了我们开发的成本。所以我们会把页面中各个不同的部分拆分成独立的组件,然后在不同的页面中就可以共享这些组件,避免重复开发。

在Vue里,所有的Vue实例都是组件。一个组件也可以是另一个组件的子组件。

2.7.1 全局组件

  • 组件其实也是一个Vue实例,因此它在定义时也会接收:data、methods、计算属性、侦听器等
  • 不同的是,组件不会与页面的元素进行绑定,否则就无法复用了,因此没有 el 属性
  • 但是组件渲染必须要html模板,所以增加了template属性,值就是html模板
  • 全局组件定义完毕,任何Vue实例都可以直接在html中通过组件名称来使用组件了
  • data必须是一个函数,不再是一个对象
<body>
    <div id="div1">
        <!-- 非组件化的写法为 -->
        <button @click="count++">我被点击了{{count}}次</button>
        
        <!-- 声明为组件后, 直接用组件名作为标签名即可复用 -->
        <counter></counter>
        <!-- 定义好的组件可以被任意复用多次, 且数据是相互隔离的 -->
        <counter></counter>
        <counter></counter>
        <counter></counter>
        <counter></counter>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        // 非组件化:
        let vm = new Vue({
            el: "#div1"
            data: {
                count: 0
            }
        });
        
        // 全局声明注册一个组件:
        // 使用Vue的component方法, 传入两个参数: "组件的名称", {对组件的设置}
        Vue.component("counter", {
            // 组件的模板
            template: `<button @click="count++">我被点击了{{count}}次</button>`,
            // 组件的数据, 写成一个data方法, 返回值是一个对象, 这个对象就是以前的data对象
            data() {
                return {
                    count: 0
                }
            }
        });
    </script>
</body>

2.7.2 局部组件

<body>
    <div id="div1">
        <!-- 局部组件只有注册到Vue实例以后才能在Vue实例绑定的页面元素进行使用
            使用方法同样是用组件名作为标签即可-->
        <counter></counter>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
    
    <script>
        // 局部声明一个组件对象
        const buttonCounter = {
            template: `<button @click="count++">我被点击了{{count}}次</button>`
            data() {
                return {
                    count: 1
                }
            }
        };
        
        let vm = new Vue({
            el: "#div1",
            // components存放当前Vue实例引用的所有组件
            components: {
                // key: value
                // key是我们自定义的组件名: value是我们声明的局部组件对象
                counter: buttonCounter 
            }
        });
    </script>
</body>

2.8 生命周期钩子函数

每个Vue实例在被创建的时候都要经过一系列的初始化过程:创建实例、装载模板、渲染模板等。Vue为生命周期中的每个状态都设置了钩子函数(即一个监听函数)。每当Vue实例处于不同的生命周期时,对应的钩子函数就会被触发自动调用。

生命周期:Vue对象的创建到销毁

<body>
    <div id="div1">
        <span id="numSpan">{{num}}</span>
        <button @click="num++">点赞!</button>
        <h2>{{name}}, 有{{num}}个人点赞</h2>
    </div>
    
    <script src="./node_modules/vue/dist/vue.js"></script>  
        
    <script>
        let vm = new Vue({
            el: "#div1",
            data: {
                name: "zhangsan",
                num: 100
            },
            methods: {
                show() {
                    return this.name;
                },
                add() {
                    this.num++;
                }
            },
            // beforeCreate: 注入之前
            beforeCreate() {
                console.log("========= beforeCreate ========");
                console.log("数据模型未加载: " + this.name , this.num);
                console.log("方法未加载: " + this.show());
                console.log("html模板未加载: " + document.getElementById("numSpan"));
            },
            // beforeCreate: 注入之后
            created() {
                console.log("========= created ========");
                console.log("数据模型已加载: " + this.name , this.num);
                console.log("方法已加载: " + this.show());
                console.log("html模板已加载: " + document.getElementById("numSpan"));
                // 未渲染是指此时看到的还是{{num}}这样一个插值表达式
                console.log("html模板未渲染: " + document.getElementById("numSpan").innerText);
            },
            // beforeMount: 挂载之前, 挂载即Vue实例与html元素绑定
            beforeMount() {
                console.log("========= beforeMount ========");
                console.log("html模板未渲染: " + document.getElementById("numSpan").innerText);
            },
            // mounted: 挂载之后
            mounted() {
                console.log("========= mounted ========");
                console.log("html模板已渲染: " + document.getElementById("numSpan").innerText);
            },
            // beforeUpdate: 数据更新之前
            beforeUpdate() {
                console.log("========= beforeUpdate ========");
                console.log("数据模型已更新: " + this.num);
                console.log("html模板未更新: " + document.getElementById("numSpan").innerText);
            },
            // updated: 数据更新之后
            updated() {
                console.log("========= updated ========");
                console.log("数据模型已更新: " + this.num);
                console.log("html模板已更新: " + document.getElementById("numSpan").innerText);
            }
        })
    </script>
</body>

2.9 Vue的模块化开发

2.9.1 Vue项目的安装与启动

Step1:全局安装webpack(直接在cmd命令行安装即可)

npm install webpack -g

Step2:全局安装Vue脚手架

npm install -g @vue/cli-init

Step3:初始化Vue项目

cmd进入想要创建Vue项目的文件夹,输入以下命令:

即以webpack为模板创建一个Vue项目

vue init webpack vuename(项目的名字)

Step4:启动Vue项目

cmd进入Step3创建的项目名字的文件夹,输入以下命令:

npm run dev

然后默认访问本的8080端口可以查看到该项目

2.9.2 模块化开发

项目的文件夹结构:

  • build:和项目打包相关
  • config:配置信息,包含了项目的ip、端口号等配置
  • node_modules:项目中用到的所有依赖
  • src:我们编写代码的文件夹
  • static:静态资源文件,比如存放图片文件
  • index.html:主入口页面
  • package.json:依赖包信息(类似Maven中的pom.xml)

index.html:只有一个id为app的div

<body>
    <div id="app"></div>
</body>

main.js:主程序

import Vue from 'vue'
import App from './App'
// . 表示当前目录
import router from './router'

Vue.config.productionTip = false

new Vue({
    el: "#app",
    // 我们自定义的路由名: 路由对象(定义了页面跳转的规则)
    router: router,
    // 我们自定义的组件名: 组件对象
    components{ App: App},
    // 指定模板为App组件
    template: '<App/>'
})

App.vue:单文件组件,分为三部分template、script、style

<!-- 组件的模板 -->
<template>
    <div id="app">
        <img src="./assets/logo.png">
        <!-- 路由视图
             即这里显示的内容要根据访问路径来动态决定 -->
        <router-view/>
    </div>
</template>

<!-- 以前Vue实例里写的代码 -->
<script>
    export default {
        name: "App"
        data(): {
            return {
                ...
            }
        },
        methods: {...}
    }
</script>

<!-- 组件的样式 -->
<style>
    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>

router/index.js:定义路由规则

import Vue from 'vue'
import Router from 'vue-router'
// @ 表示src目录
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

// 导出一个未命名的Router对象, 用于给main.js中导入
export default new Router({
    routes: [
        {
            path: '/',  // 访问的路径
            name: 'HelloWorld',  // 给这个路由起的名
            component: HelloWorld  // 使用HelloWorld组件(HelloWorld是项目自带的一个实例组件)
        }
    ]
})

2.9.3 自定义组件

创建一个自定义的组件Hellow.vue

<!-- 组件的模板 -->
<template>
    <div id="app">
        <h1>你好!Hellow!{{name}}</h1>
    </div>
</template>

<!-- 以前Vue实例里写的代码 -->
<script>
    export default {
        name: "App"
        data(): {
            return {
                name: "Zhangsan"
            }
        }
    }
</script>

<!-- 组件的样式 -->
<style>
    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>>

然后在router/index.js中添加一条路由规则:

import Vue from 'vue'
import Router from 'vue-router'
// @ 表示src目录
import HelloWorld from '@/components/HelloWorld'
import Hellow from '@/components/HelloWorld'

Vue.use(Router)

// 导出一个未命名的Router对象, 用于给main.js中导入
export default new Router({
    routes: [
        {
            path: '/',  // 访问的路径
            name: 'HelloWorld',  // 给这个路由起的名
            component: HelloWorld  // 使用HelloWorld组件(HelloWorld是项目自带的一个实例组件)
        },
        {
            path: '/hellow',  // 访问的路径
            name: 'Hello',  // 给这个路由起的名
            component: Hello  // 使用我们自定义的Hellow组件
        }
    ]
})

如果想使用超链接来与路由视图绑定,可以在App.vue中使用<router-link>标签来做超链接

<!-- 组件的模板 -->
<template>
    <div id="app">
        <img src="./assets/logo.png">
        <!-- 路由视图
             即这里显示的内容要根据访问路径来动态决定 -->
        <router-link to="/hellow">去Hellow</router-link>
        <router-link to="/">去首页</router-link>
        <router-view/>
    </div>
</template>

<!-- 以前Vue实例里写的代码 -->
<script>
    export default {
        name: "App"
        data(): {
            return {
                ...
            }
        },
        methods: {...}
    }
</script>

<!-- 组件的样式 -->
<style>
    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>

2.10 Vue整合ElementUI快速开发

ElementUI官方网站:http://element.eleme.cn

是一个帮我们整合了许多Vue常用组件的组件库,比如单选框、多选框、按钮等。

Step1:用npm安装ElementUI

npm i element-ui

Step2:在main.js中导入ElementUI组件库

import Vue from 'vue'
import App from './App'
import router from './router'
// 导入ElementUI
import ElementUI from 'element-ui'
// 导入ElementUI的css样式
import 'element-ui/lib/theme-chalk/index.css'

// 让导入的组件库生效(这样在项目的任何组件中都可以使用了)
Vue.use(ElementUI);

Vue.config.productionTip = false

new Vue({
    el: "#app",
    router: router,
    components{ App: App},
    template: '<App/>'
})

Step3:测试:在Hellow.vue组件中使用ElementUI

<!-- 组件的模板 -->
<template>
    <div id="app">
        <h1>你好!Hellow!{{name}}</h1>
        
        <!-- 是ElementUI中的单选框组件 -->
        <el-radio v-model="radio" label="1">备选项1</el-radio>
        <el-radio v-model="radio" label="2">备选项2</el-radio>
    </div>
</template>

<!-- 以前Vue实例里写的代码 -->
<script>
    export default {
        name: "App"
        data(): {
            return {
                name: "Zhangsan",
                // 这里给1, 单选框中默认选中的就是备选项1, 官方文档有说明
                radio: "1"  
            }
        }
    }
</script>

<!-- 组件的样式 -->
<style>
    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>>

更多ElementUI组件模板代码在ElementUI官方网站都有

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容