首页
归档
关于
友人帐
Search
1
Mac OS 终端利器 iTerm2 + Oh My Zsh
13,753 阅读
2
前端 History 路由及 API 反向代理的 Nginx 配置
10,568 阅读
3
解决 Xcode Command Line Tools 错误或无法安装问题
6,077 阅读
4
Mac下右键使用VSCode打开项目
3,922 阅读
5
BrowserHistory刷新页面404问题
3,374 阅读
码上世界
内容分享
生活印记
其他
登录
Search
小小孩
累计撰写
94
篇文章
累计收到
181
条评论
首页
栏目
码上世界
内容分享
生活印记
其他
页面
归档
关于
友人帐
搜索到
47
篇与
码上世界
的结果
2019-11-11
从一个文字渐变引发的一系列问题
前言事件的起因是朋友给我发的一个微信当时就奇怪字体跟数据绑定有啥关系,处于好奇我让他写个 demo 给我看看 ,然后他发来一个代码文件代码精简如下<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Demo</title> <style> body { font-size: 40px; } .time1 { display: inline-block; background: linear-gradient(to right, #a6ffcb, #1fa2ff); -webkit-background-clip: text; color: transparent; } </style> </head> <body> <div id="app"> <div class="time1">{{ time }}</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script> <script> new Vue({ el: "#app", data: { time: new Date().toLocaleString() }, methods: { updateTime() { this.time = new Date().toLocaleString(); } }, mounted() { setInterval(this.updateTime, 1000); } }); </script> </body> </html>发现在浏览器下时间并不会改变 ,当我在控制台把颜色注释掉后,发现数据是改变的,这就奇怪了呀! 这里我就试了下 另一种渐变样式写法知识拓展:css 文字渐变一些方法background 属性这种我不细说了 也很好理解 可以看上面代码实现方式mask 属性<style> .time2 { position: relative; color: #a6ffcb; } .time2:before { content: attr(time); position: absolute; z-index: 10; color: #1fa2ff; -webkit-mask: linear-gradient(to left, #1fa2ff, transparent); } </style> <div class="time2" time="time">我是渐变文字</div> :before 选择器向选定的元素前插入内容。使用 content 属性来指定要插入的内容。mask 属性让元素的某一部分显示或隐藏第一个:content 取值 attr 就是用来获取属性值的,content:attr(属性名)content: attr(time); 能获取到元素的 time 属性,这里的这个 time 属性是自己自定义的一个属性,随便写<h1 date="前端简单说">前端简单说</h1>然后content属性 这样写,content: attr(date); 同样是可以起作用的。第二个:mask 属性 允许使用者通过部分或者完全隐藏一个元素的可见区域。这种效果可以通过遮罩或者裁切特定区域的图片。详情可看https://developer.mozilla.org/zh-CN/docs/Web/CSS/mask回归问题我们试试另一种情况会不会出现不渲染情况<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Demo</title> <style> body { font-size: 40px; } .time1 { display: inline-block; } .time1 { background: linear-gradient(to right, #a6ffcb, #1fa2ff); -webkit-background-clip: text; color: transparent; } .time2 { position: relative; color: #a6ffcb; } .time2:before { content: attr(time); position: absolute; z-index: 10; color: #1fa2ff; -webkit-mask: linear-gradient(to left, #1fa2ff, transparent); } </style> </head> <body> <div id="app"> <div class="time1">{{ time }}</div> <div class="time2" :time="time">{{ time }}</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script> <script> new Vue({ el: "#app", data: { time: new Date().toLocaleString() }, methods: { updateTime() { this.time = new Date().toLocaleString(); } }, mounted() { setInterval(this.updateTime, 1000); } }); </script> </body> </html>看下效果我当时的表情我想到了是不是浏览器重排和重绘的问题知识拓展:浏览器重排和重绘我们了解下重排和重绘浏览器编译页面分为 5 步处理 html 生成 DOM(Document Object Model) Tree处理 css 生成 CSSOM(CSS Object Model) TreeDOM 树与 CSS-DOM 树合并为 Render 树对 Render 树进行布局计算遍历 Render 树的每一个节点绘制到屏幕重绘与重排概念当 DOM 变化影响了元素的几何属性(宽、高改变等等),浏览器此时需要重新计算元素几何属性,并且页面中其他元素的几何属性可能会受影响,这样渲染树就发生了改变,也就是重新构造 RenderTree 渲染树,这个过程叫做重排(reflow)如果 DOM 变化仅仅影响的了背景色等等非几何属性,此时就发生了重绘(repaint)而不是重排,因为布局没有发生改变页面布局和元素几何属性的改变就会导致重排下列情况会发生重排:页面初始渲染添加/删除可见 DOM 元素改变元素位置改变元素尺寸(宽、高、内外边距、边框等)改变元素内容(文本或图片等)改变窗口尺寸不同的条件下发生重排的范围及程度会不同某些情况甚至会重排整个页面,比如滑动滚动条以下属性或方法会刷新渲染队列(offsetTop、offsetLeft、offsetWidth、offsetHeightclientTop、clientLeft、clientWidth、clientHeightscrollTop、scrollLeft、scrollWidth、scrollHeightgetComputedStyle()(IE 中 currentStyle))再次回归问题这边时间文字改变了,但文字大小没变,我感觉按道理应该没有触发重排从而使背景色没有改变 ,那我试试改变文字大长度试试效果如下发现确实,文字长度没变,第一个不会重新渲染,长度一旦发生改变,第一个才会改变渲染,而第二个一直在渲染问题好像突然找到原因了,但我又无意发现新的问题新的问题<!DOCTYPE html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Demo</title> <style> body { font-size: 40px; } .time1 { display: inline-block; } .time1, .time3 { background: linear-gradient(to right, #a6ffcb, #1fa2ff); -webkit-background-clip: text; color: transparent; } .time2 { position: relative; color: #a6ffcb; } .time2:before { content: attr(time); position: absolute; z-index: 10; color: #1fa2ff; -webkit-mask: linear-gradient(to left, #1fa2ff, transparent); } </style> </head> <body> <div id="app"> <div class="time1">{{ time }}</div> <div class="time2" :time="time">{{ time }}</div> <div> <span class="time3">{{ time }}</span> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script> <script> new Vue({ el: "#app", data: { msg: "Hello", time: new Date().toLocaleString() }, methods: { updateTime() { this.time = new Date().toLocaleString(); } }, mounted() { setInterval(this.updateTime, 1000); } }); </script> </body> </html>当我改变 dom 结构发现又好了效果如下好了 我彻底呆了最后最后我也没搞明白,不过为了保险起见以后类似这种还是用第二种渐变样式吧!
2019年11月11日
1,193 阅读
0 评论
0 点赞
2019-08-28
在 Linux 下搭建 Git 服务器步骤
由于代码经常改动,搞得头大,就想找个代码管理工具。查了一些资料,最后选择使用 git 管理代码,下面将搭建的过程记录下来。(亲测可以使用) "content: "### 环境:服务器 CentOS6.6 + git(version 1.7.1)客户端 Windows10 + git(version 2.8.4.windows.1)① 安装 GitLinux 做为服务器端系统,Windows 作为客户端系统,分别安装 Git服务器端:yum install -y git安装完后,查看 Git 版本[root@localhost ~]# git --version git version 1.12.6客户端:下载 Git for Windows,地址:https://git-for-windows.github.io/安装完之后,可以使用 Git Bash 作为命令行客户端。安装完之后,查看 Git 版本 $ git --version git version 2.15.1.windows.2② 服务器端创建 git 用户,用来管理 Git 服务,并为 git 用户设置密码 [root@localhost home]# useradd git [root@localhost home]# passwd git③ 服务器端创建 Git 仓库设置 /home/first.git 为 Git 仓库然后把 Git 仓库的 owner 修改为 git[root@localhost home]# git init --bare first.git Initialized empty Git repository in /home/first.git [root@localhost git]# chown -R git:git first.git给仓库设置 700 权限 尽量 700 权限 chmod -R 700 /home/first.git/*④ 客户端 clone 远程仓库Git 服务器就已经搭得差不多了。下面我们在客户端 clone 一下远程仓库 $ git clone git@xxx.xxx.xxx.xxx:/home/first.git Cloning into 'first'... The authenticity of host 'xxx.xxx.xxx.xxx (xxx.xxx.xxx.xxx)' can't be established. RSA key fingerprint is 2b:55:45:e7:4c:29:cc:05:33:78:03:bd:a8:cd:08:9d. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added 'xxx.xxx.xxx.xxx' (RSA) to the list of known hosts. git@192.168.8.34's password:这里两点需要注意:第一,当你第一次使用 Git 的 clone 或者 push 命令连接 GitHub 时,会得到一个警告:这是因为 Git 使用 SSH 连接,而 SSH 连接在第一次验证 GitHub 服务器的 Key 时,需要你确认 GitHub 的 Key 的指纹信息是否真的来自 GitHub 的服务器,输入 yes 回车即可。Git 会输出一个警告,告诉你已经把 GitHub 的 Key 添加到本机的一个信任列表里了: Warning: Permanently added 'github.com' (RSA) to the list of known hosts.此时 C:\\Users\\用户名\\.ssh 下会多出一个文件 known_hosts,以后在这台电脑上再次连接目标 Git 服务器时不会再提示上面的语句。这个警告只会出现一次,后面的操作就不会有任何警告了。如果你实在担心有人冒充 GitHub 服务器,输入 yes 前可以对照 GitHub 的 RSA Key 的指纹信息是否与 SSH 连接给出的一致。第二,这里提示你输入密码才能 clone,当然如果你知道密码,可以键入密码来进行 clone,但是更为常见的方式,是利用 SSH 的公钥来完成验证。⑤ 客户端创建 SSH 公钥和私钥 $ ssh-keygen -t rsa -C "youremail@example.com"你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个 Key 也不是用于军事目的,所以也无需设置密码。如果一切顺利的话,可以在用户主目录里找到.ssh 目录,里面有 id_rsa 和 id_rsa.pub 两个文件,这两个就是 SSH Key 的秘钥对,id_rsa 是私钥,不能泄露出去,id_rsa.pub 是公钥,可以放心地告诉任何人。⑥ 服务器端 Git 打开 RSA 认证然后就可以去 Git 服务器上添加你的公钥用来验证你的信息了。在 Git 服务器上首先需要将/etc/ssh/sshd_config 中将 RSA 认证打开,即: RSAAuthentication yes PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys这里我们可以看到公钥存放在.ssh/authorized_keys 文件中。所以我们在/home/git 下创建.ssh 目录然后把 .ssh 文件夹的 owner 修改为 git [root@localhost git]# chown -R git:git .ssh⑦ 将公钥导入服务器端 authorized_keys 文件将电脑C:\\Users\\用户名\\.ssh\\id_rsa.pub文本可以导入到/home/git/.ssh/authorized_keys重要:修改 .ssh 目录的权限为 700修改 .ssh/authorized_keys 文件的权限为 600 [root@localhost git]# chmod 700 .ssh [root@localhost git]# cd .ssh [root@localhost .ssh]# chmod 600 authorized_keys再重启 ssh 服务 service sshd start之后 clone 就不要输密码了⑨ 禁止 git 用户 ssh 登录服务器之前在服务器端创建的 git 用户不允许 ssh 登录服务器编辑 /etc/passwd找到:git:x:502:504::/home/git:/bin/bash 修改为git:x:502:504::/home/git:/bin/git-shell此时 git 用户可以正常通过 ssh 使用 git,但无法通过 ssh 登录系统。
2019年08月28日
629 阅读
0 评论
0 点赞
2019-06-28
Nodejs 爬取one和墨迹天气定时发邮件
根据用户配置 爬取 one 和不同地区墨迹天气 每天定时发邮件,支持多人地区个性化定制 可以的话 可以去https://github.com/cyea/email-bot 给个小星星效果展示如何快速使用1. 拉取代码安装依赖这里使用yarn作为包管理器git clone https://github.com/cyea/email-bot.git cd email-bot yarn2. 配置① 修改发送者邮箱账号密码敏感配置新建.env文件 格式是跟.env.example 一样的 填入自己的邮箱账号密码及邮件提供商NODE_ENV = production #正式环境精简代码所用 EmianService = outlook #邮件提供商 支持列表:https://nodemailer.com/smtp/well-known/ EamilAuth_user = xxxx@outlook.com #发送者邮箱地址 EamilAuth_pass = xxxxxxxxx # smtp 授权码② 修改其他不敏感配置修改config/index.js里的配置文件const { env } = process; module.exports = { ONE: "http://wufazhuce.com/", // ONE的web版网站 MOJI_HOST: "https://tianqi.moji.com/weather/china/", // 中国墨迹天气url, EmianService: env.EmianService, // 发送者邮箱厂家 EamilAuth: { // 发送者邮箱账户用户名及密码 user: env.EamilAuth_user, pass: env.EamilAuth_pass }, EmailFrom: "yuyehack@outlook.com", // 发送者昵称与邮箱地址 EmailSubject: "一封暖暖的小邮件", // 邮件主题 /** * @description: 收信人详细 */ EmailToArr: [ { TO: "yuyehack@gmail.com", // 接收者邮箱地址 CITY: "jiangsu", // 墨迹天气链接末尾城市代码 LOCATION: "pukou-district" // 墨迹天气链接末尾详细地区代码 }, { TO: "yuyehack@qq.com", CITY: "jiangsu", LOCATION: "kunshan" } ], //每日发送时间 SENDDATE: "58 15 8 * * *" };③ 运行yarn start代码详解具体代码可见 https://github.com/cyea/email-bot.git先展示下项目结构├─config │ index.js #配置 │ ├─email │ index.js #发送邮件模块 │ ├─superagent │ index.js #获取天气及ONE 数据 │ ├─utils │ index.js #通用工具函数 │ superagent.js #请求发送封装 │ ├─view | index.js #生成邮件样式模块 | index.njk #邮件样式模板模块 │ .env.example #.env │ index.js #服务启动模块 │ schedule.js #定时模块 │ test.js #模板样式调试模块 │ yarn.lock │ .gitignore │ LICENSE │ package.json │ README.md1. 爬取数据使用 superagent 和 cheerio 组合来实现爬虫① superagent 使用因为多次两次使用的superagent 函数代码结构类似 所以我再把 superagent 封装了一次 Promise 抛出 fetch方法// utils/superagent.js const superagent = require("superagent"); //请求 function fetch(url, method, params, data, cookies) { return new Promise(function(resolve, reject) { superagent(method, url) .query(params) .send(data) .set("Content-Type", "application/x-www-form-urlencoded") .end(function(err, response) { if (err) { reject(err); } resolve(response); }); }); } module.exports = fetch;② 数据爬取爬取 ONEconst getOne = async () => { // 获取每日一句 let res = await fetch(config.ONE, "GET"); let $ = cheerio.load(res.text); //转化成类似jquery结构 let todayOneList = $("#carousel-one .carousel-inner .item"); // 通过查看DOM获取今日句子 let info = $(todayOneList[0]) .find(".fp-one-cita") .text() .replace(/(^\s*)|(\s*$)/g, ""); let imgSrc = $(todayOneList[0]) .find(".fp-one-imagen") .attr("src"); return { // 抛出 one 对象 one: { info, imgSrc } }; };爬取天气const getWeather = async (city, location) => { //获取墨迹天气 let url = config.MOJI_HOST + city + "/" + location; // 根据配置得到天气url let res = await fetch(url, "GET"); let $ = cheerio.load(res.text); //获取墨迹天气地址 let addressText = $(".search_default") .text() .trim() .split(", ") .reverse() .join("-"); //获取墨迹天气提示 let weatherTip = $(".wea_tips em").text(); // 获取现在的天气数据 const now = $(".wea_weather.clearfix"); let nowInfo = { Temp: now.find("em").text(), WeatherText: now.find("b").text(), FreshText: now.find(".info_uptime").text() }; // 循环获取未来三天数据 let threeDaysData = []; $(".forecast .days").each(function(i, elem) { // 循环获取未来几天天气数据 const SingleDay = $(elem).find("li"); threeDaysData.push({ Day: $(SingleDay[0]) .text() .replace(/(^\s*)|(\s*$)/g, ""), WeatherImgUrl: $(SingleDay[1]) .find("img") .attr("src"), WeatherText: $(SingleDay[1]) .text() .replace(/(^\s*)|(\s*$)/g, ""), Temperature: $(SingleDay[2]) .text() .replace(/(^\s*)|(\s*$)/g, ""), WindDirection: $(SingleDay[3]) .find("em") .text() .replace(/(^\s*)|(\s*$)/g, ""), WindLevel: $(SingleDay[3]) .find("b") .text() .replace(/(^\s*)|(\s*$)/g, ""), Pollution: $(SingleDay[4]) .text() .replace(/(^\s*)|(\s*$)/g, ""), PollutionLevel: $(SingleDay[4]) .find("strong") .attr("class") }); }); return { moji: { addressText, weatherTip, nowInfo, threeDaysData } }; };③ 数据合并异步获取两个数据const getAllData = async (city, location) => { let oneData = await getOne(); let weatherData = await getWeather(city, location); const allData = { today: formatDate(), ...oneData, ...weatherData }; return allData; }; module.exports = getAllData;2. 模版引擎生成 HTML① 模板编写这里选用 nunjucks 作为模板引擎,因为邮件不支持外链 css 所以使用内联 css 虽然比较麻烦使用刚获取到数据 模板渲染<!-- index.njk --> <div style="padding: 0;max-width: 600px;margin: 0 auto;"> <div style="width:100%; margin: 40px auto;font-size:20px; color:#5f5e5e;text-align:center"> <span>今天是{{today}}</span> </div> <div style="width:100%; margin: 20px auto;font-size:16px;color:#2bbc8a;text-align: center;"> <span><span style="font-family: Arial;font-size: 100px;line-height: 1;">{{moji.nowInfo.Temp}}</span><span style="vertical-align: top;">℃</span></span> <span style="font-size: 30px;padding-top: 50px;">{{moji.nowInfo.WeatherText}}</span> </div> <div style="text-align:center;font-size:12px;color:#d480aa">{{moji.addressText}}</dev> <div style="width:100%; margin: 0 auto;color:#5f5e5e;text-align:center"> <span style="display:block;color:#676767;font-size:20px">{{moji.weatherTip}}</span> <span style="display:block;margin-top:15px;color:#676767;font-size:15px">近期天气预报</span> {% for item in moji.threeDaysData %} <div style="display: flex;margin-top:5px;height: 30px;line-height: 30px;justify-content: space-around;align-items: center;"> <span style="width:15%; text-align:center;">{{ item.Day }}</span> <div style="width:25%; text-align:center;"> <img style="height:26px;vertical-align:middle;" src='{{ item.WeatherImgUrl }}' alt=""> <span style="display:inline-block">{{ item.WeatherText }}</span> </div> <span style="width:25%; text-align:center;">{{ item.Temperature }}</span> {% if (item.PollutionLevel==='level_1') %} <div style="width:35%; "> <span style="display:inline-block;padding:0 8px;line-height:25px;color:#8fc31f; border-radius:15px; text-align:center;">{{ item.Pollution }}</span> </div> {% elif (item.PollutionLevel==='level_2') %} <div style="width:35%; "> <span style="display:inline-block;padding:0 8px;line-height:25px;color:#d7af0e; border-radius:15px; text-align:center;">{{ item.Pollution }}</span> </div> {% elif (item.PollutionLevel==='level_3') %} <div style="width:35%; "> <span style="display:inline-block;padding:0 8px;line-height:25px;color:#f39800; border-radius:15px; text-align:center;">{{ item.Pollution }}</span> </div> {% elif (item.PollutionLevel==='level_4') %} <div style="width:35%; "> <span style="display:inline-block;padding:0 8px;line-height:25px;color:#e2361a; border-radius:15px; text-align:center;">{{ item.Pollution }}</span> </div> {% elif (item.PollutionLevel==='level_5') %} <div style="width:35%; "> <span style="display:inline-block;padding:0 8px;line-height:25px;color:#5f52a0; border-radius:15px; text-align:center;">{{ item.Pollution }}</span> </div> {% elif (item.PollutionLevel==='level_6') %} <div style="width:35%; "> <span style="display:inline-block;padding:0 8px;line-height:25px;color:#631541; border-radius:15px; text-align:center;">{{ item.Pollution }}</span> </div> {% else %} <div style="width:35%; "> <span style="display:inline-block;padding:0 8px;line-height:25px;color:#631541; border-radius:15px; text-align:center;">none</span> </div> {% endif %} </div> {% endfor %} </div> <div style="text-align:center;margin:35px 0;"> <span style="display:block;margin-top:55px;color:#676767;font-size:15px">ONE · 一个</span> <img src="{{ one.imgSrc }}" style="max-width:100%;margin:10px auto;" alt=""> <span style="color:#b0b0b0;font-size:13px;">摄影</span> <div style="margin:10px auto;width:85%;color:#5f5e5e;" >{{one.info}}</div> </div> </div> ② 模板渲染node fs 模块 读取本地模板文件 抛出 渲染好的 html 结构数据const nunjucks = require("nunjucks"); const fs = require("fs"); const path = require("path"); const getHtmlData = njkData => { return new Promise((resolve, reject) => { try { const njkString = fs.readFileSync( path.resolve(__dirname, "index.njk"), "utf8" ); const htmlData = nunjucks.renderString(njkString, njkData); resolve(htmlData); } catch (error) { reject(error); } }); }; module.exports = getHtmlData;3. 使用 Node 发送邮件这里使用 nodemailer注意的是邮箱密码不是你登录邮箱的密码,而是 smtp 授权码,什么是 smtp 授权码呢?就是你的邮箱账号可以使用这个 smtp 授权码在别的地方发邮件,一般 smtp 授权码在邮箱官网的设置中可以看的到.不知道的话可以使用邮箱账号及密码试试const config = require("./../config"); const sendMail = (transporter, To, HtmlData) => { return new Promise((resolve, reject) => { let mailOptions = { from: config.EmailFrom, // 发送者邮箱 to: To, // 接收邮箱 subject: config.EmailSubject, // // 邮件主题 html: HtmlData //模板数据 }; transporter.sendMail(mailOptions, (error, info = {}) => { if (error) { console.error("邮件发送成功" + error); reject(error); } else { console.log("邮件发送成功", info.messageId); console.log("静等下一次发送"); resolve(); } }); }); }; module.exports = sendMail;4. 整合运行let transporter = nodemailer.createTransport({ service: EmianService, port: 465, secureConnection: true, auth: EamilAuth, pool: true }); const getAllDataAndSendMail = async () => { for (let i = 0, len = EmailToArr.length; i < len; i++) { try { let item = EmailToArr[i]; let apiData = await getAllData(item.CITY, item.LOCATION); let htmlData = await getHtmlData(apiData); await sendMail(transporter, item.TO, htmlData); } catch (error) { console.error(error); } } }; getAllDataAndSendMail();5. 定时这里用到了 node-schedule 来定时执行任务,它跟 corn 很类似 之不是基于Node的具体用法可见 node-schedule文档这里我使用了 每天早上的 08:15:58 定时发送 尽量不取整点const schedule = require("node-schedule"); const config = require("./config"); const scheduleRun = fn => { console.log("NodeMail: 开始等待目标时刻..."); let j = schedule.scheduleJob(config.SENDDATE, function() { // SENDDATE: "58 15 8 * * *" console.log("开始执行任务......"); fn(); }); }; module.exports = scheduleRun;所以只要 引入scheduleRun方法scheduleRun(getAllDataAndSendMail);6. 配置详情因为像邮件 smtp 授权码 是敏感信息 建议放进环境变量 env2 是个不错的工具 ,具体使用可以看env2文档具体配置详见这里问题1. 邮箱登陆失败一般是在服务器上运行时,邮箱提供商安全机制 会阻止异地登陆 ,只要去邮箱提供商允许就可以了2. 发送失败因为多人定制因为邮件内容不一样,所以不是同一封邮件,会额外开辟一个线程发送,可能会超过邮件提供商允许线程
2019年06月28日
3,169 阅读
3 评论
0 点赞
2019-06-04
优雅的处理 async/await 异常
每次使用 async/await 都包裹一层 try/catch ,很麻烦,这里提供另外一个思路更好的处理 async/await思考// 定义一个全局方法 async function errorCaptured(asyncFunc) { let args = [...arguments].slice(1); try { let res = await asyncFunc(...args); return [null, res]; } catch (error) { return [error, null]; } } // 一个示例异步函数 let asyncFunc = (a, b, c) => { return new Promise((resolve, reject) => { const num = Math.random().toFixed(1); if (num > 0.5) { resolve({ a, b, c }); } else { reject({ a, b, c }); } }); }; // 运行 (async () => { let [err, res] = await errorCaptured(asyncFunc, 1, 2, 3); if (res) { console.log("success:", res); } if (err) { console.log("error:", err); } })();await-to-jsawait-to-js 是作者 scopsy 运用 Go-lang 处理异常的灵感源码是用 ts 写的,短小精悍await-to-js ts 源码export function to<T, U = Error>( promise: Promise<T>, errorExt?: object ): Promise<[U | null, T | undefined]> { return promise .then<[null, T]>((data: T) => [null, data]) .catch<[U, undefined]>((err: U) => { if (errorExt) { Object.assign(err, errorExt); } return [err, undefined]; }); } export default to;await-to-js js 代码/** * @param { Promise } promise * @param { Object= } errorExt - Additional Information you can pass to the err object * @return { Promise } */ function to(promise, errorExt) { return promise .then(function(data) { return [null, data]; }) .catch(function(err) { if (errorExt) { Object.assign(err, errorExt); } return [err, undefined]; }); }重新看我的例子function to(promise, errorExt) { return promise .then(data => [null, data]) .catch(err => { if (errorExt) { Object.assign(err, errorExt); } return [err, undefined]; }); } // 一个示例异步函数 let asyncFunc = (a, b, c) => { return new Promise((resolve, reject) => { const num = Math.random().toFixed(1); if (num > 0.5) { resolve({ a, b, c }); } else { reject({ a, b, c }); } }); }; // 运行 (async () => { let [err, res] = await to(asyncFunc(1, 2, 3), { errorTxt: "这是一个错误" }); if (res) { console.log("success:", res); } if (err) { console.log("error:", err); } })();总结两种方法都可以,都是 Go-lang 异常处理的灵感,就性能来说 有一种说法说过多的try catch 影响性能。不过现在已经没啥影响,两种方法都可以!
2019年06月04日
3,328 阅读
1 评论
0 点赞
2019-02-20
从一道题浅说 JavaScript 的事件循环
任务队列首先我们需要明白以下几件事情:JS 分为同步任务和异步任务同步任务都在主线程上执行,形成一个执行栈主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。一旦执行栈中的所有同步任务执行完毕(此时 JS 引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。 setTimeout/Promise 等 API 便是任务源,而进入任务队列的是他们指定的具体执行任务。宏任务(macro)task(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。浏览器为了能够使得 JS 内部(macro)task 与 DOM 任务能够有序的执行,会在一个(macro)task 执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染,流程如下:(macro)task->渲染->(macro)task->...(macro)task 主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)微任务microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前 task 任务后,下一个 task 之前,在渲染之前。所以它的响应速度相比 setTimeout(setTimeout 是 task)会更快,因为无需等渲染。也就是说,在某一个 macrotask 执行完后,就会将在它执行期间产生的所有 microtask 都执行完毕(在渲染前)。microtask 主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)运行机制在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:执行一个宏任务(栈中没有就从事件队列中获取)执行过程中如果遇到微任务,就将它添加到微任务的任务队列中宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)当前宏任务执行完毕,开始检查渲染,然后 GUI 线程接管渲染渲染完毕后,JS 线程继续接管,开始下一个宏任务(从事件队列中获取)流程图如下:Promise 和 async 中的立即执行我们知道 Promise 中的异步体现在then和catch中,所以写在 Promise 中的代码是被当做同步任务立即执行的。而在 async/await 中,在出现 await 出现之前,其中的代码也是立即执行的。那么出现了 await 时候发生了什么呢?await 做了什么从字面意思上看 await 就是等待,await 等待的是一个表达式,这个表达式的返回值可以是一个 promise 对象也可以是其他值。很多人以为 await 会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上 await 是一个让出线程的标志。await 后面的表达式会先执行一遍,将 await 后面的代码加入到 microtask 中,然后就会跳出整个 async 函数来执行后面的代码。这里感谢@chenjigeng 的纠正:由于因为 async await 本身就是 promise+generator 的语法糖。所以 await 后面的代码是 microtask。所以对于本题中的async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); }等价于async function async1() { console.log("async1 start"); Promise.resolve(async2()).then(() => { console.log("async1 end"); }); }回到本题以上就本道题涉及到的所有相关知识点了,下面我们再回到这道题来一步一步看看怎么回事儿。首先,事件循环从宏任务(macrotask)队列开始,这个时候,宏任务队列中,只有一个 script(整体代码)任务;当遇到任务源(task source)时,则会先分发任务到对应的任务队列中去。所以,上面例子的第一步执行如下图所示:然后我们看到首先定义了两个 async 函数,接着往下看,然后遇到了 console 语句,直接输出 script start。输出之后,script 任务继续往下执行,遇到 setTimeout,其作为一个宏任务源,则会先将其任务分发到对应的队列中:script 任务继续往下执行,执行了 async1()函数,前面讲过 async 函数中在 await 之前的代码是立即执行的,所以会立即输出async1 start。遇到了 await 时,会将 await 后面的表达式执行一遍,所以就紧接着输出async2,然后将 await 后面的代码也就是console.log('async1 end')加入到 microtask 中的 Promise 队列中,接着跳出 async1 函数来执行后面的代码。script 任务继续往下执行,遇到 Promise 实例。由于 Promise 中的函数是立即执行的,而后续的 .then 则会被分发到 microtask 的 Promise 队列中去。所以会先输出 promise1,然后执行 resolve,将 promise2 分配到对应队列。script 任务继续往下执行,最后只有一句输出了 script end,至此,全局任务就执行完毕了。根据上述,每次执行完一个宏任务之后,会去检查是否存在 Microtasks;如果有,则执行 Microtasks 直至清空 Microtask Queue。因而在 script 任务执行完毕之后,开始查找清空微任务队列。此时,微任务中, Promise 队列有的两个任务async1 end和promise2,因此按先后顺序输出 async1 end,promise2。当所有的 Microtasks 执行完毕之后,表示第一轮的循环就结束了。第二轮循环开始,这个时候就会跳回 async1 函数中执行后面的代码,然后遇到了同步任务 console 语句,直接输出 async1 end。这样第二轮的循环就结束了。(也可以理解为被加入到 script 任务队列中,所以会先与 setTimeout 队列执行)第二轮循环依旧从宏任务队列开始。此时宏任务中只有一个 setTimeout,取出直接输出即可,至此整个流程结束。下面我会改变一下代码来加深印象。变式一在第一个变式中我将 async2 中的函数也变成了 Promise 函数,代码如下:async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { //async2做出如下更改: new Promise(function(resolve) { console.log("promise1"); resolve(); }).then(function() { console.log("promise2"); }); } console.log("script start"); setTimeout(function() { console.log("setTimeout"); }, 0); async1(); new Promise(function(resolve) { console.log("promise3"); resolve(); }).then(function() { console.log("promise4"); }); console.log("script end");可以先自己看看输出顺序会是什么,下面来公布结果:script start async1 start promise1 promise3 script end promise2 async1 end promise4 setTimeout在第一次 macrotask 执行完之后,也就是输出script end之后,会去清理所有 microtask。所以会相继输出promise2,async1 end ,promise4,其余不再多说。变式二在第二个变式中,我将 async1 中 await 后面的代码和 async2 的代码都改为异步的,代码如下:async function async1() { console.log("async1 start"); await async2(); //更改如下: setTimeout(function() { console.log("setTimeout1"); }, 0); } async function async2() { //更改如下: setTimeout(function() { console.log("setTimeout2"); }, 0); } console.log("script start"); setTimeout(function() { console.log("setTimeout3"); }, 0); async1(); new Promise(function(resolve) { console.log("promise1"); resolve(); }).then(function() { console.log("promise2"); }); console.log("script end");可以先自己看看输出顺序会是什么,下面来公布结果:script start async1 start promise1 script end promise2 setTimeout3 setTimeout2 setTimeout1在输出为promise2之后,接下来会按照加入 setTimeout 队列的顺序来依次输出,通过代码我们可以看到加入顺序为3 2 1,所以会按 3,2,1 的顺序来输出。变式三变式三是我在一篇面经中看到的原题,整体来说大同小异,代码如下:async function a1() { console.log("a1 start"); await a2(); console.log("a1 end"); } async function a2() { console.log("a2"); } console.log("script start"); setTimeout(() => { console.log("setTimeout"); }, 0); Promise.resolve().then(() => { console.log("promise1"); }); a1(); let promise2 = new Promise(resolve => { resolve("promise2.then"); console.log("promise2"); }); promise2.then(res => { console.log(res); Promise.resolve().then(() => { console.log("promise3"); }); }); console.log("script end");无非是在微任务那块儿做点文章,前面的内容如果你都看懂了的话这道题一定没问题的,结果如下:script start a1 start a2 promise2 script end promise1 a1 end promise2.then promise3 setTimeout最后原文:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7
2019年02月20日
1,921 阅读
0 评论
0 点赞
2019-02-19
Javascript ES2019中的8个新特性
前言JavaScript 不断改进和添加更多功能。TC39 已经完成并批准了 ES2019 的这 8 个功能,它有 4 个阶段,这些阶段是:Stage 0: StrawmanStage 1: ProposalsStage 2: DraftsStage 3: CandidatesStage 4: Finished/Approved以下链接可以查看Stage 0,Stage 1 – 3 和Final Stage可选的 Catch 绑定能够在不使用 catch 绑定的地方选择性地删除它try { // trying to use a new ES2019 feature // which may not be implemented in other browsers } catch (unused) { // revert back to old way }现在可以删除未使用的绑定try { ... } catch { ... }JSON 超集此提议的动机是 JSON 字符串可以包含未转义的 U + 2028 LINE SEPARATOR 和 U + 2029 PARAGRAPH SEPARATOR 字符,而 ECMAScript 字符串则不能。在 ES2019 之前,它会产生错误SyntaxError: Invalid or unexpected tokenconst LS = eval('"\u2028"'); const PS = eval("'\u2029'");符号说明在 ES2015 中引入符号,具有非常独特的功能。在 ES2019 中,它现在可以提供给定的描述。其目的是避免间接获得所提供的描述Symbol.prototype.toStringconst mySymbol = Symbol("myDescription"); console.log(mySymbol); // Symbol(myDescription) console.log(mySymbol.toString()); // Symbol(myDescription) console.log(mySymbol.description); // myDescriptionFunction.prototype.toString - 修订版我们之前已经在函数原型中使用了toString方法,但是在 ES2019 中它已被修改并包含函数内的注释,请注意它在Arrow Functions上不起作用。function /* comment */ foo /* another comment */() {} // Before console.log(foo.toString()); // function foo(){} // Now ES2019 console.log(foo.toString()); // function /* comment */ foo /* another comment */ (){} // Arrow Syntax const bar /* comment */ = /* another comment */ () => {}; console.log(bar.toString()); // () => {}Object.fromEntries它是 Object.entries 的反向方法,它也是克隆对象的方法之一const obj = { prop1: 1, prop2: 2 }; const entries = Object.entries(obj); console.log(entries); // [ [ 'prop1', 1 ], [ 'prop2', 2 ] ] const fromEntries = Object.fromEntries(entries); console.log(fromEntries); // Object { prop1: 1, prop2: 2 } console.log(obj === fromEntries); // false注意:任何嵌入式对象/数组都只是通过引用复制。格式良好的 JSON.stringify这也是由同一个人提出的,并且与 JSON 超集特征有关 。ES2019 不是将未配对的代理代码点作为单个 UTF-16 代码单元返回,而是用 JSON 转义序列表示它们// Before console.log(JSON.stringify("\uD800")); // "�" // Now ES2019 console.log(JSON.stringify("\uD800")); // "\ud800"String.prototype trimStart 和 trimEnd我们已经在 String 原型中使用了trim方法,它删除了字符串开头和结尾之间的空格。但是现在开始介绍 ES2019 的 trimStart和trimEnd// Trim const name = " Codedam "; console.log(name.trim()); // "Codedam" // Trim Start const description = " Unlocks Secret Codes "; console.log(description.trimStart()); // "Unlocks Secret Codes " // Trim End const category = " JavaScript "; console.log(category.trimEnd()); // " JavaScript"Array.prototype flat 和 flatMapflat方法创建一个新数组,所有子数组元素以递归方式连接到指定的深度。 默认情况下,深度为 1,使数组上第一层嵌套数组变平。const arr = [1, 2, [3, 4, [5, 6]]]; arr.flat(); // [1, 2, 3, 4, [5, 6]] arr.flat(2); // [1, 2, 3, 4, 5, 6] // You can use Infinity to flatten all the nested arrays no matter how deep the array is const arrExtreme = [1, [2, [3, [4, [5, 6, 7, [8, 9]]]]]]; arrExtreme.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9]flatMap 类似于 flat 并且与 map 相关,其中它映射数组然后将其展平const arr = ["Codedam", "is Awsome", "!"]; const mapResult = arr.map(item => item.split(" ")); console.log(mapResult); // [ [ 'Codedam' ], [ 'is', 'Awsome' ], [ '!' ] ] const flatMapResult = arr.flatMap(chunk => chunk.split(" ")); console.log(flatMapResult); // ['Codedam', 'is', 'Awsome', '!'];其他强调一下现在 Stage 3 中的一些有用的即将推出的功能。globalThisBigIntimport()Legacy RegExPrivate instance methods and accessorsString.prototype.matchAll最后原文:8 NEW FEATURES in JavaScript ES2019作者:Rienz
2019年02月19日
1,095 阅读
0 评论
0 点赞
2019-02-13
2019前端的展望与目标
前端发展依旧迅速 各种技术,我快学不动啦> 前端发展依旧迅速 各种技术,我快学不动啦?????React Hooksreact 都核心思想就是,将一个页面拆成一堆独立的,可复用的组件,并且用自上而下的单向数据流的形式将这些组件串联起来。但假如你在大型的工作项目中用 react,你会发现你的项目中实际上很多 react 组件冗长且难以复用。尤其是那些写成 class 的组件,它们本身包含了状态(state),所以复用这类组件就变得很麻烦。React Hooks 要解决的问题是状态共享,是继 render-props 和 higher-order components 之后的第三种状态共享方案,不会产生 JSX 嵌套地狱问题。React Hooks 带来的好处不仅是 “更 FP,更新粒度更细,代码更清晰”,还有如下三个特性:多个状态不会产生嵌套,写法还是平铺的(renderProps 可以通过 compose 解决,可不但使用略为繁琐,而且因为强制封装一个新对象而增加了实体数量)。Hooks 可以引用其他 Hooks。更容易将组件的 UI 与状态分离。文章:一篇看懂 React Hooks基于 Redux 的状态管理从 2013 年 React 发布至今已近 6 个年头,前端框架逐渐形成 React/Vue/Angular 三足鼎立之势。几年前还在争论单向绑定和双向绑定孰优孰劣,现在三大框架已经不约而同选择单向绑定,双向绑定沦为单纯的语法糖。框架间的差异越来越小,加上 Ant-Design/Fusion-Design/NG-ZORRO/ElementUI 组件库的成熟,选择任一你熟悉的框架都能高效完成业务。那接下来核心问题是什么?我们认为是 状态管理。简单应用使用组件内 State 方便快捷,但随着应用复杂度上升,会发现数据散落在不同的组件,组件通信会变得异常复杂。我们先后尝试过原生 Redux、分形 Fractal 的思路、自研类 Mobx 框架、Angular Service,最终认为 Redux 依旧是复杂应用数据流处理最佳选项之一。庆幸的是除了 React 社区,Vue 社区有类似的 Vuex,Angular 社区有 NgRx 也提供了几乎同样的能力,甚至 NgRx 还可以无缝使用 redux-devtools 来调试状态变化。原则方法引发的问题Single source of truth组件 Stateless,数据来源于 Store如何组织 StoreState is read-only只能通过触发 action 来改变 Stateaction 数量膨胀,大量样板代码Changes are made with pure functionsReducer 是纯函数副作用如何处理,大量样板代码如何组织 Action?action type 需要全局惟一,因此我们给 action type 添加了 prefix,其实就是 namespace 的概念为了追求体验,请求(Fetch)场景需要处理 3 种状态,对应 LOADING/SUCCESS/ERROR 这 3 个 action,我们通过 FetchTypes 类型来自动生成对应到 3 个 action如何组织 Store/Reducer?reducer 和 view 不必一一对应,应用中同时存在组件树和状态树,按照各自需要去组织,通过 connect 来绑定状态树的一个或多个分支到组件树通过构造一些预设数据类型来减少样板代码。对于 Fetch 返回的数据我们定义了 AsyncTuple 这种类型,减少了样板代码明确的组织结构,第 1 层是 ROOT,第 2 层是各个页面,第 3 层是页面内的卡片,第 4 层是卡片的数据,这样划分最深处基本不会超过 5 层hapi.jsHapi(简称 HAPP API,发音为“happy”) 是一个容易使用的以配置为中心的框架,内置支持输入验证,缓存,身份验证以及用于构建 Web 和服务应用程序的其他基本工具。hapi 使开发人员能够专注于以高度模块化和规范性的方式编写可重用的应用程序逻辑。Hapi 是一个抽象了现有 Node API 的新框架。Express 更老,更成熟。Express 代码看起来更像本地 Node。 hapi.js 还有个优点可以用过插件 hapi-swagger,自动生成 swagger 文档免除了 api 与文档不同步的困扰GraphQLGraphQL 已被 GitHub 等技术引领者采用。 然而,它并没有像一些预测的那样快速地飞涨。 据 JS 状态调查 显示,只有 1/5 的前端开发人员使用过 GraphQL ,但是有 62.5% 的开发人员已经听说过它并希望使用它,相当惊人。TypeScriptTypeScript 可能是 JavaScript 的未来(但对于 Flow 来说就不一定了)。事实上,在 Stack Overflow 调查中,TypeScript 的评分高于 JavaScript 本身,为 67% ,而最受喜爱的语言为 61.9% 。 根据 JS 的状态调查,超过 80% 的开发人员希望使用 TS 或已经使用它并享受它。 对于 Flow,只有 34% 的开发人员正在使用它或想要使用它。根据所有迹象,TypeScript 是 JS 中静态类型的首选解决方案,许多人选择使用普通的 JavaScript 。 在 2018 年,TS 的 npm 下载数量大幅增长,而 Flow 保持不变。 TypeScript 看起来正在从狂热追随者转向广泛采用。Jest 也正在从 Flow 切换到 TS,Vue 3.0 也开始用 TS 重写。如果你还没有使用,可以考虑切换,绝对能给项目带来很大提升。TS 最大的优势是它提供了强大的静态分析能力,结合 TSLint 能对代码做到更加严格的检查约束。传统的 EcmaScript 由于没有静态类型,即使有了 ESLint 也只能做到很基本的检查,一些 type 问题可能线上出了 Bug 后才被发现。学习网址:TypeScript 中文网WebpackWebpack 3 发布仅 8 个月后,版本 4 发布了。 Webpack 4 继续推动简化和更快的构建,声称高达 98% 的改进。 它选择合理的默认值,在没有插件的情况下处理更多功能,并且不再需要使用配置文件。 Webpack 现在还支持 WebAssembly,并允许您直接导入 WebAssembly 文件。而且 Webpack@5.0.0-alpha.10 也已发布。推荐阅读:Webpack 4 教程:从零配置到生产发布(2018)展望 2019 年TypeScript 开始成为标准 JavaScript 的默认选择。随着基础的建立和不断推动的 Web 体验改进,WebAssembly 将开始看到更多的生命力。React 保持领先,但 Vue 和 Angular 用户会继续增长。CSS-in-js 可能会成为默认的样式化方法,而不是普通的 CSS。毫不奇怪,性能仍然是关注的焦点,诸如 PWAs 和代码分割之类的事情成为每个应用程序的标准。在采用 PWA 的基础上,web 变得更加本地化,具有离线功能和无缝的桌面/移动体验。我们继续看到 CLI 工具和框架的增长,以继续抽象出构建应用程序的繁琐方面,允许开发人员专注于生成功能。更多的公司采用具有统一代码库的移动解决方案,如 React Native 或 Flutter 。Containerization 的影响(即 Docker, Kubernetes)在前端过程中变得更为普遍。GraphQL 在采用方面会有大的飞跃,并在更多公司中得到应用。虚拟现实使用 A-Frame,React VR 和 Google VR 等库向前迈进。具体计划:学习 Typescript GraphQL深入 Node.js坚持更新博客,更新基础知识、技术总结和项目开发中遇到的问题更加深入的去理解 Vue 和 Node,同时实际尝试自己写一下服务端渲染至少写一个 React Reduce 相关的项目看看 deno 这种未来可能替代 Node 的新东西
2019年02月13日
2,263 阅读
0 评论
0 点赞
2019-01-23
JS的防抖与节流
防抖 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo</title> <style type="text/css"> body{ height:3000px; } </style> </head> <body> <script> function fn(){ console.log("invoke fn function"); }; document.body.onscroll = fn; </script> </body> </html>我们打开浏览器,然后打开控制台,滚动鼠标的时候,控制台频繁的打印“invoke fn function”。我们这里为了显示,所以没有涉及到相关dom操作,但是实际开发过程中,更多场景是操作dom,那么将会使你的浏览器瞬间卡卡的感觉,有没有法子来限制一下fn的调用频率呢,答案是可以的,对此,我们将上面的代码改变如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo</title> <style type="text/css"> body{ height:3000px; } </style> </head> <body> <script> function fn(){ console.log("invoke fn function"); }; document.body.onscroll = avoidShak(fn,300); // 限制调用频率 function avoidShak(fn,time){ //设置定时器 let timer; return function(...args){ //清空上一次的定时器 clearTimeout(timer); //获取执行环境的上下文 let context = this; let _arguments = args; timer = setTimeout(()=>{ fn.apply(context,_arguments); },time); }; }; </script> </body> </html>我们现在发现打开浏览器,滚动的时候不会那么频繁的调用fn函数了,只有当我们滚动的间隙稍微停顿300毫秒的时候才会调用一次,这样我们就做到了降低函数调用的频率了。原理它的原理其实很简单:1 用闭包实现一个timer变量,用来保存上一次调用函数的定时器id;2 我们不是直接调用函数,而是中间需要一个间隔,如果两次调用之间的时间差小于我们传递的值,那么清空上一次的调用值;3 我们每一次调用的时候都清除一下上一次的调用定时器id,这样就保证了,如果间隔时间小于我们设置的值,那么上一次函数一定不会调用,从而达到了降低调用频率的效果。上面这种通过设置定时器保证一段时间内事件回调函数只能执行一次的做法在javascript业界有一个专业的术语称谓——防抖!上面的防抖操作,我们发现减少了回调函数调用的频率,但是它有一点点瑕疵:如果我们一直触发事件,回调函数只会在我们停止触发事件并达到了设置的时间间隔之后才会调用一次,也就是说在我们触发事件的过程中,回调函数一直没有执行,这在某些情况下,会跟实际业务需求不符。实际业务需求可能是,1 减少触发频率;2 但不能中间很大一段时间一直不执行。ok,那么此时我们就需要通过函数节流来实现!节流把下面的代码在浏览器中打开,并水平缩放浏览器,看效果:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo</title> <style type="text/css"> *{ margin:0px; padding:0px; vertical-align:baseline; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; } .box{ width:1000px; height: 500px; background-color:#ccc; color:#fff; font-size:20px; padding:15px 20px; text-align:left; } </style> </head> <body> <div class="box" id="box">i am a div box,please resize the browser horizontally!</div> <script type="text/javascript"> let dom = document.getElementById('box'); function setWidth(){ let windowWidth = window.innerWidth; if(windowWidth>=1000)return; dom.style.width = windowWidth + 'px'; }; //采用防抖实现限制回调函数调用频率 function avoidShak(fn,time){ let timer; return function(...args){ clearTimeout(timer); let context = this; let _arguments = args; timer = setTimeout(()=>{ fn.apply(context,_arguments); },time); }; }; window.onresize = avoidShak(setWidth,300); </script> </body> </html>先来说下上面的页面需求:打开页面,在浏览器水平缩放的过程中,如果浏览器宽度不小于1000,那么不做任何事,否则设置dom的宽度为当前浏览器的宽度。但是我们发现,我们在缩放的过程中,dom的尺寸并未做相应的更新,只有在停止缩放一段时间后,dom的宽度才更新到浏览器的宽度,这跟业务需求不符,于是我们代码改变如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo</title> <style type="text/css"> *{ margin:0px; padding:0px; vertical-align:baseline; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; } .box{ width:1000px; height: 500px; background-color:#ccc; color:#fff; font-size:20px; padding:15px 20px; text-align:left; } </style> </head> <body> <div class="box" id="box">i am a div box,please resize the browser horizontally!</div> <script type="text/javascript"> let dom = document.getElementById('box'); function setWidth(){ let windowWidth = window.innerWidth; if(windowWidth>=1000)return; dom.style.width = windowWidth + 'px'; }; //采用节流实现限制回调函数调用频率 function ttrottle(fn,time){ let isNeedInvoke = true; return function(...args){ if(!isNeedInvoke)return; let context = this; let _arguments = args; isNeedInvoke = false; setTimeout(()=>{ fn.apply(context,_arguments); isNeedInvoke = true; },time); }; }; window.onresize = ttrottle(setWidth,300); </script> </body> </html>我们发现经过这样改过之后,dom的宽度变成在我们缩放的过程中也会更新了,满足了我们业务需求。好了,我们来简单介绍下什么是节流!节流其实从名字上就知道它的含义——就是限制函数调用频率。主要有两种方式实现:* 法一:时间差,原理无非就是两次调用之间的时间差小于设置时,那么不调用,反之调用。代码如下:function ttrottle(fn,time){ //上一次调用时间 let lastInvokeTime = new Date().getTime(); //当前调用时间 let currentInvokeTime; return function(...args){ currentInvokeTime = new Date().getTime(); if(currentInvokeTime - lastInvokeTime <= time)return; let context = this; let _arguments = args; lastInvokeTime = currentInvokeTime; fn.apply(context,_arguments); }; };* 法二:定时器实现,原理就是设置时间间隔,如果达不到时间间隔,就清除上一次调用回调定时器id。代码如下:function ttrottle(fn,time){ let isNeedInvoke = true; return function(...args){ if(!isNeedInvoke)return; let context = this; let _arguments = args; isNeedInvoke = false; setTimeout(()=>{ fn.apply(context,_arguments); isNeedInvoke = true; },time); }; };ok,到这里大家应该知道节流的是干嘛以及原理了吧。最后,再来总结一下防抖和节流的区别:防抖和节流的相同点就是限制回调函数调用频率;防抖在一段时间内,回调函数只会调用一次,即触发事件的最后一次;节流在一段时间内,会每隔一段时间调用一次;下面是具体函数这只是简单的实现方法函数防抖函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件function debounce(fn, wait) { var timeout = null; return function() { if(timeout !== null) clearTimeout(timeout); timeout = setTimeout(fn, wait); } } // 处理函数 function handle() { console.log(Math.random()); } // 滚动事件 window.addEventListener('scroll', debounce(handle, 1000));函数节流函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。如下,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。var throttle = function(func, delay) { var prev = Date.now(); return function() { var context = this; var args = arguments; var now = Date.now(); if (now - prev >= delay) { func.apply(context, args); prev = Date.now(); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000));总结函数防抖将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。函数节流使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。区别函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
2019年01月23日
1,812 阅读
0 评论
0 点赞
2019-01-20
BrowserHistory刷新页面404问题
使用 React 开发新项目时,遇见了刷新页面,直接访问二级或三级路由时,访问失败,出现 404 或资源加载异常的情况,本篇针对此问题进行分析并总结解决方案。发现问题使用webpack-dev-server做本地开发服务器时,正常情况只需要简单使用webpack-dev-server指令启动即可,但是当项目处于以下两种情况时,往往需要有嵌套路由和异步加载路由:我们使用 react-router 这种路由库构建单页面应用路由;使用 html-webpack-plugin 插件动态将加载 js 的script标签注入 html 文档;这时,访问首页是可以正常加载页面和 js 等文件的,但是当我们需要访问二级甚至三级路由或者刷新页面时,如/page1时,可能会出现两种情况:页面加载失败,返回 Cannot Get(404);服务响应,但是没有返回 webpack 处理输出的 html 文件,导致无法加载 js 资源;分析解决问题发现问题后,我们就要开始分析,我们判断这个问题一般是两方面原因造成:react-router路前端由配置;webpack-dev-server服务配置;发现文档中提到了使用browserHistory时,会创建真实的URL,处理初始/请求没有问题,但是对于跳转路由后,刷新页面或者直接访问该 URL 时,会发现无法正确相应.一下是几种服务器配置解决方式:nodeconst express = require('express') const path = require('path') const port = process.env.PORT || 8080 const app = express() // 通常用于加载静态资源 app.use(express.static(__dirname + '/public')) // 在你应用 JavaScript 文件中包含了一个 script 标签 // 的 index.html 中处理任何一个 route app.get('*', function (request, response){ response.sendFile(path.resolve(__dirname, 'public', 'index.html')) }) app.listen(port) console.log(\"server started on port \" + port);Nginxserver { ... location / { try_files $uri /index.html } }ApacheRewriteBase / RewriteRule ^index\\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L]以下都是针对服务器的配置,本地调试,只是使用了webpack-dev-server的内置服务,但是我们已经找到问题所在了,就是路由请求无法匹配返回 html 文档,所以接下来就该去webpack-dev-server文档查找解决方式了.这里也分两种情况:没有修改 output.publicPath,即 webpack 配置文件中没有声明值,属于默认情况;设置了 output.publicPath 为自定义值;webpack 如下配置:module.exports = { //... devServer: { historyApiFallback: true } };
2019年01月20日
3,374 阅读
0 评论
0 点赞
2019-01-18
Vue中的eventBus使用
使用 eventBus在项目入口文件main.js中 加入如下代码// EventBus window.eventBus = new Vue();这是引入全局变量eventBus准备两个需要通信的 vue 组件About.vue<template> <div class="about"> <h1>This is an {{msg}} page</h1> <h2>other :{{other}}</h2> </div> </template> <script> export default { data() { return { msg: "about", other: "old other" }; }, created() { eventBus.$on("postData", data => { this.other = data; }); } }; </script>代码中 data 里的 other 值一开始是 old other,这个组件需要另一个组件返回过来的值来进行更新。如下代码则是取回 键值是postData 的数据eventBus.$on("postData", data => { this.other = data; });Test.vue<template> <div class="test"> <h1>This is an {{msg}} page</h1> </div> </template> <script> export default { data() { return { msg: "test" }; }, methods: {}, destroyed() { eventBus.$emit("postData", "new other"); } }; </script>以下代码是在组件销毁之前给键值postData 注入值 new othereventBus.$emit("postData", "new other");效果所以效果就是一开始进入 about 页面时 other 值为 old other 进入 test 页面 再返回时 other 值则变成了 new other
2019年01月18日
282 阅读
0 评论
0 点赞
1
...
3
4
5