HtuPengyuyan

HtuPengyuyan

  • 主页
  • 归档
所有文章 友链 关于我

HtuPengyuyan

HtuPengyuyan

  • 主页
  • 归档

Node.js基础

2022-11-06

Web服务器开发

ip地址和端口号

  • ip地址用来定位计算机
  • 端口号用来定位具体的应用程序
  • 一切需要联网通信的软件都会占用一个端口号
  • 端口号的范围从0-65536之间
  • 在计算机中有一些默认端口号,最好不要去使用
    — 例如http服务的80
  • 我们在开发的过程中使用一些简单好记的就可以了,例如3000、5000等没什么含义

介绍终端

  1. 命令行窗口(小黑屏)、CMD窗口、终端、shell
    • 开始菜单 –> 运行 –> CMD –> 回车
    • 常用的指令:
      dir列出当前目录下的所有文件
      cd 目录名 进入指定的目录
      md 目录名 创建一个文件夹
      rd 目录名 删除一个文件夹
    • 目录
      . 表示当前目录
      .. 表示上一级目录

      ./style.css 同一级目录
      ../style.css 上一级目录

    • 环境变量
      需重启命令行窗口
      1
      2
      3
      4
      %USERPROFILE%\AppData\Local\Microsoft\WindowsApps;;
      D:\Microsoft VS Code\bin;
      C:\Users\Lenovo\AppData\Roaming\npm;
      C:\Users\Lenovo\Desktop\hello
      此时配置完用户变量后就可以直接用命令打开hello文件了
  • 当我们在命令行窗口打开一个文件,或者调用一个程序时
    系统会首先在当前目录下寻找文件程序,如果找到了则直接打开
    如果没有找到则会依次到环境变量path的路径去寻找,直到找到为止
    如果没有找到则报错
  • 我们可以将一些经常需要的访问程序和文件的路径添加到path中,
    这样我们就可以在任意位置来访问这些文件和程序了

引入其他模块

  • 在node中,通过require()函数来引入外部的模块
  • require()可以传递一个文件的路径作为参数,node将会自动根据该路径来引入外部模块
    1
    2
    var md=require("./test1")
    // 返回的对象是模块
    这里的路径,如果使用相对路径,必须以.或..
    模块化
  • 在Node中,一个js文件就是一个模块
  • 在Node中每一个js文件中的js代码都是独立运行在一个函数中
    而不是全局作用域,所以一个模块的中的变量和函数在其他模块中无法访问
  • 向外部暴露属性或方法
    我们可以通过exports来向外部暴露变量和方法
    只需要将需要暴露给外部的变量或方法设置为exports的属性即可
    1
    2
    3
    exports.x="我是02.module.js中的x";
    exports.y="我是02.module.js中的y";
    exports.fn=function(){}
    模块分为两大类
    核心模块
    • 由node引擎提供的模块
    • 核心模块的标识就是模块的名字
      1
      2
      3
      4
      var fs=require("fs")
      var os=require("os")
      var http=require("http")
      var path=require("path")
      常见的核心模块有文件操作的fs模块,http服务构建的http模块,path路径操作模块,os操作系统信息模块…
      文件模块
    • 由用户自己创建的模块
    • 文件模块的标识就是文件的路径(绝对路径,相对路径)
      相对路径使用.或..开头
      在Node中,没有全局作用域,只有模块作用域
      外部访问不到内部
      内部也访问不到外部

require 方法有两个作用:

  1. 加载文件模块并执行里面的代码
  2. 拿到被加载文件模块导出的接口对象

    在每个文件模块中都提供了一个对象:exports(默认是一个空对象) 可以利用exports将外部文件挂载导exports上被其他文件调用
    a.js

    1
    2
    3
    4
    5
    6
    7
    var bExports=require('./b')
    console.log(bExports.foo)
    // hello
    console.log(bExports.add(10,30))
    // 40
    bExports.readFile('./a.js')
    // 和文件管理中的readFile不一样 只是作为一个方法调用

    b.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var foo='bbb'
    exports.foo='hello'
    exports.add=function(x,y){
    return x+y
    }
    // function add(x,y){
    // return x+y
    // } 不起作用
    exports.readFile=function(path,callback){
    console.log('文件路径',path)
    }

global

  • 在node中有一个全局对象global,它的作用和网页中的window类似
    在全局创建的的变量都会作为global的属性保存
    在全局中创建的函数都会作为global的方法保存

关于运行Node.js执行函数

  • 当node在执行模块中的代码时,它会首先在代码的最顶部,添加如下代码
    1
    2
    3
    4
     function(exports,require,module,_filename,_dirname){

    // 在代码的最底部,添加了如下代码
    }
  • 实际上模块中的代码都是包装在一个函数中执行的,并且在函数执行时,同时传递了五个实参
    exports
  • 该对象用来将变量或函数暴露到外部

require

  • 函数,用来引入外部的模块

module

  • module代表的是当前模块本身
  • exports就是module的属性
  • 既可以使用exports导出,也可以使用module.exports导出

_filename
c:\Users\Lenovo\Desktop\html\03.html\xpy\node.js\test2.js:2

  • 当前模块的完整路径

_dirname
c:\Users\Lenovo\Desktop\html\03.html\xpy\node.js

  • 当前模块所在文件夹的路径

关于exports和module.exports

  • 通过exports只能使用.的方式向外暴露内部变量
    1
    exports.xxx=xxx
  • module.exports既可以通过.的形式,也可以直接赋值///(直接用这种方式以免出错)
    例子
    module.exports.属性=属性值;
    module.exports.方法=函数;
    1
    2
    module.exports.xxx=xxx
    module.exports={}

包package

  • CommonJS的包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具
  • CommonJS的包规范由包结构和包描述文件两个部分组成
  • 包结构
    用于组织包中的各种文件
  • 包描述文件
    描述包的相关信息,以供外部读取分析
包结构

实际上是一个压缩文件,解压之后为目录。符合规范的目录,应该包含如下文件:
-package.json 描述文件(说明书) 必须文件
-bin 可执行二进制文件
-lib js代码
-doc 文档
-test 单元测试

npm(Node Package Manager)

  • node package manage(node包管理器)
  • 通过npm命令安装jQuery包(npm install –save jquery),在安装时加上–save会主动生成说明书文件信息(将安装文件的信息添加到package.json里面)
  • 对于Node而言,NPM帮助其完成了第三方模块的发布、安装依赖等。借助NPM,Node与第三方模块之间形成了很好的生态系统
    npm指令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    npm -v
    <!-- 查看版本 -->
    npm
    <!-- 帮助说明 -->
    npm search 包名
    <!-- 搜索模块包 -->
    npm install 包名 /i //需npm init创建package.json //entry point入口文件
    npm install 包名 --save 安装包并添加到依赖中******
    npm install下载当前项目所需要的包
    <!-- 安装包 -->
    npm install -g 包名 //全局安装
    <!-- 删除包 -->
    npm uninstall

npm被墙问题

npm存储包文件的服务器在国外,有时候会被墙,速度很慢,所以需要解决这个问题。
参加到配置项中

1
2
3
4
npm config set registry https://npm.taobao.org;

#查看npm配置信息
npm config list;

buffer(缓冲区)

  • Buffer的结构和数组很像,操作的方法也和数组类似
  • 数组中不能存储二进制文件,而二进制就是专门用来存储二进制数据的
  • 使用buffer不需要引入模块,直接使用即可
  • buffer中存储的数据都是二进制数据,但是在显示时候都是16禁止的形式显示
  • buffer中每一个元素的范围是从00ff 0255
    将一个字符串转化为buffer
    1
    2
    3
    var str ="Hello Atguigu"
    var buf = Buffer.from(str)
    console.log(buf) //<Buffer 48 65 6c 6f 20 41 74 67 75 69 67 75>
  • 创建一个指定大小的buffer(最好别用)
    1
    var buf2= new Buffer(10) //10个字节的buffer
    一般用buffer.alloc(10)创建n个字节的buffer
    Buffer一旦确定,则不能修改,Buffer实际上是对底层内存的直接操作
    1
    var buf2 = Buffer:alloc(10)
    通过索引,来操作buf的元素
    1
    2
    3
    4
    5
    6
    buf[0] = 88;
    buf[1] = 255 ;
    buf[2] = 0xaa;
    buf[3] = 255;
    ...
    buf[10]=10; //打印结果上不会显示,因为超出了范围
  • 注:只要数字在控制台或页面中输出一定是10进制
    如果需要则调用toString(2)

读取文件

  • 在node中不能操纵相关的Dom Bom ,在浏览器中也无法操作node的相关代码
  • fs是file-system的简写,是文件系统的意思
  • Node中如果想要进行文件操作,就必须引入fs这个核心模块
  • 在fs这个核心模块中就提供了所有文件操作相关的api
  1. 使用require方法加载fs核心模块
    1
    var fs =require('fs')
  2. 读取文件
  • 第一个参数就是要读取的路径
  • 第二个参数是一个回调函数
    成功
    data 数据
    error null
    失败
    data undefined
    error 错误对象
    1
    fs.readFile('./data/hello.txt',function(error,data){})
    文件的存储是二进制数据
  1. 写入文件
    第一个参数:文件路径
    第二个参数:文件内容
    第三个参数:回调函数

良好反馈写法

1
2
3
4
5
6
7
8
fs.writerFile('./data/你好.txt','大家好,给大家介绍一下,我是Node.js',function(error){
if(error){
console.log('写入失败')
}
else{
console.log('写入成功了')
}
})

简单的http服务

  • 在Node中专门提供了一个核心模块:http
  • http这个模块的职责就是帮你创建编写服务器的
  1. 加载http核心模块
    1
    var http = require('http')
  2. 使用 http.creatServer()方法创建一个Web服务器
    1
    var server = http.createServer()
  3. 服务器要干嘛?
    提供服务:对数据的服务
    发请求
    接受请求
    处理请求
    发送响应
    注册request请求事件
    当客户端请求过来,就会自动触发服务器的request请求事件,然后执行第二个参数:回调处理
    Request 请求事件处理函数,需要接收两个参数:
    Request 请求对象
    请求对象可以用来获取客户端的一些请求信息,例如请求路径
    Reponse 响应对象
    响应对象可以用来给客户端发送响应消息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    server.on('request', function () {
    console.log('收到客户端的请求了,请求路径是:'+request.url)
    // reponse 对象有一个方法,write可以用来给客户端发送响应数据
    // write 可以使用多次,但是最后一次一定要用end来结束响应,否则客户端一直等待
    response.write('hello')
    responce.write('node.js')
    // 告诉客户端,我的话说完了,你可以给用户了
    response.end()
    })
  4. 绑定端口号
    1
    2
    3
    server.listen(3000, function () {
    console.log('服务器启动成功了,可以通过http://127.0.0.1:3000/来进行访问')
    })
    向不同页面发送响应不同内容
  • 注:多用几组if else来呈现页面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var http = require('http')
    var server = http.createServer()
    server.on('request', function (req, res) {
    console.log('收到客户端的请求了')
    var url = req.url
    if (url === '/') {
    res.end('index page')
    } else if (url === '/login') {
    res.end('login page')
    } else {
    res.end('404 Not Found')
    }
    })
    server.listen(3000, function () {
    console.log('服务器启动成功了,可以通过http://127.0.0.1:3000/来进行访问')
    })
    响应内容只能是二进制数据或者字符串
    例如
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    if(url==='/products'){
    var products=[
    {
    name:'苹果15',
    price:8888
    },
    {
    name:'苹果16',
    price:8888
    },
    {
    name:'苹果17',
    price:8888
    }
    ]
    }
    此时可以利用 res.end(JSON.stringify(products))来转化二进制

利用Content-type修改乱码

在Http协议里,Content-type就是用来告诉对方我给你发的数据内容是什么类型

1
2
res.setHeader('Content-type'.'text/plain',charset='utf-8')
res.end('hello 世界')

在服务器中编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var http = require('http')
var server=http.createServer()

server.on('request',function(req,res))
var url=req.url
if(url==='/plain'){
// text/plain就是普通文本
res.setHeader('Content-type'.'text/plain',charset='utf-8')
res.end('hello 世界')
}else if(url==='/html'){
// 如果你发送的是html格式的字符串,则也要告诉浏览器我给你发送是text/html格式的内容
res.setHeader('Content-type'.'text/html',charset='utf-8')
res.end('<p> hello<a href=''>点我</a></p>')
}

结合fs发送文件中的数据
不同资源对应的Content-Type是不一样的
一般只为字符数据才指定编码
var parseObj=url.parse(req.url),true
第二个参数为true时可以将query中的参数变成对象
parseObj.pathname相当于(url === '/')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
server.on('request', function (req, res) {
console.log('收到客户端的请求了')
var url = req.url
// 这里url可以改为pathname
if (url === '/') {
fstat.readFile('ab2.jpg', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain', charset = 'utf-8')
res.end('文件读取失败')
} else {
res.setHeader('Content-Type', 'text/html', charset = 'utf-8')
res.end(data)
//data 默认是二进制数据,可以通过.tostring转化咱们能识别的字符串
//res.end支持两种数据类型,一种是二进制,一种是字符串
// 可以通过`JSON.stringfy()`解析成一个对象
}
})
} else if (url === '/xiaoming') {
if (url === '/') {
fstat.readFile('./index.html', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain', charset = 'utf-8')
res.end('文件读取失败')
} else {
res.setHeader('Content-Type', 'image/jpeg')
res.end(data)
}
})
}
}
})
server.listen(3000, function () {
console.log('服务器启动成功了,可以通过http://127.0.0.1:3000/来进行访问')
})

初步实现Apache功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 监听server的request请求事件,设置请求处理函数
// 一个请求对应一个响应,如果在一个请求的过程中,已经结束响应了,则不能重复发送响应
// 没有请求就没有响应
// Apache服务器软件,这个软件默认有一个www目录,所有存放在www目录中的文件都可以被网址浏览到
// return两个作用
// 1.方法返回值
// 2.阻止代码继续向后执行

// 引用服务
var http = require('http');
var fs = require('fs');
// 引用模板
var template = require('art-template');
// 创建服务
var server = http.createServer();
// 公共路径
var wwwDir = 'D:/app/www';
server.on('request', function (req, res) {
var url = req.url;
// 读取文件
fs.readFile('./template-apche.html', function (err, data) {
if (err) {
return res.end('404 Not Found');
}
fs.readdir(wwwDir, function (err, files) {
if (err) {
return res.end('Can not find www Dir.')
}
// 使用模板引擎解析替换data中的模板字符串
// 去xmpTempleteList.html中编写模板语法
var htmlStr = template.render(data.toString(), {
title: 'D:/app/www/ 的索引',
files:files
});
// 发送响应数据
res.end(htmlStr);
})
})
});
server.listen(3000, function () {
console.log('running....');
})

模板引擎

  1. 安装npm unstall art-template
  2. 在需要使用的文件模块中加载art-template
    只需要使用require方法加载就可以了,require(‘art-template’)
    参数中的art-template就是你下载的包的名字
  3. 查文档 查看API
    1
    2
    3
    4
    5
    6
    var template = require('art-template')
    // render接收字符串
    var ret = template.render(`hello {{ name }}`, {
    name: 'Jack'
    })
    console.log(ret)

服务器渲染和客户端渲染的区别

  • 客户端渲染不利于SEO搜索引擎优化
  • 服务端渲染时可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
  • 所以你会发现真正的网站既不是纯异步也不是纯服务端渲染出来的
  • 例如京东的商品列表采用的是服务端渲染

处理网站中的静态资源

当浏览器收到HTML响应内容之后,就要开始从上到下依次解析,
当在解析过程中,如果发现:
link
script
img
iframe
video
audio
带有src或有href(link)
属性标签(具有外链资源)的时候,浏览器会自动对这些资源发起新的请求
为了方便的统一处理这些静态资源,所以我们约定把所有的静态资源都存放在public中

1
2
3
4
5
else if(url.indexOf(`/public/`))===0
fs.readFile('.'+url)
// 统一处理
// 如果请求路径是以/public/开头的,则我认为你要获取public中的某个资源
// 所以我们就直接可以把路径当作文件路径来进行获取

注意
在服务器中,文件中的路径就不要写相对路径,这时所有的资源是通过url标识来获取的
这里的请求路径写成/public/xxx
/在这里就是url根路径的意思

1
<link href='/public/lib/bootstrap/dist/css/bootstrap.css'>

服务器让客户端重定向

  1. 在状态码设置302临时重定向
    statusCode
  2. 在响应头中通过Location告诉客户端往哪重定向
    setHeader
  • 如果客户端发现收到服务器的响应状态码是302就会自动去响应头中找Location
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    res.statusCode=302
    res.setHeader('location','/')
    res.end()
    else{
    // 其他的都处理成404
    fd.readFile('',function(err,data){
    if(err){
    return res.end('404 Not Found.')
    }
    })
    }

Node.js REPL交互式解释器

  • 表示一个电脑的环境,类似window的终端,我们在终端输入命令,并接受系统的响应。
    使用
    调出node
    win+r调出cmd后输入node
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    $ node
    > x = 10
    10
    > var y = 10
    undefined
    > x + y
    20
    > console.log("Hello World")
    Hello World
    undefined
    > var x = 0
    undefined
    > do {
    ... x++;
    ... console.log("x: " + x);
    ... } while ( x < 5 );
    x: 1
    x: 2
    x: 3
    x: 4
    x: 5
    undefined
    > var x = 10
    undefined
    > var y = 20
    undefined
    > x + y
    30
    > var sum = _
    undefined
    > console.log(sum)
    30
    undefined

CommonJS模块规范

在Node中的JavaScript还有一个重要的概念,模块系统。

  • 模块作用域
  • 使用require方法来加载模块
  • 使用exports接口对象导出模板中的成员
    加载require
    语法:
    1
    var 自定义变量名=require('模块')
    作用:
  • 执行被加载模块中的代码
  • 得到被加载模块中的exports导出接口对象
导出exports
  • Node中是模块作用域,默认文件中所有的成员只在当前模块有效
  • 对于希望可以被其他模块访问到的成员,我们需要把这些公开的成员都挂载到exports接口对象中就可以了
    导出多个成员(必须在对象中):
    1
    2
    3
    4
    5
    6
    7
    8
    exports.a = 123;
    exports.b = function(){
    console.log('bbb')
    };
    exports.c = {
    foo:"bar"
    };
    exports.d = 'hello';
    或者
    1
    2
    3
    4
    5
    6
    module.exports={
    foo = 'hello',
    add:function(){
    return x+y;
    }
    }
    导出单个成员(拿到的就是函数,字符串):
    1
    module.exports = 'hello';

以下情况会被覆盖

1
2
3
4
5
module.exports = 'hello';
//后者会覆盖前者
module.exports = function add(x,y) {
return x+y;
}

模块原理

1
2
3
4
5
6
console.log(exports === module.exports);	//true

exports.foo = 'bar';

//等价于
module.exports.foo = 'bar';

当给exports重新赋值后,exports!= module.exports.
最终return的是module.exports,无论exports中的成员是什么都没用。

1
2
3
真正去使用的时候:
导出单个成员:exports.xxx = xxx;
导出多个成员:module.exports 或者 modeule.exports = {};

require 加载规则

  • 优先从缓存加载

  • 判断模块标识符

  • 核心模块

  • 自己写的模块(路径形式的模块)

  • 第三方模块(node_modules)

  • 第三方模块的标识就是第三方模块的名称(不可能有第三方模块和核心模块的名字一致)
    npm
    开发人员可以把写好的框架库发布到npm上
    使用者通过npm命令来下载
    使用方式:var 名称 = require(‘npm install【下载包】 的包名’)
    node_modules/express/package.json main
    如果package.json或者main不成立,则查找被选择项:index.js
    如果以上条件都不满足,则继续进入上一级目录中的node_modules按照上面的规则依次查找,直到当前文件所属此盘根目录都找不到最后报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 如果非路径形式的标识
    // 路径形式的标识:
    // ./ 当前目录 不可省略
    // ../ 上一级目录 不可省略
    // /xxx也就是D:/xxx
    // 带有绝对路径几乎不用(D:/a/foo.js)
    // 首位表示的是当前文件模块所属磁盘根目录
    // require('./a');


    // 核心模块
    // 核心模块本质也是文件,核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
    require('fs');

    // 第三方模块
    // 凡是第三方模块都必须通过npm下载(npm i node_modules),使用的时候就可以通过require('包名')来加载才可以使用
    // 第三方包的名字不可能和核心模块的名字是一样的
    // 既不是核心模块,也不是路径形式的模块
    // 先找到当前文所述目录的node_modules
    // 然后找node_modules/art-template目录
    // node_modules/art-template/package.json
    // node_modules/art-template/package.json中的main属性
    // main属性记录了art-template的入口模块
    // 然后加载使用这个第三方包
    // 实际上最终加载的还是文件

    // 如果package.json不存在或者mian指定的入口模块不存在
    // 则node会自动找该目录下的index.js
    // 也就是说index.js是一个备选项,如果main没有指定,则加载index.js文件
    //
    // 如果条件都不满足则会进入上一级目录进行查找
    // 注意:一个项目只有一个node_modules,放在项目根目录中,子目录可以直接调用根目录的文件
    var template = require('art-template');

