Nodejs.day1
01-nodejs入门
1.1-什么是nodejs
1.Node.js官网地址:https://nodejs.org/en/
2.Node是一个构建Chrome V8引擎之上的一个Javascript 运行环境
- Node
是一个运行环境,作用是让js拥有开发服务端的功能
- Node
3…Node中的
NPM是世界上最大的开源库生态系统(类似于github)- NMP官网:https://www.npmjs.com
1.2-Node.js环境安装
1.确认电脑是否安装Node环境
打开终端,输入
node -v,如果能看到版本号则说明当前电脑已经安装Node环境,如果提示Node不是内部或外部命令,则表示未安装一旦安装了node,则会自动一并安装`npm[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3tyT12I-1599793552966)(C:/Users/86188/AppData/Roaming/Typora/typora-user-images/1599028279378.png)]
1.3-服务端js与客户端js区别
- 前端js三个部分组成
- ECMAScript :确定js的语法规范
- DOM :js动态操作网页内容
- BOM :js动态操作浏览窗口
- 前端js三个部分组成
- 2.服务端nodejs 只有一个部分 ECMAScript
- nodejs可以使用 : 比那里 数据类型 运算符 分支循环
- nodejs不能使用 : document,window
- 因为服务端是没有界面的
- 在nodejs中使用dom和bom的api程序会报错
02.-ES6语法
2.1-变量声明let与const
/*
学习目标: 了解let与const声明变量特点
ES6学习技巧:对比法
学习路线:
1. 介绍ES5声明变量var特点
* a. 有变量提升--->编译器会将var声明提升到当前作业与最顶端
* b. 没有块级作用域 (块级作用域:大括号里面的变量都是局部的)
2. 介绍ES6声明变量let与const特点
* a. 没有变量提升 : 一定要先声明后使用
* b. 有块级作用域 : 只要是大括号里面声明的变量,都是局部
3. 介绍let与const区别
* a. let :变量。 可以重新赋值
* b. const :常量。 不可以重新赋值,只能在声明的时候唯一赋值一次
*/
/* 1.介绍ES5声明变量var特点 */
// 1.1 有变量提升
// var num; 编译器会将var声明提升到当前作用域最顶端
console.log(num);
var num = 10;
// 1.2 没有块级作用域 (块级作用域:大括号里面的变量都是局部的)
if(1){
// var在大括号里面声明的还是全局的
var num = 20;
}
console.log(num);
/* 2.介绍ES6声明变量let与const特点 */
// 2.1 没有变量提升
// console.log(num1);
// let num1 = 10;
// 2.2 有块级作用域
if(1){
let num2 = 20;
console.log(num2);
}
// console.log(num2);
/* 3.介绍let与const区别 */
// 3.1 let :变量(可以重新赋值) 和var一样
let a = 10;
console.log(a);
a = 100;
console.log(a);
// 3.2 const : 常量(不可重新赋值) 只能声明的时候赋值一次
const b = 800;
console.log(b);
2.2-结构赋值
/*
1.学习目标: 对象的解构赋值
解构赋值 : 变量赋值的简写形式
2.对比法学习
2.1 取出对象的属性 赋值给 变量
let {name,age,sex} = obj;
2.2 取出变量的值 赋值给 对象的属性
let obj = {name,age,sex}
2.3 解构赋值语法默认值
let {name,age=18,sex} = obj;
*/
/* 1. 取出对象的属性 赋值给 变量 */
//1.1 复习 以前ES5写法
let obj = {
name:'张三',
age:20,
sex:'男'
};
// let name = obj.name
// let age = obj.age
// let sex = obj.sex
console.log(name,age,sex);
// 1.1 复习 一起ES6写法
let obj1 = {
name:'张三',
age:20,
sex:'男'
};
// let name = obj.name
// let age = obj.age
// let sex = obj.sex
let {age,name,sex} = obj1
console.log(name,age,sex);
/* 2.2 取出变量的值 赋值给 对象的属性 */
let name = '李四';
let age = 22;
let sex = '女';
// ES5 写法
let obj2 = {
name:name,
age:age,
sex:sex
}
console.log(obj2);
// ES6 写法
let obj = {
name, // === name:name
age,
sex,
satHi:function(){console.log(11111);},
eat(){ // === eat:function(){}
console.log(22222);
}
}
console.log(obj2);
//3 解构赋值语法默认值
let obj3 = {
name:'王五',
sex:'男',
age:0
};
// let name = obj.name;
// let sex = obj.sex;
/*
(1)声明变量 let age
(2)检查obj有没有属性要age,有则赋值。没有则赋默认值
if(obj.age != undefined) {
age = obj.age
} else {
age = 18
}
*/
let {name,sex,age} = obj3;
console.log(name,sex,age);
2.3-数组解构
/* 数组解构赋值 */
let arr = [10,20,30]
/* 解构数组 */
// let n1 = arr[0]
// let n2 = arr[1]
// let n3 = arr[2]
// let n4 = arr[3]
let [n1,n2,n3,n4=100] =arr;
console.log(n1,n2,n3,n4);
2.4-函数解构与默认参数
/*
学习目标 :函数的结构赋值与函数默认参数
学习路线 :
1.复习函数传参本质: 实参给形参
2.函数解构赋值
3.函数默认参数
*/
// 1. 复习函数传参本质:实参给形参赋值
// ES5接收参数:let obj = {name:'张三',age:20}
// ES6接收参数:let{name,age,sex="男"} = {name:'张三',age:20}
// function fn ({name,age,sex="男"}){
// //ES5 :
// // console.log(obj);
// // let name = obj.name;
// // let age = obj.age;
// console.log(name,age,sex);
// }
// let preson = { name:'张三',age:20};
// fn(preson);
// 2.函数默认参数
function fn1(a=10,b=20){
// ES5 : 逻辑或短路
// a = a || 10;
// b = b || 20;
console.log(a,b);
}
fn1(100,200);
fn1();
fn1(5)
2.5-箭头函数
/*
1. 学习目标:箭头函数
*就是function函数的间歇形式
*(1)将function替换成箭头
*(2)将形参小括号移到箭头的=>的左边
* let fn = () => {}
2. 学习目标
2.1 一般用法
2.2 其他用法
* a. 如果函数只有一个形参,则可以省略形参小括号
* b. 如果函数只有一行,则可以省略函数体的大括号(此时必须要省略return
*/
// 1. ES5声明函数:function
// 无参无返回
let fn = ()=>{
console.log('5555');
}
fn();
// 有参有返回
let fn1 = (a,b)=>{
return a+b;
}
let res = fn1(10,20);
console.log(res);
// 2.其他用法
// 2.1 如果函数只有一个形参,则可以省略小括号
let fn2 = a=>{ return a*2};
let res2 = fn2(100);
console.log(res2);
// 2.2 如果函数体只有一行代码,则可以省略大括号(此时必须省略return)
let fn3 = a =>a*2;
let res3 =fn3(500);
console.log(res3);
2.6-模板字符串
/*
学习目标: 模板字符串
学习技巧: 对比法
1. 复习ES5学习的字符串特点:一切以 单引号 '' 或 双引号 "" 引起了的内容
1.1 不能字符串里面取变量的值:需要使用连接符
1.2 不能识别字符串格式 : 需要使用转义符
2. ES6新增 模板字符串:一切以反引号引起来的内容 ` `
2.1 可以去变量的值: ${变量名}
1.2 可以识别字符串格式
*/
// 1.复习ES5学习的字符串特点
// 1.1 不能在字符串中去变量的值 :只能使用字符串连接符 +
// 1.2 不能识别字符串本身格式 : 需要使用转义符
let name ="保健";
let str1 = '我爱\n'
+ name;
console.log(str1);
//2.ES6新增 模板字符串:一切以反引号引起来的内容 ` `
// 2.1 可以去变量的值:${变量名}
// 2.2 支持字符串格式
let name2 = '学生';
let str2 = `我爱
${name2}`;
console.log(str2);
2.7-拓展运算符
/*
1.拓展运算符(展开运算符) : ...
2.作用 :变量对象一种简写形式(类似于for-in循环)
3.应用场景:
a. 连接数组
b. 求最大/最小值
c. 一行代码实现数组去重
*/
// 1. 基本用法
let car = {
pinpai:'特斯拉',
price:888888
};
let hourse = {
address : '深圳湾一号'
}
let person = {
name: '维克多',
age:26,
...car,
...hourse
}
console.log(person);
// 2.应用场景
// 2.1 连接数组
let arr1 = [10,20,30]
let arr2 = [40,50,60]
// 之前:concat
let arr3 = arr1.concat(arr2); //连接arr1 和 arr2
arr3.push(70,80,90); //连接之后,继续添加元素
console.log(arr3);
// ES6展开运算符
let arr4 = [...arr1,...arr2,70,80,90];
console.log(arr4);
// 2.2 求数组最大值/最小值
let arr = [23,45,11,23,45,88,66,11,88,66];
// 之前:擂台思想、 Math.max.apply()
let max = Math.max.apply(Math,arr);
console.log(max);
// ES6展开运算符
let min = Math.min(...arr);
console.log(min);
2.8-数据类型
/*
1.新增数据类型(集合): Set
* 与数组几乎一致,惟一的区别是不支持重复元素(相当于是无法存储重复元素)
*/
// 声明Set : new Set(数组)
let set = new Set([10,20,30,66,77,30,20,77,10])
console.log(set);
// set一般不会单独使用,只是为了去掉重复数组元素。将set转成数组
let arr =[...set];
console.log(arr);
// 应用场景:数组去重
let arr1 = [21,54,67,88,54,21,67];
// 最简单的一行代码实现数组去重
let newArr = [...new Set(arr1)];
console.log(newArr);
03-babel网站:ES6转ES5
- babel官网地址:https://www.babeljs.cn/repl
- babel作用:把ES6语法转成ES5语法
- 由于ES6是js的最新语法,所以有的老版本浏览器不支持。这个网站可以查询ES6支持哪些浏览器:https://www.caniuse.com/#search=es6
Nodejs.day2
01-fs文件读写
1.1-readFile读取文件
1.前端框架使用流程
*导包 (下载) :得到一个对象,存储框架中的API
<script src=""></script>
*拥抱 : 调用对象方法
2.后端nodejs模块使用流程
*导包 :得到一个对象,存储框架中的API
*用包 :调用对象方法
fs.readFile('./data/c.txt','utf-8',(err,data)=>{
if(err){
console.log(err);
// 抛出异常,throw的作用就是让node程勋终止运行,方便调试
throw err;
}else{
console.log(data);
}
})
console.log('111');
1.2-writefile写入文件
//1. 导入模块
const fs = require('fs');
//2. 使用模块
/**
* @description: 写入文件
* @param {string} path : 文件路径
* @param {string} data : 要写入的数据
* @param {string} option : 文件编码 自动根据数据识别,一般不用传
* @param {function} callback : 写入回调 (err)=>{ err:错误信息 }
* @return:
*/
fs.writeFile('./data/b.txt','临渊羡鱼不如退而结网',err=>{
if( err ){
throw err;
}else{
console.log('写入成功');
}
});
1-3-同步异步
/* 学习目标: 同步与异步区别
1. 编译器执行代码流程
1.1 从上往下解析代码
1.2 判断代码是同步还是异步
同步 :立即执行
异步 :不执行,放入一个事件队列池 (EventLoop)
1.3 当页面所有的同步执行完毕之后,才会执行事件队列池中的代码
2. 同步与异步区别
2.1 API不同 :异步有回调函数,同步一般没有
* 前端有两个API是异步 : 定时器 和 ajax
2.2 执行顺序不同 : 同步有序且优先执行, 异步是无序执行
2.3 性能不同 : 异步性能高(不阻塞线程),同步阻塞线程
*/
// nodejs中所有的api默认都是异步的,如果希望拥抱就会后面加上Sync即可
/*异步特点
1. 有回调函数
2. 无序执行
3. 不阻塞线程 : 性能高
*/
fs.readFile('./data/c.txt','utf8',(err,data)=>{
console.log('11111');
})
/* 同步特点
1. 没有回调
2. 有序
3. 阻塞线程:性能低
*/
let data = fs.readFileSync('./data/c.txt','utf8');
console.log('7');
02-path路径
2-1.相对路径与绝对路径
//1.导入文件模块
const fs = require('fs');
//2.读取文件
/*a.注意点: node中的相对路径: ./ 不是相对于当前文件所在路径,而是相对于执行node命名的文件夹路径
在服务端开发中,一般不要使用相对路径,而使用绝对路径
b.解决方案:在nodejs中,每一个js文件都有两个全局属性,它可以帮助我们获取到文件的绝对路径
__filename:当前js文件所在目录的绝对路径
__dirname:当前js文件的绝对路径
*/
console.log(_dirname);
console.log(_filename);
/* 2.如果想要获取当前文件夹下其他文件绝对路径,可以使用 __dirname属性来拼接 */
var path = __dirname + '/aaa.txt';
console.log(path);
fs.readFile(path,'utf-8',(err,data)=>{
if(err){
console.log(err);
//抛出异常,throw的作用就是让node程序终止运行,方便调试
throw err;
}else{
console.log(data);
};
});
2-2.path模块介绍
/*
1. 前端相对路径 ./ 与nodejs后端 相对路径 ./ 区别
1.1 前端相对路径 ./ : 相对于当前文件 所在的文件夹 (固定的)
1.2 nodejs相对路径 ./ : 相对于 执行node命令 所在的文件夹 (不固定)
2. nodejs中文件路径一般不适应相对路径,使用绝对路径
* 在node中,每一个js文件都有两个默认的全局变量
* __dirname :当前js文件所在的文件夹绝对路径
* __filename : 当前js文件本身的绝对路径
3. path模块作用: 拼接文件路径
作用和自己拼接差不多,但是容错率更高
* 自动识别当前操作系统的磁盘分隔符,会自动添加
* 接错功能 :如果磁盘疯符写错了,path模块会自动纠正
*/
//1.导入模块
//(1)参数: 模块路径 (2)返回值: 对象 , 存储模块所有的api
const fs = require('fs');
//2.使用模块
fs.readFile( aPath ,'utf-8',function(err,data){
if(err)throw err;
console.log(data);
});
03-http服务器
3-1-搭建服务器
//1.导入模块
const http = require('http');
//2.创建服务器
let server = http.createServer((req,res)=>{
//req : 请求对象。 负责获取客户端的请求信息 res : 响应对象。 负责响应客户端数据的
console.log(req.url);
//了解即可: url中的中文会被进行urlencode转码
console.log(decodeURI(req.url));
});
//3.开启服务器
server.listen(3000,()=>{
console.log('服务器开启成功');
});
Nodejs.day3
01-nodejs搭建服务器
01-1-搭建静态服务器
/* nodejs搭建静态资源服务器
1. 浏览器中所有的外部资源路径,都会变成网络请求
*/
// 1. 导入模块
const http = request('http');
const fs = require('fs');
const path = require('path');
//2.创建服务器
let server = http.createServer((req,res)=>{
// (1)请求:req
console.log(req.url);
// (2)处理
if (req.url == '/') {
// 读取html文件响应返回
fs.readFile(path.join(__dirname,'www','index.html'),'utf8',(err,data)=>{
if(err) throw err;
// (3)响应:res
res.end(data);
});
}else if (req.url == '/resource/css/index.css'){
fs.readFile( path.join(__dirname,'www','resource','css','index.css'),(err,data)=>{
if(err) throw err;
//(3)响应:res
res.end(data);
});
}else if( req.url == '/resource/images/01.gif'){
fs.readFile( path.join(__dirname,'www','resource','images','01.gif'),(err,data)=>{
if(err) throw err;
//(3)响应:res
res.end(data);
});
}else if( req.url == '/resource/images/01.jpg'){
fs.readFile( path.join(__dirname,'www','resource','images','01.jpg'),(err,data)=>{
if(err) throw err;
//(3)响应:res
res.end(data);
});
}else if( req.url == '/resource/video.mp4'){
fs.readFile( path.join(__dirname,'www','resource','video.mp4'),(err,data)=>{
if(err) throw err;
//(3)响应:res
res.end(data);
});
}else{//错误路径
res.end('404 not found');
}
});
//3.开启服务器
server.listen(3000,()=>{
console.log('服务器开启成功');
});
/* nodejs搭建静态资源服务器
1. 浏览器中的所有外部资源路径,都会变成网络请求
2. 一般静态资源(css文件,图片,音视频)使用 url路径拼接的方式来处理
*/
//1.导入模块
const http = require('http');
const fs = require('fs');
const path = require('path');
//2.创建服务器
let server = http.createServer((req,res)=>{
// (1)请求:red
console.log(req.url);
// (2)处理
if( req.url == '/' ){
//读取html文件响应返回
fs.readFile( path.join(__dirname,'www','index.html'),(err,data)=>{
if(err) throw err;
//(3)响应:res
res.end(data);
});
}else if( req.url.indexOf('/resource') == 0 ){
fs.readFile( path.join(__dirname,'www',req.url),(err,data)=>{
if(err) throw err;
//(3)响应:res
res.end(data);
});
}else{//错误路径
res.end('404 not found');
}
});
//3.开启服务器
server.listen(3000,()=>{
console.log('服务器开启成功');
});
- 1.html中所有外部资源路径都会变成网络请求
- 服务器需要根据请求路径响应返回对应的文件
- 2.静态资源(图片、文件、音视频)一般使用路径拼接的方式来处理
- 服务短板可以直接拼接url来响应对应资源,简化代码
02-nodejs接收get请求与post请求
02-1-nodejs接收get参数
/*
学习目标:nodejs接受get请求参数
1. 前端发送get请求:直接在url后面拼接 url?key1=value&key2=val2
2. nodejs接受get请求参数
2.1 导入url模块
2.2 调用parse()方法解析参数: url.parse( req.url,true)
2.3 获取解析好的query属性: get参数
*/
//1.导入模块
const http = require('http');
const url = require('url');
//2.创建服务器
let server = http.createServer((req,res)=>{
// (1)get请求采纳数直接在url后面拼接
console.log(req.url);
// (2)get参数解析原理是 字符串切割
// 第一个参数:要切割的url字符串
// 第二个参数:布尔类型 默认false:query不处理 true:query处理成对象
let obj = url.parse(req.url,true);
console.log(obj);
let {query} = obj;
let query = obj.query
console.log(query);
// 响应返回参数:告诉客户端解析成功 (js对象需要转json对象才可以响应返回)
res.end(JSON.stringify(query));
});
//3.开启服务器
server.listen(3000,()=>{
console.log('服务器开启成功');
});
02-2-nodejs接收post参数
/*
学习目标:nodejs接受post请求参数
1. 前端发送post
1.1 需要设置请求头
1.2 请求头中发送: xhr.send(key=value)
2. nodejs接受post参数
2.1 给red注册data事件 (接收数据包)
2.2 给red注册end事件 (数据包接受完毕)
2.3 在end方法中使用querystring模块解析body
*/
// 1. 导入模块
//1.导入模块
const http = require('http');
// 解析get请求参数
const url = require('url');
// 解析post请求参数
const querystring = require('querystring');
//2.创建服务器
let server = http.createServer((req,res)=>{
console.log(req.url);
/* (1)给req注册data事件
客户点每发送一个数据包,这个事件就会车发一次 (触发多次)
*/
let postData = '';
req.on('data',function (chunk){
postData += chunk;
})
/* (2)给req注册end事件
客户端每一次post所有的数据包发送完毕会执行一次
*/
req.on('end',function (){
/* (3)处置post参数:使用querystring模块切割 请求体 */
console.log(postData);
let body = querystring.parse(postData);
console.log(body);
// 响应给客户端
res.end(JSON.stringify(body))
})
});
//3.开启服务器
server.listen(3000,()=>{
console.log('服务器开启成功');
});
02-3-get与post区别
get与post区别
- 位置不同:
- get是在url后面拼接(请求行)
- post是在请求体中发送(请求体)
- 大小不同: 一般文件上传接口都是post
- get发送数据有大小限制(一般1MB)
- post发送数据没有大小限制
- 速度不同:
- get速度比post快
- 安全性不同:一般登录注册都是post
- post比get安全
03-Express框架
03-1框架介绍
- Express官网:
- http://www.expressjs.com.cn/
- http://expressjs.com/
- 多去官网文档查看API,多进行尝试,熟能生巧
- 三大核心功能
- 托管静态资源
- nodejs实现静态服务器功能在express中只需一行代码
- 路由
- Express自带路由功能,Node服务端开发变得极其简单
- Express支持链式语法,可以让代码看起来更加简洁
- 中间件
- Express最为核心的技术和思想,万物皆中间件
- 中间件虽然理解起来有点困难,但是使用起来非常方便,类似于
jquery的插件一样,由于jquery库自身的功能无法满足开发需求,所以通过插件来拓展功能
- 托管静态资源
03-2响应数据
/*学习目标: express响应客户端数据
(1)响应普通文本 : res.send('字符串')
(2)响应json对象 : res.send( js对象 )
(3)响应html文件 : res.sendFile( 文件绝对路径 )
(1)express框架本质不是修改原生代码,而是在原生的基础上添加方法 (给req和res原型添加成员)
(2)在express中你可以使用一切node原生语法
*/
//1.导入模块----相当于http
const express = require('express');
//2.创建服务器
let app = express();
//3.路由(接口文档) : 一个请求对应一个函数
app.get('/',(req,res)=>{
//响应普通文本
//原生node语法
// res.end('这是首页');
//express语法
res.send('这是首页');
});
app.get('/login',(req,res)=>{
//响应json : 底层会自动将js对象转成json格式字符串
res.send({
name:'班长',
age:40
});
});
app.get('/list',(req,res)=>{
//响应html文件: 文件绝对路径
res.sendFile( `${__dirname}/list.html`);
});
app.use((req,res)=>{
//自定义404处理结果
res.end('404');
})
//4.开启服务器
app.listen(3000,()=>{
console.log('服务器开启成功');
});
03-3-静态资源
//1.导入模块
const express = require('express');
//2.创建服务器
let app = express();
//3.托管静态资源
/*
(1)如果请求路径为/ , express会自动读取www下面index.html响应返回
(2)如果请求路径是以www中文件夹开头,express会自动根据url拼接路径返回
*/
app.use(express.static('www'));
//4.写路由(接口文档)
//5.开启服务器
app.listen(3000,()=>{
console.log('服务器开启成功');
})
03-4-get与post参数
//1.导入模块
const express = require('express');
//2.创建服务器
let app = express();
//3.托管静态资源
app.use(express.static('www'));
//4.配置中间件(jq的插件) : 本质都是给req或者res原型添加成员
//4.1 body-parser中间件 : 解析post参数
//(1)导入中间件
const bodyParser = require('body-parser');
//(2)使用中间件 : 给req添加body属性,存储解析好的post参数
app.use(bodyParser.urlencoded({ extended: false }));
//5.写路由(接口文档)
/*接收get请求参数
req.query
*/
app.get('/hero/list',(req,res)=>{
console.log( req.url );
//获取get请求参数 : express会自动帮我们解析get参数并且添加到req的原型query属性中
console.log( req.query );
res.send( req.query )
});
/*接收post请求参数
(1)使用body-parser插件
(2)req.body
*/
app.post('/hero/add',(req,res)=>{
console.log( req.url );
console.log( req.body );
res.send( req.body )
});
//6.开启服务器
app.listen(3000,()=>{
console.log('服务器开启成功');
})
04-第三方模块使用流程
适合所有第三方模块
- 官网:https://www.npmjs.com/
- 按照官方文档要求安装模块`npm install 模块名
- 复制粘贴官网代码(别人作者提前写好的)
04-1-爬虫实战
- crawler爬取网页
/*
第三方模块使用流程
1. 初始化项目 : npm init -y
*** 注意点:文件夹不能有中文
* 生成 package.json文件 : 记录整个项目使用了那些第三方模块以及对应的版本号
2. 装包(下载): npm install 模块名
* 生成 package-lock.json文件 :记录每一个包的下载地址
* 提高下一次更新速度,类似于浏览器收藏夹功能
* 生成node_modules文件夹 : 第三方模块资源文件
* 类似于前端的libs,专门存放第三方模块资源的
3. 用包 : 官网文档CV
*/
//1.导入爬虫模块
var Crawler = require("crawler");
//2.创建爬虫对象
var c = new Crawler({
maxConnections : 10,
// This will be called for each crawled page
callback : function (error, res, done) {
if(error){
console.log(error);
}else{
//爬虫成功回调
//将爬取到的网页DOM树赋值给jq的$ (目的:使用jq的语法来操作DOM树)
var $ = res.$;
// $ is Cheerio by default
//a lean implementation of core jQuery designed specifically for the server
// console.log($("html").text());
// console.log( $('#jSearchHeroDiv>li').html() );
console.log( $('html').text() );
}
done();
}
});
//3.开始爬虫
// Queue just one URL, with default callback
c.queue('https://mc.163.com/index.html');
- crawler爬取文件
ar Crawler = require("crawler");
var fs = require('fs');
var c = new Crawler({
encoding:null,
jQuery:false,// set false to suppress warning message.
callback:function(err, res, done){
if(err){
console.error(err.stack);
}else{
//将爬取好的文件通过fs模块写入文件
fs.createWriteStream(res.options.filename).write(res.body);
}
done();
}
});
//绝大多数网站,都有反爬机制。只有小众网站没有
//浏览器可以下载,但是服务端爬虫无效。反爬:检测你这个请求是通过浏览器发出来,还是服务端(解决方案:让服务端伪装成浏览器来发这个请求)
c.queue({
url:"http://upos-hz-mirrorks3u.acgvideo.com/upgcxcode/75/11/112251175/112251175-1-6.mp4?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfq99M=&uipk=5&nbs=1&deadline=1571396695&gen=playurl&os=ks3u&oi=2005998532&trid=a4c624adafe64ababb2a851334eaf87eh&platform=html5&upsig=2a29cd105837278e3b4c92181fe3eb59&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=0",
filename:"./video/11111.mp4",//写入文件名 默认在根目录
headers:{'User-Agent': 'requests'}//让服务端伪装成客户端
});
Nodejs.day4
01-初始化npm
| node指令 | 作用 |
|---|---|
| npm init -y | 初始化 |
| npm i express | 搭建nodejs服务器 |
| npm i body-parser | 解析post请求 |
| nodemon + 当前文件名 | 自动刷新 |
| npm i xpress-fileupload | 处理文件上传 |
| npm i mysql-ithm | 操作mysql数据库 |
- express-fileupload文件上传中间件:
npm i xpress-fileupload - express-fileupload使用流程
- (1)装包:
npm i xpress-fileupload - (2)配置中间件:查阅文档example
- 导包:
const* fileUpload = require('express-fileupload'); - 使用中间件:
app.use(fileUpload());
- 导包:
- (3)接受用户上传文件
- (1)装包:
02-项目思路、流程
02-1-初始化
- 1.初始化npm:
npm init -y - 2.安装模块:
- express:
npm i express - body-parser:
npm i body-parser
- express:
- 3.使用模块
02-2-项目流程
- (1)导入模块
- (2)创建服务器
- (3)托管静态资源
- 只要请求路径是以www文件夹里面的文件开头,express就会自动拼接路径返回
app.use(express.static('www'));
- 只要请求路径是 / 。 express会自动读取(打开)www里面的首页index.html 返回
- 托管服务器的static文件:存储用户图像
- app.use(express.static(‘static’));
- 只要请求路径是以www文件夹里面的文件开头,express就会自动拼接路径返回
- (4)配置中间件
- (5)写路由(接口文档)
- (6)开启服务器
02-3-nodejs处理流程
- 请求
- 处理
- 响应
03-CORS跨域
1.
跨域 :是一个固定格式报错。(没有之一)- Access to XMLHttpRequest at ‘服务器url地址’ from origin ‘null’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXsW9KHq-1599793552989)(Nodejs课程笔记/day04.assets/1571856567565.png)]
2.
什么是跨域?- ajax请求地址 与 当前页面地址 不同源 称为跨域
- (1)ajax :浏览器只有是有ajax发送请求才会出现跨域。 href属性与scr属性不会出现跨域
- (2)接口地址:ajax请求的url
- (3)当前页面地址 :
window.location.href - (4)不同源 :两个网址的 协议 || id || 端口号 不一致
- ajax请求地址 与 当前页面地址 不同源 称为跨域
3.
同源策略 :协议 和 id 和 端口号 一致- 协议:http,https,file
- id:域名
- 端口号:3000, 4399
4.
浏览器同源策略存在的理由- 保证电脑安全 : 当你在一个页面,使用ajax向不同源的 地址发送请求。浏览器会认为是向两个不同的服务器发送请求。浏览器为了了保证你的安全,就会拦截你的请求
5.
如何解决跨域限制第一种 :
CORS 目前的主流方案,也是最简单的方案
简单快捷,后台只需一行代码,跟前端没有任何关系
第二种:
JSONP 面试专用,前端需要写代码
工作原理: 服务器 在返回响应报文的时候,在响应头中 设置一个允许的header
res.setHeader('Access-Control-Allow-Origin', '*');
app.use((req, res, next) => {//任何请求都会进入这个use中间件
res.setHeader('Access-Control-Allow-Origin', '*')//设置响应头
next()//执行下一个中间件
})
express有一个自带的中间件cors,它的作用是自动给每一个res设置默认请求头
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ef59Tn3S-1599793552994)(Nodejs课程笔记/day04.assets/1571858796482.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XqgEG2b7-1599793553004)(Nodejs课程笔记/day04.assets/1571858867308.png)]今日易错点:
- 图片路径写错 书写注意图片路径要和服务器地址一样
- 单词书写有误,记得细心检查
Nodejs.day4
01-MySQL数据库使用
01-1-MySQL数据库介绍
- 什么是数据库
- 存储数据的文件夹
- 数据库使用 ---- 增删改查
- 安装mysql数据库软件
- 安装phpstudy软件自带mysql数据库
02MySQL语法
02-1增删查改语句
1. 增删改查的意思
- 增:新增记录 insert
- 删:删除记录 delete
- 查:查询记录 select
- 改:修改记录 update
02-2 增加—insert语句
新增数据 用法
**insert into 表名(字段名) value(值)**
02-3 删除—delete语句
删除数据
delete from 表名
02-4 查询—select语句
查询数据
select 字段 from 表名查询所有字段
select * from 表名
02-5 修改—update语句
修改数据
update 表名 set 字段 = 新值指定条件修改
update student set name = “黑马一哥’ where id = 3
03-扩展知识
03-1 通配符—模糊查询
%为通配符,一般搭配like关键字使用
03-2 以什么开头
查询所有字段以关键字开头的数据
select * from 表名 where 字段 like 关键字%
03-3 以什么结尾
查询所有字段以关键字结尾的数据
select * from 表名 where 字段 like %关键字
03-4 包含什么内容
查询所有字段包含关键字的数据
select * from 表名 where 字段 like %关键字%
03-5 或者条件
使用or关键字
-- 找到student表中age小于10,或者age大于100的数据
select *from student where age < 10 or age > 100;
今日易错点
- 粗心,依旧会有单词错误
- 文件创建错误,每次写代码前先坚持是否准备好
Nodejs.day5
01-客户端代码
首页—index
注意点:
编辑按钮:使用window.location.href给编辑页面传id
window.location.href='./update.html?id={{ v.id }}'删除按钮:
- 用自定义属性设置id存储英雄id
- 删除按钮是动态生成,需要进行委托事件
<!-- 模板引擎一 :英雄列表 -->
<script id="hero_list" type="text/html">
{{ each heros v }}
<tr>
<td> {{ v.name }} </td>
<td> <img src="{{ v.skill }}" width="100px" height="100px"> </td>
<td><img src="{{ v.icon }}" width="72px" height="72px"></td>
<td class="manager">
<button class="btn btn-success" onclick="location.href='./update.html?id={{ v.id }}'">编辑?</button>
<button id={{ v.id }} class="btn btn-danger">删除?</button>
</td>
</tr>
{{ /each }}
</script>
<script>
// 入口函数 : 等页面加载完毕后执行
$(function () {
/* 查询英雄详情 */
$('#searchBtn').click(function (e) {
e.preventDefault();
$.ajax({
url: 'http://127.0.0.1:3000/hero/list',
type: 'get',
dataType: 'json',
data: { search: $('#search').val() },
success: function (backData) {
$('#heroListBody').html(template('hero_list', backData))
}
});
})
/* ajax查询列表 */
$('#searchBtn').click();
/* 删除英雄 */
$('tbody').on('click', '.btn-danger', function () {
$.ajax({
url: 'http://127.0.0.1:3000/hero/delete',
type: 'post',
dataType: 'json',
data: { id: $(this).attr('id') },
success: function (backData) {
if (backData.code == 200) {
alert('删除成功');
window.location.reload();
} else {
alert(backData.msg);
}
}
});
})
})
</script>
编辑—update
<script>
// 入口函数
$(function(){
/* 1. 获取上个页面的信息 */
let id = window.location.href.split('=')[1];
console.log(id);
/* 2. 查询英雄详情 */
$.ajax({
url:'http://127.0.0.1:3000/hero/info',
type:'get',
dataType:'json',
data:{id},
success: function(backData){
console.log(backData);
$('#name').val(backData.data.name);
$('#skill').val(backData.data.skill);
$('#iconImg').attr('src',backData.data.icon);
console.log(backData.data.icon);
}
});
/* 2. ajax发送请求 */
//1.给file表单元素注册onchange事件
$('#icon').change(function () {
//1.2 获取用户选择的图片
let file = this.files[0];
//1.3 将文件转为src路径
let url = URL.createObjectURL(file);
//1.4 将url路径赋值给img标签的src
$('#iconImg').attr('src', url);
});
/* 3. 文件上传 */
$('.btn-success').on('click',function(e){
//禁用表单默认提交事件
e.preventDefault();
//创建FormData对象:参数是表单dom对象
let fd = new FormData($('form')[0])
fd.append('id',id)
$.ajax({
url:'http://127.0.0.1:3000/hero/update',
type:'post',
dataType:'json',
data:fd,
contentType: false,
processData: false,
success: function(backData){
console.log(backData);
if (backData.code == 200) {
alert('编辑成功');
window.location.href = './index.html'
}else{
alert('编辑失败');
}
}
});
});
})
</script>
新增—insert
<script>
// 入口函数
$(function(){
/* 2. ajax发送请求 */
//1.给file表单元素注册onchange事件
$('#icon').change(function () {
//1.2 获取用户选择的图片
let file = this.files[0];
//1.3 将文件转为src路径
let url = URL.createObjectURL(file);
//1.4 将url路径赋值给img标签的src
$('#iconImg').attr('src', url);
});
/* 3. 文件上传 */
$('.btn-insert').on('click',function(e){
//禁用表单默认提交事件
e.preventDefault();
//创建FormData对象:参数是表单dom对象
let fd = new FormData($('form')[0])
$.ajax({
url:'http://127.0.0.1:3000/hero/add',
type:'post',
dataType:'json',
data:fd,
contentType: false,
processData: false,
success: function(backData){
console.log(backData);
if (backData.code == 200) {
alert('新增成功');
window.location.href = './index.html'
}else{
alert('新增失败');
}
}
});
});
})
</script>
02-验证码功能
- 验证码功能思路
- 1.服务端生成一个二进制验证码
图片文件与对应的验证码文本 - 2.服务端声明全局变量存储验证码文本(文本答案)
- 3.客服端img标签发起网络请求,服务端响应验证码图片
- 4.客户端提交注册数据,服务端处理
- 1.服务端生成一个二进制验证码
- svg-captcha第三方模块:https://www.npmjs.com/search?q=svg-captcha
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yZIr1AcW-1599793553007)(课程资料/上课资料–imgs/验证码node安装与使用.png)]
验证码工作流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vDe9HOgC-1599793553009)(课程资料/上课资料–imgs/验证码工作流程.png)]
验证码–服务端
/* svg 验证码模块 */
const svgCaptcha = require('svg-captcha');
let captchaText = ''; // 声明全局变量存储验证码文本
/* 5.6 验证码 */
app.get('/captcha', (req, res) => {
// 1.请求
console.log(req.url);
// 2.1 生成图片 + 文本
var captcha = svgCaptcha.create({
size: 4,
noise: 2,
color: true,
// background:'#d1c'
});
// 2.2 文本 :服务器自己存起来 (答案)
captchaText = captcha.text;
// 2.3 图片:响应返回浏览器
res.type('svg');
res.status(200).send(captcha.data);
})
客户端—register.html
- img标签src属性请求图片,服务器响应之后img标签会自动加载图片
<img class="code" src="http://127.0.0.1:3000/captcha" alt="">
03-加密功能(md5加密与加盐技术)
- 客户端点击提交的时候对密码进行md5加密 (使用前端第三方包 md5.min.js)
- 服务端接收密文保存到数据库
- 密码明文只存在于用户填写的表单input中
- 网络传输和服务器都只存储密文
- 防止http请求被攻击导致密码泄露
- 防止数据库被攻击导致密码泄露
- 下次用户登录,使用相同加密方式对登录密码加密,然后与后台服务器进行匹配看是否一致
客户端—register.html
<!-- 导入md5 -->
<script src="./libs/md5.min.js"></script>
- 官方文档:http://jquery.cuishifeng.cn/serialize.html
$(form表单).serialize()与new FormData(form表单区别)- serialize() : 用来传输文本数据的(自己一个个拼接麻烦,这是简写)
- FormData() : 用来传输文件数据的(如果要上传文件,就用formdata
Nodejs.day7
01-Promise
1.1Promise介绍 :解决 回调地狱
Promise 是一个
构造函数,用于创建Promise对象- Promise对象:可以理解为一个处理异步操作的容器,针对异步操作
Promise作用:解决回调地狱
- 回调地狱 :异步回调,层层嵌套
/* 为什么要学Promise?
1.Promise作用 :解决回调地狱问题
回调地狱 :异步回调 层层嵌套
*/
// 需求: 一次读取文件 a.txt , b.txt , c.txt , d.txt 这三个文件内容
cons fs = require('fs');
// (1)不能直接按照顺序写,因为异步操作是无序的
// (2)解决方案 :在回调函数中嵌套执行
// 弊端 :形参回调地狱(异步回调 层层嵌套,麻烦不方便维护)
//读取文件a
fs.readFile('/data/a.txt','utf8',(err,data)=>{
if(err){
throw err;
}else{
console.log(data);
//读取文件b
fs.readFile('/data/b.txt','utf8',(err,data)=>{
if(err){
throw err;
}else{
console.log(data)
//读取文件c
fs.readFile('/data/c.txt','utf8',(err,data)=>{
if(err){
throw err;
}else{
console.log(data);
//读取文件d
fs.readFile('/data/d.txt','utf8',(err,data)=>{
if(err){
throw err;
}else{
console.log(data)
}
})
}
})
}
})
}
})
1.2Promise使用
使用流程 Promise是一个后遭函数,用于创建promise实例
- 创建Promise实例对象
let p = new Promise((resolve,reject)=>{ //异步操作 })
- 调用实例对象的then方法处理异步操作结果
p.then(data=>{ //处理成功数据 },err=>{ //处理失败数据 })
/*
1.Promise是一个构造函数,返回一个Promise对象
2.使用流程
(1)实例化Promise对象 :将异步操作放入Promise中
(2)调用then()方法:处理异步操作结果
*/
const fs = require('fs');
/*
参数 :回调函数(resolve,reject)=>{ //异步操作 }
resolve : 完成回调
reject :失败回调
*/
let p = new Promise( (resolve,reject)=>{
//异步操作:读取文件a
fs.readFile(`${__dirname}/data/a.txt`,'utf8',(err,data)=>{
if(!err){ //成功
// (1)resolve: 执行then方法里面的第一个函数
reslove(data);
}else{ // 失败
// (2)reject: 执行then方法里的第二个函数
reject(err);
}
})
} )
//调用promise实例的then方法
//第一个参数:成功的回调
//第二个参数:失败的回调
p.then(data=>{
console.log(data);
},err=>{
console.log(err)
})
1.3Promise原理
promise本质不是修改异步的顺序(异步永远是无序的),而是通过控制异步结果的顺序,从而实现异步代码有序执行
/*
1.Promise注意点及原理:
1.1 Promise对象有三种状态
* pending(进行中)
* fulfilled(已成功)
* rejected(已失败)
1.2 Promise状态改变只有两种情况
* 从pending(进行中)变为fulfilled(成功)
* 从pending(进行中)变为rejected(失败)
1.3 注意 :Promise对象在创建的时候,里面的异步就会立即执行
***** 不要在创建promise的时候回处理异步结果,应该调用resolve()或reject()交给then()方法来处理
1.4 promise解决回调地狱 :在上一个promise的then方法中返回下一个promise实例对象
1.5 坤哥个人总结 :promise本质不是修改异步的顺序(异步永远是无序的),而是通过控制异步结果的顺序,从而实现异步代码有序执行
*/
const fs = require('fs');
//(1)创建三个异步操作 promise
//读取文件a
let p1 = new Promise((resolve,reject)=>{
// 读文件
fs.readFile('./data/a.txt','utf8',(err,data)=>{
if(!err){// 成功
resolve(data);
}else{// 失败
reject(err);
}
})
})
let p2 = new Promise((resolve,reject)=>{
if(!err){// 成功
resolve(data);
}else{// 失败
reject(err);
}
})
let p3 = new Promise((resolve, reject) => {
//异步操作: 读取文件a
fs.readFile(`${__dirname}/data/c.txt`, 'utf8', (err, data) => {
if (!err) {//成功
resolve(data);
} else {//失败
reject(err);
}
});
});
let p4 = new Promise((resolve, reject) => {
//异步操作: 读取文件a
fs.readFile(`${__dirname}/data/d.txt`, 'utf8', (err, data) => {
if (!err) {//成功
resolve(data);
} else {//失败
reject(err);
}
});
});
//(2)调用promise实例的then方法
//第一个参数:成功的回调
//第二个参数:失败的回调
p1.then(data=>{
console.log(data);
return p2; //在第一个promise的then方法中返回第二个promise对象
})
.then(data=>{ //p2的then
console.log(data);
return p3;
})
.then(data=>{
console.loh(data);
return p4;
})
.then(data=>{
console.log(data);
})
1.4Promise函数封装
const fs = require('fs);
//1.封装一个函数 :根据文件名生成 文件读取的promise
function createPromise(filename){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/data/${filename}.txt`,'utf8',(err,data)=>{
if(!err){
resolve(data);
}else{
reject(err);
}
})
})
}
let p1 = createPromise('a')
let p2 = createPromise('b')
let p3 = createPromise('c')
let p4 = createPromise('d')
//2.解决希求:先读a,读完a后读b,一次读取
p1.then(data=>{
console.log(data);
return p2;
})
.then(data=>{
console.log(data);
return p3;
})
.then(data=>{
console.log(data)
return p3;
})
.then(data=>{
console.log(data)
})
1.5Promise对象的catch方法
catch() : 捕捉then方法中的错误err
const fs = require("fs");
/* promise实例对象的catch方法: 用于捕获异步操作的错误信息 */
//1.封装一个函数 :根据文件名生成 文件读取的promise
function createPromise(filename){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/data/${filename}.txt`,'utf8',(err,data)=>{
if(!err){
resolve(data);
}else{
reject(err);
}
})
})
}
//2.解决需求:要先读a,读完a后读b
p1.then(data=>{
console.log(data);
return p2;
})
.then(data=>{
console.log(data);
return p3;
})
.then(data=>{
console.log(data)
return p3;
})
.then(data=>{
console.log(data)
})
.catch(err=>{
//catch :上面任何一个then出错了都会进入这个方法
console.log(err);
})
1.6Promise对象的all()方法
all() : 将多个promise对象放入数组中合并成一个promise- 所有异步全部执行完毕才会执行then方法
const fs = require('fs');
//Promise是一个构造函数,用于创建promise实例
//封装一个创建promise的函数
function createPromise(filename){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/data/${filename}.txt`, 'utf8', (err, data) => {
if (!err) {//成功
resolve(data);
} else {//失败
reject(err);
}
});
});
};
let p1 = createPromise('a');
let p2 = createPromise('b');
let p3 = createPromise('c');
let p4 = createPromise('d');
// Promise.all([p1,p2,p3,p4]) : 多个promise合成一个
let pArr = Promise.all([p1,p2,p3,p4])
// (2)调用promise实例的then方法
pArr.then(data=>{
//执行时机:pAll中所有的promise全部都完成才会执行then
// data : 数组。 数组中每一个元素就是每一个promise的结果
console.log(data)
})
1.7Promise对象的rece()方法
race() : 将多个Promise合并成一个Promise- 任何一个异步 执行完毕就会执行then方法
const fs = require('fs');
// Promise是一个构造函数,用于创建promise实例
// 封装一个创建promise的函数
function createPromise(filename){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/data/${filename}.txt`, 'utf8', (err, data) => {
if (!err) {//成功
resolve(data);
} else {//失败
reject(err);
}
});
});
};
let p1 = createPromise('a');
let p2 = createPromise('b');
let p3 = createPromise('c');
let p4 = createPromise('d');
//Promise.all([p1,p2,p3,p4]) : 多个promise合成一个
let pArr = Promise.race([p1,p2,p3,p4]);
//(2)调用promise实例的then方法
pArr.then(data =>{
//执行时机 : pAll中任何一个promise完成就会执行then
//data : 每一个执行的promise的结果
console.log(data)
})
02-js难点面试题
2.1箭头函数this指向
箭头函数中没有this:这意味着 call() apply() bind()无法修改箭头函数中的this箭头函数中的this指向:
访问上一个作用域的this<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script> /* ES6箭头函数中的this (1)箭头函数中没有this :这意味着call() apply() bind() 无法修改箭头函数中的this (2)箭头函数中的this指向 :访问上一个作用域的this 2.学习路线 : 对比法 2.1 function函数中的this : 谁 ‘调用’ 我, 我就指向谁 * 跟你怎么声明没关系,取决于调用 (变化的) 普通函数 : this指向window 对象方法 : this指向对象 构造函数 : this指向new创建的对象 2.2 箭头函数中的this : 谁 '声明' 我,我就指向谁 * 跟你怎么调用没关系,取决于声明 (不变的) */ // 1.全局函数:window let fn = () =>{ console.log(this); } fn();//window fn.call({ a:11 });//window 箭头函数中的this无法修改,因为箭头函数没有this // 2. 对象方法:window (因为对象无法开辟作用域,obj所在作用域还是window) let obj ={ sayHi:()=>{ console.log('学习使我快乐') console.log(this); } }; obj.sayHi();//window // 3.局部函数 var person ={ play:function(){ console.log('paly中的this'); console.log(this);//person //在局部作用域声明一个箭头函数 let fn2 = () =>{ console.log('fn2在一级链中被声明,我的this就是一级链的this') console.log(this);//person } fn2(); } }; person.paly(); </script> </body> </html>
2.2数组降维
- 数组降维:把一个二维数组变成一维数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
/*
二维数组 :数组每一个元素都是一个数组
*/
let arr = [['a','b','c'],['d','e','f'],['g','h','i']];
console.log(arr);
// 降维
// 方式一:数组的concat()拼接数组
// let newArr = [];
// for(let i = 0;i < arr.length;i++) {
// newArr = newArr.concat(arr[i]);
// };
// console.log(newArr);
// 方式二:使用ES6的拓展运算符
let newArr = [];
for(let i = 0;i < arr.length;i++) {
newArr.push(...arr[i]);
};
console.log(newArr);
</script>
</body>
</html>
2.3数组升维
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
升维 :一维数组生成二维
*/
let arr = [
{
type: '电子产品',
name: 'iPhone',
price: 8888
},
{
type: '家具',
name: '桌子',
price: 100
},
{
type: '食品',
name: '瓜子',
price: 10
},
{
type: '家具',
name: '椅子',
price: 380
}, {
type: '电子产品',
name: '小米手机',
price: 1380
}, {
type: '食品',
name: '辣条',
price: 5
}, {
type: '食品',
name: '咖啡',
price: 50
}
];
let newArr = []; //升维之后的数组
let obj = {}; //记录一维数组种类
for(let i = 0; i<arr.length;i++){
if(!obj[arr[i].type]){ //第一次出现
obj[arr[i].type] = 1;
newArr.push({
type:arr[i].type;
data:[arr[i]];
})
}else{ //第二次出现
//将arr[i]添加到指定的二维数组data中
for(let j=0;j<newArr.length;j++){
if(arr[i].type == newArr[j].type){
newArr[j].data.push(arr[i]);
break;
}
}
}
}
console.log(newArr);