- JavaScript-this
JavaScript-this 分享 最近又在this的指向中迷惑了,虽然之前学过一次,丝毫不影响再次掉坑,这次又捋了一遍,加深影响! 其实在javascript中this的指向主要有四种 默认绑定(全局对象 严格模式下为undefined) 隐式绑定(谁调用this指向谁) 显示绑定(call bind apply) new绑定 ! 而在箭头函数里面,这四种都不适合,因为ES6规定箭头函数是没有this的,所以它只能继承,继承外层作用域中的this,也就是要看箭头函数所定义使用的地方(宿主对象)。 网上找了好多this指向的资料,个人觉得这两个总结得很好,大家可以看一下,结合两篇文章,相信大家对this得指向问题会更加清晰。 js 中的this,默认绑定、隐式绑定、显式绑定、new绑定、箭头函数绑定详解_toforu.com-CSDN博客 ①this指向四种:默认,隐式,显示call apply bind,new构造函数②优先级③new构造函数有无return④new做了什么⑤严格模式指向_lazylYYY的博客-CSDN博客 下面是阮一峰老师讲得this原理,大家也可以看一下! JavaScript 的 this 原理 - 阮一峰的网络日志 (ruanyifeng.com) Node中的this和浏览器中的this的区别 大家注意,虽然node和浏览器都是js的运行环境,但node中this和浏览器中的this还是稍有不同的 浏览器中的全局对象是window,node的全局对象是global 浏览器最外层的的this是windows,而node中最外层的this并不是全局对象global,而是module.export [代码]//在node环境下 console.log(this==global) //false console.log(this==module.exports) //true this.a=1; console.log(module.exports.a) //1 b=2; //不用var声明的变量 默认作为全局对象的属性 [代码] module.export是es6实现模块化的新特性,大家有兴趣可以了解一下。 vue中的this 在vue中的this,其实在vue的设计转化下,this一般指向vue实例 在Vue所有的生命周期钩子方法(created,mounted等等)和普通方法(method里面定义),this指向调用它的vue实例 其他情况可以参考js的this指向 作用域和原型的区别 当访问某个变量是,就是要通过作用域链去查找了,而访问某个对象的属性时,这个时候就时要通过原型链去找了了。 这其实也是有关变量和属性的区别,变量是属于作用域的范畴,而属性其实应该是属于原型的范畴 当我们定义一个变量是,比如 var a=1;其实a不是this里面的属性,a只是一个变量。 变量和属性一个很大的区别就是,属性可以删除,而变量不可以 [代码]var a=1; this.b=2; delete a; //false delete this.b ;//true [代码]
2021-11-22 - mongodb知识点总结
mongodb知识总结 微信云开发使用的云数据库集成了简化版的mongodb,因此学习两者之一就可以很快掌握另一个 mongoDB 开篇 [代码]<!-- 1.什么是MongoDB? MongoDB和MySQL一样都是数据库, 都是存储数据的仓库, 不同的是MySQL是关系型数据库, 而MongoDB是非关系型数据库 2.什么是非关系型数据库? - 在'关系型数据库'中, 数据都是存储在表中的, 对存储的内容有严格的要求 因为在创建表的时候我们就已经规定了表中有多少个字段, 已经规定了每个字段将来要存储什么类型数据, 已经规定了每个字段将来是否可以为空,是否必须唯一等等 - 在'非关系型数据库'中, 没有表概念, 所以存储数据更加灵活 因为不需要创建表,所以也没有规定有哪些字段, 也没有规定每个字段数据类型, 也没有规定每个字段将来是否可以为空,是否必须唯一等等 - '关系型数据库'由于操作的都是结构化的数据, 所以我们需要使用结构化语言SQL来操作 - '非关系型数据库'由于数据没有严格的结构要求, 所以无需使用SQL来操作 3.什么是MongoDB? 存储文档(BSON)的非关系型数据库 --> <!-- 例如在MySQL中: |--------------------------------------------------------| | name(varchar(255) not null) | age(int unique) | |--------------------------------------------------------| 我们可以把 'zs', 33 保存到表中 但是我们不能将 33, 'zs' 保存到表中 但我们不能能将 null, 33 保存到表中 但是我们不能将 'zs', 33, '男' 保存到表中 但是我们不能再次将 'zs', 33 保存到表中 --> <!-- 例如在MongoDB中: 我们可以把 {name: 'zs', age: 33}; 保存到集合中 我们也可以把 {name: 33, age: 'zs'}; 保存到集合中 我们也可以把 {name: null, age: 33}; 保存到集合中 我们也可以把 {name: 'zs', age: 33, gender:'男'}; 保存到集合中 但是我们可以再次将 {name: 'zs', age: 33}; 保存到集合中 - '非关系型数据库'可以看做是'关系型数据库'的功能阉割版本, 通过减少用不到或很少用的功能,从而提升数据库的性能 --> <!-- 4.MongoDB是如何存储文档的? MySQL中所有的数据都是存储在表中的, 而MongoDB中所有的数据都是存储在集合中的 4.1MySQL |--行1 |--表1--|--行2 数据库--| |--行3 |--表2 |--... ... 4.2MongoDB |--文档1 |--集合1--|--文档2 数据库--| |--文档3 |--集合2 |--... ... --> <!-- 5.企业开发如何选择? - 关系型数据库和非关系型数据库之间并不是替代关系, 而是互补关系 所以在企业开发中大部分情况是结合在一起使用. - 对于数据模型比较简单、数据性能要求较高、数据灵活性较强的数据, 我们存储到非关系型数据库中 相反则存储到关系型数据库中 - 具体使用: 会在项目中实现 --> [代码] 快速上手 [代码]<!-- https://docs.mongodb.com/manual/ https://www.mongodb.org.cn/tutorial/ 1.连接MongoDB服务器 通过mongo连接MongoDB服务器 2.查看数据库 show dbs #和MySQL中的 show databases; 指令一样 3.创建数据库 use 数据库名称 #和MySQL中的 use 指令一样, 只不过MongoDB中的use数据库不存在会自动创建 4.查看数据库中有哪些集合 show collections #和MySQL中的 show tables; 指令一样 5.创建集合 db.createCollection('集合名称'); #和MySQL中的 create table xxx(); 指令一样 6.插入数据 db.集合名称.insert(文档对象); #和MySQL中的 insert into xxx values () 指令一样 7.查询数据 db.集合名称.find(); #和MySQL中的 select * from xxx; 指令一样 8.删除集合 db.集合名称.drop() #和MySQL中的 drop table xxx; 指令一样 9.删除数据库 db.dropDatabase() #在哪个数据库中就会删除哪个数据库 #和MySQL中的 drop database xxx; 指令一样 10.和MySQL的不同 - 没有MySQL中表的概念, 取而代之的是集合 - 创建集合时不用指定集合中有哪些字段 - 只要是一个合法的文档对象都可以往里面存储 - ... ... --> [代码] 主键 [代码]<!-- 1.主键 - MongoDB的主键和MySQL一样, 也是用于保证每一条数据唯一性的 - 和MySQL不同的是, MongoDB中的主键无需明确指定 + 每一个文档被添加到集合之后, MongoDB都会自动添加主键 + MongoDB中文档主键的名称叫做 _id - 默认情况下文档主键是一个ObjectId类型的数据 + ObjectId类型是一个12个字节字符串(5e8c5ae9-c9d35e-759b-d6847d) + 4字节是存储这条数据的时间戳 + 3字节的存储这条数据的那台电脑的标识符 + 2字节的存储这条数据的MongoDB进程id + 3字节是计数器 2.为什么要使用ObjectId类型数据作为主键? 因为MongoDB是支持'横向扩展'的数据库 - 横向扩展是指'增加数据库服务器的台数' - 纵向扩展是指'增加数据库库服务器的配置' - 过去一个数据库只能安装在一台电脑上, 但是每台电脑的性能是有峰值的 一旦达到峰值就会导致服务器卡顿、宕机、重启等问题. 所以过去为了防止如上问题的出现,我们只能不断的'纵向扩展' 也就是不断的提升服务器的配置, 让服务器能处理更多的请求 但是纵向扩展也是有峰值的, 一台电脑的配置不可能无限提升 所以为了解决这个问题就有了分布式数据库 - 分布式数据库是指可以在多台电脑上安装数据库, 然后把多台电脑组合成一个完整的数据库, 在分布式数据库中,我们可以通过不断同步的方式, 让多台电脑都保存相同的内容 当用户请求数据时, 我们可以把请求派发给不同的数据库服务器处理 当某一台服务器宕机后, 我们还可以继续使用其它服务器处理请求 从而有效的解决了单台电脑性能峰值和单台电脑宕机后服务器不能使用的问题 2.2为什么要使用ObjectId类型数据作为主键? 正是因为MongoDB是一个分布式数据库, 正是因为分布式数据库可以把请求派发给不同的服务器 所以第一次插入数据时, 我们可能派发给了A服务器, 插入到了A服务器的数据库中 但是第二次插入数据时, 我们又可能派发给了B服务器, 插入到了B服务器的数据库中 但是B服务器此时并不知道A服务器当前的主键值是多少, 如果通过MySQL中简单的递增来保证数据的唯一性 那么将来在多台服务器同步数据的时候就会出现重复的情况, 所以MongoDB的主键并没有使用简单的递增 而是使用了ObjectId类型数据作为主键 3.是否支持其它类型数据作为主键? 3.1在MongoDB中支持除了'数组类型'以外的其它类型数据作为主键 3.2在MongoDB中甚至还支持将一个文档作为另一个文档的主键(复合主键) db.person.insert({name: 'lnj', age: 33}); db.person.insert({_id: 1, name: 'lnj', age: 33}); #db.person.insert({_id: 1, name: 'lnj', age: 33}); #报错 db.person.insert({_id: '1', name: 'lnj', age: 33}); db.person.insert({_id: {name:'it66', gender: '男'}, name: 'lnj', age: 33}); #db.person.insert({_id: {name:'it66', gender: '男'}, name: 'lnj', age: 33}); #报错 db.person.insert({_id: {gender: '男', name:'it66'}, name: 'lnj', age: 33}); --> [代码] 创建文档 [代码]<!-- 1.主键 - MongoDB的主键和MySQL一样, 也是用于保证每一条数据唯一性的 - 和MySQL不同的是, MongoDB中的主键无需明确指定 + 每一个文档被添加到集合之后, MongoDB都会自动添加主键 + MongoDB中文档主键的名称叫做 _id - 默认情况下文档主键是一个ObjectId类型的数据 + ObjectId类型是一个12个字节字符串(5e8c5ae9-c9d35e-759b-d6847d) + 4字节是存储这条数据的时间戳 + 3字节的存储这条数据的那台电脑的标识符 + 2字节的存储这条数据的MongoDB进程id + 3字节是计数器 2.为什么要使用ObjectId类型数据作为主键? 因为MongoDB是支持'横向扩展'的数据库 - 横向扩展是指'增加数据库服务器的台数' - 纵向扩展是指'增加数据库库服务器的配置' - 过去一个数据库只能安装在一台电脑上, 但是每台电脑的性能是有峰值的 一旦达到峰值就会导致服务器卡顿、宕机、重启等问题. 所以过去为了防止如上问题的出现,我们只能不断的'纵向扩展' 也就是不断的提升服务器的配置, 让服务器能处理更多的请求 但是纵向扩展也是有峰值的, 一台电脑的配置不可能无限提升 所以为了解决这个问题就有了分布式数据库 - 分布式数据库是指可以在多台电脑上安装数据库, 然后把多台电脑组合成一个完整的数据库, 在分布式数据库中,我们可以通过不断同步的方式, 让多台电脑都保存相同的内容 当用户请求数据时, 我们可以把请求派发给不同的数据库服务器处理 当某一台服务器宕机后, 我们还可以继续使用其它服务器处理请求 从而有效的解决了单台电脑性能峰值和单台电脑宕机后服务器不能使用的问题 2.2为什么要使用ObjectId类型数据作为主键? 正是因为MongoDB是一个分布式数据库, 正是因为分布式数据库可以把请求派发给不同的服务器 所以第一次插入数据时, 我们可能派发给了A服务器, 插入到了A服务器的数据库中 但是第二次插入数据时, 我们又可能派发给了B服务器, 插入到了B服务器的数据库中 但是B服务器此时并不知道A服务器当前的主键值是多少, 如果通过MySQL中简单的递增来保证数据的唯一性 那么将来在多台服务器同步数据的时候就会出现重复的情况, 所以MongoDB的主键并没有使用简单的递增 而是使用了ObjectId类型数据作为主键 3.是否支持其它类型数据作为主键? 3.1在MongoDB中支持除了'数组类型'以外的其它类型数据作为主键 3.2在MongoDB中甚至还支持将一个文档作为另一个文档的主键(复合主键) db.person.insert({name: 'lnj', age: 33}); db.person.insert({_id: 1, name: 'lnj', age: 33}); #db.person.insert({_id: 1, name: 'lnj', age: 33}); #报错 db.person.insert({_id: '1', name: 'lnj', age: 33}); db.person.insert({_id: {name:'it66', gender: '男'}, name: 'lnj', age: 33}); #db.person.insert({_id: {name:'it66', gender: '男'}, name: 'lnj', age: 33}); #报错 db.person.insert({_id: {gender: '男', name:'it66'}, name: 'lnj', age: 33}); --> <!-- 1.写入一个文档 db.<collection>.insertOne( <document>, { writeConcern: <document> } ); document: 需要写入的文档 writeConcern: 写入安全级别 2.安全级别 用于判断数据是否写入成功, 安全级别越高, 丢失数据风险越小, 但是性能消耗(操作延迟)也就越大 默认情况下MongoDB会开启默认的安全些级别,先不用关心 3.注意点 在使用insertXXX写入文档时, 如果调用insertOne的集合不存在会自动创建 db.person.insertOne({name:'zs', age:18}) db.person.find() db.student.insertOne({name:'zs', age:18}) #集合不存在会自动创建 db.student.find() 4.其它方式 db.<collection>.save( <document>, { writeConcern: <document> } ); db.person.save({name:'ls', age:19}) db.person.find() db.teacher.save({name:'ls', age:19}) #集合不存在会自动创建 db.teacher.find() 5.insertOne和save不同 主键冲突时insertOne会报错,而save会直接用新值覆盖久值 db.person.insertOne({_id:1, name:'ww', age:22}) db.person.find() db.person.insertOne({_id:1, name:'ww', age:22}) #报错 db.person.save({_id:1, name:'it666', age:66}) #用新数据替换久数据 db.person.find() --> <!-- 1.写入多个文档 db.<collection>.insertMany( [<document>, ...], { writeConcern: <document>, ordered: <boolean> } ); ordered: 是否按顺序写入 ordered默认取值是true, 也就是会严格按照顺序写入 如果ordered是false, 则不会按照顺序写入, 但写入效率更高(系统会自动优化) db.person.insertMany( [{name:'zs', age:18},{name:'ls', age:19},{name:'ww', age:20}], {} ) db.person.find() 2.注意点: 如果ordered是true, 前面的文档出错, 后面的所有文档都不会被写入 如果ordered是false, 前面的文档出错, 后面的所有文档也会被写入 db.person.insertMany( [{_id:1, name:'zs', age:18},{_id:1, name:'ls', age:19},{_id:2, name:'ww', age:20}], { ordered: true } ) db.person.find() db.person.remove({}) db.person.insertMany( [{_id:1, name:'zs', age:18},{_id:1, name:'ls', age:19},{_id:2, name:'ww', age:20}], { ordered: false } ) db.person.find() --> <!-- 1.写入一个或多个文档 db.<collection>.insert( <document> or ,[<document>, ...] { writeConcern: <document>, ordered: <boolean> } ); insertOne和insertMany结合体 2.注意点: 和insertOne/insertMany一样, 集合不存在会自动创建 和insertOne/insertMany一样, 主键冲突会报错 和insertMany一样, 默认都是按顺序插入, 前面的文档出错, 后续所有文档不会被插入 --> [代码] 读取文档 查询文档 [代码]<!-- 1.查询文档 db.<collection>.find( <query>, <projection> ) query: 查询条件, 相当于MySQL中的where projection: 投影条件, 规定了结果集中显示那些字段, 相当于MySQL中的 select 字段1, 字段2, .. from 表名; 2.查询所有文档 db.<collection>.find(); 不传入条件, 默认就是查询所有 3.查询满足条件文档 db.person.insert([{name:'zs', age:17},{name:'ls', age:18},{name:'ww', age:19}]) 3.1单个字段条件 db.person.find() // 默认会返回指定集合中所有的数据和所以的字段 db.person.find({name:'zs'}) // 我们可以通过第一个参数指定查询条件, find方法会把所有满足条件的数据返回给我们 3.2多个字段条件 db.person.find({name:'zs', age:17}) // 默认是And关系, 也就是默认要求同时满足所有指定的条件, 才会返回对应的数据 db.person.find({age:17, name:'zs'}) // 注意点: 没有顺序要求, 只要是同时满足多个条件即可 3.3文档中又是文档情况 db.person.insert( [{name:'zs', age:17, book:{name:'HTML', price:66}}, {name:'ls', age:18, book:{name:'JavaScript', price:88}}, {name:'ww', age:19, book:{name:'Vue', price:199}}] ) db.person.find({'book.name':'JavaScript'}) // 如果某一个文档的某一个字段的取值又是一个文档, 那么在判断的时候我们可以通过'字段.文档属性名称'的方式来判断 4.查询指定字段 0表示不显示, 1表示显示 除主键以外, 其它字段不能同时出现0和1(要么不写,写了就必须全是1或者全是0) db.person.find({},{_id:0}) // 如果不想查询某一个字段, 那么就可以指定这个字段的投影取值为0 db.person.find({},{_id:0, name:1, age:1, book:1}) // 如果想查询某一个字段, 那么就可以指定这个字段的投影取值为1 // 默认情况下如果不指定, 那么所有字段的投影取值都是1 db.person.find({},{_id:0, name:1, age:1, book:0}) // 除了_id字段以外, 其它的字段不能同时出现0和1 db.person.find({},{_id:0, book:0}) --> [代码] 比较运算符 [代码]<!-- 1.比较操作符 和MySQL一样, MongodDB中也支持很多比较操作符 $eq: 等于 / $ne: 不等于 $gt: 大于 / $gte: 大于等于 $lt: 小于 / $lte: 小于等于 2.使用格式 db.<collection>.find( {<field>: {$<operator>: <value>}}, <projection> ) 3.示例 db.person.insert([{name:'zs', age:17, gender:'男'},{name:'ls', age:18},{name:'ww', age:19}]) 查询名称叫做zs的人 查询所有成年人 查询所有未成年人 查询所有不是18岁的人 db.person.find({name:'zs'}) //默认情况下就是按照相等来判断 db.person.find({name:{$eq:'zs'}}) //这里就是明确的告诉MongoDB需要按照相等来查询 db.person.find({age:{$gte: 18}}) db.person.find({age:{$lt: 18}}) db.person.find({age:{$ne: 18}}) 注意点: 没有指定字段也算作不等于 db.person.find({gender:{$ne: '女'}}) // 注意点: 在做不等于判断的时候, 没有需要判断的字段, 也算作是不等于 --> <!-- 1.其它比较操作符 $in: 匹配和任意指定值相等的文档 $nin:匹配和任意指定值都不相等的文档 2.使用格式 db.<collection>.find( {<field>: {$<operator>: [<value1>, <value2>, ...]}}, <projection> ) 3.实例 查询名称叫做zs或者ls的人 查询名称不叫zs或者ls的人 查询性别不是男或女的人 db.person.find({name:{$in:['zs', 'ls']}}) // 匹配和任意指定值相等的文档 db.person.find({name:{$nin:['zs', 'ls']}}) // 匹配和任意指定值都不相等的文档 db.person.find({gender:{$nin:['男', '女']}}) // 注意点: 和$ne一样, 如果没有需要判断的字段, 也算作满足条件 注意点: 没有指定字段也算作不包含 --> [代码] 逻辑操作符 [代码]<!-- 1.逻辑操作符 $not: 匹配条件不成立的文档 {<field>: {$not: {<expression>}}} $and: 匹配条件全部成立的文档 {<field>: {$and: [{<expression1>}, {<expression2>}, ...}]} $or : 匹配至少一个条件成立的文档 {<field>: {$or: [{<expression1>}, {<expression2>}, ...}]} $nor: 匹配多个条件全部不成立的文档 {<field>: {$nor: [{<expression1>}, {<expression2>}, ...}]} 2.示例: //2.1$not //查询所有年龄不等于18岁的人 db.person.find({age:{$ne:18}}) db.person.find({age:{$not:{$eq:18}}}) //查询不是男人的人 db.person.find({gender:{$eq:'男'}}) // 注意点: $not运算符和$ne/$nin一样, 如果需要查询的字段不存在, 也会算作条件成立 db.person.find({gender:{$not:{$eq:'男'}}}) //2.2$and //查询所有名称叫做zs的未成年人 db.person.find({$and:[{name:{$eq:'zs'}},{age:{$lt:18}}]}) db.person.find({$and:[{name:'zs'},{age:{$lt:18}}]}) db.person.find({name:'zs', age:{$lt:18}}) //2.3$or //查询所有名称叫做zs或者ls的人 db.person.find({name:{$in:['zs','ls']}}) db.person.find({$or:[{name:{$eq:'zs'}},{name:{$eq:'ls'}}]}) db.person.find({$or:[{name:'zs'},{name:'ls'}]}) //2.4$nor //查询所有名称不叫zs或者ls的人 db.person.find({name:{$nin:['zs','ls']}}) db.person.find({$nor:[{name:'zs'},{name:'ls'}]}) //查询所有名称不叫zs或者性别不是男的人 // 注意点: $nor运算符和$ne/$nin/$not一样, 如果需要查询的字段不存在, 也会算作条件成立 db.person.find({$nor:[{gender:'男'}]}) --> [代码] 字段操作符 [代码]<!-- 1.字段操作符 $exists: 查询包含某个字段的文档 {<field>: {$exists: <boolean>}} $type: 查询指定字段包含指定类型的文档 {<field>: {$type: <BSON> or [<BSON1>, <BSON2>]}} 2.查询包含字段gender的人 db.person.insert([ {name:'zs', age:17, gender:'男'}, {name:'ls', age:18}, {name:'ww', age:19}, {name:'it666', age:20, gender:'女'} ]) // 需求: 要求查询出所有拥有gender属性的文档 db.person.find({gender:{$exists: true}}) 3.应用场景: // 应用场景: 配合$ne/$nin/$nor/$not来清理数据 db.person.find({gender:{$ne:'男'}}) db.person.find({gender:{$ne:'男', $exists:true}}) 4.查询所有年龄是字符串类型的文档 db.person.insert([ {name:'itzb', age:'666'}, {name:'lnj', age:'888'}, ]) // 需求: 要求查询出所有age属性的取值是字符串类型的文档 db.person.find({age:{$type:'string'}}) --> [代码] 数组操作符 [代码]<!-- 1.数组操作符 $all : 匹配数组中包含所有指定查询值的文档 {<field>: {$all: [<value1>, <value2>, ...]}} $elemMatch: 匹配数组中至少有一个能完全匹配所有的查询条件的文档 {<field>: {$elemMatch: {<query1>, <query2>, ...}}} 2.示例 查询tags中同时拥有html和js的文档 db.person.insert([ {name: 'zs', tags:['html', 'js', 'vue']}, {name: 'ls', tags:['html', 'react', 'vue']}, {name: 'ww', tags:['html', 'node', 'js']}, ]) db.person.find({tags:{$all:['html', 'js']}}) 查询所有名称叫做zs,年龄是18岁的文档 db.school.insert([ {class: 'one', students: [ {name:'zs', age: 18}, {name:'ls', age: 19}, {name:'ww', age: 20}, ]}, {class: 'two', students: [ {name:'zs', age: 20}, {name:'ls', age: 19}, {name:'ww', age: 18}, ]}, ]) db.school.find({'studnets.name':'ww', 'studnets.age':18}) db.school.find({studnets:{$elemMatch:{name:'ww',age:18}}}) --> [代码] 运算操作符:正则表达式 [代码]<!-- 1.运算操作符 { <field>: { $regex: /pattern/, $options: '<options>' } } { <field>: { $regex: /pattern/<options> } } 查询满足正则的文档 2.示例 db.person.insert([ {name:'zs', age:18}, {name:'ls', age:19}, {name:'ww', age:17}, {name:'Zsf', age:18}, {name:'Lnj', age:19}, {name:'Wz', age:17} ]) // 需求: 要求查询出所有姓z的人(文档) db.person.find({name:{$regex:/^z/, $options: 'i'}}) // 需求: 要求查询出所有姓是z或者l的人(文档) db.person.find({name:{$in:[/^z/i, /^l/i]}}) --> [代码] 文档游标 [代码]<!-- 1.文档游标 1.1为什么学习前端都要学习MongoDB? 因为MongoDB原生就支持JavaScript, 也就是我们可以直接在MongoDB中混入JS代码 1.2什么是文档游标 我们执行find方法后, find方法其实是有返回值的, find方法会返回一个文档游标(相当于C语言指针) 1.3文档游标常用方法 hasNext(): 是否还有下一个文档 next(): 取出下一个文档 forEach(): 依次取出所有文档 1.4文档游标注意点 默认情况下通过文档游标遍历完所有文档后, 系统会在10分钟后自动关闭当前游标 如果不想自动关闭, 我们可以通过noCursorTimeout函数来保持游标一直有效 let cursor = db.person.find().noCursorTimeout() 如果想手动关闭游标, 我们也可以通过close函数来手动关闭游标 cursor.close() 2.示例 // 需求: 往person集合中插入100个文档 var arr =[]; for(var i = 0; i < 100; i++){ arr.push({name:'it'+i, age:18+i}); } db.person.insertMany(arr) var cursor = db.person.find().noCursorTimeout() //cursor[0] //cursor[1] while(cursor.hasNext()){ printjson(cursor.next()) } cursor.forEach(printjson) cursor.close() --> [代码] 分页方法 [代码]<!-- 1.分页方法 cursor.limit(<number>): 取多少个文档 cursor.skip(<offset>) : 跳过多少个文档 2.示例 //var cursor = db.person.find() // 需求: 要求取出前5个文档 //cursor.limit(5) // 需求: 要求跳过前面的5个文档, 取出剩余的所有 //cursor.skip(5) // 注意点: 我们可以直接在find方法后面调用limit方法或者skip方法 //db.person.find().limit(5) //db.person.find().skip(5) 3.分页函数注意点 // 注意点: MongoDB是支持链式调用的 // 需求: 跳过前面5个文档, 取出后面的5个文档 //db.person.find().skip(5).limit(5) // 注意点:在链式调用的时候, 无论skip写在前面还是后面, 都会在limit之前执行 db.person.find().limit(5).skip(10) --> [代码] 排序函数 [代码]<!-- 1.排序函数 cursor.sort({field: ordering, ...}): 按照指定规则排序 ordering为1表示升序排序 ordering为-1表示降序排序 2.示例 // 注意点: 默认情况下find方法只会返回100个文档 db.person.find() db.person.insert({name:'itzb', age:15}) db.person.find().limit(101) db.person.find().sort({age:1}) db.person.find().sort({age:-1}) 3.注意点 3.1find方法默认只会取出100个文档 3.2sort函数永远在分页函数之前执行 db.person.find().skip(5).limit(5) db.person.find().skip(5).limit(5).sort({age:-1}) --> [代码] 统计函数 [代码]<!-- 1.统计函数 cursor.count(<applySkipLimit>): 统计集合中文档的个数 applySkipLimit默认为false, 表示忽略skip和limit 2.示例 db.person.find().count() // 注意点: count函数可以接收一个applySkipLimit参数, 通过这个参数可以告诉MongoDB在统计的时候是否需要忽略Skip和Limit // 默认情况下applySkipLimit的取值是false, 表示忽略Skip和Limit db.person.find().skip(6).count() db.person.find().limit(5).count() db.person.find().skip(6).count({applySkipLimit:true}) db.person.find().limit(5).count({applySkipLimit:true}) 2.统计函数注意点 在find函数不提供筛选条件时, count函数会从集合的元数据中取得结果 在单台电脑上是这个结果是准确的, 但是如果数据库为分布式结构(多台电脑)时, 如果不给find函数提供筛选条件, 那么count函数返回的结果并不一定准确 --> [代码] 更新文档 save方法 [代码]<!-- 1.更新文档 MongoDB中有三个常用的更新方法: save()/update()/findAndmodify() 2.save方法 save用于往集合里添加一个新文档或者覆盖文档 当没有指定文档_id的时候就是新增 当指定了集合中已经存在的_id的时候就是覆盖 3.示例 db.person.insert([ {name:'zs', age:18}, {name:'ls', age:19}, {name:'ww', age:20}, {name:'zs', age:21}, ]) --> [代码] update方法 [代码]<!-- 1.update方法 db.collection.update(<filter>, <update>, <options>) <filter>: 筛选条件 <update>: 新的内容 <options>: 额外配置 2.通过update覆盖满足条件数据 默认情况下如果<update>没有使用更新操作符, 那么就会使用指定的内容覆盖符合条件的内容 3.示例: db.person.update({name:'lnj'}, {name:'zs'}) 4.注意点: // 注意点: update方法默认情况下就是覆盖 // 如果不想覆盖, 而是想单纯的更新, 那么就必须在第二个参数中使用'更新操作符' db.person.update({name:'ww'},{score: 99.9},{}) // 注意点: update方法默认只会更新满足条件的第一个文档 // 如果想更新所有满足条件的文档, 那么就必须指定第三个参数 db.person.update({name:'zs'}, {name:'zs', age:55}, {}) // 注意点: 如果在使用update方法的时候, 在第二个参数中指定了_id, 那么就必须保证指定的_id和被更新的文档的_id的取值一致 // 否则就无法更新, 否则就会报错 // 开发技巧: 在企业开发中如果需要使用update方法, 那么就不要指定_id db.person.update({name:'zs'}, {_id:1, name:'zs', age:55}, {}) db.person.update({name:'zs'}, {_id:ObjectId("5e9007350718cb6e37ab4515"), name:'zs', age:88}, {}) // 注意点: 如果想更新所有满足条件的文档, 我们可以指定第三个参数的取值multi:true // 注意点: 如果指定了multi:true, 那么就必须在第二个参数中使用'更新操作符' db.person.update({name:'zs'}, {name:'zs', age:55}, {multi:true}) --> [代码] $set更新操作符 [代码]<!-- 1.更新操作符 默认情况下update会使用新文档覆盖旧文档 如果不想覆盖而是仅仅想更新其中的某些字段 那么我们就需要使用update的更新操作符 2.$set更新操作符 $set: 更新或者新增字段, 字段存在就是更新, 字段不存在就是新增 格式: {$set:<value1>, ...} 3.示例: db.person.update({name:'zs'}, {$set:{name:'itzb'}}) db.person.update({name:'itzb'}, {$set:{age:'888'}}) 4.更新内嵌文档和数组 db.person.insert([ {name:'zs', age:18}, {name:'ls', age:19}, {name:'ww', age:20}, {name:'zs', age:21}, ]) db.person.update({name:'ww'}, {age:55}) // 更新普通字段 db.person.update({name:'ls'}, {$set:{age:55}}) db.person.update({name:'zs'}, {$set:{age:88}}, {multi:true}) db.person.insert( { name:'ww', age:18, book:{name:'跟着江哥学编程', price:2888}, tags:['html', 'JavaScript']} ) // 更新文档字段 db.person.update({name:'ww'}, {$set:{'book.name': 'it666.com'}}) // 更新数组字段 db.person.update({name:'ww'}, {$set: {'tags.0': 'vue'}}) 5.注意点: // 注意点: 如果操作的字段存在, 那么就是更新, 如果操作的字段不存在, 那么就是新增 db.person.update({name:'ls'}, {$set:{score: 59.5}}) // 注意点: 如果操作的是数组字段, 如果操作索引不存在, 那么也会自动新增 // 如果被操作的索引前面没有数据, 那么会自动用null来填充 db.person.update({name:'ww'}, {$set: {'tags.2': 'react'}}) db.person.update({name:'ww'}, {$set: {'tags.5': 'node'}}) --> [代码] $unset更新操作符 [代码]<!-- 1.$unset更新操作符 $unset: 删除字段 格式 :{$unset:{<field>:'', ...}} 2.示例: // 删除普通字段 db.person.update({name:'ls'}, {$unset:{score:''}}) // 注意点: 如果使用$unset删除某一个字段, 那么后面赋值为任何的内容都不重要 db.person.update({name:'ls'}, {$unset:{age:'www.it666.com'}}) // 删除文档字段中的字段 db.person.update({name:'ww'}, {$unset:{'book.price': ''}}) // 删除数组字段中的元素 // 注意点: 如果删除的是数组字段中的元素, 那么并不会修改数组的长度, 而是用null来填充删除的内容 db.person.update({name:'ww'}, {$unset:{'tags.1': ''}}) 3.注意点: 3.1删除数组元素并不会影响数组的长度, 而是设置为Null 3.2如果删除的字段不存在, 不会做任何操作 --> [代码] $rename重命名操作 [代码]<!-- 1.$rename更新操作符 $rename: 重命名字段 格式 :{$rename:{<field>:<newName>, ...}} 2.示例 db.person.update({name:'zs'}, {$rename:{name:'MyName'}}) // 注意点: 如果修改的是文档字段中的字段, 那么取值必须写上层级关系 db.person.update({name:'ww'}, {$rename:{'book.name':'book.BookName'}}) // 注意点: 如果要操作的字段不存在, 那么不会做任何的操作 db.person.update({name:'ls'}, {$rename:{age:'MyAge'}}) // 注意点: 如果重命名之后的名称已经存在了, 那么已经存在的字段就会被删除 // 底层的本质: 先调用了$unset删除了原有的book字段, 然后再调用$set修改字段的名称 db.person.update({name:'ww'}, {$rename:{name:'book'}}) // 注意点: 不能通过$rename更新操作符来操作数组 db.person.insert( { name:'it666', age:666, book:{name:'知播渔', price:999}, tags:[{name:'html', price:'123'}, {name:'js', price:456}] } ) db.person.update({name:'it666'}, {$rename:{'tags.0.name':'tags.0.TagName'}}) 4.乾坤大挪移 // 可以将外层的字段转移到内层的文档中 db.person.update({name:'it666'}, {$rename:{age:'book.age'}}) db.person.find() // 可以将内存文档中的字段, 转移到外层文档中 db.person.update({name:'it666'}, {$rename:{'book.age':'age'}}) --> [代码] $inc$mul更新操作符 [代码]<!-- 1.$inc和$mul更新操作符 $inc:更新字段值(增加或者减少字段保存的值) 格式: {$inc:{<field>: <number>}} $mul:更新字段值(乘以或者除以字段保存的值) 格式: {$mul:{<field>: <number>}} 2.示例 db.person.update({name:'lnj'}, {$inc:{age:2}}) db.person.update({name:'lnj'}, {$inc:{age:-5}}) db.person.update({name:'lnj'}, {$mul:{age:0.5}}) db.person.update({name:'lnj'}, {$mul:{age:2}}) 3.注意点: 3.1只能操作数字类型字段 3.2如果操作的字段不存在, 会自动新增这个字段 不同的是$inc会把操作的值赋值给新增的字段, 而$mul会自动赋值为0 db.person.update({name:'lnj'}, {$inc:{weight:2}}) db.person.update({name:'lnj'}, {$mul:{height:2}}) --> [代码] $max$min更新操作符 [代码]<!-- 1.$min和$max更新操作符 $min:比较保留更小字段值 格式: {$min:{<field>: <value>}} $max:比较保留更大字段值 格式: {$max:{<field>: <value>}} 2.示例 db.person.insert({name:'lnj', age:33}) db.person.update({name:'lnj'}, {$min:{age:50}}) db.person.update({name:'lnj'}, {$min:{age:18}}) db.person.update({name:'lnj'}, {$max:{age:5}}) db.person.update({name:'lnj'}, {$max:{age:55}}) 3.注意点: // 注意点: 如果操作的字段不存在, 那么会自动增加, 并且会将操作的值赋值给新增的字段 db.person.update({name:'lnj'}, {$min:{weight:120}}) db.person.update({name:'lnj'}, {$max:{height:175}}) // 注意点: 和$inc/$mul不同, $min/$max不仅仅能操作数值类型的字段, 只要是可以比较的字段都可以操作 db.person.insert({name:'def', age:666}) db.person.update({name:'def'}, {$min:{name:'efg'}}) db.person.update({name:'def'}, {$min:{name:'cde'}}) // 注意点: 不是相同的数据类型也可以进行比较 db.person.update({name:'lnj'}, {$min:{age:null}}) MongoDB对BSON的数据类型有一个潜在的排序规则 Null Numbers(ints, longs, doubles, decimals) Symbol, String Object Array BinData ObjectId Boolean Date Timestamp Regular Expression --> [代码] $addToSet数组更新操作符 [代码]<!-- 1.$addToSet数组更新操作符 $addToSet: 向数组字段中添加元素 格式 : {$addToSet: {<field>:<values>, ...}} 2.示例 db.person.insert([ {name:'zs', books:[{name:'html', price:66}, {name:'js', price:88}], tags:['html', 'js']}, {name:'ls', books:[{name:'vue', price:99}, {name:'node', price:199}], tags:['vue', 'node']} ]) db.person.update({name:'zs'}, {$addToSet:{tags:'react'}}) 3.注意点 // 注意点:如果操作的元素不存在, 那么会自动新增, 并且将操作的值赋值给新增的数组字段 db.person.update({name:'zs'}, {$addToSet:{other:'123'}}) // 注意点: $addToSet会自动去重, 如果添加的元素已经存在了, 那么就不会添加了 db.person.update({name:'zs'}, {$addToSet:{other:'123'}}) // 注意点: 如果往数组字段中添加的是文档类型, 那么必须一模一样才会去重 db.person.update({name:'zs'}, {$addToSet:{books:{name:'html', price:66}}}) db.person.update({name:'zs'}, {$addToSet:{books:{price:66, name:'html'}}}) // 注意点: 如果往数组字段中添加的是数组, 那么也必须一模一样才会去重 db.person.update({name:'ls'}, {$addToSet:{tags:['1', '2']}}) db.person.update({name:'ls'}, {$addToSet:{tags:['1', '2']}}) db.person.update({name:'ls'}, {$addToSet:{tags:['2', '1']}}) // 注意点: 如果往往数组字段中添加的是数组, 那么默认情况下会将整个数组作为一个元素添加进去 // 如果不想诶一个整体添加进去,那么必须使用$each来添加 db.person.update({name:'ls'}, {$addToSet:{tags:{$each: ['1', '2', '3']}}}) --> [代码] $push更新操作符 [代码]<!-- 1.$push数组更新操作符 $push: 向数组字段中添加元素(不去重) 格式 : {$push: {<field>:<value1>, ...}} --> <!-- 1.$pop数组更新操作符 $pop: 从数组字段中删除元素 格式: {$pop: {<field>:<1|-1>, ...}} 2.示例 db.person.update({name:'zs'}, {$pop:{tags:1}}) #删除最后一个 db.person.update({name:'zs'}, {$pop:{tags:-1}})#删除第一个 3.注意点 数组中的元素都被删除以后, 仍然会保留空的数组 --> [代码] $pull更新操作符 [代码]<!-- 1.$pull数组更新操作符 $pull: 从数组字段中删除特定元素 格式: {$pull: {<field>:<value|condition>, ...}} 2.示例 db.person.insert([ {name:'zs', books:[{name:'html', price:66}, {name:'js', price:88}], tags:['html', 'js', ['1', '2']]}, {name:'ls', books:[{name:'vue', price:99}, {name:'node', price:199}], tags:['a', 'b', 'ab', 'c', 'ac']} ]) 删除特定元素 根据条件删除元素 db.person.update({name:'zs'}, {$pull:{tags:'js'}}) db.person.update({name:'ls'}, {$pull:{tags:/^a/}}) 3.注意点 // 注意点: 如果要删除的元素是一个数组, 那么必须一模一样才能删除 db.person.update({name:'zs'}, {$pull:{tags:['2', '1']}}) db.person.update({name:'zs'}, {$pull:{tags:['1', '2']}}) // 注意点: 如果要删除的元素是一个文档, 那么不用一模一样也可以删除 db.person.update({name:'zs'}, {$pull:{books:{price:66, name:'html'}}}) db.person.update({name:'zs'}, {$pull:{books:{name:'js'}}}) --> [代码] $pullAll更新操作符 [代码]<!-- 1.$pullAll数组更新操作符 $pullAll: 从数组字段中批量删除特定元素 格式: {$pullAll: {<field>: [<value1>, <value2>, ...], ...}} 2.示例 db.person.insert([ {name:'zs', books:[{name:'html', price:66}, {name:'js', price:88}], tags:['html', 'js', ['1', '2']]}, {name:'ls', books:[{name:'vue', price:99}, {name:'node', price:199}], tags:['a', 'b', 'ab', 'c', 'ac']} ]) db.person.update({name:'zs'}, {$pullAll:{tags:['html', 'js']}}) 3.注意点 // 注意点: 和$pull一样, 如果删除的是数字字段中的数组元素, 那么必须一模一样才能删除 db.person.update({name:'zs'}, {$pullAll:{tags:[['2','1']]}}) db.person.update({name:'zs'}, {$pullAll:{tags:[['1','2']]}}) // 注意点: 和$pull不一样, 如果删除的是数组字段中的文档元素, 那么也必须一模一样才能删除 db.person.update({name:'zs'}, {$pullAll:{books:[{price:66,name:'html'}]}}) db.person.update({name:'zs'}, {$pullAll:{books:[{name:'html',price:66}]}}) --> [代码] $和$[]更新操作符 [代码]<!-- 1.$和$[]数组更新操作符 $ : 更新数组中满足条件的特定元素 格式: db.<collection>.update( { <array field>:<query selector> } { <update operator>: {'<array field>.$':value}} ) $[]: 更新数组中所有元素 db.<collection>.update( { <update operator>: {'<array field>.$[]':value}} ) 2.示例 db.person.insert([ {name:'zs', books:[{name:'html', price:66}, {name:'js', price:88}], tags:['html', 'js', ['1', '2']]}, {name:'ls', books:[{name:'vue', price:99}, {name:'node', price:199}], tags:['a', 'b', 'ab', 'c', 'ac']} ]) db.person.find() db.person.update( {name:'zs', tags:'js'}, {$set:{'tags.$':'JavaScript'}} ) db.person.update( {name:'zs'}, {$set:{'tags.$[]': 'it666'}} ) --> [代码] 删除文档 [代码]<!-- 1.删除文档 db.<collection>.remove(<query>, <options>) <query>: 删除筛选条件 <options>: 删除额外配置 2.示例 db.person.insert([ {name:'zs', age:18}, {name:'zs', age:19}, {name:'ls', age:20}, {name:'ls', age:21}, {name:'ww', age:22}, {name:'zl', age:23}, ]) 2.示例 //2.1删除所有满足条件 // 注意点: 和update方法不同, remove方法默认就会删除所有满足条件的数据 db.person.remove({name:'zs'}) //2.2删除第一个满足条件 db.person.remove({name:'ls'},{justOne:true}) //2.3删除所有文档 db.person.remove({}) --> [代码] 聚合操作 $project [代码]<!-- 1.什么是聚合操作? - 聚合操作就是通过一个方法完成一系列的操作 - 在聚合操作中, 每一个操作我们称之为一个阶段, 聚合操作会将上一个阶段处理结果传给下一个阶段继续处理, 所有阶段都处理完毕会返回一个新的结果集给我们 2.聚合操作格式 db.<collection>.aggregate(<pipeline>, <options>) <pipeline>: 定义每个阶段操作 <options> : 聚合操作额外配置 3.聚合管道阶段 $project: 对输入文档进行再次投影 作用 : 按照我们需要的格式生成结果集 格式 : {$project:{<field>:<value>}} 4.示例 db.person.insert([ {name:{firstName:'Jonathan', lastName:'Lee'}, age:18, book:{name:'玩转HTML', price: 88}}, {name:{firstName:'Amelie', lastName:'Jiang'}, age:17, book:{name:'玩转JavaScript', price: 99}} ]) db.person.aggregate([ { $project:{ _id:0, clientName: '$name.firstName', clientAge: '$age' } } ]) 5.聚合表达式 5.1字段路径表达式 $<filed>: 使用$来指示字段路径 $<filed>.<sub-field>: 使用$和.来指示内嵌文档字段路径 5.2字段路径表达式示例 $name $book.name 6.注意点: // 注意点: $project修改的是结果集而不是原有的集合 db.person.find() // 注意点: 如果字段表达式使用了不存在的字段, 那么会自动用Null填充 db.person.aggregate([ { $project:{ _id:0, fullName: ['$name.firstName', '$name.middleName','$name.lastName'], clientAge: '$age' } } ]) --> [代码] $match [代码]<!-- 1.聚合管道阶段 $match: 和find方法中的第一个参数一样, 用于筛选符合条件的文档 格式 : {$match:{<query>}} 2.示例 db.person.aggregate([ { $match:{ 'name.firstName':'Jonathan' } } ]) db.person.aggregate([ { $match:{ 'name.firstName':'Jonathan' } }, { $project:{ _id:0, clientName: '$name.firstName', clientAge: '$age' } } ]) 3.使用技巧: 应该在聚合操作的最前面使用$match, 这样可以有效减少处理文档的数量, 大大提升处理的效率 --> [代码] $limit和$skip [代码]<!-- 1.聚合管道阶段 $limit: 和游标的limit方法一样, 用于指定获取几个文档 格式 : {$limit:<number>} $skip : 和游标的skip方法一样, 用于指定跳过几个文档 格式 : {$skip:<number>} 2.示例 db.person.aggregate([ { $skip:1 }, { $limit:1 }, { $project:{ _id:0, clientName: '$name.firstName', clientAge: '$age' } } ]) --> [代码] $unwind [代码]<!-- 1.聚合管道阶段 $unwind: 展开数组字段 格式 : {$unwind:{path:<field>}} 2.示例: db.person.update({'name.firstName':'Jonathan'}, {$set:{tags:['html', 'js']}}) db.person.update({'name.firstName':'Amelie'}, {$set:{tags:'vue'}}) db.person.aggregate([ { $unwind:{ path:'$tags' } } ]) 3.注意点: 3.1$unwind会为数组中的每个元素创建一个新的文档 3.2可以通过includeArrayIndex属性添加展开之后的元素在原数组中的位置 db.person.aggregate([ { $unwind:{ path:'$tags', includeArrayIndex: 'index' } } ]) 3.3如果需要展开的字段不存在, 或者数组中没有元素, 或者为null, 会被unwind剔除 db.person.insert([ {name:{firstName:'san', lastName:'zhang'}, age:20}, {name:{firstName:'si', lastName:'li'}, age:21, tags:[]}, {name:{firstName:'wu', lastName:'wang'}, age:22, tags:null} ]) 3.4如果想让unwind不剔除不存在/没有元素/为Null的文档, 那么可以添加preserveNullAndEmptyArrays属性 db.person.aggregate([ { $unwind:{ path:'$tags', includeArrayIndex: 'index', preserveNullAndEmptyArrays: true } } ]) --> [代码] $sort [代码]<!-- 1.聚合管道阶段 $sort: 和文档游标sort方法一样, 对文档进行排序 格式 : {$sort:{<field>>:1|-1}} 2.示例 db.person.aggregate([ { $sort:{ age: 1 } } ]) --> [代码] $lookup [代码]<!-- 1.聚合管道阶段 $lookup: 用来做关联查询 格式 : {$lookup:{ from: 关联集合名称, localField: 当前集合中的字段名称, foreignField:关联集合中的字段名称, as: 输出字段的名称 }} 2.示例: db.person.insert([ {name:{firstName:'Jonathan', lastName:'Lee'}, age:18, books:['html', 'js']}, {name:{firstName:'Amelie', lastName:'Jiang'}, age:19, books:['vue']}, {name:{firstName:'si', lastName:'Li'}, age:20, books:[]} ]) db.books.insert([ {name:'html', price:88}, {name:'js', price:99}, {name:'vue', price:110}, ]) db.person.aggregate([ { $lookup:{ from: 'books', localField: 'books', foreignField: 'name', as: 'booksData' } } ]) 3.和unwind阶段结合使用 可以有效的过滤掉无效数据 可以给每个匹配的结果生成一个新的文档 db.person.aggregate([ { $unwind:{ path:'$books' } }, { $lookup:{ from: 'books', localField: 'books', foreignField: 'name', as: 'booksData' } } ]) --> [代码] $lookup相关查询 [代码]<!-- 1.聚合管道阶段 $lookup: 用来做关联查询 格式 : {$lookup:{ from: 关联集合名称, let: {定义给关联集合的聚合操作使用的当前集合的常量}, pipeline: [关联集合的聚合操作] as: 输出字段的名称 }} 2.示例: 不相关查询 db.person.aggregate([ { $lookup:{ from: 'books', pipeline: [ { $match:{ price:{$gte:100} } } ], as: 'booksData' } } ]) 相关查询 db.person.aggregate([ { $lookup:{ from: 'books', let: { bks: '$books'}, pipeline: [ { $match:{ $expr:{ $and:[ {$gte: ['$price', 100]}, {$in: ['$name', '$$bks']} ] } //price:{$gte:100} } } ], as: 'booksData' } } ]) 3系统变量表达式 $$<variable>: 使用$$来指示系统变量 --> [代码] $group [代码]<!-- 1.聚合管道阶段 $group: 对文档进行分组 格式 : {$group:{ _id:<expression>, <field1>: {<accumulator1>: <expression1>} ... ... }} _id: 定义分组规则 <field>: 定义新字段 2.示例 db.person.insert([ {name:'zs', age:10, city:'北京'}, {name:'ls', age:20, city:'上海'}, {name:'ww', age:30, city:'北京'}, {name:'zl', age:40, city:'上海'}, {name:'lnj', age:50, city:'北京'}, {name:'jjj', age:60, city:'广州'}, ]) db.person.aggregate([ {$group:{ _id:'$city', totalAge:{$sum:'$age'}, avgAge:{$avg:'$age'}, minAge:{$min:'$age'}, maxAge:{$max:'$age'}, totalName:{$push:'$name'} }} ]) --> [代码] $out [代码]<!-- 1.聚合管道阶段 $out: 前面阶段处理完的文档写入一个新的集合 格式: {$out: <new collection name>} 2.示例: db.person.aggregate([ { $group:{ _id: '$city', totalAge: {$sum:'$age'}, avgAge: {$avg: '$age'}, minAge: {$min: '$age'}, maxAge: {$max: '$age'}, totalAges: {$push: '$age'} } }, { $out:'newPerson' } ]) db.newPerson.find() 3.注意点: 如果利用$out写入一个已经存在的集合, 那么集合中的原有数据会被覆盖 --> [代码] 配置 [代码]<!-- 1.聚合操作额外配置 db.<collection>.aggregate(<pipeline>, <options>) 格式: {allowDiskUse: <boolean>} allowDiskUse默认取值是false, 默认情况下管道阶段占用的内存不能超过100M,如果超出100M就会报错 如果需要处理的数据比较多, 聚合操作使用的内存可能超过100M, 那么我们可以将allowDiskUse设置为true 如果allowDiskUse设置为true, 那么一旦超出100M就会将操作的数据写入到临时文件中, 然后再继续操作 --> <!-- 1.字段路径表达式 $<filed>: 使用$来指示字段路径 $<filed>.<sub-field>: 使用$和.来指示内嵌文档字段路径 2.示例 $name $book.name 3.系统变量表达式 $$CURRENT: 表示当前操作的文档 4.示例 $$CURRENT.name 等价于 $name 5.常量表达式 $literal:<value> : 表示常量<value> 6.示例 $literal:'$name' : 表示常量字符串$name db.person.insert([ {name:{firstName:'Jonathan', lastName:'Lee'}, age:18}, {name:{firstName:'Amelie', lastName:'Jiang'}, age:19} ]) db.person.find() db.person.aggregate([ {$project:{ _id:0, //myName:'$name.firstName', // 字段路径表达式 //myAge:'$age' // 字段路径表达式 //myName:'$$CURRENT.name.firstName', //系统变量表达式 //myAge:'$$CURRENT.age' // 系统变量表达式 myName:'$name.firstName', myAge:{$literal:'$age'} // 常量表达式 }} ]) --> [代码] 数据类型转换 [代码]<!-- 1.数据类型转换操作符 - MongoDB对于文档的格式并没有强制性的要求, 同一个集合中存储的文档, 字段的个数和数据类型都可以不同 对与文档的格式没有强制性的要求是MongoDB的一大优势, 但是同时也增加了数据消费端的使用难度 因为我们在使用数据的时候, 有可能同一个字段取出来的数据类型是不同的, 这样非常不利于我们后续操作 所以也正是因为如此, MongoDB在4.0中推出了$convert数据类型转换操作符 - 通过$convert数据类型转换操作符, 我们可以将不同的数据类型转换成相同的数据类型, 以便于后续我们在使用数据的过程中能够统一对数据进行处理 2.$convert格式 {$convert:{ input: '需要转换的字段', to: '转换之后的数据类型', onError: '不支持的转换类型', onNull: '没有需要转换的数据' }} 3.示例 db.person.insert([ {name:'zs', timestamp:ISODate('2020-08-09T11:23:34.733Z')}, {name:'ls', timestamp:'2021-02-14 12:00:06 +0800 '}, {name:'ww', timestamp:' 2023-04-01T12:00:00Z'}, {name:'zl', timestamp:'1587009270000'}, {name:'it666', timestamp:'Sunday'}, {name:'itzb'}, ]) db.person.aggregate([ {$project:{ _id:0, timestamp:{ $convert:{ input:'$timestamp', to:'date', onError: '不支持的转换类型', onNull: '没有需要转换的数据' } } }} ]) --> [代码]
2021-11-17 - 新一代的WEB开发与构建工具-vite
vite 由 vue 开发者尤雨溪尤大开发的新一代的前端开发与构建工具,基于浏览器 ES module 来开发 web 应用的方式。 两点最佳性能: 极速的服务启动(无需打包) 轻量快速的热重载HMR(再也不用编辑完代码看效果等老半天了) 我觉得真的是实现了按需编译了,在开发环境下简直是开发者的知音,那速度,你知道什么叫飞一般的感觉吗,在生产环境下实际使用的是 rollup ,而 rollup 最大的特点就是 TreeShaking ,一种采用删除不需要的额外代码的方式优化代码体积的技术,但其实 webpack后来 也增加了 TreeShaking 的功能,所以在生产环境下,其实个人还不太知道vite的优势。 webpack和vite webpack毕竟是老大哥,论社区生态和兼容性,vite还是比不了的。 其实就像尤大在知乎上说的,vite的目标本来就不是干掉webpack,webpack core只针对打包不限定场景,几乎所有可配置的环节都做成了可配置的,它的灵活性自然是很高的,而vite是限定了web的场景,用限定场景来降低复杂度。 术业有专攻,所以我觉得vite在web场景下是做的比webpack好的,下面就是webpack和vite的一个很大的区别: [图片][图片] 不过vite毕竟是新兴工具,所以可能在一些兼容性还做得不是很好,而且在配置上会略显繁琐,特别是webpack转vite,可能需要改很多配置,刨坑…但相信尤大的能力,将来vite会是web构建工具的首选!期待!感激这些大佬们的贡献,让我们这些小白站在巨人的肩膀上开发的更顺畅! 参考链接 当我尝试着把老项目 Webpack 迁移到 Vite 时,发现并没有这么香 - 云+社区 - 腾讯云 (tencent.com)
2021-11-17 - 前端页面性能优化
前端性能优化 基础优化 像代码优化,避免使用复杂的 js css 代码,善用keep-alive,减少重复沉余操作等等基础的优化这里就不详细说啦,但同样重要! 今天主要讲一下几种优化项目的方式。 路由懒加载(很重要) 配置路由各位前端得伙伴应该不陌生吧,但配置路由假如用的时一般加载,如果项目得模块少还好,要是十几个,再大点几十个上百个,一下子把所有组件都加载一遍,估计等项目加载完用户都怀疑是不是网络出问题了,自己开发预览也会很难受吧。 [代码]//一般加载 import component1 from "....." { // path: // name: component:component1 } //懒加载 { // path: // name: component:()=>import('.....') } //import是ES6的一个语法标准,如果需要浏览器兼容,需要转化成es5的语法。 [代码] 其实项目编译打包后,会把每个路由组件的代码分割成一一个 js 文件,初始化时不会加载这些 js 文件,只有当激活路由组件才会去加载对应的js文件。 gzip静态资源压缩 有些文件确实不能再优化了,那我们是不是可以考虑把它压缩了呢。 其实原理很简单,开启gzip压缩把那些大得js,css文件进行压缩,压缩比率在3到10倍左右,这样从服务器获取数据的时候(需要服务器配置),再由浏览器去解压加载,这样可以大大节省服务器的网络带宽,提高资源获取的速度。 不同的脚手架可能配置不太一样,但都大同小异,我这里用的是webpack,大家可以参考一下。 打开config/index.js文件 [代码]//找到下面啊的代码 // Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false, //把这里改成true productionGzipExtensions: ['js', 'css'], [代码] 然后安装依赖: [代码]npm install --save-dev compression-webpack-plugin@1.1.12 //注意高版本安装可能会报错,不过不确定是不是高版本不适配的原因,这里指定版本@1.1.12 [代码] 打开/build/webpack.base.config.js文件,找到module.exports的module中的rules,把limit从10000改小一点,这里改成1000 [代码] module: { rules: [{ test: /\.scss?$/, loaders: ["style", "css", "sass"] }, { test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig }, { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 1000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 1000, name: utils.assetsPath('media/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 1000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } ] }, [代码] 为什么要改这里呢? 因为 vue 中一些小的静态资源文件是会打包成base64的文件存在css中的或者js中,这里就是控制需要转换的大小,这样减少了js的大小。 服务器端(下面以Nginx为例) 打开配置文件 一般都会默认开启gzip的,但是gzip_static 是没有开启的,所有需要加上 gzip_static on; 如果没有开启gzip的话可以在手动在http{}里添加 [代码]http { gzip on; gzip_static on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_http_version 1.1; gzip_comp_level 2; gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml; gzip_vary on; gzip_proxied expired no-cache no-store private auth; gzip_disable "MSIE [1-6]\."; } [代码] webpack 的 Gzip 和服务端的 Gzip 其实服务端配置好Gzip就可以实现用户向服务器请求,服务器把资源压缩发送了,那为什么webpack也要设置呢?主要是为了减轻服务器压缩资源的负担,如果我们生产环境的资源本身就是压缩了的,那么服务器就不用压缩啦,自然就减轻了服务端的压力。建议两边都配置一下。 减少HTTP请求 不影响需求的前提下,尽可能减少http请求 改善响应时间最简单,最直接的途径当是较少 http 请求啦,有一些一个链接能搞定的就不要分几个链接来请求,也可以用巧用一些缓存来避免重复的http请求。 使用CDN 如果应用程序web服务器离用户更近,那么一个HTTP请求的响应时间将缩短。另一方面,如果组件web服务器离用户更近,则多个HTTP请求的响应时间将缩短。 CDN(内容发布网络)是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容。在优化性能时,向特定用户发布内容的服务器的选择基于对网络慕课拥堵的测量。例如,CDN可能选择网络阶跃数最小的服务器,或者具有最短响应时间的服务器。 CDN还可以进行数据备份、扩展存储能力,进行缓存,同时有助于缓和Web流量峰值压力。 CDN的缺点: 1、响应时间可能会受到其他网站流量的影响。CDN服务提供商在其所有客户之间共享Web服务器组。 2、如果CDN服务质量下降了,那么你的工作质量也将下降 3、无法直接控制组件服务器 按需加载第三方库 有时候我们开发的页面白屏时间长,打开开发者工具,看 network 那里,一般都是 vender.js 这个资源获取时间长达几s,第三方库一般都会打包到 vender.js ,就像我们常用到的 element.ui ,其实它的库是很大的,一般不设置都是整个库加载上来,自然加载慢了。我们可以把第三方库通过 CDN 的方式引入第三方库,这样vendor.js会减少,大大提升首屏加载速度,具体怎么使用大家可以搜一下,这里就不详细介绍啦。 图片压缩 这个是我自己以身示范的一个坑,在一个项目的登录界面我用了一张照片做背景,后来发现每次加载登录的界面都是超级慢,细究才发现,我这张照片10几m…加载时间自然短不了,所以照片压缩很必要。 参考链接 Web前端性能优化——如何提高页面加载速度 vue项目webpack打包app.js文件太大导致首次加载非常缓慢的解决方案
2021-11-16 - JavaScript_Promise的总结
JavaScript-Promise总结 前言: 最近在做项目对接后端接口时,遇到了一个bug,和promise有关,于是在网上找资料,找啊找,发现自己对Promise的了解还是很生疏的,于是就开始了对promise的学习,刚好最近也想开始写一些文章来记录自己的学习路程,也希望可以帮助到大家。然后,就开始了我前端之路的第一篇知识输出文章,请大家多多指教! 正文: Promise概述 异步编程是JavaScript一大特点,建议大家先了解一下JavaScript的异步编程实现,这里推荐一篇文章—JavaScript异步编程 - 熊建刚的文章 - 知乎 https://zhuanlan.zhihu.com/p/26567159 Promise是实现异步编程的一种解决方案,比传统的的解决方案(回调函数和事件)更加先进,先进在哪里呢?上代码! [代码]//传统异步编程方式 setTimeout(function(){ console.log("doThing1"); setTimeout(function(){ console.log("doThing2"); setTimeout(function(){ console.log("doThing3"); setTimeout(function(){ console.log("doThing4"); },1000) },1000) },1000) },1000) //promise实现 let p1=new Promise((resolve,reject)=>{ setTimeout(function () { console.log("doThing1"); resolve(); }, 1000); }) let p2=new Promise((resolve,reject)=>{ setTimeout(function () { console.log("doThing1"); resolve(); }, 1000); })v let p3=new Promise((resolve,reject)=>{ setTimeout(function () { console.log("doThing1"); resolve(); }, 1000); }) let p4=new Promise((resolve,reject)=>{ setTimeout(function () { console.log("doThing1"); resolve(); }, 1000v); }) p1.then(p2).then(p3).then(p4); [代码] 上面的代码,我们想按照顺序去做4件事情,如果按照传统的方式来实现很容易造成“函数瀑布”,这样的代码被称为“回调地狱”,看起来就很头疼,像promise这样实现更为直观,维护起来也方便很多,这仅仅是代码书写上的优势,在一些错误处理,顺序性等等都比传统异步编程更为优化,这里就不做深究了。 “Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息”。一个 Promise对象会将异步操作的最终结果和结果的处理程序关联起来(成功or失败),那么自然就要求promise需要有状态,我们才可以知道promise现在是成功了还是失败了。 Promise的状态: pending(等待),初始状态,未知操作成功还是失败 fulfilled(已完成),最终状态,操作成功,可以调用成功处理程序 rejected(已失败),最终状态,操作失败,可以调用失败处理程序 这里引入一个例子:小明的妈妈在做家务没空买菜,那么她叫小明去买菜,这个任务就是一个promise,好,小明去买菜了,小明买菜这个过程是一个异步操作,那么小明买菜的结果是什么呢?这就是说这个命令有没有完成?有两种情况,第一种是小明成功买到菜了,接下来小明妈妈就是做饭啦,第二种情况,小明把钱拿去打游戏了没买到菜,接下来小明妈妈就是把他打一顿啦… 接下来讲promise的使用都会用到这个例子。 Promise的方法 Promise()-promise的构造函数 作用: 创建一个新的 [代码]Promise[代码] 对象。该构造函数主要用于包装还没有添加 promise 支持的函数。 Promise接受函数两个参数(resolve, reject),当异步任务成功时,调用第一个参数resolve,将promise对象的状态设为fulfilled,并返回成功值;失败时将调用第二个参数reject将promise对象的状态设为rejected,并返回失败原因; 使用: [代码]let order=new Promise((resolve,reject)=>{ setTimeout(()=>{ //小明买菜ing // (1)小明一路破关斩将,抵御各种诱惑,成功买到一条鱼和一斤牛肉 //(2)小明看到菜市场旁边的游戏厅,被里面的游戏所吸引,把钱全花在打游戏那里了,没有买到菜 //小明回到家告诉妈妈 resolve("我买到菜了!买了一条鱼和一斤牛肉") //reject("我没有买到菜,钱全花在打游戏了......") //敢作敢当.... },1000)//这里用setTimeout来模拟小明去买菜这个异步任务 }) [代码] 注意!promise的状态一经决定,无法改变,所以当用了resolve再用reject时reject不起效果,promise状态仍是fulfilled [代码]Promise.prototype.then()[代码] 作用: 这个方法可以获取promise的状态,并进行结果处理。 它最多需要有两个参数:Promise 的成功和失败情况的回调函数。 语法: [代码]promise.then(value => { // fulfillment }, reason => { // rejection }); [代码] 使用: [代码]let order=new Promise((resolve,reject)=>{ setTimeout(()=>{ //小明买菜ing // (1)小明一路破关斩将,抵御各种诱惑,成功买到一条鱼和一斤牛肉 //(2)小明看到菜市场旁边的游戏厅,被里面的游戏所吸引,把钱全花在打游戏那里了,没有买到菜 //小明回到家告诉妈妈 resolve("我买到菜了!买了一条鱼和一斤牛肉") //reject("我没有买到菜,钱全花在打游戏了......") //敢作敢当.... },1000)//这里用setTimeout来模拟小明去买菜这个异步任务 }) order.then((res)=>{ console.log(res); console.log("妈妈夸了一顿小明,并做了红烧鱼和爆炒牛肉"); },(err)=>{ console.log(err); console.log("妈妈打了一顿小明,并决定今晚出去吃"); //好像小明这样做确实不错,可以出去吃了 }) //输出结果: 我买到菜了!买了一条鱼和一斤牛肉 妈妈夸了一顿小明,并做了红烧鱼和爆炒牛肉 //如果是reject("我没有买到菜,钱全花在打游戏了......") 我没有买到菜,钱全花在打游戏了...... 妈妈打了一顿小明,并决定今晚出去吃 [代码] tips: 由于 then 和 [代码]Promise.prototype.catch()[代码]方法的返回值都是 promise,它们可以被链式调用——这同时也是一种被称为复合( composition) 的操作。 [代码]//类似 p1.then(p2).then(p3).then(p4).catch(p5).then(p6)....... [代码] [代码]Promise.prototype.catch()[代码] 作用: 这个方法主要获取promise错误状态,并进行结果处理。其实和[代码]promise.then(undefined,onRejected)[代码]时一样的,不过promise.then优先(等下会有示例)。 语法: [代码]promise.catch(function(reason) { // 拒绝z }); [代码] 使用: [代码]let order=new Promise((resolve,reject)=>{ setTimeout(()=>{ //小明买菜ing // (1)小明一路破关斩将,抵御各种诱惑,成功买到一条鱼和一斤牛肉 //(2)小明看到菜市场旁边的游戏厅,被里面的游戏所吸引,把钱全花在打游戏那里了,没有买到菜 //小明回到家告诉妈妈 // resolve("我买到菜了!买了一条鱼和一斤牛肉") reject("我没有买到菜,钱全花在打游戏了......") //敢作敢当.... },1000)//这里用setTimeout来模拟小明去买菜这个异步任务 }) order.catch(err=>{ console.log("catch err: "+err); console.log("妈妈打了一顿小明,并决定今晚出去吃"); }) //输出结果: catch err: 我没有买到菜,钱全花在打游戏了...... 妈妈打了一顿小明,并决定今晚出去吃 [代码] tips: 当和[代码]promise.then(undefined,onRejected)[代码]同时存在时,onRejected优先 [代码]//构造Promise order同上 order.then(undefined,err=>{ console.log("then err: "+err); }) .catch(err=>{ console.log("catch err: "+err); }) //输出结果: then err: 我没有买到菜,钱全花在打游戏了...... [代码] catch可以捕获then()里面的函数 [代码]order.then(undefined,err=>{ console.log("then err: "+err); throw Error("then出错了") }) .catch(err=>{ console.log("catch err: "+err); }) //输出结果 then err: 我没有买到菜,钱全花在打游戏了...... catch err: Error: then出错了 [代码] 3.当在链式调用时,catch可以捕获前面任意一个then中的错误,这也就是说我们写代码的时候最好可以用上catch [代码]//类似 p1.then(p2).then(p3).then(p4).then(p5).catch(err)....... //catch可以捕获p2 p3 p4 p5中任意一个中的错误 [代码] [代码]Promise.prototype.finally()[代码] 作用: 当promise结束是,无论最终状态如何,fulfilled还是rejected,都会执行指定回调函数 语法: [代码]promise.finally(function() { // 返回状态为(resolved 或 rejected) }); [代码] 使用: [代码]let order=new Promise((resolve,reject)=>{ setTimeout(()=>{ //小明买菜ing // (1)小明一路破关斩将,抵御各种诱惑,成功买到一条鱼和一斤牛肉 //(2)小明看到菜市场旁边的游戏厅,被里面的游戏所吸引,把钱全花在打游戏那里了,没有买到菜 //小明回到家告诉妈妈 // resolve("我买到菜了!买了一条鱼和一斤牛肉") reject("我没有买到菜,钱全花在打游戏了......") //敢作敢当.... //promise的状态一经决定,无法改变,所以当用了resolve再用reject时reject不起效果,promise状态仍是fulfilled },1000)//这里用setTimeout来模拟小明去买菜这个异步任务 }) order.then((res)=>{ console.log(res); console.log("妈妈夸了一顿小明,并做了红烧鱼和爆炒牛肉"); },(err)=>{ console.log(err); console.log("妈妈打了一顿小明,并决定今晚出去吃"); //好像小明这样做确实不错,可以出去吃了 }).catch((err)=>{ //获取前面出现的错误,有可能妈妈做饭的时候鱼被猫吃了....... }).finally(()=>{ console.log("无论结果如何,最终都是要吃饭的!事已至此,先吃饭吧。。。。"); }) //输出结果 我没有买到菜,钱全花在打游戏了...... 妈妈打了一顿小明,并决定今晚出去吃 无论结果如何,最终都是要吃饭的!事已至此,先吃饭吧。。。。 [代码] [代码]Promise.resolve()[代码] 作用: 返回一个带有成功参数的[代码]Promise[代码]对象。注意此时的promise对象已是fulfilled的最终状态。 参数: 如果参数是一个值,将把这个值作为返回的promise的成功参数 如果这个值是一个 promise ,那么将返回这个 promise 如果是一个thenable,返回的promise会“跟随”这个thenable的对象,采用它的最终状态。 [代码]thenable:[代码] [代码]thenable[代码]是任何含有then()方法的对象或函数,作用使promise的实现更具有通用性 类似: [代码]let thenable = { then: (resolve, reject) => { resolve(thenable) } } [代码] 判断一个对象是不是thenable,用到类型检查,也称为鸭式辩型 [代码]if(p!=null&&(typeof p==="object"||typeof p==="function")&&(typeof p.then==="function")){ //p是一个thenable }else{ //p不是一个thenable } [代码] 示例: [代码]let thenable = { then: (resolve, reject) => { resolve("thenble最终状态") } } let promise=Promise.resolve(thenable) promise.then((res)=>{ console.log(res) },err=>{ //用不上 } ) //打印结果 //thenble最终状态 let promise1 = Promise.resolve(11111); let promise=Promise.resolve(promise1) promise.then((res)=>{ console.log(res) },err=>{ //用不上 } ) //打印结果 //11111 let promise=Promise.resolve(2222) promise.then((res)=>{ console.log(res) },err=>{ //用不上 } ) //打印结果 //22222 [代码] [代码]Promise.reject()[代码] 作用: 返回一个带有拒绝原因的[代码]Promise[代码]对象。注意此时的promise对象已是rejected的最终状态 和[代码]Promise.resolve()[代码]的区别 [代码]Promise.reject[代码] 方法的参数,会原封不动地作为 reject 的参数,变成后续方法的参数。这一点与 [代码]Promise.resolve[代码] 方法不一致。 [代码] //Promise.resolve() 和Promise.reject()的区别 const thenable ={ then(resolve, reject){ // resolve("成功了") reject('出错了'); } }; //Promise.resolve() 和Promise.reject()的区别 //Promise.resolve(thenable) Promise.reject(thenable) .then(res=>{ console.log("then res: "+res) console.log(res===thenable) }) .catch(err =>{ console.log("catch err: "+err) console.log(err===thenable) }) //打印结果 catch err: [object Object] true //当执行Promise.resolve(thenable) catch err: 出错了 false [代码] 对resolve(thenable)结果解析: 当thnable传入resolve时,将立即执行thnable的then方法,resolve返回它的最终状态reject(‘出错了’),此时resolve返回的promise参数并不是一个thnable了,而是“出错了” 而当thnable传入rejected时,并不会执行thnable的then方,而是原封不动将thnable作为resolve返回的promise参数 [代码]Promise.all()[代码] 作用: 整合多个promise示例,返回最终一个promise实例 参数: 一个promise的[代码]iterable[代码]类**(注:Array,Map,Set都属于ES6的iterable类型)** 返回值: Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候 只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。 个人觉得有点像与操作了,当所有promise都fulfilled时,返回的promise才是fulfilled,完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 [代码]promise[代码] 值);而当任意一个promise失败时,返回的promise时rejected,Promise.all()异步地将失败的那个结果给失败状态的回调函数,而不管其它 [代码]promise[代码] 是否完成。 [代码]let promise1=Promise.resolve("小明成功买到菜了"); let promise2=Promise.reject("小明没晾衣服"); let promise3=Promise.resolve("小明刷完高数了"); Promise.all([promise1, promise2, promise3]).then(res => { console.log(res); }); //打印结果 [ '小明成功买到菜了', '小明晾衣服了', '小明刷完高数了' ] let promise1=Promise.resolve("小明成功买到菜了"); let promise2=Promise.reject("小明没晾衣服"); let promise3=Promise.resolve("小明刷完高数了"); let promise=Promise.all([promise1, promise2, promise3]); setTimeout(()=>{ console.log(promise) },0) //打印结果 Promise { <rejected> '小明没晾衣服' } [代码] 至于第二个例子我为什么要用setTimeout,大家可以保留疑问,假如直接打印,结果会是[代码]Promise { <pending> }[代码],这就涉及到Promise的异步处理,文章后我会讲解 [代码]Promise.allSettled()[代码] 作用: 方法返回一个在所有给定的promise都已经[代码]fulfilled[代码]或[代码]rejected[代码]后的promise,并带有一个对象数组,每个对象表示对应的promise结果。 如果有多个彼此不依赖的异步任务完成时,然后你要知道所有promise的结果,又不想一个一个地获取时,可以用到这个。相反,假如你的异步任务有依赖则要用Promise.all() 参数: 和[代码]Promise.all()[代码]一致 示例: [代码]let promise1=Promise.resolve("小明成功买到菜了"); let promise2=Promise.reject("小明没晾衣服"); let promise3=Promise.resolve("小明刷完高数了"); Promise.allSettled([promise1, promise2, promise3]).then((result)=>{ result.forEach((item)=>{ console.log(item) }) }) //打印结果 [ { status: 'fulfilled', value: '小明成功买到菜了' }, { status: 'rejected', reason: '小明没晾衣服' }, { status: 'fulfilled', value: '小明刷完高数了' } ] [代码] [代码]Promise.race()[代码] 作用: 返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。 race-比赛,看哪个promise最先被定义最终状态,则返回该结果 参数: 和[代码]Promise.all()[代码]一致 示例: [代码]const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, '小明做完作业了'); //小明做完作业花费了500ms }); const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, '小东做完作业了'); //小东做完作业花费了100ms }); let promise=Promise.race([promise1, promise2]); setTimeout(()=>{ console.log(promise) },1000); //打印结果 Promise { '小东做完作业了' } const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, '小明做完作业了'); //小明做完作业花费了500ms }); const promise2 = new Promise((resolve, reject) => { setTimeout(reject, 100, '小东不想作业了'); //小东100ms后就说不做作业了... }); let promise=Promise.race([promise1, promise2]); setTimeout(()=>{ console.log(promise) },1000); //打印结果 Promise { <rejected> '小东不想作业了' } [代码] [代码]Promise.any()[代码] 作用: 与[代码]Promise.all()[代码]相反,及相当于做或操作,只要有一个promise成功了,那么最终结果就成功(只返回第一个成功的promise),如果都不成功,最终promise才为rejected。 参数: 和[代码]Promise.all()[代码]一致 示例: [代码]let promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, '小明做完作业了'); //小明做完作业花费了500ms }); let promise2 = new Promise((resolve, reject) => { setTimeout(reject, 100, '小东不想作业了'); //小东100ms后就说不做作业了... }); let promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, '小李做完作业了'); //小明做完作业花费了500ms }); Promise.any([promise1, promise2,promise3]).then(res=>{ console.log(res) }) //打印结果 "小李做完作业了" let promise1 = new Promise((resolve, reject) => { setTimeout(reject, 500, '小明不想作业了'); //小明500ms后就说不做作业了... }); let promise2 = new Promise((resolve, reject) => { setTimeout(reject, 100, '小东不想作业了'); //小东100ms后就说不做作业了... }); let promise3 = new Promise((resolve, reject) => { setTimeout(reject, 200, '小李不想作业了'); //小明100ms后就说不做作业了... }); Promise.any([promise1, promise2,promise3]).catch(err=>{ console.log(err.message)}) //打印结果 "All promises were rejected" [代码] Promise的异步执行 我们先来看一段代码! [代码]let promise1 = Promise.resolve(11111); let promise2 = promise1.then(value => { console.log("promise1的value: " + value); return value; }); console.log(promise2); setTimeout(() => { console.log(promise2); }); //打印结果: Promise { <pending> } promise1的value: 11111 Promise { 11111 } [代码] 是不是和你的预期不一样,这就是Promise的异步执行; 我们来分析一下: 其实代码执行到 [代码]let promise1 = Promise.resolve(11111);[代码] [代码]Promise.resolve[代码]不会在时不会立即执行的,这是交给异步来完成的,那么就顺着执行下面的代码了. 到[代码]let promise2 = promise1.then[代码]时,此时的promise1的状态还是pending,所以promise2也是pending,此时value是undefined的。 然后就是执行[代码]console.log(promise2)[代码],所以打印的第一个是[代码]Promise { <pending> }[代码],之后再[代码]setTimeout[代码],此时promise1 promise2才是操作完成的,value是111。 这也是和JavaScript的异步编程有关! Promise的缺点 一旦新建Promise,它就会立即执行,无法中途取消。 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。 当处于 [代码]pending[代码] 状态时,无法得知目前进展到哪一个阶段(刚开始还是即将完成)。 总结 终于写完了,自己也系统地学完了Promise,输出亦是一种学习的过程。 这篇文章主要是对Promise的理解和使用,至于Promise的实现,我也只是简单的看了大概,大家有兴趣可以继续深究源码。 此文章只代表个人见解,站在巨人的肩膀上看问题,感谢前辈们的贡献,如有错误,请指出!感谢大家!
2021-11-14