模块标识符中的/和文件操作路径中的/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 咱们所使用的所有文件操作的API都是异步的
// 就像ajax请求一样
// 读取文件
// 文件操作中 ./ 相当于当前模块所处磁盘根目录
// ./index.txt 相对于当前目录
// /index.txt 相对于当前目录
// /index.txt 绝对路径,当前文件模块所处根目录
// d:express/index.txt 绝对路径
fs.readFile('./index.txt',function(err,data){
if(err){
return console.log('读取失败');
}
console.log(data.toString());
})

模块操作路径

1
2
3
// 在模块加载中,相对路径中的./不能省略
// 这里省略了.也是磁盘根目录
require('./index')('hello')

package.json和package-lock.json

npm 5以前是不会有package-lock.json这个文件

npm5以后才加入这个文件

当你安装包的时候,npm都会生成或者更新package-lock.json这个文件

  • npm5以后的版本安装都不要加–save参数,它会自动保存依赖信息
  • 当你安装包的时候,会自动创建或者更新package-lock.json文件
  • package-lock.json这个文件会包含node_modules中所有包的信息(版本,下载地址。。。)
  • 这样的话重新npm install的时候速度就可以提升
  • 从文件来看,有一个lock称之为锁
  • 这个lock使用来锁版本的
  • 如果项目依赖了1.1.1版本
  • 如果你重新install其实会下载最细版本,而不是1.1.1
  • package-lock.json的另外一个作用就是锁定版本号,防止自动升级

修改完代码自动重启

  • 我们在这里可以使用一个第三方命名行工具:nodemon来帮助我们解决频繁修改代码重启服务器的问题。

  • nodemon是一个基于Node.js开发的一个第三方命令行工具,我们使用的时候需要独立安装:

    1
    2
    3
    4
    5
    6
    7
    #在任意目录执行该命令都可以
    #也就是说,所有需要 --global安装的包都可以在任意目录执行
    npm install --global nodemon
    npm install -g nodemon

    #如果安装不成功的话,可以使用cnpm安装
    cnpm install -g nodemon

    安装完毕之后使用:

    1
    2
    3
    4
    node app.js

    #使用nodemon
    nodemon app.js

只要是通过nodemon启动的服务,则他会监视你的文件变化,当文件发生变化的时候,会自动帮你重启服务器。

学习express

  1. 安装
    1
    cnpm install express
  2. 引入express
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 引入express
    var express = require('express');

    // 1. 创建app
    var app = express();

    // 2.
    app.get('/',function(req,res){
    // 1
    // res.write('Hello');
    // res.write('World');
    // res.end()

    // 2
    // res.end('hello world');

    // 3
    res.send('hello world');
    })

    app.listen(3000,function(){
    console.log('express app is runing...');
    })
  3. 基本路由
    路由:
  • 请求方法
  • 请求路径
  • 请求处理函数
    get:
    1
    2
    3
    4
    //当你以get方法请求/的时候,执行对应的处理函数
    app.get('/',function(req,res){
    res.send('hello world');
    })
    post:
    1
    2
    3
    4
    //当你以post方法请求/的时候,执行对应的处理函数
    app.post('/',function(req,res){
    res.send('hello world');
    })

路由的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var express = require('express')
// 创建web服务器,命名为app
var app = express()
// 挂在路由
app.get('/', (req, res) => {
res.send('Hello World')
})
app.post('/', (req, res) => {
res.send('post Request')
})
// 启动web服务器
app.listen(80, () => {
console.log('run');
})

使用Express建立基本服务器

1
2
3
4
5
const express = require('express')
const app=express()
app.listen(80,()=>{
console.log('run')
})
  1. Express静态服务API
    使用app.use
    app.use不仅仅是用来处理静态资源的,还可以做很多的工作(body-parser的配置)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    app.use(express.static('public'));
    // 可以通过这个方便的创建一个静态资源服务器,利用这段代码就可以将public目录下的图片 css文件 Js文件对外开放了
    // http://localhost:80/images/bg.jpg
    // http://localhost:80/css/style.css
    // http://localhost:80/js/login.js
    // 注意:Express在指定静态目录中查找文件,并对外提供资源访问路径。因此,存放静态文件的目录不会出现在URL中
    app.use(express.static('files'));

    app.use('/stataic',express.static('public'));
    使用静态资源
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 引入express
    var express = require('express');

    // 创建app
    var app = express();

    // 开放静态资源
    // 1.当以/public/开头的时候,去./public/目录中找对应资源
    // 访问:http://127.0.0.1:3000/public/login.html
    app.use('/public/',express.static('./public/'));

    // 2.当省略第一个参数的时候,可以通过省略/public的方式来访问
    // 访问:http://127.0.0.1:3000/login.html
    // app.use(express.static('./public/'));

    // 3.访问:http://127.0.0.1:3000/a/login.html
    // a相当于public的别名
    // app.use('/a/',express.static('./public/'));

    //
    app.get('/',function(req,res){
    res.end('hello world');
    });

    app.listen(3000,function(){
    console.log('express app is runing...');
    });
    在Express中配置使用art-template的模板引擎
  • art-template官方文档
  • 在node中,有很多第三方模板引擎都可以使用,不是只有art-template
    • 还有ejs,jade(pug),handlebars,nunjucks

安装:

1
2
3
4
5
npm install --save art-template
npm install --save express-art-template

//两个一起安装
npm i --save art-template express-art-template

配置:

1
app.engine('html', require('express-art-template'));

使用:

1
2
3
4
5
6
app.get('/',function(req,res){
// express默认会去views目录找index.html
res.render('index.html',{
title:'hello world'
});
})

如果希望修改默认的views视图渲染存储目录,可以:

1
2
// 第一个参数views千万不要写错
app.set('views',目录路径);

示例:
app.js

1
2
3
4
5
6
7
8
9
10
var express = require('express');
var app = express();
app.use('/public/', express.static('./public/'));
app.engine('html', require('express-art-template'))
app.get('/', function (req, res) {
res.render('404.html')
})
app.listen(3000, function () {
console.log('express app is runing')
})

在Rxpress中获取表单请求数据

获取get请求数据

Express内置了一个api,可以直接通过req.query来获取数据

1
2
3
// 通过requery方法获取用户输入的数据
// req.query只能拿到get请求的数据
var comment = req.query;
获取post请求数据

获取URL中的动态参数
通过req.params对象,可以访问到URL中,通过:匹配动态参数值

1
2
3
4
5
app.get('/user/:id',(req,res) =>{
// req.params默认是一个空对象
// 里面存放着通过:动态匹配的参数值
console.log(req.params)
})

在Express中没有内置获取表单post请求体的api,这里我们需要使用一个第三方包body-parser来获取数据
安装:

1
npm install --save body-parser;

配置:
// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var express = require('express')
// 引包
var bodyParser = require('body-parser')

var app = express()

// 配置body-parser
// 只要加入这个配置,则在req请求对象上会多出来一个属性:body
// 也就是说可以直接通过req.body来获取表单post请求数据
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))

// parse application/json
app.use(bodyParser.json())

模块化路由

创建路由模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// router.js

const express = require('express')
// 创建路由对象
const router = express.Router()

// 挂载具体路由
router.get('/user/list', (req, res) => {
res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
res.send('Add new user.')
})

// 向外导出路由对象
module.exports = router

注册路由模块

1
2
3
4
5
6
7
8
9
10
11
const express = require('express')
const router = require('./router')

const app = express()

// 注册路由模块,添加访问前缀
app.use('/api', router)

app.listen(80, () => {
console.log('http://127.0.0.1')
})

添加请求前缀
类似托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单

1
2
3
4
5
6
7
8
9
var express = require('express')
var app = express()
const router = require('./router.js')
// 在这里加请求前缀
app.use('/api', router)
app.listen(80, () => {
console.log('run');
})

Express 中间件

  • 中间件是指流程的中间处理环节
  • 服务器收到请求后,可先调用中间件进行预处理
  • 中间件是一个函数,包含 req, res, next 三个参数,next() 参数把流转关系交给下一个中间件或路由
    中间件注意事项;
  • 在注册路由之前注册中间件(错误级别中间件除外)
  • 中间件可连续调用多个
  • 别忘记调用 next() 函数
  • next() 函数后别写代码
  • 多个中间件共享 req、 res对象

全局中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const express = require('express')
const app = express()

// 定义第一个全局中间件
app.use((req, res, next) => {
console.log('调用了第1个全局中间件')
next()
})
// 定义第二个全局中间件
app.use((req, res, next) => {
console.log('调用了第2个全局中间件')
next()
})

app.get('/user', (req, res) => {
res.send('User page.')
})

app.listen(80, () => {
console.log('http://127.0.0.1')
})

局部中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require('express')
const app = express()

// 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了第一个局部生效的中间件')
next()
}

const mw2 = (req, res, next) => {
console.log('调用了第二个局部生效的中间件')
next()
}

// 两种定义局部中间件的方式
app.get('/hello', mw2, mw1, (req, res) => res.send('hello page.'))
app.get('/about', [mw1, mw2], (req, res) => res.send('about page.'))

app.get('/user', (req, res) => res.send('User page.'))

app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})

中间件分类

  1. 应用级别的中间件
  • 通过app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件
  1. 路由级别的中间件
  • 绑定到express.Router()实例上的中间件,叫做路由级别的中间件。用法和应用级别中间件没有区别。应用级别中间件是绑定到app 实例上,路由级别中间件绑定到 router 实例上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const app = express()
    const router = express.Router()

    router.use(function (req, res, next) {
    console.log(1)
    next()
    })

    app.use('/', router)
  1. 错误级别的中间件
  • 用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
  • 错误级别中间件的处理函数中,必须有 4 个形参,形参顺序从前到后分别是 (err, req, res, next) 。
  • 错误级别的中间件必须注册在所有路由之后
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const express = require('express')
    const app = express()

    app.get('/', (req, res) => {
    throw new Error('服务器内部发生了错误!')
    res.send('Home page.')
    })

    // 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
    app.use((err, req, res, next) => {
    console.log('发生了错误!' + err.message)
    res.send('Error:' + err.message)
    })

    app.listen(80, function () {
    console.log('Express server running at http://127.0.0.1')
    })
  1. Express 内置中间件
  • 自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:

  • express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
    express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

  • express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

    1
    2
    app.use(express.json())
    app.use(express.urlencoded({ extended: false }))

示例
express.urlencoded({ extended: false })

1
2
3
4
5
6
7
8
9
10
11
12
const express = require('express')
const app = express()
app.use(express.urlencoded({ extended: false }))
// 用express.urlencoded({ extended: false })解析,解析完之后挂载到req.body上
app.post('/user', (req, res) => {
// postman:x-www-form-urlencoded中
console.log(req.body)
res.send('ok')
})
app.listen(80, () => {
console.log('run');
})

app.use(express.json())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const express = require('express')
const app = express()
app.use(express.json())
// 将返回回来的数据挂载到req.body中
// 在服务器中,可以可以使用req.body这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于undefined
app.post('/user', (req, res) => {
console.log(req.body)
res.send('ok')
})
app.listen(80, () => {
console.log('run');
})
// postman:body json raw
// json数据
// {
// "name":"张三",
// "age":"20"
// }

编写GET接口

router.js(路由)

1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require('express')
const router = express.Router()
router.get('/get', (req, res) => {
const query = req.query
// 通过req.query获取客户端通过查询字符串,发送服务器的数据
res.send({
status: 0, // 0表示请求成功,1表示请求失败
msg: 'GET 请求成功', // 状态的描述
data: query // 需要响应给客户端的数据
})
})

module.exports = router

index.js(接口启动)

1
2
3
4
5
6
7
8
var express = require('express')
var app = express()
const router = require('./router.js')
app.use('/api', router)
// 加上请求前缀可以判断新老接口
app.listen(80, () => {
console.log('run');
})

编写POST接口

router.js(路由)

1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require('express')
const router = express.Router()
router.post('/post', (req, res) => {
const body = req.body
// 通过req.body获取请求体中包含的url-encoded格式的数据
res.send({
status: 0, // 0表示请求成功,1表示请求失败
msg: 'POST 请求成功', // 状态的描述
data: body // 需要响应给客户端的数据
})
})

module.exports = router

index.js(接口启动)

1
2
3
4
5
6
7
8
9
10
var express = require('express')
var app = express()
// 配置表单中间件
app.use(express.urlencoded({ extends: false }))
const router = require('./routerpost .js')
app.use('/api', router)
// 加上请求前缀可以判断新老接口
app.listen(80, () => {
console.log('run');
})

HTTP协议

(HTTP协议详细解释)[HTTP协议、GET请求和POST请求的区别 - 掘金 (juejin.cn)]

什么是HTTP协议

HTTP协议:是W3C制定的一种超文本传输协议。(通信协议:发送消息的模板提取被制定好)。

HTTP请求协议包括四部分

  • 请求行

  • 请求头

  • 空白行

  • 请求体

请求行

包括三部分:

  • 第一部分:请求方式(7种)

    • get(常用的)
    • post(常用的)
    • delete(常用的)
    • put(常用的)
    • head(常用的)
    • options(常用的)
    • trace(常用的)
  • 第二部分:URI

    • URI:统一资源标识符。代表网络中某个资源的名字。但是通过URI是无法定位资源的。
    • URI和URL什么关系,有什么区别?
      • URL包括URI
      • http://localhost:8080/servlet05/index.html 这是URL。
      • /servlet05/index.html 这是URI

    第三部分:HTTP版本协议号 请求头

    请求的主机

    主机的端口

    浏览器信息

    平台信息

    cookie信息

    …. 空白行

    空白行是用来区分“请求头”和“请求体” 请求体

    向服务器发送的具体数据 HTTP的响应协议(S – > B)

    HTTP的相应协议包括:4部分

    • 状态行
    • 响应头
    • 空白行
    • 响应体

    HTTP响应协议的具体报文:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    HTTP/1.1 200 ok                                     状态行
    Content-Type: text/html;charset=UTF-8 响应头
    Content-Length: 160
    Date: Mon, 08 Nov 2021 13:19:32 GMT
    Keep-Alive: timeout=20
    Connection: keep-alive
    空白行
    <!doctype html> 响应体
    <html>
    <head>
    <title>from get servlet</title>
    </head>
    <body>
    <h1>from get servlet</h1>
    </body>
    </html>

    状态行(三部分组成)

    • 第一部分:协议版本号(HTTP/1.1)

    • 第二部分:状态码(HTTP协议中规定的响应状态号。不同的响应结果对应不同的号码。)

      • 200 表示请求响应成功,正常结束。
      • 400 表示访问的资源不存在,通常是因为要么是你路径写错了,要么是路径写对了,但是服务器中对应的资源并没有启动成功,总之404错误是前端错误。
      • 450 表示前端发送的请求方式与后端请求的处理方法不一致发送:
        • 比如:前端是POST请求,后端的处理方式按照get方法进行处理时,发送405
        • 比如:前端是GET请求,后端的处理方式按照post方法进行处理时,发送405
      • 500 表示服务器端的程序出现了异常。一般会认为是服务器端的错误导致的。
      • 以4开始的,一般是浏览器端的错误导致的。
      • 以5开始的,一般是服务器端的错误导致的。

      ​ 第三部分:状态的描述信息

      • ok 表示正常成功结束
      • not found 表示资源找不到
    • 响应头

      • 响应的内容类型
      • 响应的内容长度
      • 响应的时间
      • …
    • 空白行

      • 用来分隔“响应头”和“响应体”的。
    • 响应体

      • 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果。

    具体报文:GET请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /servlet05/getServlet?username=lucy&userpwd=1111 HTTP/1.1 请求行
Host: localhost:8080 请求头
Connection: keep-alive
sec-ch-ua: “Google Chrome”;v=“95”, “Chromium”;v=“95”, “;Not A Brand”;v=“99”
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: “Windows”
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/95.0.4638.54
Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,
application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8080/servlet05/index.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
空白行
请求体
HTTP请求协议的具体报文:POST请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /servlet05/postServlet HTTP/1.1 请求行
Host: localhost:8080 请求头
Connection: keep-alive
Content-Length: 25
Cache-Control: max-age=0
sec-ch-ua: “Google Chrome”;v=“95”, “Chromium”;v=“95”, “;Not A Brand”;v=“99”
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: “Windows”
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8080/servlet05/index.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
空白行
username=lisi&userpwd=123 请求体

get请求和post请求的区别

  1. GET把参数包含在URL中,POST通过request body传递参数
    所以get的参数长度有限制,而post无限制
  2. GET在浏览器回退时是无害的,而POST会再次提交请求。
  3. GET请求只能进行url编码,而POST支持多种编码方式。
  4. GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
  5. 对参数的数据类型,GET只接受ASCII字符,而POST没有限制
  6. GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息(相对安全,因为安全和加不加密有关)

第二种解释

get请求:发送数据的时候,数据会挂在URI的后面,并且在URI后面添加一个“?”,“?”后面是数据。这样会导致发送的数据会显示在浏览器的地址栏上。(get请求在“请求行”上发送数据)

  • http://localhost:8080/servlet05/getServlet?username=zhangsan&userpwd=1234

post请求:发送数据的时候,在请求体当中发送。不会回显到浏览器的地址栏上,也就是说post发送的数据,在浏览器地址栏上看不到。(post在“请求体”当中发送数据)

get请求:只能发送普通的字符串。并且发送的字符串长度有限制,不同的浏览器限制不同。这个没有明确的规范。

get请求:无法发送大数据量。

get请求:支持缓存

post请求:可以发送任何类型的数据,包括普通字符串,流媒体等信息:视频、声音、图片。

post请求:可以发送大数据量,理论上没有长度限制。

get请求:在W3C中是这样说的:get请求比较适合从服务端获取数据。

post请求:在W3C中是这样说的:post请求比较适合从服务端传送数据。 可能好多人,一看到说get请求会把内容显示在地址栏上,就是不安全的

GET请求是安全的

et请求是绝对安全的,为什么?因为get请求只是为了从服务器上获取数据。不会对服务器造成威胁。

POST请求是危险的

为什么?因为post请求是向服务器提交数据,如果这些数据通过后门的方式进入到服务器当中,服务器是很危险的。另外post是为了提交数据,所以一般情况下拦截请求的时候,大部分会选择拦截(监听)post请求。

GET请求和POST请求如何选择,什么时候使用GET请求,什么时候使用POST请求?

  • 怎么选择GET请求和POST请求呢?衡量标准是什么呢?你这个请求是想获取服务器端的数据,还是想向服务器发送数据,如果你是想从服务器上获取资源,建议使用GET请求,如果是为了向服务器提交数据,建议使用POST请求。
  • 大部分的form表单提交,都是post方式,因为form表单中要填写大量的数据,这些数据是收集用户的信息,一般是需要传给服务器,服务器将这些数据保持或修改等。
  • 如果表单中又敏感信息,还是建议使用post请求,因为get请求会回显铭感信息到浏览器地址栏上。(例如:密码信息)
  • 做文件上传,一定是post请求。要传的数据不只是普通文本。
  • 其他情况都可使用get请求
  • 不管你是get请求还是post请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:
    • name=value&name=value&name=value&name=value
    • name是什么?
      • 以form表单为例:form表单中input标签的name。
    • value是什么?
      • 以form表单为例:form表单中input标签的value。

postman的body四个参数类型

  1. form-data
    既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有content-type来说明文件类型;
    content-disposition用来说明字段的一些信息;由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。
  2. x-www-form-urlencoded:
    就是application/x-www-from-urlencoded,会将表单内的数据转换为键值对
  3. raw
  4. 可以上传任意格式的文本,可以上传text、json、xml、html等
  5. binary
    相当于content-type:application/octet-stream,从字面意思得知,只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件。

COR跨域资源共享

  • 安装中间件:npm install cors
  • 导入中间件:const cors=require('cors')
  • 配置中间件: app.use(cors())

