-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 174 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 174 KB
1
{"pages":[{"title":"关于","text":"关于本站使用hexo搭建,themes采用Icarus 关于个人现大三软件工程,方向前端,喜欢混科技圈,热爱计算机各种技术深爱搞机,拿到第一步手机就开始捣鼓,上初一搞过 MTK 思凯 mrp 格式的 app,还记得*#*#220807#这些神奇的代码,初二用过 s60v5 平台的手机,搞过 sis,sisx 格式的签名破解,上初三拿到第一步安卓手机开始捣鼓反编译修改系统,同时开始自学了一点 C 语言跟 java,高二开始自学安卓原生开发,全程都是使用 Aide,中途玩过逆向,tiny等免流,对物联网单片机等都有一些了解,使用过 树莓派、Arduino、Esp8266、ch-08 等单片机开发板,独立开发了一个简单的遥控车,对 ch340x,cp210x 等驱动有一定的了解,现致力于Vue,React,Echarts等前端框架的学习,以及个人的开源与上线个人站点。 总结:前端,后端,移动客户端,产品,UI,都在学,除前端外其他基本全是野路子。 我的照片高中 大学 爱好逃离塔科夫忠实玩家 足球 代码 剧本杀 工作经历截止 2022年4月20日,一共有过三次工作经历,即使目前我参加的所有工作中没有和代码相关的,但也挺有趣于是愿意分享一下。 书法兴趣班那是在大二的暑假,在成都中坝,我和高中的三个兄弟一起策划就地找个工作,想着这样又有工资拿又能一起玩耍,挺好。我们在中坝转了一圈,有游泳班,有补课机构,有健身房,甚至还有在boss上都还在发布招聘公告但我们一去实地需要坐一部藏在深巷里的电梯,电梯门一打开迎面的是一堵水泥墙(墙上写着铺面已转让的告示)这样的阴森饭店。我们因为种种原因都没有被录用,但是在最后一家看起来还不错的少儿书法培训机构愿意去接待我们。接待我们的是一名可能只比我们大几岁的大学学姐。我们是临时录用的,她给我们说只能分配给我们发传单,拉客户的工作。说起发传单,反正我是挺不愿意的,可能是恐惧,害羞,换位思考后我也挺不待见给我发传单的人。但望向丁逼,赵杨朋,刘川。他们三看上去挺有自信,我们最后签了兼职工作的合同,薪酬是15元一小时,她通知我们下周一来上班。到了上班的时间,我早早的赶到了店里,赵杨朋依旧迟到。但还好,学姐并没有责备。她给了我们一张客户登记表,一支笔,一把店里的传单。领我们分别去了商城门口,地铁口,小区大门口。我们的任务就是拉客户!我被分配在了中坝鹏瑞利商场的门口,此刻我充满了自信,但谁呈想到一段艰难的时期来了。面对商城门口的车水马龙,我尽力的往人们的手中递着传单,向带着小朋友的家长介绍着我的业务。但在这充满竞争的社会,没有想象中的敬贤礼士,好言好语,换来的更多是一个白眼,一张冷漠的表情。此刻我的心情来到了冰点。当然也有令人安慰的事情,我遇到了一位阿姨,他知道我是附近书法培训班的。她真的很愿意去帮助我,即使她并没有让自己小孩报名书法培训班的需求,她把她的信息填在了客户登记表上。最后经历了难熬的4小时,我们下班了。他们哥三一个客户也没有拉到,最终我们的战果是4人·一个下午·一名客户。回到店里,我看到学姐的表情已经不对了,她让我们先回去。第二天学姐给我们发了微信,通知说我们不用再来了,第一天的工资月底会发给我们。我们也知道,4个人只拉到一个客户确实是令人难堪的成绩。我们并没有多问,选择了沉默。后来我还了解到,那天唯一填了客户登记表的阿姨,她登记的电话居然少了一位。。。好事多磨终成事, 佳期难得自有期。 电玩城兼职待续。。 苏轼广博实验室实习待续。。 未完待续。。。看我如何讲述我的人生","link":"/blog/about/index.html"}],"posts":[{"title":"四级冲刺.4.20.2","text":"1. obey force fate confuse liar quarrel report fairy wage propose phenomenon blind status 强迫 武力 命运 争吵 提议 求婚 2. refuse cant help doing yield blunt turn out to be accelerator/brake hesitate territory bend religion demand deceive tide 屈服 原来是 领土 欺骗 潮 3. infect standard rarely proof opposite tight disgust prescribe illusion oval poison get stuck frost 传染 证据 开药 椭圆 上当 霜冻","link":"/blog/2022/04/20/4-20-2/"},{"title":"Express.js.5.13.1","text":"1. 初识 Express1.1 Express 简介 什么是 Express官方给出的概念:Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的。Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。Express 的中文官网: http://www.expressjs.com.cn/ 进一步理解 Express思考:不使用 Express 能否创建 Web 服务器?答案:能,使用 Node.js 提供的原生 http 模块即可。思考:既生瑜何生亮(有了 http 内置模块,为什么还有用 Express)?答案:http 内置模块用起来很复杂,开发效率低;Express 是基于内置的 http 模块进一步封装出来的,能够极大的提高开发效率。思考:http 内置模块与 Express 是什么关系?答案:类似于浏览器中 Web API 和 jQuery 的关系。后者是基于前者进一步封装出来的。 Express 能做什么对于前端程序员来说,最常见的两种服务器,分别是: Web 网站服务器:专门对外提供 Web 网页资源的服务器。 API 接口服务器:专门对外提供 API 接口的服务器。使用 Express,我们可以方便、快速的创建 Web 网站的服务器或 API 接口的服务器。 1.2 Express 的基本使用 安装在项目所处的目录中,运行如下的终端命令,即可将 express 安装到项目中使用:1npm i express@4.17.1 创建基本的 Web 服务器12345678// 1. 导入 expressconst express = require('express')// 2. 创建 web 服务器const app = express()// 3. 启动 web 服务器app.listen(80, () => { console.log('express server running at http://127.0.0.1')}) 监听 GET 请求通过 app.get() 方法,可以监听客户端的 GET 请求,具体的语法格式如下:通过 app.post() 方法,可以监听客户端的 POST 请求,具体的语法格式如下:通过 res.send() 方法,可以把处理好的内容,发送给客户端: 123456789// 4. 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容app.get('/user', (req, res) => { // 调用 express 提供的 res.send() 方法,向客户端响应一个 JSON 对象 res.send({ name: 'zs', age: 20, gender: '男' })})app.post('/user', (req, res) => { // 调用 express 提供的 res.send() 方法,向客户端响应一个 文本字符串 res.send('请求成功')}) 获取 URL 中携带的查询参数通过 req.query 对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:123456app.get('/', (req, res) => { // 通过 req.query 可以获取到客户端发送过来的 查询参数 // 注意:默认情况下,req.query 是一个空对象 console.log(req.query) res.send(req.query)}) 获取 URL 中的动态参数通过 req.params 对象,可以访问到 URL 中,通过 : 匹配到的动态参数:123456// 注意:这里的 :id 是一个动态的参数app.get('/user/:ids/:username', (req, res) => { // req.params 是动态匹配到的 URL 参数,默认也是一个空对象 console.log(req.params) res.send(req.params)}) 1.3 托管静态资源 express.static()express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:12345678910const express = require('express')const app = express()// 在这里,调用 express.static() 方法,快速的对外提供静态资源app.use('/files', express.static('./files'))app.use(express.static('./clock'))app.listen(80, () => { console.log('express server running at http://127.0.0.1')}) 现在,你就可以访问 public 目录中的所有文件了:http://localhost:3000/images/bg.jpghttp://localhost:3000/css/style.csshttp://localhost:3000/js/login.js注意:Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在 URL 中。 托管多个静态资源目录如果要托管多个静态资源目录,请多次调用 express.static() 函数:访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件。 挂载路径前缀如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:1app.use('/public', express.static('./public')) 现在,你就可以通过带有 /public 前缀地址来访问 public 目录中的文件了:http://localhost:3000/public/images/kitten.jpghttp://localhost:3000/public/css/style.csshttp://localhost:3000/public/js/app.js 2.1 路由的概念1. 什么是路由广义上来讲,路由就是映射关系。3. Express 中的路由在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数,格式如下: 1234567891011121314const express = require('express')const app = express()// 挂载路由app.get('/', (req, res) => { res.send('hello world.')})app.post('/', (req, res) => { res.send('Post Request.')})app.listen(80, () => { console.log('http://127.0.0.1')}) 路由的匹配过程每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的 URL 同时匹配成功,则 Express 会将这次请求,转交给对应的 function 函数进行处理。路由匹配的注意点:① 按照定义的先后顺序进行匹配② 请求类型和请求的URL同时匹配成功,才会调用对应的处理函数 2.2 路由的使用 最简单的用法在 Express 中使用路由最简单的方式,就是把路由挂载到 app 上,示例代码如下: 2.2 路由的使用 最简单的用法在 Express 中使用路由最简单的方式,就是把路由挂载到 app 上,示例代码如下:1234567891011121314const express = require('express')const app = express()// 挂载路由app.get('/', (req, res) => { res.send('hello world.')})app.post('/', (req, res) => { res.send('Post Request.')})app.listen(80, () => { console.log('http://127.0.0.1')}) 模块化路由为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块。将路由抽离为单独模块的步骤如下:① 创建路由模块对应的 .js 文件② 调用 express.Router() 函数创建路由对象③ 向路由对象上挂载具体的路由④ 使用 module.exports 向外共享路由对象⑤ 使用 app.use() 函数注册路由模块 创建路由模块12345678910111213141516// 这是路由模块// 1. 导入 expressconst express = require('express')// 2. 创建路由对象const router = express.Router()// 3. 挂载具体的路由router.get('/user/list', (req, res) => { res.send('Get user list.')})router.post('/user/add', (req, res) => { res.send('Add new user.')})// 4. 向外导出路由对象module.exports = router 注册路由模块123456789101112131415const express = require('express')const app = express()// app.use('/files', express.static('./files'))// 1. 导入路由模块const router = require('./03.router')// 2. 注册路由模块app.use('/api', router)// 注意: app.use() 函数的作用,就是来注册全局中间件app.listen(80, () => { console.log('http://127.0.0.1')}) 为路由模块添加前缀类似于托管静态资源时,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单:12app.use(router)app.use('/api', router) 3.1 中间件的概念 什么是中间件中间件(Middleware ),特指业务流程的中间处理环节。 现实生活中的例子在处理污水的时候,一般都要经过三个处理环节,从而保证处理过后的废水,达到排放标准。处理污水的这三个中间处理环节,就可以叫做中间件。 Express 中间件的调用流程当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。 Express 中间件的格式Express 的中间件,本质上就是一个 function 处理函数,Express 中间件的格式如下:注意:中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res。 next 函数的作用next 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。 3.2 Express 中间件的初体验 定义中间件函数可以通过如下的方式,定义一个最简单的中间件函数:123456789const express = require('express')const app = express() // 定义一个最简单的中间件函数const mw = function (req, res, next) {console.log('这是最简单的中间件函数')// 把流转关系,转交给下一个中间件或路由next() } 全局生效的中间件客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。通过调用 app.use(中间件函数),即可定义一个全局生效的中间件,示例代码如下:1234// // 将 mw 注册为全局生效的中间件// app.use(mw)// 这是定义全局中间件的简化形式 中间件的作用多个中间件之间,共享同一份 req 和 res。基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。12345678910111213141516171819202122const express = require('express')const app = express()// 这是定义全局中间件的简化形式app.use((req, res, next) => { // 获取到请求到达服务器的时间 const time = Date.now() // 为 req 对象,挂载自定义属性,从而把时间共享给后面的所有路由 req.startTime = time next()})app.get('/', (req, res) => { res.send('Home page.' + req.startTime)})app.get('/user', (req, res) => { res.send('User page.' + req.startTime)})app.listen(80, () => { console.log('http://127.0.0.1')}) 定义多个全局中间件可以使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用,示例代码如下:12345678910111213141516171819202122const 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')}) 局部生效的中间件不使用 app.use() 定义的中间件,叫做局部生效的中间件,示例代码如下:1234567891011121314151617181920212223// 导入 express 模块const express = require('express')// 创建 express 的服务器实例const app = express()// 1. 定义中间件函数const mw1 = (req, res, next) => { console.log('调用了局部生效的中间件') next()}// 2. 创建路由app.get('/', mw1, (req, res) => { res.send('Home page.')})app.get('/user', (req, res) => { res.send('User page.')})// 调用 app.listen 方法,指定端口号并启动web服务器app.listen(80, function () { console.log('Express server running at http://127.0.0.1')}) 定义多个局部中间件可以在路由中,通过如下两种等价的方式,使用多个局部中间件:123app.get('/', [mw1, mw2], (req, res) => { res.send('Home page.')}) 了解中间件的5个使用注意事项① 一定要在路由之前注册中间件② 客户端发送过来的请求,可以连续调用多个中间件进行处理③ 执行完中间件的业务代码之后,不要忘记调用 next() 函数④ 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码⑤ 连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象 3.3 中间件的分类为了方便大家理解和记忆中间件的使用,Express 官方把常见的中间件用法,分成了 5 大类,分别是:① 应用级别的中间件② 路由级别的中间件③ 错误级别的中间件④ Express 内置的中间件⑤ 第三方的中间件 应用级别的中间件通过 app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件,叫做应用级别的中间件,代码示例如下: 路由级别的中间件绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上,代码示例如下: 错误级别的中间件错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。12345678910111213141516171819202122// 导入 express 模块const express = require('express')// 创建 express 的服务器实例const app = express()// 1. 定义路由app.get('/', (req, res) => { // 1.1 人为的制造错误 throw new Error('服务器内部发生了错误!') res.send('Home page.')})// 2. 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃app.use((err, req, res, next) => { console.log('发生了错误!' + err.message) res.send('Error:' + err.message)})// 调用 app.listen 方法,指定端口号并启动web服务器app.listen(80, function () { console.log('Express server running at http://127.0.0.1')}) 格式:错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。注意:错误级别的中间件,必须注册在所有路由之后! 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+ 版本中可用)12345678910111213141516171819202122232425262728// 导入 express 模块const express = require('express')// 创建 express 的服务器实例const app = express()// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据app.use(express.json())// 通过 express.urlencoded() 这个中间件,来解析 表单中的 url-encoded 格式的数据app.use(express.urlencoded({ extended: false }))app.post('/user', (req, res) => { // 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据 // 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined console.log(req.body) res.send('ok')})app.post('/book', (req, res) => { // 在服务器端,可以通过 req,body 来获取 JSON 格式的表单数据和 url-encoded 格式的数据 console.log(req.body) res.send('ok')})// 调用 app.listen 方法,指定端口号并启动web服务器app.listen(80, function () { console.log('Express server running at http://127.0.0.1')}) 第三方的中间件非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。例如:在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间件,来解析请求体数据。使用步骤如下:① 运行 npm install body-parser 安装中间件② 使用 require 导入中间件③ 调用 app.use() 注册并使用中间件注意:Express 内置的 express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的。 3.4 自定义中间件 需求描述与实现步骤自己手动模拟一个类似于 express.urlencoded 这样的中间件,来解析 POST 提交到服务器的表单数据。实现步骤:① 定义中间件② 监听 req 的 data 事件③ 监听 req 的 end 事件④ 使用 querystring 模块解析请求体数据⑤ 将解析出来的数据对象挂载为 req.body⑥ 将自定义中间件封装为模块 定义中间件使用 app.use() 来定义全局生效的中间件,代码如下:12345const express = require('express')const app = express()app.use((req,res,next)+>{}) 监听 req 的 data 事件在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,每一次触发 data 事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。 监听 req 的 end 事件当请求体数据接收完毕之后,会自动触发 req 的 end 事件。因此,我们可以在 req 的 end 事件中,拿到并处理完整的请求体数据。示例代码如下:12345678910111213141516171819// 这是解析表单数据的中间件app.use((req, res, next) => { // 定义中间件具体的业务逻辑 // 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据 let str = '' // 2. 监听 req 的 data 事件 req.on('data', (chunk) => { str += chunk }) // 3. 监听 req 的 end 事件 req.on('end', () => { // 在 str 中存放的是完整的请求体数据 // console.log(str) // TODO: 把字符串格式的请求体数据,解析成对象格式 const body = qs.parse(str) req.body = body next() })}) 使用 querystring 模块解析请求体数据Node.js 内置了一个 querystring 模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式。示例代码如下:1234// 导入 Node.js 内置的 querystring 模块const qs = require('querystring')// TODO: 把字符串格式的请求体数据,解析成对象格式const body = qs.parse(str) 将解析出来的数据对象挂载为 req.body上游的中间件和下游的中间件及路由之间,共享同一份 req 和 res。因此,我们可以将解析出来的数据,挂载为 req的自定义属性,命名为 req.body,供下游使用。示例代码如下:123app.post('/user', (req, res) => { res.send(req.body)}) 将自定义中间件封装为模块为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块,示例代码如下:123456789101112131415161718// 导入 express 模块const express = require('express')// 创建 express 的服务器实例const app = express()// 1. 导入自己封装的中间件模块const customBodyParser = require('./14.custom-body-parser')// 2. 将自定义的中间件函数,注册为全局可用的中间件app.use(customBodyParser)app.post('/user', (req, res) => { res.send(req.body)})// 调用 app.listen 方法,指定端口号并启动web服务器app.listen(80, function () { console.log('Express server running at http://127.0.0.1')})","link":"/blog/2022/05/13/Express-5-13-2/"},{"title":"Express.5.14.1","text":"4. 使用 Express 写接口4.1 创建基本的服务器12345const express = require('express')const app = express()app.listen(80,()=>{ console.log('服务器启动在http://127.0.0.1')}) 4.2 创建 API 路由模块1234567891011121314const express = require('express')const router = express.Router()// 在这里挂载对应的路由router.get('/get', (req, res) => { // 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据 const query = req.query // 调用 res.send() 方法,向客户端响应处理的结果 res.send({ status: 0, // 0 表示处理成功,1 表示处理失败 msg: 'GET 请求成功!', // 状态的描述 data: query, // 需要响应给客户端的数据 })}) 4.3 编写 GET 接口 1234567891011121314const express = require('express')const router = express.Router()// 在这里挂载对应的路由router.get('/get', (req, res) => { // 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据 const query = req.query // 调用 res.send() 方法,向客户端响应处理的结果 res.send({ status: 0, // 0 表示处理成功,1 表示处理失败 msg: 'GET 请求成功!', // 状态的描述 data: query, // 需要响应给客户端的数据 })}) 4.5 CORS 跨域资源共享 接口的跨域问题刚才编写的 GET 和 POST 接口,存在一个很严重的问题:不支持跨域请求。解决接口跨域问题的方案主要有两种:① CORS(主流的解决方案,推荐使用)② JSONP(有缺陷的解决方案:只支持 GET 请求) 使用 cors 中间件解决跨域问题cors 是 Express 的一个第三方中间件。通过安装和配置 cors 中间件,可以很方便地解决跨域问题。使用步骤分为如下 3 步:① 运行 npm install cors 安装中间件② 使用 const cors = require(‘cors’) 导入中间件③ 在路由之前调用 app.use(cors()) 配置中间件 什么是 CORSCORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可以解除浏览器端的跨域访问限制。 CORS 的注意事项① CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。② CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。","link":"/blog/2022/05/14/Express-5-14-1/"},{"title":"JavaScript高级.6.21.1","text":"1、函数执行-作用域链-面试题-内存管理对于这样一串代码123456789101112var message = "Hello JavaScript"function foo(){ console.log(message)}function bar(){ var message = "Hello Bar" foo()}bar() 它的输出结果是什么? 你的第一想法是不是Hello Bar但其实是Hello JavaScript我们需要用到作用域链的话题去看待这个问题在早期ECMA标准中1、js引擎处理js代码的时候会现有一个函数调用栈(ECStack)在这个调用栈中会有一个Global Object全局对象,开始编译上方代码的时候便会在这个GO里面添加上message:undefined,foo:0xa00(同时在内存堆空间存上这个函数本身+它的parent scope(此时它的父级作用域是GO),Bar:0xb00(同时在内存堆空间存上这个函数本身+它的parent scope(此时它的父级作用域是GO))2、编译完后,便开始执行代码,这个时候在函数调用栈中会有一个全局的执行上下文,在其中有一个VO的对象,此时这个VO就指向了GO,第一步就是将那个message赋值上”Hello JavaScript”,然后执行到了bar()。这个时候调用栈便会有一个函数执行上下文。其中的VO指向了AO,在这个AO里面也有一个函数的局部对象,然后添加上message:undefined,这个时候函数编译完message:”Hello Bar”,开始执行foo(),这是foo的父级作用域是GO,当前GO下的message还是”Hellp JavaScript”,所以最终答案是”Hello JavaScript”在函数中只有var,let,const 才会有AO这种局部对象的生成 a=10这种代码会将a加到全局对象中 内存管理不管什么样的编程语言,在代码的执行过程中都是需要给它分配内存的,不同的是某些编程语言需要我们自己手动的管理内存,某些编程语言会可以自动帮助我们管理内存","link":"/blog/2022/06/21/JavaScript%E9%AB%98%E7%BA%A7-6-21-1/"},{"title":"JavaScript高级.6.21.2","text":"闭包的定义-理解-内存模型-内存泄漏js闭包的使用在js中函数是可以作为函数参数来使用的,并且在底层它传的是函数的内存地址 在JavaScript中,函数是非常重要的,并且是一等公民:那么就意味着函数的使用是非常灵活的;函数可以作为另外一个函数的参数,也可以作为另外一个函数的返回值来使用; 自己编写高阶函数 使用内置的高阶函数 先介绍一些高阶函数 1234var nums = [10,5,11,100,55]var new_nums=nums.filter((item,index,arr)=>{ return item % 2 === 0}) //(传参的函数需要返回一个boolean) 12345//map:映射var nums2 = [10,5,11,100,55]var new_num=nums2.map((item,index,arr)=>{ return item * 10}) 1234//forEach:迭代nums2.forEach((item,index,arr)=>{ console.log(item)}) 123nums2.forEach((item,index,arr)=>{ console.log(item)}) 12345678910111213141516var friends = [ {name: "why", age: 18}, {name: "kobe", age: 40}, {name: "james", age: 35}, {name: "curry", age: 30}, ] var findFriend = friends.find(function(item) { return item.name === 'james' }) console.log(findFriend)var friendIndex = friends.findIndex(function(item) { return item.name === 'james'})// console.log(friendIndex) 在预编译的时候,在堆内存中 函数本体+它的上级作用域链会一直存在,只是具体的函数对象在调用函数的时候才会生成,不调用函数的时候便会自动销毁。","link":"/blog/2022/06/21/JavaScript%E9%AB%98%E7%BA%A7-6-21-2/"},{"title":"JavaScript高级.6.23.1","text":"闭包内存回收和this的四个绑定规则this在全局作用域下指向什么?在全局作用域下this绑定的就是window node环境下绑定的是{}(一个空对象) 规则一:默认绑定独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用foo() 规则二:隐式绑定也就是它的调用位置中,是通过某个对象发起的函数调用obj.foo() 规则三:显式绑定我们明确的绑定了this指向的对象,所以称之为 显示绑定foo.call(“call”,20,30)foo.apply(“apply”,[20,30]) 12345function foo(){ console.log('foo')}var new_foo=foo.bind("aaa")new_foo() 规则四:new绑定","link":"/blog/2022/06/23/JavaScript%E9%AB%98%E7%BA%A7-6-23-1/"},{"title":"JavaScript高级.6.24.1","text":"apply-call-bind实现+参数解析","link":"/blog/2022/06/24/JavaScript%E9%AB%98%E7%BA%A7-6-24-1/"},{"title":"JavaScrit高级.6.24.2","text":"纯函数-柯里化实现-组合函数纯函数 函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念; 在react开发中纯函数是被多次提及的; 比如react中组件就被要求像是一个纯函数(为什么是像,因为还有class组件),redux中有一个reducer的概念,也是要求必须是一个纯函数; 所以掌握纯函数对于理解很多框架的设计是非常有帮助的; 纯函数的维基百科定义: 在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数:此函数在相同的输入值时,需产生相同的输出。 函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。 当然上面的定义会过于的晦涩,所以我简单总结一下: 确定的输入,一定会产生确定的输出; 函数在执行过程中,不能产生副作用; 那么这里又有一个概念,叫做副作用,什么又是副作用呢?副作用(side effect)其实本身是医学的一个概念,比如我们经常说吃什么药本来是为了治病,可能会产生一些其他的副作用;在计算机科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储; 纯函数在执行的过程中就是不能产生这样的副作用:副作用往往是产生bug的 “温床”。 123456var name = ['abc','bca','cba']var new_name = name.slice(0,3)console.log(new_name)var new_name_two = name.splice(2)console.log(new_name_two)console.log(name) slice就是这样一个传函数(输入一样,输出也一样,并且没有修改全局变量和其他地方)splice因为会将我们的name修改,因此不是纯函数 柯里化 柯里化也是属于函数式编程里面一个非常重要的概念。 我们先来看一下维基百科的解释:在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化;是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术;柯里化声称 “如果你固定某些参数,你将得到接受余下参数的一个函数”; 维基百科的结束非常的抽象,我们这里做一个总结:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数;这个过程就称之为柯里化; 12345678910111213//柯里化function log_time(time,type,message) { console.log(`[${time.getHours()}:${time.getMinutes()}]+${type}+${message}`);}log_time(new Date, 'DEBUG', '监测到')var log_time_new = time => type => message => { console.log(`[${time.getHours()}:${time.getMinutes()}]+${type}+${message}`);}log_time_new(new Date)('err')('监测到')var log_time_new_one = log_time_new(new Date) 因为柯里化,我们可以对我们的函数进行一些定制化 手写柯里化时间2:19:20 组合函数组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式:比如我们现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的;那么如果每次我们都需要进行两个函数的调用,操作上就会显得重复;那么是否可以将这两个函数组合起来,自动依次调用呢?这个过程就是对函数的组合,我们称之为 组合函数(Compose Function); 12345678910111213141516//组合函数function double(data) { return data*2}function square(data) { return data**2}console.log(square(double(10)));function compose(m, n) { return function (data) { return n(m(data)) }}var new_fn = compose(double, square)console.log(new_fn(10)); 封装高端组合函数02:57:54","link":"/blog/2022/06/24/JavaScript%E9%AB%98%E7%BA%A7-6-24-2/"},{"title":"JavaScript高级.6.24.3","text":"wite-eval-严格模式-面向对象with语句with语句 扩展一个语句的作用域链。 12345var name = 'nike'var info = {name:'caeaser'}with(info){ console.log(name)} 不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源。 eval函数的使用eval是一个特殊的函数,它可以将传入的字符串当做JavaScript代码来运行。 不建议在开发中使用eval:eval代码的可读性非常的差(代码的可读性是高质量代码的重要原则);eval是一个字符串,那么有可能在执行的过程中被刻意篡改,那么可能会造成被攻击的风险;eval的执行必须经过JS解释器,不能被JS引擎优化; 12var data = 'console.log('eval')'eval(data) 严格模式 在ECMAScript5标准中,JavaScript提出了严格模式的概念(Strict Mode):严格模式很好理解,是一种具有限制性的JavaScript模式,从而使代码隐式的脱离了 ”懒散(sloppy)模式“;支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行; 严格模式对正常的JavaScript语义进行了一些限制:严格模式通过 抛出错误 来消除一些原有的 静默(silent)错误;严格模式让JS引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理);严格模式禁用了在ECMAScript未来版本中可能会定义的一些语法;这里我们来说几个严格模式下的严格语法限制:JavaScript被设计为新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的;但是这种方式可能给带来留下来安全隐患;在严格模式下,这种失误就会被当做错误,以便可以快速的发现和修正; 无法意外的创建全局变量 严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常 严格模式下试图删除不可删除的属性 4.严格模式不允许函数参数有相同的名称5. 不允许0的八进制语法6. 在严格模式下,不允许使用with7. 在严格模式下,eval不再为上层引用变量8. 严格模式下,this绑定不会默认转成对象 JavaScript面向对象JavaScript其实支持多种编程范式的,包括函数式编程和面向对象编程:JavaScript中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成;key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;如果值是一个函数,那么我们可以称之为是对象的方法; 如何创建一个对象呢? 早期使用创建对象的方式最多的是使用Object类,并且使用new关键字来创建一个对象:这是因为早期很多JavaScript开发者是从Java过来的,它们也更习惯于Java中通过new的方式创建一个对象; 后来很多开发者为了方便起见,都是直接通过字面量的形式来创建对象:这种形式看起来更加的简洁,并且对象和属性之间的内聚性也更强,所以这种方式后来就流行了起来; 对属性操作的控制在前面我们的属性都是直接定义在对象内部,或者直接添加到对象内部的:但是这样来做的时候我们就不能对这个属性进行一些限制:比如这个属性是否是可以通过delete删除的?这个属性是否在for-in遍历的时候被遍历出来呢? 如果我们想要对一个属性进行比较精准的操作控制,那么我们就可以使用属性描述符。通过属性描述符可以精准的添加或修改对象的属性;属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改; 数据属性描述符 12345678910111213// Object.defineProperty方法var obj = { name: 'ceaser', age:21}//第三个参数属性描述符是一个对象,用来配置,默认值都是falseObject.defineProperty(obj, "height", { value: 1.75, configurable: false, //在别处不可删除,不可修改, enumerable: false, //不可枚举,看不到 writable:false //不可修改})console.log(obj); 存取属性描述符 123456789101112131415161718192021222324252627var obj = { name: 'ceaser', age: 21, _height:1.75}Object.defineProperty(obj, "height", { configurable: true, enumerable: true, get: function (value) { foo() return this._height }, set: function (value) { bar() this._height=value }})obj.height=1.76console.log(obj.height);console.log(obj._height);function foo() { console.log('获取了一次height的值');}function bar() { console.log('修改了一次height的值');}","link":"/blog/2022/06/24/JavaScript%E9%AB%98%E7%BA%A7-6-24-3/"},{"title":"JavaScript高级.6.25.1","text":"原型和函数原型认识构造函数我们先理解什么是构造函数?构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;但是JavaScript中的构造函数有点不太一样; JavaScript中的构造函数是怎么样的?构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数; 那么被new调用有什么特殊的呢? 如果一个函数被使用new操作符调用了,那么它会执行如下操作: 在内存中创建一个新的对象(空对象); 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;(后面详细讲); 构造函数内部的this,会指向创建出来的新对象; 执行函数的内部代码(函数体代码); 如果构造函数没有返回非空对象,则返回创建出来的新对象;1234567891011121314151617//构造函数function Person(name,age,height,address) { this.name = name this.age=age this.height = height this.address = address this.eating = function () { console.log(this.name+'在吃东西'); } console.log('Person在调用');}console.log(new Person('ceaser', 21, 1.75, '成都'));// var foo_func = new Person('ceaser', 21, 1.75, '成都').eating// foo_func()// new Person('ceaser', 21, 1.75, '成都').eating()// new Person('ceaser', 21, 1.75, '成都') 对象的原型JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。 那么这个对象有什么用呢?当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性; 那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?答案是有的,只要是对象都会有这样的一个内置属性; 获取的方式有两种:方式一:通过对象的 proto 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题);方式二:通过 Object.getPrototypeOf 方法可以获取到; 每个对象都有一个__proto__ 属性,构造函数有一个prototype属性,当我们new的时候,prototype便会指向__proto__函数的显示原型,对象是隐式原型 12345678910111213141516171819202122232425//构造函数+原型高级写法function Person(name,age,height) { this.name = name this.age = age this.height = height}Person.prototype.eating = function () { console.log('我是eating'); console.log(this.name+'在eating');}Object.defineProperty(Person.prototype, 'running', { value: function () { console.log('我是running'); }, enumerable: true, configurable: true, writable:true})var foo = new Person('ceaser', 21, 1.75)var foo_two = new Person('nike',25,2.11)console.log(foo);console.log(foo.__proto__);foo.eating()foo_two.eating()foo_two.running()","link":"/blog/2022/06/25/JavaScript%E9%AB%98%E7%BA%A7-6-25-1/"},{"title":"JavaScript高级.6.25.2","text":"面向对象的原型链和继承实现面向对象有三大特性:封装、继承、多态封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);多态:不同的对象在执行时表现出不同的形态; 那么这里我们核心讲继承。 那么继承是做什么呢? 继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可。 那么JavaScript当中如何实现继承呢?不着急,我们先来看一下JavaScript原型链的机制;再利用原型链的机制实现一下继承; 原型链的继承12345678910111213141516171819202122232425//第一种继承的实现function Person() { this.eating = function () { console.log('我在eating'); }}Person.prototype.running = function () { console.log('我是running');}Object.defineProperty(Person.prototype, 'foo', { value: '我是foo', enumerable:true})function Student(name,age) { this.name = name this.age = age}Student.prototype = new Person()var ceaser = new Student('ceaser', 21)ceaser.eating()ceaser.running()console.log(ceaser.foo); 此方法有严重弊端:1、很多继承来的属性无法打印2、由于都是指针指向propertype,改变一处对象的值,其他对象也会一起改变3、不好传递参数 借用构造函数继承1234567891011121314151617181920212223//第二种继承function Person(name,age) { this.name = name this.age = age}Object.defineProperty(Person.prototype, 'eating', { value: function () { console.log(this.name + '我是eating'); }})function Student(name,age) { Person.call(this,name,age)}Student.prototype = new Person()Student.prototype = Object.create(Person.prototype)var stu1 = new Student('ceaser', 21)var stu2 = new Student('james', 21)console.log(stu1);console.log(stu2);stu1.eating()stu2.eating() 一些方法的额外补充1、Object.create() 123456var obj = { name:'ceaser', age:21}var new_obj = Object.create(obj)//这样obj变成了new_obj的__proto__ 2、obj.hasOwnProperty()看自身有没有这个属性3、instanceof方法console.log(stu instanceof Student)","link":"/blog/2022/06/25/JavaScript%E9%AB%98%E7%BA%A7-6-25-2/"},{"title":"JavaScript高级.6.26.1","text":"ES6类的使用我们会发现,按照前面的构造函数形式创建 类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解。在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类;但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已;所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系; 那么,如何使用class来定义一个类呢? class定义类的方式可以使用两种方式来声明类:类声明和类表达式; 1234567891011121314151617181920212223242526272829303132333435class Person {}let names = ['abc','cba','bca']var Person = class { constructor(name,age){ this.name=name, this.age=age, this.address='成都市' } eating(){ console.log(); } running(){ console.log(); } get address(){ console.log('拦截访问操作'); return this._address } set address(new_address){ this._address=new_address } static random_person(){ let name_index = Math.floor(Math.random() * names.length) let name = names[name_index] let age = Math.floor(Math.random() * 100) return new Person(name,age) }}let p1 = new Person('ceaser',30)const p2 = Person.random_person()console.log(Object.getOwnPropertypeDescriptors(Person.prototype)); 类中实现继承前面我们花了很大的篇幅讨论了在ES5中实现继承的方案,虽然最终实现了相对满意的继承机制,但是过程却依然是非常繁琐的。在ES6中新增了使用extends关键字,可以方便的帮助我们实现继承: 123456789101112131415161718192021222324class Person { constructor(name,age){ this.name=name this.age=age } eating(){ console.log('eating'); }}class Student extends Person { constructor(name,age,sno){ super(name,age) this.sno = sno } //重写eating person_method_new(){ super.eating() console.log('调完父类的eating再调'); }}const p1 = new Student('ceaser',21,'agg')p1.eating()","link":"/blog/2022/06/26/JavaScript%E9%AB%98%E7%BA%A7-6-26-1/"},{"title":"JavaScript高级.6.26.2","text":"ES6-语法解析-let-const等","link":"/blog/2022/06/26/JavaScript%E9%AB%98%E7%BA%A7-6-26-2/"},{"title":"JavaScript高级.7.4.1","text":"Proxy-Reflect-响应式原理我们先来看一个需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程通过我们前面所学的知识,能不能做到这一点呢? 其实是可以的,我们可以通过之前的属性描述符中的存储属性描述符来做到;监听对象的操作左边这段代码就利用了前面讲过的 Object.defineProperty 的存储属性描述符来对属性的操作进行监听。但是这样做有什么缺点呢?首先,Object.defineProperty设计的初衷,不是为了去监听截止一个对象中所有的属性的。我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符。其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么Object.defineProperty是无能为力的。所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象 12345678910111213141516const obj = { name:'ceaser', age:18}Object.defineProperty(obj,'name',{ get(){ console.log('name被访问了'); } set(){ console.log('name被设置了'); }})console.log(obj.name);obj.name='bob' Proxy的基本使用 在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的:也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象);之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作; 我们可以将上面的案例用Proxy来实现一次:首先,我们需要new Proxy对象,并且传入需要侦听的对象以及一个处理对象,可以称之为handler; const p = new Proxy(target, handler)其次,我们之后的操作都是直接对Proxy的操作,而不是原有的对象,因为我们需要在handler里面进行侦听; 最基本用法 1234567891011//最基本用法const obj = { name:'ceaser', age:18}const obj_proxy = new Proxy(obj,{})console.log(obj_proxy.name);obj_proxy.age = 21console.log(obj); 12345678910111213141516//13种捕获器const obj = { name:'ceaser', age:18}const obj_proxy = new Proxy(obj,{ get(target,key){ retrun target[key] } set(target,key,new_value){ target[key]=new_value }})console.log(obj_proxy.name);obj_proxy.age = 21console.log(obj); Proxy其他捕获器 12345678910111213141516171819//Proxy其他捕获器//监听in的捕获器//监听删除的捕获器const obj = { name:'ceaser', age:21}const obj_proxy = new Proxy(obj,{ has(target,key){ console.log(`监听到了对象${target}的${key}的in操作`); retrun key in target }, deleteProperty(target,key){ delete target[key] }})console.log('name' in obj_proxy);delete obj_proxy.name Proxy函数对象的监听 1234567891011121314function foo() { console.log('foo');}const foo_proxy = new Proxy(foo,{ apply(target,this,arg){ console.log(`${target}被调用`); return target.apply(this,arg) } construct(target,arg){ console.log('被new调用'); }})foo_proxy.apply({},['abc','cba'])new foo_proxy() getPrototypeOf和obj._proto_一样 Reflect的使用Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。那么这个Reflect有什么用呢?它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty() ;如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;Reflect也是13中捕获方法或Proxy一一对应 //Reflect的基本用法 123456789101112131415const obj = { name:'ceaser', age:18}const obj_proxy = new Proxy(obj,{ get(target,key){ retrun Reflect(target,key) } set(target,key,new_value){ Reflect.set(target,key,new_value) //这个还会返回一个布尔告诉你有没有设置成功 }})console.log(obj_proxy.name);obj_proxy.age = 21console.log(obj); //第三个参数receiver的用法 12345678910111213141516const obj = { _name:'ceaser', get name(){ console.log(this._name); } set name(new_name){ this._name = new_name }}const obj_proxy = new Proxy(obj,{ get(target,key,receiver){ console.log('get被调用',key); retrun Reflect.get(target,key,receiver) }})console.log(obj_proxy.name); 什么是响应式?我们先来看一下响应式意味着什么?我们来看一段代码:有一个初始化的值,有一段代码使用了这个值;那么在m有一个新的值时,这段代码可以自动重新执行;上面的这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式的。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465class Depend { constructor() { this.reactiveFns = [] } addDepend(reactiveFn) { this.reactiveFns.push(reactiveFn) } notify() { this.reactiveFns.forEach(fn => { fn() }) }}// 封装一个响应式的函数const depend = new Depend()function watchFn(fn) { depend.addDepend(fn)}// 对象的响应式const obj = { name: "why", // depend对象 age: 18 // depend对象}// 监听对象的属性变量: Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, { get: function(target, key, receiver) { return Reflect.get(target, key, receiver) }, set: function(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) depend.notify() }})watchFn(function() { const newName = objProxy.name console.log("你好啊, 李银河") console.log("Hello World") console.log(objProxy.name) // 100行})watchFn(function() { console.log(objProxy.name, "demo function -------")})watchFn(function() { console.log(objProxy.age, "age 发生变化是需要执行的----1")})watchFn(function() { console.log(objProxy.age, "age 发生变化是需要执行的----2")})objProxy.name = "kobe"objProxy.name = "james"objProxy.name = "curry"objProxy.age = 100","link":"/blog/2022/07/04/JavaScript%E9%AB%98%E7%BA%A7-7-4-1/"},{"title":"JavaScript高级.7.9.1","text":"Promise的使用和手写Promise在ES6出来之后,有很多关于Promise的讲解、文章,也有很多经典的书籍讲解Promise虽然等你学会Promise之后,会觉得Promise不过如此,但是在初次接触的时候都会觉得这个东西不好理解; 那么这里我从一个实际的例子来作为切入点:我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟);如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去;如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息; 异步请求的处理方式 1234567891011121314function request_data(url){ setTimeout(() => { if (url==='ceaser') { //成功 return 1 } else { //失败 return 0 } }, 3000);}request_data('ceaser')//显然这个函数无法拿到我们return的值 Promise就是来解决这样一个问题我们来看一下Promise的API是怎么样的:Promise是一个类,可以翻译成 承诺、许诺 、期约;当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数; 123456789101112131415161718192021222324class Person { constructor(callback){ let foo = function(){ } let bar = function(){ } callback(foo,bar) }}const p = new Person((foo,bar)=>{ foo() bar()})//传入的这个函数就是reslove和rejectconst promise = new Promise(()=>{ console.log('promise传的回调函数会立即被执行');}) 使用promise对上述代码的重构 1234567891011121314151617function request_data(url){ return new Promise((resolve,reject)=>{ setTimeout(() => { if (url==='ceaser') { //成功 let data = 1 resolve(data) } else { //失败 let data = 0 reject(data) } }, 3000); })}request_data('ceaser').then((res) => { console.log('成功',res); }).catch((err) => { console.log('失败',err); }) 1234567891011new Promise((resolve,reject)=>{ //成功 resolve() //失败 reject()}).then(res=>{}).catch(err=>{})","link":"/blog/2022/07/09/JavaScript%E9%AB%98%E7%BA%A7-7-9-1/"},{"title":"MySQL.5.14.1","text":"1. 数据库的基本概念1.1 什么是数据库数据库(database)是用来组织、存储和管理数据的仓库。当今世界是一个充满着数据的互联网世界,充斥着大量的数据。数据的来源有很多,比如出行记录、消费记录、浏览的网页、发送的消息等等。除了文本类型的数据,图像、音乐、声音都是数据。为了方便管理互联网世界中的数据,就有了数据库管理系统的概念(简称:数据库)。用户可以对数据库中的数据进行新增、查询、更新、删除等操作。 1.2 常见的数据库及分类市面上的数据库有很多种,最常见的数据库有如下几个: MySQL 数据库(目前使用最广泛、流行度最高的开源免费数据库;Community + Enterprise) Oracle 数据库(收费) SQL Server 数据库(收费) Mongodb 数据库(Community + Enterprise)其中,MySQL、Oracle、SQL Server 属于传统型数据库(又叫做:关系型数据库 或 SQL 数据库),这三者的设计理念相同,用法比较类似。而 Mongodb 属于新型数据库(又叫做:非关系型数据库 或 NoSQL 数据库),它在一定程度上弥补了传统型数据库的缺陷。 1.3 传统型数据库的数据组织结构数据的组织结构:指的就是数据以什么样的结构进行存储。传统型数据库的数据组织结构,与 Excel 中数据的组织结构比较类似。因此,我们可以对比着 Excel 来了解和学习传统型数据库的数据组织结构。 Excel 的数据组织结构每个 Excel 中,数据的组织结构分别为工作簿、工作表、数据行、列这 4 大部分组成。① 整个 Excel 叫做工作簿② users 和 books 是工作表③ users 工作表中有 3 行数据④ 每行数据由 6 列信息组成⑤ 每列信息都有对应的数据类型 传统型数据库的数据组织结构在传统型数据库中,数据的组织结构分为数据库(database)、数据表(table)、数据行(row)、字段(field)这 4 大部分组成。① 数据库类似于 Excel 的工作簿② 数据表类似于 Excel 的工作表③ 数据行类似于 Excel 的每一行数据④ 字段类似于 Excel 的列⑤ 每个字段都有对应的数据类型 实际开发中库、表、行、字段的关系① 在实际项目开发中,一般情况下,每个项目都对应独立的数据库。② 不同的数据,要存储到数据库的不同表中,例如:用户数据存储到 users 表中,图书数据存储到 books 表中。③ 每个表中具体存储哪些信息,由字段来决定,例如:我们可以为 users 表设计 id、username、password 这 3 个字段。④ 表中的行,代表每一条具体的数据。 2. 安装并配置 MySQL2.2 MySQL 在 Mac 环境下的安装在 Mac 环境下安装 MySQL 的过程比 Windows 环境下的步骤简单很多:① 先运行 mysql-8.0.19-macos10.15-x86_64.dmg 这个安装包,将 MySQL Server 安装到 Mac 系统② 再运行 mysql-workbench-community-8.0.19-macos-x86_64.dmg 这个安装包,将可视化的 MySQLWorkbench 工具安装到 Mac 系统具体的安装教程,可以参考 素材 -> MySQL for Mac ->安装教程 - Mac系统安装MySql -> README.md 2.3 MySQL 在 Windows 环境下的安装在 Windows 环境下安装 MySQL,只需要运行 mysql-installer-community-8.0.19.0.msi 这个安装包,就能一次性将 MySQL Server 和 MySQL Workbench 安装到自己的电脑上。具体的安装教程,可以参考 素材 -> MySQL for Windows ->安装教程 - Windows系统安装MySql -> README.md 3.1 使用 MySQL Workbench 管理数据库 连接数据库 创建数据库 创建数据表DataType 数据类型:① int 整数② varchar(len) 字符串③ tinyint(1) 布尔值字段的特殊标识:① PK(Primary Key)主键、唯一标识② NN(Not Null)值不允许为空③ UQ(Unique)值唯一④ AI(Auto Increment)值自动增长 向表中写入数据 3.2 使用 SQL 管理数据库 什么是 SQLSQL(英文全称:Structured Query Language)是结构化查询语言,专门用来访问和处理数据库的编程语言。能够让我们以编程的形式,操作数据库里面的数据。三个关键点:① SQL 是一门数据库编程语言② 使用 SQL 语言编写出来的代码,叫做 SQL 语句③ SQL 语言只能在关系型数据库中使用(例如 MySQL、Oracle、SQL Server)。非关系型数据库(例如 Mongodb)不支持 SQL 语言 SQL 能做什么① 从数据库中查询数据② 向数据库中插入新的数据③ 更新数据库中的数据④ 从数据库删除数据⑤ 可以创建新数据库⑥ 可在数据库中创建新表⑦ 可在数据库中创建存储过程、视图⑧ etc… SQL 的学习目标重点掌握如何使用 SQL 从数据表中:查询数据(select) 、插入数据(insert into) 、更新数据(update) 、删除数据(delete)额外需要掌握的 4 种 SQL 语法:where 条件、and 和 or 运算符、order by 排序、count(*) 函数 语法SELECT 语句用于从表中查询数据。执行的结果被存储在一个结果表中(称为结果集)。语法格式如下:注意:SQL 语句中的关键字对大小写不敏感。SELECT 等效于 select,FROM 等效于 from。 SELECT * 示例我们希望从 users 表中选取所有的列,可以使用符号 * 取代列的名称,示例如下: SELECT 列名称 示例如需获取名为 “username” 和 “password” 的列的内容(从名为 “users” 的数据库表),请使用下面的 SELECT 语句:1select username,password from my_dm_01.users 3.4 SQL 的 INSERT INTO 语句 语法INSERT INTO 语句用于向数据表中插入新的数据行,语法格式如下:1insert into my_dm_01.users (username,password) values ('sada','1314215') UPDATE 示例 - 更新某一行中的一个列把 users 表中 id 为 7 的用户密码,更新为 888888。示例如下: 1update my_dm_01.users set password='88888888' where id=4 UPDATE 示例 - 更新某一行中的若干列把 users 表中 id 为 2 的用户密码和用户状态,分别更新为 admin123 和 1。示例如下:1update my_dm_01.users set password='88888888',status='0' where id=4 DELETE 示例从 users 表中,删除 id 为 4 的用户,示例如下:1delete from my_dm_01.users where id =4 3.7 SQL 的 WHERE 子句 语法WHERE 子句用于限定选择的标准。在 SELECT、UPDATE、DELETE 语句中,皆可使用 WHERE 子句来限定选择的标准。12345-- 演示 where 子句的使用-- select * from users where status=1-- select * from users where id>=2-- select * from users where username<>'ls'-- select * from users where username!='ls' 3.8 SQL 的 AND 和 OR 运算符 语法AND 和 OR 可在 WHERE 子语句中把两个或多个条件结合起来。AND 表示必须同时满足多个条件,相当于 JavaScript 中的 && 运算符,例如 if (a !== 10 && a !== 20)OR 表示只要满足任意一个条件即可,相当于 JavaScript 中的 || 运算符,例如 if(a !== 10 || a !== 20) AND 运算符示例使用 AND 来显示所有 status 为 0,并且 id 小于 3 的用户:12-- 使用 AND 来显示所有状态为0且id小于3的用户-- select * from users where status=0 and id<3 OR 运算符示例使用 OR 来显示所有 status 为 1,或者 username 为 zs 的用户:12-- 使用 or 来显示所有状态为1 或 username 为 zs 的用户-- select * from users where status=1 or username='zs' 3.9 SQL 的 ORDER BY 子句 语法ORDER BY 语句用于根据指定的列对结果集进行排序。ORDER BY 语句默认按照升序对记录进行排序。如果您希望按照降序对记录进行排序,可以使用 DESC 关键字。 ORDER BY 子句 - 升序排序对 users 表中的数据,按照 status 字段进行升序排序,示例如下:12-- 对users表中的数据,按照 status 字段进行升序排序-- select * from users order by status ORDER BY 子句 – 降序排序对 users 表中的数据,按照 id 字段进行降序排序,示例如下:12-- 按照 id 对结果进行降序的排序 desc 表示降序排序 asc 表示升序排序(默认情况下,就是升序排序的)-- select * from users order by id desc ORDER BY 子句 – 多重排序对 users 表中的数据,先按照 status 字段进行降序排序,再按照 username 的字母顺序,进行升序排序,示例如下:12-- 对 users 表中的数据,先按照 status 进行降序排序,再按照 username 字母的顺序,进行升序的排序-- select * from users order by status desc, username asc 3.10 SQL 的 COUNT(*) 函数 语法COUNT(*) 函数用于返回查询结果的总数据条数,语法格式如下: COUNT(*) 示例查询 users 表中 status 为 0 的总数据条数:12-- 使用 count(*) 来统计 users 表中,状态为 0 用户的总数量-- select count(*) from users where status=0 使用 AS 为列设置别名如果希望给查询出来的列名称设置别名,可以使用 AS 关键字,示例如下:123-- 使用 AS 关键字给列起别名-- select count(*) as total from users where status=0-- select username as uname, password as upwd from users 4. 在项目中操作 MySQL4.1 在项目中操作数据库的步骤① 安装操作 MySQL 数据库的第三方模块(mysql)② 通过 mysql 模块连接到 MySQL 数据库③ 通过 mysql 模块执行 SQL 语句 4.2 安装与配置 mysql 模块 安装 mysql 模块mysql 模块是托管于 npm 上的第三方模块。它提供了在 Node.js 项目中连接和操作 MySQL 数据库的能力。想要在项目中使用它,需要先运行如下命令,将 mysql 安装为项目的依赖包:1npm install mysql 配置 mysql 模块在使用 mysql 模块操作 MySQL 数据库之前,必须先对 mysql 模块进行必要的配置,主要的配置步骤如下:1234567cosnt mysql = require('mysql')const db = mysql.createPool({ host:'127.0.0.1', user: 'root', password: 'admin123', database: 'my_dm_01'}) 测试 mysql 模块能否正常工作调用 db.query() 函数,指定要执行的 SQL 语句,通过回调函数拿到执行的结果:1234567// 测试 mysql 模块能否正常工作db.query('select 1', (err, results) => { //mysql 模块工作期间报错了 if(err) return console.log(err.message) //能够成功的执行 SQL 语句 console.log(results)}) 4.3 使用 mysql 模块操作 MySQL 数据库 查询数据查询 users 表中所有的数据:123456789// 查询 users 表中所有的数据/* const sqlStr = 'select * from users'db.query(sqlStr, (err, results) => { // 查询数据失败 if (err) return console.log(err.message) // 查询数据成功 // 注意:如果执行的是 select 查询语句,则执行的结果是数组 console.log(results)}) */ 插入数据向 users 表中新增数据, 其中 username 为 Spider-Man,password 为 pcc321。示例代码如下:123456789101112131415// 向 users 表中,新增一条数据,其中 username 的值为 Spider-Man,password 的值为 pcc123/* const user = { username: 'Spider-Man', password: 'pcc123' }// 定义待执行的 SQL 语句const sqlStr = 'insert into users (username, password) values (?, ?)'// 执行 SQL 语句db.query(sqlStr, [user.username, user.password], (err, results) => { // 执行 SQL 语句失败了 if (err) return console.log(err.message) // 成功了 // 注意:如果执行的是 insert into 插入语句,则 results 是一个对象 // 可以通过 affectedRows 属性,来判断是否插入数据成功 if (results.affectedRows === 1) { console.log('插入数据成功!') }}) */ 插入数据的便捷方式向表中新增数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速插入数据:1234567891011// 演示插入数据的便捷方式/* const user = { username: 'Spider-Man2', password: 'pcc4321' }// 定义待执行的 SQL 语句const sqlStr = 'insert into users set ?'// 执行 SQL 语句db.query(sqlStr, user, (err, results) => { if (err) return console.log(err.message) if (results.affectedRows === 1) { console.log('插入数据成功') }}) */ 更新数据可以通过如下方式,更新表中的数据:123456789101112// 演示如何更新用户的信息/* const user = { id: 6, username: 'aaa', password: '000' }// 定义 SQL 语句const sqlStr = 'update users set username=?, password=? where id=?'// 执行 SQL 语句db.query(sqlStr, [user.username, user.password, user.id], (err, results) => { if (err) return console.log(err.message) // 注意:执行了 update 语句之后,执行的结果,也是一个对象,可以通过 affectedRows 判断是否更新成功 if (results.affectedRows === 1) { console.log('更新成功') }}) */ 更新数据的便捷方式更新表数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速更新表数据:1234567891011// 演示更新数据的便捷方式/* const user = { id: 6, username: 'aaaa', password: '0000' }// 定义 SQL 语句const sqlStr = 'update users set ? where id=?'// 执行 SQL 语句db.query(sqlStr, [user, user.id], (err, results) => { if (err) return console.log(err.message) if (results.affectedRows === 1) { console.log('更新数据成功') }}) */ 删除数据在删除数据时,推荐根据 id 这样的唯一标识,来删除对应的数据。示例如下:123456789// 删除 id 为 5 的用户/* const sqlStr = 'delete from users where id=?'db.query(sqlStr, 5, (err, results) => { if (err) return console.log(err.message) // 注意:执行 delete 语句之后,结果也是一个对象,也会包含 affectedRows 属性 if (results.affectedRows === 1) { console.log('删除数据成功') }}) */ 标记删除使用 DELETE 语句,会把真正的把数据从表中删除掉。为了保险起见,推荐使用标记删除的形式,来模拟删除的动作。所谓的标记删除,就是在表中设置类似于 status 这样的状态字段,来标记当前这条数据是否被删除。当用户执行了删除的动作时,我们并没有执行 DELETE 语句把数据删除掉,而是执行了 UPDATE 语句,将这条数据对应的 status 字段标记为删除即可。12345678// 标记删除const sqlStr = 'update users set status=? where id=?'db.query(sqlStr, [1, 6], (err, results) => { if (err) return console.log(err.message) if (results.affectedRows === 1) { console.log('标记删除成功') }}) 5. 前后端的身份认证5.1 Web 开发模式目前主流的 Web 开发模式有两种,分别是:① 基于服务端渲染的传统 Web 开发模式② 基于前后端分离的新型 Web 开发模式 服务端渲染的 Web 开发模式服务端渲染的概念:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用 Ajax 这样的技术额外请求页面的数据。代码示例如下: 服务端渲染的优缺点优点:① 前端耗时少。因为服务器端负责动态生成 HTML 内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。② 有利于SEO。因为服务器端响应的是完整的 HTML 页面内容,所以爬虫更容易爬取获得信息,更有利于 SEO。缺点:① 占用服务器端资源。即服务器端完成 HTML 页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。② 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发。 前后端分离的 Web 开发模式前后端分离的概念:前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。简而言之,前后端分离的 Web 开发模式,就是后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式。 前后端分离的优缺点优点:① 开发体验好。前端专注于 UI 页面的开发,后端专注于api 的开发,且前端有更多的选择性。② 用户体验好。Ajax 技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。③ 减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。缺点:① 不利于 SEO。因为完整的 HTML 页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方案:利用 Vue、React 等前端框架的 SSR (server side render)技术能够很好的解决 SEO 问题!) 如何选择 Web 开发模式不谈业务场景而盲目选择使用何种开发模式都是耍流氓。 比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的 SEO,则这时我们就需要使用服务器端渲染; 而类似后台管理项目,交互性比较强,不需要考虑 SEO,那么就可以使用前后端分离的开发模式。另外,具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,一些网站采用了首屏服务器端渲染 + 其他页面前后端分离的开发模式。 5.2 身份认证 什么是身份认证身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。 日常生活中的身份认证随处可见,例如:高铁的验票乘车,手机的密码或指纹解锁,支付宝或微信的支付密码等。 在 Web 开发中,也涉及到用户身份的认证,例如:各大网站的手机验证码登录、邮箱密码登录、二维码登录等。 为什么需要身份认证身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户。例如,你去找快递员取快递,你要怎么证明这份快递是你的。在互联网项目开发中,如何对用户的身份进行认证,是一个值得深入探讨的问题。例如,如何才能保证网站不会错误的将“马云的存款数额”显示到“马化腾的账户”上。 不同开发模式下的身份认证对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:① 服务端渲染推荐使用 Session 认证机制② 前后端分离推荐使用 JWT 认证机制 5.3 Session 认证机制 HTTP 协议的无状态性了解 HTTP 协议的无状态性是进一步学习 Session 认证机制的必要前提。HTTP 协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态。 如何突破 HTTP 无状态的限制对于超市来说,为了方便收银员在进行结算时给 VIP 用户打折,超市可以为每个 VIP 用户发放会员卡。注意:现实生活中的会员卡身份认证方式,在 Web 开发中的专业术语叫做 Cookie。 什么是 CookieCookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。Cookie的几大特性:① 自动发送② 域名独立③ 过期时限④ 4KB 限制 Cookie 在身份认证中的作用客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份。 Cookie 不具有安全性由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。注意:千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。 提高身份认证的安全性为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有收银机确认存在的会员卡,才能被正常使用。这种“会员卡 + 刷卡认证”的设计理念,就是 Session 认证机制的精髓。 5.5 JWT 认证机制 了解 Session 认证的局限性Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。注意: 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制。 什么是 JWTJWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。 JWT 的工作原理总结:用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。 JWT 的组成部分JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。三者之间使用英文的“.”分隔,格式如下: JWT 的三个部分各自代表的含义JWT 的三个组成部分,从前到后分别是 Header、Payload、Signature。其中: Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。 Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性。 JWT 的使用方式客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP请求头的 Authorization 字段中,格式如下: 5.6 在 Express 中使用 JWT 安装 JWT 相关的包运行如下命令,安装如下两个 JWT 相关的包:其中: jsonwebtoken 用于生成 JWT 字符串 express-jwt 用于将 JWT 字符串解析还原成 JSON 对象","link":"/blog/2022/05/14/MySQL-5-14-1/"},{"title":"Node.js.5.12.1","text":"2.fs 文件系统模块2.1 什么是 fs 文件系统模块fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。例如: fs.readFile() 方法,用来读取指定文件中的内容 fs.writeFile() 方法,用来向指定的文件中写入内容如果要在 JavaScript 代码中,使用 fs 模块来操作文件,则需要使用如下的方式先导入它:12const fs =require( 'fs ')--- 2.2 读取指定文件中的内容 fs.readFile() 的语法格式使用 fs.readFile() 方法,可以读取指定文件中的内容,语法格式如下:1fs.readFile(path[, options], callback) 参数解读: 参数1:必选参数,字符串,表示文件的路径。 参数2:可选参数,表示以什么编码格式来读取文件。 参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果。 fs.readFile() 的示例代码以 utf8 的编码格式,读取指定文件的内容,并打印 err 和 dataStr 的值:12345const fs = require('fs')fs.readFile('../code/files/1.txt', 'utf8', (err, data_str) => { console.log(err); console.log(data_str);}) 判断文件是否读取成功可以判断 err 对象是否为 null,从而知晓文件读取的结果:12345const fs = require( 'fs ')fs.readFile( ' .ifiles/1.txt",'utf8", function(err, result) { if (err) {return console.log('文件读取失败! ' + err.message)}console.log('文件读取成功,内容是:' t result)}) 2.3 向指定的文件中写入内容 fs.writeFile() 的语法格式使用 fs.writeFile() 方法,可以向指定的文件中写入内容,语法格式如下:参数解读:1fs.writeFile(file, data[, options], callback) 参数1:必选参数,需要指定一个文件路径的字符串,表示文件的存放路径。 参数2:必选参数,表示要写入的内容。 参数3:可选参数,表示以什么格式写入文件内容,默认值是 utf8。 参数4:必选参数,文件写入完成后的回调函数。 fs.writeFile() 的示例代码向指定的文件路径中,写入文件内容:1234const fs = require('fs')fs.writeFile("../code/files/1.txt", 'hello Node.js', err => { console.log(err);}) 判断文件是否写入成功可以判断 err 对象是否为 null,从而知晓文件写入的结果:12345678const fs = require('fs')fs.readFile('./files/11.txt', 'utf8', function(err, dataStr) { if (err) { return console.log('读取文件失败!' + err.message) } console.log('读取文件成功!' + dataStr)}) 2.5 练习 - 考试成绩整理使用 fs 文件系统模块,将素材目录下成绩.txt文件中的考试数据,整理到成绩-ok.txt文件中。核心实现步骤① 导入需要的 fs 文件系统模块② 使用 fs.readFile() 方法,读取素材目录下的 成绩.txt 文件③ 判断文件是否读取失败④ 文件读取成功后,处理成绩数据⑤ 将处理完成的成绩数据,调用 fs.writeFile() 方法,写入到新文件 成绩-ok.txt 中 1234567891011121314151617181920212223242526272829// 1. 导入 fs 模块const fs = require('fs')// 2. 调用 fs.readFile() 读取文件的内容fs.readFile('../素材/成绩.txt', 'utf8', function(err, dataStr) { // 3. 判断是否读取成功 if (err) { return console.log('读取文件失败!' + err.message) } // console.log('读取文件成功!' + dataStr) // 4.1 先把成绩的数据,按照空格进行分割 const arrOld = dataStr.split(' ') // 4.2 循环分割后的数组,对每一项数据,进行字符串的替换操作 const arrNew = [] arrOld.forEach(item => { arrNew.push(item.replace('=', ':')) }) // 4.3 把新数组中的每一项,进行合并,得到一个新的字符串 const newStr = arrNew.join('\\r\\n') // 5. 调用 fs.writeFile() 方法,把处理完毕的成绩,写入到新文件中 fs.writeFile('./files/成绩-ok.txt', newStr, function(err) { if (err) { return console.log('写入文件失败!' + err.message) } console.log('成绩写入成功!') })}) 2.6 fs 模块 - 路径动态拼接的问题在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 ../ 开头的相对路径时,很容易出现路径动态拼接错误的问题。原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径。解决方案:在使用 fs 模块操作文件时,直接提供完整的路径,不要提供 ./ 或 ../ 开头的相对路径,从而防止路径动态拼接的问题。 1234567891011121314151617181920212223242526272829const fs = require('fs')// 出现路径拼接错误的问题,是因为提供了 ./ 或 ../ 开头的相对路径// 如果要解决这个问题,可以直接提供一个完整的文件存放路径就行/* fs.readFile('./files/1.txt', 'utf8', function(err, dataStr) { if (err) { return console.log('读取文件失败!' + err.message) } console.log('读取文件成功!' + dataStr)}) */// 移植性非常差、不利于维护/* fs.readFile('C:\\\\Users\\\\escook\\\\Desktop\\\\Node.js基础\\\\day1\\\\code\\\\files\\\\1.txt', 'utf8', function(err, dataStr) { if (err) { return console.log('读取文件失败!' + err.message) } console.log('读取文件成功!' + dataStr)}) */// __dirname 表示当前文件所处的目录// console.log(__dirname)fs.readFile(__dirname + '/files/1.txt', 'utf8', function(err, dataStr) { if (err) { return console.log('读取文件失败!' + err.message) } console.log('读取文件成功!' + dataStr)})","link":"/blog/2022/05/12/Node-js-5-12-1/"},{"title":"Node.js.5.12.2","text":"3. path 路径模块3.1 什么是 path 路径模块path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。例如: path.join() 方法,用来将多个路径片段拼接成一个完整的路径字符串 path.basename() 方法,用来从路径字符串中,将文件名解析出来如果要在 JavaScript 代码中,使用 path 模块来处理路径,则需要使用如下的方式先导入它:1const path = require('path') 3.2 路径拼接 path.join() 的语法格式使用 path.join() 方法,可以把多个路径片段拼接为完整的路径字符串,语法格式如下:1path.join([ . . .paths]) 参数解读: …paths 路径片段的序列 返回值: path.join() 的代码示例12345678const path = require('path')const fs = require('fs')// 注意: ../ 会抵消前面的路径const pathStr = path.join('/a', '/b/c', '../../', './d', 'e')console.log(pathStr) // \\a\\b\\d\\efs.readFile(__dirname + '/files/1.txt') 使用 path.join() 方法,可以把多个路径片段拼接为完整的路径字符串:注意:今后凡是涉及到路径拼接的操作,都要使用 path.join() 方法进行处理。不要直接使用 + 进行字符串的拼接。 3.3 获取路径中的文件名 path.basename() 的语法格式使用 path.basename() 方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下:1path.basename(path[ , ext]) 参数解读: path 必选参数,表示一个路径的字符串 ext 可选参数,表示文件扩展名 返回: 表示路径中的最后一部分 path.basename() 的代码示例使用 path.basename() 方法,可以从一个文件路径中,获取到文件的名称部分:12345678910const path = require('path')// 定义文件的存放路径const fpath = '/a/b/c/index.html'// const fullName = path.basename(fpath)// console.log(fullName)const nameWithoutExt = path.basename(fpath, '.html')console.log(nameWithoutExt) 3.4 获取路径中的文件扩展名 path.extname() 的语法格式使用 path.extname() 方法,可以获取路径中的扩展名部分,语法格式如下:1path.extname(path) 参数解读: path 必选参数,表示一个路径的字符串 返回: 返回得到的扩展名字符串 path.extname() 的代码示例使用 path.extname() 方法,可以获取路径中的扩展名部分:1234567const path = require('path')// 这是文件的存放路径const fpath = '/a/b/c/index.html'const fext = path.extname(fpath)console.log(fext) 3.5 综合案例 - 时钟案例 案例要实现的功能将素材目录下的 index.html 页面,拆分成三个文件,分别是: index.css index.js index.html并且将拆分出来的 3 个文件,存放到 clock 目录中。 案例的实现步骤① 创建两个正则表达式,分别用来匹配 style 和 script 标签② 使用 fs 模块,读取需要被处理的 HTML 文件③ 自定义 resolveCSS 方法,来写入 index.css 样式文件④ 自定义 resolveJS 方法,来写入 index.js 脚本文件⑤ 自定义 resolveHTML 方法,来写入 index.html 文件 步骤1 - 导入需要的模块并创建正则表达式12345678// 1.1 导入 fs 模块const fs = require('fs')// 1.2 导入 path 模块const path = require('path')// 1.3 定义正则表达式,分别匹配 <style></style> 和 <script></script> 标签const regStyle = /<style>[\\s\\S]*<\\/style>/const regScript = /<script>[\\s\\S]*<\\/script>/ 步骤2 - 使用 fs 模块读取需要被处理的 html 文件12345678fs.readFile(path.join(__dirname, '../素材/index.html'), 'utf8', function(err, dataStr) { // 2.2 读取 HTML 文件失败 if (err) return console.log('读取HTML文件失败!' + err.message) // 2.3 读取文件成功后,调用对应的三个方法,分别拆解出 css, js, html 文件 resolveCSS(dataStr) resolveJS(dataStr) resolveHTML(dataStr)}) 步骤3 – 自定义 resolveCSS 方法123456789101112// 3.1 定义处理 css 样式的方法function resolveCSS(htmlStr) { // 3.2 使用正则提取需要的内容 const r1 = regStyle.exec(htmlStr) // 3.3 将提取出来的样式字符串,进行字符串的 replace 替换操作 const newCSS = r1[0].replace('<style>', '').replace('</style>', '') // 3.4 调用 fs.writeFile() 方法,将提取的样式,写入到 clock 目录中 index.css 的文件里面 fs.writeFile(path.join(__dirname, './clock/index.css'), newCSS, function(err) { if (err) return console.log('写入 CSS 样式失败!' + err.message) console.log('写入样式文件成功!') })} 步骤4 – 自定义 resolveJS 方法123456789101112// 4.1 定义处理 js 脚本的方法function resolveJS(htmlStr) { // 4.2 通过正则,提取对应的 <script></script> 标签内容 const r2 = regScript.exec(htmlStr) // 4.3 将提取出来的内容,做进一步的处理 const newJS = r2[0].replace('<script>', '').replace('</script>', '') // 4.4 将处理的结果,写入到 clock 目录中的 index.js 文件里面 fs.writeFile(path.join(__dirname, './clock/index.js'), newJS, function(err) { if (err) return console.log('写入 JavaScript 脚本失败!' + err.message) console.log('写入 JS 脚本成功!') })} 步骤5 – 自定义 resolveHTML 方法12345678910// 5.1 定义处理 HTML 结构的方法function resolveHTML(htmlStr) { // 5.2 将字符串调用 replace 方法,把内嵌的 style 和 script 标签,替换为外联的 link 和 script 标签 const newHTML = htmlStr.replace(regStyle, '<link rel="stylesheet" href="./index.css" />').replace(regScript, '<script src="./index.js"></script>') // 5.3 写入 index.html 这个文件 fs.writeFile(path.join(__dirname, './clock/index.html'), newHTML, function(err) { if (err) return console.log('写入 HTML 文件失败!' + err.message) console.log('写入 HTML 页面成功!') })} 4. 案例的两个注意点① fs.writeFile() 方法只能用来创建文件,不能用来创建路径② 重复调用 fs.writeFile() 写入同一个文件,新写入的内容会覆盖之前的旧内容","link":"/blog/2022/05/12/Node-js-5-12-2/"},{"title":"Node.js.5.12.3","text":"4. http 模块4.1 什么是 http 模块回顾:什么是客户端、什么是服务器?在网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器。http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。如果要希望使用 http 模块创建 Web 服务器,则需要先导入它: 1const http -require( " http ') 4.2 进一步理解 http 模块的作用服务器和普通电脑的区别在于,服务器上安装了 web 服务器软件,例如:IIS、Apache 等。通过安装这些服务器软件,就能把一台普通的电脑变成一台 web 服务器。在 Node.js 中,我们不需要使用 IIS、Apache 等这些第三方 web 服务器软件。因为我们可以基于 Node.js 提供的http 模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供 web 服务。 4.3 服务器相关的概念 IP 地址IP 地址就是互联网上每台计算机的唯一地址,因此 IP 地址具有唯一性。如果把“个人电脑”比作“一台电话”,那么“IP地址”就相当于“电话号码”,只有在知道对方 IP 地址的前提下,才能与对应的电脑之间进行数据通信。IP 地址的格式:通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数。例如:用点分十进表示的 IP地址(192.168.1.1)注意:① 互联网中每台 Web 服务器,都有自己的 IP 地址,例如:大家可以在 Windows 的终端中运行 ping www.baidu.com 命令,即可查看到百度服务器的 IP 地址。② 在开发期间,自己的电脑既是一台服务器,也是一个客户端,为了方便测试,可以在自己的浏览器中输入 127.0.0.1 这个IP 地址,就能把自己的电脑当做一台服务器进行访问了。 域名和域名服务器尽管 IP 地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,即所谓的域名(Domain Name)地址。IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供 IP 地址和域名之间的转换服务的服务器。 注意:① 单纯使用 IP 地址,互联网中的电脑也能够正常工作。但是有了域名的加持,能让互联网的世界变得更加方便。② 在开发测试期间, 127.0.0.1 对应的域名是 localhost,它们都代表我们自己的这台电脑,在使用效果上没有任何区别。3. 端口号计算机中的端口号,就好像是现实生活中的门牌号一样。通过门牌号,外卖小哥可以在整栋大楼众多的房间中,准确把外卖送到你的手中。同样的道理,在一台电脑中,可以运行成百上千个 web 服务。每个 web 服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的 web 服务进行处理。注意:① 每个端口号不能同时被多个 web 服务占用。② 在实际应用中,URL 中的 80 端口可以被省略。 4.4 创建最基本的 web 服务器 创建 web 服务器的基本步骤① 导入 http 模块② 创建 web 服务器实例③ 为服务器实例绑定 request 事件,监听客户端的请求④ 启动服务器 步骤1 - 导入 http 模块如果希望在自己的电脑上创建一个 web 服务器,从而对外提供 web 服务,则需要导入 http 模块:1const http = require('http') 步骤2 - 创建 web 服务器实例调用 http.createServer() 方法,即可快速创建一个 web 服务器实例:const server = http.createServer() 步骤3 - 为服务器实例绑定 request 事件为服务器实例绑定 request 事件,即可监听客户端发送过来的网络请求:1234// 3. 为服务器实例绑定 request 事件,监听客户端的请求server.on('request', function (req, res) { console.log('Someone visit our web server.')}) 步骤4 - 启动服务器调用服务器实例的 .listen() 方法,即可启动当前的 web 服务器实例:1234// 4. 启动服务器server.listen(8080, function () { console.log('server running at http://127.0.0.1:8080')}) req 请求对象只要服务器接收到了客户端的请求,就会调用通过 server.on() 为服务器绑定的 request 事件处理函数。如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下的方式:12345678910111213141516const http = require('http')const server = http.createServer()// req 是请求对象,包含了与客户端相关的数据和属性server.on('request', (req, res) => { // req.url 是客户端请求的 URL 地址 const url = req.url // req.method 是客户端请求的 method 类型 const method = req.method const str = `Your request url is ${url}, and request method is ${method}` console.log(str) // 调用 res.end() 方法,向客户端响应一些内容 res.end(str)})server.listen(80, () => { console.log('server running at http://127.0.0.1')}) res 响应对象在服务器的 request 事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下的方式:123456789101112131415const http = require('http')const server = http.createServer()server.on('request', (req, res) => { // 定义一个字符串,包含中文的内容 const str = `您请求的 URL 地址是 ${req.url},请求的 method 类型为 ${req.method}` // 调用 res.setHeader() 方法,设置 Content-Type 响应头,解决中文乱码的问题 res.setHeader('Content-Type', 'text/html; charset=utf-8') // res.end() 将内容响应给客户端 res.end(str)})server.listen(80, () => { console.log('server running at http://127.0.0.1')}) 4.5 根据不同的 url 响应不同的 html 内容 核心实现步骤① 获取请求的 url 地址② 设置默认的响应内容为 404 Not found③ 判断用户请求的是否为 / 或 /index.html 首页④ 判断用户请求的是否为 /about.html 关于页面⑤ 设置 Content-Type 响应头,防止中文乱码⑥ 使用 res.end() 把内容响应给客户端 动态响应内容123456789101112131415161718192021222324const http = require('http')const server = http.createServer()server.on('request', (req, res) => { // 1. 获取请求的 url 地址 const url = req.url // 2. 设置默认的响应内容为 404 Not found let content = '<h1>404 Not found!</h1>' // 3. 判断用户请求的是否为 / 或 /index.html 首页 // 4. 判断用户请求的是否为 /about.html 关于页面 if (url === '/' || url === '/index.html') { content = '<h1>首页</h1>' } else if (url === '/about.html') { content = '<h1>关于页面</h1>' } // 5. 设置 Content-Type 响应头,防止中文乱码 res.setHeader('Content-Type', 'text/html; charset=utf-8') // 6. 使用 res.end() 把内容响应给客户端 res.end(content)})server.listen(80, () => { console.log('server running at http://127.0.0.1')}) 4.6 案例 - 实现 clock 时钟的 web 服务器 核心思路把文件的实际存放路径,作为每个资源的请求 url 地址。 实现步骤① 导入需要的模块② 创建基本的 web 服务器③ 将资源的请求 url 地址映射为文件的存放路径④ 读取文件内容并响应给客户端⑤ 优化资源的请求路径 步骤1 - 导入需要的模块123456// 1.1 导入 http 模块const http = require('http')// 1.2 导入 fs 模块const fs = require('fs')// 1.3 导入 path 模块const path = require('path') 步骤2 - 创建基本的 web 服务器123456789101112131415161718192021222324252627282930313233343536373839404142// 1.1 导入 http 模块const http = require('http')// 1.2 导入 fs 模块const fs = require('fs')// 1.3 导入 path 模块const path = require('path')// 2.1 创建 web 服务器const server = http.createServer()// 2.2 监听 web 服务器的 request 事件server.on('request', (req, res) => { // 3.1 获取到客户端请求的 URL 地址 // /clock/index.html // /clock/index.css // /clock/index.js const url = req.url // 3.2 把请求的 URL 地址映射为具体文件的存放路径 // const fpath = path.join(__dirname, url) // 5.1 预定义一个空白的文件存放路径 let fpath = '' if (url === '/') { fpath = path.join(__dirname, './clock/index.html') } else { // /index.html // /index.css // /index.js fpath = path.join(__dirname, '/clock', url) } // 4.1 根据“映射”过来的文件路径读取文件的内容 fs.readFile(fpath, 'utf8', (err, dataStr) => { // 4.2 读取失败,向客户端响应固定的“错误消息” if (err) return res.end('404 Not found.') // 4.3 读取成功,将读取成功的内容,响应给客户端 res.end(dataStr) })})// 2.3 启动服务器server.listen(80, () => { console.log('server running at http://127.0.0.1')})","link":"/blog/2022/05/12/Node-js-5-12-3/"},{"title":"Node.js.5.13.1","text":"1. 模块化的基本概念1.1 什么是模块化 现实生活中的模块化 编程领域中的模块化编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。把代码进行模块化拆分的好处:① 提高了代码的复用性② 提高了代码的可维护性③ 可以实现按需加载 1.2 模块化规范模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。例如: 使用什么样的语法格式来引用模块 在模块中使用什么样的语法格式向外暴露成员模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。 2. Node.js 中的模块化2.1 Node.js 中模块的分类Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是: 内置模块(内置模块是由 Node.js 官方提供的,例如 fs、path、http 等) 自定义模块(用户创建的每个 .js 文件,都是自定义模块) 第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载) 2.2 加载模块使用强大的 require() 方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。例如: 123456// 1.加载内置的fs 模块const fs = require( "fs ')// 2.加载用户的自定义模块const custom = require( ' ./ custom.js ')// 3.加载第三方模块(关于第三方模块的下载和使用,会在后面的课程中进行专门 的讲解)const moment = require( " moment ' ) 注意:使用 require() 方法加载其它模块时,会执行被加载模块中的代码。 2.3 Node.js 中的模块作用域 什么是模块作用域和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。 模块作用域的好处防止了全局变量污染的问题 2.4 向外共享模块作用域中的成员 module 对象在每个 .js 自定义模块中都有一个 module 对象,它里面存储了和当前模块有关的信息,打印如下: Module { id: ‘.’, path: ‘C:\\Users\\ceaser\\Desktop\\up_station\\node.js—资料\\day2\\code’, exports: {}, filename: ‘C:\\Users\\ceaser\\Desktop\\up_station\\node.js—资料\\day2\\code\\10.演示module对象.js’, loaded: false, children: [], paths: [ ‘C:\\Users\\ceaser\\Desktop\\up_station\\node.js—资料\\day2\\code\\node_modules’, ‘C:\\Users\\ceaser\\Desktop\\up_station\\node.js—资料\\day2\\node_modules’, ‘C:\\Users\\ceaser\\Desktop\\up_station\\node.js—资料\\node_modules’, ‘C:\\Users\\ceaser\\Desktop\\up_station\\node_modules’, ‘C:\\Users\\ceaser\\Desktop\\node_modules’, ‘C:\\Users\\ceaser\\node_modules’, ‘C:\\Users\\node_modules’, ‘C:\\node_modules’ ]} module.exports 对象在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象 共享成员时的注意点使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准。1234567891011121314// console.log(exports)// console.log(module.exports)// console.log(exports === module.exports)const username = 'zs'module.exports.username = usernameexports.age = 20exports.sayHello = function() { console.log('大家好!')}// 最终,向外共享的结果,永远都是 module.exports 所指向的对象 exports 对象由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了 exports 对象。默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准。 exports 和 module.exports 的使用误区时刻谨记,require() 模块时,得到的永远是 module.exports 指向的对象 3. npm与包3.1 包 什么是包Node.js 中的第三方模块又叫做包。就像电脑和计算机指的是相同的东西,第三方模块和包指的是同一个概念,只不过叫法不同。 包的来源不同于 Node.js 中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。注意:Node.js 中的包都是免费且开源的,不需要付费即可免费下载使用。 为什么需要包由于 Node.js 的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低。包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率。包和内置模块之间的关系,类似于 jQuery 和 浏览器内置 API 之间的关系。 从哪里下载包国外有一家 IT 公司,叫做 npm, Inc. 这家公司旗下有一个非常著名的网站: https://www.npmjs.com/ ,它是全球最大的包共享平台,你可以从这个网站上搜索到任何你需要的包,只要你有足够的耐心!到目前位置,全球约 1100 多万的开发人员,通过这个包共享平台,开发并共享了超过 120 多万个包 供我们使用。npm, Inc. 公司提供了一个地址为 https://registry.npmjs.org/ 的服务器,来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包。注意: 从 https://www.npmjs.com/ 网站上搜索自己所需要的包 从 https://registry.npmjs.org/ 服务器上下载自己需要的包 如何下载包npm, Inc. 公司提供了一个包管理工具,我们可以使用这个包管理工具,从 https://registry.npmjs.org/ 服务器把需要的包下载到本地使用。这个包管理工具的名字叫做 Node Package Manager(简称 npm 包管理工具),这个包管理工具随着 Node.js 的安装包一起被安装到了用户的电脑上。大家可以在终端中执行 npm -v 命令,来查看自己电脑上所安装的 npm 包管理工具的版本号: 3.2 npm 初体验 格式化时间的传统做法① 创建格式化时间的自定义模块② 定义格式化时间的方法③ 创建补零函数④ 从自定义模块中导出格式化时间的函数⑤ 导入格式化时间的自定义模块⑥ 调用格式化时间的函数12345678910111213141516171819202122232425262728293031// 1. 定义格式化时间的方法function dateFormat(dtStr) { const dt = new Date(dtStr) const y = dt.getFullYear() const m = padZero(dt.getMonth() + 1) const d = padZero(dt.getDate()) const hh = padZero(dt.getHours()) const mm = padZero(dt.getMinutes()) const ss = padZero(dt.getSeconds()) return `${y}-${m}-${d} ${hh}:${mm}:${ss}`}// 定义补零的函数function padZero(n) { return n > 9 ? n : '0' + n}module.exports = { dateFormat}const TIME = require('./15.dateFormat')// 调用方法,进行时间的格式化const dt = new Date()// console.log(dt)const newDT = TIME.dateFormat(dt)console.log(newDT) 格式化时间的高级做法① 使用 npm 包管理工具,在项目中安装格式化时间的包 moment② 使用 require() 导入格式化时间的包③ 参考 moment 的官方 API 文档对时间进行格式化123456// 1. 导入需要的包// 注意:导入的名称,就是装包时候的名称const moment = require('moment')const dt = moment().format('YYYY-MM-DD HH:mm:ss')console.log(dt) 初次装包后多了哪些文件初次装包完成后,在项目文件夹下多一个叫做 node_modules 的文件夹和 package-lock.json 的配置文件。其中:node_modules 文件夹用来存放所有已安装到项目中的包。require() 导入第三方包时,就是从这个目录中查找并加载包。package-lock.json 配置文件用来记录 node_modules 目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。注意:程序员不要手动修改 node_modules 或 package-lock.json 文件中的任何代码,npm 包管理工具会自动维护它们。 安装指定版本的包默认情况下,使用 npm install 命令安装包的时候,会自动安装最新版本的包。如果需要安装指定版本的包,可以在包名之后,通过 @ 符号指定具体的版本,例如: 包的语义化版本规范包的版本号是以“点分十进制”形式进行定义的,总共有三位数字,例如 2.24.0其中每一位数字所代表的的含义如下:第1位数字:大版本第2位数字:功能版本第3位数字:Bug修复版本版本号提升的规则:只要前面的版本号增长了,则后面的版本号归零。 3.3 包管理配置文件npm 规定,在项目根目录中,必须提供一个叫做 package.json 的包管理配置文件。用来记录与项目有关的一些配置信息。例如: 项目的名称、版本号、描述等 项目中都用到了哪些包 哪些包只在开发期间会用到 那些包在开发和部署时都需要用到 多人协作的问题整个项目的体积是 30.4M第三方包的体积是 28.8M项目源代码的体积 1.6M遇到的问题:第三方包的体积过大,不方便团队成员之间共享项目源代码。解决方案:共享时剔除node_module 如何记录项目中安装了哪些包在项目根目录中,创建一个叫做 package.json 的配置文件,即可用来记录项目中安装了哪些包。从而方便剔除node_modules 目录之后,在团队成员之间共享项目的源代码。注意:今后在项目开发中,一定要把 node_modules 文件夹,添加到 .gitignore 忽略文件中。 快速创建 package.jsonnpm 包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建 package.json 这个包管理配置文件:npm init -y注意:① 上述命令只能在英文的目录下成功运行!所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格。② 运行 npm install 命令安装包的时候,npm 包管理工具会自动把包的名称和版本号,记录到 package.json 中。 dependencies 节点package.json 文件中,有一个 dependencies 节点,专门用来记录您使用 npm install命令安装了哪些包。 一次性安装所有的包当我们拿到一个剔除了 node_modules 的项目之后,需要先把所有的包下载到项目中,才能将项目运行起来。否则会报类似于下面的错误: 一次性安装所有的包可以运行 npm install 命令(或 npm i)一次性安装所有的依赖包: 卸载包可以运行 npm uninstall 命令,来卸载指定的包:注意:npm uninstall 命令执行成功后,会把卸载的包,自动从 package.json 的 dependencies 中移除掉。 devDependencies 节点如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到 devDependencies 节点中。与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中。您可以使用如下的命令,将包记录到 devDependencies 节点中: 3.5 包的分类 项目包那些被安装到项目的 node_modules 目录中的包,都是项目包。项目包又分为两类,分别是: 开发依赖包(被记录到 devDependencies 节点中的包,只在开发期间会用到) 核心依赖包(被记录到 dependencies 节点中的包,在开发期间和项目上线之后都会用到) 全局包在执行 npm install 命令时,如果提供了 -g 参数,则会把包安装为全局包。全局包会被安装到 C:\\Users\\用户目录\\AppData\\Roaming\\npm\\node_modules 目录下。注意:① 只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令。② 判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明即可。 3.6 规范的包结构在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。一个规范的包,它的组成结构,必须符合以下 3 点要求:① 包必须以单独的目录而存在② 包的顶级目录下要必须包含 package.json 这个包管理配置文件③ package.json 中必须包含 name,version,main 这三个属性,分别代表包的名字、版本号、包的入口。注意:以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:https://yarnpkg.com/zh-Hans/docs/package-json","link":"/blog/2022/05/13/Node-js-5-13-1/"},{"title":"supermall项目开发","text":"1.划分目录结构当你通过脚手架搭建完项目框架后,第一件需要做的就是划分目录结构,这是一个需要养成的良好习惯,通常我们只会在src文件夹里面划分。除了默认的components和assets文件夹我们需要添加network文件夹,在其中开发我们所有网络请求的代码;添加router文件夹,里面是我们和路由跳转相关的代码;添加store文件夹,在里面开发复杂组件间数据通讯的Vuex代码;添加views文件夹,这是","link":"/blog/2022/05/02/supermall%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/"},{"title":"什么是webpack?","text":"从本质上来讲,webpac是一个现代的JavaScript应用的静态打包工具。从两个点来解释上面这句话:模块和打包。 对于一个完整的项目,相关的文件一定不止只有简单的html,css,js文件,不可能一次性的直接将所有文件放在服务器上,此时需要对这些文件进行统一的打包,解析。我们在通过模块化开发完成项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。webpack其中一项功能就是处理模块间的各种依赖关系。 什么是模块化?1.ES6写法在a文件导出1234567891011121314var name = '小明';var age = 18;var flag = true;function sum(num1, num2) { console.log(num1+num2);}if (flag) { sum(20,30)}export { flag, sum} 在b文件导入123456import { flag,sum } from "./aaa.js";if (flag) { console.log('flag接受到了'); console.log(sum(1,3));} 2.CommonJS写法在a文件导出1234567891011function add(num1,num2) { return(num1+num2)}function mul(num1,num2) { return num1*num2}module.exports = { add, mul} 在b文件导入1234const { add, mul } = require('./mathtools.js')console.log(add(1, 2));console.log(mul(1, 2)); 打包如何理解?就是将webpack中各种资源模块进行打包合并成一个或多个包。在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转换成css,将typescript转换成JavaScript等操作。 怎么使用webpack?在控制台输入命令webpack ./src/main.js ./dist/main.js 进阶用法1.新建配置文件 webpack.config.js在通过npm init新建包管理文件package.json在webpack.config.js下配置如下代码 12345678const path=require('path')module.exports = { entry: './src/main.js', //入口 output: { path: path.resolve(__dirname, 'dist'), //__dirname来自node环境自带path包,是读取当前路径功能(这也是现在需要包管理文件的原因),resolve这个api是用于字符串拼接 filename:'main.js' //出口 }} 这样后直接运行webpack,便能根据配置文件自动去生成。 2.在包管理文件的"scripts"的这个对象,写下"build": "webpack"这样在终端输入npm run build也可以,并且这样还会首先去找局部的包,而不是全局的。","link":"/blog/2022/04/22/webpack/"},{"title":"四级冲刺.4.22.1","text":"1. deliberate ideal efficient burst stationary blur shave fossil cautions passion horrible whistle pet sharp wonderful 静止的 激情的","link":"/blog/2022/04/22/%E5%9B%9B%E7%BA%A7%E5%86%B2%E5%88%BA-4-22-1/"},{"title":"四级冲刺.4.26.1","text":"1. roar pause strengths massage deposit terrify disturb satisfy landlady instrument delusion upright show off towel 押金/存款 毛巾","link":"/blog/2022/04/26/%E5%9B%9B%E7%BA%A7%E5%86%B2%E5%88%BA-4-26-1/"},{"title":"四级冲刺.4.27.1","text":"1. vaccinate polite pregnant tighten navigation red envelope capable parade detect reunion bushy dissatisfy prompt inspect piggy bank fertile plug 阅兵 提示/促使 检查 能生育的/肥沃","link":"/blog/2022/04/27/%E5%9B%9B%E7%BA%A7%E5%86%B2%E5%88%BA-4-27-1/"},{"title":"四级冲刺.5.4.1","text":"1. encounter tease slice bark excess vibrate injure bounce recommend in the act costume imitate conceal resist 取笑 振动 模仿 掩饰 反抗","link":"/blog/2022/05/04/%E5%9B%9B%E7%BA%A7%E5%86%B2%E5%88%BA-5-4-1/"},{"title":"四级冲刺.5.4.2","text":"1. cube deliver dial duration collapse logic vessel spray measure plough rhythm harmony instruct superb endure 喷洒 忍受 2. lamb succession grant beverage 授予/同意","link":"/blog/2022/05/04/%E5%9B%9B%E7%BA%A7%E5%86%B2%E5%88%BA-5-4-2/"},{"title":"四级冲刺.4.20.1","text":"1. pretend cherish what if scream approve betray regard identify interview inch period hint tear 接受 识别 暗示 2. penetrate complicated cruel commom sense absorb endlessly equivalent intact ordinary bet raw ripe smooth 穿过 3. trick slap pressure donate version expose attack optimistic battrey mood moral custom domestic 压力 暴露 道德 习俗","link":"/blog/2022/04/20/%E5%9B%9B%E7%BA%A7%E5%86%B2%E5%88%BA/"},{"title":"测试","text":"下载测试 点此下载","link":"/blog/2022/04/20/%E6%B5%8B%E8%AF%95/"},{"title":"开端","text":"现在是2022年4月19日20:27,我在宿舍,熊哥依旧在看抖音,带师在玩英雄联盟,东哥在玩魔兽世界,记录一下人生中第一个博客网站的搭建完成及上线。 关于本站使用基于node.js的轻量级博客框架hexo搭建,themes采用Icarus 关于个人现大三软件工程,方向前端,喜欢混科技圈,热爱计算机各种技术深爱搞机,拿到第一步手机就开始捣鼓,上初一搞过 MTK 思凯 mrp 格式的 app,还记得*#*#220807#这些神奇的代码,初二用过 s60v5 平台的手机,搞过 sis,sisx 格式的签名破解,上初三拿到第一步安卓手机开始捣鼓反编译修改系统,同时开始自学了一点 C 语言跟 java,高二开始自学安卓原生开发,全程都是使用 Aide,中途玩过逆向,tiny等免流,对物联网单片机等都有一些了解,使用过 树莓派、Arduino、Esp8266、ch-08 等单片机开发板,独立开发了一个简单的遥控车,对 ch340x,cp210x 等驱动有一定的了解,现致力于Vue,React,Echarts等前端框架的学习,以及个人的开源与上线个人站点。 总结:前端,后端,移动客户端,产品,UI,都在学,除前端外其他基本全是野路子。 我的照片高中 大学 未完待续。。。","link":"/blog/2022/04/19/%E7%AC%AC%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0/"},{"title":"JavaScript高级.7.14.1","text":"手写Promise的整个逻辑和Apithen方法then方法是Promise对象上的一个方法:它其实是放在Promise的原型上的 Promise.prototype.then then方法接受两个参数:fulfilled的回调函数:当状态变成fulfilled时会回调的函数;reject的回调函数:当状态变成reject时会回调的函数; 1、同一个的promise可以多次调用then方法,当resolve()被回调时,所有then方法都会被调用。 123456789101112const promise = new Promise((resolve,reject)=>{ resolve()})promise.then(res=>{ console.log('res1':res)})promise.then(res=>{ console.log('res2':res)})promise.then(res=>{ console.log('res2':res)}) 2、then方法传入的“回调函数”:可以有返回值如果我们返回的是一个普通值,那么这个函数的值被作为一个新的promise的reslove值 12345678910111213promise.then(res=>{ return 'aaa'}).then(res=>{ console.log(res) //输出aaa}) //等于promise.then(res=>{ return new Promise((resolve,reject)=>{ resolve('aaa') })}).then(res=>{ console.log(res)}) 3、如果没有写返回值,then()函数会返回一个undefined 4、如果返回的是一个promise 5、如果返回的是一个对象,并且该对象实现了thenable(其实也会返回一个promise) 123456789promise.then(res=>{ return { then(resolve,reject){ resolve(22222) } }}).then(res=>{ console.log('res',res)}) catch方法12345const promise = new Promise((resolve,reject)=>{ reject()}).catch(()=>{}) catch方法其实是then的一种语法糖,then方法本来就可以接收两种回调catch也有返回值,使用起来和在then里面没有区别,也是返回一个promise final方法1234567const promise = new Promise((resolve,reject)=>{ reject()}).catch(()=>{}).finally(()=>{ console.log('finally是一定会被调用,但它没有参数')}) all方法123456789101112131415161718192021222324252627// 创建多个Promiseconst p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(11111) }, 1000);})const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject(22222) }, 2000);})const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(33333) }, 3000);})// 需求: 所有的Promise都变成fulfilled时, 再拿到结果// 意外: 在拿到所有结果之前, 有一个promise变成了rejected, 那么整个promise是rejectedPromise.all([p2, p1, p3, "aaaa"]).then(res => { console.log(res)}).catch(err => { console.log("err:", err)}) allSettled方法all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的; 在ES11(ES2020)中,添加了新的API Promise.allSettled:该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;并且这个Promise的结果一定是fulfilled的; 我们来看一下打印的结果:allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;这个对象中包含status状态,以及对应的value值; 123456789101112131415161718const p1 = new Promise((resolve) => { setTimeout(() => { resolve(1111) }, 1000)})const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject(2222) }, 2000)})const p3 = new Promise((resolve) => { setTimeout(() => { resolve(3333) }, 3000)})// HYPromise.all([p1, p2, p3]).then(res => {// console.log(res)// }).catch(err => {// console.log(err)// })HYPromise.allSettled([p1, p2, p3]).then(res => { console.log(res)}) race方法any方法手写Promise。。。","link":"/blog/2022/07/14/JavaScript%E9%AB%98%E7%BA%A7-7-14-1/"},{"title":"Vue3+TypeScript.7.19.1","text":"计算属性-watch-综合案例浅拷贝,深拷贝用json方法和lodash库可以解决v-model使用v:bind和@input可以实现 子传父利用 子emit一个事件,然后父@该事件就可以了 变量的定义number 12345let num: number = 123num = 222let num2: number = 0b111 //二进制let num3: number = 0o456//八进制 boolean 12let flag: boolean = trueflag = 20>30 string 12let message: string = 'hello TS'const name = 'ceaser' array确定一个事实:names是一个数组类型,但是数组中存放的是什么类型的元素喃? 12const names: Array<string> = [] //这种写法不推荐 jsx有冲突const names2: string[] = [] object 12345const obj = { name: 'ceaser', age: 21}console.log(obj.name); undefined+null 12let n1: null = nulllet n2: undefined = undefined any 123let measssage: any = 'hello world'measssage = 123message = true void 1234function foo(num1:number): void { console.log(num1); //当一个函数返回值为任何值的时候,这个函数就是void类型,这个也可以不写} never 12345678function foo():never { while(true){ } //表示永远不会发生值的类型,当一个函数永远不会有返回值,或者处于死循环,或者不等于任何值 应用场景,让一个函数可以再增加参数的时候,提醒必须根据这个参数去写对应函数} tuple(元组) 12//允许多种元素的组合const info: [string,number,number] = ['why',21,18] 函数类型:(new_state: any) => void 对象类型&可选类型 1234function bar(obj:{x:number,y:string,z?:number}){ console.log(obj.x);}bar({x:123,y'ceaser',z:321}) 联合类型 1const foo: number|string|boolean = 123 可选类型其实就是联合类型|undefined 类型别名 12type my_type = number|stringconst foo: my_type = 123 类型断言 123//通过类型断言可以将一个普遍的类型转成一个具体的类型const el = document.getElementById('ceaser') as HTMLImageElementel.src = 'url' 非空类型断言 ! (放在可能为空的变量后面,告诉编译器这个值不可能为空) 可选链 12console.log(obj.friend?.age); !!和?? 1234!!就是转成boolean??(空值合并操作符)let meassgae: string|null = nullconst content = meassgae ?? '出现null就用我' 字面量类型 123let num: 123 = 123let num2: 'ceaser' = 'ceaser'//字面量类型和联合类型结合才有意义 函数类型()=>void()=>number 可选类型必须写在必选类型的后面 函数重载","link":"/blog/2022/07/19/Vue3-TypeScript-7-19-1/"},{"title":"JavaScript高级.7.18.1","text":"迭代器-可迭代对象-生成器用法什么是迭代器?迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中; 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等; 在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol): 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式; 那么在js中这个标准就是一个特定的next方法; next方法有如下的要求: 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象: done(boolean) 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。) 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。 value 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。 1234567891011121314151617181920212223242526272829303132// 编写的一个迭代器const iterator = { next: function() { return { done: true, value: 123 } }}// 数组const names = ["abc", "cba", "nba"]// 创建一个迭代器对象来访问数组let index = 0const namesIterator = { next: function() { if (index < names.length) { return { done: false, value: names[index++] } } else { return { done: true, value: undefined } } }}console.log(namesIterator.next())console.log(namesIterator.next())console.log(namesIterator.next()) // { done: false, value: "nba" }console.log(namesIterator.next()) // { done: true, value: undefined }console.log(namesIterator.next()) // { done: true, value: undefined }console.log(namesIterator.next()) // { done: true, value: undefined }console.log(namesIterator.next())console.log(namesIterator.next())console.log(namesIterator.next()) 可迭代对象但是上面的代码整体来说看起来是有点奇怪的:我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象;事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象; 什么又是可迭代对象呢?它和迭代器是不同的概念;当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性; 当我们要问一个问题,我们转成这样的一个东西有什么好处呢?当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for…of 操作时,其实就会调用它的@@iterator 方法; 123456789101112131415const iterable_obj = { names:['aaa','bbb','ccc'], [Symbol.iterator]: function(){ let index = 0 return { next: ()=>{ if(index < this.names.length){ return {done:false , value:this.name[index++]} } else { return {done:false , value:undefined} } } } }} 可迭代对象的应用事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:String、Array、Map、Set、arguments对象、NodeList集合;那么这些东西可以被用在哪里呢?JavaScript中语法:for …of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);循环就是靠调用迭代器的next() 生成器什么是生成器?生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。 生成器函数也是一个函数,但是和普通的函数有一些区别:首先,生成器函数需要在function的后面加一个符号:*其次,生成器函数可以通过yield关键字来控制函数的执行流程:最后,生成器函数的返回值是一个Generator(生成器):生成器事实上是一种特殊的迭代器;MDN:Instead, they return a special type of iterator, called a Generator. 123456789101112131415161718192021222324252627282930function* foo() { console.log("函数开始执行~") const value1 = 100 console.log("第一段代码:", value1) yield const value2 = 200 console.log("第二段代码:", value2) yield const value3 = 300 console.log("第三段代码:", value3) yield console.log("函数执行结束~")}// 调用生成器函数时, 会给我们返回一个生成器对象const generator = foo()// 开始执行第一段代码generator.next()// 开始执行第二端代码console.log("-------------")generator.next()generator.next()console.log("----------")generator.next() 生成器函数的执行流程12345678910111213141516171819202122232425262728// 当遇到yield时候值暂停函数的执行// 当遇到return时候生成器就停止执行function* foo() { console.log("函数开始执行~") const value1 = 100 console.log("第一段代码:", value1) yield value1 const value2 = 200 console.log("第二段代码:", value2) yield value2 const value3 = 300 console.log("第三段代码:", value3) yield value3 console.log("函数执行结束~") return "123"}// generator本质上是一个特殊的iteratorconst generator = foo()console.log("返回值1:", generator.next())console.log("返回值2:", generator.next())console.log("返回值3:", generator.next())console.log("返回值3:", generator.next())","link":"/blog/2022/07/18/JavaScript%E9%AB%98%E7%BA%A7-7-18-1/"},{"title":"Vue3+TypeScript.7.28.1","text":"项目搭建规范一. 代码规范1.1. 集成 editorconfig 配置EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。 123456789101112131415# http://editorconfig.orgroot = true[*] # 表示所有文件适用charset = utf-8 # 设置文件字符集为 utf-8indent_style = space # 缩进风格(tab | space)indent_size = 2 # 缩进大小end_of_line = lf # 控制换行类型(lf | cr | crlf)trim_trailing_whitespace = true # 去除行首的任意空白字符insert_final_newline = true # 始终在文件末尾插入一个新行[*.md] # 表示仅 md 文件适用以下规则max_line_length = offtrim_trailing_whitespace = false VSCode 需要安装一个插件:EditorConfig for VS Code 1.2. 使用 prettier 工具Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。 1.安装 prettier 1npm install prettier -D 2.配置.prettierrc 文件: useTabs:使用 tab 缩进还是空格缩进,选择 false; tabWidth:tab 是空格的情况下,是几个空格,选择 2 个; printWidth:当行字符的长度,推荐 80,也有人喜欢 100 或者 120; singleQuote:使用单引号还是双引号,选择 true,使用单引号; trailingComma:在多行输入的尾逗号是否添加,设置为 none; semi:语句末尾是否要加分号,默认值 true,选择 false 表示不加; 12345678{ "useTabs": false, "tabWidth": 2, "printWidth": 80, "singleQuote": true, "trailingComma": "none", "semi": false} 3.创建.prettierignore 忽略文件 123456789/dist/*.local.output.js/node_modules/****/*.svg**/*.sh/public/* 4.VSCode 需要安装 prettier 的插件 5.测试 prettier 是否生效 测试一:在代码中保存代码; 测试二:配置一次性修改的命令; 在 package.json 中配置一个 scripts: 1"prettier": "prettier --write ." 1.3. 使用 ESLint 检测1.在前面创建项目的时候,我们就选择了 ESLint,所以 Vue 会默认帮助我们配置需要的 ESLint 环境。 2.VSCode 需要安装 ESLint 插件: 3.解决 eslint 和 prettier 冲突的问题: 安装插件:(vue 在创建项目时,如果选择 prettier,那么这两个插件会自动安装) 1npm i eslint-plugin-prettier eslint-config-prettier -D 添加 prettier 插件: 12345678extends: [ "plugin:vue/vue3-essential", "eslint:recommended", "@vue/typescript/recommended", "@vue/prettier", "@vue/prettier/@typescript-eslint", 'plugin:prettier/recommended'], 1.4. git Husky 和 eslint虽然我们已经要求项目使用 eslint 了,但是不能保证组员提交代码之前都将 eslint 中的问题解决掉了: 也就是我们希望保证代码仓库中的代码都是符合 eslint 规范的; 那么我们需要在组员执行 git commit 命令的时候对其进行校验,如果不符合 eslint 规范,那么自动通过规范进行修复; 那么如何做到这一点呢?可以通过 Husky 工具: husky 是一个 git hook 工具,可以帮助我们触发 git 提交的各个阶段:pre-commit、commit-msg、pre-push 如何使用 husky 呢? 这里我们可以使用自动配置命令: 1npx husky-init && npm install 这里会做三件事: 1.安装 husky 相关的依赖: 2.在项目目录下创建 .husky 文件夹: 1npx huksy install 3.在 package.json 中添加一个脚本: 接下来,我们需要去完成一个操作:在进行 commit 时,执行 lint 脚本: 这个时候我们执行 git commit 的时候会自动对代码进行 lint 校验。 1.5. git commit 规范1.5.1. 代码提交风格通常我们的 git commit 会按照统一的风格来提交,这样可以快速定位每次提交的内容,方便之后对版本进行控制。 但是如果每次手动来编写这些是比较麻烦的事情,我们可以使用一个工具:Commitizen Commitizen 是一个帮助我们编写规范 commit message 的工具; 1.安装 Commitizen 1npm install commitizen -D 2.安装 cz-conventional-changelog,并且初始化 cz-conventional-changelog: 1npx commitizen init cz-conventional-changelog --save-dev --save-exact 这个命令会帮助我们安装 cz-conventional-changelog: 并且在 package.json 中进行配置: 这个时候我们提交代码需要使用 npx cz: 第一步是选择 type,本次更新的类型 Type 作用 feat 新增特性 (feature) fix 修复 Bug(bug fix) docs 修改文档 (documentation) style 代码格式修改(white-space, formatting, missing semi colons, etc) refactor 代码重构(refactor) perf 改善性能(A code change that improves performance) test 测试(when adding missing tests) build 变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等) ci 更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等 chore 变更构建流程或辅助工具(比如更改测试环境) revert 代码回退 第二步选择本次修改的范围(作用域) 第三步选择提交的信息 第四步提交详细的描述信息 第五步是否是一次重大的更改 第六步是否影响某个 open issue 我们也可以在 scripts 中构建一个命令来执行 cz: 1.5.2. 代码提交验证如果我们按照 cz 来规范了提交风格,但是依然有同事通过 git commit 按照不规范的格式提交应该怎么办呢? 我们可以通过 commitlint 来限制提交; 1.安装 @commitlint/config-conventional 和 @commitlint/cli 1npm i @commitlint/config-conventional @commitlint/cli -D 2.在根目录创建 commitlint.config.js 文件,配置 commitlint 123module.exports = { extends: ["@commitlint/config-conventional"],}; 3.使用 husky 生成 commit-msg 文件,验证提交信息: 1npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1" 二. 第三方库集成2.1. vue.config.js 配置vue.config.js 有三种配置方式: 方式一:直接通过 CLI 提供给我们的选项来配置: 比如 publicPath:配置应用程序部署的子目录(默认是 /,相当于部署在 https://www.my-app.com/); 比如 outputDir:修改输出的文件夹; 方式二:通过 configureWebpack 修改 webpack 的配置: 可以是一个对象,直接会被合并; 可以是一个函数,会接收一个 config,可以通过 config 来修改配置; 方式三:通过 chainWebpack 修改 webpack 的配置: 是一个函数,会接收一个基于 webpack-chain 的 config 对象,可以对配置进行修改; 1234567891011121314151617181920212223const path = require("path");module.exports = { outputDir: "./build", // configureWebpack: { // resolve: { // alias: { // views: '@/views' // } // } // } // configureWebpack: (config) => { // config.resolve.alias = { // '@': path.resolve(__dirname, 'src'), // views: '@/views' // } // }, chainWebpack: (config) => { config.resolve.alias .set("@", path.resolve(__dirname, "src")) .set("views", "@/views"); },}; 2.2. vue-router 集成安装 vue-router 的最新版本: 1npm install vue-router@next 创建 router 对象: 123456789101112131415161718192021222324import { createRouter, createWebHashHistory } from "vue-router";import { RouteRecordRaw } from "vue-router";const routes: RouteRecordRaw[] = [ { path: "/", redirect: "/main", }, { path: "/main", component: () => import("../views/main/main.vue"), }, { path: "/login", component: () => import("../views/login/login.vue"), },];const router = createRouter({ routes, history: createWebHashHistory(),});export default router; 安装 router: 123import router from "./router";createApp(App).use(router).mount("#app"); 在 App.vue 中配置跳转: 1234567<template> <div id="app"> <router-link to="/login">登录</router-link> <router-link to="/main">首页</router-link> <router-view></router-view> </div></template> 2.3. vuex 集成安装 vuex: 1npm install vuex@next 创建 store 对象: 1234567891011import { createStore } from "vuex";const store = createStore({ state() { return { name: "coderwhy", }; },});export default store; 安装 store: 1createApp(App).use(router).use(store).mount("#app"); 在 App.vue 中使用: 1<h2>{{ $store.state.name }}</h2> 2.4. element-plus 集成Element Plus,一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库: 相信很多同学在 Vue2 中都使用过 element-ui,而 element-plus 正是 element-ui 针对于 vue3 开发的一个 UI 组件库; 它的使用方式和很多其他的组件库是一样的,所以学会 element-plus,其他类似于 ant-design-vue、NaiveUI、VantUI 都是差不多的; 安装 element-plus 1npm install element-plus 2.4.1. 全局引入一种引入 element-plus 的方式是全局引入,代表的含义是所有的组件和插件都会被自动注册: 1234567import ElementPlus from "element-plus";import "element-plus/lib/theme-chalk/index.css";import router from "./router";import store from "./store";createApp(App).use(router).use(store).use(ElementPlus).mount("#app"); 2.4.2. 局部引入也就是在开发中用到某个组件对某个组件进行引入: 12345678910111213141516171819202122232425262728293031<template> <div id="app"> <router-link to="/login">登录</router-link> <router-link to="/main">首页</router-link> <router-view></router-view> <h2>{{ $store.state.name }}</h2> <el-button>默认按钮</el-button> <el-button type="primary">主要按钮</el-button> <el-button type="success">成功按钮</el-button> <el-button type="info">信息按钮</el-button> <el-button type="warning">警告按钮</el-button> <el-button type="danger">危险按钮</el-button> </div></template><script lang="ts">import { defineComponent } from "vue";import { ElButton } from "element-plus";export default defineComponent({ name: "App", components: { ElButton, },});</script><style lang="less"></style> 但是我们会发现是没有对应的样式的,引入样式有两种方式: 全局引用样式(像之前做的那样); 局部引用样式(通过 babel 的插件); 1.安装 babel 的插件: 1npm install babel-plugin-import -D 2.配置 babel.config.js 1234567891011121314module.exports = { plugins: [ [ "import", { libraryName: "element-plus", customStyleName: (name) => { return `element-plus/lib/theme-chalk/${name}.css`; }, }, ], ], presets: ["@vue/cli-plugin-babel/preset"],}; 但是这里依然有个弊端: 这些组件我们在多个页面或者组件中使用的时候,都需要导入并且在 components 中进行注册; 所以我们可以将它们在全局注册一次; 123456789101112131415161718192021222324252627import { ElButton, ElTable, ElAlert, ElAside, ElAutocomplete, ElAvatar, ElBacktop, ElBadge,} from "element-plus";const app = createApp(App);const components = [ ElButton, ElTable, ElAlert, ElAside, ElAutocomplete, ElAvatar, ElBacktop, ElBadge,];for (const cpn of components) { app.component(cpn.name, cpn);} 2.5. axios 集成安装 axios: 1npm install axios 封装 axios: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";import { Result } from "./types";import { useUserStore } from "/@/store/modules/user";class HYRequest { private instance: AxiosInstance; private readonly options: AxiosRequestConfig; constructor(options: AxiosRequestConfig) { this.options = options; this.instance = axios.create(options); this.instance.interceptors.request.use( (config) => { const token = useUserStore().getToken; if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (err) => { return err; } ); this.instance.interceptors.response.use( (res) => { // 拦截响应的数据 if (res.data.code === 0) { return res.data.data; } return res.data; }, (err) => { return err; } ); } request<T = any>(config: AxiosRequestConfig): Promise<T> { return new Promise((resolve, reject) => { this.instance .request<any, AxiosResponse<Result<T>>>(config) .then((res) => { resolve(res as unknown as Promise<T>); }) .catch((err) => { reject(err); }); }); } get<T = any>(config: AxiosRequestConfig): Promise<T> { return this.request({ ...config, method: "GET" }); } post<T = any>(config: AxiosRequestConfig): Promise<T> { return this.request({ ...config, method: "POST" }); } patch<T = any>(config: AxiosRequestConfig): Promise<T> { return this.request({ ...config, method: "PATCH" }); } delete<T = any>(config: AxiosRequestConfig): Promise<T> { return this.request({ ...config, method: "DELETE" }); }}export default HYRequest; 2.6. VSCode 配置1234567891011121314151617181920212223242526272829303132333435363738394041{ "workbench.iconTheme": "vscode-great-icons", "editor.fontSize": 17, "eslint.migration.2_x": "off", "[javascript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" }, "files.autoSave": "afterDelay", "editor.tabSize": 2, "terminal.integrated.fontSize": 16, "editor.renderWhitespace": "all", "editor.quickSuggestions": { "strings": true }, "debug.console.fontSize": 15, "window.zoomLevel": 1, "emmet.includeLanguages": { "javascript": "javascriptreact" }, "explorer.confirmDragAndDrop": false, "workbench.tree.indent": 16, "javascript.updateImportsOnFileMove.enabled": "always", "editor.wordWrap": "on", "path-intellisense.mappings": { "@": "${workspaceRoot}/src" }, "hediet.vscode-drawio.local-storage": "eyIuZHJhd2lvLWNvbmZpZyI6IntcImxhbmd1YWdlXCI6XCJcIixcImN1c3RvbUZvbnRzXCI6W10sXCJsaWJyYXJpZXNcIjpcImdlbmVyYWw7YmFzaWM7YXJyb3dzMjtmbG93Y2hhcnQ7ZXI7c2l0ZW1hcDt1bWw7YnBtbjt3ZWJpY29uc1wiLFwiY3VzdG9tTGlicmFyaWVzXCI6W1wiTC5zY3JhdGNocGFkXCJdLFwicGx1Z2luc1wiOltdLFwicmVjZW50Q29sb3JzXCI6W1wiRkYwMDAwXCIsXCIwMENDNjZcIixcIm5vbmVcIixcIkNDRTVGRlwiLFwiNTI1MjUyXCIsXCJGRjMzMzNcIixcIjMzMzMzM1wiLFwiMzMwMDAwXCIsXCIwMENDQ0NcIixcIkZGNjZCM1wiLFwiRkZGRkZGMDBcIl0sXCJmb3JtYXRXaWR0aFwiOjI0MCxcImNyZWF0ZVRhcmdldFwiOmZhbHNlLFwicGFnZUZvcm1hdFwiOntcInhcIjowLFwieVwiOjAsXCJ3aWR0aFwiOjExNjksXCJoZWlnaHRcIjoxNjU0fSxcInNlYXJjaFwiOnRydWUsXCJzaG93U3RhcnRTY3JlZW5cIjp0cnVlLFwiZ3JpZENvbG9yXCI6XCIjZDBkMGQwXCIsXCJkYXJrR3JpZENvbG9yXCI6XCIjNmU2ZTZlXCIsXCJhdXRvc2F2ZVwiOnRydWUsXCJyZXNpemVJbWFnZXNcIjpudWxsLFwib3BlbkNvdW50ZXJcIjowLFwidmVyc2lvblwiOjE4LFwidW5pdFwiOjEsXCJpc1J1bGVyT25cIjpmYWxzZSxcInVpXCI6XCJcIn0ifQ==", "hediet.vscode-drawio.theme": "Kennedy", "editor.fontFamily": "Source Code Pro, 'Courier New', monospace", "editor.smoothScrolling": true, "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "workbench.colorTheme": "Atom One Dark", "vetur.completion.autoImport": false, "security.workspace.trust.untrustedFiles": "open", "eslint.lintTask.enable": true, "eslint.alwaysShowStatus": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }} 三. 接口文档https://documenter.getpostman.com/view/12387168/TzsfmQvw baseURL 的值: 1http://152.136.185.210:5000 设置全局 token 的方法: 12const res = pm.response.json();pm.globals.set("token", res.data.token); 接口文档 v2 版本:(有部分更新) https://documenter.getpostman.com/view/12387168/TzzDKb12","link":"/blog/2022/07/28/Vue3-TypeScript-7-28-1/"},{"title":"Vue3+TypeScript.7.23.1","text":"TS 类的使用1234567891011class Person { name: string; age: number; //在js中这个可以不写,但是在ts不行,ts很严格 constructor(name: string, age: number) { this.name = name; this.age = age; }}const p = new Person("ceaser", 21);console.log(p.name);console.log(p.age); 类的继承初级版 123456789101112131415class Person { name: string = '' age: number = 0 eating(){ console.log('eating'); }}class Student extends Person{ sno: number = 0 studying(){ console.log('studying'); }} 进阶版 123456789101112131415161718192021222324252627class Person { name: string = ""; age: number = 0; constructor(name: string, age: number) { (this.name = name), (this.age = age); } eating() { console.log("eating"); }}class Student extends Person { sno: number = 0; constructor(name: string, age: number) { super(name, age); //super用来调用父类的构造器 } eating(): void { console.log(" student eating"); //eating方法的重写,会覆盖掉分类的方法,但是也可以在内部使用super来调用 super.eating(); } studying(): void { console.log("studying"); }}const p1 = new Student("ceaser", 21);console.log(p1);p1.eating(); 多态12345678910111213141516171819202122class Animal { action() { console.log("animal running"); }}class Dogs extends Animal { action() { console.log("dog running"); }}class fish extends Animal{ action() { console.log("fish swimming"); }}function foo(animal: Animal[]) { animal.forEach((item) => { item.action(); });}foo([new Dogs(), new fish()]); 类的成员修饰符1234567class Person { private name: string = "";//private关键字的变量只能在内部调用 get_name() { console.log(this.name); }}//protected就是可以在子类也可以访问 readonly修饰符顾名思义 getter 和setter123456789101112class Person { private _name: string = ""; get name() { return this._name; } set name(new_name) { this._name = new_name; }}const n1 = new Person()n1.name = 'ceaser'console.log(n1.name); 类的静态成员12345class Person { static time: string = "20:00";}console.log(Person.time);//可以直接调用不需要new 抽象类抽象类无法new,但是可以继承 如果子类有constructer,那么子类的constructer必须有super() 接口123iterface ISwim { swimming: () => void} 泛型认识泛型在定义这个函数时候,我不决定这些参数的类型而是让调用者以参数的形式告知,我这里的类型应该是什么 123456function sum<T>(num1: T): T { return num1;}sum<number>(2);sum<{ name: string }>({ name: "ceaser" });sum<any[]>(["ceaser", 21]); 你不传泛型的时候会推导成字面量类型 泛型接口1234567891011121314class Point<T> { x: T; y: T; z?: T; constructor(x: T, y: T, z?: T) { this.x = x; this.y = y; this.z = z; }}const n = new Point(1, 2, 3);const n2 = new Point<number>(1, 2, 3);console.log(n); 泛型继承ts中泛型继承表示类型约束 123456789interface ILength { length: number}function foo<T extends ILength>(arg: T) { return arg.length}foo('123')","link":"/blog/2022/07/23/Vue3-TypeScript-7-23-1/"},{"title":"Vue3+TypeScript.7.29.1","text":"117","link":"/blog/2022/07/29/Vue3-TypeScript-7-29-1/"},{"title":"JavaScript高级.7.31.1","text":"前端模块化早期没有模块化带来了很多的问题:比如命名冲突的问题 当然,我们有办法可以解决上面的问题:立即函数调用表达式(IIFE)IIFE (Immediately Invoked Function Expression)但是,我们其实带来了新的问题: 第一,我必须记得每一个模块中返回对象的命名,才能在其他模块使用过程中正确的使用;第二,代码写起来混乱不堪,每个文件中的代码都需要包裹在一个匿名函数中来编写;第三,在没有合适的规范情况下,每个人、每个公司都可能会任意命名、甚至出现模块名称相同的情况;所以,我们会发现,虽然实现了模块化,但是我们的实现过于简单,并且是没有规范的。我们需要制定一定的规范来约束每个人都按照这个规范去编写模块化的代码;这个规范中应该包括核心功能:模块本身可以导出暴露的属性,模块又可以导入自己需要的属性;JavaScript社区为了解决上面的问题,涌现出一系列好用的规范,接下来我们就学习具有代表性的一些规范。没说白了比如在html文件里面通过script src导入js文件,所有文件都暴露在一个作用域里面,造成命名冲突,因此之前都是包在函数中 commonJS导出 exports 123exports.name = 'ceaser'exports.age = 21 导入 require 1const { name,age } = require('./foo') //require其实就是返回了一个exports的对象,然后做了一个解构 es module导出 export 123456789export { name, age, say_hello}//或者export default function(){ console.log(111)} 导入 import from import {name,age,say_hello} from './foo.js' //或者 import foo from './foo.js'","link":"/blog/2022/07/31/JavaScript%E9%AB%98%E7%BA%A7-7-31-1/"},{"title":"JavaScript高级.8.5.1","text":"async-await-js 线程12345678910111213async function foo() { console.log("begin"); console.log("middle"); console.log("end");}//async函数的返回值一定是一个promiseconst promise = foo();promise.then((res) => { console.log(res); //因为foo函数没写返回值,默认就是返回一个undefined}); async 中使用 await 关键字 1234567891011121314function request() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(222); }, 2000); });}async function foo() { //await后面一般是返回一个表达式这个表达式一定要返回一个promise //await函数的返回值一定是这个对应的promise resolve后的结果 const res = await request(); console.log(res);} 1234567891011121314function request() { return new Promise((resolve, reject) => { setTimeout(() => { reject("err"); }, 2000); });}async function bar() { const res1 = await request();}bar().catch((err) => { console.log(err);}); 复习 promise1、then 方法本身也是有返回值的,它的返回值是 promise如果返回的是一个普通值,那么这个普通的值被作为一个新的 promise 的 resolve 值 12345678promise .then((res) => { return "aaaaa"; //这里aaaaa会被promise包裹,作为resolve的值,会返回一个promise }) .then((res) => { //这里是新的promise对应的then //如果没有返回值,那么其实也会返回undefined,也能生成一个promise }); 2、如果返回一个 promise那么 promise 由传入的这个 promise 决定 12345promise .then((res) => { return 111; }) .catch((err) => {}); 注意这个 catch 还是指向的是第一个 promise,但是如果第一个 promise 没异常,其实还是会指向第二个 promise .finally()没有参数,但不管怎么样一定会被执行 复习迭代器和生成器1234567891011121314151617const names = ["abc", "cba", "nba"];let index = 0;const name_iterater = { next: function () { if (index < names.length) { return { done: false, value: names[index++] }; } else { return { done: false, value: undefined }; } },};console.log(name_iterater.next());console.log(name_iterater.next());console.log(name_iterater.next());console.log(name_iterater.next());console.log(name_iterater.next()); 将 names,index,和迭代器放在同一个对象中,就叫可迭代对象 123456789101112131415161718192021222324252627const iterable_obj = { names: ["abc", "cba", "nba"], [Symbol.iterator]: function () { let index = 0; return { next: () => { if (index < this.names.length) { return { done: false, value: this.names[index++] }; } else { return { done: false, value: undefined }; } }, }; },};const iterator1 = iterable_obj[Symbol.iterator]();console.log("bbb", iterator1);console.log(iterable_obj[Symbol.iterator]().next());console.log(iterable_obj[Symbol.iterator]().next());console.log(iterable_obj[Symbol.iterator]().next());console.log(iterator1.next());console.log(iterator1.next());console.log(iterator1.next());console.log(iterator1.next()); for of 只能遍历可迭代对象箭头函数体内的 this 对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象 该箭头函数所在的作用域指向的对象。只要是可迭代对象就可以 for of ,展开语法,解构赋值 但是对象也可以展开语法,解构赋值,但是这是在 es9 中新增的特性,实现原理不是迭代器 生成器生成器也是一个函数,实现了控制函数暂停执行 111 12345678910111213141516//生成器函数function* foo() { console.log("开始"); const n1 = 123; console.log(n1); yield; const n2 = 234; console.log(n2); yield;}const generator = foo();generator.next();generator.next(); 在生成器函数中,return 是特殊的 yield,后面的语句也不会执行next 方法也可以传递参数 12345678910111213141516function* foo() { console.log("开始"); const n1 = 123; console.log(n1); const n = yield n1; const n2 = 234 * n; console.log(n2); yield n2;}const generator = foo();console.log(generator.next());console.log(generator.next(10));console.log(generator.next()); 用生成器的用法写迭代器//迭代器 12345678910111213141516171819function create_iterator(err) { let index = 0; return { next: function () { if (index < arr.length) { return { done: false, value: arr[index++] }; } else { return { done: true, value: undefined }; } }, };}const arr = ["abc", "cba", "nba"];const arr_iterator = create_iterator(arr);console.log(arr_iterator.next());console.log(arr_iterator.next());console.log(arr_iterator.next());console.log(arr_iterator.next()); 迭代器是一个对象,里面有一个 next 函数 12345678910111213141516function* create_iterator(arr) { let index = 0; for (const item of arr) { yield item; } // yield arr[index++] // yield arr[index++] // yield arr[index++]}const arr = ["abc", "cba", "nba", "bbc"];const arr_iterator = create_iterator(arr);console.log(arr_iterator.next());console.log(arr_iterator.next());console.log(arr_iterator.next());console.log(arr_iterator.next()); 异步代码的处理方案12345678function quest(mes, handle1, handle2) { if ((mes = "ceaser")) { const message = "ceaser come on"; handle1(message); } else { handle2("err"); }} 高级方案 1234567891011121314151617181920212223function request_data(url) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(url); }, 2000); });}function* get_data() { const res1 = yield request_data("ceaser"); const res2 = yield request_data(res1 + "bbb"); const res3 = yield request_data(res2 + "ccc"); console.log(res3);}const generator = get_data();generator.next().value.then((res) => { generator.next(res).value.then((res) => { generator.next(res).value.then((res) => { generator.next(res); }); });}); 终极方案 1234567891011121314function request_data(url) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(url); }, 2000); });}async function get_data() { const res1 = await request_data("ceaser"); const res2 = await request_data(res1 + "bbb"); const res3 = await request_data(res2 + "ccc"); console.log(res3);}get_data();","link":"/blog/2022/08/05/JavaScript%E9%AB%98%E7%BA%A7-8-5-1/"},{"title":"字节青训营笔记.8.11.1","text":"响应式系统与 React | 青训营笔记这是我参与「第四届青训营 」笔记创作活动的第 1 天 React 的历史与应用应用前端应用开发:Facebook,Instagram,Netflix移动原生应用:Discord,Oculus结合 Electron 进行桌面应用开发 历史2010 年->Facebook 在其 php 生态中,引入了 xhp 框架,首次引入了组合式组件的思想,启发了后来的 React 的设计。2011 年->Jordan Walke 创造了 FaxJS,也就是后来的 React 原型。2012 年->在 Facebook 收购 Instagram 后,该 FaxlS 项目在内部得到使用,Jordan Walke 基于 FaxJS 的经验,创造了 React。 React 的设计思路响应式:状态更新,UI 不会自动更新,需要手动地调用 DOM 进行更新。组件化:欠缺基本的代码层面的封装和隔离,代码层面没有组件化。状态归属UI 之间的数据依赖关系,需要手动维护,如果依赖链路长,则会遇到”Callback Hell”。 React(hooks)的写法123456789101112import React, { useState } from "react";function Example() { const [count, setCount] = useState(0); return ( <div> <p>click{count} times</p> <button onClick={() => setCount(count + 1)}>click</button> </div> );} 12345678910111213141516import React, { useState } from "react";function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `Click ${count} times`; }); return ( <div> <p>click{count} times</p> <button onClick={() => setCount(count + 1)}>click</button> </div> );} React 的实现遇到的问题?JSX 不符合 js 语法返回的 JSX 发生更新时,如何改变 domState 和 Props 更新时,要重新触发 render 函数实现JSX Virtual DOM 声明式编程 Diff 算法完美的最小 Diff 算法,需要 O(n3)的复杂度。牺牲理论最小 Diff,换取时间,得到了 O(n)复杂度的算法:Heuristic O(n) Algorithm启发式 O(N)算法不同类型的元素->替换同类型的 dom 元素->更新同类型的组件元素->递归 React 状态管理库将状态抽离到 UI 外部统一进行管理当前状态,收到外部事件,迁移到下一个状态 应用级框架Next.js Modern.js Blitz","link":"/blog/2022/08/11/%E5%AD%97%E8%8A%82%E9%9D%92%E8%AE%AD%E8%90%A5%E7%AC%94%E8%AE%B0-8-11-1/"},{"title":"字节青训营笔记.8.14.1","text":"初识 WebGL | 青训营笔记这是我参与「第四届青训营 」笔记创作活动的第 3 天 WebGL 是什么?WebGL 是一种 3D 绘图协议WebGL 运行在电脑的 GPU 中,因此需要使用能在 GPU 上运行的代码,这样的代码需要提供成对的方法,每对方法中的一个叫顶点着色器而另外一个叫做片元着色器,并且使用 GLSL 语言。将顶点着色器和片元着色器连接起来的方法叫做着色程序。 Modern Graphics System绘制一个 3D 图像主要分为 4 个步骤 1、轮廓提取 2、光栅化 3、帧渲染 4、绘制 光栅(Raster):几乎所有的现代图形系统都是基于光栅来绘制图形的,光栅就是指构成图像的像素阵列。像素(Pixel):一个像素对应图像上的一个点,它通常保存图像上的某个具体位置的颜色等信息。帧缓存(Frame Buffer):在绘图过程中,像素信息被存放于帧缓存中,帧缓存是一块内存地址。CPU (Central Processing Unit):中央处理单元,负责逻辑计算。GPU (Graphics Processing Unit):图形处理单元,负责图形计算。GPU 由大量的小运算单元构成每个运算单元只负责处理很简单的计算每个运算单元彼此独立因此所有计算可以并行处理 WebGL Startup1.创建 WebGL 上下文2 创建 WebGL Program3.将数据存入缓冲区4.将缓冲区数据读取到 GPU5.GPU 执行 WebGL 程序,输出结果 创建 WebGL 上下文12const canvas = document.createElement("canvas");const gl = canvas.getContext("webgl"); 1234const program = gl.createProgram();gl.attachShader(program /*某个着色器(下文的vertexShader)*/);gl.linkProgram(program);gl.useProgram(program); 123456789const vertex = ` attribute vec2 position; void main() { gl_Position = vec4(position, 1.0, 1.0); } `;const vertexShader = gl.createShader(gl.VERTEX_SHADER);gl.shaderSource(vertexShader, vertex);gl.compileShader(vertexShader); 3D 标准模型的四个齐次矩阵(mat4)1.投影矩阵 Projection Matrix2.模型矩阵 Model Matrix3.视图矩阵 View Matrix4.法向量矩阵 Normal Matrix Read moreThe Book of Shadersmesh.jsGlsl Doodle","link":"/blog/2022/08/14/%E5%AD%97%E8%8A%82%E9%9D%92%E8%AE%AD%E8%90%A5%E7%AC%94%E8%AE%B0-8-14-1/"},{"title":"字节青训营笔记.8.12.1","text":"Webpack 知识体系 | 青训营笔记这是我参与「第四届青训营 」笔记创作活动的第 2 天 什么是 Webpack?前端项目由资源组成->图片,TS,JS,less,scss 等等等当然我们可以手动管理这些资源,但这样会引发很多问题。依赖手工,比如有 50 个 JS 文件…操作,过程繁琐,当代码文件之间有依赖的时候,就得严格按依赖顺序书写,开发与生产环境一致,难以接入 TS 或 JS 新特性,比较难接入 Less、Sass 等工具,JS、图片、csS 资源管理模型不一致。 这些都是旧时代非常突出的问题,对开发效率影响非常大,直到 Webpack 等构建工具的出现 使用 Webpack======= 什么是 Webpack?前端项目由资源组成->图片,TS,JS,less,scss 等等等当然我们可以手动管理这些资源,但这样会引发很多问题。依赖手工,比如有 50 个 JS 文件…操作,过程繁琐,当代码文件之间有依赖的时候,就得严格按依赖顺序书写,开发与生产环境一致,难以接入 TS 或 JS 新特性,比较难接入 Less、Sass 等工具,JS、图片、csS 资源管理模型不一致。 这些都是旧时代非常突出的问题,对开发效率影响非常大,直到 Webpack 等构建工具的出现 5c4d5096c5ae7133aeecb6ad100a83d8014be882 安装 1npm i -D webpack webpack-cli 编辑配置文件 12345678const path = require("path");module.exports = { entry: "./src/main.js", //入口 output: { path: path.resolve(__dirname, "dist"), //__dirname来自node环境自带path包,是读取当前路径功能(这也是现在需要包管理文件的原因),resolve这个api是用于字符串拼接 filename: "main.js", //出口 },}; 执行编译命令 12npx webpack 核心流程<<<<<<< HEAD 1、入口处理2、依赖解析3、资源解析//递归调用 2、3,直到所有资源处理完毕4、资源合并打包 怎么使用 Webpack?关于 Webpack 的使用方法,基本都围绕“配置”展开,而这些配置可划分为两类:1、流程类2、工具类 loader 是什么?Webpack Loader 最核心的只能是实现内容转换器 —— 将各式各样的资源转化为标准 JavaScript 内容格式。本质上是因为 Webpack 只认识符合 JavaScript 规范的文本(Webpack 5 之后增加了其它 parser):在构建(make)阶段,解析模块内容时会调用 acorn 将文本转换为 AST 对象,进而分析代码结构,分析模块依赖;这一套逻辑对图片、json、Vue SFC 等场景就不 work 了,就需要 Loader 介入将资源转化成 Webpack 可以理解的内容形态。=======1、入口处理2、依赖解析3、资源解析//递归调用 2、3,直到所有资源处理完毕4、资源合并打包 怎么使用 Webpack?关于 Webpack 的使用方法,基本都围绕“配置”展开,而这些配置可划分为两类:1、流程类2、工具类 loader 是什么?Webpack Loader 最核心的只能是实现内容转换器 —— 将各式各样的资源转化为标准 JavaScript 内容格式。本质上是因为 Webpack 只认识符合 JavaScript 规范的文本(Webpack 5 之后增加了其它 parser):在构建(make)阶段,解析模块内容时会调用 acorn 将文本转换为 AST 对象,进而分析代码结构,分析模块依赖;这一套逻辑对图片、json、Vue SFC 等场景就不 work 了,就需要 Loader 介入将资源转化成 Webpack 可以理解的内容形态。 5c4d5096c5ae7133aeecb6ad100a83d8014be882 理解插件Webpack 会在启动后按照注册的顺序逐次调用插件对象的 apply 函数,同时传入编译器对象 compiler ,插件开发者可以以此为起点触达到 webpack 内部定义的任意钩子,例如:注意观察核心语句 compiler.hooks.thisCompilation.tap,其中 thisCompilation 为 tapable 仓库提供的钩子对象;tap 为订阅函数,用于注册回调。","link":"/blog/2022/08/12/%E5%AD%97%E8%8A%82%E9%9D%92%E8%AE%AD%E8%90%A5%E7%AC%94%E8%AE%B0-8-12-1/"},{"title":"Javascript高级.8.16.2","text":"事件循环-微任务-宏任务如果在执行 JavaScript 代码的过程中,有异步操作呢?中间我们插入了一个 setTimeout 的函数调用;这个函数被放到入调用栈中,执行会立即结束,并不会阻塞后续代码的执行;但是事件循环中并非只维护着一个队列,事实上是有两个队列: 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM 监听、UI Rendering 等微任务队列(microtask queue):Promise 的 then 回调、 Mutation Observer API、queueMicrotask()等那么事件循环对于两个队列的优先级是怎么样的呢?1.main script 中的代码优先执行(编写的顶层 script 代码);2.在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行也就是宏任务执行之前,必须保证微任务队列是空的;如果不为空,那么就优先执行微任务队列中的任务(回调); 宏任务队列 定时器 ajax DOM 操作微任务队列 promise.then() 在执行任何的宏任务之前,都需要先保证微任务队列都已经被清空 下面这串代码 await 后面的代码会被当做在 promise.then()里面执行,因此会被放在微任务中执行 12345678910async function foo() { await new Promise((resolve, reject) => { setTimeout(() => { resolve("ceaser"); }, 5000); }); console.log("后面的代码");}foo();","link":"/blog/2022/08/16/Javascript%E9%AB%98%E7%BA%A7-8-16-2/"},{"title":"字节青训营笔记.8.16.1","text":"Vite知识体系 | 青训营笔记这是我参与「第四届青训营 」笔记创作活动的第 4 天 为什么要构建工具?模块化方案->提供模块加载方案,兼容不同规范标准语法转义->高级语法转移,如sass、typescript。资源加载,如图片,字体,worker产物质量->产物压缩、无用代码删除、语法降级开发效率->热更新 Vite是什么?Why Vite?概览定位:新一代前端构建工具两大组成部分:1.No-bundle开发服务,源文件无需打包2.生产环境基于Rollup的 Bundler核心特征1.高性能,dev启动速度和热更新速度非常快!2.简单易用,开发者体验好Vite 关心它的发布和安装足迹; 快速安装新应用程序是一项功能。Vite 打包了它的大部分依赖项,并尽可能地尝试使用现代轻量级替代方案。继续这个持续的目标,Vite 3 的发布规模比 v2 小了 30%。 当下的问题缓慢的启动->项目编译等待成本高缓慢的热更新->修改代码后不能实时更新开发体验问题日渐显露!bundle带来的性能开销JavaScript语言的性能瓶颈 基于Esbuild的编译性能优化Esbuild—基于 Golang开发的前端工具,具备如下能力:1.打包器Bundler⒉.编译器Transformer3.压缩器Minifier性能极高,在Vite 中被深度使用 Vite上手使用12345#提前安装pnpmnpm i -g pnpm #初始化命令pnpm create vite #安装依赖pnpm install #启动项目npm run dev 1234VITE v3.0.0 ready in 320 ms➜ Local: http://127.0.0.1:5173/➜ Network: use --host to expose Vite整体架构为什么要进行预打包1.避免node_modules过多的文件请求2.将CommonJS格式转换为ESM格式实现原理:1.服务启动前扫描代码中用到的依赖2.用Esbuild对依赖代码进行预打包3.改写import语句指定依赖为预构建产物路径Esbuild 作为默认压缩工具,替换传统的Terser、Uglify.js等压缩工具","link":"/blog/2022/08/16/%E5%AD%97%E8%8A%82%E9%9D%92%E8%AE%AD%E8%90%A5%E7%AC%94%E8%AE%B0-8-16-1/"},{"title":"字节青训营笔记.8.19.1","text":"小程序技术全解 | 青训营笔记这是我参与「第四届青训营 」笔记创作活动的第 5 天 发展历程小程序的探索最开始是由微信开启的,2017 年 1 月微信小程序正式进入人们的视野。随后支付宝小程序及其他厂商也相继发布自己的小程序产品。标志着各大厂竟相进入到小程序领域开始竞争。到了 2020 年全网的 app 数量相比 2019 年的 367 万下降了 4.9%,而微信小程序却上升了 33.3%,来到了 450 万+的全网数量。这个也标志着小程序的爆发式发展。 业务价值1、固定语法和统一版本管理,方便审核2、平台统一控制,方便管理3、基于特殊架构,流畅性强,优秀的用户体验因此微信小程序的渠道价值,业务探索价值,数字升级价值优秀。 技术解析开发门槛低接近原生的使用体验第三方开发应用最简单最方便的方式WebView +JSBridgeWebView 是一个基于 webkit 的引擎,可以解析 DOM 元素,展示 html 页面的控件,它和浏览器展示页面的原理是相同的,所以可以把它当做浏览器看待。JSBridge:JS 与 native 代码的桥梁。主要是给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能。 12345<view class=" container" ><view class="clock ">{{ timeText }}</view> <button tt:if="{{ running }}" class="button" bindtap="onReset">重置</button ><button tt:else class="button" bindtap="onStart">开始</button></view> 12345const DEFAULT_TIME = 25 * 60;function formatTime ( time ) {const minutes = Math.floor( time / 60 );const seconds = time % 60;const mText =`0${minutes} `.slice( -2 );const sText =`0${seconds} .slice( -2 );return `${mText} : ${sText}`} 相关扩展在目前的开发环境中,能够实现跨平台就是一个巨大的优势。渲染层:结合 template 生成实际要渲染的元素树逻辑层:虚拟 dom 树,js 实现运行时方案:虚拟 DOM;Template 组件。虚拟 DOM:是一层对真实 DOM 的抽象,以 JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上。","link":"/blog/2022/08/19/%E5%AD%97%E8%8A%82%E9%9D%92%E8%AE%AD%E8%90%A5%E7%AC%94%E8%AE%B0-8-19-1/"}],"tags":[{"name":"CET4","slug":"CET4","link":"/blog/tags/CET4/"},{"name":"复习","slug":"复习","link":"/blog/tags/%E5%A4%8D%E4%B9%A0/"},{"name":"Node.js","slug":"Node-js","link":"/blog/tags/Node-js/"},{"name":"Express","slug":"Express","link":"/blog/tags/Express/"},{"name":"前端","slug":"前端","link":"/blog/tags/%E5%89%8D%E7%AB%AF/"},{"name":"ES6","slug":"ES6","link":"/blog/tags/ES6/"},{"name":"数据库","slug":"数据库","link":"/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"},{"name":"Vue.js","slug":"Vue-js","link":"/blog/tags/Vue-js/"},{"name":"生活","slug":"生活","link":"/blog/tags/%E7%94%9F%E6%B4%BB/"},{"name":"TypeScript","slug":"TypeScript","link":"/blog/tags/TypeScript/"},{"name":"Vue","slug":"Vue","link":"/blog/tags/Vue/"},{"name":"项目架构配置","slug":"项目架构配置","link":"/blog/tags/%E9%A1%B9%E7%9B%AE%E6%9E%B6%E6%9E%84%E9%85%8D%E7%BD%AE/"}],"categories":[{"name":"英语","slug":"英语","link":"/blog/categories/%E8%8B%B1%E8%AF%AD/"},{"name":"前端","slug":"前端","link":"/blog/categories/%E5%89%8D%E7%AB%AF/"},{"name":"ES6","slug":"ES6","link":"/blog/categories/ES6/"},{"name":"Node.js","slug":"Node-js","link":"/blog/categories/Node-js/"},{"name":"Express","slug":"Express","link":"/blog/categories/Express/"},{"name":"数据库","slug":"数据库","link":"/blog/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"},{"name":"项目开发","slug":"项目开发","link":"/blog/categories/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91/"},{"name":"Vue.js","slug":"Vue-js","link":"/blog/categories/Vue-js/"},{"name":"生活","slug":"生活","link":"/blog/categories/%E7%94%9F%E6%B4%BB/"},{"name":"TypeScript","slug":"TypeScript","link":"/blog/categories/TypeScript/"}]}