<!-- 逗号分隔字符串 -->
< keep-alive include = " a,b" >
< component :is = " view" > </ component>
</ keep-alive>
<span class="token comment"><!-- 正则表达式 (使用 `v-bind`) --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>keep-alive</span> <span class="token attr-name">:include</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/a|b/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>component</span> <span class="token attr-name">:is</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>view<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>component</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>keep-alive</span><span class="token punctuation">></span></span>
<span class="token comment"><!-- 数组 (使用 `v-bind`) --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>keep-alive</span> <span class="token attr-name">:include</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>[<span class="token punctuation">'</span>a<span class="token punctuation">'</span>, <span class="token punctuation">'</span>b<span class="token punctuation">'</span>]<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>component</span> <span class="token attr-name">:is</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>view<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>component</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>keep-alive</span><span class="token punctuation">></span></span>
但更多场景中,我们会使用keep-alive
来缓存路由 :
< keep-alive include = ' a' >
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>router-view</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>router-view</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>keep-alive</span><span class="token punctuation">></span></span>
匹配规则:
首先匹配组件的name选项 ,如果name
选项不可用。
则匹配它的局部注册名称 。 (父组件 components
选项的键值)
匿名组件,不可匹配 。
比如路由组件没有name
选项,并且没有注册的组件名。
只能匹配当前被包裹的组件,不能匹配更下面嵌套的子组件 。
比如用在路由上,只能匹配路由组件的name
选项,不能匹配路由组件里面的嵌套组件的name
选项。
文档:<keep-alive>
不会在函数式组件中正常工作 ,因为它们没有缓存实例。
exclude
的优先级大于include
也就是说:当include
和exclude
同时存在时,exclude
生效,include
不生效。
< keep-alive include = " a,b" exclude = " a" >
<!--只有a不被缓存-->
< router-view> </ router-view>
</ keep-alive>
当组件被exclude
匹配,该组件将不会被缓存,不会调用activated
和 deactivated
。
# 组件生命周期钩子:
关于组件的生命周期,是时候放出这张图片了:
这张图片已经讲得很清楚了,很多人这部分也很清楚了,大部分生命周期并不会用到,这里提一下几点:
ajax请求最好放在created
里面 ,因为此时已经可以访问this
了,请求到数据就可以直接放在data
里面。
这里也碰到过几次,面试官问:ajax请求应该放在哪个生命周期。
关于dom的操作要放在mounted
里面 ,在mounted
前面访问dom会是undefined
。
每次进入/离开组件都要做一些事情,用什么钩子:
不缓存:
进入的时候可以用created
和mounted
钩子,离开的时候用beforeDestory
和destroyed
钩子,beforeDestory
可以访问this
,destroyed
不可以访问this
。
缓存了组件:
缓存了组件之后,再次进入组件不会触发beforeCreate
、created
、beforeMount
、 mounted
,如果你想每次进入组件都做一些事情的话,你可以放在activated
进入缓存组件的钩子中 。
同理:离开缓存组件的时候,beforeDestroy
和destroyed
并不会触发,可以使用deactivated
离开缓存组件的钩子来代替。
# 触发钩子的完整顺序:
将路由导航、keep-alive
、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件:
beforeRouteLeave
:路由组件的组件离开路由前钩子,可取消路由离开。
beforeEach
: 路由全局前置守卫,可用于登录验证、全局路由loading等。
beforeEnter
: 路由独享守卫
beforeRouteEnter
: 路由组件的组件进入路由前钩子。
beforeResolve
:路由全局解析守卫
afterEach
:路由全局后置钩子
beforeCreate
:组件生命周期,不能访问this
。
created
:组件生命周期,可以访问this
,不能访问dom。
beforeMount
:组件生命周期
deactivated
: 离开缓存组件a,或者触发a的beforeDestroy
和destroyed
组件销毁钩子。
mounted
:访问/操作dom。
activated
:进入缓存组件,进入a的嵌套子组件(如果有的话)。
执行beforeRouteEnter回调函数next。
# 小结
Vue提供了很多钩子,但很多钩子我们几乎不会用到,只有清楚这些钩子函数的触发顺序以及背后的一些限制等,这样我们才能够正确的使用这些钩子,希望看了本文的同学,能对这些钩子有更加清晰的认识,使用起来更加得心应手。
博客链接
# H5 notification浏览器桌面通知
Notification
是HTML5新增的API,用于向用户配置和显示桌面通知。上次在别的网站上看到别人的通知弹窗,好奇之余也想知道如何实现的。实际去查一下发现并不复杂,且可以说比较简单,故写篇博客分享给大家,希望能帮你们了解这个API。
# npm包:
我还发了一个npm包:notification-Koro1 ,非常轻量简洁,觉得不错的话,点个Star吧~
# chrome下Notification
的表现:
以谷歌为例,一开始需要用户允许通知:
允许通知之后,显示的通知长这样:
# Notification
特性
该通知是脱离浏览器的,即使用户没有停留在当前标签页,甚至最小化了浏览器,也会在主屏幕的右上角显示通知,然后在一段时间后消失 。
我们可以监听通知的显示,点击,关闭等事件 ,比如点击通知打开一个页面。
博客 、前端积累文档 、公众号 、GitHub
# 栗子:去各个网站里面的控制台去运行
API的具体细节,等下再说,先试试这个API~
下面是一个简单的栗子,大家可以先在各个网站的控制台里面运行查看Notification
的效果 :
var options = {
dir: "auto" , // 文字方向
body: "通知:OBKoro1评论了你的朋友圈" , // 通知主体
requireInteraction: true , // 不自动关闭通知
// 通知图标
icon: "https://upload-images.jianshu.io/upload_images/5245297-818e624b75271127.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
} ;
notifyMe ( '这是通知的标题' , options) ;
function notifyMe ( title, options) {
// 先检查浏览器是否支持
if ( ! window. Notification) {
console. log ( '浏览器不支持通知' ) ;
} else {
// 检查用户曾经是否同意接受通知
if ( Notification. permission === 'granted' ) {
var notification = new Notification ( title, options) ; // 显示通知
} else if ( Notification. permission === 'default' ) {
// 用户还未选择,可以询问用户是否同意发送通知
Notification. requestPermission ( ) . then ( permission => {
if ( permission === 'granted' ) {
console. log ( '用户同意授权' ) ;
var notification = new Notification ( title, options) ; // 显示通知
} else if ( permission === 'default' ) {
console. warn ( '用户关闭授权 未刷新页面之前 可以再次请求授权' ) ;
} else {
// denied
console. log ( '用户拒绝授权 不能显示通知' ) ;
}
} ) ;
} else {
// denied 用户拒绝
console. log ( '用户曾经拒绝显示通知' ) ;
}
}
}
# 浏览器支持:
MDN :目前Notification
除了IE浏览器不支持外, 其他浏览器都已支持桌面通知,移动端浏览器基本都未支持。
因为兼容性问题,所以在使用Notification
之前,我们需要查看浏览器是否支持Notification
这个API:
if ( window. Notification) {
// 桌面通知的逻辑
}
# 通知权限:
为了避免网站滥用通知扰民,在向用户显示通知之前,需要经过用户同意。
Notification.permission
用于表明当前通知显示的授权状态 ,它有三个值:
default
: 默认值,用户还未选择
granted
: 用户允许该网站发送通知
denied
: 用户拒绝该网站发送通知
# 检测权限:
检测浏览器是否支持Notification
之后,需要检测一下用户通知权限。
if ( Notification. permission === 'granted' ) {
console. log ( '用户曾经同意授权' ) ;
// 随时可以显示通知
} else if ( Notification. permission === 'default' ) {
console. log ( '用户还未选择同意/拒绝' ) ;
// 下一步请求用户授权
} else {
console. log ( '用户曾经拒绝授权 不能显示通知' ) ;
}
# 请求权限
当Notification.permission
为default
的时候,我们需要使用Notification.requestPermission()
来请求用户权限。
Notification.requestPermission()
基于promise语法,then的回调函数参数是用户权限的状态Notification.permission
的值。
Notification. requestPermission ( ) . then ( permission => {
if ( permission === 'granted' ) {
console. log ( '用户同意授权' ) ;
// 随时可以显示通知
} else if ( permission === 'default' ) {
console. log ( '用户关闭授权 可以再次请求授权' ) ;
} else {
console. log ( '用户拒绝授权 不能显示通知' ) ;
}
} ) ;
// 老版本使用的是回调函数机制:Notification.requestPermission(callback); 参数一样
# 推送通知
当Notification.permission
为granted
时,请求到用户权限之后,不必立即发送通知,可以在任意时刻,以任意形式来发送通知。
const options = { } ; // 传空配置
const title = '这里是标题' ;
const notification = new Notification ( title, options) // 显示通知
上面这段代码就可以显示一个简单的通知了,只要用户允许你弹窗。
# Notification
的参数:
title:通知的标题
options:通知的设置选项(可选)。
body:字符串。通知的body内容。
tag:代表通知的一个识别标签,相同tag时只会打开一个通知窗口 。
icon:字符串。要在通知中显示的图标的URL。
data:想要和通知关联的数据,可以在new Notification
返回的实例中找到。
renotify: 布尔值。相同tag,新通知出现的时候是否替换之前的(开启此项,tag必须设置)。
requireInteraction:布尔值。通知不自动关闭,默认为false(自动关闭)。
还有一些不太重要的配置可以看张鑫旭老师的博客 和MDN 的介绍
requireInteraction : 保持通知不自动关闭
默认值为false,通知会在三四秒之后自动关闭。
当设置为true
,并且当有超过两个通知(new Notification(title, options)
)时,会出现如下图的通知叠加状态。
这种情况显然,我们只能默认操作最后一个通知,除非你把每个通知返回的实例都保存下来。
我发布的npm包:notification-koro1 ,可以自定义一定的时间间隔自动关闭不自动关闭的通知,也可以一次性关闭所有通知
PS:如果没有触发叠加,很可能是因为你两次通知的tag配置项是相同的(相同tag只能出现一个弹窗)。
PS: safari下不支持该选项,默认自动关闭
renotify :相同
默认值为false,chorme下相同tag的通知不替换,还是老的通知
设置为true
, 两个相同tag的通知,新通知替换之前旧的通知。
注意 :使用renotify
,必须要同时设置tag
选项,否则将会报错 。
PS: safari下不支持该选项,默认两个相同tag的通知,新通知替换之前旧的通知。
# Notification
的实例:
生成通知,会返回一个实例,如下:
const instanceNotification = new Notification ( title, options)
instanceNotification
就是当前通知的实例,在该实例上,我们可以查询该通知的配置,监听事件,调用实例方法 。
下文都以instanceNotification
指代通知返回的实例。
# 通知的配置:
在通知实例上可以读取到设置通知时的所有配置 ,比如:
通知标题:instanceNotification. title
、通知内容:instanceNotification. body
、通知图标:instanceNotification. icon
等。
PS: 这些属性都是只读的,不能删除,不能修改,不能遍历。
# 事件处理:
我们可以使用通知的实例来监听通知的事件:
click
: 用户点击通知时被触发
show
: 通知显示的时候被触发
error
: 通知遇到错误时被触发
close
: 用户关闭通知时被触发
instanceNotification. onclick = e => {
// do something 可以是:打开网址,发请求,关闭通知等
}
注意 :最好是一发出通知就立即监听事件,否则有些事件可能一开始没被触发或永远不会触发。
例如:用定时器5秒后才监听通知的点击和显示事件,则永远不会触发通知显示的回调,点击事件在5秒后才可以正常起作用但会错误五秒之前用户的点击。
# 关闭通知
instanceNotification. close ( )
没有设置不自动关闭的话,chrome通知将会在4.5秒左右自动关闭通知,safari则是5秒钟(无法设置不自动关闭)。
notification没有定时控制通知多久后消失的功能,当出现多个通知,也无法统一关闭。
这两个问题,在我发布的NPM包:notification-koro1 中,都解决掉了,并提供更清晰的回调
# 应用场景
即时通讯软件(邮件、聊天室)
体育赛事结果彩票/抽奖结果
新闻网站重大新闻通知
网站的重大更新,重大新闻等。
# notification其他
这里是一些API/浏览器细节,以及可能会遇到的问题,可以先不看,等真正遇到了,回头再来看。
# 用户拒绝显示通知:
一旦用户禁止网站显示通知,网站就不能再请求用户授权显示通知,需要用户去设置中更改。
chrome浏览器的通知设置位置:设置>高级>内容设置>通知
saafari浏览器:偏好设置>网站>通知>找到网站>修改权限/恢复默认
# 关闭请求权限:
在chorme浏览器中:当用户关闭请求权限的弹窗(右上角的叉叉),页面还没刷新,我们可以再次向用户请求权限。页面刷新过后,浏览器默认用户拒绝 。
在safari浏览器下,没有关闭请求权限的选项,用户必须选择同意/拒绝。
# icon不显示问题:
可能是网站进行了同源限制(比如github),不是域名下面的图片,会报错,不能调用。
# tag:
tag
相同的通知,同时只能出现一个,老通知是否会被覆盖取决于:renotify
配置和浏览器。
chrome下:当通知关闭之后,上次出现过的tag在一段时间内,不能再出现 ,比如刷新页面再请求相同tag的通知。(在safari下正常出现)
# safari下面不能显示icon
在safari下面,同一个网站(比如谷歌),同样的代码,chorme可以正常显示icon,safari却没有icon,也没有报错。
谷歌之后发现,在stack overflow 里面看到safari只支持body和tag选项,并不支持icon选项 。
# 连续触发
在safari和chrome下短时间内连续触发通知(不设tag
,不设requireInteraction
),会出现如下表现:
这个表现,通知没有icon、标题、内容,就显得没有意义了 ,浏览器以这种形式,限制开发者不要频繁打扰用户。
# notification-Koro1:
试一下notification-Koro1 啦, 持续维护,简单方便~
# 结语
本文写的比较细,可以先mark一下,然后以后真正用到这个API了,可以先通过文中的栗子,然后再查找对应的内容。
还有就是注意浏览器间的差异,我自己就试了chrome和safari,然后这两个浏览器在实现细节上有很多不一样的地方,开发的时候注意一下。
博客 、前端积累文档 、公众号 、GitHub
参考资料:
notification-Koro1
简单了解HTML5中的Web Notification桌面通知
Notification MDN
HTML5 桌面通知:Notification API
博客链接
# 手摸手教你写个ESLint插件以及了解ESLint的运行原理
这篇文章目的是介绍如何创建一个ESLint插件和创建一个ESLint
rule
,用以帮助我们更深入的理解ESLint的运行原理,并且在有必要时可以根据需求创建出一个完美满足自己需求的Lint规则。
# 插件目标
禁止项目中setTimeout
的第二个参数是数字。
PS: 如果是数字的话,很容易就成为魔鬼数字,没有人知道为什么是这个数字, 这个数字有什么含义。
# 使用模板初始化项目:
# 1. 安装NPM包
ESLint官方为了方便开发者开发插件,提供了使用Yeoman模板(generator-eslint
)。
对于Yeoman我们只需知道它是一个脚手架工具,用于生成包含指定框架结构的工程化目录结构。
npm install - g yo generator- eslint
# 2. 创建一个文件夹:
mkdir eslint- plugin- demo
cd eslint- plugin- demo
# 3. 命令行初始化ESLint插件的项目结构:
下面进入命令行交互流程,流程结束后生成ESLint插件项目框架和文件。
? What is your name? OBKoro1
? What is the plugin ID ? korolint // 这个插件的ID是什么
? Type a short description of this plugin: XX 公司的定制ESLint rule // 输入这个插件的描述
? Does this plugin contain custom ESLint rules? Yes // 这个插件包含自定义ESLint规则吗?
? Does this plugin contain one or more processors? No // 这个插件包含一个或多个处理器吗
// 处理器用于处理js以外的文件 比如.vue文件
create package . json
create lib/ index. js
create README . md
现在可以看到在文件夹内生成了一些文件夹和文件,但我们还需要创建规则具体细节的文件。
# 4. 创建规则
上一个命令行生成的是ESLint插件的项目模板,这个命令行是生成ESLint插件具体规则的文件。
yo eslint: rule // 生成 eslint rule的模板文件
创建规则命令行交互:
? What is your name? OBKoro1
? Where will this rule be published? ( Use arrow keys) // 这个规则将在哪里发布?
❯ ESLint Core // 官方核心规则 (目前有200多个规则)
ESLint Plugin // 选择ESLint插件
? What is the rule ID ? settimeout- no- number // 规则的ID
? Type a short description of this rule: setTimeout 第二个参数禁止是数字 // 输入该规则的描述
? Type a short example of the code that will fail: 占位 // 输入一个失败例子的代码
create docs/ rules/ settimeout- no- number. md
create lib/ rules/ settimeout- no- number. js
create tests/ lib/ rules/ settimeout- no- number. js
# 加了具体规则文件的项目结构
.
├── README . md
├── docs // 使用文档
│ └── rules // 所有规则的文档
│ └── settimeout- no- number. md // 具体规则文档
├── lib // eslint 规则开发
│ ├── index. js 引入+ 导出rules文件夹的规则
│ └── rules // 此目录下可以构建多个规则
│ └── settimeout- no- number. js // 规则细节
├── package . json
└── tests // 单元测试
└── lib
└── rules
└── settimeout- no- number. js // 测试该规则的文件
# 4. 安装项目依赖
以上是开发ESLint插件具体规则的准备工作,下面先来看看AST和ESLint原理的相关知识,为我们开发ESLint rule
打一下基础。
# AST——抽象语法树
AST是: Abstract Syntax Tree
的简称,中文叫做:抽象语法树。
# AST的作用
将代码抽象成树状数据结构,方便后续分析检测代码。
# 代码被解析成AST的样子
astexplorer.net 是一个工具网站:它能查看代码被解析成AST的样子。
如下图:在右侧选中一个值时,左侧对应区域也变成高亮区域,这样可以在AST中很方便的选中对应的代码 。
# AST 选择器:
下图中被圈起来的部分,称为AST selectors(选择器)。
AST 选择器的作用 :使用代码通过选择器来选中特定的代码片段,然后再对代码进行静态分析。
AST 选择器很多,ESLint官方专门有一个仓库列出了所有类型的选择器: estree
下文中开发ESLint rule
就需要用到选择器,等下用到了就懂了,现在知道一下就好了。
# ESLint的运行原理
在开发规则之前,我们需要ESLint是怎么运行的,了解插件为什么需要这么写。
# 1. 将代码解析成AST
ESLint使用JavaScript解析器Espree 把JS代码解析成AST。
PS:解析器:是将代码解析成AST的工具,ES6、react、vue都开发了对应的解析器所以ESLint能检测它们的,ESLint也是因此一统前端Lint工具的。
# 2. 深度遍历AST,监听匹配过程。
在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。
# 3. 触发监听选择器的rule
回调
在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。
# 4. 具体的检测规则等细节内容。
# 开发规则
# 规则默认模板
打开rule
生成的模板文件lib/rules/settimeout-no-number.js
, 清理一下文件,删掉不必要的选项:
module. exports = {
meta: {
docs: {
description: "setTimeout 第二个参数禁止是数字" ,
} ,
fixable: null , // 修复函数
} ,
// rule 核心
create: function ( context) {
// 公共变量和函数应该在此定义
return {
// 返回事件钩子
} ;
}
} ;
删掉的配置项,有些是ESLint官方核心规则才是用到的配置项,有些是暂时不必了解的配置,需要用到的时候,可以自行查阅ESLint 文档
# create方法-监听选择器
上文ESLint原理第三部中提到的:在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。
create
返回一个对象,对象的属性设为选择器,ESLint会收集这些选择器,在AST遍历过程中会执行所有监听该选择器的回调。
// rule 核心
create: function ( context) {
// 公共变量和函数应该在此定义
return {
// 返回事件钩子
Identifier: ( node) => {
// node是选中的内容,是我们监听的部分, 它的值参考AST
}
} ;
}
# 观察AST:
创建一个ESLint rule
需要观察代码解析成AST,选中你要检测的代码,然后进行一些判断。
以下代码都是通过astexplorer.net 在线解析的。
setTimeout ( ( ) => {
console. log ( 'settimeout' )
} , 1000 )
# rule完整文件
lib/rules/settimeout-no-number.js
:
module. exports = {
meta: {
docs: {
description: "setTimeout 第二个参数禁止是数字" ,
} ,
fixable: null , // 修复函数
} ,
// rule 核心
create: function ( context) {
// 公共变量和函数应该在此定义
return {
// 返回事件钩子
'CallExpression' : ( node) => {
if ( node. callee. name !== 'setTimeout' ) return // 不是定时器即过滤
const timeNode = node. arguments && node. arguments[ 1 ] // 获取第二个参数
if ( ! timeNode) return // 没有第二个参数
// 检测报错第二个参数是数字 报错
if ( timeNode. type === 'Literal' && typeof timeNode. value === 'number' ) {
context. report ( {
node,
message: 'setTimeout第二个参数禁止是数字'
} )
}
}
} ;
}
} ;
context.report():这个方法是用来通知ESLint这段代码是警告或错误的,用法如上。在这里 查看context
和context.report()
的文档。
规则写完了,原理就是依据AST
解析的结果,做针对性的检测,过滤出我们要选中的代码,然后对代码的值进行逻辑判断 。
可能现在会有点懵逼,但是不要紧,我们来写一下测试用例,然后用debugger
来看一下代码是怎么运行的。
# 测试用例:
测试文件tests/lib/rules/settimeout-no-number.js
:
/**
* @fileoverview setTimeout 第二个参数禁止是数字
* @author OBKoro1
*/
"use strict" ;
var rule = require ( "../../../lib/rules/settimeout-no-number" ) , // 引入rule
RuleTester = require ( "eslint" ) . RuleTester;
var ruleTester = new RuleTester ( {
parserOptions: {
ecmaVersion: 7 , // 默认支持语法为es5
} ,
} ) ;
// 运行测试用例
ruleTester. run ( "settimeout-no-number" , rule, {
// 正确的测试用例
valid: [
{
code: 'let someNumber = 1000; setTimeout(()=>{ console.log(11) },someNumber)'
} ,
{
code: 'setTimeout(()=>{ console.log(11) },someNumber)'
}
] ,
// 错误的测试用例
invalid: [
{
code: 'setTimeout(()=>{ console.log(11) },1000)' ,
errors: [ {
message: "setTimeout第二个参数禁止是数字" , // 与rule抛出的错误保持一致
type: "CallExpression" // rule监听的对应钩子
} ]
}
]
} ) ;
下面来学习一下怎么在VSCode中调试node文件,用于观察rule
是怎么运行的。
实际上打console
的形式,也是可以的,但是在调试的时候打console实在是有点慢,对于node这种节点来说,信息也不全,所以我还是比较推荐通过debugger
的方式来调试rule
。
# 在VSCode中调试node文件
点击下图中的设置按钮, 将会打开一个文件launch.json
在文件中填入如下内容,用于调试node文件。
在rule
文件中打debugger
或者在代码行数那里点一下小红点。
点击图中的开始按钮,进入debugger
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version" : "0.2.0" ,
"configurations" : [
{
"type" : "node" ,
"request" : "launch" ,
"name" : "启动程序" , // 调试界面的名称
// 运行项目下的这个文件:
"program" : "${workspaceFolder}/tests/lib/rules/settimeout-no-number.js" ,
"args" : [ ] // node 文件的参数
} ,
// 下面是用于调试package.json的命令 之前可以用,貌似vscode出了点bug导致现在用不了了
{
"name" : "Launch via NPM" ,
"type" : "node" ,
"request" : "launch" ,
"runtimeExecutable" : "npm" ,
"runtimeArgs" : [
"run-script" , "dev" //这里的dev就对应package.json中的scripts中的dev
] ,
"port" : 9229 //这个端口是调试的端口,不是项目启动的端口
} ,
]
}
# 运行测试用例进入断点
在lib/rules/settimeout-no-number.js
中打一些debugger
点击开始按钮,以调试的形式运行测试文件tests/lib/rules/settimeout-no-number.js
开始调试rule
。
# 发布插件
eslint插件都是以npm
包的形式来引用的,所以需要把插件发布一下:
注册:如果你还未注册npm账号的话,需要去注册 一下。
登录npm: npm login
发布npm
包: npm publish
即可,ESLint已经把package.json
弄好了。
# 集成到项目:
安装npm
包:npm i eslint-plugin-korolint -D
常规的方法: 引入插件一条条写入规则
// .eslintrc.js
module. exports = {
plugins: [ 'korolint' ] ,
rules: {
"korolint/settimeout-no-number" : "error"
}
}
extends
继承插件配置:
当规则比较多的时候,用户一条条去写,未免也太麻烦了,所以ESLint可以继承插件的配置 :
修改一下lib/rules/index.js
文件:
'use strict' ;
var requireIndex = require ( 'requireindex' ) ;
const output = {
rules: requireIndex ( __dirname + '/rules' ) , // 导出所有规则
configs: {
// 导出自定义规则 在项目中直接引用
koroRule: {
plugins: [ 'korolint' ] , // 引入插件
rules: {
// 开启规则
'korolint/settimeout-no-number' : 'error'
}
}
}
} ;
module. exports = output;
使用方法:
使用extends
来继承插件的配置,extends
不止这种继承方式,即使你传入一个npm包,一个文件的相对路径地址,eslint也能继承其中的配置。
// .eslintrc.js
module. exports = {
extends : [ 'plugin:korolint/koroRule' ] // 继承插件导出的配置
}
PS : 这种使用方式, npm的包名不能为eslint-plugin-xx-xx
,只能为eslint-plugin-xx
否则会有报错,被这个问题搞得头疼o(╥﹏╥)o
# 扩展:
以上内容足够开发一个插件,这里是一些扩展知识点。
# 遍历方向:
上文中说过: 在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。
我们所监听的选择器默认会在"从上至下"的过程中触发,如果需要在"从下至上"的过程中执行则需要添加:exit
,在上文中CallExpression
就变为CallExpression:exit
。
注意 :一段代码解析后可能包含多次同一个选择器,选择器的钩子也会多次触发。
# fix函数:自动修复rule错误
修复效果 :
// 修复前
setTimeout ( ( ) => {
} , 1000 )
// 修复后 变量名故意写错 为了让用户去修改它
const countNumber1 = 1000
setTimeout ( ( ) => {
} , countNumber2)
在rule的meta对象上打开修复功能:
// rule文件
module. exports = {
meta: {
docs: {
description: 'setTimeout 第二个参数禁止是数字'
} ,
fixable: 'code' // 打开修复功能
}
}
在context.report()
上提供一个fix
函数:
把上文的context.report
修改一下,增加一个fix
方法即可,更详细的介绍可以看一下文档 。
context. report ( {
node,
message: 'setTimeout第二个参数禁止是数字' ,
fix ( fixer) {
const numberValue = timeNode. value;
const statementString = `const countNumber = ${ numberValue} \n`
return [
// 修改数字为变量
fixer. replaceTextRange ( node. arguments[ 1 ] . range, 'countNumber' ) ,
// 在setTimeout之前增加一行声明变量的代码 用户自行修改变量名
fixer. insertTextBeforeRange ( node. range, statementString) ,
] ;
}
} ) ;
# 项目地址:
eslint-plugin-korolint
呼~ 这篇博客断断续续,写了好几周,终于完成了!
大家有看到这篇博客的话,建议跟着博客的一起动手写一下,动手实操一下比你mark一百篇文章都来的有用,花不了很长时间的,希望各位看完本文,都能够更深入的了解到ESLint的运行原理。
# 觉得我的博客对你有帮助的话,就关注一下/点个赞吧!
前端进阶积累 、公众号 、GitHub 、wx:OBkoro1、邮箱:[email protected]
# 基友带我飞
ESLint插件是向基友yeyan1996 学习的,在遇到问题的时候,也是他指点我的,特此感谢。
参考资料:
创建规则
ESLint 工作原理探讨
博客链接
# 数组API解析合集
数组的使用场景非常多,平日中也涉及到很多数组的api
/相关操作,一直也没有对这块内容进行一块整理总结,所以这次对这块内容做一个较为系统的总结,方便自己、也方便他人。
# 创建一个数组:
// 字面量方式:
// 这个方法也是我们最常用的,在初始化数组的时候 相当方便
let a = [ 3 , 11 , 8 ] ; // [3,11,8];
// 构造器:
// 实际上 new Array === Array,加不加new 一点影响都没有。
let a = Array ( ) ; // []
let a = Array ( 3 ) ; // [,,]
let a = Array ( 3 , 11 , 8 ) ; // [ 3,11,8 ]
# ES6 Array.of() 返回由所有参数值组成的数组
定义:返回由所有参数值组成的数组,如果没有参数,就返回一个空数组。
目的:Array.of() 出现的目的是为了解决上述构造器因参数个数不同,导致的行为有差异的问题。
let a = Array. of ( 3 , 11 , 8 ) ; // [3,11,8]
let a = Array. of ( 3 ) ; // [3]
# ES6 Arrary.from() 将两类对象转为真正的数组
定义:用于将两类对象转为真正的数组(不改变原对象,返回新的数组)。
参数:
第一个参数(必需):要转化为真正数组的对象。
第二个参数(可选): 类似数组的map方法,对每个元素进行处理,将处理后的值放入返回的数组。
第三个参数(可选): 用来绑定this。
// 1. 对象拥有length属性
let obj = { 0 : 'a' , 1 : 'b' , 2 : 'c' , length: 3 } ;
let arr = Array. from ( obj) ; // ['a','b','c'];
// 2. 部署了 Iterator接口的数据结构 比如:字符串、Set、NodeList对象
let arr = Array. from ( 'hello' ) ; // ['h','e','l','l','o']
let arr = Array. from ( new Set ( [ 'a' , 'b' ] ) ) ; // ['a','b']
# 方法:
数组原型提供了非常多的方法,这里分为三类来讲,一类会改变原数组的值,一类是不会改变原数组,以及数组的遍历方法。
# 改变原数组的方法(9个):
let a = [ 1 , 2 , 3 ] ;
ES5 :
a. splice ( ) / a. sort ( ) / a. pop ( ) / a. shift ( ) / a. push ( ) / a. unshift ( ) / a. reverse ( )
ES6 :
a. copyWithin ( ) / a. fill
对于这些能够改变原数组的方法,要注意避免在循环遍历中改变原数组的选项,比如: 改变数组的长度,导致遍历的长度出现问题。
# splice() 添加/删除数组元素
定义: splice() 方法向/从数组中添加/删除 项目,然后返回被删除的项目
语法: array.splice(index,howmany,item1,.....,itemX)
参数:
index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
howmany:可选。要删除的项目数量。如果设置为 0,则不会删除项目。
item1, ..., itemX: 可选。向数组添加的新项目。
返回值: 如果有元素被删除,返回包含被删除项目的新数组。
eg1:删除元素
let a = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] ;
let item = a. splice ( 0 , 3 ) ; // [1,2,3]
console. log ( a) ; // [4,5,6,7]
// 从数组下标0开始,删除3个元素
let item = a. splice ( - 1 , 3 ) ; // [7]
// 从最后一个元素开始删除3个元素,因为最后一个元素,所以只删除了7
eg2: 删除并添加
let a = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] ;
let item = a. splice ( 0 , 3 , '添加' ) ; // [1,2,3]
console. log ( a) ; // ['添加',4,5,6,7]
// 从数组下标0开始,删除3个元素,并添加元素'添加'
let b = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] ;
let item = b. splice ( - 2 , 3 , '添加1' , '添加2' ) ; // [6,7]
console. log ( b) ; // [1,2,3,4,5,'添加1','添加2']
// 从数组最后第二个元素开始,删除3个元素,并添加两个元素'添加1'、'添加2'
eg3: 不删除只添加:
let a = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] ;
let item = a. splice ( 0 , 0 , '添加1' , '添加2' ) ; // [] 没有删除元素,返回空数组
console. log ( a) ; // ['添加1','添加2',1,2,3,4,5,6,7]
let b = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] ;
let item = b. splice ( - 1 , 0 , '添加1' , '添加2' ) ; // [] 没有删除元素,返回空数组
console. log ( b) ; // [1,2,3,4,5,6,'添加1','添加2',7] 在最后一个元素的前面添加两个元素
从上述三个栗子可以得出:
数组如果元素不够,会删除到最后一个元素为止
操作的元素,包括开始的那个元素
可以添加很多个元素
添加是在开始的元素前面添加的
# sort() 数组排序
定义: sort()方法对数组元素进行排序,并返回这个数组。
参数可选: 规定排序顺序的比较函数。
默认情况下sort()方法没有传比较函数的话,默认按字母升序,如果不是元素不是字符串的话,会调用toString()
方法将元素转化为字符串的Unicode(万国码)位点,然后再比较字符。
// 字符串排列 看起来很正常
let a = [ "Banana" , "Orange" , "Apple" , "Mango" ] ;
a. sort ( ) ; // ["Apple","Banana","Mango","Orange"]
// 数字排序的时候 因为转换成Unicode字符串之后,有些数字会比较大会排在后面 这显然不是我们想要的
let a = [ 10 , 1 , 3 , 20 , 25 , 8 ] ;
console. log ( a. sort ( ) ) // [1,10,20,25,3,8];
比较函数的两个参数:
sort的比较函数有两个默认参数,要在函数中接收这两个参数,这两个参数是数组中两个要比较的元素,通常我们用 a 和 b 接收两个将要比较的元素:
若比较函数返回值<0,那么a将排到b的前面;
若比较函数返回值=0,那么a 和 b 相对位置不变;
若比较函数返回值>0,那么b 排在a 将的前面;
对于sort()方法更深层级的内部实现以及处理机制可以看一下这篇文章深入了解javascript的sort方法
sort排序常见用法 :
数组元素为数字的升序、降序:
let array = [ 10 , 1 , 3 , 4 , 20 , 4 , 25 , 8 ] ;
// 升序 a-b < 0 a将排到b的前面,按照a的大小来排序的
// 比如被减数a是10,减数是20 10-20 < 0 被减数a(10)在减数b(20)前面
array. sort ( function ( a, b) {
return a- b;
} ) ;
console. log ( array) ; // [1,3,4,4,8,10,20,25];
// 降序 被减数和减数调换了 20-10>0 被减数b(20)在减数a(10)的前面
array. sort ( function ( a, b) {
return b- a;
} ) ;
console. log ( array) ; // [25,20,10,8,4,4,3,1];
数组多条件排序
let array = [ { id: 10 , age: 2 } , { id: 5 , age: 4 } , { id: 6 , age: 10 } , { id: 9 , age: 6 } , { id: 2 , age: 8 } , { id: 10 , age: 9 } ] ;
array. sort ( function ( a, b) {
if ( a. id === b. id) { // 如果id的值相等,按照age的值降序
return b. age - a. age
} else { // 如果id的值不相等,按照id的值升序
return a. id - b. id
}
} )
// [{"id":2,"age":8},{"id":5,"age":4},{"id":6,"age":10},{"id":9,"age":6},{"id":10,"age":9},{"id":10,"age":2}]
自定义比较函数,天空才是你的极限
类似的:运用好返回值,我们可以写出任意符合自己需求的比较函数
let array = [ { name: 'Koro1' } , { name: 'Koro1' } , { name: 'OB' } , { name: 'Koro1' } , { name: 'OB' } , { name: 'OB' } ] ;
array. sort ( function ( a, b) {
if ( a. name === 'Koro1' ) { // 如果name是'Koro1' 返回-1 ,-1<0 a排在b的前面
return - 1
} else { // 如果不是的话,a排在b的后面
return 1
}
} )
// [{"name":"Koro1"},{"name":"Koro1"},{"name":"Koro1"},{"name":"OB"},{"name":"OB"},{"name":"OB"}]
# pop() 删除一个数组中的最后的一个元素
定义: pop() 方法删除一个数组中的最后的一个元素,并且返回这个元素。
参数: 无。
let a = [ 1 , 2 , 3 ] ;
let item = a. pop ( ) ; // 3
console. log ( a) ; // [1,2]
# shift() 删除数组的第一个元素
定义: shift()方法删除数组的第一个元素,并返回这个元素。
参数: 无。
let a = [ 1 , 2 , 3 ] ;
let item = a. shift ( ) ; // 1
console. log ( a) ; // [2,3]
# push() 向数组的末尾添加元素
定义:push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。
参数: item1, item2, ..., itemX ,要添加到数组末尾的元素
let a = [ 1 , 2 , 3 ] ;
let item = a. push ( '末尾' ) ; // 4
console. log ( a) ; // [1,2,3,'末尾']
# unshift()
定义:unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
参数: item1, item2, ..., itemX ,要添加到数组开头的元素
let a = [ 1 , 2 , 3 ] ;
let item = a. unshift ( '开头' ) ; // 4
console. log ( a) ; // ['开头',1,2,3]
# reverse() 颠倒数组中元素的顺序
定义: reverse() 方法用于颠倒数组中元素的顺序。
参数: 无
let a = [ 1 , 2 , 3 ] ;
a. reverse ( ) ;
console. log ( a) ; // [3,2,1]
# ES6: copyWithin() 指定位置的成员复制到其他位置
定义: 在当前数组内部,将指定位置的成员复制到其他位置,并返回这个数组。
语法:
array. copyWithin ( target, start = 0 , end = this . length)
参数:
三个参数都是数值,如果不是,会自动转为数值.
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
end(可选):到该位置前停止读取数据,默认等于数组长度。使用负数可从数组结尾处规定位置。
浏览器兼容(MDN): chrome 45,Edge 12,Firefox32,Opera 32,Safari 9, IE 不支持
eg:
// -2相当于3号位,-1相当于4号位
[ 1 , 2 , 3 , 4 , 5 ] . copyWithin ( 0 , - 2 , - 1 )
// [4, 2, 3, 4, 5]
let a= [ 'OB1' , 'Koro1' , 'OB2' , 'Koro2' , 'OB3' , 'Koro3' , 'OB4' , 'Koro4' , 'OB5' , 'Koro5' ]
// 2位置开始被替换,3位置开始读取要替换的 5位置前面停止替换
a. copyWithin ( 2 , 3 , 5 )
// ["OB1","Koro1","Koro2","OB3","OB3","Koro3","OB4","Koro4","OB5","Koro5"]
从上述栗子:
第一个参数是开始被替换的元素位置
要替换数据的位置范围:从第二个参数是开始读取的元素,在第三个参数前面一个元素停止读取
数组的长度不会改变
读了几个元素就从开始被替换的地方替换几个元素
# ES6: fill() 填充数组
定义: 使用给定值,填充一个数组。
参数:
第一个元素(必须): 要填充数组的值
第二个元素(可选): 填充的开始位置,默认值为0
第三个元素(可选):填充的结束位置,默认是为this.length
MDN浏览器兼容
[ 'a' , 'b' , 'c' ] . fill ( 7 )
// [7, 7, 7]
[ 'a' , 'b' , 'c' ] . fill ( 7 , 1 , 2 )
// ['a', 7, 'c']
# 不改变原数组的方法(8个):
ES5 :
slice、join、toLocateString、toStrigin、cancat、indexOf、lastIndexOf、
ES7 :
includes
# slice() 浅拷贝数组的元素
定义: 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象,且原数组不会被修改。
注意 :字符串也有一个slice() 方法是用来提取字符串的,不要弄混了。
语法:
参数:
begin(可选): 索引数值,接受负值,从该索引处开始提取原数组中的元素,默认值为0。
end(可选):索引数值(不包括),接受负值,在该索引处前结束提取原数组元素,默认值为数组末尾(包括最后一个元素)。
let a= [ 'hello' , 'world' ] ;
let b= a. slice ( 0 , 1 ) ; // ['hello']
a[ 0 ] = '改变原数组' ;
console. log ( a, b) ; // ['改变原数组','world'] ['hello']
b[ 0 ] = '改变拷贝的数组' ;
console. log ( a, b) ; // ['改变原数组','world'] ['改变拷贝的数组']
如上:新数组是浅拷贝的,元素是简单数据类型,改变之后不会互相干扰 。
如果是复杂数据类型(对象,数组)的话,改变其中一个,另外一个也会改变 。
let a= [ { name: 'OBKoro1' } ] ;
let b= a. slice ( ) ;
console. log ( b, a) ; // [{"name":"OBKoro1"}] [{"name":"OBKoro1"}]
// a[0].name='改变原数组';
// console.log(b,a); // [{"name":"改变原数组"}] [{"name":"改变原数组"}]
// b[0].name='改变拷贝数组',b[0].koro='改变拷贝数组';
// [{"name":"改变拷贝数组","koro":"改变拷贝数组"}] [{"name":"改变拷贝数组","koro":"改变拷贝数组"}]
原因在定义上面说过了的:slice()是浅拷贝,对于复杂的数据类型浅拷贝,拷贝的只是指向原数组的指针,所以无论改变原数组,还是浅拷贝的数组,都是改变原数组的数据。
# join() 数组转字符串
定义: join() 方法用于把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串。
语法:
参数:
str(可选): 指定要使用的分隔符,默认使用逗号作为分隔符。
let a= [ 'hello' , 'world' ] ;
let str= a. join ( ) ; // 'hello,world'
let str2= a. join ( '+' ) ; // 'hello+world'
使用join方法或者下文说到的toString方法时,当数组中的元素也是数组或者是对象时会出现什么情况?
let a= [ [ 'OBKoro1' , '23' ] , 'test' ] ;
let str1= a. join ( ) ; // OBKoro1,23,test
let b= [ { name: 'OBKoro1' , age: '23' } , 'test' ] ;
let str2 = b. join ( ) ; // [object Object],test
// 对象转字符串推荐JSON.stringify(obj);
所以,join()/toString()
方法在数组元素是数组的时候,会将里面的数组也调用join()/toString()
,如果是对象的话,对象会被转为[object Object]
字符串。
# toLocaleString() 数组转字符串
定义: 返回一个表示数组元素的字符串。该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成。
语法:
参数:无。
let a= [ { name: 'OBKoro1' } , 23 , 'abcd' , new Date ( ) ] ;
let str= a. toLocaleString ( ) ; // [object Object],23,abcd,2018/5/28 下午1:52:20
如上述栗子:调用数组的toLocaleString
方法,数组中的每个元素都会调用自身的toLocaleString
方法,对象调用对象的toLocaleString
,Date调用Date的toLocaleString
。
# toString() 数组转字符串 不推荐
定义: toString() 方法可把数组转换为由逗号链接起来的字符串。
语法:
参数: 无。
该方法的效果和join方法一样,都是用于数组转字符串的,但是与join方法相比没有优势,也不能自定义字符串的分隔符,因此不推荐使用。
值得注意的是 :当数组和字符串操作的时候,js 会调用这个方法将数组自动转换成字符串
let b= [ 'toString' , '演示' ] . toString ( ) ; // toString,演示
let a= [ '调用toString' , '连接在我后面' ] + '啦啦啦' ; // 调用toString,连接在我后面啦啦啦
# cancat
定义: 方法用于合并两个或多个数组,返回一个新数组。
语法:
let newArr = oldArray. concat ( arrayX, arrayX, ... ... , arrayX)
参数:
arrayX(必须):该参数可以是具体的值,也可以是数组对象。可以是任意多个。
eg1:
let a = [ 1 , 2 , 3 ] ;
let b = [ 4 , 5 , 6 ] ;
//连接两个数组
let newVal= a. concat ( b) ; // [1,2,3,4,5,6]
// 连接三个数组
let c = [ 7 , 8 , 9 ]
let newVal2 = a. concat ( b, c) ; // [1,2,3,4,5,6,7,8,9]
// 添加元素
let newVal3 = a. concat ( '添加元素' , b, c, '再加一个' ) ;
// [1,2,3,"添加元素",4,5,6,7,8,9,"再加一个"]
// 合并嵌套数组 会浅拷贝嵌套数组
let d = [ 1 , 2 ] ;
let f = [ 3 , [ 4 ] ] ;
let newVal4 = d. concat ( f) ; // [1,2,3,[4]]
ES6扩展运算符...
合并数组 :
因为ES6的语法更简洁易懂,所以现在合并数组我大部分采用...
来处理,...
运算符可以实现cancat
的每个栗子,且更简洁和具有高度自定义数组元素位置的效果。
let a = [ 2 , 3 , 4 , 5 ]
let b = [ 4 , ... a, 4 , 4 ]
console. log ( a, b) ; // [2, 3, 4, 5] [4,2,3,4,5,4,4]
更多关于扩展符的详细内容移步阮一峰大神的ECMAScript 6 入门
# indexOf() 查找数组是否存在某个元素,返回下标
定义: 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
语法:
array. indexOf ( searchElement, fromIndex)
参数:
searchElement(必须):被查找的元素
fromIndex(可选):开始查找的位置(不能大于等于数组的长度,返回-1),接受负值,默认值为0。
严格相等的搜索:
数组的indexOf搜索跟字符串的indexOf不一样,数组的indexOf使用严格相等===
搜索元素,即数组元素要完全匹配 才能搜索成功。
注意 :indexOf()不能识别NaN
eg:
let a= [ '啦啦' , 2 , 4 , 24 , NaN ]
console. log ( a. indexOf ( '啦' ) ) ; // -1
console. log ( a. indexOf ( 'NaN' ) ) ; // -1
console. log ( a. indexOf ( '啦啦' ) ) ; // 0
使用场景:
数组去重
根据获取的数组下标执行操作,改变数组中的值等。
判断是否存在,执行操作。
# lastIndexOf() 查找指定元素在数组中的最后一个位置
定义: 方法返回指定元素,在数组中的最后一个的索引,如果不存在则返回 -1。(从数组后面往前查找)
语法:
arr. lastIndexOf ( searchElement, fromIndex)
参数:
searchElement(必须): 被查找的元素
fromIndex(可选): 逆向查找开始位置,默认值数组的长度-1,即查找整个数组。
关于fromIndex有三个规则:
正值。如果该值大于或等于数组的长度,则整个数组会被查找。
负值。将其视为从数组末尾向前的偏移。(比如-2,从数组最后第二个元素开始往前查找)
负值。其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
let a= [ 'OB' , 4 , 'Koro1' , 1 , 2 , 'Koro1' , 3 , 4 , 5 , 'Koro1' ] ; // 数组长度为10
// let b=a.lastIndexOf('Koro1',4); // 从下标4开始往前找 返回下标2
// let b=a.lastIndexOf('Koro1',100); // 大于或数组的长度 查找整个数组 返回9
// let b=a.lastIndexOf('Koro1',-11); // -1 数组不会被查找
let b= a. lastIndexOf ( 'Koro1' , - 9 ) ; // 从第二个元素4往前查找,没有找到 返回-1
# ES7 includes() 查找数组是否包含某个元素 返回布尔
定义: 返回一个布尔值,表示某个数组是否包含给定的值
语法:
array. includes ( searchElement, fromIndex= 0 )
参数:
searchElement(必须):被查找的元素
fromIndex(可选):默认值为0,参数表示搜索的起始位置,接受负值。正值超过数组长度,数组不会被搜索,返回false。负值绝对值超过长数组度,重置从0开始搜索。
includes方法是为了弥补indexOf方法的缺陷而出现的:
indexOf方法不能识别NaN
indexOf方法检查是否包含某个值不够语义化,需要判断是否不等于-1
,表达不够直观
eg:
let a= [ 'OB' , 'Koro1' , 1 , NaN ] ;
// let b=a.includes(NaN); // true 识别NaN
// let b=a.includes('Koro1',100); // false 超过数组长度 不搜索
// let b=a.includes('Koro1',-3); // true 从倒数第三个元素开始搜索
// let b=a.includes('Koro1',-100); // true 负值绝对值超过数组长度,搜索整个数组
兼容性(MDN): chrome47, Firefox 43,Edge 14,Opera 34, Safari 9,IE 未实现。
# 遍历方法(12个):
js中遍历数组并不会改变原始数组的方法总共有12个:
ES5 :
forEach、every 、some、 filter、map、reduce、reduceRight、
ES6 :
find、findIndex、keys、values、entries
# 关于遍历:
关于遍历的效率,可以看一下这篇详解JS遍历
尽量不要在遍历的时候,修改后面要遍历的值
尽量不要在遍历的时候修改数组的长度(删除/添加)
# forEach
定义: 按升序为数组中含有效值的每一项执行一次回调函数。
语法:
array. forEach ( function ( currentValue, index, arr) , thisValue)
参数:
function(必须): 数组中每个元素需要调用的函数。
// 回调函数的参数
1. currentValue ( 必须) , 数组当前元素的值
2. index ( 可选) , 当前元素的索引值
3. arr ( 可选) , 数组对象本身
thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined
关于forEach()你要知道 :
无法中途退出循环,只能用return
退出本次回调,进行下一次回调。
它总是返回 undefined值,即使你return了一个值。
# 下面类似语法同样适用这些规则
对于空数组是不会执行回调函数的
对于已在迭代过程中删除的元素,或者空元素会跳过回调函数
遍历次数再第一次循环前就会确定,再添加到数组中的元素不会被遍历。
如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值。
eg:
let a = [ 1 , 2 , , 3 ] ; // 最后第二个元素是空的,不会遍历(undefined、null会遍历)
let obj = { name: 'OBKoro1' } ;
let result = a. forEach ( function ( value, index, array) {
a[ 3 ] = '改变元素' ;
a. push ( '添加到尾端,不会被遍历' )
console. log ( value, 'forEach传递的第一个参数' ) ; // 分别打印 1 ,2 ,改变元素
console. log ( this . name) ; // OBKoro1 打印三次 this绑定在obj对象上
// break; // break会报错
return value; // return只能结束本次回调 会执行下次回调
console. log ( '不会执行,因为return 会执行下一次循环回调' )
} , obj) ;
console. log ( result) ; // 即使return了一个值,也还是返回undefined
// 回调函数也接受接头函数写法
# every 检测数组所有元素是否都符合判断条件
定义: 方法用于检测数组所有元素是否都符合函数定义的条件
语法:
array. every ( function ( currentValue, index, arr) , thisValue)
参数:(这几个方法的参数,语法都类似)
function(必须): 数组中每个元素需要调用的函数 。
currentValue(必须),数组当前元素的值
index(可选), 当前元素的索引值
arr(可选),数组对象本身
thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined
方法返回值规则:
如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
如果所有元素都满足条件,则返回 true 。=
eg:
function isBigEnough ( element, index, array) {
return element >= 10 ; // 判断数组中的所有元素是否都大于10
}
let result = [ 12 , 5 , 8 , 130 , 44 ] . every ( isBigEnough) ; // false
let result = [ 12 , 54 , 18 , 130 , 44 ] . every ( isBigEnough) ; // true
// 接受箭头函数写法
[ 12 , 5 , 8 , 130 , 44 ] . every ( x => x >= 10 ) ; // false
[ 12 , 54 , 18 , 130 , 44 ] . every ( x => x >= 10 ) ; // true
# some 数组中的是否有满足判断条件的元素
定义:数组中的是否有满足判断条件的元素
语法:
array. some ( function ( currentValue, index, arr) , thisValue)
参数:(这几个方法的参数,语法都类似)
function(必须): 数组中每个元素需要调用的函数。
currentValue(必须),数组当前元素的值
index(可选), 当前元素的索引值
arr(可选),数组对象本身
thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined
方法返回值规则 :
如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
如果没有满足条件的元素,则返回false 。
function isBigEnough ( element, index, array) {
return ( element >= 10 ) ; //数组中是否有一个元素大于 10
}
let result = [ 2 , 5 , 8 , 1 , 4 ] . some ( isBigEnough) ; // false
let result = [ 12 , 5 , 8 , 1 , 4 ] . some ( isBigEnough) ; // true
# filter 过滤原始数组,返回新数组
定义: 返回一个新数组, 其包含通过所提供函数实现的测试的所有元素。
语法:
let new_array = arr. filter ( function ( currentValue, index, arr) , thisArg)
参数:(这几个方法的参数,语法都类似)
function(必须): 数组中每个元素需要调用的函数。
currentValue(必须),数组当前元素的值
index(可选), 当前元素的索引值
arr(可选),数组对象本身
thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined
eg:
let a = [ 32 , 33 , 16 , 40 ] ;
let result = a. filter ( function ( value, index, array) {
return value >= 18 ; // 返回a数组中所有大于18的元素
} ) ;
console. log ( result, a) ; // [32,33,40] [32,33,16,40]
# map 对数组中的每个元素进行处理,返回新的数组
定义:创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
语法:
let new_array = arr. map ( function ( currentValue, index, arr) , thisArg)
参数:(这几个方法的参数,语法都类似)
function(必须): 数组中每个元素需要调用的函数。
currentValue(必须),数组当前元素的值
index(可选), 当前元素的索引值
arr(可选),数组对象本身
thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined
eg:
let a = [ '1' , '2' , '3' , '4' ] ;
let result = a. map ( function ( value, index, array) {
return value + '新数组的新元素'
} ) ;
console. log ( result, a) ;
// ["1新数组的新元素","2新数组的新元素","3新数组的新元素","4新数组的新元素"] ["1","2","3","4"]
# reduce 为数组提供累加器,合并为一个值
定义:reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。
语法:
array. reduce ( function ( total, currentValue, currentIndex, arr) , initialValue)
参数:
function(必须): 数组中每个元素需要调用的函数。
total(必须),初始值, 或者上一次调用回调返回的值
currentValue(必须),数组当前元素的值
index(可选), 当前元素的索引值
arr(可选),数组对象本身
initialValue(可选): 指定第一次回调 的第一个参数。
回调第一次执行时 :
如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;
如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。
如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回。
eg:
// 数组求和
let sum = [ 0 , 1 , 2 , 3 ] . reduce ( function ( a, b) {
return a + b;
} , 0 ) ;
// 6
// 将二维数组转化为一维 将数组元素展开
let flattened = [ [ 0 , 1 ] , [ 2 , 3 ] , [ 4 , 5 ] ] . reduce (
( a, b) => a. concat ( b) ,
[ ]
) ;
// [0, 1, 2, 3, 4, 5]
# reduceRight 从右至左累加
这个方法除了与reduce执行方向相反外,其他完全与其一致,请参考上述 reduce 方法介绍。
# ES6:find()& findIndex() 根据条件找到数组成员
find()定义:用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回undefined。
findIndex()定义:返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
这两个方法
语法:
let item = arr. find ( function ( currentValue, index, arr) , thisArg)
let index = arr. findIndex ( function ( currentValue, index, arr) , thisArg)
参数:(这几个方法的参数,语法都类似)
function(必须): 数组中每个元素需要调用的函数。
currentValue(必须),数组当前元素的值
index(可选), 当前元素的索引值
arr(可选),数组对象本身
thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined
这两个方法都可以识别NaN
,弥补了indexOf
的不足.
eg:
// find
let a = [ 1 , 4 , - 5 , 10 ] . find ( ( n) => n < 0 ) ; // 返回元素-5
let b = [ 1 , 4 , - 5 , 10 , NaN ] . find ( ( n) => Object. is ( NaN , n) ) ; // 返回元素NaN
// findIndex
let a = [ 1 , 4 , - 5 , 10 ] . findIndex ( ( n) => n < 0 ) ; // 返回索引2
let b = [ 1 , 4 , - 5 , 10 , NaN ] . findIndex ( ( n) => Object. is ( NaN , n) ) ; // 返回索引4
浏览器兼容(MDN):Chrome 45,Firefox 25,Opera 32, Safari 8, Edge yes,
# ES6 keys()&values()&entries() 遍历键名、遍历键值、遍历键名+键值
定义:三个方法都返回一个新的 Array Iterator 对象,对象根据方法不同包含不同的值。
语法:
array. keys ( )
array. values ( )
array. entries ( )
参数:无。
遍历栗子(摘自ECMAScript 6 入门 ):
for ( let index of [ 'a' , 'b' ] . keys ( ) ) {
console. log ( index) ;
}
// 0
// 1
for ( let elem of [ 'a' , 'b' ] . values ( ) ) {
console. log ( elem) ;
}
// 'a'
// 'b'
for ( let [ index, elem] of [ 'a' , 'b' ] . entries ( ) ) {
console. log ( index, elem) ;
}
// 0 "a"
// 1 "b"
在for..of
中如果遍历中途要退出,可以使用break
退出循环。
如果不使用for...of
循环,可以手动调用遍历器对象的next方法,进行遍历:
let letter = [ 'a' , 'b' , 'c' ] ;
let entries = letter. entries ( ) ;
console. log ( entries. next ( ) . value) ; // [0, 'a']
console. log ( entries. next ( ) . value) ; // [1, 'b']
console. log ( entries. next ( ) . value) ; // [2, 'c']
entries()浏览器兼容性(MDN):Chrome 38, Firefox 28,Opera 25,Safari 7.1
keys()浏览器兼容性(MDN):Chrome 38, Firefox 28,Opera 25,Safari 8,
博客链接
# 一次弄懂 Object.defineProperty
# 基本用法:
let obj = {
singer: '周杰伦'
} ;
let value = '青花瓷' ;
Object. defineProperty ( obj, 'music' , {
value: value // music的值
// configurable: false, // music默认不能删除 要删除须设置为true 设为true 可删除
// writable: false, // 默认不能修改music 设为true 可修改
// enumerable: false, // music默认是不能被枚举(遍历) 设为true 可遍历
} ) ;
delete obj. music;
console. log ( obj. music) ; // 青花瓷 删除无效
obj. music = '听妈妈的话' ;
console. log ( obj. music) ; // 青花瓷 修改无效
for ( let key in obj) {
console. log ( key) ; // singer
}
// music 没有被遍历
# 默认不能修改、不能删除、不能遍历
通过栗子可以发现:通过 defineProperty 设置的属性,默认不能修改,不能删除,不能遍历 ,当然你可以通过设置更改他们。
# Object.defineProperty
的作用:
完全掌控 对象的某个属性,增删改查全都可以设定!
# 设置get
、set
:
不能同时设置
get,set 设置时不能同时设置 writable 和 value, 他们是一对情侣的存在,交叉设置或同时存在,会报错
let obj = {
singer: '周杰伦'
} ;
let value = '青花瓷' ;
Object. defineProperty ( obj, 'music' , {
enumerable: true , // 设置可枚举
get ( ) {
// 获取obj.music的时候就会调用get方法
// let value = "强行设置get的返回值"; // 打开注释 读取属性永远都是‘强行设置get的返回值’
return value;
} ,
set ( val) {
// value = val; // 将修改的值重新赋给song
value = '强行设置修改的值' ;
}
} ) ;
console. log ( obj. music) ; // 青花瓷
delete obj. music; // 删除无效
console. log ( obj. music) ; // 青花瓷
obj. music = '听妈妈的话' ;
console. log ( obj. music) ; // 强行设置修改的值
for ( let key in obj) {
console. log ( key) ; // singer, music 上面设置了enumerable可枚举
}
这个Object.defineProperty
的用法就是上面两个栗子中所展示的那样,可以将栗子copy
到本地自己玩一下。
博客链接
# Web sendBeacon 刷新/关闭页面之前发送请求
# 背景:
有一个任务非常耗时会消耗后台大量算力,所以在退出页面的时候,要求前端这边发送一个请求来杀死任务 。
一开始以为这个需求非常简单,就是在进入其他路由前,发送一下请求,杀死一下任务就好了。
然而现实狠狠的打了我的脸,因为退出页面的场景不止切换路由~
# 退出页面场景:
还在本网站,跳到其他路由
刷新页面/关闭页面 也需要发送请求来杀死任务
# 还在本网站,跳到其他路由
这个比较简单,在Vue
中可以通过路由离开的钩子beforeRouteLeave
来实现:
beforeRouteLeave ( to, from , next) {
if ( 任务运行中) {
// 发送请求
} else {
next ( true ) // 用户离开
}
}
刷新页面/关闭页面的情况:
然而在刷新页面的时候,beforeRouteLeave
并不会执行,接着想到了下面这两个API
.
# beforeunload
和unload
# beforeunload 当浏览器窗口关闭或者刷新时触发:
介绍 :
使用这个API
可以阻止页面直接关闭,用户通过点击确定/取消按钮,来决定是否不关闭/刷新当前页面。
在 chrome 下长这个样子,你们肯定都见过:
如何使用
这个 API 的使用非常简单,只要在页面加载的时候监听一下此事件,在需要出现弹窗的时候return 一个可以转化为 true 的值 ,就可以了。
// 页面卸载之前
let killTask = false ; // 是否杀死任务
window. onbeforeunload = e => {
if ( 任务运行 && 对应页面) {
killTask = true ;
return '您可能有数据没有保存' ; // 在部分浏览器可以修改弹窗标题
} else {
killTask = false ;
}
// 没有return一个可以转化为true的值 就不会出现弹窗
} ;
出现此弹窗的浏览器行为 :
以下行为是基于 chorme:
焦点:你没有点击取消/确定之前,焦点会一直在此弹窗上
你无法在出现弹窗的页面上执行任何操作
在其他页面也只能执行简单的点击操作,弹窗还是存在页面中间,无法使用键盘,
键盘:键盘被绑定在弹窗上,只能通过按键Esc
、Enter
来执行取消/确定操作
弹窗不是页面的 dom,是浏览器的行为
用户取消/确定,没有回调 API,无法得知
弹窗标题 :
chrome 中刷新页面的标题:重新加载此网站?
chrome 中关闭页面的标题:离开此网站?
现在大部分浏览器都不允许修改弹窗的标题,这个是为了安全考虑,来保证用户不受到错误信息的误导,
迷茫 :
一开始我以为既然可以拦截到用户的刷新/关闭页面的操作,出现了上面那个弹窗,这个需求就已经做完了的时候。
然后发现,浏览器竟然没有提供用户点击确定/取消刷新页面的回调 。
到这里我陷入了迷茫,盯着beforeunload
这个 API 思考了起了人生的意义(其实是在发呆),盯着盯着,从beforeunload
的before
我也就想到了unload
这个 API。
瞬间又燃起了斗志,何不试试这个unload
?
# unload
当页面正在被卸载的时候触发该事件
介绍
当页面正在被卸载的时候触发该事件,该事件不可取消,为不可逆操作。
使用
直接监听该事件就可以了。
window. onunload = e => { }
结合需求 :
killTask
为beforeunload
时定义的变量,每次进入回调,都会给killTask
赋值,使用这个值就可以判断什么时候可以发送请求杀死任务。
window. onunload = e => {
if ( killTask && 对应页面) {
// 发送请求
}
} ;
到这里大家肯定以为已经做出来了该需求,事实上,并没有!
无法发送异步请求
我使用的是axios
来发送请求,请求发出去了,但是被取消了,服务器那边根本没有收到请求,如下。
经过一顿分析:发现是axios
请求是异步的问题,谷歌之后发现axios不支持同步的请求
最后使用原生的XMLHttpRequest 对象 ,让请求同步
大功告成! 实际上,上面才是我第一次要发的内容,而下面更好的解决方法!
# 缺陷与更好的建议:
当我把这篇文章发布在公众号上,被奇舞周刊 转载了,上面一些大佬给我提了一些建议。
研究了一下,结果...好吧!我承认我是菜鸡。
hey~ 不过这正是我写博客的收获之一,分享经验,收获知识!
# 性能缺陷:
XHR同步请求会阻碍页面卸载,如果是刷新/跳转页面的话,页面重新展示速度会变慢,导致性能问题 。
毕竟向网络发送请求并获得响应可能会超级慢 ,有可能是用户网络环境比较差,又或者是服务器挂了,请求一直没返回回来...
基于性能问题,大佬们推荐使用Beacon代替XHR ,然后经过一番搜索...
# Beacon API
Beacon API用于将少量数据通过post请求发送到服务器 。
Beacon
是非阻塞请求,不需要响应
# 完美解决性能缺陷问题:
浏览器将 Beacon
请求排队让它在空闲的时候执行并立即返回控制
它在unload
状态下也可以异步发送,不阻塞页面刷新/跳转等操作。
所以**Beacon
可以完美解决上面提到的因XHR同步请求阻塞而引起的性能缺陷问题**。
# 使用:navigator.sendBeacon()
完整API :
let result = navigator. sendBeacon ( url, data) ;
Beacon
是挂在navigator
下面的,上面就是它的完整API。
result
是一个布尔值,代表这次发送请求的结果:
如果浏览器接受并且把请求排队了则返回 tru
如果在这个过程中出现了问题就返回 false
navigator.sendBeacon
接受两个参数:
url: 请求的 URL。请求是 POST 请求。
data: 要发送的数据。 数据类型可以是:ArrayBufferView, Blob, FormData,Sting。
来看一个用FormData
来传递数据的栗子,你就懂了:
// 创建一个新的 FormData 并添加一个键值对
let data = new FormData ( ) ;
data. append ( 'hello' , 'world' ) ;
let result = navigator. sendBeacon ( './src' , data) ;
if ( result) {
console. log ( '请求成功排队 等待执行' ) ;
} else {
console. log ( '失败' ) ;
}
# 浏览器支持:
浏览器支持:Edge:14+,Firefox:31+,Chrome:39+,Opera:26+,IE:不支持。
虽然现在浏览器对sendBeacon
的支持很好,我们对其做一下兼容性处理也是有必要的:
if ( navigator. sendBeacon) {
// Beacon 代码
} else {
// 回退到 XHR同步请求或者不做处理
}
# web wroker中使用Beacon
因为Beacon
是挂在navigator
下面,而web worker也有navigator
,去找了一下,真的给我找到了。
这儿有一个MDN提供的栗子 ,可以点进去看一下。
PS:对web worker不熟悉的同学可以看我这篇文章
# Beacon其他相关
客户端优化:可以将 Beacon 请求合并到其他请求上,一同处理 , 尤其在移动环境下。
Beacon更多的情况是用于做前端埋点,监控用户活动 ,它的初衷也基于此。
# 小结
本文总共讲了三个API,beforeunload
、unload
和Beacon
,Beacon
这个API估计知道的人比较少,以后遇到前端埋点和页面卸载前发送请求的需求,记得使用这三个API。
以上2019.02.19
博客 、前端积累文档 、公众号 、GitHub
参考资料:
MDN
页面跳转时,统计数据丢失问题探讨
使用 Web Beacon API 记录活动
博客链接
# JS高程中的垃圾回收机制与常见内存泄露的解决方法
起因是因为想了解闭包的内存泄露机制,然后想起《js高级程序设计》中有关于垃圾回收机制的解析,之前没有很懂,过一年回头再看就懂了,写篇博客与大家分享一下。
# 内存的生命周期:
分配你所需要的内存:
由于字符串、对象等没有固定的大小,js程序在每次创建字符串、对象的时候,程序都会分配内存来存储那个实体 。
使用分配到的内存做点什么。
不需要时将其释放回归:
在不需要字符串、对象的时候,需要释放其所占用的内存,否则将会消耗完系统中所有可用的内存,造成系统崩溃,这就是垃圾回收机制所存在的意义 。
所谓的内存泄漏指的是 :由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。
# 垃圾回收机制:
在C和C++之类的语言中,需要手动来管理内存的,这也是造成许多不必要问题的根源。幸运的是,在编写js的过程中,内存的分配以及内存的回收完全实现了自动管理,我们不用操心这种事情。
# 垃圾收集机制的原理:
垃圾收集器会按照固定的时间间隔,周期性的找出不再继续使用的变量,然后释放其占用的内存 。
什么叫不再继续使用的变量?
不再使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。
全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收 。
# 标记清除:当前采用的垃圾收集策略
工作原理:
当变量进入环境时(例如在函数中声明一个变量),将这个变量标记为“进入环境”,当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:
垃圾收集器会在运行的时候会给存储在内存中的所有变量都加上标记 。
去掉环境中的变量 以及被环境中的变量引用的变量的标记。
那些还存在标记的变量被视为准备删除的变量 。
最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间 。
到2008年为止,IE、Chorme、Fireofx、Safari、Opera 都使用标记清除式的垃圾收集策略 ,只不过垃圾收集的时间间隔互有不同。
# 引用计数略:被废弃的垃圾收集策略
循环引用:跟踪记录每个值被引用的技术
在老版本的浏览器中(对,又是IE),IE9以下BOM和DOM对象就是使用C++以COM对象的形式实现的。
COM的垃圾收集机制采用的就是引用计数策略,这种机制在出现循环引用的时候永远都释放不掉内存。
let element = document. getElementById ( 'something' ) ;
let myObject = new Object ( ) ;
myObject. element = element; // element属性指向dom
element. someThing = myObject; // someThing回指myObject 出现循环引用(两个对象一直互相包含 一直存在计数)。
解决方式是,当我们不使用它们的时候,手动切断链接:
myObject. element = null ;
element. someThing = null ;
淘汰 :
IE9把BOM和DOM对象转为了真正的js对象,避免了使用这种垃圾收集策略,消除了IE9以下常见的内存泄漏的主要原因。
IE7以下有一个声明狼藉的性能问题,大家了解一下:
256个变量,4096个对象(或数组)字面或者64KB的字符串,达到任何一个临界值会触发垃圾收集器运行。
如果一个js脚本的生命周期一直保有那么多变量,垃圾收集器会一直频繁的运行,引发严重的性能问题。
IE7已修复这个问题。
# 哪些情况会引起内存泄漏?
虽然有垃圾回收机制,但我们在编写代码的时候,有些情况还是会造成内存泄漏,了解这些情况,并在编写程序的时候,注意避免,我们的程序会更具健壮性。
# 意外的全局变量:
上文我们提到了全局变量不会被当成垃圾回收 ,我们在编码中有时会出现下面这种情况:
function foo ( ) {
this . bar2 = '默认绑定this指向全局' // 全局变量=> window.bar2
bar = '全局变量' ; // 没有声明变量 实际上是全局变量=>window.bar
}
foo ( ) ;
当我们使用默认绑定 ,this会指向全局,this.something
也会创建一个全局变量,这一点可能很多人没有注意到。
解决方法:在函数内使用严格模式or细心一点
function foo ( ) {
"use strict" ;
this . bar2 = "严格模式下this指向undefined" ;
bar = "报错" ;
}
foo ( ) ;
当然我们也可以手动释放全局变量的内存 :
window. bar = undefined
delete window. bar2
# 被遗忘的定时器和回调函数
当不需要 setInterval
或者setTimeout
时,定时器没有被clear ,定时器的回调函数以及内部依赖的变量都不能被回收 ,造成内存泄漏。
var someResource = getData ( ) ;
setInterval ( function ( ) {
var node = document. getElementById ( 'Node' ) ;
if ( node) {
node. innerHTML = JSON . stringify ( someResource) ) ;
// 定时器也没有清除
}
// node、someResource 存储了大量数据 无法回收
} , 1000 ) ;
解决方法 : 在定时器完成工作的时候,手动清除定时器。
# 闭包:
闭包可以维持函数内局部变量,使其得不到释放,造成内存泄漏 。
function bindEvent ( ) {
var obj = document. createElement ( "XXX" ) ;
var unused = function ( ) {
console. log ( obj, '闭包内引用obj obj不会被释放' ) ;
} ;
// obj = null;
}
解决方法 :手动解除引用,obj = null
。
# 循环引用问题
就是IE9以下的循环引用问题,上文讲过了。
# 没有清理DOM元素引用:
var refA = document. getElementById ( 'refA' ) ;
document. body. removeChild ( refA) ; // dom删除了
console. log ( refA, "refA" ) ; // 但是还存在引用 能console出整个div 没有被回收
不信的话,可以看下这个dom 。
解决办法 :refA = null
;
# console保存大量数据在内存中。
过多的console,比如定时器的console会导致浏览器卡死。
解决 :合理利用console,线上项目尽量少的使用console,当然如果你要发招聘除外。
# 如何避免内存泄漏:
记住一个原则:不用的东西,及时归还,毕竟你是'借的'嘛 。
减少不必要的全局变量,使用严格模式避免意外创建全局变量。
在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。
# 关于内存泄漏:
即使是1byte的内存,也叫内存泄漏,并不一定是导致浏览器崩溃、卡顿才能叫做内存泄漏。
一般是堆区内存泄漏,栈区不会泄漏。
基本类型的值存在内存中,被保存在栈内存中,引用类型的值是对象,保存在堆内存中。所以对象、数组之类的,才会发生内存泄漏 。
使用chorme监控内存泄漏,可以看一下这篇文章
# 小结
了解了内存泄漏的原因以及出现的情况,那么我们在编码过程中只要多加注意,就不会发生非常严重的内存泄漏问题。
博客链接
# JS基础-原型、原型链
JS的原型、原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对这个概念一知半解,碰到问题靠“猜”,却不理解它的规则!
# prototype
# 只有函数有prototype属性
let a = { }
let b = function ( ) { }
console. log ( a. prototype) // undefined
console. log ( b. prototype) // { constructor: function(){...} }
# Object.prototype怎么解释?
其实Object
是一个全局对象,也是一个构造函数,以及其他基本类型的全局对象也都是构造函数:
function outTypeName ( data, type) {
let typeName = Object. prototype. toString. call ( data)
console. log ( typeName)
}
outTypeName ( Object) //[object Function]
outTypeName ( String) // [object Function]
outTypeName ( Number) // [object Function]
# 为什么只有函数有prototype属性
JS通过new
来生成对象,但是仅靠构造函数,每次生成的对象都不一样。
有时候需要在两个对象之间共享属性,由于JS在设计之初没有类的概念,所以JS使用函数的prototype
来处理这部分需要被共享的属性 ,通过函数的prototype
来模拟类:
当创建一个函数时,JS会自动为函数添加prototype
属性,值是一个有constructor
的对象。
以下是共享属性prototype
的栗子:
function People ( name) {
this . name = name
}
People. prototype. age = 23 // 岁数
// 创建两个实例
let People1 = new People ( 'OBKoro1' )
let People2 = new People ( '扣肉' )
People. prototype. age = 24 // 长大了一岁
console. log ( People1. age, People2. age) // 24 24
为什么People1
和People2
可以访问到People.prototype.age
?
原因是:People1
和People2
的原型是People.prototype
,答案在下方的:构造函数是什么以及它做了什么。
# 原型链
# __proto__
和Object.getPrototypeOf(target)
: 对象的原型
__proto__
是对象实例和它的构造函数之间建立的链接,它的值是:构造函数的`prototype。
也就是说:__proto__
的值是它所对应的原型对象,是某个函数的prototype
Object.getPrototypeOf(target)
全等于__proto__
。
它是ES6的标准,兼容IE9,主流浏览器也都支持,MDN ,本文将以Object.getPrototypeOf(target)
指代__proto__
。
# 不要再使用__proto__
:
本段摘自阮一峰-ES6入门 ,具体解析请点击链接查看
__proto__
属性没有写入 ES6 的正文,而是写入了附录。
原因是它本质上是一个内部属性,而不是一个正式的对外的 API,只是由于浏览器广泛支持,才被加入了 ES6 。
标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的 。
所以无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,应该使用:Object.getPrototypeOf(target)
(读操作)、Object.setPrototypeOf(target)
(写操作)、Object.create(target)
(生成操作)代替
# 构造函数是什么、它做了什么
出自《你不知道的在js》:在js中, 实际上并不存在所谓的'构造函数',只有对于函数的'构造调用'。
上文一直提到构造函数,所谓的构造函数,实际上就是通过关键字new
来调用的函数:
let newObj = new someFn ( ) // 构造调用函数
构造/new调用函数的时候做了什么 :
创建一个全新的对象。
这个新对象的原型(Object.getPrototypeOf(target)
)指向构造函数的prototype
对象。
该函数的this会绑定在新创建的对象上。
如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
我们称这个新对象为构造函数的实例。
原型继承就是利用构造调用函数的特性 :
SubType. prototype = new SuperType ( ) ; // 原型继承:SubType继承SuperType
// 挂载SuperType的this和prototype的属性和方法到SubType.prototype上
�构造调用的第二点:将新对象的Object.getPrototypeOf(target)
指向函数的prototype
构造调用的第三点:该函数的this会绑定在新创建的对象上。
新对象赋值给SubType.prototype
原型类型还有缺点:多个实例对引用类型的操作会被篡改,这里就不扩展了。
# 原型链是什么
来看个例子:
function foo ( ) { }
const newObj = new foo ( ) // 构造调用foo 返回一个新对象
const newObj__proto__ = Object. getPrototypeOf ( newObj) // 获取newObj的原型对象
newObj__proto__ === foo. prototype // true 验证newObj的原型指向foo
const foo__proto__ = Object. getPrototypeOf ( foo. prototype) // 获取foo.prototype的原型
foo__proto__ === Object. prototype // true foo.prototype的原型是Object.prototype
如果用以前的语法,从newObj
查找foo
的原型,是这样的:
newObj. __proto__. __proto__ // 这种关系就是原型链
可以用以下三句话来理解原型链 :
每个对象都拥有一个原型对象 : newObj
的原型是foo.prototype
。
对象的原型可能也是继承其他原型对象的 : foo.prototype
也有它的原型Object.prototype
。
一层一层的,以此类推,这种关系就是原型链 。
# 一个对象是否在另一个对象的原型链上
如果一个对象存在另一个对象的原型链上,我们可以说:它们是继承关系。
判断方式有两种,但都是根据构造函数的prototype
是否在原型链上来判断的:
instanceof
: 用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置
语法:object instanceof constructor
let test = function ( ) { }
let testObject = new test ( ) ;
testObject instanceof test // true test.prototype在testObject的原型链上
testObject instanceof Function // false Function.prototype 不在testObject的原型链上
testObject instanceof Object // true Object.prototype在testObject的原型链上
isPrototypeOf
:测试一个对象是否存在于另一个对象的原型链上
语法:prototypeObj.isPrototypeOf(object)
let test = function ( ) { }
let testObject = new test ( ) ;
test. prototype. isPrototypeOf ( testObject) // true test.prototype在testObject的原型链上
Object. prototype. isPrototypeOf ( testObject) // true Object.prototype在testObject的原型链上
# 原型链的终点: Object.prototype
Object.prototype
是原型链的终点,所有对象都是从它继承了方法和属性。
Object.prototype
没有原型对象 :
const proto = Object. getPrototypeOf ( Object. prototype) // null
下面是两个验证例子,有疑虑的同学多写几个测试用例印证一下。
字符串原型链的终点 :Object.prototype
let test = '由String函数构造出来的'
let stringPrototype = Object. getPrototypeOf ( test) // 字符串的原型
stringPrototype === String. prototype // true 字符串的原型是String对象
Object. getPrototypeOf ( stringPrototype) === Object. prototype // true String对象的原型是Object对象
函数原型链的终点 :Object.prototype
let test = function ( ) { }
let fnPrototype = Object. getPrototypeOf ( test)
stringPrototype === Function. prototype // true test的原型是Function.prototype
Object. getPrototypeOf ( Function. prototype) === Object. prototype // true
# 原型链用来做什么?
# 属性查找:
如果试图访问对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性,以此类推
我们用一个例子来形象说明一下:
let test = '由String函数构造出来的'
let stringPrototype = Object. getPrototypeOf ( test) // 字符串的原型
stringPrototype === String. prototype // true 字符串的原型是String对象
Object. getPrototypeOf ( stringPrototype) === Object. prototype // true String对象的原型是Object对象
当你访问test
的某个属性时,浏览器会进行以下查找:
浏览器首先查找test
本身
接着查找它的原型对象:String.prototype
最后查找String.prototype
的原型对象:Object.prototype
一旦在原型链上找到该属性,就会立即返回该属性,停止查找。
原型链上的原型都没有找到的话,返回undefiend
这种查找机制还解释了字符串为何会有自带的方法: slice
/split
/indexOf
等。
准确的说:
这些属性和方法是定义在String
这个全局对象/函数上的。
字符串的原型指向了String
函数的prototype
。
之后通过查找原型链,在String函数的prototype
中找到这些属性和方法。
# 拒绝查找原型链:
hasOwnProperty
: 指示对象自身属性中是否具有指定的属性
语法:obj.hasOwnProperty(prop)
参数: prop
要查找的属性
返回值: 用来判断某个对象是否含有指定的属性的Boolean
。
let test = { 'OBKoro1' : '扣肉' }
test. hasOwnProperty ( 'OBKoro1' ) ; // true
test. hasOwnProperty ( 'toString' ) ; // false test本身没查找到toString
这个API
是挂载在object.prototype
上,所有对象都可以使用,API会忽略掉那些从原型链上继承到的属性。
# 扩展:
# 实例的属性
你知道构造函数的实例对象上有哪些属性吗?这些属性分别挂载在哪个地方?原因是什么?
function foo ( ) {
this . some = '222'
let ccc = 'ccc'
foo. obkoro1 = 'obkoro1'
foo. prototype. a = 'aaa'
}
foo. koro = '扣肉'
foo. prototype. test = 'test'
let foo1 = new foo ( ) // `foo1`上有哪些属性,这些属性分别挂载在哪个地方
foo. prototype. test = 'test2' // 重新赋值
上面这道是考察JS
基础的题,很多人都没说对,原因是没有彻底掌握this
、原型链
、函数
。
# 想一下再看解析:
# 想一下再看解析:
# 想一下再看解析:
# 想一下再看解析:
# 想一下再看解析:
this.some
:foo1
对象的属性
通过构造调用foo
的this
指向foo1
,所以this.some
挂载在foo1
对象下。
属性查找: foo1.some
foo1.some
直接读取foo1
的属性。
foo1.test
、foo1.a
:foo1
对象的原型
根据上文提到的:构造/new调用函数的时候会创建一个新对象(foo1
),自动将foo1
的原型(Object.getPrototypeOf(foo1)
)指向构造函数的prototype对象。
构造调用会执行函数,所以foo.prototype.a = 'aaaaa'
也会执行,单就赋值这个层面来说写在foo
外面和写在foo
里面是一样的。
属性查找:foo1.test
、foo1.a
foo1
本身没有找到,继续查找
foo1
的原型Object.getPrototypeOf(foo1)
上找到了a
和test
,返回它们,停止查找。
foo1.obkoro1
和foo1.koro
:返回undefined
# 静态属性: foo.obkoro1
、foo.koro
函数在JS中是一等公民,它也是一个对象, 用来模拟类。
这两个属性跟foo1
没有关系,它是对象foo
上的两个属性(类似函数的:arguments
/prototype
/length
等属性),称为静态属性 。
它们只能通过foo.obkoro1
和foo.koro
来访问。
# 原型对象改变,原型链下游获取的值也会改变
上面那个例子中的foo1.test
的值是什么?
foo. prototype. test = 'test'
let foo1 = new foo ( ) // `foo1`上有哪些属性,这些属性分别挂载在哪个地方
foo. prototype. test = 'test2' // 重新赋值
foo1.test
的值是test2
,原因是:foo1
的原型对象是Object.getPrototypeOf(foo1)
存的指针,指向foo.prototype
的内存地址,不是拷贝,每次读取的值都是当前foo.prototype
的最新值。
打印foo1
:
# 小结
写了好几天,之前网上很多图文博客,那些线指来指去,就我个人看来还是比较难以理解的,所以本文纯文字的形式来描述这些概念,相信认真看完的同学肯定都有所收获,如果没看懂的话,建议多看几遍,这部分概念真的很重要!
PS:实际上还有很多引申出来的东西没写全,准备放到其他文章中去写。
# 觉得我的博客对你有帮助的话,就给我点个Star 吧!
前端进阶积累 、公众号 、GitHub 、wx:OBkoro1、邮箱:[email protected]
以上2019/8/25
作者:OBKoro1
参考资料:
MDN:对象原型
JS原型链与继承别再被问倒了
从__proto__和prototype来深入理解JS对象和原型链
博客链接
# 轻松掌握浏览器重绘重排原理
很多人都知道要减少浏览器的重排和重绘,但对其中的具体原理以及如何具体操作并不是很了解,当突然提起这个话题的时候,还是会一脸懵逼。希望大家可以耐着性子阅读本文,仔细琢磨,彻底掌握这个知识点!
博客 、前端积累文档 、公众号 、GitHub
# 网页生成过程:
HTML被HTML解析器解析成DOM 树
css则被css解析器解析成CSSOM 树
结合DOM树和CSSOM树,生成一棵渲染树(Render Tree)
生成布局(flow),即将所有渲染树的所有节点进行平面合成
将布局绘制(paint)在屏幕上
第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染 。
网上找了一张图片,我加了注释会更直观一些:
# 渲染:
网页生成的时候,至少会渲染一次 。
在用户访问的过程中,还会不断重新渲染
重新渲染需要重复之前的第四步(重新生成布局)+第五步(重新绘制)或者只有第五个步(重新绘制)。
# 重排比重绘大:
大,在这个语境里的意思是:谁能影响谁?
重绘:某些元素的外观被改变,例如:元素的填充颜色
重排:重新生成布局,重新排列元素。
就如上面的概念一样,单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分 。
比如改变元素高度,这个元素乃至周边dom都需要重新绘制。
也就是说:"重绘"不一定会出现"重排","重排"必然会出现"重绘"
# 重排(reflow):
# 概念:
当DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
重排也叫回流,重排的过程以下面这种理解方式更清晰一些:
回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流
# 常见引起重排属性和方法
任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发重排 ,下面列一些栗子:
添加或者删除可见的DOM元素;
元素尺寸改变——边距、填充、边框、宽度和高度
内容变化,比如用户在input框中输入文字
浏览器窗口尺寸改变——resize事件发生时
计算 offsetWidth 和 offsetHeight 属性
设置 style 属性的值
常见引起重排属性和方法
width
height
margin
padding
display
border
position
overflow
clientWidth
clientHeight
clientTop
clientLeft
offsetWidth
offsetHeight
offsetTop
offsetLeft
scrollWidth
scrollHeight
scrollTop
scrollLeft
scrollIntoView()
scrollTo()
getComputedStyle()
getBoundingClientRect()
scrollIntoViewIfNeeded()
# 重排影响的范围:
由于浏览器渲染界面是基于流失布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种:
全局范围:从根节点html
开始对整个渲染树进行重新布局。
局部范围:对渲染树的某部分或某一个渲染对象进行重新布局
全局范围重排 :
< body>
< div class = " hello" >
< h4> hello</ h4>
< p> < strong> Name:</ strong> BDing</ p>
< h5> male</ h5>
< ol>
< li> coding</ li>
< li> loving</ li>
</ ol>
</ div>
</ body>
当p节点上发生reflow时,hello和body也会重新渲染,甚至h5和ol都会收到影响。
局部范围重排:
用局部布局来解释这种现象:把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界。
# 尽可能的减少重排的次数、重排范围:
重排需要更新渲染树,性能花销非常大:
它们的代价是高昂的,会破坏用户体验,并且让UI展示非常迟缓,我们需要尽可能的减少触发重排的次数。
重排的性能花销跟渲染树有多少节点需要重新构建有关系:
所以我们应该尽量以局部布局的形式组织html
结构,尽可能小的影响重排的范围。
而不是像全局范围的示例代码一样一溜的堆砌标签,随便一个元素触发重排都会导致全局范围的重排。
# 重绘(Repaints):
概念 :
当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。
常见的引起重绘的属性 :
color
border-style
visibility
background
text-decoration
background-image
background-position
background-repeat
outline-color
outline
outline-style
border-radius
outline-width
box-shadow
background-size
# 浏览器的渲染队列:
思考以下代码将会触发几次渲染?
div. style. left = '10px' ;
div. style. top = '10px' ;
div. style. width = '20px' ;
div. style. height = '20px' ;
根据我们上文的定义,这段代码理论上会触发4次重排+重绘,因为每一次都改变了元素的几何属性,实际上最后只触发了一次重排,这都得益于浏览器的渲染队列机制 :
当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔 时,浏览器就会批量执行这些操作。
# 强制刷新队列:
div. style. left = '10px' ;
console. log ( div. offsetLeft) ;
div. style. top = '10px' ;
console. log ( div. offsetTop) ;
div. style. width = '20px' ;
console. log ( div. offsetWidth) ;
div. style. height = '20px' ;
console. log ( div. offsetHeight) ;
这段代码会触发4次重排+重绘,因为在console
中你请求的这几个样式信息,无论何时浏览器都会立即执行渲染队列的任务,即使该值与你操作中修改的值没关联。
因为队列中,可能会有影响到这些值的操作,为了给我们最精确的值,浏览器会立即重排+重绘 。
强制刷新队列的style样式请求 :
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle(), 或者 IE的 currentStyle
我们在开发中,应该谨慎的使用这些style请求,注意上下文关系,避免一行代码一个重排,这对性能是个巨大的消耗
# 重排优化建议
就像上文提到的我们要尽可能的减少重排次数、重排范围,这样说很泛,下面是一些行之有效的建议,大家可以参考一下。
# 1. 分离读写操作
div. style. left = '10px' ;
div. style. top = '10px' ;
div. style. width = '20px' ;
div. style. height = '20px' ;
console. log ( div. offsetLeft) ;
console. log ( div. offsetTop) ;
console. log ( div. offsetWidth) ;
console. log ( div. offsetHeight) ;
还是上面触发4次重排+重绘的代码,这次只触发了一次重排:
在第一个console
的时候,浏览器把之前上面四个写操作的渲染队列都给清空了。剩下的console,因为渲染队列本来就是空的,所以并没有触发重排,仅仅拿值而已。
# 2. 样式集中改变
div. style. left = '10px' ;
div. style. top = '10px' ;
div. style. width = '20px' ;
div. style. height = '20px' ;
虽然现在大部分浏览器有渲染队列优化,不排除有些浏览器以及老版本的浏览器效率仍然低下:
建议通过改变class或者csstext属性集中改变样式
// bad
var left = 10 ;
var top = 10 ;
el. style. left = left + "px" ;
el. style. top = top + "px" ;
// good
el. className += " theclassname" ;
// good
el. style. cssText += "; left: " + left + "px; top: " + top + "px;" ;
# 3. 缓存布局信息
// bad 强制刷新 触发两次重排
div. style. left = div. offsetLeft + 1 + 'px' ;
div. style. top = div. offsetTop + 1 + 'px' ;
// good 缓存布局信息 相当于读写分离
var curLeft = div. offsetLeft;
var curTop = div. offsetTop;
div. style. left = curLeft + 1 + 'px' ;
div. style. top = curTop + 1 + 'px' ;
# 4. 离线改变dom
隐藏要操作的dom
在要操作dom之前,通过display隐藏dom,当操作完成之后,才将元素的display属性为可见,因为不可见的元素不会触发重排和重绘。
dom. display = 'none'
// 修改dom样式
dom. display = 'block'
通过使用DocumentFragment 创建一个dom
碎片,在它上面批量操作dom,操作完成之后,再添加到文档中,这样只会触发一次重排。
复制节点,在副本上工作,然后替换它!
# 5. position属性为absolute或fixed
position属性为absolute或fixed的元素,重排开销比较小,不用考虑它对其他元素的影响
# 6. 优化动画
可以把动画效果应用到position属性为absolute或fixed的元素上,这样对其他元素影响较小
动画效果还应牺牲一些平滑,来换取速度,这中间的度自己衡量:
比如实现一个动画,以1个像素为单位移动这样最平滑,但是reflow就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多。
启用GPPU加速
此部分来自优化CSS重排重绘与浏览器性能
GPU(图像加速器):
GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。
GPU 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。
/*
* 根据上面的结论
* 将 2d transform 换成 3d
* 就可以强制开启 GPU 加速
* 提高动画性能
*/
div {
transform: translate3d ( 10 px, 10 px, 0 ) ;
}
# 结语
重排也是导致DOM脚本执行效率低的关键因素之一,重排与重绘作为大厂经常出现的面试题,并且涉及的性能优化,这是前端必须掌握的基本概念/技能之一(敲黑板!)。
重排会不断触发这是不可避免的,但我们在开发时,应尽量按照文中的建议来组织代码,这种优化,需要平时有意识的去做,一点一滴的去做,希望大家重视一下。
以上2018.12.17
参考资料:
网页性能管理详解
优化CSS重排重绘与浏览器性能
博客链接
# JS基础-原型、原型链
JS的原型、原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对这个概念一知半解,碰到问题靠“猜”,却不理解它的规则!
# prototype
# 只有函数有prototype属性
let a = { }
let b = function ( ) { }
console. log ( a. prototype) // undefined
console. log ( b. prototype) // { constructor: function(){...} }
# Object.prototype怎么解释?
其实Object
是一个全局对象,也是一个构造函数,以及其他基本类型的全局对象也都是构造函数:
function outTypeName ( data, type) {
let typeName = Object. prototype. toString. call ( data)
console. log ( typeName)
}
outTypeName ( Object) //[object Function]
outTypeName ( String) // [object Function]
outTypeName ( Number) // [object Function]
# 为什么只有函数有prototype属性
JS通过new
来生成对象,但是仅靠构造函数,每次生成的对象都不一样。
有时候需要在两个对象之间共享属性,由于JS在设计之初没有类的概念,所以JS使用函数的prototype
来处理这部分需要被共享的属性 ,通过函数的prototype
来模拟类:
当创建一个函数时,JS会自动为函数添加prototype
属性,值是一个有constructor
的对象。
以下是共享属性prototype
的栗子:
function People ( name) {
this . name = name
}
People. prototype. age = 23 // 岁数
// 创建两个实例
let People1 = new People ( 'OBKoro1' )
let People2 = new People ( '扣肉' )
People. prototype. age = 24 // 长大了一岁
console. log ( People1. age, People2. age) // 24 24
为什么People1
和People2
可以访问到People.prototype.age
?
原因是:People1
和People2
的原型是People.prototype
,答案在下方的:构造函数是什么以及它做了什么。
# 原型链
# __proto__
和Object.getPrototypeOf(target)
: 对象的原型
__proto__
是对象实例和它的构造函数之间建立的链接,它的值是:构造函数的`prototype。
也就是说:__proto__
的值是它所对应的原型对象,是某个函数的prototype
Object.getPrototypeOf(target)
全等于__proto__
。
它是ES6的标准,兼容IE9,主流浏览器也都支持,MDN ,本文将以Object.getPrototypeOf(target)
指代__proto__
。
# 不要再使用__proto__
:
本段摘自阮一峰-ES6入门 ,具体解析请点击链接查看
__proto__
属性没有写入 ES6 的正文,而是写入了附录。
原因是它本质上是一个内部属性,而不是一个正式的对外的 API,只是由于浏览器广泛支持,才被加入了 ES6 。
标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的 。
所以无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,应该使用:Object.getPrototypeOf(target)
(读操作)、Object.setPrototypeOf(target)
(写操作)、Object.create(target)
(生成操作)代替
# 构造函数是什么、它做了什么
出自《你不知道的在js》:在js中, 实际上并不存在所谓的'构造函数',只有对于函数的'构造调用'。
上文一直提到构造函数,所谓的构造函数,实际上就是通过关键字new
来调用的函数:
let newObj = new someFn ( ) // 构造调用函数
构造/new调用函数的时候做了什么 :
创建一个全新的对象。
这个新对象的原型(Object.getPrototypeOf(target)
)指向构造函数的prototype
对象。
该函数的this会绑定在新创建的对象上。
如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
我们称这个新对象为构造函数的实例。
原型继承就是利用构造调用函数的特性 :
SubType. prototype = new SuperType ( ) ; // 原型继承:SubType继承SuperType
SubType. prototype. constructor = SubType // 重新指定constructor指向 方便找到构造函数
// 挂载SuperType的this和prototype的属性和方法到SubType.prototype上
�构造调用的第二点:将新对象的Object.getPrototypeOf(target)
指向函数的prototype
构造调用的第三点:该函数的this会绑定在新创建的对象上。
新对象赋值给SubType.prototype
原型类型有个缺点:多个实例对引用类型的操作会被篡改。
因为每次实例化引用类型的数据都指向同一个地址,所以它们读/写的是同一个数据,当一个实例对其进行操作,其他实例的数据就会一起更改。
# 原型链是什么
来看个例子:
function foo ( ) { }
const newObj = new foo ( ) // 构造调用foo 返回一个新对象
const newObj__proto__ = Object. getPrototypeOf ( newObj) // 获取newObj的原型对象
newObj__proto__ === foo. prototype // true 验证newObj的原型指向foo
const foo__proto__ = Object. getPrototypeOf ( foo. prototype) // 获取foo.prototype的原型
foo__proto__ === Object. prototype // true foo.prototype的原型是Object.prototype
``
如果用以前的语法,从`newObj` 查找`foo` 的原型,是这样的:
`` `js
newObj. __proto__. __proto__ // 这种关系就是原型链
可以用以下三句话来理解原型链 :
每个对象都拥有一个原型对象 : newObj
的原型是foo.prototype
。
对象的原型可能也是继承其他原型对象的 : foo.prototype
也有它的原型Object.prototype
。
一层一层的,以此类推,这种关系就是原型链 。
# 一个对象是否在另一个对象的原型链上
如果一个对象存在另一个对象的原型链上,我们可以说:它们是继承关系。
判断方式有两种,但都是根据构造函数的prototype
是否在原型链上来判断的:
instanceof
: 用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置
语法:object instanceof constructor
let test = function ( ) { }
let testObject = new test ( ) ;
testObject instanceof test // true test.prototype在testObject的原型链上
testObject instanceof Function // false Function.prototype 不在testObject的原型链上
testObject instanceof Object // true Object.prototype在testObject的原型链上
isPrototypeOf
:测试一个对象是否存在于另一个对象的原型链上
语法:prototypeObj.isPrototypeOf(object)
let test = function ( ) { }
let testObject = new test ( ) ;
test. prototype. isPrototypeOf ( testObject) // true test.prototype在testObject的原型链上
Object. prototype. isPrototypeOf ( testObject) // true Object.prototype在testObject的原型链上
# 原型链的终点: Object.prototype
Object.prototype
是原型链的终点,所有对象都是从它继承了方法和属性。
Object.prototype
没有原型对象 :
const proto = Object. getPrototypeOf ( Object. prototype) // null
下面是两个验证例子,有疑虑的同学多写几个测试用例印证一下。
字符串原型链的终点 :Object.prototype
let test = '由String函数构造出来的'
let stringPrototype = Object. getPrototypeOf ( test) // 字符串的原型
stringPrototype === String. prototype // true 字符串的原型是String对象
Object. getPrototypeOf ( stringPrototype) === Object. prototype // true String对象的原型是Object对象
函数原型链的终点 :Object.prototype
let test = function ( ) { }
let fnPrototype = Object. getPrototypeOf ( test)
fnPrototype === Function. prototype // true test的原型是Function.prototype
Object. getPrototypeOf ( Function. prototype) === Object. prototype // true
# 原型链用来做什么?
# 属性查找:
如果试图访问对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性,以此类推
我们用一个例子来形象说明一下:
let test = '由String函数构造出来的'
let stringPrototype = Object. getPrototypeOf ( test) // 字符串的原型
stringPrototype === String. prototype // true 字符串的原型是String对象
Object. getPrototypeOf ( stringPrototype) === Object. prototype // true String对象的原型是Object对象
当你访问test
的某个属性时,浏览器会进行以下查找:
浏览器首先查找test
本身
接着查找它的原型对象:String.prototype
最后查找String.prototype
的原型对象:Object.prototype
一旦在原型链上找到该属性,就会立即返回该属性,停止查找。
原型链上的原型都没有找到的话,返回undefiend
这种查找机制还解释了字符串为何会有自带的方法: slice
/split
/indexOf
等。
准确的说:
这些属性和方法是定义在String
这个全局对象/函数上的。
字符串的原型指向了String
函数的prototype
。
之后通过查找原型链,在String函数的prototype
中找到这些属性和方法。
# 拒绝查找原型链:
hasOwnProperty
: 指示对象自身属性中是否具有指定的属性
语法:obj.hasOwnProperty(prop)
参数: prop
要查找的属性
返回值: 用来判断某个对象是否含有指定的属性的Boolean
。
let test = { 'OBKoro1' : '扣肉' }
test. hasOwnProperty ( 'OBKoro1' ) ; // true
test. hasOwnProperty ( 'toString' ) ; // false test本身没查找到toString
这个API
是挂载在object.prototype
上,所有对象都可以使用,API会忽略掉那些从原型链上继承到的属性。
# 扩展:
# 实例的属性
你知道构造函数的实例对象上有哪些属性吗?这些属性分别挂载在哪个地方?原因是什么?
function foo ( ) {
this . some = '222'
let ccc = 'ccc'
foo. obkoro1 = 'obkoro1'
foo. prototype. a = 'aaa'
}
foo. koro = '扣肉'
foo. prototype. test = 'test'
let foo1 = new foo ( ) // `foo1`上有哪些属性,这些属性分别挂载在哪个地方
foo. prototype. test = 'test2' // 重新赋值
上面这道是考察JS
基础的题,很多人都没说对,原因是没有彻底掌握this
、原型链
、函数
。
# 想一下再看解析:
# 想一下再看解析:
# 想一下再看解析:
# 想一下再看解析:
# 想一下再看解析:
this.some
:foo1
对象的属性
通过构造调用foo
的this
指向foo1
,所以this.some
挂载在foo1
对象下。
属性查找: foo1.some
foo1.some
直接读取foo1
的属性。
foo1.test
、foo1.a
:foo1
对象的原型
根据上文提到的:构造/new调用函数的时候会创建一个新对象(foo1
),自动将foo1
的原型(Object.getPrototypeOf(foo1)
)指向构造函数的prototype对象。
构造调用会执行函数,所以foo.prototype.a = 'aaaaa'
也会执行,单就赋值这个层面来说写在foo
外面和写在foo
里面是一样的。
属性查找:foo1.test
、foo1.a
foo1
本身没有找到,继续查找
foo1
的原型Object.getPrototypeOf(foo1)
上找到了a
和test
,返回它们,停止查找。
foo1.obkoro1
和foo1.koro
:返回undefined
# 静态属性: foo.obkoro1
、foo.koro
函数在JS中是一等公民,它也是一个对象, 用来模拟类。
这两个属性跟foo1
没有关系,它是对象foo
上的两个属性(类似函数的:arguments
/prototype
/length
等属性),称为静态属性 。
它们只能通过foo.obkoro1
和foo.koro
来访问。
# 原型对象改变,原型链下游获取的值也会改变
上面那个例子中的foo1.test
的值是什么?
foo. prototype. test = 'test'
let foo1 = new foo ( ) // `foo1`上有哪些属性,这些属性分别挂载在哪个地方
foo. prototype. test = 'test2' // 重新赋值
foo1.test
的值是test2
,原因是:foo1
的原型对象是Object.getPrototypeOf(foo1)
存的指针,指向foo.prototype
的内存地址,不是拷贝,每次读取的值都是当前foo.prototype
的最新值。
打印foo1
:
# 小结
写了好几天,之前网上很多图文博客,那些线指来指去,就我个人看来还是比较难以理解的,所以本文纯文字的形式来描述这些概念,相信认真看完的同学肯定都有所收获,如果没看懂的话,建议多看几遍,这部分概念真的很重要!
PS:实际上还有很多引申出来的东西没写全,准备放到其他文章中去写。
# 觉得我的博客对你有帮助的话,就给我点个Star 吧!
前端进阶积累 、公众号 、GitHub 、wx:OBkoro1、邮箱:[email protected]
以上2019/8/25
作者:OBKoro1
参考资料:
MDN:对象原型
JS原型链与继承别再被问倒了
从__proto__和prototype来深入理解JS对象和原型链
博客链接
# 防抖函数&节流函数
节流函数和防抖函数是 JS 比较重要的概念,应用好了可以提高很大的性能,在面试中也是很高频的一个考点。下面一起来看看这两种方法是如何使用的:
# 防抖函数(debounce):
# 概念:
在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时 。
# 生活中的栗子:
如果有人进电梯(触发事件),那电梯将在 10 秒钟后出发(执行事件),这时如果又有人进电梯了(在 10 秒内再次触发该事件),我们又得等 10 秒再出发(重新计时)。
# 代码栗子:
/**
* @description: 防抖函数:函数被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时
* @param {Function} fn 要执行的函数
* @param {Number} wait wait毫秒后执行回调
* @param {*} ...arr 传递给fn的参数
*/
function debounce ( fn, wait, ... arr) {
let timer = null ;
return ( ) => {
if ( timer) {
// 如果有一个函数在等待执行 清除定时器 下面重新计时
clearTimeout ( timer) ;
timer = null ; // 清空timer 下次重启定时器
}
// 设定时器/重置定时器
timer = setTimeout ( ( ) => {
fn ( ... arr) ; // wait时间后 执行回调 期间再触发debounce 需要重新等待
} , wait) ;
} ;
}
// 要防抖的函数
let actionFn = function ( a, b) {
console. log ( '回调' , a, b) ;
} ;
setInterval ( debounce ( actionFn, 500 , 'actionFn参数1' , '参数2' ) , 1000 ) ; // 第一次在1500ms后触发,之后每1000ms触发一次
setInterval ( debounce ( actionFn, 2000 ) , 1000 ) ; // 还没执行就一直重复触发,不会执行
可以这样理解
函数触发停止一段时间后(期间不能再触发 debounce,否则将重新计时),再执行回调函数
# 机制:
防抖函数主要利用定时器的延迟执行特性,根据是否有定时器在等待执行:
触发了一个事件后:如果有一个定时任务待执行,就清除定时器,重新计时。
如果没有任务待执行,就定时执行这个事件。
# 应用场景:
表单的连续点击,防止重复提交。比如重复发送一篇文章。
类百度的搜索,连续输入等输入停止后再搜索。
一直拖动浏览器窗口,只想触发一次事件等。
# 节流函数(throttle):
# 概念:
规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行(单位时间内有事件被多次触发则,只生效一次) 。
# 生活中的栗子:
漏水的自来水水龙头,尽管水龙头里面有很多水(一直在触发事件),但还是一滴一滴的往下滴(单位事件内只生效一次)。
# 代码栗子:
/**
* @description: 节流函数:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行
* @param {Function} fn 要执行的函数
* @param {Number} gapTime 单位时间
* @param {*} ...arr 传递给fn的参数
*/
function throttle ( fn, gapTime, ... arr) {
let last = 0 ; // 上次执行时间 第一次马上执行
return ( ) => {
let nowTime = Date. now ( ) ; // 当前时间
// 当前时间-上次执行的时间是否超过间隔时间 就执行回调
if ( nowTime - last > gapTime) {
fn ( ... arr) ; // ...arr为fn的参数
last = nowTime; // 重置上次执行时间为当前时间 方便下次执行
}
} ;
}
let actionFn = ( a, b) => {
console. log ( '回调' , a, b) ; // 要执行的函数
} ;
setInterval ( throttle ( actionFn, 1000 , 'actionFn参数1' , '参数2' ) , 10 ) ;
// 每隔10毫秒都会触发一次throttle,每隔一秒触发一次actionFn回调(1秒内再次触发被丢弃)
# 机制:
节流函数根据时间差是否超过给定时间(gapTime)来决定是否触发回调。
# 应用场景:
自动保存草稿功能,当用户在输入的时候(一直触发事件),单位时间内只保存一次草稿。
游戏中的刷新率
# 作用和本质:
# 应用实例,需要加个括号:
因为返回debounce
和throttle
返回的是一个函数,所以如果不是自动执行的事件监听回调,我们应该再后面加个()
,执行返回的闭包函数。
document. onclick = ( ) => {
// throttle(actionFn, 1000) 这样不会执行 只返回了一个闭包函数
throttle ( actionFn, 1000 , '执行函数参数1' , '执行函数参数2' ) ( ) ; // 执行闭包函数
} ;
博客链接
# 前端弹幕效果实现思路
这是几个月前写的文章 ,文章下面有很多精彩的讨论,或许能给你一点启发
更好的方式
可以利用canvas
或者 CSS 动画来实现弹幕需求,不过文章中的思路,可以参考一下。
# 实现效果:
# 实现原理:
实现弹幕的原理,并不算太复杂,耗费一些时间,怼一怼应该都可以做出来。
获取弹幕数据。
将弹幕设置为四个通道,每个通道最多只能出现两条弹幕。
使用setInterval
动态设置dom
的left
属性。
使用 dom 的offsetWidth
和屏幕的宽度判断元素是否滚动超出屏幕,然后移除 dom。
# 实现步骤:
# 1. 首先看一下html
的结构。
< div class = " detailImg" >
< img src = " url" />
< div id = " barrageDiv" >
< div id = " barrageLayer1" > </ div>
< div id = " barrageLayer2" > </ div>
< div id = " barrageLayer3" > </ div>
< div id = " barrageLayer4" > </ div>
</ div>
</ div>
<!--detailImg 设置relative, barrageDiv设置z-index在图片上面,以及图片的位置-->
<!-- barrageLayer1~4 主要设置了一个top属性让四个div在各自的水平线上,形成四个通道-->
关于这里的 css 样式,关键点都在上面说了,就注意一下上面通道是怎样形成的,就可以了。具体的样式也就不贴出来了,就根据各自的需求来吧。
# 2.获取弹幕所需要的数据。
要实现弹幕效果肯定需要有数据,这里就是发请求了。
获取数据时,要考虑数据量,一次不可能全部都获取 ,可以一次获取一部分,当数据要加载完的时候,再次请求数据。
这里要记录数据数据是否全部请求完成 ,如果请求完成,就可以不再发送数据,直接用之前获取的全部数据就可以了。
# 3.执行弹幕的函数。
弹幕数据获取后,就执行弹幕运行的函数,因为我在写弹幕函数的时候,设置了很多数据状态,这里就大概讲一下实现思路和关键部分代码。
# 弹幕函数包括的功能:
定时获取数据(判断数据是否加载完毕)
定时发射弹幕(判断通道是否闲置),传入弹幕所需要的内容,用户头像等。
创建 dom 内容,根据传参生成弹幕 div,设置 style 属性,根据控制弹幕数据数组的下标将 div 插入对应的 dom 中。
采用定时器移动 dom,这里是根据内容长度定义弹幕的移动速度。
移动弹幕的过程中判断四个通道是否处于闲置状态,当 dom 移动出了屏幕,移动 dom 并且清除定时器。
function barrage ( ) {
//第一部分先判断数据是否加载完成 这里是一个定时器,设置为15秒。
//如果数据还未加载完毕,就再次运行请求数据的接口,请求的页数可以 数组/每次请求的条数+1
//数据加载完毕就清除定时器。(我将定时器都保存在vue 组件的data里面) 清除的时候clearInterval(this.data);
<span class="token comment">//定时发射</span>
_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>divStatus<span class="token punctuation">.</span>intervalid<span class="token operator">=</span><span class="token function">setInterval</span><span class="token punctuation">(</span> selfTime<span class="token punctuation">,</span><span class="token number">1100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">function</span> <span class="token function">selfTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>_this<span class="token punctuation">.</span>dataNum<span class="token operator">>=</span>_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>data<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">//当dataNum大于等于数组的数量时,弹幕从头再来一遍</span>
_this<span class="token punctuation">.</span>dataNum<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">//设置四个通道的变量,当这几个变量为false的时候,才可发射</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>divStatus<span class="token punctuation">.</span>div1<span class="token operator">===</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">//这里只演示其中一个变量</span>
divStatus<span class="token punctuation">.</span>div1<span class="token operator">=</span><span class="token boolean">true</span><span class="token punctuation">;</span>
_this<span class="token punctuation">.</span>dataNum<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token function">barrageOut</span><span class="token punctuation">(</span>_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>data<span class="token punctuation">[</span>_this<span class="token punctuation">.</span>dataNum<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>content<span class="token punctuation">,</span>_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>data<span class="token punctuation">[</span>_this<span class="token punctuation">.</span>dataNum<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>commentator<span class="token punctuation">.</span>headImgUrl<span class="token punctuation">,</span>_this<span class="token punctuation">.</span>dataNum<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// 创建弹幕内容,自定义弹幕移动速度</span>
<span class="token keyword">function</span> <span class="token function">barrageOut</span><span class="token punctuation">(</span>text<span class="token punctuation">,</span>imgUrl<span class="token punctuation">,</span>num<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//text:弹幕的内容,imgUrl:用户的头像,num:数组的第几个</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>num<span class="token operator">%</span><span class="token number">4</span><span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">//根据数组下标 创建对应通道的节点 这里也演示其中一个</span>
barrageLayer<span class="token operator">=</span>document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'barrageLayer1'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 创建dom内容 定义dom style样式</span>
<span class="token keyword">let</span> divBox <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> divBoxImg<span class="token operator">=</span>document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'span'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> divBoxText<span class="token operator">=</span>document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'span'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
divBox<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'class'</span><span class="token punctuation">,</span><span class="token string">'barrageDivClass'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
divBoxText<span class="token punctuation">.</span>innerHTML<span class="token operator">=</span>text<span class="token punctuation">;</span>
divBox<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>divBoxImg<span class="token punctuation">)</span><span class="token punctuation">;</span>
divBoxImg<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'class'</span><span class="token punctuation">,</span><span class="token string">'barrageDivClass_img'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
divBoxImg<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundImage<span class="token operator">=</span><span class="token template-string"><span class="token string">`url(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>imgUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)`</span></span><span class="token punctuation">;</span>
divBox<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>divBoxText<span class="token punctuation">)</span><span class="token punctuation">;</span>
divBox<span class="token punctuation">.</span>style<span class="token punctuation">.</span>left<span class="token operator">=</span>document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>clientWidth<span class="token operator">+</span><span class="token number">2000</span><span class="token operator">+</span><span class="token string">'px'</span><span class="token punctuation">;</span><span class="token comment">// 初始化left位置,一开始在屏幕的右侧</span>
barrageLayer<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>divBox<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 定时器移动dom,形成弹幕</span>
<span class="token keyword">let</span> time<span class="token punctuation">,</span>l<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>
time<span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>text<span class="token punctuation">.</span>length<span class="token operator"><</span><span class="token number">15</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">// 这里可以根据需求自定义弹幕加载的速度</span>
l<span class="token operator">=</span>l<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
l<span class="token operator">=</span>l<span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">//通过减少left属性移动这个div 从右往左移动</span>
divBox<span class="token punctuation">.</span>style<span class="token punctuation">.</span>left <span class="token operator">=</span> document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>clientWidth<span class="token operator">+</span>l<span class="token operator">+</span><span class="token string">'px'</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> <span class="token function-variable function">delDiv</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=></span><span class="token punctuation">{</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>num<span class="token operator">%</span><span class="token number">4</span><span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">//在移动弹幕的过程中判断四个通道是否处于闲置状态 这里只演示其中一个</span>
barrageLayer<span class="token operator">=</span>document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'barrageLayer1'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>barrageLayer<span class="token punctuation">.</span>childNodes<span class="token punctuation">.</span>length<span class="token operator"><</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">//判断弹幕数量,如果小于2,设为false,上面的定时器可以继续发射弹幕</span>
divStatus<span class="token punctuation">.</span>div1<span class="token operator">=</span><span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
divStatus<span class="token punctuation">.</span>div1<span class="token operator">=</span><span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span><span class="token punctuation">(</span> l <span class="token operator"><=</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token operator">-</span>divBox<span class="token punctuation">.</span>offsetWidth<span class="token operator">-</span><span class="token number">120</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>divStatus<span class="token punctuation">.</span><span class="token keyword">switch</span><span class="token operator">==</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment">//弹幕开关</span>
<span class="token function">delDiv</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>l <span class="token operator"><=</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token operator">-</span>divBox<span class="token punctuation">.</span>offsetWidth<span class="token operator">-</span>document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>clientWidth<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">//不断减少left属性,当小于这个内容的宽度,并且滚了120的时候</span>
barrageLayer<span class="token punctuation">.</span><span class="token function">removeChild</span><span class="token punctuation">(</span>divBox<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//移除dom</span>
<span class="token function">clearInterval</span><span class="token punctuation">(</span>time<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//清除这个div的定时器</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
<span class="token function">clearInterval</span><span class="token punctuation">(</span>time<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//清除这个div的定时器</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token number">20</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
}
博客链接
# 手摸手教你使用WebSocket
在本篇文章之前,WebSocket
很多人听说过,没见过,没用过,以为是个很高大上的技术,实际上这个技术并不神秘,可以说是个很容易就能掌握的技术,希望在看完本文之后,马上把文中的栗子拿出来自己试一试,实践出真知。
游泳、健身了解一下:博客 、前端积累文档 、公众号 、GitHub
# WebSocket
解决了什么问题:
客户端(浏览器)和服务器端进行通信,只能由客户端发起ajax
请求,才能进行通信,服务器端无法主动向客户端推送信息。
当出现类似体育赛事、聊天室、实时位置之类的场景时,客户端要获取服务器端的变化,就只能通过轮询(定时请求)来了解服务器端有没有新的信息变化。
轮询效率低,非常浪费资源(需要不断发送请求,不停链接服务器)
WebSocket的出现,让服务器端可以主动向服务器端发送信息,使得浏览器具备了实时双向通信的能力,这就是WebSocket
解决的问题
# 一个超简单的栗子:
新建一个html
文件,将本栗子找个地方跑一下试试,即可轻松入门WebSocket
:
function socketConnect ( url) {
// 客户端与服务器进行连接
let ws = new WebSocket ( url) ; // 返回`WebSocket`对象,赋值给变量ws
// 连接成功回调
ws. onopen = e => {
console. log ( '连接成功' , e)
ws. send ( '我发送消息给服务端' ) ; // 客户端与服务器端通信
}
// 监听服务器端返回的信息
ws. onmessage = e => {
console. log ( '服务器端返回:' , e. data)
// do something
}
return ws; // 返回websocket对象
}
let wsValue = socketConnect ( 'ws://121.40.165.18:8800' ) ; // websocket对象
上述栗子中WebSocket
的接口地址出自:WebSocket 在线测试 ,在开发的时候也可以用于测试后端给的地址是否可用。
# webSocket的class类:
当项目中很多地方使用WebSocket,把它封成一个class类,是更好的选择。
下面的栗子,做了非常详细的注释,建个html文件也可直接使用 ,websocket的常用API
都放进去了。
下方注释的代码,先不用管,涉及到心跳机制,用于保持WebSocket连接的
class WebSocketClass {
/**
* @description: 初始化实例属性,保存参数
* @param {String} url ws的接口
* @param {Function} msgCallback 服务器信息的回调传数据给函数
* @param {String} name 可选值 用于区分ws,用于debugger
*/
constructor ( url, msgCallback, name = 'default' ) {
this . url = url;
this . msgCallback = msgCallback;
this . name = name;
this . ws = null ; // websocket对象
this . status = null ; // websocket是否关闭
}
/**
* @description: 初始化 连接websocket或重连webSocket时调用
* @param {*} 可选值 要传的数据
*/
connect ( data) {
// 新建 WebSocket 实例
this . ws = new WebSocket ( this . url) ;
this . ws. onopen = e => {
// 连接ws成功回调
this . status = 'open' ;
console. log ( ` ${ this . name} 连接成功` , e)
// this.heartCheck();
if ( data !== undefined) {
// 有要传的数据,就发给后端
return this . ws. send ( data) ;
}
}
// 监听服务器端返回的信息
this . ws. onmessage = e => {
// 把数据传给回调函数,并执行回调
// if (e.data === 'pong') {
// this.pingPong = 'pong'; // 服务器端返回pong,修改pingPong的状态
// }
return this . msgCallback ( e. data) ;
}
// ws关闭回调
this . ws. onclose = e => {
this . closeHandle ( e) ; // 判断是否关闭
}
// ws出错回调
this . onerror = e => {
this . closeHandle ( e) ; // 判断是否关闭
}
}
// heartCheck() {
// // 心跳机制的时间可以自己与后端约定
// this.pingPong = 'ping'; // ws的心跳机制状态值
// this.pingInterval = setInterval(() => {
// if (this.ws.readyState === 1) {
// // 检查ws为链接状态 才可发送
// this.ws.send('ping'); // 客户端发送ping
// }
// }, 10000)
// this.pongInterval = setInterval(() => {
// this.pingPong = false;
// if (this.pingPong === 'ping') {
// this.closeHandle('pingPong没有改变为pong'); // 没有返回pong 重启webSocket
// }
// // 重置为ping 若下一次 ping 发送失败 或者pong返回失败(pingPong不会改成pong),将重启
// console.log('返回pong')
// this.pingPong = 'ping'
// }, 20000)
// }
// 发送信息给服务器
sendHandle ( data) {
console. log ( ` ${ this . name} 发送消息给服务器:` , data)
return this . ws. send ( data) ;
}
closeHandle ( e = 'err' ) {
// 因为webSocket并不稳定,规定只能手动关闭(调closeMyself方法),否则就重连
if ( this . status !== 'close' ) {
console. log ( ` ${ this . name} 断开,重连websocket` , e)
// if (this.pingInterval !== undefined && this.pongInterval !== undefined) {
// // 清除定时器
// clearInterval(this.pingInterval);
// clearInterval(this.pongInterval);
// }
this . connect ( ) ; // 重连
} else {
console. log ( ` ${ this . name} websocket手动关闭` )
}
}
// 手动关闭WebSocket
closeMyself ( ) {
console. log ( `关闭 ${ this . name} ` )
this . status = 'close' ;
return this . ws. close ( ) ;
}
}
function someFn ( data) {
console. log ( '接收服务器消息的回调:' , data) ;
}
// const wsValue = new WebSocketClass('ws://121.40.165.18:8800', someFn, 'wsName'); // 这个链接一天只能发送消息50次
const wsValue = new WebSocketClass ( 'wss://echo.websocket.org' , someFn, 'wsName' ) ; // 阮一峰老师教程链接
wsValue. connect ( '立即与服务器通信' ) ; // 连接服务器
// setTimeout(() => {
// wsValue.sendHandle('传消息给服务器')
// }, 1000);
// setTimeout(() => {
// wsValue.closeMyself(); // 关闭ws
// }, 10000)
栗子里面我直接写在了一起,可以把class
放在一个js文件里面,export
出去,然后在需要用的地方再import
进来,把参数传进去就可以用了。
# WebSocket不稳定
WebSocket并不稳定,在使用一段时间后,可能会断开连接,貌似至今没有一个为何会断开连接的公论,所以我们需要让WebSocket保持连接状态,这里推荐两种方法。
# WebSocket设置变量,判断是否手动关闭连接:
class
类中就是用的这种方式 :设置一个变量,在webSocket关闭/报错的回调中,判断是不是手动关闭的,如果不是的话,就重新连接,这样做的优缺点如下:
优点:请求较少(相对于心跳连接),易设置。
缺点:可能会导致丢失数据,在断开重连的这段时间中,恰好双方正在通信。
# WebSocket心跳机制:
因为第一种方案的缺点,并且可能会有其他一些未知情况导致断开连接而没有触发Error或Close事件。这样就导致实际连接已经断开了,而客户端和服务端却不知道,还在傻傻的等着消息来。
然后聪明的程序猿们想出了一种叫做心跳机制 的解决方法:
客户端就像心跳一样每隔固定的时间发送一次ping
,来告诉服务器,我还活着,而服务器也会返回pong
,来告诉客户端,服务器还活着。
具体的实现方法,在上面class
的注释中,将其打开,即可看到效果 。
# 关于WebSocket
怕一开始就堆太多文字性的内容,把各位吓跑了,现在大家已经会用了,我们再回头来看看WebSocket的其他知识点。
# WebSocket的当前状态:WebSocket.readyState
下面是WebSocket.readyState
的四个值(四种状态):
0: 表示正在连接
1: 表示连接成功,可以通信了
2: 表示连接正在关闭
3: 表示连接已经关闭,或者打开连接失败
我们可以利用当前状态来做一些事情,比如上面栗子中当WebSocket链接成功后,才允许客户端发送ping
。
if ( this . ws. readyState === 1 ) {
// 检查ws为链接状态 才可发送
this . ws. send ( 'ping' ) ; // 客户端发送ping
}
# WebSocket
还可以发送/接收 二进制数据
这里我也没有试过,我是看阮一峰老师的WebSocket教程 才知道有这么个东西,有兴趣的可以再去谷歌,大家知道一下就可以。
二进制数据包括:blob
对象和Arraybuffer
对象,所以我们需要分开来处理。
// 接收数据
ws. onmessage = function ( event) {
if ( event. data instanceof ArrayBuffer ) {
// 判断 ArrayBuffer 对象
}
<span class="token keyword">if</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data <span class="token keyword">instanceof</span> <span class="token class-name">Blob</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">// 判断 Blob 对象</span>
<span class="token punctuation">}</span>
}
// 发送 Blob 对象的例子
let file = document. querySelector ( 'input[type="file"]' ) . files[ 0 ] ;
ws. send ( file) ;
// 发送 ArrayBuffer 对象的例子
var img = canvas_context. getImageData ( 0 , 0 , 400 , 320 ) ;
var binary = new Uint8Array ( img. data. length) ;
for ( var i = 0 ; i < img. data. length; i++ ) {
binary[ i] = img. data[ i] ;
}
ws. send ( binary. buffer) ;
如果你要发送的二进制数据很大的话,如何判断发送完毕:
webSocket.bufferedAmount
属性,表示还有多少字节的二进制数据没有发送出去:
var data = new ArrayBuffer ( 10000000 ) ;
socket. send ( data) ;
if ( socket. bufferedAmount === 0 ) {
// 发送完毕
} else {
// 发送还没结束
}
上述栗子出自阮一峰老师的WebSocket教程
# WebSocket的优点:
最后再吹一波WebSocket:
双向通信(一开始说的,也是最重要的一点)。
数据格式比较轻量,性能开销小,通信高效
协议控制的数据包头部较小,而HTTP协议每次通信都需要携带完整的头部
更好的二进制支持
没有同源限制,客户端可以与任意服务器通信
与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器
# 结语
看了本文之后,如果还是有点迷糊的话,一定要把文中的两个栗子,新建个html文件跑起来,自己鼓捣鼓捣一下。不然读多少博客/教程都没有用,实践才出真知,切勿纸上谈兵。
以上2018.10.22
参考资料:
WebSocket 教程
理解WebSocket心跳及重连机制
WebSocket协议:5分钟从入门到精通
博客链接
# CSS 概念-BFC 深入浅出
好记性不如烂笔头,研究了一下 BFC,发现里面比较细的东西也是很多的!关于 BFC,很多人可能都听说过 BFC 这个东西,大概知道这是个啥东西,相信很多人对此并没有一个非常细致的了解,本文预计篇幅较长,认真,耐着性子看,应该都能够比较深入的理解 BFC 这个概念的规则、作用以及用法。希望喜欢的朋友可以点个赞,或者关注一波本人,谢谢。
# BFC 是什么鬼?
所谓的 BFC 就是 css 布局的一个概念,是一块区域,一个环境。
先稳住别懵逼,接着往下走。
# 关于 BFC 的定义:
BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域 ,只有Block-level box 参与(在下面有解释), 它规定了内部的 Block-level Box 如何布局,并且与这个区域外部毫不相干。
# 通俗的说:
BFC 可以简单的理解为某个元素的一个 CSS 属性 ,拥有这个属性的元素对内部元素和外部元素会表现出一些特性,这就是 BFC 。
# 触发条件
满足下列条件之一就可触发 BFC
根元素,即 HTML 元素
float
的值不为none
overflow
的值不为visible
display
的值为inline-block
、table-cell
、table-caption
position
的值为absolute
或fixed
# BFC 布局规则:
内部的 Box 会在垂直方向,一个接一个地放置。
Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
每个元素的 margin box 的左边, 与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
BFC 的区域不会与 float box 重叠。
BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
计算 BFC 的高度时,浮动元素也参与计算
# BFC 有哪些作用:
自适应两栏布局
可以阻止元素被浮动元素覆盖
可以包含浮动元素——清除内部浮动
分属于不同的 BFC 时可以阻止 margin 重叠
# BFC 的规则和作用介绍:
# BFC 布局规则 1:内部的 Box 会在垂直方向,一个接一个地放置。
上文定义中提到过的块级盒:block-level box,在这里解析一波:
我们平常说的盒子是由 margin、border、padding、content 组成的,实际上每种类型的四条边定义了一个盒子,分别是分别是content box、padding box、border box、margin box ,这四种类型的盒子一直存在,即使他们的值为 0.决定块盒在包含块中与相邻块盒的垂直间距的便是 margin-box。
提示 :Box 之间的距离虽然也可以使用 padding 来控制,但是此时实际上还是属于 box 内部里面,而且使用 padding 来控制的话就不能再使用 border 属性了。
布局规则 1 就是我们平常 div 一行一行块级放置的样式 ,大家想一下就知道了,这里就不展开了。
# BFC 布局规则 2:Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠。
上文提到过,决定块盒在包含块中与相邻块盒的垂直间距的便是 margin-box。,上面的栗子就是这种情况。
演示中 css 属性设置 :上面的 box:margin-bottom: 100px;下面的 box:margin-top: 100px;(他们是同一侧的 margin,所以会发生 margin 重叠的情况,两个 div 的距离实际上只有 100px。)
# BFC 的作用 4:阻止 margin 重叠:
当两个相邻块级子元素分属于不同的 BFC 时可以阻止 margin 重叠
操作方法 :给其中一个 div 外面包一个 div,然后通过触发外面这个 div 的 BFC,就可以阻止这两个 div 的 margin 重叠
下面是代码:
< div class = " aside" > </ div>
< div class = " text" >
< div class = " main" > </ div>
</ div>
<!-- css代码 -->
.aside {
margin-bottom: 100px; //margin属性
width: 100px;
height: 150px;
background: #f66;
}
.main {
margin-top: 100px; //margin属性
height: 200px;
background: #fcc;
}
.text {
/*盒子main的外面包一个div,通过改变此div的属性使两个盒子分属于两个不同的BFC,以此来阻止margin重叠*/
overflow: hidden; //此时已经触发了BFC属性。
}
ps :触发方式可以参考上文给出的触发条件。
# 这里有一个网址可以在线演示 ,通过演示,可以更直观一点:
# BFC 布局规则 3:每个元素的 margin box 的左边, 与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
< div class = " par" >
< div class = " child" > </ div>
//给这两个子div加浮动,浮动的结果,如果没有清除浮动的话,父div不会将下面两个div包裹,但还是在父div的范围之内。
< div class = " child" > </ div>
</ div>
解析 :给这两个子 div 加浮动,浮动的结果,如果没有清除浮动的话,父 div 不会将下面两个 div 包裹,但还是在父 div 的范围之内,左浮是子 div 的左边接触父 div 的 borderbox 的左边,右浮是子 div 接触父 div 的 borderbox 右边 ,除非设置 margin 来撑开距离,否则一直是这个规则。
# BFC 作用 3:可以包含浮动元素——清除内部浮动
给父 divpar 加上 overflow: hidden;
清除浮动原理 :触发父 div 的 BFC 属性,使下面的子 div 都处在父 div 的同一个 BFC 区域之内 ,此时已成功清除浮动。
还可以向同一个方向浮动来达到清除浮动的目的,清除浮动的原理是两个 div 都位于同一个浮动的 BFC 区域之中。
# BFC 布局规则 4:BFC 的区域不会与 float box 重叠:
< div class = " aside" > </ div>
< div class = " text" >
< div class = " main" > </ div>
</ div>
.aside {
width: 100px;
height: 150px;
float: left;
background: #f66;
}
.main {
height: 200px;
overflow: hidden; //触发main盒子的BFC
background: #fcc;
}
.text {
width: 500px;
}
上面 aside 盒子有一个浮动属性,覆盖了 main 盒子的内容,main 盒子没有清除 aside 盒子的浮动。只做了一个动作,就是触发自身的 BFC ,然后就不再被 aside 盒子覆盖 了。所以:BFC 的区域不会与 float box 重叠 。
# BFC 作用:自适应两栏布局。
还是上面的代码,此时 BFC 的区域不会与 float box 重叠,因此会根据包含块(父 div)的宽度,和 aside 的宽度,自适应宽度。
# BFC 与 Layout
IE 作为浏览器中的奇葩,当然不可能按部就班的支持 BFC 标准,于是乎 IE 中有了 Layout 这个东西。Layout 和 BFC 基本是等价的 ,为了处理 IE 的兼容性,在需要触发 BFC 时,我们除了需要用触发条件中的 CSS 属性来触发 BFC,还需要针对 IE 浏览器使用 zoom: 1 来触发 IE 浏览器的 Layout。
# 有趣的文本:
.par {
margin-top : 3rem;
border : 5px solid #fcc;
width : 300px;
}
.child {
border : 5px solid #f66;
width : 100px;
height : 100px;
float : left;
}
原因:
这里两个 div 被撑开,是因为父 div 被 p 标签撑开了,并不是因为清除浮动的原因,从下面这张图片可以清楚的知道。
其实以上的几个例子都体现了 BFC 布局规则第五条————
# BFC 布局规则 5:BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
文本环绕 float :
< div style =" float : left; width : 100px; height : 100px; background : #000; " >
</ div>
< div style =" height : 200px; background : #AAA; " >
< div style =" width : 30px; height : 30px; background : red; " > </ div>
< p> content</ p> < p> content</ p> < p> content</ p> < p> content</ p> < p> content</ p>
</ div>
问题:为什么 div 的左上角被覆盖了,而文本却没有被覆盖,float 不是应该跟普通流不在一个层级吗?是因为 float 属性不生效吗?
解决 :
float 属性定义元素在哪个方向浮动。以往这个属性总应用于图像,使文本围绕在图像周围 ,不过在 CSS 中,任何元素都可以浮动 。浮动元素会生成一个块级框,而不论它本身是何种元素。
从上图可以看到,float 属性确实生效,将 float 隐藏后,下面还有一个红色的 div,这个 div 是被黑色 div 所覆盖掉的。div 会被 float 覆盖,而文本却没有被 float 覆盖 ,是因为float 当初设计的时候 就是为了使文本围绕在浮动对象的周围 。
以上。2017.5.4.
博客链接
# [读书笔记]《高性能 JavaScript》
# 缺陷
这本书是 2010 年出版的,这本书谈性能是有时效性的,现在已经是 2018 年了,这几年前端发展的速度是飞快的,书里面还有一些内容考虑 IE6、7、8 的东西,殊不知现在这些都已经不再考虑了,所以不可避免的有一些知识是比较老的。有些解决方法现在已经不是最好的解决方式,比如工具那一章。
# 前言
总的来说,这本书整体给出的性能优化建议,以及作者耐心的实践,对我们开发优化的启发和帮助还是很大的 ,因为它里边的很多知识,都是作者通过实践总结出来的,都是经验的积累,这在一般的教科书上是学不到的。特别是对于 js 基础比较差一点的,里面有很多知识点尽管在现在还是非常有必要的。
下面我就将各章节的一些重要的知识点总结写出来,争取将干货都提取出来 。
# 第一章-加载和执行
js 的阻塞特性:
当浏览器在执行 js 代码的时候,不能同时做其他事情。(界面 ui 线程和 js 线程用的是同一进程,所以 js 执行越久,网页的响应时间越长。)
脚本的位置
如果把脚本<script>
放在<head>
中,页面会等 js 文件全部下载并执行完成后才开始渲染 ,在这些文件下载和执行的过程中:会导致访问网站的时候有明显的延迟,表现为:页面空白。
性能提升:推荐将所有的<script>
标签尽可能的放到<body>
标签的底部 ,优先渲染页面,减少页面空白时间。
组件脚本。
每个<script>
标签初始下载的时候都会阻塞页面的渲染。性能提升做法:
减少内嵌脚本:减少内嵌的<script>
标签 ,将代码写在一个标签中。
合并外链的 js 文件 :http 请求会带来额外的性能开销,栗子:下载一个 100KB 的 js 文件比下载 4 个 25kb 的 js 文件更快。具体操作方法自行搜索。
无阻塞脚本的方法
script 标签的 aync 属性 :
async 属性规定一旦脚本可用,则会异步执行。async 属性仅适用于外部脚本(只有在使用 src 属性时)。如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
script 标签的 defer 属性 :
js 文件在页面解析到 script 标签的时候开始下载,但并不会执行,dom 加载完成执行。这两个属性的区别在于执行时机 。
动态脚本元素 。
js 操作 dom 创建<script>
标签,自定义生成标签的 type、src 属性。文件会在该元素被添加到页面的时候开始下载。ps:如果文件顺序很重要的话,最好按照顺序合成一个文件。然后再添加到页面中。这样:无论何时启动下载。文件的下载和执行过程不会阻塞页面的其他进程。
# 3. XHR ajax 脚本注入、
用 get 请求一个文件,请求好了。然后创建动态脚本,最后添加进去。
缺陷:文件要再请求页面的同一个域。不能从 cdn 下载
# 第一章加载和执行小结:
把文件放在 body 标签签名,
合并脚本,减少<script>
标签。
采用无阻塞下载 js。使用 script 的 defer 和 async 属性 异步下载。动态创建 script 标签和执行代码。
# 第二章-数据存取
js 四种基本的数据存取位置 。
1、字面量:字符串、数字、布尔、对象、数组、函数、正则、null、undefined,字面量只代表自身,没有存储位置。 2、局部变量。 let var 声明的变量。3、数组元素。4、对象成员。
性能:访问字面量和局部变量的速度是最快的,访问数组和对象成员相对较慢
变量标识符解析过程 :
搜索执行环境的作用域链,查找同名标识符。搜索过程从作用域链头部开始,也就是当前运行函数的活动对象。如果找到,就使用这个标识符,对应的变量;如果没有找到,继续搜索下面的对象。搜索过程会持续进行,直到找到标识符,若无法搜索到匹配的对象,那么标识符被视为未定义、
性能问题:一个标识符所在的位置越深,它的读写速度也就越慢 。因此,函数中读写局部变量总是最快的,而读写全局变量通常是最慢的。
建议:将全局变量存储到局部变量,加快读写速度。
闭包,原型,嵌套对象。
优化建议:将常用的跨作用域变量存储到局部变量,然后直接访问局部变量 。理由如上,变量标识符解析过程。
# 第二章数据存取小结:
访问字面量和局部变量的速度最快,相反,访问数组元素和对象成员相对较慢。
由于局部变量存在于作用域链的起始位置,因为访问局部变量比访问跨作用域变量更快。这个道理同样适用于数组,对象,跨作用域变量 。
把常用的对象,数组,跨域变量保存在局部变量可以改善 js 性能,局部变量访问速度更快。
# 第三章 DOM 编程小结:
dom 操作天生就慢,尽量减少 dom 操作 ,减少访问 dom 的次数。
使用 document.querySelect 来做选择器,比其他方式快。
需要多次访问某个 dom 节点,使用局部变量存储。
html 集合,把集合长度缓存到一个变量中,然后遍历使用这个变量,如果经常操作集合,建议拷到一个数组中。
要留意浏览器的重绘和重排;批量修改样式的时候,‘离线’操作 DOM 树,使用缓存,并减少访问布局信息的次数。
重绘和重排是 DOM 编程优化的一个相当重要概念:重绘和重排 。
动画中使用绝对定义,使用拖放处理。
使用事件委托来减少事件处理器的数量。
# 第四章算法和流程控制小结:
for、while 和 do-while 循环性能差不多,for-in 循环速度只有前面几种类型的 1/7 ,所以尽量避免使用 for-in 循环,除非你需要遍历一个属性数量未知的对象。
forEach 比 for 慢,如果运行速度要求严格,不建议使用。
改善循环性能的最佳方式是减少每次迭代的工作量和减少循环迭代的次数 。
减少迭代工作量:减少属性查找和倒序循环,循环次数越多,性能优化越明显。
for ( var i= 0 ; i< items. length; i++ ) { 代码} //正序循环
for ( var i= items. length; i-- ) { 代码} //倒序循环
//减少属性查找:查找一次属性,把值存在局部变量,在控制条件里面使用这个变量
倒序循环在i> 0 的时候会自动转换为true ,等于0 的时候为false 。
//倒序循环:控制条件从(迭代数少于总数吗?它是否为true?)变为(它是否为true)
减少迭代的次数:"Duff's Device"循环体展开技术,有兴趣的可以看一下,迭代工作量大于 1000 的时候适用。
if-else 与 switch:条件数量越大,越倾向于使用 switch。
优化 if-else:
把最可能出现的条件放在首位.
使用二分法把值域分成一系列的区间。
浏览器的调用栈大小限制了递归算法在 js 中的应用;栈溢出错误会导致其他代码中断运行。
小心使用递归,现在es6递归可以尾递归,在es6中只要使用尾递归就不会发生栈溢出,相对节省性能。
# 第五章字符串和正则表达式小结:
字符串合并的时候使用简单的'+'和'+='操作符。
let str+= 'abc' + 'efg; //2个以上的字符串拼接,会产生临时字符串
let str= str+ 'abc' + 'efg' ; //推荐,提速10%~40%
书里面讲的正则原理和回溯原理,这个很重要 ,找个篇博客 :跟书里面讲的差不多,但还是建议大家可以去找找 PDF 好好看看正则表达式这节。
提高正则表达式效率的方法:
最重要的是:具体化正则表达式!具体化正则表达式!具体化正则表达式!
关注如何让匹配更快失败,找出一些必需,特殊的字符
正则表达式以简单的、必需的字元开始。
使用量词模式,使它们后面的字元互斥。
较少分支数量,缩小分支范围
使用合适的量词
把正则表达式赋值给变量并重用
将复杂的正则拆分为简单的片段
//事实上,书里面讲的方法不止这么几个,而且每一个都有详细的解析 大佬们 还是去看看这一章节吧
正则表达式并不总是最好的解决方案,当你只是搜索字面字符串或者你事先知道字符串的哪一部分将要被查找时:
使用 indexOf()和 lastIndexOf()更适合查找特定字符串的位置或者判断它们是否存在
//例如:判断当前浏览器之类。
# 第六章快速响应的用户界面小结:
js 和用户界面更新在同一个进程中运行,因此一次只能处理一件事情。高效的管理 UI 线程就是要确保 js 不能运行太长时间,以免影响用户体验。
浏览器限制了 js 任务的运行时间,这种限制很有必要,它确保某些恶意代码不能通过永不停止的密集操作锁住用户的浏览器。此限制分为两种:调用栈的大小和长时间运行脚本。
任何 js 任务都不应当执行超过 100 毫秒。过长的运行时间会导致 UI 更新出现明显延迟,从而对用户体验造成负面影响。
定时器可用来安排代码延迟执行,它使得你可以把长时间运行脚本分解成一系列的小任务。
# 第七章 AJAX 小结
这一章节貌似东西都比较老一点。。
post 更适合发送大量数据到服务器。
get 请求能够被浏览器缓存,Expires 头信息设置浏览器缓存请求多久。可用于从不改变的图片或者其他静态数据集(js、css 等)
JSON 是一种使用 js 对象和数组直接量编写的轻量级且易于解析的数据格式,它不仅传输尺寸小,而且解析速度快。JSON 是高性能 AJAX 的基础,尤其在使用动态脚本注入时。
json 应该是近几年一直在用的。。。
减少请求数,通过合并 js 和 css 文件。
缩短页面的加载时间,页面主要内容加载完成后,用 AJAX 获取那些次要的文件。
# 第八章编程实践小结
避免双重求值:避免使用 eval()和 function()构造器来避免双重求值带来的性能消耗,同样的,给 setTimeout()和 setInterval()传递函数而不是字符串作为参数。
//双重求值:就是在js代码中执行另一段js代码,不建议使用下面这些方式
eval ( '代码' )
function 构造函数-- new function ( '代码' )
setTimeout ( ‘代码’, 100 ) 和setInterval ( ‘代码’, 100 )
尽量使用直接量创建对象和数组。直接量的创建和初始化都比非直接量形式要快。
避免做重复工作,能节省的步骤就节省。
js 原生方法总会比你写的任何代码都要快。
# 第九章 构建并部署高性能 js 应用小结
构建和部署的过程对基于 js 的 web 应用的性能有着巨大影响。这个过程中最重要的步骤有:
合并、压缩 js 文件。可使用 Gzip 压缩,能够减少约 70%的体积 !
这些都是在构建过程中完成的工作,不要等到运行时去做,webpack 也是在构建过程中,完成的工作。
通过正确设置 HTTP 响应头来缓存 js 文件,通过向文件名增加时间戳来避免缓存问题。
使用 CDN 提供 js 文件;CDN 不仅可以提升性能,它也为你管理文件的压缩与缓存,。
# 第十章 工具 小结:
当网页变慢时,分析从网络下载的资源以及分析的资源以及分析脚本的运行性能能让你专注于那些最需要优化的地方。
使用网络分析工具找出加载脚本和页面中其他资源的瓶颈,这会帮助你决定那些脚本需要延迟加载,或者需要进一步分析。
检查图片、样式表和脚本的加载过程,以及它们对页面整体加载和渲染的影响。
针对性的做出优化
把脚本尽可能延迟加载,这样做可以加快页面渲染速度,给用户带来更好的体验。
确认脚本和其他资源文件的加载过程已经被优化
这里主要是说文件从服务器的下载速度,比如服务器那边的配置问题之类的。
栗子:我就被后端坑过。一个js文件就200k ,下载下来需要50秒钟!
后面发现原来是后端那边nginx没有开启加速配置什么的,导致出现的问题,找问题找半天。
测试脚本的运行时间,用一个 Date 实例减去另一个 Date 实例,得到的时间差就是脚本运行消耗的时间。
let start = new Date ( ) ;
//你的代码
let time = newDate ( ) - start;
chrome ,火狐 等主流浏览器的控制面板,已经能够反映很多性能问题 。仔细分析就能找出很多问题。例如:资源加载,断点等
# 后话
事实上,自认为这本书最宝贵的就是一些提到的细节 ,比如:
1、字符串合并的时候使用简单的'+'和'+='操作符。
let str+= 'abc' + 'efg' ; //2个以上的字符串拼接,会产生临时字符串
let str= str+ 'abc' + 'efg' ; //推荐,提速10%~40%
2、避免双重求值:避免使用 eval()和 function()构造器来避免双重求值带来的性能消耗,同样的,给 setTimeout()和 setInterval()传递函数而不是字符串作为参数。
//双重求值:就是在js代码中执行另一段js代码,不建议使用下面这些方式
eval ( '代码' )
function 构造函数-- new function ( '代码' )
setTimeout ( ‘代码’, 100 ) 和setInterval ( ‘代码’, 100 )
# 这些东西可以让我们知道什么是更好的实践,什么样的代码可以跑得更快,让我们养成更好的开发习惯。
书不太厚,如果对里面的内容感兴趣,还是建议买一本回家看一看。
以上 2018.1.9
博客链接
# 从零开始带你写一个运行命令行的终端[手把手教学]
# 前言
Electron
很出名,很多人可能了解过,知道它是用来开发桌面端的应用,但是一直没有在项目中实践过,缺乏练手的实践项目。
很多开源的命令行终端都是使用Electron
来开发的,本文将从零开始手把手的教大家用Electron
写一个命令行终端。
作为一个完整的实战项目示例,该终端demo也将集成到Electron
开源学习项目electron-playground 中,目前这个项目拥有700+ Star⭐️,它最大的特点是所见即所得的演示Electron
的各种特性,帮助大家快速学习、上手Electron
。
大家跟着本文一起来试试Electron吧~
# 终端效果
开源地址: electron-terminal-demo
giit提交代码演示
# 目录
初始化项目。
项目目录结构
Electron启动入口index-创建窗口
进程通信类-processMessage。
窗口html页面-命令行面板
命令行面板做了哪些事情
核心方法:child_process.spawn-执行命令行监听命令行的输出
stderr不能直接识别为命令行执行错误
命令行终端执行命令保存输出信息的核心代码
html完整代码
命令行终端的更多细节
下载试玩
小结
# 初始化项目
npm init
npm install electron - D
如果Electron安装不上去,需要添加一个.npmrc
文件,来修改Electron
的安装地址,文件内容如下:
registry=https://registry.npm.taobao.org/
electron_mirror=https://npm.taobao.org/mirrors/electron/
chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver
修改一下package.json
的入口main
和scripts
选项, 现在package.json
长这样,很简洁:
{
"name" : "electron-terminal" ,
"version" : "1.0.0" ,
"main" : "./src/index.js" ,
"scripts" : {
"start" : "electron ."
} ,
"devDependencies" : {
"electron" : "^11.1.1"
}
}
# 项目目录结构
我们最终实现的项目将是下面这样子的,页面css文件不算的话,我们只需要实现src下面的三个文件即可。
.
├── . vscode // 使用vscode的调试功能启动项目
├── node_dodules
├── src
│ ├── index. js // Electron启动入口-创建窗口
│ └── processMessage. js // 主进程和渲染进程通信类-进程通信、监听时间
│ └── index. html // 窗口html页面-命令行面板、执行命令并监听输出
│ └── index. css // 窗口html的css样式 这部分不写
├── package . json
└── . npmrc // 修改npm安装包的地址
└── . gitignore
# Electron启动入口index-创建窗口
创建窗口, 赋予窗口直接使用node的能力。
窗口加载本地html页面
加载主线程和渲染进程通信逻辑
// ./src/index.js
const { app, BrowserWindow } = require ( 'electron' )
const processMessage = require ( './processMessage' )
// 创建窗口
function createWindow ( ) {
// 创建窗口
const win = new BrowserWindow ( {
width: 800 ,
height: 600 ,
webPreferences: {
nodeIntegration: true , // 页面直接使用node的能力 用于引入node模块 执行命令
} ,
} )
// 加载本地页面
win. loadFile ( './src/index.html' )
win. webContents. openDevTools ( ) // 打开控制台
// 主线程和渲染进程通信
const ProcessMessage = new processMessage ( win)
ProcessMessage. init ( )
}
// app ready 创建窗口
app. whenReady ( ) . then ( createWindow)
# 进程通信类-processMessage
electron分为主进程和渲染进程,因为进程不同,在各种事件发生的对应时机需要相互通知来执行一些功能。
这个类就是用于它们之间的通信的,electron通信这部分封装的很简洁了,照着用就可以了。
// ./src/processMessage.js
const { ipcMain } = require ( 'electron' )
class ProcessMessage {
/**
* 进程通信
* @param {*} win 创建的窗口
*/
constructor ( win) {
this . win = win
}
init ( ) {
this . watch ( )
this . on ( )
}
// 监听渲染进程事件通信
watch ( ) {
// 页面准备好了
ipcMain. on ( 'page-ready' , ( ) => {
this . sendFocus ( )
} )
}
// 监听窗口、app、等模块的事件
on ( ) {
// 监听窗口是否聚焦
this . win. on ( 'focus' , ( ) => {
this . sendFocus ( true )
} )
this . win. on ( 'blur' , ( ) => {
this . sendFocus ( false )
} )
}
/**
* 窗口聚焦事件发送
* @param {*} isActive 是否聚焦
*/
sendFocus ( isActive) {
// 主线程发送事件给窗口
this . win. webContents. send ( 'win-focus' , isActive)
}
}
module. exports = ProcessMessage
# 窗口html页面-命令行面板
在创建窗口的时候,我们赋予了窗口使用node的能力, 可以在html中直接使用node模块。
所以我们不需要通过进程通信的方式来执行命令和渲染输出,可以直接在一个文件里面完成。
终端的核心在于执行命令,渲染命令行输出,保存命令行的输出 。
这些都在这个文件里面实现了,代码行数不到250行。
# 命令行面板做了哪些事情
# 核心方法:child_process.spawn-执行命令行监听命令行的输出
# child_process.spawn介绍
spawn
是node子进程模块child_process
提供的一个异步方法。
它的作用是执行命令并且可以实时监听命令行执行的输出 。
当我第一次知道这个API的时候,我就感觉这个方法简直是为命令行终端量身定做的。
终端的核心也是执行命令行,并且实时输出命令行执行期间的信息。
下面就来看看它的使用方式。
# 使用方式
const { spawn } = require ( 'child_process' ) ;
const ls = spawn ( 'ls' , {
encoding: 'utf8' ,
cwd: process. cwd ( ) , // 执行命令路径
shell: true , // 使用shell命令
} )
// 监听标准输出
ls. stdout. on ( 'data' , ( data) => {
console. log ( stdout: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
) ;
} ) ;
// 监听标准错误
ls. stderr. on ( 'data' , ( data) => {
console. error ( stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
) ;
} ) ;
// 子进程关闭事件
ls. on ( 'close' , ( code) => {
console. log ( 子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
) ;
} ) ;
api的使用很简单,但是终端信息的输出,需要很多细节的处理,比如下面这个。
# stderr不能直接识别为命令行执行错误
stderr
虽然是标准错误输出,但里面的信息不全是错误的信息,不同的工具会有不同的处理。
对于git
来说,有很多命令行操作的输出信息都输出在stederr
上。
比如git clone
、git push
等,信息输出在stederr
中,我们不能将其视为错误。
git
总是将详细的状态信息和进度报告 ,以及只读信息,发送给stederr
。
具体细节可以查看git stderr(错误流)探秘 等资料。
暂时还不清楚其他工具/命令行也有没有类似的操作,但是很明显我们不能将stederr
的信息视为错误的信息。
PS: 对于git如果想提供更好的支持,需要根据不同的git
命令进行特殊处理,比如对下面clear
命令和cd
命令的特殊处理。
根据子进程close事件判断命令行是否执行成功
我们应该检测close
事件的退出码code
, 如果code
为0则表示命令行执行成功,否则即为失败。
# 命令行终端执行命令保存输出信息的核心代码
下面这段是命令行面板的核心代码,我贴一下大家重点看一下,
其他部分都是一些细节、优化体验、状态处理这样的代码,下面会将完整的html贴上来。
const { spawn } = require ( 'child_process' ) // 使用node child_process模块
// 执行命令行
actionCommand ( ) {
// 处理command命令
const command = this . command. trim ( )
this . isClear ( command)
if ( this . command === '' ) return
// 执行命令行
this . action = true
this . handleCommand = this . cdCommand ( command)
const ls = spawn ( this . handleCommand, {
encoding: 'utf8' ,
cwd: this . path, // 执行命令路径
shell: true , // 使用shell命令
} )
// 监听命令行执行过程的输出
ls. stdout. on ( 'data' , ( data) => {
const value = data. toString ( ) . trim ( )
this . commandMsg. push ( value)
console. log ( `stdout: ${ value} ` )
} )
ls. stderr. on ( 'data' , this . stderrMsgHandle)
ls. on ( 'close' , this . closeCommandAction)
} ,
// 错误或详细状态进度报告 比如 git push
stderrMsgHandle ( data) {
console. log ( stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
)
this . commandMsg. push ( stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
)
} ,
// 执行完毕 保存信息 更新状态
closeCommandAction ( code) {
// 保存执行信息
this . commandArr. push ( {
code, // 是否执行成功
path: this . path, // 执行路径
command: this . command, // 执行命令
commandMsg: this . commandMsg. join ( '\r' ) , // 执行信息
} )
// 清空
this . updatePath ( this . handleCommand, code)
this . commandFinish ( )
console. log (
子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 运行</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'成功'</span> <span class="token punctuation">:</span> <span class="token string">'失败'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
)
}
# html完整代码
这里是html的完整代码,代码中有详细注释,建议根据上面的命令行面板做了哪些事情 ,来阅读源码。
<!DOCTYPE html>
< html>
< head>
< meta charset = " UTF-8" />
< meta name = " viewport" content = " width=device-width, initial-scale=1.0" />
< title> 极简electron终端</ title>
< link
rel = " stylesheet"
href = " https://unpkg.com/element-ui/lib/theme-chalk/index.css"
/>
< script src = " https://unpkg.com/vue" > </ script>
<!-- 引入element -->
< script src = " https://unpkg.com/element-ui/lib/index.js" > </ script>
<!-- css -->
< link rel = " stylesheet" href = " ./index.css" />
</ head>
< body>
< div id = " app" >
< div class = " main-class" >
<!-- 渲染过往的命令行 -->
< div v-for = " item in commandArr" >
< div class = " command-action" >
<!-- 执行成功或者失败图标切换 -->
< i
:class = " [' el-icon-right' , ' command-action-icon' , { ' error-icon' : item.code !== 0 }]"
> </ i>
<!-- 过往执行地址和命令行、信息 -->
< span class = " command-action-path" > {{ item.path }} $</ span>
< span class = " command-action-contenteditable"
> {{ item.command }}</ span
>
</ div>
< div class = " output-command" > {{ item.commandMsg }}</ div>
</ div>
<!-- 当前输入的命令行 -->
< div
class = " command-action command-action-editor"
@mouseup = " timeoutFocusInput"
>
< i class = " el-icon-right command-action-icon" > </ i>
<!-- 执行地址 -->
< span class = " command-action-path" > {{ path }} $</ span>
<!-- 命令行输入 -->
< span
:contenteditable = " action ? false : ' plaintext-only' "
class = " command-action-contenteditable"
@input = " onDivInput($event)"
@keydown = " keyFn"
> </ span>
</ div>
<!-- 当前命令行输出 -->
< div class = " output-command" >
< div v-for = " item in commandMsg" > {{item}}</ div>
</ div>
</ div>
</ div>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span><span class="token script language-javascript">
<span class="token keyword">const</span> <span class="token punctuation">{</span> ipcRenderer <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'electron'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> spawn <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'child_process'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span>
<span class="token keyword">var</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
el<span class="token punctuation">:</span> <span class="token string">'#app'</span><span class="token punctuation">,</span>
data<span class="token punctuation">:</span> <span class="token punctuation">{</span>
path<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 命令行目录</span>
command<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 用户输入命令</span>
handleCommand<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 经过处理的用户命令 比如清除首尾空格、添加获取路径的命令</span>
commandMsg<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 当前命令信息</span>
commandArr<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 过往命令行输出保存</span>
isActive<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 终端是否聚焦</span>
action<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token comment">// 是否正在执行命令</span>
inputDom<span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token comment">// 输入框dom</span>
addPath<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 不同系统 获取路径的命令 mac是pwd window是chdir</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token function">mounted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addGetPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>inputDom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span>
<span class="token string">'.command-action-contenteditable'</span>
<span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token function">cwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 初始化路径</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">watchFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
ipcRenderer<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">'page-ready'</span><span class="token punctuation">)</span> <span class="token comment">// 告诉主进程页面准备好了</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
<span class="token comment">// 回车执行命令</span>
<span class="token function">keyFn</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>keyCode <span class="token operator">==</span> <span class="token number">13</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">actionCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 执行命令</span>
<span class="token function">actionCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> command <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isClear</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">===</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token keyword">return</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>action <span class="token operator">=</span> <span class="token boolean">true</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cdCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span>
<span class="token keyword">const</span> ls <span class="token operator">=</span> <span class="token function">spawn</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand<span class="token punctuation">,</span> <span class="token punctuation">{</span>
encoding<span class="token punctuation">:</span> <span class="token string">'utf8'</span><span class="token punctuation">,</span>
cwd<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>path<span class="token punctuation">,</span> <span class="token comment">// 执行命令路径</span>
shell<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 使用shell命令</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 监听命令行执行过程的输出</span>
ls<span class="token punctuation">.</span>stdout<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> value <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stdout: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 错误或详细状态进度报告 比如 git push、 git clone </span>
ls<span class="token punctuation">.</span>stderr<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> value <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 子进程关闭事件 保存信息 更新状态</span>
ls<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'close'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>closeCommandAction<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 执行完毕 保存信息 更新状态</span>
<span class="token function">closeCommandAction</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 保存执行信息</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandArr<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
code<span class="token punctuation">,</span> <span class="token comment">// 是否执行成功</span>
path<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>path<span class="token punctuation">,</span> <span class="token comment">// 执行路径</span>
command<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">,</span> <span class="token comment">// 执行命令</span>
commandMsg<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'\r'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// 执行信息</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 清空</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">updatePath</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand<span class="token punctuation">,</span> code<span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>
<span class="token template-string"><span class="token string">`子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 运行</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'成功'</span> <span class="token punctuation">:</span> <span class="token string">'失败'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// cd命令处理</span>
<span class="token function">cdCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> pathCommand <span class="token operator">=</span> <span class="token string">''</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'cd '</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
pathCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addPath
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">' cd '</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
pathCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addPath
<span class="token punctuation">}</span>
<span class="token keyword">return</span> command <span class="token operator">+</span> pathCommand
<span class="token comment">// 目录自动联想...等很多细节功能 可以做但没必要2</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 清空历史</span>
<span class="token function">isClear</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>command <span class="token operator">===</span> <span class="token string">'clear'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandArr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 获取不同系统下的路径</span>
<span class="token function">addGetPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> systemName <span class="token operator">=</span> <span class="token function">getOsInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>systemName <span class="token operator">===</span> <span class="token string">'Mac'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>addPath <span class="token operator">=</span> <span class="token string">' && pwd'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>systemName <span class="token operator">===</span> <span class="token string">'Windows'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>addPath <span class="token operator">=</span> <span class="token string">' && chdir'</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 命令执行完毕 重置参数</span>
<span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> <span class="token string">''</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>action <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token comment">// 激活编辑器</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">$nextTick</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">scrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 判断命令是否添加过addPath</span>
<span class="token function">updatePath</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>code <span class="token operator">!==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span>
<span class="token keyword">const</span> isPathChange <span class="token operator">=</span> command<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>addPath<span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>isPathChange<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">[</span><span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 保存输入的命令行</span>
<span class="token function">onDivInput</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>textContent
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 点击div</span>
<span class="token function">timeoutFocusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 聚焦输入</span>
<span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//解决ff不获取焦点无法定位问题</span>
<span class="token keyword">var</span> range <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">getSelection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//创建range</span>
range<span class="token punctuation">.</span><span class="token function">selectAllChildren</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">)</span> <span class="token comment">//range 选择obj下所有子内容</span>
range<span class="token punctuation">.</span><span class="token function">collapseToEnd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//光标移至最后</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 滚动到底部</span>
<span class="token function">scrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> dom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'#app'</span><span class="token punctuation">)</span>
dom<span class="token punctuation">.</span>scrollTop <span class="token operator">=</span> dom<span class="token punctuation">.</span>scrollHeight <span class="token comment">// 滚动高度</span>
dom <span class="token operator">=</span> <span class="token keyword">null</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 监听窗口聚焦、失焦</span>
<span class="token function">watchFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
ipcRenderer<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'win-focus'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>event<span class="token punctuation">,</span> message<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>isActive <span class="token operator">=</span> message
<span class="token keyword">if</span> <span class="token punctuation">(</span>message<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 获取操作系统信息</span>
<span class="token keyword">function</span> <span class="token function">getOsInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> userAgent <span class="token operator">=</span> navigator<span class="token punctuation">.</span>userAgent<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">'Unknown'</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'win'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Windows'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'iphone'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'iPhone'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'mac'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Mac'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>
userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'x11'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'unix'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'sunname'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'bsd'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span>
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Unix'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'linux'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'android'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Android'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Linux'</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> name
<span class="token punctuation">}</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
</ body>
</ html>
以上就是整个项目的代码实现,总共只有三个文件。
更多细节
本项目终究是一个简单的demo,如果想要做成一个完整的开源项目,还需要补充很多细节。
还会有各种各样奇奇怪怪的需求和需要定制的地方,比如下面这些:
command+c
终止命令
cd
目录自动补全
命令保存上下键滑动
git等常用功能单独特殊处理。
输出信息颜色变化
等等
# 下载试玩
即使这个终端demo的代码量很少,注释足够详细,但还是需要上手体验一下一个Electron项目运行的细节。
# 项目演示
clear命令演示
实际上就是将历史命令行输出的数组重置为空数组。
执行失败箭头切换
根据子进程close
事件,判断执行是否成功,切换一下图标。
cd命令
识别cd
命令,根据系统添加获取路径(pwd
/chdir
)的命令,再将获取到的路径,更改为最终路径。
giit提交代码演示
# 项目地址
开源地址: electron-terminal-demo
# 启动与调试
安装
启动
通过vscode的调试运行项目,这种形式可以直接在VSCode中进行debugger调试。
如果不是使用vscode编辑器, 也可以通过使用命令行启动。
# 小结
命令行终端的实现原理就是这样啦,强烈推荐各位下载体验一下这个项目,最好单步调试一下,这样会更熟悉Electron
。
文章idea诞生于我们团队开源的另一个开源项目:electron-playground , 目的是为了让小伙伴学习electron
实战项目。
electron-playground是用来帮助前端小伙伴们更好、更快的学习和理解前端桌面端技术Electron, 尽量少走弯路。
它通过如下方式让我们快速学习electron。
带有gif示例和可操作的demo的教程文章。
系统性的整理了Electron相关的api和功能。
搭配演练场,自己动手尝试electron的各种特性。
前端进阶积累 、公众号 、GitHub 、wx:OBkoro1、邮箱:[email protected]
以上2021/01/12
博客链接
# 计算两个时间的时间差&时区转换
# 计算两个时间的时间差:
获取两个时间数据,通常这两个时间是:后端给的时间数据和当前时间
let dateBegin = '2018/08/28 04:56:38' ; // 初始时间
dateBegin = new Date ( dateBegin) ;
// Mon Aug 28 2017 04:56:38 GMT+0800 (**标准时间)
// 上面是Date对象的数据形式
let dateEnd = new Date ( ) ; // 第二个数据通常是当前时间
使用getTime()
返回与固定的 1970 年 1 月 1 日的时间差,再用大的时间减去短的时间即可得出双方相差的毫秒数。
let dateDiff = dateEnd. getTime ( ) - dateBegin. getTime ( ) ;
换算成你想要的时间单位,得出两个时间的时间差。
// 计算出相差天数
let dayDiff = Math. floor ( dateDiff / ( 24 * 3600 * 1000 ) ) ;
// 计算天数后剩余的毫秒数 利用这个时间去计算小时
let leave1 = dateDiff % ( 24 3600 1000 ) ;
// 计算出小时数
let hours = Math. floor ( leave1 / ( 3600 1000 ) ) ;
// 计算相差分钟数
let leave2 = leave1 % ( 3600 1000 ) ; // 计算小时数后剩余的毫秒数
let minutes = Math. floor ( leave2 / ( 60 1000 ) ) ; // 计算相差分钟数
// 计算相差秒数
let leave3 = leave2 % ( 60 1000 ) ; // 计算分钟数后剩余的毫秒数
let seconds = Math. round ( leave3 / 1000 ) ;
// 结果
console. log ( 相差</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dayDiff<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">天</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>hours<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">小时</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>minutes<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">分钟</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>seconds<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">秒
) ;
如果你只想计算相差的小时数,不想管天数:
let dayDiff = Math. floor ( dateDiff / ( 3600 * 1000 ) ) ; // 其他的同理,倍数正确即可
# 时区转换
需求 :
拿到这么一串字符串:2018-09-17
,要与当前的日期比较,求出日期差。
let time = `2018-09-17` ;
let dateBegin = new Date ( time) ;
let dateEnd = new Date ( ) ; // 这一秒的时间 需重新设置
dateEnd. setHours ( 0 , 0 , 0 , 0 ) ;
// 计算时间差 参照上文
东八区的时间 :
上面new Date(time)
之后拿到的是零时区 0 点的时间,在东八区也就是北京时间08:00:00
。
而我们第二个时间设置的是北京时间00:00:00
,这里面就出现了八个小时的误差。
转换时区的三种方法 :
设置世界时为当前时区的时间:
设置具体的时间,传进new Date()
的时候会自动转成北京时间00:00:00
,然后两个时间就可以同步了。
let time = `2018-09-17` ;
time = ` ${ time} 00:00:00` ; // 后面加上具体时间
let dateBegin = new Date ( time) ;
let dateEnd = new Date ( ) ; // 这一秒的时间 需重新设置
dateEnd. setHours ( 0 , 0 , 0 , 0 ) ;
// 计算时间差 参照上文
也可以使用setHours
:
let time = `2018-09-17` ;
let dateBegin = new Date ( time) ;
dateBegin. setHours ( 0 , 0 , 0 , 0 ) ; // new Date()之后 设为0点 两个时间同步
let dateEnd = new Date ( ) ; // 这一秒的时间 需重新设置
dateEnd. setHours ( 0 , 0 , 0 , 0 ) ;
// 计算时间差 参照上文
当前时区与世界时同步:
let time = `2018-09-17` ;
let dateBegin = new Date ( time) ;
let dateEnd = new Date ( ) ; // 这一秒的时间 需重新设置
dateEnd. setHours ( 8 , 0 , 0 , 0 ) ; // 设置为八点与世界时一致
// 计算时间差 参照上文
你可能不知道当前时区与世界时相差多少
let time = `2018-09-17` ;
let dateBegin = new Date ( time) ;
let dateEnd = new Date ( ) ; // 这一秒的时间 需重新设置
dateEnd. setUTCHours ( 0 , 0 , 0 , 0 ) ; // 设置为世界时的0点 也就是北京时间八点
// 计算时间差 参照上文
以上:2018.09.21
博客链接
# 手摸手教你写个ESLint 插件以及了解ESLint的运行原理
这篇文章目的是介绍如何创建一个ESLint插件和创建一个ESLint
rule
,用以帮助我们更深入的理解ESLint的运行原理,并且在有必要时可以根据需求创建出一个完美满足自己需求的Lint规则。
# 插件目标
禁止项目中setTimeout
的第二个参数是数字。
PS: 如果是数字的话,很容易就成为魔鬼数字,没有人知道为什么是这个数字, 这个数字有什么含义。
# 使用模板初始化项目:
# 1. 安装NPM包
ESLint官方为了方便开发者开发插件,提供了使用Yeoman模板(generator-eslint
)。
对于Yeoman我们只需知道它是一个脚手架工具,用于生成包含指定框架结构的工程化目录结构。
npm install - g yo generator- eslint
# 2. 创建一个文件夹:
mkdir eslint- plugin- demo
cd eslint- plugin- demo
# 3. 命令行初始化ESLint插件的项目结构:
下面进入命令行交互流程,流程结束后生成ESLint插件项目框架和文件。
? What is your name? OBKoro1
? What is the plugin ID ? korolint // 这个插件的ID是什么
? Type a short description of this plugin: XX 公司的定制ESLint rule // 输入这个插件的描述
? Does this plugin contain custom ESLint rules? Yes // 这个插件包含自定义ESLint规则吗?
? Does this plugin contain one or more processors? No // 这个插件包含一个或多个处理器吗
// 处理器用于处理js以外的文件 比如.vue文件
create package . json
create lib/ index. js
create README . md
现在可以看到在文件夹内生成了一些文件夹和文件,但我们还需要创建规则具体细节的文件。
# 4. 创建规则
上一个命令行生成的是ESLint插件的项目模板,这个命令行是生成ESLint插件具体规则的文件。
yo eslint: rule // 生成 eslint rule的模板文件
创建规则命令行交互:
? What is your name? OBKoro1
? Where will this rule be published? ( Use arrow keys) // 这个规则将在哪里发布?
❯ ESLint Core // 官方核心规则 (目前有200多个规则)
ESLint Plugin // 选择ESLint插件
? What is the rule ID ? settimeout- no- number // 规则的ID
? Type a short description of this rule: setTimeout 第二个参数禁止是数字 // 输入该规则的描述
? Type a short example of the code that will fail: 占位 // 输入一个失败例子的代码
create docs/ rules/ settimeout- no- number. md
create lib/ rules/ settimeout- no- number. js
create tests/ lib/ rules/ settimeout- no- number. js
# 加了具体规则文件的项目结构
.
├── README . md
├── docs // 使用文档
│ └── rules // 所有规则的文档
│ └── settimeout- no- number. md // 具体规则文档
├── lib // eslint 规则开发
│ ├── index. js 引入+ 导出rules文件夹的规则
│ └── rules // 此目录下可以构建多个规则
│ └── settimeout- no- number. js // 规则细节
├── package . json
└── tests // 单元测试
└── lib
└── rules
└── settimeout- no- number. js // 测试该规则的文件
# 4. 安装项目依赖
以上是开发ESLint插件具体规则的准备工作,下面先来看看AST和ESLint原理的相关知识,为我们开发ESLint rule
打一下基础。
# AST——抽象语法树
AST是: Abstract Syntax Tree
的简称,中文叫做:抽象语法树。
# AST的作用
将代码抽象成树状数据结构,方便后续分析检测代码。
# 代码被解析成AST的样子
astexplorer.net 是一个工具网站:它能查看代码被解析成AST的样子。
如下图:在右侧选中一个值时,左侧对应区域也变成高亮区域,这样可以在AST中很方便的选中对应的代码 。
# AST 选择器:
下图中被圈起来的部分,称为AST selectors(选择器)。
AST 选择器的作用 :使用代码通过选择器来选中特定的代码片段,然后再对代码进行静态分析。
AST 选择器很多,ESLint官方专门有一个仓库列出了所有类型的选择器: estree
下文中开发ESLint rule
就需要用到选择器,等下用到了就懂了,现在知道一下就好了。
# ESLint的运行原理
在开发规则之前,我们需要ESLint是怎么运行的,了解插件为什么需要这么写。
# 1. 将代码解析成AST
ESLint使用JavaScript解析器Espree 把JS代码解析成AST。
PS:解析器:是将代码解析成AST的工具,ES6、react、vue都开发了对应的解析器所以ESLint能检测它们的,ESLint也是因此一统前端Lint工具的。
# 2. 深度遍历AST,监听匹配过程。
在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。
# 3. 触发监听选择器的rule
回调
在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。
# 4. 具体的检测规则等细节内容。
# 开发规则
# 规则默认模板
打开rule
生成的模板文件lib/rules/settimeout-no-number.js
, 清理一下文件,删掉不必要的选项:
module. exports = {
meta: {
docs: {
description: "setTimeout 第二个参数禁止是数字" ,
} ,
fixable: null , // 修复函数
} ,
// rule 核心
create: function ( context) {
// 公共变量和函数应该在此定义
return {
// 返回事件钩子
} ;
}
} ;
删掉的配置项,有些是ESLint官方核心规则才是用到的配置项,有些是暂时不必了解的配置,需要用到的时候,可以自行查阅ESLint 文档
# create方法-监听选择器
上文ESLint原理第三部中提到的:在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。
create
返回一个对象,对象的属性设为选择器,ESLint会收集这些选择器,在AST遍历过程中会执行所有监听该选择器的回调。
// rule 核心
create: function ( context) {
// 公共变量和函数应该在此定义
return {
// 返回事件钩子
Identifier: ( node) => {
// node是选中的内容,是我们监听的部分, 它的值参考AST
}
} ;
}
# 观察AST:
创建一个ESLint rule
需要观察代码解析成AST,选中你要检测的代码,然后进行一些判断。
以下代码都是通过astexplorer.net 在线解析的。
setTimeout ( ( ) => {
console. log ( 'settimeout' )
} , 1000 )
# rule完整文件
lib/rules/settimeout-no-number.js
:
module. exports = {
meta: {
docs: {
description: "setTimeout 第二个参数禁止是数字" ,
} ,
fixable: null , // 修复函数
} ,
// rule 核心
create: function ( context) {
// 公共变量和函数应该在此定义
return {
// 返回事件钩子
'CallExpression' : ( node) => {
if ( node. callee. name !== 'setTimeout' ) return // 不是定时器即过滤
const timeNode = node. arguments && node. arguments[ 1 ] // 获取第二个参数
if ( ! timeNode) return // 没有第二个参数
// 检测报错第二个参数是数字 报错
if ( timeNode. type === 'Literal' && typeof timeNode. value === 'number' ) {
context. report ( {
node,
message: 'setTimeout第二个参数禁止是数字'
} )
}
}
} ;
}
} ;
context.report():这个方法是用来通知ESLint这段代码是警告或错误的,用法如上。在这里 查看context
和context.report()
的文档。
规则写完了,原理就是依据AST
解析的结果,做针对性的检测,过滤出我们要选中的代码,然后对代码的值进行逻辑判断 。
可能现在会有点懵逼,但是不要紧,我们来写一下测试用例,然后用debugger
来看一下代码是怎么运行的。
# 测试用例:
测试文件tests/lib/rules/settimeout-no-number.js
:
/**
* @fileoverview setTimeout 第二个参数禁止是数字
* @author OBKoro1
*/
"use strict" ;
var rule = require ( "../../../lib/rules/settimeout-no-number" ) , // 引入rule
RuleTester = require ( "eslint" ) . RuleTester;
var ruleTester = new RuleTester ( {
parserOptions: {
ecmaVersion: 7 , // 默认支持语法为es5
} ,
} ) ;
// 运行测试用例
ruleTester. run ( "settimeout-no-number" , rule, {
// 正确的测试用例
valid: [
{
code: 'let someNumber = 1000; setTimeout(()=>{ console.log(11) },someNumber)'
} ,
{
code: 'setTimeout(()=>{ console.log(11) },someNumber)'
}
] ,
// 错误的测试用例
invalid: [
{
code: 'setTimeout(()=>{ console.log(11) },1000)' ,
errors: [ {
message: "setTimeout第二个参数禁止是数字" , // 与rule抛出的错误保持一致
type: "CallExpression" // rule监听的对应钩子
} ]
}
]
} ) ;
下面来学习一下怎么在VSCode中调试node文件,用于观察rule
是怎么运行的。
实际上打console
的形式,也是可以的,但是在调试的时候打console实在是有点慢,对于node这种节点来说,信息也不全,所以我还是比较推荐通过debugger
的方式来调试rule
。
# 在VSCode中调试node文件
点击下图中的设置按钮, 将会打开一个文件launch.json
在文件中填入如下内容,用于调试node文件。
在rule
文件中打debugger
或者在代码行数那里点一下小红点。
点击图中的开始按钮,进入debugger
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version" : "0.2.0" ,
"configurations" : [
{
"type" : "node" ,
"request" : "launch" ,
"name" : "启动程序" , // 调试界面的名称
// 运行项目下的这个文件:
"program" : "${workspaceFolder}/tests/lib/rules/settimeout-no-number.js" ,
"args" : [ ] // node 文件的参数
} ,
// 下面是用于调试package.json的命令 之前可以用,貌似vscode出了点bug导致现在用不了了
{
"name" : "Launch via NPM" ,
"type" : "node" ,
"request" : "launch" ,
"runtimeExecutable" : "npm" ,
"runtimeArgs" : [
"run-script" , "dev" //这里的dev就对应package.json中的scripts中的dev
] ,
"port" : 9229 //这个端口是调试的端口,不是项目启动的端口
} ,
]
}
# 运行测试用例进入断点
在lib/rules/settimeout-no-number.js
中打一些debugger
点击开始按钮,以调试的形式运行测试文件tests/lib/rules/settimeout-no-number.js
开始调试rule
。
# 发布插件
eslint插件都是以npm
包的形式来引用的,所以需要把插件发布一下:
注册:如果你还未注册npm账号的话,需要去注册 一下。
登录npm: npm login
发布npm
包: npm publish
即可,ESLint已经把package.json
弄好了。
# 集成到项目:
安装npm
包:npm i eslint-plugin-korolint -D
常规的方法: 引入插件一条条写入规则
// .eslintrc.js
module. exports = {
plugins: [ 'korolint' ] ,
rules: {
"korolint/settimeout-no-number" : "error"
}
}
extends
继承插件配置:
当规则比较多的时候,用户一条条去写,未免也太麻烦了,所以ESLint可以继承插件的配置 :
修改一下lib/rules/index.js
文件:
'use strict' ;
var requireIndex = require ( 'requireindex' ) ;
const output = {
rules: requireIndex ( __dirname + '/rules' ) , // 导出所有规则
configs: {
// 导出自定义规则 在项目中直接引用
koroRule: {
plugins: [ 'korolint' ] , // 引入插件
rules: {
// 开启规则
'korolint/settimeout-no-number' : 'error'
}
}
}
} ;
module. exports = output;
使用方法:
// .eslintrc.js
module. exports = {
extends : [ 'plugin:korolint/koroRule' ] // 继承插件导出的配置
}
PS : 这种使用方式, npm的包名不能为eslint-plugin-xx-xx
,只能为eslint-plugin-xx
否则会有报错,被这个问题搞得头疼o(╥﹏╥)o
# 扩展:
以上内容足够开发一个插件,这里是一些扩展知识点。
# 遍历方向:
上文中说过: 在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。
我们所监听的选择器默认会在"从上至下"的过程中触发,如果需要在"从下至上"的过程中执行则需要添加:exit
,在上文中CallExpression
就变为CallExpression:exit
。
注意 :一段代码解析后可能包含多次同一个选择器,选择器的钩子也会多次触发。
# fix函数:自动修复rule错误
修复效果 :
// 修复前
setTimeout ( ( ) => {
} , 1000 )
// 修复后
const countNumber = 1000
setTimeout ( ( ) => {
} , countNumber)
在rule的meta对象上打开修复功能:
// rule文件
module. exports = {
meta: {
docs: {
description: 'setTimeout 第二个参数禁止是数字'
} ,
fixable: 'code' // 打开修复功能
}
}
在context.report()
上提供一个fix
函数:
把上文的context.report
修改一下,增加一个fix
方法即可,更详细的介绍可以看一下文档 。
context. report ( {
node,
message: 'setTimeout第二个参数禁止是数字' ,
fix ( fixer) {
const numberValue = timeNode. value;
const statementString = `const countNumber = ${ numberValue} \n`
return [
// 修改数字为变量
fixer. replaceTextRange ( node. arguments[ 1 ] . range, 'countNumber' ) ,
// 在setTimeout之前增加一行声明变量的代码 用户自行修改变量名
fixer. insertTextBeforeRange ( node. range, statementString) ,
] ;
}
} ) ;
# 项目地址:
eslint-plugin-korolint
呼~ 这篇博客断断续续,写了好几周,终于完成了!
大家有看到这篇博客的话,建议跟着博客的一起动手写一下,动手实操一下比你mark一百篇文章都来的有用,花不了很长时间的,希望各位看完本文,都能够更深入的了解到ESLint的运行原理。
# 觉得我的博客对你有帮助的话,就关注一下/点个赞吧!
前端进阶积累 、公众号 、GitHub 、wx:OBkoro1、邮箱:[email protected]
# 基友带我飞
ESLint插件是向基友yeyan1996 学习的,在遇到问题的时候,也是他指点我的,特此感谢。
参考资料:
创建规则
ESLint 工作原理探讨
博客链接
# VsCode保存时自动修复Eslint错误
同一个项目,保持代码风格的一致,是非常重要的一个规范。但事实上项目小组成员的代码校验规则、格式化工具通常都不一致,为了避免项目到后期出现无法维护的问题,项目成员使用同一套校验规则,同一个格式化方式是相当好的步骤之一。
游泳、健身了解一下:博客 、前端积累文档 、公众号
# 保存时自动统一代码风格:
先通过一些简单的配置,然后:
Ctrl
+s
/ command
+s
时自动修复代码的格式错误
自动修复的规则是读取项目根目录的Eslint规则
这样就能保证项目成员都是一套验证规则的代码风格
# 配置:
# 1.安装VsCode的EsLint
和vetur
插件
如图安装EsLint
插件:
# 2.为项目安装EsLint
包:
注意要安装在开发环境上,还有就是如果你使用的是脚手架的话,选了Eslint选项,会自带这些包。
# 3.在项目的根目录下添加.eslintrc.js
用于校验代码格式,根据项目情况,可自行编写校验规则:
module. exports = {
// Eslint规则
}
# 4.首选项设置:
将下面这部分放入首选项设置中:
"eslint.autoFixOnSave" : true , // 启用保存时自动修复, 默认只支持.js文件
"eslint.validate" : [
"javascript" , // 用eslint的规则检测js文件
{
"language" : "vue" , // 检测vue文件
"autoFix" : true // 为vue文件开启保存自动修复的功能
} ,
{
"language" : "html" ,
"autoFix" : true
} ,
] ,
想了解更多的话,推荐看一下VsCode的EsLint 插件
# 大功告成:
点开文件,你可能会看到如下报错,无需一个一个去改,只要保存一下文件,就可以自动修复这些代码格式上的问题了。
注意:
如果整个文件都飘红的话,不会一次性修改如果的格式问题,会一下改一部分,你可能需要多按几次保存。
# 一键修复项目格式问题:
遇到下面这两种情况:
你刚刚引入这个自动修复,但你项目的文件比较多,且你又比较懒。
隔一段时间,修复一下代码格式上的问题
你可以像下面这样,在package.json
里面的scripts
里面新增一条如下命令:
"lint" : "eslint --ext .js,.vue src --fix"
--ext
后面跟上的.js
、.vue
是你要检测文件的后缀,.vue
后面的src
是要检测的哪个目录下面的文件。
--fix
的作用是自动修复根据你配置的规则检测出来的格式问题
一键修复:
输入如下命令行,就可以自动修复你src
文件夹下面的所有根据你配置的规则检测出来的格式问题 。
# .eslintignore 不检测一些文件:
在项目的根目录创建一个.eslintignore
文件,用于让EsLint
不检测一些文件。
比如引的一些别人的文件,插件等,比如文件中:
文件中的内容像上面这样写,这里第一行是不检测src目录下的test文件夹下面的所有文件。
# 自定义规则:
// .eslintrc.js文件
module. exports = {
"rules" : { // 自定义规则
"no-console" : 0 ,
"no-const-assign" : 1 ,
"no-extra-bind" : 2 ,
}
}
0、1、2的意思:
"off"
或 0 - 关闭这项规则
"warn"
或 1 - 将规则视为一个警告
"error"
或 2 - 将规则视为一个错误
# 小结
使用自动VsCode+EsLint格式化代码,在团队内部相互去看别人的代码的时候,就可以更容易的看的懂,能够极大的降低团队的沟通成本和提高心情,设置这么方便,赶紧在团队中用起来吧!
博客链接
在vscode中用于生成文件头部注释和函数注释的插件,经过多版迭代后,插件:支持所有主流语言,功能强大,灵活方便,文档齐全,食用简单!觉得插件不错的话,就给个Star ⭐️吧~
# 简介
文件头部添加注释 :
在文件开头添加注释,记录文件信息
支持用户高度自定义注释选项
保存文件的时候,自动更新最后的编辑时间和编辑人
快捷键:window
:ctrl+alt+i
,mac
:ctrl+cmd+i
在光标处添加函数注释 :
在光标处自动生成一个注释模板,下方有栗子
支持用户高度自定义注释选项
快捷键:window
:ctrl+alt+t
,mac
:ctrl+cmd+t
快捷键不可用很可能是被占用了,参考这里
支持不同语言的注释格式
自定义注释符号 ,再也不用担心冷门语言插件不支持了!
自动添加头部注释
查看更多配置 ,以及有更多需求可以给我提issue 。
# 安装
在 Vscode 扩展商店中搜索koroFileHeader
,点击安装即可。
# 使用
文件头部注释:
在当前编辑文件中使用快捷键:window
:ctrl+alt+t
/mac
:ctrl+cmd+t
,即可生成文件头部注释。
函数注释:
将光标放在函数行或者将光标放在函数上方的空白行
使用快捷键window
:ctrl+alt+t
,mac
:ctrl+cmd+t
,即可生成函数注释。
事实上,函数注释在文件的任意位置都可生成,这里需要自己控制。
# 注释模板的设置
设置也超方便的,传送门
# 支持功能:
自定义注释模板信息 ,自动更新最后编辑时间,最后编辑人。
支持几乎所有语言的注释形式
自定义注释符号 ,即使插件不支持的语言,也可以自己定制。
自动添加头部注释功能 ,配合自动添加头部注释黑名单 ,麻麻再也不用担心我忘记加注释了。
头部注释第几行插入 ,类似PHP
第一行被占用了,通过设置,可以在第二行里面插入。
注释时间格式化
在头部注释之前、之后插入一段内容 ,类似python
的环境声明: #!/usr/bin/env python
特殊字段自定义 ,类似博客的时间字段有特殊要求。
头部注释中输出一段自定义信息 ,可以是版权声明、个性签名等内容。
匹配到换行自动添加注释符号 , 生成头部注释自动移动光标到`Description所在行 .
# 使用效果:
# wiki文档
更新日志
支持语言
插件设置/配置
常见问题
# 最后
如果觉得还不错的话,就给个 Star ⭐️ 鼓励一下我吧~
博客链接
# 从零开始带你写一个运行命令行的终端[手把手教学]
# 前言
Electron
很出名,很多人可能了解过,知道它是用来开发桌面端的应用,但是一直没有在项目中实践过,缺乏练手的实践项目。
很多开源的命令行终端都是使用Electron
来开发的,本文将从零开始手把手的教大家用Electron
写一个命令行终端。
作为一个完整的实战项目示例,该终端demo也将集成到Electron
开源学习项目electron-playground 中,目前这个项目拥有700+ Star⭐️,它最大的特点是所见即所得的演示Electron
的各种特性,帮助大家快速学习、上手Electron
。
大家跟着本文一起来试试Electron吧~
# 终端效果
开源地址: electron-terminal-demo
giit提交代码演示
# 目录
初始化项目。
项目目录结构
Electron启动入口index-创建窗口
进程通信类-processMessage。
窗口html页面-命令行面板
命令行面板做了哪些事情
核心方法:child_process.spawn-执行命令行监听命令行的输出
stderr不能直接识别为命令行执行错误
命令行终端执行命令保存输出信息的核心代码
html完整代码
命令行终端的更多细节
下载试玩
小结
# 初始化项目
npm init
npm install electron - D
如果Electron安装不上去,需要添加一个.npmrc
文件,来修改Electron
的安装地址,文件内容如下:
registry=https://registry.npm.taobao.org/
electron_mirror=https://npm.taobao.org/mirrors/electron/
chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver
修改一下package.json
的入口main
和scripts
选项, 现在package.json
长这样,很简洁:
{
"name" : "electron-terminal" ,
"version" : "1.0.0" ,
"main" : "./src/index.js" ,
"scripts" : {
"start" : "electron ."
} ,
"devDependencies" : {
"electron" : "^11.1.1"
}
}
# 项目目录结构
我们最终实现的项目将是下面这样子的,页面css文件不算的话,我们只需要实现src下面的三个文件即可。
.
├── . vscode // 使用vscode的调试功能启动项目
├── node_dodules
├── src
│ ├── index. js // Electron启动入口-创建窗口
│ └── processMessage. js // 主进程和渲染进程通信类-进程通信、监听时间
│ └── index. html // 窗口html页面-命令行面板、执行命令并监听输出
│ └── index. css // 窗口html的css样式 这部分不写
├── package . json
└── . npmrc // 修改npm安装包的地址
└── . gitignore
# Electron启动入口index-创建窗口
创建窗口, 赋予窗口直接使用node的能力。
窗口加载本地html页面
加载主线程和渲染进程通信逻辑
// ./src/index.js
const { app, BrowserWindow } = require ( 'electron' )
const processMessage = require ( './processMessage' )
// 创建窗口
function createWindow ( ) {
// 创建窗口
const win = new BrowserWindow ( {
width: 800 ,
height: 600 ,
webPreferences: {
nodeIntegration: true , // 页面直接使用node的能力 用于引入node模块 执行命令
} ,
} )
// 加载本地页面
win. loadFile ( './src/index.html' )
win. webContents. openDevTools ( ) // 打开控制台
// 主线程和渲染进程通信
const ProcessMessage = new processMessage ( win)
ProcessMessage. init ( )
}
// app ready 创建窗口
app. whenReady ( ) . then ( createWindow)
# 进程通信类-processMessage
electron分为主进程和渲染进程,因为进程不同,在各种事件发生的对应时机需要相互通知来执行一些功能。
这个类就是用于它们之间的通信的,electron通信这部分封装的很简洁了,照着用就可以了。
// ./src/processMessage.js
const { ipcMain } = require ( 'electron' )
class ProcessMessage {
/**
* 进程通信
* @param {*} win 创建的窗口
*/
constructor ( win) {
this . win = win
}
init ( ) {
this . watch ( )
this . on ( )
}
// 监听渲染进程事件通信
watch ( ) {
// 页面准备好了
ipcMain. on ( 'page-ready' , ( ) => {
this . sendFocus ( )
} )
}
// 监听窗口、app、等模块的事件
on ( ) {
// 监听窗口是否聚焦
this . win. on ( 'focus' , ( ) => {
this . sendFocus ( true )
} )
this . win. on ( 'blur' , ( ) => {
this . sendFocus ( false )
} )
}
/**
* 窗口聚焦事件发送
* @param {*} isActive 是否聚焦
*/
sendFocus ( isActive) {
// 主线程发送事件给窗口
this . win. webContents. send ( 'win-focus' , isActive)
}
}
module. exports = ProcessMessage
# 窗口html页面-命令行面板
在创建窗口的时候,我们赋予了窗口使用node的能力, 可以在html中直接使用node模块。
所以我们不需要通过进程通信的方式来执行命令和渲染输出,可以直接在一个文件里面完成。
终端的核心在于执行命令,渲染命令行输出,保存命令行的输出 。
这些都在这个文件里面实现了,代码行数不到250行。
# 命令行面板做了哪些事情
# 核心方法:child_process.spawn-执行命令行监听命令行的输出
# child_process.spawn介绍
spawn
是node子进程模块child_process
提供的一个异步方法。
它的作用是执行命令并且可以实时监听命令行执行的输出 。
当我第一次知道这个API的时候,我就感觉这个方法简直是为命令行终端量身定做的。
终端的核心也是执行命令行,并且实时输出命令行执行期间的信息。
下面就来看看它的使用方式。
# 使用方式
const { spawn } = require ( 'child_process' ) ;
const ls = spawn ( 'ls' , {
encoding: 'utf8' ,
cwd: process. cwd ( ) , // 执行命令路径
shell: true , // 使用shell命令
} )
// 监听标准输出
ls. stdout. on ( 'data' , ( data) => {
console. log ( stdout: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
) ;
} ) ;
// 监听标准错误
ls. stderr. on ( 'data' , ( data) => {
console. error ( stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
) ;
} ) ;
// 子进程关闭事件
ls. on ( 'close' , ( code) => {
console. log ( 子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
) ;
} ) ;
api的使用很简单,但是终端信息的输出,需要很多细节的处理,比如下面这个。
# stderr不能直接识别为命令行执行错误
stderr
虽然是标准错误输出,但里面的信息不全是错误的信息,不同的工具会有不同的处理。
对于git
来说,有很多命令行操作的输出信息都输出在stederr
上。
比如git clone
、git push
等,信息输出在stederr
中,我们不能将其视为错误。
git
总是将详细的状态信息和进度报告 ,以及只读信息,发送给stederr
。
具体细节可以查看git stderr(错误流)探秘 等资料。
暂时还不清楚其他工具/命令行也有没有类似的操作,但是很明显我们不能将stederr
的信息视为错误的信息。
PS: 对于git如果想提供更好的支持,需要根据不同的git
命令进行特殊处理,比如对下面clear
命令和cd
命令的特殊处理。
根据子进程close事件判断命令行是否执行成功
我们应该检测close
事件的退出码code
, 如果code
为0则表示命令行执行成功,否则即为失败。
# 命令行终端执行命令保存输出信息的核心代码
下面这段是命令行面板的核心代码,我贴一下大家重点看一下,
其他部分都是一些细节、优化体验、状态处理这样的代码,下面会将完整的html贴上来。
const { spawn } = require ( 'child_process' ) // 使用node child_process模块
// 执行命令行
actionCommand ( ) {
// 处理command命令
const command = this . command. trim ( )
this . isClear ( command)
if ( this . command === '' ) return
// 执行命令行
this . action = true
this . handleCommand = this . cdCommand ( command)
const ls = spawn ( this . handleCommand, {
encoding: 'utf8' ,
cwd: this . path, // 执行命令路径
shell: true , // 使用shell命令
} )
// 监听命令行执行过程的输出
ls. stdout. on ( 'data' , ( data) => {
const value = data. toString ( ) . trim ( )
this . commandMsg. push ( value)
console. log ( `stdout: ${ value} ` )
} )
ls. stderr. on ( 'data' , this . stderrMsgHandle)
ls. on ( 'close' , this . closeCommandAction)
} ,
// 错误或详细状态进度报告 比如 git push
stderrMsgHandle ( data) {
console. log ( stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
)
this . commandMsg. push ( stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
)
} ,
// 执行完毕 保存信息 更新状态
closeCommandAction ( code) {
// 保存执行信息
this . commandArr. push ( {
code, // 是否执行成功
path: this . path, // 执行路径
command: this . command, // 执行命令
commandMsg: this . commandMsg. join ( '\r' ) , // 执行信息
} )
// 清空
this . updatePath ( this . handleCommand, code)
this . commandFinish ( )
console. log (
子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 运行</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'成功'</span> <span class="token punctuation">:</span> <span class="token string">'失败'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
)
}
# html完整代码
这里是html的完整代码,代码中有详细注释,建议根据上面的命令行面板做了哪些事情 ,来阅读源码。
<!DOCTYPE html>
< html>
< head>
< meta charset = " UTF-8" />
< meta name = " viewport" content = " width=device-width, initial-scale=1.0" />
< title> 极简electron终端</ title>
< link
rel = " stylesheet"
href = " https://unpkg.com/element-ui/lib/theme-chalk/index.css"
/>
< script src = " https://unpkg.com/vue" > </ script>
<!-- 引入element -->
< script src = " https://unpkg.com/element-ui/lib/index.js" > </ script>
<!-- css -->
< link rel = " stylesheet" href = " ./index.css" />
</ head>
< body>
< div id = " app" >
< div class = " main-class" >
<!-- 渲染过往的命令行 -->
< div v-for = " item in commandArr" >
< div class = " command-action" >
<!-- 执行成功或者失败图标切换 -->
< i
:class = " [' el-icon-right' , ' command-action-icon' , { ' error-icon' : item.code !== 0 }]"
> </ i>
<!-- 过往执行地址和命令行、信息 -->
< span class = " command-action-path" > {{ item.path }} $</ span>
< span class = " command-action-contenteditable"
> {{ item.command }}</ span
>
</ div>
< div class = " output-command" > {{ item.commandMsg }}</ div>
</ div>
<!-- 当前输入的命令行 -->
< div
class = " command-action command-action-editor"
@mouseup = " timeoutFocusInput"
>
< i class = " el-icon-right command-action-icon" > </ i>
<!-- 执行地址 -->
< span class = " command-action-path" > {{ path }} $</ span>
<!-- 命令行输入 -->
< span
:contenteditable = " action ? false : ' plaintext-only' "
class = " command-action-contenteditable"
@input = " onDivInput($event)"
@keydown = " keyFn"
> </ span>
</ div>
<!-- 当前命令行输出 -->
< div class = " output-command" >
< div v-for = " item in commandMsg" > {{item}}</ div>
</ div>
</ div>
</ div>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span><span class="token script language-javascript">
<span class="token keyword">const</span> <span class="token punctuation">{</span> ipcRenderer <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'electron'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> spawn <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'child_process'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span>
<span class="token keyword">var</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
el<span class="token punctuation">:</span> <span class="token string">'#app'</span><span class="token punctuation">,</span>
data<span class="token punctuation">:</span> <span class="token punctuation">{</span>
path<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 命令行目录</span>
command<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 用户输入命令</span>
handleCommand<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 经过处理的用户命令 比如清除首尾空格、添加获取路径的命令</span>
commandMsg<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 当前命令信息</span>
commandArr<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 过往命令行输出保存</span>
isActive<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 终端是否聚焦</span>
action<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token comment">// 是否正在执行命令</span>
inputDom<span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token comment">// 输入框dom</span>
addPath<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 不同系统 获取路径的命令 mac是pwd window是chdir</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token function">mounted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addGetPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>inputDom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span>
<span class="token string">'.command-action-contenteditable'</span>
<span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token function">cwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 初始化路径</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">watchFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
ipcRenderer<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">'page-ready'</span><span class="token punctuation">)</span> <span class="token comment">// 告诉主进程页面准备好了</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
<span class="token comment">// 回车执行命令</span>
<span class="token function">keyFn</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>keyCode <span class="token operator">==</span> <span class="token number">13</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">actionCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 执行命令</span>
<span class="token function">actionCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> command <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isClear</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">===</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token keyword">return</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>action <span class="token operator">=</span> <span class="token boolean">true</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cdCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span>
<span class="token keyword">const</span> ls <span class="token operator">=</span> <span class="token function">spawn</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand<span class="token punctuation">,</span> <span class="token punctuation">{</span>
encoding<span class="token punctuation">:</span> <span class="token string">'utf8'</span><span class="token punctuation">,</span>
cwd<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>path<span class="token punctuation">,</span> <span class="token comment">// 执行命令路径</span>
shell<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 使用shell命令</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 监听命令行执行过程的输出</span>
ls<span class="token punctuation">.</span>stdout<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> value <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stdout: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 错误或详细状态进度报告 比如 git push、 git clone </span>
ls<span class="token punctuation">.</span>stderr<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> value <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 子进程关闭事件 保存信息 更新状态</span>
ls<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'close'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>closeCommandAction<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 执行完毕 保存信息 更新状态</span>
<span class="token function">closeCommandAction</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 保存执行信息</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandArr<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
code<span class="token punctuation">,</span> <span class="token comment">// 是否执行成功</span>
path<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>path<span class="token punctuation">,</span> <span class="token comment">// 执行路径</span>
command<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">,</span> <span class="token comment">// 执行命令</span>
commandMsg<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'\r'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// 执行信息</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 清空</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">updatePath</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand<span class="token punctuation">,</span> code<span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>
<span class="token template-string"><span class="token string">`子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 运行</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'成功'</span> <span class="token punctuation">:</span> <span class="token string">'失败'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// cd命令处理</span>
<span class="token function">cdCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> pathCommand <span class="token operator">=</span> <span class="token string">''</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'cd '</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
pathCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addPath
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">' cd '</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
pathCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addPath
<span class="token punctuation">}</span>
<span class="token keyword">return</span> command <span class="token operator">+</span> pathCommand
<span class="token comment">// 目录自动联想...等很多细节功能 可以做但没必要2</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 清空历史</span>
<span class="token function">isClear</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>command <span class="token operator">===</span> <span class="token string">'clear'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandArr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 获取不同系统下的路径</span>
<span class="token function">addGetPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> systemName <span class="token operator">=</span> <span class="token function">getOsInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>systemName <span class="token operator">===</span> <span class="token string">'Mac'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>addPath <span class="token operator">=</span> <span class="token string">' && pwd'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>systemName <span class="token operator">===</span> <span class="token string">'Windows'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>addPath <span class="token operator">=</span> <span class="token string">' && chdir'</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 命令执行完毕 重置参数</span>
<span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> <span class="token string">''</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>action <span class="token operator">=</span> <span class="token boolean">false</span>
<span class="token comment">// 激活编辑器</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">$nextTick</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">scrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 判断命令是否添加过addPath</span>
<span class="token function">updatePath</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>code <span class="token operator">!==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span>
<span class="token keyword">const</span> isPathChange <span class="token operator">=</span> command<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>addPath<span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>isPathChange<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">[</span><span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 保存输入的命令行</span>
<span class="token function">onDivInput</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>textContent
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 点击div</span>
<span class="token function">timeoutFocusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 聚焦输入</span>
<span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//解决ff不获取焦点无法定位问题</span>
<span class="token keyword">var</span> range <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">getSelection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//创建range</span>
range<span class="token punctuation">.</span><span class="token function">selectAllChildren</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">)</span> <span class="token comment">//range 选择obj下所有子内容</span>
range<span class="token punctuation">.</span><span class="token function">collapseToEnd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//光标移至最后</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 滚动到底部</span>
<span class="token function">scrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> dom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'#app'</span><span class="token punctuation">)</span>
dom<span class="token punctuation">.</span>scrollTop <span class="token operator">=</span> dom<span class="token punctuation">.</span>scrollHeight <span class="token comment">// 滚动高度</span>
dom <span class="token operator">=</span> <span class="token keyword">null</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token comment">// 监听窗口聚焦、失焦</span>
<span class="token function">watchFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
ipcRenderer<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'win-focus'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>event<span class="token punctuation">,</span> message<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>isActive <span class="token operator">=</span> message
<span class="token keyword">if</span> <span class="token punctuation">(</span>message<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 获取操作系统信息</span>
<span class="token keyword">function</span> <span class="token function">getOsInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> userAgent <span class="token operator">=</span> navigator<span class="token punctuation">.</span>userAgent<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">'Unknown'</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'win'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Windows'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'iphone'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'iPhone'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'mac'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Mac'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>
userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'x11'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'unix'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'sunname'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'bsd'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span>
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Unix'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'linux'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'android'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Android'</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
name <span class="token operator">=</span> <span class="token string">'Linux'</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> name
<span class="token punctuation">}</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
</ body>
</ html>
以上就是整个项目的代码实现,总共只有三个文件。
更多细节
本项目终究是一个简单的demo,如果想要做成一个完整的开源项目,还需要补充很多细节。
还会有各种各样奇奇怪怪的需求和需要定制的地方,比如下面这些:
command+c
终止命令
cd
目录自动补全
命令保存上下键滑动
git等常用功能单独特殊处理。
输出信息颜色变化
等等
# 下载试玩
即使这个终端demo的代码量很少,注释足够详细,但还是需要上手体验一下一个Electron项目运行的细节。
# 项目演示
clear命令演示
实际上就是将历史命令行输出的数组重置为空数组。
执行失败箭头切换
根据子进程close
事件,判断执行是否成功,切换一下图标。
cd命令
识别cd
命令,根据系统添加获取路径(pwd
/chdir
)的命令,再将获取到的路径,更改为最终路径。
giit提交代码演示
# 项目地址
开源地址: electron-terminal-demo
# 启动与调试
安装
启动
通过vscode的调试运行项目,这种形式可以直接在VSCode中进行debugger调试。
如果不是使用vscode编辑器, 也可以通过使用命令行启动。
# 小结
命令行终端的实现原理就是这样啦,强烈推荐各位下载体验一下这个项目,最好单步调试一下,这样会更熟悉Electron
。
文章idea诞生于我们团队开源的另一个开源项目:electron-playground , 目的是为了让小伙伴学习electron
实战项目。
electron-playground是用来帮助前端小伙伴们更好、更快的学习和理解前端桌面端技术Electron, 尽量少走弯路。
它通过如下方式让我们快速学习electron。
带有gif示例和可操作的demo的教程文章。
系统性的整理了Electron相关的api和功能。
搭配演练场,自己动手尝试electron的各种特性。
前端进阶积累 、公众号 、GitHub 、wx:OBkoro1、邮箱:[email protected]
以上2021/01/12
博客链接
# cookie、localStorage 和 sessionStorage 的使用以及区别
# localStorage 和 sessionStorage 的增删改查:
存储数据 :
sessionStorage. setItem ( 'key' , 'sessionStorage的值' ) ; // 存储数据
获取指定键名数据 :
let dataSession= sessionStorage. getItem ( 'key' ) ; //获取指定键名数据
let dataSession2= sessionStorage. key; //sessionStorage是js对象,也可以使用key的方式来获取值
console. log ( dataSession, dataSession2, '获取指定键名数据' ) ;
获取sessionStorage全部数据 :
let dataAll = sessionStorage. valueOf ( ) ; //获取全部数据
console. log ( dataAll, '获取全部数据' ) ;
清空sessionStorage数据 :
sessionStorage. clear ( ) ; //清空
localStorage
只要将sessionStorage
替换成localStorage
即可,他们两个的使用方法完全是一样的。
# cookie 的增删改茶:
保存 cookie 值:
let dataCookie = '110' ;
document. cookie = 'token' + '=' + dataCookie;
获取指定名称的 cookie 值
let cookieData = getCookie ( 'token' ) ;
function getCookie ( name) {
// 获取指定名称的cookie值
let arr = document. cookie. match (
new RegExp ( '(^| )' + name + '=([^;]*)(;|$)' )
) ; // 使用正则匹配 对应cookie,返回数组
if ( arr != null ) {
console. log ( arr) ;
return unescape ( arr[ 2 ] ) ;
}
return null ;
}
let cookieData = getCookie ( 'token' ) ; // cookie赋值给变量。
保存 cookie 并且设置过期时间:
setTime ( 'token' , 'cookie的值' , 10 ) ;
function setTime ( key, value, expiresDays) {
//存储 cookie 值并且设置 cookie 过期时间
let date= new Date ( ) ;
date. setTime ( date. getTime ( ) + expiresDays* 24 * 3600 \* 1000 ) ;
document. cookie= ` ${ key} = ${ value} ; expires= ${ date. toGMTString ( ) } ` ;
console. log ( document. cookie, '存储 cookie 值并且设置 cookie 过期时间' ) ;
}
删除 cookie:
delCookie ( 'token' ) ;
function delCookie ( cookieName1) {
//删除cookie
let date2 = new Date ( ) ;
date2. setTime ( date2. getTime ( ) - 10001 ) ; //把时间设置为过去的时间,会自动删除
document. cookie = cookieName1 + '=v; expires=' + date2. toGMTString ( ) ;
console. log ( document. cookie, '删除cookie' ) ;
}
# 三者的异同:
这个问题其实很多大厂面试的时候也都会问到,所以可以注意一下这几个之间的区别:
生命周期 :
cookie:可设置失效时间,没有设置的话,默认是关闭浏览器后失效
localStorage:除非被手动清除,否则将会永久保存。
sessionStorage: 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除。
存放数据大小 :
cookie:4KB左右
localStorage和sessionStorage:可以保存5MB的信息。
http请求 :
cookie:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题
localStorage和sessionStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信
易用性 :
cookie:需要程序员自己封装,源生的Cookie接口不友好
localStorage和sessionStorage:源生接口可以接受,亦可再次封装来对Object和Array有更好的支持
# 应用场景:
从安全性来说,因为每次http请求都会携带cookie信息,这样无形中浪费了带宽,所以cookie应该尽可能少的使用,另外cookie还需要指定作用域,不可以跨域调用,限制比较多。但是用来识别用户登录来说,cookie还是比stprage更好用的。其他情况下,可以使用storage,就用storage。
storage在存储数据的大小上面秒杀了cookie,现在基本上很少使用cookie了,因为更大总是更好的,哈哈哈你们懂得。
localStorage和sessionStorage唯一的差别一个是永久保存在浏览器里面,一个是关闭网页就清除了信息。localStorage可以用来夸页面传递参数,sessionStorage用来保存一些临时的数据,防止用户刷新页面之后丢失了一些参数。
# 浏览器支持情况:
localStorage和sessionStorage是html5才应用的新特性,可能有些浏览器并不支持,这里要注意。
cookie的浏览器支持没有找到,可以通过下面这段代码来判断所使用的浏览器是否支持cookie:
if(navigator.cookieEnabled) {
alert("你的浏览器支持cookie功能");//提示浏览器支持cookie
} else {
alert("你的浏览器不支持cookie");//提示浏览器不支持cookie }
# 数据存放处:
# 番外:各浏览器Cookie大小、个数限制。
cookie 使用起来还是需要小心一点,有兴趣的可以看一下这个链接 。
博客链接
# 前端进阶积累
本项目用于记录前端进阶路上积累的知识点,希望我们能够在纷乱的前端娱乐圈里,专注js基础,打磨核心竞争力,一通百通,无惧工具/框架变迁 。
前端进阶之路漫漫,期待与你一起成长...
如果我的博客对你的成长有帮助的话,那就给我的点个Star 吧~
# 来社区关注我,不错过最新文章:
博客链接
# 事件循环(Event Loop)机制以及实例
大家都知道js是单线程的脚本语言,在同一时间,只能做同一件事,为了协调事件、用户交互、脚本、UI渲染和网络处理等行为,防止主线程阻塞,Event Loop方案应运而生...
# 为什么js是单线程?
js作为主要运行在浏览器的脚本语言,js主要用途之一是操作DOM。
在js高程中举过一个栗子,如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪个线程的,如何判断优先级?
为了避免这种问题,js必须是一门单线程语言,并且在未来这个特点也不会改变。
# 执行栈与任务队列
因为js是单线程语言,当遇到异步任务(如ajax操作等)时,不可能一直等待异步完成,再继续往下执行,在这期间浏览器是空闲状态,显而易见这会导致巨大的资源浪费。
# 执行栈
当执行某个函数、用户点击一次鼠标,Ajax完成,一个图片加载完成等事件发生时,只要指定过回调函数,这些事件发生时就会进入执行栈队列中,等待主线程读取,遵循先进先出原则。
# 主线程
要明确的一点是,主线程跟执行栈是不同概念,主线程规定现在执行执行栈中的哪个事件。
主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。
当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务 。如果有,那么主线程会依次执行那些任务队列中的回调函数。
不太理解的话,可以运行一下下面的代码,或者点击一下这个demo
结果是当a、b、c函数都执行完成之后,三个setTimeout才会依次执行。
let a = ( ) => {
setTimeout ( ( ) => {
console. log ( '任务队列函数1' )
} , 0 )
for ( let i = 0 ; i < 5000 ; i++ ) {
console. log ( 'a的for循环' )
}
console. log ( 'a事件执行完' )
}
let b = ( ) => {
setTimeout ( ( ) => {
console. log ( '任务队列函数2' )
} , 0 )
for ( let i = 0 ; i < 5000 ; i++ ) {
console. log ( 'b的for循环' )
}
console. log ( 'b事件执行完' )
}
let c = ( ) => {
setTimeout ( ( ) => {
console. log ( '任务队列函数3' )
} , 0 )
for ( let i = 0 ; i < 5000 ; i++ ) {
console. log ( 'c的for循环' )
}
console. log ( 'c事件执行完' )
}
a ( ) ;
b ( ) ;
c ( ) ;
// 当a、b、c函数都执行完成之后,三个setTimeout才会依次执行
# js 异步执行的运行机制。
所有任务都在主线程上执行,形成一个执行栈。
主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列"。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
主线程不断重复上面的第三步 。
# 宏任务与微任务:
异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不同的API注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。
宏任务(macrotask): :
script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
微任务(microtask):
Promise、 MutaionObserver、process.nextTick(Node.js环境)
# Event Loop(事件循环):
Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:
执行栈选择最先进入队列的宏任务(通常是script
整体代码),如果有则执行
检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
更新render(每一次事件循环,浏览器都可能会去更新渲染)
重复以上步骤
宏任务 > 所有微任务 > 宏任务,如下图所示:
从上图我们可以看出:
将所有任务看成两个队列:执行队列与事件队列。
执行队列是同步的,事件队列是异步的,宏任务放入事件列表,微任务放入执行队列之后,事件队列之前。
当执行完同步代码之后,就会执行位于执行列表之后的微任务,然后再执行事件列表中的宏任务
上面提到的demo 结果可以这么理解:先执行script
宏任务,执行完了之后,再执行其他两个定时器宏任务。
# 面试题实践
下面这个题,很多人都应该看过/遇到过,重新来看会不会觉得清晰很多:
// 执行顺序问题,考察频率挺高的,先自己想答案**
setTimeout ( function ( ) {
console. log ( 1 ) ;
} ) ;
new Promise ( function ( resolve, reject) {
console. log ( 2 )
resolve ( 3 )
} ) . then ( function ( val) {
console. log ( val) ;
} )
console. log ( 4 ) ;
根据本文的解析,我们可以得到:
先执行script
同步代码
先执行new Promise中的console.log(2),then后面的不执行属于微任务
然后执行console.log(4)
执行完script
宏任务后,执行微任务,console.log(3),没有其他微任务了。
执行另一个宏任务,定时器,console.log(1)。
根据本文的内容,可以很轻松,且有理有据的猜出 写出正确答案:2,4,3,1.
# 小结
类似上文的面试题还有很多,实则都大同小异,只要掌握了事件循环的机制,这些问题都会变得很简单。
博客链接
# 简单的弹窗组件实现
最近在使用element-ui框架,用到了Dialog对话框组件,大致实现的效果,跟我之前自己在移动端项目里面弄的一个弹窗组件差不太多。然后就想着把这种弹窗组件的实现方式与大家分享一下,下面本文会带着大家手摸手实现一个弹窗组件。
本文主要内容会涉及到弹窗遮罩的实现,slot
插槽的使用方式,props
、$emit
传参,具体组件代码也传上去了。如果喜欢的话可以点波赞/关注,支持一下,希望大家看完本文可以有所收获。
# 组件最后实现的效果
# 实现步骤
先搭建组件的html和css样式,遮罩层和内容层。
定制弹窗内容:弹窗组件通过slot
插槽接受从父组件那里传过来弹窗内容。
定制弹窗样式:弹窗组件通过props
接收从父组件传过来的弹窗宽度,上下左右的位置。
组件开关:通过父组件传进来的props
控制组件的显示与隐藏,子组件关闭时通过事件$emit
触发父组件改变值。
# 1.搭建组件的html和css样式。
html结构:一层遮罩层,一层内容层,内容层里面又有一个头部title和主体内容和一个关闭按钮。
下面是组件中的html结构,里面有一些后面才要加进去的东西,如果看不懂的话可以先跳过,
< template>
< div class = " dialog" >
<!--外层的遮罩 点击事件用来关闭弹窗,isShow控制弹窗显示 隐藏的props-->
< div class = " dialog-cover back" v-if = " isShow" @click = " closeMyself" > </ div>
<!-- transition 这里可以加一些简单的动画效果 -->
< transition name = " drop" >
<!--style 通过props 控制内容的样式 -->
< div class = " dialog-content" : style =" { top : topDistance+'%' ,width : widNum+'%' ,left : leftSite+'%' } " v-if = " isShow" >
< div class = " dialog_head back" >
<!--弹窗头部 title-->
< slot name = " header" > 提示信息</ slot>
</ div>
< div class = " dialog_main" : style =" { paddingTop : pdt+'px' ,paddingBottom : pdb+'px' } " >
<!--弹窗的内容-->
< slot name = " main" > 弹窗内容</ slot>
</ div>
<!--弹窗关闭按钮-->
< div class = " foot_close" @click = " closeMyself" >
< div class = " close_img back" > </ div>
</ div>
</ div>
</ transition>
</ div>
</ template>
下面是组件中的主要的css样式,里面都做了充分的注释,主要通过z-index
和background
达到遮罩的效果,具体内容的css可以根据自己的需求来设置。
<style lang="scss" scoped>
/* 最外层 设置position定位 */
.dialog {
position : relative;
color : #2e2c2d;
font-size : 16px;
}
/* 遮罩 设置背景层,z-index值要足够大确保能覆盖,高度 宽度设置满 做到全屏遮罩 */
.dialog-cover {
background : rgba ( 0,0,0, 0.8) ;
position : fixed;
z-index : 200;
top : 0;
left : 0;
width : 100%;
height : 100%;
}
/* 内容层 z-index要比遮罩大,否则会被遮盖, */
.dialog-content {
position : fixed;
top : 35%;
/* 移动端使用felx布局 */
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
z-index : 300;
}
</style>
# 2. 通过slot
定制弹窗内容
这一步,只要理解了slot
的作用以及用法,就没有问题了。
# 单个插槽:
< slot> 这是在没有slot传进来的时候,才显示的弹窗内容</ slot>
上面是单个插槽也叫默认插槽,在父组件中使用插槽的正确姿势:
< my-component>
<!--在my-component里面的所有内容片段都将插入到slot所在的DOM位置,并且会替换掉slot标签-->
<!--这两个p标签,将替换整个slot标签里面的内容-->
< p> 这是一些初始内容</ p>
< p> 这是更多的初始内容</ p>
</ my-component>
ps:如果子组件里面包含slot
插槽,那么上面的p标签的内容将会被丢弃。
# 具名插槽:
所谓的具名插槽,即为slot
标签赋一个name
属性,具名插槽可以父组件中不同的内容片段放到子组件的不同地方,具名插槽还是可以拥有一个默认插槽。下面可以看一下弹窗组件插槽的使用方式:
< div class = " dialog_head back " >
<!--弹窗头部 title-->
< slot name = " header" > 提示信息</ slot>
</ div>
< div class = " dialog_main " : style =" { paddingTop : pdt+'px' ,paddingBottom : pdb+'px' } " >
<!--弹窗的内容-->
< slot name = " main" > 弹窗内容</ slot>
</ div>
在父组件中的使用方式:
将弹窗组件引入要使用的组件中,并通过components
注册成为组件。
父组件中弹窗组件插槽的使用方法如下。
< dialogComponent>
< div slot = " header" > 插入到name为header的slot标签里面</ div>
< div class = " dialog_publish_main" slot = " main" >
这里是内容插入到子组件的slot的name为main里面,可以在父组件中添加class定义样式,事件类型等各种操作
</ div>
</ dialogComponent>
关于组件中用到的插槽的介绍就到这里了,插槽在弹窗组件中的应用是一个典型的栗子,可以看到插槽作用相当强大,而插槽本身的使用并不难,同学们爱上插槽了没有?
# 3.通过props
控制弹窗显隐&&定制弹窗style
psops
是Vue中父组件向子组件传递数据的一种方式,不熟悉的小伙伴们可以看一下props文档 。
因为弹窗组件都是引到别的组件里面去用的,为了适合不同组件场景中的弹窗,所以弹窗组件必须具备一定的可定制性,否则这样的组件将毫无意义,下面介绍一下props的使用方式,以弹窗组件为例:
首先需要在被传入的组件中定义props的一些特性,验证之类的。
然后在父组件中绑定props数据。
< script>
export default {
props: {
isShow: {
//弹窗组件是否显示 默认不显示
type: Boolean,
default : false ,
required: true , //必须
} ,
//下面这些属性会绑定到div上面 详情参照上面的html结构
// 如: :style="{top:topDistance+'%',width:widNum+'%'}"
widNum: {
//内容宽度
type: Number,
default : 86.5
} ,
leftSite: {
// 左定位
type: Number,
default : 6.5
} ,
topDistance: {
//top上边距
type: Number,
default : 35
} ,
pdt: {
//上padding
type: Number,
default : 22
} ,
pdb: {
//下padding
type: Number,
default : 47
}
} ,
}
< / script>
父组件中使用方式:
< dialogComponent :is-show = " status.isShowPublish" :top-distance = " status.topNum" >
</ dialogComponent>
ps:props传递数据不是双向绑定的,而是单向数据流 ,父组件的数据变化时,也会传递到子组件中,这就意外着我们不应该在子组件中修改props。所以我们在关闭弹窗的时候就需要通过$emit
来修改父组件的数据 ,然后数据会自动传到子组件中。
现在基本上弹窗组件都已实现的差不多了,还差一个弹窗的关闭事件,这里就涉及到子组件往父组件传参了。
# 4.$emit
触发父组件事件修改数据,关闭弹窗
Vue中在子组件往父组件传参,很多都是通过$emit
来触发父组件的事件来修改数据。
在子组件中,在点击关闭,或者遮罩层的时候触发下面这个方法:
methods: {
closeMyself ( ) {
this . $emit ( "on-close" ) ;
//如果需要传参的话,可以在"on-close"后面再加参数,然后在父组件的函数里接收就可以了。
}
}
父组件中的写法:
< dialogComponent : is- show= "status.isShowPublish" : top- distance= "status.topNum" @on- close= "closeDialog" >
< / dialogComponent>
//"on-close是监听子组件的时间有没有触发,触发的时候执行closeDialog函数
methods: {
closeDialog ( ) {
// this.status.isShowPublish=false;
//把绑定的弹窗数组 设为false即可关闭弹窗
} ,
}
可以用弹窗组件实现下列这种信息展示,或者事件交互:
上面是把弹窗的每个步骤拆分开来,一步步解析的,每一步都说的比较清楚了,具体连起来的话,可以看看代码 ,再结合文章就能理的很清楚了。
# 小结:
这个弹窗组件,实现起来一点都不难,我这里主要是提供了一个实现方式,当项目中有需要的话,大家也可以自己撸一个出来,以上就是本文的内容了,希望同学们看完能有所收获。
以上2018.4.21
# 参考资料:
Vue文档-插槽
博客链接
# 如何搭建本文档
VuePress是尤大为了支持 Vue 及其子项目的文档需求而写的一个项目,VuePress界面十分简洁,并且非常容易上手,一个小时就可以将项目架构搭好 。现在已经有很多这种类型的文档,如果你有写技术文档/技术博客的需求,VuePress绝对可以成为你的备选项之一。
# VuePress特性:
为技术文档而优化的 内置 Markdown 拓展
在 Markdown 文件中使用 Vue 组件的能力
Vue 驱动的自定义主题系统
自动生成 Service Worker
Google Analytics 集成
基于 Git 的 “最后更新时间”
多语言支持
默认主题包含:
建议先看一下官方文档
# 效果:
可能你会搭建出一个类似这样的文档 :
# 搭建:
# 全局安装VuePress
yarn global add vuepress # 或者:npm install - g vuepress
# 新建文件夹
可以手动右键新建,也可以使用下面的命令新建文件夹:
# 项目初始化
进入到project
文件夹中,使用命令行初始化项目:
yarn init - y # 或者 npm init - y
将会创建一个package.json
文件,长这样子:
{
"name" : "project" ,
"version" : "1.0.0" ,
"description" : "" ,
"main" : "index.js" ,
"scripts" : {
"test" : "echo \"Error: no test specified\" && exit 1"
} ,
"keywords" : [ ] ,
"author" : "" ,
"license" : "ISC"
}
# 在project的根目录下新建docs文件夹:
这个文档将作为项目文档的根目录来使用:
# 在docs文件夹下创建.vuepress
文件夹:
所有 VuePress 相关的文件都将会被放在这里
# 在.vuepress
文件夹下面创建config.js
:
config.js是VuePress必要的配置文件,它导出一个javascript对象。
你可以先加入如下配置:
module. exports = {
title: 'Hello VuePress' ,
description: 'Just playing around'
}
# 在.vuepress
文件夹下面创建public文件夹:
这个文件夹是用来放置静态资源的,打包出来之后会放在.vuepress/dist/的根目录。
# 首页(像VuePress文档主页一样)
在docs文件夹下面创建一个README.md
:
默认的主题提供了一个首页,像下面一样设置home:true
即可,可以把下面的设置放入README.md
中,待会儿你将会看到跟VuePress
一样的主页。
---
home: true
heroImage: /logo.jpg
actionText: 快速上手 →
actionLink: /zh/guide/
features:
- title: 简洁至上
details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。
- title: Vue驱动
details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。
- title: 高性能
details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。
footer: MIT Licensed | Copyright © 2018-present Evan You
---
ps:你需要放一张图片到public文件夹中。
# 我们的项目结构已经搭好了:
project
├─── docs
│ ├── README.md
│ └── .vuepress
│ ├── public
│ └── config.js
└── package.json
# 在 package.json
里添加两个启动命令:
{
"scripts" : {
"docs:dev" : "vuepress dev docs" ,
"docs:build" : "vuepress build docs"
}
}
# 启动你的VuePress:
默认是localhost:8080
端口。
yarn docs: dev # 或者:npm run docs: dev
# 构建:
build生成静态的HTML文件,默认会在 .vuepress/dist
文件夹下
yarn docs: build # 或者:npm run docs: build
# 基本配置:
最标准的当然是官方文档 ,可以自己的需求来配置config.js
。
可以参考一下我的config.js
的配置:
module. exports = {
title: '网站标题' ,
description: '网站描述' ,
// 注入到当前页面的 HTML <head> 中的标签
head: [
[ 'link' , { rel: 'icon' , href: '/favicon.ico' } ] , // 增加一个自定义的 favicon(网页标签的图标)
] ,
base: '/web_accumulate/' , // 这是部署到github相关的配置 下面会讲
markdown: {
lineNumbers: true // 代码块显示行号
} ,
themeConfig: {
sidebarDepth: 2 , // e'b将同时提取markdown中h2 和 h3 标题,显示在侧边栏上。
lastUpdated: 'Last Updated' // 文档更新时间:每个文件git最后提交的时间
}
} ;
# 导航栏配置:
module. exports = {
themeConfig: {
nav: [
{ text: '前端算法' , link: '/algorithm/' } , // 内部链接 以docs为根目录
{ text: '博客' , link: 'http://obkoro1.com/' } , // 外部链接
// 下拉列表
{
text: 'GitHub' ,
items: [
{ text: 'GitHub地址' , link: 'https://github.com/OBKoro1' } ,
{
text: '算法仓库' ,
link: 'https://github.com/OBKoro1/Brush_algorithm'
}
]
}
]
}
}
# 侧边栏配置:
侧边栏的配置相对麻烦点,我里面都做了详细的注释,仔细看,自己鼓捣鼓捣 就知道怎么搞了。
module. exports = {
themeConfig: {
sidebar: {
// docs文件夹下面的accumulate文件夹 文档中md文件 书写的位置(命名随意)
'/accumulate/' : [
'/accumulate/' , // accumulate文件夹的README.md 不是下拉框形式
{
title: '侧边栏下拉框的标题1' ,
children: [
'/accumulate/JS/test' , // 以docs为根目录来查找文件
// 上面地址查找的是:docs>accumulate>JS>test.md 文件
// 自动加.md 每个子选项的标题 是该md文件中的第一个h1/h2/h3标题
]
}
] ,
// docs文件夹下面的algorithm文件夹 这是第二组侧边栏 跟第一组侧边栏没关系
'/algorithm/' : [
'/algorithm/' ,
{
title: '第二组侧边栏下拉框的标题1' ,
children: [
'/algorithm/simple/test'
]
}
]
}
}
}
# 其他:
# 代码块编译错误:
像下面这段代码会导致编译错误,VuePress会去找里面的变量,把它编译成text:
所以我们的代码块要以这种形式书写:
//```js
{ { } } 啦 { { } } // 注释需要打开 这样vuepress会把这里面包裹的当成代码块而不是js
//```
并且这样也会让我们的代码高亮显示(下图第一个没有高亮,第二个有高亮),阅读体验更好:
# 自定义容器了解一下:
更改标题:
::: tip 替换tip的标题
这里是内容。
:::
其实文档 里有,我这里只是提一下。
# 支持Emoji
文档中只提了支持Emoji,我在GitHub上找到了Emoji的列表 ,分享一下。
# 一个命令行发布到github上:
# 在 docs/.vuepress/config.js
中设置正确的 base:
如果你打算发布到 https://<USERNAME>.github.io/
,则可以省略这一步,因为 base 默认即是 "/"
。
如果你打算发布到 https://<USERNAME>.github.io/<REPO>/
(也就是说你的仓库在 https://github.com/<USERNAME>/<REPO>
),则将 base 设置为 "/<REPO>/"
。
module. exports = {
base: '/test/' , // 比如你的仓库是test
}
# 创建脚步文件:
在project
的根目录下,创建一个deploy.sh
文件:
#!/usr/bin/env sh
确保脚本抛出遇到的错误
set -e
生成静态文件
npm run docs:build
进入生成的文件夹
cd docs/.vuepress/dist
如果是发布到自定义域名
echo 'www.example.com' > CNAME
git init
git add -A
git commit -m 'deploy'
如果发布到 https://<USERNAME>.github.io USERNAME=你的用户名
git push -f [email protected] :<USERNAME>/<USERNAME>.github.io.git master
如果发布到 https://<USERNAME>.github.io/<REPO> REPO=github上的项目
git push -f [email protected] :<USERNAME>/<REPO>.git master:gh-pages
cd -
# 设置package.json:
{
<span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"d"</span><span class="token operator">:</span> <span class="token string">"bash deploy.sh"</span>
<span class="token punctuation">}</span>
}
# 部署:
然后你每次可以运行下面的命令行,来把最新更改推到github
上:
如果你对运行项目和构建项目的命令行觉得很烦,你也可以像我这么做:
"scripts" : {
"dev" : "vuepress dev docs" , // 本地运行项目 npm run dev
"build" : "vuepress build docs" , // 构建项目 nom run build
"d" : "bash deploy.sh" // 部署项目 npm run d
} ,
# 更多:
实际上VuePress的配置、用法还有很多,像还可以配置PWA,以及在markdown里面使用Vue组件等,这些功能我也还在摸索,所以大家一定要去看文档 !
# 小结
上面已经写得尽可能详细了,我遇到的坑都写上去了。搭建起来确实很简单,心动不如行动,随手花一两个小时搭建一下又不吃亏,何乐而不为?
博客链接
# Vue 定义全局函数
# 原理
通过 Vue.prototype 将函数挂载到 Vue 实例上面 ,在组件中通过 this.函数名,来运行函数。
# 1. 在 main.js 里面直接写函数
直接在 main.js 里面写 :
Vue. prototype. test = function ( ) {
console. log ( '执行全局函数test' ) ;
} ;
组件中调用 :
this . test ( ) ; // 直接通过this运行函数
# 2. 写一个模块文件,挂载到 main.js 上面。
想要定义的全局函数比较多的话,推荐写在一个js文件里面,文件位置可以放在跟 main.js 同一级,方便引用
// base.js
exports. install = function ( Vue, options) {
Vue. prototype. text1 = function ( ) {
console. log ( '执行成功1' ) ;
} ;
Vue. prototype. text2 = function ( ) {
console. log ( '执行成功2' ) ;
} ;
} ;
main.js 入口文件:
import Vue from 'vue' ; // vue要在引文件之前
import base from './base.js' ; // 引用文件
Vue. use ( base) ; //将全局函数当做插件来进行注册
组件里面调用:
this . text1 ( ) ;
this . text2 ( ) ;
博客链接
# 仿Vue极简双向绑定
现在的前端面试不管你用的什么框架,总会问你这个框架的双向绑定机制,有的甚至要求你现场实现一个双向绑定出来,那对于没有好好研究过这方面知识的同学来说,当然是很难的,接下来本文用160行代码带你实现一个极简的双向绑定机制 。如果喜欢的话可以点波赞/关注,支持一下,希望大家看完本文可以有所收获。
# 效果GIF:
# demo地址:
codepen: 仿Vue极简双向绑定
Github: 仿Vue极简双向绑定
# 了解Object.defineProperty():
这个API是实现双向绑定的核心,最主要的作用是重写数据的get
、set
方法
# 使用方式:
let obj = {
singer: "周杰伦"
} ;
let default_value = "青花瓷" ;
Object. defineProperty ( obj, "music" , {
// value: '七里香', // 设置属性的值 下面设置了get set函数 所以这里不能设置
configurable: false , // 是否可以删除属性 默认不能删除
// writable: true, // 是否可以修改对象 下面设置了get set函数 所以这里不能设置
enumerable: true , // music是否可以被枚举 默认是不能被枚举(遍历)
// ☆ get,set设置时不能设置writable和value,要一对一对设置,交叉设置/同时存在 就会报错
get ( ) {
// 获取obj.music的时候就会调用get方法
// let default_value = "强行设置get的返回值"; // 打开注释 读取属性永远都是‘强行设置get的返回值’
return default_value;
} ,
set ( val) {
// 将修改的值重新赋给song
default_value = val;
}
} ) ;
console. log ( obj. music) ; // 青花瓷
delete obj. music; // configurable设为false 删除无效
console. log ( obj. music) ; // 青花瓷
obj. music = "听妈妈的话" ;
console. log ( obj. music) ; // 听妈妈的话
for ( let key in obj) {
// 默认情况下通过defineProperty定义的属性是不能被枚举(遍历)的
// 需要设置enumerable为true才可以 否则只能拿到singer 属性
console. log ( key) ; // singer, music
}
# 示例demo:
对,这里有个demo 。
# 画一下重点:
get,set设置时不能设置writable和value, 他们是一对情侣的存在,交叉设置或同时存在,会报错
通过defineProperty
设置的属性,默认不能删除,不能遍历 ,当然你可以通过设置更改他们。
get、set 是函数,可以做的事情很多 。
兼容性 :IE 9,Firefox 4, Chorme 5,Opera 11.6,Safari 5.1
更详细的可以看一下MDN
# 实现思路:
# mvvm系列的双向绑定,关键步骤:
实现数据监听器Observer,用Object.defineProperty()
重写数据的get、set,值更新就在set中通知订阅者更新数据 。
实现模板编译Compile,深度遍历dom树,对每个元素节点的指令模板进行替换数据以及订阅数据 。
实现Watch用于连接Observer和Compile,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图 。
mvvm入口函数,整合以上三者。
# 流程图:
这部分讲的很清楚,现在有点懵逼也没关系,看完代码,自己copy下来玩一玩之后,回头再看实现思路,相信会有收获的。
# 具体代码实现:
# html结构:
< div id = " app" >
< input type = " text" v-model = " name" >
< h3 v-bind = " name" > </ h3>
< input type = " text" v-model = " testData1" >
< h3> {{ testData1 }}</ h3>
< input type = " text" v-model = " testData2" >
< h3> {{ testData2 }}</ h3>
</ div>
看到这个模板,相信用过Vue的同学都不会陌生。
# 调用方法:
采用类Vue方式来使用双向绑定:
window. onload = function ( ) {
var app = new myVue ( {
el: '#app' , // dom
data: { // 数据
testData1: '仿Vue' ,
testData2: '极简双向绑定' ,
name: 'OBKoro1'
}
} )
}
# 创建myVue函数:
实际上这里是我们实现思路中的第四步,用于整合数据监听器this._observer()
、指令解析器this._compile()
以及连接Observer和Compile的_watcherTpl的watch池。
function myVue ( options = { } ) { // 防止没传,设一个默认值
this . $options = options; // 配置挂载
this . $el = document. querySelector ( options. el) ; // 获取dom
this . _data = options. data; // 数据挂载
this . _watcherTpl = { } ; // watcher池
this . _observer ( this . _data) ; // 传入数据,执行函数,重写数据的get set
this . _compile ( this . $el) ; // 传入dom,执行函数,编译模板 发布订阅
} ;
# Watcher函数:
这是实现思路中的第三步,因为下方数据监听器_observer()
需要用到Watcher函数,所以这里就先讲了。
像实现思路中所说的,这里起到了连接Observer和Compile的作用:
在模板编译_compile()阶段发布订阅
在赋值操作的时候,更新视图
// new Watcher() 为this._compile()发布订阅+ 在this._observer()中set(赋值)的时候更新视图
function Watcher ( el, vm, val, attr) {
this . el = el; // 指令对应的DOM元素
this . vm = vm; // myVue实例
this . val = val; // 指令对应的值
this . attr = attr; // dom获取值,如value获取input的值 / innerHTML获取dom的值
this . update ( ) ; // 更新视图
}
Watcher. prototype. update = function ( ) {
this . el[ this . attr] = this . vm. _data[ this . val] ; // 获取data的最新值 赋值给dom 更新视图
}
没有看错,代码量就这么多,可能需要把整个代码连接起来,多看几遍才能够理解。
# 实现数据监听器_observer():
实现思路中的第一步,用Object.defineProperty()
遍历data重写所有属性的get set。
然后在给对象的某个属性赋值的时候,就会触发set。
在set中我们可以监听到数据的变化,然后就可以触发watch更新视图 。
myVue. prototype. _observer = function ( obj) {
var _this = this ;
Object. keys ( obj) . forEach ( key => { // 遍历数据
_this. _watcherTpl[ key] = { // 每个数据的订阅池()
_directives: [ ]
} ;
var value = obj[ key] ; // 获取属性值
var watcherTpl = _this. _watcherTpl[ key] ; // 数据的订阅池
Object. defineProperty ( _this. _data, key, { // 双向绑定最重要的部分 重写数据的set get
configurable: true , // 可以删除
enumerable: true , // 可以遍历
get ( ) {
console. log ( ` ${ key} 获取值: ${ value} ` ) ;
return value; // 获取值的时候 直接返回
} ,
set ( newVal) { // 改变值的时候 触发set
console. log ( ` ${ key} 更新: ${ newVal} ` ) ;
if ( value !== newVal) {
value = newVal;
watcherTpl. _directives. forEach ( ( item) => { // 遍历订阅池
item. update ( ) ;
// 遍历所有订阅的地方(v-model+v-bind+{{}}) 触发this._compile()中发布的订阅Watcher 更新视图
} ) ;
}
}
} )
} ) ;
}
# 实现Compile 模板编译
这里是实现思路中的第三步,让我们来总结一下这里做了哪些事情:
首先是深度遍历dom树,遍历每个节点以及子节点。
将模板中的变量替换成数据,初始化渲染页面视图。
把指令绑定的属性添加到对应的订阅池中
一旦数据有变动,收到通知,更新视图。
myVue. prototype. _compile = function ( el) {
var _this = this , nodes = el. children; // 获取app的dom
for ( var i = 0 , len = nodes. length; i < len; i++ ) { // 遍历dom节点
var node = nodes[ i] ;
if ( node. children. length) {
_this. _compile ( node) ; // 递归深度遍历 dom树
}
// 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件
if ( node. hasAttribute ( 'v-model' ) && ( node. tagName = 'INPUT' || node. tagName == 'TEXTAREA' ) ) {
node. addEventListener ( 'input' , ( function ( key) {
var attVal = node. getAttribute ( 'v-model' ) ; // 获取v-model绑定的值
_this. _watcherTpl[ attVal] . _directives. push ( new Watcher ( // 将dom替换成属性的数据并发布订阅 在set的时候更新数据
node,
_this,
attVal,
'value'
) ) ;
return function ( ) {
_this. _data[ attVal] = nodes[ key] . value; // input值改变的时候 将新值赋给数据 触发set=>set触发watch 更新视图
}
} ) ( i) ) ;
}
if ( node. hasAttribute ( 'v-bind' ) ) { // v-bind指令
var attrVal = node. getAttribute ( 'v-bind' ) ; // 绑定的data
_this. _watcherTpl[ attrVal] . _directives. push ( new Watcher ( // 将dom替换成属性的数据并发布订阅 在set的时候更新数据
node,
_this,
attrVal,
'innerHTML'
) )
}
<span class="token keyword">var</span> reg <span class="token operator">=</span> <span class="token regex">/\{\{\s*([^}]+\S)\s*\}\}/g</span><span class="token punctuation">,</span> txt <span class="token operator">=</span> node<span class="token punctuation">.</span>textContent<span class="token punctuation">;</span> <span class="token comment">// 正则匹配{{}} </span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>reg<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>txt<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
node<span class="token punctuation">.</span>textContent <span class="token operator">=</span> txt<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>reg<span class="token punctuation">,</span> <span class="token punctuation">(</span>matched<span class="token punctuation">,</span> placeholder<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token comment">// matched匹配的文本节点包括双花括号, placeholder 是双花括号中间的属性名</span>
<span class="token keyword">var</span> getName <span class="token operator">=</span> _this<span class="token punctuation">.</span>_watcherTpl<span class="token punctuation">;</span> <span class="token comment">// 所有绑定watch的数据</span>
getName <span class="token operator">=</span> getName<span class="token punctuation">[</span>placeholder<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 获取对应watch 数据的值</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>getName<span class="token punctuation">.</span>_directives<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 没有事件池 创建事件池</span>
getName<span class="token punctuation">.</span>_directives <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
getName<span class="token punctuation">.</span>_directives<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Watcher</span><span class="token punctuation">(</span> <span class="token comment">// 将dom替换成属性的数据并发布订阅 在set的时候更新数据</span>
node<span class="token punctuation">,</span>
_this<span class="token punctuation">,</span>
placeholder<span class="token punctuation">,</span>
<span class="token string">'innerHTML'</span>
<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> placeholder<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'.'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>val<span class="token punctuation">,</span> key<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> _this<span class="token punctuation">.</span>_data<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 获取数据的值 触发get 返回当前值 </span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> _this<span class="token punctuation">.</span>$el<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
}
# 完整代码&demo地址
GitHub完整代码
codepen: 仿Vue极简双向绑定
Github: 仿Vue极简双向绑定
如果觉得还不错的话,就给个Star ⭐️鼓励一下我吧~
# 小结
本文只是一个简单的实现双向绑定的方法,主要目的是帮助各位同学理解mvvm框架的双向绑定机制,也并没有很完善,这里还是有很多缺陷,比如:没有实现数据的深度对数据进行get
、set
等。希望看完本文,大家能有所收获。
以上2018.6.24
# 参考资料:
剖析Vue原理&实现双向绑定MVVM
面试题:你能写一个Vue的双向数据绑定吗?
不好意思!耽误你的十分钟,让MVVM原理还给你
博客链接
# input 的一些坑点分享
# 本文内容包括:
移动端底部 input 被弹出的键盘遮挡。
控制 input 显/隐密码。
在 input 中输入 emoji 表情导致请求失败。
input 多行输入显示换行。
输入框首尾清除空格-trim()
在 input 中监听键盘事件
# 移动端底部 input 被弹出的键盘遮挡
input 输入框是通过position:fixed
一直放在页面底部,当点击 input 进行输入的时候,就会出现如下图片情况(有的机型会遮挡一些)。
当时这个问题是去年在 ios 中遇到的,在最新版的 ios 系统中,貌似解决了这个 bug,但是为了向下兼容以及防止其他其他机型也出现这个问题,大家可以稍微记一下这个解决方法。
在解决这个问题的时候,有试过下面这种方法:
在 input 的 focus 事件中,开启一个定时器,然后每隔 300 毫秒进行一次 document.body.scrollTop=document.body.scrollHeight 的调整,运行 3 次即可。
当时还以为解决了,但是当你底部评论区还有很多内容,你每次点击 input,想要输入的时候,整个页面通过scrollTop
就会不断的向下滚动,这个体验不用说自己也知道是相当失败的,然后就再去找解决方法,结果就有了下面这个。
# Element.scrollIntoView()
Element.scrollIntoView() :方法让当前的元素滚动到浏览器窗口的可视区域内 。
document. querySelector ( '#inputId' ) . scrollIntoView ( ) ;
//只要在input的点击事件,或者获取焦点的事件中,加入这个api就好了
这个 api 还可以设置对齐方法,选择将 input 放在屏幕的上方/下方,类似的 api 还有:Element.scrollIntoViewIfNeeded() ,这两个是解决同一个问题的,选择一个用就可以了。
# 控制 input 显/隐密码
这个就很简单了,只需更改 input 的 type 属性值就可以了。可以看一下 codepen 的demo
//点击函数,获取dom,判断更改属性。
show ( ) {
let input= document. getElementById ( "inputId" ) ;
if ( input. type== "password" ) {
input. type= 'text' ;
} else {
input. type= 'password' ;
}
}
# 在 input 中输入 emoji 表情导致请求失败
现在用户输入 emoji 简直已经成为了习惯,如果前后端没有对 emoji 表情进行处理,那么用户在上传的时候,就会请求失败。
通常这个问题是后端那边处理比较合适的 ,前端是做不了这件事的,或者说很难做这件事。
之前看过一篇文章 ,这个文章里面讲了怎么在上传和拿数据下来的时候不会报错,但是不能在显示的时候转换为表情。
ps:之前拿微信用户名的时候,有些人可能在微信昵称上面就会包含表情,如果后端没对表情处理转换,那么普通请求也会出错 。
之所以说这个,当表单请求错误的时候各位如果实在找不到问题可以往这方面考虑一下,我真的被坑过的 o(╥﹏╥)o。
# textarea 多行回车换行,显示的时候换行设置:
在使用textarea
标签输入多行文本的时候,如果没有对多行文本显示处理,会导致没有换行的情况,就比如下面这种情况,用户在textarea
是有换行的。
white-space 属性用于设置如何处理元素内的空白,其中包括空白符和换行符。
只要在显示内容的地方将该属性设置为white-space: pre-line
或者white-space:pre-wrap
,多行文本就可以换行了 。
# 设置之后,显示效果:
# 输入框首尾清除空格-trim()
输入框清除首尾空格是 input 较为常见的需求,通常在上传的时候将首尾空格去除掉。一般使用:字符串的原生方法trim() 从一个字符串的两端删除空白字符。
trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。
# 原生清除方法:
//原生方法获取值,清除首尾空格上传str2
let str2 = document. getElementById ( 'inputId' ) . trim ( ) ;
# Vue 清除方法:
Vue 提供了修饰符 删除首尾空格, 加了修饰符.trim
会自动过滤用户输入的首尾空白字符
< input v-model.trim = " msg" >
貌似 angular 也提供了类似过滤的方法,感兴趣的可以自己去查一下。
# 在 input 中监听键盘事件
在用户登录或者搜索框的时候,一般都会监听键盘事件绑定回车按键,来执行登录/搜索 等操作。
# 原生绑定:
< input onkeydown= "keydownMsg(event)" type= "text" / > ;
function keydownMsg ( key) {
keyCode = key. keyCode; //获取按键代码
if ( keyCode == 13 ) {
//判断按下的是否为回车键
// 在input上监听到回车 do something
}
}
# Vue 按键修饰符
Vue 为监听键盘事件,提供了按键修饰符 ,并且为常用的按键提供了别名,使用方法如下:当回车按键在 input 中被按下的时候,会触发里面的函数。
< input @keyup.enter = " enterActive" >
博客链接
Recommend Projects
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
Recommend Topics
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
Recommend Org
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.
Jobs
Jooble
Copyright © 2024 GithubHelp ❤️ Mail to me