CORS

  • CORS(Cross-Origin Resource Sharing,跨域资源共享)解决跨域,是通过 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源
  • 浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可解除浏览器端的跨域访问限制
  • CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
  • CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。

CORS 常见响应头

  • Access-Control-Allow-Origin:制定了允许访问资源的外域 URL
    1
    2
    res.setHeader('Access-Control-Allow-Origin', 'http://bruceblog.io')
    res.setHeader('Access-Control-Allow-Origin', '*')
  • Access-Control-Allow-Headers
  • 默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
  • 如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!
    1
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
  • Access-Control-Allow-Methods
  • 默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的 HTTP 方法
    1
    2
    res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
    res.setHEader('Access-Control-Allow-Methods', '*')

CORS 请求分类

简单请求

  • 请求方式:GET、POST、HEAD 三者之一
  • HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值 application/x-www-formurlencoded、multipart/form-data、text/plain)

在项目中操作MySQL

  1. 安装mysql模块

    1
    npm install mysql
  2. 建立连接

    1
    2
    3
    4
    5
    6
    7
    8
    const mysql = require('mysql')

    const db = mysql.createPool({
    host: '127.0.0.1', // 数据库的ip
    user: 'root', // 登录数据库的账号
    password: 'root', // 登录数据库的密码
    database: 'mydb01',// 指定要操作哪个数据库
    })
  3. 测试是否正常工作

    1
    2
    3
    4
    db.query('select 1', (err, results) => {
    if (err) return console.log(err.message)
    console.log(results)
    })

    出现[ RowDataPacket { '1': 1 } ]则说明操作正常

  4. 插入语句的写法

  • 普通写法
    1
    2
    3
    4
    5
    6
    7
    8
    const user = { sno: '2128224012', sa: 50, sna: 'yuanhao1' }
    // ? 表示占位符
    const sql = 'insert into users values(?, ?, ?)'
    // 使用数组的形式为占位符指定具体的值
    db.query(sql, [username.sno, user.sna,username.sA], (err, results) => {
    if (err) return console.log(err.message)
    if (results.affectedRows === 1) console.log('插入成功')
    })
  • 简便写法
    向表中新增数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速插入数据:
    1
    2
    3
    4
    5
    6
    7
    8
    const user = { sno: '2128224012', sa: 50, sna: 'yuanhao1' }
    // ? 表示占位符
    const sql = 'insert into mytable set ?'
    // 使用数组的形式为占位符指定具体的值
    db.query(sql, user, (err, results) => {
    if (err) return console.log(err.message)
    if (results.affectedRows === 1) console.log('插入成功')
    })
  1. 更新数据写法
    1
    2
    3
    4
    5
    6
    7
    const users = { sno: '2128224042', sA: 5, sna: '凯因' }
    const sqlStr = 'update mytable set ? where sno=?'
    db.query(sqlStr, [users, users.sno], (err, results) => {
    if (err) return console.log(err.message)
    if (results.affectedRows === 1) console.log('更新成功')
    })

  2. 删除数据成功
    1
    2
    3
    4
    5
    const sqlSt = 'delete from mytable where sno=?'
    db.query(sqlSt, '212822404', (err, results) => {
    if (err) return console.log(err.message)
    if (results.affectedRows === 1) console.log('删除成功')
    })
  3. 标记删除
  • 使用 delete 语句会真正删除数据,保险起见,使用标记删除的形式,模拟删除的动作。即在表中设置状态字段,标记当前的数据是否被删除。
    1
    2
    3
    4
    5
    db.query('update mytable set status=1 where sno=?', '212822401', (err, results) => {
    if (err) return console.log(err.message)
    console.log(results)
    })

Session认证机制

服务器端推荐使用Session认证机制

Express中使用Session中间件

  1. 安装express-session中间件
    1
    npm i express-session
  2. 配置中间件
    1
    2
    3
    4
    5
    6
    7
    8
    const session = require('express-session')
    app.use(
    session({
    secret: 'Bruce', // secret密钥 的值为任意字符串
    resave: false, // 固定写法
    saveUninitalized: true, // 固定写法
    })
    )
  3. 向中间件中存数据
    中间件配置成功后,可通过req.session访问 session 对象,存储用户信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    app.post('/api/login', (req, res) => {
    if(req.body.username !== 'admin'||req.body.password!=='000000')
    {
    return res.send({status:1,msg:'登录失败'})
    }
    req.session.user = req.body // 将用户信息存储到session中
    req.session.isLogin = true // 将用户的登录状态存储到session中

    res.send({ status: 0, msg: 'login done' })
    })
  4. 从 session 取数据
    1
    2
    3
    4
    5
    6
    app.get('/api/username', (req, res) => {
    if (!req.session.isLogin) {
    return res.send({ status: 1, msg: 'fail' })
    }
    res.send({ status: 0, msg: 'success', username: req.session.user.username })
    })
  5. 清空 session
    1
    2
    3
    4
    5
    app.post('/api/logout', (req, res) => {
    // 清空当前客户端的session信息
    req.session.destroy()
    res.send({ status: 0, msg: 'logout done' })
    })

