浏览器中的JavaScript的组成部分
js核心语法
WebAPI:BOM、DOM
为什么JavaScript可以在浏览器中执行?
浏览器中有JavaScript解析引擎;不同浏览器会有不同JavaScript解析引擎
Chrome
=> V8
Firefox
=> OdinMonkey
safri
=> JSCore
IE
=> Chakra
其中,V8性能最好
为什么JavaScript可以操作DOM和BOM?
每个浏览器都内置了DOM和BOM这样的API函数,浏览器可以调用它们。
浏览器中的JavaScript运行环境
(1) 运行环境指代码正常运行所需的必要环境,比如Chrome浏览器运行环境:V8解析引擎、内置API函数
(2) V8 解析引擎 负责解析和执行 JavaScript代码
(3) 内置API是由运行环境提供的特殊接口,只能在所属的运行环境中被调用
JavaScript能否做后端开发?
能,但是要借助Node.js运行环境
node -v
,终端是专门为开发人员设计的,用于实现人机交互node 要执行的js文件路径
cd 要切换到的目录
shift
键+鼠标右键
,可以快捷打开当前目录的终端,这样打开的终端是新版本终端Power Shell,不是旧版cmd↑
键,快速定位到上一次执行命令,不用再敲一次命令了tab
键,可以快速补全文件路径esc
键,夸苏清空当前已输入的命令cls
命令,清空终端fs模块
是Node.js官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足对文件的操作需求fs.readFile()
方法,用来读取指定文件的内容,fs.writeFile()
方法,用来向指定文件写入内容等const fs = require('fs')
语法:fs.readFile(参数1,参数2,参数3)
参数1:必选,表示文件路径
参数2:可选,表示文件编码格式
参数3:必选,表示文件读取完成胡的回调函数,函数有两个参数,第一个是失败的结果,第二个是成功的结果,如果读取成功或者失败,另一个参数就会是null
// 1. 导入模块
const fs = require('fs');
// 2. 读取文件
fs.readFile('./文本文件.txt','utf8',function(err, dataStr){
console.log(err);
console.log('--------');
console.log(dataStr);
})
判断读取成功与否
判断两个参数是否为null,err为null表示成功,否则失败
const fs = require('fs');
fs.readFile('./文本文件.txt','utf8',function(err, dataStr){
if(err===null)
console.log('读取成功 '+dataStr);
else
return console.log('读取失败 '+err.message)
})
fs.writeFilse(file, data, [ options], callback)
案例描述:使用fs模块,将素材目录下 成绩.txt 文件中的考试数据整理到 成绩-ok.txt 文件中,并且原文件都在同一行,希望存到目标文件中一个人一行。
// 1.导入fs模块
const fs = require('fs');
// 2.读取文件内容
fs.readFile('D:/学习/前端/练习代码合集/004nodejs/成绩.txt','utf8',function(err,datastr){
// 3.判断是否读取成功
if(err){
return console.log('读取文件失败,'+err.message);
}
console.log('读取文件成功'+datastr);
// 4.处理读取到的数据
const arrOld = datastr.split(' ');
const arrNew = [];
arrOld.forEach(function(item){
arrNew.push(item.replace('=',':'));
})
const newStr = arrNew.join('\r\n') // 要把数组转换成字符串类型
// 5.写入新的文件
fs.writeFile('./成绩-ok.txt',newStr,function(err){
if(err){
console.log('写入失败,'+err.message);
}
else{
console.log('写入成功');
}
})
})
__dirname
,用它拼接目录,表示当前文件所处的目录fs.readFile(__dirname+'/文本文件.txt','utf8',function(err, dataStr) {} )
,当要执行的文件位置更换时,也不会影响内部代码。path.join()
方法,用来将多个路径片段拼接成一个完整的路径字符串path.bathname()
方法,用来从路径字符串中,将文件名解析出来const path = require('path');
../
会抵消前一层路径path.join( path1,path2,path3...)
,返回stringconsole.log(path.join('/app','/a','/b','../','/c')) // 输出:app/a/c
console.log(path.join(__dirname,'a'))
path.bathname(fpath, [ext])
,第一个参数必选,表示文件路径字符串,第二个参数可选,表示文件扩展名,如果有,就会从结果中删除这个扩展名;返回stringlet fpath = 'a/b/c/d/index.html'
let a = path.basename(fpath); // a = 'index.html'
let b = path.basename(fpath,'.html'); // b = 'index'
path.extname(fpath)
,返回字符串let fpath = 'a/b/c/d/index.html'
let a = path.basename(fpath); // a ='.html'
功能:将一个html文件(含style和script标签)拆分为三个文件:html、css、js
const fs = require('fs');
const path = require('path');
// 1.创建两个正则表达式匹配style和script标签
const regStyle = /', '');
fs.writeFile(path.join(__dirname, './时钟案例/css.css'), newCss, (err) => {
if (err) return console.warn('写入css失败!' + err.message);
console.log('写入css成功');
})
}
// 3.2 处理js的方法
function resolveJS(htmlStr) {
const jsStr = regScript.exec(htmlStr); // exec正则表达式的方法,返回匹配的字符串数组
const newJS = jsStr[0].replace('', '');
fs.writeFile(path.join(__dirname, './时钟案例/js.js'), newJS, (err) => {
if (err) return console.warn('写入js失败!' + err.message);
console.log('写入js成功');
})
}
// 3.3 处理纯html
function resolveHTML(htmlStr) {
const newHtml = htmlStr.replace(regStyle, '')
.replace(regScript, '');
fs.writeFile(path.join(__dirname, './时钟案例/html.html'), newHtml, (err) => {
if (err) return console.warn('写入html失败!' + err.message);
console.log('写入html成功');
})
}
const http = require('http')
ping 网址
来获得某个网站服务器的IP地址,127.0.0.1
,代表本机IP地址。只有知道对方的IP地址才能实现数据通信。a.b.c.d
,取值在0~255之间IP:端口号
,只有80端口可以省略导入http模块 const http = require('http')
创建web服务器实例 const server = http.creatServer()
为服务器绑定事件,比如request事件,监听客户端的请求 server.on('request',(req, res) =>{ console.log('yes') })
启动服务器,实例的listen方法 server.listen(80, ()=>{ 内容 })
,参数为端口号,回调函数
在vscode使用ctrl+c快捷键停止服务器
要注意80端口号一般被其他程序占用了,不要用,使用其他端口号在访问时不能省略端口号
// 这里没写响应,浏览器拿不到数据,所以页面会一直转圈圈
const http = require('http');
const server = http.createServer();
server.on('request', function (req, res) {
console.log("收到客服端请求");
})
server.listen(8080, function () {
console.log('启动服务器 http://localhost:8080');
})
只要服务器接收到了客服端的请求,绑定了请求函数,那么成功请求后请求函数request回调函数的参数req里面就会存放于客户端相关的数据和属性
// req.url 客户端请求的url地址
console.log(req.url);
// req.method 是客服端请求的方式
console.log(req.method) // 'GET'
在服务器的request中,res放服务器响应的内容,内容会显示在浏览器上;
中文会乱码,要设置响应头:res.setHeader('Content-Type', 'text/html; charset=utf-8')
,意思是响应的内容编码格式为utf-8
// 第一个参数为要设置的响应头,后面是内容
res.setHeader('Content-Type','text/html;charset=utf-8');
/
或/index.html
),或者请求的是 /about.html
,根据请求页面响应不同内容const http = require('http');
const server1 = http.createServer();
server1.on('request', (req, res) => {
let content = '404 Not Found
';
const url = req.url;
if (url === '/' || url === '/index.html') {
content = '首页
';
}
else if(url === '/about.html'){
content = '这是about页';
}
res.setHeader('Content-type', 'text/html; charset=utf-8');
res.end(content);
})
server1.listen(8080, () => {
console.log('成功开启服务器');
console.log('http://localhost:8080/');
console.log('http://localhost:8080/index.html');
console.log('http://localhost:8080/about.html');
console.log('http://localhost:8080/nothave.html');
})
根据浏览器访问的网址,回应磁盘中不同文件的内容。
客户端是不能直接访问我们的磁盘的,但是服务器可以,所以自己这个服务器就充当了一个字符串搬运工。
浏览器通过http://localhost:8080/html.html
或http://localhost:8080/
访问
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer();
server.on('request', (req,res) => {
const url = req.url.toString(); // 拿到客户端请求的地址
console.log(url);
// 拼接路径,我这里这个js文件和要展示的网页文件不在同一个文件夹,所以要注意凭借的正确性,
// 而在html文件中,css和js的引入都是__dirname,所以会自动到html所在目录下拼接路径
// 这就体现了__dirname的可移植性
let fpath = '';
if (url === '/') {
fpath = path.join(__dirname, '../clock/html.html');
}
else {
fpath = path.join(__dirname, '../clock', url);
}
console.log(fpath);
fs.readFile(fpath, 'utf-8', (err, data) => { // 读文件
if (err) return res.end('404 Not Found.');
res.end(data);
})
});
server.listen(8080, () => {
console.log('http://localhost:8080/html.html');
})
指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程;对整个系统来说,模块是可组合、可分解和可更换的单元。
编程中的模块化就是遵守固定规则,把一个大文件拆成相互依赖的多个小模块。
require(参数)
方法就可以加载三种模块,但是自定义模块的加载参数是路径(可以省略扩展名),另外两种是模块名。和函数作用域类似,在自定义模块中的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,就叫模块作用域。
防止了全局变量污染的问题。
module对象
在每个.js自定义模块中都有一个module对象,在里面存储了和当前模块有关的信息
console.log(module); // 打印当前模块内部的信息,默认情况下为空{}
在自定义模块中,可以使用module.exports
对象,将模块内的部分成员共享出去,供外界使用;外界使用require()
方法自定义模块时,得到的就是module.exports
指向的对象。
// 模块
let a = 1;
// 给module添加属性
module.exports.username = 'Jack';
// 给module添加方法
module.exports.sayHello = function() {
console.log('Hello,I am Jack')
}
// 使用模块
const m1 = require('./module1.js');
console.log(m1); // {username:'Jack',sayHello:[Function]}
console.log(m1.username); // 'Jack'
m1.sayHello(); // 'Hello,I am Jack'
注意:使用require()导入模块时,导入的结果永远以 module.export指向的对象 为准。
module.exports.name = 'Jack';
module.exports{
username:'Rose'
}
// 到这里,module.exports里面就没有name属性,只有username属性了
exports对象
,和module.exports指向同一个地址,只是简写。
使用误区:exports默认和module.exports指向同一个地址,但是当exports指向改变时,module.exports不会变,所以不要拿exports重新指向新对象来覆盖内容,最终还是以module.exports为准。
module.exports.name = 'Jack';
exports = {
username = '张三'
}
// name:'Jack'
示例分析:只是改变了exports的指向,module.exports的指向不变。
一个模块尽量只是用一种,避免出错
Node.js遵循CommonJS模块化规范,它规定了:
node.js的第三方模块又叫做包,Node.js中的包都是免费且开源的,都是基于内置模块封装出来的。
Node Package Manager(简称 npm包管理工具)
,在终端用npm -v
就可以查看版本号;用这个工具就可以下载包了。在项目中安装格式化时间的包
在终端使用:npm install 包的完整名称
,也可以简写成 npm i 包完整名称
npm install moment
使用require()导入格式化时间的包
const moment = require('moment');
参考moment的官方API文档 对时间进行格式化
const moment = require('moment');
const dt = moment().format('YYYY-MM-DD HH:mm:ss');
console.log(dt);
node_modules
文件夹和package-lock.json
的配置文件。@版本号
的格式,比如npm i [email protected],新的会覆盖旧的package.json
的包配置文件。用来记录一些配置信息,例如:项目名称版本号描述、用到的包、包的作用(开发or部署).gitignore
忽略文件中,避免有的包太大,传输太慢,资源浪费。npm init -y
命令,就会在当前项目中创建package.json文件;只能在英文的目录下成功运行,npm install命令安装时,会自动添加包名和版本号到这个包管理文件中,不需要手动维护。npm install
或者npm i
,就会按照dependencies结点读取包名称和版本号,下载项目依赖的所有包。npm install 包名 --save-dev
表示记录到dev节点中,简写为npm i 包名 -D
。使用npm uninstall 包名
就可以卸载包,并且修改配置文件中它的信息。
淘宝在国内搭建了一个服务器,专门把国外的包同步到国内服务器,我们可以到这个服务器下载,速度就提高了。
npm config get registry
查看当前的下载镜像源npm config set registry=http://registry.npm.taobao.org/
切换下载镜像源为淘宝镜像源npm install 包名 -g
就是把包安装为全局包,卸载也要加-g;只有工具性质的包才有全局安装的意义,一个可用把md文档转为html页面的小工具,使用步骤如下:
npm install i5ting_toc -g
i5ting_toc -f 要转换的md文件路径 -o
开发包发布包都跳了
下载这个包
npm i express@4.17.1
导入模块
const express = require('express');
创建服务器
const app = express();
启动服务器
app.listen(8080, () => { console.log('http://127.0.0.1:8080'); })
监听GET请求:
app.get('请求URL',(req, res)=>{ /* 处理函数 */ } )
监听POST请求:
app.post('请求URL',(req, res)=>{ /* 处理函数 */ } )
把内容响应给客户端:res.send(内容)
,响应内容可以是JSON对象{ a:x,b:y },也可以是文本内容 ‘str’
获取URL携带的查询参数:通过req.query
对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数;
客户端通过**?name1=value1&name2=value2**这种查询字符串的方式,发送到服务器,服务器自动解析到req.query内,
默认情况下是一个空对象,
// http://localhost:8080/index1?name=Jack&age=19
console.log(req.query); // { name: 'Jack', age: '19' }
获取URL中的动态参数:通过req.params
对象,可用访问到URL中通过:匹配到的动态参数。
// http://localhost:8080/index/value1/value2
// http://localhost:8080/index/1
app.get('/index2/:id', (req, res) => {
res.send(req.params);
});
const express = require('express');
const app = express();
app.listen('8080', () => {
console.log('成功启动服务器;');
console.log(`可以访问
http://localhost:8080/index1
http://localhost:8080/index2 `);
});
app.get('/index1', (req, res) => {
res.send('返回的一些数据');
console.log(req.query); // http://localhost:8080/index1?name=Jack&age=19 { name: 'Jack', age: '19' }
});
app.get('/index2', (req, res) => {
res.send({ name: 'Jack', age: 18, sex: '男' });
res.send(req.params);
});
app.use()
方法来配置express.static()
。app.use(express.static('./clock')); //clock为文件夹,目录下有三个子文件
// http://loaclhost:3000/image/1.jpg
这样就向外提供了clock静态资源,但是在访问时URL中不会出现clock,即文件目录名。app.use('/clock', express.static('./clock')); //clock为文件夹,目录下有三个子文件
// http://loaclhost:3000/clock/image/1.jpg
在编写Node.js文件的时候,如果修改了代码,需要频繁的重启项目,非常繁琐。现在可以用nodemon自动帮我们重启项目,它能监听项目。
npm install -g nodemon
nodemon .\01server.js
广义上来讲,路由就是映射关系。
在express中,路由就是值客户端与服务器处理函数之间的映射关系。
在express中路由由三部分组成:请求的类型(method)、请求的URL地址(path)、处理函数(handler)。app.method(path,handler)
每当一个请求到达服务器后,需要先经过路由匹配,只有匹配成功之后,才会调用对应的处理函数。在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL都匹配成功了,express才会将这次请求转交给对应的function函数进行处理。
为了方便对路由进行模块化管理,express官方不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
将路由抽离为单独的模块的步骤如下:
express.Router()
函数创建路由对象module.exports
向外共享路由对象app.use()
函数注册路由模块,这个函数是注册全局中间件// 路由模块
// 引入express模块
const express = require('express');
// 创建路由对象
const router = express.Router();
// 在路由对象上挂载路由
router.get('/router', (req, res) => {
res.end('Get express router.')
})
// 向外暴露路由对象
module.exports = router;
// 使用
const express = require('express');
const app = express();
// 引入路由模块,创建实例对象
const router = require('./router.js');
// 注册中间件
app.use(router);
app.listen(8080, () => {
console.log('http://localhost:8080/router')
})
类似于托管静态资源的添加前缀格式,在app.use()函数中增加前缀字符串参数即可
app.use('/api',router);
中间件就是业务处理的中间环节。
当一个请求到达Express服务器后,可以连续调用多个中间件,从而对这次请求进行预处理。
意思是中间件在路由之前,只有中间件函数执行完成了,才能执行路由函数(对请求的处理)
app.get(req, res, next)
本质就是一个function处理函数,中间件的参数中,必须包含next函数参数,放在req、res之后,而路由处理函数只包含req和res两个参数,以此可区分中间件和路由处理函数
// 定义中间件
const mw = function (req, res, next) {
console.log('最简单的中间件函数');
next();
}
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
在当前中间件业务处理完成之后,必须调用next函数。
客户端发起任何请求,到达服务器后,都会触发中间件,叫做全局生效的中间件。
通过调用 app.use
即可定义一个全局生效的中间件。
// 定义中间价
const mw = function (req, res, next) {
console.log('最简单的中间价函数');
next();
}
// 将mw注册为全局生效的中间件
app.use(mw);
将定义和全局生效合成一步
// 这里中间件就是一个匿名函数了
app.use(function (req, res, next) {
console.log('最简单的中间价函数');
next();
});
多个中间件之间是可以共享同一份req和res的,基于这样的特性,我们可以在上游给req或res对象添加自定义方法或属性,供下游中间件和路由使用。
// 使用
const express = require('express');
const app = express();
const mw = function (req, res, next) {
// 获取请求到达服务器的时间
const time = Date.now();
req.startTime = time; //这里
next();
}
app.use(mw);
app.get('/',(req, res)=>{
console.log(req.startTime);
}
app.listen(8080, () => {
console.log('http://localhost:8080/router')
})
这个服务器在 mw 路由中挂载了自定义属性 startTime ,存请求到达服务器的时间,提供给后面的路由使用。
const time = Date.now();
req.startTime = time; //这里
可以使用app.use()
连续定义多个全局中间件。客户端请求到达服务器之后,会按照use中的定义顺序依次进行调用
app.use(mw1).use(mw2)
不使用use添加中间件,而是在路由中引入,这样引入的中间件就只在当前路由中生效。
在中间可以引入任意多个中间件函数,中括号可要可不要。
app.get('/',[mw1,mw2,...], (req, res)=>{
res.send('good');
})
为了方便理解和使用,官方把常见中间件分为5大类:
通过app.use()或app.get()或app.post()绑定在app(Express实例)上的。
const express = require('express');
const app = express();
app.use(mw);
绑定到Router()实例上的中间件
const express = require('express');
const app = express();
const router = express.Router();
router.use(function(req, res, next){
next();
})
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的处理函数中,必须有4个形参,(err, req, res, next)
在项目实际运行中,一旦发生错误(抛出异常),整个项目就会崩溃停止,这里错误级别中间件就可以捕获这样的错误,对它进行处理。
错误级别的中间件一定要放在所有中间件包括路由之后
express内置了3个常用中间件:
express.staic
快速托管静态资源中间件,无兼容性,任何版本express中都可用express.json
解析JSON格式的请求体数据,有兼容性,仅在4.16.0+版本中可用express.urlencoded
解析 URL -encoded 格式的请求体数据,有兼容性,仅支持4.16.0+版本req.body
上,默认为空对象app.use(express.json());
app.use(express.urlencoded({ extend:false }))
// 这样配置后,这两个中间体就会根据请求体的数据格式自动解析并且将数据挂载到req.body
例如:body-parser
这个第三方中间件,可以用来解析请求体数据,用法和官方内置的类似。
使用步骤如下:
npm install body-parser
安装中间件parse = require('body-parser');
app.use(parse.urlencoded({ extend:false }))
手动实现一个类似于express.urlencode这样的中间件,解析POST提交到服务器的表单数据。
// api路由模块
// 导入express创建路由实例
const express = require(‘express’);
const router = express.Router();
// 挂载对应的路由,也就是接口
// 1.GET接口
router.get(‘/get‘,(req,res)=>{
//通过req.query获取客户端通过查询字符串,发送到服务端的数据
const query = req.query;
// 调用res.send向客户端响应处理的结果
// 传递的数据由前后端协商好
res.send({
status: 0, //0表示处理成功,1表示失败
msg: ‘GET 请求成功’, // 状态的描述
data: query // 需要响应给客户端的数据
})
});
// 2.POST接口
router.post(‘/post’, (req, res)=>{
// 通过req.body获取请求体中包含 url-encoded格式的数据
// 这种格式的数据必须通过配置中间件来解析,在服务器中通过use配置
const body = req.body;
res.send({
status: 0,
msg: ‘POST 请求成功‘,
data: body
})
})
// 暴露路由
module.exports = router;
// 服务器
// 导入express模块
const express = require(‘express’);
// 创建服务器实例
const app = express();
// 添加中间件,包括路由
// 配置数据解析表单数据的中间件
app.use(express.urlencoded({ extended:false }));
// api路由模块引入
const router = require(‘./apiRouter’);
// 把路由模块挂载到服务器上, 加上前缀api
app.use(‘/api’,router);
// 启动服务器
app.listen(8080,()=>{
console.log(‘express server running at http://localhost:8080’);
})
协议、域名、端口号 任何一项不同就存在跨域问题。
解决接口跨域问题的方案主要有两种:
cors是Express的一个第三方中间件,直接安装即可,使用步骤:
npm install cors
安装中间件const cors = require('cors')
导入中间件调用app.use()
配置中间件res.setHeader(‘Access-Control-Allow-Origin’, ‘*’)
,通配符为*,表示允许来自任何域的请求。res.setHeader(‘Accss-Control-Allow-Header’, ‘Content-Type, X-Custom-Hader’)
,就是声明允许这两种请求头。根据请求头和请求方式的不同分为两大类:简单请求、预检请求
同时满足两大条件,就是简单请求
简单请求只会发生一次请求,预检请求会发生两次请求,OPTION之后才会发起真正的请求。
概念:浏览器通过script标签的src属性,请求服务器上 的数据,同时,服务器返回一个调用。这种请求数据的方式就叫做JSONP。
特点:
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口,否则JSONP接口会被处理成开启了CORS的接口。
// JSONP接口,不会配置CORS
app.get('/api/jsonp',(req,res)=>{})
// CORS中间件
app.use(cors())
// 这个接口会配置CORS
app.get('/api/get',(req,res)=>{})
app.get('api/jsonp',(req, res)=>{
// 1.
const funcName = req.query.callback
// 2.
const data = {
name = 'Jack',
age = 16
}
// 3.
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4.
res.send(scriptStr)
})