Express使用JWT

  1. 安装
  • jsonwebtoken 用于生成JWT字符串
  • express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
    1
    npm install jsonwebtoken express-jwt
  • 为保证 JWT 字符串的安全性,防止其在网络传输过程中被破解,需定义用于加密和解密的 secret 密钥
  • 生成 JWT 字符串时,使用密钥加密信息,得到加密好的 JWT 字符串
  • 把 JWT 字符串解析还原成 JSON 对象时,使用密钥解密
    1
    2
    3
    4
    5
    const jwt = require('jsonwebtoken')
    const {expressjwt} = require('express-jwt')

    // 密钥为任意字符串
    const secretKey = 'Bruce'
  1. 生成JWT字符串
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    app.post('/api/login', (req, res) => {
    res.send({
    status: 200,
    message: '登录成功',
    // jwt.sign() 生成 JWT 字符串
    // 参数:用户信息对象、加密密钥、配置对象-token有效期
    // 尽量不保存敏感信息,因此只有用户名,没有密码
    token: jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '10h'})
    })
    })
  2. JWT 字符串还原为 JSON 对象
  • 客户端访问有权限的接口时,需通过请求头的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证
  • 服务器可以通过 express-jwt 中间件将客户端发送过来的 Token 解析还原成 JSON 对象
    1
    2
    // unless({ path: [/^\/api\//] }) 指定哪些接口无需访问权限
    app.use(expressjwt({ secret: secretKey , algorithms: ["HS256"]}).unless({ path: [/^\/api\//] }))
  1. 获取用户信息
  • 当 express-jwt 中间件配置成功后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息
    1
    2
    3
    4
    5
    6
    7
    8
    app.get('/admin/getinfo', (req, res) => {
    console.log(req.auth)
    res.send({
    status: 200,
    message: '获取信息成功',
    data: req.auth,
    })
    })
  1. 捕获解析 JWT 失败后产生的错误
  • 当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行
  • 通过 Express 的错误中间件,捕获这个错误并进行相关的处理
    1
    2
    3
    4
    5
    6
    app.use((err, req, res, next) => {
    if (err.name === 'UnauthorizedError') {
    return res.send({ status: 401, message: 'Invalid token' })
    }
    res.send({ status: 500, message: 'Unknown error' })
    })
    完整示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    // 导入 express 模块
    const express = require('express')
    // 创建 express 的服务器实例
    const app = express()

    // TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
    const jwt = require('jsonwebtoken')
    const { expressjwt } = require('express-jwt')


    // 解析 post 表单数据的中间件
    const bodyParser = require('body-parser')
    app.use(bodyParser.urlencoded({ extended: false }))

    // TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
    const secretKey = 'itheima No1 ^_^'

    // TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
    // 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
    app.use(expressjwt({ secret: secretKey, algorithms: ["HS256"] }).unless({ path: [/^\/api\//] }))

    // 登录接口
    app.post('/api/login', function (req, res) {
    // 将 req.body 请求体中的数据,转存为 userinfo 常量
    const userinfo = req.body
    // 登录失败
    if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
    return res.send({
    status: 400,
    message: '登录失败!',
    })
    }
    // 登录成功
    // TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
    // 参数1:用户的信息对象
    // 参数2:加密的秘钥
    // 参数3:配置对象,可以配置当前 token 的有效期
    // 记住:千万不要把密码加密到 token 字符中
    const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
    res.send({
    status: 200,
    message: '登录成功!',
    token: tokenStr, // 要发送给客户端的 token 字符串
    })
    })

    // 这是一个有权限的 API 接口
    app.get('/admin/getinfo', function (req, res) {
    // TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
    console.log(req.auth)
    res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.auth, // 要发送给客户端的用户信息
    })
    })

    // TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
    app.use((err, req, res, next) => {
    // 这次错误是由 token 解析失败导致的
    if (err.name === 'UnauthorizedError') {
    return res.send({
    status: 401,
    message: '无效的token',
    })
    }
    res.send({
    status: 500,
    message: '未知的错误',
    })
    })

    // 调用 app.listen 方法,指定端口号并启动web服务器
    app.listen(80, function () {
    console.log('Express server running at http://127.0.0.1:8888')
    })

展开全文 >>

ES6语法

2022-10-19

let关键字

声明格式

1
2
3
4
let a;
let b,c,d;
let e=100;
let f=521,g='iloveyou',h=[];
  1. 变量不能重复声明
    1
    2
    let star ='罗志祥';
    let star ='小猪';
  2. 块级作用域 全局 ,函数 , eval
    1
    2
    3
    4
    5
    if else while for
    {
    let girl = '周扬青';
    }
    console.log(girl); //输出错误

实例演示

1
2
3
4
5
6
7
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10

上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。

如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。

比较

1
2
3
4
5
6
7
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<ul class="nav">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>

var lis = document.querySelector('.nav').querySelectorAll('li');
for (let i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
console.log(i)
}
}
</script>
</body>

此时点哪个li就会出现当前的index,完美解决了之前的问题

  1. 不存在变量提升
1
2
console.log(song);
let song ='恋爱达人' //eferenceError
  1. 不影响作用域链
    1
    2
    3
    4
    5
    let school = '尚硅谷'
    function fn(){
    console.log(school);
    }
    fn() //可以打印出来结果
  2. 暂时性死去

const声明常量

  1. 一定要赋初始值

    1
    const A;  // SyntaxError
  2. 一般常量使用大写(潜规则)

    1
    const A=100
  3. 常量的值不能修改

    1
    SCHOOL = 'ATGUIGU' //控制台报错
  4. 块级作用域

    1
    2
    3
    4
    {
    const PLAYER ='UZI';
    }
    console.log(PLAYER); //控制台报错
  5. 对于数组和对象的元素修改,不算做对常量的修改,不会报错

    1
    2
    3
    const TEAM = ['UZI','MLXG','MING','LETME'];
    TEAM.push('Meiko');
    TEAM=['CLEARLOVE'] // 报错

    ES6 允许按照一定模式从数组和对象中提取值,对变量进行赋值,称为结构赋值

  6. 数组的结构

    1
    2
    3
    4
    5
    6
    const F4 =['小沈阳','刘能','赵四','宋小宝' ];
    let [xiao,liu,zhao,song]=F4;
    console.log(xiao); //小沈阳
    console.log(liu); //刘能
    console.log(zhao); //赵四
    console.log(song); //宋小宝
  7. 对象的解构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const zhao = {
    name:'赵本山',
    age:'不详',
    xiaopin:function(){
    console.log('我可以演小品');
    }
    };
    let { name, age, xiaopin } = zhao;
    // console.log(zhao.xiaopin)
    xiaopin()

变量的解构赋值

  1. 数组的解构赋值


    基本用法

    ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

    1
    2
    3
    let a = 1;
    let b = 2;
    let c = 3;

    ES6 允许写成下面这样。

    1
    let [a, b, c] = [1, 2, 3];

    本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    let [foo, [[bar], baz]] = [1, [[2], 3]];
    foo // 1
    bar // 2
    baz // 3

    let [ , , third] = ["foo", "bar", "baz"];
    third // "baz"

    let [x, , y] = [1, 2, 3];
    x // 1
    y // 3

    let [head, ...tail] = [1, 2, 3, 4];
    head // 1
    tail // [2, 3, 4]

    let [x, y, ...z] = ['a'];
    x // "a"
    y // undefined
    z // []

    如果解构不成功,变量的值就等于undefined

    1
    2
    let [foo] = [];
    let [bar, foo] = [1];

    以上两种情况都属于解构不成功,foo的值都会等于undefined。

    另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

    1
    2
    3
    4
    5
    6
    7
    8
    let [x, y] = [1, 2, 3];
    x // 1
    y // 2

    let [a, [b], d] = [1, [2, 3], 4];
    a // 1
    b // 2
    d // 4

​

ES6引入新的声明字符串的方式[``]

  1. 声明
    1
    let str = `我也是一个字符串哦!` 
  2. 内容中可以直接出现换行符
    1
    2
    3
    4
    5
    6
    let str =`<ul>
    <li>沈腾</li>
    <li>玛丽</li>
    <li>魏翔</li>
    <li>艾伦</li>
    </ul> `;
  3. 变量拼接使用${}
    1
    2
    3
    let lovest=`魏翔`;
    let out =`${lovest}是我心目中最搞笑的演员!!!`;
    console.log(out);

严格模式

介绍严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:

  1. 消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
  2. 消除代码运行的一些不安全之处,保证代码运行的安全。
  3. 提高编译器效率,增加运行速度。
  4. 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class, enum, export, extends, import, super 不能做变量名。

开启严格模式

为脚本开启严格模式

为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句“use strict”;(或'use strict')

1
2
3
4
5
<script>
  "use strict";
  console.log("这是严格模式。");
</script>

为函数开启严格模式

要给某个函数开启严格模式,需要把“use strict”; (或 'use strict';) 声明放在函数体所有语句之前。
例如现在有两个函数,但是我们只想给第一个函数加严格模式,可以进行如下操作:

1
2
3
4
5
6
7
8
9
<script>
function f1(){
'use strict';
}
function f2(){

}
</script>

严格模式中的变化

  1. 变量规定

    1.在正常模式中如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var 命令声明,然后再使用。
    例如现在有一个函数,我们没有给其变量赋值,在没有给定严格模式前:

    1
    2
    3
    4
    5
    6
    7
    8
    <script>
    function f1(){
    num = 10;
    console.log('num的值是:'+num);
    }
    f1(); //num的值是10
    </script>

    如果加入严格模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script>
    function f1(){
    'use strict';
    num = 10;
    console.log('num的值是:'+num);
    }
    f1() // ReferenceError
    </script>

  2. 严格模式下this的指向问题

    以前在全局作用域函数中的 this 指向 window 对象。 严格模式下全局作用域中函数中的 this 是 undefined。

    定时器的this指向依旧是window

    1
    2
    3
    4
    5
    6
    7
    <script>
    function f1(){
    'use strict';
    console.log('严格模式下普通函数的this:'+this);
    }
    f1() // undefined

  3. 以前构造函数时不加new也可以调用,当普通函数,this指向全局对象

    严格模式下,如果构造函数不加new调用,this会报错.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 非严格模式
    function Star() {
    this.name = 'xl';
    console.log(this)
    }
    Star() //window()
    // 严格模式
    function Star() {
    'use strict'
    this.name = 'xl';

    }
    var s = new Star();
    console.log(s.name);

高阶函数

高阶函数对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出

1
2
3
4
5
6
function fn(callback){
callback&&callback();
}
fn(function*(){
alert('hi')
})

第二种闭包

1
2
3
4
function fn(){
return function(){}
}
fn();

闭包

闭包的主要作用延伸了变量的作用范围

1
2
3
4
5
6
7
8
function fn() {
var num = 10;
function fun() {
console.log(num)
}
fun();
}
fn(); //10

上面的代码在打完断点后可以清晰的看出闭包这一问题

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<ul class="nav">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>

var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
//()()为立即执行函数
(function(i){
lis[i].onclick = function () {
console.log(i)
}
})(i) //此处用了闭包这一概念
}
</script>
</body>

callback&&callback()

如果存在回调函数就执行!
这是利用了 JS &&符号的一个小技巧
&& 符号在前面为假时就不会执行后面的语句了
所以这个就相当于

1
2
3
4
5
6
if(callback){

callback();

}

ES6简化对象写法

ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let name ='尚硅谷';
let change = function(){
console.log('加油!');
};
const school ={
name,
change,
improve:function(){
console.log("我们可以提高你的技能");
}
inset(){ //简化写法
console.log("xxxxx")
}
}

ES6箭头函数

  1. 声明定义方式

    1
    () => {}
  2. 箭头函数的函数体

    1
    2
    3
    4
    5
    6
    // 当函数体中只有一条js表达式时,可以省略大括号,且js表达式的执行结果就是return的 返回值
    const sum1 =(num1,num2)=>num1+num2;
    //等同于==
    function sum2(num1,num2){
    return num1+num2;
    }
  3. 箭头函数中this是静态的,this始终指向函数声明时所在作用域下的this的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function A(){
    console.log(this.name)
    }

    let B = () => {
    console.log(this.name);
    }

    window.name = '尚硅谷';
    const school = {
    name: 'ATGUIGU'
    }

    //直接调用
    A() //尚硅谷
    B() //尚硅谷

    //call
    A.call(school); //ATGUIGU
    B.call(school); //尚硅谷

  4. 箭头函数面试题

    obj对象不产生作用域

    1
    2
    3
    4
    5
    6
    7
    var obj={
    age:20,
    say:()=>{
    alert(this.age)
    }
    }
    obj.say();//undefined
  5. 不能作为构造实例化对象

    1
    2
    3
    4
    5
    6
    let A(name,age)=>{
    this.name=name;
    this.age=age;
    }
    let me = new A('xiao',123);
    console.log(me) //error
  6. 不能使用argumrnts变量

    1
    2
    3
    4
    let fn =()=>{
    console.log(arguments);
    }
    fn(1,2,3) //error

    注:arguments
    定义:是一个对应于传递给函数的参数的类数组对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function func1(a, b, c) {
    console.log(arguments[0]);
    // expected output: 1

    console.log(arguments[1]);
    // expected output: 2

    console.log(arguments[2]);
    // expected output: 3
    }

    func1(1, 2, 3);
  7. 简写
    a. 省略小括号,当形参有且只有一个的时候

    1
    2
    3
    let add = n =>{
    return n + 1;
    }

    b. 省略花括号,当代码体只有一条语句的时候,此时return也必须省略

    1
    let add = n => n + 1;

函数参数默认值

  1. 介绍
    ES6允许给函数参数赋值初始值
  2. 特性
    1. 可以给形参赋初始值,一般位置要靠后(潜规则)
      1
      2
      3
      4
      5
      function add(a,b.c=20){
      return a+b+c;
      }
      let result=add(1,2);
      console.log(result) //15
    2. 与解构赋值结合
      1
      2
      3
      4
      5
      6
      7
      8
      function A({host='127.0.0.1',username,password,port}){
      console.log(host+username+password+port)
      }
      A({
      username:'ran',
      password:'123456',
      port:3306
      })

rest 参数

ES6引入rest参数,用于获取函数的实参,用来代替arguments
特性
…args为rest参数(放在函数声明形参的位置)

1
2
3
4
function date(...args){
console.log(args);
}
date('aaa','bbb','ccc');

只能写成(a,b,c,…args)

扩展运算符

  1. 介绍
    扩展运算符是能将数组转换为逗号分隔的参数序列
  2. 特性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //chunwan(...tfboys)
    const tfboys=['AA','BB','CC']
    function chunwan(){
    console.log(arguments);
    }
    chunwan(...tfboys); //0:'AA' 1:'BB' 2:'CC'
    //chunwan(tfboys)
    const tfboys=['AA','BB','CC']
    function chunwan(){
    console.log(arguments);
    }
    chunwan(tfboys); //['AA','BB','CC']是一个数组
  3. 应用
    1. 数组的合并
      1
      2
      3
      4
      const A = ['aa','bb'];
      const B = ['cc','dd'];
      const C = [...A,...B];
      console.log(C) //[aa,bb,cc,dd]
    2. 数组的克隆
      1
      2
      3
      const A = ['a','b','c'];
      const B = [...A];
      console.log(B) //[a,b,c]
    3. 将伪数组转换为真正的数组
      1
      2
      3
      4
      5
      6
      <div></div>
      <div></div>
      <div></div>
      const A = documents.querySelectorAll('div');
      const B = [...A];
      console.log(B) // [div,div,div]
    4. 字符串扩展运算符可以将字符串转化为真正的数组
      1
      2
      [...'hello']
      // ['h','e','l','l','o']

Symbol

  1. 介绍

ES6引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,是一种类似于字符串的数据类型。

  1. Symbol特点:

  2. Symbol的值是唯一的,用来解决命名冲突的问题

  3. Symbol值不能与其他数据进行运算

  4. Symbol定义的对象属性不能使用for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名

  5. 特性

1
2
3
4
5
6
7
let s = Symbol('aa');
let s2= Symbol('aa');
console.log(s===s2) //false

let s3 = Symbol.for('bb');
let s4 = Symbol.for('bb');
comsole.log(s3===s4) ///true

不能与其他数据进行运算

1
2
3
let result = s + 100  //error
let result = s > 100 //error
let result = s + s //error
  1. 应用
  2. 给对象添加方法方式一:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let game = {
    name : 'ran'
    }
    let methods = {
    up:Symbol(),
    down:Symbol()
    }
    game[methods.up]=function(){
    console.log('aaa');
    }
    game[methods.down]=function(){
    console.log('bbb');
    }
    console.log(game) // name: 'ran',Symbol(),Symbol()
  3. 给对象添加方法方式二
    1
    2
    3
    4
    5
    6
    7
    let youxi = {
    name: '狼人杀',
    [Symbol('say')]:function(){ //Symbol为动态值
    console.log('阿萨德')
    }
    }
    console.log(youxi) // name:'狼人杀',Symbol(say)

instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 定义构造函数
function C(){}
function D(){}

var o = new C();


o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype


o instanceof D; // false,因为 D.prototype 不在 o 的原型链上

o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上

C.prototype = {};
var o2 = new C();

o2 instanceof C; // true

o instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上。

D.prototype = new C(); // 继承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 因为 C.prototype 现在在 o3 的原型链上

for in和for of的区别

  1. 循环数组
    区别一:for in和for of都可以循环数组,for in输出的是数组的index下标,而for of输出每一项的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const arr[1,2,3,4]

    //for ... in
    for (const key in arr){
    console.log(key) // 输出 0,1,2,3
    }

    // for ... of
    for (const key of arr){
    console.log(key) // 输出 1,2,3,4

  2. 循环对象
    区别二:for in 可以遍历对象,for of 不能遍历对象,只能遍历带有iterator接口的,例如Set,Map,String,Array
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    const object = { name: 'lx', age: 23 }
    // for ... in
    for (const key in object) {
    console.log(key) // 输出 name,age
    console.log(object[key]) // 输出 lx,23
    }

    // for ... of
    for (const key of object) {
    console.log(key) // 报错 Uncaught TypeError: object is not iterable
    }

迭代器

  1. 介绍
  2. 迭代器(lterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署lterator接口,就可以完成遍历操作。
  3. 原理:创建一个指针对象,指向数据结构的起始位置,第一次调用==next()==方法,指针自动指向数据结构第一个成员,接下来不断调用next(),指针一直往后移动,直到指向最后一个成员,没调用next()返回一个包含value和done属性的对象
  4. 特性
    1
    2
    3
    4
    5
    6
    7
    const xiyou=['AA','BB','CC','DD'];
    // for(let v of xiyou){
    //console.log(v) // 'AA','BB','CC','DD' //for in保存的是键名,for of保存的是键值
    // }
    let iterator = xiyou[Symbol.iterator]();
    console.log(iterator.next()); //{{value:'唐僧',done:false}}
    console.log(iterator.next()); //{{value:'孙悟空',done:false}}
  5. 应用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    const banji = {
    name : "终极一班",
    stus: [
    'aa',
    'bb',
    'cc',
    'dd'
    ],
    [Symbol.iterator](){
    let index = 0;
    let _this = this;
    return {
    next: () => {
    if(index < this.stus.length){
    const result = {value: _this.stus[index],done: false};
    //下标自增
    index++;
    //返回结果
    return result;
    }else {
    return {value: underfined,done:true};
    }
    }
    }
    }
    }
    for(let v of banji){
    console.log(v); // aa bb cc dd

生成器

  1. 介绍
    生成器函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同,是一种特殊的函数
  2. 特性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function * gen (){    //函数名和function中间有一个 * 
    yield '耳朵'; //yield是函数代码的分隔符
    yield '尾巴';
    yield '真奇怪';
    }
    let iterator = gen();
    console.log(iteretor.next());
    //{value:'耳朵',done:false} next()执行第一段,并且返回yield后面的值
    console.log(iteretor.next()); //{value:'尾巴',done:false}
    console.log(iteretor.next()); //{value:'真奇怪',done:false}
  3. 应用
    生成器函数的参数传递
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function * gen(args){
    console.log(args);
    let one = yield 111;
    console.log(one);
    let two = yield 222;
    console.log(two);
    let three = yield 333;
    console.log(three);
    }

    let iterator = gen('AAA');
    console.log(iterator.next());
    console.log(iterator.next('BBB')); //next中传入的BBB将作为yield 111的返回结果
    console.log(iterator.next('CCC')); //next中传入的CCC将作为yield 222的返回结果
    console.log(iterator.next('DDD')); //next中传入的DDD将作为yield 333的返回结果
    用生成器函数的方式解决回调地狱问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function one(){
    setTimeout(()=>{
    console.log('111')
    iterator.next()
    },1000)
    }
    function two(){
    setTimeout(()=>{
    console.log('222')
    iterator.next();
    },2000)
    }
    function three(){
    setTimeout(()=>{
    console.log('333')
    iterator.next();
    },3000)
    }

    function * gen(){
    yield one();
    yield two();
    yield three();
    }

    let iterator = gen();
    iterator.next();
    模拟异步获取数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    function one(){
    setTimeout(()=>{
    let data='用户数据';
    iterator.next(data)
    },1000)
    }
    function two(){
    setTimeout(()=>{
    let data='订单数据';
    iterator.next(data)
    },2000)
    }
    function three(){
    setTimeout(()=>{
    let data='商品数据';
    iterator.next(data)
    },3000)
    }

    function * gen(){
    let users=yield one();
    console.log(users)
    let orders=yield two();
    console.log(orders)
    let goods=yield three();
    console.log(goods)
    }

    let iterator = gen();
    iterator.next();

Promise

  1. 介绍
    Promise是ES6引入的异步编程的新解决方案。语法上 Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
  2. 特性
    基本特性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
    let data='数据库数据'
    //resolve(data); //正确时调用
    reject(data); //错误时调用
    })
    })
    p.then(function(value){
    console.log(value)
    },function(reason){
    console.log(reason)
    }
    )
    Promise.then()方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
        const p =new Promise((resolve, reject) =>{
    setTimeout(()=>{
    resolve('用户数据');
    })
    });

    //then()函数返回的实际也是一个Promise对象
    //1.当回调后,返回的是非Promise类型的属性时,状态为fulfilled,then()函数的返回值为对象的成功值,如reutnr 123,返回的Promise对象值为123,如果没有返回值,是undefined

    //2.当回调后,返回的是Promise类型的对象时,then()函数的返回值为这个Promise对象的状态值

    //3.当回调后,如果抛出的异常,则then()函数的返回值状态也是rejected
    let result = p.then(value => {
    console.log(value)
    // return 123;
    // return new Promise((resolve, reject) => {
    // resolve('ok')
    // })
    throw 123
    },reason => {
    console.log(reason)
    })
    console.log(result);
    发送ajax请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <script>
    //ajax请求返回一个promise
    function sendAjax(url){
    return new Promise((resolve, reject) => {

    //创建对象
    const x =new XMLHttpRequest();

    //初始化
    x.open('GET',url);

    //发送
    x.send();

    //时间绑定
    x.onreadystatechange = ()=>{
    if(x.readyState === 4 ){
    if(x.status >= 200 && x.status < 300){
    //成功
    resolve(x.response)
    }else {
    //失败
    reject(x.status)
    }
    }
    }
    })
    }

    //测试
    sendAjax("https://api.apiopen.top/getJoke").then(value => {
    console.log(value)
    },reason => {
    console.log(reason)
    })

    </script>

    使用p.then实现链式回调
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const p=new Promise((resolve,reject)=>{
    fs.readFile("./resources/为学.md",(err,data)=>{
    resolve(data);
    })
    })
    //利用p.then
    p.then(value =>{
    return new Promise((resolve,reject)=>{
    fs.readFile("./resources/插秧诗.md",(err,data)=>{
    resolve([value,data]);
    })
    })
    }).then(value =>{
    return new Promise((resolve,reject)=>{
    fs.readFile("./resources/观书有感.md",(err,data)=>{
    //压入
    value.push(data);
    resolve(value);
    })
    })
    })

Promise.catch()方法

作为一个语法糖,与then的使用方式类似,只有一个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
//catch()函数只有一个回调函数,意味着如果Promise对象状态为失败就会调用catch()方法并且调用回调函数
<script>
const p = new Promise((resolve, reject) => {
setTimeout(()=>{
reject('出错啦')
},1000)
})

p.catch(reason => {
console.log(reason)
})
</script>

set

  1. 介绍
    ES6提供了新的数据结构set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了iterator接口,所以可以使用「扩展运算符』和「 for…of…』进行遍历,集合的属性和方法:
    1. size返回集合的元素个数
    2. add增加一个新元素,返回当前
    3. delete删除元素,返回bollean值has检测集合中是否包含某个元素,返回boolean值
  2. 特性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    let s = new Set();
    let s2 = new Set(['A','B','C','D'])

    //元素个数
    console.log(s2.size);

    //添加新的元素
    s2.add('E');

    //删除元素
    s2.delete('A');

    //检测
    console.log(s2.has('C'));

    //清空
    s2.clear()

    console.log(s2);
  3. 应用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let arr = [1,2,3,4,5,4,3,2,1]

    //1.数组去重
    let result = [...new Set(arr)] //new Set(arr)可将其转化为类数组,而且里面元素不重复,扩展运算符...是将类数组转化为数组
    console.log(result);
    //2.交集
    let arr2=[4,5,6,5,6]
    let result2 = [...new Set(arr)].filter(item => new Set(arr2).has(item))
    console.log(result2);
    //3.并集
    let result3=[...new Set([...arr,...arr2])]
    console.log(result3);
    //4.差集
    let result4= [...new Set(arr)].filter(item => !(new Set(arr2).has(item)))
    console.log(result4);

Map

  1. 定义
    ES6提供了Map数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
    注:Map也实现了iterator接口,所以可以使用『扩展运算符』和「for…of…』进行遍历。Map的属性和方法。
  2. 特性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    let m = new Map();
    m.set('name','ran');
    m.set('change',()=>{
    console.log('改变!')
    })
    let key={
    school:'atguigu'
    }
    m.set(key,['成都','西安']);

    //size
    console.log(m.size);

    //删除
    m.delete('name');

    //获取
    console.log(m.get('change'));

    // //清空
    // m.clear()

    //遍历
    for(let v of m){
    console.log(v);
    }

Class

  1. 介绍
    ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
  2. 特性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class shouji {    //与构造函数类似
    constructor(brand,price) { //下面创建实例时执行了constructor函数
    this.brand=brand;
    this.price=price
    }

    call(){
    console.log('我可以打电话')
    }
    }

    let A = new shouji('1+',1999); //实例化对象
    console.log(A)

    静态成员
    1
    2
    3
    4
    5
    6
    7
    class Person{
    static name='手机';
    price=5999
    }
    let nokia = new Person();
    console.log(nokia.name); //undefined
    console.log(nokia.price) //5999

构造函数的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

function Phone(brand,price){
this.brand=brand;
this.price=price;
}
Phone.prototype.call=function (){
console.log("我可以打电话");
}
function SmartPhone(brand,price,color,size){
Phone.call(this,brand,price);
this.color=color;
this.size=size;
}

//设置子级构造函数原型
SmartPhone.prototype=new Phone;
SmartPhone.prototype.constructor=SmartPhone;

//声明子类方法
SmartPhone.prototype.photo = function (){
console.log('我可以玩游戏');
}
const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch')
console.log(chuizi);

复习构造函数及原型(prototype)

超级好的一篇博客2020面试收获 - js原型及原型链 - 掘金 (juejin.cn)

引进博客(49条消息) JS中的原型和原型链(图解)_d_ph的博客-CSDN博客_js原型和原型链

构造函数

  1. 创建一个构造函数,专门用来创建一个指定对象的
  2. 构造函数就是普通函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写
  3. 构造函数和普通函数的区别就是调用方式不同(普通函数是直接调用的,而构造函数需要使用new关键字来调用)
  4. 构造函数的执行流程
    1. 立刻创建一个对象
    2. 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
    3. 遂行执行函数中的代码
    4. 将新建的对象作为返回值返回
  5. 使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。我们将通过一个构造函数创建的对象,称为是该类的实例
  6. this的情况
    1. 当以函数的形式调用时,this是window
    2. 当以方法的形式调用时,谁调用方法this就是谁
    3. 当以构造函数的形式调用时,this就是新创建的那个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>构造函数</title>
</head>
<body>
<script type="text/javascript">
/**
*注意是:在构造函数中习惯将构造函数的首字母大写
* 一定要使用`this`关键字来区别当前全局变量的量
*/
// 定义Person构造函数
function Person(name,age,sex) {
this.name = name;// 注意是:如不写this.name,而直接写name则会对全局变量进行赋值,是错误的
this.age = age;
this.sex = sex;
this.sayName = function () {
alert(this.name);
};
}

var per = new Person("海康",21,"男");

// 定义Dog构造函数
function Dog(name,age){
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
};
}

var dog = new Dog("大黄",3);

// 注意是:可以使用instanceof来判断一个对象的类型
console.log(per instanceof Person);//true
console.log(dog instanceof Person);//false
</script>
</body>
</html>

构造函数分为 实例成员 和 静态成员

实例成员: 实例成员就是在构造函数内部,通过this添加的成员。实例成员只能通过实例化的对象来访问。

静态成员: 在构造函数本身上添加的成员,只能通过构造函数来访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Star(name,age) {
//实例成员
this.name = name;
this.age = age;
}
//静态成员
Star.sex = '女';

let stars = new Star('小红',18);
console.log(stars); // Star {name: "小红", age: 18}
console.log(stars.sex); // undefined 实例无法访问sex属性

console.log(Star.name); //Star 通过构造函数无法直接访问实例成员
console.log(Star.sex); //女 通过构造函数可直接访问静态成员


通过构造函数创建对象该过程也称作实例化

new一个新对象的过程,发生了什么?
1
2
3
4
5
function Father(name) {
this.name = name;
}
let son = new Father('Lisa');
console.log(son); //Father {name: "Lisa"}

(1) 创建一个空对象 son {}
(2) 为 son 准备原型链连接 son.__proto__ = Father.prototype
(3) 重新绑定this,使构造函数的this指向新对象 Father.call(this)
(4) 为新对象属性赋值 son.name
(5) 返回this return this,此时的新对象就拥有了构造函数的方法和属性了

每个实例方法并非共享

方法一:在构造函数上直接定义方法(不共享)

1
2
3
4
5
6
7
8
9
10
11
12
function Star() {
this.sing = function () {
console.log('我爱唱歌');
}
}
let stu1 = new Star();
let stu2 = new Star();
stu1.sing();//我爱唱歌
stu2.sing();//我爱唱歌
console.log(stu1.sing === stu2.sing);//false


很明显,stu1 和 stu2 指向的不是一个地方。 所以 在构造函数上通过this来添加方法的方式来生成实例,每次生成实例,都是新开辟一个内存空间存方法。这样会导致内存的极大浪费,从而影响性能。

方法二:通过原型添加方法(共享)

构造函数通过原型分配的函数,是所有对象共享的。

1
2
3
4
5
6
7
8
9
10
11
12
function Star(name) {
this.name = name;
}
Star.prototype.sing = function () {
console.log('我爱唱歌', this.name);
};
let stu1 = new Star('小红');
let stu2 = new Star('小蓝');
stu1.sing();//我爱唱歌 小红
stu2.sing();//我爱唱歌 小蓝
console.log(stu1.sing === stu2.sing);//true

原型

注意以下几点

1、所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。

2、所有的引用类型都有一个’_ _ proto_ _’属性(也叫隐式原型,它是一个普通的对象)。

3、所有的函数都有一个’prototype’属性(这也叫显式原型,它也是一个普通的对象)。

4、所有引用类型,它的’_ _ proto_ _’属性指向它的构造函数的’prototype’属性。

5、当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’_ _ proto_ _’属性(也就是它的构造函数的’prototype’属性)中去寻找。

定义

Father.prototype 就是原型,它是一个对象,我们也称它为原型对象。

作用

原型的作用,就是共享方法。
我们通过 Father.prototype.method 可以共享方法,不会反应开辟空间存储方法。

proto

[正确解释___proto___](用自己的方式(图)理解constructor、prototype、__proto__和原型链 - 掘金 (juejin.cn))

  1. 实例对象.proto = 创建自己的构造函数内部的prototype(原型对象)
  2. 实例对象.proto.constructor = 创建自己的构造函数

所有函数的__proto__指向他们的原型对象,即Function函数的prototype对象

所有的函数都是Function函数的实例(包括Function自己),所以他们的__proto__自然也就都指向Function函数的prototype对象。

最后一个prototype对象是Object函数内的prototype对象。

Object函数作为JS的内置对象,也是充当了很重要的角色。Object函数是所有对象通过原型链追溯到最根的构造函数。换句话说,就是官方动作,不讲道理的神仙操作

Object函数的prototype中的__proto__指向null。

这是由于Object函数的特殊性,有人会想,为什么Object函数不能像Function函数一样让__proto__属性指向自己的prototype?答案就是如果指向自己的prototype,那当找不到某一属性时沿着原型链寻找的时候就会进入死循环,所以必须指向null,这个null其实就是个跳出条件

原型链

什么是原型链

原型与原型层层相链接的过程即为原型链。

定义

对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在
每个对象都有__proto__原型的存在

原型图像演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 构造函数
function Foo(name,age){
this.name=name;
this.age=age;
}
Object.prototype.toString=function(){
//this是什么要看执行的时候谁调用了这个函数。
console.log("I'm "+this.name+" And I'm "+this.age);
}
var fn=new Foo('小明',19);
fn.toString(); //I'm 小明 And I'm 19
console.log(fn.toString===Foo.prototype.__proto__.toString); //true

console.log(fn.__proto__ ===Foo.prototype)//true
console.log(Foo.prototype.__proto__===Object.prototype)//true
console.log(Object.prototype.__proto__===null)//true



首先,fn的构造函数是Foo()。所以:
fn._ _ proto _ _=== Foo.prototype
又因为Foo.prototype是一个普通的对象,它的构造函数是Object,所以:
Foo.prototype._ _ proto _ _=== Object.prototype
通过上面的代码,我们知道这个toString()方法是在Object.prototype里面的,当调用这个对象的本身并不存在的方法时,它会一层一层地往上去找,一直到null为止。
所以当fn调用toString()时,JS发现fn中没有这个方法,于是它就去Foo.prototype中去找,发现还是没有这个方法,然后就去Object.prototype中去找,找到了,就调用Object.prototype中的toString()方法。

另外,在使用原型的时候,一般推荐将需要扩展的方法写在构造函数的prototype属性中,避免写在_ _ proto _ _属性里面。

原型链引入的新的继承方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function GrandFather() {
this.name = 'GrandFather'
}
function Father() {
this.age = 32
}
Father.prototype = new GrandFather() // Father函数改变自己的prototype指向
function Son() {}
Son.prototype = new Father() // Son函数改变自己的prototype指向

var son = new Son()
console.log(son.name) // 结果输出:GrandFather
console.log(son.age) // 结果输出:32
console.log(Son.prototype.constructor) // 结果输出:[Function: GrandFather]

Father函数和Son函数都丢弃了它们各自的prototype对象,指向一个新的对象。这形成了三个新的有趣现象:

  1. Father函数中的prototype指向了GrandFather的实例对象,这时候这个实例对象就成为了Father函数以后实例的原型对象,顺其自然GrandFather实例对象内的私有属性name就变成了Father函数以后实例的共享属性;
  2. 同样的,Son函数中的prototype指向了Father的实例对象,将Father的实例对象内的私有属性age就变成了Son函数以后实例的共享属性。
  3. 它们的__proto__属性将它们串了起来,形成一条新的原型链。

class(类)基本语法

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

简述类和对象的区别

类抽象了对象的公共部分,它泛指某一大类(class)

对象特指某一个,通过类实例化一个具体的对象

基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。

1
2
3
4
5
6
7
8
9
10
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}

上面代码定义了一个“类”,可以看到里面有一个constructor()方法,这就是构造方法,而this关键字则代表实例对象。

类的构造函数

每一个类都可以有一个自己的构造函数,这个名称是固定的constructor,当我们通过new调用一个类时,这个类就会调用自己的constructor方法(构造函数)。

  • 它用于创建对象时给类传递一些参数
  • 每一个类只能有一个构造函数,否则报错

通过new调用一个类时,会调用构造函数,执行如下操作过程:

  1. 在内存中开辟一块新的空间用于创建新的对象
  2. 这个对象内部的__proto__属性会被赋值为该类的prototype属性
  3. 构造函数内的this,指向创建出来的新对象
  4. 执行构造函数的内部代码
  5. 如果函数没有返回对象,则返回this
1
2
3
4
5
6
7
8
9
10
class Animal  {
// 类的构造方法
// 用于接收函数
constructor(name) {
this.name = name;
}
}

var a = new Animal("ABC");
console.log(a); // Animal { name: 'ABC' }

上面这个例子中,我们在class中定义的constructor,这个就是构造方法,而this代表的是实例对象。

这个class,你可以把它看作构造函数的另外一种写法,因为它和它的构造函数的相等的,即是类本身指向构造函数。

1
console.log(Animal === Animal.prototype.constructor); // true

其实,在类上的所有方法都会放在prototype属性上。

类中的共有的属性和方法一定要加this使用

上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<button>点击</button>
<script>
class Star{
constructor(uname,age){
this.uname=uname;
this.age=age;
this.btn=document.querySelector('button');
this.btn.onclick=this.sing; // 告诉浏览器需要调用哪一个实例对象的sing
}
sing(){
console.log(this.uname)
}
}
var ldh=new Star('刘德华')
</script>
</body>

constructor()方法

constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。

实例对象本身没有该属性

1.藏在自身的原型对象中

2.*prototype对象除外

1
2
3
4
5
6
7
class Point {
}

// 等同于
class Point {
constructor() {}
}

上面代码中,定义了一个空的类Point,JavaScript 引擎会自动为它添加一个空的constructor()方法。

深度理解constuctor()方法

1
2
3
function Person() {}
var person1 = new Person()
var person2 = new Person()

person1与person2是Person对象的实例,他们的constructor指向创建它们的构造函数,即Person函数;

Person是函数,但同时也是Function实例对象,它的constructor指向创建它的构造函数,即Function函数;

至于Function函数,它是JS的内置对象,在第一点我们就已经知道它的构造函数是它自身,所以内部constructor属性指向自己。

所以constructor属性其实就是一个拿来保存自己构造函数引用的属性,没有其他特殊的地方。

类中的属性

实例属性

实例的属性必须定义在类的方法里

1
2
3
4
5
6
7
class Animal{
constructor(name,height,weight) {
this.name = name;
this.height = height
this.weight = weight
}
}
静态属性

当我们把一个属性赋值给类本身,而不是赋值给它prototype,这样子的属性被称之为静态属性(static)。

静态属性直接通过类来访问,无需在实例中访问。

1
2
3
4
5
class Foo{
static name ='_island'
}

console.log(Foo.name);
私有属性

私有属性只能在类中读取、写入,不能通过外部引用私有字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal{
#age;
constructor(name,age){
this.name=name
this.#age=age
}
}

var a = new Animal('_island',18)
console.log(a); // Animal { name: '_island' }
console.log(a.name); // _island
console.log(a.age); // undefined
console.log(a.#age); // Private field '#age' must be declared in an enclosing class


类中的方法

在类上的所有方法都会放在prototype属性上。

实例方法

在ES6之前,我们定义类中的方法是类中的原型上进行定义的,防止类中的方法重复在多个对象上

1
2
3
4
function Animal() {}
Animal.prototype.eating = function () {
console.log(this.name + " eating");
};

在ES6中,定义类中的方法更加简洁,直接在类中定义即可,这样子的写法即优雅可读性也强。

1
2
3
4
5
class Animal{
eating() {
console.log(this.name + " eating");
}
}
静态方法
1
2
3
4
5
6
7
8
9
10
class Animal{
static createName(name) {
return name
}
}

var a2 = Animal.createName("_island");
console.log(a2); // _island


Class的类继承

extends关键字用于扩展子类,创建一个类作为另外一个类的一个子类。

它会将父类中的属性和方法一起继承到子类的,减少子类中重复的业务代码。

这对比之前在ES5中修改原型链实现继承的方法的可读性要强很多,而且写法很简洁。

extends的使用
1
2
3
4
5
6
7
class Point{

}

// dog 继承 Animal 类
class ColorPoint extends Point {}

上面示例中,Point是父类,ColorPoint是子类,它通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。

继承类的属性和方法
Super

super关键字用于访问和调用一个对象的父对象上的函数。

super指的是超级、顶级、父类的意思

在子类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数。

子类在构造函数中使用super,必须放到this前面(必须先调用父类的构造方法,在使用子类的构造方法)

1
2
3
4
5
6
7
class A {}

class B extends A {
constructor() {
super();
}
}

就近原则

1
2
3
4
5
6
7
8
9
10
11
12
class Father{
say(){
return '我是爸爸'
}
}
class Son extends Father{
say(){
console.log('我是儿子')
}
}
var son = new Son();
son.say() //'我是儿子'

如果使用super关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
class Father{
say(){
return '我是爸爸'
}
}
class Son extends Father{
say(){
// console.log('我是儿子')
console.log(super.say()+'的儿子')
}
}
var son = new Son();
son.say() //'我是爸爸的儿子'
Getter和Setter
访问器属性

它们本质上是用于获取和设置值的函数,但从外部代码来看就像常规属性。

访问器属性由”getter”和”setter”方法表示。在对象字面量中,他们用get和set表示

1
2
3
4
5
6
7
8
9
let obj = {
get propName() {
// 当读取 obj.propName 时,getter 起作用
},

set propName(value) {
// 当执行 obj.propName = value 操作时,setter 起作用
}
};

当读取 obj.propName 时,getter 起作用,当 obj.propName 被赋值时,setter 起作用。

理解与运用

例如,我们有一个具有 name 和 surname 属性的对象 user,现在我们想添加一个 fullName 属性,该属性值应该为 "John Smith"。当然,我们不想复制粘贴已有的信息,因此我们可以使用访问器来实现:

1
2
3
4
5
6
7
8
9
10
let user = {
name: "John",
surname: "Smith",

get fullName() {
return `${this.name} ${this.surname}`;
}
};

alert(user.fullName); // John Smith

从外表看,访问器属性看起来就像一个普通属性。这就是访问器属性的设计思想。我们不以函数的方式 调用 user.fullName,我们正常 读取 它:getter 在幕后运行。

截至目前,fullName 只有一个 getter。如果我们尝试赋值操作 user.fullName=,将会出现错误:

1
2
3
4
5
6
7
let user = {
get fullName() {
return `...`;
}
};

user.fullName = "Test"; // Error(属性只有一个 getter)

让我们通过为 user.fullName 添加一个 setter 来修复它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let user = {
name: "John",
surname: "Smith",

get fullName() {
return `${this.name} ${this.surname}`;
},

set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};

// set fullName 将以给定值执行
user.fullName = "Alice Cooper";

alert(user.name); // Alice
alert(user.surname); // Cooper

现在,我们就有一个“虚拟”属性。它是可读且可写的。

在类内部也可以使用get和set关键字,对应某个属性设置存值和取值函数,拦截属性的存取行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {
constructor() {
this._age = 3;
}

get age() {
return this._age;
}

set age(val) {
this._age = val;
}
}

var a = new Animal();
console.log(a.age); // 3
a.age = 4;
console.log(a.age); //4

类的prototype属性和__ proto __属性

大多数浏览器的 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

1
2
3
4
5
6
7
8
class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

上面代码中,子类B的__proto__属性指向父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性。

数值扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<script>
// Number.EPSILON是 JavaScript的最小精度,属性的值接近于 2.22044...E-16
function equal(a,b){
if(Math.abs(a-b) < Number.EPSILON){
return true;
}else {
return false;
}
}

console.log(equal(0.1 + 0.2 === 0.3)) //false
console.log(equal(0.1+0.2,0.3)) //true

//二进制和八进制
let b = 0b1010; //2进制
let o = 0o777; //8进制
let d = 100; //10进制
let x = 0xff; //16进制
console.log(x) //255

//检测一个数是否为有限数
console.log(Number.isFinite(100)); //true
console.log(Number.isFinite(100/0)); //false
console.log(Number.isFinite(Infinity)); //false

//检测一个数值是否为NaN
console.log(Number.isNaN(123)) //false

//字符串转整数
console.log(Number.parseInt('5213123love')); //5213123
console.log(Number.parseFloat('5.123123神器')); //5.123123

//判断是否为整数
console.log(Number.isInteger(5)); //true
console.log(Number.isInteger(2.5)); //false

//将小数部分抹除
console.log(Math.trunc(3.45345345345)) //3

//检测一个数到底是正数、负数、还是0
console.log(Math.sign(100)) //1
console.log(Math.sign(0)) //0
console.log(Math.sign(-123)) //-1
</script>

对象方法的扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script>
//1.Object.is 判断两个值是否完全相等
console.log(Object.is(120,120)) //true
console.log(Object.is(NaN,NaN)) //false

//2.Object.assign 对象的合并
const a = {
name:'ran',
age:12
}
const b = {
pass:'i love you'
}
console.log(Object.assign(a,b)) //{name:'ran',age:'12',pass:'i love you'}

//3.Object.setPrototypeOf 设置原型对象 Object.getPrototypeof
const school = {
name:'尚硅谷'
}
const cities = {
xiaoqu:['北京','上海']
}
Object.setPrototypeOf(school,cities) //相当于后面的参数为前面的原型
console.log(Object.getPrototypeOf(school)) //{xiaoqu: Array(2)}
console.log(school) //{name: "尚硅谷"}
</script>

暴露语法汇总

export命令用于规定模块的对外接口
inport命令用于输入其他模块提供的功能

  1. 统一暴露
    1
    2
    3
    4
    5
    6
    //统一暴露
    let school = '尚硅谷';
    function findjob(){
    console.log('找工作吧');
    }
    //export {school,findjob}
  2. 默认暴露
    1
    2
    3
    4
    5
    6
    7
    //默认暴露
    export default {
    school:'ATGUIGU',
    change:function(){
    console.log('我们可以改变你')
    }
    }

引入语法汇总

  1. 通用导入方式
    1
    2
    3
    import * as m1 from "./src/js/m1.js"
    import * as m2 from "./src/js/m2.js"
    import * as m3 from "./src/js/m3.js"
  2. 解构赋值方式
    1
    2
    3
    4
    import {school,teach} from "./src/js/m1.js"
    import {school as guigu,findJob} from "./src/js/m2.js"
    import {default as m3 } from "./src/js/m3.js"

  3. 简便形式(只针对默认暴露)
    1
    import m3 from "./src/js/m3.js"

async函数

async和await两种语法结合可以让异步代码像同步代码一样
async函数:

  1. async函数的返回值为promise对象
  2. async返回的promise对象的结果值由async函数执行的返回值决定
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    async function fn(){
    //1.如果返回的是一个非Promise的对象,则fn()返回的结果就是成功状态的Promise对象,值为返回值
    //2.如果返回的是一个Promise对象,则fn()返回的结果与内部Promise对象的结果一致
    //3.如果返回的是抛出错误,则fn()返回的就是失败状态的Promise对象
    return new Promise((resolve,reject)=>{
    resolve('成功的数据');
    });
    }
    const result = fn();
    result.then(value=>{
    console.log(value) //成功的数据
    },reason=>{
    console.log(reason)
    })

await表达式

  1. await必须放在async函数中
  2. await右侧的表达式一般为promise对象
  3. await可以返回的是右侧promise成功的值
  4. await右侧的promise如果失败了,就会抛出异常,需要通过try…catch捕获处理
    发送AJAX请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <script>
    //ajax请求返回一个promise
    function sendAjax(url){
    return new Promise((resolve, reject) => {

    //创建对象
    const x =new XMLHttpRequest();

    //初始化
    x.open('GET',url);

    //发送
    x.send();

    //时间绑定
    x.onreadystatechange = ()=>{
    if(x.readyState === 4 ){
    if(x.status >= 200 && x.status < 300){
    //成功
    resolve(x.response)
    }else {
    //失败
    reject(x.status)
    }
    }
    }
    })
    }

    //async 与 await 测试
    async function main(){
    let result = await sendAjax("https://api.apiopen.top/getJoke")
    console.log(result);
    }
    main()

    </script>

this指向

this作用域问题

先看代码

非常重要

1
2
3
4
5
6
7
8
9
10
function foo() {
var a = 2;
this.bar();// 这里的this指向window 所以下面的函数是可以正常运行的
}
function bar() {
console.log(this.a);
console.log(this) // window
}
foo(); // undefined

上段代码中我们在 foo 函数内部使用 this 调用了 bar 函数,然后在 bar 函数内部打印 a 变量,如果我们按照作用域链的思想思考的话,此时的 a 变量按道理是能够读取到的,但是事实却是 undefined。

造成上述问题的原因有多个,其中有一个就是 this 在任何情况下都不指向函数的词法作用域,上段代码就使用使用 this 将 foo 和 bar 函数的词法作用域联通,这是不可行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
test:function (){
var self = this;
console.log(this === obj); //true
f();
function f(){
console.log(this === obj); //false
console.log(self === obj); //true
console.log(this === window); //true
};
}
};
obj.test();

this的定义

this 就是一个对象,this 是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

四种方式(绑定规则)

默认绑定

我们比较常见的一种函数调用类型就是独立函数的调用,形如foo()等。这个时候的 this 绑定就是采用的默认绑定规则。

当函数不带用任何修饰进行调用时,此时 this 的绑定就是默认绑定规则,this 指向全局对象。

箭头函数没有this

1
2
3
4
5
6
var name = '小猪课堂';
function foo(){
console.log(this) // Window{}
console.log(this.name) // 小猪课堂
}
foo(); // 小猪课堂

上段代码非常简单,我们在全局作用域中定义了一个变量name,然后我们在函数 foo 中使用this.name,输出的结果就是全局变量name,这说明我们 this 指向了全局作用域,也就是说 this 绑定到了 window 对象上。

隐式绑定

前面的默认绑定规则很好理解,因为我们的函数执行上下文就是全局作用域,this 自然而然绑定到了全局对象上。

独立函数的调用我们可以直接看出执行上下文在哪里,但如果不是独立函数调用,比如下面代码。

1
2
3
4
5
6
7
8
function foo() {
console.log(this.name) // 小猪课堂
}
let obj = {
name: '小猪课堂',
foo: foo
}
obj.foo();

上段代码我们在 obj 对象中引用了函数 foo,然后我们使用 obj.foo(函数别名)的方式调用了该函数,此时不是独立函数调用,我们不能使用默认绑定规则。

此时 this 的绑定规则称为隐式绑定规则,因为我们不能直接看出函数的调用位置,它的实际调用位置在 obj 对象里面,调用 foo 时,它的执行上下文对象为 obj 对象,所以 this 将会被绑定到 obj 对象上,所以我们函数中的 this.name 其实就是obj.name。这就是我们的隐式绑定规则。

显式绑定

前面我们已经说了默认绑定和隐式绑定,其中隐式绑定我们通常是以 obj.foo 这种形式来调用函数的,目的就是为了让 foo 的 this 绑定到 obj 对象上。

这个时候,如果我们不想通过 obj.foo 的形式调用函数,我们想要很明确的将函数的 this 绑定在某个对象上。那么可以使用 call、apply 等方法,这就是所谓的显式绑定规则。

1
2
3
4
5
6
7
8
9
10
11
function foo() {
console.log(this.name) // 小猪课堂
}


let obj = {
name: '小猪课堂',
}


foo.call(obj);
new绑定

new 关键词相信大家都知道或者使用过吧,这就是我们将要将的第 4 种 this 绑定,叫做 new 绑定。

想要知道 new 绑定规则,我们就很有必要知道一个当我们 new 一个对象的时候做了什么,或者说 new 关键词会做哪些操作。

使用 new 来调用函数时,会执行下面操作:

  • 创建一个全新的对象
  • 这个新对象会被执行原型连接
  • 这个新对象会绑定到函数调用的 this
  • 如果函数没有返回其它对象,那么 new 表达式种的函数调用会自动返回这个新对象

我们可以看到 new 的操作中就有 this 的绑定,我们在来看看代码。

1
2
3
4
5
function foo(name) {
this.name = name;
}
let bar = new foo('小猪课堂');
console.log(bar.name); // 小猪课堂

this 绑定规则优先级:

默认绑定 < 隐式绑定 < 显式绑定 < new 绑定

面试官:JS中this指向哪儿?你是如何确定this的? - 掘金 (juejin.cn)

改变函数内的this指向 js提供了三种方法call() apply()bind()

call方法

  1. call()可以调用函数

  2. -call()可以改变这个函数的this的指向call()可以改变这个函数的this的指向

  3. call的主要作用可以实现继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function fn(x,y){
    console.log('我想喝手磨咖啡')
    console.log(this)
    console.log(x+y)
    }
    var o={
    name:'andy'
    }
    fn.call(o,1,2) // o代表this指向 1,2是参数

apply方法

  1. 也是调用函数 第二个可以改变函数内部的this指向
  2. 但是它的参数必须是数组(伪数组)
1
2
3
4
5
6
7
8
var o={
name:'andy'
};
function fn(arr){
consloe.log(this);
consloe.log(arr);
};
fn.apply(o,['pink']) // 第一个参数表示this要指向哪个对象
  1. apply的主要应用

    1
    2
    3
    4
    5
    6
    7
    8
    function fn(arr){
    consloe.log(this);
    consloe.log(arr);
    };
    fn.apply(o,['pink'])
    var arr=[1,66,3,99,4];
    var max=Math.max.apply(Math,arr);
    console.log(max)

bind方法

  1. 不会调用原来的函数 可改变函数内部的this指向

  2. 返回的是原函数改变this之后产生的新函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var o = {
    name: 'andy'
    };
    function fn() {
    console.log(this)
    }
    // fn.bind(o) // 控制台不会打印对象,因为bind函数会生成一个新函数
    var f = fn.bind(o);
    f(); // 可以看出bind方法不会调用函数
  3. 如果有的函数我部门不需要调用,但是又想改变这个函数内部的this的指向此时用bind

  4. 我们有一个按钮,当我们点击之后就禁用这个按钮,3秒钟之后开启这个按钮

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var btn =document.querSelector('button');
    btn.onclick=function(){
    this.disabled=true; // 这个this指向的是btn这个按钮
    // var that=this;
    setTimeout(function(){
    // that.disabled=false
    // 定时器函数里面的this指向是window
    this.disabled=false;
    // 定时器函数里面的this指向的是window
    }.bind(this),3000)
    // bind这个函数在定时器外面
    }

立即执行函数—-IIFE

立即执行函数是指指函数声明后立即执行,也就是说函数自己调用自己

语法:
直接在函数声明后加括号

1
2
3
function() {
alert(aa);
}();

变量函数

1
2
3
4
var demo = function(){
return "I love js very much!";
}
alert(demo)

变量名不加括号时,这个变量名就代 表整个函数,加括号时代表函数的调用

1
2
3
4
5
6
7
8
9
var fun1=function(){
var fun2=function(){
alert("good good study day day up!");
}
return fun2;
}
alert(fun1()) //function(){
alert("good good study day day up!");
}

函数返回值

函数返回值—–return

  • 返回值的类型由return后面的值决定的,返回值可以是任意数据类型
  • return后没有值,会返回undefined
  • 函数不写返回值也会返回undefined
  • 函数中return就代表函数执行完毕,后面的语句不会再执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function a(){
var b = 10;
console.log(b); 控制台正常打印10
}
var c = a(); 此时这里的c 就会变成undefined


function a(){
var b = 10;
return b;
}
var c = a(); 此时这里的c 就会变成10


//return 后面的代码不在执行 具有阻断作用

function a(){
var b = 10;
var b1 = 20;
rutn {
a:b,
b:b1
}
}
var c = a(); 此时这里的c 就会变成{a:10,b:20}

展开全文 >>

vue实战shop

2022-10-09

小知识点

创建分支

在login分支中开发当前项目vue_shop:
打开vue_shop终端,使用git status确定当前项目状态。
确定当前工作目录是干净的之后,创建一个分支进行开发,开发完毕之后将其合并到master。
git checkout -b login
查看新创建的分支:git branch
确定我们正在使用login分支进行开发

提交分支

将代码提交到码云
新建一个项目终端,输入命令git status查看修改过的与新增的文件内容
将所有文件添加到暂存区:git add .
将所有代码提交到本地仓库:git commit -m “添加登录功能以及/home的基本结构”
查看分支: git branch 发现所有代码都被提交到了login分支
将login分支代码合并到master主分支,先切换到master:git checkout master
在master分支进行代码合并:git merge login
将本地的master推送到远端的码云:git push
推送本地的子分支到码云,先切换到子分支:git checkout 分支名
然后推送到码云:git push -u origin 远端分支名

token原理分析

客户端 (登录页面输入用户名和密码进行登录)—–> 服务器
<—–(服务器验证通过之后生成该用户的token并返回)

     -----|(客户端存储该token)
     <----|
       (后续所有的请求都携带该token发送请求)------>
                    (服务器验证token是否通过)  |-------
                                             |------>

​
​

利用confirm删除文件

首先根据api中的接口创造一个函数包括弹窗组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<script>
async removeRightById(role, rightId) {
// 弹框提示用户是否要删除
// 此文段为复制element UI复制的一个提示框
const confirmResult = await this.$confirm(
// comfirmResult接收confirm返回的参数
'此操作将永久删除该文件, 是否继续?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
// catch捕获错误并返回错误
).catch(err => err)
// 判断是否删除
// 不删除则打印取消了删除
if (confirmResult !== 'confirm') {
return this.$message.info('取消了删除!')
}
// 删除了则调用端口
const { data: res } = await this.$http.delete(
`roles/${role.id}/rights/${rightId}`
// 字符串拼接
)

if (res.meta.status !== 200) {
return this.$message.error('删除权限失败!')
}

// this.getRolesList()
role.children = res.data
},
</script>

获取更新页面数据

  1. 定义getXXXXXXXList()函数
    getXXXXXXXList()函数的作用是获取页面数据并在每次打开页面在created()生命周期函数中重新更新整个页面的值
    在methods中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    methods: {
    async getCateList() {
    const { data: res } = await this.$http.get('categories', { params: this.querInfo })
    console.log(res)
    if (res.meta.status !== 200) {
    return this.$message.error('获取商品分类失败!')
    }
    console.log(res)
    // 把数据列表,赋值给catesult
    this.cateList = res.data.result
    // 为总数据条数赋值
    this.total = res.data.total
    }}
    在生命周期函数中
    1
    2
    3
    created() {
    this.getCateList()
    }
  2. 在其他函数调用时有时也需要调用函数

vue中的.then()与.catch()

  1. .then()
    在axios请求完成后执行的下一步操作(异步执行),包括两个参数
    1
    2
    3
    4
    5
    6
    7
    axios.post(url, data)
    .then(res => {
    console.log(res);//第一个参数:成功后返回结果
    }, error => {
    console.log(error);//第二个参数:请求失败后返回值
    })

  2. .catch()
    在.then()中代码逻辑出错、请求失败等的回调函数,防止因代码造成程序崩溃,这里的方法类似于try{}.catch(e){},省略了try{}
    1
    2
    3
    4
    5
    6
    7
    8
    axios.post(url, data)
    .then(data => {
    console.log(data)
    })
    .catch(err => {
    console.log(err)//代码错误、请求失败捕获
    })

vue中$ref的三种用法

  1. ref加在普通元素上,用this.$refs.(ref值)获取的是dom元素
  2. ref加在子组件上,用this.$refs.(ref值)获取到的是组件实例,可以使用组件所有的方法
  3. 利用v-for和ref获取一组数组或者dom节点
    注:
    1. 如果通过v-for遍历相加不同的ref时记得加:号,即:ref=某变量;
    2. ref需要在dom渲染完成后才会有,在使用的时候确保dom已经渲染完成
      添加ref属性
      1
      2
      3
      4
      <div id="app">
      <h1 ref="h1ele">这是h1</h1>
      <hello ref="ho"></hello>
      </div>
      获取ref组件及元素
      1
      2
      3
      4
      5
      nethods:{
      getref(){
      this.$refs.h1ele.color='red'
      }
      }

element ui

Form表单验证
  1. 需通过rules属性传入约定的验证规则,并将Form-Item的prop属性设置为需校验的字段名。
  2. 验证规则是加给item的,不是加给文本框的
    1
    2
    3
    4
    5
    <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
    <el-form-item label="活动名称" prop="name">
    <el-input v-model="ruleForm.name"></el-input>
    </el-form-item>
    </el-form>
    1
    2
    3
    4
    5
    6
    7
    8
    export default {
    data() {
    rules: {
    name: [
    { required: true, message: '请输入活动名称', trigger: 'blur' },
    { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
    ],
    }}}
表单的重置

resetFields:对整个表单进行重置,将所有字段值重置为初始值并移除校验结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules" label-width="0px" class="login_form">
</el-form>
<el-button type="info" @click="resetLoginForm">重置</el-button>


export default {
data () {
return {
methods:{
resetLoginForm () {
this.$refs.loginFormRef.resetFields()
}
}

表单的预验证
1
2
3
4
5
login(){
this.$refs.loginFormRef.validate(valid => {
console.log(valid);//打印出来为bull值
})
}

Storage.getItem()的用法

定义
getItem() 作为 Storage 接口的方法,接受一个键名(key name)作为参数,并返回对应键名的值(key’s value)。

  1. 语法
    1
    2
    var aValue = storage.getItem(keyName);

  2. 返回值
    一个 DOMString,键名对应的值。如果键名不存在于存储中,则返回 null。

Storage.setItem()的用法

定义
Storage 接口的 setItem() 方法,接受一个键名和值作为参数,将会把键名添加到给定的 Storage 对象中,如果键名已存在,则更新其对应的值

  1. 语法
    1
    storage.setItem(keyName, keyValue);
  2. 示例
    下面函数在本地存储中创建三个数据项
    1
    2
    3
    4
    5
    6
    function populateStorage() {
    localStorage.setItem('bgcolor', 'red');
    localStorage.setItem('font', 'Helvetica');
    localStorage.setItem('image', 'myCat.png');
    }

vue使用this.$http.get和this.$http.post传参

get传参方式

1
2
3
4
5
6
7
8
this.$http.get('http://localhost:8080/testApi', {
params: {
name: "张三",
phone: "13888888888"
}
}).then((res) => {
console.log('请求完成')
});

post传参方式

1
2
3
4
5
6
7
this.$http.get('http://localhost:8080/testApi', {
name: "张三",
phone: "13888888888"
}).then((res) => {
console.log('请求完成')
});

${}的用法

可以代替 ‘’ 和 “” 在 `` 中可以使用 ${} 直接把变量和字符串拼接起来

  1. 用法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    let name = '彭于晏'
    console.log( '名字为:'+ name )
    console.log( `名字为:${name} ` )


    let a='Karry Wang';

    let str=`I love ${a}, because he is handsome.`;
    //注意:这行代码是用返单号引起来的

    alert(str);

自定义校验规则的使用

示例
data()中所需要的数据为一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<script>
export default {
data() {
// 验证邮箱的规则
var checkEmail = (rule, value, cb) => {
// 验证邮箱的正则表达式
const regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/
if (regEmail.test(value)) {
// 合法的邮箱
return cb()
}
cb(new Error('请输入合法的邮箱'))
}
// 验证手机号的规则
var checkMobile = (rule, value, cb) => {
// 正则表达式的含义为验证的内容
const regMobile = /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/
if (regMobile.test(value)) {
// 合法的邮箱
return cb()
}
cb(new Error('请输入合法的手机号'))
}
return {
...

data()中return所需的数据

1
2
3
4
5
6
7
8
9
10
11
12
addFormRules: {
email: [
// 必填项
{ required: true, message: '请输入邮箱', trigger: 'blur' },
// validator调用函数
{ validator: checkEmail, trigger: 'blur' }
],
mobel: [
{ required: true, message: '请输入联系方式', trigger: 'blur' },
{ validator: checkMobile, trigger: 'blur' }
]
}

与服务器api建立联系

若在一个函数中,并要与后台数据库保持同步则需调用axios异步操作
注:async与await为简化promise 函数
示例

1
2
3
4
5
6
7
8
9
10
11
12
13
async userStateChanged(userinfo) {
console.log(userinfo)
// 利用data:res解构后将得到的值传给res
// `${}`为拼接字符串
const { data: res } = await this.$http.put(`users/${userinfo.id}/state/${userinfo.mg_state}`)
// 判断返回的status值
if (res.meta.status !== 200) {
// 不能正常操作则返回原来状态
userinfo.mg_state = !userinfo.mg_state
return this.$message.error('更新用户状态失败!')
}
this.$message.success('更新用户状态成功!')
},

forEach基本用法

定义
forEach() 方法对数组的每个元素执行一次给定的函数。
语法

1
2
3
4
5
// 箭头函数
forEach((element) => { /* … */ })
forEach((element, index) => { /* … */ })
forEach((element, index, array) => { /* … */ })

示例

1
2
3
4
5
6
7
8
const array1 = ['a', 'b', 'c'];

array1.forEach(element => console.log(element));

// expected output: "a"
// expected output: "b"
// expected output: "c"

Vue中ref属性使用的注意事项

  1. 在vue中为HTML标签设置ref属性,主要是为了一些需要进行操作DOM才能完成的功能而设置的。
  2. ref属性相当于给标签设置了一个ID,可以使用该特殊标识来进行一些DOM的操作,但是使用的时候有如下几个注意事项:
    注:
  3. 使用时不是直接this.ref值进行访问DOM节点,而是需要通过this.$refs.ref值 进行访问DOM节点,因为vue会将实例中所有的ref属性值都保存到vue实例的,$refs属性内。
    1
    this.$refs.treeRef.getCheckedKeys()
  4. 所有的ref属性进行的操作都不是响应式的,所以避免在计算属性(Computed),和模板()中使用ref属性。
  5. ref属性值绑定元素都是唯一的,如果一个ref属性绑定了多个dom节点,那么这个ref属性将会默认绑定到最后设置该ref属性值的DOM节点。

filter的用法

定义
filter()创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
语法

1
array.filter(function(currentValue,index,arr),thisValue)

示例

1
2
3
const array=[14,17,18,32,33,16,40];
const newArr=array.filter(num=>num==14)
// [17,18,32,33,16,40]

注意事项

  1. filter不会对空数组进行检测
  2. filter不会改变原始数组

vue-table-with-tree-grid

使用
a.先在Vue UI中安装依赖
b.在main.js中引用

1
2
import TreeTable from 'vue-table-with-tree-grid'
Vue.component('tree-table', TreeTable)

c.在子组件中应用

1
2
<tree-table :data="cateList" :columns="columns" :selection-type="false" :expand-type="false" show-index index-text="#" border>
</tree-table>

d.配合模板列使用
模板列的使用方法
html内写入

1
2
3
4
5
6
7
8
9
10
11
<template slot="isok" slot-scope="scope">
/* scope.row="scope"可以获得所有数据即cateList的内容 */
<i class="el-icon-success" v-if="scope.row.cat_deleted === false" style="color: lightgreen"></i>
<i class="el-icon-error" v-else style="color: red"></i>
</template>
<!-- 排序 -->
<template slot="order" slot-scope="scope">
<el-tag size="mini" v-if="scope.row.cat_level === 0">一级</el-tag>
<el-tag type="success" size="mini" v-else-if="scope.row.cat_level === 1">二级</el-tag>
<el-tag type="warning" size="mini" v-else>三级</el-tag>
</template>

scrpt写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
columns: [
// 注:columns为树形结构中的列部分
// 传入需要使用的数据及名称
{
label: '分类名称',
prop: 'cat_name'
},
// 下面为模板列具体格式
{
label: '是否有效',
// 表示,将当前列定义为模板列
type: 'template',
// 表示当前这一列使用模板名称
template: 'isok'
},
{
label: '排序',
// 表示,将当前列定义为模板列
type: 'template',
// 表示当前这一列使用模板名称
template: 'order'
}
]
  1. 树形结构基本api
    具体api看网站

    https://github.com/MisterTaki/vue-table-with-tree-grid

设置分页区域

  1. 首先引入element ui
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <el-pagination
    @size-change="handleSizeChange"
    @current-change="handleCurrentChange"
    :current-page="querInfo.pagenum"
    :page-sizes="[3, 5, 10, 15]"
    :page-size="querInfo.pagesize"
    layout="total, sizes, prev, pager, next, jumper"
    :total="total"
    >
    </el-pagination>
  2. 在methods中调用函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    handleCurrentChange(newPage) {
    this.querInfo.pagenum = newPage
    this.getCateList()
    },
    handleSizeChange(newSize) {
    this.querInfo.pagesize = newSize
    this.getCateList()
    },
    // newSize与newPage为需要更新的值,赋值给data中的对应数据
    具体参数见

    https://element.eleme.cn/#/zh-CN/component/installation

确定与取消按钮

一般存在于对话框内

  1. 首先引入element ui中的按钮组件
    注:必要时修改点击事件中的函数名
    1
    2
    3
    4
    <span slot="footer" class="dialog-footer">
    <el-button @click="addCateDialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCateDialogVisible = false">确 定</el-button>
    </span>
  2. 在data中存放addCateDialogVisible将值修改为false
    1
    addCateDialogVisible: false,
  3. 在methods中定义函数
    1

运用树形结构

  1. 首先运用element ui引入el-tree
    el-tree中有prop属性,prop属性绑定需要引入的内容
    data中
    1
    2
    3
    4
    5
    treeProps: {
    label: 'authName',
    // label为api中返回的数据名字
    children: 'children'
    }

el-upload上传头像

  1. 引入组件el-upload
  2. 调用多个回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <el-upload
    class="avatar-uploader"
    ref="uploadRef"
    action // 由于自定义上传,所以此值为空
    :auto-upload="false" // 是否在选取文件后立即进行上传
    :show-file-list="false" // 是否显示已上传文件列表
    accept=".jpg,.jpeg,.png,.bmp" // 接受上传的文件类型
    :on-change="imgChange" // 图片改变时触发
    :file-list="imgList" // 上传的文件列表
    :before-upload="beforeUpload" // 上传文件之前的钩子,参数为上传的文件
    :limit="1" // 最大长度1
    :http-request="uploadImg" // 自定义上传覆盖默认上传
    >
    // 为展示模块
    <img v-if="imageUrl" :src="'********' + imageUrl" class="avatar" /> // ****为后端接口
    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
    </el-upload>
  3. 根据需求添加
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    export default {
    data() {
    return {
    imgList: [], // 图片上传列表,已限制最大长度为1
    imageUrl: '' // 头像图片链接(图片上传成狗后除了让他返回200,还让他把图片路径发过来,你存在这里)
    }
    },
    methods: {
    // 图片改变的钩子
    imgChange(img, imgList) {
    console.log('文件状态改变')
    console.log('当前图片上传列表长度' + imgList.length) // 图片上传列表
    // console.log(img.raw) // 这里拿到图片文件
    // 再上传前检查文件是否合规
    const res = this.beforeUpload(img.raw)
    console.log(res)
    if (res === true) {
    // 上传图片
    this.uploadImg(img.raw)
    } else {
    // 清空上传列表
    this.imgList = []
    }

    },
    // 上传前的函数(用于检查图片是否合规)
    beforeUpload(file) {
    console.log('上传图片前的函数')
    // console.log(file)
    // 定义要接收的文件类型
    const isJPG = file.type === 'image/jpeg' || 'image/png' || 'image/jpg' || 'image/bmp'
    // 定义要接受的文件大小限制
    const isLt2M = file.size / 1024 / 1024 < 2
    if (!isJPG) {
    this.$message.error('抱歉,不支持该格式的图片!')
    }
    if (!isLt2M) {
    this.$message.error('请选择2MB以下大小的图片')
    }
    return isJPG && isLt2M // 只要有一个是false,该函数返回值就是false
    },
    // 头像自定义上传行为
    async uploadImg(img) {
    console.log('自定义上传行为')
    // console.log(img) // 拿到图片文件
    // const { data: res } = await this.$http.post('/Person/touxiang', { avater: img })

    // 定义一个formdata对象
    const formData = new FormData()
    // 通过 append 函数往formdata对象里传参,这里传的是后端需求的接口信息
    formData.append('avater', img)
    const { data: res } = await this.$http.post('/Person/touxiang', formData)
    console.log(res)
    console.log(this.imageUrl)
    this.imageUrl = res.avater
    }
    }
    }
    </script>

element-ui更改表头背景颜色和字体颜色

根据elementui官网的说法,header-cell-style是表头单元格的 style 的回调方法!!!

1
2
3
4
5
6
<template>
<el-table :header-cell-style="{
background:'#e2d2d2',height:'100px',color:'#000000',border: '1px solid tan'}">
...
</el-table>
</template>

用/deep/深度监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<el-table
class="ones">
...
</el-table>
</template>


<style lang="scss" scoped>
/deep/ .ones{
background: #e2d2d2;
height:100px;
border: 1px solid #000;
}
</style>

简述const {data:res}=this.$http.post的作用

const{data} = await login(XXX) 就是es6中的解构赋值,将login(XXX)的data属性取出来。

const {data:res} = await login(XXX)就是将data重命名为res。

添加分类表单

展开全文 >>

vue 小知识

2022-10-06

set使用

Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。

1
2
3
mounted() {
this.$set(this.student,'age',24)
}

全局事件总线

首先,想要实现全局事件总线,就要安装全局事件总线,在main.js中完成全局事件总线的安装配置

1
2
3
4
5
6
7
8
9
10
//创建vm
new Vue({
el:'#app',
render: h => h(App),
// beforeCreate中模板未解析,且this是vm
beforeCreate(){
Vue.prototype.$bus = this //安装全局事件总线
}
}

接下来,我们就要对想要接收到数据的组件进行自定义事件的绑定,简单来说就是,谁要接收数据,自定义事件就绑定在谁身上

1
2
3
4
5
6
mounted(){
// 绑定自定义事件
this.$bus.$on('自定义事件名', (接收参数)=>{
console.log('我是TestB组件,收到了数据', 接收参数);
})
}

最后一步,全局事件总线的触发,事件的触发是在发送数据的组件中完成的,简单来说,谁是数据的发送者,谁就来触发事件

1
2
3
4
5
6
 methods:{
// 触发事件,事件名不能重复
触发事件方法名(){
this.$bus.$emit('自定义事件名', 传递参数);
}
},

在得到数据之后,解绑事件,提高性能

1
2
3
4
// 销毁对应自定义事件
beforeDestroy(){
this.$bus.$off('自定义事件名')
}

自定义事件

父组件使用props传递数据给子组件,子组件通过自定义事件传递给父组件
props父传子

  1. 首先在父组件APP的methods里面定义一个函数(需要接收一个参数),给子组件标签绑定这个函数
  2. 在子组件通过props声明接收这个函数,定义click一个事件
  3. 在子组件的methods里面书写事件函数,通过this函数名括号传数据
    APP组件代码
    1
    2
    3
    4
    5
    6
    7
    8
    APP
    <School :getSchool='getSchoolName'/>
    定义一个接收参数的函数
    methods:{
    getSchoolName(name){
    console.log('app收到了学校名',name)
    }
    }
    子组件代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    定义一个click事件
    <button @click="sendSchoolName">把学校名字给app</button>
    声明接收父组件函数
    props:['getSchoolName'],
    调用事件函数并传参
    methods:{
    sendSchoolName(){
    this.getSchoolName(this.name)
    }
    }
绑定自定义事件

实现步骤:

  1. 首先给子组件标签通过v-on:自定义事件名字=”函数名”,绑定一个函数,接着在父组件APP的methods里面定义这个函数(需要接收一个参数)
  2. 在子组件中,首先定义一个click事件,接着在methods内书写click事件函数,接着在methods内书写click事件函数,在其内部通过this.$emit()去触发APP组件里面的自定义事件,括号内的第一个参数是自定义事件名字,后面的参数是数据。(注意:给哪个子组件绑定自定义事件就去哪个子组件中去触发这个事件)
  3. 在子组件的methods里面定义触发这个函数的函数,通过this函数名括号传数据
    APP组件代码
    1
    2
    3
    4
    5
    6
    7
    8
    给子组件标签绑定函数
    <Student :self='incident'/>
    定义一个接收参数的函数
    methods:{
    incident(name){
    console.log('app收到了学生名',name)
    }
    }
    子组件代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    定义一个click事件
    <button @click='sendSchoolName'>把学生名名字给app<button/>
    调用事件函数,通过this.$emit()去触发APP组件里面的自定义事件
    methods:{
    sendSchoolName(){

    this.$emit('self',this.name)
    }
    }

展开全文 >>

数据库系统概论

2022-09-22

用MySQL数据库创建表格

  1. 创建数据库
    1
    CREATE DATABASE student
    删除数据库
    1
    drop database student
    创建完数据库需要刷新才能出现数据库
  2. 创建表格
需要先确定列的类型
1
2
3
4
5
6
USE student;
CREATE TABLE myTable(
sno CHAR(9) PRIMARY KEY(设置主键,主键是唯一的),
sa SMALLINT,
...
)
  1. 数值类型
    TINYINT 微整型,占1个字节 范围-128127
    SMALLINT 小整型,占2个字节 范围-32768
    32767
    INT 整型,占4个字节 范围 -2147483648~2147483647
    BIGINT 大整型,占8个字节
    FLOAT 单精度浮点型,占4个字节,范围3.4e38,范围比INT大的多,可能产生计算误差。
    DOUBLE 双精度浮点型,占8个字节,范围比BIGINT大的多
    DECIMAL(M,D) 定点小数,不会产生计算误差,M代表总的有效位数,D代表小数点后的有效位数
    BOOL 布尔型,只有两个结果TRUE/1、FALSE/0,TRUE和FALSE不能添加引号;真正存储数据的时候,会使用TINYINT。

  2. 日期时间类型
    DATE 日期型 ‘2018-12-31’
    TIME 时间型 ‘14:22:30’
    DATETIME 日期时间型 ‘2018-12-31 14:22:30’

  3. 字符串类型——必须添加引号
    VARCHAR(M) 变长字符串,不会产生空间浪费,操作速度相对较慢,M最大值是65535
    CHAR(M) 定长字符串,可能产生空间浪费,操作速度较快,M最大值是255;用于存储手机号码,身份证号等固定长度的字符。
    TEXT(M) 大型变长字符串,最多存2G

  4. 填值

  5. 增加

    1
    INSERT INTO student Values('张三',2,...),
  6. 修改

    1
    UPDATE student SET sa=18 where sno='张三' 
  7. 删除

    1
    DELETE FROM student where sno='张三'
  8. 查表

查看数据库所有的表
1
SHOW TABLES;
修改表
1
2
3
4
5
6
添加字段
alter TABLE student add ssex VARCHAR;
重命名
alter TABLE student change ssex sDate VARCHAR;
删除字段
alter TABLE student drop birthday;
查询列
1
2
3
4
查询所有列
select * from student
查询指定列
select sno from student
增加
1
2
3
4
全列插入
INSERT INTO student Values('李四',1,20)
部分列插入
INSERT INTO student (sno,ssex)Values('王五','男')

select一般形式

selcet 列名,*,运算,聚合函数
From 表|视图|派生表
where 条件

  1. 比较运算符<> != > < >= <= =
  2. 范围 between and | not between and
  3. 集合in not in
  4. 字符匹配(模糊查询)like ‘%_’%匹配次数不限 _只匹配一次
  5. 多重条件(逻辑运算)and or not
    group by[HAVING<条件表达式>]
    order by[ASC|DESC]
条件查询
1
2
3
4
查询编号大于三的学生
select * from student where id>3
查询姓名不是张三的学生
select * from student where name !='张三'
逻辑运算符
1
2
3
4
5
and or not
查询编号大于三的女同学
select * from student where id=3 and gender=0
查询编号小于四或没被删除的学生
select * from student where id<4 or is_delete
范围查询
1
2
3
4
5
6
7
in 和 between and
in 用在一个非连续的范围内
查询编号为1或3或8的学生
select * from where id in (1,3,8)
between and 表示在一个连续的范围内
查询编号3-8的学生
select * from student where id between 3 and 8



连接查询

连接查询:同时涉及两个以上的表的查询
连接条件或连接谓词:用来连接两个表的条件
注:连接条件中的各连接字段类型必须是可比的,但名字不必相同

等值与非等值连接查询

等值连接:连接运算符为=
查询每个学生及其选修课程情况

1
2
3
SELECT stu.*, SC.*
FROM Student stu, SC
WHERE stu.Sno = SC.Sno;

查询结果

嵌套循环法

首先在表1中找到第一个元组,然后从头开始扫描表2,逐一查找满足连接件的元组,找到后就将表1中的第一个元组与该元组拼接起来,形成结果表中一个元组。
表2全部查找完后,再找表1中第二个元组,然后再从头开始扫描表2,逐一查找满足连接条件的元组,找到后就将表1中的第二个元组与该元组拼接起来,形成结果表中一个元组。

排序合并法

常用于=连接
首先按连接属性对表1和表2排序
对表1的第一个元组,从头开始扫描表2,顺序查找满足连接条件的元组,找到后就将表1中的第一个元组与该元组拼接起来,形成结果表中一个元组。当遇到表2中第一条大于表1连接字段值的元组时,对表2的查询不再继续

索引连接

对表2按连接字段建立索引
对表1中的每个元组,依次根据其连接字段值查询表2的索引,从中找到满足条件的元组,找到后就将表1中的第一个元组与该元组拼接起来,形成结果表中一个元组

自身连接

自身连接:一个表与其自己进行连接
注:1. 需要给表起别名用来区分
2.由于所有属性名都是同名属性,因此必须用别名前缀
3.要为单个表取两个别名,一个是first,另一个是second

1
2
3
SELECT  FIRST.Cno, SECOND.Cpno
FROM Course FIRST, Course SECOND
WHERE FIRST.Cpno = SECOND.Cno;

外连接
  1. 普通连接操作只输出满足连接条件的元组
  2. 外连接操作以指定表为连接主体,将主体表中不满足连接条件的元组一并输出
  3. 左外连接
    列出左边关系中所有的元组
  4. 右外连接
    列出右边关系中所有的元组
多表连接

多表连接:两个以上的表进行连接
查询每个学生的学号、姓名、选修的课程名及成绩

1
2
3
4
SELECT Student.Sno,Sname,Cname,Grade
From Student,Sc,Course
Where Student.Sno=SC.Sno
And SC.Cno=Course.cno;
嵌套查询

一个SELECT-FROM-WHERE语句称为一个查询块
将一个查询块嵌套在另一个查询块的WHERE子句或HAVING短语的条件中的查询称为嵌套查询

1
2
3
4
5
6
SELECT Sname	                           /*外层查询/父查询*/
FROM Student
WHERE Sno IN
( SELECT Sno /*内层查询/子查询*/
FROM SC
WHERE Cno= ' 2 ');

上层的查询块称为外层查询或父查询
下层查询块称为内层查询或子查询
注:

  1. SQL语言允许多层嵌套查询
    即一个子查询中还可以嵌套其他子查询
  2. 子查询的限制
    不能使用ORDER BY
嵌套查询求解方法

不相关子查询:

  1. 子查询的查询条件不依赖于父查询
  2. 由里向外 逐层处理。即每个子查询在上一级查询处理之前求解,子查询的结果用于建立其父查询的查找条件
    相关子查询:子查询的查询条件依赖于父查询
  3. 首先取外层查询中表的第一个元组,根据它与内层查询相关的属性值处理内层查询,若where返回值为真,则取此元组放入结果表中
  4. 然后再取外层表的下一个元组
  5. 重复这一过程,直至外层表全部检查完为止
带有IN谓词的子查询

查询与“刘晨”在同一个系学习的学生。(此查询要求可以分步来完成)

1
2
3
4
5
 <!-- 确定“刘晨”所在系名              -->
SELECT Sdept
FROM Student
WHERE Sname= ' 刘晨 ';
<!-- 结果为: CS -->

查找所有在CS系学习的学生。

1
2
3
4
SELECT   Sno, Sname, Sdept     
FROM Student
WHERE Sdept= ' CS ';

1
2
3
4
5
6
SELECT Sno, Sname, Sdept
FROM Student
WHERE Sdept IN /*可以替换为=号*/
(SELECT Sdept
FROM Student
WHERE Sname= ' 刘晨 ');
带有比较运算符的子查询

当能确切知道内层查询返回单值时,可用比较运算符(<,>,=,>=,<=,!=,<>)
找出每个学生超过他选修课程平均成绩的课程号。

1
2
3
4
5
6
SELECT Sno, C5no
FROM SC x
WHERE Grade >=(SELECT AVG(Grade)
FROM SC y
WHERE y.Sno=x.Sno);

分为三步

  1. 从外层查询中取出SC的一个元组x,将元组x的Sno值(201215121)传送给内层查询。
1
2
3
4
SELECT AVG(Grade)
FROM SC y
WHERE y.Sno='201215121‘;

  1. 执行内层查询,得到值88(近似值),用该值代替内层查询,得到外层查询:
    1
    2
    3
    SELECT Sno,Cno
    FROM SC x
    WHERE Grade >=88;
带有ANY(SOME)或ALL谓词的子查询
  1. ALL> 大于子查询结果中的所有值
  2. < ANY 小于子查询结果中的某个值
  3. < ALL 小于子查询结果中的所有值
  4. => ANY 大于等于子查询结果中的某个值
  5. => ALL 大于等于子查询结果中的所有值
  6. !=(或<>)ANY 不等于子查询结果中的某个值
  7. !=(或<>)ALL 不等于子查询结果中的任何一个值
    查询非计算机科学系中比计算机科学系任意一个学生年龄小的学生姓名和年龄
    1
    2
    3
    4
    5
    6
    SELECT Sname,Sage
    FROM Student
    WHERE Sage < ANY (SELECT Sage
    FROM Student
    WHERE Sdept= ' CS ')
    AND Sdept <> ‘CS ' /*父查询块中的条件 */
    查询非计算机科学系中比计算机科学系所有学生年龄都小的学生姓名及年龄。
    用ALL谓词
    1
    2
    3
    4
    5
    6
    7
    8
      SELECT Sname,Sage
    FROM Student
    WHERE Sage < ALL
    (SELECT Sage
    FROM Student
    WHERE Sdept= ' CS ')
    AND Sdept <> ' CS ’;

    聚集函数
    1
    2
    3
    4
    5
    6
    7
    8
    SELECT Sname,Sage
    FROM Student
    WHERE Sage <
    (SELECT MIN(Sage)
    FROM Student
    WHERE Sdept= ' CS ')
    AND Sdept <>' CS ';

带有EXISTS的子查询
  1. 带有EXISTS谓词的子查询不返回任何数据,只产生逻辑真值“true”或逻辑假值“false”。
  2. 若内层查询结果非空,则外层的WHERE子句返回真值
  3. 若内层查询结果为空,则外层的WHERE子句返回假值
  4. 由EXISTS引出的子查询,其目标列表达式通常都用 * ,因为带EXISTS的子查询只返回真值或假值,给出列名无实际意义。
    查询所有选修了1号课程的学生姓名。
  5. 在Student中依次取每个元组的Sno值,用此值去检查SC表
  6. 若SC中存在这样的元组,其Sno值等于此Student.Sno值,并且其Cno= ‘1’,则取此Student.Sname送入结果表
    1
    2
    3
    4
    5
    6
    7
    SELECT Sname
    FROM Student
    WHERE EXISTS
    (SELECT *
    FROM SC
    WHERE Sno=Student.Sno AND Cno= ' 1 ');

UNION和UNION ALL
  1. UNION:将多个查询结果合并起来时,系统自动去掉重复元组
  2. UNION ALL:将多个查询结果合并起来时,保留重复元组
    查询选修了课程1或者选修了课程2的学生。
    1
    2
    3
    4
    5
    6
    7
    8
    SELECT Sno
    FROM SC
    WHERE Cno=' 1 '
    UNION
    SELECT Sno
    FROM SC
    WHERE Cno= ' 2 ';

展开全文 >>

Vue基础

2022-07-06

Vue简介

1.Javascript框架
2.简化DOM操作
3.响应式数据驱动

初识Vue

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

注意区分:js表达式 和 js代码(语句)
1.表达式:一个表达式会产生一个值,可以放在一个需要值的地方:
1.a
2.a+b
3.demo(1)函数
4.x===y?’a’:’b’
2.js代码(语句)
1.if(){}
2.for(){}

第一次写页面就出现了warn警告,发现是data写成date了

1
2
3
4
5
6
7
8
9
10
11
12
<div id="app">
{{ message }}
</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
message: "你好HtuPengyuyan"
}
})
</script>

el挂载点

1.Vue会管理el选项命中的元素及其内部的后代元素
2.可以使用其他的选择器,但是建议使用id选择器
3.可以使用其他双标签,布恩那个使用HTML和body

data数据对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
{{message}}
<h2>{{school.name}}{{school.mobile}}</h2>
<ul>
<li>{{campus[0]}}</li>
<li>{{campus[1]}}</li>
</ul>
</div>
var app=new Vue({
el:"#app",
data:{
message:"你好小黑",
school:{
name:"黑马程序员",
mobile:"400-618-9090"
},
campus:["北京","上海","广州","深圳"]

}
})

data数据对象和el挂载点的两种写法

1.el有两种写法
1.new Vue时候配置el属性
2.先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值
2.data有两种写法
1.对象式
2.函数式
3.一个重要原则
有Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const v=new Vue
//({
//el:'#root',
// data:{
// name:'xxx'
// }
// })
setTimeout(()=>{
v.$mount('#root')
},1000)
data:function(){
return{
name:'xxx'
}
}

MVVM模型

1.M:模型(Model):data中的数据
2.V:视图(View):模板代码
3.VM:视图模型(ViewModel):Vue实例

观察发现:

1.data中所有的属性,最后都出现在了vm身上。
2.vm身上所有的属性及Vue原型上所有的属性,在Vue模板中都可以直接使用

数据代理

回顾defineProperty()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
let number =18
let person={
name:'xxx',
sex:'男',
// age:'18',
}
//调用defineProperty给age值
Object.defineProperty(person,'age',{
// value:18
//enumberable:true//控制属性是否可以枚举,默认值时false
//writable:true//控制属性是否可以被修改,默认值时false
//configurable:true//控制属性是否可以被删除,默认值是false
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值时age的值
get(){
return number
},
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值时age的值
set(){
number=value
}

})
</script>

数据代理

定义:通过一个对象对另一个对象中属性的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
let obj={x:100}
let obj2={y:200}
Object.defineProperty(obj2,'x',{
get(){
return obj.x
//调用obj.x的值
},
set(value){
obj.x=value
}
})
</script>

Vue中的数据代理

通过vm对象来代理data对象中属性的操作

Vue中数据代理的好处:

更加方便的操作data中的数据

基本原理

1.通过Object.defineProperty()把data对象中所有属性参加到vm上
2. 为每一个参加到vm上的属性,都指定一个getter/setter
3. 在getter/setter内部去操作(读/写)data中对应的属性

事件修饰符

Vue中的事件修饰符:
1.prevent:阻止默认事件(常用);
2.stop:阻止事件冒泡(常用);
3.once:事件只触发一次(常用);
4.capture:使用事件的捕获模式;
5.self:只有eventtarget是当前操作的元素时才触发事件;
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

1
<button @click.stop='showInfo'>点我提示信息</button>

键盘事件

1.Vue中常用的按键别名
回车 => enter
删除 => delete(捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab
上 => up
下 => down
左 => left
右 => right

2.系统修饰键(用法特殊):ctrl、alt、shift、meta
1.配合keyup使用:按下修饰键同时,再按下其他键,随后释放其他键,事件才被触发
2.配合keydown使用:正常触发事件

3.Vue.config.keyCodes.自定义键名=键码,可以定制按键别名
1
<input type="text" placeholder="按下回车提示输入" @keydown.tab="showInfo">

计算属性

1.定义:要用的属性不存在,要通过已有的属性计算得来
2.原理:底层借助了Objcet.defineproperty的方法提供getter和setter
3.get函数什么时候执行?
(1).初次读取时会执行一次
(2).当依赖的数据发生变化时会再次调用
4.优势:与methods实现相比,内部有缓存机制(可以重复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可
2.如果计算属性要被修改,那必须写set函数相应修改,且set中要引起计算时依赖的数据发生变动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<div id="root">
姓: <input type="text" v-model="firstName"><br><br>
<!-- 这里用v-model实现双向绑定 -->
名: <input type="text" v-model="lastName"><br><br>
全名<span>{{fullName}}</span><br><br>
全名<span>{{fullName}}</span><br><br>
全名<span>{{fullName}}</span><br><br>
全名<span>{{fullName}}</span><br><br>
</div>

<script src="vue.js"></script>
<script>
const vm = new Vue({
el: "#root",
data: {
firstName: '张',
lastName: '三'
},
methods: {

},
computed: {
fullName: {
get() {
//get有什么用?当有人读取fullName时,get就会被调用,且返回值作为fullName的值
//get什么时候可以调用?1.初次读取fullName时
//所以来的数据发生变化时
console.log('get被调用了', this)
//console.log(this)//此处的this是vm
return this.firstName + '-' + this.lastName
},
set(value) {
//set调用时,fullName被修改时
console.log('set', value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
})
</script>

监视属性

Vue监视数据的原理:
1.Vue会监视data中所有层次的数据。
2.如何监视对象中的数据?
通过setter实现监视,且要在new Vue时就传入要检测的数据
(1).对象中后加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target.propertyName/index,value)或
vm.$set(target.propertyName/index,value)
3.如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新
(2).重新解析模板,进而更新页面
4.Vue修改数组中的某个元素一定要使用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2.Vue.set()或vm.$set()
特别注意:Vue.set()或vm.$set()不能给vm或vm的跟数据对象添加属性

1
 

天气案例

监视属性watch:
1.当被监视的属性变化时,回调函数自动调用,进行相关操作
2.监视的属性必须存在,才能进行监视!!
3.监视的两种写法:
1.new Vue传入watch配置
2.通过vm.$watch监视

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    <div id="root">
<h2>今天天气很{{info}},{{x}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script src="vue.js"></script>
<script>
const vm= new Vue({
el:'#root',
data:{
isHot:true,
x:1
},
computed:{
info(){
return this.isHot?'炎热':'寒冷'
}
},
methods: {
changeWeather(){
this.isHot=!this.isHot
this.x++
}
},
watch:{
isHot:{
immediate :true,//初始化时让handler调用一下
//handler函数什么时候调用?当isHot发生改变时
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
}
})

深度监视

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const vm= new Vue({
el:'#root',
data:{
isHot:true,
x:1,
d:{
c:{
e=100
},
f:{

}

}
},

watch:{
d:{
deep:true
handler(){

}
}
}
})

计算属性(computed)和监视属性(watch)的区别

1.computed能完成的功能,watch都可以完成
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
####两个重要的小原则
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象
2.所有不被Vue所管理的函数(定时器的回调函数,ajax的回调函数,promise的回调函数),最好写成箭头函数,这样this的指向才是vm或组件实例对象

1
2
3
4
5
6
7
8
9
10
11
<script>
watch:{
firstName(val){
//这里的定时器函数要用箭头函数函数的指向才是this
setTimeout(()=>{
consoule.log(this)
this.fullName=val+'-'+this.lastName
},1000)
}
}
</script>

vue的各种指令

1.v-text指令

使用v-text属性会使内容被替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<h2 v-text="message+'!'">深圳哦</h2>
<h2 v-text="info+'!'">深圳12300</h2>

<h2>{{message+"!"}}深圳</h2>
</div>
<script src="vue.js"></script>
<script>
var app= new Vue({
el:"#app",
data:{
message:"黑马程序员!!!",
info:"前端与移动端"
}
})
</script>

v-html指令

v-html的指令作用是:设置元素的innerhtml
内容中有html结构会被解析为标签
v-text指令无论内容是什么,只会解析为文本

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<p v-html="content"></p>
<p v-text="content"></p>
</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
content: "<a href='http://www.itheima.com'>黑马程序员</a>"
}
})
</script>

v-on指令

v-on指令在methods里写下将要做的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
<input type="button" value="v-on指令" v-on:click="doIt">
//可缩写为@
<input type="button" value="v-on简写" @click="doIt">
<input type="button" value="双击事件" @dblclick="doIt">
<h2 @click="changeFood">{{ food }}</h2>
</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
food: "番茄炒鸡蛋"
},
methods: {
doIt: function () {
alert("做It")
},
changeFood: function () {
this.food += "好吃!"
}
}
})
</script>

v-cloak指令(没有值):

  1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
  2.使用v-cloak可以解决网速慢时页面展示出的大括号xxx大括号的问题

v-once指令:

1.v-once所在节点在初次动态渲染后,就视为静态内容了
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能

v-pre指令:

1.跳过其所在结点的编译过程
2.可利用它跳过,没有使用指令语法、没有使用插值语法的节点,会加快编译

v-show指令

 写法:v-show='表达式'

v-show指令作用是:根据真假切换元素的显示状态
原理是修改元素的display,实现显示和隐藏
指令后面的内容,最终都会解析为布尔值
值为true元素显示,值为false元素隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
<input type="button" value="切换显示状态" @click="changeIsshow">
<img v-show="isShow" src="profile.jpg" alt="">
<img v-show="age>=18" src="profile.jpg" alt="">
<input type="button" value="更改年龄" @click="addAge">
</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
isShow: false,
age: 17
},
methods: {
changeIsshow: function () {
this.isShow = !this.isShow;
},
addAge: function () {
this.age++;
console.log(this.age)
}
}
})

v-if指令

1.v-if='表达式'
2.v-else-if='表达式'
3.v-else='表达式'

注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”
v-if指令作用是:根据表达式的真假切换元素显示状态
本质是通过操纵dom元素来切换显示状态
表达式的值为true,元素存在于dom树中,为false,从dom树中移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="app">
<input type="button" value="切换显示" @click="toggleIsShow">
<p v-if="isShow">黑马程序员</p>
<p v-show="isShow">黑马程序员 - v-show修饰</p>
<h2 v-if="temperature>=35">热死了</h2>
</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
isShow: false,
temperature: 40
},
methods: {
toggleIsShow: function () {
this.isShow = !this.isShow;
}
}
})
</script>

template语法(模板)使用

当v-if条件成立时 template会在页面生成后自动删除

1
2
3
4
5
<template v-if="n===1">
<h2>你好</h2>
<h2>北京</h2>
<h2>南京</h2>
</template>

v-bind属性

v-bind指令的作用是:为元素绑定属性,数据只能从data流向页面
可以简写为:属性名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<style>
.active {
border: 1px solid red;
}
</style>

<body>
<div id="app">
<img v-bind:src="imgSrc" alt="">
<br>
<!-- v-vlind可简写 -->
<img :src="imgSrc" alt="" :title="imgTitle+'!!!'" :class="{active:isActive}" @click="toggleActive">

</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
imgSrc: "profile.jpg",
imgTitle: "黑马",
isActive: false
},
methods: {
toggleActive: function () {
this.isActive = !this.isActive;
}
}
})
</script>

绑定class事件

字符串写法,适用于:样式的类名不确定,需要动态指定
数组写法,适用于:要绑定的样式个数不确定、名字也不确定
对象写法:适用于:要绑定的央视个数确定,名字也确定,但要动态决定用不用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<div class="basic" :class="mood" @click="changeMood"></div>
<div class="basic" :class="classArr" @click="changeMood"></div>
<div class="basic" :class="classObj" @click="changeMood"></div>
<script src="vue.js"></script>
<script>
const vm=new Vue({
el:'',
data:{
name:'',
mood:'',
classArr:['class01','class02','class03'],
classObj:{
class01:false,
class02:false
}
}
})
</script>

style样式

:style=’{fontSize:xxx}’其中xxx是动态值
:style=’[a,b]’其中a,b是样式对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="basic" :style='styleObi'>{{}}</div>
<script>
const vm=new Vue(
{
el:'#root',
data:{
name:'',
styleObj:{
fontSize:'40px',
color:'red',
}
}
}
)
</script>

v-model指令

双向绑定,数据不仅能从data流向页面,还可以从页面流向data
一般用在表单元素上(input、select)
v-model:value 可以简写为v-model,因为v-model默认收集的就是value

1
2
3
4
5
<input type="text" v-model="name">
//相当于
<input type="text" v-model:value="name">
//如下代码错误
<h2 v-model:x="name">你好啊</h2>

v-for指令

v-for指令的作用是:根据数据生成列表结构
v-for不仅可以遍历数组也可以遍历对象字符串
数组经常和v-for结合使用
语法是(item,index)in数据
item和index可以结合其他指令一起使用

key的作用和原理

1.虚拟DOM中key的作用
key是虚拟DOM对象标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【虚拟DOM】,随后Vue进行【新的虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2.对比原则:
(1)旧的虚拟DOM中找到了与新虚拟DOM相同的key:
1.若虚拟DOM中内容没变,直接使用之前的真实DOM
2.若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
(2)旧的虚拟DOM中未找到与新虚拟DOM相同的key
3.用index作为key可能引发的问题:
1.若对数据进行:逆序添加、逆序删除:
会产生没有必要的真实DOM更新==>界面效果没问题,但效率低
2.若结构还包括输入类的DOM:
会产生错误DOM更新==>界面有问题
4.开发中如何选择key?
1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  <div id="app">
<input type="button" value="添加数据" @click="add">
<input type="button" value="移除数据" @click="remove">
<ul>
<li v-for="(it,index) in arr">
<!-- it可以用其它英文代替 -->
{{ index+1 }}HtuPengyuyan:{{ it }}
</li>
</ul>
<h2 v-for="item in food" v-bind:title="item.name">
{{item.name}}
</h2>
</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
arr: ["北京", "上海", "广州", "深圳"],
food: [
{ name: "西兰花" },
{ name: "西红柿" }
]
},
methods: {
add: function () {
this.food.push({ name: "黄瓜" })
},
remove: function () {
this.food.shift();
}
}
})
</script>
> 收集表单数据
若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value的值。
账号:<input type='text' v-model.trim="userInfo.account"><br><br/>
1
若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
男:<input type="radio" name="sex" v-model="userInfo.sex" value="male"> 女:<input type="radio" name="sex" v-model="userInfo.sex" value="female"><br/><br>
1
2
若:<input type="checkbox"/>
1.没有配置input的value属性,那么收集的就是checked(勾选or未勾选,是布尔值)

阅读并接受

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

(2)v-model的初始值是数组,那么收集的就是value组成的数组

爱好:
学习
打游戏
吃饭

1
2
3
4
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效数字
trim:输入首尾空格要过滤

v-on补充

事件绑定的方法写成函数调用的形式,可以传入自定义参数
定义方法时需要定义形参来接收传入的实参
事件的后面跟上.修饰符可以对事件进行限制
.enter可以限制触发的按键为回车

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<input type="button" value="点击" @click="doIt(666,'老铁')">
<input type="text" @keyup.enter="sayHi">
</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
methods: {
doIt: function (p1, p2) {
console.log(p1);
console.log(p2)
},
sayHi: function () {
alert("吃了没")
}
}
})
</script>

v-model指令

v-model指令的作用是便携的设置和获取表单元素的值
绑定的数据会和表单元素值相关联
绑定的数据⬅➡相关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="app">
<input type="button" value="修改message" @click="setM">
<input type="text" v-model="message" @keyup.enter="getM">
<h2>{{ message }}</h2>
</div>
<script src="vue.js"></script>
<script>
var app = new Vue({
el: "#app",
data: {
message: "黑马程序员"
},
methods: {
getM: function () {
alert(this.message);
},
setM: function () {
this.message = "帅气"
}
}
})
</script>

自定义指令

定义语法:
1.局部指令:

1
2
3
new Vue({                                     new Vue({
directives:{指令名:配置对象} 或 directives(指令名:回调函数)
}) })

2.全局质量:

1
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名),回调函数

配置对象中常用的三个回调:
1.bind:指令与元素成功绑定时调用
2.inserted:指令所在元素被插入页面时调用
3.update:指令所在模板结构被重新解析时调用
备注:
1.指令定义时不加v-,但使用时要加v-
2.指令名如果时多个单词,要使用kebab-case明明方式,不要用cameCase命名

1
2
3
4
5
6
7
8
9
10
11
12
Vue.directive('fbind',{
bind(element,binding){
//主要用到binding里的value值
element.value=binding.value
},
inserted(element,binding){
element.focus()
},
update{
element.value=binding.value
},
})

过滤器:

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
1.注册过滤器:Vue.filter(name,callback)或new Vue(filter:{})
2.使用过滤器: xxx | 过滤器名 或 v-bind:属性=”xxx | 过滤器名”
备注:
1.过滤器也可以接受额外参数、多个过滤器也可以串联
2.并没有改变原本的数据,是产生新的对应的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<div id="root">
<h2>显示格式化后的事件</h2>
//计算属性实现
<h3>现在是:{{fmtTime}}</h3>
//methods实现
<h3>现在是:{{getFmtTime()}}</h3>
//过滤器实现
<h3>现在是{{time | timeFormater}}</h3>
//过滤器实现(传参)
<h3>现在是{{time | timeFormater('YYYY_MM__mySlice')}}</h3>
<script>
new Vue({
el:"#root",
data:{
time:
},
computed:{
fmtTime(){
return datjs(this.time.format(YYYY-MM-DD-HH:mm:ss))
//format格式化
}
},
methods{
getFmtTime(){
return datjs(this.time.format(YYYY-MM-DD-HH:mm:ss))
}
},
filiters:{
timeFormater(value){
return datjs(this.time.format(YYYY-MM-DD-HH:mm:ss))
}
}
})
</script>

生命周期:

1.又名:生命周期回调函数、生命周期函数、生命周期钩子
2.是什么:Vue在关键时刻帮我们调用的一些特殊名称函数
3.生命周期函数的名字不可更改,但函数的具体内容时程序员根据需求编写的
4.生命周期函数中的this指向时vm 或 组件实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<title>分析生命周期</title>
<script type="text/javascript" src="../js/vue.js"></script>

<div id="root" :x="n">
<h2 v-text="n"></h2>
<h2>当前的n值是:{{ n }}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>

<script type="text/javascript">
Vue.config.productionTip = false

new Vue({
el: '#root',
// template:`
// <div>
// <h2>当前的n值是:{{n}}</h2>
// <button @click="add">点我n+1</button>
// </div>
// `,
data: {
n: 1
},
methods: {
add() { console.log('add')
this.n++
},
bye() {
console.log('bye')
this.$destroy()
}
},
watch: {
n() {
console.log('n变了')
}
},
beforeCreate() {console.log('beforeCreate')},
created() {console.log('created')},
beforeMount() {console.log('beforeMount')},
mounted() {console.log('mounted')},
beforeUpdate() {console.log('beforeUpdate')},
updated() {console.log('updated')},
beforeDestroy() {console.log('beforeDestroy')},
destroyed() {console.log('destroyed')},
})
</script>

总结

常用的生命周期钩子:
1.mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
2.beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息
2.销毁后自定义事件会失效,但原生DOM依然有效、
3.一般不会再beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<title>引出生命周期</title>
<script type="text/javascript" src="../js/vue.js"></script>

<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>

<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
opacity: 1
},
methods: {
stop() {
this.$destroy()
}
},
// Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted() {
console.log('mounted', this)
this.timer = setInterval(() => {
console.log('setInterval')
this.opacity -= 0.01
if (this.opacity <= 0) this.opacity = 1
}, 16)
},
beforeDestroy() {
clearInterval(this.timer)
console.log('vm即将驾鹤西游了')
},
})
</script>

Vue组件化编程

模块

  1. 理解:向外提供特定的js程序,一般就是一个js文件
  2. 为什么:js文件很多很复杂
  3. 作用:复用、简化js的编写,提高js运行效率
    组件
  4. 定义:用来实现局部功能的代码和资源的集合
  5. 为什么:一个界面的功能很复杂
  6. 作用:复用编码、简化项目编写,提高运行效率
    模块化
    当应用中的js豆一木块来编写的,那这个应用就是一个模块化的应用
    组件化
    当应用中的功能都是多组件的方式编写的,那这个应用就是一个组件化的应用

非单文件组件

非单文件组件:一个文件中包含有n个组件
单文件组件:一个文件中只包含有1个组件
#####基本使用
1.定义组件
使用Vue.extend(options)常见,其中options和new Vue(options)时传入的options几乎一样,但也有点区别

  1. el不要写,因为最终所有的组件都要经过一个vm的管理,由vm中的el才决定服务哪个容器
  2. data必须写成函数,避免组件被复用时,数据存在引用关系
    1
    2
    3
    4
    5
    6
    data() {
    return {
    studentName: '张三',
    age: 18
    }
    }
    2.注册组件
  3. 局部注册:new Vue()的时候opyions传入compoents选项
  4. 全局注册:Vue.component(’组件名’,组件)
    3.使用组件
    编写组件标签如
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    <title>基本使用</title>
    <script type="text/javascript" src="../js/vue.js"></script>

    <div id="root">
    <h2>{{msg}}</h2><hr>
    <!-- 第三步:编写组件标签 -->
    <school></school><hr>
    <student></student><hr>
    <hello></hello><hr>
    </div>

    <div id="root2">
    <hello></hello>
    </div>

    <script type="text/javascript">
    Vue.config.productionTip = false

    //第一步:创建school组件
    const school = Vue.extend({
    // el:'#root', //组件定义时,一定不要写el配置项,
    // 因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
    template: `
    <div class="demo">
    <h3>学校名称:{{schoolName}}</h3>
    <h3>学校地址:{{address}}</h3>
    <button @click="showName">点我提示学校名</button>
    </div>
    `,
    data() {
    return {
    schoolName: '尚硅谷',
    address: '北京昌平'
    }
    },
    methods: {
    showName() {
    alert(this.schoolName)
    }
    },
    })

    //第一步:创建student组件
    const student = Vue.extend({
    template: `
    <div>
    <h3>学生姓名:{{studentName}}</h3>
    <h3>学生年龄:{{age}}</h3>
    </div>
    `,
    data() {
    return {
    studentName: '张三',
    age: 18
    }
    }
    })

    //第一步:创建hello组件
    const hello = Vue.extend({
    template: `
    <div>
    <h3>你好啊!{{name}}</h3>
    </div>
    `,
    data() {
    return {
    name: 'cess'
    }
    }
    })

    //第二步:全局注册组件
    Vue.component('hello', hello)

    //创建vm
    new Vue({
    el: '#root',
    data: {
    msg: '你好啊!'
    },
    //第二步:注册组件(局部注册)
    components: {
    school,
    student
    }
    })

    new Vue({
    el: '#root2',
    })
    </script>

组件注意事项

关于组件名

1.一个单词组成

  1. 第一种写法(首字母小写):school
  2. 第二种写法(首字母大写):School
    2.多个单词组成
  3. 第一种写法(kebab-case,命名):my-school
  4. 第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)!Vue官方推荐
    3.备注
  5. 组件名尽可能回避html中已有的元素名称,例如:好、H2都不行
  6. 可以使用name配置项指定组件在开发者工具中呈现的名字
关于组件的标签
  1. 第一种写法:
  2. 第二种写法:(需要Vue脚手架的支持)
  3. 备注:不使用脚手架时,会导致后续组件不能渲染
    一个简写方式:const school=Vue.extend(options)可简写为 const school=options,因为父组件components引入的时候会自动创建

组件的嵌套

VueComponent

  1. school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,而是Vue.extend()生成的
  2. 我么只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的new VueComponent(options)
  3. 每次调用Vue.extend,返回的都是一个全新的VueCompoent,即不同的组件是不同的对象
  4. 关于this指向
  5. 组件配置中data 函数、methods中的函数、watch中的函数、computed中的函数它们的this均是VueComponent实例对象
  6. newVue(options)配置中:data函数、watch中的函数、computed中的函数它们的this均是Vue实例对象,即vm
  7. VueComponent的实例对象,以后简称vc(组件实例对象)Vue的实例对象,以后简称为vm

单文件组件

School.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div id='Demo'>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>

<script>
export default {
name:'School',
data() {
return {
name:'UESTC',
address:'成都'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>

<style>
#Demo{
background: orange;
}
</style>

Student.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>

<script>
export default {
name:'Student',
data() {
return {
name:'cess',
age:20
}
},
}
</script>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>

<script>
import School from './School.vue'
import Student from './Student.vue'

export default {
name:'App',
components:{
School,
Student
}
}
</script>

main.js

1
2
3
4
5
6
7
import App from './App.vue'

new Vue({
template:`<App></App>`,
el:'#root',
components:{App}
})

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!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>单文件组件练习</title>
</head>
<body>
<div id="root"></div>
<script src="../../js/vue.js"></script>
<script src="./main.js"></script>
</body>
</html>

VueCLi初始化脚手架

初始化脚手架

说明

  1. Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
  2. 最新版本是4.x
  3. 文档Vue CLi
    具体步骤
  4. 如果下载缓慢请配置npm淘宝镜像npm comfig registry http://registry.npm.taobao.org
  5. 全局安装@vue/cli npm install -g @vue/cli
  6. 切换到创建项目的目录,使用命令创建项目vue create xxx
  7. 选择使用vue的版本
  8. 启动项目npm run serve
  9. 打包项目npm run bulid
  10. 暂停项目ctrl+c

脚手架文件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.文件目录
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件
render函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
el:'#app',
// render函数功能:将App组件放入容器中
// 简写形式
render: h => h(App),
// 完整形式
// render(createElement){
// return createElement(App)
// }
})

vue.config.js配置文件

使用vue.config.js可以对脚手架进行个性化定制,和package.json同级目录

1
2
3
4
5
6
7
8
module.exports = {
pages: {
index: {
entry: 'src/index/main.js' // 入口
}
},
lineOnSave: false // 关闭语法检查
}

ref属性

ref被用来给元素或子组件注册引用信息(id的替代者)

  1. 应用在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象vc
  2. 使用方式
    1. 打表示:
      1
      <h1 ref="xxx"></h1>或<School ref='xxx'><School>
    2. 获取:
      1
      this.$ref.xxx
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      <template>
      <div>
      <h1 v-text="msg" ref="title"></h1>
      <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
      <School ref="sch"/>
      </div>
      </template>

      <script>
      import School from './components/School'

      export default {
      name:'App',
      components:{ School },
      data() {
      return {
      msg:'欢迎学习Vue!'
      }
      },
      methods: {
      showDOM(){
      console.log(this.$refs.title) // 真实DOM元素
      console.log(this.$refs.btn) // 真实DOM元素
      console.log(this.$refs.sch) // School组件的实例对象(vc)
      }
      },
      }
      </script>

props配置项

props 让组件接收外部传过来的数据

  1. 传递数据,通过v-bind使得里面的18是数字
  2. 接收数据
    1. 第一种方式(只接受)
      1
      props::['name','age']
    2. 第二种方式(限制类型)
      1
      props::[name:String,age:Number]
    3. 第三种方式(限制类型、限制必要性、指定默认值)
      1
      2
      3
      4
      5
      6
      7
         props: {
      name: {
      type: String, // 类型
      required: true,// 必要性
      default: 'cess'// 默认值
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中,然后去修改data中的数据

      ### mixin混入
      1. 功能:可以把多个组件共用的配置提取成一个混入对象
      2. 使用方式
      1. 定义混入
      ```javascript
      const mixin = {
      data() {....},
      methods: {....}
      ....
      }
  3. 使用混入
  4. 全局混入
    1
    Vue.mixin(xxx)
    2.局部混入
    1
    mixins['xxx']
    备注
  5. 组件和混入对象含同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时组件优先
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var mixin = {
    data: function () {
    return {
    message: 'hello',
    foo: 'abc'
    }
    }
    }

    new Vue({
    mixins: [mixin],
    data () {
    return {
    message: 'goodbye',
    bar: 'def'
    }
    },
    created () {
    console.log(this.$data)
    // => { message: "goodbye", foo: "abc", bar: "def" }
    }
    })
  6. 同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var mixin = {
    created () {
    console.log('混入对象的钩子被调用')
    }
    }

    new Vue({
    mixins: [mixin],
    created () {
    console.log('组件钩子被调用')
    }
    })

    // => "混入对象的钩子被调用"
    // => "组件钩子被调用"

plugin插件

  1. 功能:用于增强Vue
  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
  3. 定义插件(见下src/plugin.js)
  4. 使用插件:Vue.use()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    export default {
    install(Vue,x,y,z){
    console.log(x,y,z)
    //全局过滤器
    Vue.filter('mySlice', function(value){return value.slice(0,4)})

    //定义全局指令
    Vue.directive('fbind',{
    //指令与元素成功绑定时(一上来)
    bind(element,binding){element.value = binding.value},
    //指令所在元素被插入页面时
    inserted(element,binding){element.focus()},
    //指令所在的模板被重新解析时
    update(element,binding){element.value = binding.value}
    })

    //定义混入
    Vue.mixin({
    data() {return {x:100,y:200}},
    })

    //给Vue原型上添加一个方法(vm和vc就都能用了)
    Vue.prototype.hello = ()=>{alert('你好啊')}
    }
    }
    main.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import Vue from 'vue'
    import App from './App.vue'
    import plugins from './plugins' // 引入插件

    Vue.config.productionTip = false

    Vue.use(plugins,1,2,3) // 应用(使用)插件

    new Vue({
    el:'#app',
    render: h => h(App)
    })

scoped样式

  1. 作用:让样式在局部生效,防止冲突
  2. 写法: