diff --git a/2016-06-14-animate-numberundefined b/2016-06-14-animate-numberundefined new file mode 100644 index 00000000..39aba4a4 --- /dev/null +++ b/2016-06-14-animate-numberundefined @@ -0,0 +1,499 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +实现数字滚动变化以及延伸 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 实现数字滚动变化以及延伸 +

+ + +
+ + + + +
+

利用jquery的插件jquery.animateNumber实现一个简单的数字滚动效果

+
+

需求分析

    +
  1. 处理数据:因为数据是后端提供,所以有可能格式不是我们想要的,所以也许需要格式化数据;
  2. +
  3. 根据页面设计的效果图(如图),需要把数字字符串拆分成单个数字字符串
    animateNumber_01
  4. +
  5. 每一个数字进行滚动变化
  6. +
  7. 最后,在项目中,我选取了插件jquery.animateNumber来实现滚动效果。这个插件的使用方式很简单,在官方有很详尽的文档来展示各个案例,就不一一赘述了。
  8. +
+

HTML布局

其中num是后台传入的值,notChangeUint用来标记不进行单位变换的值

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="warp">
<div class="net-credit-num" >
<p>平台累积会员人数(人)</p>
<span date-num="123" class="animateNumber notChangeUint"></span>
</div>
<div class="net-credit-money">
<p>平台完成投资金额(万元)</p>
<span date-num="91,123,456.00" class="animateNumber"></span>
</div>
<div class="net-return-money">
<p>累计已还款金额(万元)</p>
<span date-num="8,895,678.00" class="animateNumber"></span>
</div>
</div>
+ +

撸JS

去除逗号(,)

1
num = num.replace(',','');
+ +

上面这种方法只能去除字符串中的第一个逗号,但是实际数据中可能存在多个逗号,所以需要用到正则全局匹配替换,代码如下:

+
1
2
var reg = new RegExp(',','g');
num = num.replace(reg,'');
+ +

转化单位(元–>万元)

把金额单位转化为万元,并且保留两位小数,人数不进行转化

+
1
2
3
if(!numWarpParent.hasClass('notChangeUint')){
num = (Number(num) / 10000).toFixed(2);
}
+ +

字符串拆分为数组

1
numArry = num.split('');
+ +

把数字添加到页面并调用animateNumber的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
for(var i = 0; i < numArry.length; i++){
var thisNum = parseInt(numArry[i]);
var spanNum;
if (!isNaN(thisNum)){
spanNum = $('<span class="single-num">' + numArry[i] +'</span>');
}else{
spanNum = $('<span class="single-point">.</span>');
};
numWarpParent.append(spanNum);
thisNumWarp.prop('number', stratNum).animateNumber({
number: thisNum
}, time);
}
+ +

最后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$('.animateNumber').each(function(){
var _this = $(this);
var totalNum = _this.attr('date-num'); //后台数据储存在date-num上
appendNum(totalNum,_this);
});
function appendNum(num,numWarpParent){
var newNum;
var reg = new RegExp(',','g'); //正则匹配所有逗号
newNum = num.replace(reg,'');
if(isNaN(num)) newNum = 0; //容错,当后台传入的参数错误(非数字)时,将只值置为0,以保证页面的正常渲染
if(!numWarpParent.hasClass('notChangeUint')){ //判断是否需要转换单位
newNum = (Number(newNum) / 10000).toFixed(2);
};
numArry = newNum.split('');
for(var i = 0; i < numArry.length; i++){
var thisNum = numArry[i];
var numWarp;
if (!isNaN(thisNum)){ //判断是否可以转化为数字
numWarp = $('<label class="single-num">' + numArry[i] +'</label>');
}else{
numWarp = $('<label class="single-point">.</label>');
};
numWarpParent.append(numWarp);
isAnimate(thisNum,numWarpParent,i);
};
};
function isAnimate(num,numWarpParent,index){
if (isNaN(num))return;
//调用animate.js插件方法
numWarpParent.find('label').eq(index).prop('number', 0).animateNumber({
number: num
}, num * 100);
};
+ +

总结

1.功能模块化,尽量一个方法(函数)只做一件事情
2.容错,由于涉及到DOM操作,所以为了保证页面的正常渲染必须有容错处理机制:数据出错不影响整个流程(页面渲染)的畅通

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016-06-15-get-url-valuesundefined b/2016-06-15-get-url-valuesundefined new file mode 100644 index 00000000..8b3ac10d --- /dev/null +++ b/2016-06-15-get-url-valuesundefined @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +利用javascrit获取url传递的参数 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 利用javascrit获取url传递的参数 +

+ + +
+ + + + +

神奇的url

一条url包含了很丰富的信息,那么我们如何来获取这些信息并有效的加以利用呢?
随便举个例子:https://github.com/search?utf8=%E2%9C%93&q=javascript
这条url就是在github上搜索javascript后跳转页面对应的url。我们要做的就是获取’?’后面的参数,以及获取后可以用来做什么。

+

获取参数

window.location的对象方法

window.location的参数

+

获取url所有的参数

我们可以直接通过window.location.search来取得这部分,也就是我们需要的url参数。

+

url没有包含?时,window.location.search会返回undefined

+
1
2
3
4
5
6
7
8
9
10
11
12
13
function getUrlVal(str){
if(!str || str.indexOf('?') != 0) return false;
var urlValArry = str.replace('?','').split('&');
var urlValObject = {};
for(var i in urlValArry){
urlValObject[urlValArry[i].split('=')[0]] = urlValArry[i].split('=')[1];
};
return urlValObject;
};

// https://github.com/search?utf8=%E2%9C%93&q=javascript
var urlStr = window.location.search.replace('?','');
console.log(getUrlVal(urlStr)); //输出 Object {utf8: "%E2%9C%93", q: "javascript"}
+ +

获取url中指定键名(name)的键值(val)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getOneVal(str,name){
if(!str || str.indexOf('?') != 0) return false;
var afterNameStr = str.replace('?','').split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
// 返回第一个&位置,如果没有'&'则返回字符串长度
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSite
var reslt = afterNameStr.slice(1,strFirstSite);
return reslt;
};

// 'http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614&place=N230&peopleNum=657'
var urlStr = window.location.search;
console.log(getOneVal(urlStr,'time')) //输出20160614
console.log(getOneVal(urlStr,'peopleNum')) //657
+ +

将方法绑定到原型链上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
String.prototype.toObj = function(key){
/*
如果有传入key,那么就只返回key对应的Val(找不到则返回undefined)
如果没有传入key,那么就返回一个object对象
*/
var str = this;
if(str.indexOf('?') != 0) return {};
if(str.indexOf(key) == -1) return undefined;
var tmpArry = str.replace('?','').split('&');
var reslt = {};
for(var i in tmpArry){
var tempKeyVal = tmpArry[i].split('=');
if(!!key) {
if(tempKeyVal[0] != key) reslt = undefined;
reslt = tempKeyVal[1];
break;
}else {
reslt[tempKeyVal[0]] = tempKeyVal[1];
}
};
return reslt;
};
// http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614
var urlStr = window.location.search;
console.log(urlStr.toObj()) //{itemtype:'sport',active:'basketball',time:'20160614'}
console.log(urlStr.toObj('active')) //basketball
+ +

利用正则表达式来获取参数

强大的正则总是让人心生向往,利用正则无疑是最简洁优雅的一种方法

+

获取指定某个参数

1
2
3
4
5
6
7
8
9
10
function getUrlParam(url,name){
if(!name) return;
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
r = url.substr(1).match(reg);
if (r != null) {
return (r[2]);
}else{
return null;
}
}
+ +

获取所有的参数

1
2
3
4
5
6
7
8
9
function parse_url(url){
if(!url) return;
var pattern = /(\w+)=(\w+)/ig;
var parames = {};
url.replace(pattern, function(a, b, c){
parames[b] = c;
});
return parames;
}
+ +

参数的利用

在项目中这些参数有哪些用处呢,下面列举几个比较常用的用处

+
    +
  • 传递数据
  • +
  • 导航定位
  • +
  • 更改状态
  • +
  • +
+

导航定位

什么是导航定位?就是点击导航栏的标签,页面跳转后,对应的标签相应的会突出变化。如下图:
navLocation

+

跳转后有两种情况:
一种ajax异步刷新,只是局部页面发生变化,因为可以直接用点击事件来控制。
另外一种比较常见的方式就是整个页面刷新,这种情况下,点击事件就没用了,就必须另辟蹊径:
1.比较传统的方法就是每个页面里面写一段CSS样式来控制
2.那么另外一种不用说就是通过url的参数来定位咯
假如用每个页面写CSS样式来控制,可以明显感受到的弊端是:每次新的页面都需要修改对应的CSS
那么利用url来控制又需要做哪些事呢?
1.首先需要约定参数,并且后台来传递这些参数
2.然后在导航栏部分,对应的地方加上参数值,这一步,导航栏都是公用模板,并且规则都一样,所以只需要一次添加
3.跳转后定位

+

html代码

1
2
3
4
5
6
<div class="nav">
<a href="/index.htm?nav=index">首页</a>
<a href="/layout/post.htm?nav=post">文章</a>
<a href="/layout/tag.htm?nav=tags">标签</a>
<a href="/layout/about.htm?nav=aboutUs">关于我</a>
</div>
+ +

js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getOneVal(name,urlValStr){
var afterNameStr = urlValStr.split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSit;
var val = afterNameStr.slice(1,strFirstSite);
return val;
};

var thisUrlVal = window.location.search.replace('?','');
var thisNVal = getOneVal('nav',urlValStr);

//定位
$('.nav a').each(function(){
var _this = $(this);
var urlValStr = _this.attr('href').split('?')[1];
var nVal = getOneVal('nav',urlValStr);
if(nVal == thisNVal) {
_this.addClass('on');
}
})
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016-06-20-learn-git-1undefined b/2016-06-20-learn-git-1undefined new file mode 100644 index 00000000..2255de7c --- /dev/null +++ b/2016-06-20-learn-git-1undefined @@ -0,0 +1,584 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列之新手入门 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列之新手入门 +

+ + +
+ + + + +
+

工欲善其事,必先利其器。git一个不可或缺的利器,其魅力值得我们慢慢品尝!

+
+

安装

官网下载最新版本安装,然后查看是否安装成功

+
1
2
$ git -v
git version 2.7.3.windows.1 //2.7.3为当前版本
+ +

全局个人信息配置

1
2
$ git config --global user.name "username"
$ git config --global user.email "email"
+ +

windows环境也可以打开计算机用户文件夹下的.gitconfig编辑

+
1
2
3
[user]
name = username
email = email
+ +

基本命令

初始化本地仓库

1
2
3
$ cd storage
$ git init //初始化当前目录为本地仓库
Initialized empty Git repository in D:/storages/.git/ //初始化了一个空的目录为本地仓库
+ +

第一次提交(提交到本地)

1
2
$ git add README.md
$ git commit -m "添加项目文档简介" //-m 参数后面跟表示对当前提交的一个简单说明
+ +

查看状态

查看当前文件处于何种状态

+
1
$ git status
+ +

回退

当你addcommit了错误的文件时,可以使用以下命令来撤回add或者commit;
那么你必须首先明白工作区和暂存区是什么?
直白的说.git文件夹所在的目录(即git init初始化的目录)为当前工作区
add之后,add的文件就会进入暂存区
commit之后,暂存区就会清空,commit的文件就会进入本地当前的分支(如master分支)
PS:关于工作区和暂存区更详细的解释可以去看廖雪峰工作区和暂存区

+

丢弃工区的修改

命令git checkout -- filename可以把filename文件在工作区的修改全部撤销

+
1
$ git checkout -- README.md
+ +

撤销暂存区的修改

命令git reset HEAD filename可以把filename文件在暂存区的修改全部撤销

+
1
2
3
4
5
6
$ git reset HEAD README.md
$ git checkout -- README.md //--参数不能省
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
+ +

当然reset 命令,不仅能够撤销暂存区的内容,还能回退版本,即使你错误提交,也可以用reset回滚到之前的版本
如果你提交了错误文件,可以使用以下命令来回退;

+
1
$ git reset --hard HEAD^
+ +

git reset --hard HEAD^表示回退到上一个版本,HEAD后面的参数可以跟commit ID,这个ID可以通过以下命令获得

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git log
commit 97c8460bc1cda8233866686d9cae270e0e0113f1
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 20 15:44:01 2016 +0800

update README.md

.....(中间有10条日志)

commit 2d936223341333384dd41533dd44ba8e0640493c
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 01 13:16:22 2016 +0800

README.md
+ +

commit 97c8460bc1cda8233866686d9cae270e0e0113f1,这一长串就是我们所需的ID,一般情况下,只需要前7位就够了。

+
1
2
$ git reset --hard HEAD 2d93622 //回退到commit 2d936223341333384dd41533dd44ba8e0640493c
$ git reset --hard HEAD~12 //回退到往上第12个版本,也是2d93622这个版本
+ +

提交到服务器

commit后,你做出的修改只是提交到了暂存区,下一步就需要把代码提交到服务器。这里的服务器,可以自己搭建,也可以是公司的,或者第三方的。这里我们将代码提交到github上(假设你是已经有一个github账户,并创建了一个仓库)。

+

本地生成SSH KEY

SSH KEY就是连通本地与github的桥梁,钥匙,使用以下命令来生成:

+
1
$ ssh-keygen -t rsa -C "youremail@example.com"
+ +

然后回车:

+
    +
  • 提示设置密码
      +
    • 如果不设置,直接按回车,然后会提示你确认密码,再按一次回车
    • +
    • 如果设置密码,输入你想设置的密码,并确认,这样以后每次提交需要输入密码
    • +
    +
  • +
  • 提示生成key的文件名
      +
    • 如果不修改,则使用默认文件名id_rsa
    • +
    • 如果你有在该电脑管理多个key或者已经有生成的key占用了该文件名,则需要重命令
    • +
    +
  • +
+

这里简单起见,先不设置密码,并保持文件名默认

+

然后在用户主目录(例如如果是windows系统一般就在:C:\Users\Administrator)找到.ssh,其中id_rsa表示私钥不能泄露和id_rsa.pub表示公钥,用于对外。

+

github设置本地公钥

打开id_rsa.pub,复制里面的全部内容;
进入github账户;
找到 setting
打开SSH KEYS新建一个SSH KEY,名字随便取,然后粘贴id_rsa.pub的内容,保存;

+

测试是否连通

理论上讲,上一步操作无特殊错误,就已经和github连通的,为了放心,我们可以测试一下

+
1
2
3
$ ssh -T git@github.com
Enter passphrase for key '/c/Users/Administrator/.ssh/id_rsa': //如果设置了密码,此处将会提示你输入密码
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+

上面就是成功的提示

+

github仓库和本地仓库关联

有两种方式把本地仓库和github仓库关联起来

+

方法一:clone自己的仓库,简单,并且本地文件目录和github上名字一样

登陆账号,新建仓库(new repository);
进入仓库主页,找到按钮clone or download按钮,复制里面的git@github.com:userName/repositoryName.git;
打开git bash,clone;

+
1
$ git clone git@github.com:userName/repositoryName.git
+ +

就会把这个项目克隆到你当前目录下。此时你就可以顺畅的像github推送你的东西了。

+

方法二:利用命令关联仓库,本地仓库名可以和github的仓库名不一样

假设你github上有一个项目,git@github.com:userName/repositoryName.git

+
1
2
3
4
5
6
$ mkdir testRepository  //新建目录testRepository
$ cd testRepository //切换到这个目录
$ git init //初始化当前目录
$ git remote add origin git@github.com:userName/repositoryName.git
// origin为本地暂存区的名字,为了语义化,建议默认为它
// 注意github上的repositoryName与本地的testRepository可以不一样
+ +

提交(push)

第一次提交

1
$ git push -u origin master
+ +

第二次及以后

1
$ git push origin master
+ +

第一次提交带参数-u是为了让你本地和github的仓库保持同步。

+

更新代码(pull)

当我们在一台设备上对github提交(push)代码之后,在另外的设备上修改这个项目时就必须先从github更新代码,以保持代码的同步

+
1
$ git pull
+ +
+

总结

至此一个比较完整的流程走通了,当然在这些过程中也许还会遇到其它的问题,如,代码冲突,分支,分支合并等等。

github上创建一个新的项目
github repositoryName:testGit
github userName:2ue

+
1
2
3
4
5
6
7
8
9
10
$ makdir testGit   //创建一个空文件夹testGit(名字任意取),做仓库
$ cd testGit //切换到目录testGit
$ git init //初始化testGit为本地仓库
$ echo 'this repository is localhost' > README.md
//创建文件README.md,并写入'this repository is localhost'
//在window下以上操作的第一步和第四步可以在图形化界面中完成
$ git add README.md
$ git commit -m 'add README.md'//提交更新,并注释信息“add README.md”
$ git remote add origin git@github.com:2ue/testGit.git //关联github上的项目
$ git push -u origin master //提交到github
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016-08-11-javascript-array-methodundefined b/2016-08-11-javascript-array-methodundefined new file mode 100644 index 00000000..6a7515c4 --- /dev/null +++ b/2016-08-11-javascript-array-methodundefined @@ -0,0 +1,696 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Javascript系列 - Javascript数组方法 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Javascript系列 - Javascript数组方法 +

+ + +
+ + + + +
+

Javascript的Array(数组对象)方法整理,对比他们的功能,返回值,分析他们的参数,以及具体的作用。

+
+

length:获取数组长度

+
    +
  • Method: Arry.length
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: 无
  • +
  • Return: 返回被引用数组长度
  • +
+
+
1
2
var arry = [1,6,8,'2ue','o90']
arry.length //返回5,arry = [1,6,8,'2ue','o90']
+ +

join:连接数组内各元素组成一个字符串

+
    +
  • Method: Arry.join(str)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: str非必需
      +
    • str不存在时(不传递str)以默认逗号连接元素
    • +
    • str可以为任意字符串,也可以为空(‘’)(字符串为空时,各元素之间无连接符号)
    • +
    +
  • +
  • Return: 返回连接后的字符串
  • +
+
+
1
2
3
4
var arry = [1,6,8,'2ue','o90']
arry.join() //返回字符串1,6,8,2ue,o90,arry = [1,6,8,'2ue','o90']
arry.join('-') //返回字符串1-6-8-2ue-o90,arry = [1,6,8,'2ue','o90']
arry.join('') //返回字符串1682ueo90,arry = [1,6,8,'2ue','o90']
+ +

注意如果需要加数组arry以逗号形式展示到页面,则不需要.join()方法,因为javascript的赋值操作会自动调用.toString()方法,如

+
1
2
3
4
5
6
7
//JS
var arry = [1,6,8,'2ue','o90']
var divBox = document.getElementById('div');
divBox.innerHTML = arry;

//前面赋值的操作将会调用toString方法,因此
console.log(divBox) //<div id="div">1,6,8,2ue,o90</div>
+ +

push:在数组尾部添加一个元素

+
    +
  • Method: Arry.push(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意义
    • +
    • value可以为合法的布尔值,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中,也可以接收多个参数
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arry = [1,6,8,'2ue','o90']
arry.push() //返回5,arry = [1,6,8,'2ue','o90'] 实际没有任何意义
arry.push(true) //返回6,arry = [1,6,8,'2ue','o90',true]
arry.push('dmw') //返回7,arry = [1,6,8,'2ue','o90',true,'dmw']
arry.push('') //返回8,arry = [1,6,8,'2ue','o90',true,'dmw','']
arry.push(3) //返回9,arry = [1,6,8,'2ue','o90',true,'dmw','',3]
arry.push(undefined) //返回10,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined]
arry.push(null) //返回11,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null]
arry.push(['9','8']) //返回12,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8']]
arry.push({key:'hah'}) //返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
//接收多个参数
var arry = [1,6,8,'2ue','o90']
arry.push(true,'dmw','',3,undefined,null,['9','8'],{key:'hah'})
//返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
+ +

unshift:在数组尾部添加一个元素

+
    +
  • Method: Arry.unshift(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中也可以接收多个参数。
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+

.push()方法

+

concat:在尾部添加元素到数组

+
    +
  • Method: Arry.concat(value,…)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回组成的新数组,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,也可以接收多个参数。
    • +
    • 其中当value为数组时,那么添加的是数组中的元素,而不是数组,所以可以用.concat()来连接数组
    • +
    +
  • +
  • Return: 返回组成的新数组
  • +
+
+
1
2
3
4
5
6
7
//返回值为一个新的数组,不改变原数组
//参数为数组时
var arry = [1,6,8,'2ue','o90']
var newArry = arry.concat(['lalal','mof'])
//返回值 newArry = [1,6,8,'2ue','o90','lalal','mof']
//原数组 arry = [1,6,8,'2ue','o90']
//其它情况同push方法一致
+ +

pop:删除最后一个元素

+
    +
  • Method: Arry.pop()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(最后一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+
1
2
3
var arry = [1]
arry.pop() //返回1,arry = []
arry.pop() //返回undefined,arry = []
+ +

shift:删除第一个元素

+
    +
  • Method: Arry.shift()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(第一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+

.pop()

+

reverse:颠倒数组元素顺序

+
    +
  • Method: Arry.reverse()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
1
2
var arry = [1,6,8,'2ue','o90']
arry.reverse() //返回['o90','2ue',8,6,1] arry = ['o90','2ue',8,6,1]
+ +

sort:数组元素排序

+
    +
  • Method: Arry.sort(fun)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: fun非必需
      +
    • fun如果为空,那么默认安装字符编码的顺序进行排序
    • +
    • 如不为空,那么fun必须为函数类型
    • +
    • Arry.sort(fun(value1,value2){}),fun函数参数value1 的值为Arry[i]value2 的值为Arry[i+1],其中0< = i < Arry.length - 1。所以请注意,.sort()方法排序会对被引用数组进行遍历,遍历的次数为Arry.length - 1,而非Arry.length。因为在Arry.length - 1次时,排序其实就已经完成了。
    • +
    +
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
1
2
3
4
5
6
7
var arry = [1,'2ue','o90',6,890,9,7990]
arry.sort()//返回[1, "2ue", 6, 7990, 890, 9, "o90"] arry = [1, "2ue", 6, 7990, 890, 9, "o90"]
var arry = [1,65443,6,890,9,7990]
arry.sort(function(value1,value2){
return value2-value1
})
//返回[65443, 7990, 890, 9, 6, 1] arry = [65443, 7990, 890, 9, 6, 1]
+ +

slice:根据索引返回数组的一部分

+
    +
  • Method: Arry.slice(satrtIndex,endIndex)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments:
      +
    • satrtIndex开始索引(不包含开始索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • endIndex结束索引(不包含结束索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • 最终satrtIndex的实际值必须小于endIndex,且他们所在的那段索引必须与被引用数组的索引有交集,否则返回空数组。
    • +
    +
  • +
  • Return: 根据索引返回数组的一部分,返回值为数组类型
  • +
+
+
1
2
3
4
5
6
7
8
9
//被引用数组值不会改变
var arry = [1,6,8,'2ue','o90']
arry.slice(1,3) //返回[6,8,'2ue']
arry.slice(3,1) //返回[]
arry.slice(-1,2) //返回[]
arry.slice(1,-2) //返回[6,8]
arry.slice(1,-4) //返回[]
arry.slice(-3,-1) //返回[6,8,'2ue']
arry.slice(-1,-3) //返回[]
+ +

splice:移除元素

+
    +
  • Method: Arry.splice(satrtIndex, deleteCount, value, …)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments:
      +
    • satrtIndex开始索引,必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • deleteCount将删除的个数,非必须,且必须为nubmer类型。从start开始,包括start所指的元素在内要删除的元素个数。这个参数是可选的,如果没有指定它,splice()将删除从start开始到原数组结尾的所有元素,小于等于0将不会删除。
    • +
    • value要插入数组的零个或多个值,从start所指的下标处开始插入。可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,可接收多个参数。
    • +
    +
  • +
  • Return: 被移除元素组成的数组
  • +
+
+
1
2
3
4
5
var arry = [1,6,8,'2ue','o90',4,5,6,7]
arry.splice(7) // 返回 [6,7]; arry = [1,6,8,'2ue','o90',4,5]
arry.splice(1,2) // 返回 [6,8]; arry = [1,'2ue','o90',4,5]
arry.splice(-1,1) //返回 [5]; arry = [1,'o90',4]
arry.splice(0,0,2,3,[8,9]) // 返回 []; arry = [2,3,[8,9],1,'o90',4]
+ +

来一张表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法名功能原数组是否改变返回
length获取数组长度NO被引用数组长度
join将数组元素连接起来以构建一个字符串NO转换后的字符串
push在尾部添加元素YES新数组长度
unshift在头部添加元素YES新数组长度
concat在尾部添加元素NO新数组
pop删除最后一个元素YES被删除元素
shift删除第一个元素YES被删除元素
reverse颠倒数组元素顺序YES对数组的引用
sort数组元素排序YES对数组的引用
slice根据索引返回数组的一部分NO根据索引返回数组的一部分
splice插入、删除或替换数组的元素YES被移除元素组成的数组
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016-09-05-regexundefined b/2016-09-05-regexundefined new file mode 100644 index 00000000..8004344c --- /dev/null +++ b/2016-09-05-regexundefined @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常用正则整理(持续收集) | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 常用正则整理(持续收集) +

+ + +
+ + + + +
+

正则的魅力在于使用很简洁的方式解决一些比较复杂的方式,使代码变得更优雅,也使实现的过程变得更简单透明。本文搜集整理一些常用正则,记录以便查阅

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var regex = window.regex || (function (document, $) {
var _reg = {};
/* 'pwd':/^[\@A-Za-z0-9\!\#\$\%\^\&\*\.\~]{6,16}$/,//密码 */
//验证数字
$.extend(_reg, {
'num': /^\d+$/, //数字
'znum': /^[1-9](\d+)?$/, // 大于0的数字
'float': /^[-]{0,1}(\d+)[\.]+(\d+)$/, //浮点数
'money': /^\d{1,12}(?:\.\d{1,3})?$/, // money
'idCard': /^\d{15}$|^\d{18}$|^\d{17}[xX]$/, //身份证
'idCardStrict':/^(\d{6})([1-2])(\d{3})((?:0[1-9])|(?:1[0-2]))((?:0[0-9])|(?:[1-2][0-9])|(?:3[0-1]))(\d{3})(\d{1})$/,
'qq': /^[1-9]\d{4,15}$/, //QQ
'pwd': /^[\@A-Za-z0-9]{6,16}$/, //密码
'areacode': /^(0[1,2]{1}\d{1})$|^(0[3-9]{1}\d{2})$/, //区号
'tel': /^\d{7,8}$/, // 固话格式
'mobile': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$/, //验证手机号码
'telephone': /^(((\+)?86)|(\(\+86\)))?-?((((0)?[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,12}))-?(\d{1,8})?$/, //验证固定电话
'phone': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$|^((\+86)|(\(\+86\)))?-?(((0[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,8}))$/, //手机号码和固定电话
'zipcode': /^\d{6}$/ //验证邮编
});
//验证字符串
$.extend(_reg, {
'email': /^\w{1,16}([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, //邮箱
'chinese': /^[\u4E00-\u9FA5]+$/, //仅汉字
'char': /^[A-Za-z]+$/, //仅仅是字母
'charn': /^[A-Za-z0-9]+$/, //数字加字母
'nospecial': /^[\u4E00-\u9FA5A-Za-z0-9]+$/, // 不包含特殊字符
'url': /^((http|https|ftp):\/\/)?(\w(\:\w)?@)?([0-9a-z_-]+\.)*?([a-z0-9-]+\.[a-z]{2,6}(\.[a-z]{2})?(\:[0-9]{2,6})?)((\/[^?#<>\/\\*":]*)+(\?[^#]*)?(#.*)?)?$/,
'loginName': /^(13|14|15|18|17)[0-9]{9}$|^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, // 用户名
'userName': /^[\u4e00-\u9fa5]{2`,4}$|[a-zA-Z]{4,20}$/, //真实姓名
'nickName': /^([a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{3,19})$/ //昵称
});
return _reg;
})(document, window.jQuery);
window.regex = regex;
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+
    +
  • + 原作者: J.2ue +
  • +
  • + 本文链接: + https://blog.imx0.com/2016/09/05/regex.html +
  • +
  • + 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处! +
  • +
+
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016-09-24-use-gulp-and-webpack-to-bulid-resourceundefined b/2016-09-24-use-gulp-and-webpack-to-bulid-resourceundefined new file mode 100644 index 00000000..aa3bfa12 --- /dev/null +++ b/2016-09-24-use-gulp-and-webpack-to-bulid-resourceundefined @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用webpack + gulp构建项目 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用webpack + gulp构建项目 +

+ + +
+ + + + +
+

有人说为什么会使用webpack+gulp呢?强大的webpack完全可以摒弃gulp了嘛?话虽如此,但个人觉得webpack配置太繁琐复杂,相对来说gulp更简单一点,并且gulp也能很好的完成我期望的任务。所以我想用webpack来处理js任务(因为它支持AMD和CMD,并且可以直接引入模块),用gulp处理images/css/html等资源

+
+

ps: 平时在项目中使用它们的机会不多,以下都是自己项目之外的折腾,如果有错误之处,请不吝指出。

+

demo

先上DEMO

+

配置webpack

webpack的有很强大的配置选项,官方中英文文档都已经很详尽。
中文文档
英文文档

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

上面的配置就是对js进行打包处理,当然webpack肯定也可以处理css和images等资源,webpack的强大毋庸置疑,但为什么不用webpack来处理css等任务呢?

+
    +
  • webpack处理css默认情况下会把css合并到js文件,这点很不爽
  • +
  • 如果要把css文件独立处理,则需要额外的配置,有点烦,所以果断用gulp了
  • +
+

执行webpack,看看效果,可以正常运行

+
1
2
3
4
5
6
7
8
9
{ webpackGulpDeom }  » webpack
Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1141ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
+ 5 hidden modules
{ webpackGulpDeom } »
+ +

配置gulp

gulpfile.js同样,gulp的配置文档详情参考官方文档,这里以编译less文档并压缩css文档为例

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'use strict';
var gulp = require('gulp'),
less = require('gulp-less');

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + 'css/*',['less'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less','watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify']);
+ +

ps:这里只列举了一个编译less的任务。

+

在gulp里执行webpack任务

到了这一步,gulp和webpack任务都编写完成了,如果单单是这样是没有意思的,因为每次启动都需要单独的执行两次命令:一次webpack,一次gulp命令,这样无疑是非常糟糕的。所以我们必须得想办法把gulp和webpack连接起来。怎么连接呢?具体的有两种办法:

+
    +
  • 一种是使用gulp-webpack插件。
  • +
  • 另一种是使用gulp-util插件。
  • +
+

那么我们来重写gulpfile.js和webpack.config.js吧

+

利用gulp-webpack插件

重写gulpfile.js

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
webpack = require("gulp-webpack"),
webpackConfig = require("./webpack.config.js");

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//用gulp执行webpack.config.js
gulp.task('buildJs', function () {
var myConfig = Object.create(webpackConfig);
return gulp
.src([entrySrc + 'js/page/*.js'])
.pipe(webpack(myConfig))
.pipe(gulp.dest('dist/js/page')); //出口文件目录,此处配置之后在webpack.config.js中就必须去掉,不然会报错
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

gulpfile.js的变化:

+
    +
  • 增加了可以一个buildJs任务来执行webpack.config.js文件的配置
  • +
  • 相应的监听对象扩大了
  • +
+

现在来重写webpack.config.js,注释掉output项中的path就行了

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
//path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

webpack.config.jsgulpfile.js都配置好了,那么现在只要执行gulp的相关命令就可以了

+
1
2
3
4
5
6
7
8
9
10
11
{ webpackGulpDeom }  » gulp
[14:20:43] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:20:43] Starting 'webpack'...
[14:20:45] Version: webpack 1.13.3
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
[14:20:45] Finished 'webpack' after 1.37 s
[14:20:45] Starting 'default'...
[14:20:45] Finished 'default' after 34 μs
{ webpackGulpDeom } »
+ +

利用gulp-util插件

+

这种方案只需要修改gulpfile.js就行了,webpack.config.js理论上来说不需要任何变化

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
gutil = require('gulp-util'),
webpackConfig = require("./webpack.config.js"),
myDevConfig = Object.create(webpackConfig),
devCompiler = webpack(myDevConfig);

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//引用webpack.config.js对js资源进行打包
gulp.task("buildJs", function(callback) {
devCompiler.run(function(err, stats) {
if(err) throw new gutil.PluginError("webpack:buildJs", err);
gutil.log("[webpack:buildJs]", stats.toString({
colors: true
}));
callback();
});
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

执行结果:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{ webpackGulpDeom }  » gulp
[14:28:59] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:28:59] Starting 'buildJs'...
[14:29:00] [webpack:buildJs] Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1232ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
chunk {0} app.js (app) 357 kB {1} [rendered]
[0] ./src/js/page/app.js 285 bytes {0} [built]
[1] ./src/js/common/jquery-1.9.1.min.js 92.6 kB {0} [built]
[2] (webpack)/buildin/amd-options.js 42 bytes {0} [built]
[3] ./~/vue/dist/vue.common.js 259 kB {0} [built]
[4] ./~/process/browser.js 5.3 kB {0} [built]
chunk {1} common.js (common.js) 0 bytes [rendered]
[14:29:00] Finished 'buildJs' after 1.24 s
[14:29:00] Starting 'default'...
[14:29:00] Finished 'default' after 7.21 μs
{ webpackGulpDeom } »
+ +

两种方案对比

从以上输出结果可以看出:

+

使用gulp-webpack

    +
  • gulpfile.jswebpack.config.js都要修改
  • +
  • 执行命令打印的信息更少
  • +
  • 编译时间更多?
  • +
+

使用gulp-util

    +
  • 只需要修改gulpfile.js,即使以后单独使用其中一个也不需要再做额外修改
  • +
  • 打印信息更丰富
  • +
  • 编译时间更短?
  • +
+

关于编译时间多少这块,我也没弄太清楚,如果有错误,请读者指出。就个人而言是比较喜欢第二种方案的

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016-11-22-Mobile-terminal-adapterundefined b/2016-11-22-Mobile-terminal-adapterundefined new file mode 100644 index 00000000..c5259d94 --- /dev/null +++ b/2016-11-22-Mobile-terminal-adapterundefined @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +移动端适配方案 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 移动端适配方案 +

+ + +
+ + + + +
+

移动端越来越被大众所接收,那么相应的技术就越来越向它靠拢,这是一种不可阻挡的趋势,也是万物发展的规律。移动端有三大难题:兼容、调试和适配。这三大问题就好像三座无法逾越的大山阻挡者我们前进的步伐,此文将记录我在项目中关于移动端适配的一些方式,供大家参考

+
+

分析

移动端适配的根本原因

+
    +
  • 屏幕窗口的大小
  • +
  • 设备像素比(devicepixelratio,简称dpr)
  • +
+

很多地方介绍设备像素比的,这里就不做具体探讨,简单总结一下:devicepixelratio(设备像素比,即dpr) = physicalpixel (物理像素) / density-independent pixel(设备独立像素,即dip)。dipdp,(device independent pixels,设备独立像素)与屏幕密度有关。dip可以用来辅助区分视网膜设备还是非视网膜设备。
JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr,但遗憾的是并不是所有的都支持。
在CSS中,可以分别针对屏幕大小和设备像素比做适配:
针对窗口大小,一般使用媒体查询的only screenmin-widthmax-width来适配,也是使用css做适配最常见的一种方式
针对像素比,可以使用-webkit-device-pixel-ratio-webkit-min-device-pixel-ratio-webkit-max-device-pixel-ratio属性,同样他的支持度一样不高(其实是很低),所以几乎没有用武之地。

+

适配方式

移动端适配主要有两大不同的方向:

+
    +
  • 响应式布局:它是根据屏幕大小自动的调整布局位置(非单纯的缩放),实现适配
  • +
  • 自适应布局:它是根据屏幕大小自动的缩放大小,实现适配。
  • +
+

两种方式应用的场景不同,各有优劣,本人对自适应布局使用的比较多

+

解决方案

为了解决这个老大难问题,从最初开始百分比到em,然后到现在rem的使用,都一一体现着技术的滚滚向前。目前是用的最多的也就是rem,他们的区别和有点请自行GG
有了rem这个大杀器,解决问题就变得简单起来,具体请往下看。

+

纯css实现方式 – 媒体查询

使用原生css来实现媒体查询是很繁琐的,因为每个媒体查询都要去设定规则。推荐使用css的预编译器(sass,less,stylus),比较方便。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* 定义规则 */
html {
font-size: 20px;
}
@media only screen and (min-width: 401px) {
html {
font-size: 24px !important;
}
}
@media only screen and (min-width: 428px) {
html {
font-size: 28 !important;
}
}
@media only screen and (min-width: 481px) {
html {
font-size: 30px !important;
}
}
@media only screen and (min-width: 569px) {
html {
font-size: 35px !important;
}
}
@media only screen and (min-width: 641px) {
html {
font-size: 40px !important;
}
}
@media only screen and (min-width: 751px) {
html {
font-size: 50px !important;
}
}
@media only screen and (min-width: 1080px) {
html {
font-size: 60px !important;
}
}
+ +
1
2
3
4
//less 方式调用
@unit: 50rem; //基准单位,根据设计稿来确定。假设:设计稿尺寸为750,那么@unit设置为50rem(1rem=50px更方便下面计算)
.warp{with: 100 / @unit} // 设计稿上元素的尺寸为100px => .warp{with: 2rem}
.warp{with: 10 / @unit} // 设计稿上元素的尺寸为10px => .warp{with: 0.2rem}
+ +

如果这里使用原生css来做,每个尺寸都需要去计算,如果使用预处理器,只需要定义一个变量,计算的事情直接交给它们就行。

+

这样当页面展示在750的屏幕上时,html的font-size50px,那么当设置为2rem的元素显示的尺寸就为2*50px=100px。在其他尺寸的设备也会根据媒体查询设置的不同font-size进行自动缩放适配。
当然上面也提到了,在css中也是可以获取到devicePixelRatio的值,那么为了更精确在写媒体查询的时候可以把它也加上去,这里就不展开了。

+

纯css实现方式 – 计算属性

当然除了媒体查询,还有一种更潮的方式就是利用css3的一些新属性:计算属性和vw属性来实现自动设置根字体大小的目的

+
1
html{font-size:calc(100vw/6.4)} //6.4为psd设计稿尺寸/100
+ +

这套方案几乎是目前最简洁的方案了,并且calcvw在移动端的支持也不错哟。

+

js的实现方式

js的实现方式,参考了网易淘宝的实现方式,对他们进行了整合。并且修复了手机端1px问题

+
    +
  • 网易实现方式是通过设备尺寸动态的设置DOM的根元素字体大小,没有考虑devicePixelRatio的因素;
  • +
  • 淘宝实现方式也是通过设备尺寸动态的设置DOM的根元素字体大小,并且考虑了devicePixelRatio的因素,但淘宝在设置rem时,显得较复杂(不方便写css把px转化成rem);
  • +
  • 1px问题简单点说就是因为devicePixelRatio的存在,css的1px不等于移动端的1px。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function(doc, win, designSize) { //designSize为设计稿的尺寸(宽)

var docEl = document.documentElement,
devWidth = docEl.clientWidth > 1080 ? 1080 : docEl.clientWidth,
dpr = devicePixelRatio || 1,
scale = 1 / dpr,
width = dpr * devWidth,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'onresize', //判断横屏和窗口重置
recalc = function() {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
document.querySelector('meta[name="viewport"]')
.setAttribute('content','width=' + width +
', initial-scale=' + scale +
', maximum-scale=' + scale +
', minimum-scale=' + scale +
', user-scalable=no');
docEl.style.fontSize = devWidth / (designSize / 100) * dpr + 'px';
};

if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);

})(document, window, 750);
+ +

总结

    +
  • 以上三种方案对比,第一种媒体查询是最死板的,基本就是纯体力活。
  • +
  • 利用css的计算属性可以很优雅的解决问题,但是在兼容方面来说,目前还不是很完美
  • +
  • 并且利用纯css也没考虑devicePixelRatio(像素问题)这个因素。
  • +
  • 最后的javascript解决方案则是考虑到了兼容和devicePixelRatio这些因素,但是这里有一个很大的弊端就是:页面在某些情况(性能慢)会经过两次重回(给HTML根设置font-size和设置meta标签时),在网络或者终端性能不是很好的情况用户体验很不错甚至页面错乱的情况。
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016-12-31-2016-to-2017undefined b/2016-12-31-2016-to-2017undefined new file mode 100644 index 00000000..10ed30cf --- /dev/null +++ b/2016-12-31-2016-to-2017undefined @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<2016/><2017> | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ <2016/><2017> +

+ + +
+ + + + +
+ +
+

与其在上一秒中沉醉,不如努力拥抱下一秒.

+
+

匆匆而去的2016

入行前端的第二年,也是毕业的第二年。

+

短短两载却有翻天覆地的改变:从大学走入社会,从设计跨行到前端。这其中有必然也有偶然。

+

年初

年初换了新东家,毕业后的第二份工作,有对自身能力的底气不足而提高的渴望,也有对新东家的满怀期望,一切在有条不紊的进行着。2016年2月22日入职,从此多了一个别号二月(2ue)。座位在老大旁边,感到一丝压力。

+

适应与压力

经历了最初的彷徨后,慢慢开始适应新的环境,作为新手的我,跟着大神们一起做项目是开心的,但也同时感受到更多的压力。因此在入职后的几个月进入了疯狂学习模式,逛技术社区,微博关注技术达人,微信关注相关公众号,啃书等等,连早上挤公交地铁的时候都抽时间在手机上学习,当时感觉自己已经疯魔。虽然也就仅仅疯狂了三个月,但这短暂的三个月疯狂却让我焕然一新:从最初的复制粘贴到有自己的思考、从无处可问到能独立解决问题、更加擅于利用网络解决问题、能够更快的阅读新的知识、更好的编码习惯…林林总总,这三个月是很重要的三个月。

+

生活

换了新房东,空间更大,也换了一台新电脑,更方便撸(打)代(游)码(戏)。总之一切还算满意。

+

同事

我其实是个不擅交际,怕生的人儿。因为我说话很笨,所以在新的环境想要和人混熟,总是要花更多的时间去处理这些关系。不过幸运的是遇到一群很好相处的同事,让我更容易的处理好这些问题。所以再后来遇到各种问题后我都是大胆的去问部门大神,充分发挥了死灿烂打的精神(这个三秒,市委应该深有体会)。当然作为一个胖纸,肯定也有一堆饭友,也会利用工作过的闲暇之余去腐败腐败。

+

项目与加班

新东家的项目总是要得比较急,这就常常导致加班和代码的低质量产出。这是我最开始的想法,但到后来发现,诚然这两者是有一定的因果关系,但是有些东西本身就不可力抗(比如项目交付时间节点),如果一直持有因为时间导致自己加班这种心态,而不去提高自身的能力,那么不论你换到哪家公司你都会陷入无尽的加班与抱怨中。加班不可避免,那就努力提高自己,减少加班时间吧。

+

技术浪潮

这一年是技术浪潮爆发的一年。大数据、AI、人工智能、无人驾驶…各种技术名词频频见诸于头条新闻。在这喷薄的大浪中,前端领域也爆发出自己的色彩:MVCMVVCstyluslesssassgruntgulpwebpackngreactvuenode、大前端开发….等等,技术的更新换代不断地推动着前端领域的向前发展。作为一个前端开发者,深深的感受到我大前端的魅力,也越发对技术存有敬畏之心,不断的驱使我追赶它的浪潮,我愿意在它的浪潮中沉浮。

+

我的Github

github是在2015年末注册的,期间一直不知怎么玩。在上半年,学习了git,喜欢使用命令行,也喜欢在github上提交一些东西,后面学习了vue后,慢慢的提交一些简单的demo上去,挺享受这个过程的。对了,我也通过github+hexo托管了一个静态博客,平时自己的写的东西也往上丢,不过貌似没坚持多久…..2017我会更加勤劳。

+

换了个显示器

这是一个不得不说的显示器:前端开发当然得用大的好的显示器,于是三秒写了一年的12Q换来一个帅气的显示器,从此以后心情舒畅,撸码不累,加班更勤。

+

进步与不足

踩着2016的尾巴,我细数了2016发生在我身上的变化,虽有些许进步,但却也说不出个所以然,如果非要说,那么就是自己解决问题的能力强了,眼界开阔了,有自己的思考了,目标清晰了。
相对于进步,不足之处就很明显了,有很多也是我的致命伤,更是职业道路前进的致命伤。

+
    +
  • 习惯
      +
    • 有点小拖拉
    • +
    • 三分钟热情
    • +
    • 不仔细
    • +
    +
  • +
  • 技术
      +
    • 基础不够
    • +
    • 没有创新
    • +
    • 阅读很不够
    • +
    • 至今没有代表作
    • +
    +
  • +
+

有不足,能正视不足,更需要我弥补不足。

+

滚滚而来的2017

2017年似乎要做的事情很多。一件件做,总有完成;一步步走,总有尽头。

+

TODO

    +
  • EMAC 2016-2017
  • +
  • VUE,REACT
  • +
  • NODE
  • +
  • 涨工资
  • +
+

又是新的一年开启,新年愿有一番好景。

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/06/14/animate-number.html b/2016/06/14/animate-number.html new file mode 100644 index 00000000..2efb33e2 --- /dev/null +++ b/2016/06/14/animate-number.html @@ -0,0 +1,499 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +实现数字滚动变化以及延伸 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 实现数字滚动变化以及延伸 +

+ + +
+ + + + +
+

利用jquery的插件jquery.animateNumber实现一个简单的数字滚动效果

+
+

需求分析

    +
  1. 处理数据:因为数据是后端提供,所以有可能格式不是我们想要的,所以也许需要格式化数据;
  2. +
  3. 根据页面设计的效果图(如图),需要把数字字符串拆分成单个数字字符串
    animateNumber_01
  4. +
  5. 每一个数字进行滚动变化
  6. +
  7. 最后,在项目中,我选取了插件jquery.animateNumber来实现滚动效果。这个插件的使用方式很简单,在官方有很详尽的文档来展示各个案例,就不一一赘述了。
  8. +
+

HTML布局

其中num是后台传入的值,notChangeUint用来标记不进行单位变换的值

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="warp">
<div class="net-credit-num" >
<p>平台累积会员人数(人)</p>
<span date-num="123" class="animateNumber notChangeUint"></span>
</div>
<div class="net-credit-money">
<p>平台完成投资金额(万元)</p>
<span date-num="91,123,456.00" class="animateNumber"></span>
</div>
<div class="net-return-money">
<p>累计已还款金额(万元)</p>
<span date-num="8,895,678.00" class="animateNumber"></span>
</div>
</div>
+ +

撸JS

去除逗号(,)

1
num = num.replace(',','');
+ +

上面这种方法只能去除字符串中的第一个逗号,但是实际数据中可能存在多个逗号,所以需要用到正则全局匹配替换,代码如下:

+
1
2
var reg = new RegExp(',','g');
num = num.replace(reg,'');
+ +

转化单位(元–>万元)

把金额单位转化为万元,并且保留两位小数,人数不进行转化

+
1
2
3
if(!numWarpParent.hasClass('notChangeUint')){
num = (Number(num) / 10000).toFixed(2);
}
+ +

字符串拆分为数组

1
numArry = num.split('');
+ +

把数字添加到页面并调用animateNumber的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
for(var i = 0; i < numArry.length; i++){
var thisNum = parseInt(numArry[i]);
var spanNum;
if (!isNaN(thisNum)){
spanNum = $('<span class="single-num">' + numArry[i] +'</span>');
}else{
spanNum = $('<span class="single-point">.</span>');
};
numWarpParent.append(spanNum);
thisNumWarp.prop('number', stratNum).animateNumber({
number: thisNum
}, time);
}
+ +

最后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$('.animateNumber').each(function(){
var _this = $(this);
var totalNum = _this.attr('date-num'); //后台数据储存在date-num上
appendNum(totalNum,_this);
});
function appendNum(num,numWarpParent){
var newNum;
var reg = new RegExp(',','g'); //正则匹配所有逗号
newNum = num.replace(reg,'');
if(isNaN(num)) newNum = 0; //容错,当后台传入的参数错误(非数字)时,将只值置为0,以保证页面的正常渲染
if(!numWarpParent.hasClass('notChangeUint')){ //判断是否需要转换单位
newNum = (Number(newNum) / 10000).toFixed(2);
};
numArry = newNum.split('');
for(var i = 0; i < numArry.length; i++){
var thisNum = numArry[i];
var numWarp;
if (!isNaN(thisNum)){ //判断是否可以转化为数字
numWarp = $('<label class="single-num">' + numArry[i] +'</label>');
}else{
numWarp = $('<label class="single-point">.</label>');
};
numWarpParent.append(numWarp);
isAnimate(thisNum,numWarpParent,i);
};
};
function isAnimate(num,numWarpParent,index){
if (isNaN(num))return;
//调用animate.js插件方法
numWarpParent.find('label').eq(index).prop('number', 0).animateNumber({
number: num
}, num * 100);
};
+ +

总结

1.功能模块化,尽量一个方法(函数)只做一件事情
2.容错,由于涉及到DOM操作,所以为了保证页面的正常渲染必须有容错处理机制:数据出错不影响整个流程(页面渲染)的畅通

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/06/14/animate-numberundefined b/2016/06/14/animate-numberundefined new file mode 100644 index 00000000..6de75c83 --- /dev/null +++ b/2016/06/14/animate-numberundefined @@ -0,0 +1,499 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +实现数字滚动变化以及延伸 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 实现数字滚动变化以及延伸 +

+ + +
+ + + + +
+

利用jquery的插件jquery.animateNumber实现一个简单的数字滚动效果

+
+

需求分析

    +
  1. 处理数据:因为数据是后端提供,所以有可能格式不是我们想要的,所以也许需要格式化数据;
  2. +
  3. 根据页面设计的效果图(如图),需要把数字字符串拆分成单个数字字符串
    animateNumber_01
  4. +
  5. 每一个数字进行滚动变化
  6. +
  7. 最后,在项目中,我选取了插件jquery.animateNumber来实现滚动效果。这个插件的使用方式很简单,在官方有很详尽的文档来展示各个案例,就不一一赘述了。
  8. +
+

HTML布局

其中num是后台传入的值,notChangeUint用来标记不进行单位变换的值

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="warp">
<div class="net-credit-num" >
<p>平台累积会员人数(人)</p>
<span date-num="123" class="animateNumber notChangeUint"></span>
</div>
<div class="net-credit-money">
<p>平台完成投资金额(万元)</p>
<span date-num="91,123,456.00" class="animateNumber"></span>
</div>
<div class="net-return-money">
<p>累计已还款金额(万元)</p>
<span date-num="8,895,678.00" class="animateNumber"></span>
</div>
</div>
+ +

撸JS

去除逗号(,)

1
num = num.replace(',','');
+ +

上面这种方法只能去除字符串中的第一个逗号,但是实际数据中可能存在多个逗号,所以需要用到正则全局匹配替换,代码如下:

+
1
2
var reg = new RegExp(',','g');
num = num.replace(reg,'');
+ +

转化单位(元–>万元)

把金额单位转化为万元,并且保留两位小数,人数不进行转化

+
1
2
3
if(!numWarpParent.hasClass('notChangeUint')){
num = (Number(num) / 10000).toFixed(2);
}
+ +

字符串拆分为数组

1
numArry = num.split('');
+ +

把数字添加到页面并调用animateNumber的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
for(var i = 0; i < numArry.length; i++){
var thisNum = parseInt(numArry[i]);
var spanNum;
if (!isNaN(thisNum)){
spanNum = $('<span class="single-num">' + numArry[i] +'</span>');
}else{
spanNum = $('<span class="single-point">.</span>');
};
numWarpParent.append(spanNum);
thisNumWarp.prop('number', stratNum).animateNumber({
number: thisNum
}, time);
}
+ +

最后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$('.animateNumber').each(function(){
var _this = $(this);
var totalNum = _this.attr('date-num'); //后台数据储存在date-num上
appendNum(totalNum,_this);
});
function appendNum(num,numWarpParent){
var newNum;
var reg = new RegExp(',','g'); //正则匹配所有逗号
newNum = num.replace(reg,'');
if(isNaN(num)) newNum = 0; //容错,当后台传入的参数错误(非数字)时,将只值置为0,以保证页面的正常渲染
if(!numWarpParent.hasClass('notChangeUint')){ //判断是否需要转换单位
newNum = (Number(newNum) / 10000).toFixed(2);
};
numArry = newNum.split('');
for(var i = 0; i < numArry.length; i++){
var thisNum = numArry[i];
var numWarp;
if (!isNaN(thisNum)){ //判断是否可以转化为数字
numWarp = $('<label class="single-num">' + numArry[i] +'</label>');
}else{
numWarp = $('<label class="single-point">.</label>');
};
numWarpParent.append(numWarp);
isAnimate(thisNum,numWarpParent,i);
};
};
function isAnimate(num,numWarpParent,index){
if (isNaN(num))return;
//调用animate.js插件方法
numWarpParent.find('label').eq(index).prop('number', 0).animateNumber({
number: num
}, num * 100);
};
+ +

总结

1.功能模块化,尽量一个方法(函数)只做一件事情
2.容错,由于涉及到DOM操作,所以为了保证页面的正常渲染必须有容错处理机制:数据出错不影响整个流程(页面渲染)的畅通

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/06/15/get-url-values.html b/2016/06/15/get-url-values.html new file mode 100644 index 00000000..98b57652 --- /dev/null +++ b/2016/06/15/get-url-values.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +利用javascrit获取url传递的参数 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 利用javascrit获取url传递的参数 +

+ + +
+ + + + +

神奇的url

一条url包含了很丰富的信息,那么我们如何来获取这些信息并有效的加以利用呢?
随便举个例子:https://github.com/search?utf8=%E2%9C%93&q=javascript
这条url就是在github上搜索javascript后跳转页面对应的url。我们要做的就是获取’?’后面的参数,以及获取后可以用来做什么。

+

获取参数

window.location的对象方法

window.location的参数

+

获取url所有的参数

我们可以直接通过window.location.search来取得这部分,也就是我们需要的url参数。

+

url没有包含?时,window.location.search会返回undefined

+
1
2
3
4
5
6
7
8
9
10
11
12
13
function getUrlVal(str){
if(!str || str.indexOf('?') != 0) return false;
var urlValArry = str.replace('?','').split('&');
var urlValObject = {};
for(var i in urlValArry){
urlValObject[urlValArry[i].split('=')[0]] = urlValArry[i].split('=')[1];
};
return urlValObject;
};

// https://github.com/search?utf8=%E2%9C%93&q=javascript
var urlStr = window.location.search.replace('?','');
console.log(getUrlVal(urlStr)); //输出 Object {utf8: "%E2%9C%93", q: "javascript"}
+ +

获取url中指定键名(name)的键值(val)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getOneVal(str,name){
if(!str || str.indexOf('?') != 0) return false;
var afterNameStr = str.replace('?','').split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
// 返回第一个&位置,如果没有'&'则返回字符串长度
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSite
var reslt = afterNameStr.slice(1,strFirstSite);
return reslt;
};

// 'http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614&place=N230&peopleNum=657'
var urlStr = window.location.search;
console.log(getOneVal(urlStr,'time')) //输出20160614
console.log(getOneVal(urlStr,'peopleNum')) //657
+ +

将方法绑定到原型链上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
String.prototype.toObj = function(key){
/*
如果有传入key,那么就只返回key对应的Val(找不到则返回undefined)
如果没有传入key,那么就返回一个object对象
*/
var str = this;
if(str.indexOf('?') != 0) return {};
if(str.indexOf(key) == -1) return undefined;
var tmpArry = str.replace('?','').split('&');
var reslt = {};
for(var i in tmpArry){
var tempKeyVal = tmpArry[i].split('=');
if(!!key) {
if(tempKeyVal[0] != key) reslt = undefined;
reslt = tempKeyVal[1];
break;
}else {
reslt[tempKeyVal[0]] = tempKeyVal[1];
}
};
return reslt;
};
// http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614
var urlStr = window.location.search;
console.log(urlStr.toObj()) //{itemtype:'sport',active:'basketball',time:'20160614'}
console.log(urlStr.toObj('active')) //basketball
+ +

利用正则表达式来获取参数

强大的正则总是让人心生向往,利用正则无疑是最简洁优雅的一种方法

+

获取指定某个参数

1
2
3
4
5
6
7
8
9
10
function getUrlParam(url,name){
if(!name) return;
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
r = url.substr(1).match(reg);
if (r != null) {
return (r[2]);
}else{
return null;
}
}
+ +

获取所有的参数

1
2
3
4
5
6
7
8
9
function parse_url(url){
if(!url) return;
var pattern = /(\w+)=(\w+)/ig;
var parames = {};
url.replace(pattern, function(a, b, c){
parames[b] = c;
});
return parames;
}
+ +

参数的利用

在项目中这些参数有哪些用处呢,下面列举几个比较常用的用处

+
    +
  • 传递数据
  • +
  • 导航定位
  • +
  • 更改状态
  • +
  • +
+

导航定位

什么是导航定位?就是点击导航栏的标签,页面跳转后,对应的标签相应的会突出变化。如下图:
navLocation

+

跳转后有两种情况:
一种ajax异步刷新,只是局部页面发生变化,因为可以直接用点击事件来控制。
另外一种比较常见的方式就是整个页面刷新,这种情况下,点击事件就没用了,就必须另辟蹊径:
1.比较传统的方法就是每个页面里面写一段CSS样式来控制
2.那么另外一种不用说就是通过url的参数来定位咯
假如用每个页面写CSS样式来控制,可以明显感受到的弊端是:每次新的页面都需要修改对应的CSS
那么利用url来控制又需要做哪些事呢?
1.首先需要约定参数,并且后台来传递这些参数
2.然后在导航栏部分,对应的地方加上参数值,这一步,导航栏都是公用模板,并且规则都一样,所以只需要一次添加
3.跳转后定位

+

html代码

1
2
3
4
5
6
<div class="nav">
<a href="/index.htm?nav=index">首页</a>
<a href="/layout/post.htm?nav=post">文章</a>
<a href="/layout/tag.htm?nav=tags">标签</a>
<a href="/layout/about.htm?nav=aboutUs">关于我</a>
</div>
+ +

js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getOneVal(name,urlValStr){
var afterNameStr = urlValStr.split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSit;
var val = afterNameStr.slice(1,strFirstSite);
return val;
};

var thisUrlVal = window.location.search.replace('?','');
var thisNVal = getOneVal('nav',urlValStr);

//定位
$('.nav a').each(function(){
var _this = $(this);
var urlValStr = _this.attr('href').split('?')[1];
var nVal = getOneVal('nav',urlValStr);
if(nVal == thisNVal) {
_this.addClass('on');
}
})
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/06/15/get-url-valuesundefined b/2016/06/15/get-url-valuesundefined new file mode 100644 index 00000000..4d7b691b --- /dev/null +++ b/2016/06/15/get-url-valuesundefined @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +利用javascrit获取url传递的参数 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 利用javascrit获取url传递的参数 +

+ + +
+ + + + +

神奇的url

一条url包含了很丰富的信息,那么我们如何来获取这些信息并有效的加以利用呢?
随便举个例子:https://github.com/search?utf8=%E2%9C%93&q=javascript
这条url就是在github上搜索javascript后跳转页面对应的url。我们要做的就是获取’?’后面的参数,以及获取后可以用来做什么。

+

获取参数

window.location的对象方法

window.location的参数

+

获取url所有的参数

我们可以直接通过window.location.search来取得这部分,也就是我们需要的url参数。

+

url没有包含?时,window.location.search会返回undefined

+
1
2
3
4
5
6
7
8
9
10
11
12
13
function getUrlVal(str){
if(!str || str.indexOf('?') != 0) return false;
var urlValArry = str.replace('?','').split('&');
var urlValObject = {};
for(var i in urlValArry){
urlValObject[urlValArry[i].split('=')[0]] = urlValArry[i].split('=')[1];
};
return urlValObject;
};

// https://github.com/search?utf8=%E2%9C%93&q=javascript
var urlStr = window.location.search.replace('?','');
console.log(getUrlVal(urlStr)); //输出 Object {utf8: "%E2%9C%93", q: "javascript"}
+ +

获取url中指定键名(name)的键值(val)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getOneVal(str,name){
if(!str || str.indexOf('?') != 0) return false;
var afterNameStr = str.replace('?','').split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
// 返回第一个&位置,如果没有'&'则返回字符串长度
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSite
var reslt = afterNameStr.slice(1,strFirstSite);
return reslt;
};

// 'http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614&place=N230&peopleNum=657'
var urlStr = window.location.search;
console.log(getOneVal(urlStr,'time')) //输出20160614
console.log(getOneVal(urlStr,'peopleNum')) //657
+ +

将方法绑定到原型链上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
String.prototype.toObj = function(key){
/*
如果有传入key,那么就只返回key对应的Val(找不到则返回undefined)
如果没有传入key,那么就返回一个object对象
*/
var str = this;
if(str.indexOf('?') != 0) return {};
if(str.indexOf(key) == -1) return undefined;
var tmpArry = str.replace('?','').split('&');
var reslt = {};
for(var i in tmpArry){
var tempKeyVal = tmpArry[i].split('=');
if(!!key) {
if(tempKeyVal[0] != key) reslt = undefined;
reslt = tempKeyVal[1];
break;
}else {
reslt[tempKeyVal[0]] = tempKeyVal[1];
}
};
return reslt;
};
// http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614
var urlStr = window.location.search;
console.log(urlStr.toObj()) //{itemtype:'sport',active:'basketball',time:'20160614'}
console.log(urlStr.toObj('active')) //basketball
+ +

利用正则表达式来获取参数

强大的正则总是让人心生向往,利用正则无疑是最简洁优雅的一种方法

+

获取指定某个参数

1
2
3
4
5
6
7
8
9
10
function getUrlParam(url,name){
if(!name) return;
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
r = url.substr(1).match(reg);
if (r != null) {
return (r[2]);
}else{
return null;
}
}
+ +

获取所有的参数

1
2
3
4
5
6
7
8
9
function parse_url(url){
if(!url) return;
var pattern = /(\w+)=(\w+)/ig;
var parames = {};
url.replace(pattern, function(a, b, c){
parames[b] = c;
});
return parames;
}
+ +

参数的利用

在项目中这些参数有哪些用处呢,下面列举几个比较常用的用处

+
    +
  • 传递数据
  • +
  • 导航定位
  • +
  • 更改状态
  • +
  • +
+

导航定位

什么是导航定位?就是点击导航栏的标签,页面跳转后,对应的标签相应的会突出变化。如下图:
navLocation

+

跳转后有两种情况:
一种ajax异步刷新,只是局部页面发生变化,因为可以直接用点击事件来控制。
另外一种比较常见的方式就是整个页面刷新,这种情况下,点击事件就没用了,就必须另辟蹊径:
1.比较传统的方法就是每个页面里面写一段CSS样式来控制
2.那么另外一种不用说就是通过url的参数来定位咯
假如用每个页面写CSS样式来控制,可以明显感受到的弊端是:每次新的页面都需要修改对应的CSS
那么利用url来控制又需要做哪些事呢?
1.首先需要约定参数,并且后台来传递这些参数
2.然后在导航栏部分,对应的地方加上参数值,这一步,导航栏都是公用模板,并且规则都一样,所以只需要一次添加
3.跳转后定位

+

html代码

1
2
3
4
5
6
<div class="nav">
<a href="/index.htm?nav=index">首页</a>
<a href="/layout/post.htm?nav=post">文章</a>
<a href="/layout/tag.htm?nav=tags">标签</a>
<a href="/layout/about.htm?nav=aboutUs">关于我</a>
</div>
+ +

js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getOneVal(name,urlValStr){
var afterNameStr = urlValStr.split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSit;
var val = afterNameStr.slice(1,strFirstSite);
return val;
};

var thisUrlVal = window.location.search.replace('?','');
var thisNVal = getOneVal('nav',urlValStr);

//定位
$('.nav a').each(function(){
var _this = $(this);
var urlValStr = _this.attr('href').split('?')[1];
var nVal = getOneVal('nav',urlValStr);
if(nVal == thisNVal) {
_this.addClass('on');
}
})
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/06/20/learn-git-1.html b/2016/06/20/learn-git-1.html new file mode 100644 index 00000000..ca54023d --- /dev/null +++ b/2016/06/20/learn-git-1.html @@ -0,0 +1,584 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列之新手入门 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列之新手入门 +

+ + +
+ + + + +
+

工欲善其事,必先利其器。git一个不可或缺的利器,其魅力值得我们慢慢品尝!

+
+

安装

官网下载最新版本安装,然后查看是否安装成功

+
1
2
$ git -v
git version 2.7.3.windows.1 //2.7.3为当前版本
+ +

全局个人信息配置

1
2
$ git config --global user.name "username"
$ git config --global user.email "email"
+ +

windows环境也可以打开计算机用户文件夹下的.gitconfig编辑

+
1
2
3
[user]
name = username
email = email
+ +

基本命令

初始化本地仓库

1
2
3
$ cd storage
$ git init //初始化当前目录为本地仓库
Initialized empty Git repository in D:/storages/.git/ //初始化了一个空的目录为本地仓库
+ +

第一次提交(提交到本地)

1
2
$ git add README.md
$ git commit -m "添加项目文档简介" //-m 参数后面跟表示对当前提交的一个简单说明
+ +

查看状态

查看当前文件处于何种状态

+
1
$ git status
+ +

回退

当你addcommit了错误的文件时,可以使用以下命令来撤回add或者commit;
那么你必须首先明白工作区和暂存区是什么?
直白的说.git文件夹所在的目录(即git init初始化的目录)为当前工作区
add之后,add的文件就会进入暂存区
commit之后,暂存区就会清空,commit的文件就会进入本地当前的分支(如master分支)
PS:关于工作区和暂存区更详细的解释可以去看廖雪峰工作区和暂存区

+

丢弃工区的修改

命令git checkout -- filename可以把filename文件在工作区的修改全部撤销

+
1
$ git checkout -- README.md
+ +

撤销暂存区的修改

命令git reset HEAD filename可以把filename文件在暂存区的修改全部撤销

+
1
2
3
4
5
6
$ git reset HEAD README.md
$ git checkout -- README.md //--参数不能省
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
+ +

当然reset 命令,不仅能够撤销暂存区的内容,还能回退版本,即使你错误提交,也可以用reset回滚到之前的版本
如果你提交了错误文件,可以使用以下命令来回退;

+
1
$ git reset --hard HEAD^
+ +

git reset --hard HEAD^表示回退到上一个版本,HEAD后面的参数可以跟commit ID,这个ID可以通过以下命令获得

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git log
commit 97c8460bc1cda8233866686d9cae270e0e0113f1
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 20 15:44:01 2016 +0800

update README.md

.....(中间有10条日志)

commit 2d936223341333384dd41533dd44ba8e0640493c
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 01 13:16:22 2016 +0800

README.md
+ +

commit 97c8460bc1cda8233866686d9cae270e0e0113f1,这一长串就是我们所需的ID,一般情况下,只需要前7位就够了。

+
1
2
$ git reset --hard HEAD 2d93622 //回退到commit 2d936223341333384dd41533dd44ba8e0640493c
$ git reset --hard HEAD~12 //回退到往上第12个版本,也是2d93622这个版本
+ +

提交到服务器

commit后,你做出的修改只是提交到了暂存区,下一步就需要把代码提交到服务器。这里的服务器,可以自己搭建,也可以是公司的,或者第三方的。这里我们将代码提交到github上(假设你是已经有一个github账户,并创建了一个仓库)。

+

本地生成SSH KEY

SSH KEY就是连通本地与github的桥梁,钥匙,使用以下命令来生成:

+
1
$ ssh-keygen -t rsa -C "youremail@example.com"
+ +

然后回车:

+
    +
  • 提示设置密码
      +
    • 如果不设置,直接按回车,然后会提示你确认密码,再按一次回车
    • +
    • 如果设置密码,输入你想设置的密码,并确认,这样以后每次提交需要输入密码
    • +
    +
  • +
  • 提示生成key的文件名
      +
    • 如果不修改,则使用默认文件名id_rsa
    • +
    • 如果你有在该电脑管理多个key或者已经有生成的key占用了该文件名,则需要重命令
    • +
    +
  • +
+

这里简单起见,先不设置密码,并保持文件名默认

+

然后在用户主目录(例如如果是windows系统一般就在:C:\Users\Administrator)找到.ssh,其中id_rsa表示私钥不能泄露和id_rsa.pub表示公钥,用于对外。

+

github设置本地公钥

打开id_rsa.pub,复制里面的全部内容;
进入github账户;
找到 setting
打开SSH KEYS新建一个SSH KEY,名字随便取,然后粘贴id_rsa.pub的内容,保存;

+

测试是否连通

理论上讲,上一步操作无特殊错误,就已经和github连通的,为了放心,我们可以测试一下

+
1
2
3
$ ssh -T git@github.com
Enter passphrase for key '/c/Users/Administrator/.ssh/id_rsa': //如果设置了密码,此处将会提示你输入密码
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+

上面就是成功的提示

+

github仓库和本地仓库关联

有两种方式把本地仓库和github仓库关联起来

+

方法一:clone自己的仓库,简单,并且本地文件目录和github上名字一样

登陆账号,新建仓库(new repository);
进入仓库主页,找到按钮clone or download按钮,复制里面的git@github.com:userName/repositoryName.git;
打开git bash,clone;

+
1
$ git clone git@github.com:userName/repositoryName.git
+ +

就会把这个项目克隆到你当前目录下。此时你就可以顺畅的像github推送你的东西了。

+

方法二:利用命令关联仓库,本地仓库名可以和github的仓库名不一样

假设你github上有一个项目,git@github.com:userName/repositoryName.git

+
1
2
3
4
5
6
$ mkdir testRepository  //新建目录testRepository
$ cd testRepository //切换到这个目录
$ git init //初始化当前目录
$ git remote add origin git@github.com:userName/repositoryName.git
// origin为本地暂存区的名字,为了语义化,建议默认为它
// 注意github上的repositoryName与本地的testRepository可以不一样
+ +

提交(push)

第一次提交

1
$ git push -u origin master
+ +

第二次及以后

1
$ git push origin master
+ +

第一次提交带参数-u是为了让你本地和github的仓库保持同步。

+

更新代码(pull)

当我们在一台设备上对github提交(push)代码之后,在另外的设备上修改这个项目时就必须先从github更新代码,以保持代码的同步

+
1
$ git pull
+ +
+

总结

至此一个比较完整的流程走通了,当然在这些过程中也许还会遇到其它的问题,如,代码冲突,分支,分支合并等等。

github上创建一个新的项目
github repositoryName:testGit
github userName:2ue

+
1
2
3
4
5
6
7
8
9
10
$ makdir testGit   //创建一个空文件夹testGit(名字任意取),做仓库
$ cd testGit //切换到目录testGit
$ git init //初始化testGit为本地仓库
$ echo 'this repository is localhost' > README.md
//创建文件README.md,并写入'this repository is localhost'
//在window下以上操作的第一步和第四步可以在图形化界面中完成
$ git add README.md
$ git commit -m 'add README.md'//提交更新,并注释信息“add README.md”
$ git remote add origin git@github.com:2ue/testGit.git //关联github上的项目
$ git push -u origin master //提交到github
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/06/20/learn-git-1undefined b/2016/06/20/learn-git-1undefined new file mode 100644 index 00000000..9521c755 --- /dev/null +++ b/2016/06/20/learn-git-1undefined @@ -0,0 +1,584 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列之新手入门 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列之新手入门 +

+ + +
+ + + + +
+

工欲善其事,必先利其器。git一个不可或缺的利器,其魅力值得我们慢慢品尝!

+
+

安装

官网下载最新版本安装,然后查看是否安装成功

+
1
2
$ git -v
git version 2.7.3.windows.1 //2.7.3为当前版本
+ +

全局个人信息配置

1
2
$ git config --global user.name "username"
$ git config --global user.email "email"
+ +

windows环境也可以打开计算机用户文件夹下的.gitconfig编辑

+
1
2
3
[user]
name = username
email = email
+ +

基本命令

初始化本地仓库

1
2
3
$ cd storage
$ git init //初始化当前目录为本地仓库
Initialized empty Git repository in D:/storages/.git/ //初始化了一个空的目录为本地仓库
+ +

第一次提交(提交到本地)

1
2
$ git add README.md
$ git commit -m "添加项目文档简介" //-m 参数后面跟表示对当前提交的一个简单说明
+ +

查看状态

查看当前文件处于何种状态

+
1
$ git status
+ +

回退

当你addcommit了错误的文件时,可以使用以下命令来撤回add或者commit;
那么你必须首先明白工作区和暂存区是什么?
直白的说.git文件夹所在的目录(即git init初始化的目录)为当前工作区
add之后,add的文件就会进入暂存区
commit之后,暂存区就会清空,commit的文件就会进入本地当前的分支(如master分支)
PS:关于工作区和暂存区更详细的解释可以去看廖雪峰工作区和暂存区

+

丢弃工区的修改

命令git checkout -- filename可以把filename文件在工作区的修改全部撤销

+
1
$ git checkout -- README.md
+ +

撤销暂存区的修改

命令git reset HEAD filename可以把filename文件在暂存区的修改全部撤销

+
1
2
3
4
5
6
$ git reset HEAD README.md
$ git checkout -- README.md //--参数不能省
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
+ +

当然reset 命令,不仅能够撤销暂存区的内容,还能回退版本,即使你错误提交,也可以用reset回滚到之前的版本
如果你提交了错误文件,可以使用以下命令来回退;

+
1
$ git reset --hard HEAD^
+ +

git reset --hard HEAD^表示回退到上一个版本,HEAD后面的参数可以跟commit ID,这个ID可以通过以下命令获得

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git log
commit 97c8460bc1cda8233866686d9cae270e0e0113f1
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 20 15:44:01 2016 +0800

update README.md

.....(中间有10条日志)

commit 2d936223341333384dd41533dd44ba8e0640493c
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 01 13:16:22 2016 +0800

README.md
+ +

commit 97c8460bc1cda8233866686d9cae270e0e0113f1,这一长串就是我们所需的ID,一般情况下,只需要前7位就够了。

+
1
2
$ git reset --hard HEAD 2d93622 //回退到commit 2d936223341333384dd41533dd44ba8e0640493c
$ git reset --hard HEAD~12 //回退到往上第12个版本,也是2d93622这个版本
+ +

提交到服务器

commit后,你做出的修改只是提交到了暂存区,下一步就需要把代码提交到服务器。这里的服务器,可以自己搭建,也可以是公司的,或者第三方的。这里我们将代码提交到github上(假设你是已经有一个github账户,并创建了一个仓库)。

+

本地生成SSH KEY

SSH KEY就是连通本地与github的桥梁,钥匙,使用以下命令来生成:

+
1
$ ssh-keygen -t rsa -C "youremail@example.com"
+ +

然后回车:

+
    +
  • 提示设置密码
      +
    • 如果不设置,直接按回车,然后会提示你确认密码,再按一次回车
    • +
    • 如果设置密码,输入你想设置的密码,并确认,这样以后每次提交需要输入密码
    • +
    +
  • +
  • 提示生成key的文件名
      +
    • 如果不修改,则使用默认文件名id_rsa
    • +
    • 如果你有在该电脑管理多个key或者已经有生成的key占用了该文件名,则需要重命令
    • +
    +
  • +
+

这里简单起见,先不设置密码,并保持文件名默认

+

然后在用户主目录(例如如果是windows系统一般就在:C:\Users\Administrator)找到.ssh,其中id_rsa表示私钥不能泄露和id_rsa.pub表示公钥,用于对外。

+

github设置本地公钥

打开id_rsa.pub,复制里面的全部内容;
进入github账户;
找到 setting
打开SSH KEYS新建一个SSH KEY,名字随便取,然后粘贴id_rsa.pub的内容,保存;

+

测试是否连通

理论上讲,上一步操作无特殊错误,就已经和github连通的,为了放心,我们可以测试一下

+
1
2
3
$ ssh -T git@github.com
Enter passphrase for key '/c/Users/Administrator/.ssh/id_rsa': //如果设置了密码,此处将会提示你输入密码
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+

上面就是成功的提示

+

github仓库和本地仓库关联

有两种方式把本地仓库和github仓库关联起来

+

方法一:clone自己的仓库,简单,并且本地文件目录和github上名字一样

登陆账号,新建仓库(new repository);
进入仓库主页,找到按钮clone or download按钮,复制里面的git@github.com:userName/repositoryName.git;
打开git bash,clone;

+
1
$ git clone git@github.com:userName/repositoryName.git
+ +

就会把这个项目克隆到你当前目录下。此时你就可以顺畅的像github推送你的东西了。

+

方法二:利用命令关联仓库,本地仓库名可以和github的仓库名不一样

假设你github上有一个项目,git@github.com:userName/repositoryName.git

+
1
2
3
4
5
6
$ mkdir testRepository  //新建目录testRepository
$ cd testRepository //切换到这个目录
$ git init //初始化当前目录
$ git remote add origin git@github.com:userName/repositoryName.git
// origin为本地暂存区的名字,为了语义化,建议默认为它
// 注意github上的repositoryName与本地的testRepository可以不一样
+ +

提交(push)

第一次提交

1
$ git push -u origin master
+ +

第二次及以后

1
$ git push origin master
+ +

第一次提交带参数-u是为了让你本地和github的仓库保持同步。

+

更新代码(pull)

当我们在一台设备上对github提交(push)代码之后,在另外的设备上修改这个项目时就必须先从github更新代码,以保持代码的同步

+
1
$ git pull
+ +
+

总结

至此一个比较完整的流程走通了,当然在这些过程中也许还会遇到其它的问题,如,代码冲突,分支,分支合并等等。

github上创建一个新的项目
github repositoryName:testGit
github userName:2ue

+
1
2
3
4
5
6
7
8
9
10
$ makdir testGit   //创建一个空文件夹testGit(名字任意取),做仓库
$ cd testGit //切换到目录testGit
$ git init //初始化testGit为本地仓库
$ echo 'this repository is localhost' > README.md
//创建文件README.md,并写入'this repository is localhost'
//在window下以上操作的第一步和第四步可以在图形化界面中完成
$ git add README.md
$ git commit -m 'add README.md'//提交更新,并注释信息“add README.md”
$ git remote add origin git@github.com:2ue/testGit.git //关联github上的项目
$ git push -u origin master //提交到github
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/08/11/javascript-array-method.html b/2016/08/11/javascript-array-method.html new file mode 100644 index 00000000..9d489e55 --- /dev/null +++ b/2016/08/11/javascript-array-method.html @@ -0,0 +1,696 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Javascript系列 - Javascript数组方法 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Javascript系列 - Javascript数组方法 +

+ + +
+ + + + +
+

Javascript的Array(数组对象)方法整理,对比他们的功能,返回值,分析他们的参数,以及具体的作用。

+
+

length:获取数组长度

+
    +
  • Method: Arry.length
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: 无
  • +
  • Return: 返回被引用数组长度
  • +
+
+
1
2
var arry = [1,6,8,'2ue','o90']
arry.length //返回5,arry = [1,6,8,'2ue','o90']
+ +

join:连接数组内各元素组成一个字符串

+
    +
  • Method: Arry.join(str)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: str非必需
      +
    • str不存在时(不传递str)以默认逗号连接元素
    • +
    • str可以为任意字符串,也可以为空(‘’)(字符串为空时,各元素之间无连接符号)
    • +
    +
  • +
  • Return: 返回连接后的字符串
  • +
+
+
1
2
3
4
var arry = [1,6,8,'2ue','o90']
arry.join() //返回字符串1,6,8,2ue,o90,arry = [1,6,8,'2ue','o90']
arry.join('-') //返回字符串1-6-8-2ue-o90,arry = [1,6,8,'2ue','o90']
arry.join('') //返回字符串1682ueo90,arry = [1,6,8,'2ue','o90']
+ +

注意如果需要加数组arry以逗号形式展示到页面,则不需要.join()方法,因为javascript的赋值操作会自动调用.toString()方法,如

+
1
2
3
4
5
6
7
//JS
var arry = [1,6,8,'2ue','o90']
var divBox = document.getElementById('div');
divBox.innerHTML = arry;

//前面赋值的操作将会调用toString方法,因此
console.log(divBox) //<div id="div">1,6,8,2ue,o90</div>
+ +

push:在数组尾部添加一个元素

+
    +
  • Method: Arry.push(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意义
    • +
    • value可以为合法的布尔值,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中,也可以接收多个参数
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arry = [1,6,8,'2ue','o90']
arry.push() //返回5,arry = [1,6,8,'2ue','o90'] 实际没有任何意义
arry.push(true) //返回6,arry = [1,6,8,'2ue','o90',true]
arry.push('dmw') //返回7,arry = [1,6,8,'2ue','o90',true,'dmw']
arry.push('') //返回8,arry = [1,6,8,'2ue','o90',true,'dmw','']
arry.push(3) //返回9,arry = [1,6,8,'2ue','o90',true,'dmw','',3]
arry.push(undefined) //返回10,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined]
arry.push(null) //返回11,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null]
arry.push(['9','8']) //返回12,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8']]
arry.push({key:'hah'}) //返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
//接收多个参数
var arry = [1,6,8,'2ue','o90']
arry.push(true,'dmw','',3,undefined,null,['9','8'],{key:'hah'})
//返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
+ +

unshift:在数组尾部添加一个元素

+
    +
  • Method: Arry.unshift(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中也可以接收多个参数。
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+

.push()方法

+

concat:在尾部添加元素到数组

+
    +
  • Method: Arry.concat(value,…)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回组成的新数组,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,也可以接收多个参数。
    • +
    • 其中当value为数组时,那么添加的是数组中的元素,而不是数组,所以可以用.concat()来连接数组
    • +
    +
  • +
  • Return: 返回组成的新数组
  • +
+
+
1
2
3
4
5
6
7
//返回值为一个新的数组,不改变原数组
//参数为数组时
var arry = [1,6,8,'2ue','o90']
var newArry = arry.concat(['lalal','mof'])
//返回值 newArry = [1,6,8,'2ue','o90','lalal','mof']
//原数组 arry = [1,6,8,'2ue','o90']
//其它情况同push方法一致
+ +

pop:删除最后一个元素

+
    +
  • Method: Arry.pop()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(最后一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+
1
2
3
var arry = [1]
arry.pop() //返回1,arry = []
arry.pop() //返回undefined,arry = []
+ +

shift:删除第一个元素

+
    +
  • Method: Arry.shift()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(第一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+

.pop()

+

reverse:颠倒数组元素顺序

+
    +
  • Method: Arry.reverse()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
1
2
var arry = [1,6,8,'2ue','o90']
arry.reverse() //返回['o90','2ue',8,6,1] arry = ['o90','2ue',8,6,1]
+ +

sort:数组元素排序

+
    +
  • Method: Arry.sort(fun)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: fun非必需
      +
    • fun如果为空,那么默认安装字符编码的顺序进行排序
    • +
    • 如不为空,那么fun必须为函数类型
    • +
    • Arry.sort(fun(value1,value2){}),fun函数参数value1 的值为Arry[i]value2 的值为Arry[i+1],其中0< = i < Arry.length - 1。所以请注意,.sort()方法排序会对被引用数组进行遍历,遍历的次数为Arry.length - 1,而非Arry.length。因为在Arry.length - 1次时,排序其实就已经完成了。
    • +
    +
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
1
2
3
4
5
6
7
var arry = [1,'2ue','o90',6,890,9,7990]
arry.sort()//返回[1, "2ue", 6, 7990, 890, 9, "o90"] arry = [1, "2ue", 6, 7990, 890, 9, "o90"]
var arry = [1,65443,6,890,9,7990]
arry.sort(function(value1,value2){
return value2-value1
})
//返回[65443, 7990, 890, 9, 6, 1] arry = [65443, 7990, 890, 9, 6, 1]
+ +

slice:根据索引返回数组的一部分

+
    +
  • Method: Arry.slice(satrtIndex,endIndex)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments:
      +
    • satrtIndex开始索引(不包含开始索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • endIndex结束索引(不包含结束索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • 最终satrtIndex的实际值必须小于endIndex,且他们所在的那段索引必须与被引用数组的索引有交集,否则返回空数组。
    • +
    +
  • +
  • Return: 根据索引返回数组的一部分,返回值为数组类型
  • +
+
+
1
2
3
4
5
6
7
8
9
//被引用数组值不会改变
var arry = [1,6,8,'2ue','o90']
arry.slice(1,3) //返回[6,8,'2ue']
arry.slice(3,1) //返回[]
arry.slice(-1,2) //返回[]
arry.slice(1,-2) //返回[6,8]
arry.slice(1,-4) //返回[]
arry.slice(-3,-1) //返回[6,8,'2ue']
arry.slice(-1,-3) //返回[]
+ +

splice:移除元素

+
    +
  • Method: Arry.splice(satrtIndex, deleteCount, value, …)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments:
      +
    • satrtIndex开始索引,必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • deleteCount将删除的个数,非必须,且必须为nubmer类型。从start开始,包括start所指的元素在内要删除的元素个数。这个参数是可选的,如果没有指定它,splice()将删除从start开始到原数组结尾的所有元素,小于等于0将不会删除。
    • +
    • value要插入数组的零个或多个值,从start所指的下标处开始插入。可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,可接收多个参数。
    • +
    +
  • +
  • Return: 被移除元素组成的数组
  • +
+
+
1
2
3
4
5
var arry = [1,6,8,'2ue','o90',4,5,6,7]
arry.splice(7) // 返回 [6,7]; arry = [1,6,8,'2ue','o90',4,5]
arry.splice(1,2) // 返回 [6,8]; arry = [1,'2ue','o90',4,5]
arry.splice(-1,1) //返回 [5]; arry = [1,'o90',4]
arry.splice(0,0,2,3,[8,9]) // 返回 []; arry = [2,3,[8,9],1,'o90',4]
+ +

来一张表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法名功能原数组是否改变返回
length获取数组长度NO被引用数组长度
join将数组元素连接起来以构建一个字符串NO转换后的字符串
push在尾部添加元素YES新数组长度
unshift在头部添加元素YES新数组长度
concat在尾部添加元素NO新数组
pop删除最后一个元素YES被删除元素
shift删除第一个元素YES被删除元素
reverse颠倒数组元素顺序YES对数组的引用
sort数组元素排序YES对数组的引用
slice根据索引返回数组的一部分NO根据索引返回数组的一部分
splice插入、删除或替换数组的元素YES被移除元素组成的数组
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/08/11/javascript-array-methodundefined b/2016/08/11/javascript-array-methodundefined new file mode 100644 index 00000000..b3b82b15 --- /dev/null +++ b/2016/08/11/javascript-array-methodundefined @@ -0,0 +1,696 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Javascript系列 - Javascript数组方法 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Javascript系列 - Javascript数组方法 +

+ + +
+ + + + +
+

Javascript的Array(数组对象)方法整理,对比他们的功能,返回值,分析他们的参数,以及具体的作用。

+
+

length:获取数组长度

+
    +
  • Method: Arry.length
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: 无
  • +
  • Return: 返回被引用数组长度
  • +
+
+
1
2
var arry = [1,6,8,'2ue','o90']
arry.length //返回5,arry = [1,6,8,'2ue','o90']
+ +

join:连接数组内各元素组成一个字符串

+
    +
  • Method: Arry.join(str)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: str非必需
      +
    • str不存在时(不传递str)以默认逗号连接元素
    • +
    • str可以为任意字符串,也可以为空(‘’)(字符串为空时,各元素之间无连接符号)
    • +
    +
  • +
  • Return: 返回连接后的字符串
  • +
+
+
1
2
3
4
var arry = [1,6,8,'2ue','o90']
arry.join() //返回字符串1,6,8,2ue,o90,arry = [1,6,8,'2ue','o90']
arry.join('-') //返回字符串1-6-8-2ue-o90,arry = [1,6,8,'2ue','o90']
arry.join('') //返回字符串1682ueo90,arry = [1,6,8,'2ue','o90']
+ +

注意如果需要加数组arry以逗号形式展示到页面,则不需要.join()方法,因为javascript的赋值操作会自动调用.toString()方法,如

+
1
2
3
4
5
6
7
//JS
var arry = [1,6,8,'2ue','o90']
var divBox = document.getElementById('div');
divBox.innerHTML = arry;

//前面赋值的操作将会调用toString方法,因此
console.log(divBox) //<div id="div">1,6,8,2ue,o90</div>
+ +

push:在数组尾部添加一个元素

+
    +
  • Method: Arry.push(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意义
    • +
    • value可以为合法的布尔值,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中,也可以接收多个参数
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arry = [1,6,8,'2ue','o90']
arry.push() //返回5,arry = [1,6,8,'2ue','o90'] 实际没有任何意义
arry.push(true) //返回6,arry = [1,6,8,'2ue','o90',true]
arry.push('dmw') //返回7,arry = [1,6,8,'2ue','o90',true,'dmw']
arry.push('') //返回8,arry = [1,6,8,'2ue','o90',true,'dmw','']
arry.push(3) //返回9,arry = [1,6,8,'2ue','o90',true,'dmw','',3]
arry.push(undefined) //返回10,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined]
arry.push(null) //返回11,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null]
arry.push(['9','8']) //返回12,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8']]
arry.push({key:'hah'}) //返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
//接收多个参数
var arry = [1,6,8,'2ue','o90']
arry.push(true,'dmw','',3,undefined,null,['9','8'],{key:'hah'})
//返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
+ +

unshift:在数组尾部添加一个元素

+
    +
  • Method: Arry.unshift(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中也可以接收多个参数。
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+

.push()方法

+

concat:在尾部添加元素到数组

+
    +
  • Method: Arry.concat(value,…)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回组成的新数组,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,也可以接收多个参数。
    • +
    • 其中当value为数组时,那么添加的是数组中的元素,而不是数组,所以可以用.concat()来连接数组
    • +
    +
  • +
  • Return: 返回组成的新数组
  • +
+
+
1
2
3
4
5
6
7
//返回值为一个新的数组,不改变原数组
//参数为数组时
var arry = [1,6,8,'2ue','o90']
var newArry = arry.concat(['lalal','mof'])
//返回值 newArry = [1,6,8,'2ue','o90','lalal','mof']
//原数组 arry = [1,6,8,'2ue','o90']
//其它情况同push方法一致
+ +

pop:删除最后一个元素

+
    +
  • Method: Arry.pop()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(最后一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+
1
2
3
var arry = [1]
arry.pop() //返回1,arry = []
arry.pop() //返回undefined,arry = []
+ +

shift:删除第一个元素

+
    +
  • Method: Arry.shift()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(第一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+

.pop()

+

reverse:颠倒数组元素顺序

+
    +
  • Method: Arry.reverse()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
1
2
var arry = [1,6,8,'2ue','o90']
arry.reverse() //返回['o90','2ue',8,6,1] arry = ['o90','2ue',8,6,1]
+ +

sort:数组元素排序

+
    +
  • Method: Arry.sort(fun)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: fun非必需
      +
    • fun如果为空,那么默认安装字符编码的顺序进行排序
    • +
    • 如不为空,那么fun必须为函数类型
    • +
    • Arry.sort(fun(value1,value2){}),fun函数参数value1 的值为Arry[i]value2 的值为Arry[i+1],其中0< = i < Arry.length - 1。所以请注意,.sort()方法排序会对被引用数组进行遍历,遍历的次数为Arry.length - 1,而非Arry.length。因为在Arry.length - 1次时,排序其实就已经完成了。
    • +
    +
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
1
2
3
4
5
6
7
var arry = [1,'2ue','o90',6,890,9,7990]
arry.sort()//返回[1, "2ue", 6, 7990, 890, 9, "o90"] arry = [1, "2ue", 6, 7990, 890, 9, "o90"]
var arry = [1,65443,6,890,9,7990]
arry.sort(function(value1,value2){
return value2-value1
})
//返回[65443, 7990, 890, 9, 6, 1] arry = [65443, 7990, 890, 9, 6, 1]
+ +

slice:根据索引返回数组的一部分

+
    +
  • Method: Arry.slice(satrtIndex,endIndex)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments:
      +
    • satrtIndex开始索引(不包含开始索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • endIndex结束索引(不包含结束索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • 最终satrtIndex的实际值必须小于endIndex,且他们所在的那段索引必须与被引用数组的索引有交集,否则返回空数组。
    • +
    +
  • +
  • Return: 根据索引返回数组的一部分,返回值为数组类型
  • +
+
+
1
2
3
4
5
6
7
8
9
//被引用数组值不会改变
var arry = [1,6,8,'2ue','o90']
arry.slice(1,3) //返回[6,8,'2ue']
arry.slice(3,1) //返回[]
arry.slice(-1,2) //返回[]
arry.slice(1,-2) //返回[6,8]
arry.slice(1,-4) //返回[]
arry.slice(-3,-1) //返回[6,8,'2ue']
arry.slice(-1,-3) //返回[]
+ +

splice:移除元素

+
    +
  • Method: Arry.splice(satrtIndex, deleteCount, value, …)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments:
      +
    • satrtIndex开始索引,必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • deleteCount将删除的个数,非必须,且必须为nubmer类型。从start开始,包括start所指的元素在内要删除的元素个数。这个参数是可选的,如果没有指定它,splice()将删除从start开始到原数组结尾的所有元素,小于等于0将不会删除。
    • +
    • value要插入数组的零个或多个值,从start所指的下标处开始插入。可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,可接收多个参数。
    • +
    +
  • +
  • Return: 被移除元素组成的数组
  • +
+
+
1
2
3
4
5
var arry = [1,6,8,'2ue','o90',4,5,6,7]
arry.splice(7) // 返回 [6,7]; arry = [1,6,8,'2ue','o90',4,5]
arry.splice(1,2) // 返回 [6,8]; arry = [1,'2ue','o90',4,5]
arry.splice(-1,1) //返回 [5]; arry = [1,'o90',4]
arry.splice(0,0,2,3,[8,9]) // 返回 []; arry = [2,3,[8,9],1,'o90',4]
+ +

来一张表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法名功能原数组是否改变返回
length获取数组长度NO被引用数组长度
join将数组元素连接起来以构建一个字符串NO转换后的字符串
push在尾部添加元素YES新数组长度
unshift在头部添加元素YES新数组长度
concat在尾部添加元素NO新数组
pop删除最后一个元素YES被删除元素
shift删除第一个元素YES被删除元素
reverse颠倒数组元素顺序YES对数组的引用
sort数组元素排序YES对数组的引用
slice根据索引返回数组的一部分NO根据索引返回数组的一部分
splice插入、删除或替换数组的元素YES被移除元素组成的数组
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/09/05/regex.html b/2016/09/05/regex.html new file mode 100644 index 00000000..4fcc8ce6 --- /dev/null +++ b/2016/09/05/regex.html @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常用正则整理(持续收集) | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 常用正则整理(持续收集) +

+ + +
+ + + + +
+

正则的魅力在于使用很简洁的方式解决一些比较复杂的方式,使代码变得更优雅,也使实现的过程变得更简单透明。本文搜集整理一些常用正则,记录以便查阅

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var regex = window.regex || (function (document, $) {
var _reg = {};
/* 'pwd':/^[\@A-Za-z0-9\!\#\$\%\^\&\*\.\~]{6,16}$/,//密码 */
//验证数字
$.extend(_reg, {
'num': /^\d+$/, //数字
'znum': /^[1-9](\d+)?$/, // 大于0的数字
'float': /^[-]{0,1}(\d+)[\.]+(\d+)$/, //浮点数
'money': /^\d{1,12}(?:\.\d{1,3})?$/, // money
'idCard': /^\d{15}$|^\d{18}$|^\d{17}[xX]$/, //身份证
'idCardStrict':/^(\d{6})([1-2])(\d{3})((?:0[1-9])|(?:1[0-2]))((?:0[0-9])|(?:[1-2][0-9])|(?:3[0-1]))(\d{3})(\d{1})$/,
'qq': /^[1-9]\d{4,15}$/, //QQ
'pwd': /^[\@A-Za-z0-9]{6,16}$/, //密码
'areacode': /^(0[1,2]{1}\d{1})$|^(0[3-9]{1}\d{2})$/, //区号
'tel': /^\d{7,8}$/, // 固话格式
'mobile': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$/, //验证手机号码
'telephone': /^(((\+)?86)|(\(\+86\)))?-?((((0)?[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,12}))-?(\d{1,8})?$/, //验证固定电话
'phone': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$|^((\+86)|(\(\+86\)))?-?(((0[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,8}))$/, //手机号码和固定电话
'zipcode': /^\d{6}$/ //验证邮编
});
//验证字符串
$.extend(_reg, {
'email': /^\w{1,16}([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, //邮箱
'chinese': /^[\u4E00-\u9FA5]+$/, //仅汉字
'char': /^[A-Za-z]+$/, //仅仅是字母
'charn': /^[A-Za-z0-9]+$/, //数字加字母
'nospecial': /^[\u4E00-\u9FA5A-Za-z0-9]+$/, // 不包含特殊字符
'url': /^((http|https|ftp):\/\/)?(\w(\:\w)?@)?([0-9a-z_-]+\.)*?([a-z0-9-]+\.[a-z]{2,6}(\.[a-z]{2})?(\:[0-9]{2,6})?)((\/[^?#<>\/\\*":]*)+(\?[^#]*)?(#.*)?)?$/,
'loginName': /^(13|14|15|18|17)[0-9]{9}$|^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, // 用户名
'userName': /^[\u4e00-\u9fa5]{2`,4}$|[a-zA-Z]{4,20}$/, //真实姓名
'nickName': /^([a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{3,19})$/ //昵称
});
return _reg;
})(document, window.jQuery);
window.regex = regex;
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+
    +
  • + 原作者: J.2ue +
  • +
  • + 本文链接: + https://blog.imx0.com/2016/09/05/regex.html +
  • +
  • + 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处! +
  • +
+
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/09/05/regexundefined b/2016/09/05/regexundefined new file mode 100644 index 00000000..eeb12ea6 --- /dev/null +++ b/2016/09/05/regexundefined @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常用正则整理(持续收集) | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 常用正则整理(持续收集) +

+ + +
+ + + + +
+

正则的魅力在于使用很简洁的方式解决一些比较复杂的方式,使代码变得更优雅,也使实现的过程变得更简单透明。本文搜集整理一些常用正则,记录以便查阅

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var regex = window.regex || (function (document, $) {
var _reg = {};
/* 'pwd':/^[\@A-Za-z0-9\!\#\$\%\^\&\*\.\~]{6,16}$/,//密码 */
//验证数字
$.extend(_reg, {
'num': /^\d+$/, //数字
'znum': /^[1-9](\d+)?$/, // 大于0的数字
'float': /^[-]{0,1}(\d+)[\.]+(\d+)$/, //浮点数
'money': /^\d{1,12}(?:\.\d{1,3})?$/, // money
'idCard': /^\d{15}$|^\d{18}$|^\d{17}[xX]$/, //身份证
'idCardStrict':/^(\d{6})([1-2])(\d{3})((?:0[1-9])|(?:1[0-2]))((?:0[0-9])|(?:[1-2][0-9])|(?:3[0-1]))(\d{3})(\d{1})$/,
'qq': /^[1-9]\d{4,15}$/, //QQ
'pwd': /^[\@A-Za-z0-9]{6,16}$/, //密码
'areacode': /^(0[1,2]{1}\d{1})$|^(0[3-9]{1}\d{2})$/, //区号
'tel': /^\d{7,8}$/, // 固话格式
'mobile': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$/, //验证手机号码
'telephone': /^(((\+)?86)|(\(\+86\)))?-?((((0)?[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,12}))-?(\d{1,8})?$/, //验证固定电话
'phone': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$|^((\+86)|(\(\+86\)))?-?(((0[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,8}))$/, //手机号码和固定电话
'zipcode': /^\d{6}$/ //验证邮编
});
//验证字符串
$.extend(_reg, {
'email': /^\w{1,16}([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, //邮箱
'chinese': /^[\u4E00-\u9FA5]+$/, //仅汉字
'char': /^[A-Za-z]+$/, //仅仅是字母
'charn': /^[A-Za-z0-9]+$/, //数字加字母
'nospecial': /^[\u4E00-\u9FA5A-Za-z0-9]+$/, // 不包含特殊字符
'url': /^((http|https|ftp):\/\/)?(\w(\:\w)?@)?([0-9a-z_-]+\.)*?([a-z0-9-]+\.[a-z]{2,6}(\.[a-z]{2})?(\:[0-9]{2,6})?)((\/[^?#<>\/\\*":]*)+(\?[^#]*)?(#.*)?)?$/,
'loginName': /^(13|14|15|18|17)[0-9]{9}$|^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, // 用户名
'userName': /^[\u4e00-\u9fa5]{2`,4}$|[a-zA-Z]{4,20}$/, //真实姓名
'nickName': /^([a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{3,19})$/ //昵称
});
return _reg;
})(document, window.jQuery);
window.regex = regex;
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+
    +
  • + 原作者: J.2ue +
  • +
  • + 本文链接: + https://blog.imx0.com/2016/09/05/regex.html +
  • +
  • + 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处! +
  • +
+
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/09/24/use-gulp-and-webpack-to-bulid-resource.html b/2016/09/24/use-gulp-and-webpack-to-bulid-resource.html new file mode 100644 index 00000000..6f2a4713 --- /dev/null +++ b/2016/09/24/use-gulp-and-webpack-to-bulid-resource.html @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用webpack + gulp构建项目 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用webpack + gulp构建项目 +

+ + +
+ + + + +
+

有人说为什么会使用webpack+gulp呢?强大的webpack完全可以摒弃gulp了嘛?话虽如此,但个人觉得webpack配置太繁琐复杂,相对来说gulp更简单一点,并且gulp也能很好的完成我期望的任务。所以我想用webpack来处理js任务(因为它支持AMD和CMD,并且可以直接引入模块),用gulp处理images/css/html等资源

+
+

ps: 平时在项目中使用它们的机会不多,以下都是自己项目之外的折腾,如果有错误之处,请不吝指出。

+

demo

先上DEMO

+

配置webpack

webpack的有很强大的配置选项,官方中英文文档都已经很详尽。
中文文档
英文文档

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

上面的配置就是对js进行打包处理,当然webpack肯定也可以处理css和images等资源,webpack的强大毋庸置疑,但为什么不用webpack来处理css等任务呢?

+
    +
  • webpack处理css默认情况下会把css合并到js文件,这点很不爽
  • +
  • 如果要把css文件独立处理,则需要额外的配置,有点烦,所以果断用gulp了
  • +
+

执行webpack,看看效果,可以正常运行

+
1
2
3
4
5
6
7
8
9
{ webpackGulpDeom }  » webpack
Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1141ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
+ 5 hidden modules
{ webpackGulpDeom } »
+ +

配置gulp

gulpfile.js同样,gulp的配置文档详情参考官方文档,这里以编译less文档并压缩css文档为例

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'use strict';
var gulp = require('gulp'),
less = require('gulp-less');

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + 'css/*',['less'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less','watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify']);
+ +

ps:这里只列举了一个编译less的任务。

+

在gulp里执行webpack任务

到了这一步,gulp和webpack任务都编写完成了,如果单单是这样是没有意思的,因为每次启动都需要单独的执行两次命令:一次webpack,一次gulp命令,这样无疑是非常糟糕的。所以我们必须得想办法把gulp和webpack连接起来。怎么连接呢?具体的有两种办法:

+
    +
  • 一种是使用gulp-webpack插件。
  • +
  • 另一种是使用gulp-util插件。
  • +
+

那么我们来重写gulpfile.js和webpack.config.js吧

+

利用gulp-webpack插件

重写gulpfile.js

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
webpack = require("gulp-webpack"),
webpackConfig = require("./webpack.config.js");

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//用gulp执行webpack.config.js
gulp.task('buildJs', function () {
var myConfig = Object.create(webpackConfig);
return gulp
.src([entrySrc + 'js/page/*.js'])
.pipe(webpack(myConfig))
.pipe(gulp.dest('dist/js/page')); //出口文件目录,此处配置之后在webpack.config.js中就必须去掉,不然会报错
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

gulpfile.js的变化:

+
    +
  • 增加了可以一个buildJs任务来执行webpack.config.js文件的配置
  • +
  • 相应的监听对象扩大了
  • +
+

现在来重写webpack.config.js,注释掉output项中的path就行了

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
//path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

webpack.config.jsgulpfile.js都配置好了,那么现在只要执行gulp的相关命令就可以了

+
1
2
3
4
5
6
7
8
9
10
11
{ webpackGulpDeom }  » gulp
[14:20:43] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:20:43] Starting 'webpack'...
[14:20:45] Version: webpack 1.13.3
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
[14:20:45] Finished 'webpack' after 1.37 s
[14:20:45] Starting 'default'...
[14:20:45] Finished 'default' after 34 μs
{ webpackGulpDeom } »
+ +

利用gulp-util插件

+

这种方案只需要修改gulpfile.js就行了,webpack.config.js理论上来说不需要任何变化

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
gutil = require('gulp-util'),
webpackConfig = require("./webpack.config.js"),
myDevConfig = Object.create(webpackConfig),
devCompiler = webpack(myDevConfig);

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//引用webpack.config.js对js资源进行打包
gulp.task("buildJs", function(callback) {
devCompiler.run(function(err, stats) {
if(err) throw new gutil.PluginError("webpack:buildJs", err);
gutil.log("[webpack:buildJs]", stats.toString({
colors: true
}));
callback();
});
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

执行结果:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{ webpackGulpDeom }  » gulp
[14:28:59] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:28:59] Starting 'buildJs'...
[14:29:00] [webpack:buildJs] Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1232ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
chunk {0} app.js (app) 357 kB {1} [rendered]
[0] ./src/js/page/app.js 285 bytes {0} [built]
[1] ./src/js/common/jquery-1.9.1.min.js 92.6 kB {0} [built]
[2] (webpack)/buildin/amd-options.js 42 bytes {0} [built]
[3] ./~/vue/dist/vue.common.js 259 kB {0} [built]
[4] ./~/process/browser.js 5.3 kB {0} [built]
chunk {1} common.js (common.js) 0 bytes [rendered]
[14:29:00] Finished 'buildJs' after 1.24 s
[14:29:00] Starting 'default'...
[14:29:00] Finished 'default' after 7.21 μs
{ webpackGulpDeom } »
+ +

两种方案对比

从以上输出结果可以看出:

+

使用gulp-webpack

    +
  • gulpfile.jswebpack.config.js都要修改
  • +
  • 执行命令打印的信息更少
  • +
  • 编译时间更多?
  • +
+

使用gulp-util

    +
  • 只需要修改gulpfile.js,即使以后单独使用其中一个也不需要再做额外修改
  • +
  • 打印信息更丰富
  • +
  • 编译时间更短?
  • +
+

关于编译时间多少这块,我也没弄太清楚,如果有错误,请读者指出。就个人而言是比较喜欢第二种方案的

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/09/24/use-gulp-and-webpack-to-bulid-resourceundefined b/2016/09/24/use-gulp-and-webpack-to-bulid-resourceundefined new file mode 100644 index 00000000..627d6647 --- /dev/null +++ b/2016/09/24/use-gulp-and-webpack-to-bulid-resourceundefined @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用webpack + gulp构建项目 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用webpack + gulp构建项目 +

+ + +
+ + + + +
+

有人说为什么会使用webpack+gulp呢?强大的webpack完全可以摒弃gulp了嘛?话虽如此,但个人觉得webpack配置太繁琐复杂,相对来说gulp更简单一点,并且gulp也能很好的完成我期望的任务。所以我想用webpack来处理js任务(因为它支持AMD和CMD,并且可以直接引入模块),用gulp处理images/css/html等资源

+
+

ps: 平时在项目中使用它们的机会不多,以下都是自己项目之外的折腾,如果有错误之处,请不吝指出。

+

demo

先上DEMO

+

配置webpack

webpack的有很强大的配置选项,官方中英文文档都已经很详尽。
中文文档
英文文档

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

上面的配置就是对js进行打包处理,当然webpack肯定也可以处理css和images等资源,webpack的强大毋庸置疑,但为什么不用webpack来处理css等任务呢?

+
    +
  • webpack处理css默认情况下会把css合并到js文件,这点很不爽
  • +
  • 如果要把css文件独立处理,则需要额外的配置,有点烦,所以果断用gulp了
  • +
+

执行webpack,看看效果,可以正常运行

+
1
2
3
4
5
6
7
8
9
{ webpackGulpDeom }  » webpack
Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1141ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
+ 5 hidden modules
{ webpackGulpDeom } »
+ +

配置gulp

gulpfile.js同样,gulp的配置文档详情参考官方文档,这里以编译less文档并压缩css文档为例

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'use strict';
var gulp = require('gulp'),
less = require('gulp-less');

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + 'css/*',['less'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less','watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify']);
+ +

ps:这里只列举了一个编译less的任务。

+

在gulp里执行webpack任务

到了这一步,gulp和webpack任务都编写完成了,如果单单是这样是没有意思的,因为每次启动都需要单独的执行两次命令:一次webpack,一次gulp命令,这样无疑是非常糟糕的。所以我们必须得想办法把gulp和webpack连接起来。怎么连接呢?具体的有两种办法:

+
    +
  • 一种是使用gulp-webpack插件。
  • +
  • 另一种是使用gulp-util插件。
  • +
+

那么我们来重写gulpfile.js和webpack.config.js吧

+

利用gulp-webpack插件

重写gulpfile.js

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
webpack = require("gulp-webpack"),
webpackConfig = require("./webpack.config.js");

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//用gulp执行webpack.config.js
gulp.task('buildJs', function () {
var myConfig = Object.create(webpackConfig);
return gulp
.src([entrySrc + 'js/page/*.js'])
.pipe(webpack(myConfig))
.pipe(gulp.dest('dist/js/page')); //出口文件目录,此处配置之后在webpack.config.js中就必须去掉,不然会报错
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

gulpfile.js的变化:

+
    +
  • 增加了可以一个buildJs任务来执行webpack.config.js文件的配置
  • +
  • 相应的监听对象扩大了
  • +
+

现在来重写webpack.config.js,注释掉output项中的path就行了

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
//path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

webpack.config.jsgulpfile.js都配置好了,那么现在只要执行gulp的相关命令就可以了

+
1
2
3
4
5
6
7
8
9
10
11
{ webpackGulpDeom }  » gulp
[14:20:43] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:20:43] Starting 'webpack'...
[14:20:45] Version: webpack 1.13.3
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
[14:20:45] Finished 'webpack' after 1.37 s
[14:20:45] Starting 'default'...
[14:20:45] Finished 'default' after 34 μs
{ webpackGulpDeom } »
+ +

利用gulp-util插件

+

这种方案只需要修改gulpfile.js就行了,webpack.config.js理论上来说不需要任何变化

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
gutil = require('gulp-util'),
webpackConfig = require("./webpack.config.js"),
myDevConfig = Object.create(webpackConfig),
devCompiler = webpack(myDevConfig);

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//引用webpack.config.js对js资源进行打包
gulp.task("buildJs", function(callback) {
devCompiler.run(function(err, stats) {
if(err) throw new gutil.PluginError("webpack:buildJs", err);
gutil.log("[webpack:buildJs]", stats.toString({
colors: true
}));
callback();
});
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

执行结果:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{ webpackGulpDeom }  » gulp
[14:28:59] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:28:59] Starting 'buildJs'...
[14:29:00] [webpack:buildJs] Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1232ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
chunk {0} app.js (app) 357 kB {1} [rendered]
[0] ./src/js/page/app.js 285 bytes {0} [built]
[1] ./src/js/common/jquery-1.9.1.min.js 92.6 kB {0} [built]
[2] (webpack)/buildin/amd-options.js 42 bytes {0} [built]
[3] ./~/vue/dist/vue.common.js 259 kB {0} [built]
[4] ./~/process/browser.js 5.3 kB {0} [built]
chunk {1} common.js (common.js) 0 bytes [rendered]
[14:29:00] Finished 'buildJs' after 1.24 s
[14:29:00] Starting 'default'...
[14:29:00] Finished 'default' after 7.21 μs
{ webpackGulpDeom } »
+ +

两种方案对比

从以上输出结果可以看出:

+

使用gulp-webpack

    +
  • gulpfile.jswebpack.config.js都要修改
  • +
  • 执行命令打印的信息更少
  • +
  • 编译时间更多?
  • +
+

使用gulp-util

    +
  • 只需要修改gulpfile.js,即使以后单独使用其中一个也不需要再做额外修改
  • +
  • 打印信息更丰富
  • +
  • 编译时间更短?
  • +
+

关于编译时间多少这块,我也没弄太清楚,如果有错误,请读者指出。就个人而言是比较喜欢第二种方案的

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/11/22/Mobile-terminal-adapter.html b/2016/11/22/Mobile-terminal-adapter.html new file mode 100644 index 00000000..cd4ba595 --- /dev/null +++ b/2016/11/22/Mobile-terminal-adapter.html @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +移动端适配方案 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 移动端适配方案 +

+ + +
+ + + + +
+

移动端越来越被大众所接收,那么相应的技术就越来越向它靠拢,这是一种不可阻挡的趋势,也是万物发展的规律。移动端有三大难题:兼容、调试和适配。这三大问题就好像三座无法逾越的大山阻挡者我们前进的步伐,此文将记录我在项目中关于移动端适配的一些方式,供大家参考

+
+

分析

移动端适配的根本原因

+
    +
  • 屏幕窗口的大小
  • +
  • 设备像素比(devicepixelratio,简称dpr)
  • +
+

很多地方介绍设备像素比的,这里就不做具体探讨,简单总结一下:devicepixelratio(设备像素比,即dpr) = physicalpixel (物理像素) / density-independent pixel(设备独立像素,即dip)。dipdp,(device independent pixels,设备独立像素)与屏幕密度有关。dip可以用来辅助区分视网膜设备还是非视网膜设备。
JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr,但遗憾的是并不是所有的都支持。
在CSS中,可以分别针对屏幕大小和设备像素比做适配:
针对窗口大小,一般使用媒体查询的only screenmin-widthmax-width来适配,也是使用css做适配最常见的一种方式
针对像素比,可以使用-webkit-device-pixel-ratio-webkit-min-device-pixel-ratio-webkit-max-device-pixel-ratio属性,同样他的支持度一样不高(其实是很低),所以几乎没有用武之地。

+

适配方式

移动端适配主要有两大不同的方向:

+
    +
  • 响应式布局:它是根据屏幕大小自动的调整布局位置(非单纯的缩放),实现适配
  • +
  • 自适应布局:它是根据屏幕大小自动的缩放大小,实现适配。
  • +
+

两种方式应用的场景不同,各有优劣,本人对自适应布局使用的比较多

+

解决方案

为了解决这个老大难问题,从最初开始百分比到em,然后到现在rem的使用,都一一体现着技术的滚滚向前。目前是用的最多的也就是rem,他们的区别和有点请自行GG
有了rem这个大杀器,解决问题就变得简单起来,具体请往下看。

+

纯css实现方式 – 媒体查询

使用原生css来实现媒体查询是很繁琐的,因为每个媒体查询都要去设定规则。推荐使用css的预编译器(sass,less,stylus),比较方便。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* 定义规则 */
html {
font-size: 20px;
}
@media only screen and (min-width: 401px) {
html {
font-size: 24px !important;
}
}
@media only screen and (min-width: 428px) {
html {
font-size: 28 !important;
}
}
@media only screen and (min-width: 481px) {
html {
font-size: 30px !important;
}
}
@media only screen and (min-width: 569px) {
html {
font-size: 35px !important;
}
}
@media only screen and (min-width: 641px) {
html {
font-size: 40px !important;
}
}
@media only screen and (min-width: 751px) {
html {
font-size: 50px !important;
}
}
@media only screen and (min-width: 1080px) {
html {
font-size: 60px !important;
}
}
+ +
1
2
3
4
//less 方式调用
@unit: 50rem; //基准单位,根据设计稿来确定。假设:设计稿尺寸为750,那么@unit设置为50rem(1rem=50px更方便下面计算)
.warp{with: 100 / @unit} // 设计稿上元素的尺寸为100px => .warp{with: 2rem}
.warp{with: 10 / @unit} // 设计稿上元素的尺寸为10px => .warp{with: 0.2rem}
+ +

如果这里使用原生css来做,每个尺寸都需要去计算,如果使用预处理器,只需要定义一个变量,计算的事情直接交给它们就行。

+

这样当页面展示在750的屏幕上时,html的font-size50px,那么当设置为2rem的元素显示的尺寸就为2*50px=100px。在其他尺寸的设备也会根据媒体查询设置的不同font-size进行自动缩放适配。
当然上面也提到了,在css中也是可以获取到devicePixelRatio的值,那么为了更精确在写媒体查询的时候可以把它也加上去,这里就不展开了。

+

纯css实现方式 – 计算属性

当然除了媒体查询,还有一种更潮的方式就是利用css3的一些新属性:计算属性和vw属性来实现自动设置根字体大小的目的

+
1
html{font-size:calc(100vw/6.4)} //6.4为psd设计稿尺寸/100
+ +

这套方案几乎是目前最简洁的方案了,并且calcvw在移动端的支持也不错哟。

+

js的实现方式

js的实现方式,参考了网易淘宝的实现方式,对他们进行了整合。并且修复了手机端1px问题

+
    +
  • 网易实现方式是通过设备尺寸动态的设置DOM的根元素字体大小,没有考虑devicePixelRatio的因素;
  • +
  • 淘宝实现方式也是通过设备尺寸动态的设置DOM的根元素字体大小,并且考虑了devicePixelRatio的因素,但淘宝在设置rem时,显得较复杂(不方便写css把px转化成rem);
  • +
  • 1px问题简单点说就是因为devicePixelRatio的存在,css的1px不等于移动端的1px。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function(doc, win, designSize) { //designSize为设计稿的尺寸(宽)

var docEl = document.documentElement,
devWidth = docEl.clientWidth > 1080 ? 1080 : docEl.clientWidth,
dpr = devicePixelRatio || 1,
scale = 1 / dpr,
width = dpr * devWidth,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'onresize', //判断横屏和窗口重置
recalc = function() {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
document.querySelector('meta[name="viewport"]')
.setAttribute('content','width=' + width +
', initial-scale=' + scale +
', maximum-scale=' + scale +
', minimum-scale=' + scale +
', user-scalable=no');
docEl.style.fontSize = devWidth / (designSize / 100) * dpr + 'px';
};

if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);

})(document, window, 750);
+ +

总结

    +
  • 以上三种方案对比,第一种媒体查询是最死板的,基本就是纯体力活。
  • +
  • 利用css的计算属性可以很优雅的解决问题,但是在兼容方面来说,目前还不是很完美
  • +
  • 并且利用纯css也没考虑devicePixelRatio(像素问题)这个因素。
  • +
  • 最后的javascript解决方案则是考虑到了兼容和devicePixelRatio这些因素,但是这里有一个很大的弊端就是:页面在某些情况(性能慢)会经过两次重回(给HTML根设置font-size和设置meta标签时),在网络或者终端性能不是很好的情况用户体验很不错甚至页面错乱的情况。
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/11/22/Mobile-terminal-adapterundefined b/2016/11/22/Mobile-terminal-adapterundefined new file mode 100644 index 00000000..906f751d --- /dev/null +++ b/2016/11/22/Mobile-terminal-adapterundefined @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +移动端适配方案 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 移动端适配方案 +

+ + +
+ + + + +
+

移动端越来越被大众所接收,那么相应的技术就越来越向它靠拢,这是一种不可阻挡的趋势,也是万物发展的规律。移动端有三大难题:兼容、调试和适配。这三大问题就好像三座无法逾越的大山阻挡者我们前进的步伐,此文将记录我在项目中关于移动端适配的一些方式,供大家参考

+
+

分析

移动端适配的根本原因

+
    +
  • 屏幕窗口的大小
  • +
  • 设备像素比(devicepixelratio,简称dpr)
  • +
+

很多地方介绍设备像素比的,这里就不做具体探讨,简单总结一下:devicepixelratio(设备像素比,即dpr) = physicalpixel (物理像素) / density-independent pixel(设备独立像素,即dip)。dipdp,(device independent pixels,设备独立像素)与屏幕密度有关。dip可以用来辅助区分视网膜设备还是非视网膜设备。
JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr,但遗憾的是并不是所有的都支持。
在CSS中,可以分别针对屏幕大小和设备像素比做适配:
针对窗口大小,一般使用媒体查询的only screenmin-widthmax-width来适配,也是使用css做适配最常见的一种方式
针对像素比,可以使用-webkit-device-pixel-ratio-webkit-min-device-pixel-ratio-webkit-max-device-pixel-ratio属性,同样他的支持度一样不高(其实是很低),所以几乎没有用武之地。

+

适配方式

移动端适配主要有两大不同的方向:

+
    +
  • 响应式布局:它是根据屏幕大小自动的调整布局位置(非单纯的缩放),实现适配
  • +
  • 自适应布局:它是根据屏幕大小自动的缩放大小,实现适配。
  • +
+

两种方式应用的场景不同,各有优劣,本人对自适应布局使用的比较多

+

解决方案

为了解决这个老大难问题,从最初开始百分比到em,然后到现在rem的使用,都一一体现着技术的滚滚向前。目前是用的最多的也就是rem,他们的区别和有点请自行GG
有了rem这个大杀器,解决问题就变得简单起来,具体请往下看。

+

纯css实现方式 – 媒体查询

使用原生css来实现媒体查询是很繁琐的,因为每个媒体查询都要去设定规则。推荐使用css的预编译器(sass,less,stylus),比较方便。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* 定义规则 */
html {
font-size: 20px;
}
@media only screen and (min-width: 401px) {
html {
font-size: 24px !important;
}
}
@media only screen and (min-width: 428px) {
html {
font-size: 28 !important;
}
}
@media only screen and (min-width: 481px) {
html {
font-size: 30px !important;
}
}
@media only screen and (min-width: 569px) {
html {
font-size: 35px !important;
}
}
@media only screen and (min-width: 641px) {
html {
font-size: 40px !important;
}
}
@media only screen and (min-width: 751px) {
html {
font-size: 50px !important;
}
}
@media only screen and (min-width: 1080px) {
html {
font-size: 60px !important;
}
}
+ +
1
2
3
4
//less 方式调用
@unit: 50rem; //基准单位,根据设计稿来确定。假设:设计稿尺寸为750,那么@unit设置为50rem(1rem=50px更方便下面计算)
.warp{with: 100 / @unit} // 设计稿上元素的尺寸为100px => .warp{with: 2rem}
.warp{with: 10 / @unit} // 设计稿上元素的尺寸为10px => .warp{with: 0.2rem}
+ +

如果这里使用原生css来做,每个尺寸都需要去计算,如果使用预处理器,只需要定义一个变量,计算的事情直接交给它们就行。

+

这样当页面展示在750的屏幕上时,html的font-size50px,那么当设置为2rem的元素显示的尺寸就为2*50px=100px。在其他尺寸的设备也会根据媒体查询设置的不同font-size进行自动缩放适配。
当然上面也提到了,在css中也是可以获取到devicePixelRatio的值,那么为了更精确在写媒体查询的时候可以把它也加上去,这里就不展开了。

+

纯css实现方式 – 计算属性

当然除了媒体查询,还有一种更潮的方式就是利用css3的一些新属性:计算属性和vw属性来实现自动设置根字体大小的目的

+
1
html{font-size:calc(100vw/6.4)} //6.4为psd设计稿尺寸/100
+ +

这套方案几乎是目前最简洁的方案了,并且calcvw在移动端的支持也不错哟。

+

js的实现方式

js的实现方式,参考了网易淘宝的实现方式,对他们进行了整合。并且修复了手机端1px问题

+
    +
  • 网易实现方式是通过设备尺寸动态的设置DOM的根元素字体大小,没有考虑devicePixelRatio的因素;
  • +
  • 淘宝实现方式也是通过设备尺寸动态的设置DOM的根元素字体大小,并且考虑了devicePixelRatio的因素,但淘宝在设置rem时,显得较复杂(不方便写css把px转化成rem);
  • +
  • 1px问题简单点说就是因为devicePixelRatio的存在,css的1px不等于移动端的1px。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function(doc, win, designSize) { //designSize为设计稿的尺寸(宽)

var docEl = document.documentElement,
devWidth = docEl.clientWidth > 1080 ? 1080 : docEl.clientWidth,
dpr = devicePixelRatio || 1,
scale = 1 / dpr,
width = dpr * devWidth,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'onresize', //判断横屏和窗口重置
recalc = function() {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
document.querySelector('meta[name="viewport"]')
.setAttribute('content','width=' + width +
', initial-scale=' + scale +
', maximum-scale=' + scale +
', minimum-scale=' + scale +
', user-scalable=no');
docEl.style.fontSize = devWidth / (designSize / 100) * dpr + 'px';
};

if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);

})(document, window, 750);
+ +

总结

    +
  • 以上三种方案对比,第一种媒体查询是最死板的,基本就是纯体力活。
  • +
  • 利用css的计算属性可以很优雅的解决问题,但是在兼容方面来说,目前还不是很完美
  • +
  • 并且利用纯css也没考虑devicePixelRatio(像素问题)这个因素。
  • +
  • 最后的javascript解决方案则是考虑到了兼容和devicePixelRatio这些因素,但是这里有一个很大的弊端就是:页面在某些情况(性能慢)会经过两次重回(给HTML根设置font-size和设置meta标签时),在网络或者终端性能不是很好的情况用户体验很不错甚至页面错乱的情况。
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/12/31/2016-to-2017.html b/2016/12/31/2016-to-2017.html new file mode 100644 index 00000000..bac4e715 --- /dev/null +++ b/2016/12/31/2016-to-2017.html @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<2016/><2017> | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ <2016/><2017> +

+ + +
+ + + + +
+ +
+

与其在上一秒中沉醉,不如努力拥抱下一秒.

+
+

匆匆而去的2016

入行前端的第二年,也是毕业的第二年。

+

短短两载却有翻天覆地的改变:从大学走入社会,从设计跨行到前端。这其中有必然也有偶然。

+

年初

年初换了新东家,毕业后的第二份工作,有对自身能力的底气不足而提高的渴望,也有对新东家的满怀期望,一切在有条不紊的进行着。2016年2月22日入职,从此多了一个别号二月(2ue)。座位在老大旁边,感到一丝压力。

+

适应与压力

经历了最初的彷徨后,慢慢开始适应新的环境,作为新手的我,跟着大神们一起做项目是开心的,但也同时感受到更多的压力。因此在入职后的几个月进入了疯狂学习模式,逛技术社区,微博关注技术达人,微信关注相关公众号,啃书等等,连早上挤公交地铁的时候都抽时间在手机上学习,当时感觉自己已经疯魔。虽然也就仅仅疯狂了三个月,但这短暂的三个月疯狂却让我焕然一新:从最初的复制粘贴到有自己的思考、从无处可问到能独立解决问题、更加擅于利用网络解决问题、能够更快的阅读新的知识、更好的编码习惯…林林总总,这三个月是很重要的三个月。

+

生活

换了新房东,空间更大,也换了一台新电脑,更方便撸(打)代(游)码(戏)。总之一切还算满意。

+

同事

我其实是个不擅交际,怕生的人儿。因为我说话很笨,所以在新的环境想要和人混熟,总是要花更多的时间去处理这些关系。不过幸运的是遇到一群很好相处的同事,让我更容易的处理好这些问题。所以再后来遇到各种问题后我都是大胆的去问部门大神,充分发挥了死灿烂打的精神(这个三秒,市委应该深有体会)。当然作为一个胖纸,肯定也有一堆饭友,也会利用工作过的闲暇之余去腐败腐败。

+

项目与加班

新东家的项目总是要得比较急,这就常常导致加班和代码的低质量产出。这是我最开始的想法,但到后来发现,诚然这两者是有一定的因果关系,但是有些东西本身就不可力抗(比如项目交付时间节点),如果一直持有因为时间导致自己加班这种心态,而不去提高自身的能力,那么不论你换到哪家公司你都会陷入无尽的加班与抱怨中。加班不可避免,那就努力提高自己,减少加班时间吧。

+

技术浪潮

这一年是技术浪潮爆发的一年。大数据、AI、人工智能、无人驾驶…各种技术名词频频见诸于头条新闻。在这喷薄的大浪中,前端领域也爆发出自己的色彩:MVCMVVCstyluslesssassgruntgulpwebpackngreactvuenode、大前端开发….等等,技术的更新换代不断地推动着前端领域的向前发展。作为一个前端开发者,深深的感受到我大前端的魅力,也越发对技术存有敬畏之心,不断的驱使我追赶它的浪潮,我愿意在它的浪潮中沉浮。

+

我的Github

github是在2015年末注册的,期间一直不知怎么玩。在上半年,学习了git,喜欢使用命令行,也喜欢在github上提交一些东西,后面学习了vue后,慢慢的提交一些简单的demo上去,挺享受这个过程的。对了,我也通过github+hexo托管了一个静态博客,平时自己的写的东西也往上丢,不过貌似没坚持多久…..2017我会更加勤劳。

+

换了个显示器

这是一个不得不说的显示器:前端开发当然得用大的好的显示器,于是三秒写了一年的12Q换来一个帅气的显示器,从此以后心情舒畅,撸码不累,加班更勤。

+

进步与不足

踩着2016的尾巴,我细数了2016发生在我身上的变化,虽有些许进步,但却也说不出个所以然,如果非要说,那么就是自己解决问题的能力强了,眼界开阔了,有自己的思考了,目标清晰了。
相对于进步,不足之处就很明显了,有很多也是我的致命伤,更是职业道路前进的致命伤。

+
    +
  • 习惯
      +
    • 有点小拖拉
    • +
    • 三分钟热情
    • +
    • 不仔细
    • +
    +
  • +
  • 技术
      +
    • 基础不够
    • +
    • 没有创新
    • +
    • 阅读很不够
    • +
    • 至今没有代表作
    • +
    +
  • +
+

有不足,能正视不足,更需要我弥补不足。

+

滚滚而来的2017

2017年似乎要做的事情很多。一件件做,总有完成;一步步走,总有尽头。

+

TODO

    +
  • EMAC 2016-2017
  • +
  • VUE,REACT
  • +
  • NODE
  • +
  • 涨工资
  • +
+

又是新的一年开启,新年愿有一番好景。

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2016/12/31/2016-to-2017undefined b/2016/12/31/2016-to-2017undefined new file mode 100644 index 00000000..e057e37f --- /dev/null +++ b/2016/12/31/2016-to-2017undefined @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<2016/><2017> | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ <2016/><2017> +

+ + +
+ + + + +
+ +
+

与其在上一秒中沉醉,不如努力拥抱下一秒.

+
+

匆匆而去的2016

入行前端的第二年,也是毕业的第二年。

+

短短两载却有翻天覆地的改变:从大学走入社会,从设计跨行到前端。这其中有必然也有偶然。

+

年初

年初换了新东家,毕业后的第二份工作,有对自身能力的底气不足而提高的渴望,也有对新东家的满怀期望,一切在有条不紊的进行着。2016年2月22日入职,从此多了一个别号二月(2ue)。座位在老大旁边,感到一丝压力。

+

适应与压力

经历了最初的彷徨后,慢慢开始适应新的环境,作为新手的我,跟着大神们一起做项目是开心的,但也同时感受到更多的压力。因此在入职后的几个月进入了疯狂学习模式,逛技术社区,微博关注技术达人,微信关注相关公众号,啃书等等,连早上挤公交地铁的时候都抽时间在手机上学习,当时感觉自己已经疯魔。虽然也就仅仅疯狂了三个月,但这短暂的三个月疯狂却让我焕然一新:从最初的复制粘贴到有自己的思考、从无处可问到能独立解决问题、更加擅于利用网络解决问题、能够更快的阅读新的知识、更好的编码习惯…林林总总,这三个月是很重要的三个月。

+

生活

换了新房东,空间更大,也换了一台新电脑,更方便撸(打)代(游)码(戏)。总之一切还算满意。

+

同事

我其实是个不擅交际,怕生的人儿。因为我说话很笨,所以在新的环境想要和人混熟,总是要花更多的时间去处理这些关系。不过幸运的是遇到一群很好相处的同事,让我更容易的处理好这些问题。所以再后来遇到各种问题后我都是大胆的去问部门大神,充分发挥了死灿烂打的精神(这个三秒,市委应该深有体会)。当然作为一个胖纸,肯定也有一堆饭友,也会利用工作过的闲暇之余去腐败腐败。

+

项目与加班

新东家的项目总是要得比较急,这就常常导致加班和代码的低质量产出。这是我最开始的想法,但到后来发现,诚然这两者是有一定的因果关系,但是有些东西本身就不可力抗(比如项目交付时间节点),如果一直持有因为时间导致自己加班这种心态,而不去提高自身的能力,那么不论你换到哪家公司你都会陷入无尽的加班与抱怨中。加班不可避免,那就努力提高自己,减少加班时间吧。

+

技术浪潮

这一年是技术浪潮爆发的一年。大数据、AI、人工智能、无人驾驶…各种技术名词频频见诸于头条新闻。在这喷薄的大浪中,前端领域也爆发出自己的色彩:MVCMVVCstyluslesssassgruntgulpwebpackngreactvuenode、大前端开发….等等,技术的更新换代不断地推动着前端领域的向前发展。作为一个前端开发者,深深的感受到我大前端的魅力,也越发对技术存有敬畏之心,不断的驱使我追赶它的浪潮,我愿意在它的浪潮中沉浮。

+

我的Github

github是在2015年末注册的,期间一直不知怎么玩。在上半年,学习了git,喜欢使用命令行,也喜欢在github上提交一些东西,后面学习了vue后,慢慢的提交一些简单的demo上去,挺享受这个过程的。对了,我也通过github+hexo托管了一个静态博客,平时自己的写的东西也往上丢,不过貌似没坚持多久…..2017我会更加勤劳。

+

换了个显示器

这是一个不得不说的显示器:前端开发当然得用大的好的显示器,于是三秒写了一年的12Q换来一个帅气的显示器,从此以后心情舒畅,撸码不累,加班更勤。

+

进步与不足

踩着2016的尾巴,我细数了2016发生在我身上的变化,虽有些许进步,但却也说不出个所以然,如果非要说,那么就是自己解决问题的能力强了,眼界开阔了,有自己的思考了,目标清晰了。
相对于进步,不足之处就很明显了,有很多也是我的致命伤,更是职业道路前进的致命伤。

+
    +
  • 习惯
      +
    • 有点小拖拉
    • +
    • 三分钟热情
    • +
    • 不仔细
    • +
    +
  • +
  • 技术
      +
    • 基础不够
    • +
    • 没有创新
    • +
    • 阅读很不够
    • +
    • 至今没有代表作
    • +
    +
  • +
+

有不足,能正视不足,更需要我弥补不足。

+

滚滚而来的2017

2017年似乎要做的事情很多。一件件做,总有完成;一步步走,总有尽头。

+

TODO

    +
  • EMAC 2016-2017
  • +
  • VUE,REACT
  • +
  • NODE
  • +
  • 涨工资
  • +
+

又是新的一年开启,新年愿有一番好景。

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-01-05-vue-datapickerundefined b/2017-01-05-vue-datapickerundefined new file mode 100644 index 00000000..50ae2247 --- /dev/null +++ b/2017-01-05-vue-datapickerundefined @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用vue框架造了一个日历控件 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用vue框架造了一个日历控件 +

+ + +
+ + + + +
+

使用官方提供的vue-simple-template配置打包,写的一个简单的vue-datepicker。支持选择功能,功能比较简单,欢迎大家拍砖.

+
+

效果预览(demo)

vue-datepicker

+

项目构建

1
2
3
4
5
6
7
8
9
10
#全局安装vue,vue-cli,webpack,如以安装则跳过
npm install -g vue vue-cli webpack

# 安装依赖
npm install

# 运行项目=>localhost:8080
npm run dev

# 更多的构建信息请参考官网
+ +

更新记录

2017-2-10 15:14:43

+
    +
  • 修正选择日期后高亮错误问题
  • +
  • 修正多出方法中计算时数字会被转化成字符串问题
  • +
+

2016-12-9 10:12:58

+
    +
  • 更新效果图
  • +
  • 修正computed计算时数字会被转化成字符串问题
    1
    const startNum = self.chooseType ? +self.YearChangeSyboml - 4 : 1;
  • +
+

2016-12-8 17:30:04

+
    +
  • 增加输入框唤醒日历
  • +
  • 增加选择功能
  • +
+

2016-12-8 12:10:14

+
    +
  • 上传日历,只有简单展示版本
  • +
+

参考

待整理…

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-03-15-babunundefined b/2017-03-15-babunundefined new file mode 100644 index 00000000..6755fe1f --- /dev/null +++ b/2017-03-15-babunundefined @@ -0,0 +1,552 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +windows平台下超强的cmd工具Babun使用笔记 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ windows平台下超强的cmd工具Babun使用笔记 +

+ + +
+ + + + +
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

Babun

官方贴出了Babun的十大特性

+
    +
  • Pre-configured Cygwin with a lot of addons: 预置大量的Cygwin插件
  • +
  • Silent command-line installer, no admin rights required:静默命令行安装,不需要管理员权限
  • +
  • pact - advanced package manager (like apt-get or yum): 支持pact高级包管理器,类似于apt-get、yum等
  • +
  • xTerm-256 compatible console: xterm-256兼容控制台
  • +
  • HTTP(s) proxying support: HTTP(s) 代理支持
  • +
  • Plugin-oriented architecture: 插件体系,可以安装丰富的插件
  • +
  • Pre-configured git and shell: 预置git和shell,支持自定义配置
  • +
  • Integrated oh-my-zsh: 集成了zsh
  • +
  • Auto update feature: 自动检测最新版本
  • +
  • “Open Babun Here” context menu entry: 支持右键菜单“此处打开Babun”
  • +
+

当然对于上面这些特性,我不得不补充一点,那就是它强大的命令提示功能,能从根据你的输入匹配历史输入,狠棒!

+

Cygwin

Babun的核心包括一个预配置的Cygwincygwin是一个非常好的工具,但有很多使用技巧,使你能够节省大量的时间。Babun解决了很多问题,它里面包含了很多重要的软件包,是你能够第一时间能够使用它们

+

shell

Babunshell通过调整,已达到最佳的用户体验,Babun有两个配置之后马上使用的shell(默认使用zsh,可以使用bash或者zsh命令切换到对应的模式),Babunshell具有以下的特点:

+
    +
  • 语法高亮
  • +
  • 具有unix的工具
  • +
  • 软件开发工具
  • +
  • git-语义提示
  • +
  • 自定义脚本和别名
  • +
  • +
+

Console

Babun支持HTTP代理,只需添加地址和HTTP代理服务器的凭据。Babunrc文件所在文件夹执行源Babunrc启用HTTP代理。目前还不支持SOCKS代理。

+

开发者工具

Babun提供多种方便的工具和脚本,是你的开发工作更轻松,具有的功能如下:

+
    +
  • 编程语言(python,Perl, etc等)
  • +
  • git(各种各样的别名调整)
  • +
  • UNIX工具((grep, wget, curl, etc)
  • +
  • vcs (svn, git)
  • +
  • oh-my-zsh
  • +
  • 自定义脚本(pbcopy, pbpaste, Babun, etc)
  • +
+

安装

默认安装

双击install.bat脚本,Babun使用默认安装位置C:\Users\userName\.Babun,安装好的Babun会在C:\Users\userName\下;
当然也可以指定安装位置

+

自定义安装

通过cmd命令行在执行install.bat时指定参数/t/target指定安装的目录。
执行:Babun.bat /t install-dir

+
1
Babun.bat /t c:\Babun
+ +

安装好之后会在d:\Babun目录下生成一个.Babun的目录,Babun所有文件都在这个目录中。注意安装目录最好不要有空格,这是cygwin要求的

+

启动Babun默认是在’%userprofile%.Babun\cygwin\home\username’

+

开发环境配置

pip

Babun内置了PythonPerl等解释器。cygwin自带的python没有pip,需手动安装。
直接执行下面这个命令就好了。

+
1
wget https://bootstrap.pypa.io/get-pip.py -O - | python
+ +

有了pip就可以自由的安装诸如ipython之类的东西,还有包罗万象的类库。

+

常用插件

Babun默认是安装了oh-my-zsh的,这里可以根据自身情况安装一些插件。具体可参考利用oh-my-zsh打造你的超级终端一文;

+

包管理器使用

Babun提供一个叫pact包管理工具,类似于linux上面的apt-getyum的包管理工具

+

配置别名(alias)

可以在.Babun\cygwin\home\username目录下配置对应工具的别名,而并不仅限于git-bash
当然记忆别名其实也是体力活,我的想法是对一些常用的命令、经常手滑手速过快打错的命令、复杂的命令配置一些别名,例如

+
1
2
3
4
5
6
gt = git
gti = git
n = npm
nr = npm run dev
gtlg = git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
...等等
+ +

问题

本地SSH-KEY不可用

查看我记录的Babun导致本地SSH-KEY不可用一文

+

中文乱码问题

找了很多解决方案,都不能完美的解决问题,最后还是回归原始:不解决!!!

+

锁定文件夹

在使用Babun时(比如此时进入了a目录),它会锁定文件夹a目录,导致你可能无法做一些危险操作。必须关闭Babun后才能解锁进程

+

参考文章:

+
    +
  • windows下的命令行工具Babun
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+
    +
  • + 原作者: J.2ue +
  • +
  • + 本文链接: + https://blog.imx0.com/2017/03/15/babun.html +
  • +
  • + 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处! +
  • +
+
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-03-31-javascript-typeundefined b/2017-03-31-javascript-typeundefined new file mode 100644 index 00000000..6e036c89 --- /dev/null +++ b/2017-03-31-javascript-typeundefined @@ -0,0 +1,712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Javascript常用见问题之变量类型判断终极篇 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Javascript常用见问题之变量类型判断终极篇 +

+ + +
+ + + + +
+

类型判断是我们在编程中常遇到的棘手问题,严格的变量类型约束会为代码减少很多致命的BUG。本文是对常用到的类型判断的一个整理,以求以最简洁的方式来判断变量的类型。
本文所有的如果没特指,都是基于ES5的原生javascript

+
+

变量的基本类型

JavaScript变量包含两种不同的数据类型的值:基本类型引用类型。基本类型是指简单的数据,有NumberStringBooleanUndefinedNull(null可以算作是一个特殊的基本数据类型),而引用类型指那些可能包含多个值的对象,有ObjectArrayDateRegExpFunction等。在JavaScript中,我们通过var来声明变量,由于JavaScript弱语言类型,我们无法在申明的时候规定他的类型,JavaScript变量的类型是随变量的值改变而改变的。为了代码的安全性,在有些情况下我们要判断变量值的类型,如何正确的判断变量的类型就成了一个比较有深度的问题。下图列举一些常见的类型:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型举例
Nullnull
Undefinedundefined、未赋值的变量
Booleantrue、false
Number-1、0 、 1、 NaN
String‘1’、’a’
Array[]、new Array()
Object{}、new Object()
Functionfunction(){}
+

判断他们的类型,第一时间可能你会想到用typeof去检测它们的类型,然后你就崩溃了:明明是Null为什么结果却是oject,明明是Array为什么还是obejct?…因此可以看出typeof方法不是很可靠,我们必须寻找一种行之有效的方法来解决这个问题?请继续往下读(为了方便阅读,下文中所有的para表示要判断的变量):

+
    +
  • isNaN(para)
  • +
  • !para
  • +
  • typeof para
  • +
  • Object.prototype.toString.call(para);
  • +
+

除了上面这些方法,未来可能会有更多方法来增强变量的约束和判断,比如isNumber

+

isNaN(para)

用来判断是否为number类型的专有方法。但是需要注意的是,如果使用typeof判断那么结果会是number

+

!para

常用来判断一个变量是否存在,面对ArryObject引用类型变量时无论是否为空都会被转换成true

+

typeof para

事实证明typeof并不是万能的,在对除Null以外的基本类型变量是相当有威力的,但是对引用类型变量Null时都会被识别成object,但是请注意:

+
1
2
typeof {}; //object
typeof Object; //Function
+

为什么会出现这样的情况呢?因为Object是一个构造函数,而不是object数据类型对象,同理ArrayDateFunction等都是属于构造函数

+

Object.prototype.toString.call(para)

前面的typeof死在半路,无法打探到引用类型变量null的真实情报,但是我们得出了另一个情报:**他们都是obejct**。别慌,我们另外一个强大的武器,可以直指要害,Object.prototype.toString.call(para)

+

判断结果比较表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型isNaN(para)!paratypeof paratoString(para)
Nullnulltruetrueobejct[obejct Null]
Undefinedundefinedtruetrueundefined[obejct Undefined]
Booleantrue/falsetruefalse/truetrue[obejct Boolean]
Number-1falsefalsenumber[obejct Number]
Number0falsetruenumber[obejct Number]
Number1falsefalsenumber[obejct Number]
NumberNaNtruetruenumber[obejct Number]
String‘1’falsefalsestring[obejct String]
String‘a’truefalsestring[obejct String]
String‘’truetruestring[obejct String]
String‘ ‘(中间包含空格)truefalsestring[obejct String]
Array[]/[4]truefalseobejct[obejct Array]
Object{}/{n:4}truefalseobejct[obejct Object]
Functionfunction(){}truefalseobejct[obejct Function]
+

总结方法

根据上面的表格对比,我整理了一些常见的方法。并且再比较结果精准的情况下尽可能的简化比较过程.

+

判断数字(非严格)

字符串’1‘会被识别成number

+
1
2
3
function isNumber(para){
return !isNaN(para);
};
+ +

判断数字(严格)

在必要的情况下使用:此方法会把字符串’1‘识别成string类型

+
1
2
3
function isStrictNumber(para){
return !isNaN(para) && typeof para === 'number';
};
+ +

判断字符串(非严格)

1
2
3
function isString(para){
return typeof para === 'string';
};
+ +

判断字符串(严格)

在必要的情况下使用:此种方法会把字符串’1‘识别成number类型

+
1
2
3
function isStrictString(para){
return isNaN(para) && typeof para === 'string';
};
+ +

判断一般数据类型(即非引用类型)

注意:使用typeof判断null结果为object

+
1
2
3
function isBasicType(para){
return typeof para !== 'obejct';
};
+ +

判断是否为null(不能识别’’)

此方法只能识别null,如果要包含’’,请结合方法isStringNull()一起使用

+
1
2
3
function isNull(para){
return !para && typeof para === 'object';
};
+ +

判断是否为空字符串(不包含空格)

此方法只能识别'',如果要包含null,请结合方法isNull()一起使用

+
1
2
3
function isStringtNull(para){
return !para && typeof para === 'string';
};
+ +

判断是否为undefined

1
2
3
function isUndefined(para){
return typeof para === 'undefined';
};
+ +

判断是否为false

当为null,undefined,'',0,-0,false,NaN

+
1
2
3
function isFalse(para){
return !para;
};
+ +

判断对象(非严格1–所有的obejct对象)

1
2
3
function isAllObject(_v){
return typeof _v === 'obejct';
};
+ +

判断对象(非严格2–除去null的所有object对象)<–> 判断引用类型

1
2
3
function isObject(_v){
return !!v && typeof _v === 'obejct';
};
+ +

判断对象(严格–只识别{}JSON对象)

1
2
3
function isStrictObject(_v){
return Object.prototype.toString.call(_v) === '[object Object]';
};
+ +

判断数组

1
2
3
function isArray(para){
return Object.prototype.toString.call(para) === '[object Array]';
};
+ +

判断对象

这里特指{}类JSON对象

+
1
2
3
function isObject(para){
return Object.prototype.toString.call(para) === '[object Object]';
};
+ +

判断可执行函数

1
2
3
function isFunction(para){
return typeof para === 'function';
};
+ +
+

总结

当我们需要判断其他类型时,完全可以参照上面的表来写出自己的方法哦。
当然现在前端各种流行库不断推陈出新,我们完全可以直接使用别人封装好的库来实现这些功能,比如underscore.jslodash.js等,但是编码的乐趣不就是在于自己解决最本质的问题么。所以即使有这么多的流行库大行其道,也不妨碍我们了解这些知识的初心,说不定哪天你自己也写出一个很火的库呢~
当然随着ES6标准的不断被各大浏览器厂商支持,ES6的普及度越来越广,这些方法都会被内置到原生javascript内部吧(有些方法已经加进去了~)。

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-06-15-babun-casue-ssh-key-badundefined b/2017-06-15-babun-casue-ssh-key-badundefined new file mode 100644 index 00000000..74f66264 --- /dev/null +++ b/2017-06-15-babun-casue-ssh-key-badundefined @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Babun导致本地SSH-KEY不可用 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Babun导致本地SSH-KEY不可用 +

+ + +
+ + + + +
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

ps: 前文有我记录的关于Babun的一些特点,以及使用,请看windows平台下超强的cmd工具Babun使用笔记一文

+

问题描述

如果你本地先安装了git命令行工具并生成了ssh-key,再安装Babun之后,可能会导致原有的ssh-key不可用,原因:
安装Babun会添加全局变量Home,指向Babun安装目录下的.Babun/cymwin/home,因此在使用命令生成key时不会在C:\Users\userName\.ssh目录。

+
1
2
ssh -T git@github.com
Permission denied (publickey).
+ +

解决办法

    +
  • 删掉以前目录(C:\Users\userName\.ssh)下的ssh-key。
  • +
  • 生成重新生成ssh key,此时生成的key在.Babun\cymwin\home\userName\.ssh下。
  • +
  • 把生成的key映射到C:\Users\userName\.ssh目录。
  • +
  • 获取权限
  • +
  • 把key关联到相应github账户(此处以github为例)。
  • +
  • 测试ssh key是否可用
  • +
+
1
2
3
4
5
6
7
8
9
ssh -T git@github.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0670 for '/home/Administrator/.ssh/id_rsa' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/Administrator/.ssh/id_rsa": bad permissions
Permission denied (publickey).
+ +

当生成key之后,测试是否联通,你会发现还是报错了,提示权限不够,错误信息为Permissions 0670

+

在终端切换到C:\Users\userName\.ssh目录,执行下面命令

1
ln -s /c/Users/userName/.ssh /home/userName/.ssh
+ +

此操作会把.Babun\cymwin\home\userName\.ssh目录下的ssh key映射C:\Users\userName\.ssh

+

在终端切换到根目录(~),执行以下命令(一般只执行其中一个)

1
2
3
4
5
chmod 400 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh/id_rsa
ssh -T git@github.com
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+ +

参考文章:

+

1.https://github.com/Babun/Babun/issues/327
2.http://stackoverflow.com/questions/9270734/ssh-permissions-are-too-open-error

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-07-01-learn-git-2undefined b/2017-07-01-learn-git-2undefined new file mode 100644 index 00000000..903adb09 --- /dev/null +++ b/2017-07-01-learn-git-2undefined @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列之常用命令(一) | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列之常用命令(一) +

+ + +
+ + + + +
+

虽然有了git的图形化工具,但个人还是喜欢命令行,并且结合alias功能,也能非常快速的完成很多功能。记录一些项目中常用的git命令,方便查阅

+
+

查看状态

可以查看工作区(edit即add之前)和暂存区(add之后,commit之前)

+
1
git status
+ +

提交到暂存区

1
2
3
4
5
6
//提交某个文件
git add fileName
//提交所有修改文件的三种写法
git add *
git add .
git add --all
+

保存在本地仓库

1
git commit -m "note text"
+ +

提交到远程仓库

其中origin是本地仓库名,remote是远程仓库分支名

+
1
2
3
git push <origin> <remote>
//如提交本覅origin到远程master分支
git push origin master
+ +

分支

查看分支

1
2
3
4
5
6
//查看本地分支
git branch
//查看远程分支
git branch -r
//查看所有分支
git branch -a
+ +

创建分支

1
git branch name
+ +

删除分支

1
2
3
4
5
//删除本地分支
git branch -d name
//删除远程分支两种方法
git push origin :name //冒号不能省
git branch -r -d origin/name
+ +

切换分支

1
git checkout name //如果分支不存在则创建一个名为name的新分支
+ +

合并分支

1
2
3
4
5
6
7
8
//例如:合并分支dev到master
//首先保持dev和master分支最新,即在本地切换到对应分支,各pull一次
//然后切换分支到最终要合并的分支上(此处为master)
git checkout master
//执行本地合并(合并dev到master)
git merge dev
//推送合并到远程
git push origin master
+ +

放弃本地修改强制更新

+

git fetch只是下载远程的库的内容,不做任何的合并;git reset把HEAD指向刚刚下载的最新的版本

+
+
1
2
git fetch --all
git reset --hard origin/master
+ +

回退到某个历史版本

+

首先使用git log命令获取某个历史版本的ID,假设ID是c3470ee7edf566cc359b666d3e27a38220abaf66

+
+
1
2
3
4
//在本地回退到c3470ee7edf566cc359b666d3e27a38220abaf66版本
git reset --hard c3470ee7edf566cc359b666d3e27a38220abaf66
//推送到远程分支,注意:由于本地版本旧于远程仓库版本,这里需要使用-f参数,强制推送
git push -f origin master
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-08-21-nvm-node-version-managerundefined b/2017-08-21-nvm-node-version-managerundefined new file mode 100644 index 00000000..9ccf71b2 --- /dev/null +++ b/2017-08-21-nvm-node-version-managerundefined @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +node和npm版本管理器nvm的安装和使用 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ node和npm版本管理器nvm的安装和使用 +

+ + +
+ + + + +
+

nvm:一个node和npm的版本管理器(node&npm version manager),能让你快速的在不同版本间切换。

+
+

安装

下载地址:官网下载
有两种版本nvm-noinstall.zip(便携版)和nvm-setup.zip(exe安装版)
两者唯一区别就是便携版需要手动配置全局变量,而安装版只需要在安装时选定安装目录则会自动配置好。

+
+

注意:安装之前最好先卸载之前的node

+
+

便携版安装

    +
  • 下载最新版的nvm-noinstall.zip后解压放到D:\devTools(可以放到任意位置,此处是我安装的目录,注意文件夹名不能存在空格);
    1
    2
    3
    4
    5
    elevate.cmd
    elevate.vbs
    install.cmd
    LICENSE
    nvm.exe
  • +
  • 配置nvm,生成settings.txt,填写配置
    方法一:双击install.cmd,会生成settings.txt文件(生成位置就是你输入的地址,一般是在nvm目录下,如果不是,需要拷贝过来)
    方法二:直接在nvm目录下新建settings.txt文件
    1
    2
    3
    4
    5
    6
    root: D:\devTools\nvm
    path: D:\devTools\nodejsv
    arch: 64
    proxy: none
    node_mirror: http://npm.taobao.org/mirrors/node/
    npm_mirror: https://npm.taobao.org/mirrors/npm/
    +
      +
    • root : nvm的存放地址
    • +
    • path : 存放指向node版本的快捷方式,使用nvm的过程中会自动生成。一般写的时候与nvm同级。
    • +
    • arch : 电脑系统是64位就写64,32位就写32
    • +
    • proxy : 代理
    • +
    • node_mirror: node镜像源,安装node时会从此镜像源下载。
    • +
    • npm_mirror: 同上,npm镜像源
    • +
    +
  • +
  • 全局变量配置
    1.添加变量NVM_HOME,值为D:\devTools\nvm
    2.添加变量NVM_SYMLINK,值为D:\devTools\nodejsv
    3.添加变量NVM_HOMENVM_SYMLINK到全局变量path: 修改path的值最后加上;%NVM_HOME%;%NVM_SYMLINK%;
    到此便携版nvm安装完成
  • +
+

exe安装版

直接双击安装,可以使用默认的选项。也可以自己选择安装地址。然后安装过程中会自动把路径写入到全局变量。

+
+

注意: 如果安装了杀毒软件,应该先关闭杀毒软件,因为写入全局变量是一个敏感操作,某些杀毒软件会报警(不关闭,报警时需要选择允许操作)

+
+

使用

版本检测

1
2
3
4
5
$ nvm version
1.1.6
// or
$ nvm v
1.1.6
+ +

安装node&npm

1
2
3
$ nvm install [version]
// 如果安装最新版的,直接使用
$ nvm install latest
+ +

查看安装的node&npm

1
2
3
$ nvm list
* 8.4.0 (Currently using 64-bit executable)
6.9.0
+ +

切换node版本

1
2
$ nvm use [version]
Now using node v8.4.0 (64-bit)
+ +

卸载某个版本node

1
$ nvm uninstall [version]
+ +

nvm命令查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ nvm
Running version 1.1.6.

Usage:

nvm arch : Show if node is running in 32 or 64 bit mode.
nvm install <version> [arch] : The version can be a node.js version or "latest" for the latest stable version.
Optionally specify whether to install the 32 or 64 bit version (defaults to system arch).
Set [arch] to "all" to install 32 AND 64 bit versions.
Add --insecure to the end of this command to bypass SSL validation of the remote download server.
nvm list [available] : List the node.js installations. Type "available" at the end to see what can be installed. Aliased as ls.
nvm on : Enable node.js version management.
nvm off : Disable node.js version management.
nvm proxy [url] : Set a proxy to use for downloads. Leave [url] blank to see the current proxy.
Set [url] to "none" to remove the proxy.
nvm node_mirror [url] : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.
nvm npm_mirror [url] : Set the npm mirror. Defaults to https://github.com/npm/npm/archive/. Leave [url] blank to default url.
nvm uninstall <version> : The version must be a specific version.
nvm use [version] [arch] : Switch to use the specified version. Optionally specify 32/64bit architecture.
nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.
nvm root [path] : Set the directory where nvm should store different versions of node.js.
If <path> is not set, the current root will be displayed.
nvm version : Displays the current running version of nvm for Windows. Aliased as v.
+ +

总结

    +
  • settings.txtrootpath文件路径中不能存在空格,否则在使用nvm use命令时会报错
  • +
  • 在使用nvm use命令时,貌似无法再git-bash中使用,暂时不知道原因,在自带的cmd中可以
  • +
+

最后的最后

+

安装nvm比较简单,喜欢折腾的可以使用便携版,反之这直接使用安装版一键安装。最后大家愉快的玩耍吧

+
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-10-10-fed-testundefined b/2017-10-10-fed-testundefined new file mode 100644 index 00000000..38bd6dee --- /dev/null +++ b/2017-10-10-fed-testundefined @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +前端测试探索 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 前端测试探索 +

+ + +
+ + + + +
+

前端测试是保证代码质量以及程序稳定的一种可靠方式,同时也从代码层面解决了自测难,自测烦等前端测试综合症。当然在前端开发引入测试环节无疑会增加人力和时间成本,如果最终产生的结果和增加的成本能够两两抵消产生正面效应,那么有必要考虑把测试环节加入到团队的规划中。但是在实际开发过程中,测试用例更多的被用于开源项目中,在大部分公司的生成项目很少使用维护测试用例,其根本原因是不纯粹的生产环境导致维护测试用例变得困难,而目的性很强的开源项目则更纯粹,所以前端测试的发展任重而道远。

+
+

Ps:本次更多的是讲述概念性的东西,代码性的具体实例不做过多实践

+
+

GUI(Graphical User Interface)软件测试

+

前端测试不同于后端测试,因为除了一般的逻辑测试以外,由于存在界面交互,所以涉及到模拟用户行为达到测试的目的。由此引入了一个概念:GUI(Graphical User Interface)软件测试,也就是图形用户界面软件测试

+
+

TDD(Test Driven Development) & BDD(Behaviour Driven Development)

TDD很明显的意思是测试驱动开发,也就是说我们可以从测试的角度来检验整个项目。大概的流程是先针对每个功能点抽象出接口代码,然后编写单元测试代码,接下来实现接口,运行单元测试代码,循环此过程,直到整个单元测试都通过。
TDD的好处自然不用多说,它能让你减少程序逻辑方面的错误,尽可能的减少项目中的bug,开始接触编程的时候我们大都有过这样的体验,可能你觉得完成得很完美,自我感觉良好,但是实际测试或者应用的时候才发现里面可能存在一堆bug,或者存在设计问题,或者更严重的逻辑问题,而TDD正好可以帮助我们尽量减少类似事件的发生。
当然,并不是所有的项目都适合TDD,要使用TDD,我认为必须至少具备以下两个条件

+
    +
  • 项目的业务逻辑很清晰,并且程序员对开发逻辑很清晰
  • +
  • 项目模块的复杂度和依赖度不高。如果复杂度高和依赖度高会导致在最开始拆分单元的时候造成很大的困扰,有可能根本不能顺利拆分
  • +
+

BDD行为驱动开发,这里的行为不是指程序员的行为,而是指业务(程序)的逻辑行为,实际上BDD可以看作是对TDD的一种补充,当然你也可以把它看作TDD的一个分支,因为在TDD中,我们并不能完全保证根据设计所编写的测试就是用户所期望的功能

+

如何实现自动化

说一千道一万,新环节的引入必然带来成本的增加,那么我们如何控制增加的成本在合理范围内?很自然的我们想到了使用工具来实现自动化的测试,让机器帮我完成复杂的交互和测试,并自动监控返回错误报警,为我们手动排除问题提供参考

+

可覆盖的测试

+

那到底前端在开发中需要测试哪些东西?在目前技术又可以实现那些测试?

+
+
    +
  • 函数功能测试
      +
    • 全局变量
    • +
    • 公共方法
    • +
    +
  • +
  • 界面&交互测试
      +
    • 事件交互
    • +
    • 数据输入交互
    • +
    • 特征检测
        +
      • 设计图还原度
      • +
      • 图片大小
      • +
      • +
      +
    • +
    • 特殊情况
        +
      • 自适应和响应式测试
      • +
      • 浏览器兼容
      • +
      • 多端测试
      • +
      • +
      +
    • +
    +
  • +
  • 网络请求测试
      +
    • 数据库访问
    • +
    • 模拟用户登陆等
    • +
    • ajax请求
    • +
    +
  • +
  • 直观的错误信息展示
      +
    • 网页表格
    • +
    • 截图
    • +
    +
  • +
  • 性能测试
  • +
  • 回归测试
  • +
  • 自动化
      +
    • 测试用例数据自动化 - 结合mockjs打造假数据
    • +
    • 测试用例自动化创建 - 通过读取源码中的注释来自动生成测试用例?
    • +
    +
  • +
+

业务逻辑/业务代码/测试用例的关系

业务代码的颗粒度与测试用例的复杂度成反比:颗粒度划分越多(细),复杂度越低
业务代码的量与测试用例的量成正比

+

Good

    +
  • 相对于等待问题产生,更倾向于避免可能的问题
  • +
  • 有利于形成团队代码规范,对团队未来成员的扩充是一个很好的约束规范
  • +
  • 对输出的产品有进一步的质量保证
  • +
+

Bad

    +
  • 增加维护测试用例本(时间和人力)
  • +
  • 增加编码复杂度(需要靠如何更友好的进行测试),对团队人员的编码要求提高了
  • +
  • 也许会增加学习成本(并不一定所有人都会写测试用例)
  • +
  • 需要把控测试用例的合理性、覆盖率、通过率
  • +
+

测试框架

PhantomJS/CasperJS

PhantomJS是一个服务器端的支持JavaScript APIWebKit。其支持各种Web标准:DOM处理, CSS选择器, JSON, CanvasSVG。对于web测试、界面、网络捕获、页面自动化访问等等方面。当启动的时候会在内存在开启一个无界面浏览器,以此模拟用户各种操作,可以对界面截图
Casperjs是对PhantomJS的封装,提供了更加易用的API, 增强了测试等方面的支持

+

PhantomCSS

像素对比工具,基于PhantomJs开发,结合了Casperjs截图和ResembleJs图像对比分析

+

Page-monitor

DOM结构对比工具,基于PhantomJS开发,根据DOM结构与样式的对比来对比整个页面的变动部分

+

BackstopJS

主要通过PhantomJScapserJS等工具在不同尺寸下截图,然后根据resemberJS进行像素比对判断是否正常,以实现响应式测试

+

Mocha + Chai

mocha+chai是一个经典的组合主要用来测试函数功能,也能测试异步操作。也有常用chai的超集(拓展库)sion-chai来加强chai

+

Selenium2

Selenium2,它的主要新功能是集成了Selenium1.0以及WebDriver
也就是说Selenium2SeleniumWebDriver两个项目的合并,即Selenium2兼容Selenium,它既支持Selenium API也支持WebDriver APIWebDriver是一个用来进行复杂重复的web自动化测试的工具,意在提供一种比Selenium1.0更简单易学,有利于维护的API。它没有和任何测试框架进行绑定,所以他可以很好的在单元测试中调用。当启动Selenium2时通常会调起一个可见的界面,但也可以通过设置,让它以PhantomJS的形式进行无界面的测试
当然使用Selenium2必须额外的安装每种浏览器的WebDriver
Selenium2上手难度大于PhantomJS

+

NightwatchJs

推特出品,基于Selenium WebDriver API开发,意味着支持浏览器自动化测试,内部集成了mocha+chai并将它加强,同时支持分组测试和单个测试,对语法进行了简化,归纳有以下特点:

+
    +
  • 简单但强大的语法(更符合js书写习惯),只需要使用JavaScriptCSS选择器,开发者就能够非常迅捷地撰写测试。
  • +
  • 开发者也不必初始化其他对象和类,只需要编写测试规范即可。
  • +
  • 使用CSS选择器或Xpath,定位并验证页面中的元素或是执行命令。
  • +
  • 易于扩展,便于开发者根据需要,实现与自己应用相关的命令。
  • +
  • +
+

目前,SeleniumJavaScript的验收测试方面最流行的工具之一,同类的还有PhantomJS。二者都有其独到的方法:Selenium使用WebDriver API,而PhantomJS使用无界面的WebKit浏览器。它们都是非常成熟的工具,都具有强大的社区支持。它们与Nightwatch之间最大的不同,主要是在于语法的简易度以及对持续集成的支持。与Nightwatch相比,SeleniumPhantomJS都拥有更加冗长的语法,这会让编码变得更庞大

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
this.demoTestGoogle = function (browser) {
browser
.url(“http://www.google.com”)
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'The Night Watch')
.end();
};

//也可以
module.exports = {
'step one' : function (browser) {
browser
.url('http://www.google.com')
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
},

'step two' : function (browser) {
browser
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'Night Watch')
.end();
}
};
+ +

对前端框架的支持

在实际开发中,我们可能是用了不同的框架。虽然我们完全可以在把源码编译成普通的HTML/CSS/JS代码然后测试,但是此种方法的弊端也显而易见:不易于自动化,必须等到所有模块开发完成才能测试…为此我们必须寻找某种方式使得测试不收框架的限制

+

Vue

本身可以通过new一个Vue的方式挂载节点达到效果。
下面是一个简单的测试用例,测试.hello h1标签内容是否符合预期

+
1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {
it('should render correct contents', () => {
const Constructor = Vue.extend(HelloWorld)
const vm = new Constructor().$mount()
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App')
})
})
+ +

React

1.官方提供了两种方法:

+
    +
  • 渲染虚拟DOMShallow Rendering
  • +
+

只渲染第一层,不渲染子组件,速度快,返回一个浅渲染的虚拟DOM对象。然后拿到节点的各种信息,进行测试

+
    +
  • 渲染真实DOM节点(renderIntoDocument
  • +
+

renderIntoDocument 方法要求存在一个真实的DOM环境,否则会报错。因此,测试用例之中,DOM环境(即window, documentnavigator 对象)必须是存在的。jsdom库提供这项功能

+
1
2
3
4
5
6
7
import jsdom from 'jsdom';

if (typeof document === 'undefined') {
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = global.window.navigator;
}
+ +

2.Enzyme
Enzyme是官方测试工具库的封装,它模拟了jQueryAPI,非常直观,易于使用和学习,主要提供三种方法:

+
    +
  • shallow
  • +
+

shallow方法就是官方的shallow rendering的封装

+
1
2
3
4
5
6
7
8
import {shallow} from 'enzyme';

describe('Enzyme Shallow', function () {
it('App\'s title should be Todos', function () {
let app = shallow(<App/>);
expect(app.find('h1').text()).to.equal('Todos');
});
};
+ +
    +
  • render
  • +
+

render方法将React组件渲染成静态的HTML字符串,然后分析这段HTML代码的结构,返回一个对象。它跟shallow方法非常像,主要的不同是采用了第三方HTML解析库Cheerio,它返回的是一个Cheerio实例对象。

+
    +
  • mount
  • +
+

mount方法用于将React组件加载为真实DOM节点

+

最后

回到开始,个人认为不要滥用测试,需要合理评估测试用例对团队项目的积极作用和消极作用。不合理或者不恰当的使用测试只会增加工作复杂度和成本。
并且测试用例只是检查代码的工具,所以不要本末倒置以测试用例强行约束业务代码

+

参考

    +
  • 关于TDD、BDD和DDD的一些看法
  • +
  • 虚拟座谈会:代码测试比率、测试驱动开发及行为驱动开发
  • +
  • Mocha
  • +
  • PhantomJS
  • +
  • NightwatchJs
  • +
  • 前端自动化测试探索
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-10-10-mocha+chaiundefined b/2017-10-10-mocha+chaiundefined new file mode 100644 index 00000000..8c7eec62 --- /dev/null +++ b/2017-10-10-mocha+chaiundefined @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +mocha+chai使用记录 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ mocha+chai使用记录 +

+ + +
+ + + + +
+

mocha+chai是比较流行的测试框架‘套装’,能实现一些基本的前端测试如函数功能测试,模拟登陆,异步测试,流程测试等

+
+

官方文档

mocha

chai

基本用法

+

mocha是一个测试工具库,它只纯粹对测试行为(过程)进行描述;而chai是一个断言(推断)库,它可以将测试结果进行各种判断,以此推断是否符合预期,因此两者常常进行组合使用

+
+

安装

1
2
//全局安装
$ npm install -g mocha
+ +

ps: 全局安装之后,mocha命令将会在全局注册,可以在任何地方使用mocha命令

+
1
2
//安装项目依赖
$ npm install mocha chai
+ +

使用

目录结构

1
2
3
4
5
6
7
8
9
├── test //测试用例
│   ├── hooks.js //生命钩子
│   ├── test.js //入口文件
│   └── unit //测试单例
│   ├── add.js
│   └── ...
└── src //业务代码
   ├── add.js
   └── ...
+ +

所有测试代码都在test目录,所有的业务代码都在src目录

+

一个简单的例子

1
2
3
4
5
6
//src/add.js
function add(a, b){
return a + b;
}
module.exports = add;

+ +
1
2
3
4
5
6
7
8
9
//test/unit/add.js
var add = require('../../src/add.js');
var expect = require('chai').expect;

describe('加法函数', function () {
it('1 + 3 = 4', function () {
expect(add(1, 3)).to.be.equal(4);
});
});
+ +
1
2
3
4
$ mocha mocha/unit/add
加法函数
√ 1 + 3 = 4
1 passing (16ms)
+ +

Expect/Should/Assert

The Expect / Should API covers the BDD assertion styles.
+The Assert API covers the TDD assertion style.
+
+

异步

    +
  • promise异步一定要带上done(),用于通知mocha该测试已经完成
  • +
  • 异步通常和参数-t结合一起用
  • +
+

通配符

1
2
3
4
//test/unit目录下add.js和minus.js
$ mocha test/unit/{add,minus}.js
//test/unit目录下所有js后缀的文件
$ mocha test/unit/*.js
+ +

mocha支持shellnode的通配符匹配规则,更多通配符规则可以查看各自的文档

+

命令行参数与配置文件mocha.opts

常用的命令行

    +
  • –recursive
  • +
+

Mocha默认不对指定目录的子级目录匹配,如果需要使自己目录的测试用例运行,则需要加上--recursive

+
    +
  • –reporter
  • +
+

输出报告的格式,默认是--reporter spec,可以用--reporters命令查看有哪些输出格式

+
    +
  • –watch
  • +
+

监听变化,每次修改自动执行test

+
    +
  • –timeout -t
  • +
+

Mocha默认每个测试用例最多执行2000毫秒,如果到时没有得到结果,就报错。对于涉及异步操作的测试用例,这个时间往往是不够的,需要用-t--timeout参数指定超时门槛

+
    +
  • –hlep,-h
  • +
+

查看有哪些命令
mocha.opts放在test目录下,执行mocha命令时回去读取里面的配置
命令行参数可以写在mocha.opts文件内,如

+
1
$ mocha --reporter tap --recursive -t 3000
+ +

等价于
test/mocha.opts文件内容

+
1
2
3
--reporter tap
--recursive
-t 13000
+ +
1
$ mocha
+ +

hooks(钩子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
describe('hooks', function() {

before(function() {
// 在本区块的所有测试用例之前执行
});

after(function() {
// 在本区块的所有测试用例之后执行
});

beforeEach(function() {
// 在本区块的每个测试用例之前执行
});

afterEach(function() {
// 在本区块的每个测试用例之后执行
});

// test cases
});
+ +

可以写在测试用例内(每个describe块内),此时只对当前测试用例有效
也可以写在外部,此时对所有的测试用例有效

+

注意

    +
  • 内置promise对象
  • +
  • ES6需要转码
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-10-16-desktop-notificationundefined b/2017-10-16-desktop-notificationundefined new file mode 100644 index 00000000..0c7dd0b6 --- /dev/null +++ b/2017-10-16-desktop-notificationundefined @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +H5的Notification特性 - Web的桌面通知功能 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ H5的Notification特性 - Web的桌面通知功能 +

+ + +
+ + + + +
+

目前,web网页使用桌面通知功能的越来越多,包括微博,腾讯视频等大厂站,桌面通知功能是H5的一个API - Notifications。它允许网页或应用程序可以发出通知,通知将被显示在页面之外的系统层面上(通常使用操作系统的标准通知机制,但是在不同的平台和浏览器上的表现会有差异),这样即使应用程序空闲或在后台也可以向用户发送信息。

+
+

应用场景

Notifications的诞生简化了网站或者应用与用户之间的沟通成本(时间成本和开发成本),增强用户黏性(减少了用户离开应用的可能)。传统的通知方式,大多是通过站内信(消息),邮件,短信等方式,它们通常需要刷新(跳转)页面、离开应用打开其他应用或终端来查看消息;而桌面通知功能大大的简化了这个过程,消息的传递基本不消耗时间(如果不设置setTimeout,用时基本不会超过1s),并且用户不需要离开应用,这都带来了极大的方便。可以预见,Notifications将会在很多网页或应用中被大量使用。当然Notifications也具有它的局限性:无法存档、即看即毁
那么,这个功能到底能用在哪些场景呢?只能说能应用的场景很多:

+
    +
  • 社交类网站
  • +
  • 资讯类网站
  • +
  • 网页版邮件服务
  • +
  • 即时通知类网站
  • +
  • +
+

举个例子,当你打开微博页面,你可能会看到(使用新版浏览器)如下图的通知:
desktop-notification1

+

这就是网站使用了桌面通知功能,当你选择允许,那么当网站有推送消息或者你登陆账号有新的消息将会在桌面的右下角出现一个小弹窗通知,如下:
desktop-notification2

+

感觉有点酷酷的!!!

+

用户权限 - Notification.permission

Notification.permission是一个静态方法,可以获取用户当前的通知权限状态,返回一个String,可以根据返回值判断用户是否授予了通知权限。返回值有三种情况:

+
    +
  • default
      +
    • 用户还未被询问是否授权,所以通知不会被显示。
    • +
    +
  • +
  • granted
      +
    • 表示之前已经询问过用户,并且用户已经授予了显示通知的权限。
    • +
    +
  • +
  • denied
      +
    • 用户已经明确的拒绝了显示通知的权限。
    • +
    +
  • +
+

当值为default或者denied时都不会显示通知消息,只有明确的被设置成granted才会显示通知消息

+
1
2
3
4
5
6
const permission = Notification.permission;
if(permission === 'granted'){
console.log('已经授权通知,可以进行你的通知啦!');
}else{
console.log('用户还未授权,请先授权!');
}
+ +

请求权限 - Notification.requestPermission(CALLBACK)

应用发送通知之前必须要取得发送通知的权限,才能成功进行通知。Notification.requestPermission(CALLBACK)是请求获取权限的方法(有点类似javascriptconfirm弹窗窗),允许传入一个回调,回调会返回用户选择的何种权限,返回两个值,granted代表允许,denied代表拒绝。并且Notification.requestPermission()支持then方式的链式调用,也就意味着可以异步调用它。

+
1
2
3
4
5
6
7
Notification.requestPermission(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
//两种方式是等价的
Notification.requestPermission().then(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
+ +

创建通知 - new Notification(TITLE, OPTIONS)

new Notification(TITLE, OPTIONS)方法创建可以创建一个通知实例,允许参入参数两个参数TITLEOPTIONS。注意默认情况下(实际可以通过OPTIONS中的timestamp参数控制)一旦通知实例被创建出来,它会立即被显示出来。

+

TITLE参数

TITLE表示通知的标题。必须参数,允许数字、字符串和空

+

OPTIONS参数

OPTIONS是非必须参数,必须为一个对象,它包含:
ps: 部分参数在某些浏览器可能会不生效,建议使用最新版的谷歌浏览器。以下某些内容从Notification-MDN-EN结合谷歌翻译得来,很有可能翻译不准确,如有,请提出。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
//通知显示正文。非必须,默认为空
body: '你的好友XX上线了!',
//通知显示正文的图片地址。非必须,默认为空
image: 'imgae url',
//通知左侧图标。非必须,默认为空
icon: 'imgae url',
//通知的分类标记(ID)。非必须,默认为空
tag: 'test',
//通知相关联的数据,通常用于方法的回调,传参。非必须,默认为空
data: '可以是任意数据类型',
//通知显示延迟的时间。非必须,默认通知实例创建完成就显示
timestamp: '',
//通知主体内容的水平展示顺序,有点类似direction属性。非必须,默认值是auto, 可以是ltr或rtl
dir: 'auto',
//当没有足够的空间来显示通知本身时,用于表示通知的图像的URL。非必须,默认为空
badge: 'xxx',
//通知的语言。非必须默认为空
lang: '',
//通知显示时,设备的振动模式。非必须,默认为空
vibrate: [200, 100, 200],
//新通知出现是否覆盖旧的通知,覆盖(true)则永远只显示一条通知,不覆盖(false)则会多条通知重叠。非必须,默认为true
renotify: true,
//通知是否静音。非必须,默认为false,表示无声
silent: false,
//通知声源文件地址。非必须,默认为空
sound: 'mp3',
//是否不在屏幕上显示通知信息。非必须,默认为false表示要显示
noscreen: false,
//指定通知是否应该粘滞性,即不容易被用户清理。非必须,默认false表示不具粘滞性
sticky: false,
//指定通知是否保持活性,知道用户点击或关闭。非必须,默认为false
requireInteraction: false
}
+ +

事件及事件钩子

当通知被创建成功后:

+
    +
  • 通知实例具有一个静态方法可以用来关闭通知
  • +
  • 通知实例具有四个事件钩子,来跟踪通知当前的状态。这些事件可以通过事件处理跟踪onshowonclickoncloseonerror。因为Notification同样继承自EventTarget,因此可以对它调用addEventListener()方法。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const n = new Notification('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
},
timestamp: 3000
});

n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
+ +

demo

写一个简单的例子,可以打开页面体验一下,建议用最新版谷歌浏览器打开~ Notification.js

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const NotificationInstance = Notification || window.Notification;
if (!!NotificationInstance) {
const permissionNow = NotificationInstance.permission;
if (permissionNow === 'granted') {//允许通知
CreatNotification();
} else if (permissionNow === 'denied') {
console.log('用户拒绝了你!!!');
} else {
setPermission();
}
function setPermission() {
//请求获取通知权限
NotificationInstance.requestPermission(function (PERMISSION) {
if (PERMISSION === 'granted') {
CreatNotification();
} else {
console.log('用户无情残忍的拒绝了你!!!');
}
});
}
function CreatNotification() {
const n = new NotificationInstance('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
}
});
n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
setTimeout(() => {
n.close();
}, 2000);
}
}
+ +

兼容

+ +

参考

    +
  • Notification-MDN-EN
  • +
  • Notification-MDN-CN
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-10-27-for-in-and-for-ofundefined b/2017-10-27-for-in-and-for-ofundefined new file mode 100644 index 00000000..5af3ef91 --- /dev/null +++ b/2017-10-27-for-in-and-for-ofundefined @@ -0,0 +1,492 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +慎重用for...in与for...of | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 慎重用for...in与for...of +

+ + +
+ + + + +
+

for...infor...of都是用于数据的遍历。for...inES5标准,用于遍历对象属性(键),而for...ofES6标准,是对for...in的修正,用于遍历对象元素(值),for...of兼容性不是很好(除了PC端老顽固IE之外,移动端某些安卓机和浏览器也是不支持它,具体可以查看MDN)。

+
+

for…in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i in arr) {
console.log(i); // "0", "1", "2", "msg", "arrCustom", "objCustom"
}

for (var i in arr) {
if (arr.hasOwnProperty(i)) {
console.log(i); // "0", "1", "2", "msg"
}
}
for (var i in obj) {
console.log(i); // "name", "w", "msg", "objCustom"
}

for (var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i); // "name", "w", "msg"
}
}
+ +

由上面的例子可以看出,for...in的一些特性:

+
    +
  • 可以对JSON对象(数组和对象)进行遍历
  • +
  • for...in会遍历对象的所有可枚举属性,包括原型,例如一些我们挂载到原型链上的一些methodname
  • +
  • 遍历很有可能不是按照对象的内部顺序(我们预期的)进行
  • +
  • 对数组遍历时index索引为字符串型,在某些时候直接进行几何运算可能达不到预期结果
  • +
+

for…of

for...in貌似强大的同时也带来很多副作用,想要达到预期的记过需要额外的代码来处理,所以for...of应运而生

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i of arr) {
console.log(i); // 2, 9, 5
}
//如果用for...of循环对象,会报错`obj is not iterable`
for (var i of obj) {
console.log(i);
}
+ +

可以看出,for...of方法在for...in上做了优化,并且限制了只能遍历数组。当然在ES5中,具有遍历数组功能的还有mapfiltersomeeveryreducereduceRight等,但是需要注意的是,有些方法不能被break句柄打断循环,使用retun也不能返回到外层,如forEach

+

其实不难看出for...in是属于鸡肋属性了,而for...of由于兼容性原因,在某些地方也应该慎用,即使是移动端也要慎用,应该它并不兼容所有内核。

+

参考

for…in
for…of
for-of循环是遍历实现iterator接口的成员

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-11-02-how-to-make-a-kalendarundefined b/2017-11-02-how-to-make-a-kalendarundefined new file mode 100644 index 00000000..38a063a6 --- /dev/null +++ b/2017-11-02-how-to-make-a-kalendarundefined @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何写一个日历组件 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 如何写一个日历组件 +

+ + +
+ + + + +
+

众所周知,虽然javascript中关于时间的API有不少,我们可以通过方法单独的获取年、月、日、时、分、秒、毫秒…貌似很多,最近写了一个日历(以前写的,但写得很烂,最近优化一下),所以下面简单的记录一下如何写一个日历,列出了一些我在写日历过程中自己封装的一些方法

+
+

效果图

先来一张效果图,由于没有UI设计,所以就自己简单的设计了一个样式(好歹我也是设计专业的,虽然已不做设计很多年),虽然略丑,但重要的是功能!!!

+

datepicker

+

思路

一个日历到底是怎样用代码生成的?其实观察一下现有的日历展现形式,可以很快的形成思路,就是:根据计算把日期号数对应到正确的星期几上,并按照顺序逐一输出。
以下是我的思路:

+
    +
  • 取得月份的天数
  • +
  • 取得月份第一天是星期几
  • +
  • 循环对应号数和星期几返回一个数组对象
      +
    • 返回数组对象的每一个子项至少包含:号数,星期几,然后根据情况添加:是否高亮,是否当前月,是否节日…等属性
    • +
    +
  • +
+

方法封装

注意,为了保持方便调用javascript的方法,以及保持输出结果符合实际,所有的方法都有如下约定:

+
    +
  • 在计算过程中
      +
    • 所有的关于月份都是0~11的数字
    • +
    • 所有的关于星期都是0~6的数字
    • +
    +
  • +
  • 在输出的结果中
      +
    • 所有关于月份的输出默认都是1-12的数字
    • +
    • 所有关于星期的输出默认都是1-7的数字
    • +
    +
  • +
+

所以在向调用方法传递参数过程中,月份以及星期几统统都需要按照实际月份减一

+

获取月份天数

javascript中没有直接获取月份天数的方法,但是它提供了一个getDate方法可以获取日期的某一天。那我们只需要获取月份的最后一天(下一个月的第0天)就可以得知这个月的天数:

+
1
2
3
4
5
6
7
8
9
// year是要获取的年份,闰年不一样
// month是要获取的月份
// 返回当前月天数
function getMonthDays(year, month){
return new Date(year, month + 1, 0).getDate();
}

getMonthDays(2016,2) //29
getMonthDays(2017,2) //28
+ +

获取星期几

1
2
3
4
5
6
7
8
9
10
// year是要获取的年份
// month是要获取的月份
// 返回数字几则是星期几
function getWeekday(year, month, day){
return new Date(year, month, day).getDate() + 1;
}

getWeekday(2016,10,9) //输出4,表示2016年11月9是星期4
getWeekday(2017,10,9) //输出5,表示2017年11月9是星期5

+ +

获取月份有几个星期

要计算月份包含几个星期,需要两个数据:月份天数和月份第一天是星期几,就能得到想要的结果

+
1
2
3
4
5
6
7
8
9
// year是要获取的年份
// month是要获取的月份
// 返回当前月包含几个星期
function getweeksInMonth(year, month){

var days = getMonthDays(year, month);
var FirstDayWeekday = getWeekday(year, month, 1);
return Math.ceil(days + FirstDayWeekday);
}
+ +

循环生成月份对象

有了以上方法之后,就可以通过循环生成一个简单的月份对象了。
在这里需要注意,日历的排序有两种:

+
    +
  • 每一行以星期日开头
  • +
  • 每一行以星期开头
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// year是要获取的年份
// month是要获取的月份
// day天,用来判断是否是当前天
// type表明要星期几开头,0为星期一开头,1为星期日开头,默认为0
// 返回当前月包含几个星期

const WEEKTABLE = [{
cn: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
cns: ['日', '一', '二', '三', '四', '五', '六'],
en: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
},{
cn: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
cns: ['一', '二', '三', '四', '五', '六', '日'],
en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
}]

getMonthDaysArray(year, month, day, type) {
if (typeof day === 'undefined' && year === YEAR && month === MONTH) day = DAY;

var dayArrays = [];
var days = this.getMonthDays(year, month), preDays = this.getMonthDays(year, month - 1);
var thisMonthFirstDayInWeek = this.getWeekday(year, month, 1), thisMonthLastDayInWeek = this.getWeekday(year, month, days);

type = !type || type !== 1 ? 0 : 1;

//上月在当月日历面板中的排列
for (var i = 0; i < thisMonthFirstDayInWeek; i++) {
dayArrays.push({
dayNum: (preDays - thisMonthFirstDayInWeek + i + 1),
weekDay: WEEKTABLE[type].cn[i]
})
}
//当月日历面板中的排列
for (var i = 1; i <= days; i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag],
selected: i === +day,
isThisMonth: true
})
};
//下月在当月日历面板中的排列
for (var i = 1; i <= (6 - thisMonthLastDayInWeek); i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + days + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag]
})
};
return dayArrays;
}
+ +

格式化时间

涉及到时间时,常常需要把时间格式进行转换,为了应对多中需求,所以自己封装了一个

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 参数fmt必须
// date参数不必须,允许字符串和时间对象,不传或者传无法转换成合法时间对象的字符串则默认当前时间,
// 年(YYYY/yyyy)固定四个占位符
// 月(M)、日(d)、小时(h)、分(m)、秒(s)可以用 1-2个占位符,严格区分大小写,
// 毫秒(ms/mss)最多三个占位符,分别对应56,056这种类型
// 例子:
// (Format("yyyy-MM-dd hh:mm:ss:ms") ==> 2006-07-02 08:09:04:23
// (Format("yyyy-MM-dd hh:mm:ss:mss") ==> 2006-07-02 08:09:04:023
// (Format("yyyy-M-d h:m:s:ms") ==> 2006-7-2 8:9:4.180
function formate(fmt, date){
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
var _rules = [{
rule: '[yY]{4}',
value: _date.getFullYear()
}, {
rule: 'M+',
value: _date.getMonth() + 1
}, {
rule: '[dD]+',
value: _date.getDate()
}, {
rule: 'h+',
value: _date.getHours()
}, {
rule: 'm+',
value: _date.getMinutes()
}, {
rule: 's+',
value: _date.getSeconds()
}, {
rule: 'ms{1,2}',
value: _date.getMilliseconds()
}];

_rules.forEach(function (_r){
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join().substr(rLen);
});
});
return fmt;
}
//调用:
var time1 = formate("YYYY/MM/DD hh:mm:ss", new Date()); //2017/11/2 11:09:20
var time2 = formate("YYYY-MM-DD", time1); //2017-11-2
var time3 = formate("MM-DD-YYYY", time2); //11-2-2017
+ +

最后

附上这些方法的源码datepicker
基于vue实现的一个日历:

+
    +
  • demovue-datepicker
  • +
  • 源码datePickerPanel.vue
  • +
+

当然这只是最简单的日历输出,思路也是超级简单(感觉有点Low),如果有大神愿意分享它的经验欢迎,来邮~

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-11-15-vue-style-scopedundefined b/2017-11-15-vue-style-scopedundefined new file mode 100644 index 00000000..b47bc3ac --- /dev/null +++ b/2017-11-15-vue-style-scopedundefined @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +vue中慎用style的scoped属性 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ vue中慎用style的scoped属性 +

+ + +
+ + + + +
+

在vue组件中,在style标签上添加scoped属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的,这是一个非常好的机制。但是为什么要慎用呢?在实际业务中我们往往会对公共组件样式做细微的调整,如果添加了scoped属性,那么样式将会变得不易修改。初写这篇文章时,本人没有找到一个好的方法去解决这个问题,后来经过大伙的解答,才让我恍然大悟。

+
+

何为谨慎使用

谨慎使用不是不用,而是持一种审视的目光去看待它。scoped肯定是解决了样式私有化的问题,但同时也引入了新的问题—样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的。所以我才说要谨慎使用

+

解决方案

首先要说明的问题是,最开始我以为这是一个BUG或者说一个弊端(因为当时没有搞明白scoped的真正作用),就很英勇的去提了一个issue,然后理所当然的被关闭了,关闭的理由是:scoped设计的初衷就是让样式变得私有,让它不会影响其他任何地方的样式。但是由于我在业务中经常遇到需要修改有scoped属性的组件,就写了一篇文章记录一下这个问题,希望大家谨慎的使用这个属性。
然而事实再一次证明了我的愚蠢,在vue-loader的文档中已经详细的对这个问题做了分析,并且对我遇到这种问题给出了解决方法:vue-loader的深度作用选择器。
因为我并没有去深入了解这些问题,所以注定这篇文章被大伙拍砖,😂😂😂😂😂😂

+

解决方案:vue-loader之scoped-css

+

鉴于此,虽然这篇文章没有什么价值,但为了提醒我自己深究的意义,我对后面的内容做了保留,以下内容是最开始文章的原文,请大家忽略,上面的内容才是正文,没错,正文就是这么少。

+ +

——————————–正文分割线,以下是无营养的内容——————————–

+
+

scoped实现私有化样式的原理

为什么会说,会增加复杂度?那么我们先从的实现模块的原理说起。为了方便称呼,我们假设把这种组件叫做模块私有组件,其他的未加scoped的叫做模块一般组件
通过查看DOM结构发现:vue通过在DOM结构以及css样式上加唯一不重复的标记,以保证唯一,达到样式私有化模块化的目的。具体的渲染结果是怎样的,通过一个例子来说明。

+

公共组件button组件

一个公共组件button,为了样式模块化,给其加上scoped属性,

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//button.vue
<template>
<div class="button-warp">
<button class="button">text</button>
</div>
</template>
...
<style scoped>
.button-warp{
display:inline-block;
}
.button{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
</style>
+ +

浏览器渲染button组件

button组件在浏览器渲染出的html部分和css部分分别为:

+
1
2
3
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
+ +
1
2
3
4
5
6
7
8
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
+ +

从上面的字可以看出,添加了scoped属性的组件,为了达到组件样式模块化,做了两个处理:

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
+

大家都知道css样式有一个优先级的说法,scoped的这一操作,虽然达到了组件样式模块化的目的,但是会造成一种后果:每个样式的权重加重了:理论上我们要去修改这个样式,需要更高的权重去覆盖这个样式。这是增加复杂度的其中一个维度。

+

其他组件引用button组件

上面分析了单个组件渲染后的结果,那么组件互相调用之后会出现什么样的结果呢?,具体分两种情况:模块一般组件引用模块私有组件(本质和模块私有组件引用模块一般组件一样);模块私有组件引用模块私有组件。

+

举个例子:在组件content.vue中使用了button组件,那么content.vue组件是否添加scoped属性渲染出来的结果有什么区别呢?

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style>
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
</style>
+ +

模块一般组件(未添加scoped)引用模块私有组件

如果style上没有加scoped属性,那么渲染出来htmlcss分别就是:

+
1
2
3
4
5
6
7
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
+ +

可以看出,虽然在content组件中,修改了buttonborder-raduis属性,但是由于权重关系,生效的依然是组件内部的样式(此时是外部的样式被覆盖)。所以如果要达到修改样式的目的,就必须加重我们要修改样式的权重(增加选择器层级,ID选择器,并列选择器,impotant等)

+

模块私有组件(添加scoped)引用模块私有组件

如果加了scoped属性呢?按照开始分析出来的规则(事实也是这么的):
首先是在所有的DOM节点加上data属性
然后在css选择器尾部加上data属性选择器

+

那么渲染出来htmlcss分别就是:

+
1
2
3
4
5
6
7
<div data-v-57bc25a0 class="content">
<p data-v-57bc25a0 class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-57bc25a0 data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content[data-v-57bc25a0]{
width: 1200px;
margin: 0 auto;
}
.content .button[data-v-57bc25a0]{
border-raduis: 5px;
}
+ +

对于上面的两种情况,可以明显看出来渲染后的结果大不相同。
虽然我们在content添加了想要修改button组件的样式的代码,但是仔细看,由于.content .button这句在末尾加的是content组件的scoped标记,最后这句其实根本作用不到我们想要的DOM节点上,所以这种情况我们在content内部写的任何样式都不会影响到button.vue组件,所以这就尴尬了。。。。
当然这个问题也是可以解决的,就是直接加全局样式可以修改到,但这势必会影响全部地方的组件;所以需要另外一种方法在content.vue组件内再加一个不带scoped属性的style标签,也就意味着要加两个style,一个用于私有样式,一个用于共有样式。这肯定是有点shit的,并且这两种解决方案都回避不了一个问题:权重!!!

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style scoped>
.content{
width: 1200px;
margin: 0 auto;
}
</style>
<style>
.content .button{
border-raduis: 5px;
}
</style>
+ +

这样符合规范么?貌似没看到不能这么写,并且这么写也确实生效了。。。但这样确实增加了思维的复杂度,有点苦恼啊。

+

总结scoped的渲染规则

总结一下scoped三条渲染规则

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
  • 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性
  • +
+

解决方案

对于引用的三方库,如果对方使用了scoped,我们无力改变什么,如果确实需要修改他的样式最能在不加scoped的组件中修改样式,或者全局样式直接修改,这很粗暴!
对于自己维护的组件,一定要想清楚,组件的样式能否满足所有的情况。如果确实需要加,无疑会增加使用这个组件的开发同学的工作!

+

当然对于这个问题,如果诸君有更好的解决方案,请诸君TELL ME一下下!

+

趣事

在使用scoped一定要谨慎scoped的这个特性,本人以为这是一个BUG,就去提了issue ,结果尤大很霸气的回复
scoped设计的初衷就是不能让当前组件的样式修改其他任何地方的样式,因为设计如此。所以理所当然的这个issue已被干掉。。。😂😂😂😂😂😂

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-11-24-regex-to-somethingundefined b/2017-11-24-regex-to-somethingundefined new file mode 100644 index 00000000..2e6ef361 --- /dev/null +++ b/2017-11-24-regex-to-somethingundefined @@ -0,0 +1,566 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +一道小小的题目引发对javascript支持正则表达式相关方法的探讨 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 一道小小的题目引发对javascript支持正则表达式相关方法的探讨 +

+ + +
+ + + + +
+

以前对于正则是非常惧怕的,因为看不懂和学不会。但最近项目中频繁的使用到了正则,因此强迫自己去学习了解,慢慢的体会到了他的魅力与强大。当然学习正则初入门的时候有些枯燥难懂,但越学越觉得轻松。本文不准备说关于正则本身的事儿,而是说一说关于javascript中关于正则的几个方法中被很多人忽略的地方。

+
+

工具

说到正则,很多人都是从抄到改到自己写,这个过程可能有时候很漫长。如一些工具能帮助你快速分析和学习正则,那么学习的过程你肯定要轻松得多。下面我推荐两个我经常使用的正则在线可视化工具,正则可视化工具图解符合铁路图规律(其实不明白什么是铁路一样很容易看懂,只是一些细微的地方和我们的常规思维有点差别)。

+
    +
  • regexper 我最常用的一个,个人觉得UI做得比其他好
  • +
  • regulex 备选,他有一个很舒心的功能,可以提供一段js,嵌套到你的网站,生成正则可视化图
  • +
+

一道小小的题目

这道题目是在群里日常闲聊时,公司同事抛出来的,具体是出自哪里本人没去考察。先先说说题目:

+
+

写一个方法使得数字末尾的连续0变成9,如1230000变成1239999

+
+

一道很简单的题目,直接正则就能搞定,也许你会写:

+
1
2
3
4
5
6
7
function zoreToNine(num){
return (num + '').replace(/0/g,9);
}
//或者
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,9);
}
+ +

这也是此题的陷阱所在,按照上面的方法,1023000就会被转化成1923999,这样是不符合要求的,所以改进一下:

+
1
2
3
4
5
6
7
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,function($1){
return $1.replace(/0/g,9);
});
}
zoreToNine(1223000); //1223999
zoreToNine(1023000); //1023999
+ +

关于这个问题的解决方案@微醺岁月同学提供了一种,位置匹配的方法,简单了很多,厉害!

+
1
"12300100000".replace(/0(?=(0+$)|\b)/g,9); //12300199999
+ +

当然解决问题的方法很多,不一定非要用正则,还完全可以使用纯算术的方法实现,大家有兴趣可以尝试,闲话少说进入这次的主题:javascript支持正则表达式相关方法,注意并不是正则对象的方法。
上述方法使用了正则,有趣的是在回调函数里有一个$1,这个$1到底是什么?所有的匹配规则匹配后都有$1这个变量么?…一连串的问题,以前我从来没有去追探过,趁着昨个比较空闲,去追探了一番,并在今天整理了一下,写下此文记录。

+

主角

javascript中正则对象有三个方法:testexeccompile,但是此次的主角并不是它们!我们讨论的是能够使用正则表示的相关方法:searchmatchreplacesplit,注意它们都是String对象的方法,使用它们必须要是String类型.

+

replace(rule[regexp/substr], replacement)

replace是一个用于替换字符串的方法,虽然看似简单,但是它隐藏的机关也是常常被人忽略。具体分析一下它的特点:
它接收两个参数
无副作用不影响原始变量
返回被改变的字符串(一定是字符串类型)

+

定义一些变量,方便全文取用。

+
1
let a = '12309800', b = '12309800[object Object]', b = '12309800{}';
+ +

参数rule

在一般情况,rule参数一般是正则、字符串、数字。
如果是字符串,将会在匹配到第一个符合条件的目标,结束方法;
如果是正则,则按照正则的规则进行匹配

+
1
2
3
4
//匹配第一个0替换成5
a.replace(0,5); //'12359800'
//匹配所有的0替换成5
a.replace(/0/g,5); //'12359855'
+ +

参数replacement

在一般情况,replacement参数是字符串、数字、者回调。

+

包含$的字符串

当参数rule为正则,并且正则至少包含有一对完整的()时,如果replacement包含有$的字符串,那么对于$n(n为大于0的整数,n的长度取决于正则中括号的对数),会被解析成一个变量。但是也仅仅只是作为一个变量,无法在字符串中进行计算,此时更类似特别的字符串模板变量。

+

一般情况下,$n中n的长度取决于正则中括号的对数,$1表示第1对括号匹配的结果,$2表示第2对匹配的结果…在正则所有的括号对中,左括号出现在第几个位置(或者说从左往右),则它就是第几对括号,以此类推。姑且我们把这种规则成为正则匹配分割规则(ps:这完全是我自己取的一个名字,方便文章后面使用和记忆)。

+
1
2
3
4
5
6
a.replace(0,'$0'); //'123$09800'
a.replace(/00/g,'$0'); //'123098$0'
a.replace(/[1-9]0+$/,'$1'); //'12309$1'
a.replace(/([1-9](0+$))/,'$1'); //'12309800',此时$1为[1-9](0+$)匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1'); //'123098',此时$1为[1-9]匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1*$2'); //'123098*00',此处的$1和$2不会安照期待的情况进行乘法计算,要进行计算可以用回调
+ +
+

请注意:虽然目前参数replacement中携带有$n仍然能正常使用,但是这种方式已经不被规范所推荐,更应该使用回调来完成这个操作。这一点谢谢@lucky4同学的指出

+
+

如果正则中包含有全局匹配标志(g),那么每次匹配的都符合上述规则

+

回调函数

先看例子:

+
1
2
3
4
5
6
7
8
9
10
11
12
a.replace(/[1-9]0+$/,function(){
console.log(arguments); //["800",5,"12309800"]、
});
a.replace(/([1-9])0+$/,function(){
console.log(arguments); //["800","8",5,"12309800"]
});
a.replace(/([1-9])(0+$)/,function(){
console.log(arguments); //["800","8","00",5,"12309800"]
});
a.replace(/(([1-9])(0+$))/,function(){
console.log(arguments); //["800","800","8","00",5,"12309800"]
});
+ +

回调函数的arguments数组部分组成:[完整匹配的字符串,$1,$2,…,$n,匹配的开始位置,原始字符串],$1...$n表示每个括号对的匹配,规则和前面的相同。
所以有一下规律:

+
1
2
3
4
5
let arr = [...arguments], len = arr.length;
(len >= 3) === true;
arr[0] = 完整匹配的字符串;
arr[len-2] = 匹配的开始位置;
arr[len-1] = 原始字符串;
+ +

注意:除了匹配的开始位置是Number类型外,其余的都是String类型

+

非常规类型参数

如果参数类型不是上述两种情况,会发生什么呢?看看下面的例子:

+
1
2
3
4
5
6
7
8
a.replace(0,null); //123null9800
a.replace(0,undefined); //123null9800
a.replace(0,[]); //1239800
a.replace(0,Array); //1230,3,123098009800
b.replace({},5); //123098005
c.replace({},5); //'12309800{}'
a.replace(0,{}); //123[object Object]9800
a.replace(0,Object); //12309800
+ +

由上面的例子可以看出,如果非正则也非字符串,则有以下规则:
null变量,则会转换成'null'字符串;
undefined变量,则会转换成'undefined'字符串;
[]变量,则会调用join()方法转换成字符串,默认以,分割,值得注意的是空数组将会被转换成空字符串(没有任何字符),通常会被匹配源字符串的开始位置(默认开始位置为空字符串);
Array变量,则会先转成成一个匹配的数组,形如[完整匹配的字符串,$1,$2,...,$n,匹配的开始位置,原始字符串],然后对它调用join()方法转换成字符串,默认以,分割;
{}变量,则会调用Object.protype.toString.call()方法把{}转换成[object Object];
Object变量,则貌似什么都没做

+

虽然可以传入这些非正常参数,但大多数情况下这些类型的参数对实际是毫无意义的,所以不建议传入以上类型的参数。同上面的正则匹配分割规则一样,为了方便使用称呼,姑且我把上面的转换规则称为正则匹配参数转换规则

+

match(rule[regex/substr])

match方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
该方法类似indexOflastIndexOf,但是它返回指定的值,而不是字符串的位置;

+

参数

参数的传递除了常规的正则和字符串以外,其余所有类型的参数都会按照上述的正则匹配参数转换规则转换成字符串形式来匹配。

+

返回值

返回值根据传入的参数类型和规则的不同,返回的内容不同,但总体来说,它是返回一个对象,而不是索引,如果没匹配到任何符合条件的字符串,则返回null

+

非全局匹配正则

如果匹配规则是一个非全局匹配规则,那么,它此时的返回值是一个伪数组对象(likeArr),形如:[一个展开的匹配到的字符串数组, 匹配到的字符串位置, 原始字符串],它有如下规律:

+
1
2
3
4
5
var likeArr = a.match(regex);
likeArr[0] = 匹配到的字符串;
likeArr[1...n] = 正则匹配分割规则匹配的字符串;
likeArr.index = 匹配到字符串的位置
likeArr.inupt = 原始字符串
+ +

看例子:

+
1
2
3
4
a.match(/[1-9]0+$/); //[0:'800',index:5,input:'12309800']
a.match(/([1-9])0+$/); //[0:'800',1:'8',index:5,input:'12309800']
a.match(/[1-9](0+$)/); //[0:'800',1:'00',index:5,input:'12309800']
a.match(/([1-9])(0+$)/); //[0:'800',1:'8',2:'00',index:5,input:'12309800']
+ +

全局匹配正则

如果匹配规则是一个全局匹配规则(正在携带有g标志),那么,它此时的返回值是一个数组对象(arr),形如:[匹配到的字符串数1,匹配到的字符串数2,匹配到的字符串数3];
看例子:

+
1
2
a.match(/[1-9]0/); //[0:'30',index:2,input:'12309800']
a.match(/[1-9]0/g); //[0:'30',1:'80']
+ +

search(rule[regex/substr])

search方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
stringObject中第一个与rule相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回-1
注意:

+
    +
  • search方法不执行全局匹配,它将忽略标志g
  • +
  • 忽略regexplastIndex属性,总是从字符串的开始进行检索,这意味着它总是返回stringObject的第一个匹配的位置
  • +
+

同样,search可以传入任何参数类型,它会遵循正则匹配参数转换规则进行转换

+

split(rule[regex/substr],len)

这个方法就不用多说,很常用的字符串分割方法。
第二个参数的作用就是限制返回值的长度,表示返回值的最大长度

+

当然,它依然可以传入任何参数类型,会遵循正则匹配参数转换规则进行转换

+
+

有一段加密的后的密码,我们需要分离出字符串’12a344gg333tt445656ffa6778ii99’中的前三组数字,通过某种计算才能得出正确的密码

+
+
1
'12a344gg333tt445656ffa6778ii99'.split(/[a-zA-Z]+/g,3); //['12','334','333']
+ +

最后

写了这么多,突然发现以前仅仅是在用这些方法,了解得很不够深入。越是学习才发现其中的奥秘!学无止境,与诸君共勉!
以上内容如有错误之处,希望诸君不吝指出!

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-11-26-javascript-sinpatsundefined b/2017-11-26-javascript-sinpatsundefined new file mode 100644 index 00000000..18c2823a --- /dev/null +++ b/2017-11-26-javascript-sinpatsundefined @@ -0,0 +1,495 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常用javascript代码片段 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 常用javascript代码片段 +

+ + +
+ + + + +
+

下面记录的主要是本人在项目中遇到的一些问题的解决方案或者个人觉得精妙的代码,方案也许来自于自己的想法,也有可能来自于网络(我尽量给出出处,以便大家追源溯流),也许干脆就是一个插件库(实际在项目中对于一些小的功能个人是拒绝引入不纯粹的三方库的)。肯定,这其中有些方案也不是尽善尽美,我会一一列出。如果对于某个问题在你看来有更好的解决方案,请不吝指出!

+
+

placeholder属性支持

有时候项目中placeholder也许要兼容某些特殊的浏览器,所以才有了下面这段代码。这种方案有一个缺点就是:如果你恰好要对这个输入框绑定focus事件并操作它的值,必须要小心处理

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//修复不支持placeholder属性 start
const isSurportPlder = "placeholder" in document.createElement("input"); // 判断浏览器是否支持 placeholder
if (!isSurportPlder) {
$("[placeholder]").focus(function () {
const _this = $(this);
if (_this.val() == _this.attr("placeholder")) {
_this.val('');
}
}).blur(function () {
const _this = $(this);
if (_this.val() == '' || _this.val() == _this.attr("placeholder")) {
_this.val(_this.attr("placeholder"));
}
}).blur();
};
+ +

格式化时间

关于格式化时间有很多插件,其中比较有名的就可以列很大一堆出来,比如老牌的moment.js,最近比较多star的luxon.js; 对于为什么不选择他么,上面已经说了原因了。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
formate(fmt, date) {
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
const _rules = [{
rule: '[yY]{4}',
value: date.getFullYear()
}, {
rule: 'M+',
value: date.getMonth() + 1
}, {
rule: '[dD]+',
value: date.getDate()
}, {
rule: 'h+',
value: date.getHours()
}, {
rule: 'm+',
value: date.getMinutes()
}, {
rule: 's+',
value: date.getSeconds()
}, {
rule: 'ms{1,2}',
value: date.getMilliseconds()
}];

_rules.forEach((_r) => {
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join('').substr(rLen);
});
});
return fmt;
}
+ +

解析浏览器的版本等详细信息

浏览器的判断涉及到很多复杂的变量和参数,所以偷懒选取了一个比较好的库,这个库它唯一的功能就是识别浏览器的版本等详细信息。
如果大家有更好的库,请推荐过来!

+

browser.js

+

数字转换成千分位格式(如:123,456,9.89)

1
2
3
function translateThree(num) {
return num.split('').reverse().join('').replace(/(\d{3}(?=\d)(?!\d+\.|$))/g, '$1,').split('').reverse().join('');
}
+ +

判断任意数据的类型

精准的判断类型,'2'会识别成string类型,2会识别成number类型

+
1
2
3
4
5
6
7
8
9
10
function tryType(para) {
const type = typeof para;
if (type === "number" && isNaN(para)) return "NaN";
if (type !== "object") return type;
return Object.prototype.toString
.call(para)
.replace(/[\[\]]/g, "")
.split(" ")[1]
.toLowerCase();
}
+ +

判断是不是一个可计算的数字

上面的tryType方法会将'2'2区别成两种类型,而这个方法将忽略这种区别

+
1
2
3
4
5
function isNumber(para) {
if (window.isNumber) return window.isNumber(para);
if (Number.isNumber) return Number.isNumber(para);
return typeof para !== "undefined" && !isNaN(para);
}
+ +

深度取值防止代码挂掉

在项目中经常会遇到层级很深的json数据,这时候可能就会写类似这样的代码a[2].list[3].name,这种代码很不可靠,很容易由于数据的一点小错误,导致整段js代码挂掉。出的问题多了自然就会思考问题的解决方案,正当我苦思不得其解时,看到一篇文章如何优雅安全地在深层数据结构中取值,他详细的分析了深层取值如何避免报错的情况。
我阅读了博主的文章,整理了一下思路,没有像原博主那样使用xs && xs[x]判断来打断取值,是因为这种情况可能会把0这种类型的值误伤

+
1
2
3
4
function getValueFromDeepData (props, target){
if (!props || !target) return undefined;
return props.reduce((pre, nxt) => (typeof pre === 'undefined' || typeof pre[nxt] === 'undefined' ? undefined : pre[nxt]), target);
}
+ +

反转义字符串

何谓反转义字符串?就是后端在传输HTML代码的时候往往会对字符串处理:把一些特殊符号转义了;当我们拿到HTML渲染到页面希望他按照HTML代码的格式来显示,而不是按照转义的字符串显示成文本,所以我们需要对这段字符串反转义!
网上看到很多解决方案是通过正则的方式一一替换过来,但是个人觉得这样不好维护。后面发现一种浏览器自动转换的办法,利用这一特性,可以通过js创建一个虚拟的DOM节点,然后把需要转义的字符串使用innerHTML方法放进去,再通过nodeValue方法取出来。
但这种方法需要注意的是:e.childNodes是一个数组,它将_html分段(每65536字符分一段)存储到e.childNodes[i]中,使用e.childNodes方法取得反转义后的字符串需要循环e.childNodes数组。我看到网上很多方法都是直接取的e.childNodes[0].nodeValue,这样在内容过多的时候,是无法把数据取完整的

+
1
2
3
4
5
6
7
8
function htmlDecode(str) {
const e = document.createElement('div'), _html = '';
e.innerHTML = str;
for (let i = 0; i < e.childNodes.length; i++) {
_html += e.childNodes[i].nodeValue;
};
return _html;
};
+ +

未完

+
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017-11-29-blow-foreverundefined b/2017-11-29-blow-foreverundefined new file mode 100644 index 00000000..c93a557f --- /dev/null +++ b/2017-11-29-blow-foreverundefined @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +风继续吹 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 风继续吹 +

+ + +
+ + + + +
+

最近在某些比较看重的事情上,处理的很失败,感觉整个心态都发生了变化。下班独行的那段路上,突然惊醒自己也许需要做出一些改变,晚饭后静静的思考了一阵:似乎勇往前行才是我最需需要的改变。古语有云:走自己的路,让别人去说吧。是的,生活需要你不断踽踽独行,活在他人的世界里是可怕的,特别他(她)还是陌生人。随手记录一些句子,它们是我最近的心态和生活的写照,也以此鞭策自己砥砺前行。

+
+
+

时间不会因你沮丧而停滞不前
生活不会因你懊恼而雨过天晴

+

如风,继续吹
吹来往日的欢歌笑语
吹来远方的殷殷思念
化作雨露滋润心田

+

如风,继续吹
随风飘荡去远方
随风逐浪勇往前
风不止浪不息

+

如风,继续吹
时而狂乱,掠过天空大地,高山河流
时而低喃,轻抚绿茵花朵,平原盆谷
最后都消散在其它风里

+

然后
其它风,继续吹

+

—-< 谨鼓励我砥砺前行 >

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/01/05/vue-datapicker.html b/2017/01/05/vue-datapicker.html new file mode 100644 index 00000000..2a83968b --- /dev/null +++ b/2017/01/05/vue-datapicker.html @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用vue框架造了一个日历控件 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用vue框架造了一个日历控件 +

+ + +
+ + + + +
+

使用官方提供的vue-simple-template配置打包,写的一个简单的vue-datepicker。支持选择功能,功能比较简单,欢迎大家拍砖.

+
+

效果预览(demo)

vue-datepicker

+

项目构建

1
2
3
4
5
6
7
8
9
10
#全局安装vue,vue-cli,webpack,如以安装则跳过
npm install -g vue vue-cli webpack

# 安装依赖
npm install

# 运行项目=>localhost:8080
npm run dev

# 更多的构建信息请参考官网
+ +

更新记录

2017-2-10 15:14:43

+
    +
  • 修正选择日期后高亮错误问题
  • +
  • 修正多出方法中计算时数字会被转化成字符串问题
  • +
+

2016-12-9 10:12:58

+
    +
  • 更新效果图
  • +
  • 修正computed计算时数字会被转化成字符串问题
    1
    const startNum = self.chooseType ? +self.YearChangeSyboml - 4 : 1;
  • +
+

2016-12-8 17:30:04

+
    +
  • 增加输入框唤醒日历
  • +
  • 增加选择功能
  • +
+

2016-12-8 12:10:14

+
    +
  • 上传日历,只有简单展示版本
  • +
+

参考

待整理…

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/01/05/vue-datapickerundefined b/2017/01/05/vue-datapickerundefined new file mode 100644 index 00000000..6e0f62e7 --- /dev/null +++ b/2017/01/05/vue-datapickerundefined @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用vue框架造了一个日历控件 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用vue框架造了一个日历控件 +

+ + +
+ + + + +
+

使用官方提供的vue-simple-template配置打包,写的一个简单的vue-datepicker。支持选择功能,功能比较简单,欢迎大家拍砖.

+
+

效果预览(demo)

vue-datepicker

+

项目构建

1
2
3
4
5
6
7
8
9
10
#全局安装vue,vue-cli,webpack,如以安装则跳过
npm install -g vue vue-cli webpack

# 安装依赖
npm install

# 运行项目=>localhost:8080
npm run dev

# 更多的构建信息请参考官网
+ +

更新记录

2017-2-10 15:14:43

+
    +
  • 修正选择日期后高亮错误问题
  • +
  • 修正多出方法中计算时数字会被转化成字符串问题
  • +
+

2016-12-9 10:12:58

+
    +
  • 更新效果图
  • +
  • 修正computed计算时数字会被转化成字符串问题
    1
    const startNum = self.chooseType ? +self.YearChangeSyboml - 4 : 1;
  • +
+

2016-12-8 17:30:04

+
    +
  • 增加输入框唤醒日历
  • +
  • 增加选择功能
  • +
+

2016-12-8 12:10:14

+
    +
  • 上传日历,只有简单展示版本
  • +
+

参考

待整理…

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/03/15/babun.html b/2017/03/15/babun.html new file mode 100644 index 00000000..ae7f52a1 --- /dev/null +++ b/2017/03/15/babun.html @@ -0,0 +1,552 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +windows平台下超强的cmd工具Babun使用笔记 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ windows平台下超强的cmd工具Babun使用笔记 +

+ + +
+ + + + +
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

Babun

官方贴出了Babun的十大特性

+
    +
  • Pre-configured Cygwin with a lot of addons: 预置大量的Cygwin插件
  • +
  • Silent command-line installer, no admin rights required:静默命令行安装,不需要管理员权限
  • +
  • pact - advanced package manager (like apt-get or yum): 支持pact高级包管理器,类似于apt-get、yum等
  • +
  • xTerm-256 compatible console: xterm-256兼容控制台
  • +
  • HTTP(s) proxying support: HTTP(s) 代理支持
  • +
  • Plugin-oriented architecture: 插件体系,可以安装丰富的插件
  • +
  • Pre-configured git and shell: 预置git和shell,支持自定义配置
  • +
  • Integrated oh-my-zsh: 集成了zsh
  • +
  • Auto update feature: 自动检测最新版本
  • +
  • “Open Babun Here” context menu entry: 支持右键菜单“此处打开Babun”
  • +
+

当然对于上面这些特性,我不得不补充一点,那就是它强大的命令提示功能,能从根据你的输入匹配历史输入,狠棒!

+

Cygwin

Babun的核心包括一个预配置的Cygwincygwin是一个非常好的工具,但有很多使用技巧,使你能够节省大量的时间。Babun解决了很多问题,它里面包含了很多重要的软件包,是你能够第一时间能够使用它们

+

shell

Babunshell通过调整,已达到最佳的用户体验,Babun有两个配置之后马上使用的shell(默认使用zsh,可以使用bash或者zsh命令切换到对应的模式),Babunshell具有以下的特点:

+
    +
  • 语法高亮
  • +
  • 具有unix的工具
  • +
  • 软件开发工具
  • +
  • git-语义提示
  • +
  • 自定义脚本和别名
  • +
  • +
+

Console

Babun支持HTTP代理,只需添加地址和HTTP代理服务器的凭据。Babunrc文件所在文件夹执行源Babunrc启用HTTP代理。目前还不支持SOCKS代理。

+

开发者工具

Babun提供多种方便的工具和脚本,是你的开发工作更轻松,具有的功能如下:

+
    +
  • 编程语言(python,Perl, etc等)
  • +
  • git(各种各样的别名调整)
  • +
  • UNIX工具((grep, wget, curl, etc)
  • +
  • vcs (svn, git)
  • +
  • oh-my-zsh
  • +
  • 自定义脚本(pbcopy, pbpaste, Babun, etc)
  • +
+

安装

默认安装

双击install.bat脚本,Babun使用默认安装位置C:\Users\userName\.Babun,安装好的Babun会在C:\Users\userName\下;
当然也可以指定安装位置

+

自定义安装

通过cmd命令行在执行install.bat时指定参数/t/target指定安装的目录。
执行:Babun.bat /t install-dir

+
1
Babun.bat /t c:\Babun
+ +

安装好之后会在d:\Babun目录下生成一个.Babun的目录,Babun所有文件都在这个目录中。注意安装目录最好不要有空格,这是cygwin要求的

+

启动Babun默认是在’%userprofile%.Babun\cygwin\home\username’

+

开发环境配置

pip

Babun内置了PythonPerl等解释器。cygwin自带的python没有pip,需手动安装。
直接执行下面这个命令就好了。

+
1
wget https://bootstrap.pypa.io/get-pip.py -O - | python
+ +

有了pip就可以自由的安装诸如ipython之类的东西,还有包罗万象的类库。

+

常用插件

Babun默认是安装了oh-my-zsh的,这里可以根据自身情况安装一些插件。具体可参考利用oh-my-zsh打造你的超级终端一文;

+

包管理器使用

Babun提供一个叫pact包管理工具,类似于linux上面的apt-getyum的包管理工具

+

配置别名(alias)

可以在.Babun\cygwin\home\username目录下配置对应工具的别名,而并不仅限于git-bash
当然记忆别名其实也是体力活,我的想法是对一些常用的命令、经常手滑手速过快打错的命令、复杂的命令配置一些别名,例如

+
1
2
3
4
5
6
gt = git
gti = git
n = npm
nr = npm run dev
gtlg = git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
...等等
+ +

问题

本地SSH-KEY不可用

查看我记录的Babun导致本地SSH-KEY不可用一文

+

中文乱码问题

找了很多解决方案,都不能完美的解决问题,最后还是回归原始:不解决!!!

+

锁定文件夹

在使用Babun时(比如此时进入了a目录),它会锁定文件夹a目录,导致你可能无法做一些危险操作。必须关闭Babun后才能解锁进程

+

参考文章:

+
    +
  • windows下的命令行工具Babun
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+
    +
  • + 原作者: J.2ue +
  • +
  • + 本文链接: + https://blog.imx0.com/2017/03/15/babun.html +
  • +
  • + 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处! +
  • +
+
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/03/15/babunundefined b/2017/03/15/babunundefined new file mode 100644 index 00000000..32d8d2be --- /dev/null +++ b/2017/03/15/babunundefined @@ -0,0 +1,552 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +windows平台下超强的cmd工具Babun使用笔记 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ windows平台下超强的cmd工具Babun使用笔记 +

+ + +
+ + + + +
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

Babun

官方贴出了Babun的十大特性

+
    +
  • Pre-configured Cygwin with a lot of addons: 预置大量的Cygwin插件
  • +
  • Silent command-line installer, no admin rights required:静默命令行安装,不需要管理员权限
  • +
  • pact - advanced package manager (like apt-get or yum): 支持pact高级包管理器,类似于apt-get、yum等
  • +
  • xTerm-256 compatible console: xterm-256兼容控制台
  • +
  • HTTP(s) proxying support: HTTP(s) 代理支持
  • +
  • Plugin-oriented architecture: 插件体系,可以安装丰富的插件
  • +
  • Pre-configured git and shell: 预置git和shell,支持自定义配置
  • +
  • Integrated oh-my-zsh: 集成了zsh
  • +
  • Auto update feature: 自动检测最新版本
  • +
  • “Open Babun Here” context menu entry: 支持右键菜单“此处打开Babun”
  • +
+

当然对于上面这些特性,我不得不补充一点,那就是它强大的命令提示功能,能从根据你的输入匹配历史输入,狠棒!

+

Cygwin

Babun的核心包括一个预配置的Cygwincygwin是一个非常好的工具,但有很多使用技巧,使你能够节省大量的时间。Babun解决了很多问题,它里面包含了很多重要的软件包,是你能够第一时间能够使用它们

+

shell

Babunshell通过调整,已达到最佳的用户体验,Babun有两个配置之后马上使用的shell(默认使用zsh,可以使用bash或者zsh命令切换到对应的模式),Babunshell具有以下的特点:

+
    +
  • 语法高亮
  • +
  • 具有unix的工具
  • +
  • 软件开发工具
  • +
  • git-语义提示
  • +
  • 自定义脚本和别名
  • +
  • +
+

Console

Babun支持HTTP代理,只需添加地址和HTTP代理服务器的凭据。Babunrc文件所在文件夹执行源Babunrc启用HTTP代理。目前还不支持SOCKS代理。

+

开发者工具

Babun提供多种方便的工具和脚本,是你的开发工作更轻松,具有的功能如下:

+
    +
  • 编程语言(python,Perl, etc等)
  • +
  • git(各种各样的别名调整)
  • +
  • UNIX工具((grep, wget, curl, etc)
  • +
  • vcs (svn, git)
  • +
  • oh-my-zsh
  • +
  • 自定义脚本(pbcopy, pbpaste, Babun, etc)
  • +
+

安装

默认安装

双击install.bat脚本,Babun使用默认安装位置C:\Users\userName\.Babun,安装好的Babun会在C:\Users\userName\下;
当然也可以指定安装位置

+

自定义安装

通过cmd命令行在执行install.bat时指定参数/t/target指定安装的目录。
执行:Babun.bat /t install-dir

+
1
Babun.bat /t c:\Babun
+ +

安装好之后会在d:\Babun目录下生成一个.Babun的目录,Babun所有文件都在这个目录中。注意安装目录最好不要有空格,这是cygwin要求的

+

启动Babun默认是在’%userprofile%.Babun\cygwin\home\username’

+

开发环境配置

pip

Babun内置了PythonPerl等解释器。cygwin自带的python没有pip,需手动安装。
直接执行下面这个命令就好了。

+
1
wget https://bootstrap.pypa.io/get-pip.py -O - | python
+ +

有了pip就可以自由的安装诸如ipython之类的东西,还有包罗万象的类库。

+

常用插件

Babun默认是安装了oh-my-zsh的,这里可以根据自身情况安装一些插件。具体可参考利用oh-my-zsh打造你的超级终端一文;

+

包管理器使用

Babun提供一个叫pact包管理工具,类似于linux上面的apt-getyum的包管理工具

+

配置别名(alias)

可以在.Babun\cygwin\home\username目录下配置对应工具的别名,而并不仅限于git-bash
当然记忆别名其实也是体力活,我的想法是对一些常用的命令、经常手滑手速过快打错的命令、复杂的命令配置一些别名,例如

+
1
2
3
4
5
6
gt = git
gti = git
n = npm
nr = npm run dev
gtlg = git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
...等等
+ +

问题

本地SSH-KEY不可用

查看我记录的Babun导致本地SSH-KEY不可用一文

+

中文乱码问题

找了很多解决方案,都不能完美的解决问题,最后还是回归原始:不解决!!!

+

锁定文件夹

在使用Babun时(比如此时进入了a目录),它会锁定文件夹a目录,导致你可能无法做一些危险操作。必须关闭Babun后才能解锁进程

+

参考文章:

+
    +
  • windows下的命令行工具Babun
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+
    +
  • + 原作者: J.2ue +
  • +
  • + 本文链接: + https://blog.imx0.com/2017/03/15/babun.html +
  • +
  • + 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处! +
  • +
+
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/03/31/javascript-type.html b/2017/03/31/javascript-type.html new file mode 100644 index 00000000..663ff196 --- /dev/null +++ b/2017/03/31/javascript-type.html @@ -0,0 +1,712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Javascript常用见问题之变量类型判断终极篇 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Javascript常用见问题之变量类型判断终极篇 +

+ + +
+ + + + +
+

类型判断是我们在编程中常遇到的棘手问题,严格的变量类型约束会为代码减少很多致命的BUG。本文是对常用到的类型判断的一个整理,以求以最简洁的方式来判断变量的类型。
本文所有的如果没特指,都是基于ES5的原生javascript

+
+

变量的基本类型

JavaScript变量包含两种不同的数据类型的值:基本类型引用类型。基本类型是指简单的数据,有NumberStringBooleanUndefinedNull(null可以算作是一个特殊的基本数据类型),而引用类型指那些可能包含多个值的对象,有ObjectArrayDateRegExpFunction等。在JavaScript中,我们通过var来声明变量,由于JavaScript弱语言类型,我们无法在申明的时候规定他的类型,JavaScript变量的类型是随变量的值改变而改变的。为了代码的安全性,在有些情况下我们要判断变量值的类型,如何正确的判断变量的类型就成了一个比较有深度的问题。下图列举一些常见的类型:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型举例
Nullnull
Undefinedundefined、未赋值的变量
Booleantrue、false
Number-1、0 、 1、 NaN
String‘1’、’a’
Array[]、new Array()
Object{}、new Object()
Functionfunction(){}
+

判断他们的类型,第一时间可能你会想到用typeof去检测它们的类型,然后你就崩溃了:明明是Null为什么结果却是oject,明明是Array为什么还是obejct?…因此可以看出typeof方法不是很可靠,我们必须寻找一种行之有效的方法来解决这个问题?请继续往下读(为了方便阅读,下文中所有的para表示要判断的变量):

+
    +
  • isNaN(para)
  • +
  • !para
  • +
  • typeof para
  • +
  • Object.prototype.toString.call(para);
  • +
+

除了上面这些方法,未来可能会有更多方法来增强变量的约束和判断,比如isNumber

+

isNaN(para)

用来判断是否为number类型的专有方法。但是需要注意的是,如果使用typeof判断那么结果会是number

+

!para

常用来判断一个变量是否存在,面对ArryObject引用类型变量时无论是否为空都会被转换成true

+

typeof para

事实证明typeof并不是万能的,在对除Null以外的基本类型变量是相当有威力的,但是对引用类型变量Null时都会被识别成object,但是请注意:

+
1
2
typeof {}; //object
typeof Object; //Function
+

为什么会出现这样的情况呢?因为Object是一个构造函数,而不是object数据类型对象,同理ArrayDateFunction等都是属于构造函数

+

Object.prototype.toString.call(para)

前面的typeof死在半路,无法打探到引用类型变量null的真实情报,但是我们得出了另一个情报:**他们都是obejct**。别慌,我们另外一个强大的武器,可以直指要害,Object.prototype.toString.call(para)

+

判断结果比较表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型isNaN(para)!paratypeof paratoString(para)
Nullnulltruetrueobejct[obejct Null]
Undefinedundefinedtruetrueundefined[obejct Undefined]
Booleantrue/falsetruefalse/truetrue[obejct Boolean]
Number-1falsefalsenumber[obejct Number]
Number0falsetruenumber[obejct Number]
Number1falsefalsenumber[obejct Number]
NumberNaNtruetruenumber[obejct Number]
String‘1’falsefalsestring[obejct String]
String‘a’truefalsestring[obejct String]
String‘’truetruestring[obejct String]
String‘ ‘(中间包含空格)truefalsestring[obejct String]
Array[]/[4]truefalseobejct[obejct Array]
Object{}/{n:4}truefalseobejct[obejct Object]
Functionfunction(){}truefalseobejct[obejct Function]
+

总结方法

根据上面的表格对比,我整理了一些常见的方法。并且再比较结果精准的情况下尽可能的简化比较过程.

+

判断数字(非严格)

字符串’1‘会被识别成number

+
1
2
3
function isNumber(para){
return !isNaN(para);
};
+ +

判断数字(严格)

在必要的情况下使用:此方法会把字符串’1‘识别成string类型

+
1
2
3
function isStrictNumber(para){
return !isNaN(para) && typeof para === 'number';
};
+ +

判断字符串(非严格)

1
2
3
function isString(para){
return typeof para === 'string';
};
+ +

判断字符串(严格)

在必要的情况下使用:此种方法会把字符串’1‘识别成number类型

+
1
2
3
function isStrictString(para){
return isNaN(para) && typeof para === 'string';
};
+ +

判断一般数据类型(即非引用类型)

注意:使用typeof判断null结果为object

+
1
2
3
function isBasicType(para){
return typeof para !== 'obejct';
};
+ +

判断是否为null(不能识别’’)

此方法只能识别null,如果要包含’’,请结合方法isStringNull()一起使用

+
1
2
3
function isNull(para){
return !para && typeof para === 'object';
};
+ +

判断是否为空字符串(不包含空格)

此方法只能识别'',如果要包含null,请结合方法isNull()一起使用

+
1
2
3
function isStringtNull(para){
return !para && typeof para === 'string';
};
+ +

判断是否为undefined

1
2
3
function isUndefined(para){
return typeof para === 'undefined';
};
+ +

判断是否为false

当为null,undefined,'',0,-0,false,NaN

+
1
2
3
function isFalse(para){
return !para;
};
+ +

判断对象(非严格1–所有的obejct对象)

1
2
3
function isAllObject(_v){
return typeof _v === 'obejct';
};
+ +

判断对象(非严格2–除去null的所有object对象)<–> 判断引用类型

1
2
3
function isObject(_v){
return !!v && typeof _v === 'obejct';
};
+ +

判断对象(严格–只识别{}JSON对象)

1
2
3
function isStrictObject(_v){
return Object.prototype.toString.call(_v) === '[object Object]';
};
+ +

判断数组

1
2
3
function isArray(para){
return Object.prototype.toString.call(para) === '[object Array]';
};
+ +

判断对象

这里特指{}类JSON对象

+
1
2
3
function isObject(para){
return Object.prototype.toString.call(para) === '[object Object]';
};
+ +

判断可执行函数

1
2
3
function isFunction(para){
return typeof para === 'function';
};
+ +
+

总结

当我们需要判断其他类型时,完全可以参照上面的表来写出自己的方法哦。
当然现在前端各种流行库不断推陈出新,我们完全可以直接使用别人封装好的库来实现这些功能,比如underscore.jslodash.js等,但是编码的乐趣不就是在于自己解决最本质的问题么。所以即使有这么多的流行库大行其道,也不妨碍我们了解这些知识的初心,说不定哪天你自己也写出一个很火的库呢~
当然随着ES6标准的不断被各大浏览器厂商支持,ES6的普及度越来越广,这些方法都会被内置到原生javascript内部吧(有些方法已经加进去了~)。

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/03/31/javascript-typeundefined b/2017/03/31/javascript-typeundefined new file mode 100644 index 00000000..0ebc2742 --- /dev/null +++ b/2017/03/31/javascript-typeundefined @@ -0,0 +1,712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Javascript常用见问题之变量类型判断终极篇 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Javascript常用见问题之变量类型判断终极篇 +

+ + +
+ + + + +
+

类型判断是我们在编程中常遇到的棘手问题,严格的变量类型约束会为代码减少很多致命的BUG。本文是对常用到的类型判断的一个整理,以求以最简洁的方式来判断变量的类型。
本文所有的如果没特指,都是基于ES5的原生javascript

+
+

变量的基本类型

JavaScript变量包含两种不同的数据类型的值:基本类型引用类型。基本类型是指简单的数据,有NumberStringBooleanUndefinedNull(null可以算作是一个特殊的基本数据类型),而引用类型指那些可能包含多个值的对象,有ObjectArrayDateRegExpFunction等。在JavaScript中,我们通过var来声明变量,由于JavaScript弱语言类型,我们无法在申明的时候规定他的类型,JavaScript变量的类型是随变量的值改变而改变的。为了代码的安全性,在有些情况下我们要判断变量值的类型,如何正确的判断变量的类型就成了一个比较有深度的问题。下图列举一些常见的类型:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型举例
Nullnull
Undefinedundefined、未赋值的变量
Booleantrue、false
Number-1、0 、 1、 NaN
String‘1’、’a’
Array[]、new Array()
Object{}、new Object()
Functionfunction(){}
+

判断他们的类型,第一时间可能你会想到用typeof去检测它们的类型,然后你就崩溃了:明明是Null为什么结果却是oject,明明是Array为什么还是obejct?…因此可以看出typeof方法不是很可靠,我们必须寻找一种行之有效的方法来解决这个问题?请继续往下读(为了方便阅读,下文中所有的para表示要判断的变量):

+
    +
  • isNaN(para)
  • +
  • !para
  • +
  • typeof para
  • +
  • Object.prototype.toString.call(para);
  • +
+

除了上面这些方法,未来可能会有更多方法来增强变量的约束和判断,比如isNumber

+

isNaN(para)

用来判断是否为number类型的专有方法。但是需要注意的是,如果使用typeof判断那么结果会是number

+

!para

常用来判断一个变量是否存在,面对ArryObject引用类型变量时无论是否为空都会被转换成true

+

typeof para

事实证明typeof并不是万能的,在对除Null以外的基本类型变量是相当有威力的,但是对引用类型变量Null时都会被识别成object,但是请注意:

+
1
2
typeof {}; //object
typeof Object; //Function
+

为什么会出现这样的情况呢?因为Object是一个构造函数,而不是object数据类型对象,同理ArrayDateFunction等都是属于构造函数

+

Object.prototype.toString.call(para)

前面的typeof死在半路,无法打探到引用类型变量null的真实情报,但是我们得出了另一个情报:**他们都是obejct**。别慌,我们另外一个强大的武器,可以直指要害,Object.prototype.toString.call(para)

+

判断结果比较表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型isNaN(para)!paratypeof paratoString(para)
Nullnulltruetrueobejct[obejct Null]
Undefinedundefinedtruetrueundefined[obejct Undefined]
Booleantrue/falsetruefalse/truetrue[obejct Boolean]
Number-1falsefalsenumber[obejct Number]
Number0falsetruenumber[obejct Number]
Number1falsefalsenumber[obejct Number]
NumberNaNtruetruenumber[obejct Number]
String‘1’falsefalsestring[obejct String]
String‘a’truefalsestring[obejct String]
String‘’truetruestring[obejct String]
String‘ ‘(中间包含空格)truefalsestring[obejct String]
Array[]/[4]truefalseobejct[obejct Array]
Object{}/{n:4}truefalseobejct[obejct Object]
Functionfunction(){}truefalseobejct[obejct Function]
+

总结方法

根据上面的表格对比,我整理了一些常见的方法。并且再比较结果精准的情况下尽可能的简化比较过程.

+

判断数字(非严格)

字符串’1‘会被识别成number

+
1
2
3
function isNumber(para){
return !isNaN(para);
};
+ +

判断数字(严格)

在必要的情况下使用:此方法会把字符串’1‘识别成string类型

+
1
2
3
function isStrictNumber(para){
return !isNaN(para) && typeof para === 'number';
};
+ +

判断字符串(非严格)

1
2
3
function isString(para){
return typeof para === 'string';
};
+ +

判断字符串(严格)

在必要的情况下使用:此种方法会把字符串’1‘识别成number类型

+
1
2
3
function isStrictString(para){
return isNaN(para) && typeof para === 'string';
};
+ +

判断一般数据类型(即非引用类型)

注意:使用typeof判断null结果为object

+
1
2
3
function isBasicType(para){
return typeof para !== 'obejct';
};
+ +

判断是否为null(不能识别’’)

此方法只能识别null,如果要包含’’,请结合方法isStringNull()一起使用

+
1
2
3
function isNull(para){
return !para && typeof para === 'object';
};
+ +

判断是否为空字符串(不包含空格)

此方法只能识别'',如果要包含null,请结合方法isNull()一起使用

+
1
2
3
function isStringtNull(para){
return !para && typeof para === 'string';
};
+ +

判断是否为undefined

1
2
3
function isUndefined(para){
return typeof para === 'undefined';
};
+ +

判断是否为false

当为null,undefined,'',0,-0,false,NaN

+
1
2
3
function isFalse(para){
return !para;
};
+ +

判断对象(非严格1–所有的obejct对象)

1
2
3
function isAllObject(_v){
return typeof _v === 'obejct';
};
+ +

判断对象(非严格2–除去null的所有object对象)<–> 判断引用类型

1
2
3
function isObject(_v){
return !!v && typeof _v === 'obejct';
};
+ +

判断对象(严格–只识别{}JSON对象)

1
2
3
function isStrictObject(_v){
return Object.prototype.toString.call(_v) === '[object Object]';
};
+ +

判断数组

1
2
3
function isArray(para){
return Object.prototype.toString.call(para) === '[object Array]';
};
+ +

判断对象

这里特指{}类JSON对象

+
1
2
3
function isObject(para){
return Object.prototype.toString.call(para) === '[object Object]';
};
+ +

判断可执行函数

1
2
3
function isFunction(para){
return typeof para === 'function';
};
+ +
+

总结

当我们需要判断其他类型时,完全可以参照上面的表来写出自己的方法哦。
当然现在前端各种流行库不断推陈出新,我们完全可以直接使用别人封装好的库来实现这些功能,比如underscore.jslodash.js等,但是编码的乐趣不就是在于自己解决最本质的问题么。所以即使有这么多的流行库大行其道,也不妨碍我们了解这些知识的初心,说不定哪天你自己也写出一个很火的库呢~
当然随着ES6标准的不断被各大浏览器厂商支持,ES6的普及度越来越广,这些方法都会被内置到原生javascript内部吧(有些方法已经加进去了~)。

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/06/15/babun-casue-ssh-key-bad.html b/2017/06/15/babun-casue-ssh-key-bad.html new file mode 100644 index 00000000..12659ff8 --- /dev/null +++ b/2017/06/15/babun-casue-ssh-key-bad.html @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Babun导致本地SSH-KEY不可用 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Babun导致本地SSH-KEY不可用 +

+ + +
+ + + + +
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

ps: 前文有我记录的关于Babun的一些特点,以及使用,请看windows平台下超强的cmd工具Babun使用笔记一文

+

问题描述

如果你本地先安装了git命令行工具并生成了ssh-key,再安装Babun之后,可能会导致原有的ssh-key不可用,原因:
安装Babun会添加全局变量Home,指向Babun安装目录下的.Babun/cymwin/home,因此在使用命令生成key时不会在C:\Users\userName\.ssh目录。

+
1
2
ssh -T git@github.com
Permission denied (publickey).
+ +

解决办法

    +
  • 删掉以前目录(C:\Users\userName\.ssh)下的ssh-key。
  • +
  • 生成重新生成ssh key,此时生成的key在.Babun\cymwin\home\userName\.ssh下。
  • +
  • 把生成的key映射到C:\Users\userName\.ssh目录。
  • +
  • 获取权限
  • +
  • 把key关联到相应github账户(此处以github为例)。
  • +
  • 测试ssh key是否可用
  • +
+
1
2
3
4
5
6
7
8
9
ssh -T git@github.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0670 for '/home/Administrator/.ssh/id_rsa' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/Administrator/.ssh/id_rsa": bad permissions
Permission denied (publickey).
+ +

当生成key之后,测试是否联通,你会发现还是报错了,提示权限不够,错误信息为Permissions 0670

+

在终端切换到C:\Users\userName\.ssh目录,执行下面命令

1
ln -s /c/Users/userName/.ssh /home/userName/.ssh
+ +

此操作会把.Babun\cymwin\home\userName\.ssh目录下的ssh key映射C:\Users\userName\.ssh

+

在终端切换到根目录(~),执行以下命令(一般只执行其中一个)

1
2
3
4
5
chmod 400 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh/id_rsa
ssh -T git@github.com
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+ +

参考文章:

+

1.https://github.com/Babun/Babun/issues/327
2.http://stackoverflow.com/questions/9270734/ssh-permissions-are-too-open-error

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/06/15/babun-casue-ssh-key-badundefined b/2017/06/15/babun-casue-ssh-key-badundefined new file mode 100644 index 00000000..3032c969 --- /dev/null +++ b/2017/06/15/babun-casue-ssh-key-badundefined @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Babun导致本地SSH-KEY不可用 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Babun导致本地SSH-KEY不可用 +

+ + +
+ + + + +
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

ps: 前文有我记录的关于Babun的一些特点,以及使用,请看windows平台下超强的cmd工具Babun使用笔记一文

+

问题描述

如果你本地先安装了git命令行工具并生成了ssh-key,再安装Babun之后,可能会导致原有的ssh-key不可用,原因:
安装Babun会添加全局变量Home,指向Babun安装目录下的.Babun/cymwin/home,因此在使用命令生成key时不会在C:\Users\userName\.ssh目录。

+
1
2
ssh -T git@github.com
Permission denied (publickey).
+ +

解决办法

    +
  • 删掉以前目录(C:\Users\userName\.ssh)下的ssh-key。
  • +
  • 生成重新生成ssh key,此时生成的key在.Babun\cymwin\home\userName\.ssh下。
  • +
  • 把生成的key映射到C:\Users\userName\.ssh目录。
  • +
  • 获取权限
  • +
  • 把key关联到相应github账户(此处以github为例)。
  • +
  • 测试ssh key是否可用
  • +
+
1
2
3
4
5
6
7
8
9
ssh -T git@github.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0670 for '/home/Administrator/.ssh/id_rsa' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/Administrator/.ssh/id_rsa": bad permissions
Permission denied (publickey).
+ +

当生成key之后,测试是否联通,你会发现还是报错了,提示权限不够,错误信息为Permissions 0670

+

在终端切换到C:\Users\userName\.ssh目录,执行下面命令

1
ln -s /c/Users/userName/.ssh /home/userName/.ssh
+ +

此操作会把.Babun\cymwin\home\userName\.ssh目录下的ssh key映射C:\Users\userName\.ssh

+

在终端切换到根目录(~),执行以下命令(一般只执行其中一个)

1
2
3
4
5
chmod 400 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh/id_rsa
ssh -T git@github.com
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+ +

参考文章:

+

1.https://github.com/Babun/Babun/issues/327
2.http://stackoverflow.com/questions/9270734/ssh-permissions-are-too-open-error

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/07/01/learn-git-2.html b/2017/07/01/learn-git-2.html new file mode 100644 index 00000000..047c67cc --- /dev/null +++ b/2017/07/01/learn-git-2.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列之常用命令(一) | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列之常用命令(一) +

+ + +
+ + + + +
+

虽然有了git的图形化工具,但个人还是喜欢命令行,并且结合alias功能,也能非常快速的完成很多功能。记录一些项目中常用的git命令,方便查阅

+
+

查看状态

可以查看工作区(edit即add之前)和暂存区(add之后,commit之前)

+
1
git status
+ +

提交到暂存区

1
2
3
4
5
6
//提交某个文件
git add fileName
//提交所有修改文件的三种写法
git add *
git add .
git add --all
+

保存在本地仓库

1
git commit -m "note text"
+ +

提交到远程仓库

其中origin是本地仓库名,remote是远程仓库分支名

+
1
2
3
git push <origin> <remote>
//如提交本覅origin到远程master分支
git push origin master
+ +

分支

查看分支

1
2
3
4
5
6
//查看本地分支
git branch
//查看远程分支
git branch -r
//查看所有分支
git branch -a
+ +

创建分支

1
git branch name
+ +

删除分支

1
2
3
4
5
//删除本地分支
git branch -d name
//删除远程分支两种方法
git push origin :name //冒号不能省
git branch -r -d origin/name
+ +

切换分支

1
git checkout name //如果分支不存在则创建一个名为name的新分支
+ +

合并分支

1
2
3
4
5
6
7
8
//例如:合并分支dev到master
//首先保持dev和master分支最新,即在本地切换到对应分支,各pull一次
//然后切换分支到最终要合并的分支上(此处为master)
git checkout master
//执行本地合并(合并dev到master)
git merge dev
//推送合并到远程
git push origin master
+ +

放弃本地修改强制更新

+

git fetch只是下载远程的库的内容,不做任何的合并;git reset把HEAD指向刚刚下载的最新的版本

+
+
1
2
git fetch --all
git reset --hard origin/master
+ +

回退到某个历史版本

+

首先使用git log命令获取某个历史版本的ID,假设ID是c3470ee7edf566cc359b666d3e27a38220abaf66

+
+
1
2
3
4
//在本地回退到c3470ee7edf566cc359b666d3e27a38220abaf66版本
git reset --hard c3470ee7edf566cc359b666d3e27a38220abaf66
//推送到远程分支,注意:由于本地版本旧于远程仓库版本,这里需要使用-f参数,强制推送
git push -f origin master
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/07/01/learn-git-2undefined b/2017/07/01/learn-git-2undefined new file mode 100644 index 00000000..f374a67c --- /dev/null +++ b/2017/07/01/learn-git-2undefined @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列之常用命令(一) | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列之常用命令(一) +

+ + +
+ + + + +
+

虽然有了git的图形化工具,但个人还是喜欢命令行,并且结合alias功能,也能非常快速的完成很多功能。记录一些项目中常用的git命令,方便查阅

+
+

查看状态

可以查看工作区(edit即add之前)和暂存区(add之后,commit之前)

+
1
git status
+ +

提交到暂存区

1
2
3
4
5
6
//提交某个文件
git add fileName
//提交所有修改文件的三种写法
git add *
git add .
git add --all
+

保存在本地仓库

1
git commit -m "note text"
+ +

提交到远程仓库

其中origin是本地仓库名,remote是远程仓库分支名

+
1
2
3
git push <origin> <remote>
//如提交本覅origin到远程master分支
git push origin master
+ +

分支

查看分支

1
2
3
4
5
6
//查看本地分支
git branch
//查看远程分支
git branch -r
//查看所有分支
git branch -a
+ +

创建分支

1
git branch name
+ +

删除分支

1
2
3
4
5
//删除本地分支
git branch -d name
//删除远程分支两种方法
git push origin :name //冒号不能省
git branch -r -d origin/name
+ +

切换分支

1
git checkout name //如果分支不存在则创建一个名为name的新分支
+ +

合并分支

1
2
3
4
5
6
7
8
//例如:合并分支dev到master
//首先保持dev和master分支最新,即在本地切换到对应分支,各pull一次
//然后切换分支到最终要合并的分支上(此处为master)
git checkout master
//执行本地合并(合并dev到master)
git merge dev
//推送合并到远程
git push origin master
+ +

放弃本地修改强制更新

+

git fetch只是下载远程的库的内容,不做任何的合并;git reset把HEAD指向刚刚下载的最新的版本

+
+
1
2
git fetch --all
git reset --hard origin/master
+ +

回退到某个历史版本

+

首先使用git log命令获取某个历史版本的ID,假设ID是c3470ee7edf566cc359b666d3e27a38220abaf66

+
+
1
2
3
4
//在本地回退到c3470ee7edf566cc359b666d3e27a38220abaf66版本
git reset --hard c3470ee7edf566cc359b666d3e27a38220abaf66
//推送到远程分支,注意:由于本地版本旧于远程仓库版本,这里需要使用-f参数,强制推送
git push -f origin master
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/08/21/nvm-node-version-manager.html b/2017/08/21/nvm-node-version-manager.html new file mode 100644 index 00000000..544d4356 --- /dev/null +++ b/2017/08/21/nvm-node-version-manager.html @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +node和npm版本管理器nvm的安装和使用 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ node和npm版本管理器nvm的安装和使用 +

+ + +
+ + + + +
+

nvm:一个node和npm的版本管理器(node&npm version manager),能让你快速的在不同版本间切换。

+
+

安装

下载地址:官网下载
有两种版本nvm-noinstall.zip(便携版)和nvm-setup.zip(exe安装版)
两者唯一区别就是便携版需要手动配置全局变量,而安装版只需要在安装时选定安装目录则会自动配置好。

+
+

注意:安装之前最好先卸载之前的node

+
+

便携版安装

    +
  • 下载最新版的nvm-noinstall.zip后解压放到D:\devTools(可以放到任意位置,此处是我安装的目录,注意文件夹名不能存在空格);
    1
    2
    3
    4
    5
    elevate.cmd
    elevate.vbs
    install.cmd
    LICENSE
    nvm.exe
  • +
  • 配置nvm,生成settings.txt,填写配置
    方法一:双击install.cmd,会生成settings.txt文件(生成位置就是你输入的地址,一般是在nvm目录下,如果不是,需要拷贝过来)
    方法二:直接在nvm目录下新建settings.txt文件
    1
    2
    3
    4
    5
    6
    root: D:\devTools\nvm
    path: D:\devTools\nodejsv
    arch: 64
    proxy: none
    node_mirror: http://npm.taobao.org/mirrors/node/
    npm_mirror: https://npm.taobao.org/mirrors/npm/
    +
      +
    • root : nvm的存放地址
    • +
    • path : 存放指向node版本的快捷方式,使用nvm的过程中会自动生成。一般写的时候与nvm同级。
    • +
    • arch : 电脑系统是64位就写64,32位就写32
    • +
    • proxy : 代理
    • +
    • node_mirror: node镜像源,安装node时会从此镜像源下载。
    • +
    • npm_mirror: 同上,npm镜像源
    • +
    +
  • +
  • 全局变量配置
    1.添加变量NVM_HOME,值为D:\devTools\nvm
    2.添加变量NVM_SYMLINK,值为D:\devTools\nodejsv
    3.添加变量NVM_HOMENVM_SYMLINK到全局变量path: 修改path的值最后加上;%NVM_HOME%;%NVM_SYMLINK%;
    到此便携版nvm安装完成
  • +
+

exe安装版

直接双击安装,可以使用默认的选项。也可以自己选择安装地址。然后安装过程中会自动把路径写入到全局变量。

+
+

注意: 如果安装了杀毒软件,应该先关闭杀毒软件,因为写入全局变量是一个敏感操作,某些杀毒软件会报警(不关闭,报警时需要选择允许操作)

+
+

使用

版本检测

1
2
3
4
5
$ nvm version
1.1.6
// or
$ nvm v
1.1.6
+ +

安装node&npm

1
2
3
$ nvm install [version]
// 如果安装最新版的,直接使用
$ nvm install latest
+ +

查看安装的node&npm

1
2
3
$ nvm list
* 8.4.0 (Currently using 64-bit executable)
6.9.0
+ +

切换node版本

1
2
$ nvm use [version]
Now using node v8.4.0 (64-bit)
+ +

卸载某个版本node

1
$ nvm uninstall [version]
+ +

nvm命令查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ nvm
Running version 1.1.6.

Usage:

nvm arch : Show if node is running in 32 or 64 bit mode.
nvm install <version> [arch] : The version can be a node.js version or "latest" for the latest stable version.
Optionally specify whether to install the 32 or 64 bit version (defaults to system arch).
Set [arch] to "all" to install 32 AND 64 bit versions.
Add --insecure to the end of this command to bypass SSL validation of the remote download server.
nvm list [available] : List the node.js installations. Type "available" at the end to see what can be installed. Aliased as ls.
nvm on : Enable node.js version management.
nvm off : Disable node.js version management.
nvm proxy [url] : Set a proxy to use for downloads. Leave [url] blank to see the current proxy.
Set [url] to "none" to remove the proxy.
nvm node_mirror [url] : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.
nvm npm_mirror [url] : Set the npm mirror. Defaults to https://github.com/npm/npm/archive/. Leave [url] blank to default url.
nvm uninstall <version> : The version must be a specific version.
nvm use [version] [arch] : Switch to use the specified version. Optionally specify 32/64bit architecture.
nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.
nvm root [path] : Set the directory where nvm should store different versions of node.js.
If <path> is not set, the current root will be displayed.
nvm version : Displays the current running version of nvm for Windows. Aliased as v.
+ +

总结

    +
  • settings.txtrootpath文件路径中不能存在空格,否则在使用nvm use命令时会报错
  • +
  • 在使用nvm use命令时,貌似无法再git-bash中使用,暂时不知道原因,在自带的cmd中可以
  • +
+

最后的最后

+

安装nvm比较简单,喜欢折腾的可以使用便携版,反之这直接使用安装版一键安装。最后大家愉快的玩耍吧

+
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/08/21/nvm-node-version-managerundefined b/2017/08/21/nvm-node-version-managerundefined new file mode 100644 index 00000000..c3b6e3c4 --- /dev/null +++ b/2017/08/21/nvm-node-version-managerundefined @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +node和npm版本管理器nvm的安装和使用 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ node和npm版本管理器nvm的安装和使用 +

+ + +
+ + + + +
+

nvm:一个node和npm的版本管理器(node&npm version manager),能让你快速的在不同版本间切换。

+
+

安装

下载地址:官网下载
有两种版本nvm-noinstall.zip(便携版)和nvm-setup.zip(exe安装版)
两者唯一区别就是便携版需要手动配置全局变量,而安装版只需要在安装时选定安装目录则会自动配置好。

+
+

注意:安装之前最好先卸载之前的node

+
+

便携版安装

    +
  • 下载最新版的nvm-noinstall.zip后解压放到D:\devTools(可以放到任意位置,此处是我安装的目录,注意文件夹名不能存在空格);
    1
    2
    3
    4
    5
    elevate.cmd
    elevate.vbs
    install.cmd
    LICENSE
    nvm.exe
  • +
  • 配置nvm,生成settings.txt,填写配置
    方法一:双击install.cmd,会生成settings.txt文件(生成位置就是你输入的地址,一般是在nvm目录下,如果不是,需要拷贝过来)
    方法二:直接在nvm目录下新建settings.txt文件
    1
    2
    3
    4
    5
    6
    root: D:\devTools\nvm
    path: D:\devTools\nodejsv
    arch: 64
    proxy: none
    node_mirror: http://npm.taobao.org/mirrors/node/
    npm_mirror: https://npm.taobao.org/mirrors/npm/
    +
      +
    • root : nvm的存放地址
    • +
    • path : 存放指向node版本的快捷方式,使用nvm的过程中会自动生成。一般写的时候与nvm同级。
    • +
    • arch : 电脑系统是64位就写64,32位就写32
    • +
    • proxy : 代理
    • +
    • node_mirror: node镜像源,安装node时会从此镜像源下载。
    • +
    • npm_mirror: 同上,npm镜像源
    • +
    +
  • +
  • 全局变量配置
    1.添加变量NVM_HOME,值为D:\devTools\nvm
    2.添加变量NVM_SYMLINK,值为D:\devTools\nodejsv
    3.添加变量NVM_HOMENVM_SYMLINK到全局变量path: 修改path的值最后加上;%NVM_HOME%;%NVM_SYMLINK%;
    到此便携版nvm安装完成
  • +
+

exe安装版

直接双击安装,可以使用默认的选项。也可以自己选择安装地址。然后安装过程中会自动把路径写入到全局变量。

+
+

注意: 如果安装了杀毒软件,应该先关闭杀毒软件,因为写入全局变量是一个敏感操作,某些杀毒软件会报警(不关闭,报警时需要选择允许操作)

+
+

使用

版本检测

1
2
3
4
5
$ nvm version
1.1.6
// or
$ nvm v
1.1.6
+ +

安装node&npm

1
2
3
$ nvm install [version]
// 如果安装最新版的,直接使用
$ nvm install latest
+ +

查看安装的node&npm

1
2
3
$ nvm list
* 8.4.0 (Currently using 64-bit executable)
6.9.0
+ +

切换node版本

1
2
$ nvm use [version]
Now using node v8.4.0 (64-bit)
+ +

卸载某个版本node

1
$ nvm uninstall [version]
+ +

nvm命令查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ nvm
Running version 1.1.6.

Usage:

nvm arch : Show if node is running in 32 or 64 bit mode.
nvm install <version> [arch] : The version can be a node.js version or "latest" for the latest stable version.
Optionally specify whether to install the 32 or 64 bit version (defaults to system arch).
Set [arch] to "all" to install 32 AND 64 bit versions.
Add --insecure to the end of this command to bypass SSL validation of the remote download server.
nvm list [available] : List the node.js installations. Type "available" at the end to see what can be installed. Aliased as ls.
nvm on : Enable node.js version management.
nvm off : Disable node.js version management.
nvm proxy [url] : Set a proxy to use for downloads. Leave [url] blank to see the current proxy.
Set [url] to "none" to remove the proxy.
nvm node_mirror [url] : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.
nvm npm_mirror [url] : Set the npm mirror. Defaults to https://github.com/npm/npm/archive/. Leave [url] blank to default url.
nvm uninstall <version> : The version must be a specific version.
nvm use [version] [arch] : Switch to use the specified version. Optionally specify 32/64bit architecture.
nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.
nvm root [path] : Set the directory where nvm should store different versions of node.js.
If <path> is not set, the current root will be displayed.
nvm version : Displays the current running version of nvm for Windows. Aliased as v.
+ +

总结

    +
  • settings.txtrootpath文件路径中不能存在空格,否则在使用nvm use命令时会报错
  • +
  • 在使用nvm use命令时,貌似无法再git-bash中使用,暂时不知道原因,在自带的cmd中可以
  • +
+

最后的最后

+

安装nvm比较简单,喜欢折腾的可以使用便携版,反之这直接使用安装版一键安装。最后大家愉快的玩耍吧

+
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/10/10/fed-test.html b/2017/10/10/fed-test.html new file mode 100644 index 00000000..a6e16632 --- /dev/null +++ b/2017/10/10/fed-test.html @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +前端测试探索 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 前端测试探索 +

+ + +
+ + + + +
+

前端测试是保证代码质量以及程序稳定的一种可靠方式,同时也从代码层面解决了自测难,自测烦等前端测试综合症。当然在前端开发引入测试环节无疑会增加人力和时间成本,如果最终产生的结果和增加的成本能够两两抵消产生正面效应,那么有必要考虑把测试环节加入到团队的规划中。但是在实际开发过程中,测试用例更多的被用于开源项目中,在大部分公司的生成项目很少使用维护测试用例,其根本原因是不纯粹的生产环境导致维护测试用例变得困难,而目的性很强的开源项目则更纯粹,所以前端测试的发展任重而道远。

+
+

Ps:本次更多的是讲述概念性的东西,代码性的具体实例不做过多实践

+
+

GUI(Graphical User Interface)软件测试

+

前端测试不同于后端测试,因为除了一般的逻辑测试以外,由于存在界面交互,所以涉及到模拟用户行为达到测试的目的。由此引入了一个概念:GUI(Graphical User Interface)软件测试,也就是图形用户界面软件测试

+
+

TDD(Test Driven Development) & BDD(Behaviour Driven Development)

TDD很明显的意思是测试驱动开发,也就是说我们可以从测试的角度来检验整个项目。大概的流程是先针对每个功能点抽象出接口代码,然后编写单元测试代码,接下来实现接口,运行单元测试代码,循环此过程,直到整个单元测试都通过。
TDD的好处自然不用多说,它能让你减少程序逻辑方面的错误,尽可能的减少项目中的bug,开始接触编程的时候我们大都有过这样的体验,可能你觉得完成得很完美,自我感觉良好,但是实际测试或者应用的时候才发现里面可能存在一堆bug,或者存在设计问题,或者更严重的逻辑问题,而TDD正好可以帮助我们尽量减少类似事件的发生。
当然,并不是所有的项目都适合TDD,要使用TDD,我认为必须至少具备以下两个条件

+
    +
  • 项目的业务逻辑很清晰,并且程序员对开发逻辑很清晰
  • +
  • 项目模块的复杂度和依赖度不高。如果复杂度高和依赖度高会导致在最开始拆分单元的时候造成很大的困扰,有可能根本不能顺利拆分
  • +
+

BDD行为驱动开发,这里的行为不是指程序员的行为,而是指业务(程序)的逻辑行为,实际上BDD可以看作是对TDD的一种补充,当然你也可以把它看作TDD的一个分支,因为在TDD中,我们并不能完全保证根据设计所编写的测试就是用户所期望的功能

+

如何实现自动化

说一千道一万,新环节的引入必然带来成本的增加,那么我们如何控制增加的成本在合理范围内?很自然的我们想到了使用工具来实现自动化的测试,让机器帮我完成复杂的交互和测试,并自动监控返回错误报警,为我们手动排除问题提供参考

+

可覆盖的测试

+

那到底前端在开发中需要测试哪些东西?在目前技术又可以实现那些测试?

+
+
    +
  • 函数功能测试
      +
    • 全局变量
    • +
    • 公共方法
    • +
    +
  • +
  • 界面&交互测试
      +
    • 事件交互
    • +
    • 数据输入交互
    • +
    • 特征检测
        +
      • 设计图还原度
      • +
      • 图片大小
      • +
      • +
      +
    • +
    • 特殊情况
        +
      • 自适应和响应式测试
      • +
      • 浏览器兼容
      • +
      • 多端测试
      • +
      • +
      +
    • +
    +
  • +
  • 网络请求测试
      +
    • 数据库访问
    • +
    • 模拟用户登陆等
    • +
    • ajax请求
    • +
    +
  • +
  • 直观的错误信息展示
      +
    • 网页表格
    • +
    • 截图
    • +
    +
  • +
  • 性能测试
  • +
  • 回归测试
  • +
  • 自动化
      +
    • 测试用例数据自动化 - 结合mockjs打造假数据
    • +
    • 测试用例自动化创建 - 通过读取源码中的注释来自动生成测试用例?
    • +
    +
  • +
+

业务逻辑/业务代码/测试用例的关系

业务代码的颗粒度与测试用例的复杂度成反比:颗粒度划分越多(细),复杂度越低
业务代码的量与测试用例的量成正比

+

Good

    +
  • 相对于等待问题产生,更倾向于避免可能的问题
  • +
  • 有利于形成团队代码规范,对团队未来成员的扩充是一个很好的约束规范
  • +
  • 对输出的产品有进一步的质量保证
  • +
+

Bad

    +
  • 增加维护测试用例本(时间和人力)
  • +
  • 增加编码复杂度(需要靠如何更友好的进行测试),对团队人员的编码要求提高了
  • +
  • 也许会增加学习成本(并不一定所有人都会写测试用例)
  • +
  • 需要把控测试用例的合理性、覆盖率、通过率
  • +
+

测试框架

PhantomJS/CasperJS

PhantomJS是一个服务器端的支持JavaScript APIWebKit。其支持各种Web标准:DOM处理, CSS选择器, JSON, CanvasSVG。对于web测试、界面、网络捕获、页面自动化访问等等方面。当启动的时候会在内存在开启一个无界面浏览器,以此模拟用户各种操作,可以对界面截图
Casperjs是对PhantomJS的封装,提供了更加易用的API, 增强了测试等方面的支持

+

PhantomCSS

像素对比工具,基于PhantomJs开发,结合了Casperjs截图和ResembleJs图像对比分析

+

Page-monitor

DOM结构对比工具,基于PhantomJS开发,根据DOM结构与样式的对比来对比整个页面的变动部分

+

BackstopJS

主要通过PhantomJScapserJS等工具在不同尺寸下截图,然后根据resemberJS进行像素比对判断是否正常,以实现响应式测试

+

Mocha + Chai

mocha+chai是一个经典的组合主要用来测试函数功能,也能测试异步操作。也有常用chai的超集(拓展库)sion-chai来加强chai

+

Selenium2

Selenium2,它的主要新功能是集成了Selenium1.0以及WebDriver
也就是说Selenium2SeleniumWebDriver两个项目的合并,即Selenium2兼容Selenium,它既支持Selenium API也支持WebDriver APIWebDriver是一个用来进行复杂重复的web自动化测试的工具,意在提供一种比Selenium1.0更简单易学,有利于维护的API。它没有和任何测试框架进行绑定,所以他可以很好的在单元测试中调用。当启动Selenium2时通常会调起一个可见的界面,但也可以通过设置,让它以PhantomJS的形式进行无界面的测试
当然使用Selenium2必须额外的安装每种浏览器的WebDriver
Selenium2上手难度大于PhantomJS

+

NightwatchJs

推特出品,基于Selenium WebDriver API开发,意味着支持浏览器自动化测试,内部集成了mocha+chai并将它加强,同时支持分组测试和单个测试,对语法进行了简化,归纳有以下特点:

+
    +
  • 简单但强大的语法(更符合js书写习惯),只需要使用JavaScriptCSS选择器,开发者就能够非常迅捷地撰写测试。
  • +
  • 开发者也不必初始化其他对象和类,只需要编写测试规范即可。
  • +
  • 使用CSS选择器或Xpath,定位并验证页面中的元素或是执行命令。
  • +
  • 易于扩展,便于开发者根据需要,实现与自己应用相关的命令。
  • +
  • +
+

目前,SeleniumJavaScript的验收测试方面最流行的工具之一,同类的还有PhantomJS。二者都有其独到的方法:Selenium使用WebDriver API,而PhantomJS使用无界面的WebKit浏览器。它们都是非常成熟的工具,都具有强大的社区支持。它们与Nightwatch之间最大的不同,主要是在于语法的简易度以及对持续集成的支持。与Nightwatch相比,SeleniumPhantomJS都拥有更加冗长的语法,这会让编码变得更庞大

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
this.demoTestGoogle = function (browser) {
browser
.url(“http://www.google.com”)
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'The Night Watch')
.end();
};

//也可以
module.exports = {
'step one' : function (browser) {
browser
.url('http://www.google.com')
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
},

'step two' : function (browser) {
browser
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'Night Watch')
.end();
}
};
+ +

对前端框架的支持

在实际开发中,我们可能是用了不同的框架。虽然我们完全可以在把源码编译成普通的HTML/CSS/JS代码然后测试,但是此种方法的弊端也显而易见:不易于自动化,必须等到所有模块开发完成才能测试…为此我们必须寻找某种方式使得测试不收框架的限制

+

Vue

本身可以通过new一个Vue的方式挂载节点达到效果。
下面是一个简单的测试用例,测试.hello h1标签内容是否符合预期

+
1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {
it('should render correct contents', () => {
const Constructor = Vue.extend(HelloWorld)
const vm = new Constructor().$mount()
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App')
})
})
+ +

React

1.官方提供了两种方法:

+
    +
  • 渲染虚拟DOMShallow Rendering
  • +
+

只渲染第一层,不渲染子组件,速度快,返回一个浅渲染的虚拟DOM对象。然后拿到节点的各种信息,进行测试

+
    +
  • 渲染真实DOM节点(renderIntoDocument
  • +
+

renderIntoDocument 方法要求存在一个真实的DOM环境,否则会报错。因此,测试用例之中,DOM环境(即window, documentnavigator 对象)必须是存在的。jsdom库提供这项功能

+
1
2
3
4
5
6
7
import jsdom from 'jsdom';

if (typeof document === 'undefined') {
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = global.window.navigator;
}
+ +

2.Enzyme
Enzyme是官方测试工具库的封装,它模拟了jQueryAPI,非常直观,易于使用和学习,主要提供三种方法:

+
    +
  • shallow
  • +
+

shallow方法就是官方的shallow rendering的封装

+
1
2
3
4
5
6
7
8
import {shallow} from 'enzyme';

describe('Enzyme Shallow', function () {
it('App\'s title should be Todos', function () {
let app = shallow(<App/>);
expect(app.find('h1').text()).to.equal('Todos');
});
};
+ +
    +
  • render
  • +
+

render方法将React组件渲染成静态的HTML字符串,然后分析这段HTML代码的结构,返回一个对象。它跟shallow方法非常像,主要的不同是采用了第三方HTML解析库Cheerio,它返回的是一个Cheerio实例对象。

+
    +
  • mount
  • +
+

mount方法用于将React组件加载为真实DOM节点

+

最后

回到开始,个人认为不要滥用测试,需要合理评估测试用例对团队项目的积极作用和消极作用。不合理或者不恰当的使用测试只会增加工作复杂度和成本。
并且测试用例只是检查代码的工具,所以不要本末倒置以测试用例强行约束业务代码

+

参考

    +
  • 关于TDD、BDD和DDD的一些看法
  • +
  • 虚拟座谈会:代码测试比率、测试驱动开发及行为驱动开发
  • +
  • Mocha
  • +
  • PhantomJS
  • +
  • NightwatchJs
  • +
  • 前端自动化测试探索
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/10/10/fed-testundefined b/2017/10/10/fed-testundefined new file mode 100644 index 00000000..484727c2 --- /dev/null +++ b/2017/10/10/fed-testundefined @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +前端测试探索 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 前端测试探索 +

+ + +
+ + + + +
+

前端测试是保证代码质量以及程序稳定的一种可靠方式,同时也从代码层面解决了自测难,自测烦等前端测试综合症。当然在前端开发引入测试环节无疑会增加人力和时间成本,如果最终产生的结果和增加的成本能够两两抵消产生正面效应,那么有必要考虑把测试环节加入到团队的规划中。但是在实际开发过程中,测试用例更多的被用于开源项目中,在大部分公司的生成项目很少使用维护测试用例,其根本原因是不纯粹的生产环境导致维护测试用例变得困难,而目的性很强的开源项目则更纯粹,所以前端测试的发展任重而道远。

+
+

Ps:本次更多的是讲述概念性的东西,代码性的具体实例不做过多实践

+
+

GUI(Graphical User Interface)软件测试

+

前端测试不同于后端测试,因为除了一般的逻辑测试以外,由于存在界面交互,所以涉及到模拟用户行为达到测试的目的。由此引入了一个概念:GUI(Graphical User Interface)软件测试,也就是图形用户界面软件测试

+
+

TDD(Test Driven Development) & BDD(Behaviour Driven Development)

TDD很明显的意思是测试驱动开发,也就是说我们可以从测试的角度来检验整个项目。大概的流程是先针对每个功能点抽象出接口代码,然后编写单元测试代码,接下来实现接口,运行单元测试代码,循环此过程,直到整个单元测试都通过。
TDD的好处自然不用多说,它能让你减少程序逻辑方面的错误,尽可能的减少项目中的bug,开始接触编程的时候我们大都有过这样的体验,可能你觉得完成得很完美,自我感觉良好,但是实际测试或者应用的时候才发现里面可能存在一堆bug,或者存在设计问题,或者更严重的逻辑问题,而TDD正好可以帮助我们尽量减少类似事件的发生。
当然,并不是所有的项目都适合TDD,要使用TDD,我认为必须至少具备以下两个条件

+
    +
  • 项目的业务逻辑很清晰,并且程序员对开发逻辑很清晰
  • +
  • 项目模块的复杂度和依赖度不高。如果复杂度高和依赖度高会导致在最开始拆分单元的时候造成很大的困扰,有可能根本不能顺利拆分
  • +
+

BDD行为驱动开发,这里的行为不是指程序员的行为,而是指业务(程序)的逻辑行为,实际上BDD可以看作是对TDD的一种补充,当然你也可以把它看作TDD的一个分支,因为在TDD中,我们并不能完全保证根据设计所编写的测试就是用户所期望的功能

+

如何实现自动化

说一千道一万,新环节的引入必然带来成本的增加,那么我们如何控制增加的成本在合理范围内?很自然的我们想到了使用工具来实现自动化的测试,让机器帮我完成复杂的交互和测试,并自动监控返回错误报警,为我们手动排除问题提供参考

+

可覆盖的测试

+

那到底前端在开发中需要测试哪些东西?在目前技术又可以实现那些测试?

+
+
    +
  • 函数功能测试
      +
    • 全局变量
    • +
    • 公共方法
    • +
    +
  • +
  • 界面&交互测试
      +
    • 事件交互
    • +
    • 数据输入交互
    • +
    • 特征检测
        +
      • 设计图还原度
      • +
      • 图片大小
      • +
      • +
      +
    • +
    • 特殊情况
        +
      • 自适应和响应式测试
      • +
      • 浏览器兼容
      • +
      • 多端测试
      • +
      • +
      +
    • +
    +
  • +
  • 网络请求测试
      +
    • 数据库访问
    • +
    • 模拟用户登陆等
    • +
    • ajax请求
    • +
    +
  • +
  • 直观的错误信息展示
      +
    • 网页表格
    • +
    • 截图
    • +
    +
  • +
  • 性能测试
  • +
  • 回归测试
  • +
  • 自动化
      +
    • 测试用例数据自动化 - 结合mockjs打造假数据
    • +
    • 测试用例自动化创建 - 通过读取源码中的注释来自动生成测试用例?
    • +
    +
  • +
+

业务逻辑/业务代码/测试用例的关系

业务代码的颗粒度与测试用例的复杂度成反比:颗粒度划分越多(细),复杂度越低
业务代码的量与测试用例的量成正比

+

Good

    +
  • 相对于等待问题产生,更倾向于避免可能的问题
  • +
  • 有利于形成团队代码规范,对团队未来成员的扩充是一个很好的约束规范
  • +
  • 对输出的产品有进一步的质量保证
  • +
+

Bad

    +
  • 增加维护测试用例本(时间和人力)
  • +
  • 增加编码复杂度(需要靠如何更友好的进行测试),对团队人员的编码要求提高了
  • +
  • 也许会增加学习成本(并不一定所有人都会写测试用例)
  • +
  • 需要把控测试用例的合理性、覆盖率、通过率
  • +
+

测试框架

PhantomJS/CasperJS

PhantomJS是一个服务器端的支持JavaScript APIWebKit。其支持各种Web标准:DOM处理, CSS选择器, JSON, CanvasSVG。对于web测试、界面、网络捕获、页面自动化访问等等方面。当启动的时候会在内存在开启一个无界面浏览器,以此模拟用户各种操作,可以对界面截图
Casperjs是对PhantomJS的封装,提供了更加易用的API, 增强了测试等方面的支持

+

PhantomCSS

像素对比工具,基于PhantomJs开发,结合了Casperjs截图和ResembleJs图像对比分析

+

Page-monitor

DOM结构对比工具,基于PhantomJS开发,根据DOM结构与样式的对比来对比整个页面的变动部分

+

BackstopJS

主要通过PhantomJScapserJS等工具在不同尺寸下截图,然后根据resemberJS进行像素比对判断是否正常,以实现响应式测试

+

Mocha + Chai

mocha+chai是一个经典的组合主要用来测试函数功能,也能测试异步操作。也有常用chai的超集(拓展库)sion-chai来加强chai

+

Selenium2

Selenium2,它的主要新功能是集成了Selenium1.0以及WebDriver
也就是说Selenium2SeleniumWebDriver两个项目的合并,即Selenium2兼容Selenium,它既支持Selenium API也支持WebDriver APIWebDriver是一个用来进行复杂重复的web自动化测试的工具,意在提供一种比Selenium1.0更简单易学,有利于维护的API。它没有和任何测试框架进行绑定,所以他可以很好的在单元测试中调用。当启动Selenium2时通常会调起一个可见的界面,但也可以通过设置,让它以PhantomJS的形式进行无界面的测试
当然使用Selenium2必须额外的安装每种浏览器的WebDriver
Selenium2上手难度大于PhantomJS

+

NightwatchJs

推特出品,基于Selenium WebDriver API开发,意味着支持浏览器自动化测试,内部集成了mocha+chai并将它加强,同时支持分组测试和单个测试,对语法进行了简化,归纳有以下特点:

+
    +
  • 简单但强大的语法(更符合js书写习惯),只需要使用JavaScriptCSS选择器,开发者就能够非常迅捷地撰写测试。
  • +
  • 开发者也不必初始化其他对象和类,只需要编写测试规范即可。
  • +
  • 使用CSS选择器或Xpath,定位并验证页面中的元素或是执行命令。
  • +
  • 易于扩展,便于开发者根据需要,实现与自己应用相关的命令。
  • +
  • +
+

目前,SeleniumJavaScript的验收测试方面最流行的工具之一,同类的还有PhantomJS。二者都有其独到的方法:Selenium使用WebDriver API,而PhantomJS使用无界面的WebKit浏览器。它们都是非常成熟的工具,都具有强大的社区支持。它们与Nightwatch之间最大的不同,主要是在于语法的简易度以及对持续集成的支持。与Nightwatch相比,SeleniumPhantomJS都拥有更加冗长的语法,这会让编码变得更庞大

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
this.demoTestGoogle = function (browser) {
browser
.url(“http://www.google.com”)
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'The Night Watch')
.end();
};

//也可以
module.exports = {
'step one' : function (browser) {
browser
.url('http://www.google.com')
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
},

'step two' : function (browser) {
browser
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'Night Watch')
.end();
}
};
+ +

对前端框架的支持

在实际开发中,我们可能是用了不同的框架。虽然我们完全可以在把源码编译成普通的HTML/CSS/JS代码然后测试,但是此种方法的弊端也显而易见:不易于自动化,必须等到所有模块开发完成才能测试…为此我们必须寻找某种方式使得测试不收框架的限制

+

Vue

本身可以通过new一个Vue的方式挂载节点达到效果。
下面是一个简单的测试用例,测试.hello h1标签内容是否符合预期

+
1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {
it('should render correct contents', () => {
const Constructor = Vue.extend(HelloWorld)
const vm = new Constructor().$mount()
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App')
})
})
+ +

React

1.官方提供了两种方法:

+
    +
  • 渲染虚拟DOMShallow Rendering
  • +
+

只渲染第一层,不渲染子组件,速度快,返回一个浅渲染的虚拟DOM对象。然后拿到节点的各种信息,进行测试

+
    +
  • 渲染真实DOM节点(renderIntoDocument
  • +
+

renderIntoDocument 方法要求存在一个真实的DOM环境,否则会报错。因此,测试用例之中,DOM环境(即window, documentnavigator 对象)必须是存在的。jsdom库提供这项功能

+
1
2
3
4
5
6
7
import jsdom from 'jsdom';

if (typeof document === 'undefined') {
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = global.window.navigator;
}
+ +

2.Enzyme
Enzyme是官方测试工具库的封装,它模拟了jQueryAPI,非常直观,易于使用和学习,主要提供三种方法:

+
    +
  • shallow
  • +
+

shallow方法就是官方的shallow rendering的封装

+
1
2
3
4
5
6
7
8
import {shallow} from 'enzyme';

describe('Enzyme Shallow', function () {
it('App\'s title should be Todos', function () {
let app = shallow(<App/>);
expect(app.find('h1').text()).to.equal('Todos');
});
};
+ +
    +
  • render
  • +
+

render方法将React组件渲染成静态的HTML字符串,然后分析这段HTML代码的结构,返回一个对象。它跟shallow方法非常像,主要的不同是采用了第三方HTML解析库Cheerio,它返回的是一个Cheerio实例对象。

+
    +
  • mount
  • +
+

mount方法用于将React组件加载为真实DOM节点

+

最后

回到开始,个人认为不要滥用测试,需要合理评估测试用例对团队项目的积极作用和消极作用。不合理或者不恰当的使用测试只会增加工作复杂度和成本。
并且测试用例只是检查代码的工具,所以不要本末倒置以测试用例强行约束业务代码

+

参考

    +
  • 关于TDD、BDD和DDD的一些看法
  • +
  • 虚拟座谈会:代码测试比率、测试驱动开发及行为驱动开发
  • +
  • Mocha
  • +
  • PhantomJS
  • +
  • NightwatchJs
  • +
  • 前端自动化测试探索
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/10/10/mocha+chai.html b/2017/10/10/mocha+chai.html new file mode 100644 index 00000000..a22386d0 --- /dev/null +++ b/2017/10/10/mocha+chai.html @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +mocha+chai使用记录 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ mocha+chai使用记录 +

+ + +
+ + + + +
+

mocha+chai是比较流行的测试框架‘套装’,能实现一些基本的前端测试如函数功能测试,模拟登陆,异步测试,流程测试等

+
+

官方文档

mocha

chai

基本用法

+

mocha是一个测试工具库,它只纯粹对测试行为(过程)进行描述;而chai是一个断言(推断)库,它可以将测试结果进行各种判断,以此推断是否符合预期,因此两者常常进行组合使用

+
+

安装

1
2
//全局安装
$ npm install -g mocha
+ +

ps: 全局安装之后,mocha命令将会在全局注册,可以在任何地方使用mocha命令

+
1
2
//安装项目依赖
$ npm install mocha chai
+ +

使用

目录结构

1
2
3
4
5
6
7
8
9
├── test //测试用例
│   ├── hooks.js //生命钩子
│   ├── test.js //入口文件
│   └── unit //测试单例
│   ├── add.js
│   └── ...
└── src //业务代码
   ├── add.js
   └── ...
+ +

所有测试代码都在test目录,所有的业务代码都在src目录

+

一个简单的例子

1
2
3
4
5
6
//src/add.js
function add(a, b){
return a + b;
}
module.exports = add;

+ +
1
2
3
4
5
6
7
8
9
//test/unit/add.js
var add = require('../../src/add.js');
var expect = require('chai').expect;

describe('加法函数', function () {
it('1 + 3 = 4', function () {
expect(add(1, 3)).to.be.equal(4);
});
});
+ +
1
2
3
4
$ mocha mocha/unit/add
加法函数
√ 1 + 3 = 4
1 passing (16ms)
+ +

Expect/Should/Assert

The Expect / Should API covers the BDD assertion styles.
+The Assert API covers the TDD assertion style.
+
+

异步

    +
  • promise异步一定要带上done(),用于通知mocha该测试已经完成
  • +
  • 异步通常和参数-t结合一起用
  • +
+

通配符

1
2
3
4
//test/unit目录下add.js和minus.js
$ mocha test/unit/{add,minus}.js
//test/unit目录下所有js后缀的文件
$ mocha test/unit/*.js
+ +

mocha支持shellnode的通配符匹配规则,更多通配符规则可以查看各自的文档

+

命令行参数与配置文件mocha.opts

常用的命令行

    +
  • –recursive
  • +
+

Mocha默认不对指定目录的子级目录匹配,如果需要使自己目录的测试用例运行,则需要加上--recursive

+
    +
  • –reporter
  • +
+

输出报告的格式,默认是--reporter spec,可以用--reporters命令查看有哪些输出格式

+
    +
  • –watch
  • +
+

监听变化,每次修改自动执行test

+
    +
  • –timeout -t
  • +
+

Mocha默认每个测试用例最多执行2000毫秒,如果到时没有得到结果,就报错。对于涉及异步操作的测试用例,这个时间往往是不够的,需要用-t--timeout参数指定超时门槛

+
    +
  • –hlep,-h
  • +
+

查看有哪些命令
mocha.opts放在test目录下,执行mocha命令时回去读取里面的配置
命令行参数可以写在mocha.opts文件内,如

+
1
$ mocha --reporter tap --recursive -t 3000
+ +

等价于
test/mocha.opts文件内容

+
1
2
3
--reporter tap
--recursive
-t 13000
+ +
1
$ mocha
+ +

hooks(钩子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
describe('hooks', function() {

before(function() {
// 在本区块的所有测试用例之前执行
});

after(function() {
// 在本区块的所有测试用例之后执行
});

beforeEach(function() {
// 在本区块的每个测试用例之前执行
});

afterEach(function() {
// 在本区块的每个测试用例之后执行
});

// test cases
});
+ +

可以写在测试用例内(每个describe块内),此时只对当前测试用例有效
也可以写在外部,此时对所有的测试用例有效

+

注意

    +
  • 内置promise对象
  • +
  • ES6需要转码
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/10/10/mocha+chaiundefined b/2017/10/10/mocha+chaiundefined new file mode 100644 index 00000000..38a22356 --- /dev/null +++ b/2017/10/10/mocha+chaiundefined @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +mocha+chai使用记录 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ mocha+chai使用记录 +

+ + +
+ + + + +
+

mocha+chai是比较流行的测试框架‘套装’,能实现一些基本的前端测试如函数功能测试,模拟登陆,异步测试,流程测试等

+
+

官方文档

mocha

chai

基本用法

+

mocha是一个测试工具库,它只纯粹对测试行为(过程)进行描述;而chai是一个断言(推断)库,它可以将测试结果进行各种判断,以此推断是否符合预期,因此两者常常进行组合使用

+
+

安装

1
2
//全局安装
$ npm install -g mocha
+ +

ps: 全局安装之后,mocha命令将会在全局注册,可以在任何地方使用mocha命令

+
1
2
//安装项目依赖
$ npm install mocha chai
+ +

使用

目录结构

1
2
3
4
5
6
7
8
9
├── test //测试用例
│   ├── hooks.js //生命钩子
│   ├── test.js //入口文件
│   └── unit //测试单例
│   ├── add.js
│   └── ...
└── src //业务代码
   ├── add.js
   └── ...
+ +

所有测试代码都在test目录,所有的业务代码都在src目录

+

一个简单的例子

1
2
3
4
5
6
//src/add.js
function add(a, b){
return a + b;
}
module.exports = add;

+ +
1
2
3
4
5
6
7
8
9
//test/unit/add.js
var add = require('../../src/add.js');
var expect = require('chai').expect;

describe('加法函数', function () {
it('1 + 3 = 4', function () {
expect(add(1, 3)).to.be.equal(4);
});
});
+ +
1
2
3
4
$ mocha mocha/unit/add
加法函数
√ 1 + 3 = 4
1 passing (16ms)
+ +

Expect/Should/Assert

The Expect / Should API covers the BDD assertion styles.
+The Assert API covers the TDD assertion style.
+
+

异步

    +
  • promise异步一定要带上done(),用于通知mocha该测试已经完成
  • +
  • 异步通常和参数-t结合一起用
  • +
+

通配符

1
2
3
4
//test/unit目录下add.js和minus.js
$ mocha test/unit/{add,minus}.js
//test/unit目录下所有js后缀的文件
$ mocha test/unit/*.js
+ +

mocha支持shellnode的通配符匹配规则,更多通配符规则可以查看各自的文档

+

命令行参数与配置文件mocha.opts

常用的命令行

    +
  • –recursive
  • +
+

Mocha默认不对指定目录的子级目录匹配,如果需要使自己目录的测试用例运行,则需要加上--recursive

+
    +
  • –reporter
  • +
+

输出报告的格式,默认是--reporter spec,可以用--reporters命令查看有哪些输出格式

+
    +
  • –watch
  • +
+

监听变化,每次修改自动执行test

+
    +
  • –timeout -t
  • +
+

Mocha默认每个测试用例最多执行2000毫秒,如果到时没有得到结果,就报错。对于涉及异步操作的测试用例,这个时间往往是不够的,需要用-t--timeout参数指定超时门槛

+
    +
  • –hlep,-h
  • +
+

查看有哪些命令
mocha.opts放在test目录下,执行mocha命令时回去读取里面的配置
命令行参数可以写在mocha.opts文件内,如

+
1
$ mocha --reporter tap --recursive -t 3000
+ +

等价于
test/mocha.opts文件内容

+
1
2
3
--reporter tap
--recursive
-t 13000
+ +
1
$ mocha
+ +

hooks(钩子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
describe('hooks', function() {

before(function() {
// 在本区块的所有测试用例之前执行
});

after(function() {
// 在本区块的所有测试用例之后执行
});

beforeEach(function() {
// 在本区块的每个测试用例之前执行
});

afterEach(function() {
// 在本区块的每个测试用例之后执行
});

// test cases
});
+ +

可以写在测试用例内(每个describe块内),此时只对当前测试用例有效
也可以写在外部,此时对所有的测试用例有效

+

注意

    +
  • 内置promise对象
  • +
  • ES6需要转码
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/10/16/desktop-notification.html b/2017/10/16/desktop-notification.html new file mode 100644 index 00000000..3c51f719 --- /dev/null +++ b/2017/10/16/desktop-notification.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +H5的Notification特性 - Web的桌面通知功能 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ H5的Notification特性 - Web的桌面通知功能 +

+ + +
+ + + + +
+

目前,web网页使用桌面通知功能的越来越多,包括微博,腾讯视频等大厂站,桌面通知功能是H5的一个API - Notifications。它允许网页或应用程序可以发出通知,通知将被显示在页面之外的系统层面上(通常使用操作系统的标准通知机制,但是在不同的平台和浏览器上的表现会有差异),这样即使应用程序空闲或在后台也可以向用户发送信息。

+
+

应用场景

Notifications的诞生简化了网站或者应用与用户之间的沟通成本(时间成本和开发成本),增强用户黏性(减少了用户离开应用的可能)。传统的通知方式,大多是通过站内信(消息),邮件,短信等方式,它们通常需要刷新(跳转)页面、离开应用打开其他应用或终端来查看消息;而桌面通知功能大大的简化了这个过程,消息的传递基本不消耗时间(如果不设置setTimeout,用时基本不会超过1s),并且用户不需要离开应用,这都带来了极大的方便。可以预见,Notifications将会在很多网页或应用中被大量使用。当然Notifications也具有它的局限性:无法存档、即看即毁
那么,这个功能到底能用在哪些场景呢?只能说能应用的场景很多:

+
    +
  • 社交类网站
  • +
  • 资讯类网站
  • +
  • 网页版邮件服务
  • +
  • 即时通知类网站
  • +
  • +
+

举个例子,当你打开微博页面,你可能会看到(使用新版浏览器)如下图的通知:
desktop-notification1

+

这就是网站使用了桌面通知功能,当你选择允许,那么当网站有推送消息或者你登陆账号有新的消息将会在桌面的右下角出现一个小弹窗通知,如下:
desktop-notification2

+

感觉有点酷酷的!!!

+

用户权限 - Notification.permission

Notification.permission是一个静态方法,可以获取用户当前的通知权限状态,返回一个String,可以根据返回值判断用户是否授予了通知权限。返回值有三种情况:

+
    +
  • default
      +
    • 用户还未被询问是否授权,所以通知不会被显示。
    • +
    +
  • +
  • granted
      +
    • 表示之前已经询问过用户,并且用户已经授予了显示通知的权限。
    • +
    +
  • +
  • denied
      +
    • 用户已经明确的拒绝了显示通知的权限。
    • +
    +
  • +
+

当值为default或者denied时都不会显示通知消息,只有明确的被设置成granted才会显示通知消息

+
1
2
3
4
5
6
const permission = Notification.permission;
if(permission === 'granted'){
console.log('已经授权通知,可以进行你的通知啦!');
}else{
console.log('用户还未授权,请先授权!');
}
+ +

请求权限 - Notification.requestPermission(CALLBACK)

应用发送通知之前必须要取得发送通知的权限,才能成功进行通知。Notification.requestPermission(CALLBACK)是请求获取权限的方法(有点类似javascriptconfirm弹窗窗),允许传入一个回调,回调会返回用户选择的何种权限,返回两个值,granted代表允许,denied代表拒绝。并且Notification.requestPermission()支持then方式的链式调用,也就意味着可以异步调用它。

+
1
2
3
4
5
6
7
Notification.requestPermission(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
//两种方式是等价的
Notification.requestPermission().then(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
+ +

创建通知 - new Notification(TITLE, OPTIONS)

new Notification(TITLE, OPTIONS)方法创建可以创建一个通知实例,允许参入参数两个参数TITLEOPTIONS。注意默认情况下(实际可以通过OPTIONS中的timestamp参数控制)一旦通知实例被创建出来,它会立即被显示出来。

+

TITLE参数

TITLE表示通知的标题。必须参数,允许数字、字符串和空

+

OPTIONS参数

OPTIONS是非必须参数,必须为一个对象,它包含:
ps: 部分参数在某些浏览器可能会不生效,建议使用最新版的谷歌浏览器。以下某些内容从Notification-MDN-EN结合谷歌翻译得来,很有可能翻译不准确,如有,请提出。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
//通知显示正文。非必须,默认为空
body: '你的好友XX上线了!',
//通知显示正文的图片地址。非必须,默认为空
image: 'imgae url',
//通知左侧图标。非必须,默认为空
icon: 'imgae url',
//通知的分类标记(ID)。非必须,默认为空
tag: 'test',
//通知相关联的数据,通常用于方法的回调,传参。非必须,默认为空
data: '可以是任意数据类型',
//通知显示延迟的时间。非必须,默认通知实例创建完成就显示
timestamp: '',
//通知主体内容的水平展示顺序,有点类似direction属性。非必须,默认值是auto, 可以是ltr或rtl
dir: 'auto',
//当没有足够的空间来显示通知本身时,用于表示通知的图像的URL。非必须,默认为空
badge: 'xxx',
//通知的语言。非必须默认为空
lang: '',
//通知显示时,设备的振动模式。非必须,默认为空
vibrate: [200, 100, 200],
//新通知出现是否覆盖旧的通知,覆盖(true)则永远只显示一条通知,不覆盖(false)则会多条通知重叠。非必须,默认为true
renotify: true,
//通知是否静音。非必须,默认为false,表示无声
silent: false,
//通知声源文件地址。非必须,默认为空
sound: 'mp3',
//是否不在屏幕上显示通知信息。非必须,默认为false表示要显示
noscreen: false,
//指定通知是否应该粘滞性,即不容易被用户清理。非必须,默认false表示不具粘滞性
sticky: false,
//指定通知是否保持活性,知道用户点击或关闭。非必须,默认为false
requireInteraction: false
}
+ +

事件及事件钩子

当通知被创建成功后:

+
    +
  • 通知实例具有一个静态方法可以用来关闭通知
  • +
  • 通知实例具有四个事件钩子,来跟踪通知当前的状态。这些事件可以通过事件处理跟踪onshowonclickoncloseonerror。因为Notification同样继承自EventTarget,因此可以对它调用addEventListener()方法。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const n = new Notification('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
},
timestamp: 3000
});

n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
+ +

demo

写一个简单的例子,可以打开页面体验一下,建议用最新版谷歌浏览器打开~ Notification.js

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const NotificationInstance = Notification || window.Notification;
if (!!NotificationInstance) {
const permissionNow = NotificationInstance.permission;
if (permissionNow === 'granted') {//允许通知
CreatNotification();
} else if (permissionNow === 'denied') {
console.log('用户拒绝了你!!!');
} else {
setPermission();
}
function setPermission() {
//请求获取通知权限
NotificationInstance.requestPermission(function (PERMISSION) {
if (PERMISSION === 'granted') {
CreatNotification();
} else {
console.log('用户无情残忍的拒绝了你!!!');
}
});
}
function CreatNotification() {
const n = new NotificationInstance('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
}
});
n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
setTimeout(() => {
n.close();
}, 2000);
}
}
+ +

兼容

+ +

参考

    +
  • Notification-MDN-EN
  • +
  • Notification-MDN-CN
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/10/16/desktop-notificationundefined b/2017/10/16/desktop-notificationundefined new file mode 100644 index 00000000..8b1dc821 --- /dev/null +++ b/2017/10/16/desktop-notificationundefined @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +H5的Notification特性 - Web的桌面通知功能 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ H5的Notification特性 - Web的桌面通知功能 +

+ + +
+ + + + +
+

目前,web网页使用桌面通知功能的越来越多,包括微博,腾讯视频等大厂站,桌面通知功能是H5的一个API - Notifications。它允许网页或应用程序可以发出通知,通知将被显示在页面之外的系统层面上(通常使用操作系统的标准通知机制,但是在不同的平台和浏览器上的表现会有差异),这样即使应用程序空闲或在后台也可以向用户发送信息。

+
+

应用场景

Notifications的诞生简化了网站或者应用与用户之间的沟通成本(时间成本和开发成本),增强用户黏性(减少了用户离开应用的可能)。传统的通知方式,大多是通过站内信(消息),邮件,短信等方式,它们通常需要刷新(跳转)页面、离开应用打开其他应用或终端来查看消息;而桌面通知功能大大的简化了这个过程,消息的传递基本不消耗时间(如果不设置setTimeout,用时基本不会超过1s),并且用户不需要离开应用,这都带来了极大的方便。可以预见,Notifications将会在很多网页或应用中被大量使用。当然Notifications也具有它的局限性:无法存档、即看即毁
那么,这个功能到底能用在哪些场景呢?只能说能应用的场景很多:

+
    +
  • 社交类网站
  • +
  • 资讯类网站
  • +
  • 网页版邮件服务
  • +
  • 即时通知类网站
  • +
  • +
+

举个例子,当你打开微博页面,你可能会看到(使用新版浏览器)如下图的通知:
desktop-notification1

+

这就是网站使用了桌面通知功能,当你选择允许,那么当网站有推送消息或者你登陆账号有新的消息将会在桌面的右下角出现一个小弹窗通知,如下:
desktop-notification2

+

感觉有点酷酷的!!!

+

用户权限 - Notification.permission

Notification.permission是一个静态方法,可以获取用户当前的通知权限状态,返回一个String,可以根据返回值判断用户是否授予了通知权限。返回值有三种情况:

+
    +
  • default
      +
    • 用户还未被询问是否授权,所以通知不会被显示。
    • +
    +
  • +
  • granted
      +
    • 表示之前已经询问过用户,并且用户已经授予了显示通知的权限。
    • +
    +
  • +
  • denied
      +
    • 用户已经明确的拒绝了显示通知的权限。
    • +
    +
  • +
+

当值为default或者denied时都不会显示通知消息,只有明确的被设置成granted才会显示通知消息

+
1
2
3
4
5
6
const permission = Notification.permission;
if(permission === 'granted'){
console.log('已经授权通知,可以进行你的通知啦!');
}else{
console.log('用户还未授权,请先授权!');
}
+ +

请求权限 - Notification.requestPermission(CALLBACK)

应用发送通知之前必须要取得发送通知的权限,才能成功进行通知。Notification.requestPermission(CALLBACK)是请求获取权限的方法(有点类似javascriptconfirm弹窗窗),允许传入一个回调,回调会返回用户选择的何种权限,返回两个值,granted代表允许,denied代表拒绝。并且Notification.requestPermission()支持then方式的链式调用,也就意味着可以异步调用它。

+
1
2
3
4
5
6
7
Notification.requestPermission(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
//两种方式是等价的
Notification.requestPermission().then(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
+ +

创建通知 - new Notification(TITLE, OPTIONS)

new Notification(TITLE, OPTIONS)方法创建可以创建一个通知实例,允许参入参数两个参数TITLEOPTIONS。注意默认情况下(实际可以通过OPTIONS中的timestamp参数控制)一旦通知实例被创建出来,它会立即被显示出来。

+

TITLE参数

TITLE表示通知的标题。必须参数,允许数字、字符串和空

+

OPTIONS参数

OPTIONS是非必须参数,必须为一个对象,它包含:
ps: 部分参数在某些浏览器可能会不生效,建议使用最新版的谷歌浏览器。以下某些内容从Notification-MDN-EN结合谷歌翻译得来,很有可能翻译不准确,如有,请提出。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
//通知显示正文。非必须,默认为空
body: '你的好友XX上线了!',
//通知显示正文的图片地址。非必须,默认为空
image: 'imgae url',
//通知左侧图标。非必须,默认为空
icon: 'imgae url',
//通知的分类标记(ID)。非必须,默认为空
tag: 'test',
//通知相关联的数据,通常用于方法的回调,传参。非必须,默认为空
data: '可以是任意数据类型',
//通知显示延迟的时间。非必须,默认通知实例创建完成就显示
timestamp: '',
//通知主体内容的水平展示顺序,有点类似direction属性。非必须,默认值是auto, 可以是ltr或rtl
dir: 'auto',
//当没有足够的空间来显示通知本身时,用于表示通知的图像的URL。非必须,默认为空
badge: 'xxx',
//通知的语言。非必须默认为空
lang: '',
//通知显示时,设备的振动模式。非必须,默认为空
vibrate: [200, 100, 200],
//新通知出现是否覆盖旧的通知,覆盖(true)则永远只显示一条通知,不覆盖(false)则会多条通知重叠。非必须,默认为true
renotify: true,
//通知是否静音。非必须,默认为false,表示无声
silent: false,
//通知声源文件地址。非必须,默认为空
sound: 'mp3',
//是否不在屏幕上显示通知信息。非必须,默认为false表示要显示
noscreen: false,
//指定通知是否应该粘滞性,即不容易被用户清理。非必须,默认false表示不具粘滞性
sticky: false,
//指定通知是否保持活性,知道用户点击或关闭。非必须,默认为false
requireInteraction: false
}
+ +

事件及事件钩子

当通知被创建成功后:

+
    +
  • 通知实例具有一个静态方法可以用来关闭通知
  • +
  • 通知实例具有四个事件钩子,来跟踪通知当前的状态。这些事件可以通过事件处理跟踪onshowonclickoncloseonerror。因为Notification同样继承自EventTarget,因此可以对它调用addEventListener()方法。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const n = new Notification('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
},
timestamp: 3000
});

n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
+ +

demo

写一个简单的例子,可以打开页面体验一下,建议用最新版谷歌浏览器打开~ Notification.js

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const NotificationInstance = Notification || window.Notification;
if (!!NotificationInstance) {
const permissionNow = NotificationInstance.permission;
if (permissionNow === 'granted') {//允许通知
CreatNotification();
} else if (permissionNow === 'denied') {
console.log('用户拒绝了你!!!');
} else {
setPermission();
}
function setPermission() {
//请求获取通知权限
NotificationInstance.requestPermission(function (PERMISSION) {
if (PERMISSION === 'granted') {
CreatNotification();
} else {
console.log('用户无情残忍的拒绝了你!!!');
}
});
}
function CreatNotification() {
const n = new NotificationInstance('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
}
});
n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
setTimeout(() => {
n.close();
}, 2000);
}
}
+ +

兼容

+ +

参考

    +
  • Notification-MDN-EN
  • +
  • Notification-MDN-CN
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/10/27/for-in-and-for-of.html b/2017/10/27/for-in-and-for-of.html new file mode 100644 index 00000000..c39ca5c0 --- /dev/null +++ b/2017/10/27/for-in-and-for-of.html @@ -0,0 +1,492 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +慎重用for...in与for...of | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 慎重用for...in与for...of +

+ + +
+ + + + +
+

for...infor...of都是用于数据的遍历。for...inES5标准,用于遍历对象属性(键),而for...ofES6标准,是对for...in的修正,用于遍历对象元素(值),for...of兼容性不是很好(除了PC端老顽固IE之外,移动端某些安卓机和浏览器也是不支持它,具体可以查看MDN)。

+
+

for…in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i in arr) {
console.log(i); // "0", "1", "2", "msg", "arrCustom", "objCustom"
}

for (var i in arr) {
if (arr.hasOwnProperty(i)) {
console.log(i); // "0", "1", "2", "msg"
}
}
for (var i in obj) {
console.log(i); // "name", "w", "msg", "objCustom"
}

for (var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i); // "name", "w", "msg"
}
}
+ +

由上面的例子可以看出,for...in的一些特性:

+
    +
  • 可以对JSON对象(数组和对象)进行遍历
  • +
  • for...in会遍历对象的所有可枚举属性,包括原型,例如一些我们挂载到原型链上的一些methodname
  • +
  • 遍历很有可能不是按照对象的内部顺序(我们预期的)进行
  • +
  • 对数组遍历时index索引为字符串型,在某些时候直接进行几何运算可能达不到预期结果
  • +
+

for…of

for...in貌似强大的同时也带来很多副作用,想要达到预期的记过需要额外的代码来处理,所以for...of应运而生

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i of arr) {
console.log(i); // 2, 9, 5
}
//如果用for...of循环对象,会报错`obj is not iterable`
for (var i of obj) {
console.log(i);
}
+ +

可以看出,for...of方法在for...in上做了优化,并且限制了只能遍历数组。当然在ES5中,具有遍历数组功能的还有mapfiltersomeeveryreducereduceRight等,但是需要注意的是,有些方法不能被break句柄打断循环,使用retun也不能返回到外层,如forEach

+

其实不难看出for...in是属于鸡肋属性了,而for...of由于兼容性原因,在某些地方也应该慎用,即使是移动端也要慎用,应该它并不兼容所有内核。

+

参考

for…in
for…of
for-of循环是遍历实现iterator接口的成员

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/10/27/for-in-and-for-ofundefined b/2017/10/27/for-in-and-for-ofundefined new file mode 100644 index 00000000..baece257 --- /dev/null +++ b/2017/10/27/for-in-and-for-ofundefined @@ -0,0 +1,492 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +慎重用for...in与for...of | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 慎重用for...in与for...of +

+ + +
+ + + + +
+

for...infor...of都是用于数据的遍历。for...inES5标准,用于遍历对象属性(键),而for...ofES6标准,是对for...in的修正,用于遍历对象元素(值),for...of兼容性不是很好(除了PC端老顽固IE之外,移动端某些安卓机和浏览器也是不支持它,具体可以查看MDN)。

+
+

for…in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i in arr) {
console.log(i); // "0", "1", "2", "msg", "arrCustom", "objCustom"
}

for (var i in arr) {
if (arr.hasOwnProperty(i)) {
console.log(i); // "0", "1", "2", "msg"
}
}
for (var i in obj) {
console.log(i); // "name", "w", "msg", "objCustom"
}

for (var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i); // "name", "w", "msg"
}
}
+ +

由上面的例子可以看出,for...in的一些特性:

+
    +
  • 可以对JSON对象(数组和对象)进行遍历
  • +
  • for...in会遍历对象的所有可枚举属性,包括原型,例如一些我们挂载到原型链上的一些methodname
  • +
  • 遍历很有可能不是按照对象的内部顺序(我们预期的)进行
  • +
  • 对数组遍历时index索引为字符串型,在某些时候直接进行几何运算可能达不到预期结果
  • +
+

for…of

for...in貌似强大的同时也带来很多副作用,想要达到预期的记过需要额外的代码来处理,所以for...of应运而生

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i of arr) {
console.log(i); // 2, 9, 5
}
//如果用for...of循环对象,会报错`obj is not iterable`
for (var i of obj) {
console.log(i);
}
+ +

可以看出,for...of方法在for...in上做了优化,并且限制了只能遍历数组。当然在ES5中,具有遍历数组功能的还有mapfiltersomeeveryreducereduceRight等,但是需要注意的是,有些方法不能被break句柄打断循环,使用retun也不能返回到外层,如forEach

+

其实不难看出for...in是属于鸡肋属性了,而for...of由于兼容性原因,在某些地方也应该慎用,即使是移动端也要慎用,应该它并不兼容所有内核。

+

参考

for…in
for…of
for-of循环是遍历实现iterator接口的成员

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/02/how-to-make-a-kalendar.html b/2017/11/02/how-to-make-a-kalendar.html new file mode 100644 index 00000000..f54a9430 --- /dev/null +++ b/2017/11/02/how-to-make-a-kalendar.html @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何写一个日历组件 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 如何写一个日历组件 +

+ + +
+ + + + +
+

众所周知,虽然javascript中关于时间的API有不少,我们可以通过方法单独的获取年、月、日、时、分、秒、毫秒…貌似很多,最近写了一个日历(以前写的,但写得很烂,最近优化一下),所以下面简单的记录一下如何写一个日历,列出了一些我在写日历过程中自己封装的一些方法

+
+

效果图

先来一张效果图,由于没有UI设计,所以就自己简单的设计了一个样式(好歹我也是设计专业的,虽然已不做设计很多年),虽然略丑,但重要的是功能!!!

+

datepicker

+

思路

一个日历到底是怎样用代码生成的?其实观察一下现有的日历展现形式,可以很快的形成思路,就是:根据计算把日期号数对应到正确的星期几上,并按照顺序逐一输出。
以下是我的思路:

+
    +
  • 取得月份的天数
  • +
  • 取得月份第一天是星期几
  • +
  • 循环对应号数和星期几返回一个数组对象
      +
    • 返回数组对象的每一个子项至少包含:号数,星期几,然后根据情况添加:是否高亮,是否当前月,是否节日…等属性
    • +
    +
  • +
+

方法封装

注意,为了保持方便调用javascript的方法,以及保持输出结果符合实际,所有的方法都有如下约定:

+
    +
  • 在计算过程中
      +
    • 所有的关于月份都是0~11的数字
    • +
    • 所有的关于星期都是0~6的数字
    • +
    +
  • +
  • 在输出的结果中
      +
    • 所有关于月份的输出默认都是1-12的数字
    • +
    • 所有关于星期的输出默认都是1-7的数字
    • +
    +
  • +
+

所以在向调用方法传递参数过程中,月份以及星期几统统都需要按照实际月份减一

+

获取月份天数

javascript中没有直接获取月份天数的方法,但是它提供了一个getDate方法可以获取日期的某一天。那我们只需要获取月份的最后一天(下一个月的第0天)就可以得知这个月的天数:

+
1
2
3
4
5
6
7
8
9
// year是要获取的年份,闰年不一样
// month是要获取的月份
// 返回当前月天数
function getMonthDays(year, month){
return new Date(year, month + 1, 0).getDate();
}

getMonthDays(2016,2) //29
getMonthDays(2017,2) //28
+ +

获取星期几

1
2
3
4
5
6
7
8
9
10
// year是要获取的年份
// month是要获取的月份
// 返回数字几则是星期几
function getWeekday(year, month, day){
return new Date(year, month, day).getDate() + 1;
}

getWeekday(2016,10,9) //输出4,表示2016年11月9是星期4
getWeekday(2017,10,9) //输出5,表示2017年11月9是星期5

+ +

获取月份有几个星期

要计算月份包含几个星期,需要两个数据:月份天数和月份第一天是星期几,就能得到想要的结果

+
1
2
3
4
5
6
7
8
9
// year是要获取的年份
// month是要获取的月份
// 返回当前月包含几个星期
function getweeksInMonth(year, month){

var days = getMonthDays(year, month);
var FirstDayWeekday = getWeekday(year, month, 1);
return Math.ceil(days + FirstDayWeekday);
}
+ +

循环生成月份对象

有了以上方法之后,就可以通过循环生成一个简单的月份对象了。
在这里需要注意,日历的排序有两种:

+
    +
  • 每一行以星期日开头
  • +
  • 每一行以星期开头
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// year是要获取的年份
// month是要获取的月份
// day天,用来判断是否是当前天
// type表明要星期几开头,0为星期一开头,1为星期日开头,默认为0
// 返回当前月包含几个星期

const WEEKTABLE = [{
cn: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
cns: ['日', '一', '二', '三', '四', '五', '六'],
en: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
},{
cn: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
cns: ['一', '二', '三', '四', '五', '六', '日'],
en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
}]

getMonthDaysArray(year, month, day, type) {
if (typeof day === 'undefined' && year === YEAR && month === MONTH) day = DAY;

var dayArrays = [];
var days = this.getMonthDays(year, month), preDays = this.getMonthDays(year, month - 1);
var thisMonthFirstDayInWeek = this.getWeekday(year, month, 1), thisMonthLastDayInWeek = this.getWeekday(year, month, days);

type = !type || type !== 1 ? 0 : 1;

//上月在当月日历面板中的排列
for (var i = 0; i < thisMonthFirstDayInWeek; i++) {
dayArrays.push({
dayNum: (preDays - thisMonthFirstDayInWeek + i + 1),
weekDay: WEEKTABLE[type].cn[i]
})
}
//当月日历面板中的排列
for (var i = 1; i <= days; i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag],
selected: i === +day,
isThisMonth: true
})
};
//下月在当月日历面板中的排列
for (var i = 1; i <= (6 - thisMonthLastDayInWeek); i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + days + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag]
})
};
return dayArrays;
}
+ +

格式化时间

涉及到时间时,常常需要把时间格式进行转换,为了应对多中需求,所以自己封装了一个

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 参数fmt必须
// date参数不必须,允许字符串和时间对象,不传或者传无法转换成合法时间对象的字符串则默认当前时间,
// 年(YYYY/yyyy)固定四个占位符
// 月(M)、日(d)、小时(h)、分(m)、秒(s)可以用 1-2个占位符,严格区分大小写,
// 毫秒(ms/mss)最多三个占位符,分别对应56,056这种类型
// 例子:
// (Format("yyyy-MM-dd hh:mm:ss:ms") ==> 2006-07-02 08:09:04:23
// (Format("yyyy-MM-dd hh:mm:ss:mss") ==> 2006-07-02 08:09:04:023
// (Format("yyyy-M-d h:m:s:ms") ==> 2006-7-2 8:9:4.180
function formate(fmt, date){
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
var _rules = [{
rule: '[yY]{4}',
value: _date.getFullYear()
}, {
rule: 'M+',
value: _date.getMonth() + 1
}, {
rule: '[dD]+',
value: _date.getDate()
}, {
rule: 'h+',
value: _date.getHours()
}, {
rule: 'm+',
value: _date.getMinutes()
}, {
rule: 's+',
value: _date.getSeconds()
}, {
rule: 'ms{1,2}',
value: _date.getMilliseconds()
}];

_rules.forEach(function (_r){
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join().substr(rLen);
});
});
return fmt;
}
//调用:
var time1 = formate("YYYY/MM/DD hh:mm:ss", new Date()); //2017/11/2 11:09:20
var time2 = formate("YYYY-MM-DD", time1); //2017-11-2
var time3 = formate("MM-DD-YYYY", time2); //11-2-2017
+ +

最后

附上这些方法的源码datepicker
基于vue实现的一个日历:

+
    +
  • demovue-datepicker
  • +
  • 源码datePickerPanel.vue
  • +
+

当然这只是最简单的日历输出,思路也是超级简单(感觉有点Low),如果有大神愿意分享它的经验欢迎,来邮~

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/02/how-to-make-a-kalendarundefined b/2017/11/02/how-to-make-a-kalendarundefined new file mode 100644 index 00000000..04d8508e --- /dev/null +++ b/2017/11/02/how-to-make-a-kalendarundefined @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何写一个日历组件 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 如何写一个日历组件 +

+ + +
+ + + + +
+

众所周知,虽然javascript中关于时间的API有不少,我们可以通过方法单独的获取年、月、日、时、分、秒、毫秒…貌似很多,最近写了一个日历(以前写的,但写得很烂,最近优化一下),所以下面简单的记录一下如何写一个日历,列出了一些我在写日历过程中自己封装的一些方法

+
+

效果图

先来一张效果图,由于没有UI设计,所以就自己简单的设计了一个样式(好歹我也是设计专业的,虽然已不做设计很多年),虽然略丑,但重要的是功能!!!

+

datepicker

+

思路

一个日历到底是怎样用代码生成的?其实观察一下现有的日历展现形式,可以很快的形成思路,就是:根据计算把日期号数对应到正确的星期几上,并按照顺序逐一输出。
以下是我的思路:

+
    +
  • 取得月份的天数
  • +
  • 取得月份第一天是星期几
  • +
  • 循环对应号数和星期几返回一个数组对象
      +
    • 返回数组对象的每一个子项至少包含:号数,星期几,然后根据情况添加:是否高亮,是否当前月,是否节日…等属性
    • +
    +
  • +
+

方法封装

注意,为了保持方便调用javascript的方法,以及保持输出结果符合实际,所有的方法都有如下约定:

+
    +
  • 在计算过程中
      +
    • 所有的关于月份都是0~11的数字
    • +
    • 所有的关于星期都是0~6的数字
    • +
    +
  • +
  • 在输出的结果中
      +
    • 所有关于月份的输出默认都是1-12的数字
    • +
    • 所有关于星期的输出默认都是1-7的数字
    • +
    +
  • +
+

所以在向调用方法传递参数过程中,月份以及星期几统统都需要按照实际月份减一

+

获取月份天数

javascript中没有直接获取月份天数的方法,但是它提供了一个getDate方法可以获取日期的某一天。那我们只需要获取月份的最后一天(下一个月的第0天)就可以得知这个月的天数:

+
1
2
3
4
5
6
7
8
9
// year是要获取的年份,闰年不一样
// month是要获取的月份
// 返回当前月天数
function getMonthDays(year, month){
return new Date(year, month + 1, 0).getDate();
}

getMonthDays(2016,2) //29
getMonthDays(2017,2) //28
+ +

获取星期几

1
2
3
4
5
6
7
8
9
10
// year是要获取的年份
// month是要获取的月份
// 返回数字几则是星期几
function getWeekday(year, month, day){
return new Date(year, month, day).getDate() + 1;
}

getWeekday(2016,10,9) //输出4,表示2016年11月9是星期4
getWeekday(2017,10,9) //输出5,表示2017年11月9是星期5

+ +

获取月份有几个星期

要计算月份包含几个星期,需要两个数据:月份天数和月份第一天是星期几,就能得到想要的结果

+
1
2
3
4
5
6
7
8
9
// year是要获取的年份
// month是要获取的月份
// 返回当前月包含几个星期
function getweeksInMonth(year, month){

var days = getMonthDays(year, month);
var FirstDayWeekday = getWeekday(year, month, 1);
return Math.ceil(days + FirstDayWeekday);
}
+ +

循环生成月份对象

有了以上方法之后,就可以通过循环生成一个简单的月份对象了。
在这里需要注意,日历的排序有两种:

+
    +
  • 每一行以星期日开头
  • +
  • 每一行以星期开头
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// year是要获取的年份
// month是要获取的月份
// day天,用来判断是否是当前天
// type表明要星期几开头,0为星期一开头,1为星期日开头,默认为0
// 返回当前月包含几个星期

const WEEKTABLE = [{
cn: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
cns: ['日', '一', '二', '三', '四', '五', '六'],
en: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
},{
cn: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
cns: ['一', '二', '三', '四', '五', '六', '日'],
en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
}]

getMonthDaysArray(year, month, day, type) {
if (typeof day === 'undefined' && year === YEAR && month === MONTH) day = DAY;

var dayArrays = [];
var days = this.getMonthDays(year, month), preDays = this.getMonthDays(year, month - 1);
var thisMonthFirstDayInWeek = this.getWeekday(year, month, 1), thisMonthLastDayInWeek = this.getWeekday(year, month, days);

type = !type || type !== 1 ? 0 : 1;

//上月在当月日历面板中的排列
for (var i = 0; i < thisMonthFirstDayInWeek; i++) {
dayArrays.push({
dayNum: (preDays - thisMonthFirstDayInWeek + i + 1),
weekDay: WEEKTABLE[type].cn[i]
})
}
//当月日历面板中的排列
for (var i = 1; i <= days; i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag],
selected: i === +day,
isThisMonth: true
})
};
//下月在当月日历面板中的排列
for (var i = 1; i <= (6 - thisMonthLastDayInWeek); i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + days + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag]
})
};
return dayArrays;
}
+ +

格式化时间

涉及到时间时,常常需要把时间格式进行转换,为了应对多中需求,所以自己封装了一个

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 参数fmt必须
// date参数不必须,允许字符串和时间对象,不传或者传无法转换成合法时间对象的字符串则默认当前时间,
// 年(YYYY/yyyy)固定四个占位符
// 月(M)、日(d)、小时(h)、分(m)、秒(s)可以用 1-2个占位符,严格区分大小写,
// 毫秒(ms/mss)最多三个占位符,分别对应56,056这种类型
// 例子:
// (Format("yyyy-MM-dd hh:mm:ss:ms") ==> 2006-07-02 08:09:04:23
// (Format("yyyy-MM-dd hh:mm:ss:mss") ==> 2006-07-02 08:09:04:023
// (Format("yyyy-M-d h:m:s:ms") ==> 2006-7-2 8:9:4.180
function formate(fmt, date){
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
var _rules = [{
rule: '[yY]{4}',
value: _date.getFullYear()
}, {
rule: 'M+',
value: _date.getMonth() + 1
}, {
rule: '[dD]+',
value: _date.getDate()
}, {
rule: 'h+',
value: _date.getHours()
}, {
rule: 'm+',
value: _date.getMinutes()
}, {
rule: 's+',
value: _date.getSeconds()
}, {
rule: 'ms{1,2}',
value: _date.getMilliseconds()
}];

_rules.forEach(function (_r){
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join().substr(rLen);
});
});
return fmt;
}
//调用:
var time1 = formate("YYYY/MM/DD hh:mm:ss", new Date()); //2017/11/2 11:09:20
var time2 = formate("YYYY-MM-DD", time1); //2017-11-2
var time3 = formate("MM-DD-YYYY", time2); //11-2-2017
+ +

最后

附上这些方法的源码datepicker
基于vue实现的一个日历:

+
    +
  • demovue-datepicker
  • +
  • 源码datePickerPanel.vue
  • +
+

当然这只是最简单的日历输出,思路也是超级简单(感觉有点Low),如果有大神愿意分享它的经验欢迎,来邮~

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/15/vue-style-scoped.html b/2017/11/15/vue-style-scoped.html new file mode 100644 index 00000000..898f79ff --- /dev/null +++ b/2017/11/15/vue-style-scoped.html @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +vue中慎用style的scoped属性 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ vue中慎用style的scoped属性 +

+ + +
+ + + + +
+

在vue组件中,在style标签上添加scoped属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的,这是一个非常好的机制。但是为什么要慎用呢?在实际业务中我们往往会对公共组件样式做细微的调整,如果添加了scoped属性,那么样式将会变得不易修改。初写这篇文章时,本人没有找到一个好的方法去解决这个问题,后来经过大伙的解答,才让我恍然大悟。

+
+

何为谨慎使用

谨慎使用不是不用,而是持一种审视的目光去看待它。scoped肯定是解决了样式私有化的问题,但同时也引入了新的问题—样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的。所以我才说要谨慎使用

+

解决方案

首先要说明的问题是,最开始我以为这是一个BUG或者说一个弊端(因为当时没有搞明白scoped的真正作用),就很英勇的去提了一个issue,然后理所当然的被关闭了,关闭的理由是:scoped设计的初衷就是让样式变得私有,让它不会影响其他任何地方的样式。但是由于我在业务中经常遇到需要修改有scoped属性的组件,就写了一篇文章记录一下这个问题,希望大家谨慎的使用这个属性。
然而事实再一次证明了我的愚蠢,在vue-loader的文档中已经详细的对这个问题做了分析,并且对我遇到这种问题给出了解决方法:vue-loader的深度作用选择器。
因为我并没有去深入了解这些问题,所以注定这篇文章被大伙拍砖,😂😂😂😂😂😂

+

解决方案:vue-loader之scoped-css

+

鉴于此,虽然这篇文章没有什么价值,但为了提醒我自己深究的意义,我对后面的内容做了保留,以下内容是最开始文章的原文,请大家忽略,上面的内容才是正文,没错,正文就是这么少。

+ +

——————————–正文分割线,以下是无营养的内容——————————–

+
+

scoped实现私有化样式的原理

为什么会说,会增加复杂度?那么我们先从的实现模块的原理说起。为了方便称呼,我们假设把这种组件叫做模块私有组件,其他的未加scoped的叫做模块一般组件
通过查看DOM结构发现:vue通过在DOM结构以及css样式上加唯一不重复的标记,以保证唯一,达到样式私有化模块化的目的。具体的渲染结果是怎样的,通过一个例子来说明。

+

公共组件button组件

一个公共组件button,为了样式模块化,给其加上scoped属性,

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//button.vue
<template>
<div class="button-warp">
<button class="button">text</button>
</div>
</template>
...
<style scoped>
.button-warp{
display:inline-block;
}
.button{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
</style>
+ +

浏览器渲染button组件

button组件在浏览器渲染出的html部分和css部分分别为:

+
1
2
3
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
+ +
1
2
3
4
5
6
7
8
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
+ +

从上面的字可以看出,添加了scoped属性的组件,为了达到组件样式模块化,做了两个处理:

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
+

大家都知道css样式有一个优先级的说法,scoped的这一操作,虽然达到了组件样式模块化的目的,但是会造成一种后果:每个样式的权重加重了:理论上我们要去修改这个样式,需要更高的权重去覆盖这个样式。这是增加复杂度的其中一个维度。

+

其他组件引用button组件

上面分析了单个组件渲染后的结果,那么组件互相调用之后会出现什么样的结果呢?,具体分两种情况:模块一般组件引用模块私有组件(本质和模块私有组件引用模块一般组件一样);模块私有组件引用模块私有组件。

+

举个例子:在组件content.vue中使用了button组件,那么content.vue组件是否添加scoped属性渲染出来的结果有什么区别呢?

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style>
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
</style>
+ +

模块一般组件(未添加scoped)引用模块私有组件

如果style上没有加scoped属性,那么渲染出来htmlcss分别就是:

+
1
2
3
4
5
6
7
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
+ +

可以看出,虽然在content组件中,修改了buttonborder-raduis属性,但是由于权重关系,生效的依然是组件内部的样式(此时是外部的样式被覆盖)。所以如果要达到修改样式的目的,就必须加重我们要修改样式的权重(增加选择器层级,ID选择器,并列选择器,impotant等)

+

模块私有组件(添加scoped)引用模块私有组件

如果加了scoped属性呢?按照开始分析出来的规则(事实也是这么的):
首先是在所有的DOM节点加上data属性
然后在css选择器尾部加上data属性选择器

+

那么渲染出来htmlcss分别就是:

+
1
2
3
4
5
6
7
<div data-v-57bc25a0 class="content">
<p data-v-57bc25a0 class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-57bc25a0 data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content[data-v-57bc25a0]{
width: 1200px;
margin: 0 auto;
}
.content .button[data-v-57bc25a0]{
border-raduis: 5px;
}
+ +

对于上面的两种情况,可以明显看出来渲染后的结果大不相同。
虽然我们在content添加了想要修改button组件的样式的代码,但是仔细看,由于.content .button这句在末尾加的是content组件的scoped标记,最后这句其实根本作用不到我们想要的DOM节点上,所以这种情况我们在content内部写的任何样式都不会影响到button.vue组件,所以这就尴尬了。。。。
当然这个问题也是可以解决的,就是直接加全局样式可以修改到,但这势必会影响全部地方的组件;所以需要另外一种方法在content.vue组件内再加一个不带scoped属性的style标签,也就意味着要加两个style,一个用于私有样式,一个用于共有样式。这肯定是有点shit的,并且这两种解决方案都回避不了一个问题:权重!!!

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style scoped>
.content{
width: 1200px;
margin: 0 auto;
}
</style>
<style>
.content .button{
border-raduis: 5px;
}
</style>
+ +

这样符合规范么?貌似没看到不能这么写,并且这么写也确实生效了。。。但这样确实增加了思维的复杂度,有点苦恼啊。

+

总结scoped的渲染规则

总结一下scoped三条渲染规则

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
  • 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性
  • +
+

解决方案

对于引用的三方库,如果对方使用了scoped,我们无力改变什么,如果确实需要修改他的样式最能在不加scoped的组件中修改样式,或者全局样式直接修改,这很粗暴!
对于自己维护的组件,一定要想清楚,组件的样式能否满足所有的情况。如果确实需要加,无疑会增加使用这个组件的开发同学的工作!

+

当然对于这个问题,如果诸君有更好的解决方案,请诸君TELL ME一下下!

+

趣事

在使用scoped一定要谨慎scoped的这个特性,本人以为这是一个BUG,就去提了issue ,结果尤大很霸气的回复
scoped设计的初衷就是不能让当前组件的样式修改其他任何地方的样式,因为设计如此。所以理所当然的这个issue已被干掉。。。😂😂😂😂😂😂

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/15/vue-style-scopedundefined b/2017/11/15/vue-style-scopedundefined new file mode 100644 index 00000000..7bf531e7 --- /dev/null +++ b/2017/11/15/vue-style-scopedundefined @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +vue中慎用style的scoped属性 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ vue中慎用style的scoped属性 +

+ + +
+ + + + +
+

在vue组件中,在style标签上添加scoped属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的,这是一个非常好的机制。但是为什么要慎用呢?在实际业务中我们往往会对公共组件样式做细微的调整,如果添加了scoped属性,那么样式将会变得不易修改。初写这篇文章时,本人没有找到一个好的方法去解决这个问题,后来经过大伙的解答,才让我恍然大悟。

+
+

何为谨慎使用

谨慎使用不是不用,而是持一种审视的目光去看待它。scoped肯定是解决了样式私有化的问题,但同时也引入了新的问题—样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的。所以我才说要谨慎使用

+

解决方案

首先要说明的问题是,最开始我以为这是一个BUG或者说一个弊端(因为当时没有搞明白scoped的真正作用),就很英勇的去提了一个issue,然后理所当然的被关闭了,关闭的理由是:scoped设计的初衷就是让样式变得私有,让它不会影响其他任何地方的样式。但是由于我在业务中经常遇到需要修改有scoped属性的组件,就写了一篇文章记录一下这个问题,希望大家谨慎的使用这个属性。
然而事实再一次证明了我的愚蠢,在vue-loader的文档中已经详细的对这个问题做了分析,并且对我遇到这种问题给出了解决方法:vue-loader的深度作用选择器。
因为我并没有去深入了解这些问题,所以注定这篇文章被大伙拍砖,😂😂😂😂😂😂

+

解决方案:vue-loader之scoped-css

+

鉴于此,虽然这篇文章没有什么价值,但为了提醒我自己深究的意义,我对后面的内容做了保留,以下内容是最开始文章的原文,请大家忽略,上面的内容才是正文,没错,正文就是这么少。

+ +

——————————–正文分割线,以下是无营养的内容——————————–

+
+

scoped实现私有化样式的原理

为什么会说,会增加复杂度?那么我们先从的实现模块的原理说起。为了方便称呼,我们假设把这种组件叫做模块私有组件,其他的未加scoped的叫做模块一般组件
通过查看DOM结构发现:vue通过在DOM结构以及css样式上加唯一不重复的标记,以保证唯一,达到样式私有化模块化的目的。具体的渲染结果是怎样的,通过一个例子来说明。

+

公共组件button组件

一个公共组件button,为了样式模块化,给其加上scoped属性,

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//button.vue
<template>
<div class="button-warp">
<button class="button">text</button>
</div>
</template>
...
<style scoped>
.button-warp{
display:inline-block;
}
.button{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
</style>
+ +

浏览器渲染button组件

button组件在浏览器渲染出的html部分和css部分分别为:

+
1
2
3
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
+ +
1
2
3
4
5
6
7
8
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
+ +

从上面的字可以看出,添加了scoped属性的组件,为了达到组件样式模块化,做了两个处理:

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
+

大家都知道css样式有一个优先级的说法,scoped的这一操作,虽然达到了组件样式模块化的目的,但是会造成一种后果:每个样式的权重加重了:理论上我们要去修改这个样式,需要更高的权重去覆盖这个样式。这是增加复杂度的其中一个维度。

+

其他组件引用button组件

上面分析了单个组件渲染后的结果,那么组件互相调用之后会出现什么样的结果呢?,具体分两种情况:模块一般组件引用模块私有组件(本质和模块私有组件引用模块一般组件一样);模块私有组件引用模块私有组件。

+

举个例子:在组件content.vue中使用了button组件,那么content.vue组件是否添加scoped属性渲染出来的结果有什么区别呢?

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style>
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
</style>
+ +

模块一般组件(未添加scoped)引用模块私有组件

如果style上没有加scoped属性,那么渲染出来htmlcss分别就是:

+
1
2
3
4
5
6
7
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
+ +

可以看出,虽然在content组件中,修改了buttonborder-raduis属性,但是由于权重关系,生效的依然是组件内部的样式(此时是外部的样式被覆盖)。所以如果要达到修改样式的目的,就必须加重我们要修改样式的权重(增加选择器层级,ID选择器,并列选择器,impotant等)

+

模块私有组件(添加scoped)引用模块私有组件

如果加了scoped属性呢?按照开始分析出来的规则(事实也是这么的):
首先是在所有的DOM节点加上data属性
然后在css选择器尾部加上data属性选择器

+

那么渲染出来htmlcss分别就是:

+
1
2
3
4
5
6
7
<div data-v-57bc25a0 class="content">
<p data-v-57bc25a0 class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-57bc25a0 data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content[data-v-57bc25a0]{
width: 1200px;
margin: 0 auto;
}
.content .button[data-v-57bc25a0]{
border-raduis: 5px;
}
+ +

对于上面的两种情况,可以明显看出来渲染后的结果大不相同。
虽然我们在content添加了想要修改button组件的样式的代码,但是仔细看,由于.content .button这句在末尾加的是content组件的scoped标记,最后这句其实根本作用不到我们想要的DOM节点上,所以这种情况我们在content内部写的任何样式都不会影响到button.vue组件,所以这就尴尬了。。。。
当然这个问题也是可以解决的,就是直接加全局样式可以修改到,但这势必会影响全部地方的组件;所以需要另外一种方法在content.vue组件内再加一个不带scoped属性的style标签,也就意味着要加两个style,一个用于私有样式,一个用于共有样式。这肯定是有点shit的,并且这两种解决方案都回避不了一个问题:权重!!!

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style scoped>
.content{
width: 1200px;
margin: 0 auto;
}
</style>
<style>
.content .button{
border-raduis: 5px;
}
</style>
+ +

这样符合规范么?貌似没看到不能这么写,并且这么写也确实生效了。。。但这样确实增加了思维的复杂度,有点苦恼啊。

+

总结scoped的渲染规则

总结一下scoped三条渲染规则

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
  • 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性
  • +
+

解决方案

对于引用的三方库,如果对方使用了scoped,我们无力改变什么,如果确实需要修改他的样式最能在不加scoped的组件中修改样式,或者全局样式直接修改,这很粗暴!
对于自己维护的组件,一定要想清楚,组件的样式能否满足所有的情况。如果确实需要加,无疑会增加使用这个组件的开发同学的工作!

+

当然对于这个问题,如果诸君有更好的解决方案,请诸君TELL ME一下下!

+

趣事

在使用scoped一定要谨慎scoped的这个特性,本人以为这是一个BUG,就去提了issue ,结果尤大很霸气的回复
scoped设计的初衷就是不能让当前组件的样式修改其他任何地方的样式,因为设计如此。所以理所当然的这个issue已被干掉。。。😂😂😂😂😂😂

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/24/regex-to-something.html b/2017/11/24/regex-to-something.html new file mode 100644 index 00000000..98007b6e --- /dev/null +++ b/2017/11/24/regex-to-something.html @@ -0,0 +1,566 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +一道小小的题目引发对javascript支持正则表达式相关方法的探讨 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 一道小小的题目引发对javascript支持正则表达式相关方法的探讨 +

+ + +
+ + + + +
+

以前对于正则是非常惧怕的,因为看不懂和学不会。但最近项目中频繁的使用到了正则,因此强迫自己去学习了解,慢慢的体会到了他的魅力与强大。当然学习正则初入门的时候有些枯燥难懂,但越学越觉得轻松。本文不准备说关于正则本身的事儿,而是说一说关于javascript中关于正则的几个方法中被很多人忽略的地方。

+
+

工具

说到正则,很多人都是从抄到改到自己写,这个过程可能有时候很漫长。如一些工具能帮助你快速分析和学习正则,那么学习的过程你肯定要轻松得多。下面我推荐两个我经常使用的正则在线可视化工具,正则可视化工具图解符合铁路图规律(其实不明白什么是铁路一样很容易看懂,只是一些细微的地方和我们的常规思维有点差别)。

+
    +
  • regexper 我最常用的一个,个人觉得UI做得比其他好
  • +
  • regulex 备选,他有一个很舒心的功能,可以提供一段js,嵌套到你的网站,生成正则可视化图
  • +
+

一道小小的题目

这道题目是在群里日常闲聊时,公司同事抛出来的,具体是出自哪里本人没去考察。先先说说题目:

+
+

写一个方法使得数字末尾的连续0变成9,如1230000变成1239999

+
+

一道很简单的题目,直接正则就能搞定,也许你会写:

+
1
2
3
4
5
6
7
function zoreToNine(num){
return (num + '').replace(/0/g,9);
}
//或者
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,9);
}
+ +

这也是此题的陷阱所在,按照上面的方法,1023000就会被转化成1923999,这样是不符合要求的,所以改进一下:

+
1
2
3
4
5
6
7
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,function($1){
return $1.replace(/0/g,9);
});
}
zoreToNine(1223000); //1223999
zoreToNine(1023000); //1023999
+ +

关于这个问题的解决方案@微醺岁月同学提供了一种,位置匹配的方法,简单了很多,厉害!

+
1
"12300100000".replace(/0(?=(0+$)|\b)/g,9); //12300199999
+ +

当然解决问题的方法很多,不一定非要用正则,还完全可以使用纯算术的方法实现,大家有兴趣可以尝试,闲话少说进入这次的主题:javascript支持正则表达式相关方法,注意并不是正则对象的方法。
上述方法使用了正则,有趣的是在回调函数里有一个$1,这个$1到底是什么?所有的匹配规则匹配后都有$1这个变量么?…一连串的问题,以前我从来没有去追探过,趁着昨个比较空闲,去追探了一番,并在今天整理了一下,写下此文记录。

+

主角

javascript中正则对象有三个方法:testexeccompile,但是此次的主角并不是它们!我们讨论的是能够使用正则表示的相关方法:searchmatchreplacesplit,注意它们都是String对象的方法,使用它们必须要是String类型.

+

replace(rule[regexp/substr], replacement)

replace是一个用于替换字符串的方法,虽然看似简单,但是它隐藏的机关也是常常被人忽略。具体分析一下它的特点:
它接收两个参数
无副作用不影响原始变量
返回被改变的字符串(一定是字符串类型)

+

定义一些变量,方便全文取用。

+
1
let a = '12309800', b = '12309800[object Object]', b = '12309800{}';
+ +

参数rule

在一般情况,rule参数一般是正则、字符串、数字。
如果是字符串,将会在匹配到第一个符合条件的目标,结束方法;
如果是正则,则按照正则的规则进行匹配

+
1
2
3
4
//匹配第一个0替换成5
a.replace(0,5); //'12359800'
//匹配所有的0替换成5
a.replace(/0/g,5); //'12359855'
+ +

参数replacement

在一般情况,replacement参数是字符串、数字、者回调。

+

包含$的字符串

当参数rule为正则,并且正则至少包含有一对完整的()时,如果replacement包含有$的字符串,那么对于$n(n为大于0的整数,n的长度取决于正则中括号的对数),会被解析成一个变量。但是也仅仅只是作为一个变量,无法在字符串中进行计算,此时更类似特别的字符串模板变量。

+

一般情况下,$n中n的长度取决于正则中括号的对数,$1表示第1对括号匹配的结果,$2表示第2对匹配的结果…在正则所有的括号对中,左括号出现在第几个位置(或者说从左往右),则它就是第几对括号,以此类推。姑且我们把这种规则成为正则匹配分割规则(ps:这完全是我自己取的一个名字,方便文章后面使用和记忆)。

+
1
2
3
4
5
6
a.replace(0,'$0'); //'123$09800'
a.replace(/00/g,'$0'); //'123098$0'
a.replace(/[1-9]0+$/,'$1'); //'12309$1'
a.replace(/([1-9](0+$))/,'$1'); //'12309800',此时$1为[1-9](0+$)匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1'); //'123098',此时$1为[1-9]匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1*$2'); //'123098*00',此处的$1和$2不会安照期待的情况进行乘法计算,要进行计算可以用回调
+ +
+

请注意:虽然目前参数replacement中携带有$n仍然能正常使用,但是这种方式已经不被规范所推荐,更应该使用回调来完成这个操作。这一点谢谢@lucky4同学的指出

+
+

如果正则中包含有全局匹配标志(g),那么每次匹配的都符合上述规则

+

回调函数

先看例子:

+
1
2
3
4
5
6
7
8
9
10
11
12
a.replace(/[1-9]0+$/,function(){
console.log(arguments); //["800",5,"12309800"]、
});
a.replace(/([1-9])0+$/,function(){
console.log(arguments); //["800","8",5,"12309800"]
});
a.replace(/([1-9])(0+$)/,function(){
console.log(arguments); //["800","8","00",5,"12309800"]
});
a.replace(/(([1-9])(0+$))/,function(){
console.log(arguments); //["800","800","8","00",5,"12309800"]
});
+ +

回调函数的arguments数组部分组成:[完整匹配的字符串,$1,$2,…,$n,匹配的开始位置,原始字符串],$1...$n表示每个括号对的匹配,规则和前面的相同。
所以有一下规律:

+
1
2
3
4
5
let arr = [...arguments], len = arr.length;
(len >= 3) === true;
arr[0] = 完整匹配的字符串;
arr[len-2] = 匹配的开始位置;
arr[len-1] = 原始字符串;
+ +

注意:除了匹配的开始位置是Number类型外,其余的都是String类型

+

非常规类型参数

如果参数类型不是上述两种情况,会发生什么呢?看看下面的例子:

+
1
2
3
4
5
6
7
8
a.replace(0,null); //123null9800
a.replace(0,undefined); //123null9800
a.replace(0,[]); //1239800
a.replace(0,Array); //1230,3,123098009800
b.replace({},5); //123098005
c.replace({},5); //'12309800{}'
a.replace(0,{}); //123[object Object]9800
a.replace(0,Object); //12309800
+ +

由上面的例子可以看出,如果非正则也非字符串,则有以下规则:
null变量,则会转换成'null'字符串;
undefined变量,则会转换成'undefined'字符串;
[]变量,则会调用join()方法转换成字符串,默认以,分割,值得注意的是空数组将会被转换成空字符串(没有任何字符),通常会被匹配源字符串的开始位置(默认开始位置为空字符串);
Array变量,则会先转成成一个匹配的数组,形如[完整匹配的字符串,$1,$2,...,$n,匹配的开始位置,原始字符串],然后对它调用join()方法转换成字符串,默认以,分割;
{}变量,则会调用Object.protype.toString.call()方法把{}转换成[object Object];
Object变量,则貌似什么都没做

+

虽然可以传入这些非正常参数,但大多数情况下这些类型的参数对实际是毫无意义的,所以不建议传入以上类型的参数。同上面的正则匹配分割规则一样,为了方便使用称呼,姑且我把上面的转换规则称为正则匹配参数转换规则

+

match(rule[regex/substr])

match方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
该方法类似indexOflastIndexOf,但是它返回指定的值,而不是字符串的位置;

+

参数

参数的传递除了常规的正则和字符串以外,其余所有类型的参数都会按照上述的正则匹配参数转换规则转换成字符串形式来匹配。

+

返回值

返回值根据传入的参数类型和规则的不同,返回的内容不同,但总体来说,它是返回一个对象,而不是索引,如果没匹配到任何符合条件的字符串,则返回null

+

非全局匹配正则

如果匹配规则是一个非全局匹配规则,那么,它此时的返回值是一个伪数组对象(likeArr),形如:[一个展开的匹配到的字符串数组, 匹配到的字符串位置, 原始字符串],它有如下规律:

+
1
2
3
4
5
var likeArr = a.match(regex);
likeArr[0] = 匹配到的字符串;
likeArr[1...n] = 正则匹配分割规则匹配的字符串;
likeArr.index = 匹配到字符串的位置
likeArr.inupt = 原始字符串
+ +

看例子:

+
1
2
3
4
a.match(/[1-9]0+$/); //[0:'800',index:5,input:'12309800']
a.match(/([1-9])0+$/); //[0:'800',1:'8',index:5,input:'12309800']
a.match(/[1-9](0+$)/); //[0:'800',1:'00',index:5,input:'12309800']
a.match(/([1-9])(0+$)/); //[0:'800',1:'8',2:'00',index:5,input:'12309800']
+ +

全局匹配正则

如果匹配规则是一个全局匹配规则(正在携带有g标志),那么,它此时的返回值是一个数组对象(arr),形如:[匹配到的字符串数1,匹配到的字符串数2,匹配到的字符串数3];
看例子:

+
1
2
a.match(/[1-9]0/); //[0:'30',index:2,input:'12309800']
a.match(/[1-9]0/g); //[0:'30',1:'80']
+ +

search(rule[regex/substr])

search方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
stringObject中第一个与rule相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回-1
注意:

+
    +
  • search方法不执行全局匹配,它将忽略标志g
  • +
  • 忽略regexplastIndex属性,总是从字符串的开始进行检索,这意味着它总是返回stringObject的第一个匹配的位置
  • +
+

同样,search可以传入任何参数类型,它会遵循正则匹配参数转换规则进行转换

+

split(rule[regex/substr],len)

这个方法就不用多说,很常用的字符串分割方法。
第二个参数的作用就是限制返回值的长度,表示返回值的最大长度

+

当然,它依然可以传入任何参数类型,会遵循正则匹配参数转换规则进行转换

+
+

有一段加密的后的密码,我们需要分离出字符串’12a344gg333tt445656ffa6778ii99’中的前三组数字,通过某种计算才能得出正确的密码

+
+
1
'12a344gg333tt445656ffa6778ii99'.split(/[a-zA-Z]+/g,3); //['12','334','333']
+ +

最后

写了这么多,突然发现以前仅仅是在用这些方法,了解得很不够深入。越是学习才发现其中的奥秘!学无止境,与诸君共勉!
以上内容如有错误之处,希望诸君不吝指出!

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/24/regex-to-somethingundefined b/2017/11/24/regex-to-somethingundefined new file mode 100644 index 00000000..24408679 --- /dev/null +++ b/2017/11/24/regex-to-somethingundefined @@ -0,0 +1,566 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +一道小小的题目引发对javascript支持正则表达式相关方法的探讨 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 一道小小的题目引发对javascript支持正则表达式相关方法的探讨 +

+ + +
+ + + + +
+

以前对于正则是非常惧怕的,因为看不懂和学不会。但最近项目中频繁的使用到了正则,因此强迫自己去学习了解,慢慢的体会到了他的魅力与强大。当然学习正则初入门的时候有些枯燥难懂,但越学越觉得轻松。本文不准备说关于正则本身的事儿,而是说一说关于javascript中关于正则的几个方法中被很多人忽略的地方。

+
+

工具

说到正则,很多人都是从抄到改到自己写,这个过程可能有时候很漫长。如一些工具能帮助你快速分析和学习正则,那么学习的过程你肯定要轻松得多。下面我推荐两个我经常使用的正则在线可视化工具,正则可视化工具图解符合铁路图规律(其实不明白什么是铁路一样很容易看懂,只是一些细微的地方和我们的常规思维有点差别)。

+
    +
  • regexper 我最常用的一个,个人觉得UI做得比其他好
  • +
  • regulex 备选,他有一个很舒心的功能,可以提供一段js,嵌套到你的网站,生成正则可视化图
  • +
+

一道小小的题目

这道题目是在群里日常闲聊时,公司同事抛出来的,具体是出自哪里本人没去考察。先先说说题目:

+
+

写一个方法使得数字末尾的连续0变成9,如1230000变成1239999

+
+

一道很简单的题目,直接正则就能搞定,也许你会写:

+
1
2
3
4
5
6
7
function zoreToNine(num){
return (num + '').replace(/0/g,9);
}
//或者
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,9);
}
+ +

这也是此题的陷阱所在,按照上面的方法,1023000就会被转化成1923999,这样是不符合要求的,所以改进一下:

+
1
2
3
4
5
6
7
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,function($1){
return $1.replace(/0/g,9);
});
}
zoreToNine(1223000); //1223999
zoreToNine(1023000); //1023999
+ +

关于这个问题的解决方案@微醺岁月同学提供了一种,位置匹配的方法,简单了很多,厉害!

+
1
"12300100000".replace(/0(?=(0+$)|\b)/g,9); //12300199999
+ +

当然解决问题的方法很多,不一定非要用正则,还完全可以使用纯算术的方法实现,大家有兴趣可以尝试,闲话少说进入这次的主题:javascript支持正则表达式相关方法,注意并不是正则对象的方法。
上述方法使用了正则,有趣的是在回调函数里有一个$1,这个$1到底是什么?所有的匹配规则匹配后都有$1这个变量么?…一连串的问题,以前我从来没有去追探过,趁着昨个比较空闲,去追探了一番,并在今天整理了一下,写下此文记录。

+

主角

javascript中正则对象有三个方法:testexeccompile,但是此次的主角并不是它们!我们讨论的是能够使用正则表示的相关方法:searchmatchreplacesplit,注意它们都是String对象的方法,使用它们必须要是String类型.

+

replace(rule[regexp/substr], replacement)

replace是一个用于替换字符串的方法,虽然看似简单,但是它隐藏的机关也是常常被人忽略。具体分析一下它的特点:
它接收两个参数
无副作用不影响原始变量
返回被改变的字符串(一定是字符串类型)

+

定义一些变量,方便全文取用。

+
1
let a = '12309800', b = '12309800[object Object]', b = '12309800{}';
+ +

参数rule

在一般情况,rule参数一般是正则、字符串、数字。
如果是字符串,将会在匹配到第一个符合条件的目标,结束方法;
如果是正则,则按照正则的规则进行匹配

+
1
2
3
4
//匹配第一个0替换成5
a.replace(0,5); //'12359800'
//匹配所有的0替换成5
a.replace(/0/g,5); //'12359855'
+ +

参数replacement

在一般情况,replacement参数是字符串、数字、者回调。

+

包含$的字符串

当参数rule为正则,并且正则至少包含有一对完整的()时,如果replacement包含有$的字符串,那么对于$n(n为大于0的整数,n的长度取决于正则中括号的对数),会被解析成一个变量。但是也仅仅只是作为一个变量,无法在字符串中进行计算,此时更类似特别的字符串模板变量。

+

一般情况下,$n中n的长度取决于正则中括号的对数,$1表示第1对括号匹配的结果,$2表示第2对匹配的结果…在正则所有的括号对中,左括号出现在第几个位置(或者说从左往右),则它就是第几对括号,以此类推。姑且我们把这种规则成为正则匹配分割规则(ps:这完全是我自己取的一个名字,方便文章后面使用和记忆)。

+
1
2
3
4
5
6
a.replace(0,'$0'); //'123$09800'
a.replace(/00/g,'$0'); //'123098$0'
a.replace(/[1-9]0+$/,'$1'); //'12309$1'
a.replace(/([1-9](0+$))/,'$1'); //'12309800',此时$1为[1-9](0+$)匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1'); //'123098',此时$1为[1-9]匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1*$2'); //'123098*00',此处的$1和$2不会安照期待的情况进行乘法计算,要进行计算可以用回调
+ +
+

请注意:虽然目前参数replacement中携带有$n仍然能正常使用,但是这种方式已经不被规范所推荐,更应该使用回调来完成这个操作。这一点谢谢@lucky4同学的指出

+
+

如果正则中包含有全局匹配标志(g),那么每次匹配的都符合上述规则

+

回调函数

先看例子:

+
1
2
3
4
5
6
7
8
9
10
11
12
a.replace(/[1-9]0+$/,function(){
console.log(arguments); //["800",5,"12309800"]、
});
a.replace(/([1-9])0+$/,function(){
console.log(arguments); //["800","8",5,"12309800"]
});
a.replace(/([1-9])(0+$)/,function(){
console.log(arguments); //["800","8","00",5,"12309800"]
});
a.replace(/(([1-9])(0+$))/,function(){
console.log(arguments); //["800","800","8","00",5,"12309800"]
});
+ +

回调函数的arguments数组部分组成:[完整匹配的字符串,$1,$2,…,$n,匹配的开始位置,原始字符串],$1...$n表示每个括号对的匹配,规则和前面的相同。
所以有一下规律:

+
1
2
3
4
5
let arr = [...arguments], len = arr.length;
(len >= 3) === true;
arr[0] = 完整匹配的字符串;
arr[len-2] = 匹配的开始位置;
arr[len-1] = 原始字符串;
+ +

注意:除了匹配的开始位置是Number类型外,其余的都是String类型

+

非常规类型参数

如果参数类型不是上述两种情况,会发生什么呢?看看下面的例子:

+
1
2
3
4
5
6
7
8
a.replace(0,null); //123null9800
a.replace(0,undefined); //123null9800
a.replace(0,[]); //1239800
a.replace(0,Array); //1230,3,123098009800
b.replace({},5); //123098005
c.replace({},5); //'12309800{}'
a.replace(0,{}); //123[object Object]9800
a.replace(0,Object); //12309800
+ +

由上面的例子可以看出,如果非正则也非字符串,则有以下规则:
null变量,则会转换成'null'字符串;
undefined变量,则会转换成'undefined'字符串;
[]变量,则会调用join()方法转换成字符串,默认以,分割,值得注意的是空数组将会被转换成空字符串(没有任何字符),通常会被匹配源字符串的开始位置(默认开始位置为空字符串);
Array变量,则会先转成成一个匹配的数组,形如[完整匹配的字符串,$1,$2,...,$n,匹配的开始位置,原始字符串],然后对它调用join()方法转换成字符串,默认以,分割;
{}变量,则会调用Object.protype.toString.call()方法把{}转换成[object Object];
Object变量,则貌似什么都没做

+

虽然可以传入这些非正常参数,但大多数情况下这些类型的参数对实际是毫无意义的,所以不建议传入以上类型的参数。同上面的正则匹配分割规则一样,为了方便使用称呼,姑且我把上面的转换规则称为正则匹配参数转换规则

+

match(rule[regex/substr])

match方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
该方法类似indexOflastIndexOf,但是它返回指定的值,而不是字符串的位置;

+

参数

参数的传递除了常规的正则和字符串以外,其余所有类型的参数都会按照上述的正则匹配参数转换规则转换成字符串形式来匹配。

+

返回值

返回值根据传入的参数类型和规则的不同,返回的内容不同,但总体来说,它是返回一个对象,而不是索引,如果没匹配到任何符合条件的字符串,则返回null

+

非全局匹配正则

如果匹配规则是一个非全局匹配规则,那么,它此时的返回值是一个伪数组对象(likeArr),形如:[一个展开的匹配到的字符串数组, 匹配到的字符串位置, 原始字符串],它有如下规律:

+
1
2
3
4
5
var likeArr = a.match(regex);
likeArr[0] = 匹配到的字符串;
likeArr[1...n] = 正则匹配分割规则匹配的字符串;
likeArr.index = 匹配到字符串的位置
likeArr.inupt = 原始字符串
+ +

看例子:

+
1
2
3
4
a.match(/[1-9]0+$/); //[0:'800',index:5,input:'12309800']
a.match(/([1-9])0+$/); //[0:'800',1:'8',index:5,input:'12309800']
a.match(/[1-9](0+$)/); //[0:'800',1:'00',index:5,input:'12309800']
a.match(/([1-9])(0+$)/); //[0:'800',1:'8',2:'00',index:5,input:'12309800']
+ +

全局匹配正则

如果匹配规则是一个全局匹配规则(正在携带有g标志),那么,它此时的返回值是一个数组对象(arr),形如:[匹配到的字符串数1,匹配到的字符串数2,匹配到的字符串数3];
看例子:

+
1
2
a.match(/[1-9]0/); //[0:'30',index:2,input:'12309800']
a.match(/[1-9]0/g); //[0:'30',1:'80']
+ +

search(rule[regex/substr])

search方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
stringObject中第一个与rule相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回-1
注意:

+
    +
  • search方法不执行全局匹配,它将忽略标志g
  • +
  • 忽略regexplastIndex属性,总是从字符串的开始进行检索,这意味着它总是返回stringObject的第一个匹配的位置
  • +
+

同样,search可以传入任何参数类型,它会遵循正则匹配参数转换规则进行转换

+

split(rule[regex/substr],len)

这个方法就不用多说,很常用的字符串分割方法。
第二个参数的作用就是限制返回值的长度,表示返回值的最大长度

+

当然,它依然可以传入任何参数类型,会遵循正则匹配参数转换规则进行转换

+
+

有一段加密的后的密码,我们需要分离出字符串’12a344gg333tt445656ffa6778ii99’中的前三组数字,通过某种计算才能得出正确的密码

+
+
1
'12a344gg333tt445656ffa6778ii99'.split(/[a-zA-Z]+/g,3); //['12','334','333']
+ +

最后

写了这么多,突然发现以前仅仅是在用这些方法,了解得很不够深入。越是学习才发现其中的奥秘!学无止境,与诸君共勉!
以上内容如有错误之处,希望诸君不吝指出!

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/26/javascript-sinpats.html b/2017/11/26/javascript-sinpats.html new file mode 100644 index 00000000..38406408 --- /dev/null +++ b/2017/11/26/javascript-sinpats.html @@ -0,0 +1,495 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常用javascript代码片段 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 常用javascript代码片段 +

+ + +
+ + + + +
+

下面记录的主要是本人在项目中遇到的一些问题的解决方案或者个人觉得精妙的代码,方案也许来自于自己的想法,也有可能来自于网络(我尽量给出出处,以便大家追源溯流),也许干脆就是一个插件库(实际在项目中对于一些小的功能个人是拒绝引入不纯粹的三方库的)。肯定,这其中有些方案也不是尽善尽美,我会一一列出。如果对于某个问题在你看来有更好的解决方案,请不吝指出!

+
+

placeholder属性支持

有时候项目中placeholder也许要兼容某些特殊的浏览器,所以才有了下面这段代码。这种方案有一个缺点就是:如果你恰好要对这个输入框绑定focus事件并操作它的值,必须要小心处理

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//修复不支持placeholder属性 start
const isSurportPlder = "placeholder" in document.createElement("input"); // 判断浏览器是否支持 placeholder
if (!isSurportPlder) {
$("[placeholder]").focus(function () {
const _this = $(this);
if (_this.val() == _this.attr("placeholder")) {
_this.val('');
}
}).blur(function () {
const _this = $(this);
if (_this.val() == '' || _this.val() == _this.attr("placeholder")) {
_this.val(_this.attr("placeholder"));
}
}).blur();
};
+ +

格式化时间

关于格式化时间有很多插件,其中比较有名的就可以列很大一堆出来,比如老牌的moment.js,最近比较多star的luxon.js; 对于为什么不选择他么,上面已经说了原因了。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
formate(fmt, date) {
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
const _rules = [{
rule: '[yY]{4}',
value: date.getFullYear()
}, {
rule: 'M+',
value: date.getMonth() + 1
}, {
rule: '[dD]+',
value: date.getDate()
}, {
rule: 'h+',
value: date.getHours()
}, {
rule: 'm+',
value: date.getMinutes()
}, {
rule: 's+',
value: date.getSeconds()
}, {
rule: 'ms{1,2}',
value: date.getMilliseconds()
}];

_rules.forEach((_r) => {
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join('').substr(rLen);
});
});
return fmt;
}
+ +

解析浏览器的版本等详细信息

浏览器的判断涉及到很多复杂的变量和参数,所以偷懒选取了一个比较好的库,这个库它唯一的功能就是识别浏览器的版本等详细信息。
如果大家有更好的库,请推荐过来!

+

browser.js

+

数字转换成千分位格式(如:123,456,9.89)

1
2
3
function translateThree(num) {
return num.split('').reverse().join('').replace(/(\d{3}(?=\d)(?!\d+\.|$))/g, '$1,').split('').reverse().join('');
}
+ +

判断任意数据的类型

精准的判断类型,'2'会识别成string类型,2会识别成number类型

+
1
2
3
4
5
6
7
8
9
10
function tryType(para) {
const type = typeof para;
if (type === "number" && isNaN(para)) return "NaN";
if (type !== "object") return type;
return Object.prototype.toString
.call(para)
.replace(/[\[\]]/g, "")
.split(" ")[1]
.toLowerCase();
}
+ +

判断是不是一个可计算的数字

上面的tryType方法会将'2'2区别成两种类型,而这个方法将忽略这种区别

+
1
2
3
4
5
function isNumber(para) {
if (window.isNumber) return window.isNumber(para);
if (Number.isNumber) return Number.isNumber(para);
return typeof para !== "undefined" && !isNaN(para);
}
+ +

深度取值防止代码挂掉

在项目中经常会遇到层级很深的json数据,这时候可能就会写类似这样的代码a[2].list[3].name,这种代码很不可靠,很容易由于数据的一点小错误,导致整段js代码挂掉。出的问题多了自然就会思考问题的解决方案,正当我苦思不得其解时,看到一篇文章如何优雅安全地在深层数据结构中取值,他详细的分析了深层取值如何避免报错的情况。
我阅读了博主的文章,整理了一下思路,没有像原博主那样使用xs && xs[x]判断来打断取值,是因为这种情况可能会把0这种类型的值误伤

+
1
2
3
4
function getValueFromDeepData (props, target){
if (!props || !target) return undefined;
return props.reduce((pre, nxt) => (typeof pre === 'undefined' || typeof pre[nxt] === 'undefined' ? undefined : pre[nxt]), target);
}
+ +

反转义字符串

何谓反转义字符串?就是后端在传输HTML代码的时候往往会对字符串处理:把一些特殊符号转义了;当我们拿到HTML渲染到页面希望他按照HTML代码的格式来显示,而不是按照转义的字符串显示成文本,所以我们需要对这段字符串反转义!
网上看到很多解决方案是通过正则的方式一一替换过来,但是个人觉得这样不好维护。后面发现一种浏览器自动转换的办法,利用这一特性,可以通过js创建一个虚拟的DOM节点,然后把需要转义的字符串使用innerHTML方法放进去,再通过nodeValue方法取出来。
但这种方法需要注意的是:e.childNodes是一个数组,它将_html分段(每65536字符分一段)存储到e.childNodes[i]中,使用e.childNodes方法取得反转义后的字符串需要循环e.childNodes数组。我看到网上很多方法都是直接取的e.childNodes[0].nodeValue,这样在内容过多的时候,是无法把数据取完整的

+
1
2
3
4
5
6
7
8
function htmlDecode(str) {
const e = document.createElement('div'), _html = '';
e.innerHTML = str;
for (let i = 0; i < e.childNodes.length; i++) {
_html += e.childNodes[i].nodeValue;
};
return _html;
};
+ +

未完

+
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/26/javascript-sinpatsundefined b/2017/11/26/javascript-sinpatsundefined new file mode 100644 index 00000000..7a4d7b64 --- /dev/null +++ b/2017/11/26/javascript-sinpatsundefined @@ -0,0 +1,495 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常用javascript代码片段 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 常用javascript代码片段 +

+ + +
+ + + + +
+

下面记录的主要是本人在项目中遇到的一些问题的解决方案或者个人觉得精妙的代码,方案也许来自于自己的想法,也有可能来自于网络(我尽量给出出处,以便大家追源溯流),也许干脆就是一个插件库(实际在项目中对于一些小的功能个人是拒绝引入不纯粹的三方库的)。肯定,这其中有些方案也不是尽善尽美,我会一一列出。如果对于某个问题在你看来有更好的解决方案,请不吝指出!

+
+

placeholder属性支持

有时候项目中placeholder也许要兼容某些特殊的浏览器,所以才有了下面这段代码。这种方案有一个缺点就是:如果你恰好要对这个输入框绑定focus事件并操作它的值,必须要小心处理

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//修复不支持placeholder属性 start
const isSurportPlder = "placeholder" in document.createElement("input"); // 判断浏览器是否支持 placeholder
if (!isSurportPlder) {
$("[placeholder]").focus(function () {
const _this = $(this);
if (_this.val() == _this.attr("placeholder")) {
_this.val('');
}
}).blur(function () {
const _this = $(this);
if (_this.val() == '' || _this.val() == _this.attr("placeholder")) {
_this.val(_this.attr("placeholder"));
}
}).blur();
};
+ +

格式化时间

关于格式化时间有很多插件,其中比较有名的就可以列很大一堆出来,比如老牌的moment.js,最近比较多star的luxon.js; 对于为什么不选择他么,上面已经说了原因了。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
formate(fmt, date) {
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
const _rules = [{
rule: '[yY]{4}',
value: date.getFullYear()
}, {
rule: 'M+',
value: date.getMonth() + 1
}, {
rule: '[dD]+',
value: date.getDate()
}, {
rule: 'h+',
value: date.getHours()
}, {
rule: 'm+',
value: date.getMinutes()
}, {
rule: 's+',
value: date.getSeconds()
}, {
rule: 'ms{1,2}',
value: date.getMilliseconds()
}];

_rules.forEach((_r) => {
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join('').substr(rLen);
});
});
return fmt;
}
+ +

解析浏览器的版本等详细信息

浏览器的判断涉及到很多复杂的变量和参数,所以偷懒选取了一个比较好的库,这个库它唯一的功能就是识别浏览器的版本等详细信息。
如果大家有更好的库,请推荐过来!

+

browser.js

+

数字转换成千分位格式(如:123,456,9.89)

1
2
3
function translateThree(num) {
return num.split('').reverse().join('').replace(/(\d{3}(?=\d)(?!\d+\.|$))/g, '$1,').split('').reverse().join('');
}
+ +

判断任意数据的类型

精准的判断类型,'2'会识别成string类型,2会识别成number类型

+
1
2
3
4
5
6
7
8
9
10
function tryType(para) {
const type = typeof para;
if (type === "number" && isNaN(para)) return "NaN";
if (type !== "object") return type;
return Object.prototype.toString
.call(para)
.replace(/[\[\]]/g, "")
.split(" ")[1]
.toLowerCase();
}
+ +

判断是不是一个可计算的数字

上面的tryType方法会将'2'2区别成两种类型,而这个方法将忽略这种区别

+
1
2
3
4
5
function isNumber(para) {
if (window.isNumber) return window.isNumber(para);
if (Number.isNumber) return Number.isNumber(para);
return typeof para !== "undefined" && !isNaN(para);
}
+ +

深度取值防止代码挂掉

在项目中经常会遇到层级很深的json数据,这时候可能就会写类似这样的代码a[2].list[3].name,这种代码很不可靠,很容易由于数据的一点小错误,导致整段js代码挂掉。出的问题多了自然就会思考问题的解决方案,正当我苦思不得其解时,看到一篇文章如何优雅安全地在深层数据结构中取值,他详细的分析了深层取值如何避免报错的情况。
我阅读了博主的文章,整理了一下思路,没有像原博主那样使用xs && xs[x]判断来打断取值,是因为这种情况可能会把0这种类型的值误伤

+
1
2
3
4
function getValueFromDeepData (props, target){
if (!props || !target) return undefined;
return props.reduce((pre, nxt) => (typeof pre === 'undefined' || typeof pre[nxt] === 'undefined' ? undefined : pre[nxt]), target);
}
+ +

反转义字符串

何谓反转义字符串?就是后端在传输HTML代码的时候往往会对字符串处理:把一些特殊符号转义了;当我们拿到HTML渲染到页面希望他按照HTML代码的格式来显示,而不是按照转义的字符串显示成文本,所以我们需要对这段字符串反转义!
网上看到很多解决方案是通过正则的方式一一替换过来,但是个人觉得这样不好维护。后面发现一种浏览器自动转换的办法,利用这一特性,可以通过js创建一个虚拟的DOM节点,然后把需要转义的字符串使用innerHTML方法放进去,再通过nodeValue方法取出来。
但这种方法需要注意的是:e.childNodes是一个数组,它将_html分段(每65536字符分一段)存储到e.childNodes[i]中,使用e.childNodes方法取得反转义后的字符串需要循环e.childNodes数组。我看到网上很多方法都是直接取的e.childNodes[0].nodeValue,这样在内容过多的时候,是无法把数据取完整的

+
1
2
3
4
5
6
7
8
function htmlDecode(str) {
const e = document.createElement('div'), _html = '';
e.innerHTML = str;
for (let i = 0; i < e.childNodes.length; i++) {
_html += e.childNodes[i].nodeValue;
};
return _html;
};
+ +

未完

+
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/29/blow-forever.html b/2017/11/29/blow-forever.html new file mode 100644 index 00000000..fa32d498 --- /dev/null +++ b/2017/11/29/blow-forever.html @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +风继续吹 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 风继续吹 +

+ + +
+ + + + +
+

最近在某些比较看重的事情上,处理的很失败,感觉整个心态都发生了变化。下班独行的那段路上,突然惊醒自己也许需要做出一些改变,晚饭后静静的思考了一阵:似乎勇往前行才是我最需需要的改变。古语有云:走自己的路,让别人去说吧。是的,生活需要你不断踽踽独行,活在他人的世界里是可怕的,特别他(她)还是陌生人。随手记录一些句子,它们是我最近的心态和生活的写照,也以此鞭策自己砥砺前行。

+
+
+

时间不会因你沮丧而停滞不前
生活不会因你懊恼而雨过天晴

+

如风,继续吹
吹来往日的欢歌笑语
吹来远方的殷殷思念
化作雨露滋润心田

+

如风,继续吹
随风飘荡去远方
随风逐浪勇往前
风不止浪不息

+

如风,继续吹
时而狂乱,掠过天空大地,高山河流
时而低喃,轻抚绿茵花朵,平原盆谷
最后都消散在其它风里

+

然后
其它风,继续吹

+

—-< 谨鼓励我砥砺前行 >

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2017/11/29/blow-foreverundefined b/2017/11/29/blow-foreverundefined new file mode 100644 index 00000000..f402e2a7 --- /dev/null +++ b/2017/11/29/blow-foreverundefined @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +风继续吹 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 风继续吹 +

+ + +
+ + + + +
+

最近在某些比较看重的事情上,处理的很失败,感觉整个心态都发生了变化。下班独行的那段路上,突然惊醒自己也许需要做出一些改变,晚饭后静静的思考了一阵:似乎勇往前行才是我最需需要的改变。古语有云:走自己的路,让别人去说吧。是的,生活需要你不断踽踽独行,活在他人的世界里是可怕的,特别他(她)还是陌生人。随手记录一些句子,它们是我最近的心态和生活的写照,也以此鞭策自己砥砺前行。

+
+
+

时间不会因你沮丧而停滞不前
生活不会因你懊恼而雨过天晴

+

如风,继续吹
吹来往日的欢歌笑语
吹来远方的殷殷思念
化作雨露滋润心田

+

如风,继续吹
随风飘荡去远方
随风逐浪勇往前
风不止浪不息

+

如风,继续吹
时而狂乱,掠过天空大地,高山河流
时而低喃,轻抚绿茵花朵,平原盆谷
最后都消散在其它风里

+

然后
其它风,继续吹

+

—-< 谨鼓励我砥砺前行 >

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2018-01-20-Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271undefined" "b/2018-01-20-Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271undefined" new file mode 100644 index 00000000..d40dbcc2 --- /dev/null +++ "b/2018-01-20-Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271undefined" @@ -0,0 +1,612 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列常用命令之放弃修改 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列常用命令之放弃修改 +

+ + +
+ + + + +
+

Git 是一种流行的分布式版本控制系统,它允许开发者在不同的分支上进行工作,同时保持代码的完整性和历史记录。在使用 Git 进行版本控制的过程中,我们可能会遇到需要放弃当前工作进度的情况,例如,当发现当前分支的修改与主分支的代码不兼容,或者需要切换到另一个分支继续工作时。

+
+

概述

这篇文章会介绍如何在 Git 中放弃对文件的修改,包括以下几个方面:

+
    +
  • 放弃对单个文件/文件夹的修改:如果只想放弃对某个特定文件的修改,可以使用 git checkout 命令。
  • +
  • 放弃所有未提交的修改:如果需要放弃对所有未提交文件的修改,可以使用 git reset 命令。
  • +
  • 撤销已经提交的更改:如果需要撤销已经提交但尚未推送到远程仓库的更改,可以使用 git revert 命令。
  • +
  • 清理未跟踪的文件:有时候,你可能想要删除那些被错误添加到工作目录中但未被 Git 跟踪的文件,可以使用 git clean 命令。
  • +
+

文章可能会通过具体的命令示例和场景,帮助读者理解在不同情况下如何使用 Git 命令来放弃修改,以保持工作流程的流畅和代码的整洁。

+

两个概念

在 Git 中,indexcommit 是两个核心概念,它们在版本控制过程中扮演着不同的角色,理解这两个概念有助于加深对git工作原理的理解:

+

Index(索引)

    +
  • Index 是 Git 用来准备下一次提交的暂存区。你可以将其视为一个准备提交的文件列表,这些文件已经被审查和选择,准备成为下一次提交的一部分。
  • +
  • 当你对工作目录中的文件进行更改并希望将这些更改包含在下一次提交中时,你需要使用 git add 命令将它们添加到 index 中。
  • +
  • Index 允许你在提交之前进行多次修改,并且可以对这些修改进行排序和组织,以确保提交是有意义的。
  • +
  • Index 是一个文件,位于 .git/index 文件中。
  • +
+

Commit(提交)

    +
  • Commit 是项目快照的记录,它包含了项目的某个特定版本。当你执行 git commit 命令时,Git 会将当前 index 中的内容以及一些附加的元数据(如提交信息、作者、时间戳等)打包成一个提交对象,并存储在本地仓库中。

    +
  • +
  • 提交是不可变的,意味着一旦创建,其内容就不能被更改。这保证了项目历史的完整性和一致性。

    +
  • +
  • 提交可以看作是项目的版本号,每个提交都有一个唯一的哈希值,用于标识和引用特定的项目状态。

    +
  • +
  • 提交是 Git 分布式特性的基础,因为它允许开发者在本地进行提交,而不必立即与远程仓库同步。

    +
  • +
+

两者之间的关系可以这样理解:

+
    +
  • 你首先对文件进行修改。
  • +
  • 使用 git add 将这些修改的文件添加到 index 中,这时候修改被暂存,准备提交。
  • +
  • 使用 git commit 将 index 中的内容以及提交信息一起打包,创建一个新的提交对象。
  • +
+

简而言之,index 是准备提交的暂存区,而 commit 是已经提交的快照记录。在进行提交之前,你可以多次修改 index,但是一旦执行了 commit,那么这个提交就是最终的,不可更改的。

+

请深刻理解这两个概念,有助于帮助您理解后面的内容

+

命令

Git Checkout - 切换分支或恢复工作目录树文件

+

更新工作区中的文件,使其与索引或指定的树中的版本一致。 如果没有给出pathspec,’git checkout’也将更新HEAD,将指定的分支设为当前分支

+
+

使用git checkout来切换分支用的很多,但是没想到还可以用来恢复工作目录,可以理解为:
将一个文件从另一个提交中取出,从索引中恢复,所以它不会影响到git add及之后的内容。

+

具体用法如下:

+
1
git checkout -- <filename>
+ +

注意这里的filename是支持通配符匹配的,比如

+
1
2
3
4
5
6
7
8
# 恢复hello.html
git checkout -- hello.html

# 恢复hello.开头的文件
git checkout -- "hello.*"

# 恢复.html结尾的文件
git checkout -- *.html
+ +
+

注意此方法只是将本地文件恢复了,没有对git add 和git commit 产生影响

+
+

Git-reset - 重置当前HEAD到指定的状态

+

复制条目到索索引或者将当前的分支头(HEAD)设置为某个commit

+
+

Git-revert - 还原某些现有提交

+

给定一个或多个现有提交,还原相关补丁引入的更改,并记录一些新提交来记录这些更改。 这要求你的工作区是干净的(没有对 HEAD 提交的修改)。

+
+

Git-clean - 删除工作目录树中未跟踪的文件

+

从当前目录开始,通过递归删除不在版本控制之下的文件来清理工作区。
通常情况下,只有 Git 未知的文件会被删除,但如果指定了 -x 选项,被忽略的文件也会被删除。例如,这对删除所有构建产品很有用。
如果给出任何可选的<路径规范>…​参数,只有那些与路径规范相匹配的路径会受到影响

+
+

一些常见场景

本地修改了一些文件 (并没有使用 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
git checkout -- <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
1
git checkout .
+ +

本地新增了一些文件 (并没有 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
rm  -rf <filename>
+ +
    +
  •   所有文件
  • +
+
1
git clean -xdf
+ +
+

删除新增的文件,如果文件已经已经 git add 到暂存区,并不会删除!

+
+
    +
  •   所有文件和文件夹:
  • +
+
1
git clean -xdff
+ +
+

ps:谨慎操作: 本命令删除新增的文件和文件夹,如果文件已经已经 git add 到暂存区,并不会删除!

+
+

本地修改/新增了一些文件,已经 Git Add 到暂存区,想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
git reset HEAD <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
1
git reset HEAD .
+ +

本地通过 Git Add 和 Git Commit 后,想要撤销此次 Commit

    +
  • 撤销 commit, 同时保留该 commit 修改:
  • +
+
1
git reset <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前 6 位。

+
+

撤销之后,你所做的已经 commit 的修改还在工作区!

+
+
    +
  •   撤销 commit, 同时本地删除该 commit 修改:
  • +
+
1
git reset --hard <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前6位

+
+

ps:谨慎操作: 撤销之后,你所做的已经 commit 的修改将会清除,仍在工作区/暂存区的代码也将会清除!

+
+

参考

1.Git - git-checkout Documentation
2.Git - git-reset Documentation
3.Git - git-revert Documentation
4.Git - git-clean Documentation

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2018-07-21-node.js-version-management-artifact-nvmundefined b/2018-07-21-node.js-version-management-artifact-nvmundefined new file mode 100644 index 00000000..9f8330bf --- /dev/null +++ b/2018-07-21-node.js-version-management-artifact-nvmundefined @@ -0,0 +1,520 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Node.js版本神器之nvm | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Node.js版本神器之nvm +

+ + +
+ + + + +

简介

Node Version Manager (nvm) 是一个易于使用的工具,可以帮助您在不同Node.js版本之间进行切换,以满足不同项目的需求。

+

背景

在软件开发过程中,因项目依赖和兼容性考虑,可能需要使用不同版本的Node.js。通过nvm,可以方便地管理多个Node.js版本,而无需担心全局安装的冲突问题。

+

安装

通用安装

如果你在你的机器(无论是windows,macOs还是linux)上已经安装了node,则可以直接使用npm命令进行安装:

+
1
npm install -g nvm
+ +

安装成功后,你可以跳过后续安装部分,进行阅读使用部分。
如果你没有办法通过以上方式进行安装,请接着进行阅读后续安装部分。

+

在Windows上安装

下载Windows安装程序:nvm-windows releases,然后一路回车安装

+

有时候安装完成后,会出现等nvm命令无法识别类似情况:

+
1
2
3
nvm --verison

nvm not fund
+ +

请检查环境变量,如果不存在,则添加nvm到环境变量PATH中

+

在Mac上安装

使用Homebrew安装

1
brew install nvm
+ +

使用命令行安装

1
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
+ +

安装完毕后,需要将nvm写入到环境变量中, ~/.bash_profile, ~/.zshrc~/.profile 文件中:

+
1
2
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+ +

修改完毕后,执行

+
1
2
3
4
5
source ~/.bash_profile
# 或者
source ~/.zshrc
#或者
source ~/.profile
+ +

在Linux上安装

和macos上安装一样,参照mac上安装-使用命令行安装

+

使用

查看所有可用版本

1
nvm ls-remote
+ +

安装指定版本

1
nvm install <version>
+ +

选择使用版本

1
nvm use <version>
+ +

设置默认版本

1
nvm alias default <version>
+ +

卸载特定版本

1
nvm uninstall <version>
+ +

注意事项

    +
  • 切换Node.js版本时,请确保项目的依赖支持所选版本。
  • +
  • 避免在生产环境频繁更改Node.js版本,以避免潜在的稳定性问题。
  • +
+

参考文章

    +
  • Node Version Manager (GitHub)
  • +
  • How to Use Node Version Manager (nvm) for Node.js
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2018/01/20/Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271.html" "b/2018/01/20/Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271.html" new file mode 100644 index 00000000..7ba6c311 --- /dev/null +++ "b/2018/01/20/Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271.html" @@ -0,0 +1,612 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列常用命令之放弃修改 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列常用命令之放弃修改 +

+ + +
+ + + + +
+

Git 是一种流行的分布式版本控制系统,它允许开发者在不同的分支上进行工作,同时保持代码的完整性和历史记录。在使用 Git 进行版本控制的过程中,我们可能会遇到需要放弃当前工作进度的情况,例如,当发现当前分支的修改与主分支的代码不兼容,或者需要切换到另一个分支继续工作时。

+
+

概述

这篇文章会介绍如何在 Git 中放弃对文件的修改,包括以下几个方面:

+
    +
  • 放弃对单个文件/文件夹的修改:如果只想放弃对某个特定文件的修改,可以使用 git checkout 命令。
  • +
  • 放弃所有未提交的修改:如果需要放弃对所有未提交文件的修改,可以使用 git reset 命令。
  • +
  • 撤销已经提交的更改:如果需要撤销已经提交但尚未推送到远程仓库的更改,可以使用 git revert 命令。
  • +
  • 清理未跟踪的文件:有时候,你可能想要删除那些被错误添加到工作目录中但未被 Git 跟踪的文件,可以使用 git clean 命令。
  • +
+

文章可能会通过具体的命令示例和场景,帮助读者理解在不同情况下如何使用 Git 命令来放弃修改,以保持工作流程的流畅和代码的整洁。

+

两个概念

在 Git 中,indexcommit 是两个核心概念,它们在版本控制过程中扮演着不同的角色,理解这两个概念有助于加深对git工作原理的理解:

+

Index(索引)

    +
  • Index 是 Git 用来准备下一次提交的暂存区。你可以将其视为一个准备提交的文件列表,这些文件已经被审查和选择,准备成为下一次提交的一部分。
  • +
  • 当你对工作目录中的文件进行更改并希望将这些更改包含在下一次提交中时,你需要使用 git add 命令将它们添加到 index 中。
  • +
  • Index 允许你在提交之前进行多次修改,并且可以对这些修改进行排序和组织,以确保提交是有意义的。
  • +
  • Index 是一个文件,位于 .git/index 文件中。
  • +
+

Commit(提交)

    +
  • Commit 是项目快照的记录,它包含了项目的某个特定版本。当你执行 git commit 命令时,Git 会将当前 index 中的内容以及一些附加的元数据(如提交信息、作者、时间戳等)打包成一个提交对象,并存储在本地仓库中。

    +
  • +
  • 提交是不可变的,意味着一旦创建,其内容就不能被更改。这保证了项目历史的完整性和一致性。

    +
  • +
  • 提交可以看作是项目的版本号,每个提交都有一个唯一的哈希值,用于标识和引用特定的项目状态。

    +
  • +
  • 提交是 Git 分布式特性的基础,因为它允许开发者在本地进行提交,而不必立即与远程仓库同步。

    +
  • +
+

两者之间的关系可以这样理解:

+
    +
  • 你首先对文件进行修改。
  • +
  • 使用 git add 将这些修改的文件添加到 index 中,这时候修改被暂存,准备提交。
  • +
  • 使用 git commit 将 index 中的内容以及提交信息一起打包,创建一个新的提交对象。
  • +
+

简而言之,index 是准备提交的暂存区,而 commit 是已经提交的快照记录。在进行提交之前,你可以多次修改 index,但是一旦执行了 commit,那么这个提交就是最终的,不可更改的。

+

请深刻理解这两个概念,有助于帮助您理解后面的内容

+

命令

Git Checkout - 切换分支或恢复工作目录树文件

+

更新工作区中的文件,使其与索引或指定的树中的版本一致。 如果没有给出pathspec,’git checkout’也将更新HEAD,将指定的分支设为当前分支

+
+

使用git checkout来切换分支用的很多,但是没想到还可以用来恢复工作目录,可以理解为:
将一个文件从另一个提交中取出,从索引中恢复,所以它不会影响到git add及之后的内容。

+

具体用法如下:

+
1
git checkout -- <filename>
+ +

注意这里的filename是支持通配符匹配的,比如

+
1
2
3
4
5
6
7
8
# 恢复hello.html
git checkout -- hello.html

# 恢复hello.开头的文件
git checkout -- "hello.*"

# 恢复.html结尾的文件
git checkout -- *.html
+ +
+

注意此方法只是将本地文件恢复了,没有对git add 和git commit 产生影响

+
+

Git-reset - 重置当前HEAD到指定的状态

+

复制条目到索索引或者将当前的分支头(HEAD)设置为某个commit

+
+

Git-revert - 还原某些现有提交

+

给定一个或多个现有提交,还原相关补丁引入的更改,并记录一些新提交来记录这些更改。 这要求你的工作区是干净的(没有对 HEAD 提交的修改)。

+
+

Git-clean - 删除工作目录树中未跟踪的文件

+

从当前目录开始,通过递归删除不在版本控制之下的文件来清理工作区。
通常情况下,只有 Git 未知的文件会被删除,但如果指定了 -x 选项,被忽略的文件也会被删除。例如,这对删除所有构建产品很有用。
如果给出任何可选的<路径规范>…​参数,只有那些与路径规范相匹配的路径会受到影响

+
+

一些常见场景

本地修改了一些文件 (并没有使用 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
git checkout -- <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
1
git checkout .
+ +

本地新增了一些文件 (并没有 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
rm  -rf <filename>
+ +
    +
  •   所有文件
  • +
+
1
git clean -xdf
+ +
+

删除新增的文件,如果文件已经已经 git add 到暂存区,并不会删除!

+
+
    +
  •   所有文件和文件夹:
  • +
+
1
git clean -xdff
+ +
+

ps:谨慎操作: 本命令删除新增的文件和文件夹,如果文件已经已经 git add 到暂存区,并不会删除!

+
+

本地修改/新增了一些文件,已经 Git Add 到暂存区,想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
git reset HEAD <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
1
git reset HEAD .
+ +

本地通过 Git Add 和 Git Commit 后,想要撤销此次 Commit

    +
  • 撤销 commit, 同时保留该 commit 修改:
  • +
+
1
git reset <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前 6 位。

+
+

撤销之后,你所做的已经 commit 的修改还在工作区!

+
+
    +
  •   撤销 commit, 同时本地删除该 commit 修改:
  • +
+
1
git reset --hard <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前6位

+
+

ps:谨慎操作: 撤销之后,你所做的已经 commit 的修改将会清除,仍在工作区/暂存区的代码也将会清除!

+
+

参考

1.Git - git-checkout Documentation
2.Git - git-reset Documentation
3.Git - git-revert Documentation
4.Git - git-clean Documentation

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2018/01/20/Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271undefined" "b/2018/01/20/Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271undefined" new file mode 100644 index 00000000..b94bf41b --- /dev/null +++ "b/2018/01/20/Git\347\263\273\345\210\227\345\270\270\347\224\250\345\221\275\344\273\244\344\271\213\346\224\276\345\274\203\344\277\256\346\224\271undefined" @@ -0,0 +1,612 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列常用命令之放弃修改 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列常用命令之放弃修改 +

+ + +
+ + + + +
+

Git 是一种流行的分布式版本控制系统,它允许开发者在不同的分支上进行工作,同时保持代码的完整性和历史记录。在使用 Git 进行版本控制的过程中,我们可能会遇到需要放弃当前工作进度的情况,例如,当发现当前分支的修改与主分支的代码不兼容,或者需要切换到另一个分支继续工作时。

+
+

概述

这篇文章会介绍如何在 Git 中放弃对文件的修改,包括以下几个方面:

+
    +
  • 放弃对单个文件/文件夹的修改:如果只想放弃对某个特定文件的修改,可以使用 git checkout 命令。
  • +
  • 放弃所有未提交的修改:如果需要放弃对所有未提交文件的修改,可以使用 git reset 命令。
  • +
  • 撤销已经提交的更改:如果需要撤销已经提交但尚未推送到远程仓库的更改,可以使用 git revert 命令。
  • +
  • 清理未跟踪的文件:有时候,你可能想要删除那些被错误添加到工作目录中但未被 Git 跟踪的文件,可以使用 git clean 命令。
  • +
+

文章可能会通过具体的命令示例和场景,帮助读者理解在不同情况下如何使用 Git 命令来放弃修改,以保持工作流程的流畅和代码的整洁。

+

两个概念

在 Git 中,indexcommit 是两个核心概念,它们在版本控制过程中扮演着不同的角色,理解这两个概念有助于加深对git工作原理的理解:

+

Index(索引)

    +
  • Index 是 Git 用来准备下一次提交的暂存区。你可以将其视为一个准备提交的文件列表,这些文件已经被审查和选择,准备成为下一次提交的一部分。
  • +
  • 当你对工作目录中的文件进行更改并希望将这些更改包含在下一次提交中时,你需要使用 git add 命令将它们添加到 index 中。
  • +
  • Index 允许你在提交之前进行多次修改,并且可以对这些修改进行排序和组织,以确保提交是有意义的。
  • +
  • Index 是一个文件,位于 .git/index 文件中。
  • +
+

Commit(提交)

    +
  • Commit 是项目快照的记录,它包含了项目的某个特定版本。当你执行 git commit 命令时,Git 会将当前 index 中的内容以及一些附加的元数据(如提交信息、作者、时间戳等)打包成一个提交对象,并存储在本地仓库中。

    +
  • +
  • 提交是不可变的,意味着一旦创建,其内容就不能被更改。这保证了项目历史的完整性和一致性。

    +
  • +
  • 提交可以看作是项目的版本号,每个提交都有一个唯一的哈希值,用于标识和引用特定的项目状态。

    +
  • +
  • 提交是 Git 分布式特性的基础,因为它允许开发者在本地进行提交,而不必立即与远程仓库同步。

    +
  • +
+

两者之间的关系可以这样理解:

+
    +
  • 你首先对文件进行修改。
  • +
  • 使用 git add 将这些修改的文件添加到 index 中,这时候修改被暂存,准备提交。
  • +
  • 使用 git commit 将 index 中的内容以及提交信息一起打包,创建一个新的提交对象。
  • +
+

简而言之,index 是准备提交的暂存区,而 commit 是已经提交的快照记录。在进行提交之前,你可以多次修改 index,但是一旦执行了 commit,那么这个提交就是最终的,不可更改的。

+

请深刻理解这两个概念,有助于帮助您理解后面的内容

+

命令

Git Checkout - 切换分支或恢复工作目录树文件

+

更新工作区中的文件,使其与索引或指定的树中的版本一致。 如果没有给出pathspec,’git checkout’也将更新HEAD,将指定的分支设为当前分支

+
+

使用git checkout来切换分支用的很多,但是没想到还可以用来恢复工作目录,可以理解为:
将一个文件从另一个提交中取出,从索引中恢复,所以它不会影响到git add及之后的内容。

+

具体用法如下:

+
1
git checkout -- <filename>
+ +

注意这里的filename是支持通配符匹配的,比如

+
1
2
3
4
5
6
7
8
# 恢复hello.html
git checkout -- hello.html

# 恢复hello.开头的文件
git checkout -- "hello.*"

# 恢复.html结尾的文件
git checkout -- *.html
+ +
+

注意此方法只是将本地文件恢复了,没有对git add 和git commit 产生影响

+
+

Git-reset - 重置当前HEAD到指定的状态

+

复制条目到索索引或者将当前的分支头(HEAD)设置为某个commit

+
+

Git-revert - 还原某些现有提交

+

给定一个或多个现有提交,还原相关补丁引入的更改,并记录一些新提交来记录这些更改。 这要求你的工作区是干净的(没有对 HEAD 提交的修改)。

+
+

Git-clean - 删除工作目录树中未跟踪的文件

+

从当前目录开始,通过递归删除不在版本控制之下的文件来清理工作区。
通常情况下,只有 Git 未知的文件会被删除,但如果指定了 -x 选项,被忽略的文件也会被删除。例如,这对删除所有构建产品很有用。
如果给出任何可选的<路径规范>…​参数,只有那些与路径规范相匹配的路径会受到影响

+
+

一些常见场景

本地修改了一些文件 (并没有使用 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
git checkout -- <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
1
git checkout .
+ +

本地新增了一些文件 (并没有 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
rm  -rf <filename>
+ +
    +
  •   所有文件
  • +
+
1
git clean -xdf
+ +
+

删除新增的文件,如果文件已经已经 git add 到暂存区,并不会删除!

+
+
    +
  •   所有文件和文件夹:
  • +
+
1
git clean -xdff
+ +
+

ps:谨慎操作: 本命令删除新增的文件和文件夹,如果文件已经已经 git add 到暂存区,并不会删除!

+
+

本地修改/新增了一些文件,已经 Git Add 到暂存区,想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
git reset HEAD <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
1
git reset HEAD .
+ +

本地通过 Git Add 和 Git Commit 后,想要撤销此次 Commit

    +
  • 撤销 commit, 同时保留该 commit 修改:
  • +
+
1
git reset <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前 6 位。

+
+

撤销之后,你所做的已经 commit 的修改还在工作区!

+
+
    +
  •   撤销 commit, 同时本地删除该 commit 修改:
  • +
+
1
git reset --hard <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前6位

+
+

ps:谨慎操作: 撤销之后,你所做的已经 commit 的修改将会清除,仍在工作区/暂存区的代码也将会清除!

+
+

参考

1.Git - git-checkout Documentation
2.Git - git-reset Documentation
3.Git - git-revert Documentation
4.Git - git-clean Documentation

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2018/07/21/node.js-version-management-artifact-nvm.html b/2018/07/21/node.js-version-management-artifact-nvm.html new file mode 100644 index 00000000..1526d3a6 --- /dev/null +++ b/2018/07/21/node.js-version-management-artifact-nvm.html @@ -0,0 +1,520 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Node.js版本神器之nvm | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Node.js版本神器之nvm +

+ + +
+ + + + +

简介

Node Version Manager (nvm) 是一个易于使用的工具,可以帮助您在不同Node.js版本之间进行切换,以满足不同项目的需求。

+

背景

在软件开发过程中,因项目依赖和兼容性考虑,可能需要使用不同版本的Node.js。通过nvm,可以方便地管理多个Node.js版本,而无需担心全局安装的冲突问题。

+

安装

通用安装

如果你在你的机器(无论是windows,macOs还是linux)上已经安装了node,则可以直接使用npm命令进行安装:

+
1
npm install -g nvm
+ +

安装成功后,你可以跳过后续安装部分,进行阅读使用部分。
如果你没有办法通过以上方式进行安装,请接着进行阅读后续安装部分。

+

在Windows上安装

下载Windows安装程序:nvm-windows releases,然后一路回车安装

+

有时候安装完成后,会出现等nvm命令无法识别类似情况:

+
1
2
3
nvm --verison

nvm not fund
+ +

请检查环境变量,如果不存在,则添加nvm到环境变量PATH中

+

在Mac上安装

使用Homebrew安装

1
brew install nvm
+ +

使用命令行安装

1
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
+ +

安装完毕后,需要将nvm写入到环境变量中, ~/.bash_profile, ~/.zshrc~/.profile 文件中:

+
1
2
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+ +

修改完毕后,执行

+
1
2
3
4
5
source ~/.bash_profile
# 或者
source ~/.zshrc
#或者
source ~/.profile
+ +

在Linux上安装

和macos上安装一样,参照mac上安装-使用命令行安装

+

使用

查看所有可用版本

1
nvm ls-remote
+ +

安装指定版本

1
nvm install <version>
+ +

选择使用版本

1
nvm use <version>
+ +

设置默认版本

1
nvm alias default <version>
+ +

卸载特定版本

1
nvm uninstall <version>
+ +

注意事项

    +
  • 切换Node.js版本时,请确保项目的依赖支持所选版本。
  • +
  • 避免在生产环境频繁更改Node.js版本,以避免潜在的稳定性问题。
  • +
+

参考文章

    +
  • Node Version Manager (GitHub)
  • +
  • How to Use Node Version Manager (nvm) for Node.js
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2018/07/21/node.js-version-management-artifact-nvmundefined b/2018/07/21/node.js-version-management-artifact-nvmundefined new file mode 100644 index 00000000..21343e40 --- /dev/null +++ b/2018/07/21/node.js-version-management-artifact-nvmundefined @@ -0,0 +1,520 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Node.js版本神器之nvm | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Node.js版本神器之nvm +

+ + +
+ + + + +

简介

Node Version Manager (nvm) 是一个易于使用的工具,可以帮助您在不同Node.js版本之间进行切换,以满足不同项目的需求。

+

背景

在软件开发过程中,因项目依赖和兼容性考虑,可能需要使用不同版本的Node.js。通过nvm,可以方便地管理多个Node.js版本,而无需担心全局安装的冲突问题。

+

安装

通用安装

如果你在你的机器(无论是windows,macOs还是linux)上已经安装了node,则可以直接使用npm命令进行安装:

+
1
npm install -g nvm
+ +

安装成功后,你可以跳过后续安装部分,进行阅读使用部分。
如果你没有办法通过以上方式进行安装,请接着进行阅读后续安装部分。

+

在Windows上安装

下载Windows安装程序:nvm-windows releases,然后一路回车安装

+

有时候安装完成后,会出现等nvm命令无法识别类似情况:

+
1
2
3
nvm --verison

nvm not fund
+ +

请检查环境变量,如果不存在,则添加nvm到环境变量PATH中

+

在Mac上安装

使用Homebrew安装

1
brew install nvm
+ +

使用命令行安装

1
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
+ +

安装完毕后,需要将nvm写入到环境变量中, ~/.bash_profile, ~/.zshrc~/.profile 文件中:

+
1
2
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+ +

修改完毕后,执行

+
1
2
3
4
5
source ~/.bash_profile
# 或者
source ~/.zshrc
#或者
source ~/.profile
+ +

在Linux上安装

和macos上安装一样,参照mac上安装-使用命令行安装

+

使用

查看所有可用版本

1
nvm ls-remote
+ +

安装指定版本

1
nvm install <version>
+ +

选择使用版本

1
nvm use <version>
+ +

设置默认版本

1
nvm alias default <version>
+ +

卸载特定版本

1
nvm uninstall <version>
+ +

注意事项

    +
  • 切换Node.js版本时,请确保项目的依赖支持所选版本。
  • +
  • 避免在生产环境频繁更改Node.js版本,以避免潜在的稳定性问题。
  • +
+

参考文章

    +
  • Node Version Manager (GitHub)
  • +
  • How to Use Node Version Manager (nvm) for Node.js
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019-10-12-\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205undefined" "b/2019-10-12-\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205undefined" new file mode 100644 index 00000000..84bc1f3a --- /dev/null +++ "b/2019-10-12-\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205undefined" @@ -0,0 +1,586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用Npm Token免登陆发包 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用Npm Token免登陆发包 +

+ + +
+ + + + +
+

在使用 npm 包管理器进行包的发布时,通常需要手动登录 npm 账户,并且我们在本地使用npm时因为某些不可抗原因,使用了非npm官方源,导致我们每次发布都需要切换到官方源,显得非常繁琐。现在,通过使用 npm Token,可以避免手动登录,切换源等步骤,实现自动化发布流程,特别是在持续集成/持续部署(CI/CD)环境中。

+
+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

通过登陆官网获取

+

确保记录下生成的 Token,因为它只会在创建的时候显示一次,刷新后不会再次显示,如果忘记就只能重新申请

+
+

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

通过命令行获取

需要现在本地执行npm login登陆,然后调用文档的命令生产token,具体可以看下官方文档:
Creating and viewing access tokens | npm Docs

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。

+
1
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
1
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

如果你没有成功,出现如下报错:

+
1
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • 确保你的.npmrc文件路径,命名和内容正确
  • +
  • 确保你的access token正确,或者具有publish或write权限
  • +
  • 由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此,你已经做到免登陆发布包了,但别急,这还没完。

+

这样安全吗?

如果你真像上面这样做了,那你npm仓库的安全就没有了。

+

为什么呢,因为你刚刚申请的access token有读写仓库的权限,你如果随着你的代码提交到仓库,就会将他暴露出去,造成损失,那么怎么做才安全呢?下面会将

+

更安全的免登陆发包

忽略.npmrc

将.npmrc加入到.gitignore文件中,不提交到代码仓库,只在你本地使用

+

变量

在.npmrc使用变量

将.npmrc改成如下内容:

+
1
//registry.npmjs.org/:_authToken=${NPM_PUBLISH_TOKEN}
+ +

NPM_PUBLISH_TOKEN是我们后面要设置的全局变量

+

设置全局变量

在全局变量(环境变量)中设置NPM_PUBLISH_TOKEN为你刚刚申请的token

+

不同的平台设置方式不一样:

+

linux和mac平台可以通过编辑.bashrc或者.zshrc文件实现:

+
    +
  • 打开.bashrc或者.zshrc编辑
  • +
+
1
vi ~/.bashrc
+ +
    +
  • 在文件内容末尾追加:
  • +
+
1
export NPM_PUBLISH_TOKEN=你刚刚申请的token
+ +
    +
  • 然后执行source命令使其生效
  • +
+
1
source ~/.bashrc
+ +

如果提示没有权限加上sudo

+

window平台可以直接打开高级属性界面设置

+

未完待续

当然可以看出在本地使用token还是有很多限制,没有彻底解放生产力,后续我会出一篇文章讲述如何结合CI/CD去实现自动化发包

+

重要

任何时候都不要将access token相关的信息暴露到公网上,以免造成不必要损失

+

参考

1.Creating and viewing access tokens | npm Docs
2.Using private packages in a CI/CD workflow | npm Docs
3.Working with the npm registry - GitHub Docs

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/10/12/\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205.html" "b/2019/10/12/\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205.html" new file mode 100644 index 00000000..ed754cdd --- /dev/null +++ "b/2019/10/12/\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205.html" @@ -0,0 +1,586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用Npm Token免登陆发包 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用Npm Token免登陆发包 +

+ + +
+ + + + +
+

在使用 npm 包管理器进行包的发布时,通常需要手动登录 npm 账户,并且我们在本地使用npm时因为某些不可抗原因,使用了非npm官方源,导致我们每次发布都需要切换到官方源,显得非常繁琐。现在,通过使用 npm Token,可以避免手动登录,切换源等步骤,实现自动化发布流程,特别是在持续集成/持续部署(CI/CD)环境中。

+
+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

通过登陆官网获取

+

确保记录下生成的 Token,因为它只会在创建的时候显示一次,刷新后不会再次显示,如果忘记就只能重新申请

+
+

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

通过命令行获取

需要现在本地执行npm login登陆,然后调用文档的命令生产token,具体可以看下官方文档:
Creating and viewing access tokens | npm Docs

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。

+
1
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
1
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

如果你没有成功,出现如下报错:

+
1
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • 确保你的.npmrc文件路径,命名和内容正确
  • +
  • 确保你的access token正确,或者具有publish或write权限
  • +
  • 由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此,你已经做到免登陆发布包了,但别急,这还没完。

+

这样安全吗?

如果你真像上面这样做了,那你npm仓库的安全就没有了。

+

为什么呢,因为你刚刚申请的access token有读写仓库的权限,你如果随着你的代码提交到仓库,就会将他暴露出去,造成损失,那么怎么做才安全呢?下面会将

+

更安全的免登陆发包

忽略.npmrc

将.npmrc加入到.gitignore文件中,不提交到代码仓库,只在你本地使用

+

变量

在.npmrc使用变量

将.npmrc改成如下内容:

+
1
//registry.npmjs.org/:_authToken=${NPM_PUBLISH_TOKEN}
+ +

NPM_PUBLISH_TOKEN是我们后面要设置的全局变量

+

设置全局变量

在全局变量(环境变量)中设置NPM_PUBLISH_TOKEN为你刚刚申请的token

+

不同的平台设置方式不一样:

+

linux和mac平台可以通过编辑.bashrc或者.zshrc文件实现:

+
    +
  • 打开.bashrc或者.zshrc编辑
  • +
+
1
vi ~/.bashrc
+ +
    +
  • 在文件内容末尾追加:
  • +
+
1
export NPM_PUBLISH_TOKEN=你刚刚申请的token
+ +
    +
  • 然后执行source命令使其生效
  • +
+
1
source ~/.bashrc
+ +

如果提示没有权限加上sudo

+

window平台可以直接打开高级属性界面设置

+

未完待续

当然可以看出在本地使用token还是有很多限制,没有彻底解放生产力,后续我会出一篇文章讲述如何结合CI/CD去实现自动化发包

+

重要

任何时候都不要将access token相关的信息暴露到公网上,以免造成不必要损失

+

参考

1.Creating and viewing access tokens | npm Docs
2.Using private packages in a CI/CD workflow | npm Docs
3.Working with the npm registry - GitHub Docs

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2019/10/12/\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205undefined" "b/2019/10/12/\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205undefined" new file mode 100644 index 00000000..f501d142 --- /dev/null +++ "b/2019/10/12/\345\246\202\344\275\225\344\275\277\347\224\250Npm Token\345\205\215\347\231\273\351\231\206\345\217\221\345\214\205undefined" @@ -0,0 +1,586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用Npm Token免登陆发包 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用Npm Token免登陆发包 +

+ + +
+ + + + +
+

在使用 npm 包管理器进行包的发布时,通常需要手动登录 npm 账户,并且我们在本地使用npm时因为某些不可抗原因,使用了非npm官方源,导致我们每次发布都需要切换到官方源,显得非常繁琐。现在,通过使用 npm Token,可以避免手动登录,切换源等步骤,实现自动化发布流程,特别是在持续集成/持续部署(CI/CD)环境中。

+
+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

通过登陆官网获取

+

确保记录下生成的 Token,因为它只会在创建的时候显示一次,刷新后不会再次显示,如果忘记就只能重新申请

+
+

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

通过命令行获取

需要现在本地执行npm login登陆,然后调用文档的命令生产token,具体可以看下官方文档:
Creating and viewing access tokens | npm Docs

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。

+
1
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
1
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

如果你没有成功,出现如下报错:

+
1
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • 确保你的.npmrc文件路径,命名和内容正确
  • +
  • 确保你的access token正确,或者具有publish或write权限
  • +
  • 由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此,你已经做到免登陆发布包了,但别急,这还没完。

+

这样安全吗?

如果你真像上面这样做了,那你npm仓库的安全就没有了。

+

为什么呢,因为你刚刚申请的access token有读写仓库的权限,你如果随着你的代码提交到仓库,就会将他暴露出去,造成损失,那么怎么做才安全呢?下面会将

+

更安全的免登陆发包

忽略.npmrc

将.npmrc加入到.gitignore文件中,不提交到代码仓库,只在你本地使用

+

变量

在.npmrc使用变量

将.npmrc改成如下内容:

+
1
//registry.npmjs.org/:_authToken=${NPM_PUBLISH_TOKEN}
+ +

NPM_PUBLISH_TOKEN是我们后面要设置的全局变量

+

设置全局变量

在全局变量(环境变量)中设置NPM_PUBLISH_TOKEN为你刚刚申请的token

+

不同的平台设置方式不一样:

+

linux和mac平台可以通过编辑.bashrc或者.zshrc文件实现:

+
    +
  • 打开.bashrc或者.zshrc编辑
  • +
+
1
vi ~/.bashrc
+ +
    +
  • 在文件内容末尾追加:
  • +
+
1
export NPM_PUBLISH_TOKEN=你刚刚申请的token
+ +
    +
  • 然后执行source命令使其生效
  • +
+
1
source ~/.bashrc
+ +

如果提示没有权限加上sudo

+

window平台可以直接打开高级属性界面设置

+

未完待续

当然可以看出在本地使用token还是有很多限制,没有彻底解放生产力,后续我会出一篇文章讲述如何结合CI/CD去实现自动化发包

+

重要

任何时候都不要将access token相关的信息暴露到公网上,以免造成不必要损失

+

参考

1.Creating and viewing access tokens | npm Docs
2.Using private packages in a CI/CD workflow | npm Docs
3.Working with the npm registry - GitHub Docs

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2021-09-14-Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225undefined" "b/2021-09-14-Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225undefined" new file mode 100644 index 00000000..a12b6b53 --- /dev/null +++ "b/2021-09-14-Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225undefined" @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 +

+ + +
+ + + + +

随着mac系统的升级,对app安全控制越来越严,经常遇到一些非官方渠道安装的app,安装后无法打开的情况,如提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”,一般情况下,出现该问题是因为软件没有签名,所以被MAC系统禁止运行。可以尝试以下解决方案

+

老系统:设置允许任何来源下载的App

比较老的版本系统,可以按以下步骤操作:
打开”系统偏好设置 -> 安全与隐私 -> 通用”选项卡,检查是否已经启用了”任何来源”选项。如果没有启用,先点击左下角的小黄锁图标解锁,然后选中”任何来源”。(由于作者mac系统比较新,故此图来源网络)
image.png

+

如果没有”任何来源”的选项,打开终端,输入以下命令:

+
1
sudo spctl --master-disable
+ +

sudo spctl --master-disable 是一个在macOS操作系统中使用的命令行指令,用于修改系统安全策略控制(System Integrity Protection,简称SIP)的设置。SIP 是一种安全特性,用于保护系统文件和目录免受未授权的修改。

+

sudo spctl --master-disable 的作用如下:

+
    +
  1. 禁用 SIP:这个命令会禁用 SIP 功能,允许用户对系统文件进行修改。默认情况下,macOS 会阻止对某些系统文件和目录的修改,以保护系统安全。
  2. +
  3. 需要管理员权限:由于这个命令涉及到系统级别的更改,因此需要使用 sudo 来获取管理员权限。
  4. +
  5. 临时禁用:这个命令的禁用效果是临时的,重启计算机后 SIP 会重新启用。
  6. +
  7. 安全性风险:禁用 SIP 会降低系统的安全性,因为它允许对系统文件进行修改。因此,只有在确实需要修改系统文件时才应该使用这个命令,并且在完成修改后应立即重新启用 SIP。
  8. +
  9. 重新启用 SIP:要重新启用 SIP,可以使用 sudo spctl --master-enable 命令。
  10. +
+

一般来讲到这一步就可以了,但是如果你的系统比较新,你还得继续看下去

+

新系统:使用xattr -rd命令

如果你的系统比较新,或者已经打开了通用 > 信任任何来源安装后还是报错,那么在终端里执行以下命令:

+
1
2
3
sudo xattr -rd com.apple.quarantine /Applications/xxxx.app
# 将xxx替换成app的名字,如果你无法准确知道app名称,可以直接将app拖到终端中
# 按提示输入你的电脑密码即可。
+ +

sudo xattr -rd com.apple.quarantine /Applications/xxxx.app 是一个在macOS操作系统中使用的命令行指令,它用于移除文件或应用程序的扩展属性(extended attribute),具体来说,是移除一个名为 com.apple.quarantine 的属性。

+

这个属性通常在文件或应用程序从互联网下载后被添加,作为macOS的一种安全机制。它提示用户,该文件可能来自不信任的来源,需要确认是否信任并运行该应用程序。这个属性有时也被称为”隔离标记”(quarantine flag)。

+

命令的各个部分含义如下:

+
    +
  1. sudo:以管理员权限执行后面的命令。由于修改文件的扩展属性需要管理员权限,所以这里使用 sudo
  2. +
  3. xattr:这是用于查看和修改文件扩展属性的命令行工具。
  4. +
  5. -rd-r 表示递归地移除属性,-d 表示删除指定的属性。
  6. +
  7. com.apple.quarantine:这是要删除的扩展属性的名称。
  8. +
  9. /Applications/xxxx.app:这是要移除隔离标记的应用程序的路径。xxxx.app 应该替换为实际的应用程序名称。
  10. +
+

使用这个命令后,应用程序将不再显示警告,提示它可能来自互联网。这在安装从可信来源下载的应用程序时很有用,尤其是当用户确信该应用程序是安全的,但macOS仍然显示隔离警告时。

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2021/09/14/Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225.html" "b/2021/09/14/Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225.html" new file mode 100644 index 00000000..f5645969 --- /dev/null +++ "b/2021/09/14/Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225.html" @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 +

+ + +
+ + + + +

随着mac系统的升级,对app安全控制越来越严,经常遇到一些非官方渠道安装的app,安装后无法打开的情况,如提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”,一般情况下,出现该问题是因为软件没有签名,所以被MAC系统禁止运行。可以尝试以下解决方案

+

老系统:设置允许任何来源下载的App

比较老的版本系统,可以按以下步骤操作:
打开”系统偏好设置 -> 安全与隐私 -> 通用”选项卡,检查是否已经启用了”任何来源”选项。如果没有启用,先点击左下角的小黄锁图标解锁,然后选中”任何来源”。(由于作者mac系统比较新,故此图来源网络)
image.png

+

如果没有”任何来源”的选项,打开终端,输入以下命令:

+
1
sudo spctl --master-disable
+ +

sudo spctl --master-disable 是一个在macOS操作系统中使用的命令行指令,用于修改系统安全策略控制(System Integrity Protection,简称SIP)的设置。SIP 是一种安全特性,用于保护系统文件和目录免受未授权的修改。

+

sudo spctl --master-disable 的作用如下:

+
    +
  1. 禁用 SIP:这个命令会禁用 SIP 功能,允许用户对系统文件进行修改。默认情况下,macOS 会阻止对某些系统文件和目录的修改,以保护系统安全。
  2. +
  3. 需要管理员权限:由于这个命令涉及到系统级别的更改,因此需要使用 sudo 来获取管理员权限。
  4. +
  5. 临时禁用:这个命令的禁用效果是临时的,重启计算机后 SIP 会重新启用。
  6. +
  7. 安全性风险:禁用 SIP 会降低系统的安全性,因为它允许对系统文件进行修改。因此,只有在确实需要修改系统文件时才应该使用这个命令,并且在完成修改后应立即重新启用 SIP。
  8. +
  9. 重新启用 SIP:要重新启用 SIP,可以使用 sudo spctl --master-enable 命令。
  10. +
+

一般来讲到这一步就可以了,但是如果你的系统比较新,你还得继续看下去

+

新系统:使用xattr -rd命令

如果你的系统比较新,或者已经打开了通用 > 信任任何来源安装后还是报错,那么在终端里执行以下命令:

+
1
2
3
sudo xattr -rd com.apple.quarantine /Applications/xxxx.app
# 将xxx替换成app的名字,如果你无法准确知道app名称,可以直接将app拖到终端中
# 按提示输入你的电脑密码即可。
+ +

sudo xattr -rd com.apple.quarantine /Applications/xxxx.app 是一个在macOS操作系统中使用的命令行指令,它用于移除文件或应用程序的扩展属性(extended attribute),具体来说,是移除一个名为 com.apple.quarantine 的属性。

+

这个属性通常在文件或应用程序从互联网下载后被添加,作为macOS的一种安全机制。它提示用户,该文件可能来自不信任的来源,需要确认是否信任并运行该应用程序。这个属性有时也被称为”隔离标记”(quarantine flag)。

+

命令的各个部分含义如下:

+
    +
  1. sudo:以管理员权限执行后面的命令。由于修改文件的扩展属性需要管理员权限,所以这里使用 sudo
  2. +
  3. xattr:这是用于查看和修改文件扩展属性的命令行工具。
  4. +
  5. -rd-r 表示递归地移除属性,-d 表示删除指定的属性。
  6. +
  7. com.apple.quarantine:这是要删除的扩展属性的名称。
  8. +
  9. /Applications/xxxx.app:这是要移除隔离标记的应用程序的路径。xxxx.app 应该替换为实际的应用程序名称。
  10. +
+

使用这个命令后,应用程序将不再显示警告,提示它可能来自互联网。这在安装从可信来源下载的应用程序时很有用,尤其是当用户确信该应用程序是安全的,但macOS仍然显示隔离警告时。

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2021/09/14/Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225undefined" "b/2021/09/14/Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225undefined" new file mode 100644 index 00000000..2600e58d --- /dev/null +++ "b/2021/09/14/Mac\346\217\220\347\244\272\342\200\235xxx.app\345\267\262\346\215\237\345\235\217\357\274\214\346\227\240\346\263\225\346\211\223\345\274\200\357\274\214\344\275\240\345\272\224\350\257\245\345\260\206\345\256\203\347\247\273\345\210\260\345\272\237\347\272\270\347\257\223\342\200\235\347\232\204\350\247\243\345\206\263\346\226\271\346\263\225undefined" @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 +

+ + +
+ + + + +

随着mac系统的升级,对app安全控制越来越严,经常遇到一些非官方渠道安装的app,安装后无法打开的情况,如提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”,一般情况下,出现该问题是因为软件没有签名,所以被MAC系统禁止运行。可以尝试以下解决方案

+

老系统:设置允许任何来源下载的App

比较老的版本系统,可以按以下步骤操作:
打开”系统偏好设置 -> 安全与隐私 -> 通用”选项卡,检查是否已经启用了”任何来源”选项。如果没有启用,先点击左下角的小黄锁图标解锁,然后选中”任何来源”。(由于作者mac系统比较新,故此图来源网络)
image.png

+

如果没有”任何来源”的选项,打开终端,输入以下命令:

+
1
sudo spctl --master-disable
+ +

sudo spctl --master-disable 是一个在macOS操作系统中使用的命令行指令,用于修改系统安全策略控制(System Integrity Protection,简称SIP)的设置。SIP 是一种安全特性,用于保护系统文件和目录免受未授权的修改。

+

sudo spctl --master-disable 的作用如下:

+
    +
  1. 禁用 SIP:这个命令会禁用 SIP 功能,允许用户对系统文件进行修改。默认情况下,macOS 会阻止对某些系统文件和目录的修改,以保护系统安全。
  2. +
  3. 需要管理员权限:由于这个命令涉及到系统级别的更改,因此需要使用 sudo 来获取管理员权限。
  4. +
  5. 临时禁用:这个命令的禁用效果是临时的,重启计算机后 SIP 会重新启用。
  6. +
  7. 安全性风险:禁用 SIP 会降低系统的安全性,因为它允许对系统文件进行修改。因此,只有在确实需要修改系统文件时才应该使用这个命令,并且在完成修改后应立即重新启用 SIP。
  8. +
  9. 重新启用 SIP:要重新启用 SIP,可以使用 sudo spctl --master-enable 命令。
  10. +
+

一般来讲到这一步就可以了,但是如果你的系统比较新,你还得继续看下去

+

新系统:使用xattr -rd命令

如果你的系统比较新,或者已经打开了通用 > 信任任何来源安装后还是报错,那么在终端里执行以下命令:

+
1
2
3
sudo xattr -rd com.apple.quarantine /Applications/xxxx.app
# 将xxx替换成app的名字,如果你无法准确知道app名称,可以直接将app拖到终端中
# 按提示输入你的电脑密码即可。
+ +

sudo xattr -rd com.apple.quarantine /Applications/xxxx.app 是一个在macOS操作系统中使用的命令行指令,它用于移除文件或应用程序的扩展属性(extended attribute),具体来说,是移除一个名为 com.apple.quarantine 的属性。

+

这个属性通常在文件或应用程序从互联网下载后被添加,作为macOS的一种安全机制。它提示用户,该文件可能来自不信任的来源,需要确认是否信任并运行该应用程序。这个属性有时也被称为”隔离标记”(quarantine flag)。

+

命令的各个部分含义如下:

+
    +
  1. sudo:以管理员权限执行后面的命令。由于修改文件的扩展属性需要管理员权限,所以这里使用 sudo
  2. +
  3. xattr:这是用于查看和修改文件扩展属性的命令行工具。
  4. +
  5. -rd-r 表示递归地移除属性,-d 表示删除指定的属性。
  6. +
  7. com.apple.quarantine:这是要删除的扩展属性的名称。
  8. +
  9. /Applications/xxxx.app:这是要移除隔离标记的应用程序的路径。xxxx.app 应该替换为实际的应用程序名称。
  10. +
+

使用这个命令后,应用程序将不再显示警告,提示它可能来自互联网。这在安装从可信来源下载的应用程序时很有用,尤其是当用户确信该应用程序是安全的,但macOS仍然显示隔离警告时。

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024-03-07-using-conda-to-quickly-initialize-a-project-pythonundefined b/2024-03-07-using-conda-to-quickly-initialize-a-project-pythonundefined new file mode 100644 index 00000000..7887331a --- /dev/null +++ b/2024-03-07-using-conda-to-quickly-initialize-a-project-pythonundefined @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用conda快速初始化项目python | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用conda快速初始化项目python +

+ + +
+ + + + +

环境准备

如果你想使用conda来管理你的Python环境和依赖,你需要在你的机器上安装Anaconda或Miniconda。Anaconda包含了conda,Python和150+科学包及其依赖。而Miniconda只包含了conda和Python。

+

当你安装好后Anaconda或Miniconda后,你就可以愉快的使用conda管理项目了

+

新建项目

1
2
mkdir test-python-project
cd test-python-project
+ +

创建虚拟环境

为什么需要创建虚拟环境?
Python创建虚拟环境的目的是为了在同一台计算机上同时管理和运行多个独立的Python项目。虚拟环境提供了一个隔离的运行环境,使得每个项目可以拥有自己独立的Python解释器和依赖库,而不会相互干扰。

+

以下是创建虚拟环境的几个主要目的:

+
    +
  1. 隔离项目:每个项目都可以在自己的虚拟环境中运行,避免不同项目之间的依赖冲突。这样可以确保每个项目都能够独立地使用所需的特定Python版本和依赖库。
  2. +
  3. 管理依赖:虚拟环境允许您为每个项目单独安装和管理所需的依赖库。这样可以确保每个项目都使用其特定版本的依赖库,而不会受其他项目的影响。
  4. +
  5. 简化部署:使用虚拟环境可以更轻松地将项目部署到其他计算机或服务器上。您可以将虚拟环境与项目一起打包,并确保在不同环境中具有一致的运行结果。
  6. +
  7. 提高可移植性:虚拟环境使得项目在不同操作系统和计算机上的移植更加容易。您可以在不同平台上创建相同的虚拟环境,并确保项目在各个环境中都能够正常运行。
  8. +
+

总之,创建虚拟环境可以提供一个独立、隔离和可管理的Python运行环境,使得多个项目能够在同一台计算机上同时运行,而不会相互干扰。这为项目开发、依赖管理和部署提供了更大的灵活性和可靠性

+
1
2
3
4
5
6
7
8
# 选择你需要的python版本来创建虚拟环境
conda create --name myenv python=3.10
# 导出虚拟环境配置:保证其他合作成员环境一致
conda env export --name dev > environment.yml
# 其他成员创建虚拟环境,使用以下命令
conda env create --file environment.yml
# 激活虚拟环境
conda activate myenv
+ + +

安装依赖

你应该将这些依赖写入一个requirements.txt文件中,这样其他人在运行你的项目时可以方便地安装这些依赖。

+
1
2
3
4
5
6
7
# 自动将所有依赖导入到requirements中,并生成一个requirements.txt文件
conda list --export > requirements.txt
# 导出当前环境中的所有依赖包,并且不包含构建信息,以避免导出不必要的包
conda env export --no-builds > requirements.txt

# 安装依赖
conda install --file requirements.txt
+ +

那么现在就可以愉快的编写代码了~

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024-04-19-\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242undefined" "b/2024-04-19-\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242undefined" new file mode 100644 index 00000000..907f2826 --- /dev/null +++ "b/2024-04-19-\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242undefined" @@ -0,0 +1,572 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何使用Github Actions实现自动化部署Hexo博客 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 如何使用Github Actions实现自动化部署Hexo博客 +

+ + +
+ + + + +
+

Hexo 是一个基于 Node.js 的静态博客框架,它以简洁的速度和易用性受到许多开发者的青睐。如果你的博客部署在github上,那么这里有一种新的部署方式可能让摆脱频繁提交部署的苦恼!GitHub Actions 为自动化部署Hexo提供了一种解决方案,极大地简化了部署流程和节省了时间,可以让我们更多的专注到写作本身。

+
+

为什么使用Github Actions

    +
  • 将文章源文件和部署文件分离,有助于保护源文件中的隐私和你的草稿文件等
  • +
  • 节省手动部署的时间,专注于写作本身
  • +
  • 学习Github Actions相关知识,不折腾不作死发作了
  • +
+

如果你符合以上任何一条,你都应该考虑往下看下去。

+

什么是Github Actions

GitHub Actions 是 GitHub 推出的一项强大的自动化工具,它允许用户在 GitHub 仓库中创建、编辑和运行自动化脚本,这些脚本被称为工作流程(workflows)。这些工作流程可以响应 GitHub 上的各种事件,例如代码被推送、issue 被创建、pull request 被打开或定期调度任务等。

+

GitHub Actions 的工作流程(workflow)由 YAML 文件定义,这些文件放在仓库的 .github/workflows 目录下。每个工作流程文件描述了一系列的作业(jobs)和步骤(steps),定义了运行环境、触发条件、要执行的任务等。

+

前期准备

创建两个仓库

创建部署仓库

    +
  • 建立名为 <你的 GitHub 用户名>.github.io 的公开储存库,若之前已将 Hexo 上传至其他储存库,将该储存库重命名即可
  • +
  • 将 Hexo 文件夹中的文件 push 到储存库的默认分支,默认分支通常名为 main,旧一点的储存库可能名为 master
  • +
  • 开启Github Pages能力,使其可以 https://<你的 GitHub 用户名>.github.io访问
  • +
+

比如笔者使用2ue.github.io储存库用于存在文章编译后的代码,开启github pages后可通过https://2ue.github.io访问

+

创建文章源码仓库

    +
  • 创建一个私有仓库,用于储存文章源代码
    比如,笔者使用myblog储存库存放文章的源文件
  • +
+

至此两个仓库已经准备完了,后面所有的操作都在myblog上进行

+

生成Personal access tokens

+

github提供了access tokens的能力,可以在不登录的情况访问仓库和操作仓库等,所以我们可以通过相关能力来实现自动化部署,但同时也要保管好该token

+
+

Personal access tokens申请地址:Personal access tokens

+

打开网址后,点击 Generate new token -> Generate new token (classic)

+

image.png

+

然后依次填写

+
    +
  • Note: 注释或者名字,按自己喜欢填写符合规则的名字
  • +
  • Expiration:过期时间,可以选择合适的时间,这里我选择的是永不过期,No Expiration
  • +
  • Select scopes:选择权限,勾上repo和workflow,这里的作用是使得这个token具有读写repo的权限和通过github Action更新的能力
  • +
+

拖到页面最底部,点击生成按钮

+

image.png

+

token生成后,会在这里展示刚刚生成的token。
注意:新生成的token只会在创建时显示一次,刷新之后就会消失,记得备份保存,如果忘记了,删除重新生成即可

+

image.png

+

Token填写到myblog仓库

点击:New repository secret ,将刚刚申请的token填入:

+
    +
  • Name:尽量按规范使用大写,多个单词用_分割
  • +
  • Secret:填入刚刚申请的Token
  • +
+

image.png
image.png

+

部署脚本编写

将myblog仓库代码克隆到本地,新增.github/workflow文件夹,然后再新增一个.yml的文件,假设这里叫hexo-deploy.yml,然后将以下代码复制到文件中,保存后使用git提交到myblog仓库

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
name: deploying Hexo project to GitHub pages
on:
push:
branches:
- main # 分支有 push 行为时就触发这个 action

jobs:
build-and-deploy:
runs-on: ubuntu-latest
env:
GITHUB_USER: 2ue
# GitHub Pages仓库 即博客部署需要用到的仓库
DEPLOY_REPO: 2ue.github.io
DEPLOY_BRANCH: main
GIT_USER: 2ue
GIT_EMAIL: xxxx@xxx.com
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0

- name: Set timezone to Asia/Shanghai
uses: szenius/set-timezone@v1.0
with:
# 设置执行环境的时区为 Linux 上海时区
timezoneLinux: "Asia/Shanghai"

- name: Echo current time
run: timedatectl

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16.15.0

- name: Generate pages
run: |
export TZ='Asia/Shanghai'
npm install -g hexo-cli hexo
npm install
hexo clean && hexo g

- name: Git config
run: |
git config --global user.name "${{GIT_USER}}"
git config --global user.email "${{GIT_EMAIL}}"

- name: Pull blog repo && Copy files
run: |
git clone https://github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
rm -rf ${{DEPLOY_REPO}}/*
cp -r ./public/* ./${{DEPLOY_REPO}}

- name: Deploy blog
run: |
echo '>_ Enter repo: ${{GITHUB_USER}}/${{DEPLOY_REPO}} ...'
cd ${{DEPLOY_REPO}}
echo '>_ Pwd current dir ...'
pwd
echo '>_ Show files ...'
ls -al
echo '>_ Set remote git ...'
rm -rf .git
git init
git remote add origin https://${{secrets.DEPLOY_TOKEN}}@github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
git branch -M ${{DEPLOY_BRANCH}}
git add .
echo '>_ Start Commit ...'
git commit --allow-empty -m "Github Action Automated Deployment $(date +'%Y-%m-%d %H:%M:%S')"
echo '>_ Start Push ...'
git push -u origin ${{DEPLOY_BRANCH}} --force
+ +

至此Hexo利用Github Actions自动化部署就实现了

+

部署

将代码提交到github后,打开github的myblog仓库(你自己的文章源文件仓库),点击:Actions可以看到有任务正在执行,以下是执行成功后的界面:

+

image.png

+

如果有失败,可以点击对应的步骤,查询详细情况:

+

image.png

+

当然如果你使用的vscode,也可以安装Github Actions插件,然后再vscode中直接查看执行过程,不过在插件中没有办法查看失败信息,但是可以点击直接跳转到对应页面查看详细信息

+

image.png

+

参考

在 GitHub Pages 上部署 Hexo | Hexo
GitHub Actions 文档 - GitHub 文档

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024-04-20-\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205undefined" "b/2024-04-20-\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205undefined" new file mode 100644 index 00000000..3c3256b7 --- /dev/null +++ "b/2024-04-20-\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205undefined" @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用Github Action自动化发布npm包 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用Github Action自动化发布npm包 +

+ + +
+ + + + +
+

GitHub Actions 提供了一种强大的自动化构建和发布流程的方式,通过组合github action的方式形成了workflow,来实现CI/CD。我们完全可以将 npm 包的发布流程集成到 GitHub workflow中,做到自动化发包,这可以可以提高效率并减少人为错误。以下是如何使用 GitHub Actions 实现自动化发布的详细指南。

+
+

0. 原理解释

github actions是一个github推出的CI/CD工具,可以模拟平台(比如linux等)自动化执行一些操作。
npm可以通过token实现免登陆发布包,并且Github Secrets能很好的保护token不被泄露(后面会说简单说一下为什么),这样就避免了在登陆信息在CI/CD时泄露

+

这是我编写的权限验证的一个包,支持vue,react以及函数式调用,同时使用github workflow实现的自动npm化发包。欢迎大家参考,同时提出优化意见:
validate-permission/.github/workflows at main · 2ue/validate-permission · GitHub

+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你有一个 GitHub 账户,并且已经创建了一个仓库。
  • +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。但要注意,因为access token具有你的npm账户读写权利,所有不应该将.npmrc文件提交到远程仓库,需要将该文件加入到.gitignore中

+
1
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
1
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

发布失败

如果你没有成功,出现如下报错:

+
1
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • .npmrc配置问题:确保你的.npmrc文件路径,命名和内容正确
  • +
  • .npmrc:确保你的access token正确,或者具有publish或write权限
  • +
  • 本地没有使用npm官方源:由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此使用npm access token发布npm包的流程已经跑通了。

+

相信大家已经发现了,上面这种方式有一个问题:.npmrc只能放到本地,意味着每次换一个电脑写代码都要重新去复制access token,生成.npmrc,那有没有一种方式可以比较安全的管理它呢,答案肯定是有的:答案就在Github。
我们不但可以利用GitHub Secrets管理npm的access token,而且还可利用github actions能力实现自动化workflow,来自动化发包到npm。

+

3. 配置 GitHub Secrets

在 GitHub 仓库中添加 npm token:

+
    +
  1. 进入 GitHub 仓库的 “Settings” > “Secrets”。
  2. +
  3. 点击 “New repository secret”。
  4. +
  5. 输入 NPM_TOKEN 作为名称,并粘贴你的 npm token 作为值。
  6. +
  7. 保存秘密。
    image.png
  8. +
+

请注意,这里我们可以选择Secrets和Variables,他们都在我们编写的workflow中被读取到,其实都是变量,但是两者有区别:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
变量类型存储方式界面是否可见修改内容修改变量名workflow是否可见
Secrets加密不可见只能更新且更新时不能看到之前内容无法修改以***显示
Variables明文明文且可见可自由更改且可以看到之前内容可以修改明文可见
从上面的比较可以看出为什么github Secrets能够有效的保护npm access token,当然万事不是绝对,如果非要追求百分百安全,那就使用最原始的方式吧,毕竟放在你本地的文件也可能因为中木马泄露出去。
+

4. 编写 GitHub Actions Workflow

至此我们就可以利用github actions来编写一个workflow。让我们捋一下思路:

+
    +
  • 什么时候执行:在代码推送到某分支,或者打tag的时候,这里我们选择当代码推送时
  • +
  • 执行流程
      +
    • 拉取代码
    • +
    • 设置时区:如果需要
    • +
    • 设置node环境
    • +
    • 安装pnpm,yarn等工具:如果需要
    • +
    • 安装代码依赖,并打包
    • +
    • 写入token到.npmrc
    • +
    • 执行npm 发布
      当然如果所有的操作都需要我们自己去实现,就比较复杂,好在有第三方action可以快速的让我们实现这些能力:
    • +
    +
  • +
  • run:可以执行脚本命令,让我们可以执行类似npm i这样的命令
  • +
  • actions/checkout@v4:实现代码拉取
  • +
  • actions/setup-node@v4:实现node环境,可以指定node版本
  • +
+

那么我们就用这些能力创建一个workflow吧

+

在项目根目录下创建 .github/workflows/npm-publish.yml 文件,并添加以下内容:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# workflow名字
name: Publish to npm

# 触发条件:当 main 分支有 push 事件时触发
on:
push:
branches:
- main

jobs:
publish:
# 在 Ubuntu 最新版本上运行作业
runs-on: ubuntu-latest
steps:
# 使用 actions/checkout 检出当前代码
- name: Checkout code
uses: actions/checkout@v4

# 设置 Node.js 环境
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.11.1

# 安装项目依赖,并编译
- name: Install dependencies
run: |
npm i
npm run build

# 添加access token到.npmrc
- name: Add Npm Token
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ./.npmrc
ls -al
cat ./.npmrc

# 发布到 npm registry
- name: Publish to npm
run: npm publish
env:
# 读取github的secrets变量,这里的NPM_TOKEN是前面设置的变量名
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ +

此 workflow 会在推送到 main 分支时触发。

+

image.png

+

5. 推送更改到 GitHub

将你的更改推送到 GitHub 仓库:

+
1
2
3
git add .
git commit -m "Set up automated npm publishing with GitHub Actions"
git push
+ +

6. 测试 Workflow

一旦你推送了更改,GitHub Actions workflow 将会运行。你可以在 GitHub 仓库的 “Actions” 选项卡中查看 workflow 的状态。

+

7. 发布新版本

当你准备发布新版本时,更新 package.json 中的版本号,然后提交并推送这些更改到 main 分支。GitHub Actions 将自动处理剩余的发布流程。

+

8.优化

这个workflow脚本还有很多优化的空间,比如:

+

优化触发时机

如果我们推送代码就执行发包操作,在有些情况下可能不符合我们要求,所有我们可以更改触发条件:
正常情况我们想的是创建了一个v1.0.0这样的tag分支才触发,并且是修改了关键源码才触发

+
    +
  • 设置当推送了tag分支并且需要tag符合某种格式才触发流水
  • +
+
1
2
3
4
5
name: Npm publish
on:
push:
tags:
- "v*" # 这段的意思是仅在出现名为 v 字符开头的tag时,触发此任务,如v1.2.1
+ +
    +
  • 设置修改了某些文件才执行发包
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- uses: dorny/paths-filter@v3
- #必须设定ID,用于后面读取这一步的结果
id: changes
with:
filters: |
update:
- 'main/**'
- 'types/**'
- 'utils/**'
- README.md
- package.json
- rollup.config.js
- tsconfig.json
- .babelrc
- .github/workflows/publish.yml
- name: Install pnpm
- # 这里是当【changes】输出了update为true才执行执行编译等工作
if: steps.changes.outputs.update == 'true'
run: |
npm install -g pnpm
+ +

dorny/paths-filter@v3github actions市场的一个包,可以用来检查哪些文件有变动,如果有变动则输出一个变量update,为true时表示有变动,可以用steps.changes.outputs.update来读取这个变量

+

自动化生成版本号

每次改动需要发布npm包时,都需要修改package.json的版本号,无疑有点麻烦,那我们可以使用npm version 命令来做到自动化版本修改:

+
    +
  • <update_type> 是语义化版本类型之一,如 majorminor 或 patch
  • +
  • 例如,运行 npm version patch 将会更新版本号为当前版本号的下一个补丁版本
  • +
+

甚至你可以通过github workflow的内置变量拿到tag名字,根据tag特征做到修改哪一个版本号

+

自动化生成tag和release

如果你的commit比较规范,比如使用了@changesets/cli等,那么可以利用github actions市场的三方包来自动化为你的发布生成changelog,tag,release等信息,比如semantic-release-action等

+

当然根据个人需求不同,可能还可以做更多的优化,这里就不一一述说了,毕竟本篇文章仅仅是一个穿针引线。

+

问题总结

问题 1:权限问题

如果你的 npm token 没有足够的权限发布包,你可能会遇到权限错误。[参考](#获取Access Token)

+

解决方案:确保你的 npm token 有发布包的权限。通常,这意味着你需要在 npm 网站上生成一个具有适当权限的 token。

+

问题 2:网络问题

如果你的 CI/CD 服务器无法访问 npm registry,发布可能会失败。一般在github上不会出现该问题,可能在本地使用了代理或者切换了npm源会出现这个问题。参考

+

解决方案:检查你的 CI/CD 服务器的网络设置,确保它可以无障碍地访问 https://registry.npmjs.org/

+

问题 3:Workflow 配置错误

如果你的 workflow 配置有误,它可能不会按预期触发。

+

解决方案:仔细检查 workflow 文件的语法和配置,特别是触发条件和 secret 的使用。

+

问题 4:.npmrc问题

如果你的 .npmrc 文件没有被正确地添加到项目根目录或内容不正确,那么可能导致发布失败。参考

+

解决方案:确保 .npmrc 文件被正确添加到你的版本控制系统,并且格式正确无误。

+

通过以上步骤和解决方案,你可以确保 npm 包的自动化发布流程顺畅运行。

+

参考

Using private packages in a CI/CD workflow | npm Docs
Working with the npm registry - GitHub Docs
Variables - GitHub Docs

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024-04-23-Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257undefined" "b/2024-04-23-Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257undefined" new file mode 100644 index 00000000..a7d5df13 --- /dev/null +++ "b/2024-04-23-Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257undefined" @@ -0,0 +1,545 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 +

+ + +
+ + + + +
+

在 Git 工作流中,有时需要在同一台电脑上针对不同的项目设置不同的用户身份。例如,开发人员可能需要在个人项目和公司项目之间切换,而这两个项目可能需要不同的 Git 用户名和电子邮件地址。Oh My Zsh 提供了强大的自定义功能,允许我们通过编写自定义的 shell 函数来实现这一需求。本文将展示如何使用 Oh My Zsh 的 chpwd 函数来自动设置特定目录及其子目录、孙目录下的 Git 用户配置。

+
+

开始之前

假设你对git身份信息和ssh key概念有比较清晰的认识和了解其基础使用,比如git身份信息是用户名和邮箱,在你使用git commit的时候进行记录;而ssh key是一个凭证(分为公钥和私钥),用于远端身份信息验证,和git没有直接关系,也可以用于其他场景使用,比如ssh登录远程主机。

+

简单说下为什么要使用不同的git身份信息呢?
公司项目要求使用规定的name(比如企微名,花名或者公司内部系统的唯一标识等)和email(公司邮箱)作为git提交记录,这样方便团队协作和代码管理,这些信息有一定的敏感性。所有在提交代码到外网的时候必须考虑使用不同的git身份信息来提交。

+

当然我们可以在每次克隆一个新项目的时候,根据不同需要使用git config –local进行设置,但这明手动操作容易遗忘而且繁琐,所以想到使用脚本来自动化完成。

+

最终效果

本文不是解决:不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的ssh key凭证这个问题,而且为了解决以下问题:

+
    +
  • 不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的git身份信息
  • +
+

最终想达到:

+
    +
  • 进入到指定目录及其子目录,孙目录,如果是git仓库,就自动设置对应的git身份信息
  • +
+

约定目录结构:

1
2
3
4
5
/Users/developer/teamA         # 根目录
└── projectA # 子目录,团队项目
└── src # 孙目录

/Users/developer/teamB
+ +

解决方案

解决这个问题的方案有很多,下面分享一种使用zsh脚本的方案。首先无论哪一种方案,都需要在全局设置全局git信息:

+
1
2
git config --global user.name "name1"
git config --global user.email"email1@email.com"
+ +

安装 Oh My Zsh

如果尚未安装,通过以下命令安装(已经安装就跳过):

+
1
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
+ +

编辑 .zshrc 文件

添加 chpwd 函数:在 .zshrc 最后添加以下函数

+
1
vi ~/.zshrc
+ +

确保启用了plugin:

+
1
2
# 保证这一行不被注释, 括号的内容可能不一样
plugins=(git)
+ +

.zshrc 最后,添加 chpwd 函数:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 chpwd() {
# 指定目标目录
local root_dir="/Users/developer/teamA" # 指定目录
# 指定该目录要设置的git name信息
local user_name="name"
# 指定该目录要设置的git email信息
local user_email="email@email.com"
# 获取到当前目录
local current_dir="$(pwd -P)"
# 如果当前目录是否是目标目录,或者是否其子目录,孙目录
if [[ "$current_dir" == "$root_dir"* ]]; then
# 输出当前目录,用于调试,后续可删除
echo "chpwd: $PWD"

if [ -d "$current_dir/.git" ]; then
git -C "$current_dir" config --local user.name "$user_name"
git -C "$current_dir" config --local user.email "$user_email"
echo "chpwd: 成功设置 user.name 和 user.email"
fi
fi
}
# 执行 chpwd 函数
chpwd
+ +

为了让更改生效,需要重新加载 .zshrc 文件。在终端中运行以下命令(或者关闭后重新打开)

+
1
source ~/.zshrc
+ +

ps: 当然你如果使用的其他命令行终端,可以参考这个脚本,根据终端的特点,自行配置

+

测试

在vscode中打开控制台:

使用vscode打开项目/Users/developer/teamA/projectA,并且打开控制台,如果发现有成功输出信息,就表示成功了。

+
1
chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。如果输出的信息是你预期的表示脚本成功了。

+

同样,使用vscode打开一个不在/Users/developer/teamA目录下的项目,如果没有输出该消息说明符合预期的

+

测试cd命令

打开zsh终端,使用cd命令进入/Users/developer/teamA/projectA,如果发现有成功输出信息,就表示成功了。

+
1
2
3
cd /Users/developer/teamA/projectA

chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。

+

同样,进入/Users/developer/teamB/projectB:

+
1
cd /Users/developer/teamB/projectB
+ +

如果没有输出该消息说明符合预期的

+

方案限制

必须限制不同的远程仓库放到对应的目录,如果你现在的本地项目已经分散到不同的目录了,就必须要重新移动一下本地目录或者重新clone一下远程仓库到对应目录

+

总结

通过 Oh My Zsh 的 chpwd 函数,我们能够自动化 Git 用户信息的设置,这不仅提升了工作效率,也减少了配置错误的可能性。本文提供的步骤和测试验证了解决方案的有效性,展示了 Oh My Zsh 在自动化 shell 任务中的实用性。

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/03/07/using-conda-to-quickly-initialize-a-project-python.html b/2024/03/07/using-conda-to-quickly-initialize-a-project-python.html new file mode 100644 index 00000000..8172cf03 --- /dev/null +++ b/2024/03/07/using-conda-to-quickly-initialize-a-project-python.html @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用conda快速初始化项目python | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用conda快速初始化项目python +

+ + +
+ + + + +

环境准备

如果你想使用conda来管理你的Python环境和依赖,你需要在你的机器上安装Anaconda或Miniconda。Anaconda包含了conda,Python和150+科学包及其依赖。而Miniconda只包含了conda和Python。

+

当你安装好后Anaconda或Miniconda后,你就可以愉快的使用conda管理项目了

+

新建项目

1
2
mkdir test-python-project
cd test-python-project
+ +

创建虚拟环境

为什么需要创建虚拟环境?
Python创建虚拟环境的目的是为了在同一台计算机上同时管理和运行多个独立的Python项目。虚拟环境提供了一个隔离的运行环境,使得每个项目可以拥有自己独立的Python解释器和依赖库,而不会相互干扰。

+

以下是创建虚拟环境的几个主要目的:

+
    +
  1. 隔离项目:每个项目都可以在自己的虚拟环境中运行,避免不同项目之间的依赖冲突。这样可以确保每个项目都能够独立地使用所需的特定Python版本和依赖库。
  2. +
  3. 管理依赖:虚拟环境允许您为每个项目单独安装和管理所需的依赖库。这样可以确保每个项目都使用其特定版本的依赖库,而不会受其他项目的影响。
  4. +
  5. 简化部署:使用虚拟环境可以更轻松地将项目部署到其他计算机或服务器上。您可以将虚拟环境与项目一起打包,并确保在不同环境中具有一致的运行结果。
  6. +
  7. 提高可移植性:虚拟环境使得项目在不同操作系统和计算机上的移植更加容易。您可以在不同平台上创建相同的虚拟环境,并确保项目在各个环境中都能够正常运行。
  8. +
+

总之,创建虚拟环境可以提供一个独立、隔离和可管理的Python运行环境,使得多个项目能够在同一台计算机上同时运行,而不会相互干扰。这为项目开发、依赖管理和部署提供了更大的灵活性和可靠性

+
1
2
3
4
5
6
7
8
# 选择你需要的python版本来创建虚拟环境
conda create --name myenv python=3.10
# 导出虚拟环境配置:保证其他合作成员环境一致
conda env export --name dev > environment.yml
# 其他成员创建虚拟环境,使用以下命令
conda env create --file environment.yml
# 激活虚拟环境
conda activate myenv
+ + +

安装依赖

你应该将这些依赖写入一个requirements.txt文件中,这样其他人在运行你的项目时可以方便地安装这些依赖。

+
1
2
3
4
5
6
7
# 自动将所有依赖导入到requirements中,并生成一个requirements.txt文件
conda list --export > requirements.txt
# 导出当前环境中的所有依赖包,并且不包含构建信息,以避免导出不必要的包
conda env export --no-builds > requirements.txt

# 安装依赖
conda install --file requirements.txt
+ +

那么现在就可以愉快的编写代码了~

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/03/07/using-conda-to-quickly-initialize-a-project-pythonundefined b/2024/03/07/using-conda-to-quickly-initialize-a-project-pythonundefined new file mode 100644 index 00000000..eab16811 --- /dev/null +++ b/2024/03/07/using-conda-to-quickly-initialize-a-project-pythonundefined @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用conda快速初始化项目python | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用conda快速初始化项目python +

+ + +
+ + + + +

环境准备

如果你想使用conda来管理你的Python环境和依赖,你需要在你的机器上安装Anaconda或Miniconda。Anaconda包含了conda,Python和150+科学包及其依赖。而Miniconda只包含了conda和Python。

+

当你安装好后Anaconda或Miniconda后,你就可以愉快的使用conda管理项目了

+

新建项目

1
2
mkdir test-python-project
cd test-python-project
+ +

创建虚拟环境

为什么需要创建虚拟环境?
Python创建虚拟环境的目的是为了在同一台计算机上同时管理和运行多个独立的Python项目。虚拟环境提供了一个隔离的运行环境,使得每个项目可以拥有自己独立的Python解释器和依赖库,而不会相互干扰。

+

以下是创建虚拟环境的几个主要目的:

+
    +
  1. 隔离项目:每个项目都可以在自己的虚拟环境中运行,避免不同项目之间的依赖冲突。这样可以确保每个项目都能够独立地使用所需的特定Python版本和依赖库。
  2. +
  3. 管理依赖:虚拟环境允许您为每个项目单独安装和管理所需的依赖库。这样可以确保每个项目都使用其特定版本的依赖库,而不会受其他项目的影响。
  4. +
  5. 简化部署:使用虚拟环境可以更轻松地将项目部署到其他计算机或服务器上。您可以将虚拟环境与项目一起打包,并确保在不同环境中具有一致的运行结果。
  6. +
  7. 提高可移植性:虚拟环境使得项目在不同操作系统和计算机上的移植更加容易。您可以在不同平台上创建相同的虚拟环境,并确保项目在各个环境中都能够正常运行。
  8. +
+

总之,创建虚拟环境可以提供一个独立、隔离和可管理的Python运行环境,使得多个项目能够在同一台计算机上同时运行,而不会相互干扰。这为项目开发、依赖管理和部署提供了更大的灵活性和可靠性

+
1
2
3
4
5
6
7
8
# 选择你需要的python版本来创建虚拟环境
conda create --name myenv python=3.10
# 导出虚拟环境配置:保证其他合作成员环境一致
conda env export --name dev > environment.yml
# 其他成员创建虚拟环境,使用以下命令
conda env create --file environment.yml
# 激活虚拟环境
conda activate myenv
+ + +

安装依赖

你应该将这些依赖写入一个requirements.txt文件中,这样其他人在运行你的项目时可以方便地安装这些依赖。

+
1
2
3
4
5
6
7
# 自动将所有依赖导入到requirements中,并生成一个requirements.txt文件
conda list --export > requirements.txt
# 导出当前环境中的所有依赖包,并且不包含构建信息,以避免导出不必要的包
conda env export --no-builds > requirements.txt

# 安装依赖
conda install --file requirements.txt
+ +

那么现在就可以愉快的编写代码了~

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024/04/19/\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242.html" "b/2024/04/19/\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242.html" new file mode 100644 index 00000000..fb4715a6 --- /dev/null +++ "b/2024/04/19/\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242.html" @@ -0,0 +1,572 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何使用Github Actions实现自动化部署Hexo博客 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 如何使用Github Actions实现自动化部署Hexo博客 +

+ + +
+ + + + +
+

Hexo 是一个基于 Node.js 的静态博客框架,它以简洁的速度和易用性受到许多开发者的青睐。如果你的博客部署在github上,那么这里有一种新的部署方式可能让摆脱频繁提交部署的苦恼!GitHub Actions 为自动化部署Hexo提供了一种解决方案,极大地简化了部署流程和节省了时间,可以让我们更多的专注到写作本身。

+
+

为什么使用Github Actions

    +
  • 将文章源文件和部署文件分离,有助于保护源文件中的隐私和你的草稿文件等
  • +
  • 节省手动部署的时间,专注于写作本身
  • +
  • 学习Github Actions相关知识,不折腾不作死发作了
  • +
+

如果你符合以上任何一条,你都应该考虑往下看下去。

+

什么是Github Actions

GitHub Actions 是 GitHub 推出的一项强大的自动化工具,它允许用户在 GitHub 仓库中创建、编辑和运行自动化脚本,这些脚本被称为工作流程(workflows)。这些工作流程可以响应 GitHub 上的各种事件,例如代码被推送、issue 被创建、pull request 被打开或定期调度任务等。

+

GitHub Actions 的工作流程(workflow)由 YAML 文件定义,这些文件放在仓库的 .github/workflows 目录下。每个工作流程文件描述了一系列的作业(jobs)和步骤(steps),定义了运行环境、触发条件、要执行的任务等。

+

前期准备

创建两个仓库

创建部署仓库

    +
  • 建立名为 <你的 GitHub 用户名>.github.io 的公开储存库,若之前已将 Hexo 上传至其他储存库,将该储存库重命名即可
  • +
  • 将 Hexo 文件夹中的文件 push 到储存库的默认分支,默认分支通常名为 main,旧一点的储存库可能名为 master
  • +
  • 开启Github Pages能力,使其可以 https://<你的 GitHub 用户名>.github.io访问
  • +
+

比如笔者使用2ue.github.io储存库用于存在文章编译后的代码,开启github pages后可通过https://2ue.github.io访问

+

创建文章源码仓库

    +
  • 创建一个私有仓库,用于储存文章源代码
    比如,笔者使用myblog储存库存放文章的源文件
  • +
+

至此两个仓库已经准备完了,后面所有的操作都在myblog上进行

+

生成Personal access tokens

+

github提供了access tokens的能力,可以在不登录的情况访问仓库和操作仓库等,所以我们可以通过相关能力来实现自动化部署,但同时也要保管好该token

+
+

Personal access tokens申请地址:Personal access tokens

+

打开网址后,点击 Generate new token -> Generate new token (classic)

+

image.png

+

然后依次填写

+
    +
  • Note: 注释或者名字,按自己喜欢填写符合规则的名字
  • +
  • Expiration:过期时间,可以选择合适的时间,这里我选择的是永不过期,No Expiration
  • +
  • Select scopes:选择权限,勾上repo和workflow,这里的作用是使得这个token具有读写repo的权限和通过github Action更新的能力
  • +
+

拖到页面最底部,点击生成按钮

+

image.png

+

token生成后,会在这里展示刚刚生成的token。
注意:新生成的token只会在创建时显示一次,刷新之后就会消失,记得备份保存,如果忘记了,删除重新生成即可

+

image.png

+

Token填写到myblog仓库

点击:New repository secret ,将刚刚申请的token填入:

+
    +
  • Name:尽量按规范使用大写,多个单词用_分割
  • +
  • Secret:填入刚刚申请的Token
  • +
+

image.png
image.png

+

部署脚本编写

将myblog仓库代码克隆到本地,新增.github/workflow文件夹,然后再新增一个.yml的文件,假设这里叫hexo-deploy.yml,然后将以下代码复制到文件中,保存后使用git提交到myblog仓库

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
name: deploying Hexo project to GitHub pages
on:
push:
branches:
- main # 分支有 push 行为时就触发这个 action

jobs:
build-and-deploy:
runs-on: ubuntu-latest
env:
GITHUB_USER: 2ue
# GitHub Pages仓库 即博客部署需要用到的仓库
DEPLOY_REPO: 2ue.github.io
DEPLOY_BRANCH: main
GIT_USER: 2ue
GIT_EMAIL: xxxx@xxx.com
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0

- name: Set timezone to Asia/Shanghai
uses: szenius/set-timezone@v1.0
with:
# 设置执行环境的时区为 Linux 上海时区
timezoneLinux: "Asia/Shanghai"

- name: Echo current time
run: timedatectl

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16.15.0

- name: Generate pages
run: |
export TZ='Asia/Shanghai'
npm install -g hexo-cli hexo
npm install
hexo clean && hexo g

- name: Git config
run: |
git config --global user.name "${{GIT_USER}}"
git config --global user.email "${{GIT_EMAIL}}"

- name: Pull blog repo && Copy files
run: |
git clone https://github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
rm -rf ${{DEPLOY_REPO}}/*
cp -r ./public/* ./${{DEPLOY_REPO}}

- name: Deploy blog
run: |
echo '>_ Enter repo: ${{GITHUB_USER}}/${{DEPLOY_REPO}} ...'
cd ${{DEPLOY_REPO}}
echo '>_ Pwd current dir ...'
pwd
echo '>_ Show files ...'
ls -al
echo '>_ Set remote git ...'
rm -rf .git
git init
git remote add origin https://${{secrets.DEPLOY_TOKEN}}@github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
git branch -M ${{DEPLOY_BRANCH}}
git add .
echo '>_ Start Commit ...'
git commit --allow-empty -m "Github Action Automated Deployment $(date +'%Y-%m-%d %H:%M:%S')"
echo '>_ Start Push ...'
git push -u origin ${{DEPLOY_BRANCH}} --force
+ +

至此Hexo利用Github Actions自动化部署就实现了

+

部署

将代码提交到github后,打开github的myblog仓库(你自己的文章源文件仓库),点击:Actions可以看到有任务正在执行,以下是执行成功后的界面:

+

image.png

+

如果有失败,可以点击对应的步骤,查询详细情况:

+

image.png

+

当然如果你使用的vscode,也可以安装Github Actions插件,然后再vscode中直接查看执行过程,不过在插件中没有办法查看失败信息,但是可以点击直接跳转到对应页面查看详细信息

+

image.png

+

参考

在 GitHub Pages 上部署 Hexo | Hexo
GitHub Actions 文档 - GitHub 文档

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024/04/19/\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242undefined" "b/2024/04/19/\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242undefined" new file mode 100644 index 00000000..00696be7 --- /dev/null +++ "b/2024/04/19/\345\246\202\344\275\225\344\275\277\347\224\250Github Actions\345\256\236\347\216\260\350\207\252\345\212\250\345\214\226\351\203\250\347\275\262Hexo\345\215\232\345\256\242undefined" @@ -0,0 +1,572 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何使用Github Actions实现自动化部署Hexo博客 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 如何使用Github Actions实现自动化部署Hexo博客 +

+ + +
+ + + + +
+

Hexo 是一个基于 Node.js 的静态博客框架,它以简洁的速度和易用性受到许多开发者的青睐。如果你的博客部署在github上,那么这里有一种新的部署方式可能让摆脱频繁提交部署的苦恼!GitHub Actions 为自动化部署Hexo提供了一种解决方案,极大地简化了部署流程和节省了时间,可以让我们更多的专注到写作本身。

+
+

为什么使用Github Actions

    +
  • 将文章源文件和部署文件分离,有助于保护源文件中的隐私和你的草稿文件等
  • +
  • 节省手动部署的时间,专注于写作本身
  • +
  • 学习Github Actions相关知识,不折腾不作死发作了
  • +
+

如果你符合以上任何一条,你都应该考虑往下看下去。

+

什么是Github Actions

GitHub Actions 是 GitHub 推出的一项强大的自动化工具,它允许用户在 GitHub 仓库中创建、编辑和运行自动化脚本,这些脚本被称为工作流程(workflows)。这些工作流程可以响应 GitHub 上的各种事件,例如代码被推送、issue 被创建、pull request 被打开或定期调度任务等。

+

GitHub Actions 的工作流程(workflow)由 YAML 文件定义,这些文件放在仓库的 .github/workflows 目录下。每个工作流程文件描述了一系列的作业(jobs)和步骤(steps),定义了运行环境、触发条件、要执行的任务等。

+

前期准备

创建两个仓库

创建部署仓库

    +
  • 建立名为 <你的 GitHub 用户名>.github.io 的公开储存库,若之前已将 Hexo 上传至其他储存库,将该储存库重命名即可
  • +
  • 将 Hexo 文件夹中的文件 push 到储存库的默认分支,默认分支通常名为 main,旧一点的储存库可能名为 master
  • +
  • 开启Github Pages能力,使其可以 https://<你的 GitHub 用户名>.github.io访问
  • +
+

比如笔者使用2ue.github.io储存库用于存在文章编译后的代码,开启github pages后可通过https://2ue.github.io访问

+

创建文章源码仓库

    +
  • 创建一个私有仓库,用于储存文章源代码
    比如,笔者使用myblog储存库存放文章的源文件
  • +
+

至此两个仓库已经准备完了,后面所有的操作都在myblog上进行

+

生成Personal access tokens

+

github提供了access tokens的能力,可以在不登录的情况访问仓库和操作仓库等,所以我们可以通过相关能力来实现自动化部署,但同时也要保管好该token

+
+

Personal access tokens申请地址:Personal access tokens

+

打开网址后,点击 Generate new token -> Generate new token (classic)

+

image.png

+

然后依次填写

+
    +
  • Note: 注释或者名字,按自己喜欢填写符合规则的名字
  • +
  • Expiration:过期时间,可以选择合适的时间,这里我选择的是永不过期,No Expiration
  • +
  • Select scopes:选择权限,勾上repo和workflow,这里的作用是使得这个token具有读写repo的权限和通过github Action更新的能力
  • +
+

拖到页面最底部,点击生成按钮

+

image.png

+

token生成后,会在这里展示刚刚生成的token。
注意:新生成的token只会在创建时显示一次,刷新之后就会消失,记得备份保存,如果忘记了,删除重新生成即可

+

image.png

+

Token填写到myblog仓库

点击:New repository secret ,将刚刚申请的token填入:

+
    +
  • Name:尽量按规范使用大写,多个单词用_分割
  • +
  • Secret:填入刚刚申请的Token
  • +
+

image.png
image.png

+

部署脚本编写

将myblog仓库代码克隆到本地,新增.github/workflow文件夹,然后再新增一个.yml的文件,假设这里叫hexo-deploy.yml,然后将以下代码复制到文件中,保存后使用git提交到myblog仓库

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
name: deploying Hexo project to GitHub pages
on:
push:
branches:
- main # 分支有 push 行为时就触发这个 action

jobs:
build-and-deploy:
runs-on: ubuntu-latest
env:
GITHUB_USER: 2ue
# GitHub Pages仓库 即博客部署需要用到的仓库
DEPLOY_REPO: 2ue.github.io
DEPLOY_BRANCH: main
GIT_USER: 2ue
GIT_EMAIL: xxxx@xxx.com
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0

- name: Set timezone to Asia/Shanghai
uses: szenius/set-timezone@v1.0
with:
# 设置执行环境的时区为 Linux 上海时区
timezoneLinux: "Asia/Shanghai"

- name: Echo current time
run: timedatectl

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16.15.0

- name: Generate pages
run: |
export TZ='Asia/Shanghai'
npm install -g hexo-cli hexo
npm install
hexo clean && hexo g

- name: Git config
run: |
git config --global user.name "${{GIT_USER}}"
git config --global user.email "${{GIT_EMAIL}}"

- name: Pull blog repo && Copy files
run: |
git clone https://github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
rm -rf ${{DEPLOY_REPO}}/*
cp -r ./public/* ./${{DEPLOY_REPO}}

- name: Deploy blog
run: |
echo '>_ Enter repo: ${{GITHUB_USER}}/${{DEPLOY_REPO}} ...'
cd ${{DEPLOY_REPO}}
echo '>_ Pwd current dir ...'
pwd
echo '>_ Show files ...'
ls -al
echo '>_ Set remote git ...'
rm -rf .git
git init
git remote add origin https://${{secrets.DEPLOY_TOKEN}}@github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
git branch -M ${{DEPLOY_BRANCH}}
git add .
echo '>_ Start Commit ...'
git commit --allow-empty -m "Github Action Automated Deployment $(date +'%Y-%m-%d %H:%M:%S')"
echo '>_ Start Push ...'
git push -u origin ${{DEPLOY_BRANCH}} --force
+ +

至此Hexo利用Github Actions自动化部署就实现了

+

部署

将代码提交到github后,打开github的myblog仓库(你自己的文章源文件仓库),点击:Actions可以看到有任务正在执行,以下是执行成功后的界面:

+

image.png

+

如果有失败,可以点击对应的步骤,查询详细情况:

+

image.png

+

当然如果你使用的vscode,也可以安装Github Actions插件,然后再vscode中直接查看执行过程,不过在插件中没有办法查看失败信息,但是可以点击直接跳转到对应页面查看详细信息

+

image.png

+

参考

在 GitHub Pages 上部署 Hexo | Hexo
GitHub Actions 文档 - GitHub 文档

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024/04/20/\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205.html" "b/2024/04/20/\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205.html" new file mode 100644 index 00000000..beb32304 --- /dev/null +++ "b/2024/04/20/\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205.html" @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用Github Action自动化发布npm包 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用Github Action自动化发布npm包 +

+ + +
+ + + + +
+

GitHub Actions 提供了一种强大的自动化构建和发布流程的方式,通过组合github action的方式形成了workflow,来实现CI/CD。我们完全可以将 npm 包的发布流程集成到 GitHub workflow中,做到自动化发包,这可以可以提高效率并减少人为错误。以下是如何使用 GitHub Actions 实现自动化发布的详细指南。

+
+

0. 原理解释

github actions是一个github推出的CI/CD工具,可以模拟平台(比如linux等)自动化执行一些操作。
npm可以通过token实现免登陆发布包,并且Github Secrets能很好的保护token不被泄露(后面会说简单说一下为什么),这样就避免了在登陆信息在CI/CD时泄露

+

这是我编写的权限验证的一个包,支持vue,react以及函数式调用,同时使用github workflow实现的自动npm化发包。欢迎大家参考,同时提出优化意见:
validate-permission/.github/workflows at main · 2ue/validate-permission · GitHub

+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你有一个 GitHub 账户,并且已经创建了一个仓库。
  • +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。但要注意,因为access token具有你的npm账户读写权利,所有不应该将.npmrc文件提交到远程仓库,需要将该文件加入到.gitignore中

+
1
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
1
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

发布失败

如果你没有成功,出现如下报错:

+
1
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • .npmrc配置问题:确保你的.npmrc文件路径,命名和内容正确
  • +
  • .npmrc:确保你的access token正确,或者具有publish或write权限
  • +
  • 本地没有使用npm官方源:由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此使用npm access token发布npm包的流程已经跑通了。

+

相信大家已经发现了,上面这种方式有一个问题:.npmrc只能放到本地,意味着每次换一个电脑写代码都要重新去复制access token,生成.npmrc,那有没有一种方式可以比较安全的管理它呢,答案肯定是有的:答案就在Github。
我们不但可以利用GitHub Secrets管理npm的access token,而且还可利用github actions能力实现自动化workflow,来自动化发包到npm。

+

3. 配置 GitHub Secrets

在 GitHub 仓库中添加 npm token:

+
    +
  1. 进入 GitHub 仓库的 “Settings” > “Secrets”。
  2. +
  3. 点击 “New repository secret”。
  4. +
  5. 输入 NPM_TOKEN 作为名称,并粘贴你的 npm token 作为值。
  6. +
  7. 保存秘密。
    image.png
  8. +
+

请注意,这里我们可以选择Secrets和Variables,他们都在我们编写的workflow中被读取到,其实都是变量,但是两者有区别:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
变量类型存储方式界面是否可见修改内容修改变量名workflow是否可见
Secrets加密不可见只能更新且更新时不能看到之前内容无法修改以***显示
Variables明文明文且可见可自由更改且可以看到之前内容可以修改明文可见
从上面的比较可以看出为什么github Secrets能够有效的保护npm access token,当然万事不是绝对,如果非要追求百分百安全,那就使用最原始的方式吧,毕竟放在你本地的文件也可能因为中木马泄露出去。
+

4. 编写 GitHub Actions Workflow

至此我们就可以利用github actions来编写一个workflow。让我们捋一下思路:

+
    +
  • 什么时候执行:在代码推送到某分支,或者打tag的时候,这里我们选择当代码推送时
  • +
  • 执行流程
      +
    • 拉取代码
    • +
    • 设置时区:如果需要
    • +
    • 设置node环境
    • +
    • 安装pnpm,yarn等工具:如果需要
    • +
    • 安装代码依赖,并打包
    • +
    • 写入token到.npmrc
    • +
    • 执行npm 发布
      当然如果所有的操作都需要我们自己去实现,就比较复杂,好在有第三方action可以快速的让我们实现这些能力:
    • +
    +
  • +
  • run:可以执行脚本命令,让我们可以执行类似npm i这样的命令
  • +
  • actions/checkout@v4:实现代码拉取
  • +
  • actions/setup-node@v4:实现node环境,可以指定node版本
  • +
+

那么我们就用这些能力创建一个workflow吧

+

在项目根目录下创建 .github/workflows/npm-publish.yml 文件,并添加以下内容:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# workflow名字
name: Publish to npm

# 触发条件:当 main 分支有 push 事件时触发
on:
push:
branches:
- main

jobs:
publish:
# 在 Ubuntu 最新版本上运行作业
runs-on: ubuntu-latest
steps:
# 使用 actions/checkout 检出当前代码
- name: Checkout code
uses: actions/checkout@v4

# 设置 Node.js 环境
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.11.1

# 安装项目依赖,并编译
- name: Install dependencies
run: |
npm i
npm run build

# 添加access token到.npmrc
- name: Add Npm Token
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ./.npmrc
ls -al
cat ./.npmrc

# 发布到 npm registry
- name: Publish to npm
run: npm publish
env:
# 读取github的secrets变量,这里的NPM_TOKEN是前面设置的变量名
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ +

此 workflow 会在推送到 main 分支时触发。

+

image.png

+

5. 推送更改到 GitHub

将你的更改推送到 GitHub 仓库:

+
1
2
3
git add .
git commit -m "Set up automated npm publishing with GitHub Actions"
git push
+ +

6. 测试 Workflow

一旦你推送了更改,GitHub Actions workflow 将会运行。你可以在 GitHub 仓库的 “Actions” 选项卡中查看 workflow 的状态。

+

7. 发布新版本

当你准备发布新版本时,更新 package.json 中的版本号,然后提交并推送这些更改到 main 分支。GitHub Actions 将自动处理剩余的发布流程。

+

8.优化

这个workflow脚本还有很多优化的空间,比如:

+

优化触发时机

如果我们推送代码就执行发包操作,在有些情况下可能不符合我们要求,所有我们可以更改触发条件:
正常情况我们想的是创建了一个v1.0.0这样的tag分支才触发,并且是修改了关键源码才触发

+
    +
  • 设置当推送了tag分支并且需要tag符合某种格式才触发流水
  • +
+
1
2
3
4
5
name: Npm publish
on:
push:
tags:
- "v*" # 这段的意思是仅在出现名为 v 字符开头的tag时,触发此任务,如v1.2.1
+ +
    +
  • 设置修改了某些文件才执行发包
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- uses: dorny/paths-filter@v3
- #必须设定ID,用于后面读取这一步的结果
id: changes
with:
filters: |
update:
- 'main/**'
- 'types/**'
- 'utils/**'
- README.md
- package.json
- rollup.config.js
- tsconfig.json
- .babelrc
- .github/workflows/publish.yml
- name: Install pnpm
- # 这里是当【changes】输出了update为true才执行执行编译等工作
if: steps.changes.outputs.update == 'true'
run: |
npm install -g pnpm
+ +

dorny/paths-filter@v3github actions市场的一个包,可以用来检查哪些文件有变动,如果有变动则输出一个变量update,为true时表示有变动,可以用steps.changes.outputs.update来读取这个变量

+

自动化生成版本号

每次改动需要发布npm包时,都需要修改package.json的版本号,无疑有点麻烦,那我们可以使用npm version 命令来做到自动化版本修改:

+
    +
  • <update_type> 是语义化版本类型之一,如 majorminor 或 patch
  • +
  • 例如,运行 npm version patch 将会更新版本号为当前版本号的下一个补丁版本
  • +
+

甚至你可以通过github workflow的内置变量拿到tag名字,根据tag特征做到修改哪一个版本号

+

自动化生成tag和release

如果你的commit比较规范,比如使用了@changesets/cli等,那么可以利用github actions市场的三方包来自动化为你的发布生成changelog,tag,release等信息,比如semantic-release-action等

+

当然根据个人需求不同,可能还可以做更多的优化,这里就不一一述说了,毕竟本篇文章仅仅是一个穿针引线。

+

问题总结

问题 1:权限问题

如果你的 npm token 没有足够的权限发布包,你可能会遇到权限错误。[参考](#获取Access Token)

+

解决方案:确保你的 npm token 有发布包的权限。通常,这意味着你需要在 npm 网站上生成一个具有适当权限的 token。

+

问题 2:网络问题

如果你的 CI/CD 服务器无法访问 npm registry,发布可能会失败。一般在github上不会出现该问题,可能在本地使用了代理或者切换了npm源会出现这个问题。参考

+

解决方案:检查你的 CI/CD 服务器的网络设置,确保它可以无障碍地访问 https://registry.npmjs.org/

+

问题 3:Workflow 配置错误

如果你的 workflow 配置有误,它可能不会按预期触发。

+

解决方案:仔细检查 workflow 文件的语法和配置,特别是触发条件和 secret 的使用。

+

问题 4:.npmrc问题

如果你的 .npmrc 文件没有被正确地添加到项目根目录或内容不正确,那么可能导致发布失败。参考

+

解决方案:确保 .npmrc 文件被正确添加到你的版本控制系统,并且格式正确无误。

+

通过以上步骤和解决方案,你可以确保 npm 包的自动化发布流程顺畅运行。

+

参考

Using private packages in a CI/CD workflow | npm Docs
Working with the npm registry - GitHub Docs
Variables - GitHub Docs

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024/04/20/\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205undefined" "b/2024/04/20/\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205undefined" new file mode 100644 index 00000000..78fc72f0 --- /dev/null +++ "b/2024/04/20/\344\275\277\347\224\250Github Action\350\207\252\345\212\250\345\214\226\345\217\221\345\270\203npm\345\214\205undefined" @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用Github Action自动化发布npm包 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用Github Action自动化发布npm包 +

+ + +
+ + + + +
+

GitHub Actions 提供了一种强大的自动化构建和发布流程的方式,通过组合github action的方式形成了workflow,来实现CI/CD。我们完全可以将 npm 包的发布流程集成到 GitHub workflow中,做到自动化发包,这可以可以提高效率并减少人为错误。以下是如何使用 GitHub Actions 实现自动化发布的详细指南。

+
+

0. 原理解释

github actions是一个github推出的CI/CD工具,可以模拟平台(比如linux等)自动化执行一些操作。
npm可以通过token实现免登陆发布包,并且Github Secrets能很好的保护token不被泄露(后面会说简单说一下为什么),这样就避免了在登陆信息在CI/CD时泄露

+

这是我编写的权限验证的一个包,支持vue,react以及函数式调用,同时使用github workflow实现的自动npm化发包。欢迎大家参考,同时提出优化意见:
validate-permission/.github/workflows at main · 2ue/validate-permission · GitHub

+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你有一个 GitHub 账户,并且已经创建了一个仓库。
  • +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。但要注意,因为access token具有你的npm账户读写权利,所有不应该将.npmrc文件提交到远程仓库,需要将该文件加入到.gitignore中

+
1
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
1
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

发布失败

如果你没有成功,出现如下报错:

+
1
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • .npmrc配置问题:确保你的.npmrc文件路径,命名和内容正确
  • +
  • .npmrc:确保你的access token正确,或者具有publish或write权限
  • +
  • 本地没有使用npm官方源:由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此使用npm access token发布npm包的流程已经跑通了。

+

相信大家已经发现了,上面这种方式有一个问题:.npmrc只能放到本地,意味着每次换一个电脑写代码都要重新去复制access token,生成.npmrc,那有没有一种方式可以比较安全的管理它呢,答案肯定是有的:答案就在Github。
我们不但可以利用GitHub Secrets管理npm的access token,而且还可利用github actions能力实现自动化workflow,来自动化发包到npm。

+

3. 配置 GitHub Secrets

在 GitHub 仓库中添加 npm token:

+
    +
  1. 进入 GitHub 仓库的 “Settings” > “Secrets”。
  2. +
  3. 点击 “New repository secret”。
  4. +
  5. 输入 NPM_TOKEN 作为名称,并粘贴你的 npm token 作为值。
  6. +
  7. 保存秘密。
    image.png
  8. +
+

请注意,这里我们可以选择Secrets和Variables,他们都在我们编写的workflow中被读取到,其实都是变量,但是两者有区别:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
变量类型存储方式界面是否可见修改内容修改变量名workflow是否可见
Secrets加密不可见只能更新且更新时不能看到之前内容无法修改以***显示
Variables明文明文且可见可自由更改且可以看到之前内容可以修改明文可见
从上面的比较可以看出为什么github Secrets能够有效的保护npm access token,当然万事不是绝对,如果非要追求百分百安全,那就使用最原始的方式吧,毕竟放在你本地的文件也可能因为中木马泄露出去。
+

4. 编写 GitHub Actions Workflow

至此我们就可以利用github actions来编写一个workflow。让我们捋一下思路:

+
    +
  • 什么时候执行:在代码推送到某分支,或者打tag的时候,这里我们选择当代码推送时
  • +
  • 执行流程
      +
    • 拉取代码
    • +
    • 设置时区:如果需要
    • +
    • 设置node环境
    • +
    • 安装pnpm,yarn等工具:如果需要
    • +
    • 安装代码依赖,并打包
    • +
    • 写入token到.npmrc
    • +
    • 执行npm 发布
      当然如果所有的操作都需要我们自己去实现,就比较复杂,好在有第三方action可以快速的让我们实现这些能力:
    • +
    +
  • +
  • run:可以执行脚本命令,让我们可以执行类似npm i这样的命令
  • +
  • actions/checkout@v4:实现代码拉取
  • +
  • actions/setup-node@v4:实现node环境,可以指定node版本
  • +
+

那么我们就用这些能力创建一个workflow吧

+

在项目根目录下创建 .github/workflows/npm-publish.yml 文件,并添加以下内容:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# workflow名字
name: Publish to npm

# 触发条件:当 main 分支有 push 事件时触发
on:
push:
branches:
- main

jobs:
publish:
# 在 Ubuntu 最新版本上运行作业
runs-on: ubuntu-latest
steps:
# 使用 actions/checkout 检出当前代码
- name: Checkout code
uses: actions/checkout@v4

# 设置 Node.js 环境
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.11.1

# 安装项目依赖,并编译
- name: Install dependencies
run: |
npm i
npm run build

# 添加access token到.npmrc
- name: Add Npm Token
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ./.npmrc
ls -al
cat ./.npmrc

# 发布到 npm registry
- name: Publish to npm
run: npm publish
env:
# 读取github的secrets变量,这里的NPM_TOKEN是前面设置的变量名
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ +

此 workflow 会在推送到 main 分支时触发。

+

image.png

+

5. 推送更改到 GitHub

将你的更改推送到 GitHub 仓库:

+
1
2
3
git add .
git commit -m "Set up automated npm publishing with GitHub Actions"
git push
+ +

6. 测试 Workflow

一旦你推送了更改,GitHub Actions workflow 将会运行。你可以在 GitHub 仓库的 “Actions” 选项卡中查看 workflow 的状态。

+

7. 发布新版本

当你准备发布新版本时,更新 package.json 中的版本号,然后提交并推送这些更改到 main 分支。GitHub Actions 将自动处理剩余的发布流程。

+

8.优化

这个workflow脚本还有很多优化的空间,比如:

+

优化触发时机

如果我们推送代码就执行发包操作,在有些情况下可能不符合我们要求,所有我们可以更改触发条件:
正常情况我们想的是创建了一个v1.0.0这样的tag分支才触发,并且是修改了关键源码才触发

+
    +
  • 设置当推送了tag分支并且需要tag符合某种格式才触发流水
  • +
+
1
2
3
4
5
name: Npm publish
on:
push:
tags:
- "v*" # 这段的意思是仅在出现名为 v 字符开头的tag时,触发此任务,如v1.2.1
+ +
    +
  • 设置修改了某些文件才执行发包
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- uses: dorny/paths-filter@v3
- #必须设定ID,用于后面读取这一步的结果
id: changes
with:
filters: |
update:
- 'main/**'
- 'types/**'
- 'utils/**'
- README.md
- package.json
- rollup.config.js
- tsconfig.json
- .babelrc
- .github/workflows/publish.yml
- name: Install pnpm
- # 这里是当【changes】输出了update为true才执行执行编译等工作
if: steps.changes.outputs.update == 'true'
run: |
npm install -g pnpm
+ +

dorny/paths-filter@v3github actions市场的一个包,可以用来检查哪些文件有变动,如果有变动则输出一个变量update,为true时表示有变动,可以用steps.changes.outputs.update来读取这个变量

+

自动化生成版本号

每次改动需要发布npm包时,都需要修改package.json的版本号,无疑有点麻烦,那我们可以使用npm version 命令来做到自动化版本修改:

+
    +
  • <update_type> 是语义化版本类型之一,如 majorminor 或 patch
  • +
  • 例如,运行 npm version patch 将会更新版本号为当前版本号的下一个补丁版本
  • +
+

甚至你可以通过github workflow的内置变量拿到tag名字,根据tag特征做到修改哪一个版本号

+

自动化生成tag和release

如果你的commit比较规范,比如使用了@changesets/cli等,那么可以利用github actions市场的三方包来自动化为你的发布生成changelog,tag,release等信息,比如semantic-release-action等

+

当然根据个人需求不同,可能还可以做更多的优化,这里就不一一述说了,毕竟本篇文章仅仅是一个穿针引线。

+

问题总结

问题 1:权限问题

如果你的 npm token 没有足够的权限发布包,你可能会遇到权限错误。[参考](#获取Access Token)

+

解决方案:确保你的 npm token 有发布包的权限。通常,这意味着你需要在 npm 网站上生成一个具有适当权限的 token。

+

问题 2:网络问题

如果你的 CI/CD 服务器无法访问 npm registry,发布可能会失败。一般在github上不会出现该问题,可能在本地使用了代理或者切换了npm源会出现这个问题。参考

+

解决方案:检查你的 CI/CD 服务器的网络设置,确保它可以无障碍地访问 https://registry.npmjs.org/

+

问题 3:Workflow 配置错误

如果你的 workflow 配置有误,它可能不会按预期触发。

+

解决方案:仔细检查 workflow 文件的语法和配置,特别是触发条件和 secret 的使用。

+

问题 4:.npmrc问题

如果你的 .npmrc 文件没有被正确地添加到项目根目录或内容不正确,那么可能导致发布失败。参考

+

解决方案:确保 .npmrc 文件被正确添加到你的版本控制系统,并且格式正确无误。

+

通过以上步骤和解决方案,你可以确保 npm 包的自动化发布流程顺畅运行。

+

参考

Using private packages in a CI/CD workflow | npm Docs
Working with the npm registry - GitHub Docs
Variables - GitHub Docs

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024/04/23/Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257.html" "b/2024/04/23/Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257.html" new file mode 100644 index 00000000..b7709170 --- /dev/null +++ "b/2024/04/23/Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257.html" @@ -0,0 +1,545 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 +

+ + +
+ + + + +
+

在 Git 工作流中,有时需要在同一台电脑上针对不同的项目设置不同的用户身份。例如,开发人员可能需要在个人项目和公司项目之间切换,而这两个项目可能需要不同的 Git 用户名和电子邮件地址。Oh My Zsh 提供了强大的自定义功能,允许我们通过编写自定义的 shell 函数来实现这一需求。本文将展示如何使用 Oh My Zsh 的 chpwd 函数来自动设置特定目录及其子目录、孙目录下的 Git 用户配置。

+
+

开始之前

假设你对git身份信息和ssh key概念有比较清晰的认识和了解其基础使用,比如git身份信息是用户名和邮箱,在你使用git commit的时候进行记录;而ssh key是一个凭证(分为公钥和私钥),用于远端身份信息验证,和git没有直接关系,也可以用于其他场景使用,比如ssh登录远程主机。

+

简单说下为什么要使用不同的git身份信息呢?
公司项目要求使用规定的name(比如企微名,花名或者公司内部系统的唯一标识等)和email(公司邮箱)作为git提交记录,这样方便团队协作和代码管理,这些信息有一定的敏感性。所有在提交代码到外网的时候必须考虑使用不同的git身份信息来提交。

+

当然我们可以在每次克隆一个新项目的时候,根据不同需要使用git config –local进行设置,但这明手动操作容易遗忘而且繁琐,所以想到使用脚本来自动化完成。

+

最终效果

本文不是解决:不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的ssh key凭证这个问题,而且为了解决以下问题:

+
    +
  • 不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的git身份信息
  • +
+

最终想达到:

+
    +
  • 进入到指定目录及其子目录,孙目录,如果是git仓库,就自动设置对应的git身份信息
  • +
+

约定目录结构:

1
2
3
4
5
/Users/developer/teamA         # 根目录
└── projectA # 子目录,团队项目
└── src # 孙目录

/Users/developer/teamB
+ +

解决方案

解决这个问题的方案有很多,下面分享一种使用zsh脚本的方案。首先无论哪一种方案,都需要在全局设置全局git信息:

+
1
2
git config --global user.name "name1"
git config --global user.email"email1@email.com"
+ +

安装 Oh My Zsh

如果尚未安装,通过以下命令安装(已经安装就跳过):

+
1
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
+ +

编辑 .zshrc 文件

添加 chpwd 函数:在 .zshrc 最后添加以下函数

+
1
vi ~/.zshrc
+ +

确保启用了plugin:

+
1
2
# 保证这一行不被注释, 括号的内容可能不一样
plugins=(git)
+ +

.zshrc 最后,添加 chpwd 函数:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 chpwd() {
# 指定目标目录
local root_dir="/Users/developer/teamA" # 指定目录
# 指定该目录要设置的git name信息
local user_name="name"
# 指定该目录要设置的git email信息
local user_email="email@email.com"
# 获取到当前目录
local current_dir="$(pwd -P)"
# 如果当前目录是否是目标目录,或者是否其子目录,孙目录
if [[ "$current_dir" == "$root_dir"* ]]; then
# 输出当前目录,用于调试,后续可删除
echo "chpwd: $PWD"

if [ -d "$current_dir/.git" ]; then
git -C "$current_dir" config --local user.name "$user_name"
git -C "$current_dir" config --local user.email "$user_email"
echo "chpwd: 成功设置 user.name 和 user.email"
fi
fi
}
# 执行 chpwd 函数
chpwd
+ +

为了让更改生效,需要重新加载 .zshrc 文件。在终端中运行以下命令(或者关闭后重新打开)

+
1
source ~/.zshrc
+ +

ps: 当然你如果使用的其他命令行终端,可以参考这个脚本,根据终端的特点,自行配置

+

测试

在vscode中打开控制台:

使用vscode打开项目/Users/developer/teamA/projectA,并且打开控制台,如果发现有成功输出信息,就表示成功了。

+
1
chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。如果输出的信息是你预期的表示脚本成功了。

+

同样,使用vscode打开一个不在/Users/developer/teamA目录下的项目,如果没有输出该消息说明符合预期的

+

测试cd命令

打开zsh终端,使用cd命令进入/Users/developer/teamA/projectA,如果发现有成功输出信息,就表示成功了。

+
1
2
3
cd /Users/developer/teamA/projectA

chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。

+

同样,进入/Users/developer/teamB/projectB:

+
1
cd /Users/developer/teamB/projectB
+ +

如果没有输出该消息说明符合预期的

+

方案限制

必须限制不同的远程仓库放到对应的目录,如果你现在的本地项目已经分散到不同的目录了,就必须要重新移动一下本地目录或者重新clone一下远程仓库到对应目录

+

总结

通过 Oh My Zsh 的 chpwd 函数,我们能够自动化 Git 用户信息的设置,这不仅提升了工作效率,也减少了配置错误的可能性。本文提供的步骤和测试验证了解决方案的有效性,展示了 Oh My Zsh 在自动化 shell 任务中的实用性。

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/2024/04/23/Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257undefined" "b/2024/04/23/Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257undefined" new file mode 100644 index 00000000..d3572647 --- /dev/null +++ "b/2024/04/23/Oh My Zsh \350\207\252\345\212\250\345\214\226-\350\275\273\346\235\276\347\256\241\347\220\206\345\244\232\344\270\252 Git \347\224\250\346\210\267\350\272\253\344\273\275\344\277\241\346\201\257undefined" @@ -0,0 +1,545 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 +

+ + +
+ + + + +
+

在 Git 工作流中,有时需要在同一台电脑上针对不同的项目设置不同的用户身份。例如,开发人员可能需要在个人项目和公司项目之间切换,而这两个项目可能需要不同的 Git 用户名和电子邮件地址。Oh My Zsh 提供了强大的自定义功能,允许我们通过编写自定义的 shell 函数来实现这一需求。本文将展示如何使用 Oh My Zsh 的 chpwd 函数来自动设置特定目录及其子目录、孙目录下的 Git 用户配置。

+
+

开始之前

假设你对git身份信息和ssh key概念有比较清晰的认识和了解其基础使用,比如git身份信息是用户名和邮箱,在你使用git commit的时候进行记录;而ssh key是一个凭证(分为公钥和私钥),用于远端身份信息验证,和git没有直接关系,也可以用于其他场景使用,比如ssh登录远程主机。

+

简单说下为什么要使用不同的git身份信息呢?
公司项目要求使用规定的name(比如企微名,花名或者公司内部系统的唯一标识等)和email(公司邮箱)作为git提交记录,这样方便团队协作和代码管理,这些信息有一定的敏感性。所有在提交代码到外网的时候必须考虑使用不同的git身份信息来提交。

+

当然我们可以在每次克隆一个新项目的时候,根据不同需要使用git config –local进行设置,但这明手动操作容易遗忘而且繁琐,所以想到使用脚本来自动化完成。

+

最终效果

本文不是解决:不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的ssh key凭证这个问题,而且为了解决以下问题:

+
    +
  • 不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的git身份信息
  • +
+

最终想达到:

+
    +
  • 进入到指定目录及其子目录,孙目录,如果是git仓库,就自动设置对应的git身份信息
  • +
+

约定目录结构:

1
2
3
4
5
/Users/developer/teamA         # 根目录
└── projectA # 子目录,团队项目
└── src # 孙目录

/Users/developer/teamB
+ +

解决方案

解决这个问题的方案有很多,下面分享一种使用zsh脚本的方案。首先无论哪一种方案,都需要在全局设置全局git信息:

+
1
2
git config --global user.name "name1"
git config --global user.email"email1@email.com"
+ +

安装 Oh My Zsh

如果尚未安装,通过以下命令安装(已经安装就跳过):

+
1
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
+ +

编辑 .zshrc 文件

添加 chpwd 函数:在 .zshrc 最后添加以下函数

+
1
vi ~/.zshrc
+ +

确保启用了plugin:

+
1
2
# 保证这一行不被注释, 括号的内容可能不一样
plugins=(git)
+ +

.zshrc 最后,添加 chpwd 函数:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 chpwd() {
# 指定目标目录
local root_dir="/Users/developer/teamA" # 指定目录
# 指定该目录要设置的git name信息
local user_name="name"
# 指定该目录要设置的git email信息
local user_email="email@email.com"
# 获取到当前目录
local current_dir="$(pwd -P)"
# 如果当前目录是否是目标目录,或者是否其子目录,孙目录
if [[ "$current_dir" == "$root_dir"* ]]; then
# 输出当前目录,用于调试,后续可删除
echo "chpwd: $PWD"

if [ -d "$current_dir/.git" ]; then
git -C "$current_dir" config --local user.name "$user_name"
git -C "$current_dir" config --local user.email "$user_email"
echo "chpwd: 成功设置 user.name 和 user.email"
fi
fi
}
# 执行 chpwd 函数
chpwd
+ +

为了让更改生效,需要重新加载 .zshrc 文件。在终端中运行以下命令(或者关闭后重新打开)

+
1
source ~/.zshrc
+ +

ps: 当然你如果使用的其他命令行终端,可以参考这个脚本,根据终端的特点,自行配置

+

测试

在vscode中打开控制台:

使用vscode打开项目/Users/developer/teamA/projectA,并且打开控制台,如果发现有成功输出信息,就表示成功了。

+
1
chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。如果输出的信息是你预期的表示脚本成功了。

+

同样,使用vscode打开一个不在/Users/developer/teamA目录下的项目,如果没有输出该消息说明符合预期的

+

测试cd命令

打开zsh终端,使用cd命令进入/Users/developer/teamA/projectA,如果发现有成功输出信息,就表示成功了。

+
1
2
3
cd /Users/developer/teamA/projectA

chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。

+

同样,进入/Users/developer/teamB/projectB:

+
1
cd /Users/developer/teamB/projectB
+ +

如果没有输出该消息说明符合预期的

+

方案限制

必须限制不同的远程仓库放到对应的目录,如果你现在的本地项目已经分散到不同的目录了,就必须要重新移动一下本地目录或者重新clone一下远程仓库到对应目录

+

总结

通过 Oh My Zsh 的 chpwd 函数,我们能够自动化 Git 用户信息的设置,这不仅提升了工作效率,也减少了配置错误的可能性。本文提供的步骤和测试验证了解决方案的有效性,展示了 Oh My Zsh 在自动化 shell 任务中的实用性。

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/404.html b/404.html new file mode 100644 index 00000000..bb470cac --- /dev/null +++ b/404.html @@ -0,0 +1,214 @@ + + + + + + + + 404 - 前端不是终点 + + + + +
+
+
+

What have you done?

+
Now Go Back Using Below LInk
+ +

! ERROR DECETED !

+
+
+
+ GO TO HOME PAGE +
+
+ + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 00000000..2d12a165 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# [My Blog](https://2ue.github.io) + +# 构建说明 + +基于github的github-page +使用开源项目[hexo](https://hexo.io)构建托管于`github`上的静态博客 +主题采用的是@iissnan贡献的[hexo-theme-next](https://github.com/iissnan/hexo-theme-next) +本人在有些地方做了一些修改,具体修改请见下列条目 + + +# 主题改动 + +- 文章摘要显示的方式,见[首页](https://2ue.github.io/)和文章详情页 +- 增加文章版权信息(没有用官方的版权样式) +- 增加文章结尾标记 +- 页面footer增加访客统计 +- 页面footer增加字数统计 +- 修改内容宽度 +- 修改首页文章分割线 +- 开启了背景canvas +- ... + +# 文章列表 + +- [[2024-04-23 14:05:00 - Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息](https://blog.imx0.com/post/05m1i396.html)] +- [[2024-04-20 16:32:00 - 使用Github Action自动化发布npm包](https://blog.imx0.com/post/i9knerk1.html)] +- [[2024-04-19 22:01:02 - 如何使用Github Actions实现自动化部署Hexo博客](https://blog.imx0.com/post/275cl921.html)] +- [[2024-03-07 21:15:10 - 使用conda快速初始化项目python](https://blog.imx0.com/post/5vm1lh7e.html)] +- [[2021-09-15 03:50:48 - Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法](https://blog.imx0.com/post/56i1jh21.html)] +- [[2019-10-12 22:01:24 - 使用Npm Token免登陆发包](https://blog.imx0.com/post/lsu9244c.html)] +- [[2018-07-22 03:47:23 - Node.js版本神器之nvm](https://blog.imx0.com/post/619on06z.html)] +- [[2018-01-20 19:52:00 - Git系列常用命令之放弃修改](https://blog.imx0.com/post/8ah3s5p4.html)] +- [[2017-11-30 06:53:37 - 风继续吹](https://blog.imx0.com/post/t6oopnba.html)] +- [[2017-11-27 06:37:45 - 常用javascript代码片段](https://blog.imx0.com/post/g0b67ue4.html)] +- [[2017-11-24 17:57:10 - 一道小小的题目引发对javascript支持正则表达式相关方法的探讨](https://blog.imx0.com/post/f358mgm5.html)] +- [[2017-11-16 03:09:16 - vue中慎用style的scoped属性](https://blog.imx0.com/post/m824cy0u.html)] +- [[2017-11-02 18:08:43 - 如何写一个日历组件](https://blog.imx0.com/post/wow943e6.html)] +- [[2017-10-27 22:43:12 - 慎重用for...in与for...of](https://blog.imx0.com/post/2145i587.html)] +- [[2017-10-16 18:16:37 - H5的Notification特性 - Web的桌面通知功能](https://blog.imx0.com/post/k0pgips8.html)] +- [[2017-10-10 19:06:52 - mocha+chai使用记录](https://blog.imx0.com/post/5164uhjd.html)] +- [[2017-10-10 18:06:30 - 前端测试探索](https://blog.imx0.com/post/6qn1yqkz.html)] +- [[2017-08-21 22:02:36 - node和npm版本管理器nvm的安装和使用](https://blog.imx0.com/post/kqdkiqw2.html)] +- [[2017-07-01 21:13:01 - Git系列之常用命令(一)](https://blog.imx0.com/post/130ppwo9.html)] +- [[2017-06-16 03:11:24 - Babun导致本地SSH-KEY不可用](https://blog.imx0.com/post/447n9qgo.html)] +- [[2017-04-01 01:05:06 - Javascript常用见问题之变量类型判断终极篇](https://blog.imx0.com/post/1mj936z8.html)] +- [[2017-03-15 21:01:33 - windows平台下超强的cmd工具Babun使用笔记](https://blog.imx0.com/post/d9bqnro8.html)] +- [[2017-01-06 07:03:29 - 使用vue框架造了一个日历控件](https://blog.imx0.com/post/2mb6p0h7.html)] +- [[2017-01-01 07:33:08 - <2016/><2017>](https://blog.imx0.com/post/7ft694p3.html)] +- [[2016-11-22 23:39:04 - 移动端适配方案](https://blog.imx0.com/post/5av4m4yi.html)] +- [[2016-09-25 04:46:41 - 使用webpack + gulp构建项目](https://blog.imx0.com/post/3e4nf265.html)] +- [[2016-09-05 20:12:24 - 常用正则整理(持续收集)](https://blog.imx0.com/post/794v5n58.html)] +- [[2016-08-12 05:22:35 - Javascript系列 - Javascript数组方法](https://blog.imx0.com/post/40xchoai.html)] +- [[2016-06-20 23:51:17 - Git系列之新手入门](https://blog.imx0.com/post/7rrj4k61.html)] +- [[2016-06-16 04:54:14 - 利用javascrit获取url传递的参数](https://blog.imx0.com/post/12ie522i.html)] +- [[2016-06-14 20:39:19 - 实现数字滚动变化以及延伸](https://blog.imx0.com/post/u5nm8s0b.html)] + +## 博客地址 + +[访问](https://2ue.github.io) \ No newline at end of file diff --git a/about/index.html b/about/index.html new file mode 100644 index 00000000..7da52538 --- /dev/null +++ b/about/index.html @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +about | 前端路上 + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ +

about +

+ + + +
+ + + +
+ +
+ + + +
+ + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2016/06/index.html b/archives/2016/06/index.html new file mode 100644 index 00000000..921c3980 --- /dev/null +++ b/archives/2016/06/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2016 +
+ + + + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2016/08/index.html b/archives/2016/08/index.html new file mode 100644 index 00000000..1ead25ae --- /dev/null +++ b/archives/2016/08/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2016 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2016/09/index.html b/archives/2016/09/index.html new file mode 100644 index 00000000..b062d637 --- /dev/null +++ b/archives/2016/09/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2016 +
+ + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2016/11/index.html b/archives/2016/11/index.html new file mode 100644 index 00000000..5f49a001 --- /dev/null +++ b/archives/2016/11/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2016 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2016/12/index.html b/archives/2016/12/index.html new file mode 100644 index 00000000..8941bb10 --- /dev/null +++ b/archives/2016/12/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2016 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2016/index.html b/archives/2016/index.html new file mode 100644 index 00000000..8526f8c2 --- /dev/null +++ b/archives/2016/index.html @@ -0,0 +1,488 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2016 +
+ + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/01/index.html b/archives/2017/01/index.html new file mode 100644 index 00000000..fc139943 --- /dev/null +++ b/archives/2017/01/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/03/index.html b/archives/2017/03/index.html new file mode 100644 index 00000000..9eb242fb --- /dev/null +++ b/archives/2017/03/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/06/index.html b/archives/2017/06/index.html new file mode 100644 index 00000000..709b5421 --- /dev/null +++ b/archives/2017/06/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/07/index.html b/archives/2017/07/index.html new file mode 100644 index 00000000..5c2f33bf --- /dev/null +++ b/archives/2017/07/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/08/index.html b/archives/2017/08/index.html new file mode 100644 index 00000000..74961d59 --- /dev/null +++ b/archives/2017/08/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/10/index.html b/archives/2017/10/index.html new file mode 100644 index 00000000..5406f877 --- /dev/null +++ b/archives/2017/10/index.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + + + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/11/index.html b/archives/2017/11/index.html new file mode 100644 index 00000000..007f5961 --- /dev/null +++ b/archives/2017/11/index.html @@ -0,0 +1,428 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + + + + + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/index.html b/archives/2017/index.html new file mode 100644 index 00000000..412cd254 --- /dev/null +++ b/archives/2017/index.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2017/page/2/index.html b/archives/2017/page/2/index.html new file mode 100644 index 00000000..119f9e0a --- /dev/null +++ b/archives/2017/page/2/index.html @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/01/index.html b/archives/2018/01/index.html new file mode 100644 index 00000000..b909d905 --- /dev/null +++ b/archives/2018/01/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2018 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/07/index.html b/archives/2018/07/index.html new file mode 100644 index 00000000..9ce754f6 --- /dev/null +++ b/archives/2018/07/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2018 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/index.html b/archives/2018/index.html new file mode 100644 index 00000000..285c46a7 --- /dev/null +++ b/archives/2018/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2018 +
+ + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/10/index.html b/archives/2019/10/index.html new file mode 100644 index 00000000..45c5ae6d --- /dev/null +++ b/archives/2019/10/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2019/index.html b/archives/2019/index.html new file mode 100644 index 00000000..fb17e9f6 --- /dev/null +++ b/archives/2019/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2019 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/09/index.html b/archives/2021/09/index.html new file mode 100644 index 00000000..4bffd3f4 --- /dev/null +++ b/archives/2021/09/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/index.html b/archives/2021/index.html new file mode 100644 index 00000000..d0f3e123 --- /dev/null +++ b/archives/2021/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/03/index.html b/archives/2024/03/index.html new file mode 100644 index 00000000..8c577700 --- /dev/null +++ b/archives/2024/03/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/04/index.html b/archives/2024/04/index.html new file mode 100644 index 00000000..56019ea5 --- /dev/null +++ b/archives/2024/04/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/index.html b/archives/2024/index.html new file mode 100644 index 00000000..770819fe --- /dev/null +++ b/archives/2024/index.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + + + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 00000000..15b5b877 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + + + + + +
+ 2021 +
+ + +
+ 2019 +
+ + +
+ 2018 +
+ + + + +
+ 2017 +
+ + + + + + +
+
+ + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 00000000..1212a2d2 --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/3/index.html b/archives/page/3/index.html new file mode 100644 index 00000000..83378340 --- /dev/null +++ b/archives/page/3/index.html @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2017 +
+ + + + + + +
+ 2016 +
+ + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/4/index.html b/archives/page/4/index.html new file mode 100644 index 00000000..2a249fda --- /dev/null +++ b/archives/page/4/index.html @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +归档 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+ + 还行! 目前共计 31 篇日志。 继续努力。 +
+ + +
+ 2016 +
+ + + + +
+
+ + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atom.xml b/atom.xml new file mode 100644 index 00000000..ebe56d8d --- /dev/null +++ b/atom.xml @@ -0,0 +1,520 @@ + + + 前端路上 + https://blog.imx0.com/icon.png + 万物皆有裂缝 + + + + 2024-04-23T06:05:00.000Z + https://blog.imx0.com/ + + + 2ue + + + + Hexo + + + Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 + + https://blog.imx0.com/2024/04/23/Oh%20My%20Zsh%20%E8%87%AA%E5%8A%A8%E5%8C%96-%E8%BD%BB%E6%9D%BE%E7%AE%A1%E7%90%86%E5%A4%9A%E4%B8%AA%20Git%20%E7%94%A8%E6%88%B7%E8%BA%AB%E4%BB%BD%E4%BF%A1%E6%81%AF.html + 2024-04-23T06:05:00.000Z + 2024-04-23T06:05:00.000Z + + + <blockquote> +<p>在 Git 工作流中,有时需要在同一台电脑上针对不同的项目设置不同的用户身份。例如,开发人员可能需要在个人项目和公司项目之间切换,而这两个项目可能需要不同的 Git 用户名和电子邮件地址。Oh My Zsh 提供了强大的自定义功能,允许我们通过编写自定义的 shell 函数来实现这一需求。本文将展示如何使用 Oh My Zsh 的 <code>chpwd</code> 函数来自动设置特定目录及其子目录、孙目录下的 Git 用户配置。</p> +</blockquote> +<h2 id="开始之前"><a href="#开始之前" class="headerlink" title="开始之前"></a>开始之前</h2><p>假设你对git身份信息和ssh key概念有比较清晰的认识和了解其基础使用,比如git身份信息是用户名和邮箱,在你使用git commit的时候进行记录;而ssh key是一个凭证(分为公钥和私钥),用于远端身份信息验证,和git没有直接关系,也可以用于其他场景使用,比如ssh登录远程主机。</p> + + + + + + + + + + + + + + + + + + 使用Github Action自动化发布npm包 + + https://blog.imx0.com/2024/04/20/%E4%BD%BF%E7%94%A8Github%20Action%E8%87%AA%E5%8A%A8%E5%8C%96%E5%8F%91%E5%B8%83npm%E5%8C%85.html + 2024-04-20T08:32:00.000Z + 2024-04-20T08:32:00.000Z + + + <blockquote> +<p>GitHub Actions 提供了一种强大的自动化构建和发布流程的方式,通过组合github action的方式形成了workflow,来实现CI&#x2F;CD。我们完全可以将 npm 包的发布流程集成到 GitHub workflow中,做到自动化发包,这可以可以提高效率并减少人为错误。以下是如何使用 GitHub Actions 实现自动化发布的详细指南。</p> +</blockquote> +<h2 id="0-原理解释"><a href="#0-原理解释" class="headerlink" title="0. 原理解释"></a>0. 原理解释</h2><p>github actions是一个github推出的CI&#x2F;CD工具,可以模拟平台(比如linux等)自动化执行一些操作。<br>npm可以通过token实现免登陆发布包,并且Github Secrets能很好的保护token不被泄露(后面会说简单说一下为什么),这样就避免了在登陆信息在CI&#x2F;CD时泄露</p> + + + + + + + + + + + + + + + + + + + + 如何使用Github Actions实现自动化部署Hexo博客 + + https://blog.imx0.com/2024/04/19/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Github%20Actions%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2Hexo%E5%8D%9A%E5%AE%A2.html + 2024-04-19T06:01:02.000Z + 2024-04-19T06:01:02.000Z + + + <blockquote> +<p>Hexo 是一个基于 Node.js 的静态博客框架,它以简洁的速度和易用性受到许多开发者的青睐。如果你的博客部署在github上,那么这里有一种新的部署方式可能让摆脱频繁提交部署的苦恼!GitHub Actions 为自动化部署Hexo提供了一种解决方案,极大地简化了部署流程和节省了时间,可以让我们更多的专注到写作本身。</p> +</blockquote> +<h2 id="为什么使用Github-Actions"><a href="#为什么使用Github-Actions" class="headerlink" title="为什么使用Github Actions"></a>为什么使用Github Actions</h2><ul> +<li>将文章源文件和部署文件分离,有助于保护源文件中的隐私和你的草稿文件等</li> +<li>节省手动部署的时间,专注于写作本身</li> +<li>学习Github Actions相关知识,不折腾不作死发作了</li> +</ul> + + + + + + + + + + + + + + + + + + 使用conda快速初始化项目python + + https://blog.imx0.com/2024/03/07/using-conda-to-quickly-initialize-a-project-python.html + 2024-03-07T05:15:10.000Z + 2024-03-07T05:15:10.000Z + + + <h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><p>如果你想使用conda来管理你的Python环境和依赖,你需要在你的机器上安装Anaconda或Miniconda。Anaconda包含了conda,Python和150+科学包及其依赖。而Miniconda只包含了conda和Python。</p> +<p>当你安装好后Anaconda或Miniconda后,你就可以愉快的使用conda管理项目了</p> + + + + + + + + + + + + + + + + Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 + + https://blog.imx0.com/2021/09/14/Mac%E6%8F%90%E7%A4%BA%E2%80%9Dxxx.app%E5%B7%B2%E6%8D%9F%E5%9D%8F%EF%BC%8C%E6%97%A0%E6%B3%95%E6%89%93%E5%BC%80%EF%BC%8C%E4%BD%A0%E5%BA%94%E8%AF%A5%E5%B0%86%E5%AE%83%E7%A7%BB%E5%88%B0%E5%BA%9F%E7%BA%B8%E7%AF%93%E2%80%9D%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95.html + 2021-09-14T11:50:48.000Z + 2021-09-14T11:50:48.000Z + + + <p>随着mac系统的升级,对app安全控制越来越严,经常遇到一些非官方渠道安装的app,安装后无法打开的情况,如提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”,一般情况下,出现该问题是因为软件没有签名,所以被MAC系统禁止运行。可以尝试以下解决方案</p> +<h2 id="老系统:设置允许任何来源下载的App"><a href="#老系统:设置允许任何来源下载的App" class="headerlink" title="老系统:设置允许任何来源下载的App"></a>老系统:设置允许任何来源下载的App</h2><p>比较老的版本系统,可以按以下步骤操作:<br>打开”系统偏好设置 -&gt; 安全与隐私 -&gt; 通用”选项卡,检查是否已经启用了”任何来源”选项。如果没有启用,先点击左下角的小黄锁图标解锁,然后选中”任何来源”。(由于作者mac系统比较新,故此图来源网络)<br><img data-src="https://raw.githubusercontent.com/2ue/post-files/main/files/20240424125739-50d88ba9028afaa7fbbfd06d2ff6c107-1713934660.png" alt="image.png"></p> + + + + + + + + + + + + + + 使用Npm Token免登陆发包 + + https://blog.imx0.com/2019/10/12/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Npm%20Token%E5%85%8D%E7%99%BB%E9%99%86%E5%8F%91%E5%8C%85.html + 2019-10-12T06:01:24.000Z + 2024-04-13T09:51:31.000Z + + + <blockquote> +<p>在使用 npm 包管理器进行包的发布时,通常需要手动登录 npm 账户,并且我们在本地使用npm时因为某些不可抗原因,使用了非npm官方源,导致我们每次发布都需要切换到官方源,显得非常繁琐。现在,通过使用 npm Token,可以避免手动登录,切换源等步骤,实现自动化发布流程,特别是在持续集成&#x2F;持续部署(CI&#x2F;CD)环境中。</p> +</blockquote> +<h2 id="1-准备工作"><a href="#1-准备工作" class="headerlink" title="1. 准备工作"></a>1. 准备工作</h2><p>在开始之前,请确保:</p> + + + + + + + + + + + + Node.js版本神器之nvm + + https://blog.imx0.com/2018/07/21/node.js-version-management-artifact-nvm.html + 2018-07-21T11:47:23.000Z + 2018-07-21T11:47:23.000Z + + + <h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Node Version Manager (nvm) 是一个易于使用的工具,可以帮助您在不同Node.js版本之间进行切换,以满足不同项目的需求。</p> +<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2> + + + + + + + + + + + + + + + + + + + + Git系列常用命令之放弃修改 + + https://blog.imx0.com/2018/01/20/Git%E7%B3%BB%E5%88%97%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E4%B9%8B%E6%94%BE%E5%BC%83%E4%BF%AE%E6%94%B9.html + 2018-01-20T11:52:00.000Z + 2018-05-03T06:50:00.000Z + + + <blockquote> +<p>Git 是一种流行的分布式版本控制系统,它允许开发者在不同的分支上进行工作,同时保持代码的完整性和历史记录。在使用 Git 进行版本控制的过程中,我们可能会遇到需要放弃当前工作进度的情况,例如,当发现当前分支的修改与主分支的代码不兼容,或者需要切换到另一个分支继续工作时。</p> +</blockquote> +<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>这篇文章会介绍如何在 Git 中放弃对文件的修改,包括以下几个方面:</p> + + + + + + + + + + + + 风继续吹 + + https://blog.imx0.com/2017/11/29/blow-forever.html + 2017-11-29T14:53:37.000Z + 2017-11-29T14:53:37.000Z + + + <blockquote> +<p>最近在某些比较看重的事情上,处理的很失败,感觉整个心态都发生了变化。下班独行的那段路上,突然惊醒自己也许需要做出一些改变,晚饭后静静的思考了一阵:似乎勇往前行才是我最需需要的改变。古语有云:走自己的路,让别人去说吧。是的,生活需要你不断踽踽独行,活在他人的世界里是可怕的,特别他(她)还是陌生人。随手记录一些句子,它们是我最近的心态和生活的写照,也以此鞭策自己砥砺前行。</p> +</blockquote> +<hr> +<p>时间不会因你沮丧而停滞不前<br>生活不会因你懊恼而雨过天晴</p> + + + + + + + + + + + + 常用javascript代码片段 + + https://blog.imx0.com/2017/11/26/javascript-sinpats.html + 2017-11-26T14:37:45.000Z + 2017-11-26T14:37:45.000Z + + + <blockquote> +<p>下面记录的主要是本人在项目中遇到的一些问题的解决方案或者个人觉得精妙的代码,方案也许来自于自己的想法,也有可能来自于网络(我尽量给出出处,以便大家追源溯流),也许干脆就是一个插件库(实际在项目中对于一些小的功能个人是拒绝引入不纯粹的三方库的)。肯定,这其中有些方案也不是尽善尽美,我会一一列出。如果对于某个问题在你看来有更好的解决方案,请不吝指出!</p> +</blockquote> +<h2 id="placeholder属性支持"><a href="#placeholder属性支持" class="headerlink" title="placeholder属性支持"></a>placeholder属性支持</h2><p>有时候项目中placeholder也许要兼容某些特殊的浏览器,所以才有了下面这段代码。这种方案有一个缺点就是:如果你恰好要对这个输入框绑定focus事件并操作它的值,必须要小心处理</p> + + + + + + + + + + + + 一道小小的题目引发对javascript支持正则表达式相关方法的探讨 + + https://blog.imx0.com/2017/11/24/regex-to-something.html + 2017-11-24T01:57:10.000Z + 2017-11-24T01:57:10.000Z + + + <blockquote> +<p>以前对于正则是非常惧怕的,因为看不懂和学不会。但最近项目中频繁的使用到了正则,因此强迫自己去学习了解,慢慢的体会到了他的魅力与强大。当然学习正则初入门的时候有些枯燥难懂,但越学越觉得轻松。本文不准备说关于正则本身的事儿,而是说一说关于javascript中关于正则的几个方法中被很多人忽略的地方。</p> +</blockquote> +<h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>说到正则,很多人都是从抄到改到自己写,这个过程可能有时候很漫长。如一些工具能帮助你快速分析和学习正则,那么学习的过程你肯定要轻松得多。下面我推荐两个我经常使用的正则在线可视化工具,正则可视化工具图解符合铁路图规律(其实不明白什么是铁路一样很容易看懂,只是一些细微的地方和我们的常规思维有点差别)。</p> + + + + + + + + + + + + + + vue中慎用style的scoped属性 + + https://blog.imx0.com/2017/11/15/vue-style-scoped.html + 2017-11-15T11:09:16.000Z + 2017-11-15T11:09:16.000Z + + + <blockquote> +<p>在vue组件中,在style标签上添加scoped属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的,这是一个非常好的机制。但是为什么要慎用呢?在实际业务中我们往往会对公共组件样式做细微的调整,如果添加了scoped属性,那么样式将会变得不易修改。初写这篇文章时,本人没有找到一个好的方法去解决这个问题,后来经过大伙的解答,才让我恍然大悟。</p> +</blockquote> +<h2 id="何为谨慎使用"><a href="#何为谨慎使用" class="headerlink" title="何为谨慎使用"></a>何为谨慎使用</h2><p>谨慎使用不是不用,而是持一种审视的目光去看待它。<code>scoped</code>肯定是解决了样式私有化的问题,但同时也引入了新的问题—样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的。所以我才说要谨慎使用</p> + + + + + + + + + + + + + + 如何写一个日历组件 + + https://blog.imx0.com/2017/11/02/how-to-make-a-kalendar.html + 2017-11-02T02:08:43.000Z + 2017-11-02T02:08:43.000Z + + + <blockquote> +<p>众所周知,虽然<code>javascript</code>中关于时间的API有不少,我们可以通过方法单独的获取年、月、日、时、分、秒、毫秒…貌似很多,最近写了一个日历(以前写的,但写得很烂,最近优化一下),所以下面简单的记录一下如何写一个日历,列出了一些我在写日历过程中自己封装的一些方法</p> +</blockquote> +<h2 id="效果图"><a href="#效果图" class="headerlink" title="效果图"></a>效果图</h2><p>先来一张效果图,由于没有UI设计,所以就自己简单的设计了一个样式(好歹我也是设计专业的,虽然已不做设计很多年),虽然略丑,但重要的是功能!!!</p> + + + + + + + + + + + + + + 慎重用for...in与for...of + + https://blog.imx0.com/2017/10/27/for-in-and-for-of.html + 2017-10-27T06:43:12.000Z + 2017-10-27T06:43:12.000Z + + + <blockquote> +<p><code>for...in</code>和<code>for...of</code>都是用于数据的遍历。<code>for...in</code>是<code>ES5</code>标准,用于遍历对象属性(键),而<code>for...of</code>是<code>ES6</code>标准,是对<code>for...in</code>的修正,用于遍历对象元素(值),<code>for...of</code>兼容性不是很好(除了PC端老顽固<code>IE</code>之外,移动端某些安卓机和浏览器也是不支持它,具体可以<span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvemgtQ04vZG9jcy9XZWIvSmF2YVNjcmlwdC9SZWZlcmVuY2UvU3RhdGVtZW50cy9mb3IuLi5vZg==">查看MDN<i class="fa fa-external-link-alt"></i></span>)。</p> +</blockquote> +<h2 id="for…in"><a href="#for…in" class="headerlink" title="for…in"></a>for…in</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">objMethod</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;&#125;;</span><br><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">arrMethod</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">2</span>, <span class="number">9</span>, <span class="number">5</span>], obj = &#123; <span class="attr">name</span>: <span class="string">&#x27;2ue&#x27;</span>, <span class="attr">w</span>: <span class="number">130</span> &#125;;</span><br><span class="line">arr.<span class="property">msg</span> = <span class="string">&#x27;hello&#x27;</span>;</span><br><span class="line">obj.<span class="property">msg</span> = <span class="string">&#x27;hello&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i <span class="keyword">in</span> arr) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// &quot;0&quot;, &quot;1&quot;, &quot;2&quot;, &quot;msg&quot;, &quot;arrCustom&quot;, &quot;objCustom&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i <span class="keyword">in</span> arr) &#123;</span><br><span class="line"> <span class="keyword">if</span> (arr.<span class="title function_">hasOwnProperty</span>(i)) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// &quot;0&quot;, &quot;1&quot;, &quot;2&quot;, &quot;msg&quot;</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i <span class="keyword">in</span> obj) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// &quot;name&quot;, &quot;w&quot;, &quot;msg&quot;, &quot;objCustom&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i <span class="keyword">in</span> obj) &#123;</span><br><span class="line"> <span class="keyword">if</span> (obj.<span class="title function_">hasOwnProperty</span>(i)) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// &quot;name&quot;, &quot;w&quot;, &quot;msg&quot;</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure> + + + + + + + + + + + + + + + + H5的Notification特性 - Web的桌面通知功能 + + https://blog.imx0.com/2017/10/16/desktop-notification.html + 2017-10-16T02:16:37.000Z + 2017-10-16T02:16:37.000Z + + + <blockquote> +<p>目前,<code>web</code>网页使用桌面通知功能的越来越多,包括微博,腾讯视频等大厂站,桌面通知功能是<code>H5</code>的一个<code>API</code> - <code>Notifications</code>。它允许网页或应用程序可以发出通知,通知将被显示在页面之外的系统层面上(通常使用操作系统的标准通知机制,但是在不同的平台和浏览器上的表现会有差异),这样即使应用程序空闲或在后台也可以向用户发送信息。</p> +</blockquote> +<h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><p><code>Notifications</code>的诞生简化了网站或者应用与用户之间的沟通成本(时间成本和开发成本),增强用户黏性(减少了用户离开应用的可能)。传统的通知方式,大多是通过站内信(消息),邮件,短信等方式,它们通常需要刷新(跳转)页面、离开应用打开其他应用或终端来查看消息;而桌面通知功能大大的简化了这个过程,消息的传递基本不消耗时间(如果不设置<code>setTimeout</code>,用时基本不会超过<code>1s</code>),并且用户不需要离开应用,这都带来了极大的方便。可以预见,<code>Notifications</code>将会在很多网页或应用中被大量使用。当然<code>Notifications</code>也具有它的局限性:无法存档、即看即毁<br>那么,这个功能到底能用在哪些场景呢?只能说能应用的场景很多:</p> + + + + + + + + + + + + + + mocha+chai使用记录 + + https://blog.imx0.com/2017/10/10/mocha+chai.html + 2017-10-10T03:06:52.000Z + 2017-10-10T03:06:52.000Z + + + <blockquote> +<p><code>mocha+chai</code>是比较流行的测试框架‘套装’,能实现一些基本的前端测试如函数功能测试,模拟登陆,异步测试,流程测试等</p> +</blockquote> +<h2 id="官方文档"><a href="#官方文档" class="headerlink" title="官方文档"></a>官方文档</h2><h3 id="mocha"><a href="#mocha" class="headerlink" title="mocha"></a><span class="exturl" data-url="aHR0cDovL21vY2hhanMub3JnLw==">mocha<i class="fa fa-external-link-alt"></i></span></h3> + + + + + + + + + + + + + + 前端测试探索 + + https://blog.imx0.com/2017/10/10/fed-test.html + 2017-10-10T02:06:30.000Z + 2017-10-10T02:06:30.000Z + + + <blockquote> +<p>前端测试是保证代码质量以及程序稳定的一种可靠方式,同时也从代码层面解决了自测难,自测烦等前端测试综合症。当然在前端开发引入测试环节无疑会增加人力和时间成本,如果最终产生的结果和增加的成本能够两两抵消产生正面效应,那么有必要考虑把测试环节加入到团队的规划中。但是在实际开发过程中,测试用例更多的被用于开源项目中,在大部分公司的生成项目很少使用维护测试用例,其根本原因是不纯粹的生产环境导致维护测试用例变得困难,而目的性很强的开源项目则更纯粹,所以前端测试的发展任重而道远。</p> +</blockquote> +<p>Ps:本次更多的是讲述概念性的东西,代码性的具体实例不做过多实践</p> +<hr> + + + + + + + + + + + + + + node和npm版本管理器nvm的安装和使用 + + https://blog.imx0.com/2017/08/21/nvm-node-version-manager.html + 2017-08-21T06:02:36.000Z + 2017-08-21T06:02:36.000Z + + + <blockquote> +<p>nvm:一个node和npm的版本管理器(node&amp;npm version manager),能让你快速的在不同版本间切换。</p> +</blockquote> +<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>下载地址:<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2NvcmV5YnV0bGVyL252bS13aW5kb3dzL3JlbGVhc2Vz">官网下载<i class="fa fa-external-link-alt"></i></span><br>有两种版本<code>nvm-noinstall.zip</code>(便携版)和<code>nvm-setup.zip</code>(exe安装版)<br>两者唯一区别就是便携版需要手动配置全局变量,而安装版只需要在安装时选定安装目录则会自动配置好。</p> + + + + + + + + + + + + + + + + Git系列之常用命令(一) + + https://blog.imx0.com/2017/07/01/learn-git-2.html + 2017-07-01T05:13:01.000Z + 2017-07-01T05:13:01.000Z + + + <blockquote> +<p>虽然有了git的图形化工具,但个人还是喜欢命令行,并且结合alias功能,也能非常快速的完成很多功能。记录一些项目中常用的git命令,方便查阅</p> +</blockquote> +<h2 id="查看状态"><a href="#查看状态" class="headerlink" title="查看状态"></a>查看状态</h2><p>可以查看工作区(edit即add之前)和暂存区(add之后,commit之前)</p> + + + + + + + + + + + + Babun导致本地SSH-KEY不可用 + + https://blog.imx0.com/2017/06/15/babun-casue-ssh-key-bad.html + 2017-06-15T11:11:24.000Z + 2017-06-15T11:11:24.000Z + + + <blockquote> +<p>Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。</p> +</blockquote> +<p>ps: 前文有我记录的关于<code>Babun</code>的一些特点,以及使用,请看<span class="exturl" data-url="aHR0cHM6Ly8ydWUuZ2l0aHViLmlvLzIwMTcvMDMvMTUvQmFidW4v">windows平台下超强的cmd工具Babun使用笔记<i class="fa fa-external-link-alt"></i></span>一文</p> +<h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2> + + + + + + + + + + + + + + + + + diff --git a/baidusitemap.xml b/baidusitemap.xml new file mode 100644 index 00000000..2f003172 --- /dev/null +++ b/baidusitemap.xml @@ -0,0 +1,127 @@ + + + + https://blog.imx0.com/2024/04/23/Oh%20My%20Zsh%20%E8%87%AA%E5%8A%A8%E5%8C%96-%E8%BD%BB%E6%9D%BE%E7%AE%A1%E7%90%86%E5%A4%9A%E4%B8%AA%20Git%20%E7%94%A8%E6%88%B7%E8%BA%AB%E4%BB%BD%E4%BF%A1%E6%81%AF.html + 2024-04-23 + + + https://blog.imx0.com/2024/04/20/%E4%BD%BF%E7%94%A8Github%20Action%E8%87%AA%E5%8A%A8%E5%8C%96%E5%8F%91%E5%B8%83npm%E5%8C%85.html + 2024-04-20 + + + https://blog.imx0.com/2024/04/19/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Github%20Actions%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2Hexo%E5%8D%9A%E5%AE%A2.html + 2024-04-19 + + + https://blog.imx0.com/2019/10/12/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Npm%20Token%E5%85%8D%E7%99%BB%E9%99%86%E5%8F%91%E5%8C%85.html + 2024-04-13 + + + https://blog.imx0.com/2024/03/07/using-conda-to-quickly-initialize-a-project-python.html + 2024-03-07 + + + https://blog.imx0.com/2021/09/14/Mac%E6%8F%90%E7%A4%BA%E2%80%9Dxxx.app%E5%B7%B2%E6%8D%9F%E5%9D%8F%EF%BC%8C%E6%97%A0%E6%B3%95%E6%89%93%E5%BC%80%EF%BC%8C%E4%BD%A0%E5%BA%94%E8%AF%A5%E5%B0%86%E5%AE%83%E7%A7%BB%E5%88%B0%E5%BA%9F%E7%BA%B8%E7%AF%93%E2%80%9D%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95.html + 2021-09-14 + + + https://blog.imx0.com/2018/07/21/node.js-version-management-artifact-nvm.html + 2018-07-21 + + + https://blog.imx0.com/2018/01/20/Git%E7%B3%BB%E5%88%97%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E4%B9%8B%E6%94%BE%E5%BC%83%E4%BF%AE%E6%94%B9.html + 2018-05-03 + + + https://blog.imx0.com/2016/12/31/2016-to-2017.html + 2018-03-02 + + + https://blog.imx0.com/2017/11/29/blow-forever.html + 2017-11-29 + + + https://blog.imx0.com/2017/11/26/javascript-sinpats.html + 2017-11-26 + + + https://blog.imx0.com/2017/11/24/regex-to-something.html + 2017-11-24 + + + https://blog.imx0.com/2017/11/15/vue-style-scoped.html + 2017-11-15 + + + https://blog.imx0.com/2017/11/02/how-to-make-a-kalendar.html + 2017-11-02 + + + https://blog.imx0.com/2017/10/27/for-in-and-for-of.html + 2017-10-27 + + + https://blog.imx0.com/2017/10/16/desktop-notification.html + 2017-10-16 + + + https://blog.imx0.com/2017/10/10/mocha+chai.html + 2017-10-10 + + + https://blog.imx0.com/2017/10/10/fed-test.html + 2017-10-10 + + + https://blog.imx0.com/2017/08/21/nvm-node-version-manager.html + 2017-08-21 + + + https://blog.imx0.com/2017/07/01/learn-git-2.html + 2017-07-01 + + + https://blog.imx0.com/2017/06/15/babun-casue-ssh-key-bad.html + 2017-06-15 + + + https://blog.imx0.com/2017/03/31/javascript-type.html + 2017-03-31 + + + https://blog.imx0.com/2017/03/15/babun.html + 2017-03-15 + + + https://blog.imx0.com/2017/01/05/vue-datapicker.html + 2017-01-05 + + + https://blog.imx0.com/2016/11/22/Mobile-terminal-adapter.html + 2016-11-22 + + + https://blog.imx0.com/2016/09/24/use-gulp-and-webpack-to-bulid-resource.html + 2016-09-24 + + + https://blog.imx0.com/2016/09/05/regex.html + 2016-09-05 + + + https://blog.imx0.com/2016/08/11/javascript-array-method.html + 2016-08-11 + + + https://blog.imx0.com/2016/06/20/learn-git-1.html + 2016-06-20 + + + https://blog.imx0.com/2016/06/15/get-url-values.html + 2016-06-15 + + + https://blog.imx0.com/2016/06/14/animate-number.html + 2016-06-14 + + \ No newline at end of file diff --git a/categories/H5/index.html b/categories/H5/index.html new file mode 100644 index 00000000..0e6cdb6c --- /dev/null +++ b/categories/H5/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: H5 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

H5 + 分类 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/Javascript/index.html b/categories/Javascript/index.html new file mode 100644 index 00000000..f3160e78 --- /dev/null +++ b/categories/Javascript/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: Javascript | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

Javascript + 分类 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/git/index.html b/categories/git/index.html new file mode 100644 index 00000000..ebac7263 --- /dev/null +++ b/categories/git/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: git | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

git + 分类 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/git/oh-my-zsh/index.html b/categories/git/oh-my-zsh/index.html new file mode 100644 index 00000000..28ae7d2a --- /dev/null +++ b/categories/git/oh-my-zsh/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: oh-my-zsh | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

oh-my-zsh + 分类 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/github-actions/index.html b/categories/github-actions/index.html new file mode 100644 index 00000000..9c6ef6bf --- /dev/null +++ b/categories/github-actions/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: github/actions | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

github/actions + 分类 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/hexo/github/index.html b/categories/hexo/github/index.html new file mode 100644 index 00000000..92267dbb --- /dev/null +++ b/categories/hexo/github/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: github | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

github + 分类 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/hexo/index.html b/categories/hexo/index.html new file mode 100644 index 00000000..c7202efe --- /dev/null +++ b/categories/hexo/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: hexo | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

hexo + 分类 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 00000000..df44f80c --- /dev/null +++ b/categories/index.html @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +categories | 前端路上 + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ +

categories +

+ + + +
+ + + +
+ +
+ + + +
+ + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/javascript/index.html b/categories/javascript/index.html new file mode 100644 index 00000000..d3ad2a8e --- /dev/null +++ b/categories/javascript/index.html @@ -0,0 +1,491 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: javascript | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

javascript + 分类 +

+
+ + +
+ 2017 +
+ + + + + + +
+ 2016 +
+ + + + + + + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/mac/index.html b/categories/mac/index.html new file mode 100644 index 00000000..1058a4b9 --- /dev/null +++ b/categories/mac/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: mac | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

mac + 分类 +

+
+ + +
+ 2021 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/npm/index.html b/categories/npm/index.html new file mode 100644 index 00000000..735db3fd --- /dev/null +++ b/categories/npm/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: npm | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

npm + 分类 +

+
+ + +
+ 2019 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/python/Anaconda/index.html b/categories/python/Anaconda/index.html new file mode 100644 index 00000000..240937a7 --- /dev/null +++ b/categories/python/Anaconda/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: Anaconda | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

Anaconda + 分类 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/python/index.html b/categories/python/index.html new file mode 100644 index 00000000..865ae626 --- /dev/null +++ b/categories/python/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: python | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

python + 分类 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/record/index.html b/categories/record/index.html new file mode 100644 index 00000000..33a1a01e --- /dev/null +++ b/categories/record/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: record | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

record + 分类 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/regex/index.html b/categories/regex/index.html new file mode 100644 index 00000000..d9a9a07f --- /dev/null +++ b/categories/regex/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: regex | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

regex + 分类 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/test/index.html b/categories/test/index.html new file mode 100644 index 00000000..49b7bbf1 --- /dev/null +++ b/categories/test/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: test | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

test + 分类 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/tools/index.html b/categories/tools/index.html new file mode 100644 index 00000000..22a94cd6 --- /dev/null +++ b/categories/tools/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: tools | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

tools + 分类 +

+
+ + +
+ 2017 +
+ + + + + + + + +
+ 2016 +
+ + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/vue/index.html b/categories/vue/index.html new file mode 100644 index 00000000..8872b1c4 --- /dev/null +++ b/categories/vue/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: vue | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

vue + 分类 +

+
+ + +
+ 2017 +
+ + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\267\245\345\205\267-git/index.html" "b/categories/\345\267\245\345\205\267-git/index.html" new file mode 100644 index 00000000..23bc4202 --- /dev/null +++ "b/categories/\345\267\245\345\205\267-git/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: 工具/git | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

工具/git + 分类 +

+
+ + +
+ 2018 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\267\245\345\205\267/index.html" "b/categories/\345\267\245\345\205\267/index.html" new file mode 100644 index 00000000..165e1c35 --- /dev/null +++ "b/categories/\345\267\245\345\205\267/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: 工具 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

工具 + 分类 +

+
+ + +
+ 2018 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\345\267\245\345\205\267/nvm/index.html" "b/categories/\345\267\245\345\205\267/nvm/index.html" new file mode 100644 index 00000000..a4474823 --- /dev/null +++ "b/categories/\345\267\245\345\205\267/nvm/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: nvm | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

nvm + 分类 +

+
+ + +
+ 2018 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\346\265\213\350\257\225/index.html" "b/categories/\346\265\213\350\257\225/index.html" new file mode 100644 index 00000000..1243ecf4 --- /dev/null +++ "b/categories/\346\265\213\350\257\225/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: 测试 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

测试 + 分类 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/categories/\351\232\217\346\203\263/index.html" "b/categories/\351\232\217\346\203\263/index.html" new file mode 100644 index 00000000..6188a27a --- /dev/null +++ "b/categories/\351\232\217\346\203\263/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分类: 随想 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

随想 + 分类 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/css/main.css b/css/main.css new file mode 100644 index 00000000..dcd7eeac --- /dev/null +++ b/css/main.css @@ -0,0 +1,2976 @@ +:root { + --body-bg-color: #f5f7f9; + --content-bg-color: #fff; + --card-bg-color: #f5f5f5; + --text-color: #555; + --blockquote-color: #666; + --link-color: #555; + --link-hover-color: #222; + --brand-color: #fff; + --brand-hover-color: #fff; + --table-row-odd-bg-color: #f9f9f9; + --table-row-hover-bg-color: #f5f5f5; + --menu-item-bg-color: #f5f5f5; + --theme-color: #222; + --btn-default-bg: #fff; + --btn-default-color: #555; + --btn-default-border-color: #555; + --btn-default-hover-bg: #222; + --btn-default-hover-color: #fff; + --btn-default-hover-border-color: #222; + --highlight-background: #fff; + --highlight-foreground: #333; + --highlight-gutter-background: #eaeaea; + --highlight-gutter-foreground: #474747; + color-scheme: light; +} +@media (prefers-color-scheme: dark) { + :root { + --body-bg-color: #282828; + --content-bg-color: #333; + --card-bg-color: #555; + --text-color: #ccc; + --blockquote-color: #bbb; + --link-color: #ccc; + --link-hover-color: #eee; + --brand-color: #ddd; + --brand-hover-color: #ddd; + --table-row-odd-bg-color: #282828; + --table-row-hover-bg-color: #363636; + --menu-item-bg-color: #555; + --theme-color: #222; + --btn-default-bg: #222; + --btn-default-color: #ccc; + --btn-default-border-color: #555; + --btn-default-hover-bg: #666; + --btn-default-hover-color: #ccc; + --btn-default-hover-border-color: #666; + --highlight-background: #fff; + --highlight-foreground: #333; + --highlight-gutter-background: #eaeaea; + --highlight-gutter-foreground: #474747; + color-scheme: dark; + } + img { + opacity: 0.75; + } + img:hover { + opacity: 0.9; + } + iframe { + color-scheme: light; + } +} +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} +body { + margin: 0; +} +main { + display: block; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} +a { + background: transparent; +} +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} +b, +strong { + font-weight: bolder; +} +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +img { + border-style: none; +} +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} +button, +input { +/* 1 */ + overflow: visible; +} +button, +select { +/* 1 */ + text-transform: none; +} +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; +} +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + border-style: none; + padding: 0; +} +button:-moz-focusring, +[type='button']:-moz-focusring, +[type='reset']:-moz-focusring, +[type='submit']:-moz-focusring { + outline: 1px dotted ButtonText; +} +fieldset { + padding: 0.35em 0.75em 0.625em; +} +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} +progress { + vertical-align: baseline; +} +textarea { + overflow: auto; +} +[type='checkbox'], +[type='radio'] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} +[type='number']::-webkit-inner-spin-button, +[type='number']::-webkit-outer-spin-button { + height: auto; +} +[type='search'] { + outline-offset: -2px; /* 2 */ + -webkit-appearance: textfield; /* 1 */ +} +[type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-file-upload-button { + font: inherit; /* 2 */ + -webkit-appearance: button; /* 1 */ +} +details { + display: block; +} +summary { + display: list-item; +} +template { + display: none; +} +[hidden] { + display: none; +} +::selection { + background: #262a30; + color: #eee; +} +html, +body { + height: 100%; +} +body { + background: var(--body-bg-color); + box-sizing: border-box; + color: var(--text-color); + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-size: 1em; + line-height: 2; + min-height: 100%; + position: relative; + transition: padding 0.2s ease-in-out; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-weight: bold; + line-height: 1.5; + margin: 30px 0 15px; +} +h1 { + font-size: 1.5em; +} +h2 { + font-size: 1.375em; +} +h3 { + font-size: 1.25em; +} +h4 { + font-size: 1.125em; +} +h5 { + font-size: 1em; +} +h6 { + font-size: 0.875em; +} +p { + margin: 0 0 20px; +} +a { + border-bottom: 1px solid #999; + color: var(--link-color); + cursor: pointer; + outline: 0; + text-decoration: none; + overflow-wrap: break-word; +} +a:hover { + border-bottom-color: var(--link-hover-color); + color: var(--link-hover-color); +} +iframe, +img, +video, +embed { + display: block; + margin-left: auto; + margin-right: auto; + max-width: 100%; +} +hr { + background-image: repeating-linear-gradient(-45deg, #ddd, #ddd 4px, transparent 4px, transparent 8px); + border: 0; + height: 3px; + margin: 40px 0; +} +blockquote { + border-left: 4px solid #ddd; + color: var(--blockquote-color); + margin: 0; + padding: 0 15px; +} +blockquote cite::before { + content: '-'; + padding: 0 5px; +} +dt { + font-weight: bold; +} +dd { + margin: 0; + padding: 0; +} +.table-container { + overflow: auto; +} +table { + border-collapse: collapse; + border-spacing: 0; + font-size: 0.875em; + margin: 0 0 20px; + width: 100%; +} +tbody tr:nth-of-type(odd) { + background: var(--table-row-odd-bg-color); +} +tbody tr:hover { + background: var(--table-row-hover-bg-color); +} +caption, +th, +td { + padding: 8px; +} +th, +td { + border: 1px solid #ddd; + border-bottom: 3px solid #ddd; +} +th { + font-weight: 700; + padding-bottom: 10px; +} +td { + border-bottom-width: 1px; +} +.btn { + background: var(--btn-default-bg); + border: 2px solid var(--btn-default-border-color); + border-radius: 2px; + color: var(--btn-default-color); + display: inline-block; + font-size: 0.875em; + line-height: 2; + padding: 0 20px; + transition: background-color 0.2s ease-in-out; +} +.btn:hover { + background: var(--btn-default-hover-bg); + border-color: var(--btn-default-hover-border-color); + color: var(--btn-default-hover-color); +} +.btn + .btn { + margin: 0 0 8px 8px; +} +.btn .fa-fw { + text-align: left; + width: 1.285714285714286em; +} +.toggle { + line-height: 0; +} +.toggle .toggle-line { + background: #fff; + display: block; + height: 2px; + left: 0; + position: relative; + top: 0; + transition: all 0.4s; + width: 100%; +} +.toggle .toggle-line:first-child { + margin-top: 1px; +} +.toggle .toggle-line:not(:first-child) { + margin-top: 4px; +} +.toggle.toggle-arrow :first-child { + left: 50%; + top: 2px; + transform: rotate(45deg); + width: 50%; +} +.toggle.toggle-arrow :last-child { + left: 50%; + top: -2px; + transform: rotate(-45deg); + width: 50%; +} +.toggle.toggle-close :nth-child(2) { + opacity: 0; +} +.toggle.toggle-close :first-child { + top: 6px; + transform: rotate(45deg); +} +.toggle.toggle-close :last-child { + top: -6px; + transform: rotate(-45deg); +} +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em +} +code.hljs { + padding: 3px 5px +} +/*! + Theme: Github + Author: Defman21 + License: ~ MIT (or more permissive) [via base16-schemes-source] + Maintainer: @highlightjs/core-team + Version: 2021.09.0 +*/ +/* + WARNING: DO NOT EDIT THIS FILE DIRECTLY. + + This theme file was auto-generated from the Base16 scheme github + by the Highlight.js Base16 template builder. + + - https://github.com/highlightjs/base16-highlightjs +*/ +/* +base00 #ffffff Default Background +base01 #f5f5f5 Lighter Background (Used for status bars, line number and folding marks) +base02 #c8c8fa Selection Background +base03 #969896 Comments, Invisibles, Line Highlighting +base04 #e8e8e8 Dark Foreground (Used for status bars) +base05 #333333 Default Foreground, Caret, Delimiters, Operators +base06 #ffffff Light Foreground (Not often used) +base07 #ffffff Light Background (Not often used) +base08 #ed6a43 Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted +base09 #0086b3 Integers, Boolean, Constants, XML Attributes, Markup Link Url +base0A #795da3 Classes, Markup Bold, Search Text Background +base0B #183691 Strings, Inherited Class, Markup Code, Diff Inserted +base0C #183691 Support, Regular Expressions, Escape Characters, Markup Quotes +base0D #795da3 Functions, Methods, Attribute IDs, Headings +base0E #a71d5d Keywords, Storage, Selector, Markup Italic, Diff Changed +base0F #333333 Deprecated, Opening/Closing Embedded Language Tags, e.g. +*/ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em +} +code.hljs { + padding: 3px 5px +} +.hljs { + color: #333333; + background: #ffffff +} +.hljs::selection, +.hljs ::selection { + background-color: #c8c8fa; + color: #333333 +} +/* purposely do not highlight these things */ +.hljs-formula, +.hljs-params, +.hljs-property { + +} +/* base03 - #969896 - Comments, Invisibles, Line Highlighting */ +.hljs-comment { + color: #969896 +} +/* base04 - #e8e8e8 - Dark Foreground (Used for status bars) */ +.hljs-tag { + color: #e8e8e8 +} +/* base05 - #333333 - Default Foreground, Caret, Delimiters, Operators */ +.hljs-subst, +.hljs-punctuation, +.hljs-operator { + color: #333333 +} +.hljs-operator { + opacity: 0.7 +} +/* base08 - Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted */ +.hljs-bullet, +.hljs-variable, +.hljs-template-variable, +.hljs-selector-tag, +.hljs-name, +.hljs-deletion { + color: #ed6a43 +} +/* base09 - Integers, Boolean, Constants, XML Attributes, Markup Link Url */ +.hljs-symbol, +.hljs-number, +.hljs-link, +.hljs-attr, +.hljs-variable.constant_, +.hljs-literal { + color: #0086b3 +} +/* base0A - Classes, Markup Bold, Search Text Background */ +.hljs-title, +.hljs-class .hljs-title, +.hljs-title.class_ { + color: #795da3 +} +.hljs-strong { + font-weight: bold; + color: #795da3 +} +/* base0B - Strings, Inherited Class, Markup Code, Diff Inserted */ +.hljs-code, +.hljs-addition, +.hljs-title.class_.inherited__, +.hljs-string { + color: #183691 +} +/* base0C - Support, Regular Expressions, Escape Characters, Markup Quotes */ +/* guessing */ +.hljs-built_in, +.hljs-doctag, +.hljs-quote, +.hljs-keyword.hljs-atrule, +.hljs-regexp { + color: #183691 +} +/* base0D - Functions, Methods, Attribute IDs, Headings */ +.hljs-function .hljs-title, +.hljs-attribute, +.ruby .hljs-property, +.hljs-title.function_, +.hljs-section { + color: #795da3 +} +/* base0E - Keywords, Storage, Selector, Markup Italic, Diff Changed */ +/* .hljs-selector-id, */ +/* .hljs-selector-class, */ +/* .hljs-selector-attr, */ +/* .hljs-selector-pseudo, */ +.hljs-type, +.hljs-template-tag, +.diff .hljs-meta, +.hljs-keyword { + color: #a71d5d +} +.hljs-emphasis { + color: #a71d5d; + font-style: italic +} +/* base0F - Deprecated, Opening/Closing Embedded Language Tags, e.g. */ +/* + prevent top level .keyword and .string scopes + from leaking into meta by accident +*/ +.hljs-meta, +.hljs-meta .hljs-keyword, +.hljs-meta .hljs-string { + color: #333333 +} +/* for v10 compatible themes */ +.hljs-meta .hljs-keyword, +.hljs-meta-keyword { + font-weight: bold +} +.highlight:hover .copy-btn, +.code-container:hover .copy-btn { + opacity: 1; +} +.code-container { + position: relative; +} +.copy-btn { + color: #333; + cursor: pointer; + line-height: 1.6; + opacity: 0; + padding: 2px 6px; + position: absolute; + transition: opacity 0.2s ease-in-out; + background-color: #eee; + background-image: linear-gradient(#fcfcfc, #eee); + border: 1px solid #d5d5d5; + border-radius: 3px; + font-size: 0.8125em; + right: 4px; + top: 8px; +} +code, +kbd, +figure.highlight, +pre { + background: var(--highlight-background); + color: var(--highlight-foreground); +} +figure.highlight, +pre { + line-height: 1.6; + margin: 0 auto 20px; +} +figure.highlight figcaption, +pre .caption, +pre figcaption { + background: var(--highlight-gutter-background); + color: var(--highlight-foreground); + display: flow-root; + font-size: 0.875em; + line-height: 1.2; + padding: 0.5em; +} +figure.highlight figcaption a, +pre .caption a, +pre figcaption a { + color: var(--highlight-foreground); + float: right; +} +figure.highlight figcaption a:hover, +pre .caption a:hover, +pre figcaption a:hover { + border-bottom-color: var(--highlight-foreground); +} +pre, +code { + font-family: consolas, Menlo, monospace, 'PingFang SC', 'Microsoft YaHei'; +} +code { + border-radius: 3px; + font-size: 0.875em; + padding: 2px 4px; + overflow-wrap: break-word; +} +kbd { + border: 2px solid #ccc; + border-radius: 0.2em; + box-shadow: 0.1em 0.1em 0.2em rgba(0,0,0,0.1); + font-family: inherit; + padding: 0.1em 0.3em; + white-space: nowrap; +} +figure.highlight { + overflow: auto; + position: relative; +} +figure.highlight pre { + border: 0; + margin: 0; + padding: 10px 0; +} +figure.highlight table { + border: 0; + margin: 0; + width: auto; +} +figure.highlight td { + border: 0; + padding: 0; +} +figure.highlight .gutter { + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +figure.highlight .gutter pre { + background: var(--highlight-gutter-background); + color: var(--highlight-gutter-foreground); + padding-left: 10px; + padding-right: 10px; + text-align: right; +} +figure.highlight .code pre { + padding-left: 10px; + width: 100%; +} +figure.highlight .marked { + background: rgba(0,0,0,0.3); +} +pre .caption, +pre figcaption { + margin-bottom: 10px; +} +.gist table { + width: auto; +} +.gist table td { + border: 0; +} +pre { + overflow: auto; + padding: 10px; +} +pre code { + background: none; + padding: 0; + text-shadow: none; +} +.blockquote-center { + border-left: 0; + margin: 40px 0; + padding: 0; + position: relative; + text-align: center; +} +.blockquote-center::before, +.blockquote-center::after { + left: 0; + line-height: 1; + opacity: 0.6; + position: absolute; + width: 100%; +} +.blockquote-center::before { + border-top: 1px solid #ccc; + text-align: left; + top: -20px; + content: '\f10d'; + font-family: 'Font Awesome 6 Free'; + font-weight: 900; +} +.blockquote-center::after { + border-bottom: 1px solid #ccc; + bottom: -20px; + text-align: right; + content: '\f10e'; + font-family: 'Font Awesome 6 Free'; + font-weight: 900; +} +.blockquote-center p, +.blockquote-center div { + text-align: center; +} +.group-picture { + margin-bottom: 20px; +} +.group-picture .group-picture-row { + display: flex; + gap: 3px; + margin-bottom: 3px; +} +.group-picture .group-picture-column { + flex: 1; +} +.group-picture .group-picture-column img { + height: 100%; + margin: 0; + object-fit: cover; + width: 100%; +} +.post-body .label { + color: #555; + padding: 0 2px; +} +.post-body .label.default { + background: #f0f0f0; +} +.post-body .label.primary { + background: #efe6f7; +} +.post-body .label.info { + background: #e5f2f8; +} +.post-body .label.success { + background: #e7f4e9; +} +.post-body .label.warning { + background: #fcf6e1; +} +.post-body .label.danger { + background: #fae8eb; +} +.post-body .link-grid { + display: grid; + grid-gap: 1.5rem; + gap: 1.5rem; + grid-template-columns: 1fr 1fr; + margin-bottom: 20px; + padding: 1rem; +} +@media (max-width: 767px) { + .post-body .link-grid { + grid-template-columns: 1fr; + } +} +.post-body .link-grid .link-grid-container { + border: solid #ddd; + box-shadow: 1rem 1rem 0.5rem rgba(0,0,0,0.5); + min-height: 5rem; + min-width: 0; + padding: 0.5rem; + position: relative; + transition: background 0.3s; +} +.post-body .link-grid .link-grid-container:hover { + animation: next-shake 0.5s; + background: var(--card-bg-color); +} +.post-body .link-grid .link-grid-container:active { + box-shadow: 0.5rem 0.5rem 0.25rem rgba(0,0,0,0.5); + transform: translate(0.2rem, 0.2rem); +} +.post-body .link-grid .link-grid-container .link-grid-image { + border: 1px solid #ddd; + border-radius: 50%; + box-sizing: border-box; + height: 5rem; + padding: 3px; + position: absolute; + width: 5rem; +} +.post-body .link-grid .link-grid-container p { + margin: 0 1rem 0 6rem; +} +.post-body .link-grid .link-grid-container p:first-of-type { + font-size: 1.2em; +} +.post-body .link-grid .link-grid-container p:last-of-type { + font-size: 0.8em; + line-height: 1.3rem; + opacity: 0.7; +} +.post-body .link-grid .link-grid-container a { + border: 0; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +@keyframes next-shake { + 0% { + transform: translate(1pt, 1pt) rotate(0deg); + } + 10% { + transform: translate(-1pt, -2pt) rotate(-1deg); + } + 20% { + transform: translate(-3pt, 0pt) rotate(1deg); + } + 30% { + transform: translate(3pt, 2pt) rotate(0deg); + } + 40% { + transform: translate(1pt, -1pt) rotate(1deg); + } + 50% { + transform: translate(-1pt, 2pt) rotate(-1deg); + } + 60% { + transform: translate(-3pt, 1pt) rotate(0deg); + } + 70% { + transform: translate(3pt, 1pt) rotate(-1deg); + } + 80% { + transform: translate(-1pt, -1pt) rotate(1deg); + } + 90% { + transform: translate(1pt, 2pt) rotate(0deg); + } + 100% { + transform: translate(1pt, -2pt) rotate(-1deg); + } +} +.mermaid { + margin-bottom: 20px; + text-align: center; +} +.wavedrom { + margin-bottom: 20px; + text-align: center; +} +.post-body .note { + border-radius: 3px; + margin-bottom: 20px; + padding: 1em; + position: relative; + border: 1px solid #eee; + border-left-width: 5px; +} +.post-body .note summary { + cursor: pointer; + outline: 0; +} +.post-body .note summary p { + display: inline; +} +.post-body .note h2, +.post-body .note h3, +.post-body .note h4, +.post-body .note h5, +.post-body .note h6 { + border-bottom: initial; + margin: 0; + padding-top: 0; +} +.post-body .note :first-child { + margin-top: 0; +} +.post-body .note :last-child { + margin-bottom: 0; +} +.post-body .note.default { + border-left-color: #777; +} +.post-body .note.default h2, +.post-body .note.default h3, +.post-body .note.default h4, +.post-body .note.default h5, +.post-body .note.default h6 { + color: #777; +} +.post-body .note.primary { + border-left-color: #6f42c1; +} +.post-body .note.primary h2, +.post-body .note.primary h3, +.post-body .note.primary h4, +.post-body .note.primary h5, +.post-body .note.primary h6 { + color: #6f42c1; +} +.post-body .note.info { + border-left-color: #428bca; +} +.post-body .note.info h2, +.post-body .note.info h3, +.post-body .note.info h4, +.post-body .note.info h5, +.post-body .note.info h6 { + color: #428bca; +} +.post-body .note.success { + border-left-color: #5cb85c; +} +.post-body .note.success h2, +.post-body .note.success h3, +.post-body .note.success h4, +.post-body .note.success h5, +.post-body .note.success h6 { + color: #5cb85c; +} +.post-body .note.warning { + border-left-color: #f0ad4e; +} +.post-body .note.warning h2, +.post-body .note.warning h3, +.post-body .note.warning h4, +.post-body .note.warning h5, +.post-body .note.warning h6 { + color: #f0ad4e; +} +.post-body .note.danger { + border-left-color: #d9534f; +} +.post-body .note.danger h2, +.post-body .note.danger h3, +.post-body .note.danger h4, +.post-body .note.danger h5, +.post-body .note.danger h6 { + color: #d9534f; +} +.post-body .tabs { + margin-bottom: 20px; +} +.post-body .tabs, +.tabs-comment { + padding-top: 10px; +} +.post-body .tabs ul.nav-tabs, +.tabs-comment ul.nav-tabs { + background: var(--content-bg-color); + display: flex; + display: flex; + flex-wrap: wrap; + justify-content: center; + margin: 0; + padding: 0; + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 5; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs, + .tabs-comment ul.nav-tabs { + display: block; + margin-bottom: 5px; + } +} +.post-body .tabs ul.nav-tabs li.tab, +.tabs-comment ul.nav-tabs li.tab { + border-bottom: 1px solid #ddd; + border-left: 1px solid transparent; + border-right: 1px solid transparent; + border-radius: 0 0 0 0; + border-top: 3px solid transparent; + flex-grow: 1; + list-style-type: none; + transition: all 0.2s ease-out; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab, + .tabs-comment ul.nav-tabs li.tab { + border-bottom: 1px solid transparent; + border-left: 3px solid transparent; + border-right: 1px solid transparent; + border-top: 1px solid transparent; + } +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab, + .tabs-comment ul.nav-tabs li.tab { + border-radius: 0; + } +} +.post-body .tabs ul.nav-tabs li.tab a, +.tabs-comment ul.nav-tabs li.tab a { + border-bottom: initial; + display: block; + line-height: 1.8; + padding: 0.25em 0.75em; + text-align: center; + transition: all 0.2s ease-out; +} +.post-body .tabs ul.nav-tabs li.tab a i[class^='fa'], +.tabs-comment ul.nav-tabs li.tab a i[class^='fa'] { + width: 1.285714285714286em; +} +.post-body .tabs ul.nav-tabs li.tab.active, +.tabs-comment ul.nav-tabs li.tab.active { + border-bottom-color: transparent; + border-left-color: #ddd; + border-right-color: #ddd; + border-top-color: #fc6423; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab.active, + .tabs-comment ul.nav-tabs li.tab.active { + border-bottom-color: #ddd; + border-left-color: #fc6423; + border-right-color: #ddd; + border-top-color: #ddd; + } +} +.post-body .tabs ul.nav-tabs li.tab.active a, +.tabs-comment ul.nav-tabs li.tab.active a { + cursor: default; +} +.post-body .tabs .tab-content, +.tabs-comment .tab-content { + border: 1px solid #ddd; + border-radius: 0 0 0 0; + border-top-color: transparent; +} +@media (max-width: 413px) { + .post-body .tabs .tab-content, + .tabs-comment .tab-content { + border-radius: 0; + border-top-color: #ddd; + } +} +.post-body .tabs .tab-content .tab-pane, +.tabs-comment .tab-content .tab-pane { + padding: 20px 20px 0; +} +.post-body .tabs .tab-content .tab-pane:not(.active), +.tabs-comment .tab-content .tab-pane:not(.active) { + display: none; +} +.pagination .prev, +.pagination .next, +.pagination .page-number, +.pagination .space { + display: inline-block; + margin: -1px 10px 0; + padding: 0 10px; +} +@media (max-width: 767px) { + .pagination .prev, + .pagination .next, + .pagination .page-number, + .pagination .space { + margin: 0 5px; + } +} +.pagination .page-number.current { + background: #ccc; + border-color: #ccc; + color: var(--content-bg-color); +} +.pagination { + border-top: 1px solid #eee; + margin: 120px 0 0; + text-align: center; +} +.pagination .prev, +.pagination .next, +.pagination .page-number { + border-bottom: 0; + border-top: 1px solid #eee; + transition: border-color 0.2s ease-in-out; +} +.pagination .prev:hover, +.pagination .next:hover, +.pagination .page-number:hover { + border-top-color: var(--link-hover-color); +} +@media (max-width: 767px) { + .pagination { + border-top: 0; + } + .pagination .prev, + .pagination .next, + .pagination .page-number { + border-bottom: 1px solid #eee; + border-top: 0; + } + .pagination .prev:hover, + .pagination .next:hover, + .pagination .page-number:hover { + border-bottom-color: var(--link-hover-color); + } +} +.pagination .space { + margin: 0; + padding: 0; +} +.comments { + margin-top: 60px; + overflow: hidden; +} +.comment-button-group { + display: flex; + display: flex; + flex-wrap: wrap; + justify-content: center; + justify-content: center; + margin: 1em 0; +} +.comment-button-group .comment-button { + margin: 0.1em 0.2em; +} +.comment-button-group .comment-button.active { + background: var(--btn-default-hover-bg); + border-color: var(--btn-default-hover-border-color); + color: var(--btn-default-hover-color); +} +.comment-position { + display: none; +} +.comment-position.active { + display: block; +} +.tabs-comment { + margin-top: 4em; + padding-top: 0; +} +.tabs-comment .comments { + margin-top: 0; + padding-top: 0; +} +.headband { + background: var(--theme-color); + height: 3px; +} +@media (max-width: 991px) { + .headband { + display: none; + } +} +.site-brand-container { + display: flex; + flex-shrink: 0; + padding: 0 10px; +} +.use-motion .column, +.use-motion .site-brand-container .toggle { + opacity: 0; +} +.site-meta { + flex-grow: 1; + text-align: center; +} +@media (max-width: 767px) { + .site-meta { + text-align: center; + } +} +.custom-logo-image { + margin-top: 20px; +} +@media (max-width: 991px) { + .custom-logo-image { + display: none; + } +} +.brand { + border-bottom: 0; + color: var(--brand-color); + display: inline-block; + padding: 0; +} +.brand:hover { + color: var(--brand-hover-color); +} +.site-title { + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-size: 1.375em; + font-weight: normal; + line-height: 1.5; + margin: 0; +} +.site-subtitle { + color: #ddd; + font-size: 0.8125em; + margin: 10px 10px 0; +} +.use-motion .site-title, +.use-motion .site-subtitle, +.use-motion .custom-logo-image { + opacity: 0; + position: relative; + top: -10px; +} +.site-nav-toggle, +.site-nav-right { + display: none; +} +@media (max-width: 767px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; + } +} +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: var(--text-color); + padding: 10px; + width: 22px; +} +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: var(--text-color); + border-radius: 1px; +} +@media (max-width: 767px) { + .site-nav { + --scroll-height: 0; + height: 0; + overflow: hidden; + transition: 0.2s ease-in-out; + transition-property: height, visibility; + visibility: hidden; + } + body:not(.site-nav-on) .site-nav .animated { + animation: none; + } + body.site-nav-on .site-nav { + height: var(--scroll-height); + visibility: unset; + } +} +.menu { + margin: 0; + padding: 1em 0; + text-align: center; +} +.menu-item { + display: inline-block; + list-style: none; + margin: 0 10px; +} +@media (max-width: 767px) { + .menu-item { + display: block; + margin-top: 10px; + } + .menu-item.menu-item-search { + display: none; + } +} +.menu-item a { + border-bottom: 0; + display: block; + font-size: 0.8125em; + transition: border-color 0.2s ease-in-out; +} +.menu-item a:hover, +.menu-item a.menu-item-active { + background: var(--menu-item-bg-color); +} +.menu-item i[class^='fa'] { + margin-right: 8px; +} +.menu-item .badge { + display: inline-block; + font-weight: bold; + line-height: 1; + margin-left: 0.35em; + margin-top: 0.35em; + text-align: center; + white-space: nowrap; +} +@media (max-width: 767px) { + .menu-item .badge { + float: right; + margin-left: 0; + } +} +.use-motion .menu-item { + visibility: hidden; +} +.sidebar-inner { + color: #999; + padding: 18px 10px; + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; +} +.cc-license .cc-opacity { + border-bottom: 0; + opacity: 0.7; +} +.cc-license .cc-opacity:hover { + opacity: 0.9; +} +.cc-license img { + display: inline-block; +} +.site-author-image { + border: 1px solid #eee; + max-width: 120px; + padding: 2px; +} +.site-author-name { + color: var(--text-color); + font-weight: 600; + margin: 0; +} +.site-description { + color: #999; + font-size: 0.8125em; + margin-top: 0; +} +.links-of-author a { + font-size: 0.8125em; +} +.sidebar .sidebar-button:not(:first-child) { + margin-top: 15px; +} +.sidebar .sidebar-button button { + background: transparent; + color: #fc6423; + cursor: pointer; + line-height: 2; + padding: 0 15px; + border: 1px solid #fc6423; + border-radius: 4px; +} +.sidebar .sidebar-button button:hover { + background: #fc6423; + color: #fff; +} +.sidebar .sidebar-button button i[class^='fa'] { + margin-right: 5px; +} +.links-of-blogroll { + font-size: 0.8125em; +} +.links-of-blogroll-title { + font-size: 0.875em; + font-weight: 600; +} +.links-of-blogroll-list { + list-style: none; + margin: 0; + padding: 0; +} +.sidebar-nav { + font-size: 0.875em; + height: 0; + margin: 0; + overflow: hidden; + padding-left: 0; + pointer-events: none; + transition: 0.2s ease-in-out; + transition-property: height, visibility; + visibility: hidden; +} +.sidebar-nav-active .sidebar-nav { + height: calc(2em + 1px); + pointer-events: unset; + visibility: unset; +} +.sidebar-nav li { + border-bottom: 1px solid transparent; + color: var(--text-color); + cursor: pointer; + display: inline-block; + transition: 0.2s ease-in-out; + transition-property: border-bottom-color, color; +} +.sidebar-nav li.sidebar-nav-overview { + margin-left: 10px; +} +.sidebar-nav li:hover { + color: #fc6423; +} +.sidebar-toc-active .sidebar-nav-toc, +.sidebar-overview-active .sidebar-nav-overview { + border-bottom-color: #fc6423; + color: #fc6423; + transition-delay: 0.2s; +} +.sidebar-toc-active .sidebar-nav-toc:hover, +.sidebar-overview-active .sidebar-nav-overview:hover { + color: #fc6423; +} +.sidebar-panel-container { + align-items: start; + display: grid; + flex: 1; + overflow-x: hidden; + overflow-y: auto; + padding-top: 0; + transition: padding-top 0.2s ease-in-out; +} +.sidebar-nav-active .sidebar-panel-container { + padding-top: 20px; +} +.sidebar-panel { + animation: deactivate-sidebar-panel 0.2s ease-in-out; + grid-area: 1/1; + height: 0; + opacity: 0; + overflow: hidden; + pointer-events: none; + transform: translateY(0); + transition: 0.2s ease-in-out; + transition-delay: 0s; + transition-property: opacity, transform, visibility; + visibility: hidden; +} +.sidebar-nav-active .sidebar-panel, +.sidebar-overview-active .sidebar-panel.post-toc-wrap { + transform: translateY(-20px); +} +.sidebar-overview-active:not(.sidebar-nav-active) .sidebar-panel.post-toc-wrap { + transition-delay: 0s, 0.2s, 0s; +} +.sidebar-overview-active .sidebar-panel.site-overview-wrap, +.sidebar-toc-active .sidebar-panel.post-toc-wrap { + animation-name: activate-sidebar-panel; + height: auto; + opacity: 1; + pointer-events: unset; + transform: translateY(0); + transition-delay: 0.2s, 0.2s, 0s; + visibility: unset; +} +.sidebar-panel.site-overview-wrap { + display: flex; + flex-direction: column; + justify-content: center; + gap: 10px; + justify-content: flex-start; +} +@keyframes deactivate-sidebar-panel { + from { + height: var(--inactive-panel-height, 0); + } + to { + height: var(--active-panel-height, 0); + } +} +@keyframes activate-sidebar-panel { + from { + height: var(--inactive-panel-height, auto); + } + to { + height: var(--active-panel-height, auto); + } +} +.sidebar-toggle { + bottom: 61px; + height: 16px; + padding: 5px; + width: 16px; + background: #222; + cursor: pointer; + opacity: 0.6; + position: fixed; + z-index: 30; + right: 30px; +} +@media (max-width: 991px) { + .sidebar-toggle { + right: 20px; + } +} +.sidebar-toggle:hover { + opacity: 0.8; +} +@media (max-width: 991px) { + .sidebar-toggle { + opacity: 0.8; + } +} +.sidebar-toggle:hover .toggle-line { + background: #fc6423; +} +@media (any-hover: hover) { + body:not(.sidebar-active) .sidebar-toggle:hover :first-child { + left: 50%; + top: 2px; + transform: rotate(45deg); + width: 50%; + } + body:not(.sidebar-active) .sidebar-toggle:hover :last-child { + left: 50%; + top: -2px; + transform: rotate(-45deg); + width: 50%; + } +} +.sidebar-active .sidebar-toggle :nth-child(2) { + opacity: 0; +} +.sidebar-active .sidebar-toggle :first-child { + top: 6px; + transform: rotate(45deg); +} +.sidebar-active .sidebar-toggle :last-child { + top: -6px; + transform: rotate(-45deg); +} +.post-toc { + font-size: 0.875em; +} +.post-toc ol { + list-style: none; + margin: 0; + padding: 0 2px 0 10px; + text-align: left; +} +.post-toc ol > :last-child { + margin-bottom: 5px; +} +.post-toc ol > ol { + padding-left: 0; +} +.post-toc ol a { + transition: all 0.2s ease-in-out; +} +.post-toc .nav-item { + line-height: 1.8; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.post-toc .nav .nav-child { + --height: 0; + height: 0; + opacity: 0; + overflow: hidden; + transition-property: height, opacity, visibility; + transition: 0.2s ease-in-out; + visibility: hidden; +} +.post-toc .nav .active > .nav-child { + height: var(--height, auto); + opacity: 1; + visibility: unset; +} +.post-toc .nav .active > a { + border-bottom-color: #fc6423; + color: #fc6423; +} +.post-toc .nav .active-current > a { + color: #fc6423; +} +.post-toc .nav .active-current > a:hover { + color: #fc6423; +} +.site-state { + display: flex; + flex-wrap: wrap; + justify-content: center; + line-height: 1.4; +} +.site-state-item { + padding: 0 15px; +} +.site-state-item a { + border-bottom: 0; + display: block; +} +.site-state-item-count { + display: block; + font-size: 1em; + font-weight: 600; +} +.site-state-item-name { + color: #999; + font-size: 0.8125em; +} +.sidebar-post-related { + font-size: 0.8125em; + padding: 18px 0 0 0; +} +.popular-posts { + margin: 0; + padding: 1em 0; + text-align: left; +} +.popular-posts .popular-posts-item { + display: block; +} +.popular-posts .popular-posts-item .popular-posts-link { + border-bottom: 0; + display: block; + padding: 5px 20px; + transition: background 0.2s ease-in-out; +} +.popular-posts .popular-posts-item .popular-posts-link:hover { + background: var(--menu-item-bg-color); +} +.popular-posts .popular-posts-item .popular-posts-time { + color: #999; +} +.footer { + color: #999; + font-size: 0.875em; + padding: 20px 0; + transition: 0.2s ease-in-out; + transition-property: left, right; +} +.footer.footer-fixed { + bottom: 0; + left: 0; + position: absolute; + right: 0; +} +.footer-inner { + box-sizing: border-box; + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + margin: 0 auto; + width: calc(100% - 20px); +} +@media (max-width: 767px) { + .footer-inner { + width: auto; + } +} +@media (min-width: 1200px) { + .footer-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .footer-inner { + width: 73%; + } +} +.use-motion .footer { + opacity: 0; +} +.languages { + display: inline-block; + font-size: 1.125em; + position: relative; +} +.languages .lang-select-label span { + margin: 0 0.5em; +} +.languages .lang-select { + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + width: 100%; +} +.with-love { + color: #f00; + display: inline-block; + margin: 0 5px; +} +.busuanzi-count #busuanzi_container_site_uv { + display: none; +} +.busuanzi-count #busuanzi_container_site_pv { + display: none; +} +@keyframes icon-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@media (max-width: 567px) { + .main-inner { + padding: initial !important; + } + .posts-expand .post-header { + margin-bottom: 10px !important; + } + .post-block { + margin-top: initial !important; + padding: 8px 18px 8px !important; + } + .post-body h1, + .post-body h2, + .post-body h3, + .post-body h4, + .post-body h5, + .post-body h6 { + margin: 20px 0 8px; + } + .post-body .note h1, + .post-body .tabs .tab-content .tab-pane h1, + .post-body .note h2, + .post-body .tabs .tab-content .tab-pane h2, + .post-body .note h3, + .post-body .tabs .tab-content .tab-pane h3, + .post-body .note h4, + .post-body .tabs .tab-content .tab-pane h4, + .post-body .note h5, + .post-body .tabs .tab-content .tab-pane h5, + .post-body .note h6, + .post-body .tabs .tab-content .tab-pane h6 { + margin: 0 5px; + } + .post-body > p { + margin: 0 0 10px; + } + .post-body .note > p, + .post-body .tabs .tab-content .tab-pane > p { + padding: 0 5px; + } + .post-body img, + .post-body video { + margin-bottom: 10px !important; + } + .post-body img + figcaption, + .post-body .fancybox + figcaption { + margin: -5px auto 15px !important; + } + .post-body .note { + margin-bottom: 10px !important; + padding: 10px !important; + } + .post-body .tabs .tab-content .tab-pane { + padding: 10px 10px 0 !important; + } + .post-eof { + margin: 40px auto 20px !important; + } + .pagination { + margin-top: 40px; + } +} +.back-to-top { + font-size: 12px; + align-items: center; + bottom: -100px; + color: #fff; + display: flex; + height: 26px; + transition: bottom 0.2s ease-in-out; + background: #222; + cursor: pointer; + opacity: 0.6; + position: fixed; + z-index: 30; + right: 30px; +} +.back-to-top span { + margin-right: 8px; +} +.back-to-top .fa { + text-align: center; + width: 26px; +} +@media (max-width: 991px) { + .back-to-top { + right: 20px; + } +} +.back-to-top:hover { + opacity: 0.8; +} +@media (max-width: 991px) { + .back-to-top { + opacity: 0.8; + } +} +.back-to-top:hover { + color: #fc6423; +} +.back-to-top.back-to-top-on { + bottom: 30px; +} +.reading-progress-bar { + --progress: 0; + background: #333; + height: 3px; + position: fixed; + z-index: 50; + width: var(--progress); + left: 0; + top: 0; +} +.rtl.post-body p, +.rtl.post-body a, +.rtl.post-body h1, +.rtl.post-body h2, +.rtl.post-body h3, +.rtl.post-body h4, +.rtl.post-body h5, +.rtl.post-body h6, +.rtl.post-body li, +.rtl.post-body ul, +.rtl.post-body ol { + direction: rtl; + font-family: UKIJ Ekran; +} +.rtl.post-title { + font-family: UKIJ Ekran; +} +.post-button { + margin-top: 40px; + text-align: center; +} +.use-motion .post-block, +.use-motion .pagination, +.use-motion .comments { + visibility: hidden; +} +.use-motion .post-header { + visibility: hidden; +} +.use-motion .post-body { + visibility: hidden; +} +.use-motion .collection-header { + visibility: hidden; +} +.posts-collapse .post-content { + margin-bottom: 35px; + margin-left: 35px; + position: relative; +} +@media (max-width: 767px) { + .posts-collapse .post-content { + margin-left: 0; + margin-right: 0; + } +} +.posts-collapse .post-content .collection-title { + font-size: 1.125em; + position: relative; +} +.posts-collapse .post-content .collection-title::before { + background: #999; + border: 1px solid #fff; + margin-left: -6px; + margin-top: -4px; + position: absolute; + top: 50%; + border-radius: 50%; + content: ' '; + height: 10px; + width: 10px; +} +.posts-collapse .post-content .collection-year { + font-size: 1.5em; + font-weight: bold; + margin: 60px 0; + position: relative; +} +.posts-collapse .post-content .collection-year::before { + background: #bbb; + margin-left: -4px; + margin-top: -4px; + position: absolute; + top: 50%; + border-radius: 50%; + content: ' '; + height: 8px; + width: 8px; +} +.posts-collapse .post-content .collection-header { + display: block; + margin-left: 20px; +} +.posts-collapse .post-content .collection-header small { + color: #bbb; + margin-left: 5px; +} +.posts-collapse .post-content .post-header { + border-bottom: 1px dashed #ccc; + margin: 30px 2px 0; + padding-left: 15px; + position: relative; + transition: border 0.2s ease-in-out; +} +.posts-collapse .post-content .post-header::before { + background: #bbb; + border: 1px solid #fff; + left: -6px; + position: absolute; + top: 0.75em; + transition: background 0.2s ease-in-out; + border-radius: 50%; + content: ' '; + height: 6px; + width: 6px; +} +.posts-collapse .post-content .post-header:hover { + border-bottom-color: #666; +} +.posts-collapse .post-content .post-header:hover::before { + background: #222; +} +.posts-collapse .post-content .post-meta-container { + display: inline; + font-size: 0.75em; + margin-right: 10px; +} +.posts-collapse .post-content .post-title { + display: inline; +} +.posts-collapse .post-content .post-title a { + border-bottom: 0; + color: var(--link-color); +} +.posts-collapse .post-content .post-title .fa { + font-size: 0.875em; + margin-left: 5px; +} +.posts-collapse .post-content::before { + background: #f5f5f5; + content: ' '; + height: 100%; + margin-left: -2px; + position: absolute; + top: 1.25em; + width: 4px; +} +.post-body { + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + overflow-wrap: break-word; +} +@media (min-width: 1200px) { + .post-body { + font-size: 1.125em; + } +} +@media (min-width: 992px) { + .post-body { + text-align: justify; + } +} +@media (max-width: 991px) { + .post-body { + text-align: justify; + } +} +.post-body h1 .header-anchor, +.post-body h2 .header-anchor, +.post-body h3 .header-anchor, +.post-body h4 .header-anchor, +.post-body h5 .header-anchor, +.post-body h6 .header-anchor, +.post-body h1 .headerlink, +.post-body h2 .headerlink, +.post-body h3 .headerlink, +.post-body h4 .headerlink, +.post-body h5 .headerlink, +.post-body h6 .headerlink { + border-bottom-style: none; + color: inherit; + float: right; + font-size: 0.875em; + margin-left: 10px; + opacity: 0; +} +.post-body h1 .header-anchor::before, +.post-body h2 .header-anchor::before, +.post-body h3 .header-anchor::before, +.post-body h4 .header-anchor::before, +.post-body h5 .header-anchor::before, +.post-body h6 .header-anchor::before, +.post-body h1 .headerlink::before, +.post-body h2 .headerlink::before, +.post-body h3 .headerlink::before, +.post-body h4 .headerlink::before, +.post-body h5 .headerlink::before, +.post-body h6 .headerlink::before { + content: '\f0c1'; + font-family: 'Font Awesome 6 Free'; + font-weight: 900; +} +.post-body h1:hover .header-anchor, +.post-body h2:hover .header-anchor, +.post-body h3:hover .header-anchor, +.post-body h4:hover .header-anchor, +.post-body h5:hover .header-anchor, +.post-body h6:hover .header-anchor, +.post-body h1:hover .headerlink, +.post-body h2:hover .headerlink, +.post-body h3:hover .headerlink, +.post-body h4:hover .headerlink, +.post-body h5:hover .headerlink, +.post-body h6:hover .headerlink { + opacity: 0.5; +} +.post-body h1:hover .header-anchor:hover, +.post-body h2:hover .header-anchor:hover, +.post-body h3:hover .header-anchor:hover, +.post-body h4:hover .header-anchor:hover, +.post-body h5:hover .header-anchor:hover, +.post-body h6:hover .header-anchor:hover, +.post-body h1:hover .headerlink:hover, +.post-body h2:hover .headerlink:hover, +.post-body h3:hover .headerlink:hover, +.post-body h4:hover .headerlink:hover, +.post-body h5:hover .headerlink:hover, +.post-body h6:hover .headerlink:hover { + opacity: 1; +} +.post-body .exturl .fa { + font-size: 0.875em; + margin-left: 4px; +} +.post-body img + figcaption, +.post-body .fancybox + figcaption { + color: #999; + font-size: 0.875em; + font-weight: bold; + line-height: 1; + margin: -15px auto 15px; + text-align: center; +} +.post-body iframe, +.post-body img, +.post-body video, +.post-body embed { + margin-bottom: 20px; +} +.post-body .video-container { + height: 0; + margin-bottom: 20px; + overflow: hidden; + padding-top: 75%; + position: relative; + width: 100%; +} +.post-body .video-container iframe, +.post-body .video-container object, +.post-body .video-container embed { + height: 100%; + left: 0; + margin: 0; + position: absolute; + top: 0; + width: 100%; +} +.post-gallery { + display: flex; + min-height: 200px; +} +.post-gallery .post-gallery-image { + flex: 1; +} +.post-gallery .post-gallery-image:not(:first-child) { + clip-path: polygon(40px 0, 100% 0, 100% 100%, 0 100%); + margin-left: -20px; +} +.post-gallery .post-gallery-image:not(:last-child) { + margin-right: -20px; +} +.post-gallery .post-gallery-image img { + height: 100%; + object-fit: cover; + opacity: 1; + width: 100%; +} +.posts-expand .post-gallery { + margin-bottom: 60px; +} +.posts-collapse .post-gallery { + margin: 15px 0; +} +.posts-expand .post-header { + font-size: 1.125em; + margin-bottom: 60px; + text-align: center; +} +.posts-expand .post-title { + font-size: 1.5em; + font-weight: normal; + margin: initial; + overflow-wrap: break-word; +} +.posts-expand .post-title-link { + border-bottom: 0; + color: var(--link-color); + display: inline-block; + position: relative; +} +.posts-expand .post-title-link::before { + background: var(--link-color); + bottom: 0; + content: ''; + height: 2px; + left: 0; + position: absolute; + transform: scaleX(0); + transition: transform 0.2s ease-in-out; + width: 100%; +} +.posts-expand .post-title-link:hover::before { + transform: scaleX(1); +} +.posts-expand .post-title-link .fa { + font-size: 0.875em; + margin-left: 5px; +} +.post-sticky-flag { + display: inline-block; + margin-right: 8px; + transform: rotate(30deg); +} +.posts-expand .post-meta-container { + color: #999; + font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-size: 0.75em; + margin-top: 3px; +} +.posts-expand .post-meta-container .post-description { + font-size: 0.875em; + margin-top: 2px; +} +.posts-expand .post-meta-container time { + border-bottom: 1px dashed #999; +} +.post-meta { + display: flex; + flex-wrap: wrap; + justify-content: center; +} +:not(.post-meta-break) + .post-meta-item::before { + content: '|'; + margin: 0 0.5em; +} +.post-meta-item-icon { + margin-right: 3px; +} +@media (max-width: 991px) { + .post-meta-item-text { + display: none; + } +} +.post-meta-break { + flex-basis: 100%; + height: 0; +} +#busuanzi_container_page_pv { + display: none; +} +.post-nav { + border-top: 1px solid #eee; + display: flex; + gap: 30px; + justify-content: space-between; + margin-top: 1em; + padding: 10px 5px 0; +} +.post-nav-item { + flex: 1; +} +.post-nav-item a { + border-bottom: 0; + display: block; + font-size: 0.875em; + line-height: 1.6; +} +.post-nav-item a:active { + top: 2px; +} +.post-nav-item .fa { + font-size: 0.75em; +} +.post-nav-item:first-child .fa { + margin-right: 5px; +} +.post-nav-item:last-child { + text-align: right; +} +.post-nav-item:last-child .fa { + margin-left: 5px; +} +.post-footer { + display: flex; + flex-direction: column; + justify-content: center; +} +.post-eof { + background: #ccc; + height: 1px; + margin: 80px auto 60px; + width: 8%; +} +.post-block:last-of-type .post-eof { + display: none; +} +.post-copyright ul { + list-style: none; + overflow: hidden; + padding: 0.5em 1em; + position: relative; + background: var(--card-bg-color); + border-left: 3px solid #ff2a2a; + margin: 1em 0 0; +} +.post-copyright ul::after { + content: '\f25e'; + font-family: 'Font Awesome 6 Brands'; + font-size: 200px; + opacity: 0.1; + position: absolute; + right: -50px; + top: -150px; +} +.post-tags { + margin-top: 40px; + text-align: center; +} +.post-tags a { + display: inline-block; + font-size: 0.8125em; +} +.post-tags a:not(:last-child) { + margin-right: 10px; +} +.social-like { + border-top: 1px solid #eee; + font-size: 0.875em; + margin-top: 1em; + padding-top: 1em; + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.social-like a { + border-bottom: none; +} +.reward-container { + margin: 1em 0 0; + padding: 1em 0; + text-align: center; +} +.reward-container button { + background: transparent; + color: #fc6423; + cursor: pointer; + line-height: 2; + padding: 0 15px; + border: 2px solid #fc6423; + border-radius: 2px; + outline: 0; + transition: all 0.2s ease-in-out; + vertical-align: text-top; +} +.reward-container button:hover { + background: #fc6423; + color: #fff; +} +.post-reward { + display: none; + padding-top: 20px; +} +.post-reward.active { + display: block; +} +.post-reward div { + display: inline-block; +} +.post-reward div span { + display: block; +} +.post-reward img { + display: inline-block; + margin: 0.8em 2em 0; + max-width: 100%; + width: 180px; +} +@keyframes next-roll { + from { + transform: rotateZ(30deg); + } + to { + transform: rotateZ(-30deg); + } +} +.followme { + color: #bbb; + padding: 1em 1.5em; + text-align: center; + background: var(--card-bg-color); + border-left: 3px solid #ff2a2a; + margin: 1em 0 0; +} +.followme .social-list { + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.followme .social-list .social-item { + margin: 0.5em 2em; + position: relative; +} +@media (max-width: 991px) { + .followme .social-list .social-item { + margin: 0.5em 0.75em; + } +} +.followme .social-list .social-link { + border: 0; + display: block; +} +.followme .social-list .social-link .icon { + font-size: 1.75em; +} +.followme .social-list .social-link .label { + display: block; + font-size: 14px; +} +.followme .social-list .social-link:hover + .social-item-img { + display: block; +} +.followme .social-list span.social-link { + color: var(--link-color); +} +.followme .social-list span.social-link:hover { + color: var(--link-hover-color); +} +.followme .social-list .social-item-img { + display: none; + left: 50%; + max-width: 180px; + position: absolute; + transform: translate(-50%, 20px); +} +.category-all-page .category-all-title { + text-align: center; +} +.category-all-page .category-all { + margin-top: 20px; +} +.category-all-page .category-list { + list-style: none; + margin: 0; + padding: 0; +} +.category-all-page .category-list-item { + margin: 5px 10px; +} +.category-all-page .category-list-count { + color: #bbb; +} +.category-all-page .category-list-count::before { + content: ' ('; +} +.category-all-page .category-list-count::after { + content: ') '; +} +.category-all-page .category-list-child { + padding-left: 10px; +} +.event-list hr { + background: #222; + margin: 20px 0 45px; +} +.event-list hr::after { + background: #222; + color: #fff; + content: 'NOW'; + display: inline-block; + font-weight: bold; + padding: 0 5px; +} +.event-list .event { + --event-background: #222; + --event-foreground: #bbb; + --event-title: #fff; + background: var(--event-background); + padding: 15px; +} +.event-list .event .event-summary { + border-bottom: 0; + color: var(--event-title); + margin: 0; + padding: 0 0 0 35px; + position: relative; +} +.event-list .event .event-summary::before { + animation: dot-flash 1s alternate infinite ease-in-out; + background: var(--event-title); + left: 0; + margin-top: -6px; + position: absolute; + top: 50%; + border-radius: 50%; + content: ' '; + height: 12px; + width: 12px; +} +.event-list .event:nth-of-type(odd) .event-summary::before { + animation-delay: 0.5s; +} +.event-list .event:not(:last-child) { + margin-bottom: 20px; +} +.event-list .event .event-relative-time { + color: var(--event-foreground); + display: inline-block; + font-size: 12px; + font-weight: normal; + padding-left: 12px; +} +.event-list .event .event-details { + color: var(--event-foreground); + display: block; + line-height: 18px; + padding: 6px 0 6px 35px; +} +.event-list .event .event-details::before { + color: var(--event-foreground); + display: inline-block; + margin-right: 9px; + width: 14px; + font-family: 'Font Awesome 6 Free'; + font-weight: 900; +} +.event-list .event .event-details.event-location::before { + content: '\f041'; +} +.event-list .event .event-details.event-duration::before { + content: '\f017'; +} +.event-list .event .event-details.event-description::before { + content: '\f024'; +} +.event-list .event-past { + --event-background: #f5f5f5; + --event-foreground: #999; + --event-title: #222; +} +@keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +ul.breadcrumb { + font-size: 0.75em; + list-style: none; + margin: 1em 0; + padding: 0 2em; + text-align: center; +} +ul.breadcrumb li { + display: inline; +} +ul.breadcrumb li:not(:first-child)::before { + content: '/\00a0'; + font-weight: normal; + padding: 0.5em; +} +ul.breadcrumb li:last-child { + font-weight: bold; +} +.tag-cloud { + text-align: center; +} +.tag-cloud a { + display: inline-block; + margin: 10px; +} +.tag-cloud-0 { + border-bottom-color: #aaa; + color: #aaa; +} +.tag-cloud-1 { + border-bottom-color: #9a9a9a; + color: #9a9a9a; +} +.tag-cloud-2 { + border-bottom-color: #8b8b8b; + color: #8b8b8b; +} +.tag-cloud-3 { + border-bottom-color: #7c7c7c; + color: #7c7c7c; +} +.tag-cloud-4 { + border-bottom-color: #6c6c6c; + color: #6c6c6c; +} +.tag-cloud-5 { + border-bottom-color: #5d5d5d; + color: #5d5d5d; +} +.tag-cloud-6 { + border-bottom-color: #4e4e4e; + color: #4e4e4e; +} +.tag-cloud-7 { + border-bottom-color: #3e3e3e; + color: #3e3e3e; +} +.tag-cloud-8 { + border-bottom-color: #2f2f2f; + color: #2f2f2f; +} +.tag-cloud-9 { + border-bottom-color: #202020; + color: #202020; +} +.tag-cloud-10 { + border-bottom-color: #111; + color: #111; +} +@media (prefers-color-scheme: dark) { + .tag-cloud-0 { + border-bottom-color: #555; + color: #555; + } + .tag-cloud-1 { + border-bottom-color: #646464; + color: #646464; + } + .tag-cloud-2 { + border-bottom-color: #737373; + color: #737373; + } + .tag-cloud-3 { + border-bottom-color: #828282; + color: #828282; + } + .tag-cloud-4 { + border-bottom-color: #929292; + color: #929292; + } + .tag-cloud-5 { + border-bottom-color: #a1a1a1; + color: #a1a1a1; + } + .tag-cloud-6 { + border-bottom-color: #b0b0b0; + color: #b0b0b0; + } + .tag-cloud-7 { + border-bottom-color: #c0c0c0; + color: #c0c0c0; + } + .tag-cloud-8 { + border-bottom-color: #cfcfcf; + color: #cfcfcf; + } + .tag-cloud-9 { + border-bottom-color: #dedede; + color: #dedede; + } + .tag-cloud-10 { + border-bottom-color: #eee; + color: #eee; + } +} +.gt-header a, +.gt-comments a, +.gt-popup a { + border-bottom: 0; +} +.gt-container .gt-popup .gt-action.is--active::before { + top: 0.7em; +} +@media (prefers-color-scheme: dark) { + .gt-container .gt-header-textarea { + background-color: var(--card-bg-color) !important; + } +} +.search-active { + overflow: hidden; +} +.search-pop-overlay { + background: rgba(0,0,0,0); + display: flex; + height: 100%; + left: 0; + position: fixed; + top: 0; + transition: visibility 0.4s, background 0.4s; + visibility: hidden; + width: 100%; + z-index: 40; +} +.search-active .search-pop-overlay { + background: rgba(0,0,0,0.3); + visibility: visible; +} +.search-popup { + background: var(--card-bg-color); + border-radius: 5px; + height: 80%; + margin: auto; + transform: scale(0); + transition: transform 0.4s; + width: 700px; +} +.search-active .search-popup { + transform: scale(1); +} +@media (max-width: 767px) { + .search-popup { + border-radius: 0; + height: 100%; + width: 100%; + } +} +.search-popup .search-icon, +.search-popup .popup-btn-close { + color: #999; + font-size: 18px; + padding: 0 10px; +} +.search-popup .popup-btn-close { + cursor: pointer; +} +.search-popup .popup-btn-close:hover .fa { + color: #222; +} +.search-popup .search-header { + background: #eee; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + display: flex; + padding: 5px; +} +@media (prefers-color-scheme: dark) { + .search-popup .search-header { + background: #666; + } +} +.search-popup input.search-input { + background: transparent; + border: 0; + outline: 0; + width: 100%; +} +.search-popup input.search-input::-webkit-search-cancel-button { + display: none; +} +.search-popup .search-result-container { + height: calc(100% - 55px); + overflow: auto; + padding: 5px 25px; +} +.search-popup .search-result-container hr { + margin: 5px 0 10px; +} +.search-popup .search-result-container hr:first-child { + display: none; +} +.search-popup .search-result-list { + margin: 0 5px; + padding: 0; +} +.search-popup a.search-result-title { + font-weight: bold; +} +.search-popup p.search-result { + border-bottom: 1px dashed #ccc; + padding: 5px 0; +} +.search-popup .search-input-container { + flex-grow: 1; + padding: 2px; +} +.search-popup .no-result { + display: flex; +} +.search-popup .search-result-list { + width: 100%; +} +.search-popup .search-result-icon { + color: #ccc; + margin: auto; +} +mark.search-keyword { + background: transparent; + border-bottom: 1px dashed #ff2a2a; + color: #ff2a2a; + font-weight: bold; +} +.use-motion .animated { + animation-fill-mode: none; + visibility: inherit; +} +.use-motion .sidebar .animated { + animation-fill-mode: both; +} +header.header { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; +} +@media (max-width: 991px) { + header.header { + border-radius: initial; + } +} +.main { + align-items: stretch; + display: flex; + justify-content: space-between; + margin: 0 auto; + width: calc(100% - 20px); +} +@media (max-width: 767px) { + .main { + width: auto; + } +} +@media (min-width: 1200px) { + .main { + width: 1160px; + } +} +@media (min-width: 1600px) { + .main { + width: 73%; + } +} +@media (max-width: 991px) { + .main { + display: block; + width: auto; + } +} +.main-inner { + border-radius: initial; + box-sizing: border-box; + width: calc(100% - 252px); +} +@media (max-width: 991px) { + .main-inner { + border-radius: initial; + width: 100%; + } +} +.footer-inner { + padding-left: 252px; +} +@media (max-width: 991px) { + .footer-inner { + padding-left: 0; + padding-right: 0; + width: auto; + } +} +.column { + width: 240px; +} +@media (max-width: 991px) { + .column { + width: auto; + } +} +.site-brand-container { + background: var(--theme-color); +} +@media (max-width: 991px) { + .site-nav-on .site-brand-container { + box-shadow: 0 0 16px rgba(0,0,0,0.5); + } +} +.site-meta { + padding: 20px 0; +} +@media (min-width: 768px) and (max-width: 991px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; + } +} +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: #fff; +} +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: #fff; +} +@media (min-width: 768px) and (max-width: 991px) { + .site-nav { + --scroll-height: 0; + height: 0; + overflow: hidden; + transition: 0.2s ease-in-out; + transition-property: height, visibility; + visibility: hidden; + } + body:not(.site-nav-on) .site-nav .animated { + animation: none; + } + body.site-nav-on .site-nav { + height: var(--scroll-height); + visibility: unset; + } +} +.menu .menu-item { + display: block; + margin: 0; +} +.menu .menu-item a { + padding: 5px 20px; + position: relative; + text-align: left; + transition-property: background-color; +} +@media (max-width: 991px) { + .menu .menu-item.menu-item-search { + display: none; + } +} +.menu .menu-item .badge { + background: #ccc; + border-radius: 10px; + color: var(--content-bg-color); + float: right; + padding: 2px 5px; + text-shadow: 1px 1px 0 rgba(0,0,0,0.1); +} +.sub-menu { + margin: 0; + padding: 6px 0; +} +.sub-menu .menu-item { + display: inline-block; +} +.sub-menu .menu-item a { + background: transparent; + margin: 5px 10px; + padding: initial; +} +.sub-menu .menu-item a:hover { + background: transparent; + color: #fc6423; +} +.sub-menu .menu-item-active { + border-bottom-color: #fc6423; + color: #fc6423; +} +.sub-menu .menu-item-active:hover { + border-bottom-color: #fc6423; +} +.sidebar { + position: -webkit-sticky; + position: sticky; + top: 12px; +} +@media (max-width: 991px) { + .sidebar { + display: none; + } +} +.sidebar-inner { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: initial; + box-sizing: border-box; + color: var(--text-color); + margin-top: 12px; + max-height: calc(100vh - 24px); + visibility: hidden; +} +.site-state-item { + padding: 0 10px; +} +.sidebar .sidebar-button { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; +} +.sidebar .sidebar-button button { + border: 0; + color: #fc6423; + display: block; + width: 100%; +} +.sidebar .sidebar-button button:hover { + background: none; + border: 0; + color: #e34603; +} +.links-of-author { + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.links-of-author-item { + margin: 5px 0 0; +} +.links-of-author-item a { + box-sizing: border-box; + display: inline-block; + max-width: 100%; + overflow: hidden; + padding: 0 5px; + text-overflow: ellipsis; + white-space: nowrap; +} +.links-of-author-item a { + border-bottom: 0; + border-radius: 4px; + display: block; +} +.links-of-author-item a:hover { + background: var(--body-bg-color); +} +.main-inner { + background: var(--content-bg-color); + box-shadow: initial; + padding: 40px; +} +@media (max-width: 991px) { + .main-inner { + padding: 20px; + } +} +.sub-menu { + border-bottom: 1px solid #ddd; +} +.post-block:first-of-type { + padding-top: 40px; +} +@media (max-width: 767px) { + .pagination { + margin-bottom: 10px; + } +} diff --git a/css/noscript.css b/css/noscript.css new file mode 100644 index 00000000..6418c57d --- /dev/null +++ b/css/noscript.css @@ -0,0 +1,48 @@ +body { + margin-top: 2rem; +} +.use-motion .menu-item, +.use-motion .sidebar, +.use-motion .sidebar-inner, +.use-motion .post-block, +.use-motion .pagination, +.use-motion .comments, +.use-motion .post-header, +.use-motion .post-body, +.use-motion .collection-header { + visibility: visible; +} +.use-motion .column, +.use-motion .site-brand-container .toggle, +.use-motion .footer { + opacity: initial; +} +.use-motion .site-title, +.use-motion .site-subtitle, +.use-motion .custom-logo-image { + opacity: initial; + top: initial; +} +.use-motion .logo-line { + transform: scaleX(1); +} +.search-pop-overlay, +.sidebar-nav { + display: none; +} +.sidebar-panel { + display: block; +} +.noscript-warning { + background-color: #f55; + color: #fff; + font-family: sans-serif; + font-size: 1rem; + font-weight: bold; + left: 0; + position: fixed; + text-align: center; + top: 0; + width: 100%; + z-index: 50; +} diff --git a/images/apple-touch-icon-next.png b/images/apple-touch-icon-next.png new file mode 100644 index 00000000..86a0d1d3 Binary files /dev/null and b/images/apple-touch-icon-next.png differ diff --git a/images/avatar.gif b/images/avatar.gif new file mode 100644 index 00000000..3b5d744b Binary files /dev/null and b/images/avatar.gif differ diff --git a/images/common/avatar-1.png b/images/common/avatar-1.png new file mode 100644 index 00000000..e724ac40 Binary files /dev/null and b/images/common/avatar-1.png differ diff --git a/images/common/avatar-2.png b/images/common/avatar-2.png new file mode 100644 index 00000000..0d417413 Binary files /dev/null and b/images/common/avatar-2.png differ diff --git a/images/common/avatar.png b/images/common/avatar.png new file mode 100644 index 00000000..5786c92a Binary files /dev/null and b/images/common/avatar.png differ diff --git a/images/common/favicon-142.png b/images/common/favicon-142.png new file mode 100644 index 00000000..510c22cb Binary files /dev/null and b/images/common/favicon-142.png differ diff --git a/images/common/favicon-20.png b/images/common/favicon-20.png new file mode 100644 index 00000000..3ff6327c Binary files /dev/null and b/images/common/favicon-20.png differ diff --git a/images/common/favicon-50.png b/images/common/favicon-50.png new file mode 100644 index 00000000..39534ce3 Binary files /dev/null and b/images/common/favicon-50.png differ diff --git a/images/favicon-16x16-next.png b/images/favicon-16x16-next.png new file mode 100644 index 00000000..de8c5d3a Binary files /dev/null and b/images/favicon-16x16-next.png differ diff --git a/images/favicon-32x32-next.png b/images/favicon-32x32-next.png new file mode 100644 index 00000000..e02f5f4d Binary files /dev/null and b/images/favicon-32x32-next.png differ diff --git a/images/logo-algolia-nebula-blue-full.svg b/images/logo-algolia-nebula-blue-full.svg new file mode 100644 index 00000000..886c422e --- /dev/null +++ b/images/logo-algolia-nebula-blue-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 00000000..992c1a58 --- /dev/null +++ b/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/posts/animateNumber_01.png b/images/posts/animateNumber_01.png new file mode 100644 index 00000000..b9f334c0 Binary files /dev/null and b/images/posts/animateNumber_01.png differ diff --git a/images/posts/datepicker.gif b/images/posts/datepicker.gif new file mode 100644 index 00000000..36a6003e Binary files /dev/null and b/images/posts/datepicker.gif differ diff --git a/images/posts/desktop-notification1.png b/images/posts/desktop-notification1.png new file mode 100644 index 00000000..109b3a83 Binary files /dev/null and b/images/posts/desktop-notification1.png differ diff --git a/images/posts/desktop-notification2.png b/images/posts/desktop-notification2.png new file mode 100644 index 00000000..8cd8255c Binary files /dev/null and b/images/posts/desktop-notification2.png differ diff --git a/images/posts/navLocation.png b/images/posts/navLocation.png new file mode 100644 index 00000000..f3e26385 Binary files /dev/null and b/images/posts/navLocation.png differ diff --git a/images/posts/notify-jianrong-mobile.png b/images/posts/notify-jianrong-mobile.png new file mode 100644 index 00000000..e36041cb Binary files /dev/null and b/images/posts/notify-jianrong-mobile.png differ diff --git a/images/posts/notify-jianrong-pc.png b/images/posts/notify-jianrong-pc.png new file mode 100644 index 00000000..8ce7a672 Binary files /dev/null and b/images/posts/notify-jianrong-pc.png differ diff --git a/images/posts/post-user-bg.jpg b/images/posts/post-user-bg.jpg new file mode 100644 index 00000000..4cbc5f85 Binary files /dev/null and b/images/posts/post-user-bg.jpg differ diff --git a/images/posts/vue-datepicker2.gif b/images/posts/vue-datepicker2.gif new file mode 100644 index 00000000..d78a2de2 Binary files /dev/null and b/images/posts/vue-datepicker2.gif differ diff --git a/images/posts/windowLocation.png b/images/posts/windowLocation.png new file mode 100644 index 00000000..787d455e Binary files /dev/null and b/images/posts/windowLocation.png differ diff --git a/images/reward/alipay-reward-image.png b/images/reward/alipay-reward-image.png new file mode 100644 index 00000000..007a0584 Binary files /dev/null and b/images/reward/alipay-reward-image.png differ diff --git a/images/reward/wechat-reward-image.png b/images/reward/wechat-reward-image.png new file mode 100644 index 00000000..886e1af8 Binary files /dev/null and b/images/reward/wechat-reward-image.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..e1a8e143 --- /dev/null +++ b/index.html @@ -0,0 +1,1368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +前端路上 - 万物皆有裂缝 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

在 Git 工作流中,有时需要在同一台电脑上针对不同的项目设置不同的用户身份。例如,开发人员可能需要在个人项目和公司项目之间切换,而这两个项目可能需要不同的 Git 用户名和电子邮件地址。Oh My Zsh 提供了强大的自定义功能,允许我们通过编写自定义的 shell 函数来实现这一需求。本文将展示如何使用 Oh My Zsh 的 chpwd 函数来自动设置特定目录及其子目录、孙目录下的 Git 用户配置。

+
+

开始之前

假设你对git身份信息和ssh key概念有比较清晰的认识和了解其基础使用,比如git身份信息是用户名和邮箱,在你使用git commit的时候进行记录;而ssh key是一个凭证(分为公钥和私钥),用于远端身份信息验证,和git没有直接关系,也可以用于其他场景使用,比如ssh登录远程主机。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

GitHub Actions 提供了一种强大的自动化构建和发布流程的方式,通过组合github action的方式形成了workflow,来实现CI/CD。我们完全可以将 npm 包的发布流程集成到 GitHub workflow中,做到自动化发包,这可以可以提高效率并减少人为错误。以下是如何使用 GitHub Actions 实现自动化发布的详细指南。

+
+

0. 原理解释

github actions是一个github推出的CI/CD工具,可以模拟平台(比如linux等)自动化执行一些操作。
npm可以通过token实现免登陆发布包,并且Github Secrets能很好的保护token不被泄露(后面会说简单说一下为什么),这样就避免了在登陆信息在CI/CD时泄露

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

Hexo 是一个基于 Node.js 的静态博客框架,它以简洁的速度和易用性受到许多开发者的青睐。如果你的博客部署在github上,那么这里有一种新的部署方式可能让摆脱频繁提交部署的苦恼!GitHub Actions 为自动化部署Hexo提供了一种解决方案,极大地简化了部署流程和节省了时间,可以让我们更多的专注到写作本身。

+
+

为什么使用Github Actions

    +
  • 将文章源文件和部署文件分离,有助于保护源文件中的隐私和你的草稿文件等
  • +
  • 节省手动部署的时间,专注于写作本身
  • +
  • 学习Github Actions相关知识,不折腾不作死发作了
  • +
+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

环境准备

如果你想使用conda来管理你的Python环境和依赖,你需要在你的机器上安装Anaconda或Miniconda。Anaconda包含了conda,Python和150+科学包及其依赖。而Miniconda只包含了conda和Python。

+

当你安装好后Anaconda或Miniconda后,你就可以愉快的使用conda管理项目了

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

随着mac系统的升级,对app安全控制越来越严,经常遇到一些非官方渠道安装的app,安装后无法打开的情况,如提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”,一般情况下,出现该问题是因为软件没有签名,所以被MAC系统禁止运行。可以尝试以下解决方案

+

老系统:设置允许任何来源下载的App

比较老的版本系统,可以按以下步骤操作:
打开”系统偏好设置 -> 安全与隐私 -> 通用”选项卡,检查是否已经启用了”任何来源”选项。如果没有启用,先点击左下角的小黄锁图标解锁,然后选中”任何来源”。(由于作者mac系统比较新,故此图来源网络)
image.png

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

在使用 npm 包管理器进行包的发布时,通常需要手动登录 npm 账户,并且我们在本地使用npm时因为某些不可抗原因,使用了非npm官方源,导致我们每次发布都需要切换到官方源,显得非常繁琐。现在,通过使用 npm Token,可以避免手动登录,切换源等步骤,实现自动化发布流程,特别是在持续集成/持续部署(CI/CD)环境中。

+
+

1. 准备工作

在开始之前,请确保:

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

简介

Node Version Manager (nvm) 是一个易于使用的工具,可以帮助您在不同Node.js版本之间进行切换,以满足不同项目的需求。

+

背景

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

Git 是一种流行的分布式版本控制系统,它允许开发者在不同的分支上进行工作,同时保持代码的完整性和历史记录。在使用 Git 进行版本控制的过程中,我们可能会遇到需要放弃当前工作进度的情况,例如,当发现当前分支的修改与主分支的代码不兼容,或者需要切换到另一个分支继续工作时。

+
+

概述

这篇文章会介绍如何在 Git 中放弃对文件的修改,包括以下几个方面:

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

最近在某些比较看重的事情上,处理的很失败,感觉整个心态都发生了变化。下班独行的那段路上,突然惊醒自己也许需要做出一些改变,晚饭后静静的思考了一阵:似乎勇往前行才是我最需需要的改变。古语有云:走自己的路,让别人去说吧。是的,生活需要你不断踽踽独行,活在他人的世界里是可怕的,特别他(她)还是陌生人。随手记录一些句子,它们是我最近的心态和生活的写照,也以此鞭策自己砥砺前行。

+
+
+

时间不会因你沮丧而停滞不前
生活不会因你懊恼而雨过天晴

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

下面记录的主要是本人在项目中遇到的一些问题的解决方案或者个人觉得精妙的代码,方案也许来自于自己的想法,也有可能来自于网络(我尽量给出出处,以便大家追源溯流),也许干脆就是一个插件库(实际在项目中对于一些小的功能个人是拒绝引入不纯粹的三方库的)。肯定,这其中有些方案也不是尽善尽美,我会一一列出。如果对于某个问题在你看来有更好的解决方案,请不吝指出!

+
+

placeholder属性支持

有时候项目中placeholder也许要兼容某些特殊的浏览器,所以才有了下面这段代码。这种方案有一个缺点就是:如果你恰好要对这个输入框绑定focus事件并操作它的值,必须要小心处理

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/bookmark.js b/js/bookmark.js new file mode 100644 index 00000000..8e3ae6ad --- /dev/null +++ b/js/bookmark.js @@ -0,0 +1,56 @@ +/* global CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + 'use strict'; + + const doSaveScroll = () => { + localStorage.setItem('bookmark' + location.pathname, window.scrollY); + }; + + const scrollToMark = () => { + let top = localStorage.getItem('bookmark' + location.pathname); + top = parseInt(top, 10); + // If the page opens with a specific hash, just jump out + if (!isNaN(top) && location.hash === '') { + // Auto scroll to the position + window.anime({ + targets : document.scrollingElement, + duration : 200, + easing : 'linear', + scrollTop: top + }); + } + }; + // Register everything + const init = function(trigger) { + // Create a link element + const link = document.querySelector('.book-mark-link'); + // Scroll event + window.addEventListener('scroll', () => link.classList.toggle('book-mark-link-fixed', window.scrollY === 0), { passive: true }); + // Register beforeunload event when the trigger is auto + if (trigger === 'auto') { + // Register beforeunload event + window.addEventListener('beforeunload', doSaveScroll); + document.addEventListener('pjax:send', doSaveScroll); + } + // Save the position by clicking the icon + link.addEventListener('click', () => { + doSaveScroll(); + window.anime({ + targets : link, + duration: 200, + easing : 'linear', + top : -30, + complete: () => { + setTimeout(() => { + link.style.top = ''; + }, 400); + } + }); + }); + scrollToMark(); + document.addEventListener('pjax:success', scrollToMark); + }; + + init(CONFIG.bookmark.save); +}); diff --git a/js/comments-buttons.js b/js/comments-buttons.js new file mode 100644 index 00000000..505c21b7 --- /dev/null +++ b/js/comments-buttons.js @@ -0,0 +1,25 @@ +/* global CONFIG */ + +(function() { + const commentButton = document.querySelectorAll('.comment-button'); + commentButton.forEach(element => { + const commentClass = element.classList[2]; + element.addEventListener('click', () => { + commentButton.forEach(active => active.classList.toggle('active', active === element)); + document.querySelectorAll('.comment-position').forEach(active => active.classList.toggle('active', active.classList.contains(commentClass))); + if (CONFIG.comments.storage) { + localStorage.setItem('comments_active', commentClass); + } + }); + }); + let { activeClass } = CONFIG.comments; + if (CONFIG.comments.storage) { + activeClass = localStorage.getItem('comments_active') || activeClass; + } + if (activeClass) { + const activeButton = document.querySelector(`.comment-button.${activeClass}`); + if (activeButton) { + activeButton.click(); + } + } +})(); diff --git a/js/comments.js b/js/comments.js new file mode 100644 index 00000000..4045e8c0 --- /dev/null +++ b/js/comments.js @@ -0,0 +1,21 @@ +/* global CONFIG */ + +window.addEventListener('tabs:register', () => { + let { activeClass } = CONFIG.comments; + if (CONFIG.comments.storage) { + activeClass = localStorage.getItem('comments_active') || activeClass; + } + if (activeClass) { + const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`); + if (activeTab) { + activeTab.click(); + } + } +}); +if (CONFIG.comments.storage) { + window.addEventListener('tabs:click', event => { + if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; + const commentClass = event.target.classList[1]; + localStorage.setItem('comments_active', commentClass); + }); +} diff --git a/js/config.js b/js/config.js new file mode 100644 index 00000000..caa0075b --- /dev/null +++ b/js/config.js @@ -0,0 +1,66 @@ +if (!window.NexT) window.NexT = {}; + +(function() { + const className = 'next-config'; + + const staticConfig = {}; + let variableConfig = {}; + + const parse = text => JSON.parse(text || '{}'); + + const update = name => { + const targetEle = document.querySelector(`.${className}[data-name="${name}"]`); + if (!targetEle) return; + const parsedConfig = parse(targetEle.text); + if (name === 'main') { + Object.assign(staticConfig, parsedConfig); + } else { + variableConfig[name] = parsedConfig; + } + }; + + update('main'); + + window.CONFIG = new Proxy({}, { + get(overrideConfig, name) { + let existing; + if (name in staticConfig) { + existing = staticConfig[name]; + } else { + if (!(name in variableConfig)) update(name); + existing = variableConfig[name]; + } + + // For unset override and mixable existing + if (!(name in overrideConfig) && typeof existing === 'object') { + // Get ready to mix. + overrideConfig[name] = {}; + } + + if (name in overrideConfig) { + const override = overrideConfig[name]; + + // When mixable + if (typeof override === 'object' && typeof existing === 'object') { + // Mix, proxy changes to the override. + return new Proxy({ ...existing, ...override }, { + set(target, prop, value) { + target[prop] = value; + override[prop] = value; + return true; + } + }); + } + + return override; + } + + // Only when not mixable and override hasn't been set. + return existing; + } + }); + + document.addEventListener('pjax:success', () => { + variableConfig = {}; + }); +})(); diff --git a/js/motion.js b/js/motion.js new file mode 100644 index 00000000..aad22db1 --- /dev/null +++ b/js/motion.js @@ -0,0 +1,140 @@ +/* global NexT, CONFIG */ + +NexT.motion = {}; + +NexT.motion.integrator = { + queue: [], + init : function() { + this.queue = []; + return this; + }, + add: function(fn) { + const sequence = fn(); + if (CONFIG.motion.async) this.queue.push(sequence); + else this.queue = this.queue.concat(sequence); + return this; + }, + bootstrap: function() { + if (!CONFIG.motion.async) this.queue = [this.queue]; + this.queue.forEach(sequence => { + const timeline = window.anime.timeline({ + duration: 200, + easing : 'linear' + }); + sequence.forEach(item => { + if (item.deltaT) timeline.add(item, item.deltaT); + else timeline.add(item); + }); + }); + } +}; + +NexT.motion.middleWares = { + header: function() { + const sequence = []; + + function getMistLineSettings(targets) { + sequence.push({ + targets, + scaleX : [0, 1], + duration: 500, + deltaT : '-=200' + }); + } + + function pushToSequence(targets, sequenceQueue = false) { + sequence.push({ + targets, + opacity: 1, + top : 0, + deltaT : sequenceQueue ? '-=200' : '-=0' + }); + } + + pushToSequence('.column'); + CONFIG.scheme === 'Mist' && getMistLineSettings('.logo-line'); + CONFIG.scheme === 'Muse' && pushToSequence('.custom-logo-image'); + pushToSequence('.site-title'); + pushToSequence('.site-brand-container .toggle', true); + pushToSequence('.site-subtitle'); + (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') && pushToSequence('.custom-logo-image'); + + const menuItemTransition = CONFIG.motion.transition.menu_item; + if (menuItemTransition) { + document.querySelectorAll('.menu-item').forEach(targets => { + sequence.push({ + targets, + complete: () => targets.classList.add('animated', menuItemTransition), + deltaT : '-=200' + }); + }); + } + + return sequence; + }, + + subMenu: function() { + const subMenuItem = document.querySelectorAll('.sub-menu .menu-item'); + if (subMenuItem.length > 0) { + subMenuItem.forEach(element => { + element.classList.add('animated'); + }); + } + return []; + }, + + postList: function() { + const sequence = []; + const { post_block, post_header, post_body, coll_header } = CONFIG.motion.transition; + + function animate(animation, elements) { + if (!animation) return; + elements.forEach(targets => { + sequence.push({ + targets, + complete: () => targets.classList.add('animated', animation), + deltaT : '-=100' + }); + }); + } + + document.querySelectorAll('.post-block').forEach(targets => { + sequence.push({ + targets, + complete: () => targets.classList.add('animated', post_block), + deltaT : '-=100' + }); + animate(coll_header, targets.querySelectorAll('.collection-header')); + animate(post_header, targets.querySelectorAll('.post-header')); + animate(post_body, targets.querySelectorAll('.post-body')); + }); + + animate(post_block, document.querySelectorAll('.pagination, .comments')); + + return sequence; + }, + + sidebar: function() { + const sequence = []; + const sidebar = document.querySelectorAll('.sidebar-inner'); + const sidebarTransition = CONFIG.motion.transition.sidebar; + // Only for Pisces | Gemini. + if (sidebarTransition && (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini')) { + sidebar.forEach(targets => { + sequence.push({ + targets, + complete: () => targets.classList.add('animated', sidebarTransition), + deltaT : '-=100' + }); + }); + } + return sequence; + }, + + footer: function() { + return [{ + targets: document.querySelector('.footer'), + opacity: 1 + }]; + } +}; diff --git a/js/next-boot.js b/js/next-boot.js new file mode 100644 index 00000000..fceb80bb --- /dev/null +++ b/js/next-boot.js @@ -0,0 +1,79 @@ +/* global NexT, CONFIG */ + +NexT.boot = {}; + +NexT.boot.registerEvents = function() { + + NexT.utils.registerScrollPercent(); + NexT.utils.registerCanIUseTag(); + + // Mobile top menu bar. + document.querySelector('.site-nav-toggle .toggle').addEventListener('click', event => { + event.currentTarget.classList.toggle('toggle-close'); + const siteNav = document.querySelector('.site-nav'); + if (!siteNav) return; + siteNav.style.setProperty('--scroll-height', siteNav.scrollHeight + 'px'); + document.body.classList.toggle('site-nav-on'); + }); + + document.querySelectorAll('.sidebar-nav li').forEach((element, index) => { + element.addEventListener('click', () => { + NexT.utils.activateSidebarPanel(index); + }); + }); + + window.addEventListener('hashchange', () => { + const tHash = location.hash; + if (tHash !== '' && !tHash.match(/%\S{2}/)) { + const target = document.querySelector(`.tabs ul.nav-tabs li a[href="${tHash}"]`); + target && target.click(); + } + }); + + window.addEventListener('tabs:click', e => { + NexT.utils.registerCodeblock(e.target); + }); +}; + +NexT.boot.refresh = function() { + + /** + * Register JS handlers by condition option. + * Need to add config option in Front-End at 'scripts/helpers/next-config.js' file. + */ + CONFIG.prism && window.Prism.highlightAll(); + CONFIG.mediumzoom && window.mediumZoom('.post-body :not(a) > img, .post-body > img', { + background: 'var(--content-bg-color)' + }); + CONFIG.lazyload && window.lozad('.post-body img').observe(); + CONFIG.pangu && window.pangu.spacingPage(); + + CONFIG.exturl && NexT.utils.registerExtURL(); + NexT.utils.wrapTableWithBox(); + NexT.utils.registerCodeblock(); + NexT.utils.registerTabsTag(); + NexT.utils.registerActiveMenuItem(); + NexT.utils.registerLangSelect(); + NexT.utils.registerSidebarTOC(); + NexT.utils.registerPostReward(); + NexT.utils.registerVideoIframe(); +}; + +NexT.boot.motion = function() { + // Define Motion Sequence & Bootstrap Motion. + if (CONFIG.motion.enable) { + NexT.motion.integrator + .add(NexT.motion.middleWares.header) + .add(NexT.motion.middleWares.postList) + .add(NexT.motion.middleWares.sidebar) + .add(NexT.motion.middleWares.footer) + .bootstrap(); + } + NexT.utils.updateSidebarPosition(); +}; + +document.addEventListener('DOMContentLoaded', () => { + NexT.boot.registerEvents(); + NexT.boot.refresh(); + NexT.boot.motion(); +}); diff --git a/js/pjax.js b/js/pjax.js new file mode 100644 index 00000000..f81a6a0b --- /dev/null +++ b/js/pjax.js @@ -0,0 +1,50 @@ +/* global NexT, CONFIG, Pjax */ + +const pjax = new Pjax({ + selectors: [ + 'head title', + 'script[type="application/json"]', + // Precede .main-inner to prevent placeholder TOC changes asap + '.post-toc-wrap', + '.main-inner', + '.languages', + '.pjax' + ], + switches: { + '.post-toc-wrap': function(oldWrap, newWrap) { + if (newWrap.querySelector('.post-toc')) { + Pjax.switches.outerHTML.call(this, oldWrap, newWrap); + } else { + const curTOC = oldWrap.querySelector('.post-toc'); + if (curTOC) { + curTOC.classList.add('placeholder-toc'); + } + this.onSwitch(); + } + } + }, + analytics: false, + cacheBust: false, + scrollTo : !CONFIG.bookmark.enable +}); + +document.addEventListener('pjax:success', () => { + pjax.executeScripts(document.querySelectorAll('script[data-pjax]')); + NexT.boot.refresh(); + // Define Motion Sequence & Bootstrap Motion. + if (CONFIG.motion.enable) { + NexT.motion.integrator + .init() + .add(NexT.motion.middleWares.subMenu) + .add(NexT.motion.middleWares.postList) + // Add sidebar-post-related transition. + .add(NexT.motion.middleWares.sidebar) + .bootstrap(); + } + if (CONFIG.sidebar.display !== 'remove') { + const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)'); + document.querySelector('.sidebar-inner').classList.toggle('sidebar-nav-active', hasTOC); + NexT.utils.activateSidebarPanel(hasTOC ? 0 : 1); + NexT.utils.updateSidebarPosition(); + } +}); diff --git a/js/schedule.js b/js/schedule.js new file mode 100644 index 00000000..8f0c26cc --- /dev/null +++ b/js/schedule.js @@ -0,0 +1,138 @@ +/* global CONFIG */ + +// https://developers.google.com/calendar/api/v3/reference/events/list +(function() { + // Initialization + const calendar = { + orderBy : 'startTime', + showLocation: false, + offsetMax : 72, + offsetMin : 4, + showDeleted : false, + singleEvents: true, + maxResults : 250 + }; + + // Read config form theme config file + Object.assign(calendar, CONFIG.calendar); + + const now = new Date(); + const timeMax = new Date(); + const timeMin = new Date(); + + timeMax.setHours(now.getHours() + calendar.offsetMax); + timeMin.setHours(now.getHours() - calendar.offsetMin); + + // Build URL + const params = { + key : calendar.api_key, + orderBy : calendar.orderBy, + timeMax : timeMax.toISOString(), + timeMin : timeMin.toISOString(), + showDeleted : calendar.showDeleted, + singleEvents: calendar.singleEvents, + maxResults : calendar.maxResults + }; + + const request_url = new URL(`https://www.googleapis.com/calendar/v3/calendars/${calendar.calendar_id}/events`); + Object.entries(params).forEach(param => request_url.searchParams.append(...param)); + + function getRelativeTime(current, previous) { + const msPerMinute = 60 * 1000; + const msPerHour = msPerMinute * 60; + const msPerDay = msPerHour * 24; + const msPerMonth = msPerDay * 30; + const msPerYear = msPerDay * 365; + + let elapsed = current - previous; + const tense = elapsed > 0 ? ' ago' : ' later'; + + elapsed = Math.abs(elapsed); + + if (elapsed < msPerHour) { + return Math.round(elapsed / msPerMinute) + ' minutes' + tense; + } else if (elapsed < msPerDay) { + return Math.round(elapsed / msPerHour) + ' hours' + tense; + } else if (elapsed < msPerMonth) { + return 'about ' + Math.round(elapsed / msPerDay) + ' days' + tense; + } else if (elapsed < msPerYear) { + return 'about ' + Math.round(elapsed / msPerMonth) + ' months' + tense; + } + + return 'about ' + Math.round(elapsed / msPerYear) + ' years' + tense; + } + + function buildEventDOM(tense, event, start, end) { + const durationFormat = { + weekday: 'short', + hour : '2-digit', + minute : '2-digit' + }; + const relativeTime = tense === 'now' ? 'NOW' : getRelativeTime(now, start); + const duration = start.toLocaleTimeString([], durationFormat) + ' - ' + end.toLocaleTimeString([], durationFormat); + + let location = ''; + if (calendar.showLocation && event.location) { + location = `${event.location}`; + } + let description = ''; + if (event.description) { + description = `${event.description}`; + } + + const eventContent = `
+

+ ${event.summary} + ${relativeTime} +

+ ${location} + ${duration} + ${description} +
`; + return eventContent; + } + + function fetchData() { + const eventList = document.querySelector('.event-list'); + if (!eventList) return; + + fetch(request_url.href).then(response => { + return response.json(); + }).then(data => { + if (data.items.length === 0) { + eventList.innerHTML = '
'; + return; + } + // Clean the event list + eventList.innerHTML = ''; + let prevEnd = 0; // used to decide where to insert an
+ const utc = new Date().getTimezoneOffset() * 60000; + + data.items.forEach(event => { + // Parse data + const start = new Date(event.start.dateTime || (new Date(event.start.date).getTime() + utc)); + const end = new Date(event.end.dateTime || (new Date(event.end.date).getTime() + utc)); + + let tense = 'now'; + if (end < now) { + tense = 'past'; + } else if (start > now) { + tense = 'future'; + } + + if (tense === 'future' && prevEnd < now) { + eventList.insertAdjacentHTML('beforeend', '
'); + } + + eventList.insertAdjacentHTML('beforeend', buildEventDOM(tense, event, start, end)); + prevEnd = end; + }); + }); + } + + fetchData(); + const fetchDataTimer = setInterval(fetchData, 60000); + document.addEventListener('pjax:send', () => { + clearInterval(fetchDataTimer); + }); +})(); diff --git a/js/schemes/muse.js b/js/schemes/muse.js new file mode 100644 index 00000000..ba60b515 --- /dev/null +++ b/js/schemes/muse.js @@ -0,0 +1,60 @@ +/* global CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + + const isRight = CONFIG.sidebar.position === 'right'; + + const sidebarToggleMotion = { + mouse: {}, + init : function() { + window.addEventListener('mousedown', this.mousedownHandler.bind(this)); + window.addEventListener('mouseup', this.mouseupHandler.bind(this)); + document.querySelector('.sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this)); + document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this)); + window.addEventListener('sidebar:show', this.showSidebar); + window.addEventListener('sidebar:hide', this.hideSidebar); + }, + mousedownHandler: function(event) { + this.mouse.X = event.pageX; + this.mouse.Y = event.pageY; + }, + mouseupHandler: function(event) { + const deltaX = event.pageX - this.mouse.X; + const deltaY = event.pageY - this.mouse.Y; + const clickingBlankPart = Math.hypot(deltaX, deltaY) < 20 && event.target.matches('.main'); + // Fancybox has z-index property, but medium-zoom does not, so the sidebar will overlay the zoomed image. + if (clickingBlankPart || event.target.matches('img.medium-zoom-image')) { + this.hideSidebar(); + } + }, + clickHandler: function() { + document.body.classList.contains('sidebar-active') ? this.hideSidebar() : this.showSidebar(); + }, + showSidebar: function() { + document.body.classList.add('sidebar-active'); + const animateAction = isRight ? 'fadeInRight' : 'fadeInLeft'; + document.querySelectorAll('.sidebar .animated').forEach((element, index) => { + element.style.animationDelay = (100 * index) + 'ms'; + element.classList.remove(animateAction); + setTimeout(() => { + // Trigger a DOM reflow + element.classList.add(animateAction); + }); + }); + }, + hideSidebar: function() { + document.body.classList.remove('sidebar-active'); + } + }; + if (CONFIG.sidebar.display !== 'remove') sidebarToggleMotion.init(); + + function updateFooterPosition() { + const footer = document.querySelector('.footer'); + const containerHeight = document.querySelector('.main').offsetHeight + footer.offsetHeight; + footer.classList.toggle('footer-fixed', containerHeight <= window.innerHeight); + } + + updateFooterPosition(); + window.addEventListener('resize', updateFooterPosition); + window.addEventListener('scroll', updateFooterPosition, { passive: true }); +}); diff --git a/js/third-party/addtoany.js b/js/third-party/addtoany.js new file mode 100644 index 00000000..f9009f87 --- /dev/null +++ b/js/third-party/addtoany.js @@ -0,0 +1,8 @@ +/* global NexT */ + +document.addEventListener('page:loaded', () => { + NexT.utils.getScript('https://static.addtoany.com/menu/page.js', { condition: window.a2a }) + .then(() => { + window.a2a.init(); + }); +}); diff --git a/js/third-party/analytics/baidu-analytics.js b/js/third-party/analytics/baidu-analytics.js new file mode 100644 index 00000000..c10e7d01 --- /dev/null +++ b/js/third-party/analytics/baidu-analytics.js @@ -0,0 +1,7 @@ +/* global _hmt */ + +if (!window._hmt) window._hmt = []; + +document.addEventListener('pjax:success', () => { + _hmt.push(['_trackPageview', location.pathname]); +}); diff --git a/js/third-party/analytics/google-analytics.js b/js/third-party/analytics/google-analytics.js new file mode 100644 index 00000000..8601806e --- /dev/null +++ b/js/third-party/analytics/google-analytics.js @@ -0,0 +1,53 @@ +/* global CONFIG, dataLayer, gtag */ + +if (!CONFIG.google_analytics.only_pageview) { + if (CONFIG.hostname === location.hostname) { + window.dataLayer = window.dataLayer || []; + window.gtag = function() { + dataLayer.push(arguments); + }; + gtag('js', new Date()); + gtag('config', CONFIG.google_analytics.tracking_id); + + document.addEventListener('pjax:success', () => { + gtag('event', 'page_view', { + page_location: location.href, + page_path : location.pathname, + page_title : document.title + }); + }); + } +} else { + const sendPageView = () => { + if (CONFIG.hostname !== location.hostname) return; + const uid = localStorage.getItem('uid') || (Math.random() + '.' + Math.random()); + localStorage.setItem('uid', uid); + fetch( + 'https://www.google-analytics.com/mp/collect?' + new URLSearchParams({ + api_secret : CONFIG.google_analytics.measure_protocol_api_secret, + measurement_id: CONFIG.google_analytics.tracking_id + }), + { + method : 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + client_id: uid, + events : [ + { + name : 'page_view', + params: { + page_location: location.href, + page_title : document.title + } + } + ] + }), + mode: 'no-cors' + } + ); + }; + document.addEventListener('pjax:complete', sendPageView); + sendPageView(); +} diff --git a/js/third-party/analytics/growingio.js b/js/third-party/analytics/growingio.js new file mode 100644 index 00000000..0460833b --- /dev/null +++ b/js/third-party/analytics/growingio.js @@ -0,0 +1,10 @@ +/* global CONFIG, gio */ + +if (!window.gio) { + window.gio = function() { + (window.gio.q = window.gio.q || []).push(arguments); + }; +} + +gio('init', `${CONFIG.growingio_analytics}`, {}); +gio('send'); diff --git a/js/third-party/analytics/matomo.js b/js/third-party/analytics/matomo.js new file mode 100644 index 00000000..290a3e09 --- /dev/null +++ b/js/third-party/analytics/matomo.js @@ -0,0 +1,19 @@ +/* global CONFIG */ + +if (CONFIG.matomo.enable) { + window._paq = window._paq || []; + const _paq = window._paq; + + /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ + _paq.push(['trackPageView']); + _paq.push(['enableLinkTracking']); + const u = CONFIG.matomo.server_url; + _paq.push(['setTrackerUrl', u + 'matomo.php']); + _paq.push(['setSiteId', CONFIG.matomo.site_id]); + const d = document; + const g = d.createElement('script'); + const s = d.getElementsByTagName('script')[0]; + g.async = true; + g.src = u + 'matomo.js'; + s.parentNode.insertBefore(g, s); +} diff --git a/js/third-party/chat/chatra.js b/js/third-party/chat/chatra.js new file mode 100644 index 00000000..e495b8e1 --- /dev/null +++ b/js/third-party/chat/chatra.js @@ -0,0 +1,19 @@ +/* global CONFIG, Chatra */ + +(function() { + if (CONFIG.chatra.embed) { + window.ChatraSetup = { + mode : 'frame', + injectTo: CONFIG.chatra.embed + }; + } + + window.ChatraID = CONFIG.chatra.id; + + const chatButton = document.querySelector('.sidebar-button button'); + if (chatButton) { + chatButton.addEventListener('click', () => { + Chatra('openChat', true); + }); + } +})(); diff --git a/js/third-party/chat/tidio.js b/js/third-party/chat/tidio.js new file mode 100644 index 00000000..bffb918e --- /dev/null +++ b/js/third-party/chat/tidio.js @@ -0,0 +1,10 @@ +/* global tidioChatApi */ + +(function() { + const chatButton = document.querySelector('.sidebar-button button'); + if (chatButton) { + chatButton.addEventListener('click', () => { + tidioChatApi.open(); + }); + } +})(); diff --git a/js/third-party/comments/changyan.js b/js/third-party/comments/changyan.js new file mode 100644 index 00000000..18a1be4f --- /dev/null +++ b/js/third-party/comments/changyan.js @@ -0,0 +1,39 @@ +/* global NexT, CONFIG */ + +document.addEventListener('page:loaded', () => { + const { appid, appkey } = CONFIG.changyan; + const mainJs = 'https://cy-cdn.kuaizhan.com/upload/changyan.js'; + const countJs = `https://cy-cdn.kuaizhan.com/upload/plugins/plugins.list.count.js?clientId=${appid}`; + + // Get the number of comments + setTimeout(() => { + return NexT.utils.getScript(countJs, { + attributes: { + async: true, + id : 'cy_cmt_num' + } + }); + }, 0); + + // When scroll to comment section + if (CONFIG.page.comments && !CONFIG.page.isHome) { + NexT.utils.loadComments('#SOHUCS') + .then(() => { + return NexT.utils.getScript(mainJs, { + attributes: { + async: true + } + }); + }) + .then(() => { + window.changyan.api.config({ + appid, + conf: appkey + }); + }) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Failed to load Changyan', error); + }); + } +}); diff --git a/js/third-party/comments/disqus.js b/js/third-party/comments/disqus.js new file mode 100644 index 00000000..4d1ca9e7 --- /dev/null +++ b/js/third-party/comments/disqus.js @@ -0,0 +1,41 @@ +/* global NexT, CONFIG, DISQUS */ + +document.addEventListener('page:loaded', () => { + + if (CONFIG.disqus.count) { + if (window.DISQUSWIDGETS) { + window.DISQUSWIDGETS.getCount({ reset: true }); + } else { + // Defer loading until the whole page loading is completed + NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/count.js`, { + attributes: { id: 'dsq-count-scr', defer: true } + }); + } + } + + if (CONFIG.page.comments) { + // `disqus_config` should be a global variable + // See https://help.disqus.com/en/articles/1717084-javascript-configuration-variables + window.disqus_config = function() { + this.page.url = CONFIG.page.permalink; + this.page.identifier = CONFIG.page.path; + this.page.title = CONFIG.page.title; + if (CONFIG.disqus.i18n.disqus !== 'disqus') { + this.language = CONFIG.disqus.i18n.disqus; + } + }; + NexT.utils.loadComments('#disqus_thread').then(() => { + if (window.DISQUS) { + DISQUS.reset({ + reload: true, + config: window.disqus_config + }); + } else { + NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/embed.js`, { + attributes: { dataset: { timestamp: '' + +new Date() } } + }); + } + }); + } + +}); diff --git a/js/third-party/comments/disqusjs.js b/js/third-party/comments/disqusjs.js new file mode 100644 index 00000000..d8401eee --- /dev/null +++ b/js/third-party/comments/disqusjs.js @@ -0,0 +1,23 @@ +/* global NexT, CONFIG, DisqusJS */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('#disqus_thread') + .then(() => NexT.utils.getScript(CONFIG.disqusjs.js, { condition: window.DisqusJS })) + .then(() => { + window.dsqjs = new DisqusJS({ + api : CONFIG.disqusjs.api || 'https://disqus.com/api/', + apikey : CONFIG.disqusjs.apikey, + shortname : CONFIG.disqusjs.shortname, + url : CONFIG.page.permalink, + identifier: CONFIG.page.path, + title : CONFIG.page.title + }); + window.dsqjs.render(document.querySelector('.disqusjs-container')); + }); +}); + +document.addEventListener('pjax:send', () => { + if (window.dsqjs) window.dsqjs.destroy(); +}); diff --git a/js/third-party/comments/gitalk.js b/js/third-party/comments/gitalk.js new file mode 100644 index 00000000..08d07f4c --- /dev/null +++ b/js/third-party/comments/gitalk.js @@ -0,0 +1,24 @@ +/* global NexT, CONFIG, Gitalk */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('.gitalk-container') + .then(() => NexT.utils.getScript(CONFIG.gitalk.js, { + condition: window.Gitalk + })) + .then(() => { + const gitalk = new Gitalk({ + clientID : CONFIG.gitalk.client_id, + clientSecret : CONFIG.gitalk.client_secret, + repo : CONFIG.gitalk.repo, + owner : CONFIG.gitalk.github_id, + admin : [CONFIG.gitalk.admin_user], + id : CONFIG.gitalk.path_md5, + proxy : CONFIG.gitalk.proxy, + language : CONFIG.gitalk.language || window.navigator.language, + distractionFreeMode: CONFIG.gitalk.distraction_free_mode + }); + gitalk.render(document.querySelector('.gitalk-container')); + }); +}); diff --git a/js/third-party/comments/isso.js b/js/third-party/comments/isso.js new file mode 100644 index 00000000..2c706013 --- /dev/null +++ b/js/third-party/comments/isso.js @@ -0,0 +1,15 @@ +/* global NexT, CONFIG */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('#isso-thread') + .then(() => NexT.utils.getScript(`${CONFIG.isso}js/embed.min.js`, { + attributes: { + dataset: { + isso: `${CONFIG.isso}` + } + }, + parentNode: document.querySelector('#isso-thread') + })); +}); diff --git a/js/third-party/comments/livere.js b/js/third-party/comments/livere.js new file mode 100644 index 00000000..c4bcd2e1 --- /dev/null +++ b/js/third-party/comments/livere.js @@ -0,0 +1,19 @@ +/* global NexT, CONFIG, LivereTower */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('#lv-container').then(() => { + window.livereOptions = { + refer: CONFIG.page.path.replace(/index\.html$/, '') + }; + + if (typeof LivereTower === 'function') return; + + NexT.utils.getScript('https://cdn-city.livere.com/js/embed.dist.js', { + attributes: { + async: true + } + }); + }); +}); diff --git a/js/third-party/comments/utterances.js b/js/third-party/comments/utterances.js new file mode 100644 index 00000000..332ee057 --- /dev/null +++ b/js/third-party/comments/utterances.js @@ -0,0 +1,17 @@ +/* global NexT, CONFIG */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.page.comments) return; + + NexT.utils.loadComments('.utterances-container') + .then(() => NexT.utils.getScript('https://utteranc.es/client.js', { + attributes: { + async : true, + crossOrigin : 'anonymous', + 'repo' : CONFIG.utterances.repo, + 'issue-term': CONFIG.utterances.issue_term, + 'theme' : CONFIG.utterances.theme + }, + parentNode: document.querySelector('.utterances-container') + })); +}); diff --git a/js/third-party/fancybox.js b/js/third-party/fancybox.js new file mode 100644 index 00000000..178db4b1 --- /dev/null +++ b/js/third-party/fancybox.js @@ -0,0 +1,35 @@ +/* global Fancybox */ + +document.addEventListener('page:loaded', () => { + + /** + * Wrap images with fancybox. + */ + document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(image => { + const imageLink = image.dataset.src || image.src; + const imageWrapLink = document.createElement('a'); + imageWrapLink.classList.add('fancybox'); + imageWrapLink.href = imageLink; + imageWrapLink.setAttribute('itemscope', ''); + imageWrapLink.setAttribute('itemtype', 'http://schema.org/ImageObject'); + imageWrapLink.setAttribute('itemprop', 'url'); + + let dataFancybox = 'default'; + if (image.closest('.post-gallery') !== null) { + dataFancybox = 'gallery'; + } else if (image.closest('.group-picture') !== null) { + dataFancybox = 'group'; + } + imageWrapLink.dataset.fancybox = dataFancybox; + + const imageTitle = image.title || image.alt; + if (imageTitle) { + imageWrapLink.title = imageTitle; + // Make sure img captions will show correctly in fancybox + imageWrapLink.dataset.caption = imageTitle; + } + image.wrap(imageWrapLink); + }); + + Fancybox.bind('[data-fancybox]'); +}); diff --git a/js/third-party/math/katex.js b/js/third-party/math/katex.js new file mode 100644 index 00000000..ad745b18 --- /dev/null +++ b/js/third-party/math/katex.js @@ -0,0 +1,7 @@ +/* global NexT, CONFIG */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.enableMath) return; + + NexT.utils.getScript(CONFIG.katex.copy_tex_js).catch(() => {}); +}); diff --git a/js/third-party/math/mathjax.js b/js/third-party/math/mathjax.js new file mode 100644 index 00000000..fe4d4488 --- /dev/null +++ b/js/third-party/math/mathjax.js @@ -0,0 +1,36 @@ +/* global NexT, CONFIG, MathJax */ + +document.addEventListener('page:loaded', () => { + if (!CONFIG.enableMath) return; + + if (typeof MathJax === 'undefined') { + window.MathJax = { + tex: { + inlineMath: { '[+]': [['$', '$']] }, + tags : CONFIG.mathjax.tags + }, + options: { + renderActions: { + insertedScript: [200, () => { + document.querySelectorAll('mjx-container').forEach(node => { + const target = node.parentNode; + if (target.nodeName.toLowerCase() === 'li') { + target.parentNode.classList.add('has-jax'); + } + }); + }, '', false] + } + } + }; + NexT.utils.getScript(CONFIG.mathjax.js, { + attributes: { + defer: true + } + }); + } else { + MathJax.startup.document.state(0); + MathJax.typesetClear(); + MathJax.texReset(); + MathJax.typesetPromise(); + } +}); diff --git a/js/third-party/pace.js b/js/third-party/pace.js new file mode 100644 index 00000000..c22d59f0 --- /dev/null +++ b/js/third-party/pace.js @@ -0,0 +1,7 @@ +/* global Pace */ + +Pace.options.restartOnPushState = false; + +document.addEventListener('pjax:send', () => { + Pace.restart(); +}); diff --git a/js/third-party/quicklink.js b/js/third-party/quicklink.js new file mode 100644 index 00000000..2543ad1e --- /dev/null +++ b/js/third-party/quicklink.js @@ -0,0 +1,37 @@ +/* global CONFIG, quicklink */ + +(function() { + if (typeof CONFIG.quicklink.ignores === 'string') { + const ignoresStr = `[${CONFIG.quicklink.ignores}]`; + CONFIG.quicklink.ignores = JSON.parse(ignoresStr); + } + + let resetFn = null; + + const onRefresh = () => { + if (resetFn) resetFn(); + if (!CONFIG.quicklink.enable) return; + + let ignoresArr = CONFIG.quicklink.ignores || []; + if (!Array.isArray(ignoresArr)) { + ignoresArr = [ignoresArr]; + } + + resetFn = quicklink.listen({ + timeout : CONFIG.quicklink.timeout, + priority: CONFIG.quicklink.priority, + ignores : [ + uri => uri.includes('#'), + uri => uri === CONFIG.quicklink.url, + ...ignoresArr + ] + }); + }; + + if (CONFIG.quicklink.delay) { + window.addEventListener('load', onRefresh); + document.addEventListener('pjax:success', onRefresh); + } else { + document.addEventListener('page:loaded', onRefresh); + } +})(); diff --git a/js/third-party/search/algolia-search.js b/js/third-party/search/algolia-search.js new file mode 100644 index 00000000..12a554c8 --- /dev/null +++ b/js/third-party/search/algolia-search.js @@ -0,0 +1,130 @@ +/* global instantsearch, algoliasearch, CONFIG, pjax */ + +document.addEventListener('DOMContentLoaded', () => { + const { indexName, appID, apiKey, hits } = CONFIG.algolia; + + const search = instantsearch({ + indexName, + searchClient : algoliasearch(appID, apiKey), + searchFunction: helper => { + if (document.querySelector('.search-input').value) { + helper.search(); + } + } + }); + + if (typeof pjax === 'object') { + search.on('render', () => { + pjax.refresh(document.querySelector('.algolia-hits')); + }); + } + + // Registering Widgets + search.addWidgets([ + instantsearch.widgets.configure({ + hitsPerPage: hits.per_page || 10 + }), + + instantsearch.widgets.searchBox({ + container : '.search-input-container', + placeholder : CONFIG.i18n.placeholder, + // Hide default icons of algolia search + showReset : false, + showSubmit : false, + showLoadingIndicator: false, + cssClasses : { + input: 'search-input' + } + }), + + instantsearch.widgets.stats({ + container: '.algolia-stats', + templates: { + text: data => { + const stats = CONFIG.i18n.hits_time + .replace('${hits}', data.nbHits) + .replace('${time}', data.processingTimeMS); + return `${stats} + Algolia`; + } + }, + cssClasses: { + text: 'search-stats' + } + }), + + instantsearch.widgets.hits({ + container : '.algolia-hits', + escapeHTML: false, + templates : { + item: data => { + const { title, excerpt, excerptStrip, contentStripTruncate } = data._highlightResult; + let result = `${title.value}`; + const content = excerpt || excerptStrip || contentStripTruncate; + if (content && content.value) { + const div = document.createElement('div'); + div.innerHTML = content.value; + result += `

${div.textContent.substring(0, 100)}...

`; + } + return result; + }, + empty: data => { + return `
+ ${CONFIG.i18n.empty.replace('${query}', data.query)} +
`; + } + }, + cssClasses: { + list: 'search-result-list' + } + }), + + instantsearch.widgets.pagination({ + container: '.algolia-pagination', + scrollTo : false, + showFirst: false, + showLast : false, + templates: { + first : '', + last : '', + previous: '', + next : '' + }, + cssClasses: { + list : ['pagination', 'algolia-pagination'], + item : 'pagination-item', + link : 'page-number', + selectedItem: 'current', + disabledItem: 'disabled-item' + } + }) + ]); + + search.start(); + + // Handle and trigger popup window + document.querySelectorAll('.popup-trigger').forEach(element => { + element.addEventListener('click', () => { + document.body.classList.add('search-active'); + setTimeout(() => document.querySelector('.search-input').focus(), 500); + }); + }); + + // Monitor main search box + const onPopupClose = () => { + document.body.classList.remove('search-active'); + }; + + document.querySelector('.search-pop-overlay').addEventListener('click', event => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose(); + } + }); + document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); + document.addEventListener('pjax:success', onPopupClose); + window.addEventListener('keyup', event => { + if (event.key === 'Escape') { + onPopupClose(); + } + }); +}); diff --git a/js/third-party/search/local-search.js b/js/third-party/search/local-search.js new file mode 100644 index 00000000..92a264dc --- /dev/null +++ b/js/third-party/search/local-search.js @@ -0,0 +1,99 @@ +/* global CONFIG, pjax, LocalSearch */ + +document.addEventListener('DOMContentLoaded', () => { + if (!CONFIG.path) { + // Search DB path + console.warn('`hexo-generator-searchdb` plugin is not installed!'); + return; + } + const localSearch = new LocalSearch({ + path : CONFIG.path, + top_n_per_article: CONFIG.localsearch.top_n_per_article, + unescape : CONFIG.localsearch.unescape + }); + + const input = document.querySelector('.search-input'); + + const inputEventFunction = () => { + if (!localSearch.isfetched) return; + const searchText = input.value.trim().toLowerCase(); + const keywords = searchText.split(/[-\s]+/); + const container = document.querySelector('.search-result-container'); + let resultItems = []; + if (searchText.length > 0) { + // Perform local searching + resultItems = localSearch.getResultItems(keywords); + } + if (keywords.length === 1 && keywords[0] === '') { + container.classList.add('no-result'); + container.innerHTML = '
'; + } else if (resultItems.length === 0) { + container.classList.add('no-result'); + container.innerHTML = '
'; + } else { + resultItems.sort((left, right) => { + if (left.includedCount !== right.includedCount) { + return right.includedCount - left.includedCount; + } else if (left.hitCount !== right.hitCount) { + return right.hitCount - left.hitCount; + } + return right.id - left.id; + }); + const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length); + + container.classList.remove('no-result'); + container.innerHTML = `
${stats}
+
+ `; + if (typeof pjax === 'object') pjax.refresh(container); + } + }; + + localSearch.highlightSearchWords(document.querySelector('.post-body')); + if (CONFIG.localsearch.preload) { + localSearch.fetchData(); + } + + if (CONFIG.localsearch.trigger === 'auto') { + input.addEventListener('input', inputEventFunction); + } else { + document.querySelector('.search-icon').addEventListener('click', inputEventFunction); + input.addEventListener('keypress', event => { + if (event.key === 'Enter') { + inputEventFunction(); + } + }); + } + window.addEventListener('search:loaded', inputEventFunction); + + // Handle and trigger popup window + document.querySelectorAll('.popup-trigger').forEach(element => { + element.addEventListener('click', () => { + document.body.classList.add('search-active'); + // Wait for search-popup animation to complete + setTimeout(() => input.focus(), 500); + if (!localSearch.isfetched) localSearch.fetchData(); + }); + }); + + // Monitor main search box + const onPopupClose = () => { + document.body.classList.remove('search-active'); + }; + + document.querySelector('.search-pop-overlay').addEventListener('click', event => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose(); + } + }); + document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); + document.addEventListener('pjax:success', () => { + localSearch.highlightSearchWords(document.querySelector('.post-body')); + onPopupClose(); + }); + window.addEventListener('keyup', event => { + if (event.key === 'Escape') { + onPopupClose(); + } + }); +}); diff --git a/js/third-party/statistics/firestore.js b/js/third-party/statistics/firestore.js new file mode 100644 index 00000000..3ea7ba67 --- /dev/null +++ b/js/third-party/statistics/firestore.js @@ -0,0 +1,60 @@ +/* global CONFIG, firebase */ + +firebase.initializeApp({ + apiKey : CONFIG.firestore.apiKey, + projectId: CONFIG.firestore.projectId +}); + +(function() { + const getCount = (doc, increaseCount) => { + // IncreaseCount will be false when not in article page + return doc.get().then(d => { + // Has no data, initialize count + let count = d.exists ? d.data().count : 0; + // If first view this article + if (increaseCount) { + // Increase count + count++; + doc.set({ + count + }); + } + return count; + }); + }; + + const db = firebase.firestore(); + const articles = db.collection(CONFIG.firestore.collection); + + document.addEventListener('page:loaded', () => { + + if (CONFIG.page.isPost) { + // Fix issue #118 + // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent + const title = document.querySelector('.post-title').textContent.trim(); + const doc = articles.doc(title); + let increaseCount = CONFIG.hostname === location.hostname; + if (localStorage.getItem(title)) { + increaseCount = false; + } else { + // Mark as visited + localStorage.setItem(title, true); + } + getCount(doc, increaseCount).then(count => { + document.querySelector('.firestore-visitors-count').innerText = count; + }); + } else if (CONFIG.page.isHome) { + const promises = [...document.querySelectorAll('.post-title')].map(element => { + const title = element.textContent.trim(); + const doc = articles.doc(title); + return getCount(doc); + }); + Promise.all(promises).then(counts => { + const metas = document.querySelectorAll('.firestore-visitors-count'); + counts.forEach((val, idx) => { + metas[idx].innerText = val; + }); + }); + } + }); +})(); diff --git a/js/third-party/statistics/lean-analytics.js b/js/third-party/statistics/lean-analytics.js new file mode 100644 index 00000000..8397112b --- /dev/null +++ b/js/third-party/statistics/lean-analytics.js @@ -0,0 +1,107 @@ +/* global CONFIG */ +/* eslint-disable no-console */ + +(function() { + const leancloudSelector = url => { + url = encodeURI(url); + return document.getElementById(url).querySelector('.leancloud-visitors-count'); + }; + + const addCount = Counter => { + const visitors = document.querySelector('.leancloud_visitors'); + const url = decodeURI(visitors.id); + const title = visitors.dataset.flagTitle; + + Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url }))}`) + .then(response => response.json()) + .then(({ results }) => { + if (results.length > 0) { + const counter = results[0]; + leancloudSelector(url).innerText = counter.time + 1; + Counter('put', '/classes/Counter/' + counter.objectId, { + time: { + '__op' : 'Increment', + 'amount': 1 + } + }) + .catch(error => { + console.error('Failed to save visitor count', error); + }); + } else if (CONFIG.leancloud_visitors.security) { + leancloudSelector(url).innerText = 'Counter not initialized! More info at console err msg.'; + console.error('ATTENTION! LeanCloud counter has security bug, see how to solve it here: https://github.com/theme-next/hexo-leancloud-counter-security. \n However, you can still use LeanCloud without security, by setting `security` option to `false`.'); + } else { + Counter('post', '/classes/Counter', { title, url, time: 1 }) + .then(response => response.json()) + .then(() => { + leancloudSelector(url).innerText = 1; + }) + .catch(error => { + console.error('Failed to create', error); + }); + } + }) + .catch(error => { + console.error('LeanCloud Counter Error', error); + }); + }; + + const showTime = Counter => { + const visitors = document.querySelectorAll('.leancloud_visitors'); + const entries = [...visitors].map(element => { + return decodeURI(element.id); + }); + + Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url: { '$in': entries } }))}`) + .then(response => response.json()) + .then(({ results }) => { + for (const url of entries) { + const target = results.find(item => item.url === url); + leancloudSelector(url).innerText = target ? target.time : 0; + } + }) + .catch(error => { + console.error('LeanCloud Counter Error', error); + }); + }; + + const { app_id, app_key, server_url } = CONFIG.leancloud_visitors; + const fetchData = api_server => { + const Counter = (method, url, data) => { + return fetch(`${api_server}/1.1${url}`, { + method, + headers: { + 'X-LC-Id' : app_id, + 'X-LC-Key' : app_key, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + }; + if (CONFIG.page.isPost) { + if (CONFIG.hostname !== location.hostname) return; + addCount(Counter); + } else if (document.querySelectorAll('.post-title-link').length >= 1) { + showTime(Counter); + } + }; + + let api_server; + if (server_url) { + api_server = server_url; + } else if (app_id.slice(-9) === '-MdYXbMMI') { + api_server = `https://${app_id.slice(0, 8).toLowerCase()}.api.lncldglobal.com`; + } + + document.addEventListener('page:loaded', () => { + if (api_server) { + fetchData(api_server); + } else { + fetch(`https://app-router.leancloud.cn/2/route?appId=${app_id}`) + .then(response => response.json()) + .then(({ api_server }) => { + fetchData(`https://${api_server}`); + }); + } + }); +})(); diff --git a/js/third-party/tags/mermaid.js b/js/third-party/tags/mermaid.js new file mode 100644 index 00000000..54f62885 --- /dev/null +++ b/js/third-party/tags/mermaid.js @@ -0,0 +1,32 @@ +/* global NexT, CONFIG, mermaid */ + +document.addEventListener('page:loaded', () => { + const mermaidElements = document.querySelectorAll('.mermaid'); + if (mermaidElements.length) { + NexT.utils.getScript(CONFIG.mermaid.js, { + condition: window.mermaid + }).then(() => { + mermaidElements.forEach(element => { + const newElement = document.createElement('div'); + newElement.innerHTML = element.innerHTML; + newElement.className = element.className; + const parent = element.parentNode; + // Fix issue #347 + // Support mermaid inside backtick code block + if (parent.matches('pre')) { + parent.parentNode.replaceChild(newElement, parent); + } else { + parent.replaceChild(newElement, element); + } + }); + mermaid.initialize({ + theme : CONFIG.darkmode && window.matchMedia('(prefers-color-scheme: dark)').matches ? CONFIG.mermaid.theme.dark : CONFIG.mermaid.theme.light, + logLevel : 4, + flowchart: { curve: 'linear' }, + gantt : { axisFormat: '%m/%d/%Y' }, + sequence : { actorMargin: 50 } + }); + mermaid.run(); + }); + } +}); diff --git a/js/third-party/tags/pdf.js b/js/third-party/tags/pdf.js new file mode 100644 index 00000000..7e828911 --- /dev/null +++ b/js/third-party/tags/pdf.js @@ -0,0 +1,23 @@ +/* global NexT, CONFIG, PDFObject */ + +document.addEventListener('page:loaded', () => { + if (document.querySelectorAll('.pdf-container').length) { + NexT.utils.getScript(CONFIG.pdf.object_url, { + condition: window.PDFObject + }).then(() => { + document.querySelectorAll('.pdf-container').forEach(element => { + PDFObject.embed(element.dataset.target, element, { + pdfOpenParams: { + navpanes : 0, + toolbar : 0, + statusbar: 0, + pagemode : 'thumbs', + view : 'FitH' + }, + PDFJS_URL: CONFIG.pdf.url, + height : element.dataset.height + }); + }); + }); + } +}); diff --git a/js/third-party/tags/wavedrom.js b/js/third-party/tags/wavedrom.js new file mode 100644 index 00000000..ddd9a1d9 --- /dev/null +++ b/js/third-party/tags/wavedrom.js @@ -0,0 +1,13 @@ +/* global NexT, CONFIG, WaveDrom */ + +document.addEventListener('page:loaded', () => { + NexT.utils.getScript(CONFIG.wavedrom.js, { + condition: window.WaveDrom + }).then(() => { + NexT.utils.getScript(CONFIG.wavedrom_skin.js, { + condition: window.WaveSkin + }).then(() => { + WaveDrom.ProcessAll(); + }); + }); +}); diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 00000000..808d838f --- /dev/null +++ b/js/utils.js @@ -0,0 +1,489 @@ +/* global NexT, CONFIG */ + +HTMLElement.prototype.wrap = function(wrapper) { + this.parentNode.insertBefore(wrapper, this); + this.parentNode.removeChild(this); + wrapper.appendChild(this); +}; + +(function() { + const onPageLoaded = () => document.dispatchEvent( + new Event('page:loaded', { + bubbles: true + }) + ); + + if (document.readyState === 'loading') { + document.addEventListener('readystatechange', onPageLoaded, { once: true }); + } else { + onPageLoaded(); + } + document.addEventListener('pjax:success', onPageLoaded); +})(); + +NexT.utils = { + + registerExtURL: function() { + document.querySelectorAll('span.exturl').forEach(element => { + const link = document.createElement('a'); + // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings + link.href = decodeURIComponent(atob(element.dataset.url).split('').map(c => { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + link.rel = 'noopener external nofollow noreferrer'; + link.target = '_blank'; + link.className = element.className; + link.title = element.title; + link.innerHTML = element.innerHTML; + element.parentNode.replaceChild(link, element); + }); + }, + + registerCodeblock: function(element) { + const inited = !!element; + let figure = (inited ? element : document).querySelectorAll('figure.highlight'); + let isHljsWithWrap = true; + if (figure.length === 0) { + figure = document.querySelectorAll('pre:not(.mermaid)'); + isHljsWithWrap = false; + } + figure.forEach(element => { + if (!inited) { + let span = element.querySelectorAll('.code .line span'); + if (span.length === 0) { + // Hljs without line_number and wrap + span = element.querySelectorAll('code.highlight span'); + } + span.forEach(s => { + s.classList.forEach(name => { + s.classList.replace(name, `hljs-${name}`); + }); + }); + } + const height = parseInt(window.getComputedStyle(element).height.replace('px', ''), 10); + const needFold = CONFIG.fold.enable && (height > CONFIG.fold.height); + if (!needFold && !CONFIG.copycode.enable) return; + let target; + if (isHljsWithWrap && CONFIG.copycode.style === 'mac') { + target = element; + } else { + let box = element.querySelector('.code-container'); + if (!box) { + // https://github.com/next-theme/hexo-theme-next/issues/98 + // https://github.com/next-theme/hexo-theme-next/pull/508 + const container = element.querySelector('.table-container') || element; + box = document.createElement('div'); + box.className = 'code-container'; + container.wrap(box); + + // add "notranslate" to prevent Google Translate from translating it, which also completely messes up the layout + box.classList.add('notranslate'); + } + target = box; + } + if (needFold && !target.classList.contains('unfold')) { + target.classList.add('highlight-fold'); + target.insertAdjacentHTML('beforeend', '
'); + target.querySelector('.expand-btn').addEventListener('click', () => { + target.classList.remove('highlight-fold'); + target.classList.add('unfold'); + }); + } + if (inited || !CONFIG.copycode.enable) return; + // One-click copy code support. + target.insertAdjacentHTML('beforeend', '
'); + const button = target.querySelector('.copy-btn'); + button.addEventListener('click', () => { + const lines = element.querySelector('.code') || element.querySelector('code'); + const code = lines.innerText; + if (navigator.clipboard) { + // https://caniuse.com/mdn-api_clipboard_writetext + navigator.clipboard.writeText(code).then(() => { + button.querySelector('i').className = 'fa fa-check-circle fa-fw'; + }, () => { + button.querySelector('i').className = 'fa fa-times-circle fa-fw'; + }); + } else { + const ta = document.createElement('textarea'); + ta.style.top = window.scrollY + 'px'; // Prevent page scrolling + ta.style.position = 'absolute'; + ta.style.opacity = '0'; + ta.readOnly = true; + ta.value = code; + document.body.append(ta); + ta.select(); + ta.setSelectionRange(0, code.length); + ta.readOnly = false; + const result = document.execCommand('copy'); + button.querySelector('i').className = result ? 'fa fa-check-circle fa-fw' : 'fa fa-times-circle fa-fw'; + ta.blur(); // For iOS + button.blur(); + document.body.removeChild(ta); + } + }); + element.addEventListener('mouseleave', () => { + setTimeout(() => { + button.querySelector('i').className = 'fa fa-copy fa-fw'; + }, 300); + }); + }); + }, + + wrapTableWithBox: function() { + document.querySelectorAll('table').forEach(element => { + const box = document.createElement('div'); + box.className = 'table-container'; + element.wrap(box); + }); + }, + + registerVideoIframe: function() { + document.querySelectorAll('iframe').forEach(element => { + const supported = [ + 'www.youtube.com', + 'player.vimeo.com', + 'player.youku.com', + 'player.bilibili.com', + 'www.tudou.com' + ].some(host => element.src.includes(host)); + if (supported && !element.parentNode.matches('.video-container')) { + const box = document.createElement('div'); + box.className = 'video-container'; + element.wrap(box); + const width = Number(element.width); + const height = Number(element.height); + if (width && height) { + box.style.paddingTop = (height / width * 100) + '%'; + } + } + }); + }, + + updateActiveNav: function() { + if (!Array.isArray(NexT.utils.sections)) return; + let index = NexT.utils.sections.findIndex(element => { + return element && element.getBoundingClientRect().top > 10; + }); + if (index === -1) { + index = NexT.utils.sections.length - 1; + } else if (index > 0) { + index--; + } + this.activateNavByIndex(index); + }, + + registerScrollPercent: function() { + const backToTop = document.querySelector('.back-to-top'); + const readingProgressBar = document.querySelector('.reading-progress-bar'); + // For init back to top in sidebar if page was scrolled after page refresh. + window.addEventListener('scroll', () => { + if (backToTop || readingProgressBar) { + const contentHeight = document.body.scrollHeight - window.innerHeight; + const scrollPercent = contentHeight > 0 ? Math.min(100 * window.scrollY / contentHeight, 100) : 0; + if (backToTop) { + backToTop.classList.toggle('back-to-top-on', Math.round(scrollPercent) >= 5); + backToTop.querySelector('span').innerText = Math.round(scrollPercent) + '%'; + } + if (readingProgressBar) { + readingProgressBar.style.setProperty('--progress', scrollPercent.toFixed(2) + '%'); + } + } + this.updateActiveNav(); + }, { passive: true }); + + backToTop && backToTop.addEventListener('click', () => { + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: 0 + }); + }); + }, + + /** + * Tabs tag listener (without twitter bootstrap). + */ + registerTabsTag: function() { + // Binding `nav-tabs` & `tab-content` by real time permalink changing. + document.querySelectorAll('.tabs ul.nav-tabs .tab').forEach(element => { + element.addEventListener('click', event => { + event.preventDefault(); + // Prevent selected tab to select again. + if (element.classList.contains('active')) return; + const nav = element.parentNode; + // Get the height of `tab-pane` which is activated before, and set it as the height of `tab-content` with extra margin / paddings. + const tabContent = nav.nextElementSibling; + tabContent.style.overflow = 'hidden'; + tabContent.style.transition = 'height 1s'; + // Comment system selection tab does not contain .active class. + const activeTab = tabContent.querySelector('.active') || tabContent.firstElementChild; + // Hight might be `auto`. + const prevHeight = parseInt(window.getComputedStyle(activeTab).height.replace('px', ''), 10) || 0; + const paddingTop = parseInt(window.getComputedStyle(activeTab).paddingTop.replace('px', ''), 10); + const marginBottom = parseInt(window.getComputedStyle(activeTab.firstElementChild).marginBottom.replace('px', ''), 10); + tabContent.style.height = prevHeight + paddingTop + marginBottom + 'px'; + // Add & Remove active class on `nav-tabs` & `tab-content`. + [...nav.children].forEach(target => { + target.classList.toggle('active', target === element); + }); + // https://stackoverflow.com/questions/20306204/using-queryselector-with-ids-that-are-numbers + const tActive = document.getElementById(element.querySelector('a').getAttribute('href').replace('#', '')); + [...tActive.parentNode.children].forEach(target => { + target.classList.toggle('active', target === tActive); + }); + // Trigger event + tActive.dispatchEvent(new Event('tabs:click', { + bubbles: true + })); + // Get the height of `tab-pane` which is activated now. + const hasScrollBar = document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight); + const currHeight = parseInt(window.getComputedStyle(tabContent.querySelector('.active')).height.replace('px', ''), 10); + // Reset the height of `tab-content` and see the animation. + tabContent.style.height = currHeight + paddingTop + marginBottom + 'px'; + // Change the height of `tab-content` may cause scrollbar show / disappear, which may result in the change of the `tab-pane`'s height + setTimeout(() => { + if ((document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight)) !== hasScrollBar) { + tabContent.style.transition = 'height 0.3s linear'; + // After the animation, we need reset the height of `tab-content` again. + const currHeightAfterScrollBarChange = parseInt(window.getComputedStyle(tabContent.querySelector('.active')).height.replace('px', ''), 10); + tabContent.style.height = currHeightAfterScrollBarChange + paddingTop + marginBottom + 'px'; + } + // Remove all the inline styles, and let the height be adaptive again. + setTimeout(() => { + tabContent.style.transition = ''; + tabContent.style.height = ''; + }, 250); + }, 1000); + if (!CONFIG.stickytabs) return; + const offset = nav.parentNode.getBoundingClientRect().top + window.scrollY + 10; + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: offset + }); + }); + }); + + window.dispatchEvent(new Event('tabs:register')); + }, + + registerCanIUseTag: function() { + // Get responsive height passed from iframe. + window.addEventListener('message', ({ data }) => { + if (typeof data === 'string' && data.includes('ciu_embed')) { + const featureID = data.split(':')[1]; + const height = data.split(':')[2]; + document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 5 + 'px'; + } + }, false); + }, + + registerActiveMenuItem: function() { + document.querySelectorAll('.menu-item a[href]').forEach(target => { + const isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', ''); + const isSubPath = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname); + target.classList.toggle('menu-item-active', target.hostname === location.hostname && (isSamePath || isSubPath)); + }); + }, + + registerLangSelect: function() { + const selects = document.querySelectorAll('.lang-select'); + selects.forEach(sel => { + sel.value = CONFIG.page.lang; + sel.addEventListener('change', () => { + const target = sel.options[sel.selectedIndex]; + document.querySelectorAll('.lang-select-label span').forEach(span => { + span.innerText = target.text; + }); + // Disable Pjax to force refresh translation of menu item + window.location.href = target.dataset.href; + }); + }); + }, + + registerSidebarTOC: function() { + this.sections = [...document.querySelectorAll('.post-toc:not(.placeholder-toc) li a.nav-link')].map(element => { + const target = document.getElementById(decodeURI(element.getAttribute('href')).replace('#', '')); + // TOC item animation navigate. + element.addEventListener('click', event => { + event.preventDefault(); + const offset = target.getBoundingClientRect().top + window.scrollY; + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: offset, + complete : () => { + history.pushState(null, document.title, element.href); + } + }); + }); + return target; + }); + this.updateActiveNav(); + }, + + registerPostReward: function() { + const button = document.querySelector('.reward-container button'); + if (!button) return; + button.addEventListener('click', () => { + document.querySelector('.post-reward').classList.toggle('active'); + }); + }, + + activateNavByIndex: function(index) { + const nav = document.querySelector('.post-toc:not(.placeholder-toc) .nav'); + if (!nav) return; + + const navItemList = nav.querySelectorAll('.nav-item'); + const target = navItemList[index]; + if (!target || target.classList.contains('active-current')) return; + + const singleHeight = navItemList[navItemList.length - 1].offsetHeight; + + nav.querySelectorAll('.active').forEach(navItem => { + navItem.classList.remove('active', 'active-current'); + }); + target.classList.add('active', 'active-current'); + + let activateEle = target.querySelector('.nav-child') || target.parentElement; + let navChildHeight = 0; + + while (nav.contains(activateEle)) { + if (activateEle.classList.contains('nav-item')) { + activateEle.classList.add('active'); + } else { // .nav-child or .nav + // scrollHeight isn't reliable for transitioning child items. + // The last nav-item in a list has a margin-bottom of 5px. + navChildHeight += (singleHeight * activateEle.childElementCount) + 5; + activateEle.style.setProperty('--height', `${navChildHeight}px`); + } + activateEle = activateEle.parentElement; + } + + // Scrolling to center active TOC element if TOC content is taller then viewport. + const tocElement = document.querySelector(CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini' ? '.sidebar-panel-container' : '.sidebar'); + if (!document.querySelector('.sidebar-toc-active')) return; + window.anime({ + targets : tocElement, + duration : 200, + easing : 'linear', + scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top + }); + }, + + updateSidebarPosition: function() { + if (window.innerWidth < 1200 || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return; + // Expand sidebar on post detail page by default, when post has a toc. + const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)'); + let display = CONFIG.page.sidebar; + if (typeof display !== 'boolean') { + // There's no definition sidebar in the page front-matter. + display = CONFIG.sidebar.display === 'always' || (CONFIG.sidebar.display === 'post' && hasTOC); + } + if (display) { + window.dispatchEvent(new Event('sidebar:show')); + } + }, + + activateSidebarPanel: function(index) { + const sidebar = document.querySelector('.sidebar-inner'); + const activeClassNames = ['sidebar-toc-active', 'sidebar-overview-active']; + if (sidebar.classList.contains(activeClassNames[index])) return; + + const panelContainer = sidebar.querySelector('.sidebar-panel-container'); + const tocPanel = panelContainer.firstElementChild; + const overviewPanel = panelContainer.lastElementChild; + + let postTOCHeight = tocPanel.scrollHeight; + // For TOC activation, try to use the animated TOC height + if (index === 0) { + const nav = tocPanel.querySelector('.nav'); + if (nav) { + postTOCHeight = parseInt(nav.style.getPropertyValue('--height'), 10); + } + } + const panelHeights = [ + postTOCHeight, + overviewPanel.scrollHeight + ]; + panelContainer.style.setProperty('--inactive-panel-height', `${panelHeights[1 - index]}px`); + panelContainer.style.setProperty('--active-panel-height', `${panelHeights[index]}px`); + + sidebar.classList.replace(activeClassNames[1 - index], activeClassNames[index]); + }, + + getScript: function(src, options = {}, legacyCondition) { + if (typeof options === 'function') { + return this.getScript(src, { + condition: legacyCondition + }).then(options); + } + const { + condition = false, + attributes: { + id = '', + async = false, + defer = false, + crossOrigin = '', + dataset = {}, + ...otherAttributes + } = {}, + parentNode = null + } = options; + return new Promise((resolve, reject) => { + if (condition) { + resolve(); + } else { + const script = document.createElement('script'); + + if (id) script.id = id; + if (crossOrigin) script.crossOrigin = crossOrigin; + script.async = async; + script.defer = defer; + Object.assign(script.dataset, dataset); + Object.entries(otherAttributes).forEach(([name, value]) => { + script.setAttribute(name, String(value)); + }); + + script.onload = resolve; + script.onerror = reject; + + if (typeof src === 'object') { + const { url, integrity } = src; + script.src = url; + if (integrity) { + script.integrity = integrity; + script.crossOrigin = 'anonymous'; + } + } else { + script.src = src; + } + (parentNode || document.head).appendChild(script); + } + }); + }, + + loadComments: function(selector, legacyCallback) { + if (legacyCallback) { + return this.loadComments(selector).then(legacyCallback); + } + return new Promise(resolve => { + const element = document.querySelector(selector); + if (!CONFIG.comments.lazyload || !element) { + resolve(); + return; + } + const intersectionObserver = new IntersectionObserver((entries, observer) => { + const entry = entries[0]; + if (!entry.isIntersecting) return; + + resolve(); + observer.disconnect(); + }); + intersectionObserver.observe(element); + }); + } +}; diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 00000000..9d1fe84b --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,1341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +前端路上 - 万物皆有裂缝 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

以前对于正则是非常惧怕的,因为看不懂和学不会。但最近项目中频繁的使用到了正则,因此强迫自己去学习了解,慢慢的体会到了他的魅力与强大。当然学习正则初入门的时候有些枯燥难懂,但越学越觉得轻松。本文不准备说关于正则本身的事儿,而是说一说关于javascript中关于正则的几个方法中被很多人忽略的地方。

+
+

工具

说到正则,很多人都是从抄到改到自己写,这个过程可能有时候很漫长。如一些工具能帮助你快速分析和学习正则,那么学习的过程你肯定要轻松得多。下面我推荐两个我经常使用的正则在线可视化工具,正则可视化工具图解符合铁路图规律(其实不明白什么是铁路一样很容易看懂,只是一些细微的地方和我们的常规思维有点差别)。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

在vue组件中,在style标签上添加scoped属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的,这是一个非常好的机制。但是为什么要慎用呢?在实际业务中我们往往会对公共组件样式做细微的调整,如果添加了scoped属性,那么样式将会变得不易修改。初写这篇文章时,本人没有找到一个好的方法去解决这个问题,后来经过大伙的解答,才让我恍然大悟。

+
+

何为谨慎使用

谨慎使用不是不用,而是持一种审视的目光去看待它。scoped肯定是解决了样式私有化的问题,但同时也引入了新的问题—样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的。所以我才说要谨慎使用

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

众所周知,虽然javascript中关于时间的API有不少,我们可以通过方法单独的获取年、月、日、时、分、秒、毫秒…貌似很多,最近写了一个日历(以前写的,但写得很烂,最近优化一下),所以下面简单的记录一下如何写一个日历,列出了一些我在写日历过程中自己封装的一些方法

+
+

效果图

先来一张效果图,由于没有UI设计,所以就自己简单的设计了一个样式(好歹我也是设计专业的,虽然已不做设计很多年),虽然略丑,但重要的是功能!!!

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

for...infor...of都是用于数据的遍历。for...inES5标准,用于遍历对象属性(键),而for...ofES6标准,是对for...in的修正,用于遍历对象元素(值),for...of兼容性不是很好(除了PC端老顽固IE之外,移动端某些安卓机和浏览器也是不支持它,具体可以查看MDN)。

+
+

for…in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i in arr) {
console.log(i); // "0", "1", "2", "msg", "arrCustom", "objCustom"
}

for (var i in arr) {
if (arr.hasOwnProperty(i)) {
console.log(i); // "0", "1", "2", "msg"
}
}
for (var i in obj) {
console.log(i); // "name", "w", "msg", "objCustom"
}

for (var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i); // "name", "w", "msg"
}
}
+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

目前,web网页使用桌面通知功能的越来越多,包括微博,腾讯视频等大厂站,桌面通知功能是H5的一个API - Notifications。它允许网页或应用程序可以发出通知,通知将被显示在页面之外的系统层面上(通常使用操作系统的标准通知机制,但是在不同的平台和浏览器上的表现会有差异),这样即使应用程序空闲或在后台也可以向用户发送信息。

+
+

应用场景

Notifications的诞生简化了网站或者应用与用户之间的沟通成本(时间成本和开发成本),增强用户黏性(减少了用户离开应用的可能)。传统的通知方式,大多是通过站内信(消息),邮件,短信等方式,它们通常需要刷新(跳转)页面、离开应用打开其他应用或终端来查看消息;而桌面通知功能大大的简化了这个过程,消息的传递基本不消耗时间(如果不设置setTimeout,用时基本不会超过1s),并且用户不需要离开应用,这都带来了极大的方便。可以预见,Notifications将会在很多网页或应用中被大量使用。当然Notifications也具有它的局限性:无法存档、即看即毁
那么,这个功能到底能用在哪些场景呢?只能说能应用的场景很多:

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

mocha+chai是比较流行的测试框架‘套装’,能实现一些基本的前端测试如函数功能测试,模拟登陆,异步测试,流程测试等

+
+

官方文档

mocha

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

前端测试是保证代码质量以及程序稳定的一种可靠方式,同时也从代码层面解决了自测难,自测烦等前端测试综合症。当然在前端开发引入测试环节无疑会增加人力和时间成本,如果最终产生的结果和增加的成本能够两两抵消产生正面效应,那么有必要考虑把测试环节加入到团队的规划中。但是在实际开发过程中,测试用例更多的被用于开源项目中,在大部分公司的生成项目很少使用维护测试用例,其根本原因是不纯粹的生产环境导致维护测试用例变得困难,而目的性很强的开源项目则更纯粹,所以前端测试的发展任重而道远。

+
+

Ps:本次更多的是讲述概念性的东西,代码性的具体实例不做过多实践

+
+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

nvm:一个node和npm的版本管理器(node&npm version manager),能让你快速的在不同版本间切换。

+
+

安装

下载地址:官网下载
有两种版本nvm-noinstall.zip(便携版)和nvm-setup.zip(exe安装版)
两者唯一区别就是便携版需要手动配置全局变量,而安装版只需要在安装时选定安装目录则会自动配置好。

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

虽然有了git的图形化工具,但个人还是喜欢命令行,并且结合alias功能,也能非常快速的完成很多功能。记录一些项目中常用的git命令,方便查阅

+
+

查看状态

可以查看工作区(edit即add之前)和暂存区(add之后,commit之前)

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

ps: 前文有我记录的关于Babun的一些特点,以及使用,请看windows平台下超强的cmd工具Babun使用笔记一文

+

问题描述

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 00000000..cffd5617 --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,1355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +前端路上 - 万物皆有裂缝 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

类型判断是我们在编程中常遇到的棘手问题,严格的变量类型约束会为代码减少很多致命的BUG。本文是对常用到的类型判断的一个整理,以求以最简洁的方式来判断变量的类型。
本文所有的如果没特指,都是基于ES5的原生javascript

+
+

变量的基本类型

JavaScript变量包含两种不同的数据类型的值:基本类型引用类型。基本类型是指简单的数据,有NumberStringBooleanUndefinedNull(null可以算作是一个特殊的基本数据类型),而引用类型指那些可能包含多个值的对象,有ObjectArrayDateRegExpFunction等。在JavaScript中,我们通过var来声明变量,由于JavaScript弱语言类型,我们无法在申明的时候规定他的类型,JavaScript变量的类型是随变量的值改变而改变的。为了代码的安全性,在有些情况下我们要判断变量值的类型,如何正确的判断变量的类型就成了一个比较有深度的问题。下图列举一些常见的类型:

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

Babun

官方贴出了Babun的十大特性

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

使用官方提供的vue-simple-template配置打包,写的一个简单的vue-datepicker。支持选择功能,功能比较简单,欢迎大家拍砖.

+
+

效果预览(demo)

vue-datepicker

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+ + +
+

与其在上一秒中沉醉,不如努力拥抱下一秒.

+
+

匆匆而去的2016

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

移动端越来越被大众所接收,那么相应的技术就越来越向它靠拢,这是一种不可阻挡的趋势,也是万物发展的规律。移动端有三大难题:兼容、调试和适配。这三大问题就好像三座无法逾越的大山阻挡者我们前进的步伐,此文将记录我在项目中关于移动端适配的一些方式,供大家参考

+
+

分析

移动端适配的根本原因

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

有人说为什么会使用webpack+gulp呢?强大的webpack完全可以摒弃gulp了嘛?话虽如此,但个人觉得webpack配置太繁琐复杂,相对来说gulp更简单一点,并且gulp也能很好的完成我期望的任务。所以我想用webpack来处理js任务(因为它支持AMD和CMD,并且可以直接引入模块),用gulp处理images/css/html等资源

+
+

ps: 平时在项目中使用它们的机会不多,以下都是自己项目之外的折腾,如果有错误之处,请不吝指出。

+

demo

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

正则的魅力在于使用很简洁的方式解决一些比较复杂的方式,使代码变得更优雅,也使实现的过程变得更简单透明。本文搜集整理一些常用正则,记录以便查阅

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var regex = window.regex || (function (document, $) {
var _reg = {};
/* 'pwd':/^[\@A-Za-z0-9\!\#\$\%\^\&\*\.\~]{6,16}$/,//密码 */
//验证数字
$.extend(_reg, {
'num': /^\d+$/, //数字
'znum': /^[1-9](\d+)?$/, // 大于0的数字
'float': /^[-]{0,1}(\d+)[\.]+(\d+)$/, //浮点数
'money': /^\d{1,12}(?:\.\d{1,3})?$/, // money
'idCard': /^\d{15}$|^\d{18}$|^\d{17}[xX]$/, //身份证
'idCardStrict':/^(\d{6})([1-2])(\d{3})((?:0[1-9])|(?:1[0-2]))((?:0[0-9])|(?:[1-2][0-9])|(?:3[0-1]))(\d{3})(\d{1})$/,
'qq': /^[1-9]\d{4,15}$/, //QQ
'pwd': /^[\@A-Za-z0-9]{6,16}$/, //密码
'areacode': /^(0[1,2]{1}\d{1})$|^(0[3-9]{1}\d{2})$/, //区号
'tel': /^\d{7,8}$/, // 固话格式
'mobile': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$/, //验证手机号码
'telephone': /^(((\+)?86)|(\(\+86\)))?-?((((0)?[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,12}))-?(\d{1,8})?$/, //验证固定电话
'phone': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$|^((\+86)|(\(\+86\)))?-?(((0[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,8}))$/, //手机号码和固定电话
'zipcode': /^\d{6}$/ //验证邮编
});
//验证字符串
$.extend(_reg, {
'email': /^\w{1,16}([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, //邮箱
'chinese': /^[\u4E00-\u9FA5]+$/, //仅汉字
'char': /^[A-Za-z]+$/, //仅仅是字母
'charn': /^[A-Za-z0-9]+$/, //数字加字母
'nospecial': /^[\u4E00-\u9FA5A-Za-z0-9]+$/, // 不包含特殊字符
'url': /^((http|https|ftp):\/\/)?(\w(\:\w)?@)?([0-9a-z_-]+\.)*?([a-z0-9-]+\.[a-z]{2,6}(\.[a-z]{2})?(\:[0-9]{2,6})?)((\/[^?#<>\/\\*":]*)+(\?[^#]*)?(#.*)?)?$/,
'loginName': /^(13|14|15|18|17)[0-9]{9}$|^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, // 用户名
'userName': /^[\u4e00-\u9fa5]{2`,4}$|[a-zA-Z]{4,20}$/, //真实姓名
'nickName': /^([a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{3,19})$/ //昵称
});
return _reg;
})(document, window.jQuery);
window.regex = regex;
+ + +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

Javascript的Array(数组对象)方法整理,对比他们的功能,返回值,分析他们的参数,以及具体的作用。

+
+

length:获取数组长度

+
    +
  • Method: Arry.length
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: 无
  • +
  • Return: 返回被引用数组长度
  • +
+
+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

工欲善其事,必先利其器。git一个不可或缺的利器,其魅力值得我们慢慢品尝!

+
+

安装

官网下载最新版本安装,然后查看是否安装成功

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+

神奇的url

一条url包含了很丰富的信息,那么我们如何来获取这些信息并有效的加以利用呢?
随便举个例子:https://github.com/search?utf8=%E2%9C%93&q=javascript
这条url就是在github上搜索javascript后跳转页面对应的url。我们要做的就是获取’?’后面的参数,以及获取后可以用来做什么。

+

获取参数

+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/page/4/index.html b/page/4/index.html new file mode 100644 index 00000000..3db0e972 --- /dev/null +++ b/page/4/index.html @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +前端路上 - 万物皆有裂缝 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + +
+

+ +

+ + +
+ + + + +
+
+

利用jquery的插件jquery.animateNumber实现一个简单的数字滚动效果

+
+

需求分析

    +
  1. 处理数据:因为数据是后端提供,所以有可能格式不是我们想要的,所以也许需要格式化数据;
  2. +
  3. 根据页面设计的效果图(如图),需要把数字字符串拆分成单个数字字符串
    animateNumber_01
  4. +
  5. 每一个数字进行滚动变化
  6. +
  7. 最后,在项目中,我选取了插件jquery.animateNumber来实现滚动效果。这个插件的使用方式很简单,在官方有很详尽的文档来展示各个案例,就不一一赘述了。
  8. +
+ +
+ + 阅读全文 » + +
+ + + +
+ + + + + +
+
+ +
+
+
+ + + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/05m1i396.html b/post/05m1i396.html new file mode 100644 index 00000000..d7cb49c3 --- /dev/null +++ b/post/05m1i396.html @@ -0,0 +1,545 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 +

+ + +
+ + + + +
+

在 Git 工作流中,有时需要在同一台电脑上针对不同的项目设置不同的用户身份。例如,开发人员可能需要在个人项目和公司项目之间切换,而这两个项目可能需要不同的 Git 用户名和电子邮件地址。Oh My Zsh 提供了强大的自定义功能,允许我们通过编写自定义的 shell 函数来实现这一需求。本文将展示如何使用 Oh My Zsh 的 chpwd 函数来自动设置特定目录及其子目录、孙目录下的 Git 用户配置。

+
+

开始之前

假设你对git身份信息和ssh key概念有比较清晰的认识和了解其基础使用,比如git身份信息是用户名和邮箱,在你使用git commit的时候进行记录;而ssh key是一个凭证(分为公钥和私钥),用于远端身份信息验证,和git没有直接关系,也可以用于其他场景使用,比如ssh登录远程主机。

+

简单说下为什么要使用不同的git身份信息呢?
公司项目要求使用规定的name(比如企微名,花名或者公司内部系统的唯一标识等)和email(公司邮箱)作为git提交记录,这样方便团队协作和代码管理,这些信息有一定的敏感性。所有在提交代码到外网的时候必须考虑使用不同的git身份信息来提交。

+

当然我们可以在每次克隆一个新项目的时候,根据不同需要使用git config –local进行设置,但这明手动操作容易遗忘而且繁琐,所以想到使用脚本来自动化完成。

+

最终效果

本文不是解决:不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的ssh key凭证这个问题,而且为了解决以下问题:

+
    +
  • 不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的git身份信息
  • +
+

最终想达到:

+
    +
  • 进入到指定目录及其子目录,孙目录,如果是git仓库,就自动设置对应的git身份信息
  • +
+

约定目录结构:

1
2
3
4
5
/Users/developer/teamA         # 根目录
└── projectA # 子目录,团队项目
└── src # 孙目录

/Users/developer/teamB
+ +

解决方案

解决这个问题的方案有很多,下面分享一种使用zsh脚本的方案。首先无论哪一种方案,都需要在全局设置全局git信息:

+
1
2
git config --global user.name "name1"
git config --global user.email"email1@email.com"
+ +

安装 Oh My Zsh

如果尚未安装,通过以下命令安装(已经安装就跳过):

+
1
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
+ +

编辑 .zshrc 文件

添加 chpwd 函数:在 .zshrc 最后添加以下函数

+
1
vi ~/.zshrc
+ +

确保启用了plugin:

+
1
2
# 保证这一行不被注释, 括号的内容可能不一样
plugins=(git)
+ +

.zshrc 最后,添加 chpwd 函数:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 chpwd() {
# 指定目标目录
local root_dir="/Users/developer/teamA" # 指定目录
# 指定该目录要设置的git name信息
local user_name="name"
# 指定该目录要设置的git email信息
local user_email="email@email.com"
# 获取到当前目录
local current_dir="$(pwd -P)"
# 如果当前目录是否是目标目录,或者是否其子目录,孙目录
if [[ "$current_dir" == "$root_dir"* ]]; then
# 输出当前目录,用于调试,后续可删除
echo "chpwd: $PWD"

if [ -d "$current_dir/.git" ]; then
git -C "$current_dir" config --local user.name "$user_name"
git -C "$current_dir" config --local user.email "$user_email"
echo "chpwd: 成功设置 user.name 和 user.email"
fi
fi
}
# 执行 chpwd 函数
chpwd
+ +

为了让更改生效,需要重新加载 .zshrc 文件。在终端中运行以下命令(或者关闭后重新打开)

+
1
source ~/.zshrc
+ +

ps: 当然你如果使用的其他命令行终端,可以参考这个脚本,根据终端的特点,自行配置

+

测试

在vscode中打开控制台:

使用vscode打开项目/Users/developer/teamA/projectA,并且打开控制台,如果发现有成功输出信息,就表示成功了。

+
1
chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。如果输出的信息是你预期的表示脚本成功了。

+

同样,使用vscode打开一个不在/Users/developer/teamA目录下的项目,如果没有输出该消息说明符合预期的

+

测试cd命令

打开zsh终端,使用cd命令进入/Users/developer/teamA/projectA,如果发现有成功输出信息,就表示成功了。

+
1
2
3
cd /Users/developer/teamA/projectA

chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。

+

同样,进入/Users/developer/teamB/projectB:

+
1
cd /Users/developer/teamB/projectB
+ +

如果没有输出该消息说明符合预期的

+

方案限制

必须限制不同的远程仓库放到对应的目录,如果你现在的本地项目已经分散到不同的目录了,就必须要重新移动一下本地目录或者重新clone一下远程仓库到对应目录

+

总结

通过 Oh My Zsh 的 chpwd 函数,我们能够自动化 Git 用户信息的设置,这不仅提升了工作效率,也减少了配置错误的可能性。本文提供的步骤和测试验证了解决方案的有效性,展示了 Oh My Zsh 在自动化 shell 任务中的实用性。

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/12ie522i.html b/post/12ie522i.html new file mode 100644 index 00000000..1c873c8c --- /dev/null +++ b/post/12ie522i.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +利用javascrit获取url传递的参数 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 利用javascrit获取url传递的参数 +

+ + +
+ + + + +

神奇的url

一条url包含了很丰富的信息,那么我们如何来获取这些信息并有效的加以利用呢?
随便举个例子:https://github.com/search?utf8=%E2%9C%93&q=javascript
这条url就是在github上搜索javascript后跳转页面对应的url。我们要做的就是获取’?’后面的参数,以及获取后可以用来做什么。

+

获取参数

window.location的对象方法

window.location的参数

+

获取url所有的参数

我们可以直接通过window.location.search来取得这部分,也就是我们需要的url参数。

+

url没有包含?时,window.location.search会返回undefined

+
1
2
3
4
5
6
7
8
9
10
11
12
13
function getUrlVal(str){
if(!str || str.indexOf('?') != 0) return false;
var urlValArry = str.replace('?','').split('&');
var urlValObject = {};
for(var i in urlValArry){
urlValObject[urlValArry[i].split('=')[0]] = urlValArry[i].split('=')[1];
};
return urlValObject;
};

// https://github.com/search?utf8=%E2%9C%93&q=javascript
var urlStr = window.location.search.replace('?','');
console.log(getUrlVal(urlStr)); //输出 Object {utf8: "%E2%9C%93", q: "javascript"}
+ +

获取url中指定键名(name)的键值(val)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getOneVal(str,name){
if(!str || str.indexOf('?') != 0) return false;
var afterNameStr = str.replace('?','').split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
// 返回第一个&位置,如果没有'&'则返回字符串长度
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSite
var reslt = afterNameStr.slice(1,strFirstSite);
return reslt;
};

// 'http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614&place=N230&peopleNum=657'
var urlStr = window.location.search;
console.log(getOneVal(urlStr,'time')) //输出20160614
console.log(getOneVal(urlStr,'peopleNum')) //657
+ +

将方法绑定到原型链上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
String.prototype.toObj = function(key){
/*
如果有传入key,那么就只返回key对应的Val(找不到则返回undefined)
如果没有传入key,那么就返回一个object对象
*/
var str = this;
if(str.indexOf('?') != 0) return {};
if(str.indexOf(key) == -1) return undefined;
var tmpArry = str.replace('?','').split('&');
var reslt = {};
for(var i in tmpArry){
var tempKeyVal = tmpArry[i].split('=');
if(!!key) {
if(tempKeyVal[0] != key) reslt = undefined;
reslt = tempKeyVal[1];
break;
}else {
reslt[tempKeyVal[0]] = tempKeyVal[1];
}
};
return reslt;
};
// http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614
var urlStr = window.location.search;
console.log(urlStr.toObj()) //{itemtype:'sport',active:'basketball',time:'20160614'}
console.log(urlStr.toObj('active')) //basketball
+ +

利用正则表达式来获取参数

强大的正则总是让人心生向往,利用正则无疑是最简洁优雅的一种方法

+

获取指定某个参数

1
2
3
4
5
6
7
8
9
10
function getUrlParam(url,name){
if(!name) return;
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
r = url.substr(1).match(reg);
if (r != null) {
return (r[2]);
}else{
return null;
}
}
+ +

获取所有的参数

1
2
3
4
5
6
7
8
9
function parse_url(url){
if(!url) return;
var pattern = /(\w+)=(\w+)/ig;
var parames = {};
url.replace(pattern, function(a, b, c){
parames[b] = c;
});
return parames;
}
+ +

参数的利用

在项目中这些参数有哪些用处呢,下面列举几个比较常用的用处

+
    +
  • 传递数据
  • +
  • 导航定位
  • +
  • 更改状态
  • +
  • +
+

导航定位

什么是导航定位?就是点击导航栏的标签,页面跳转后,对应的标签相应的会突出变化。如下图:
navLocation

+

跳转后有两种情况:
一种ajax异步刷新,只是局部页面发生变化,因为可以直接用点击事件来控制。
另外一种比较常见的方式就是整个页面刷新,这种情况下,点击事件就没用了,就必须另辟蹊径:
1.比较传统的方法就是每个页面里面写一段CSS样式来控制
2.那么另外一种不用说就是通过url的参数来定位咯
假如用每个页面写CSS样式来控制,可以明显感受到的弊端是:每次新的页面都需要修改对应的CSS
那么利用url来控制又需要做哪些事呢?
1.首先需要约定参数,并且后台来传递这些参数
2.然后在导航栏部分,对应的地方加上参数值,这一步,导航栏都是公用模板,并且规则都一样,所以只需要一次添加
3.跳转后定位

+

html代码

1
2
3
4
5
6
<div class="nav">
<a href="/index.htm?nav=index">首页</a>
<a href="/layout/post.htm?nav=post">文章</a>
<a href="/layout/tag.htm?nav=tags">标签</a>
<a href="/layout/about.htm?nav=aboutUs">关于我</a>
</div>
+ +

js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getOneVal(name,urlValStr){
var afterNameStr = urlValStr.split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSit;
var val = afterNameStr.slice(1,strFirstSite);
return val;
};

var thisUrlVal = window.location.search.replace('?','');
var thisNVal = getOneVal('nav',urlValStr);

//定位
$('.nav a').each(function(){
var _this = $(this);
var urlValStr = _this.attr('href').split('?')[1];
var nVal = getOneVal('nav',urlValStr);
if(nVal == thisNVal) {
_this.addClass('on');
}
})
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/130ppwo9.html b/post/130ppwo9.html new file mode 100644 index 00000000..5aad9fa1 --- /dev/null +++ b/post/130ppwo9.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列之常用命令(一) | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列之常用命令(一) +

+ + +
+ + + + +
+

虽然有了git的图形化工具,但个人还是喜欢命令行,并且结合alias功能,也能非常快速的完成很多功能。记录一些项目中常用的git命令,方便查阅

+
+

查看状态

可以查看工作区(edit即add之前)和暂存区(add之后,commit之前)

+
1
git status
+ +

提交到暂存区

1
2
3
4
5
6
//提交某个文件
git add fileName
//提交所有修改文件的三种写法
git add *
git add .
git add --all
+

保存在本地仓库

1
git commit -m "note text"
+ +

提交到远程仓库

其中origin是本地仓库名,remote是远程仓库分支名

+
1
2
3
git push <origin> <remote>
//如提交本覅origin到远程master分支
git push origin master
+ +

分支

查看分支

1
2
3
4
5
6
//查看本地分支
git branch
//查看远程分支
git branch -r
//查看所有分支
git branch -a
+ +

创建分支

1
git branch name
+ +

删除分支

1
2
3
4
5
//删除本地分支
git branch -d name
//删除远程分支两种方法
git push origin :name //冒号不能省
git branch -r -d origin/name
+ +

切换分支

1
git checkout name //如果分支不存在则创建一个名为name的新分支
+ +

合并分支

1
2
3
4
5
6
7
8
//例如:合并分支dev到master
//首先保持dev和master分支最新,即在本地切换到对应分支,各pull一次
//然后切换分支到最终要合并的分支上(此处为master)
git checkout master
//执行本地合并(合并dev到master)
git merge dev
//推送合并到远程
git push origin master
+ +

放弃本地修改强制更新

+

git fetch只是下载远程的库的内容,不做任何的合并;git reset把HEAD指向刚刚下载的最新的版本

+
+
1
2
git fetch --all
git reset --hard origin/master
+ +

回退到某个历史版本

+

首先使用git log命令获取某个历史版本的ID,假设ID是c3470ee7edf566cc359b666d3e27a38220abaf66

+
+
1
2
3
4
//在本地回退到c3470ee7edf566cc359b666d3e27a38220abaf66版本
git reset --hard c3470ee7edf566cc359b666d3e27a38220abaf66
//推送到远程分支,注意:由于本地版本旧于远程仓库版本,这里需要使用-f参数,强制推送
git push -f origin master
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/1mj936z8.html b/post/1mj936z8.html new file mode 100644 index 00000000..8adaff0f --- /dev/null +++ b/post/1mj936z8.html @@ -0,0 +1,712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Javascript常用见问题之变量类型判断终极篇 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Javascript常用见问题之变量类型判断终极篇 +

+ + +
+ + + + +
+

类型判断是我们在编程中常遇到的棘手问题,严格的变量类型约束会为代码减少很多致命的BUG。本文是对常用到的类型判断的一个整理,以求以最简洁的方式来判断变量的类型。
本文所有的如果没特指,都是基于ES5的原生javascript

+
+

变量的基本类型

JavaScript变量包含两种不同的数据类型的值:基本类型引用类型。基本类型是指简单的数据,有NumberStringBooleanUndefinedNull(null可以算作是一个特殊的基本数据类型),而引用类型指那些可能包含多个值的对象,有ObjectArrayDateRegExpFunction等。在JavaScript中,我们通过var来声明变量,由于JavaScript弱语言类型,我们无法在申明的时候规定他的类型,JavaScript变量的类型是随变量的值改变而改变的。为了代码的安全性,在有些情况下我们要判断变量值的类型,如何正确的判断变量的类型就成了一个比较有深度的问题。下图列举一些常见的类型:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型举例
Nullnull
Undefinedundefined、未赋值的变量
Booleantrue、false
Number-1、0 、 1、 NaN
String‘1’、’a’
Array[]、new Array()
Object{}、new Object()
Functionfunction(){}
+

判断他们的类型,第一时间可能你会想到用typeof去检测它们的类型,然后你就崩溃了:明明是Null为什么结果却是oject,明明是Array为什么还是obejct?…因此可以看出typeof方法不是很可靠,我们必须寻找一种行之有效的方法来解决这个问题?请继续往下读(为了方便阅读,下文中所有的para表示要判断的变量):

+
    +
  • isNaN(para)
  • +
  • !para
  • +
  • typeof para
  • +
  • Object.prototype.toString.call(para);
  • +
+

除了上面这些方法,未来可能会有更多方法来增强变量的约束和判断,比如isNumber

+

isNaN(para)

用来判断是否为number类型的专有方法。但是需要注意的是,如果使用typeof判断那么结果会是number

+

!para

常用来判断一个变量是否存在,面对ArryObject引用类型变量时无论是否为空都会被转换成true

+

typeof para

事实证明typeof并不是万能的,在对除Null以外的基本类型变量是相当有威力的,但是对引用类型变量Null时都会被识别成object,但是请注意:

+
1
2
typeof {}; //object
typeof Object; //Function
+

为什么会出现这样的情况呢?因为Object是一个构造函数,而不是object数据类型对象,同理ArrayDateFunction等都是属于构造函数

+

Object.prototype.toString.call(para)

前面的typeof死在半路,无法打探到引用类型变量null的真实情报,但是我们得出了另一个情报:**他们都是obejct**。别慌,我们另外一个强大的武器,可以直指要害,Object.prototype.toString.call(para)

+

判断结果比较表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型isNaN(para)!paratypeof paratoString(para)
Nullnulltruetrueobejct[obejct Null]
Undefinedundefinedtruetrueundefined[obejct Undefined]
Booleantrue/falsetruefalse/truetrue[obejct Boolean]
Number-1falsefalsenumber[obejct Number]
Number0falsetruenumber[obejct Number]
Number1falsefalsenumber[obejct Number]
NumberNaNtruetruenumber[obejct Number]
String‘1’falsefalsestring[obejct String]
String‘a’truefalsestring[obejct String]
String‘’truetruestring[obejct String]
String‘ ‘(中间包含空格)truefalsestring[obejct String]
Array[]/[4]truefalseobejct[obejct Array]
Object{}/{n:4}truefalseobejct[obejct Object]
Functionfunction(){}truefalseobejct[obejct Function]
+

总结方法

根据上面的表格对比,我整理了一些常见的方法。并且再比较结果精准的情况下尽可能的简化比较过程.

+

判断数字(非严格)

字符串’1‘会被识别成number

+
1
2
3
function isNumber(para){
return !isNaN(para);
};
+ +

判断数字(严格)

在必要的情况下使用:此方法会把字符串’1‘识别成string类型

+
1
2
3
function isStrictNumber(para){
return !isNaN(para) && typeof para === 'number';
};
+ +

判断字符串(非严格)

1
2
3
function isString(para){
return typeof para === 'string';
};
+ +

判断字符串(严格)

在必要的情况下使用:此种方法会把字符串’1‘识别成number类型

+
1
2
3
function isStrictString(para){
return isNaN(para) && typeof para === 'string';
};
+ +

判断一般数据类型(即非引用类型)

注意:使用typeof判断null结果为object

+
1
2
3
function isBasicType(para){
return typeof para !== 'obejct';
};
+ +

判断是否为null(不能识别’’)

此方法只能识别null,如果要包含’’,请结合方法isStringNull()一起使用

+
1
2
3
function isNull(para){
return !para && typeof para === 'object';
};
+ +

判断是否为空字符串(不包含空格)

此方法只能识别'',如果要包含null,请结合方法isNull()一起使用

+
1
2
3
function isStringtNull(para){
return !para && typeof para === 'string';
};
+ +

判断是否为undefined

1
2
3
function isUndefined(para){
return typeof para === 'undefined';
};
+ +

判断是否为false

当为null,undefined,'',0,-0,false,NaN

+
1
2
3
function isFalse(para){
return !para;
};
+ +

判断对象(非严格1–所有的obejct对象)

1
2
3
function isAllObject(_v){
return typeof _v === 'obejct';
};
+ +

判断对象(非严格2–除去null的所有object对象)<–> 判断引用类型

1
2
3
function isObject(_v){
return !!v && typeof _v === 'obejct';
};
+ +

判断对象(严格–只识别{}JSON对象)

1
2
3
function isStrictObject(_v){
return Object.prototype.toString.call(_v) === '[object Object]';
};
+ +

判断数组

1
2
3
function isArray(para){
return Object.prototype.toString.call(para) === '[object Array]';
};
+ +

判断对象

这里特指{}类JSON对象

+
1
2
3
function isObject(para){
return Object.prototype.toString.call(para) === '[object Object]';
};
+ +

判断可执行函数

1
2
3
function isFunction(para){
return typeof para === 'function';
};
+ +
+

总结

当我们需要判断其他类型时,完全可以参照上面的表来写出自己的方法哦。
当然现在前端各种流行库不断推陈出新,我们完全可以直接使用别人封装好的库来实现这些功能,比如underscore.jslodash.js等,但是编码的乐趣不就是在于自己解决最本质的问题么。所以即使有这么多的流行库大行其道,也不妨碍我们了解这些知识的初心,说不定哪天你自己也写出一个很火的库呢~
当然随着ES6标准的不断被各大浏览器厂商支持,ES6的普及度越来越广,这些方法都会被内置到原生javascript内部吧(有些方法已经加进去了~)。

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/2145i587.html b/post/2145i587.html new file mode 100644 index 00000000..79441a7d --- /dev/null +++ b/post/2145i587.html @@ -0,0 +1,492 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +慎重用for...in与for...of | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 慎重用for...in与for...of +

+ + +
+ + + + +
+

for...infor...of都是用于数据的遍历。for...inES5标准,用于遍历对象属性(键),而for...ofES6标准,是对for...in的修正,用于遍历对象元素(值),for...of兼容性不是很好(除了PC端老顽固IE之外,移动端某些安卓机和浏览器也是不支持它,具体可以查看MDN)。

+
+

for…in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i in arr) {
console.log(i); // "0", "1", "2", "msg", "arrCustom", "objCustom"
}

for (var i in arr) {
if (arr.hasOwnProperty(i)) {
console.log(i); // "0", "1", "2", "msg"
}
}
for (var i in obj) {
console.log(i); // "name", "w", "msg", "objCustom"
}

for (var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i); // "name", "w", "msg"
}
}
+ +

由上面的例子可以看出,for...in的一些特性:

+
    +
  • 可以对JSON对象(数组和对象)进行遍历
  • +
  • for...in会遍历对象的所有可枚举属性,包括原型,例如一些我们挂载到原型链上的一些methodname
  • +
  • 遍历很有可能不是按照对象的内部顺序(我们预期的)进行
  • +
  • 对数组遍历时index索引为字符串型,在某些时候直接进行几何运算可能达不到预期结果
  • +
+

for…of

for...in貌似强大的同时也带来很多副作用,想要达到预期的记过需要额外的代码来处理,所以for...of应运而生

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i of arr) {
console.log(i); // 2, 9, 5
}
//如果用for...of循环对象,会报错`obj is not iterable`
for (var i of obj) {
console.log(i);
}
+ +

可以看出,for...of方法在for...in上做了优化,并且限制了只能遍历数组。当然在ES5中,具有遍历数组功能的还有mapfiltersomeeveryreducereduceRight等,但是需要注意的是,有些方法不能被break句柄打断循环,使用retun也不能返回到外层,如forEach

+

其实不难看出for...in是属于鸡肋属性了,而for...of由于兼容性原因,在某些地方也应该慎用,即使是移动端也要慎用,应该它并不兼容所有内核。

+

参考

for…in
for…of
for-of循环是遍历实现iterator接口的成员

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/275cl921.html b/post/275cl921.html new file mode 100644 index 00000000..abf6ae96 --- /dev/null +++ b/post/275cl921.html @@ -0,0 +1,572 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何使用Github Actions实现自动化部署Hexo博客 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 如何使用Github Actions实现自动化部署Hexo博客 +

+ + +
+ + + + +
+

Hexo 是一个基于 Node.js 的静态博客框架,它以简洁的速度和易用性受到许多开发者的青睐。如果你的博客部署在github上,那么这里有一种新的部署方式可能让摆脱频繁提交部署的苦恼!GitHub Actions 为自动化部署Hexo提供了一种解决方案,极大地简化了部署流程和节省了时间,可以让我们更多的专注到写作本身。

+
+

为什么使用Github Actions

    +
  • 将文章源文件和部署文件分离,有助于保护源文件中的隐私和你的草稿文件等
  • +
  • 节省手动部署的时间,专注于写作本身
  • +
  • 学习Github Actions相关知识,不折腾不作死发作了
  • +
+

如果你符合以上任何一条,你都应该考虑往下看下去。

+

什么是Github Actions

GitHub Actions 是 GitHub 推出的一项强大的自动化工具,它允许用户在 GitHub 仓库中创建、编辑和运行自动化脚本,这些脚本被称为工作流程(workflows)。这些工作流程可以响应 GitHub 上的各种事件,例如代码被推送、issue 被创建、pull request 被打开或定期调度任务等。

+

GitHub Actions 的工作流程(workflow)由 YAML 文件定义,这些文件放在仓库的 .github/workflows 目录下。每个工作流程文件描述了一系列的作业(jobs)和步骤(steps),定义了运行环境、触发条件、要执行的任务等。

+

前期准备

创建两个仓库

创建部署仓库

    +
  • 建立名为 <你的 GitHub 用户名>.github.io 的公开储存库,若之前已将 Hexo 上传至其他储存库,将该储存库重命名即可
  • +
  • 将 Hexo 文件夹中的文件 push 到储存库的默认分支,默认分支通常名为 main,旧一点的储存库可能名为 master
  • +
  • 开启Github Pages能力,使其可以 https://<你的 GitHub 用户名>.github.io访问
  • +
+

比如笔者使用2ue.github.io储存库用于存在文章编译后的代码,开启github pages后可通过https://2ue.github.io访问

+

创建文章源码仓库

    +
  • 创建一个私有仓库,用于储存文章源代码
    比如,笔者使用myblog储存库存放文章的源文件
  • +
+

至此两个仓库已经准备完了,后面所有的操作都在myblog上进行

+

生成Personal access tokens

+

github提供了access tokens的能力,可以在不登录的情况访问仓库和操作仓库等,所以我们可以通过相关能力来实现自动化部署,但同时也要保管好该token

+
+

Personal access tokens申请地址:Personal access tokens

+

打开网址后,点击 Generate new token -> Generate new token (classic)

+

image.png

+

然后依次填写

+
    +
  • Note: 注释或者名字,按自己喜欢填写符合规则的名字
  • +
  • Expiration:过期时间,可以选择合适的时间,这里我选择的是永不过期,No Expiration
  • +
  • Select scopes:选择权限,勾上repo和workflow,这里的作用是使得这个token具有读写repo的权限和通过github Action更新的能力
  • +
+

拖到页面最底部,点击生成按钮

+

image.png

+

token生成后,会在这里展示刚刚生成的token。
注意:新生成的token只会在创建时显示一次,刷新之后就会消失,记得备份保存,如果忘记了,删除重新生成即可

+

image.png

+

Token填写到myblog仓库

点击:New repository secret ,将刚刚申请的token填入:

+
    +
  • Name:尽量按规范使用大写,多个单词用_分割
  • +
  • Secret:填入刚刚申请的Token
  • +
+

image.png
image.png

+

部署脚本编写

将myblog仓库代码克隆到本地,新增.github/workflow文件夹,然后再新增一个.yml的文件,假设这里叫hexo-deploy.yml,然后将以下代码复制到文件中,保存后使用git提交到myblog仓库

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
name: deploying Hexo project to GitHub pages
on:
push:
branches:
- main # 分支有 push 行为时就触发这个 action

jobs:
build-and-deploy:
runs-on: ubuntu-latest
env:
GITHUB_USER: 2ue
# GitHub Pages仓库 即博客部署需要用到的仓库
DEPLOY_REPO: 2ue.github.io
DEPLOY_BRANCH: main
GIT_USER: 2ue
GIT_EMAIL: xxxx@xxx.com
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0

- name: Set timezone to Asia/Shanghai
uses: szenius/set-timezone@v1.0
with:
# 设置执行环境的时区为 Linux 上海时区
timezoneLinux: "Asia/Shanghai"

- name: Echo current time
run: timedatectl

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16.15.0

- name: Generate pages
run: |
export TZ='Asia/Shanghai'
npm install -g hexo-cli hexo
npm install
hexo clean && hexo g

- name: Git config
run: |
git config --global user.name "${{GIT_USER}}"
git config --global user.email "${{GIT_EMAIL}}"

- name: Pull blog repo && Copy files
run: |
git clone https://github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
rm -rf ${{DEPLOY_REPO}}/*
cp -r ./public/* ./${{DEPLOY_REPO}}

- name: Deploy blog
run: |
echo '>_ Enter repo: ${{GITHUB_USER}}/${{DEPLOY_REPO}} ...'
cd ${{DEPLOY_REPO}}
echo '>_ Pwd current dir ...'
pwd
echo '>_ Show files ...'
ls -al
echo '>_ Set remote git ...'
rm -rf .git
git init
git remote add origin https://${{secrets.DEPLOY_TOKEN}}@github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
git branch -M ${{DEPLOY_BRANCH}}
git add .
echo '>_ Start Commit ...'
git commit --allow-empty -m "Github Action Automated Deployment $(date +'%Y-%m-%d %H:%M:%S')"
echo '>_ Start Push ...'
git push -u origin ${{DEPLOY_BRANCH}} --force
+ +

至此Hexo利用Github Actions自动化部署就实现了

+

部署

将代码提交到github后,打开github的myblog仓库(你自己的文章源文件仓库),点击:Actions可以看到有任务正在执行,以下是执行成功后的界面:

+

image.png

+

如果有失败,可以点击对应的步骤,查询详细情况:

+

image.png

+

当然如果你使用的vscode,也可以安装Github Actions插件,然后再vscode中直接查看执行过程,不过在插件中没有办法查看失败信息,但是可以点击直接跳转到对应页面查看详细信息

+

image.png

+

参考

在 GitHub Pages 上部署 Hexo | Hexo
GitHub Actions 文档 - GitHub 文档

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/2mb6p0h7.html b/post/2mb6p0h7.html new file mode 100644 index 00000000..a64850a0 --- /dev/null +++ b/post/2mb6p0h7.html @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用vue框架造了一个日历控件 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用vue框架造了一个日历控件 +

+ + +
+ + + + +
+

使用官方提供的vue-simple-template配置打包,写的一个简单的vue-datepicker。支持选择功能,功能比较简单,欢迎大家拍砖.

+
+

效果预览(demo)

vue-datepicker

+

项目构建

1
2
3
4
5
6
7
8
9
10
#全局安装vue,vue-cli,webpack,如以安装则跳过
npm install -g vue vue-cli webpack

# 安装依赖
npm install

# 运行项目=>localhost:8080
npm run dev

# 更多的构建信息请参考官网
+ +

更新记录

2017-2-10 15:14:43

+
    +
  • 修正选择日期后高亮错误问题
  • +
  • 修正多出方法中计算时数字会被转化成字符串问题
  • +
+

2016-12-9 10:12:58

+
    +
  • 更新效果图
  • +
  • 修正computed计算时数字会被转化成字符串问题
    1
    const startNum = self.chooseType ? +self.YearChangeSyboml - 4 : 1;
  • +
+

2016-12-8 17:30:04

+
    +
  • 增加输入框唤醒日历
  • +
  • 增加选择功能
  • +
+

2016-12-8 12:10:14

+
    +
  • 上传日历,只有简单展示版本
  • +
+

参考

待整理…

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/3e4nf265.html b/post/3e4nf265.html new file mode 100644 index 00000000..f4c69385 --- /dev/null +++ b/post/3e4nf265.html @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用webpack + gulp构建项目 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用webpack + gulp构建项目 +

+ + +
+ + + + +
+

有人说为什么会使用webpack+gulp呢?强大的webpack完全可以摒弃gulp了嘛?话虽如此,但个人觉得webpack配置太繁琐复杂,相对来说gulp更简单一点,并且gulp也能很好的完成我期望的任务。所以我想用webpack来处理js任务(因为它支持AMD和CMD,并且可以直接引入模块),用gulp处理images/css/html等资源

+
+

ps: 平时在项目中使用它们的机会不多,以下都是自己项目之外的折腾,如果有错误之处,请不吝指出。

+

demo

先上DEMO

+

配置webpack

webpack的有很强大的配置选项,官方中英文文档都已经很详尽。
中文文档
英文文档

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

上面的配置就是对js进行打包处理,当然webpack肯定也可以处理css和images等资源,webpack的强大毋庸置疑,但为什么不用webpack来处理css等任务呢?

+
    +
  • webpack处理css默认情况下会把css合并到js文件,这点很不爽
  • +
  • 如果要把css文件独立处理,则需要额外的配置,有点烦,所以果断用gulp了
  • +
+

执行webpack,看看效果,可以正常运行

+
1
2
3
4
5
6
7
8
9
{ webpackGulpDeom }  » webpack
Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1141ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
+ 5 hidden modules
{ webpackGulpDeom } »
+ +

配置gulp

gulpfile.js同样,gulp的配置文档详情参考官方文档,这里以编译less文档并压缩css文档为例

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'use strict';
var gulp = require('gulp'),
less = require('gulp-less');

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + 'css/*',['less'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less','watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify']);
+ +

ps:这里只列举了一个编译less的任务。

+

在gulp里执行webpack任务

到了这一步,gulp和webpack任务都编写完成了,如果单单是这样是没有意思的,因为每次启动都需要单独的执行两次命令:一次webpack,一次gulp命令,这样无疑是非常糟糕的。所以我们必须得想办法把gulp和webpack连接起来。怎么连接呢?具体的有两种办法:

+
    +
  • 一种是使用gulp-webpack插件。
  • +
  • 另一种是使用gulp-util插件。
  • +
+

那么我们来重写gulpfile.js和webpack.config.js吧

+

利用gulp-webpack插件

重写gulpfile.js

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
webpack = require("gulp-webpack"),
webpackConfig = require("./webpack.config.js");

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//用gulp执行webpack.config.js
gulp.task('buildJs', function () {
var myConfig = Object.create(webpackConfig);
return gulp
.src([entrySrc + 'js/page/*.js'])
.pipe(webpack(myConfig))
.pipe(gulp.dest('dist/js/page')); //出口文件目录,此处配置之后在webpack.config.js中就必须去掉,不然会报错
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

gulpfile.js的变化:

+
    +
  • 增加了可以一个buildJs任务来执行webpack.config.js文件的配置
  • +
  • 相应的监听对象扩大了
  • +
+

现在来重写webpack.config.js,注释掉output项中的path就行了

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
//path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

webpack.config.jsgulpfile.js都配置好了,那么现在只要执行gulp的相关命令就可以了

+
1
2
3
4
5
6
7
8
9
10
11
{ webpackGulpDeom }  » gulp
[14:20:43] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:20:43] Starting 'webpack'...
[14:20:45] Version: webpack 1.13.3
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
[14:20:45] Finished 'webpack' after 1.37 s
[14:20:45] Starting 'default'...
[14:20:45] Finished 'default' after 34 μs
{ webpackGulpDeom } »
+ +

利用gulp-util插件

+

这种方案只需要修改gulpfile.js就行了,webpack.config.js理论上来说不需要任何变化

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
gutil = require('gulp-util'),
webpackConfig = require("./webpack.config.js"),
myDevConfig = Object.create(webpackConfig),
devCompiler = webpack(myDevConfig);

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//引用webpack.config.js对js资源进行打包
gulp.task("buildJs", function(callback) {
devCompiler.run(function(err, stats) {
if(err) throw new gutil.PluginError("webpack:buildJs", err);
gutil.log("[webpack:buildJs]", stats.toString({
colors: true
}));
callback();
});
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

执行结果:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{ webpackGulpDeom }  » gulp
[14:28:59] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:28:59] Starting 'buildJs'...
[14:29:00] [webpack:buildJs] Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1232ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
chunk {0} app.js (app) 357 kB {1} [rendered]
[0] ./src/js/page/app.js 285 bytes {0} [built]
[1] ./src/js/common/jquery-1.9.1.min.js 92.6 kB {0} [built]
[2] (webpack)/buildin/amd-options.js 42 bytes {0} [built]
[3] ./~/vue/dist/vue.common.js 259 kB {0} [built]
[4] ./~/process/browser.js 5.3 kB {0} [built]
chunk {1} common.js (common.js) 0 bytes [rendered]
[14:29:00] Finished 'buildJs' after 1.24 s
[14:29:00] Starting 'default'...
[14:29:00] Finished 'default' after 7.21 μs
{ webpackGulpDeom } »
+ +

两种方案对比

从以上输出结果可以看出:

+

使用gulp-webpack

    +
  • gulpfile.jswebpack.config.js都要修改
  • +
  • 执行命令打印的信息更少
  • +
  • 编译时间更多?
  • +
+

使用gulp-util

    +
  • 只需要修改gulpfile.js,即使以后单独使用其中一个也不需要再做额外修改
  • +
  • 打印信息更丰富
  • +
  • 编译时间更短?
  • +
+

关于编译时间多少这块,我也没弄太清楚,如果有错误,请读者指出。就个人而言是比较喜欢第二种方案的

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/40xchoai.html b/post/40xchoai.html new file mode 100644 index 00000000..dd793b99 --- /dev/null +++ b/post/40xchoai.html @@ -0,0 +1,696 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Javascript系列 - Javascript数组方法 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Javascript系列 - Javascript数组方法 +

+ + +
+ + + + +
+

Javascript的Array(数组对象)方法整理,对比他们的功能,返回值,分析他们的参数,以及具体的作用。

+
+

length:获取数组长度

+
    +
  • Method: Arry.length
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: 无
  • +
  • Return: 返回被引用数组长度
  • +
+
+
1
2
var arry = [1,6,8,'2ue','o90']
arry.length //返回5,arry = [1,6,8,'2ue','o90']
+ +

join:连接数组内各元素组成一个字符串

+
    +
  • Method: Arry.join(str)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: str非必需
      +
    • str不存在时(不传递str)以默认逗号连接元素
    • +
    • str可以为任意字符串,也可以为空(‘’)(字符串为空时,各元素之间无连接符号)
    • +
    +
  • +
  • Return: 返回连接后的字符串
  • +
+
+
1
2
3
4
var arry = [1,6,8,'2ue','o90']
arry.join() //返回字符串1,6,8,2ue,o90,arry = [1,6,8,'2ue','o90']
arry.join('-') //返回字符串1-6-8-2ue-o90,arry = [1,6,8,'2ue','o90']
arry.join('') //返回字符串1682ueo90,arry = [1,6,8,'2ue','o90']
+ +

注意如果需要加数组arry以逗号形式展示到页面,则不需要.join()方法,因为javascript的赋值操作会自动调用.toString()方法,如

+
1
2
3
4
5
6
7
//JS
var arry = [1,6,8,'2ue','o90']
var divBox = document.getElementById('div');
divBox.innerHTML = arry;

//前面赋值的操作将会调用toString方法,因此
console.log(divBox) //<div id="div">1,6,8,2ue,o90</div>
+ +

push:在数组尾部添加一个元素

+
    +
  • Method: Arry.push(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意义
    • +
    • value可以为合法的布尔值,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中,也可以接收多个参数
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arry = [1,6,8,'2ue','o90']
arry.push() //返回5,arry = [1,6,8,'2ue','o90'] 实际没有任何意义
arry.push(true) //返回6,arry = [1,6,8,'2ue','o90',true]
arry.push('dmw') //返回7,arry = [1,6,8,'2ue','o90',true,'dmw']
arry.push('') //返回8,arry = [1,6,8,'2ue','o90',true,'dmw','']
arry.push(3) //返回9,arry = [1,6,8,'2ue','o90',true,'dmw','',3]
arry.push(undefined) //返回10,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined]
arry.push(null) //返回11,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null]
arry.push(['9','8']) //返回12,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8']]
arry.push({key:'hah'}) //返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
//接收多个参数
var arry = [1,6,8,'2ue','o90']
arry.push(true,'dmw','',3,undefined,null,['9','8'],{key:'hah'})
//返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
+ +

unshift:在数组尾部添加一个元素

+
    +
  • Method: Arry.unshift(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中也可以接收多个参数。
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+

.push()方法

+

concat:在尾部添加元素到数组

+
    +
  • Method: Arry.concat(value,…)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回组成的新数组,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,也可以接收多个参数。
    • +
    • 其中当value为数组时,那么添加的是数组中的元素,而不是数组,所以可以用.concat()来连接数组
    • +
    +
  • +
  • Return: 返回组成的新数组
  • +
+
+
1
2
3
4
5
6
7
//返回值为一个新的数组,不改变原数组
//参数为数组时
var arry = [1,6,8,'2ue','o90']
var newArry = arry.concat(['lalal','mof'])
//返回值 newArry = [1,6,8,'2ue','o90','lalal','mof']
//原数组 arry = [1,6,8,'2ue','o90']
//其它情况同push方法一致
+ +

pop:删除最后一个元素

+
    +
  • Method: Arry.pop()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(最后一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+
1
2
3
var arry = [1]
arry.pop() //返回1,arry = []
arry.pop() //返回undefined,arry = []
+ +

shift:删除第一个元素

+
    +
  • Method: Arry.shift()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(第一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+

.pop()

+

reverse:颠倒数组元素顺序

+
    +
  • Method: Arry.reverse()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
1
2
var arry = [1,6,8,'2ue','o90']
arry.reverse() //返回['o90','2ue',8,6,1] arry = ['o90','2ue',8,6,1]
+ +

sort:数组元素排序

+
    +
  • Method: Arry.sort(fun)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: fun非必需
      +
    • fun如果为空,那么默认安装字符编码的顺序进行排序
    • +
    • 如不为空,那么fun必须为函数类型
    • +
    • Arry.sort(fun(value1,value2){}),fun函数参数value1 的值为Arry[i]value2 的值为Arry[i+1],其中0< = i < Arry.length - 1。所以请注意,.sort()方法排序会对被引用数组进行遍历,遍历的次数为Arry.length - 1,而非Arry.length。因为在Arry.length - 1次时,排序其实就已经完成了。
    • +
    +
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
1
2
3
4
5
6
7
var arry = [1,'2ue','o90',6,890,9,7990]
arry.sort()//返回[1, "2ue", 6, 7990, 890, 9, "o90"] arry = [1, "2ue", 6, 7990, 890, 9, "o90"]
var arry = [1,65443,6,890,9,7990]
arry.sort(function(value1,value2){
return value2-value1
})
//返回[65443, 7990, 890, 9, 6, 1] arry = [65443, 7990, 890, 9, 6, 1]
+ +

slice:根据索引返回数组的一部分

+
    +
  • Method: Arry.slice(satrtIndex,endIndex)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments:
      +
    • satrtIndex开始索引(不包含开始索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • endIndex结束索引(不包含结束索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • 最终satrtIndex的实际值必须小于endIndex,且他们所在的那段索引必须与被引用数组的索引有交集,否则返回空数组。
    • +
    +
  • +
  • Return: 根据索引返回数组的一部分,返回值为数组类型
  • +
+
+
1
2
3
4
5
6
7
8
9
//被引用数组值不会改变
var arry = [1,6,8,'2ue','o90']
arry.slice(1,3) //返回[6,8,'2ue']
arry.slice(3,1) //返回[]
arry.slice(-1,2) //返回[]
arry.slice(1,-2) //返回[6,8]
arry.slice(1,-4) //返回[]
arry.slice(-3,-1) //返回[6,8,'2ue']
arry.slice(-1,-3) //返回[]
+ +

splice:移除元素

+
    +
  • Method: Arry.splice(satrtIndex, deleteCount, value, …)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments:
      +
    • satrtIndex开始索引,必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • deleteCount将删除的个数,非必须,且必须为nubmer类型。从start开始,包括start所指的元素在内要删除的元素个数。这个参数是可选的,如果没有指定它,splice()将删除从start开始到原数组结尾的所有元素,小于等于0将不会删除。
    • +
    • value要插入数组的零个或多个值,从start所指的下标处开始插入。可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,可接收多个参数。
    • +
    +
  • +
  • Return: 被移除元素组成的数组
  • +
+
+
1
2
3
4
5
var arry = [1,6,8,'2ue','o90',4,5,6,7]
arry.splice(7) // 返回 [6,7]; arry = [1,6,8,'2ue','o90',4,5]
arry.splice(1,2) // 返回 [6,8]; arry = [1,'2ue','o90',4,5]
arry.splice(-1,1) //返回 [5]; arry = [1,'o90',4]
arry.splice(0,0,2,3,[8,9]) // 返回 []; arry = [2,3,[8,9],1,'o90',4]
+ +

来一张表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法名功能原数组是否改变返回
length获取数组长度NO被引用数组长度
join将数组元素连接起来以构建一个字符串NO转换后的字符串
push在尾部添加元素YES新数组长度
unshift在头部添加元素YES新数组长度
concat在尾部添加元素NO新数组
pop删除最后一个元素YES被删除元素
shift删除第一个元素YES被删除元素
reverse颠倒数组元素顺序YES对数组的引用
sort数组元素排序YES对数组的引用
slice根据索引返回数组的一部分NO根据索引返回数组的一部分
splice插入、删除或替换数组的元素YES被移除元素组成的数组
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/447n9qgo.html b/post/447n9qgo.html new file mode 100644 index 00000000..6b8daa48 --- /dev/null +++ b/post/447n9qgo.html @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Babun导致本地SSH-KEY不可用 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Babun导致本地SSH-KEY不可用 +

+ + +
+ + + + +
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

ps: 前文有我记录的关于Babun的一些特点,以及使用,请看windows平台下超强的cmd工具Babun使用笔记一文

+

问题描述

如果你本地先安装了git命令行工具并生成了ssh-key,再安装Babun之后,可能会导致原有的ssh-key不可用,原因:
安装Babun会添加全局变量Home,指向Babun安装目录下的.Babun/cymwin/home,因此在使用命令生成key时不会在C:\Users\userName\.ssh目录。

+
1
2
ssh -T git@github.com
Permission denied (publickey).
+ +

解决办法

    +
  • 删掉以前目录(C:\Users\userName\.ssh)下的ssh-key。
  • +
  • 生成重新生成ssh key,此时生成的key在.Babun\cymwin\home\userName\.ssh下。
  • +
  • 把生成的key映射到C:\Users\userName\.ssh目录。
  • +
  • 获取权限
  • +
  • 把key关联到相应github账户(此处以github为例)。
  • +
  • 测试ssh key是否可用
  • +
+
1
2
3
4
5
6
7
8
9
ssh -T git@github.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0670 for '/home/Administrator/.ssh/id_rsa' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/Administrator/.ssh/id_rsa": bad permissions
Permission denied (publickey).
+ +

当生成key之后,测试是否联通,你会发现还是报错了,提示权限不够,错误信息为Permissions 0670

+

在终端切换到C:\Users\userName\.ssh目录,执行下面命令

1
ln -s /c/Users/userName/.ssh /home/userName/.ssh
+ +

此操作会把.Babun\cymwin\home\userName\.ssh目录下的ssh key映射C:\Users\userName\.ssh

+

在终端切换到根目录(~),执行以下命令(一般只执行其中一个)

1
2
3
4
5
chmod 400 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh/id_rsa
ssh -T git@github.com
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+ +

参考文章:

+

1.https://github.com/Babun/Babun/issues/327
2.http://stackoverflow.com/questions/9270734/ssh-permissions-are-too-open-error

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/5164uhjd.html b/post/5164uhjd.html new file mode 100644 index 00000000..f683052d --- /dev/null +++ b/post/5164uhjd.html @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +mocha+chai使用记录 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ mocha+chai使用记录 +

+ + +
+ + + + +
+

mocha+chai是比较流行的测试框架‘套装’,能实现一些基本的前端测试如函数功能测试,模拟登陆,异步测试,流程测试等

+
+

官方文档

mocha

chai

基本用法

+

mocha是一个测试工具库,它只纯粹对测试行为(过程)进行描述;而chai是一个断言(推断)库,它可以将测试结果进行各种判断,以此推断是否符合预期,因此两者常常进行组合使用

+
+

安装

1
2
//全局安装
$ npm install -g mocha
+ +

ps: 全局安装之后,mocha命令将会在全局注册,可以在任何地方使用mocha命令

+
1
2
//安装项目依赖
$ npm install mocha chai
+ +

使用

目录结构

1
2
3
4
5
6
7
8
9
├── test //测试用例
│   ├── hooks.js //生命钩子
│   ├── test.js //入口文件
│   └── unit //测试单例
│   ├── add.js
│   └── ...
└── src //业务代码
   ├── add.js
   └── ...
+ +

所有测试代码都在test目录,所有的业务代码都在src目录

+

一个简单的例子

1
2
3
4
5
6
//src/add.js
function add(a, b){
return a + b;
}
module.exports = add;

+ +
1
2
3
4
5
6
7
8
9
//test/unit/add.js
var add = require('../../src/add.js');
var expect = require('chai').expect;

describe('加法函数', function () {
it('1 + 3 = 4', function () {
expect(add(1, 3)).to.be.equal(4);
});
});
+ +
1
2
3
4
$ mocha mocha/unit/add
加法函数
√ 1 + 3 = 4
1 passing (16ms)
+ +

Expect/Should/Assert

The Expect / Should API covers the BDD assertion styles.
+The Assert API covers the TDD assertion style.
+
+

异步

    +
  • promise异步一定要带上done(),用于通知mocha该测试已经完成
  • +
  • 异步通常和参数-t结合一起用
  • +
+

通配符

1
2
3
4
//test/unit目录下add.js和minus.js
$ mocha test/unit/{add,minus}.js
//test/unit目录下所有js后缀的文件
$ mocha test/unit/*.js
+ +

mocha支持shellnode的通配符匹配规则,更多通配符规则可以查看各自的文档

+

命令行参数与配置文件mocha.opts

常用的命令行

    +
  • –recursive
  • +
+

Mocha默认不对指定目录的子级目录匹配,如果需要使自己目录的测试用例运行,则需要加上--recursive

+
    +
  • –reporter
  • +
+

输出报告的格式,默认是--reporter spec,可以用--reporters命令查看有哪些输出格式

+
    +
  • –watch
  • +
+

监听变化,每次修改自动执行test

+
    +
  • –timeout -t
  • +
+

Mocha默认每个测试用例最多执行2000毫秒,如果到时没有得到结果,就报错。对于涉及异步操作的测试用例,这个时间往往是不够的,需要用-t--timeout参数指定超时门槛

+
    +
  • –hlep,-h
  • +
+

查看有哪些命令
mocha.opts放在test目录下,执行mocha命令时回去读取里面的配置
命令行参数可以写在mocha.opts文件内,如

+
1
$ mocha --reporter tap --recursive -t 3000
+ +

等价于
test/mocha.opts文件内容

+
1
2
3
--reporter tap
--recursive
-t 13000
+ +
1
$ mocha
+ +

hooks(钩子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
describe('hooks', function() {

before(function() {
// 在本区块的所有测试用例之前执行
});

after(function() {
// 在本区块的所有测试用例之后执行
});

beforeEach(function() {
// 在本区块的每个测试用例之前执行
});

afterEach(function() {
// 在本区块的每个测试用例之后执行
});

// test cases
});
+ +

可以写在测试用例内(每个describe块内),此时只对当前测试用例有效
也可以写在外部,此时对所有的测试用例有效

+

注意

    +
  • 内置promise对象
  • +
  • ES6需要转码
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/56i1jh21.html b/post/56i1jh21.html new file mode 100644 index 00000000..425c5039 --- /dev/null +++ b/post/56i1jh21.html @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 +

+ + +
+ + + + +

随着mac系统的升级,对app安全控制越来越严,经常遇到一些非官方渠道安装的app,安装后无法打开的情况,如提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”,一般情况下,出现该问题是因为软件没有签名,所以被MAC系统禁止运行。可以尝试以下解决方案

+

老系统:设置允许任何来源下载的App

比较老的版本系统,可以按以下步骤操作:
打开”系统偏好设置 -> 安全与隐私 -> 通用”选项卡,检查是否已经启用了”任何来源”选项。如果没有启用,先点击左下角的小黄锁图标解锁,然后选中”任何来源”。(由于作者mac系统比较新,故此图来源网络)
image.png

+

如果没有”任何来源”的选项,打开终端,输入以下命令:

+
1
sudo spctl --master-disable
+ +

sudo spctl --master-disable 是一个在macOS操作系统中使用的命令行指令,用于修改系统安全策略控制(System Integrity Protection,简称SIP)的设置。SIP 是一种安全特性,用于保护系统文件和目录免受未授权的修改。

+

sudo spctl --master-disable 的作用如下:

+
    +
  1. 禁用 SIP:这个命令会禁用 SIP 功能,允许用户对系统文件进行修改。默认情况下,macOS 会阻止对某些系统文件和目录的修改,以保护系统安全。
  2. +
  3. 需要管理员权限:由于这个命令涉及到系统级别的更改,因此需要使用 sudo 来获取管理员权限。
  4. +
  5. 临时禁用:这个命令的禁用效果是临时的,重启计算机后 SIP 会重新启用。
  6. +
  7. 安全性风险:禁用 SIP 会降低系统的安全性,因为它允许对系统文件进行修改。因此,只有在确实需要修改系统文件时才应该使用这个命令,并且在完成修改后应立即重新启用 SIP。
  8. +
  9. 重新启用 SIP:要重新启用 SIP,可以使用 sudo spctl --master-enable 命令。
  10. +
+

一般来讲到这一步就可以了,但是如果你的系统比较新,你还得继续看下去

+

新系统:使用xattr -rd命令

如果你的系统比较新,或者已经打开了通用 > 信任任何来源安装后还是报错,那么在终端里执行以下命令:

+
1
2
3
sudo xattr -rd com.apple.quarantine /Applications/xxxx.app
# 将xxx替换成app的名字,如果你无法准确知道app名称,可以直接将app拖到终端中
# 按提示输入你的电脑密码即可。
+ +

sudo xattr -rd com.apple.quarantine /Applications/xxxx.app 是一个在macOS操作系统中使用的命令行指令,它用于移除文件或应用程序的扩展属性(extended attribute),具体来说,是移除一个名为 com.apple.quarantine 的属性。

+

这个属性通常在文件或应用程序从互联网下载后被添加,作为macOS的一种安全机制。它提示用户,该文件可能来自不信任的来源,需要确认是否信任并运行该应用程序。这个属性有时也被称为”隔离标记”(quarantine flag)。

+

命令的各个部分含义如下:

+
    +
  1. sudo:以管理员权限执行后面的命令。由于修改文件的扩展属性需要管理员权限,所以这里使用 sudo
  2. +
  3. xattr:这是用于查看和修改文件扩展属性的命令行工具。
  4. +
  5. -rd-r 表示递归地移除属性,-d 表示删除指定的属性。
  6. +
  7. com.apple.quarantine:这是要删除的扩展属性的名称。
  8. +
  9. /Applications/xxxx.app:这是要移除隔离标记的应用程序的路径。xxxx.app 应该替换为实际的应用程序名称。
  10. +
+

使用这个命令后,应用程序将不再显示警告,提示它可能来自互联网。这在安装从可信来源下载的应用程序时很有用,尤其是当用户确信该应用程序是安全的,但macOS仍然显示隔离警告时。

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/5av4m4yi.html b/post/5av4m4yi.html new file mode 100644 index 00000000..900ccb4f --- /dev/null +++ b/post/5av4m4yi.html @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +移动端适配方案 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 移动端适配方案 +

+ + +
+ + + + +
+

移动端越来越被大众所接收,那么相应的技术就越来越向它靠拢,这是一种不可阻挡的趋势,也是万物发展的规律。移动端有三大难题:兼容、调试和适配。这三大问题就好像三座无法逾越的大山阻挡者我们前进的步伐,此文将记录我在项目中关于移动端适配的一些方式,供大家参考

+
+

分析

移动端适配的根本原因

+
    +
  • 屏幕窗口的大小
  • +
  • 设备像素比(devicepixelratio,简称dpr)
  • +
+

很多地方介绍设备像素比的,这里就不做具体探讨,简单总结一下:devicepixelratio(设备像素比,即dpr) = physicalpixel (物理像素) / density-independent pixel(设备独立像素,即dip)。dipdp,(device independent pixels,设备独立像素)与屏幕密度有关。dip可以用来辅助区分视网膜设备还是非视网膜设备。
JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr,但遗憾的是并不是所有的都支持。
在CSS中,可以分别针对屏幕大小和设备像素比做适配:
针对窗口大小,一般使用媒体查询的only screenmin-widthmax-width来适配,也是使用css做适配最常见的一种方式
针对像素比,可以使用-webkit-device-pixel-ratio-webkit-min-device-pixel-ratio-webkit-max-device-pixel-ratio属性,同样他的支持度一样不高(其实是很低),所以几乎没有用武之地。

+

适配方式

移动端适配主要有两大不同的方向:

+
    +
  • 响应式布局:它是根据屏幕大小自动的调整布局位置(非单纯的缩放),实现适配
  • +
  • 自适应布局:它是根据屏幕大小自动的缩放大小,实现适配。
  • +
+

两种方式应用的场景不同,各有优劣,本人对自适应布局使用的比较多

+

解决方案

为了解决这个老大难问题,从最初开始百分比到em,然后到现在rem的使用,都一一体现着技术的滚滚向前。目前是用的最多的也就是rem,他们的区别和有点请自行GG
有了rem这个大杀器,解决问题就变得简单起来,具体请往下看。

+

纯css实现方式 – 媒体查询

使用原生css来实现媒体查询是很繁琐的,因为每个媒体查询都要去设定规则。推荐使用css的预编译器(sass,less,stylus),比较方便。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* 定义规则 */
html {
font-size: 20px;
}
@media only screen and (min-width: 401px) {
html {
font-size: 24px !important;
}
}
@media only screen and (min-width: 428px) {
html {
font-size: 28 !important;
}
}
@media only screen and (min-width: 481px) {
html {
font-size: 30px !important;
}
}
@media only screen and (min-width: 569px) {
html {
font-size: 35px !important;
}
}
@media only screen and (min-width: 641px) {
html {
font-size: 40px !important;
}
}
@media only screen and (min-width: 751px) {
html {
font-size: 50px !important;
}
}
@media only screen and (min-width: 1080px) {
html {
font-size: 60px !important;
}
}
+ +
1
2
3
4
//less 方式调用
@unit: 50rem; //基准单位,根据设计稿来确定。假设:设计稿尺寸为750,那么@unit设置为50rem(1rem=50px更方便下面计算)
.warp{with: 100 / @unit} // 设计稿上元素的尺寸为100px => .warp{with: 2rem}
.warp{with: 10 / @unit} // 设计稿上元素的尺寸为10px => .warp{with: 0.2rem}
+ +

如果这里使用原生css来做,每个尺寸都需要去计算,如果使用预处理器,只需要定义一个变量,计算的事情直接交给它们就行。

+

这样当页面展示在750的屏幕上时,html的font-size50px,那么当设置为2rem的元素显示的尺寸就为2*50px=100px。在其他尺寸的设备也会根据媒体查询设置的不同font-size进行自动缩放适配。
当然上面也提到了,在css中也是可以获取到devicePixelRatio的值,那么为了更精确在写媒体查询的时候可以把它也加上去,这里就不展开了。

+

纯css实现方式 – 计算属性

当然除了媒体查询,还有一种更潮的方式就是利用css3的一些新属性:计算属性和vw属性来实现自动设置根字体大小的目的

+
1
html{font-size:calc(100vw/6.4)} //6.4为psd设计稿尺寸/100
+ +

这套方案几乎是目前最简洁的方案了,并且calcvw在移动端的支持也不错哟。

+

js的实现方式

js的实现方式,参考了网易淘宝的实现方式,对他们进行了整合。并且修复了手机端1px问题

+
    +
  • 网易实现方式是通过设备尺寸动态的设置DOM的根元素字体大小,没有考虑devicePixelRatio的因素;
  • +
  • 淘宝实现方式也是通过设备尺寸动态的设置DOM的根元素字体大小,并且考虑了devicePixelRatio的因素,但淘宝在设置rem时,显得较复杂(不方便写css把px转化成rem);
  • +
  • 1px问题简单点说就是因为devicePixelRatio的存在,css的1px不等于移动端的1px。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function(doc, win, designSize) { //designSize为设计稿的尺寸(宽)

var docEl = document.documentElement,
devWidth = docEl.clientWidth > 1080 ? 1080 : docEl.clientWidth,
dpr = devicePixelRatio || 1,
scale = 1 / dpr,
width = dpr * devWidth,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'onresize', //判断横屏和窗口重置
recalc = function() {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
document.querySelector('meta[name="viewport"]')
.setAttribute('content','width=' + width +
', initial-scale=' + scale +
', maximum-scale=' + scale +
', minimum-scale=' + scale +
', user-scalable=no');
docEl.style.fontSize = devWidth / (designSize / 100) * dpr + 'px';
};

if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);

})(document, window, 750);
+ +

总结

    +
  • 以上三种方案对比,第一种媒体查询是最死板的,基本就是纯体力活。
  • +
  • 利用css的计算属性可以很优雅的解决问题,但是在兼容方面来说,目前还不是很完美
  • +
  • 并且利用纯css也没考虑devicePixelRatio(像素问题)这个因素。
  • +
  • 最后的javascript解决方案则是考虑到了兼容和devicePixelRatio这些因素,但是这里有一个很大的弊端就是:页面在某些情况(性能慢)会经过两次重回(给HTML根设置font-size和设置meta标签时),在网络或者终端性能不是很好的情况用户体验很不错甚至页面错乱的情况。
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/5vm1lh7e.html b/post/5vm1lh7e.html new file mode 100644 index 00000000..12c3c447 --- /dev/null +++ b/post/5vm1lh7e.html @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用conda快速初始化项目python | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用conda快速初始化项目python +

+ + +
+ + + + +

环境准备

如果你想使用conda来管理你的Python环境和依赖,你需要在你的机器上安装Anaconda或Miniconda。Anaconda包含了conda,Python和150+科学包及其依赖。而Miniconda只包含了conda和Python。

+

当你安装好后Anaconda或Miniconda后,你就可以愉快的使用conda管理项目了

+

新建项目

1
2
mkdir test-python-project
cd test-python-project
+ +

创建虚拟环境

为什么需要创建虚拟环境?
Python创建虚拟环境的目的是为了在同一台计算机上同时管理和运行多个独立的Python项目。虚拟环境提供了一个隔离的运行环境,使得每个项目可以拥有自己独立的Python解释器和依赖库,而不会相互干扰。

+

以下是创建虚拟环境的几个主要目的:

+
    +
  1. 隔离项目:每个项目都可以在自己的虚拟环境中运行,避免不同项目之间的依赖冲突。这样可以确保每个项目都能够独立地使用所需的特定Python版本和依赖库。
  2. +
  3. 管理依赖:虚拟环境允许您为每个项目单独安装和管理所需的依赖库。这样可以确保每个项目都使用其特定版本的依赖库,而不会受其他项目的影响。
  4. +
  5. 简化部署:使用虚拟环境可以更轻松地将项目部署到其他计算机或服务器上。您可以将虚拟环境与项目一起打包,并确保在不同环境中具有一致的运行结果。
  6. +
  7. 提高可移植性:虚拟环境使得项目在不同操作系统和计算机上的移植更加容易。您可以在不同平台上创建相同的虚拟环境,并确保项目在各个环境中都能够正常运行。
  8. +
+

总之,创建虚拟环境可以提供一个独立、隔离和可管理的Python运行环境,使得多个项目能够在同一台计算机上同时运行,而不会相互干扰。这为项目开发、依赖管理和部署提供了更大的灵活性和可靠性

+
1
2
3
4
5
6
7
8
# 选择你需要的python版本来创建虚拟环境
conda create --name myenv python=3.10
# 导出虚拟环境配置:保证其他合作成员环境一致
conda env export --name dev > environment.yml
# 其他成员创建虚拟环境,使用以下命令
conda env create --file environment.yml
# 激活虚拟环境
conda activate myenv
+ + +

安装依赖

你应该将这些依赖写入一个requirements.txt文件中,这样其他人在运行你的项目时可以方便地安装这些依赖。

+
1
2
3
4
5
6
7
# 自动将所有依赖导入到requirements中,并生成一个requirements.txt文件
conda list --export > requirements.txt
# 导出当前环境中的所有依赖包,并且不包含构建信息,以避免导出不必要的包
conda env export --no-builds > requirements.txt

# 安装依赖
conda install --file requirements.txt
+ +

那么现在就可以愉快的编写代码了~

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/619on06z.html b/post/619on06z.html new file mode 100644 index 00000000..f0f378f3 --- /dev/null +++ b/post/619on06z.html @@ -0,0 +1,520 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Node.js版本神器之nvm | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Node.js版本神器之nvm +

+ + +
+ + + + +

简介

Node Version Manager (nvm) 是一个易于使用的工具,可以帮助您在不同Node.js版本之间进行切换,以满足不同项目的需求。

+

背景

在软件开发过程中,因项目依赖和兼容性考虑,可能需要使用不同版本的Node.js。通过nvm,可以方便地管理多个Node.js版本,而无需担心全局安装的冲突问题。

+

安装

通用安装

如果你在你的机器(无论是windows,macOs还是linux)上已经安装了node,则可以直接使用npm命令进行安装:

+
1
npm install -g nvm
+ +

安装成功后,你可以跳过后续安装部分,进行阅读使用部分。
如果你没有办法通过以上方式进行安装,请接着进行阅读后续安装部分。

+

在Windows上安装

下载Windows安装程序:nvm-windows releases,然后一路回车安装

+

有时候安装完成后,会出现等nvm命令无法识别类似情况:

+
1
2
3
nvm --verison

nvm not fund
+ +

请检查环境变量,如果不存在,则添加nvm到环境变量PATH中

+

在Mac上安装

使用Homebrew安装

1
brew install nvm
+ +

使用命令行安装

1
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
+ +

安装完毕后,需要将nvm写入到环境变量中, ~/.bash_profile, ~/.zshrc~/.profile 文件中:

+
1
2
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+ +

修改完毕后,执行

+
1
2
3
4
5
source ~/.bash_profile
# 或者
source ~/.zshrc
#或者
source ~/.profile
+ +

在Linux上安装

和macos上安装一样,参照mac上安装-使用命令行安装

+

使用

查看所有可用版本

1
nvm ls-remote
+ +

安装指定版本

1
nvm install <version>
+ +

选择使用版本

1
nvm use <version>
+ +

设置默认版本

1
nvm alias default <version>
+ +

卸载特定版本

1
nvm uninstall <version>
+ +

注意事项

    +
  • 切换Node.js版本时,请确保项目的依赖支持所选版本。
  • +
  • 避免在生产环境频繁更改Node.js版本,以避免潜在的稳定性问题。
  • +
+

参考文章

    +
  • Node Version Manager (GitHub)
  • +
  • How to Use Node Version Manager (nvm) for Node.js
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/6qn1yqkz.html b/post/6qn1yqkz.html new file mode 100644 index 00000000..53772932 --- /dev/null +++ b/post/6qn1yqkz.html @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +前端测试探索 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 前端测试探索 +

+ + +
+ + + + +
+

前端测试是保证代码质量以及程序稳定的一种可靠方式,同时也从代码层面解决了自测难,自测烦等前端测试综合症。当然在前端开发引入测试环节无疑会增加人力和时间成本,如果最终产生的结果和增加的成本能够两两抵消产生正面效应,那么有必要考虑把测试环节加入到团队的规划中。但是在实际开发过程中,测试用例更多的被用于开源项目中,在大部分公司的生成项目很少使用维护测试用例,其根本原因是不纯粹的生产环境导致维护测试用例变得困难,而目的性很强的开源项目则更纯粹,所以前端测试的发展任重而道远。

+
+

Ps:本次更多的是讲述概念性的东西,代码性的具体实例不做过多实践

+
+

GUI(Graphical User Interface)软件测试

+

前端测试不同于后端测试,因为除了一般的逻辑测试以外,由于存在界面交互,所以涉及到模拟用户行为达到测试的目的。由此引入了一个概念:GUI(Graphical User Interface)软件测试,也就是图形用户界面软件测试

+
+

TDD(Test Driven Development) & BDD(Behaviour Driven Development)

TDD很明显的意思是测试驱动开发,也就是说我们可以从测试的角度来检验整个项目。大概的流程是先针对每个功能点抽象出接口代码,然后编写单元测试代码,接下来实现接口,运行单元测试代码,循环此过程,直到整个单元测试都通过。
TDD的好处自然不用多说,它能让你减少程序逻辑方面的错误,尽可能的减少项目中的bug,开始接触编程的时候我们大都有过这样的体验,可能你觉得完成得很完美,自我感觉良好,但是实际测试或者应用的时候才发现里面可能存在一堆bug,或者存在设计问题,或者更严重的逻辑问题,而TDD正好可以帮助我们尽量减少类似事件的发生。
当然,并不是所有的项目都适合TDD,要使用TDD,我认为必须至少具备以下两个条件

+
    +
  • 项目的业务逻辑很清晰,并且程序员对开发逻辑很清晰
  • +
  • 项目模块的复杂度和依赖度不高。如果复杂度高和依赖度高会导致在最开始拆分单元的时候造成很大的困扰,有可能根本不能顺利拆分
  • +
+

BDD行为驱动开发,这里的行为不是指程序员的行为,而是指业务(程序)的逻辑行为,实际上BDD可以看作是对TDD的一种补充,当然你也可以把它看作TDD的一个分支,因为在TDD中,我们并不能完全保证根据设计所编写的测试就是用户所期望的功能

+

如何实现自动化

说一千道一万,新环节的引入必然带来成本的增加,那么我们如何控制增加的成本在合理范围内?很自然的我们想到了使用工具来实现自动化的测试,让机器帮我完成复杂的交互和测试,并自动监控返回错误报警,为我们手动排除问题提供参考

+

可覆盖的测试

+

那到底前端在开发中需要测试哪些东西?在目前技术又可以实现那些测试?

+
+
    +
  • 函数功能测试
      +
    • 全局变量
    • +
    • 公共方法
    • +
    +
  • +
  • 界面&交互测试
      +
    • 事件交互
    • +
    • 数据输入交互
    • +
    • 特征检测
        +
      • 设计图还原度
      • +
      • 图片大小
      • +
      • +
      +
    • +
    • 特殊情况
        +
      • 自适应和响应式测试
      • +
      • 浏览器兼容
      • +
      • 多端测试
      • +
      • +
      +
    • +
    +
  • +
  • 网络请求测试
      +
    • 数据库访问
    • +
    • 模拟用户登陆等
    • +
    • ajax请求
    • +
    +
  • +
  • 直观的错误信息展示
      +
    • 网页表格
    • +
    • 截图
    • +
    +
  • +
  • 性能测试
  • +
  • 回归测试
  • +
  • 自动化
      +
    • 测试用例数据自动化 - 结合mockjs打造假数据
    • +
    • 测试用例自动化创建 - 通过读取源码中的注释来自动生成测试用例?
    • +
    +
  • +
+

业务逻辑/业务代码/测试用例的关系

业务代码的颗粒度与测试用例的复杂度成反比:颗粒度划分越多(细),复杂度越低
业务代码的量与测试用例的量成正比

+

Good

    +
  • 相对于等待问题产生,更倾向于避免可能的问题
  • +
  • 有利于形成团队代码规范,对团队未来成员的扩充是一个很好的约束规范
  • +
  • 对输出的产品有进一步的质量保证
  • +
+

Bad

    +
  • 增加维护测试用例本(时间和人力)
  • +
  • 增加编码复杂度(需要靠如何更友好的进行测试),对团队人员的编码要求提高了
  • +
  • 也许会增加学习成本(并不一定所有人都会写测试用例)
  • +
  • 需要把控测试用例的合理性、覆盖率、通过率
  • +
+

测试框架

PhantomJS/CasperJS

PhantomJS是一个服务器端的支持JavaScript APIWebKit。其支持各种Web标准:DOM处理, CSS选择器, JSON, CanvasSVG。对于web测试、界面、网络捕获、页面自动化访问等等方面。当启动的时候会在内存在开启一个无界面浏览器,以此模拟用户各种操作,可以对界面截图
Casperjs是对PhantomJS的封装,提供了更加易用的API, 增强了测试等方面的支持

+

PhantomCSS

像素对比工具,基于PhantomJs开发,结合了Casperjs截图和ResembleJs图像对比分析

+

Page-monitor

DOM结构对比工具,基于PhantomJS开发,根据DOM结构与样式的对比来对比整个页面的变动部分

+

BackstopJS

主要通过PhantomJScapserJS等工具在不同尺寸下截图,然后根据resemberJS进行像素比对判断是否正常,以实现响应式测试

+

Mocha + Chai

mocha+chai是一个经典的组合主要用来测试函数功能,也能测试异步操作。也有常用chai的超集(拓展库)sion-chai来加强chai

+

Selenium2

Selenium2,它的主要新功能是集成了Selenium1.0以及WebDriver
也就是说Selenium2SeleniumWebDriver两个项目的合并,即Selenium2兼容Selenium,它既支持Selenium API也支持WebDriver APIWebDriver是一个用来进行复杂重复的web自动化测试的工具,意在提供一种比Selenium1.0更简单易学,有利于维护的API。它没有和任何测试框架进行绑定,所以他可以很好的在单元测试中调用。当启动Selenium2时通常会调起一个可见的界面,但也可以通过设置,让它以PhantomJS的形式进行无界面的测试
当然使用Selenium2必须额外的安装每种浏览器的WebDriver
Selenium2上手难度大于PhantomJS

+

NightwatchJs

推特出品,基于Selenium WebDriver API开发,意味着支持浏览器自动化测试,内部集成了mocha+chai并将它加强,同时支持分组测试和单个测试,对语法进行了简化,归纳有以下特点:

+
    +
  • 简单但强大的语法(更符合js书写习惯),只需要使用JavaScriptCSS选择器,开发者就能够非常迅捷地撰写测试。
  • +
  • 开发者也不必初始化其他对象和类,只需要编写测试规范即可。
  • +
  • 使用CSS选择器或Xpath,定位并验证页面中的元素或是执行命令。
  • +
  • 易于扩展,便于开发者根据需要,实现与自己应用相关的命令。
  • +
  • +
+

目前,SeleniumJavaScript的验收测试方面最流行的工具之一,同类的还有PhantomJS。二者都有其独到的方法:Selenium使用WebDriver API,而PhantomJS使用无界面的WebKit浏览器。它们都是非常成熟的工具,都具有强大的社区支持。它们与Nightwatch之间最大的不同,主要是在于语法的简易度以及对持续集成的支持。与Nightwatch相比,SeleniumPhantomJS都拥有更加冗长的语法,这会让编码变得更庞大

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
this.demoTestGoogle = function (browser) {
browser
.url(“http://www.google.com”)
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'The Night Watch')
.end();
};

//也可以
module.exports = {
'step one' : function (browser) {
browser
.url('http://www.google.com')
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
},

'step two' : function (browser) {
browser
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'Night Watch')
.end();
}
};
+ +

对前端框架的支持

在实际开发中,我们可能是用了不同的框架。虽然我们完全可以在把源码编译成普通的HTML/CSS/JS代码然后测试,但是此种方法的弊端也显而易见:不易于自动化,必须等到所有模块开发完成才能测试…为此我们必须寻找某种方式使得测试不收框架的限制

+

Vue

本身可以通过new一个Vue的方式挂载节点达到效果。
下面是一个简单的测试用例,测试.hello h1标签内容是否符合预期

+
1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {
it('should render correct contents', () => {
const Constructor = Vue.extend(HelloWorld)
const vm = new Constructor().$mount()
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App')
})
})
+ +

React

1.官方提供了两种方法:

+
    +
  • 渲染虚拟DOMShallow Rendering
  • +
+

只渲染第一层,不渲染子组件,速度快,返回一个浅渲染的虚拟DOM对象。然后拿到节点的各种信息,进行测试

+
    +
  • 渲染真实DOM节点(renderIntoDocument
  • +
+

renderIntoDocument 方法要求存在一个真实的DOM环境,否则会报错。因此,测试用例之中,DOM环境(即window, documentnavigator 对象)必须是存在的。jsdom库提供这项功能

+
1
2
3
4
5
6
7
import jsdom from 'jsdom';

if (typeof document === 'undefined') {
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = global.window.navigator;
}
+ +

2.Enzyme
Enzyme是官方测试工具库的封装,它模拟了jQueryAPI,非常直观,易于使用和学习,主要提供三种方法:

+
    +
  • shallow
  • +
+

shallow方法就是官方的shallow rendering的封装

+
1
2
3
4
5
6
7
8
import {shallow} from 'enzyme';

describe('Enzyme Shallow', function () {
it('App\'s title should be Todos', function () {
let app = shallow(<App/>);
expect(app.find('h1').text()).to.equal('Todos');
});
};
+ +
    +
  • render
  • +
+

render方法将React组件渲染成静态的HTML字符串,然后分析这段HTML代码的结构,返回一个对象。它跟shallow方法非常像,主要的不同是采用了第三方HTML解析库Cheerio,它返回的是一个Cheerio实例对象。

+
    +
  • mount
  • +
+

mount方法用于将React组件加载为真实DOM节点

+

最后

回到开始,个人认为不要滥用测试,需要合理评估测试用例对团队项目的积极作用和消极作用。不合理或者不恰当的使用测试只会增加工作复杂度和成本。
并且测试用例只是检查代码的工具,所以不要本末倒置以测试用例强行约束业务代码

+

参考

    +
  • 关于TDD、BDD和DDD的一些看法
  • +
  • 虚拟座谈会:代码测试比率、测试驱动开发及行为驱动开发
  • +
  • Mocha
  • +
  • PhantomJS
  • +
  • NightwatchJs
  • +
  • 前端自动化测试探索
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/794v5n58.html b/post/794v5n58.html new file mode 100644 index 00000000..d15fcf70 --- /dev/null +++ b/post/794v5n58.html @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常用正则整理(持续收集) | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 常用正则整理(持续收集) +

+ + +
+ + + + +
+

正则的魅力在于使用很简洁的方式解决一些比较复杂的方式,使代码变得更优雅,也使实现的过程变得更简单透明。本文搜集整理一些常用正则,记录以便查阅

+
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var regex = window.regex || (function (document, $) {
var _reg = {};
/* 'pwd':/^[\@A-Za-z0-9\!\#\$\%\^\&\*\.\~]{6,16}$/,//密码 */
//验证数字
$.extend(_reg, {
'num': /^\d+$/, //数字
'znum': /^[1-9](\d+)?$/, // 大于0的数字
'float': /^[-]{0,1}(\d+)[\.]+(\d+)$/, //浮点数
'money': /^\d{1,12}(?:\.\d{1,3})?$/, // money
'idCard': /^\d{15}$|^\d{18}$|^\d{17}[xX]$/, //身份证
'idCardStrict':/^(\d{6})([1-2])(\d{3})((?:0[1-9])|(?:1[0-2]))((?:0[0-9])|(?:[1-2][0-9])|(?:3[0-1]))(\d{3})(\d{1})$/,
'qq': /^[1-9]\d{4,15}$/, //QQ
'pwd': /^[\@A-Za-z0-9]{6,16}$/, //密码
'areacode': /^(0[1,2]{1}\d{1})$|^(0[3-9]{1}\d{2})$/, //区号
'tel': /^\d{7,8}$/, // 固话格式
'mobile': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$/, //验证手机号码
'telephone': /^(((\+)?86)|(\(\+86\)))?-?((((0)?[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,12}))-?(\d{1,8})?$/, //验证固定电话
'phone': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$|^((\+86)|(\(\+86\)))?-?(((0[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,8}))$/, //手机号码和固定电话
'zipcode': /^\d{6}$/ //验证邮编
});
//验证字符串
$.extend(_reg, {
'email': /^\w{1,16}([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, //邮箱
'chinese': /^[\u4E00-\u9FA5]+$/, //仅汉字
'char': /^[A-Za-z]+$/, //仅仅是字母
'charn': /^[A-Za-z0-9]+$/, //数字加字母
'nospecial': /^[\u4E00-\u9FA5A-Za-z0-9]+$/, // 不包含特殊字符
'url': /^((http|https|ftp):\/\/)?(\w(\:\w)?@)?([0-9a-z_-]+\.)*?([a-z0-9-]+\.[a-z]{2,6}(\.[a-z]{2})?(\:[0-9]{2,6})?)((\/[^?#<>\/\\*":]*)+(\?[^#]*)?(#.*)?)?$/,
'loginName': /^(13|14|15|18|17)[0-9]{9}$|^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, // 用户名
'userName': /^[\u4e00-\u9fa5]{2`,4}$|[a-zA-Z]{4,20}$/, //真实姓名
'nickName': /^([a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{3,19})$/ //昵称
});
return _reg;
})(document, window.jQuery);
window.regex = regex;
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+
    +
  • + 原作者: J.2ue +
  • +
  • + 本文链接: + https://blog.imx0.com/2016/09/05/regex.html +
  • +
  • + 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处! +
  • +
+
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/7ft694p3.html b/post/7ft694p3.html new file mode 100644 index 00000000..b70d4b6b --- /dev/null +++ b/post/7ft694p3.html @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<2016/><2017> | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ <2016/><2017> +

+ + +
+ + + + +
+ +
+

与其在上一秒中沉醉,不如努力拥抱下一秒.

+
+

匆匆而去的2016

入行前端的第二年,也是毕业的第二年。

+

短短两载却有翻天覆地的改变:从大学走入社会,从设计跨行到前端。这其中有必然也有偶然。

+

年初

年初换了新东家,毕业后的第二份工作,有对自身能力的底气不足而提高的渴望,也有对新东家的满怀期望,一切在有条不紊的进行着。2016年2月22日入职,从此多了一个别号二月(2ue)。座位在老大旁边,感到一丝压力。

+

适应与压力

经历了最初的彷徨后,慢慢开始适应新的环境,作为新手的我,跟着大神们一起做项目是开心的,但也同时感受到更多的压力。因此在入职后的几个月进入了疯狂学习模式,逛技术社区,微博关注技术达人,微信关注相关公众号,啃书等等,连早上挤公交地铁的时候都抽时间在手机上学习,当时感觉自己已经疯魔。虽然也就仅仅疯狂了三个月,但这短暂的三个月疯狂却让我焕然一新:从最初的复制粘贴到有自己的思考、从无处可问到能独立解决问题、更加擅于利用网络解决问题、能够更快的阅读新的知识、更好的编码习惯…林林总总,这三个月是很重要的三个月。

+

生活

换了新房东,空间更大,也换了一台新电脑,更方便撸(打)代(游)码(戏)。总之一切还算满意。

+

同事

我其实是个不擅交际,怕生的人儿。因为我说话很笨,所以在新的环境想要和人混熟,总是要花更多的时间去处理这些关系。不过幸运的是遇到一群很好相处的同事,让我更容易的处理好这些问题。所以再后来遇到各种问题后我都是大胆的去问部门大神,充分发挥了死灿烂打的精神(这个三秒,市委应该深有体会)。当然作为一个胖纸,肯定也有一堆饭友,也会利用工作过的闲暇之余去腐败腐败。

+

项目与加班

新东家的项目总是要得比较急,这就常常导致加班和代码的低质量产出。这是我最开始的想法,但到后来发现,诚然这两者是有一定的因果关系,但是有些东西本身就不可力抗(比如项目交付时间节点),如果一直持有因为时间导致自己加班这种心态,而不去提高自身的能力,那么不论你换到哪家公司你都会陷入无尽的加班与抱怨中。加班不可避免,那就努力提高自己,减少加班时间吧。

+

技术浪潮

这一年是技术浪潮爆发的一年。大数据、AI、人工智能、无人驾驶…各种技术名词频频见诸于头条新闻。在这喷薄的大浪中,前端领域也爆发出自己的色彩:MVCMVVCstyluslesssassgruntgulpwebpackngreactvuenode、大前端开发….等等,技术的更新换代不断地推动着前端领域的向前发展。作为一个前端开发者,深深的感受到我大前端的魅力,也越发对技术存有敬畏之心,不断的驱使我追赶它的浪潮,我愿意在它的浪潮中沉浮。

+

我的Github

github是在2015年末注册的,期间一直不知怎么玩。在上半年,学习了git,喜欢使用命令行,也喜欢在github上提交一些东西,后面学习了vue后,慢慢的提交一些简单的demo上去,挺享受这个过程的。对了,我也通过github+hexo托管了一个静态博客,平时自己的写的东西也往上丢,不过貌似没坚持多久…..2017我会更加勤劳。

+

换了个显示器

这是一个不得不说的显示器:前端开发当然得用大的好的显示器,于是三秒写了一年的12Q换来一个帅气的显示器,从此以后心情舒畅,撸码不累,加班更勤。

+

进步与不足

踩着2016的尾巴,我细数了2016发生在我身上的变化,虽有些许进步,但却也说不出个所以然,如果非要说,那么就是自己解决问题的能力强了,眼界开阔了,有自己的思考了,目标清晰了。
相对于进步,不足之处就很明显了,有很多也是我的致命伤,更是职业道路前进的致命伤。

+
    +
  • 习惯
      +
    • 有点小拖拉
    • +
    • 三分钟热情
    • +
    • 不仔细
    • +
    +
  • +
  • 技术
      +
    • 基础不够
    • +
    • 没有创新
    • +
    • 阅读很不够
    • +
    • 至今没有代表作
    • +
    +
  • +
+

有不足,能正视不足,更需要我弥补不足。

+

滚滚而来的2017

2017年似乎要做的事情很多。一件件做,总有完成;一步步走,总有尽头。

+

TODO

    +
  • EMAC 2016-2017
  • +
  • VUE,REACT
  • +
  • NODE
  • +
  • 涨工资
  • +
+

又是新的一年开启,新年愿有一番好景。

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/7rrj4k61.html b/post/7rrj4k61.html new file mode 100644 index 00000000..32c6fab7 --- /dev/null +++ b/post/7rrj4k61.html @@ -0,0 +1,584 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列之新手入门 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列之新手入门 +

+ + +
+ + + + +
+

工欲善其事,必先利其器。git一个不可或缺的利器,其魅力值得我们慢慢品尝!

+
+

安装

官网下载最新版本安装,然后查看是否安装成功

+
1
2
$ git -v
git version 2.7.3.windows.1 //2.7.3为当前版本
+ +

全局个人信息配置

1
2
$ git config --global user.name "username"
$ git config --global user.email "email"
+ +

windows环境也可以打开计算机用户文件夹下的.gitconfig编辑

+
1
2
3
[user]
name = username
email = email
+ +

基本命令

初始化本地仓库

1
2
3
$ cd storage
$ git init //初始化当前目录为本地仓库
Initialized empty Git repository in D:/storages/.git/ //初始化了一个空的目录为本地仓库
+ +

第一次提交(提交到本地)

1
2
$ git add README.md
$ git commit -m "添加项目文档简介" //-m 参数后面跟表示对当前提交的一个简单说明
+ +

查看状态

查看当前文件处于何种状态

+
1
$ git status
+ +

回退

当你addcommit了错误的文件时,可以使用以下命令来撤回add或者commit;
那么你必须首先明白工作区和暂存区是什么?
直白的说.git文件夹所在的目录(即git init初始化的目录)为当前工作区
add之后,add的文件就会进入暂存区
commit之后,暂存区就会清空,commit的文件就会进入本地当前的分支(如master分支)
PS:关于工作区和暂存区更详细的解释可以去看廖雪峰工作区和暂存区

+

丢弃工区的修改

命令git checkout -- filename可以把filename文件在工作区的修改全部撤销

+
1
$ git checkout -- README.md
+ +

撤销暂存区的修改

命令git reset HEAD filename可以把filename文件在暂存区的修改全部撤销

+
1
2
3
4
5
6
$ git reset HEAD README.md
$ git checkout -- README.md //--参数不能省
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
+ +

当然reset 命令,不仅能够撤销暂存区的内容,还能回退版本,即使你错误提交,也可以用reset回滚到之前的版本
如果你提交了错误文件,可以使用以下命令来回退;

+
1
$ git reset --hard HEAD^
+ +

git reset --hard HEAD^表示回退到上一个版本,HEAD后面的参数可以跟commit ID,这个ID可以通过以下命令获得

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git log
commit 97c8460bc1cda8233866686d9cae270e0e0113f1
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 20 15:44:01 2016 +0800

update README.md

.....(中间有10条日志)

commit 2d936223341333384dd41533dd44ba8e0640493c
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 01 13:16:22 2016 +0800

README.md
+ +

commit 97c8460bc1cda8233866686d9cae270e0e0113f1,这一长串就是我们所需的ID,一般情况下,只需要前7位就够了。

+
1
2
$ git reset --hard HEAD 2d93622 //回退到commit 2d936223341333384dd41533dd44ba8e0640493c
$ git reset --hard HEAD~12 //回退到往上第12个版本,也是2d93622这个版本
+ +

提交到服务器

commit后,你做出的修改只是提交到了暂存区,下一步就需要把代码提交到服务器。这里的服务器,可以自己搭建,也可以是公司的,或者第三方的。这里我们将代码提交到github上(假设你是已经有一个github账户,并创建了一个仓库)。

+

本地生成SSH KEY

SSH KEY就是连通本地与github的桥梁,钥匙,使用以下命令来生成:

+
1
$ ssh-keygen -t rsa -C "youremail@example.com"
+ +

然后回车:

+
    +
  • 提示设置密码
      +
    • 如果不设置,直接按回车,然后会提示你确认密码,再按一次回车
    • +
    • 如果设置密码,输入你想设置的密码,并确认,这样以后每次提交需要输入密码
    • +
    +
  • +
  • 提示生成key的文件名
      +
    • 如果不修改,则使用默认文件名id_rsa
    • +
    • 如果你有在该电脑管理多个key或者已经有生成的key占用了该文件名,则需要重命令
    • +
    +
  • +
+

这里简单起见,先不设置密码,并保持文件名默认

+

然后在用户主目录(例如如果是windows系统一般就在:C:\Users\Administrator)找到.ssh,其中id_rsa表示私钥不能泄露和id_rsa.pub表示公钥,用于对外。

+

github设置本地公钥

打开id_rsa.pub,复制里面的全部内容;
进入github账户;
找到 setting
打开SSH KEYS新建一个SSH KEY,名字随便取,然后粘贴id_rsa.pub的内容,保存;

+

测试是否连通

理论上讲,上一步操作无特殊错误,就已经和github连通的,为了放心,我们可以测试一下

+
1
2
3
$ ssh -T git@github.com
Enter passphrase for key '/c/Users/Administrator/.ssh/id_rsa': //如果设置了密码,此处将会提示你输入密码
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+

上面就是成功的提示

+

github仓库和本地仓库关联

有两种方式把本地仓库和github仓库关联起来

+

方法一:clone自己的仓库,简单,并且本地文件目录和github上名字一样

登陆账号,新建仓库(new repository);
进入仓库主页,找到按钮clone or download按钮,复制里面的git@github.com:userName/repositoryName.git;
打开git bash,clone;

+
1
$ git clone git@github.com:userName/repositoryName.git
+ +

就会把这个项目克隆到你当前目录下。此时你就可以顺畅的像github推送你的东西了。

+

方法二:利用命令关联仓库,本地仓库名可以和github的仓库名不一样

假设你github上有一个项目,git@github.com:userName/repositoryName.git

+
1
2
3
4
5
6
$ mkdir testRepository  //新建目录testRepository
$ cd testRepository //切换到这个目录
$ git init //初始化当前目录
$ git remote add origin git@github.com:userName/repositoryName.git
// origin为本地暂存区的名字,为了语义化,建议默认为它
// 注意github上的repositoryName与本地的testRepository可以不一样
+ +

提交(push)

第一次提交

1
$ git push -u origin master
+ +

第二次及以后

1
$ git push origin master
+ +

第一次提交带参数-u是为了让你本地和github的仓库保持同步。

+

更新代码(pull)

当我们在一台设备上对github提交(push)代码之后,在另外的设备上修改这个项目时就必须先从github更新代码,以保持代码的同步

+
1
$ git pull
+ +
+

总结

至此一个比较完整的流程走通了,当然在这些过程中也许还会遇到其它的问题,如,代码冲突,分支,分支合并等等。

github上创建一个新的项目
github repositoryName:testGit
github userName:2ue

+
1
2
3
4
5
6
7
8
9
10
$ makdir testGit   //创建一个空文件夹testGit(名字任意取),做仓库
$ cd testGit //切换到目录testGit
$ git init //初始化testGit为本地仓库
$ echo 'this repository is localhost' > README.md
//创建文件README.md,并写入'this repository is localhost'
//在window下以上操作的第一步和第四步可以在图形化界面中完成
$ git add README.md
$ git commit -m 'add README.md'//提交更新,并注释信息“add README.md”
$ git remote add origin git@github.com:2ue/testGit.git //关联github上的项目
$ git push -u origin master //提交到github
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/8ah3s5p4.html b/post/8ah3s5p4.html new file mode 100644 index 00000000..ae732200 --- /dev/null +++ b/post/8ah3s5p4.html @@ -0,0 +1,612 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Git系列常用命令之放弃修改 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ Git系列常用命令之放弃修改 +

+ + +
+ + + + +
+

Git 是一种流行的分布式版本控制系统,它允许开发者在不同的分支上进行工作,同时保持代码的完整性和历史记录。在使用 Git 进行版本控制的过程中,我们可能会遇到需要放弃当前工作进度的情况,例如,当发现当前分支的修改与主分支的代码不兼容,或者需要切换到另一个分支继续工作时。

+
+

概述

这篇文章会介绍如何在 Git 中放弃对文件的修改,包括以下几个方面:

+
    +
  • 放弃对单个文件/文件夹的修改:如果只想放弃对某个特定文件的修改,可以使用 git checkout 命令。
  • +
  • 放弃所有未提交的修改:如果需要放弃对所有未提交文件的修改,可以使用 git reset 命令。
  • +
  • 撤销已经提交的更改:如果需要撤销已经提交但尚未推送到远程仓库的更改,可以使用 git revert 命令。
  • +
  • 清理未跟踪的文件:有时候,你可能想要删除那些被错误添加到工作目录中但未被 Git 跟踪的文件,可以使用 git clean 命令。
  • +
+

文章可能会通过具体的命令示例和场景,帮助读者理解在不同情况下如何使用 Git 命令来放弃修改,以保持工作流程的流畅和代码的整洁。

+

两个概念

在 Git 中,indexcommit 是两个核心概念,它们在版本控制过程中扮演着不同的角色,理解这两个概念有助于加深对git工作原理的理解:

+

Index(索引)

    +
  • Index 是 Git 用来准备下一次提交的暂存区。你可以将其视为一个准备提交的文件列表,这些文件已经被审查和选择,准备成为下一次提交的一部分。
  • +
  • 当你对工作目录中的文件进行更改并希望将这些更改包含在下一次提交中时,你需要使用 git add 命令将它们添加到 index 中。
  • +
  • Index 允许你在提交之前进行多次修改,并且可以对这些修改进行排序和组织,以确保提交是有意义的。
  • +
  • Index 是一个文件,位于 .git/index 文件中。
  • +
+

Commit(提交)

    +
  • Commit 是项目快照的记录,它包含了项目的某个特定版本。当你执行 git commit 命令时,Git 会将当前 index 中的内容以及一些附加的元数据(如提交信息、作者、时间戳等)打包成一个提交对象,并存储在本地仓库中。

    +
  • +
  • 提交是不可变的,意味着一旦创建,其内容就不能被更改。这保证了项目历史的完整性和一致性。

    +
  • +
  • 提交可以看作是项目的版本号,每个提交都有一个唯一的哈希值,用于标识和引用特定的项目状态。

    +
  • +
  • 提交是 Git 分布式特性的基础,因为它允许开发者在本地进行提交,而不必立即与远程仓库同步。

    +
  • +
+

两者之间的关系可以这样理解:

+
    +
  • 你首先对文件进行修改。
  • +
  • 使用 git add 将这些修改的文件添加到 index 中,这时候修改被暂存,准备提交。
  • +
  • 使用 git commit 将 index 中的内容以及提交信息一起打包,创建一个新的提交对象。
  • +
+

简而言之,index 是准备提交的暂存区,而 commit 是已经提交的快照记录。在进行提交之前,你可以多次修改 index,但是一旦执行了 commit,那么这个提交就是最终的,不可更改的。

+

请深刻理解这两个概念,有助于帮助您理解后面的内容

+

命令

Git Checkout - 切换分支或恢复工作目录树文件

+

更新工作区中的文件,使其与索引或指定的树中的版本一致。 如果没有给出pathspec,’git checkout’也将更新HEAD,将指定的分支设为当前分支

+
+

使用git checkout来切换分支用的很多,但是没想到还可以用来恢复工作目录,可以理解为:
将一个文件从另一个提交中取出,从索引中恢复,所以它不会影响到git add及之后的内容。

+

具体用法如下:

+
1
git checkout -- <filename>
+ +

注意这里的filename是支持通配符匹配的,比如

+
1
2
3
4
5
6
7
8
# 恢复hello.html
git checkout -- hello.html

# 恢复hello.开头的文件
git checkout -- "hello.*"

# 恢复.html结尾的文件
git checkout -- *.html
+ +
+

注意此方法只是将本地文件恢复了,没有对git add 和git commit 产生影响

+
+

Git-reset - 重置当前HEAD到指定的状态

+

复制条目到索索引或者将当前的分支头(HEAD)设置为某个commit

+
+

Git-revert - 还原某些现有提交

+

给定一个或多个现有提交,还原相关补丁引入的更改,并记录一些新提交来记录这些更改。 这要求你的工作区是干净的(没有对 HEAD 提交的修改)。

+
+

Git-clean - 删除工作目录树中未跟踪的文件

+

从当前目录开始,通过递归删除不在版本控制之下的文件来清理工作区。
通常情况下,只有 Git 未知的文件会被删除,但如果指定了 -x 选项,被忽略的文件也会被删除。例如,这对删除所有构建产品很有用。
如果给出任何可选的<路径规范>…​参数,只有那些与路径规范相匹配的路径会受到影响

+
+

一些常见场景

本地修改了一些文件 (并没有使用 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
git checkout -- <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
1
git checkout .
+ +

本地新增了一些文件 (并没有 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
rm  -rf <filename>
+ +
    +
  •   所有文件
  • +
+
1
git clean -xdf
+ +
+

删除新增的文件,如果文件已经已经 git add 到暂存区,并不会删除!

+
+
    +
  •   所有文件和文件夹:
  • +
+
1
git clean -xdff
+ +
+

ps:谨慎操作: 本命令删除新增的文件和文件夹,如果文件已经已经 git add 到暂存区,并不会删除!

+
+

本地修改/新增了一些文件,已经 Git Add 到暂存区,想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
1
git reset HEAD <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
1
git reset HEAD .
+ +

本地通过 Git Add 和 Git Commit 后,想要撤销此次 Commit

    +
  • 撤销 commit, 同时保留该 commit 修改:
  • +
+
1
git reset <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前 6 位。

+
+

撤销之后,你所做的已经 commit 的修改还在工作区!

+
+
    +
  •   撤销 commit, 同时本地删除该 commit 修改:
  • +
+
1
git reset --hard <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前6位

+
+

ps:谨慎操作: 撤销之后,你所做的已经 commit 的修改将会清除,仍在工作区/暂存区的代码也将会清除!

+
+

参考

1.Git - git-checkout Documentation
2.Git - git-reset Documentation
3.Git - git-revert Documentation
4.Git - git-clean Documentation

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/d9bqnro8.html b/post/d9bqnro8.html new file mode 100644 index 00000000..4e05c310 --- /dev/null +++ b/post/d9bqnro8.html @@ -0,0 +1,552 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +windows平台下超强的cmd工具Babun使用笔记 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ windows平台下超强的cmd工具Babun使用笔记 +

+ + +
+ + + + +
+

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+
+

Babun

官方贴出了Babun的十大特性

+
    +
  • Pre-configured Cygwin with a lot of addons: 预置大量的Cygwin插件
  • +
  • Silent command-line installer, no admin rights required:静默命令行安装,不需要管理员权限
  • +
  • pact - advanced package manager (like apt-get or yum): 支持pact高级包管理器,类似于apt-get、yum等
  • +
  • xTerm-256 compatible console: xterm-256兼容控制台
  • +
  • HTTP(s) proxying support: HTTP(s) 代理支持
  • +
  • Plugin-oriented architecture: 插件体系,可以安装丰富的插件
  • +
  • Pre-configured git and shell: 预置git和shell,支持自定义配置
  • +
  • Integrated oh-my-zsh: 集成了zsh
  • +
  • Auto update feature: 自动检测最新版本
  • +
  • “Open Babun Here” context menu entry: 支持右键菜单“此处打开Babun”
  • +
+

当然对于上面这些特性,我不得不补充一点,那就是它强大的命令提示功能,能从根据你的输入匹配历史输入,狠棒!

+

Cygwin

Babun的核心包括一个预配置的Cygwincygwin是一个非常好的工具,但有很多使用技巧,使你能够节省大量的时间。Babun解决了很多问题,它里面包含了很多重要的软件包,是你能够第一时间能够使用它们

+

shell

Babunshell通过调整,已达到最佳的用户体验,Babun有两个配置之后马上使用的shell(默认使用zsh,可以使用bash或者zsh命令切换到对应的模式),Babunshell具有以下的特点:

+
    +
  • 语法高亮
  • +
  • 具有unix的工具
  • +
  • 软件开发工具
  • +
  • git-语义提示
  • +
  • 自定义脚本和别名
  • +
  • +
+

Console

Babun支持HTTP代理,只需添加地址和HTTP代理服务器的凭据。Babunrc文件所在文件夹执行源Babunrc启用HTTP代理。目前还不支持SOCKS代理。

+

开发者工具

Babun提供多种方便的工具和脚本,是你的开发工作更轻松,具有的功能如下:

+
    +
  • 编程语言(python,Perl, etc等)
  • +
  • git(各种各样的别名调整)
  • +
  • UNIX工具((grep, wget, curl, etc)
  • +
  • vcs (svn, git)
  • +
  • oh-my-zsh
  • +
  • 自定义脚本(pbcopy, pbpaste, Babun, etc)
  • +
+

安装

默认安装

双击install.bat脚本,Babun使用默认安装位置C:\Users\userName\.Babun,安装好的Babun会在C:\Users\userName\下;
当然也可以指定安装位置

+

自定义安装

通过cmd命令行在执行install.bat时指定参数/t/target指定安装的目录。
执行:Babun.bat /t install-dir

+
1
Babun.bat /t c:\Babun
+ +

安装好之后会在d:\Babun目录下生成一个.Babun的目录,Babun所有文件都在这个目录中。注意安装目录最好不要有空格,这是cygwin要求的

+

启动Babun默认是在’%userprofile%.Babun\cygwin\home\username’

+

开发环境配置

pip

Babun内置了PythonPerl等解释器。cygwin自带的python没有pip,需手动安装。
直接执行下面这个命令就好了。

+
1
wget https://bootstrap.pypa.io/get-pip.py -O - | python
+ +

有了pip就可以自由的安装诸如ipython之类的东西,还有包罗万象的类库。

+

常用插件

Babun默认是安装了oh-my-zsh的,这里可以根据自身情况安装一些插件。具体可参考利用oh-my-zsh打造你的超级终端一文;

+

包管理器使用

Babun提供一个叫pact包管理工具,类似于linux上面的apt-getyum的包管理工具

+

配置别名(alias)

可以在.Babun\cygwin\home\username目录下配置对应工具的别名,而并不仅限于git-bash
当然记忆别名其实也是体力活,我的想法是对一些常用的命令、经常手滑手速过快打错的命令、复杂的命令配置一些别名,例如

+
1
2
3
4
5
6
gt = git
gti = git
n = npm
nr = npm run dev
gtlg = git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
...等等
+ +

问题

本地SSH-KEY不可用

查看我记录的Babun导致本地SSH-KEY不可用一文

+

中文乱码问题

找了很多解决方案,都不能完美的解决问题,最后还是回归原始:不解决!!!

+

锁定文件夹

在使用Babun时(比如此时进入了a目录),它会锁定文件夹a目录,导致你可能无法做一些危险操作。必须关闭Babun后才能解锁进程

+

参考文章:

+
    +
  • windows下的命令行工具Babun
  • +
+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+
    +
  • + 原作者: J.2ue +
  • +
  • + 本文链接: + https://blog.imx0.com/2017/03/15/babun.html +
  • +
  • + 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处! +
  • +
+
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/f358mgm5.html b/post/f358mgm5.html new file mode 100644 index 00000000..a8d4d182 --- /dev/null +++ b/post/f358mgm5.html @@ -0,0 +1,566 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +一道小小的题目引发对javascript支持正则表达式相关方法的探讨 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 一道小小的题目引发对javascript支持正则表达式相关方法的探讨 +

+ + +
+ + + + +
+

以前对于正则是非常惧怕的,因为看不懂和学不会。但最近项目中频繁的使用到了正则,因此强迫自己去学习了解,慢慢的体会到了他的魅力与强大。当然学习正则初入门的时候有些枯燥难懂,但越学越觉得轻松。本文不准备说关于正则本身的事儿,而是说一说关于javascript中关于正则的几个方法中被很多人忽略的地方。

+
+

工具

说到正则,很多人都是从抄到改到自己写,这个过程可能有时候很漫长。如一些工具能帮助你快速分析和学习正则,那么学习的过程你肯定要轻松得多。下面我推荐两个我经常使用的正则在线可视化工具,正则可视化工具图解符合铁路图规律(其实不明白什么是铁路一样很容易看懂,只是一些细微的地方和我们的常规思维有点差别)。

+
    +
  • regexper 我最常用的一个,个人觉得UI做得比其他好
  • +
  • regulex 备选,他有一个很舒心的功能,可以提供一段js,嵌套到你的网站,生成正则可视化图
  • +
+

一道小小的题目

这道题目是在群里日常闲聊时,公司同事抛出来的,具体是出自哪里本人没去考察。先先说说题目:

+
+

写一个方法使得数字末尾的连续0变成9,如1230000变成1239999

+
+

一道很简单的题目,直接正则就能搞定,也许你会写:

+
1
2
3
4
5
6
7
function zoreToNine(num){
return (num + '').replace(/0/g,9);
}
//或者
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,9);
}
+ +

这也是此题的陷阱所在,按照上面的方法,1023000就会被转化成1923999,这样是不符合要求的,所以改进一下:

+
1
2
3
4
5
6
7
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,function($1){
return $1.replace(/0/g,9);
});
}
zoreToNine(1223000); //1223999
zoreToNine(1023000); //1023999
+ +

关于这个问题的解决方案@微醺岁月同学提供了一种,位置匹配的方法,简单了很多,厉害!

+
1
"12300100000".replace(/0(?=(0+$)|\b)/g,9); //12300199999
+ +

当然解决问题的方法很多,不一定非要用正则,还完全可以使用纯算术的方法实现,大家有兴趣可以尝试,闲话少说进入这次的主题:javascript支持正则表达式相关方法,注意并不是正则对象的方法。
上述方法使用了正则,有趣的是在回调函数里有一个$1,这个$1到底是什么?所有的匹配规则匹配后都有$1这个变量么?…一连串的问题,以前我从来没有去追探过,趁着昨个比较空闲,去追探了一番,并在今天整理了一下,写下此文记录。

+

主角

javascript中正则对象有三个方法:testexeccompile,但是此次的主角并不是它们!我们讨论的是能够使用正则表示的相关方法:searchmatchreplacesplit,注意它们都是String对象的方法,使用它们必须要是String类型.

+

replace(rule[regexp/substr], replacement)

replace是一个用于替换字符串的方法,虽然看似简单,但是它隐藏的机关也是常常被人忽略。具体分析一下它的特点:
它接收两个参数
无副作用不影响原始变量
返回被改变的字符串(一定是字符串类型)

+

定义一些变量,方便全文取用。

+
1
let a = '12309800', b = '12309800[object Object]', b = '12309800{}';
+ +

参数rule

在一般情况,rule参数一般是正则、字符串、数字。
如果是字符串,将会在匹配到第一个符合条件的目标,结束方法;
如果是正则,则按照正则的规则进行匹配

+
1
2
3
4
//匹配第一个0替换成5
a.replace(0,5); //'12359800'
//匹配所有的0替换成5
a.replace(/0/g,5); //'12359855'
+ +

参数replacement

在一般情况,replacement参数是字符串、数字、者回调。

+

包含$的字符串

当参数rule为正则,并且正则至少包含有一对完整的()时,如果replacement包含有$的字符串,那么对于$n(n为大于0的整数,n的长度取决于正则中括号的对数),会被解析成一个变量。但是也仅仅只是作为一个变量,无法在字符串中进行计算,此时更类似特别的字符串模板变量。

+

一般情况下,$n中n的长度取决于正则中括号的对数,$1表示第1对括号匹配的结果,$2表示第2对匹配的结果…在正则所有的括号对中,左括号出现在第几个位置(或者说从左往右),则它就是第几对括号,以此类推。姑且我们把这种规则成为正则匹配分割规则(ps:这完全是我自己取的一个名字,方便文章后面使用和记忆)。

+
1
2
3
4
5
6
a.replace(0,'$0'); //'123$09800'
a.replace(/00/g,'$0'); //'123098$0'
a.replace(/[1-9]0+$/,'$1'); //'12309$1'
a.replace(/([1-9](0+$))/,'$1'); //'12309800',此时$1为[1-9](0+$)匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1'); //'123098',此时$1为[1-9]匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1*$2'); //'123098*00',此处的$1和$2不会安照期待的情况进行乘法计算,要进行计算可以用回调
+ +
+

请注意:虽然目前参数replacement中携带有$n仍然能正常使用,但是这种方式已经不被规范所推荐,更应该使用回调来完成这个操作。这一点谢谢@lucky4同学的指出

+
+

如果正则中包含有全局匹配标志(g),那么每次匹配的都符合上述规则

+

回调函数

先看例子:

+
1
2
3
4
5
6
7
8
9
10
11
12
a.replace(/[1-9]0+$/,function(){
console.log(arguments); //["800",5,"12309800"]、
});
a.replace(/([1-9])0+$/,function(){
console.log(arguments); //["800","8",5,"12309800"]
});
a.replace(/([1-9])(0+$)/,function(){
console.log(arguments); //["800","8","00",5,"12309800"]
});
a.replace(/(([1-9])(0+$))/,function(){
console.log(arguments); //["800","800","8","00",5,"12309800"]
});
+ +

回调函数的arguments数组部分组成:[完整匹配的字符串,$1,$2,…,$n,匹配的开始位置,原始字符串],$1...$n表示每个括号对的匹配,规则和前面的相同。
所以有一下规律:

+
1
2
3
4
5
let arr = [...arguments], len = arr.length;
(len >= 3) === true;
arr[0] = 完整匹配的字符串;
arr[len-2] = 匹配的开始位置;
arr[len-1] = 原始字符串;
+ +

注意:除了匹配的开始位置是Number类型外,其余的都是String类型

+

非常规类型参数

如果参数类型不是上述两种情况,会发生什么呢?看看下面的例子:

+
1
2
3
4
5
6
7
8
a.replace(0,null); //123null9800
a.replace(0,undefined); //123null9800
a.replace(0,[]); //1239800
a.replace(0,Array); //1230,3,123098009800
b.replace({},5); //123098005
c.replace({},5); //'12309800{}'
a.replace(0,{}); //123[object Object]9800
a.replace(0,Object); //12309800
+ +

由上面的例子可以看出,如果非正则也非字符串,则有以下规则:
null变量,则会转换成'null'字符串;
undefined变量,则会转换成'undefined'字符串;
[]变量,则会调用join()方法转换成字符串,默认以,分割,值得注意的是空数组将会被转换成空字符串(没有任何字符),通常会被匹配源字符串的开始位置(默认开始位置为空字符串);
Array变量,则会先转成成一个匹配的数组,形如[完整匹配的字符串,$1,$2,...,$n,匹配的开始位置,原始字符串],然后对它调用join()方法转换成字符串,默认以,分割;
{}变量,则会调用Object.protype.toString.call()方法把{}转换成[object Object];
Object变量,则貌似什么都没做

+

虽然可以传入这些非正常参数,但大多数情况下这些类型的参数对实际是毫无意义的,所以不建议传入以上类型的参数。同上面的正则匹配分割规则一样,为了方便使用称呼,姑且我把上面的转换规则称为正则匹配参数转换规则

+

match(rule[regex/substr])

match方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
该方法类似indexOflastIndexOf,但是它返回指定的值,而不是字符串的位置;

+

参数

参数的传递除了常规的正则和字符串以外,其余所有类型的参数都会按照上述的正则匹配参数转换规则转换成字符串形式来匹配。

+

返回值

返回值根据传入的参数类型和规则的不同,返回的内容不同,但总体来说,它是返回一个对象,而不是索引,如果没匹配到任何符合条件的字符串,则返回null

+

非全局匹配正则

如果匹配规则是一个非全局匹配规则,那么,它此时的返回值是一个伪数组对象(likeArr),形如:[一个展开的匹配到的字符串数组, 匹配到的字符串位置, 原始字符串],它有如下规律:

+
1
2
3
4
5
var likeArr = a.match(regex);
likeArr[0] = 匹配到的字符串;
likeArr[1...n] = 正则匹配分割规则匹配的字符串;
likeArr.index = 匹配到字符串的位置
likeArr.inupt = 原始字符串
+ +

看例子:

+
1
2
3
4
a.match(/[1-9]0+$/); //[0:'800',index:5,input:'12309800']
a.match(/([1-9])0+$/); //[0:'800',1:'8',index:5,input:'12309800']
a.match(/[1-9](0+$)/); //[0:'800',1:'00',index:5,input:'12309800']
a.match(/([1-9])(0+$)/); //[0:'800',1:'8',2:'00',index:5,input:'12309800']
+ +

全局匹配正则

如果匹配规则是一个全局匹配规则(正在携带有g标志),那么,它此时的返回值是一个数组对象(arr),形如:[匹配到的字符串数1,匹配到的字符串数2,匹配到的字符串数3];
看例子:

+
1
2
a.match(/[1-9]0/); //[0:'30',index:2,input:'12309800']
a.match(/[1-9]0/g); //[0:'30',1:'80']
+ +

search(rule[regex/substr])

search方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
stringObject中第一个与rule相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回-1
注意:

+
    +
  • search方法不执行全局匹配,它将忽略标志g
  • +
  • 忽略regexplastIndex属性,总是从字符串的开始进行检索,这意味着它总是返回stringObject的第一个匹配的位置
  • +
+

同样,search可以传入任何参数类型,它会遵循正则匹配参数转换规则进行转换

+

split(rule[regex/substr],len)

这个方法就不用多说,很常用的字符串分割方法。
第二个参数的作用就是限制返回值的长度,表示返回值的最大长度

+

当然,它依然可以传入任何参数类型,会遵循正则匹配参数转换规则进行转换

+
+

有一段加密的后的密码,我们需要分离出字符串’12a344gg333tt445656ffa6778ii99’中的前三组数字,通过某种计算才能得出正确的密码

+
+
1
'12a344gg333tt445656ffa6778ii99'.split(/[a-zA-Z]+/g,3); //['12','334','333']
+ +

最后

写了这么多,突然发现以前仅仅是在用这些方法,了解得很不够深入。越是学习才发现其中的奥秘!学无止境,与诸君共勉!
以上内容如有错误之处,希望诸君不吝指出!

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/g0b67ue4.html b/post/g0b67ue4.html new file mode 100644 index 00000000..f2219ede --- /dev/null +++ b/post/g0b67ue4.html @@ -0,0 +1,495 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常用javascript代码片段 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 常用javascript代码片段 +

+ + +
+ + + + +
+

下面记录的主要是本人在项目中遇到的一些问题的解决方案或者个人觉得精妙的代码,方案也许来自于自己的想法,也有可能来自于网络(我尽量给出出处,以便大家追源溯流),也许干脆就是一个插件库(实际在项目中对于一些小的功能个人是拒绝引入不纯粹的三方库的)。肯定,这其中有些方案也不是尽善尽美,我会一一列出。如果对于某个问题在你看来有更好的解决方案,请不吝指出!

+
+

placeholder属性支持

有时候项目中placeholder也许要兼容某些特殊的浏览器,所以才有了下面这段代码。这种方案有一个缺点就是:如果你恰好要对这个输入框绑定focus事件并操作它的值,必须要小心处理

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//修复不支持placeholder属性 start
const isSurportPlder = "placeholder" in document.createElement("input"); // 判断浏览器是否支持 placeholder
if (!isSurportPlder) {
$("[placeholder]").focus(function () {
const _this = $(this);
if (_this.val() == _this.attr("placeholder")) {
_this.val('');
}
}).blur(function () {
const _this = $(this);
if (_this.val() == '' || _this.val() == _this.attr("placeholder")) {
_this.val(_this.attr("placeholder"));
}
}).blur();
};
+ +

格式化时间

关于格式化时间有很多插件,其中比较有名的就可以列很大一堆出来,比如老牌的moment.js,最近比较多star的luxon.js; 对于为什么不选择他么,上面已经说了原因了。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
formate(fmt, date) {
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
const _rules = [{
rule: '[yY]{4}',
value: date.getFullYear()
}, {
rule: 'M+',
value: date.getMonth() + 1
}, {
rule: '[dD]+',
value: date.getDate()
}, {
rule: 'h+',
value: date.getHours()
}, {
rule: 'm+',
value: date.getMinutes()
}, {
rule: 's+',
value: date.getSeconds()
}, {
rule: 'ms{1,2}',
value: date.getMilliseconds()
}];

_rules.forEach((_r) => {
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join('').substr(rLen);
});
});
return fmt;
}
+ +

解析浏览器的版本等详细信息

浏览器的判断涉及到很多复杂的变量和参数,所以偷懒选取了一个比较好的库,这个库它唯一的功能就是识别浏览器的版本等详细信息。
如果大家有更好的库,请推荐过来!

+

browser.js

+

数字转换成千分位格式(如:123,456,9.89)

1
2
3
function translateThree(num) {
return num.split('').reverse().join('').replace(/(\d{3}(?=\d)(?!\d+\.|$))/g, '$1,').split('').reverse().join('');
}
+ +

判断任意数据的类型

精准的判断类型,'2'会识别成string类型,2会识别成number类型

+
1
2
3
4
5
6
7
8
9
10
function tryType(para) {
const type = typeof para;
if (type === "number" && isNaN(para)) return "NaN";
if (type !== "object") return type;
return Object.prototype.toString
.call(para)
.replace(/[\[\]]/g, "")
.split(" ")[1]
.toLowerCase();
}
+ +

判断是不是一个可计算的数字

上面的tryType方法会将'2'2区别成两种类型,而这个方法将忽略这种区别

+
1
2
3
4
5
function isNumber(para) {
if (window.isNumber) return window.isNumber(para);
if (Number.isNumber) return Number.isNumber(para);
return typeof para !== "undefined" && !isNaN(para);
}
+ +

深度取值防止代码挂掉

在项目中经常会遇到层级很深的json数据,这时候可能就会写类似这样的代码a[2].list[3].name,这种代码很不可靠,很容易由于数据的一点小错误,导致整段js代码挂掉。出的问题多了自然就会思考问题的解决方案,正当我苦思不得其解时,看到一篇文章如何优雅安全地在深层数据结构中取值,他详细的分析了深层取值如何避免报错的情况。
我阅读了博主的文章,整理了一下思路,没有像原博主那样使用xs && xs[x]判断来打断取值,是因为这种情况可能会把0这种类型的值误伤

+
1
2
3
4
function getValueFromDeepData (props, target){
if (!props || !target) return undefined;
return props.reduce((pre, nxt) => (typeof pre === 'undefined' || typeof pre[nxt] === 'undefined' ? undefined : pre[nxt]), target);
}
+ +

反转义字符串

何谓反转义字符串?就是后端在传输HTML代码的时候往往会对字符串处理:把一些特殊符号转义了;当我们拿到HTML渲染到页面希望他按照HTML代码的格式来显示,而不是按照转义的字符串显示成文本,所以我们需要对这段字符串反转义!
网上看到很多解决方案是通过正则的方式一一替换过来,但是个人觉得这样不好维护。后面发现一种浏览器自动转换的办法,利用这一特性,可以通过js创建一个虚拟的DOM节点,然后把需要转义的字符串使用innerHTML方法放进去,再通过nodeValue方法取出来。
但这种方法需要注意的是:e.childNodes是一个数组,它将_html分段(每65536字符分一段)存储到e.childNodes[i]中,使用e.childNodes方法取得反转义后的字符串需要循环e.childNodes数组。我看到网上很多方法都是直接取的e.childNodes[0].nodeValue,这样在内容过多的时候,是无法把数据取完整的

+
1
2
3
4
5
6
7
8
function htmlDecode(str) {
const e = document.createElement('div'), _html = '';
e.innerHTML = str;
for (let i = 0; i < e.childNodes.length; i++) {
_html += e.childNodes[i].nodeValue;
};
return _html;
};
+ +

未完

+
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/i9knerk1.html b/post/i9knerk1.html new file mode 100644 index 00000000..8c67ed9e --- /dev/null +++ b/post/i9knerk1.html @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用Github Action自动化发布npm包 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用Github Action自动化发布npm包 +

+ + +
+ + + + +
+

GitHub Actions 提供了一种强大的自动化构建和发布流程的方式,通过组合github action的方式形成了workflow,来实现CI/CD。我们完全可以将 npm 包的发布流程集成到 GitHub workflow中,做到自动化发包,这可以可以提高效率并减少人为错误。以下是如何使用 GitHub Actions 实现自动化发布的详细指南。

+
+

0. 原理解释

github actions是一个github推出的CI/CD工具,可以模拟平台(比如linux等)自动化执行一些操作。
npm可以通过token实现免登陆发布包,并且Github Secrets能很好的保护token不被泄露(后面会说简单说一下为什么),这样就避免了在登陆信息在CI/CD时泄露

+

这是我编写的权限验证的一个包,支持vue,react以及函数式调用,同时使用github workflow实现的自动npm化发包。欢迎大家参考,同时提出优化意见:
validate-permission/.github/workflows at main · 2ue/validate-permission · GitHub

+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你有一个 GitHub 账户,并且已经创建了一个仓库。
  • +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。但要注意,因为access token具有你的npm账户读写权利,所有不应该将.npmrc文件提交到远程仓库,需要将该文件加入到.gitignore中

+
1
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
1
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

发布失败

如果你没有成功,出现如下报错:

+
1
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • .npmrc配置问题:确保你的.npmrc文件路径,命名和内容正确
  • +
  • .npmrc:确保你的access token正确,或者具有publish或write权限
  • +
  • 本地没有使用npm官方源:由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此使用npm access token发布npm包的流程已经跑通了。

+

相信大家已经发现了,上面这种方式有一个问题:.npmrc只能放到本地,意味着每次换一个电脑写代码都要重新去复制access token,生成.npmrc,那有没有一种方式可以比较安全的管理它呢,答案肯定是有的:答案就在Github。
我们不但可以利用GitHub Secrets管理npm的access token,而且还可利用github actions能力实现自动化workflow,来自动化发包到npm。

+

3. 配置 GitHub Secrets

在 GitHub 仓库中添加 npm token:

+
    +
  1. 进入 GitHub 仓库的 “Settings” > “Secrets”。
  2. +
  3. 点击 “New repository secret”。
  4. +
  5. 输入 NPM_TOKEN 作为名称,并粘贴你的 npm token 作为值。
  6. +
  7. 保存秘密。
    image.png
  8. +
+

请注意,这里我们可以选择Secrets和Variables,他们都在我们编写的workflow中被读取到,其实都是变量,但是两者有区别:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
变量类型存储方式界面是否可见修改内容修改变量名workflow是否可见
Secrets加密不可见只能更新且更新时不能看到之前内容无法修改以***显示
Variables明文明文且可见可自由更改且可以看到之前内容可以修改明文可见
从上面的比较可以看出为什么github Secrets能够有效的保护npm access token,当然万事不是绝对,如果非要追求百分百安全,那就使用最原始的方式吧,毕竟放在你本地的文件也可能因为中木马泄露出去。
+

4. 编写 GitHub Actions Workflow

至此我们就可以利用github actions来编写一个workflow。让我们捋一下思路:

+
    +
  • 什么时候执行:在代码推送到某分支,或者打tag的时候,这里我们选择当代码推送时
  • +
  • 执行流程
      +
    • 拉取代码
    • +
    • 设置时区:如果需要
    • +
    • 设置node环境
    • +
    • 安装pnpm,yarn等工具:如果需要
    • +
    • 安装代码依赖,并打包
    • +
    • 写入token到.npmrc
    • +
    • 执行npm 发布
      当然如果所有的操作都需要我们自己去实现,就比较复杂,好在有第三方action可以快速的让我们实现这些能力:
    • +
    +
  • +
  • run:可以执行脚本命令,让我们可以执行类似npm i这样的命令
  • +
  • actions/checkout@v4:实现代码拉取
  • +
  • actions/setup-node@v4:实现node环境,可以指定node版本
  • +
+

那么我们就用这些能力创建一个workflow吧

+

在项目根目录下创建 .github/workflows/npm-publish.yml 文件,并添加以下内容:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# workflow名字
name: Publish to npm

# 触发条件:当 main 分支有 push 事件时触发
on:
push:
branches:
- main

jobs:
publish:
# 在 Ubuntu 最新版本上运行作业
runs-on: ubuntu-latest
steps:
# 使用 actions/checkout 检出当前代码
- name: Checkout code
uses: actions/checkout@v4

# 设置 Node.js 环境
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.11.1

# 安装项目依赖,并编译
- name: Install dependencies
run: |
npm i
npm run build

# 添加access token到.npmrc
- name: Add Npm Token
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ./.npmrc
ls -al
cat ./.npmrc

# 发布到 npm registry
- name: Publish to npm
run: npm publish
env:
# 读取github的secrets变量,这里的NPM_TOKEN是前面设置的变量名
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ +

此 workflow 会在推送到 main 分支时触发。

+

image.png

+

5. 推送更改到 GitHub

将你的更改推送到 GitHub 仓库:

+
1
2
3
git add .
git commit -m "Set up automated npm publishing with GitHub Actions"
git push
+ +

6. 测试 Workflow

一旦你推送了更改,GitHub Actions workflow 将会运行。你可以在 GitHub 仓库的 “Actions” 选项卡中查看 workflow 的状态。

+

7. 发布新版本

当你准备发布新版本时,更新 package.json 中的版本号,然后提交并推送这些更改到 main 分支。GitHub Actions 将自动处理剩余的发布流程。

+

8.优化

这个workflow脚本还有很多优化的空间,比如:

+

优化触发时机

如果我们推送代码就执行发包操作,在有些情况下可能不符合我们要求,所有我们可以更改触发条件:
正常情况我们想的是创建了一个v1.0.0这样的tag分支才触发,并且是修改了关键源码才触发

+
    +
  • 设置当推送了tag分支并且需要tag符合某种格式才触发流水
  • +
+
1
2
3
4
5
name: Npm publish
on:
push:
tags:
- "v*" # 这段的意思是仅在出现名为 v 字符开头的tag时,触发此任务,如v1.2.1
+ +
    +
  • 设置修改了某些文件才执行发包
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- uses: dorny/paths-filter@v3
- #必须设定ID,用于后面读取这一步的结果
id: changes
with:
filters: |
update:
- 'main/**'
- 'types/**'
- 'utils/**'
- README.md
- package.json
- rollup.config.js
- tsconfig.json
- .babelrc
- .github/workflows/publish.yml
- name: Install pnpm
- # 这里是当【changes】输出了update为true才执行执行编译等工作
if: steps.changes.outputs.update == 'true'
run: |
npm install -g pnpm
+ +

dorny/paths-filter@v3github actions市场的一个包,可以用来检查哪些文件有变动,如果有变动则输出一个变量update,为true时表示有变动,可以用steps.changes.outputs.update来读取这个变量

+

自动化生成版本号

每次改动需要发布npm包时,都需要修改package.json的版本号,无疑有点麻烦,那我们可以使用npm version 命令来做到自动化版本修改:

+
    +
  • <update_type> 是语义化版本类型之一,如 majorminor 或 patch
  • +
  • 例如,运行 npm version patch 将会更新版本号为当前版本号的下一个补丁版本
  • +
+

甚至你可以通过github workflow的内置变量拿到tag名字,根据tag特征做到修改哪一个版本号

+

自动化生成tag和release

如果你的commit比较规范,比如使用了@changesets/cli等,那么可以利用github actions市场的三方包来自动化为你的发布生成changelog,tag,release等信息,比如semantic-release-action等

+

当然根据个人需求不同,可能还可以做更多的优化,这里就不一一述说了,毕竟本篇文章仅仅是一个穿针引线。

+

问题总结

问题 1:权限问题

如果你的 npm token 没有足够的权限发布包,你可能会遇到权限错误。[参考](#获取Access Token)

+

解决方案:确保你的 npm token 有发布包的权限。通常,这意味着你需要在 npm 网站上生成一个具有适当权限的 token。

+

问题 2:网络问题

如果你的 CI/CD 服务器无法访问 npm registry,发布可能会失败。一般在github上不会出现该问题,可能在本地使用了代理或者切换了npm源会出现这个问题。参考

+

解决方案:检查你的 CI/CD 服务器的网络设置,确保它可以无障碍地访问 https://registry.npmjs.org/

+

问题 3:Workflow 配置错误

如果你的 workflow 配置有误,它可能不会按预期触发。

+

解决方案:仔细检查 workflow 文件的语法和配置,特别是触发条件和 secret 的使用。

+

问题 4:.npmrc问题

如果你的 .npmrc 文件没有被正确地添加到项目根目录或内容不正确,那么可能导致发布失败。参考

+

解决方案:确保 .npmrc 文件被正确添加到你的版本控制系统,并且格式正确无误。

+

通过以上步骤和解决方案,你可以确保 npm 包的自动化发布流程顺畅运行。

+

参考

Using private packages in a CI/CD workflow | npm Docs
Working with the npm registry - GitHub Docs
Variables - GitHub Docs

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/k0pgips8.html b/post/k0pgips8.html new file mode 100644 index 00000000..c60a7149 --- /dev/null +++ b/post/k0pgips8.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +H5的Notification特性 - Web的桌面通知功能 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ H5的Notification特性 - Web的桌面通知功能 +

+ + +
+ + + + +
+

目前,web网页使用桌面通知功能的越来越多,包括微博,腾讯视频等大厂站,桌面通知功能是H5的一个API - Notifications。它允许网页或应用程序可以发出通知,通知将被显示在页面之外的系统层面上(通常使用操作系统的标准通知机制,但是在不同的平台和浏览器上的表现会有差异),这样即使应用程序空闲或在后台也可以向用户发送信息。

+
+

应用场景

Notifications的诞生简化了网站或者应用与用户之间的沟通成本(时间成本和开发成本),增强用户黏性(减少了用户离开应用的可能)。传统的通知方式,大多是通过站内信(消息),邮件,短信等方式,它们通常需要刷新(跳转)页面、离开应用打开其他应用或终端来查看消息;而桌面通知功能大大的简化了这个过程,消息的传递基本不消耗时间(如果不设置setTimeout,用时基本不会超过1s),并且用户不需要离开应用,这都带来了极大的方便。可以预见,Notifications将会在很多网页或应用中被大量使用。当然Notifications也具有它的局限性:无法存档、即看即毁
那么,这个功能到底能用在哪些场景呢?只能说能应用的场景很多:

+
    +
  • 社交类网站
  • +
  • 资讯类网站
  • +
  • 网页版邮件服务
  • +
  • 即时通知类网站
  • +
  • +
+

举个例子,当你打开微博页面,你可能会看到(使用新版浏览器)如下图的通知:
desktop-notification1

+

这就是网站使用了桌面通知功能,当你选择允许,那么当网站有推送消息或者你登陆账号有新的消息将会在桌面的右下角出现一个小弹窗通知,如下:
desktop-notification2

+

感觉有点酷酷的!!!

+

用户权限 - Notification.permission

Notification.permission是一个静态方法,可以获取用户当前的通知权限状态,返回一个String,可以根据返回值判断用户是否授予了通知权限。返回值有三种情况:

+
    +
  • default
      +
    • 用户还未被询问是否授权,所以通知不会被显示。
    • +
    +
  • +
  • granted
      +
    • 表示之前已经询问过用户,并且用户已经授予了显示通知的权限。
    • +
    +
  • +
  • denied
      +
    • 用户已经明确的拒绝了显示通知的权限。
    • +
    +
  • +
+

当值为default或者denied时都不会显示通知消息,只有明确的被设置成granted才会显示通知消息

+
1
2
3
4
5
6
const permission = Notification.permission;
if(permission === 'granted'){
console.log('已经授权通知,可以进行你的通知啦!');
}else{
console.log('用户还未授权,请先授权!');
}
+ +

请求权限 - Notification.requestPermission(CALLBACK)

应用发送通知之前必须要取得发送通知的权限,才能成功进行通知。Notification.requestPermission(CALLBACK)是请求获取权限的方法(有点类似javascriptconfirm弹窗窗),允许传入一个回调,回调会返回用户选择的何种权限,返回两个值,granted代表允许,denied代表拒绝。并且Notification.requestPermission()支持then方式的链式调用,也就意味着可以异步调用它。

+
1
2
3
4
5
6
7
Notification.requestPermission(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
//两种方式是等价的
Notification.requestPermission().then(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
+ +

创建通知 - new Notification(TITLE, OPTIONS)

new Notification(TITLE, OPTIONS)方法创建可以创建一个通知实例,允许参入参数两个参数TITLEOPTIONS。注意默认情况下(实际可以通过OPTIONS中的timestamp参数控制)一旦通知实例被创建出来,它会立即被显示出来。

+

TITLE参数

TITLE表示通知的标题。必须参数,允许数字、字符串和空

+

OPTIONS参数

OPTIONS是非必须参数,必须为一个对象,它包含:
ps: 部分参数在某些浏览器可能会不生效,建议使用最新版的谷歌浏览器。以下某些内容从Notification-MDN-EN结合谷歌翻译得来,很有可能翻译不准确,如有,请提出。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
//通知显示正文。非必须,默认为空
body: '你的好友XX上线了!',
//通知显示正文的图片地址。非必须,默认为空
image: 'imgae url',
//通知左侧图标。非必须,默认为空
icon: 'imgae url',
//通知的分类标记(ID)。非必须,默认为空
tag: 'test',
//通知相关联的数据,通常用于方法的回调,传参。非必须,默认为空
data: '可以是任意数据类型',
//通知显示延迟的时间。非必须,默认通知实例创建完成就显示
timestamp: '',
//通知主体内容的水平展示顺序,有点类似direction属性。非必须,默认值是auto, 可以是ltr或rtl
dir: 'auto',
//当没有足够的空间来显示通知本身时,用于表示通知的图像的URL。非必须,默认为空
badge: 'xxx',
//通知的语言。非必须默认为空
lang: '',
//通知显示时,设备的振动模式。非必须,默认为空
vibrate: [200, 100, 200],
//新通知出现是否覆盖旧的通知,覆盖(true)则永远只显示一条通知,不覆盖(false)则会多条通知重叠。非必须,默认为true
renotify: true,
//通知是否静音。非必须,默认为false,表示无声
silent: false,
//通知声源文件地址。非必须,默认为空
sound: 'mp3',
//是否不在屏幕上显示通知信息。非必须,默认为false表示要显示
noscreen: false,
//指定通知是否应该粘滞性,即不容易被用户清理。非必须,默认false表示不具粘滞性
sticky: false,
//指定通知是否保持活性,知道用户点击或关闭。非必须,默认为false
requireInteraction: false
}
+ +

事件及事件钩子

当通知被创建成功后:

+
    +
  • 通知实例具有一个静态方法可以用来关闭通知
  • +
  • 通知实例具有四个事件钩子,来跟踪通知当前的状态。这些事件可以通过事件处理跟踪onshowonclickoncloseonerror。因为Notification同样继承自EventTarget,因此可以对它调用addEventListener()方法。
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const n = new Notification('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
},
timestamp: 3000
});

n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
+ +

demo

写一个简单的例子,可以打开页面体验一下,建议用最新版谷歌浏览器打开~ Notification.js

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const NotificationInstance = Notification || window.Notification;
if (!!NotificationInstance) {
const permissionNow = NotificationInstance.permission;
if (permissionNow === 'granted') {//允许通知
CreatNotification();
} else if (permissionNow === 'denied') {
console.log('用户拒绝了你!!!');
} else {
setPermission();
}
function setPermission() {
//请求获取通知权限
NotificationInstance.requestPermission(function (PERMISSION) {
if (PERMISSION === 'granted') {
CreatNotification();
} else {
console.log('用户无情残忍的拒绝了你!!!');
}
});
}
function CreatNotification() {
const n = new NotificationInstance('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
}
});
n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
setTimeout(() => {
n.close();
}, 2000);
}
}
+ +

兼容

+ +

参考

    +
  • Notification-MDN-EN
  • +
  • Notification-MDN-CN
  • +
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/kqdkiqw2.html b/post/kqdkiqw2.html new file mode 100644 index 00000000..90f1e5fc --- /dev/null +++ b/post/kqdkiqw2.html @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +node和npm版本管理器nvm的安装和使用 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ node和npm版本管理器nvm的安装和使用 +

+ + +
+ + + + +
+

nvm:一个node和npm的版本管理器(node&npm version manager),能让你快速的在不同版本间切换。

+
+

安装

下载地址:官网下载
有两种版本nvm-noinstall.zip(便携版)和nvm-setup.zip(exe安装版)
两者唯一区别就是便携版需要手动配置全局变量,而安装版只需要在安装时选定安装目录则会自动配置好。

+
+

注意:安装之前最好先卸载之前的node

+
+

便携版安装

    +
  • 下载最新版的nvm-noinstall.zip后解压放到D:\devTools(可以放到任意位置,此处是我安装的目录,注意文件夹名不能存在空格);
    1
    2
    3
    4
    5
    elevate.cmd
    elevate.vbs
    install.cmd
    LICENSE
    nvm.exe
  • +
  • 配置nvm,生成settings.txt,填写配置
    方法一:双击install.cmd,会生成settings.txt文件(生成位置就是你输入的地址,一般是在nvm目录下,如果不是,需要拷贝过来)
    方法二:直接在nvm目录下新建settings.txt文件
    1
    2
    3
    4
    5
    6
    root: D:\devTools\nvm
    path: D:\devTools\nodejsv
    arch: 64
    proxy: none
    node_mirror: http://npm.taobao.org/mirrors/node/
    npm_mirror: https://npm.taobao.org/mirrors/npm/
    +
      +
    • root : nvm的存放地址
    • +
    • path : 存放指向node版本的快捷方式,使用nvm的过程中会自动生成。一般写的时候与nvm同级。
    • +
    • arch : 电脑系统是64位就写64,32位就写32
    • +
    • proxy : 代理
    • +
    • node_mirror: node镜像源,安装node时会从此镜像源下载。
    • +
    • npm_mirror: 同上,npm镜像源
    • +
    +
  • +
  • 全局变量配置
    1.添加变量NVM_HOME,值为D:\devTools\nvm
    2.添加变量NVM_SYMLINK,值为D:\devTools\nodejsv
    3.添加变量NVM_HOMENVM_SYMLINK到全局变量path: 修改path的值最后加上;%NVM_HOME%;%NVM_SYMLINK%;
    到此便携版nvm安装完成
  • +
+

exe安装版

直接双击安装,可以使用默认的选项。也可以自己选择安装地址。然后安装过程中会自动把路径写入到全局变量。

+
+

注意: 如果安装了杀毒软件,应该先关闭杀毒软件,因为写入全局变量是一个敏感操作,某些杀毒软件会报警(不关闭,报警时需要选择允许操作)

+
+

使用

版本检测

1
2
3
4
5
$ nvm version
1.1.6
// or
$ nvm v
1.1.6
+ +

安装node&npm

1
2
3
$ nvm install [version]
// 如果安装最新版的,直接使用
$ nvm install latest
+ +

查看安装的node&npm

1
2
3
$ nvm list
* 8.4.0 (Currently using 64-bit executable)
6.9.0
+ +

切换node版本

1
2
$ nvm use [version]
Now using node v8.4.0 (64-bit)
+ +

卸载某个版本node

1
$ nvm uninstall [version]
+ +

nvm命令查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ nvm
Running version 1.1.6.

Usage:

nvm arch : Show if node is running in 32 or 64 bit mode.
nvm install <version> [arch] : The version can be a node.js version or "latest" for the latest stable version.
Optionally specify whether to install the 32 or 64 bit version (defaults to system arch).
Set [arch] to "all" to install 32 AND 64 bit versions.
Add --insecure to the end of this command to bypass SSL validation of the remote download server.
nvm list [available] : List the node.js installations. Type "available" at the end to see what can be installed. Aliased as ls.
nvm on : Enable node.js version management.
nvm off : Disable node.js version management.
nvm proxy [url] : Set a proxy to use for downloads. Leave [url] blank to see the current proxy.
Set [url] to "none" to remove the proxy.
nvm node_mirror [url] : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.
nvm npm_mirror [url] : Set the npm mirror. Defaults to https://github.com/npm/npm/archive/. Leave [url] blank to default url.
nvm uninstall <version> : The version must be a specific version.
nvm use [version] [arch] : Switch to use the specified version. Optionally specify 32/64bit architecture.
nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.
nvm root [path] : Set the directory where nvm should store different versions of node.js.
If <path> is not set, the current root will be displayed.
nvm version : Displays the current running version of nvm for Windows. Aliased as v.
+ +

总结

    +
  • settings.txtrootpath文件路径中不能存在空格,否则在使用nvm use命令时会报错
  • +
  • 在使用nvm use命令时,貌似无法再git-bash中使用,暂时不知道原因,在自带的cmd中可以
  • +
+

最后的最后

+

安装nvm比较简单,喜欢折腾的可以使用便携版,反之这直接使用安装版一键安装。最后大家愉快的玩耍吧

+
+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/lsu9244c.html b/post/lsu9244c.html new file mode 100644 index 00000000..9eeb9693 --- /dev/null +++ b/post/lsu9244c.html @@ -0,0 +1,586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用Npm Token免登陆发包 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 使用Npm Token免登陆发包 +

+ + +
+ + + + +
+

在使用 npm 包管理器进行包的发布时,通常需要手动登录 npm 账户,并且我们在本地使用npm时因为某些不可抗原因,使用了非npm官方源,导致我们每次发布都需要切换到官方源,显得非常繁琐。现在,通过使用 npm Token,可以避免手动登录,切换源等步骤,实现自动化发布流程,特别是在持续集成/持续部署(CI/CD)环境中。

+
+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

通过登陆官网获取

+

确保记录下生成的 Token,因为它只会在创建的时候显示一次,刷新后不会再次显示,如果忘记就只能重新申请

+
+

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

通过命令行获取

需要现在本地执行npm login登陆,然后调用文档的命令生产token,具体可以看下官方文档:
Creating and viewing access tokens | npm Docs

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。

+
1
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
1
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

如果你没有成功,出现如下报错:

+
1
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • 确保你的.npmrc文件路径,命名和内容正确
  • +
  • 确保你的access token正确,或者具有publish或write权限
  • +
  • 由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此,你已经做到免登陆发布包了,但别急,这还没完。

+

这样安全吗?

如果你真像上面这样做了,那你npm仓库的安全就没有了。

+

为什么呢,因为你刚刚申请的access token有读写仓库的权限,你如果随着你的代码提交到仓库,就会将他暴露出去,造成损失,那么怎么做才安全呢?下面会将

+

更安全的免登陆发包

忽略.npmrc

将.npmrc加入到.gitignore文件中,不提交到代码仓库,只在你本地使用

+

变量

在.npmrc使用变量

将.npmrc改成如下内容:

+
1
//registry.npmjs.org/:_authToken=${NPM_PUBLISH_TOKEN}
+ +

NPM_PUBLISH_TOKEN是我们后面要设置的全局变量

+

设置全局变量

在全局变量(环境变量)中设置NPM_PUBLISH_TOKEN为你刚刚申请的token

+

不同的平台设置方式不一样:

+

linux和mac平台可以通过编辑.bashrc或者.zshrc文件实现:

+
    +
  • 打开.bashrc或者.zshrc编辑
  • +
+
1
vi ~/.bashrc
+ +
    +
  • 在文件内容末尾追加:
  • +
+
1
export NPM_PUBLISH_TOKEN=你刚刚申请的token
+ +
    +
  • 然后执行source命令使其生效
  • +
+
1
source ~/.bashrc
+ +

如果提示没有权限加上sudo

+

window平台可以直接打开高级属性界面设置

+

未完待续

当然可以看出在本地使用token还是有很多限制,没有彻底解放生产力,后续我会出一篇文章讲述如何结合CI/CD去实现自动化发包

+

重要

任何时候都不要将access token相关的信息暴露到公网上,以免造成不必要损失

+

参考

1.Creating and viewing access tokens | npm Docs
2.Using private packages in a CI/CD workflow | npm Docs
3.Working with the npm registry - GitHub Docs

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/m824cy0u.html b/post/m824cy0u.html new file mode 100644 index 00000000..a5e4773f --- /dev/null +++ b/post/m824cy0u.html @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +vue中慎用style的scoped属性 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ vue中慎用style的scoped属性 +

+ + +
+ + + + +
+

在vue组件中,在style标签上添加scoped属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的,这是一个非常好的机制。但是为什么要慎用呢?在实际业务中我们往往会对公共组件样式做细微的调整,如果添加了scoped属性,那么样式将会变得不易修改。初写这篇文章时,本人没有找到一个好的方法去解决这个问题,后来经过大伙的解答,才让我恍然大悟。

+
+

何为谨慎使用

谨慎使用不是不用,而是持一种审视的目光去看待它。scoped肯定是解决了样式私有化的问题,但同时也引入了新的问题—样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的。所以我才说要谨慎使用

+

解决方案

首先要说明的问题是,最开始我以为这是一个BUG或者说一个弊端(因为当时没有搞明白scoped的真正作用),就很英勇的去提了一个issue,然后理所当然的被关闭了,关闭的理由是:scoped设计的初衷就是让样式变得私有,让它不会影响其他任何地方的样式。但是由于我在业务中经常遇到需要修改有scoped属性的组件,就写了一篇文章记录一下这个问题,希望大家谨慎的使用这个属性。
然而事实再一次证明了我的愚蠢,在vue-loader的文档中已经详细的对这个问题做了分析,并且对我遇到这种问题给出了解决方法:vue-loader的深度作用选择器。
因为我并没有去深入了解这些问题,所以注定这篇文章被大伙拍砖,😂😂😂😂😂😂

+

解决方案:vue-loader之scoped-css

+

鉴于此,虽然这篇文章没有什么价值,但为了提醒我自己深究的意义,我对后面的内容做了保留,以下内容是最开始文章的原文,请大家忽略,上面的内容才是正文,没错,正文就是这么少。

+ +

——————————–正文分割线,以下是无营养的内容——————————–

+
+

scoped实现私有化样式的原理

为什么会说,会增加复杂度?那么我们先从的实现模块的原理说起。为了方便称呼,我们假设把这种组件叫做模块私有组件,其他的未加scoped的叫做模块一般组件
通过查看DOM结构发现:vue通过在DOM结构以及css样式上加唯一不重复的标记,以保证唯一,达到样式私有化模块化的目的。具体的渲染结果是怎样的,通过一个例子来说明。

+

公共组件button组件

一个公共组件button,为了样式模块化,给其加上scoped属性,

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//button.vue
<template>
<div class="button-warp">
<button class="button">text</button>
</div>
</template>
...
<style scoped>
.button-warp{
display:inline-block;
}
.button{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
</style>
+ +

浏览器渲染button组件

button组件在浏览器渲染出的html部分和css部分分别为:

+
1
2
3
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
+ +
1
2
3
4
5
6
7
8
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
+ +

从上面的字可以看出,添加了scoped属性的组件,为了达到组件样式模块化,做了两个处理:

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
+

大家都知道css样式有一个优先级的说法,scoped的这一操作,虽然达到了组件样式模块化的目的,但是会造成一种后果:每个样式的权重加重了:理论上我们要去修改这个样式,需要更高的权重去覆盖这个样式。这是增加复杂度的其中一个维度。

+

其他组件引用button组件

上面分析了单个组件渲染后的结果,那么组件互相调用之后会出现什么样的结果呢?,具体分两种情况:模块一般组件引用模块私有组件(本质和模块私有组件引用模块一般组件一样);模块私有组件引用模块私有组件。

+

举个例子:在组件content.vue中使用了button组件,那么content.vue组件是否添加scoped属性渲染出来的结果有什么区别呢?

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style>
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
</style>
+ +

模块一般组件(未添加scoped)引用模块私有组件

如果style上没有加scoped属性,那么渲染出来htmlcss分别就是:

+
1
2
3
4
5
6
7
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
+ +

可以看出,虽然在content组件中,修改了buttonborder-raduis属性,但是由于权重关系,生效的依然是组件内部的样式(此时是外部的样式被覆盖)。所以如果要达到修改样式的目的,就必须加重我们要修改样式的权重(增加选择器层级,ID选择器,并列选择器,impotant等)

+

模块私有组件(添加scoped)引用模块私有组件

如果加了scoped属性呢?按照开始分析出来的规则(事实也是这么的):
首先是在所有的DOM节点加上data属性
然后在css选择器尾部加上data属性选择器

+

那么渲染出来htmlcss分别就是:

+
1
2
3
4
5
6
7
<div data-v-57bc25a0 class="content">
<p data-v-57bc25a0 class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-57bc25a0 data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content[data-v-57bc25a0]{
width: 1200px;
margin: 0 auto;
}
.content .button[data-v-57bc25a0]{
border-raduis: 5px;
}
+ +

对于上面的两种情况,可以明显看出来渲染后的结果大不相同。
虽然我们在content添加了想要修改button组件的样式的代码,但是仔细看,由于.content .button这句在末尾加的是content组件的scoped标记,最后这句其实根本作用不到我们想要的DOM节点上,所以这种情况我们在content内部写的任何样式都不会影响到button.vue组件,所以这就尴尬了。。。。
当然这个问题也是可以解决的,就是直接加全局样式可以修改到,但这势必会影响全部地方的组件;所以需要另外一种方法在content.vue组件内再加一个不带scoped属性的style标签,也就意味着要加两个style,一个用于私有样式,一个用于共有样式。这肯定是有点shit的,并且这两种解决方案都回避不了一个问题:权重!!!

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style scoped>
.content{
width: 1200px;
margin: 0 auto;
}
</style>
<style>
.content .button{
border-raduis: 5px;
}
</style>
+ +

这样符合规范么?貌似没看到不能这么写,并且这么写也确实生效了。。。但这样确实增加了思维的复杂度,有点苦恼啊。

+

总结scoped的渲染规则

总结一下scoped三条渲染规则

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
  • 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性
  • +
+

解决方案

对于引用的三方库,如果对方使用了scoped,我们无力改变什么,如果确实需要修改他的样式最能在不加scoped的组件中修改样式,或者全局样式直接修改,这很粗暴!
对于自己维护的组件,一定要想清楚,组件的样式能否满足所有的情况。如果确实需要加,无疑会增加使用这个组件的开发同学的工作!

+

当然对于这个问题,如果诸君有更好的解决方案,请诸君TELL ME一下下!

+

趣事

在使用scoped一定要谨慎scoped的这个特性,本人以为这是一个BUG,就去提了issue ,结果尤大很霸气的回复
scoped设计的初衷就是不能让当前组件的样式修改其他任何地方的样式,因为设计如此。所以理所当然的这个issue已被干掉。。。😂😂😂😂😂😂

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/t6oopnba.html b/post/t6oopnba.html new file mode 100644 index 00000000..a82f7db1 --- /dev/null +++ b/post/t6oopnba.html @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +风继续吹 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 风继续吹 +

+ + +
+ + + + +
+

最近在某些比较看重的事情上,处理的很失败,感觉整个心态都发生了变化。下班独行的那段路上,突然惊醒自己也许需要做出一些改变,晚饭后静静的思考了一阵:似乎勇往前行才是我最需需要的改变。古语有云:走自己的路,让别人去说吧。是的,生活需要你不断踽踽独行,活在他人的世界里是可怕的,特别他(她)还是陌生人。随手记录一些句子,它们是我最近的心态和生活的写照,也以此鞭策自己砥砺前行。

+
+
+

时间不会因你沮丧而停滞不前
生活不会因你懊恼而雨过天晴

+

如风,继续吹
吹来往日的欢歌笑语
吹来远方的殷殷思念
化作雨露滋润心田

+

如风,继续吹
随风飘荡去远方
随风逐浪勇往前
风不止浪不息

+

如风,继续吹
时而狂乱,掠过天空大地,高山河流
时而低喃,轻抚绿茵花朵,平原盆谷
最后都消散在其它风里

+

然后
其它风,继续吹

+

—-< 谨鼓励我砥砺前行 >

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/u5nm8s0b.html b/post/u5nm8s0b.html new file mode 100644 index 00000000..ca88784a --- /dev/null +++ b/post/u5nm8s0b.html @@ -0,0 +1,499 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +实现数字滚动变化以及延伸 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 实现数字滚动变化以及延伸 +

+ + +
+ + + + +
+

利用jquery的插件jquery.animateNumber实现一个简单的数字滚动效果

+
+

需求分析

    +
  1. 处理数据:因为数据是后端提供,所以有可能格式不是我们想要的,所以也许需要格式化数据;
  2. +
  3. 根据页面设计的效果图(如图),需要把数字字符串拆分成单个数字字符串
    animateNumber_01
  4. +
  5. 每一个数字进行滚动变化
  6. +
  7. 最后,在项目中,我选取了插件jquery.animateNumber来实现滚动效果。这个插件的使用方式很简单,在官方有很详尽的文档来展示各个案例,就不一一赘述了。
  8. +
+

HTML布局

其中num是后台传入的值,notChangeUint用来标记不进行单位变换的值

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="warp">
<div class="net-credit-num" >
<p>平台累积会员人数(人)</p>
<span date-num="123" class="animateNumber notChangeUint"></span>
</div>
<div class="net-credit-money">
<p>平台完成投资金额(万元)</p>
<span date-num="91,123,456.00" class="animateNumber"></span>
</div>
<div class="net-return-money">
<p>累计已还款金额(万元)</p>
<span date-num="8,895,678.00" class="animateNumber"></span>
</div>
</div>
+ +

撸JS

去除逗号(,)

1
num = num.replace(',','');
+ +

上面这种方法只能去除字符串中的第一个逗号,但是实际数据中可能存在多个逗号,所以需要用到正则全局匹配替换,代码如下:

+
1
2
var reg = new RegExp(',','g');
num = num.replace(reg,'');
+ +

转化单位(元–>万元)

把金额单位转化为万元,并且保留两位小数,人数不进行转化

+
1
2
3
if(!numWarpParent.hasClass('notChangeUint')){
num = (Number(num) / 10000).toFixed(2);
}
+ +

字符串拆分为数组

1
numArry = num.split('');
+ +

把数字添加到页面并调用animateNumber的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
for(var i = 0; i < numArry.length; i++){
var thisNum = parseInt(numArry[i]);
var spanNum;
if (!isNaN(thisNum)){
spanNum = $('<span class="single-num">' + numArry[i] +'</span>');
}else{
spanNum = $('<span class="single-point">.</span>');
};
numWarpParent.append(spanNum);
thisNumWarp.prop('number', stratNum).animateNumber({
number: thisNum
}, time);
}
+ +

最后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$('.animateNumber').each(function(){
var _this = $(this);
var totalNum = _this.attr('date-num'); //后台数据储存在date-num上
appendNum(totalNum,_this);
});
function appendNum(num,numWarpParent){
var newNum;
var reg = new RegExp(',','g'); //正则匹配所有逗号
newNum = num.replace(reg,'');
if(isNaN(num)) newNum = 0; //容错,当后台传入的参数错误(非数字)时,将只值置为0,以保证页面的正常渲染
if(!numWarpParent.hasClass('notChangeUint')){ //判断是否需要转换单位
newNum = (Number(newNum) / 10000).toFixed(2);
};
numArry = newNum.split('');
for(var i = 0; i < numArry.length; i++){
var thisNum = numArry[i];
var numWarp;
if (!isNaN(thisNum)){ //判断是否可以转化为数字
numWarp = $('<label class="single-num">' + numArry[i] +'</label>');
}else{
numWarp = $('<label class="single-point">.</label>');
};
numWarpParent.append(numWarp);
isAnimate(thisNum,numWarpParent,i);
};
};
function isAnimate(num,numWarpParent,index){
if (isNaN(num))return;
//调用animate.js插件方法
numWarpParent.find('label').eq(index).prop('number', 0).animateNumber({
number: num
}, num * 100);
};
+ +

总结

1.功能模块化,尽量一个方法(函数)只做一件事情
2.容错,由于涉及到DOM操作,所以为了保证页面的正常渲染必须有容错处理机制:数据出错不影响整个流程(页面渲染)的畅通

+ +
+ + + + + +
+
+
请我一杯咖啡吧!
+ +
+
+ 2ue 微信 + 微信 +
+
+ 2ue 支付宝 + 支付宝 +
+ +
+
+ + + +
+ +
+ +
+ 欢迎关注我的其它发布渠道 + + +
+ + + + + + +
+
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/post/wow943e6.html b/post/wow943e6.html new file mode 100644 index 00000000..477af64e --- /dev/null +++ b/post/wow943e6.html @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何写一个日历组件 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ + + +
+ + + + + + + +
+

+ 如何写一个日历组件 +

+ + +
+ + + + +
+

众所周知,虽然javascript中关于时间的API有不少,我们可以通过方法单独的获取年、月、日、时、分、秒、毫秒…貌似很多,最近写了一个日历(以前写的,但写得很烂,最近优化一下),所以下面简单的记录一下如何写一个日历,列出了一些我在写日历过程中自己封装的一些方法

+
+

效果图

先来一张效果图,由于没有UI设计,所以就自己简单的设计了一个样式(好歹我也是设计专业的,虽然已不做设计很多年),虽然略丑,但重要的是功能!!!

+

datepicker

+

思路

一个日历到底是怎样用代码生成的?其实观察一下现有的日历展现形式,可以很快的形成思路,就是:根据计算把日期号数对应到正确的星期几上,并按照顺序逐一输出。
以下是我的思路:

+
    +
  • 取得月份的天数
  • +
  • 取得月份第一天是星期几
  • +
  • 循环对应号数和星期几返回一个数组对象
      +
    • 返回数组对象的每一个子项至少包含:号数,星期几,然后根据情况添加:是否高亮,是否当前月,是否节日…等属性
    • +
    +
  • +
+

方法封装

注意,为了保持方便调用javascript的方法,以及保持输出结果符合实际,所有的方法都有如下约定:

+
    +
  • 在计算过程中
      +
    • 所有的关于月份都是0~11的数字
    • +
    • 所有的关于星期都是0~6的数字
    • +
    +
  • +
  • 在输出的结果中
      +
    • 所有关于月份的输出默认都是1-12的数字
    • +
    • 所有关于星期的输出默认都是1-7的数字
    • +
    +
  • +
+

所以在向调用方法传递参数过程中,月份以及星期几统统都需要按照实际月份减一

+

获取月份天数

javascript中没有直接获取月份天数的方法,但是它提供了一个getDate方法可以获取日期的某一天。那我们只需要获取月份的最后一天(下一个月的第0天)就可以得知这个月的天数:

+
1
2
3
4
5
6
7
8
9
// year是要获取的年份,闰年不一样
// month是要获取的月份
// 返回当前月天数
function getMonthDays(year, month){
return new Date(year, month + 1, 0).getDate();
}

getMonthDays(2016,2) //29
getMonthDays(2017,2) //28
+ +

获取星期几

1
2
3
4
5
6
7
8
9
10
// year是要获取的年份
// month是要获取的月份
// 返回数字几则是星期几
function getWeekday(year, month, day){
return new Date(year, month, day).getDate() + 1;
}

getWeekday(2016,10,9) //输出4,表示2016年11月9是星期4
getWeekday(2017,10,9) //输出5,表示2017年11月9是星期5

+ +

获取月份有几个星期

要计算月份包含几个星期,需要两个数据:月份天数和月份第一天是星期几,就能得到想要的结果

+
1
2
3
4
5
6
7
8
9
// year是要获取的年份
// month是要获取的月份
// 返回当前月包含几个星期
function getweeksInMonth(year, month){

var days = getMonthDays(year, month);
var FirstDayWeekday = getWeekday(year, month, 1);
return Math.ceil(days + FirstDayWeekday);
}
+ +

循环生成月份对象

有了以上方法之后,就可以通过循环生成一个简单的月份对象了。
在这里需要注意,日历的排序有两种:

+
    +
  • 每一行以星期日开头
  • +
  • 每一行以星期开头
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// year是要获取的年份
// month是要获取的月份
// day天,用来判断是否是当前天
// type表明要星期几开头,0为星期一开头,1为星期日开头,默认为0
// 返回当前月包含几个星期

const WEEKTABLE = [{
cn: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
cns: ['日', '一', '二', '三', '四', '五', '六'],
en: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
},{
cn: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
cns: ['一', '二', '三', '四', '五', '六', '日'],
en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
}]

getMonthDaysArray(year, month, day, type) {
if (typeof day === 'undefined' && year === YEAR && month === MONTH) day = DAY;

var dayArrays = [];
var days = this.getMonthDays(year, month), preDays = this.getMonthDays(year, month - 1);
var thisMonthFirstDayInWeek = this.getWeekday(year, month, 1), thisMonthLastDayInWeek = this.getWeekday(year, month, days);

type = !type || type !== 1 ? 0 : 1;

//上月在当月日历面板中的排列
for (var i = 0; i < thisMonthFirstDayInWeek; i++) {
dayArrays.push({
dayNum: (preDays - thisMonthFirstDayInWeek + i + 1),
weekDay: WEEKTABLE[type].cn[i]
})
}
//当月日历面板中的排列
for (var i = 1; i <= days; i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag],
selected: i === +day,
isThisMonth: true
})
};
//下月在当月日历面板中的排列
for (var i = 1; i <= (6 - thisMonthLastDayInWeek); i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + days + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag]
})
};
return dayArrays;
}
+ +

格式化时间

涉及到时间时,常常需要把时间格式进行转换,为了应对多中需求,所以自己封装了一个

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 参数fmt必须
// date参数不必须,允许字符串和时间对象,不传或者传无法转换成合法时间对象的字符串则默认当前时间,
// 年(YYYY/yyyy)固定四个占位符
// 月(M)、日(d)、小时(h)、分(m)、秒(s)可以用 1-2个占位符,严格区分大小写,
// 毫秒(ms/mss)最多三个占位符,分别对应56,056这种类型
// 例子:
// (Format("yyyy-MM-dd hh:mm:ss:ms") ==> 2006-07-02 08:09:04:23
// (Format("yyyy-MM-dd hh:mm:ss:mss") ==> 2006-07-02 08:09:04:023
// (Format("yyyy-M-d h:m:s:ms") ==> 2006-7-2 8:9:4.180
function formate(fmt, date){
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
var _rules = [{
rule: '[yY]{4}',
value: _date.getFullYear()
}, {
rule: 'M+',
value: _date.getMonth() + 1
}, {
rule: '[dD]+',
value: _date.getDate()
}, {
rule: 'h+',
value: _date.getHours()
}, {
rule: 'm+',
value: _date.getMinutes()
}, {
rule: 's+',
value: _date.getSeconds()
}, {
rule: 'ms{1,2}',
value: _date.getMilliseconds()
}];

_rules.forEach(function (_r){
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join().substr(rLen);
});
});
return fmt;
}
//调用:
var time1 = formate("YYYY/MM/DD hh:mm:ss", new Date()); //2017/11/2 11:09:20
var time2 = formate("YYYY-MM-DD", time1); //2017-11-2
var time3 = formate("MM-DD-YYYY", time2); //11-2-2017
+ +

最后

附上这些方法的源码datepicker
基于vue实现的一个日历:

+
    +
  • demovue-datepicker
  • +
  • 源码datePickerPanel.vue
  • +
+

当然这只是最简单的日历输出,思路也是超级简单(感觉有点Low),如果有大神愿意分享它的经验欢迎,来邮~

+ +
+ + + + + + +
+
+ + + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/robots.txt b/robots.txt new file mode 100644 index 00000000..50164812 --- /dev/null +++ b/robots.txt @@ -0,0 +1,16 @@ +# hexo robots.txt +User-agent: * +Allow: / +Allow: /archives/ +Allow: /categories/ +Allow: /about/ + +Disallow: /vendors/ +Disallow: /js/ +Disallow: /css/ +Disallow: /fonts/ +Disallow: /vendors/ +Disallow: /fancybox/ + +Sitemap: http://blog.imx0.com/sitemap.xml +Sitemap: http://blog.imx0.com/baidusitemap.xml diff --git a/schedule/index.html b/schedule/index.html new file mode 100644 index 00000000..8b3a4971 --- /dev/null +++ b/schedule/index.html @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +schedule | 前端路上 + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ +

schedule +

+ + + +
+ + + +
+ +
+ + + +
+ + + + + +
+
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/search.xml b/search.xml new file mode 100644 index 00000000..5e439a92 --- /dev/null +++ b/search.xml @@ -0,0 +1,2317 @@ + + + + <2016/><2017> + /2016/12/31/2016-to-2017.html + + +
+

与其在上一秒中沉醉,不如努力拥抱下一秒.

+
+

匆匆而去的2016

入行前端的第二年,也是毕业的第二年。

+

短短两载却有翻天覆地的改变:从大学走入社会,从设计跨行到前端。这其中有必然也有偶然。

+

年初

年初换了新东家,毕业后的第二份工作,有对自身能力的底气不足而提高的渴望,也有对新东家的满怀期望,一切在有条不紊的进行着。2016年2月22日入职,从此多了一个别号二月(2ue)。座位在老大旁边,感到一丝压力。

+

适应与压力

经历了最初的彷徨后,慢慢开始适应新的环境,作为新手的我,跟着大神们一起做项目是开心的,但也同时感受到更多的压力。因此在入职后的几个月进入了疯狂学习模式,逛技术社区,微博关注技术达人,微信关注相关公众号,啃书等等,连早上挤公交地铁的时候都抽时间在手机上学习,当时感觉自己已经疯魔。虽然也就仅仅疯狂了三个月,但这短暂的三个月疯狂却让我焕然一新:从最初的复制粘贴到有自己的思考、从无处可问到能独立解决问题、更加擅于利用网络解决问题、能够更快的阅读新的知识、更好的编码习惯…林林总总,这三个月是很重要的三个月。

+

生活

换了新房东,空间更大,也换了一台新电脑,更方便撸(打)代(游)码(戏)。总之一切还算满意。

+

同事

我其实是个不擅交际,怕生的人儿。因为我说话很笨,所以在新的环境想要和人混熟,总是要花更多的时间去处理这些关系。不过幸运的是遇到一群很好相处的同事,让我更容易的处理好这些问题。所以再后来遇到各种问题后我都是大胆的去问部门大神,充分发挥了死灿烂打的精神(这个三秒,市委应该深有体会)。当然作为一个胖纸,肯定也有一堆饭友,也会利用工作过的闲暇之余去腐败腐败。

+

项目与加班

新东家的项目总是要得比较急,这就常常导致加班和代码的低质量产出。这是我最开始的想法,但到后来发现,诚然这两者是有一定的因果关系,但是有些东西本身就不可力抗(比如项目交付时间节点),如果一直持有因为时间导致自己加班这种心态,而不去提高自身的能力,那么不论你换到哪家公司你都会陷入无尽的加班与抱怨中。加班不可避免,那就努力提高自己,减少加班时间吧。

+

技术浪潮

这一年是技术浪潮爆发的一年。大数据、AI、人工智能、无人驾驶…各种技术名词频频见诸于头条新闻。在这喷薄的大浪中,前端领域也爆发出自己的色彩:MVCMVVCstyluslesssassgruntgulpwebpackngreactvuenode、大前端开发….等等,技术的更新换代不断地推动着前端领域的向前发展。作为一个前端开发者,深深的感受到我大前端的魅力,也越发对技术存有敬畏之心,不断的驱使我追赶它的浪潮,我愿意在它的浪潮中沉浮。

+

我的Github

github是在2015年末注册的,期间一直不知怎么玩。在上半年,学习了git,喜欢使用命令行,也喜欢在github上提交一些东西,后面学习了vue后,慢慢的提交一些简单的demo上去,挺享受这个过程的。对了,我也通过github+hexo托管了一个静态博客,平时自己的写的东西也往上丢,不过貌似没坚持多久…..2017我会更加勤劳。

+

换了个显示器

这是一个不得不说的显示器:前端开发当然得用大的好的显示器,于是三秒写了一年的12Q换来一个帅气的显示器,从此以后心情舒畅,撸码不累,加班更勤。

+

进步与不足

踩着2016的尾巴,我细数了2016发生在我身上的变化,虽有些许进步,但却也说不出个所以然,如果非要说,那么就是自己解决问题的能力强了,眼界开阔了,有自己的思考了,目标清晰了。
相对于进步,不足之处就很明显了,有很多也是我的致命伤,更是职业道路前进的致命伤。

+
    +
  • 习惯
      +
    • 有点小拖拉
    • +
    • 三分钟热情
    • +
    • 不仔细
    • +
    +
  • +
  • 技术
      +
    • 基础不够
    • +
    • 没有创新
    • +
    • 阅读很不够
    • +
    • 至今没有代表作
    • +
    +
  • +
+

有不足,能正视不足,更需要我弥补不足。

+

滚滚而来的2017

2017年似乎要做的事情很多。一件件做,总有完成;一步步走,总有尽头。

+

TODO

    +
  • EMAC 2016-2017
  • +
  • VUE,REACT
  • +
  • NODE
  • +
  • 涨工资
  • +
+

又是新的一年开启,新年愿有一番好景。

+]]>
+ + record + + + 总结 + +
+ + Git系列常用命令之放弃修改 + /2018/01/20/Git%E7%B3%BB%E5%88%97%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E4%B9%8B%E6%94%BE%E5%BC%83%E4%BF%AE%E6%94%B9.html + +

Git 是一种流行的分布式版本控制系统,它允许开发者在不同的分支上进行工作,同时保持代码的完整性和历史记录。在使用 Git 进行版本控制的过程中,我们可能会遇到需要放弃当前工作进度的情况,例如,当发现当前分支的修改与主分支的代码不兼容,或者需要切换到另一个分支继续工作时。

+ +

概述

这篇文章会介绍如何在 Git 中放弃对文件的修改,包括以下几个方面:

+
    +
  • 放弃对单个文件/文件夹的修改:如果只想放弃对某个特定文件的修改,可以使用 git checkout 命令。
  • +
  • 放弃所有未提交的修改:如果需要放弃对所有未提交文件的修改,可以使用 git reset 命令。
  • +
  • 撤销已经提交的更改:如果需要撤销已经提交但尚未推送到远程仓库的更改,可以使用 git revert 命令。
  • +
  • 清理未跟踪的文件:有时候,你可能想要删除那些被错误添加到工作目录中但未被 Git 跟踪的文件,可以使用 git clean 命令。
  • +
+

文章可能会通过具体的命令示例和场景,帮助读者理解在不同情况下如何使用 Git 命令来放弃修改,以保持工作流程的流畅和代码的整洁。

+

两个概念

在 Git 中,indexcommit 是两个核心概念,它们在版本控制过程中扮演着不同的角色,理解这两个概念有助于加深对git工作原理的理解:

+

Index(索引)

    +
  • Index 是 Git 用来准备下一次提交的暂存区。你可以将其视为一个准备提交的文件列表,这些文件已经被审查和选择,准备成为下一次提交的一部分。
  • +
  • 当你对工作目录中的文件进行更改并希望将这些更改包含在下一次提交中时,你需要使用 git add 命令将它们添加到 index 中。
  • +
  • Index 允许你在提交之前进行多次修改,并且可以对这些修改进行排序和组织,以确保提交是有意义的。
  • +
  • Index 是一个文件,位于 .git/index 文件中。
  • +
+

Commit(提交)

    +
  • Commit 是项目快照的记录,它包含了项目的某个特定版本。当你执行 git commit 命令时,Git 会将当前 index 中的内容以及一些附加的元数据(如提交信息、作者、时间戳等)打包成一个提交对象,并存储在本地仓库中。

    +
  • +
  • 提交是不可变的,意味着一旦创建,其内容就不能被更改。这保证了项目历史的完整性和一致性。

    +
  • +
  • 提交可以看作是项目的版本号,每个提交都有一个唯一的哈希值,用于标识和引用特定的项目状态。

    +
  • +
  • 提交是 Git 分布式特性的基础,因为它允许开发者在本地进行提交,而不必立即与远程仓库同步。

    +
  • +
+

两者之间的关系可以这样理解:

+
    +
  • 你首先对文件进行修改。
  • +
  • 使用 git add 将这些修改的文件添加到 index 中,这时候修改被暂存,准备提交。
  • +
  • 使用 git commit 将 index 中的内容以及提交信息一起打包,创建一个新的提交对象。
  • +
+

简而言之,index 是准备提交的暂存区,而 commit 是已经提交的快照记录。在进行提交之前,你可以多次修改 index,但是一旦执行了 commit,那么这个提交就是最终的,不可更改的。

+

请深刻理解这两个概念,有助于帮助您理解后面的内容

+

命令

Git Checkout - 切换分支或恢复工作目录树文件

+

更新工作区中的文件,使其与索引或指定的树中的版本一致。 如果没有给出pathspec,’git checkout’也将更新HEAD,将指定的分支设为当前分支

+
+

使用git checkout来切换分支用的很多,但是没想到还可以用来恢复工作目录,可以理解为:
将一个文件从另一个提交中取出,从索引中恢复,所以它不会影响到git add及之后的内容。

+

具体用法如下:

+
git checkout -- <filename>
+ +

注意这里的filename是支持通配符匹配的,比如

+
# 恢复hello.html
git checkout -- hello.html

# 恢复hello.开头的文件
git checkout -- "hello.*"

# 恢复.html结尾的文件
git checkout -- *.html
+ +
+

注意此方法只是将本地文件恢复了,没有对git add 和git commit 产生影响

+
+

Git-reset - 重置当前HEAD到指定的状态

+

复制条目到索索引或者将当前的分支头(HEAD)设置为某个commit

+
+

Git-revert - 还原某些现有提交

+

给定一个或多个现有提交,还原相关补丁引入的更改,并记录一些新提交来记录这些更改。 这要求你的工作区是干净的(没有对 HEAD 提交的修改)。

+
+

Git-clean - 删除工作目录树中未跟踪的文件

+

从当前目录开始,通过递归删除不在版本控制之下的文件来清理工作区。
通常情况下,只有 Git 未知的文件会被删除,但如果指定了 -x 选项,被忽略的文件也会被删除。例如,这对删除所有构建产品很有用。
如果给出任何可选的<路径规范>…​参数,只有那些与路径规范相匹配的路径会受到影响

+
+

一些常见场景

本地修改了一些文件 (并没有使用 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
git checkout -- <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
git checkout .
+ +

本地新增了一些文件 (并没有 Git Add 到暂存区),想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
rm  -rf <filename>
+ +
    +
  •   所有文件
  • +
+
git clean -xdf
+ +
+

删除新增的文件,如果文件已经已经 git add 到暂存区,并不会删除!

+
+
    +
  •   所有文件和文件夹:
  • +
+
git clean -xdff
+ +
+

ps:谨慎操作: 本命令删除新增的文件和文件夹,如果文件已经已经 git add 到暂存区,并不会删除!

+
+

本地修改/新增了一些文件,已经 Git Add 到暂存区,想放弃修改

    +
  •   单个文件/文件夹:
  • +
+
git reset HEAD <filename>
+ +
    +
  •   所有文件/文件夹:
  • +
+
git reset HEAD .
+ +

本地通过 Git Add 和 Git Commit 后,想要撤销此次 Commit

    +
  • 撤销 commit, 同时保留该 commit 修改:
  • +
+
git reset <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前 6 位。

+
+

撤销之后,你所做的已经 commit 的修改还在工作区!

+
+
    +
  •   撤销 commit, 同时本地删除该 commit 修改:
  • +
+
git reset --hard <commit_id>
+ +

这个 commit_id 是你想要回到的那个节点,可以通过 git log 查看,可以只选前6位

+
+

ps:谨慎操作: 撤销之后,你所做的已经 commit 的修改将会清除,仍在工作区/暂存区的代码也将会清除!

+
+

参考

1.Git - git-checkout Documentation
2.Git - git-reset Documentation
3.Git - git-revert Documentation
4.Git - git-clean Documentation

+]]>
+ + 工具/git + + + git + +
+ + Mac提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”的解决方法 + /2021/09/14/Mac%E6%8F%90%E7%A4%BA%E2%80%9Dxxx.app%E5%B7%B2%E6%8D%9F%E5%9D%8F%EF%BC%8C%E6%97%A0%E6%B3%95%E6%89%93%E5%BC%80%EF%BC%8C%E4%BD%A0%E5%BA%94%E8%AF%A5%E5%B0%86%E5%AE%83%E7%A7%BB%E5%88%B0%E5%BA%9F%E7%BA%B8%E7%AF%93%E2%80%9D%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95.html + 随着mac系统的升级,对app安全控制越来越严,经常遇到一些非官方渠道安装的app,安装后无法打开的情况,如提示”xxx.app已损坏,无法打开,你应该将它移到废纸篓”,一般情况下,出现该问题是因为软件没有签名,所以被MAC系统禁止运行。可以尝试以下解决方案

+

老系统:设置允许任何来源下载的App

比较老的版本系统,可以按以下步骤操作:
打开”系统偏好设置 -> 安全与隐私 -> 通用”选项卡,检查是否已经启用了”任何来源”选项。如果没有启用,先点击左下角的小黄锁图标解锁,然后选中”任何来源”。(由于作者mac系统比较新,故此图来源网络)
image.png

+

如果没有”任何来源”的选项,打开终端,输入以下命令:

+
sudo spctl --master-disable
+ +

sudo spctl --master-disable 是一个在macOS操作系统中使用的命令行指令,用于修改系统安全策略控制(System Integrity Protection,简称SIP)的设置。SIP 是一种安全特性,用于保护系统文件和目录免受未授权的修改。

+

sudo spctl --master-disable 的作用如下:

+
    +
  1. 禁用 SIP:这个命令会禁用 SIP 功能,允许用户对系统文件进行修改。默认情况下,macOS 会阻止对某些系统文件和目录的修改,以保护系统安全。
  2. +
  3. 需要管理员权限:由于这个命令涉及到系统级别的更改,因此需要使用 sudo 来获取管理员权限。
  4. +
  5. 临时禁用:这个命令的禁用效果是临时的,重启计算机后 SIP 会重新启用。
  6. +
  7. 安全性风险:禁用 SIP 会降低系统的安全性,因为它允许对系统文件进行修改。因此,只有在确实需要修改系统文件时才应该使用这个命令,并且在完成修改后应立即重新启用 SIP。
  8. +
  9. 重新启用 SIP:要重新启用 SIP,可以使用 sudo spctl --master-enable 命令。
  10. +
+

一般来讲到这一步就可以了,但是如果你的系统比较新,你还得继续看下去

+

新系统:使用xattr -rd命令

如果你的系统比较新,或者已经打开了通用 > 信任任何来源安装后还是报错,那么在终端里执行以下命令:

+
sudo xattr -rd com.apple.quarantine /Applications/xxxx.app
# 将xxx替换成app的名字,如果你无法准确知道app名称,可以直接将app拖到终端中
# 按提示输入你的电脑密码即可。
+ +

sudo xattr -rd com.apple.quarantine /Applications/xxxx.app 是一个在macOS操作系统中使用的命令行指令,它用于移除文件或应用程序的扩展属性(extended attribute),具体来说,是移除一个名为 com.apple.quarantine 的属性。

+

这个属性通常在文件或应用程序从互联网下载后被添加,作为macOS的一种安全机制。它提示用户,该文件可能来自不信任的来源,需要确认是否信任并运行该应用程序。这个属性有时也被称为”隔离标记”(quarantine flag)。

+

命令的各个部分含义如下:

+
    +
  1. sudo:以管理员权限执行后面的命令。由于修改文件的扩展属性需要管理员权限,所以这里使用 sudo
  2. +
  3. xattr:这是用于查看和修改文件扩展属性的命令行工具。
  4. +
  5. -rd-r 表示递归地移除属性,-d 表示删除指定的属性。
  6. +
  7. com.apple.quarantine:这是要删除的扩展属性的名称。
  8. +
  9. /Applications/xxxx.app:这是要移除隔离标记的应用程序的路径。xxxx.app 应该替换为实际的应用程序名称。
  10. +
+

使用这个命令后,应用程序将不再显示警告,提示它可能来自互联网。这在安装从可信来源下载的应用程序时很有用,尤其是当用户确信该应用程序是安全的,但macOS仍然显示隔离警告时。

+]]>
+ + mac + + + mac + app损坏 + +
+ + 移动端适配方案 + /2016/11/22/Mobile-terminal-adapter.html + +

移动端越来越被大众所接收,那么相应的技术就越来越向它靠拢,这是一种不可阻挡的趋势,也是万物发展的规律。移动端有三大难题:兼容、调试和适配。这三大问题就好像三座无法逾越的大山阻挡者我们前进的步伐,此文将记录我在项目中关于移动端适配的一些方式,供大家参考

+ +

分析

移动端适配的根本原因

+
    +
  • 屏幕窗口的大小
  • +
  • 设备像素比(devicepixelratio,简称dpr)
  • +
+

很多地方介绍设备像素比的,这里就不做具体探讨,简单总结一下:devicepixelratio(设备像素比,即dpr) = physicalpixel (物理像素) / density-independent pixel(设备独立像素,即dip)。dipdp,(device independent pixels,设备独立像素)与屏幕密度有关。dip可以用来辅助区分视网膜设备还是非视网膜设备。
JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr,但遗憾的是并不是所有的都支持。
在CSS中,可以分别针对屏幕大小和设备像素比做适配:
针对窗口大小,一般使用媒体查询的only screenmin-widthmax-width来适配,也是使用css做适配最常见的一种方式
针对像素比,可以使用-webkit-device-pixel-ratio-webkit-min-device-pixel-ratio-webkit-max-device-pixel-ratio属性,同样他的支持度一样不高(其实是很低),所以几乎没有用武之地。

+

适配方式

移动端适配主要有两大不同的方向:

+
    +
  • 响应式布局:它是根据屏幕大小自动的调整布局位置(非单纯的缩放),实现适配
  • +
  • 自适应布局:它是根据屏幕大小自动的缩放大小,实现适配。
  • +
+

两种方式应用的场景不同,各有优劣,本人对自适应布局使用的比较多

+

解决方案

为了解决这个老大难问题,从最初开始百分比到em,然后到现在rem的使用,都一一体现着技术的滚滚向前。目前是用的最多的也就是rem,他们的区别和有点请自行GG
有了rem这个大杀器,解决问题就变得简单起来,具体请往下看。

+

纯css实现方式 – 媒体查询

使用原生css来实现媒体查询是很繁琐的,因为每个媒体查询都要去设定规则。推荐使用css的预编译器(sass,less,stylus),比较方便。

+
/* 定义规则 */
html {
font-size: 20px;
}
@media only screen and (min-width: 401px) {
html {
font-size: 24px !important;
}
}
@media only screen and (min-width: 428px) {
html {
font-size: 28 !important;
}
}
@media only screen and (min-width: 481px) {
html {
font-size: 30px !important;
}
}
@media only screen and (min-width: 569px) {
html {
font-size: 35px !important;
}
}
@media only screen and (min-width: 641px) {
html {
font-size: 40px !important;
}
}
@media only screen and (min-width: 751px) {
html {
font-size: 50px !important;
}
}
@media only screen and (min-width: 1080px) {
html {
font-size: 60px !important;
}
}
+ +
//less 方式调用
@unit: 50rem; //基准单位,根据设计稿来确定。假设:设计稿尺寸为750,那么@unit设置为50rem(1rem=50px更方便下面计算)
.warp{with: 100 / @unit} // 设计稿上元素的尺寸为100px => .warp{with: 2rem}
.warp{with: 10 / @unit} // 设计稿上元素的尺寸为10px => .warp{with: 0.2rem}
+ +

如果这里使用原生css来做,每个尺寸都需要去计算,如果使用预处理器,只需要定义一个变量,计算的事情直接交给它们就行。

+

这样当页面展示在750的屏幕上时,html的font-size50px,那么当设置为2rem的元素显示的尺寸就为2*50px=100px。在其他尺寸的设备也会根据媒体查询设置的不同font-size进行自动缩放适配。
当然上面也提到了,在css中也是可以获取到devicePixelRatio的值,那么为了更精确在写媒体查询的时候可以把它也加上去,这里就不展开了。

+

纯css实现方式 – 计算属性

当然除了媒体查询,还有一种更潮的方式就是利用css3的一些新属性:计算属性和vw属性来实现自动设置根字体大小的目的

+
html{font-size:calc(100vw/6.4)} //6.4为psd设计稿尺寸/100
+ +

这套方案几乎是目前最简洁的方案了,并且calcvw在移动端的支持也不错哟。

+

js的实现方式

js的实现方式,参考了网易淘宝的实现方式,对他们进行了整合。并且修复了手机端1px问题

+
    +
  • 网易实现方式是通过设备尺寸动态的设置DOM的根元素字体大小,没有考虑devicePixelRatio的因素;
  • +
  • 淘宝实现方式也是通过设备尺寸动态的设置DOM的根元素字体大小,并且考虑了devicePixelRatio的因素,但淘宝在设置rem时,显得较复杂(不方便写css把px转化成rem);
  • +
  • 1px问题简单点说就是因为devicePixelRatio的存在,css的1px不等于移动端的1px。
  • +
+
(function(doc, win, designSize) { //designSize为设计稿的尺寸(宽)

var docEl = document.documentElement,
devWidth = docEl.clientWidth > 1080 ? 1080 : docEl.clientWidth,
dpr = devicePixelRatio || 1,
scale = 1 / dpr,
width = dpr * devWidth,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'onresize', //判断横屏和窗口重置
recalc = function() {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
document.querySelector('meta[name="viewport"]')
.setAttribute('content','width=' + width +
', initial-scale=' + scale +
', maximum-scale=' + scale +
', minimum-scale=' + scale +
', user-scalable=no');
docEl.style.fontSize = devWidth / (designSize / 100) * dpr + 'px';
};

if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);

})(document, window, 750);
+ +

总结

    +
  • 以上三种方案对比,第一种媒体查询是最死板的,基本就是纯体力活。
  • +
  • 利用css的计算属性可以很优雅的解决问题,但是在兼容方面来说,目前还不是很完美
  • +
  • 并且利用纯css也没考虑devicePixelRatio(像素问题)这个因素。
  • +
  • 最后的javascript解决方案则是考虑到了兼容和devicePixelRatio这些因素,但是这里有一个很大的弊端就是:页面在某些情况(性能慢)会经过两次重回(给HTML根设置font-size和设置meta标签时),在网络或者终端性能不是很好的情况用户体验很不错甚至页面错乱的情况。
  • +
+]]>
+ + javascript + + + rem + mobile + +
+ + Oh My Zsh 自动化:轻松管理多个 Git 用户身份信息 + /2024/04/23/Oh%20My%20Zsh%20%E8%87%AA%E5%8A%A8%E5%8C%96-%E8%BD%BB%E6%9D%BE%E7%AE%A1%E7%90%86%E5%A4%9A%E4%B8%AA%20Git%20%E7%94%A8%E6%88%B7%E8%BA%AB%E4%BB%BD%E4%BF%A1%E6%81%AF.html + +

在 Git 工作流中,有时需要在同一台电脑上针对不同的项目设置不同的用户身份。例如,开发人员可能需要在个人项目和公司项目之间切换,而这两个项目可能需要不同的 Git 用户名和电子邮件地址。Oh My Zsh 提供了强大的自定义功能,允许我们通过编写自定义的 shell 函数来实现这一需求。本文将展示如何使用 Oh My Zsh 的 chpwd 函数来自动设置特定目录及其子目录、孙目录下的 Git 用户配置。

+ +

开始之前

假设你对git身份信息和ssh key概念有比较清晰的认识和了解其基础使用,比如git身份信息是用户名和邮箱,在你使用git commit的时候进行记录;而ssh key是一个凭证(分为公钥和私钥),用于远端身份信息验证,和git没有直接关系,也可以用于其他场景使用,比如ssh登录远程主机。

+

简单说下为什么要使用不同的git身份信息呢?
公司项目要求使用规定的name(比如企微名,花名或者公司内部系统的唯一标识等)和email(公司邮箱)作为git提交记录,这样方便团队协作和代码管理,这些信息有一定的敏感性。所有在提交代码到外网的时候必须考虑使用不同的git身份信息来提交。

+

当然我们可以在每次克隆一个新项目的时候,根据不同需要使用git config –local进行设置,但这明手动操作容易遗忘而且繁琐,所以想到使用脚本来自动化完成。

+

最终效果

本文不是解决:不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的ssh key凭证这个问题,而且为了解决以下问题:

+
    +
  • 不同的远程仓库(比如github和gitee,公司自建的gitlab等)使用不同的git身份信息
  • +
+

最终想达到:

+
    +
  • 进入到指定目录及其子目录,孙目录,如果是git仓库,就自动设置对应的git身份信息
  • +
+

约定目录结构:

/Users/developer/teamA         # 根目录
└── projectA # 子目录,团队项目
└── src # 孙目录

/Users/developer/teamB
+ +

解决方案

解决这个问题的方案有很多,下面分享一种使用zsh脚本的方案。首先无论哪一种方案,都需要在全局设置全局git信息:

+
git config --global user.name "name1"
git config --global user.email"email1@email.com"
+ +

安装 Oh My Zsh

如果尚未安装,通过以下命令安装(已经安装就跳过):

+
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
+ +

编辑 .zshrc 文件

添加 chpwd 函数:在 .zshrc 最后添加以下函数

+
vi ~/.zshrc
+ +

确保启用了plugin:

+
# 保证这一行不被注释, 括号的内容可能不一样
plugins=(git)
+ +

.zshrc 最后,添加 chpwd 函数:

+
 chpwd() {
# 指定目标目录
local root_dir="/Users/developer/teamA" # 指定目录
# 指定该目录要设置的git name信息
local user_name="name"
# 指定该目录要设置的git email信息
local user_email="email@email.com"
# 获取到当前目录
local current_dir="$(pwd -P)"
# 如果当前目录是否是目标目录,或者是否其子目录,孙目录
if [[ "$current_dir" == "$root_dir"* ]]; then
# 输出当前目录,用于调试,后续可删除
echo "chpwd: $PWD"

if [ -d "$current_dir/.git" ]; then
git -C "$current_dir" config --local user.name "$user_name"
git -C "$current_dir" config --local user.email "$user_email"
echo "chpwd: 成功设置 user.name 和 user.email"
fi
fi
}
# 执行 chpwd 函数
chpwd
+ +

为了让更改生效,需要重新加载 .zshrc 文件。在终端中运行以下命令(或者关闭后重新打开)

+
source ~/.zshrc
+ +

ps: 当然你如果使用的其他命令行终端,可以参考这个脚本,根据终端的特点,自行配置

+

测试

在vscode中打开控制台:

使用vscode打开项目/Users/developer/teamA/projectA,并且打开控制台,如果发现有成功输出信息,就表示成功了。

+
chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。如果输出的信息是你预期的表示脚本成功了。

+

同样,使用vscode打开一个不在/Users/developer/teamA目录下的项目,如果没有输出该消息说明符合预期的

+

测试cd命令

打开zsh终端,使用cd命令进入/Users/developer/teamA/projectA,如果发现有成功输出信息,就表示成功了。

+
cd /Users/developer/teamA/projectA

chpwd: 成功设置 user.name 和 user.email
+ +

保险起见,也用git config --local命令查看一下信息有没有正确设置。

+

同样,进入/Users/developer/teamB/projectB:

+
cd /Users/developer/teamB/projectB
+ +

如果没有输出该消息说明符合预期的

+

方案限制

必须限制不同的远程仓库放到对应的目录,如果你现在的本地项目已经分散到不同的目录了,就必须要重新移动一下本地目录或者重新clone一下远程仓库到对应目录

+

总结

通过 Oh My Zsh 的 chpwd 函数,我们能够自动化 Git 用户信息的设置,这不仅提升了工作效率,也减少了配置错误的可能性。本文提供的步骤和测试验证了解决方案的有效性,展示了 Oh My Zsh 在自动化 shell 任务中的实用性。

+]]>
+ + git + oh-my-zsh + + + git + oh-my-zsh + git多身份 + +
+ + 实现数字滚动变化以及延伸 + /2016/06/14/animate-number.html + +

利用jquery的插件jquery.animateNumber实现一个简单的数字滚动效果

+ +

需求分析

    +
  1. 处理数据:因为数据是后端提供,所以有可能格式不是我们想要的,所以也许需要格式化数据;
  2. +
  3. 根据页面设计的效果图(如图),需要把数字字符串拆分成单个数字字符串
    animateNumber_01
  4. +
  5. 每一个数字进行滚动变化
  6. +
  7. 最后,在项目中,我选取了插件jquery.animateNumber来实现滚动效果。这个插件的使用方式很简单,在官方有很详尽的文档来展示各个案例,就不一一赘述了。
  8. +
+

HTML布局

其中num是后台传入的值,notChangeUint用来标记不进行单位变换的值

+
<div class="warp">
<div class="net-credit-num" >
<p>平台累积会员人数(人)</p>
<span date-num="123" class="animateNumber notChangeUint"></span>
</div>
<div class="net-credit-money">
<p>平台完成投资金额(万元)</p>
<span date-num="91,123,456.00" class="animateNumber"></span>
</div>
<div class="net-return-money">
<p>累计已还款金额(万元)</p>
<span date-num="8,895,678.00" class="animateNumber"></span>
</div>
</div>
+ +

撸JS

去除逗号(,)

num = num.replace(',','');
+ +

上面这种方法只能去除字符串中的第一个逗号,但是实际数据中可能存在多个逗号,所以需要用到正则全局匹配替换,代码如下:

+
var reg = new RegExp(',','g');
num = num.replace(reg,'');
+ +

转化单位(元–>万元)

把金额单位转化为万元,并且保留两位小数,人数不进行转化

+
if(!numWarpParent.hasClass('notChangeUint')){
num = (Number(num) / 10000).toFixed(2);
}
+ +

字符串拆分为数组

numArry = num.split('');
+ +

把数字添加到页面并调用animateNumber的方法

for(var i = 0; i < numArry.length; i++){
var thisNum = parseInt(numArry[i]);
var spanNum;
if (!isNaN(thisNum)){
spanNum = $('<span class="single-num">' + numArry[i] +'</span>');
}else{
spanNum = $('<span class="single-point">.</span>');
};
numWarpParent.append(spanNum);
thisNumWarp.prop('number', stratNum).animateNumber({
number: thisNum
}, time);
}
+ +

最后代码

$('.animateNumber').each(function(){
var _this = $(this);
var totalNum = _this.attr('date-num'); //后台数据储存在date-num上
appendNum(totalNum,_this);
});
function appendNum(num,numWarpParent){
var newNum;
var reg = new RegExp(',','g'); //正则匹配所有逗号
newNum = num.replace(reg,'');
if(isNaN(num)) newNum = 0; //容错,当后台传入的参数错误(非数字)时,将只值置为0,以保证页面的正常渲染
if(!numWarpParent.hasClass('notChangeUint')){ //判断是否需要转换单位
newNum = (Number(newNum) / 10000).toFixed(2);
};
numArry = newNum.split('');
for(var i = 0; i < numArry.length; i++){
var thisNum = numArry[i];
var numWarp;
if (!isNaN(thisNum)){ //判断是否可以转化为数字
numWarp = $('<label class="single-num">' + numArry[i] +'</label>');
}else{
numWarp = $('<label class="single-point">.</label>');
};
numWarpParent.append(numWarp);
isAnimate(thisNum,numWarpParent,i);
};
};
function isAnimate(num,numWarpParent,index){
if (isNaN(num))return;
//调用animate.js插件方法
numWarpParent.find('label').eq(index).prop('number', 0).animateNumber({
number: num
}, num * 100);
};
+ +

总结

1.功能模块化,尽量一个方法(函数)只做一件事情
2.容错,由于涉及到DOM操作,所以为了保证页面的正常渲染必须有容错处理机制:数据出错不影响整个流程(页面渲染)的畅通

+]]>
+ + javascript + + + animation + +
+ + Babun导致本地SSH-KEY不可用 + /2017/06/15/babun-casue-ssh-key-bad.html + +

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+ +

ps: 前文有我记录的关于Babun的一些特点,以及使用,请看windows平台下超强的cmd工具Babun使用笔记一文

+

问题描述

如果你本地先安装了git命令行工具并生成了ssh-key,再安装Babun之后,可能会导致原有的ssh-key不可用,原因:
安装Babun会添加全局变量Home,指向Babun安装目录下的.Babun/cymwin/home,因此在使用命令生成key时不会在C:\Users\userName\.ssh目录。

+
ssh -T git@github.com
Permission denied (publickey).
+ +

解决办法

    +
  • 删掉以前目录(C:\Users\userName\.ssh)下的ssh-key。
  • +
  • 生成重新生成ssh key,此时生成的key在.Babun\cymwin\home\userName\.ssh下。
  • +
  • 把生成的key映射到C:\Users\userName\.ssh目录。
  • +
  • 获取权限
  • +
  • 把key关联到相应github账户(此处以github为例)。
  • +
  • 测试ssh key是否可用
  • +
+
ssh -T git@github.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0670 for '/home/Administrator/.ssh/id_rsa' are too open.
It is recommended that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/Administrator/.ssh/id_rsa": bad permissions
Permission denied (publickey).
+ +

当生成key之后,测试是否联通,你会发现还是报错了,提示权限不够,错误信息为Permissions 0670

+

在终端切换到C:\Users\userName\.ssh目录,执行下面命令

ln -s /c/Users/userName/.ssh /home/userName/.ssh
+ +

此操作会把.Babun\cymwin\home\userName\.ssh目录下的ssh key映射C:\Users\userName\.ssh

+

在终端切换到根目录(~),执行以下命令(一般只执行其中一个)

chmod 400 ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh/id_rsa
ssh -T git@github.com
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+ +

参考文章:

+

1.https://github.com/Babun/Babun/issues/327
2.http://stackoverflow.com/questions/9270734/ssh-permissions-are-too-open-error

+]]>
+ + tools + + + git + Babun + cmd + shell + +
+ + windows平台下超强的cmd工具Babun使用笔记 + /2017/03/15/babun.html + +

Babun是一款集颜值功能于一身的window平台下的命令行工具。它集成了zsh、Cygwin等强大的工具,支持各种配置,并且有丰富的插件支持;并且有丰富的命令和命令提示功能,以及超级棒的历史命令提示。

+ +

Babun

官方贴出了Babun的十大特性

+
    +
  • Pre-configured Cygwin with a lot of addons: 预置大量的Cygwin插件
  • +
  • Silent command-line installer, no admin rights required:静默命令行安装,不需要管理员权限
  • +
  • pact - advanced package manager (like apt-get or yum): 支持pact高级包管理器,类似于apt-get、yum等
  • +
  • xTerm-256 compatible console: xterm-256兼容控制台
  • +
  • HTTP(s) proxying support: HTTP(s) 代理支持
  • +
  • Plugin-oriented architecture: 插件体系,可以安装丰富的插件
  • +
  • Pre-configured git and shell: 预置git和shell,支持自定义配置
  • +
  • Integrated oh-my-zsh: 集成了zsh
  • +
  • Auto update feature: 自动检测最新版本
  • +
  • “Open Babun Here” context menu entry: 支持右键菜单“此处打开Babun”
  • +
+

当然对于上面这些特性,我不得不补充一点,那就是它强大的命令提示功能,能从根据你的输入匹配历史输入,狠棒!

+

Cygwin

Babun的核心包括一个预配置的Cygwincygwin是一个非常好的工具,但有很多使用技巧,使你能够节省大量的时间。Babun解决了很多问题,它里面包含了很多重要的软件包,是你能够第一时间能够使用它们

+

shell

Babunshell通过调整,已达到最佳的用户体验,Babun有两个配置之后马上使用的shell(默认使用zsh,可以使用bash或者zsh命令切换到对应的模式),Babunshell具有以下的特点:

+
    +
  • 语法高亮
  • +
  • 具有unix的工具
  • +
  • 软件开发工具
  • +
  • git-语义提示
  • +
  • 自定义脚本和别名
  • +
  • +
+

Console

Babun支持HTTP代理,只需添加地址和HTTP代理服务器的凭据。Babunrc文件所在文件夹执行源Babunrc启用HTTP代理。目前还不支持SOCKS代理。

+

开发者工具

Babun提供多种方便的工具和脚本,是你的开发工作更轻松,具有的功能如下:

+
    +
  • 编程语言(python,Perl, etc等)
  • +
  • git(各种各样的别名调整)
  • +
  • UNIX工具((grep, wget, curl, etc)
  • +
  • vcs (svn, git)
  • +
  • oh-my-zsh
  • +
  • 自定义脚本(pbcopy, pbpaste, Babun, etc)
  • +
+

安装

默认安装

双击install.bat脚本,Babun使用默认安装位置C:\Users\userName\.Babun,安装好的Babun会在C:\Users\userName\下;
当然也可以指定安装位置

+

自定义安装

通过cmd命令行在执行install.bat时指定参数/t/target指定安装的目录。
执行:Babun.bat /t install-dir

+
Babun.bat /t c:\Babun
+ +

安装好之后会在d:\Babun目录下生成一个.Babun的目录,Babun所有文件都在这个目录中。注意安装目录最好不要有空格,这是cygwin要求的

+

启动Babun默认是在’%userprofile%.Babun\cygwin\home\username’

+

开发环境配置

pip

Babun内置了PythonPerl等解释器。cygwin自带的python没有pip,需手动安装。
直接执行下面这个命令就好了。

+
wget https://bootstrap.pypa.io/get-pip.py -O - | python
+ +

有了pip就可以自由的安装诸如ipython之类的东西,还有包罗万象的类库。

+

常用插件

Babun默认是安装了oh-my-zsh的,这里可以根据自身情况安装一些插件。具体可参考利用oh-my-zsh打造你的超级终端一文;

+

包管理器使用

Babun提供一个叫pact包管理工具,类似于linux上面的apt-getyum的包管理工具

+

配置别名(alias)

可以在.Babun\cygwin\home\username目录下配置对应工具的别名,而并不仅限于git-bash
当然记忆别名其实也是体力活,我的想法是对一些常用的命令、经常手滑手速过快打错的命令、复杂的命令配置一些别名,例如

+
gt = git
gti = git
n = npm
nr = npm run dev
gtlg = git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
...等等
+ +

问题

本地SSH-KEY不可用

查看我记录的Babun导致本地SSH-KEY不可用一文

+

中文乱码问题

找了很多解决方案,都不能完美的解决问题,最后还是回归原始:不解决!!!

+

锁定文件夹

在使用Babun时(比如此时进入了a目录),它会锁定文件夹a目录,导致你可能无法做一些危险操作。必须关闭Babun后才能解锁进程

+

参考文章:

+
    +
  • windows下的命令行工具Babun
  • +
+]]>
+ + tools + + + git + Babun + cmd + shell + +
+ + 风继续吹 + /2017/11/29/blow-forever.html + +

最近在某些比较看重的事情上,处理的很失败,感觉整个心态都发生了变化。下班独行的那段路上,突然惊醒自己也许需要做出一些改变,晚饭后静静的思考了一阵:似乎勇往前行才是我最需需要的改变。古语有云:走自己的路,让别人去说吧。是的,生活需要你不断踽踽独行,活在他人的世界里是可怕的,特别他(她)还是陌生人。随手记录一些句子,它们是我最近的心态和生活的写照,也以此鞭策自己砥砺前行。

+ +
+

时间不会因你沮丧而停滞不前
生活不会因你懊恼而雨过天晴

+

如风,继续吹
吹来往日的欢歌笑语
吹来远方的殷殷思念
化作雨露滋润心田

+

如风,继续吹
随风飘荡去远方
随风逐浪勇往前
风不止浪不息

+

如风,继续吹
时而狂乱,掠过天空大地,高山河流
时而低喃,轻抚绿茵花朵,平原盆谷
最后都消散在其它风里

+

然后
其它风,继续吹

+

—-< 谨鼓励我砥砺前行 >

+]]>
+ + 随想 + + + 人生 + +
+ + H5的Notification特性 - Web的桌面通知功能 + /2017/10/16/desktop-notification.html + +

目前,web网页使用桌面通知功能的越来越多,包括微博,腾讯视频等大厂站,桌面通知功能是H5的一个API - Notifications。它允许网页或应用程序可以发出通知,通知将被显示在页面之外的系统层面上(通常使用操作系统的标准通知机制,但是在不同的平台和浏览器上的表现会有差异),这样即使应用程序空闲或在后台也可以向用户发送信息。

+ +

应用场景

Notifications的诞生简化了网站或者应用与用户之间的沟通成本(时间成本和开发成本),增强用户黏性(减少了用户离开应用的可能)。传统的通知方式,大多是通过站内信(消息),邮件,短信等方式,它们通常需要刷新(跳转)页面、离开应用打开其他应用或终端来查看消息;而桌面通知功能大大的简化了这个过程,消息的传递基本不消耗时间(如果不设置setTimeout,用时基本不会超过1s),并且用户不需要离开应用,这都带来了极大的方便。可以预见,Notifications将会在很多网页或应用中被大量使用。当然Notifications也具有它的局限性:无法存档、即看即毁
那么,这个功能到底能用在哪些场景呢?只能说能应用的场景很多:

+
    +
  • 社交类网站
  • +
  • 资讯类网站
  • +
  • 网页版邮件服务
  • +
  • 即时通知类网站
  • +
  • +
+

举个例子,当你打开微博页面,你可能会看到(使用新版浏览器)如下图的通知:
desktop-notification1

+

这就是网站使用了桌面通知功能,当你选择允许,那么当网站有推送消息或者你登陆账号有新的消息将会在桌面的右下角出现一个小弹窗通知,如下:
desktop-notification2

+

感觉有点酷酷的!!!

+

用户权限 - Notification.permission

Notification.permission是一个静态方法,可以获取用户当前的通知权限状态,返回一个String,可以根据返回值判断用户是否授予了通知权限。返回值有三种情况:

+
    +
  • default
      +
    • 用户还未被询问是否授权,所以通知不会被显示。
    • +
    +
  • +
  • granted
      +
    • 表示之前已经询问过用户,并且用户已经授予了显示通知的权限。
    • +
    +
  • +
  • denied
      +
    • 用户已经明确的拒绝了显示通知的权限。
    • +
    +
  • +
+

当值为default或者denied时都不会显示通知消息,只有明确的被设置成granted才会显示通知消息

+
const permission = Notification.permission;
if(permission === 'granted'){
console.log('已经授权通知,可以进行你的通知啦!');
}else{
console.log('用户还未授权,请先授权!');
}
+ +

请求权限 - Notification.requestPermission(CALLBACK)

应用发送通知之前必须要取得发送通知的权限,才能成功进行通知。Notification.requestPermission(CALLBACK)是请求获取权限的方法(有点类似javascriptconfirm弹窗窗),允许传入一个回调,回调会返回用户选择的何种权限,返回两个值,granted代表允许,denied代表拒绝。并且Notification.requestPermission()支持then方式的链式调用,也就意味着可以异步调用它。

+
Notification.requestPermission(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
//两种方式是等价的
Notification.requestPermission().then(function (permission) {
console.log('用户是否允许通知: ',permission === 'granted' ? '允许' : '拒绝');
});
+ +

创建通知 - new Notification(TITLE, OPTIONS)

new Notification(TITLE, OPTIONS)方法创建可以创建一个通知实例,允许参入参数两个参数TITLEOPTIONS。注意默认情况下(实际可以通过OPTIONS中的timestamp参数控制)一旦通知实例被创建出来,它会立即被显示出来。

+

TITLE参数

TITLE表示通知的标题。必须参数,允许数字、字符串和空

+

OPTIONS参数

OPTIONS是非必须参数,必须为一个对象,它包含:
ps: 部分参数在某些浏览器可能会不生效,建议使用最新版的谷歌浏览器。以下某些内容从Notification-MDN-EN结合谷歌翻译得来,很有可能翻译不准确,如有,请提出。

+
{
//通知显示正文。非必须,默认为空
body: '你的好友XX上线了!',
//通知显示正文的图片地址。非必须,默认为空
image: 'imgae url',
//通知左侧图标。非必须,默认为空
icon: 'imgae url',
//通知的分类标记(ID)。非必须,默认为空
tag: 'test',
//通知相关联的数据,通常用于方法的回调,传参。非必须,默认为空
data: '可以是任意数据类型',
//通知显示延迟的时间。非必须,默认通知实例创建完成就显示
timestamp: '',
//通知主体内容的水平展示顺序,有点类似direction属性。非必须,默认值是auto, 可以是ltr或rtl
dir: 'auto',
//当没有足够的空间来显示通知本身时,用于表示通知的图像的URL。非必须,默认为空
badge: 'xxx',
//通知的语言。非必须默认为空
lang: '',
//通知显示时,设备的振动模式。非必须,默认为空
vibrate: [200, 100, 200],
//新通知出现是否覆盖旧的通知,覆盖(true)则永远只显示一条通知,不覆盖(false)则会多条通知重叠。非必须,默认为true
renotify: true,
//通知是否静音。非必须,默认为false,表示无声
silent: false,
//通知声源文件地址。非必须,默认为空
sound: 'mp3',
//是否不在屏幕上显示通知信息。非必须,默认为false表示要显示
noscreen: false,
//指定通知是否应该粘滞性,即不容易被用户清理。非必须,默认false表示不具粘滞性
sticky: false,
//指定通知是否保持活性,知道用户点击或关闭。非必须,默认为false
requireInteraction: false
}
+ +

事件及事件钩子

当通知被创建成功后:

+
    +
  • 通知实例具有一个静态方法可以用来关闭通知
  • +
  • 通知实例具有四个事件钩子,来跟踪通知当前的状态。这些事件可以通过事件处理跟踪onshowonclickoncloseonerror。因为Notification同样继承自EventTarget,因此可以对它调用addEventListener()方法。
  • +
+
const n = new Notification('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
},
timestamp: 3000
});

n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
+ +

demo

写一个简单的例子,可以打开页面体验一下,建议用最新版谷歌浏览器打开~ Notification.js

+
const NotificationInstance = Notification || window.Notification;
if (!!NotificationInstance) {
const permissionNow = NotificationInstance.permission;
if (permissionNow === 'granted') {//允许通知
CreatNotification();
} else if (permissionNow === 'denied') {
console.log('用户拒绝了你!!!');
} else {
setPermission();
}
function setPermission() {
//请求获取通知权限
NotificationInstance.requestPermission(function (PERMISSION) {
if (PERMISSION === 'granted') {
CreatNotification();
} else {
console.log('用户无情残忍的拒绝了你!!!');
}
});
}
function CreatNotification() {
const n = new NotificationInstance('XX网站消息通知', {
body: '你的朋友有新状态啦,快去围观吧!',
tag: '2ue',
icon: 'https://2ue.github.io/images/common/avatar.png',
data: {
url: 'https://2ue.github.io'
}
});
n.onshow = function () {
console.log('通知显示了!');
}
n.onclick = function (e) {
//可以直接通过实例的方式获取data内自定义的数据
//也可以通过访问回调参数e来获取data的数据
window.open(n.data.url, '_blank');
n.close();
}
n.onclose = function () {
console.log('你墙壁了我!!!');
}
n.onerror = function (err) {
console.log('出错了,小伙子在检查一下吧');
throw err;
}
setTimeout(() => {
n.close();
}, 2000);
}
}
+ +

兼容

+ +

参考

    +
  • Notification-MDN-EN
  • +
  • Notification-MDN-CN
  • +
+]]>
+ + H5 + + + Notification + javascript + +
+ + 前端测试探索 + /2017/10/10/fed-test.html + +

前端测试是保证代码质量以及程序稳定的一种可靠方式,同时也从代码层面解决了自测难,自测烦等前端测试综合症。当然在前端开发引入测试环节无疑会增加人力和时间成本,如果最终产生的结果和增加的成本能够两两抵消产生正面效应,那么有必要考虑把测试环节加入到团队的规划中。但是在实际开发过程中,测试用例更多的被用于开源项目中,在大部分公司的生成项目很少使用维护测试用例,其根本原因是不纯粹的生产环境导致维护测试用例变得困难,而目的性很强的开源项目则更纯粹,所以前端测试的发展任重而道远。

+ +

Ps:本次更多的是讲述概念性的东西,代码性的具体实例不做过多实践

+
+

GUI(Graphical User Interface)软件测试

+

前端测试不同于后端测试,因为除了一般的逻辑测试以外,由于存在界面交互,所以涉及到模拟用户行为达到测试的目的。由此引入了一个概念:GUI(Graphical User Interface)软件测试,也就是图形用户界面软件测试

+
+

TDD(Test Driven Development) & BDD(Behaviour Driven Development)

TDD很明显的意思是测试驱动开发,也就是说我们可以从测试的角度来检验整个项目。大概的流程是先针对每个功能点抽象出接口代码,然后编写单元测试代码,接下来实现接口,运行单元测试代码,循环此过程,直到整个单元测试都通过。
TDD的好处自然不用多说,它能让你减少程序逻辑方面的错误,尽可能的减少项目中的bug,开始接触编程的时候我们大都有过这样的体验,可能你觉得完成得很完美,自我感觉良好,但是实际测试或者应用的时候才发现里面可能存在一堆bug,或者存在设计问题,或者更严重的逻辑问题,而TDD正好可以帮助我们尽量减少类似事件的发生。
当然,并不是所有的项目都适合TDD,要使用TDD,我认为必须至少具备以下两个条件

+
    +
  • 项目的业务逻辑很清晰,并且程序员对开发逻辑很清晰
  • +
  • 项目模块的复杂度和依赖度不高。如果复杂度高和依赖度高会导致在最开始拆分单元的时候造成很大的困扰,有可能根本不能顺利拆分
  • +
+

BDD行为驱动开发,这里的行为不是指程序员的行为,而是指业务(程序)的逻辑行为,实际上BDD可以看作是对TDD的一种补充,当然你也可以把它看作TDD的一个分支,因为在TDD中,我们并不能完全保证根据设计所编写的测试就是用户所期望的功能

+

如何实现自动化

说一千道一万,新环节的引入必然带来成本的增加,那么我们如何控制增加的成本在合理范围内?很自然的我们想到了使用工具来实现自动化的测试,让机器帮我完成复杂的交互和测试,并自动监控返回错误报警,为我们手动排除问题提供参考

+

可覆盖的测试

+

那到底前端在开发中需要测试哪些东西?在目前技术又可以实现那些测试?

+
+
    +
  • 函数功能测试
      +
    • 全局变量
    • +
    • 公共方法
    • +
    +
  • +
  • 界面&交互测试
      +
    • 事件交互
    • +
    • 数据输入交互
    • +
    • 特征检测
        +
      • 设计图还原度
      • +
      • 图片大小
      • +
      • +
      +
    • +
    • 特殊情况
        +
      • 自适应和响应式测试
      • +
      • 浏览器兼容
      • +
      • 多端测试
      • +
      • +
      +
    • +
    +
  • +
  • 网络请求测试
      +
    • 数据库访问
    • +
    • 模拟用户登陆等
    • +
    • ajax请求
    • +
    +
  • +
  • 直观的错误信息展示
      +
    • 网页表格
    • +
    • 截图
    • +
    +
  • +
  • 性能测试
  • +
  • 回归测试
  • +
  • 自动化
      +
    • 测试用例数据自动化 - 结合mockjs打造假数据
    • +
    • 测试用例自动化创建 - 通过读取源码中的注释来自动生成测试用例?
    • +
    +
  • +
+

业务逻辑/业务代码/测试用例的关系

业务代码的颗粒度与测试用例的复杂度成反比:颗粒度划分越多(细),复杂度越低
业务代码的量与测试用例的量成正比

+

Good

    +
  • 相对于等待问题产生,更倾向于避免可能的问题
  • +
  • 有利于形成团队代码规范,对团队未来成员的扩充是一个很好的约束规范
  • +
  • 对输出的产品有进一步的质量保证
  • +
+

Bad

    +
  • 增加维护测试用例本(时间和人力)
  • +
  • 增加编码复杂度(需要靠如何更友好的进行测试),对团队人员的编码要求提高了
  • +
  • 也许会增加学习成本(并不一定所有人都会写测试用例)
  • +
  • 需要把控测试用例的合理性、覆盖率、通过率
  • +
+

测试框架

PhantomJS/CasperJS

PhantomJS是一个服务器端的支持JavaScript APIWebKit。其支持各种Web标准:DOM处理, CSS选择器, JSON, CanvasSVG。对于web测试、界面、网络捕获、页面自动化访问等等方面。当启动的时候会在内存在开启一个无界面浏览器,以此模拟用户各种操作,可以对界面截图
Casperjs是对PhantomJS的封装,提供了更加易用的API, 增强了测试等方面的支持

+

PhantomCSS

像素对比工具,基于PhantomJs开发,结合了Casperjs截图和ResembleJs图像对比分析

+

Page-monitor

DOM结构对比工具,基于PhantomJS开发,根据DOM结构与样式的对比来对比整个页面的变动部分

+

BackstopJS

主要通过PhantomJScapserJS等工具在不同尺寸下截图,然后根据resemberJS进行像素比对判断是否正常,以实现响应式测试

+

Mocha + Chai

mocha+chai是一个经典的组合主要用来测试函数功能,也能测试异步操作。也有常用chai的超集(拓展库)sion-chai来加强chai

+

Selenium2

Selenium2,它的主要新功能是集成了Selenium1.0以及WebDriver
也就是说Selenium2SeleniumWebDriver两个项目的合并,即Selenium2兼容Selenium,它既支持Selenium API也支持WebDriver APIWebDriver是一个用来进行复杂重复的web自动化测试的工具,意在提供一种比Selenium1.0更简单易学,有利于维护的API。它没有和任何测试框架进行绑定,所以他可以很好的在单元测试中调用。当启动Selenium2时通常会调起一个可见的界面,但也可以通过设置,让它以PhantomJS的形式进行无界面的测试
当然使用Selenium2必须额外的安装每种浏览器的WebDriver
Selenium2上手难度大于PhantomJS

+

NightwatchJs

推特出品,基于Selenium WebDriver API开发,意味着支持浏览器自动化测试,内部集成了mocha+chai并将它加强,同时支持分组测试和单个测试,对语法进行了简化,归纳有以下特点:

+
    +
  • 简单但强大的语法(更符合js书写习惯),只需要使用JavaScriptCSS选择器,开发者就能够非常迅捷地撰写测试。
  • +
  • 开发者也不必初始化其他对象和类,只需要编写测试规范即可。
  • +
  • 使用CSS选择器或Xpath,定位并验证页面中的元素或是执行命令。
  • +
  • 易于扩展,便于开发者根据需要,实现与自己应用相关的命令。
  • +
  • +
+

目前,SeleniumJavaScript的验收测试方面最流行的工具之一,同类的还有PhantomJS。二者都有其独到的方法:Selenium使用WebDriver API,而PhantomJS使用无界面的WebKit浏览器。它们都是非常成熟的工具,都具有强大的社区支持。它们与Nightwatch之间最大的不同,主要是在于语法的简易度以及对持续集成的支持。与Nightwatch相比,SeleniumPhantomJS都拥有更加冗长的语法,这会让编码变得更庞大

+
this.demoTestGoogle = function (browser) {
browser
.url(“http://www.google.com”)
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'The Night Watch')
.end();
};

//也可以
module.exports = {
'step one' : function (browser) {
browser
.url('http://www.google.com')
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
},

'step two' : function (browser) {
browser
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'Night Watch')
.end();
}
};
+ +

对前端框架的支持

在实际开发中,我们可能是用了不同的框架。虽然我们完全可以在把源码编译成普通的HTML/CSS/JS代码然后测试,但是此种方法的弊端也显而易见:不易于自动化,必须等到所有模块开发完成才能测试…为此我们必须寻找某种方式使得测试不收框架的限制

+

Vue

本身可以通过new一个Vue的方式挂载节点达到效果。
下面是一个简单的测试用例,测试.hello h1标签内容是否符合预期

+
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {
it('should render correct contents', () => {
const Constructor = Vue.extend(HelloWorld)
const vm = new Constructor().$mount()
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App')
})
})
+ +

React

1.官方提供了两种方法:

+
    +
  • 渲染虚拟DOMShallow Rendering
  • +
+

只渲染第一层,不渲染子组件,速度快,返回一个浅渲染的虚拟DOM对象。然后拿到节点的各种信息,进行测试

+
    +
  • 渲染真实DOM节点(renderIntoDocument
  • +
+

renderIntoDocument 方法要求存在一个真实的DOM环境,否则会报错。因此,测试用例之中,DOM环境(即window, documentnavigator 对象)必须是存在的。jsdom库提供这项功能

+
import jsdom from 'jsdom';

if (typeof document === 'undefined') {
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = global.window.navigator;
}
+ +

2.Enzyme
Enzyme是官方测试工具库的封装,它模拟了jQueryAPI,非常直观,易于使用和学习,主要提供三种方法:

+
    +
  • shallow
  • +
+

shallow方法就是官方的shallow rendering的封装

+
import {shallow} from 'enzyme';

describe('Enzyme Shallow', function () {
it('App\'s title should be Todos', function () {
let app = shallow(<App/>);
expect(app.find('h1').text()).to.equal('Todos');
});
};
+ +
    +
  • render
  • +
+

render方法将React组件渲染成静态的HTML字符串,然后分析这段HTML代码的结构,返回一个对象。它跟shallow方法非常像,主要的不同是采用了第三方HTML解析库Cheerio,它返回的是一个Cheerio实例对象。

+
    +
  • mount
  • +
+

mount方法用于将React组件加载为真实DOM节点

+

最后

回到开始,个人认为不要滥用测试,需要合理评估测试用例对团队项目的积极作用和消极作用。不合理或者不恰当的使用测试只会增加工作复杂度和成本。
并且测试用例只是检查代码的工具,所以不要本末倒置以测试用例强行约束业务代码

+

参考

    +
  • 关于TDD、BDD和DDD的一些看法
  • +
  • 虚拟座谈会:代码测试比率、测试驱动开发及行为驱动开发
  • +
  • Mocha
  • +
  • PhantomJS
  • +
  • NightwatchJs
  • +
  • 前端自动化测试探索
  • +
+]]>
+ + test + + + 自动化测试 + 前端测试 + +
+ + 慎重用for...in与for...of + /2017/10/27/for-in-and-for-of.html + +

for...infor...of都是用于数据的遍历。for...inES5标准,用于遍历对象属性(键),而for...ofES6标准,是对for...in的修正,用于遍历对象元素(值),for...of兼容性不是很好(除了PC端老顽固IE之外,移动端某些安卓机和浏览器也是不支持它,具体可以查看MDN)。

+ +

for…in

Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i in arr) {
console.log(i); // "0", "1", "2", "msg", "arrCustom", "objCustom"
}

for (var i in arr) {
if (arr.hasOwnProperty(i)) {
console.log(i); // "0", "1", "2", "msg"
}
}
for (var i in obj) {
console.log(i); // "name", "w", "msg", "objCustom"
}

for (var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i); // "name", "w", "msg"
}
}
+ +

由上面的例子可以看出,for...in的一些特性:

+
    +
  • 可以对JSON对象(数组和对象)进行遍历
  • +
  • for...in会遍历对象的所有可枚举属性,包括原型,例如一些我们挂载到原型链上的一些methodname
  • +
  • 遍历很有可能不是按照对象的内部顺序(我们预期的)进行
  • +
  • 对数组遍历时index索引为字符串型,在某些时候直接进行几何运算可能达不到预期结果
  • +
+

for…of

for...in貌似强大的同时也带来很多副作用,想要达到预期的记过需要额外的代码来处理,所以for...of应运而生

+
Object.prototype.objMethod = function() {};
Array.prototype.arrMethod = function() {};

var arr = [2, 9, 5], obj = { name: '2ue', w: 130 };
arr.msg = 'hello';
obj.msg = 'hello';

for (var i of arr) {
console.log(i); // 2, 9, 5
}
//如果用for...of循环对象,会报错`obj is not iterable`
for (var i of obj) {
console.log(i);
}
+ +

可以看出,for...of方法在for...in上做了优化,并且限制了只能遍历数组。当然在ES5中,具有遍历数组功能的还有mapfiltersomeeveryreducereduceRight等,但是需要注意的是,有些方法不能被break句柄打断循环,使用retun也不能返回到外层,如forEach

+

其实不难看出for...in是属于鸡肋属性了,而for...of由于兼容性原因,在某些地方也应该慎用,即使是移动端也要慎用,应该它并不兼容所有内核。

+

参考

for…in
for…of
for-of循环是遍历实现iterator接口的成员

+]]>
+ + javascript + + + 遍历 + for...in + for...of + +
+ + 利用javascrit获取url传递的参数 + /2016/06/15/get-url-values.html + 神奇的url

一条url包含了很丰富的信息,那么我们如何来获取这些信息并有效的加以利用呢?
随便举个例子:https://github.com/search?utf8=%E2%9C%93&q=javascript
这条url就是在github上搜索javascript后跳转页面对应的url。我们要做的就是获取’?’后面的参数,以及获取后可以用来做什么。

+

获取参数

window.location的对象方法

window.location的参数

+

获取url所有的参数

我们可以直接通过window.location.search来取得这部分,也就是我们需要的url参数。

+

url没有包含?时,window.location.search会返回undefined

+
function getUrlVal(str){
if(!str || str.indexOf('?') != 0) return false;
var urlValArry = str.replace('?','').split('&');
var urlValObject = {};
for(var i in urlValArry){
urlValObject[urlValArry[i].split('=')[0]] = urlValArry[i].split('=')[1];
};
return urlValObject;
};

// https://github.com/search?utf8=%E2%9C%93&q=javascript
var urlStr = window.location.search.replace('?','');
console.log(getUrlVal(urlStr)); //输出 Object {utf8: "%E2%9C%93", q: "javascript"}
+ +

获取url中指定键名(name)的键值(val)

function getOneVal(str,name){
if(!str || str.indexOf('?') != 0) return false;
var afterNameStr = str.replace('?','').split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
// 返回第一个&位置,如果没有'&'则返回字符串长度
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSite
var reslt = afterNameStr.slice(1,strFirstSite);
return reslt;
};

// 'http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614&place=N230&peopleNum=657'
var urlStr = window.location.search;
console.log(getOneVal(urlStr,'time')) //输出20160614
console.log(getOneVal(urlStr,'peopleNum')) //657
+ +

将方法绑定到原型链上

String.prototype.toObj = function(key){
/*
如果有传入key,那么就只返回key对应的Val(找不到则返回undefined)
如果没有传入key,那么就返回一个object对象
*/
var str = this;
if(str.indexOf('?') != 0) return {};
if(str.indexOf(key) == -1) return undefined;
var tmpArry = str.replace('?','').split('&');
var reslt = {};
for(var i in tmpArry){
var tempKeyVal = tmpArry[i].split('=');
if(!!key) {
if(tempKeyVal[0] != key) reslt = undefined;
reslt = tempKeyVal[1];
break;
}else {
reslt[tempKeyVal[0]] = tempKeyVal[1];
}
};
return reslt;
};
// http://www.gotoplay.com/active?itemtype=sport&active=basketball&time=20160614
var urlStr = window.location.search;
console.log(urlStr.toObj()) //{itemtype:'sport',active:'basketball',time:'20160614'}
console.log(urlStr.toObj('active')) //basketball
+ +

利用正则表达式来获取参数

强大的正则总是让人心生向往,利用正则无疑是最简洁优雅的一种方法

+

获取指定某个参数

function getUrlParam(url,name){
if(!name) return;
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
r = url.substr(1).match(reg);
if (r != null) {
return (r[2]);
}else{
return null;
}
}
+ +

获取所有的参数

function parse_url(url){
if(!url) return;
var pattern = /(\w+)=(\w+)/ig;
var parames = {};
url.replace(pattern, function(a, b, c){
parames[b] = c;
});
return parames;
}
+ +

参数的利用

在项目中这些参数有哪些用处呢,下面列举几个比较常用的用处

+
    +
  • 传递数据
  • +
  • 导航定位
  • +
  • 更改状态
  • +
  • +
+

导航定位

什么是导航定位?就是点击导航栏的标签,页面跳转后,对应的标签相应的会突出变化。如下图:
navLocation

+

跳转后有两种情况:
一种ajax异步刷新,只是局部页面发生变化,因为可以直接用点击事件来控制。
另外一种比较常见的方式就是整个页面刷新,这种情况下,点击事件就没用了,就必须另辟蹊径:
1.比较传统的方法就是每个页面里面写一段CSS样式来控制
2.那么另外一种不用说就是通过url的参数来定位咯
假如用每个页面写CSS样式来控制,可以明显感受到的弊端是:每次新的页面都需要修改对应的CSS
那么利用url来控制又需要做哪些事呢?
1.首先需要约定参数,并且后台来传递这些参数
2.然后在导航栏部分,对应的地方加上参数值,这一步,导航栏都是公用模板,并且规则都一样,所以只需要一次添加
3.跳转后定位

+

html代码

<div class="nav">
<a href="/index.htm?nav=index">首页</a>
<a href="/layout/post.htm?nav=post">文章</a>
<a href="/layout/tag.htm?nav=tags">标签</a>
<a href="/layout/about.htm?nav=aboutUs">关于我</a>
</div>
+ +

js代码

function getOneVal(name,urlValStr){
var afterNameStr = urlValStr.split(name)[1];
var strFirstSite = afterNameStr.indexOf('&');
strFirstSite = (strFirstSite == -1) ? afterNameStr.length : strFirstSit;
var val = afterNameStr.slice(1,strFirstSite);
return val;
};

var thisUrlVal = window.location.search.replace('?','');
var thisNVal = getOneVal('nav',urlValStr);

//定位
$('.nav a').each(function(){
var _this = $(this);
var urlValStr = _this.attr('href').split('?')[1];
var nVal = getOneVal('nav',urlValStr);
if(nVal == thisNVal) {
_this.addClass('on');
}
})
+]]>
+ + javascript + + + url + +
+ + 如何写一个日历组件 + /2017/11/02/how-to-make-a-kalendar.html + +

众所周知,虽然javascript中关于时间的API有不少,我们可以通过方法单独的获取年、月、日、时、分、秒、毫秒…貌似很多,最近写了一个日历(以前写的,但写得很烂,最近优化一下),所以下面简单的记录一下如何写一个日历,列出了一些我在写日历过程中自己封装的一些方法

+ +

效果图

先来一张效果图,由于没有UI设计,所以就自己简单的设计了一个样式(好歹我也是设计专业的,虽然已不做设计很多年),虽然略丑,但重要的是功能!!!

+

datepicker

+

思路

一个日历到底是怎样用代码生成的?其实观察一下现有的日历展现形式,可以很快的形成思路,就是:根据计算把日期号数对应到正确的星期几上,并按照顺序逐一输出。
以下是我的思路:

+
    +
  • 取得月份的天数
  • +
  • 取得月份第一天是星期几
  • +
  • 循环对应号数和星期几返回一个数组对象
      +
    • 返回数组对象的每一个子项至少包含:号数,星期几,然后根据情况添加:是否高亮,是否当前月,是否节日…等属性
    • +
    +
  • +
+

方法封装

注意,为了保持方便调用javascript的方法,以及保持输出结果符合实际,所有的方法都有如下约定:

+
    +
  • 在计算过程中
      +
    • 所有的关于月份都是0~11的数字
    • +
    • 所有的关于星期都是0~6的数字
    • +
    +
  • +
  • 在输出的结果中
      +
    • 所有关于月份的输出默认都是1-12的数字
    • +
    • 所有关于星期的输出默认都是1-7的数字
    • +
    +
  • +
+

所以在向调用方法传递参数过程中,月份以及星期几统统都需要按照实际月份减一

+

获取月份天数

javascript中没有直接获取月份天数的方法,但是它提供了一个getDate方法可以获取日期的某一天。那我们只需要获取月份的最后一天(下一个月的第0天)就可以得知这个月的天数:

+
// year是要获取的年份,闰年不一样
// month是要获取的月份
// 返回当前月天数
function getMonthDays(year, month){
return new Date(year, month + 1, 0).getDate();
}

getMonthDays(2016,2) //29
getMonthDays(2017,2) //28
+ +

获取星期几

// year是要获取的年份
// month是要获取的月份
// 返回数字几则是星期几
function getWeekday(year, month, day){
return new Date(year, month, day).getDate() + 1;
}

getWeekday(2016,10,9) //输出4,表示2016年11月9是星期4
getWeekday(2017,10,9) //输出5,表示2017年11月9是星期5

+ +

获取月份有几个星期

要计算月份包含几个星期,需要两个数据:月份天数和月份第一天是星期几,就能得到想要的结果

+
// year是要获取的年份
// month是要获取的月份
// 返回当前月包含几个星期
function getweeksInMonth(year, month){

var days = getMonthDays(year, month);
var FirstDayWeekday = getWeekday(year, month, 1);
return Math.ceil(days + FirstDayWeekday);
}
+ +

循环生成月份对象

有了以上方法之后,就可以通过循环生成一个简单的月份对象了。
在这里需要注意,日历的排序有两种:

+
    +
  • 每一行以星期日开头
  • +
  • 每一行以星期开头
  • +
+
// year是要获取的年份
// month是要获取的月份
// day天,用来判断是否是当前天
// type表明要星期几开头,0为星期一开头,1为星期日开头,默认为0
// 返回当前月包含几个星期

const WEEKTABLE = [{
cn: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
cns: ['日', '一', '二', '三', '四', '五', '六'],
en: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
},{
cn: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
cns: ['一', '二', '三', '四', '五', '六', '日'],
en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
}]

getMonthDaysArray(year, month, day, type) {
if (typeof day === 'undefined' && year === YEAR && month === MONTH) day = DAY;

var dayArrays = [];
var days = this.getMonthDays(year, month), preDays = this.getMonthDays(year, month - 1);
var thisMonthFirstDayInWeek = this.getWeekday(year, month, 1), thisMonthLastDayInWeek = this.getWeekday(year, month, days);

type = !type || type !== 1 ? 0 : 1;

//上月在当月日历面板中的排列
for (var i = 0; i < thisMonthFirstDayInWeek; i++) {
dayArrays.push({
dayNum: (preDays - thisMonthFirstDayInWeek + i + 1),
weekDay: WEEKTABLE[type].cn[i]
})
}
//当月日历面板中的排列
for (var i = 1; i <= days; i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag],
selected: i === +day,
isThisMonth: true
})
};
//下月在当月日历面板中的排列
for (var i = 1; i <= (6 - thisMonthLastDayInWeek); i++) {
var weekDayFlag = (thisMonthFirstDayInWeek + days + i - 1) % 7
dayArrays.push({
dayNum: i,
weekDay: WEEKTABLE[type].cn[weekDayFlag]
})
};
return dayArrays;
}
+ +

格式化时间

涉及到时间时,常常需要把时间格式进行转换,为了应对多中需求,所以自己封装了一个

+
// 参数fmt必须
// date参数不必须,允许字符串和时间对象,不传或者传无法转换成合法时间对象的字符串则默认当前时间,
// 年(YYYY/yyyy)固定四个占位符
// 月(M)、日(d)、小时(h)、分(m)、秒(s)可以用 1-2个占位符,严格区分大小写,
// 毫秒(ms/mss)最多三个占位符,分别对应56,056这种类型
// 例子:
// (Format("yyyy-MM-dd hh:mm:ss:ms") ==> 2006-07-02 08:09:04:23
// (Format("yyyy-MM-dd hh:mm:ss:mss") ==> 2006-07-02 08:09:04:023
// (Format("yyyy-M-d h:m:s:ms") ==> 2006-7-2 8:9:4.180
function formate(fmt, date){
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
var _rules = [{
rule: '[yY]{4}',
value: _date.getFullYear()
}, {
rule: 'M+',
value: _date.getMonth() + 1
}, {
rule: '[dD]+',
value: _date.getDate()
}, {
rule: 'h+',
value: _date.getHours()
}, {
rule: 'm+',
value: _date.getMinutes()
}, {
rule: 's+',
value: _date.getSeconds()
}, {
rule: 'ms{1,2}',
value: _date.getMilliseconds()
}];

_rules.forEach(function (_r){
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join().substr(rLen);
});
});
return fmt;
}
//调用:
var time1 = formate("YYYY/MM/DD hh:mm:ss", new Date()); //2017/11/2 11:09:20
var time2 = formate("YYYY-MM-DD", time1); //2017-11-2
var time3 = formate("MM-DD-YYYY", time2); //11-2-2017
+ +

最后

附上这些方法的源码datepicker
基于vue实现的一个日历:

+
    +
  • demovue-datepicker
  • +
  • 源码datePickerPanel.vue
  • +
+

当然这只是最简单的日历输出,思路也是超级简单(感觉有点Low),如果有大神愿意分享它的经验欢迎,来邮~

+]]>
+ + javascript + + + 日历 + kalendar + +
+ + Javascript系列 - Javascript数组方法 + /2016/08/11/javascript-array-method.html + +

Javascript的Array(数组对象)方法整理,对比他们的功能,返回值,分析他们的参数,以及具体的作用。

+ +

length:获取数组长度

+
    +
  • Method: Arry.length
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: 无
  • +
  • Return: 返回被引用数组长度
  • +
+
+
var arry = [1,6,8,'2ue','o90']
arry.length //返回5,arry = [1,6,8,'2ue','o90']
+ +

join:连接数组内各元素组成一个字符串

+
    +
  • Method: Arry.join(str)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: str非必需
      +
    • str不存在时(不传递str)以默认逗号连接元素
    • +
    • str可以为任意字符串,也可以为空(‘’)(字符串为空时,各元素之间无连接符号)
    • +
    +
  • +
  • Return: 返回连接后的字符串
  • +
+
+
var arry = [1,6,8,'2ue','o90']
arry.join() //返回字符串1,6,8,2ue,o90,arry = [1,6,8,'2ue','o90']
arry.join('-') //返回字符串1-6-8-2ue-o90,arry = [1,6,8,'2ue','o90']
arry.join('') //返回字符串1682ueo90,arry = [1,6,8,'2ue','o90']
+ +

注意如果需要加数组arry以逗号形式展示到页面,则不需要.join()方法,因为javascript的赋值操作会自动调用.toString()方法,如

+
//JS
var arry = [1,6,8,'2ue','o90']
var divBox = document.getElementById('div');
divBox.innerHTML = arry;

//前面赋值的操作将会调用toString方法,因此
console.log(divBox) //<div id="div">1,6,8,2ue,o90</div>
+ +

push:在数组尾部添加一个元素

+
    +
  • Method: Arry.push(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意义
    • +
    • value可以为合法的布尔值,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中,也可以接收多个参数
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+
var arry = [1,6,8,'2ue','o90']
arry.push() //返回5,arry = [1,6,8,'2ue','o90'] 实际没有任何意义
arry.push(true) //返回6,arry = [1,6,8,'2ue','o90',true]
arry.push('dmw') //返回7,arry = [1,6,8,'2ue','o90',true,'dmw']
arry.push('') //返回8,arry = [1,6,8,'2ue','o90',true,'dmw','']
arry.push(3) //返回9,arry = [1,6,8,'2ue','o90',true,'dmw','',3]
arry.push(undefined) //返回10,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined]
arry.push(null) //返回11,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null]
arry.push(['9','8']) //返回12,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8']]
arry.push({key:'hah'}) //返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
//接收多个参数
var arry = [1,6,8,'2ue','o90']
arry.push(true,'dmw','',3,undefined,null,['9','8'],{key:'hah'})
//返回13,arry = [1,6,8,'2ue','o90',true,'dmw','',3,undefined,null,['9','8'],{key:'hah'}]
+ +

unshift:在数组尾部添加一个元素

+
    +
  • Method: Arry.unshift(value,…)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回被引用数组长度,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空
    • +
    • 其中也可以接收多个参数。
    • +
    +
  • +
  • Return: 返回被引用数组长度
  • +
+
+

.push()方法

+

concat:在尾部添加元素到数组

+
    +
  • Method: Arry.concat(value,…)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments: value非必需。
      +
    • value不存在(不传递str)返回组成的新数组,无实际意
    • +
    • value可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,也可以接收多个参数。
    • +
    • 其中当value为数组时,那么添加的是数组中的元素,而不是数组,所以可以用.concat()来连接数组
    • +
    +
  • +
  • Return: 返回组成的新数组
  • +
+
+
//返回值为一个新的数组,不改变原数组
//参数为数组时
var arry = [1,6,8,'2ue','o90']
var newArry = arry.concat(['lalal','mof'])
//返回值 newArry = [1,6,8,'2ue','o90','lalal','mof']
//原数组 arry = [1,6,8,'2ue','o90']
//其它情况同push方法一致
+ +

pop:删除最后一个元素

+
    +
  • Method: Arry.pop()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(最后一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+
var arry = [1]
arry.pop() //返回1,arry = []
arry.pop() //返回undefined,arry = []
+ +

shift:删除第一个元素

+
    +
  • Method: Arry.shift()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回被删除(第一个)元素,被引用数组为空,不改变数组,返回undefined
  • +
+
+

.pop()

+

reverse:颠倒数组元素顺序

+
    +
  • Method: Arry.reverse()
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: 无
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
var arry = [1,6,8,'2ue','o90']
arry.reverse() //返回['o90','2ue',8,6,1] arry = ['o90','2ue',8,6,1]
+ +

sort:数组元素排序

+
    +
  • Method: Arry.sort(fun)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments: fun非必需
      +
    • fun如果为空,那么默认安装字符编码的顺序进行排序
    • +
    • 如不为空,那么fun必须为函数类型
    • +
    • Arry.sort(fun(value1,value2){}),fun函数参数value1 的值为Arry[i]value2 的值为Arry[i+1],其中0< = i < Arry.length - 1。所以请注意,.sort()方法排序会对被引用数组进行遍历,遍历的次数为Arry.length - 1,而非Arry.length。因为在Arry.length - 1次时,排序其实就已经完成了。
    • +
    +
  • +
  • Return: 返回对数组的引用,返回值为数组类型
  • +
+
+
var arry = [1,'2ue','o90',6,890,9,7990]
arry.sort()//返回[1, "2ue", 6, 7990, 890, 9, "o90"] arry = [1, "2ue", 6, 7990, 890, 9, "o90"]
var arry = [1,65443,6,890,9,7990]
arry.sort(function(value1,value2){
return value2-value1
})
//返回[65443, 7990, 890, 9, 6, 1] arry = [65443, 7990, 890, 9, 6, 1]
+ +

slice:根据索引返回数组的一部分

+
    +
  • Method: Arry.slice(satrtIndex,endIndex)
  • +
  • 被引用数组(Arry)是否改变: NO
  • +
  • Arguments:
      +
    • satrtIndex开始索引(不包含开始索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • endIndex结束索引(不包含结束索引),必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • 最终satrtIndex的实际值必须小于endIndex,且他们所在的那段索引必须与被引用数组的索引有交集,否则返回空数组。
    • +
    +
  • +
  • Return: 根据索引返回数组的一部分,返回值为数组类型
  • +
+
+
//被引用数组值不会改变
var arry = [1,6,8,'2ue','o90']
arry.slice(1,3) //返回[6,8,'2ue']
arry.slice(3,1) //返回[]
arry.slice(-1,2) //返回[]
arry.slice(1,-2) //返回[6,8]
arry.slice(1,-4) //返回[]
arry.slice(-3,-1) //返回[6,8,'2ue']
arry.slice(-1,-3) //返回[]
+ +

splice:移除元素

+
    +
  • Method: Arry.splice(satrtIndex, deleteCount, value, …)
  • +
  • 被引用数组(Arry)是否改变: YES
  • +
  • Arguments:
      +
    • satrtIndex开始索引,必须,且必须为nubmer类型,正负皆可。为正或者0(0,+0,-0)表示从数组正向开始索引,为负表示从方向开始索引(-1表示倒数的0)。
    • +
    • deleteCount将删除的个数,非必须,且必须为nubmer类型。从start开始,包括start所指的元素在内要删除的元素个数。这个参数是可选的,如果没有指定它,splice()将删除从start开始到原数组结尾的所有元素,小于等于0将不会删除。
    • +
    • value要插入数组的零个或多个值,从start所指的下标处开始插入。可以为合法的布尔,字符串,数组,对象,数字,nullundefined,空,可接收多个参数。
    • +
    +
  • +
  • Return: 被移除元素组成的数组
  • +
+
+
var arry = [1,6,8,'2ue','o90',4,5,6,7]
arry.splice(7) // 返回 [6,7]; arry = [1,6,8,'2ue','o90',4,5]
arry.splice(1,2) // 返回 [6,8]; arry = [1,'2ue','o90',4,5]
arry.splice(-1,1) //返回 [5]; arry = [1,'o90',4]
arry.splice(0,0,2,3,[8,9]) // 返回 []; arry = [2,3,[8,9],1,'o90',4]
+ +

来一张表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法名功能原数组是否改变返回
length获取数组长度NO被引用数组长度
join将数组元素连接起来以构建一个字符串NO转换后的字符串
push在尾部添加元素YES新数组长度
unshift在头部添加元素YES新数组长度
concat在尾部添加元素NO新数组
pop删除最后一个元素YES被删除元素
shift删除第一个元素YES被删除元素
reverse颠倒数组元素顺序YES对数组的引用
sort数组元素排序YES对数组的引用
slice根据索引返回数组的一部分NO根据索引返回数组的一部分
splice插入、删除或替换数组的元素YES被移除元素组成的数组
+]]>
+ + javascript + + + Array + 数组 + +
+ + 常用javascript代码片段 + /2017/11/26/javascript-sinpats.html + +

下面记录的主要是本人在项目中遇到的一些问题的解决方案或者个人觉得精妙的代码,方案也许来自于自己的想法,也有可能来自于网络(我尽量给出出处,以便大家追源溯流),也许干脆就是一个插件库(实际在项目中对于一些小的功能个人是拒绝引入不纯粹的三方库的)。肯定,这其中有些方案也不是尽善尽美,我会一一列出。如果对于某个问题在你看来有更好的解决方案,请不吝指出!

+ +

placeholder属性支持

有时候项目中placeholder也许要兼容某些特殊的浏览器,所以才有了下面这段代码。这种方案有一个缺点就是:如果你恰好要对这个输入框绑定focus事件并操作它的值,必须要小心处理

+
//修复不支持placeholder属性 start
const isSurportPlder = "placeholder" in document.createElement("input"); // 判断浏览器是否支持 placeholder
if (!isSurportPlder) {
$("[placeholder]").focus(function () {
const _this = $(this);
if (_this.val() == _this.attr("placeholder")) {
_this.val('');
}
}).blur(function () {
const _this = $(this);
if (_this.val() == '' || _this.val() == _this.attr("placeholder")) {
_this.val(_this.attr("placeholder"));
}
}).blur();
};
+ +

格式化时间

关于格式化时间有很多插件,其中比较有名的就可以列很大一堆出来,比如老牌的moment.js,最近比较多star的luxon.js; 对于为什么不选择他么,上面已经说了原因了。

+
formate(fmt, date) {
date = new Date(date).toString() === 'Invalid Date' ? new Date() : new Date(date);
const _rules = [{
rule: '[yY]{4}',
value: date.getFullYear()
}, {
rule: 'M+',
value: date.getMonth() + 1
}, {
rule: '[dD]+',
value: date.getDate()
}, {
rule: 'h+',
value: date.getHours()
}, {
rule: 'm+',
value: date.getMinutes()
}, {
rule: 's+',
value: date.getSeconds()
}, {
rule: 'ms{1,2}',
value: date.getMilliseconds()
}];

_rules.forEach((_r) => {
const rule = _r.rule, val = _r.value;
fmt = fmt.replace(new RegExp(rule), function ($1) {
const rLen = val.toString().length, fLen = $1.length;
return (fLen !== 2 || rLen >= fLen) ? val : ['00', val].join('').substr(rLen);
});
});
return fmt;
}
+ +

解析浏览器的版本等详细信息

浏览器的判断涉及到很多复杂的变量和参数,所以偷懒选取了一个比较好的库,这个库它唯一的功能就是识别浏览器的版本等详细信息。
如果大家有更好的库,请推荐过来!

+

browser.js

+

数字转换成千分位格式(如:123,456,9.89)

function translateThree(num) {
return num.split('').reverse().join('').replace(/(\d{3}(?=\d)(?!\d+\.|$))/g, '$1,').split('').reverse().join('');
}
+ +

判断任意数据的类型

精准的判断类型,'2'会识别成string类型,2会识别成number类型

+
function tryType(para) {
const type = typeof para;
if (type === "number" && isNaN(para)) return "NaN";
if (type !== "object") return type;
return Object.prototype.toString
.call(para)
.replace(/[\[\]]/g, "")
.split(" ")[1]
.toLowerCase();
}
+ +

判断是不是一个可计算的数字

上面的tryType方法会将'2'2区别成两种类型,而这个方法将忽略这种区别

+
function isNumber(para) {
if (window.isNumber) return window.isNumber(para);
if (Number.isNumber) return Number.isNumber(para);
return typeof para !== "undefined" && !isNaN(para);
}
+ +

深度取值防止代码挂掉

在项目中经常会遇到层级很深的json数据,这时候可能就会写类似这样的代码a[2].list[3].name,这种代码很不可靠,很容易由于数据的一点小错误,导致整段js代码挂掉。出的问题多了自然就会思考问题的解决方案,正当我苦思不得其解时,看到一篇文章如何优雅安全地在深层数据结构中取值,他详细的分析了深层取值如何避免报错的情况。
我阅读了博主的文章,整理了一下思路,没有像原博主那样使用xs && xs[x]判断来打断取值,是因为这种情况可能会把0这种类型的值误伤

+
function getValueFromDeepData (props, target){
if (!props || !target) return undefined;
return props.reduce((pre, nxt) => (typeof pre === 'undefined' || typeof pre[nxt] === 'undefined' ? undefined : pre[nxt]), target);
}
+ +

反转义字符串

何谓反转义字符串?就是后端在传输HTML代码的时候往往会对字符串处理:把一些特殊符号转义了;当我们拿到HTML渲染到页面希望他按照HTML代码的格式来显示,而不是按照转义的字符串显示成文本,所以我们需要对这段字符串反转义!
网上看到很多解决方案是通过正则的方式一一替换过来,但是个人觉得这样不好维护。后面发现一种浏览器自动转换的办法,利用这一特性,可以通过js创建一个虚拟的DOM节点,然后把需要转义的字符串使用innerHTML方法放进去,再通过nodeValue方法取出来。
但这种方法需要注意的是:e.childNodes是一个数组,它将_html分段(每65536字符分一段)存储到e.childNodes[i]中,使用e.childNodes方法取得反转义后的字符串需要循环e.childNodes数组。我看到网上很多方法都是直接取的e.childNodes[0].nodeValue,这样在内容过多的时候,是无法把数据取完整的

+
function htmlDecode(str) {
const e = document.createElement('div'), _html = '';
e.innerHTML = str;
for (let i = 0; i < e.childNodes.length; i++) {
_html += e.childNodes[i].nodeValue;
};
return _html;
};
+ +

未完

]]>
+ + javascript + + + sinpats + +
+ + Javascript常用见问题之变量类型判断终极篇 + /2017/03/31/javascript-type.html + +

类型判断是我们在编程中常遇到的棘手问题,严格的变量类型约束会为代码减少很多致命的BUG。本文是对常用到的类型判断的一个整理,以求以最简洁的方式来判断变量的类型。
本文所有的如果没特指,都是基于ES5的原生javascript

+ +

变量的基本类型

JavaScript变量包含两种不同的数据类型的值:基本类型引用类型。基本类型是指简单的数据,有NumberStringBooleanUndefinedNull(null可以算作是一个特殊的基本数据类型),而引用类型指那些可能包含多个值的对象,有ObjectArrayDateRegExpFunction等。在JavaScript中,我们通过var来声明变量,由于JavaScript弱语言类型,我们无法在申明的时候规定他的类型,JavaScript变量的类型是随变量的值改变而改变的。为了代码的安全性,在有些情况下我们要判断变量值的类型,如何正确的判断变量的类型就成了一个比较有深度的问题。下图列举一些常见的类型:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型举例
Nullnull
Undefinedundefined、未赋值的变量
Booleantrue、false
Number-1、0 、 1、 NaN
String‘1’、’a’
Array[]、new Array()
Object{}、new Object()
Functionfunction(){}
+

判断他们的类型,第一时间可能你会想到用typeof去检测它们的类型,然后你就崩溃了:明明是Null为什么结果却是oject,明明是Array为什么还是obejct?…因此可以看出typeof方法不是很可靠,我们必须寻找一种行之有效的方法来解决这个问题?请继续往下读(为了方便阅读,下文中所有的para表示要判断的变量):

+
    +
  • isNaN(para)
  • +
  • !para
  • +
  • typeof para
  • +
  • Object.prototype.toString.call(para);
  • +
+

除了上面这些方法,未来可能会有更多方法来增强变量的约束和判断,比如isNumber

+

isNaN(para)

用来判断是否为number类型的专有方法。但是需要注意的是,如果使用typeof判断那么结果会是number

+

!para

常用来判断一个变量是否存在,面对ArryObject引用类型变量时无论是否为空都会被转换成true

+

typeof para

事实证明typeof并不是万能的,在对除Null以外的基本类型变量是相当有威力的,但是对引用类型变量Null时都会被识别成object,但是请注意:

+
typeof {}; //object
typeof Object; //Function
+

为什么会出现这样的情况呢?因为Object是一个构造函数,而不是object数据类型对象,同理ArrayDateFunction等都是属于构造函数

+

Object.prototype.toString.call(para)

前面的typeof死在半路,无法打探到引用类型变量null的真实情报,但是我们得出了另一个情报:**他们都是obejct**。别慌,我们另外一个强大的武器,可以直指要害,Object.prototype.toString.call(para)

+

判断结果比较表

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型isNaN(para)!paratypeof paratoString(para)
Nullnulltruetrueobejct[obejct Null]
Undefinedundefinedtruetrueundefined[obejct Undefined]
Booleantrue/falsetruefalse/truetrue[obejct Boolean]
Number-1falsefalsenumber[obejct Number]
Number0falsetruenumber[obejct Number]
Number1falsefalsenumber[obejct Number]
NumberNaNtruetruenumber[obejct Number]
String‘1’falsefalsestring[obejct String]
String‘a’truefalsestring[obejct String]
String‘’truetruestring[obejct String]
String‘ ‘(中间包含空格)truefalsestring[obejct String]
Array[]/[4]truefalseobejct[obejct Array]
Object{}/{n:4}truefalseobejct[obejct Object]
Functionfunction(){}truefalseobejct[obejct Function]
+

总结方法

根据上面的表格对比,我整理了一些常见的方法。并且再比较结果精准的情况下尽可能的简化比较过程.

+

判断数字(非严格)

字符串’1‘会被识别成number

+
function isNumber(para){
return !isNaN(para);
};
+ +

判断数字(严格)

在必要的情况下使用:此方法会把字符串’1‘识别成string类型

+
function isStrictNumber(para){
return !isNaN(para) && typeof para === 'number';
};
+ +

判断字符串(非严格)

function isString(para){
return typeof para === 'string';
};
+ +

判断字符串(严格)

在必要的情况下使用:此种方法会把字符串’1‘识别成number类型

+
function isStrictString(para){
return isNaN(para) && typeof para === 'string';
};
+ +

判断一般数据类型(即非引用类型)

注意:使用typeof判断null结果为object

+
function isBasicType(para){
return typeof para !== 'obejct';
};
+ +

判断是否为null(不能识别’’)

此方法只能识别null,如果要包含’’,请结合方法isStringNull()一起使用

+
function isNull(para){
return !para && typeof para === 'object';
};
+ +

判断是否为空字符串(不包含空格)

此方法只能识别'',如果要包含null,请结合方法isNull()一起使用

+
function isStringtNull(para){
return !para && typeof para === 'string';
};
+ +

判断是否为undefined

function isUndefined(para){
return typeof para === 'undefined';
};
+ +

判断是否为false

当为null,undefined,'',0,-0,false,NaN

+
function isFalse(para){
return !para;
};
+ +

判断对象(非严格1–所有的obejct对象)

function isAllObject(_v){
return typeof _v === 'obejct';
};
+ +

判断对象(非严格2–除去null的所有object对象)<–> 判断引用类型

function isObject(_v){
return !!v && typeof _v === 'obejct';
};
+ +

判断对象(严格–只识别{}JSON对象)

function isStrictObject(_v){
return Object.prototype.toString.call(_v) === '[object Object]';
};
+ +

判断数组

function isArray(para){
return Object.prototype.toString.call(para) === '[object Array]';
};
+ +

判断对象

这里特指{}类JSON对象

+
function isObject(para){
return Object.prototype.toString.call(para) === '[object Object]';
};
+ +

判断可执行函数

function isFunction(para){
return typeof para === 'function';
};
+ +
+

总结

当我们需要判断其他类型时,完全可以参照上面的表来写出自己的方法哦。
当然现在前端各种流行库不断推陈出新,我们完全可以直接使用别人封装好的库来实现这些功能,比如underscore.jslodash.js等,但是编码的乐趣不就是在于自己解决最本质的问题么。所以即使有这么多的流行库大行其道,也不妨碍我们了解这些知识的初心,说不定哪天你自己也写出一个很火的库呢~
当然随着ES6标准的不断被各大浏览器厂商支持,ES6的普及度越来越广,这些方法都会被内置到原生javascript内部吧(有些方法已经加进去了~)。

+]]>
+ + Javascript + + + typeof + object + +
+ + Git系列之新手入门 + /2016/06/20/learn-git-1.html + +

工欲善其事,必先利其器。git一个不可或缺的利器,其魅力值得我们慢慢品尝!

+ +

安装

官网下载最新版本安装,然后查看是否安装成功

+
$ git -v
git version 2.7.3.windows.1 //2.7.3为当前版本
+ +

全局个人信息配置

$ git config --global user.name "username"
$ git config --global user.email "email"
+ +

windows环境也可以打开计算机用户文件夹下的.gitconfig编辑

+
[user]
name = username
email = email
+ +

基本命令

初始化本地仓库

$ cd storage
$ git init //初始化当前目录为本地仓库
Initialized empty Git repository in D:/storages/.git/ //初始化了一个空的目录为本地仓库
+ +

第一次提交(提交到本地)

$ git add README.md
$ git commit -m "添加项目文档简介" //-m 参数后面跟表示对当前提交的一个简单说明
+ +

查看状态

查看当前文件处于何种状态

+
$ git status
+ +

回退

当你addcommit了错误的文件时,可以使用以下命令来撤回add或者commit;
那么你必须首先明白工作区和暂存区是什么?
直白的说.git文件夹所在的目录(即git init初始化的目录)为当前工作区
add之后,add的文件就会进入暂存区
commit之后,暂存区就会清空,commit的文件就会进入本地当前的分支(如master分支)
PS:关于工作区和暂存区更详细的解释可以去看廖雪峰工作区和暂存区

+

丢弃工区的修改

命令git checkout -- filename可以把filename文件在工作区的修改全部撤销

+
$ git checkout -- README.md
+ +

撤销暂存区的修改

命令git reset HEAD filename可以把filename文件在暂存区的修改全部撤销

+
$ git reset HEAD README.md
$ git checkout -- README.md //--参数不能省
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
+ +

当然reset 命令,不仅能够撤销暂存区的内容,还能回退版本,即使你错误提交,也可以用reset回滚到之前的版本
如果你提交了错误文件,可以使用以下命令来回退;

+
$ git reset --hard HEAD^
+ +

git reset --hard HEAD^表示回退到上一个版本,HEAD后面的参数可以跟commit ID,这个ID可以通过以下命令获得

+
$ git log
commit 97c8460bc1cda8233866686d9cae270e0e0113f1
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 20 15:44:01 2016 +0800

update README.md

.....(中间有10条日志)

commit 2d936223341333384dd41533dd44ba8e0640493c
Author: 2ue <jie746635835@163.com>
Date: Mon Jun 01 13:16:22 2016 +0800

README.md
+ +

commit 97c8460bc1cda8233866686d9cae270e0e0113f1,这一长串就是我们所需的ID,一般情况下,只需要前7位就够了。

+
$ git reset --hard HEAD 2d93622 //回退到commit 2d936223341333384dd41533dd44ba8e0640493c
$ git reset --hard HEAD~12 //回退到往上第12个版本,也是2d93622这个版本
+ +

提交到服务器

commit后,你做出的修改只是提交到了暂存区,下一步就需要把代码提交到服务器。这里的服务器,可以自己搭建,也可以是公司的,或者第三方的。这里我们将代码提交到github上(假设你是已经有一个github账户,并创建了一个仓库)。

+

本地生成SSH KEY

SSH KEY就是连通本地与github的桥梁,钥匙,使用以下命令来生成:

+
$ ssh-keygen -t rsa -C "youremail@example.com"
+ +

然后回车:

+
    +
  • 提示设置密码
      +
    • 如果不设置,直接按回车,然后会提示你确认密码,再按一次回车
    • +
    • 如果设置密码,输入你想设置的密码,并确认,这样以后每次提交需要输入密码
    • +
    +
  • +
  • 提示生成key的文件名
      +
    • 如果不修改,则使用默认文件名id_rsa
    • +
    • 如果你有在该电脑管理多个key或者已经有生成的key占用了该文件名,则需要重命令
    • +
    +
  • +
+

这里简单起见,先不设置密码,并保持文件名默认

+

然后在用户主目录(例如如果是windows系统一般就在:C:\Users\Administrator)找到.ssh,其中id_rsa表示私钥不能泄露和id_rsa.pub表示公钥,用于对外。

+

github设置本地公钥

打开id_rsa.pub,复制里面的全部内容;
进入github账户;
找到 setting
打开SSH KEYS新建一个SSH KEY,名字随便取,然后粘贴id_rsa.pub的内容,保存;

+

测试是否连通

理论上讲,上一步操作无特殊错误,就已经和github连通的,为了放心,我们可以测试一下

+
$ ssh -T git@github.com
Enter passphrase for key '/c/Users/Administrator/.ssh/id_rsa': //如果设置了密码,此处将会提示你输入密码
Hi 2ue! You've successfully authenticated, but GitHub does not provide shell access.
+

上面就是成功的提示

+

github仓库和本地仓库关联

有两种方式把本地仓库和github仓库关联起来

+

方法一:clone自己的仓库,简单,并且本地文件目录和github上名字一样

登陆账号,新建仓库(new repository);
进入仓库主页,找到按钮clone or download按钮,复制里面的git@github.com:userName/repositoryName.git;
打开git bash,clone;

+
$ git clone git@github.com:userName/repositoryName.git
+ +

就会把这个项目克隆到你当前目录下。此时你就可以顺畅的像github推送你的东西了。

+

方法二:利用命令关联仓库,本地仓库名可以和github的仓库名不一样

假设你github上有一个项目,git@github.com:userName/repositoryName.git

+
$ mkdir testRepository  //新建目录testRepository
$ cd testRepository //切换到这个目录
$ git init //初始化当前目录
$ git remote add origin git@github.com:userName/repositoryName.git
// origin为本地暂存区的名字,为了语义化,建议默认为它
// 注意github上的repositoryName与本地的testRepository可以不一样
+ +

提交(push)

第一次提交

$ git push -u origin master
+ +

第二次及以后

$ git push origin master
+ +

第一次提交带参数-u是为了让你本地和github的仓库保持同步。

+

更新代码(pull)

当我们在一台设备上对github提交(push)代码之后,在另外的设备上修改这个项目时就必须先从github更新代码,以保持代码的同步

+
$ git pull
+ +
+

总结

至此一个比较完整的流程走通了,当然在这些过程中也许还会遇到其它的问题,如,代码冲突,分支,分支合并等等。

github上创建一个新的项目
github repositoryName:testGit
github userName:2ue

+
$ makdir testGit   //创建一个空文件夹testGit(名字任意取),做仓库
$ cd testGit //切换到目录testGit
$ git init //初始化testGit为本地仓库
$ echo 'this repository is localhost' > README.md
//创建文件README.md,并写入'this repository is localhost'
//在window下以上操作的第一步和第四步可以在图形化界面中完成
$ git add README.md
$ git commit -m 'add README.md'//提交更新,并注释信息“add README.md”
$ git remote add origin git@github.com:2ue/testGit.git //关联github上的项目
$ git push -u origin master //提交到github
+]]>
+ + tools + + + git + +
+ + Git系列之常用命令(一) + /2017/07/01/learn-git-2.html + +

虽然有了git的图形化工具,但个人还是喜欢命令行,并且结合alias功能,也能非常快速的完成很多功能。记录一些项目中常用的git命令,方便查阅

+ +

查看状态

可以查看工作区(edit即add之前)和暂存区(add之后,commit之前)

+
git status
+ +

提交到暂存区

//提交某个文件
git add fileName
//提交所有修改文件的三种写法
git add *
git add .
git add --all
+

保存在本地仓库

git commit -m "note text"
+ +

提交到远程仓库

其中origin是本地仓库名,remote是远程仓库分支名

+
git push <origin> <remote>
//如提交本覅origin到远程master分支
git push origin master
+ +

分支

查看分支

//查看本地分支
git branch
//查看远程分支
git branch -r
//查看所有分支
git branch -a
+ +

创建分支

git branch name
+ +

删除分支

//删除本地分支
git branch -d name
//删除远程分支两种方法
git push origin :name //冒号不能省
git branch -r -d origin/name
+ +

切换分支

git checkout name //如果分支不存在则创建一个名为name的新分支
+ +

合并分支

//例如:合并分支dev到master
//首先保持dev和master分支最新,即在本地切换到对应分支,各pull一次
//然后切换分支到最终要合并的分支上(此处为master)
git checkout master
//执行本地合并(合并dev到master)
git merge dev
//推送合并到远程
git push origin master
+ +

放弃本地修改强制更新

+

git fetch只是下载远程的库的内容,不做任何的合并;git reset把HEAD指向刚刚下载的最新的版本

+
+
git fetch --all
git reset --hard origin/master
+ +

回退到某个历史版本

+

首先使用git log命令获取某个历史版本的ID,假设ID是c3470ee7edf566cc359b666d3e27a38220abaf66

+
+
//在本地回退到c3470ee7edf566cc359b666d3e27a38220abaf66版本
git reset --hard c3470ee7edf566cc359b666d3e27a38220abaf66
//推送到远程分支,注意:由于本地版本旧于远程仓库版本,这里需要使用-f参数,强制推送
git push -f origin master
+]]>
+ + tools + + + git + +
+ + mocha+chai使用记录 + /2017/10/10/mocha+chai.html + +

mocha+chai是比较流行的测试框架‘套装’,能实现一些基本的前端测试如函数功能测试,模拟登陆,异步测试,流程测试等

+ +

官方文档

mocha

chai

基本用法

+

mocha是一个测试工具库,它只纯粹对测试行为(过程)进行描述;而chai是一个断言(推断)库,它可以将测试结果进行各种判断,以此推断是否符合预期,因此两者常常进行组合使用

+
+

安装

//全局安装
$ npm install -g mocha
+ +

ps: 全局安装之后,mocha命令将会在全局注册,可以在任何地方使用mocha命令

+
//安装项目依赖
$ npm install mocha chai
+ +

使用

目录结构

├── test //测试用例
│   ├── hooks.js //生命钩子
│   ├── test.js //入口文件
│   └── unit //测试单例
│   ├── add.js
│   └── ...
└── src //业务代码
   ├── add.js
   └── ...
+ +

所有测试代码都在test目录,所有的业务代码都在src目录

+

一个简单的例子

//src/add.js
function add(a, b){
return a + b;
}
module.exports = add;

+ +
//test/unit/add.js
var add = require('../../src/add.js');
var expect = require('chai').expect;

describe('加法函数', function () {
it('1 + 3 = 4', function () {
expect(add(1, 3)).to.be.equal(4);
});
});
+ +
$ mocha mocha/unit/add
加法函数
√ 1 + 3 = 4
1 passing (16ms)
+ +

Expect/Should/Assert

The Expect / Should API covers the BDD assertion styles.
+The Assert API covers the TDD assertion style.
+
+

异步

    +
  • promise异步一定要带上done(),用于通知mocha该测试已经完成
  • +
  • 异步通常和参数-t结合一起用
  • +
+

通配符

//test/unit目录下add.js和minus.js
$ mocha test/unit/{add,minus}.js
//test/unit目录下所有js后缀的文件
$ mocha test/unit/*.js
+ +

mocha支持shellnode的通配符匹配规则,更多通配符规则可以查看各自的文档

+

命令行参数与配置文件mocha.opts

常用的命令行

    +
  • –recursive
  • +
+

Mocha默认不对指定目录的子级目录匹配,如果需要使自己目录的测试用例运行,则需要加上--recursive

+
    +
  • –reporter
  • +
+

输出报告的格式,默认是--reporter spec,可以用--reporters命令查看有哪些输出格式

+
    +
  • –watch
  • +
+

监听变化,每次修改自动执行test

+
    +
  • –timeout -t
  • +
+

Mocha默认每个测试用例最多执行2000毫秒,如果到时没有得到结果,就报错。对于涉及异步操作的测试用例,这个时间往往是不够的,需要用-t--timeout参数指定超时门槛

+
    +
  • –hlep,-h
  • +
+

查看有哪些命令
mocha.opts放在test目录下,执行mocha命令时回去读取里面的配置
命令行参数可以写在mocha.opts文件内,如

+
$ mocha --reporter tap --recursive -t 3000
+ +

等价于
test/mocha.opts文件内容

+
--reporter tap
--recursive
-t 13000
+ +
$ mocha
+ +

hooks(钩子)

describe('hooks', function() {

before(function() {
// 在本区块的所有测试用例之前执行
});

after(function() {
// 在本区块的所有测试用例之后执行
});

beforeEach(function() {
// 在本区块的每个测试用例之前执行
});

afterEach(function() {
// 在本区块的每个测试用例之后执行
});

// test cases
});
+ +

可以写在测试用例内(每个describe块内),此时只对当前测试用例有效
也可以写在外部,此时对所有的测试用例有效

+

注意

    +
  • 内置promise对象
  • +
  • ES6需要转码
  • +
+]]>
+ + 测试 + + + javascript + 测试 + +
+ + Node.js版本神器之nvm + /2018/07/21/node.js-version-management-artifact-nvm.html + 简介

Node Version Manager (nvm) 是一个易于使用的工具,可以帮助您在不同Node.js版本之间进行切换,以满足不同项目的需求。

+

背景

在软件开发过程中,因项目依赖和兼容性考虑,可能需要使用不同版本的Node.js。通过nvm,可以方便地管理多个Node.js版本,而无需担心全局安装的冲突问题。

+

安装

通用安装

如果你在你的机器(无论是windows,macOs还是linux)上已经安装了node,则可以直接使用npm命令进行安装:

+
npm install -g nvm
+ +

安装成功后,你可以跳过后续安装部分,进行阅读使用部分。
如果你没有办法通过以上方式进行安装,请接着进行阅读后续安装部分。

+

在Windows上安装

下载Windows安装程序:nvm-windows releases,然后一路回车安装

+

有时候安装完成后,会出现等nvm命令无法识别类似情况:

+
nvm --verison

nvm not fund
+ +

请检查环境变量,如果不存在,则添加nvm到环境变量PATH中

+

在Mac上安装

使用Homebrew安装

brew install nvm
+ +

使用命令行安装

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
+ +

安装完毕后,需要将nvm写入到环境变量中, ~/.bash_profile, ~/.zshrc~/.profile 文件中:

+
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
+ +

修改完毕后,执行

+
source ~/.bash_profile
# 或者
source ~/.zshrc
#或者
source ~/.profile
+ +

在Linux上安装

和macos上安装一样,参照mac上安装-使用命令行安装

+

使用

查看所有可用版本

nvm ls-remote
+ +

安装指定版本

nvm install <version>
+ +

选择使用版本

nvm use <version>
+ +

设置默认版本

nvm alias default <version>
+ +

卸载特定版本

nvm uninstall <version>
+ +

注意事项

    +
  • 切换Node.js版本时,请确保项目的依赖支持所选版本。
  • +
  • 避免在生产环境频繁更改Node.js版本,以避免潜在的稳定性问题。
  • +
+

参考文章

    +
  • Node Version Manager (GitHub)
  • +
  • How to Use Node Version Manager (nvm) for Node.js
  • +
+]]>
+ + 工具 + nvm + + + node + npm + nvm + 版本管理 + +
+ + node和npm版本管理器nvm的安装和使用 + /2017/08/21/nvm-node-version-manager.html + +

nvm:一个node和npm的版本管理器(node&npm version manager),能让你快速的在不同版本间切换。

+ +

安装

下载地址:官网下载
有两种版本nvm-noinstall.zip(便携版)和nvm-setup.zip(exe安装版)
两者唯一区别就是便携版需要手动配置全局变量,而安装版只需要在安装时选定安装目录则会自动配置好。

+
+

注意:安装之前最好先卸载之前的node

+
+

便携版安装

    +
  • 下载最新版的nvm-noinstall.zip后解压放到D:\devTools(可以放到任意位置,此处是我安装的目录,注意文件夹名不能存在空格);
    elevate.cmd
    elevate.vbs
    install.cmd
    LICENSE
    nvm.exe
  • +
  • 配置nvm,生成settings.txt,填写配置
    方法一:双击install.cmd,会生成settings.txt文件(生成位置就是你输入的地址,一般是在nvm目录下,如果不是,需要拷贝过来)
    方法二:直接在nvm目录下新建settings.txt文件
    root: D:\devTools\nvm
    path: D:\devTools\nodejsv
    arch: 64
    proxy: none
    node_mirror: http://npm.taobao.org/mirrors/node/
    npm_mirror: https://npm.taobao.org/mirrors/npm/
    +
      +
    • root : nvm的存放地址
    • +
    • path : 存放指向node版本的快捷方式,使用nvm的过程中会自动生成。一般写的时候与nvm同级。
    • +
    • arch : 电脑系统是64位就写64,32位就写32
    • +
    • proxy : 代理
    • +
    • node_mirror: node镜像源,安装node时会从此镜像源下载。
    • +
    • npm_mirror: 同上,npm镜像源
    • +
    +
  • +
  • 全局变量配置
    1.添加变量NVM_HOME,值为D:\devTools\nvm
    2.添加变量NVM_SYMLINK,值为D:\devTools\nodejsv
    3.添加变量NVM_HOMENVM_SYMLINK到全局变量path: 修改path的值最后加上;%NVM_HOME%;%NVM_SYMLINK%;
    到此便携版nvm安装完成
  • +
+

exe安装版

直接双击安装,可以使用默认的选项。也可以自己选择安装地址。然后安装过程中会自动把路径写入到全局变量。

+
+

注意: 如果安装了杀毒软件,应该先关闭杀毒软件,因为写入全局变量是一个敏感操作,某些杀毒软件会报警(不关闭,报警时需要选择允许操作)

+
+

使用

版本检测

$ nvm version
1.1.6
// or
$ nvm v
1.1.6
+ +

安装node&npm

$ nvm install [version]
// 如果安装最新版的,直接使用
$ nvm install latest
+ +

查看安装的node&npm

$ nvm list
* 8.4.0 (Currently using 64-bit executable)
6.9.0
+ +

切换node版本

$ nvm use [version]
Now using node v8.4.0 (64-bit)
+ +

卸载某个版本node

$ nvm uninstall [version]
+ +

nvm命令查看

$ nvm
Running version 1.1.6.

Usage:

nvm arch : Show if node is running in 32 or 64 bit mode.
nvm install <version> [arch] : The version can be a node.js version or "latest" for the latest stable version.
Optionally specify whether to install the 32 or 64 bit version (defaults to system arch).
Set [arch] to "all" to install 32 AND 64 bit versions.
Add --insecure to the end of this command to bypass SSL validation of the remote download server.
nvm list [available] : List the node.js installations. Type "available" at the end to see what can be installed. Aliased as ls.
nvm on : Enable node.js version management.
nvm off : Disable node.js version management.
nvm proxy [url] : Set a proxy to use for downloads. Leave [url] blank to see the current proxy.
Set [url] to "none" to remove the proxy.
nvm node_mirror [url] : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.
nvm npm_mirror [url] : Set the npm mirror. Defaults to https://github.com/npm/npm/archive/. Leave [url] blank to default url.
nvm uninstall <version> : The version must be a specific version.
nvm use [version] [arch] : Switch to use the specified version. Optionally specify 32/64bit architecture.
nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.
nvm root [path] : Set the directory where nvm should store different versions of node.js.
If <path> is not set, the current root will be displayed.
nvm version : Displays the current running version of nvm for Windows. Aliased as v.
+ +

总结

    +
  • settings.txtrootpath文件路径中不能存在空格,否则在使用nvm use命令时会报错
  • +
  • 在使用nvm use命令时,貌似无法再git-bash中使用,暂时不知道原因,在自带的cmd中可以
  • +
+

最后的最后

+

安装nvm比较简单,喜欢折腾的可以使用便携版,反之这直接使用安装版一键安装。最后大家愉快的玩耍吧

+
+]]>
+ + tools + + + node + npm + nvm + +
+ + 一道小小的题目引发对javascript支持正则表达式相关方法的探讨 + /2017/11/24/regex-to-something.html + +

以前对于正则是非常惧怕的,因为看不懂和学不会。但最近项目中频繁的使用到了正则,因此强迫自己去学习了解,慢慢的体会到了他的魅力与强大。当然学习正则初入门的时候有些枯燥难懂,但越学越觉得轻松。本文不准备说关于正则本身的事儿,而是说一说关于javascript中关于正则的几个方法中被很多人忽略的地方。

+ +

工具

说到正则,很多人都是从抄到改到自己写,这个过程可能有时候很漫长。如一些工具能帮助你快速分析和学习正则,那么学习的过程你肯定要轻松得多。下面我推荐两个我经常使用的正则在线可视化工具,正则可视化工具图解符合铁路图规律(其实不明白什么是铁路一样很容易看懂,只是一些细微的地方和我们的常规思维有点差别)。

+
    +
  • regexper 我最常用的一个,个人觉得UI做得比其他好
  • +
  • regulex 备选,他有一个很舒心的功能,可以提供一段js,嵌套到你的网站,生成正则可视化图
  • +
+

一道小小的题目

这道题目是在群里日常闲聊时,公司同事抛出来的,具体是出自哪里本人没去考察。先先说说题目:

+
+

写一个方法使得数字末尾的连续0变成9,如1230000变成1239999

+
+

一道很简单的题目,直接正则就能搞定,也许你会写:

+
function zoreToNine(num){
return (num + '').replace(/0/g,9);
}
//或者
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,9);
}
+ +

这也是此题的陷阱所在,按照上面的方法,1023000就会被转化成1923999,这样是不符合要求的,所以改进一下:

+
function zoreToNine(num){
return (num + '').replace(/[1-9]0+$/,function($1){
return $1.replace(/0/g,9);
});
}
zoreToNine(1223000); //1223999
zoreToNine(1023000); //1023999
+ +

关于这个问题的解决方案@微醺岁月同学提供了一种,位置匹配的方法,简单了很多,厉害!

+
"12300100000".replace(/0(?=(0+$)|\b)/g,9); //12300199999
+ +

当然解决问题的方法很多,不一定非要用正则,还完全可以使用纯算术的方法实现,大家有兴趣可以尝试,闲话少说进入这次的主题:javascript支持正则表达式相关方法,注意并不是正则对象的方法。
上述方法使用了正则,有趣的是在回调函数里有一个$1,这个$1到底是什么?所有的匹配规则匹配后都有$1这个变量么?…一连串的问题,以前我从来没有去追探过,趁着昨个比较空闲,去追探了一番,并在今天整理了一下,写下此文记录。

+

主角

javascript中正则对象有三个方法:testexeccompile,但是此次的主角并不是它们!我们讨论的是能够使用正则表示的相关方法:searchmatchreplacesplit,注意它们都是String对象的方法,使用它们必须要是String类型.

+

replace(rule[regexp/substr], replacement)

replace是一个用于替换字符串的方法,虽然看似简单,但是它隐藏的机关也是常常被人忽略。具体分析一下它的特点:
它接收两个参数
无副作用不影响原始变量
返回被改变的字符串(一定是字符串类型)

+

定义一些变量,方便全文取用。

+
let a = '12309800', b = '12309800[object Object]', b = '12309800{}';
+ +

参数rule

在一般情况,rule参数一般是正则、字符串、数字。
如果是字符串,将会在匹配到第一个符合条件的目标,结束方法;
如果是正则,则按照正则的规则进行匹配

+
//匹配第一个0替换成5
a.replace(0,5); //'12359800'
//匹配所有的0替换成5
a.replace(/0/g,5); //'12359855'
+ +

参数replacement

在一般情况,replacement参数是字符串、数字、者回调。

+

包含$的字符串

当参数rule为正则,并且正则至少包含有一对完整的()时,如果replacement包含有$的字符串,那么对于$n(n为大于0的整数,n的长度取决于正则中括号的对数),会被解析成一个变量。但是也仅仅只是作为一个变量,无法在字符串中进行计算,此时更类似特别的字符串模板变量。

+

一般情况下,$n中n的长度取决于正则中括号的对数,$1表示第1对括号匹配的结果,$2表示第2对匹配的结果…在正则所有的括号对中,左括号出现在第几个位置(或者说从左往右),则它就是第几对括号,以此类推。姑且我们把这种规则成为正则匹配分割规则(ps:这完全是我自己取的一个名字,方便文章后面使用和记忆)。

+
a.replace(0,'$0'); //'123$09800'
a.replace(/00/g,'$0'); //'123098$0'
a.replace(/[1-9]0+$/,'$1'); //'12309$1'
a.replace(/([1-9](0+$))/,'$1'); //'12309800',此时$1为[1-9](0+$)匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1'); //'123098',此时$1为[1-9]匹配到的内容,$2为0+$匹配到的内容
a.replace(/([1-9])(0+$)/,'$1*$2'); //'123098*00',此处的$1和$2不会安照期待的情况进行乘法计算,要进行计算可以用回调
+ +
+

请注意:虽然目前参数replacement中携带有$n仍然能正常使用,但是这种方式已经不被规范所推荐,更应该使用回调来完成这个操作。这一点谢谢@lucky4同学的指出

+
+

如果正则中包含有全局匹配标志(g),那么每次匹配的都符合上述规则

+

回调函数

先看例子:

+
a.replace(/[1-9]0+$/,function(){
console.log(arguments); //["800",5,"12309800"]、
});
a.replace(/([1-9])0+$/,function(){
console.log(arguments); //["800","8",5,"12309800"]
});
a.replace(/([1-9])(0+$)/,function(){
console.log(arguments); //["800","8","00",5,"12309800"]
});
a.replace(/(([1-9])(0+$))/,function(){
console.log(arguments); //["800","800","8","00",5,"12309800"]
});
+ +

回调函数的arguments数组部分组成:[完整匹配的字符串,$1,$2,…,$n,匹配的开始位置,原始字符串],$1...$n表示每个括号对的匹配,规则和前面的相同。
所以有一下规律:

+
let arr = [...arguments], len = arr.length;
(len >= 3) === true;
arr[0] = 完整匹配的字符串;
arr[len-2] = 匹配的开始位置;
arr[len-1] = 原始字符串;
+ +

注意:除了匹配的开始位置是Number类型外,其余的都是String类型

+

非常规类型参数

如果参数类型不是上述两种情况,会发生什么呢?看看下面的例子:

+
a.replace(0,null); //123null9800
a.replace(0,undefined); //123null9800
a.replace(0,[]); //1239800
a.replace(0,Array); //1230,3,123098009800
b.replace({},5); //123098005
c.replace({},5); //'12309800{}'
a.replace(0,{}); //123[object Object]9800
a.replace(0,Object); //12309800
+ +

由上面的例子可以看出,如果非正则也非字符串,则有以下规则:
null变量,则会转换成'null'字符串;
undefined变量,则会转换成'undefined'字符串;
[]变量,则会调用join()方法转换成字符串,默认以,分割,值得注意的是空数组将会被转换成空字符串(没有任何字符),通常会被匹配源字符串的开始位置(默认开始位置为空字符串);
Array变量,则会先转成成一个匹配的数组,形如[完整匹配的字符串,$1,$2,...,$n,匹配的开始位置,原始字符串],然后对它调用join()方法转换成字符串,默认以,分割;
{}变量,则会调用Object.protype.toString.call()方法把{}转换成[object Object];
Object变量,则貌似什么都没做

+

虽然可以传入这些非正常参数,但大多数情况下这些类型的参数对实际是毫无意义的,所以不建议传入以上类型的参数。同上面的正则匹配分割规则一样,为了方便使用称呼,姑且我把上面的转换规则称为正则匹配参数转换规则

+

match(rule[regex/substr])

match方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
该方法类似indexOflastIndexOf,但是它返回指定的值,而不是字符串的位置;

+

参数

参数的传递除了常规的正则和字符串以外,其余所有类型的参数都会按照上述的正则匹配参数转换规则转换成字符串形式来匹配。

+

返回值

返回值根据传入的参数类型和规则的不同,返回的内容不同,但总体来说,它是返回一个对象,而不是索引,如果没匹配到任何符合条件的字符串,则返回null

+

非全局匹配正则

如果匹配规则是一个非全局匹配规则,那么,它此时的返回值是一个伪数组对象(likeArr),形如:[一个展开的匹配到的字符串数组, 匹配到的字符串位置, 原始字符串],它有如下规律:

+
var likeArr = a.match(regex);
likeArr[0] = 匹配到的字符串;
likeArr[1...n] = 正则匹配分割规则匹配的字符串;
likeArr.index = 匹配到字符串的位置
likeArr.inupt = 原始字符串
+ +

看例子:

+
a.match(/[1-9]0+$/); //[0:'800',index:5,input:'12309800']
a.match(/([1-9])0+$/); //[0:'800',1:'8',index:5,input:'12309800']
a.match(/[1-9](0+$)/); //[0:'800',1:'00',index:5,input:'12309800']
a.match(/([1-9])(0+$)/); //[0:'800',1:'8',2:'00',index:5,input:'12309800']
+ +

全局匹配正则

如果匹配规则是一个全局匹配规则(正在携带有g标志),那么,它此时的返回值是一个数组对象(arr),形如:[匹配到的字符串数1,匹配到的字符串数2,匹配到的字符串数3];
看例子:

+
a.match(/[1-9]0/); //[0:'30',index:2,input:'12309800']
a.match(/[1-9]0/g); //[0:'30',1:'80']
+ +

search(rule[regex/substr])

search方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
stringObject中第一个与rule相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回-1
注意:

+
    +
  • search方法不执行全局匹配,它将忽略标志g
  • +
  • 忽略regexplastIndex属性,总是从字符串的开始进行检索,这意味着它总是返回stringObject的第一个匹配的位置
  • +
+

同样,search可以传入任何参数类型,它会遵循正则匹配参数转换规则进行转换

+

split(rule[regex/substr],len)

这个方法就不用多说,很常用的字符串分割方法。
第二个参数的作用就是限制返回值的长度,表示返回值的最大长度

+

当然,它依然可以传入任何参数类型,会遵循正则匹配参数转换规则进行转换

+
+

有一段加密的后的密码,我们需要分离出字符串’12a344gg333tt445656ffa6778ii99’中的前三组数字,通过某种计算才能得出正确的密码

+
+
'12a344gg333tt445656ffa6778ii99'.split(/[a-zA-Z]+/g,3); //['12','334','333']
+ +

最后

写了这么多,突然发现以前仅仅是在用这些方法,了解得很不够深入。越是学习才发现其中的奥秘!学无止境,与诸君共勉!
以上内容如有错误之处,希望诸君不吝指出!

+]]>
+ + regex + + + javascript + regex + +
+ + 常用正则整理(持续收集) + /2016/09/05/regex.html + +

正则的魅力在于使用很简洁的方式解决一些比较复杂的方式,使代码变得更优雅,也使实现的过程变得更简单透明。本文搜集整理一些常用正则,记录以便查阅

+ +
var regex = window.regex || (function (document, $) {
var _reg = {};
/* 'pwd':/^[\@A-Za-z0-9\!\#\$\%\^\&\*\.\~]{6,16}$/,//密码 */
//验证数字
$.extend(_reg, {
'num': /^\d+$/, //数字
'znum': /^[1-9](\d+)?$/, // 大于0的数字
'float': /^[-]{0,1}(\d+)[\.]+(\d+)$/, //浮点数
'money': /^\d{1,12}(?:\.\d{1,3})?$/, // money
'idCard': /^\d{15}$|^\d{18}$|^\d{17}[xX]$/, //身份证
'idCardStrict':/^(\d{6})([1-2])(\d{3})((?:0[1-9])|(?:1[0-2]))((?:0[0-9])|(?:[1-2][0-9])|(?:3[0-1]))(\d{3})(\d{1})$/,
'qq': /^[1-9]\d{4,15}$/, //QQ
'pwd': /^[\@A-Za-z0-9]{6,16}$/, //密码
'areacode': /^(0[1,2]{1}\d{1})$|^(0[3-9]{1}\d{2})$/, //区号
'tel': /^\d{7,8}$/, // 固话格式
'mobile': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$/, //验证手机号码
'telephone': /^(((\+)?86)|(\(\+86\)))?-?((((0)?[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,12}))-?(\d{1,8})?$/, //验证固定电话
'phone': /^((\+86)|(\(\+86\)))?-?(13|14|15|18|17)[0-9]{9}$|^((\+86)|(\(\+86\)))?-?(((0[1,2]{1}\d{1})?-?\d{8})|((0[3-9]{1}\d{2})?-?\d{7,8}))$/, //手机号码和固定电话
'zipcode': /^\d{6}$/ //验证邮编
});
//验证字符串
$.extend(_reg, {
'email': /^\w{1,16}([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, //邮箱
'chinese': /^[\u4E00-\u9FA5]+$/, //仅汉字
'char': /^[A-Za-z]+$/, //仅仅是字母
'charn': /^[A-Za-z0-9]+$/, //数字加字母
'nospecial': /^[\u4E00-\u9FA5A-Za-z0-9]+$/, // 不包含特殊字符
'url': /^((http|https|ftp):\/\/)?(\w(\:\w)?@)?([0-9a-z_-]+\.)*?([a-z0-9-]+\.[a-z]{2,6}(\.[a-z]{2})?(\:[0-9]{2,6})?)((\/[^?#<>\/\\*":]*)+(\?[^#]*)?(#.*)?)?$/,
'loginName': /^(13|14|15|18|17)[0-9]{9}$|^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, // 用户名
'userName': /^[\u4e00-\u9fa5]{2`,4}$|[a-zA-Z]{4,20}$/, //真实姓名
'nickName': /^([a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{3,19})$/ //昵称
});
return _reg;
})(document, window.jQuery);
window.regex = regex;
+]]>
+ + javascript + + + 正则 + +
+ + 使用webpack + gulp构建项目 + /2016/09/24/use-gulp-and-webpack-to-bulid-resource.html + +

有人说为什么会使用webpack+gulp呢?强大的webpack完全可以摒弃gulp了嘛?话虽如此,但个人觉得webpack配置太繁琐复杂,相对来说gulp更简单一点,并且gulp也能很好的完成我期望的任务。所以我想用webpack来处理js任务(因为它支持AMD和CMD,并且可以直接引入模块),用gulp处理images/css/html等资源

+ +

ps: 平时在项目中使用它们的机会不多,以下都是自己项目之外的折腾,如果有错误之处,请不吝指出。

+

demo

先上DEMO

+

配置webpack

webpack的有很强大的配置选项,官方中英文文档都已经很详尽。
中文文档
英文文档

+
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

上面的配置就是对js进行打包处理,当然webpack肯定也可以处理css和images等资源,webpack的强大毋庸置疑,但为什么不用webpack来处理css等任务呢?

+
    +
  • webpack处理css默认情况下会把css合并到js文件,这点很不爽
  • +
  • 如果要把css文件独立处理,则需要额外的配置,有点烦,所以果断用gulp了
  • +
+

执行webpack,看看效果,可以正常运行

+
{ webpackGulpDeom }  » webpack
Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1141ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
+ 5 hidden modules
{ webpackGulpDeom } »
+ +

配置gulp

gulpfile.js同样,gulp的配置文档详情参考官方文档,这里以编译less文档并压缩css文档为例

+
'use strict';
var gulp = require('gulp'),
less = require('gulp-less');

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});
//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + 'css/*',['less'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less','watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify']);
+ +

ps:这里只列举了一个编译less的任务。

+

在gulp里执行webpack任务

到了这一步,gulp和webpack任务都编写完成了,如果单单是这样是没有意思的,因为每次启动都需要单独的执行两次命令:一次webpack,一次gulp命令,这样无疑是非常糟糕的。所以我们必须得想办法把gulp和webpack连接起来。怎么连接呢?具体的有两种办法:

+
    +
  • 一种是使用gulp-webpack插件。
  • +
  • 另一种是使用gulp-util插件。
  • +
+

那么我们来重写gulpfile.js和webpack.config.js吧

+

利用gulp-webpack插件

重写gulpfile.js

+
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
webpack = require("gulp-webpack"),
webpackConfig = require("./webpack.config.js");

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//用gulp执行webpack.config.js
gulp.task('buildJs', function () {
var myConfig = Object.create(webpackConfig);
return gulp
.src([entrySrc + 'js/page/*.js'])
.pipe(webpack(myConfig))
.pipe(gulp.dest('dist/js/page')); //出口文件目录,此处配置之后在webpack.config.js中就必须去掉,不然会报错
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

gulpfile.js的变化:

+
    +
  • 增加了可以一个buildJs任务来执行webpack.config.js文件的配置
  • +
  • 相应的监听对象扩大了
  • +
+

现在来重写webpack.config.js,注释掉output项中的path就行了

+
'use strict';
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); //公共部分打包到common.js,输出路径为output中的path

module.exports = {
// devtool: 'cheap-module-eval-source-map', //配置生成Source Maps,选择合适的选项
//插件项
plugins: [commonsPlugin],
//页面入口文件配置
entry: {
'app' : './src/js/page/app.js',
},
//入口文件输出配置
output: {
//path: './dist/js/page', //webpack启动时需要
filename: '[name].js' //[name]指向entry中'app',对应关系
},
module: {
//加载器配置,另外还可以添加额外的css/images等加载器
loaders: [
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
},
//其它解决方案配置
resolve: {
extensions: ['', '.js', '.json', '.scss']
}
};
+ +

webpack.config.jsgulpfile.js都配置好了,那么现在只要执行gulp的相关命令就可以了

+
{ webpackGulpDeom }  » gulp
[14:20:43] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:20:43] Starting 'webpack'...
[14:20:45] Version: webpack 1.13.3
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
[14:20:45] Finished 'webpack' after 1.37 s
[14:20:45] Starting 'default'...
[14:20:45] Finished 'default' after 34 μs
{ webpackGulpDeom } »
+ +

利用gulp-util插件

+

这种方案只需要修改gulpfile.js就行了,webpack.config.js理论上来说不需要任何变化

+
+
'use strict';
var gulp = require('gulp'),
less = require('gulp-less'),
gutil = require('gulp-util'),
webpackConfig = require("./webpack.config.js"),
myDevConfig = Object.create(webpackConfig),
devCompiler = webpack(myDevConfig);

var isDev = true;
var entrySrc = 'src/'
var pathSrc = isDev ? 'dist/' : 'online/';

//开发环境:编译less和css
gulp.task('less', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//上线发布:压缩合并css,有less和css,具体看情况
gulp.task('cssUglify', function (done) {
gulp.src([entrySrc + 'css/main.less', entrySrc + 'css/*.css'])
.pipe(less())
.pipe(concat('style.min.css'))
.pipe(gulp.dest(pathSrc + 'css/'))
.on('end', done);
});

//引用webpack.config.js对js资源进行打包
gulp.task("buildJs", function(callback) {
devCompiler.run(function(err, stats) {
if(err) throw new gutil.PluginError("webpack:buildJs", err);
gutil.log("[webpack:buildJs]", stats.toString({
colors: true
}));
callback();
});
});

//开发环境
gulp.task('watch', function (done) {
gulp.watch(entrySrc + '*/*',['less', 'buildJs'], function(event) {
console.log('File' + event.path + ' was ' + event.type + ', running tasks...');
}).on('end', done);
});

//开发,执行gulp
gulp.task('default', ['less', 'buildJs', 'watch']);
//发布,执行gulp dev
gulp.task('dev', ['cssUglify', 'buildJs']);
+ +

执行结果:

+
{ webpackGulpDeom }  » gulp
[14:28:59] Using gulpfile D:\wamp64\www\webpackGulpDeom\gulpfile.js
[14:28:59] Starting 'buildJs'...
[14:29:00] [webpack:buildJs] Hash: 4eeaff8e1f7bda682e10
Version: webpack 1.13.3
Time: 1232ms
Asset Size Chunks Chunk Names
app.js 367 kB 0, 1 [emitted] app
common.js 3.54 kB 1 [emitted] common.js
chunk {0} app.js (app) 357 kB {1} [rendered]
[0] ./src/js/page/app.js 285 bytes {0} [built]
[1] ./src/js/common/jquery-1.9.1.min.js 92.6 kB {0} [built]
[2] (webpack)/buildin/amd-options.js 42 bytes {0} [built]
[3] ./~/vue/dist/vue.common.js 259 kB {0} [built]
[4] ./~/process/browser.js 5.3 kB {0} [built]
chunk {1} common.js (common.js) 0 bytes [rendered]
[14:29:00] Finished 'buildJs' after 1.24 s
[14:29:00] Starting 'default'...
[14:29:00] Finished 'default' after 7.21 μs
{ webpackGulpDeom } »
+ +

两种方案对比

从以上输出结果可以看出:

+

使用gulp-webpack

    +
  • gulpfile.jswebpack.config.js都要修改
  • +
  • 执行命令打印的信息更少
  • +
  • 编译时间更多?
  • +
+

使用gulp-util

    +
  • 只需要修改gulpfile.js,即使以后单独使用其中一个也不需要再做额外修改
  • +
  • 打印信息更丰富
  • +
  • 编译时间更短?
  • +
+

关于编译时间多少这块,我也没弄太清楚,如果有错误,请读者指出。就个人而言是比较喜欢第二种方案的

+]]>
+ + tools + + + gulp + webpack + +
+ + 使用conda快速初始化项目python + /2024/03/07/using-conda-to-quickly-initialize-a-project-python.html + 环境准备

如果你想使用conda来管理你的Python环境和依赖,你需要在你的机器上安装Anaconda或Miniconda。Anaconda包含了conda,Python和150+科学包及其依赖。而Miniconda只包含了conda和Python。

+

当你安装好后Anaconda或Miniconda后,你就可以愉快的使用conda管理项目了

+

新建项目

mkdir test-python-project
cd test-python-project
+ +

创建虚拟环境

为什么需要创建虚拟环境?
Python创建虚拟环境的目的是为了在同一台计算机上同时管理和运行多个独立的Python项目。虚拟环境提供了一个隔离的运行环境,使得每个项目可以拥有自己独立的Python解释器和依赖库,而不会相互干扰。

+

以下是创建虚拟环境的几个主要目的:

+
    +
  1. 隔离项目:每个项目都可以在自己的虚拟环境中运行,避免不同项目之间的依赖冲突。这样可以确保每个项目都能够独立地使用所需的特定Python版本和依赖库。
  2. +
  3. 管理依赖:虚拟环境允许您为每个项目单独安装和管理所需的依赖库。这样可以确保每个项目都使用其特定版本的依赖库,而不会受其他项目的影响。
  4. +
  5. 简化部署:使用虚拟环境可以更轻松地将项目部署到其他计算机或服务器上。您可以将虚拟环境与项目一起打包,并确保在不同环境中具有一致的运行结果。
  6. +
  7. 提高可移植性:虚拟环境使得项目在不同操作系统和计算机上的移植更加容易。您可以在不同平台上创建相同的虚拟环境,并确保项目在各个环境中都能够正常运行。
  8. +
+

总之,创建虚拟环境可以提供一个独立、隔离和可管理的Python运行环境,使得多个项目能够在同一台计算机上同时运行,而不会相互干扰。这为项目开发、依赖管理和部署提供了更大的灵活性和可靠性

+
# 选择你需要的python版本来创建虚拟环境
conda create --name myenv python=3.10
# 导出虚拟环境配置:保证其他合作成员环境一致
conda env export --name dev > environment.yml
# 其他成员创建虚拟环境,使用以下命令
conda env create --file environment.yml
# 激活虚拟环境
conda activate myenv
+ + +

安装依赖

你应该将这些依赖写入一个requirements.txt文件中,这样其他人在运行你的项目时可以方便地安装这些依赖。

+
# 自动将所有依赖导入到requirements中,并生成一个requirements.txt文件
conda list --export > requirements.txt
# 导出当前环境中的所有依赖包,并且不包含构建信息,以避免导出不必要的包
conda env export --no-builds > requirements.txt

# 安装依赖
conda install --file requirements.txt
+ +

那么现在就可以愉快的编写代码了~

+]]>
+ + python + Anaconda + + + python + Anaconda + +
+ + 使用vue框架造了一个日历控件 + /2017/01/05/vue-datapicker.html + +

使用官方提供的vue-simple-template配置打包,写的一个简单的vue-datepicker。支持选择功能,功能比较简单,欢迎大家拍砖.

+ +

效果预览(demo)

vue-datepicker

+

项目构建

#全局安装vue,vue-cli,webpack,如以安装则跳过
npm install -g vue vue-cli webpack

# 安装依赖
npm install

# 运行项目=>localhost:8080
npm run dev

# 更多的构建信息请参考官网
+ +

更新记录

2017-2-10 15:14:43

+
    +
  • 修正选择日期后高亮错误问题
  • +
  • 修正多出方法中计算时数字会被转化成字符串问题
  • +
+

2016-12-9 10:12:58

+
    +
  • 更新效果图
  • +
  • 修正computed计算时数字会被转化成字符串问题
    const startNum = self.chooseType ? +self.YearChangeSyboml - 4 : 1;
  • +
+

2016-12-8 17:30:04

+
    +
  • 增加输入框唤醒日历
  • +
  • 增加选择功能
  • +
+

2016-12-8 12:10:14

+
    +
  • 上传日历,只有简单展示版本
  • +
+

参考

待整理…

+]]>
+ + vue + + + datepicker + +
+ + vue中慎用style的scoped属性 + /2017/11/15/vue-style-scoped.html + +

在vue组件中,在style标签上添加scoped属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的,这是一个非常好的机制。但是为什么要慎用呢?在实际业务中我们往往会对公共组件样式做细微的调整,如果添加了scoped属性,那么样式将会变得不易修改。初写这篇文章时,本人没有找到一个好的方法去解决这个问题,后来经过大伙的解答,才让我恍然大悟。

+ +

何为谨慎使用

谨慎使用不是不用,而是持一种审视的目光去看待它。scoped肯定是解决了样式私有化的问题,但同时也引入了新的问题—样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的。所以我才说要谨慎使用

+

解决方案

首先要说明的问题是,最开始我以为这是一个BUG或者说一个弊端(因为当时没有搞明白scoped的真正作用),就很英勇的去提了一个issue,然后理所当然的被关闭了,关闭的理由是:scoped设计的初衷就是让样式变得私有,让它不会影响其他任何地方的样式。但是由于我在业务中经常遇到需要修改有scoped属性的组件,就写了一篇文章记录一下这个问题,希望大家谨慎的使用这个属性。
然而事实再一次证明了我的愚蠢,在vue-loader的文档中已经详细的对这个问题做了分析,并且对我遇到这种问题给出了解决方法:vue-loader的深度作用选择器。
因为我并没有去深入了解这些问题,所以注定这篇文章被大伙拍砖,😂😂😂😂😂😂

+

解决方案:vue-loader之scoped-css

+

鉴于此,虽然这篇文章没有什么价值,但为了提醒我自己深究的意义,我对后面的内容做了保留,以下内容是最开始文章的原文,请大家忽略,上面的内容才是正文,没错,正文就是这么少。

+ +

——————————–正文分割线,以下是无营养的内容——————————–

+
+

scoped实现私有化样式的原理

为什么会说,会增加复杂度?那么我们先从的实现模块的原理说起。为了方便称呼,我们假设把这种组件叫做模块私有组件,其他的未加scoped的叫做模块一般组件
通过查看DOM结构发现:vue通过在DOM结构以及css样式上加唯一不重复的标记,以保证唯一,达到样式私有化模块化的目的。具体的渲染结果是怎样的,通过一个例子来说明。

+

公共组件button组件

一个公共组件button,为了样式模块化,给其加上scoped属性,

+
//button.vue
<template>
<div class="button-warp">
<button class="button">text</button>
</div>
</template>
...
<style scoped>
.button-warp{
display:inline-block;
}
.button{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
</style>
+ +

浏览器渲染button组件

button组件在浏览器渲染出的html部分和css部分分别为:

+
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
+ +
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
+ +

从上面的字可以看出,添加了scoped属性的组件,为了达到组件样式模块化,做了两个处理:

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
+

大家都知道css样式有一个优先级的说法,scoped的这一操作,虽然达到了组件样式模块化的目的,但是会造成一种后果:每个样式的权重加重了:理论上我们要去修改这个样式,需要更高的权重去覆盖这个样式。这是增加复杂度的其中一个维度。

+

其他组件引用button组件

上面分析了单个组件渲染后的结果,那么组件互相调用之后会出现什么样的结果呢?,具体分两种情况:模块一般组件引用模块私有组件(本质和模块私有组件引用模块一般组件一样);模块私有组件引用模块私有组件。

+

举个例子:在组件content.vue中使用了button组件,那么content.vue组件是否添加scoped属性渲染出来的结果有什么区别呢?

+
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style>
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
</style>
+ +

模块一般组件(未添加scoped)引用模块私有组件

如果style上没有加scoped属性,那么渲染出来htmlcss分别就是:

+
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content{
width: 1200px;
margin: 0 auto;
}
.content .button{
border-raduis: 5px;
}
+ +

可以看出,虽然在content组件中,修改了buttonborder-raduis属性,但是由于权重关系,生效的依然是组件内部的样式(此时是外部的样式被覆盖)。所以如果要达到修改样式的目的,就必须加重我们要修改样式的权重(增加选择器层级,ID选择器,并列选择器,impotant等)

+

模块私有组件(添加scoped)引用模块私有组件

如果加了scoped属性呢?按照开始分析出来的规则(事实也是这么的):
首先是在所有的DOM节点加上data属性
然后在css选择器尾部加上data属性选择器

+

那么渲染出来htmlcss分别就是:

+
<div data-v-57bc25a0 class="content">
<p data-v-57bc25a0 class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<div data-v-57bc25a0 data-v-2311c06a class="button-warp">
<button data-v-2311c06a class="button">text</button>
</div>
</div>
+ +
/*button.vue渲染出来的css*/
.button-warp[data-v-2311c06a]{
display:inline-block;
}
.button[data-v-2311c06a]{
padding: 5px 10px;
font-size: 12px;
border-radus: 2px;
}
/*content.vue渲染出来的css*/
.content[data-v-57bc25a0]{
width: 1200px;
margin: 0 auto;
}
.content .button[data-v-57bc25a0]{
border-raduis: 5px;
}
+ +

对于上面的两种情况,可以明显看出来渲染后的结果大不相同。
虽然我们在content添加了想要修改button组件的样式的代码,但是仔细看,由于.content .button这句在末尾加的是content组件的scoped标记,最后这句其实根本作用不到我们想要的DOM节点上,所以这种情况我们在content内部写的任何样式都不会影响到button.vue组件,所以这就尴尬了。。。。
当然这个问题也是可以解决的,就是直接加全局样式可以修改到,但这势必会影响全部地方的组件;所以需要另外一种方法在content.vue组件内再加一个不带scoped属性的style标签,也就意味着要加两个style,一个用于私有样式,一个用于共有样式。这肯定是有点shit的,并且这两种解决方案都回避不了一个问题:权重!!!

+
//content.vue
<template>
<div class="content">
<p class="title"></p>
<!-- v-button假设是上面定义的组件 -->
<v-button></v-button>
</div>
</template>
...
<style scoped>
.content{
width: 1200px;
margin: 0 auto;
}
</style>
<style>
.content .button{
border-raduis: 5px;
}
</style>
+ +

这样符合规范么?貌似没看到不能这么写,并且这么写也确实生效了。。。但这样确实增加了思维的复杂度,有点苦恼啊。

+

总结scoped的渲染规则

总结一下scoped三条渲染规则

+
    +
  • HTMLDOM节点加一个不重复data属性(形如:data-v-2311c06a)来表示他的唯一性
  • +
  • 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-2311c06a])来私有化样式
  • +
  • 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上当前组件的data属性
  • +
+

解决方案

对于引用的三方库,如果对方使用了scoped,我们无力改变什么,如果确实需要修改他的样式最能在不加scoped的组件中修改样式,或者全局样式直接修改,这很粗暴!
对于自己维护的组件,一定要想清楚,组件的样式能否满足所有的情况。如果确实需要加,无疑会增加使用这个组件的开发同学的工作!

+

当然对于这个问题,如果诸君有更好的解决方案,请诸君TELL ME一下下!

+

趣事

在使用scoped一定要谨慎scoped的这个特性,本人以为这是一个BUG,就去提了issue ,结果尤大很霸气的回复
scoped设计的初衷就是不能让当前组件的样式修改其他任何地方的样式,因为设计如此。所以理所当然的这个issue已被干掉。。。😂😂😂😂😂😂

+]]>
+ + vue + + + javascript + vue + +
+ + 使用Github Action自动化发布npm包 + /2024/04/20/%E4%BD%BF%E7%94%A8Github%20Action%E8%87%AA%E5%8A%A8%E5%8C%96%E5%8F%91%E5%B8%83npm%E5%8C%85.html + +

GitHub Actions 提供了一种强大的自动化构建和发布流程的方式,通过组合github action的方式形成了workflow,来实现CI/CD。我们完全可以将 npm 包的发布流程集成到 GitHub workflow中,做到自动化发包,这可以可以提高效率并减少人为错误。以下是如何使用 GitHub Actions 实现自动化发布的详细指南。

+ +

0. 原理解释

github actions是一个github推出的CI/CD工具,可以模拟平台(比如linux等)自动化执行一些操作。
npm可以通过token实现免登陆发布包,并且Github Secrets能很好的保护token不被泄露(后面会说简单说一下为什么),这样就避免了在登陆信息在CI/CD时泄露

+

这是我编写的权限验证的一个包,支持vue,react以及函数式调用,同时使用github workflow实现的自动npm化发包。欢迎大家参考,同时提出优化意见:
validate-permission/.github/workflows at main · 2ue/validate-permission · GitHub

+

1. 准备工作

在开始之前,请确保:

+
    +
  • 你有一个 GitHub 账户,并且已经创建了一个仓库。
  • +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。但要注意,因为access token具有你的npm账户读写权利,所有不应该将.npmrc文件提交到远程仓库,需要将该文件加入到.gitignore中

+
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

发布失败

如果你没有成功,出现如下报错:

+
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • .npmrc配置问题:确保你的.npmrc文件路径,命名和内容正确
  • +
  • .npmrc:确保你的access token正确,或者具有publish或write权限
  • +
  • 本地没有使用npm官方源:由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此使用npm access token发布npm包的流程已经跑通了。

+

相信大家已经发现了,上面这种方式有一个问题:.npmrc只能放到本地,意味着每次换一个电脑写代码都要重新去复制access token,生成.npmrc,那有没有一种方式可以比较安全的管理它呢,答案肯定是有的:答案就在Github。
我们不但可以利用GitHub Secrets管理npm的access token,而且还可利用github actions能力实现自动化workflow,来自动化发包到npm。

+

3. 配置 GitHub Secrets

在 GitHub 仓库中添加 npm token:

+
    +
  1. 进入 GitHub 仓库的 “Settings” > “Secrets”。
  2. +
  3. 点击 “New repository secret”。
  4. +
  5. 输入 NPM_TOKEN 作为名称,并粘贴你的 npm token 作为值。
  6. +
  7. 保存秘密。
    image.png
  8. +
+

请注意,这里我们可以选择Secrets和Variables,他们都在我们编写的workflow中被读取到,其实都是变量,但是两者有区别:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
变量类型存储方式界面是否可见修改内容修改变量名workflow是否可见
Secrets加密不可见只能更新且更新时不能看到之前内容无法修改以***显示
Variables明文明文且可见可自由更改且可以看到之前内容可以修改明文可见
从上面的比较可以看出为什么github Secrets能够有效的保护npm access token,当然万事不是绝对,如果非要追求百分百安全,那就使用最原始的方式吧,毕竟放在你本地的文件也可能因为中木马泄露出去。
+

4. 编写 GitHub Actions Workflow

至此我们就可以利用github actions来编写一个workflow。让我们捋一下思路:

+
    +
  • 什么时候执行:在代码推送到某分支,或者打tag的时候,这里我们选择当代码推送时
  • +
  • 执行流程
      +
    • 拉取代码
    • +
    • 设置时区:如果需要
    • +
    • 设置node环境
    • +
    • 安装pnpm,yarn等工具:如果需要
    • +
    • 安装代码依赖,并打包
    • +
    • 写入token到.npmrc
    • +
    • 执行npm 发布
      当然如果所有的操作都需要我们自己去实现,就比较复杂,好在有第三方action可以快速的让我们实现这些能力:
    • +
    +
  • +
  • run:可以执行脚本命令,让我们可以执行类似npm i这样的命令
  • +
  • actions/checkout@v4:实现代码拉取
  • +
  • actions/setup-node@v4:实现node环境,可以指定node版本
  • +
+

那么我们就用这些能力创建一个workflow吧

+

在项目根目录下创建 .github/workflows/npm-publish.yml 文件,并添加以下内容:

+
# workflow名字
name: Publish to npm

# 触发条件:当 main 分支有 push 事件时触发
on:
push:
branches:
- main

jobs:
publish:
# 在 Ubuntu 最新版本上运行作业
runs-on: ubuntu-latest
steps:
# 使用 actions/checkout 检出当前代码
- name: Checkout code
uses: actions/checkout@v4

# 设置 Node.js 环境
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.11.1

# 安装项目依赖,并编译
- name: Install dependencies
run: |
npm i
npm run build

# 添加access token到.npmrc
- name: Add Npm Token
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ./.npmrc
ls -al
cat ./.npmrc

# 发布到 npm registry
- name: Publish to npm
run: npm publish
env:
# 读取github的secrets变量,这里的NPM_TOKEN是前面设置的变量名
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ +

此 workflow 会在推送到 main 分支时触发。

+

image.png

+

5. 推送更改到 GitHub

将你的更改推送到 GitHub 仓库:

+
git add .
git commit -m "Set up automated npm publishing with GitHub Actions"
git push
+ +

6. 测试 Workflow

一旦你推送了更改,GitHub Actions workflow 将会运行。你可以在 GitHub 仓库的 “Actions” 选项卡中查看 workflow 的状态。

+

7. 发布新版本

当你准备发布新版本时,更新 package.json 中的版本号,然后提交并推送这些更改到 main 分支。GitHub Actions 将自动处理剩余的发布流程。

+

8.优化

这个workflow脚本还有很多优化的空间,比如:

+

优化触发时机

如果我们推送代码就执行发包操作,在有些情况下可能不符合我们要求,所有我们可以更改触发条件:
正常情况我们想的是创建了一个v1.0.0这样的tag分支才触发,并且是修改了关键源码才触发

+
    +
  • 设置当推送了tag分支并且需要tag符合某种格式才触发流水
  • +
+
name: Npm publish
on:
push:
tags:
- "v*" # 这段的意思是仅在出现名为 v 字符开头的tag时,触发此任务,如v1.2.1
+ +
    +
  • 设置修改了某些文件才执行发包
  • +
+
- uses: dorny/paths-filter@v3
- #必须设定ID,用于后面读取这一步的结果
id: changes
with:
filters: |
update:
- 'main/**'
- 'types/**'
- 'utils/**'
- README.md
- package.json
- rollup.config.js
- tsconfig.json
- .babelrc
- .github/workflows/publish.yml
- name: Install pnpm
- # 这里是当【changes】输出了update为true才执行执行编译等工作
if: steps.changes.outputs.update == 'true'
run: |
npm install -g pnpm
+ +

dorny/paths-filter@v3github actions市场的一个包,可以用来检查哪些文件有变动,如果有变动则输出一个变量update,为true时表示有变动,可以用steps.changes.outputs.update来读取这个变量

+

自动化生成版本号

每次改动需要发布npm包时,都需要修改package.json的版本号,无疑有点麻烦,那我们可以使用npm version 命令来做到自动化版本修改:

+
    +
  • <update_type> 是语义化版本类型之一,如 majorminor 或 patch
  • +
  • 例如,运行 npm version patch 将会更新版本号为当前版本号的下一个补丁版本
  • +
+

甚至你可以通过github workflow的内置变量拿到tag名字,根据tag特征做到修改哪一个版本号

+

自动化生成tag和release

如果你的commit比较规范,比如使用了@changesets/cli等,那么可以利用github actions市场的三方包来自动化为你的发布生成changelog,tag,release等信息,比如semantic-release-action等

+

当然根据个人需求不同,可能还可以做更多的优化,这里就不一一述说了,毕竟本篇文章仅仅是一个穿针引线。

+

问题总结

问题 1:权限问题

如果你的 npm token 没有足够的权限发布包,你可能会遇到权限错误。[参考](#获取Access Token)

+

解决方案:确保你的 npm token 有发布包的权限。通常,这意味着你需要在 npm 网站上生成一个具有适当权限的 token。

+

问题 2:网络问题

如果你的 CI/CD 服务器无法访问 npm registry,发布可能会失败。一般在github上不会出现该问题,可能在本地使用了代理或者切换了npm源会出现这个问题。参考

+

解决方案:检查你的 CI/CD 服务器的网络设置,确保它可以无障碍地访问 https://registry.npmjs.org/

+

问题 3:Workflow 配置错误

如果你的 workflow 配置有误,它可能不会按预期触发。

+

解决方案:仔细检查 workflow 文件的语法和配置,特别是触发条件和 secret 的使用。

+

问题 4:.npmrc问题

如果你的 .npmrc 文件没有被正确地添加到项目根目录或内容不正确,那么可能导致发布失败。参考

+

解决方案:确保 .npmrc 文件被正确添加到你的版本控制系统,并且格式正确无误。

+

通过以上步骤和解决方案,你可以确保 npm 包的自动化发布流程顺畅运行。

+

参考

Using private packages in a CI/CD workflow | npm Docs
Working with the npm registry - GitHub Docs
Variables - GitHub Docs

+]]>
+ + github/actions + + + npm + CD + CI + action + github + +
+ + 如何使用Github Actions实现自动化部署Hexo博客 + /2024/04/19/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Github%20Actions%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2Hexo%E5%8D%9A%E5%AE%A2.html + +

Hexo 是一个基于 Node.js 的静态博客框架,它以简洁的速度和易用性受到许多开发者的青睐。如果你的博客部署在github上,那么这里有一种新的部署方式可能让摆脱频繁提交部署的苦恼!GitHub Actions 为自动化部署Hexo提供了一种解决方案,极大地简化了部署流程和节省了时间,可以让我们更多的专注到写作本身。

+ +

为什么使用Github Actions

    +
  • 将文章源文件和部署文件分离,有助于保护源文件中的隐私和你的草稿文件等
  • +
  • 节省手动部署的时间,专注于写作本身
  • +
  • 学习Github Actions相关知识,不折腾不作死发作了
  • +
+

如果你符合以上任何一条,你都应该考虑往下看下去。

+

什么是Github Actions

GitHub Actions 是 GitHub 推出的一项强大的自动化工具,它允许用户在 GitHub 仓库中创建、编辑和运行自动化脚本,这些脚本被称为工作流程(workflows)。这些工作流程可以响应 GitHub 上的各种事件,例如代码被推送、issue 被创建、pull request 被打开或定期调度任务等。

+

GitHub Actions 的工作流程(workflow)由 YAML 文件定义,这些文件放在仓库的 .github/workflows 目录下。每个工作流程文件描述了一系列的作业(jobs)和步骤(steps),定义了运行环境、触发条件、要执行的任务等。

+

前期准备

创建两个仓库

创建部署仓库

    +
  • 建立名为 <你的 GitHub 用户名>.github.io 的公开储存库,若之前已将 Hexo 上传至其他储存库,将该储存库重命名即可
  • +
  • 将 Hexo 文件夹中的文件 push 到储存库的默认分支,默认分支通常名为 main,旧一点的储存库可能名为 master
  • +
  • 开启Github Pages能力,使其可以 https://<你的 GitHub 用户名>.github.io访问
  • +
+

比如笔者使用2ue.github.io储存库用于存在文章编译后的代码,开启github pages后可通过https://2ue.github.io访问

+

创建文章源码仓库

    +
  • 创建一个私有仓库,用于储存文章源代码
    比如,笔者使用myblog储存库存放文章的源文件
  • +
+

至此两个仓库已经准备完了,后面所有的操作都在myblog上进行

+

生成Personal access tokens

+

github提供了access tokens的能力,可以在不登录的情况访问仓库和操作仓库等,所以我们可以通过相关能力来实现自动化部署,但同时也要保管好该token

+
+

Personal access tokens申请地址:Personal access tokens

+

打开网址后,点击 Generate new token -> Generate new token (classic)

+

image.png

+

然后依次填写

+
    +
  • Note: 注释或者名字,按自己喜欢填写符合规则的名字
  • +
  • Expiration:过期时间,可以选择合适的时间,这里我选择的是永不过期,No Expiration
  • +
  • Select scopes:选择权限,勾上repo和workflow,这里的作用是使得这个token具有读写repo的权限和通过github Action更新的能力
  • +
+

拖到页面最底部,点击生成按钮

+

image.png

+

token生成后,会在这里展示刚刚生成的token。
注意:新生成的token只会在创建时显示一次,刷新之后就会消失,记得备份保存,如果忘记了,删除重新生成即可

+

image.png

+

Token填写到myblog仓库

点击:New repository secret ,将刚刚申请的token填入:

+
    +
  • Name:尽量按规范使用大写,多个单词用_分割
  • +
  • Secret:填入刚刚申请的Token
  • +
+

image.png
image.png

+

部署脚本编写

将myblog仓库代码克隆到本地,新增.github/workflow文件夹,然后再新增一个.yml的文件,假设这里叫hexo-deploy.yml,然后将以下代码复制到文件中,保存后使用git提交到myblog仓库

+
name: deploying Hexo project to GitHub pages
on:
push:
branches:
- main # 分支有 push 行为时就触发这个 action

jobs:
build-and-deploy:
runs-on: ubuntu-latest
env:
GITHUB_USER: 2ue
# GitHub Pages仓库 即博客部署需要用到的仓库
DEPLOY_REPO: 2ue.github.io
DEPLOY_BRANCH: main
GIT_USER: 2ue
GIT_EMAIL: xxxx@xxx.com
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0

- name: Set timezone to Asia/Shanghai
uses: szenius/set-timezone@v1.0
with:
# 设置执行环境的时区为 Linux 上海时区
timezoneLinux: "Asia/Shanghai"

- name: Echo current time
run: timedatectl

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16.15.0

- name: Generate pages
run: |
export TZ='Asia/Shanghai'
npm install -g hexo-cli hexo
npm install
hexo clean && hexo g

- name: Git config
run: |
git config --global user.name "${{GIT_USER}}"
git config --global user.email "${{GIT_EMAIL}}"

- name: Pull blog repo && Copy files
run: |
git clone https://github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
rm -rf ${{DEPLOY_REPO}}/*
cp -r ./public/* ./${{DEPLOY_REPO}}

- name: Deploy blog
run: |
echo '>_ Enter repo: ${{GITHUB_USER}}/${{DEPLOY_REPO}} ...'
cd ${{DEPLOY_REPO}}
echo '>_ Pwd current dir ...'
pwd
echo '>_ Show files ...'
ls -al
echo '>_ Set remote git ...'
rm -rf .git
git init
git remote add origin https://${{secrets.DEPLOY_TOKEN}}@github.com/${{GITHUB_USER}}/${{DEPLOY_REPO}}.git
git branch -M ${{DEPLOY_BRANCH}}
git add .
echo '>_ Start Commit ...'
git commit --allow-empty -m "Github Action Automated Deployment $(date +'%Y-%m-%d %H:%M:%S')"
echo '>_ Start Push ...'
git push -u origin ${{DEPLOY_BRANCH}} --force
+ +

至此Hexo利用Github Actions自动化部署就实现了

+

部署

将代码提交到github后,打开github的myblog仓库(你自己的文章源文件仓库),点击:Actions可以看到有任务正在执行,以下是执行成功后的界面:

+

image.png

+

如果有失败,可以点击对应的步骤,查询详细情况:

+

image.png

+

当然如果你使用的vscode,也可以安装Github Actions插件,然后再vscode中直接查看执行过程,不过在插件中没有办法查看失败信息,但是可以点击直接跳转到对应页面查看详细信息

+

image.png

+

参考

在 GitHub Pages 上部署 Hexo | Hexo
GitHub Actions 文档 - GitHub 文档

+]]>
+ + hexo + github + + + action + github + hexo + +
+ + 使用Npm Token免登陆发包 + /2019/10/12/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Npm%20Token%E5%85%8D%E7%99%BB%E9%99%86%E5%8F%91%E5%8C%85.html + +

在使用 npm 包管理器进行包的发布时,通常需要手动登录 npm 账户,并且我们在本地使用npm时因为某些不可抗原因,使用了非npm官方源,导致我们每次发布都需要切换到官方源,显得非常繁琐。现在,通过使用 npm Token,可以避免手动登录,切换源等步骤,实现自动化发布流程,特别是在持续集成/持续部署(CI/CD)环境中。

+ +

1. 准备工作

在开始之前,请确保:

+
    +
  • 你的项目中包含一个 package.json 文件。
  • +
  • 你有一个 npm 账户,并且已经获取了 npm token。
  • +
  • 你即将发布的包的包名没有被占用。
  • +
+

获取Access Token

通过登陆官网获取

+

确保记录下生成的 Token,因为它只会在创建的时候显示一次,刷新后不会再次显示,如果忘记就只能重新申请

+
+

首先登陆npm官网,然后点击Access Tokens:

+

image.png

+

然后进入access Tokens生成页面:

+

image.png

+

两者任选一个点击进入,两者都可以作为token在github actions中使用,只是Classic Token设置更简单,Granular Access Token设置更多,控制更精细化

+

点击Classic Token后:一定勾选publish,后面才能正常从github actions推送

+

image.png

+

点击:Granular Access Token,红框勾选的三个必填:

+
    +
  • Token name:不必多说,token名字,起一个你喜欢的
  • +
  • Expiration:有效期,默认三十天,可以改得更长,过期后需要重新生成
  • +
  • Permissions:权限,即生成的token能做什么样的操作,同样需要选择 read && write,不然github actions执行是无法推送
  • +
  • 其余的选项可以根据需要适当勾选
  • +
+

填写完成后,滑到底部点击Generate token按钮

+

image.png

+

通过命令行获取

需要现在本地执行npm login登陆,然后调用文档的命令生产token,具体可以看下官方文档:
Creating and viewing access tokens | npm Docs

+

2. 如何免登陆发布npm包

创建.npmrc文件

前面你已经生成了一个npm的具有publish或者write权限的access token。
那么你只需在项目根目录下创建 .npmrc 文件,写入以下内容这个文件将包含 npm registry 的授权信息。

+
//registry.npmjs.org/:_authToken=你刚刚申请的token
+ +

或者直接在项目根目录命令行执行,将会自动生成:

+
echo "//registry.npmjs.org/:_authToken=你刚刚申请的token" > .npmrc
+ +

然后,将 .npmrc 文件添加到你的版本控制系统中。

+

发布

将你准备发布的文件,编译好。假设你已经准备好了,那么就愉快的执行:

+
npm logout
npm publish

# 将会输出下面类似字样,就是成功了
# ...
# npm notice package size: 12.6 kB
# npm notice unpacked size: 84.1 kB
# npm notice shasum: d282cbe9374611e6d10d6fb30215f61bb6c95c89
# npm notice integrity: sha512-N4iu+YUebBPCP[...]21dOqKE6G43iQ==
# npm notice total files: 18
# npm notice
# npm notice Publishing to [https://registry.npmjs.org/](https://registry.npmjs.org/) with tag latest and default access
# ...
+ +

或者登陆npm官网,查看刚刚发布的包

+

如果你没有成功,出现如下报错:

+
npm ERR! need auth You need to authorize this machine using `npm adduser` [https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)
+ +

有三个可能原因:

+
    +
  • 确保你的.npmrc文件路径,命名和内容正确
  • +
  • 确保你的access token正确,或者具有publish或write权限
  • +
  • 由于某些不可描述原因,你将本地npm的源设置为其他源,没有设置为https://registry.npmjs.org,比如腾讯源,阿里云云等,此时需要切换过来才能正确
  • +
+

至此,你已经做到免登陆发布包了,但别急,这还没完。

+

这样安全吗?

如果你真像上面这样做了,那你npm仓库的安全就没有了。

+

为什么呢,因为你刚刚申请的access token有读写仓库的权限,你如果随着你的代码提交到仓库,就会将他暴露出去,造成损失,那么怎么做才安全呢?下面会将

+

更安全的免登陆发包

忽略.npmrc

将.npmrc加入到.gitignore文件中,不提交到代码仓库,只在你本地使用

+

变量

在.npmrc使用变量

将.npmrc改成如下内容:

+
//registry.npmjs.org/:_authToken=${NPM_PUBLISH_TOKEN}
+ +

NPM_PUBLISH_TOKEN是我们后面要设置的全局变量

+

设置全局变量

在全局变量(环境变量)中设置NPM_PUBLISH_TOKEN为你刚刚申请的token

+

不同的平台设置方式不一样:

+

linux和mac平台可以通过编辑.bashrc或者.zshrc文件实现:

+
    +
  • 打开.bashrc或者.zshrc编辑
  • +
+
vi ~/.bashrc
+ +
    +
  • 在文件内容末尾追加:
  • +
+
export NPM_PUBLISH_TOKEN=你刚刚申请的token
+ +
    +
  • 然后执行source命令使其生效
  • +
+
source ~/.bashrc
+ +

如果提示没有权限加上sudo

+

window平台可以直接打开高级属性界面设置

+

未完待续

当然可以看出在本地使用token还是有很多限制,没有彻底解放生产力,后续我会出一篇文章讲述如何结合CI/CD去实现自动化发包

+

重要

任何时候都不要将access token相关的信息暴露到公网上,以免造成不必要损失

+

参考

1.Creating and viewing access tokens | npm Docs
2.Using private packages in a CI/CD workflow | npm Docs
3.Working with the npm registry - GitHub Docs

+]]>
+ + npm + + + npm + +
+
diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..a18f8dc5 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,806 @@ + + + + + https://blog.imx0.com/2024/04/23/Oh%20My%20Zsh%20%E8%87%AA%E5%8A%A8%E5%8C%96-%E8%BD%BB%E6%9D%BE%E7%AE%A1%E7%90%86%E5%A4%9A%E4%B8%AA%20Git%20%E7%94%A8%E6%88%B7%E8%BA%AB%E4%BB%BD%E4%BF%A1%E6%81%AF.html + + 2024-04-23 + + monthly + 0.6 + + + + https://blog.imx0.com/2024/04/20/%E4%BD%BF%E7%94%A8Github%20Action%E8%87%AA%E5%8A%A8%E5%8C%96%E5%8F%91%E5%B8%83npm%E5%8C%85.html + + 2024-04-20 + + monthly + 0.6 + + + + https://blog.imx0.com/2024/04/19/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Github%20Actions%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2Hexo%E5%8D%9A%E5%AE%A2.html + + 2024-04-19 + + monthly + 0.6 + + + + https://blog.imx0.com/2019/10/12/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Npm%20Token%E5%85%8D%E7%99%BB%E9%99%86%E5%8F%91%E5%8C%85.html + + 2024-04-13 + + monthly + 0.6 + + + + https://blog.imx0.com/about/index.html + + 2024-03-28 + + monthly + 0.6 + + + + https://blog.imx0.com/2024/03/07/using-conda-to-quickly-initialize-a-project-python.html + + 2024-03-07 + + monthly + 0.6 + + + + https://blog.imx0.com/2021/09/14/Mac%E6%8F%90%E7%A4%BA%E2%80%9Dxxx.app%E5%B7%B2%E6%8D%9F%E5%9D%8F%EF%BC%8C%E6%97%A0%E6%B3%95%E6%89%93%E5%BC%80%EF%BC%8C%E4%BD%A0%E5%BA%94%E8%AF%A5%E5%B0%86%E5%AE%83%E7%A7%BB%E5%88%B0%E5%BA%9F%E7%BA%B8%E7%AF%93%E2%80%9D%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95.html + + 2021-09-14 + + monthly + 0.6 + + + + https://blog.imx0.com/2018/07/21/node.js-version-management-artifact-nvm.html + + 2018-07-21 + + monthly + 0.6 + + + + https://blog.imx0.com/2018/01/20/Git%E7%B3%BB%E5%88%97%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E4%B9%8B%E6%94%BE%E5%BC%83%E4%BF%AE%E6%94%B9.html + + 2018-05-03 + + monthly + 0.6 + + + + https://blog.imx0.com/2016/12/31/2016-to-2017.html + + 2018-03-02 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/11/29/blow-forever.html + + 2017-11-29 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/11/26/javascript-sinpats.html + + 2017-11-26 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/11/24/regex-to-something.html + + 2017-11-24 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/11/15/vue-style-scoped.html + + 2017-11-15 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/11/02/how-to-make-a-kalendar.html + + 2017-11-02 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/10/27/for-in-and-for-of.html + + 2017-10-27 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/10/16/desktop-notification.html + + 2017-10-16 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/10/10/mocha+chai.html + + 2017-10-10 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/10/10/fed-test.html + + 2017-10-10 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/08/21/nvm-node-version-manager.html + + 2017-08-21 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/07/01/learn-git-2.html + + 2017-07-01 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/06/15/babun-casue-ssh-key-bad.html + + 2017-06-15 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/03/31/javascript-type.html + + 2017-03-31 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/03/15/babun.html + + 2017-03-15 + + monthly + 0.6 + + + + https://blog.imx0.com/2017/01/05/vue-datapicker.html + + 2017-01-05 + + monthly + 0.6 + + + + https://blog.imx0.com/2016/11/22/Mobile-terminal-adapter.html + + 2016-11-22 + + monthly + 0.6 + + + + https://blog.imx0.com/categories/index.html + + 2016-11-14 + + monthly + 0.6 + + + + https://blog.imx0.com/schedule/index.html + + 2016-11-09 + + monthly + 0.6 + + + + https://blog.imx0.com/tags/index.html + + 2016-11-09 + + monthly + 0.6 + + + + https://blog.imx0.com/2016/09/24/use-gulp-and-webpack-to-bulid-resource.html + + 2016-09-24 + + monthly + 0.6 + + + + https://blog.imx0.com/2016/09/05/regex.html + + 2016-09-05 + + monthly + 0.6 + + + + https://blog.imx0.com/2016/08/11/javascript-array-method.html + + 2016-08-11 + + monthly + 0.6 + + + + https://blog.imx0.com/2016/06/20/learn-git-1.html + + 2016-06-20 + + monthly + 0.6 + + + + https://blog.imx0.com/2016/06/15/get-url-values.html + + 2016-06-15 + + monthly + 0.6 + + + + https://blog.imx0.com/2016/06/14/animate-number.html + + 2016-06-14 + + monthly + 0.6 + + + + + https://blog.imx0.com/ + 2024-05-05 + daily + 1.0 + + + + + https://blog.imx0.com/tags/%E6%80%BB%E7%BB%93/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/git/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/mac/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/app%E6%8D%9F%E5%9D%8F/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/rem/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/mobile/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/oh-my-zsh/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/git%E5%A4%9A%E8%BA%AB%E4%BB%BD/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/animation/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/Babun/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/cmd/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/shell/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E4%BA%BA%E7%94%9F/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/Notification/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/javascript/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E5%89%8D%E7%AB%AF%E6%B5%8B%E8%AF%95/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E9%81%8D%E5%8E%86/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/for-in/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/for-of/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/url/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E6%97%A5%E5%8E%86/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/kalendar/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/Array/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E6%95%B0%E7%BB%84/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/sinpats/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/typeof/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/object/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E6%B5%8B%E8%AF%95/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/node/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/npm/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/nvm/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/regex/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/%E6%AD%A3%E5%88%99/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/gulp/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/webpack/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/python/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/Anaconda/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/datepicker/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/vue/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/CD/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/CI/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/action/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/github/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/tags/hexo/ + 2024-05-05 + weekly + 0.2 + + + + + + https://blog.imx0.com/categories/record/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/%E5%B7%A5%E5%85%B7-git/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/mac/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/javascript/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/git/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/tools/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/%E9%9A%8F%E6%83%B3/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/H5/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/test/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/git/oh-my-zsh/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/Javascript/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/%E6%B5%8B%E8%AF%95/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/%E5%B7%A5%E5%85%B7/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/regex/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/python/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/vue/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/github-actions/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/hexo/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/%E5%B7%A5%E5%85%B7/nvm/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/python/Anaconda/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/hexo/github/ + 2024-05-05 + weekly + 0.2 + + + + https://blog.imx0.com/categories/npm/ + 2024-05-05 + weekly + 0.2 + + + diff --git a/tags/Anaconda/index.html b/tags/Anaconda/index.html new file mode 100644 index 00000000..a6d1f2e7 --- /dev/null +++ b/tags/Anaconda/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: Anaconda | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

Anaconda + 标签 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Array/index.html b/tags/Array/index.html new file mode 100644 index 00000000..cc9c5cb9 --- /dev/null +++ b/tags/Array/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: Array | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

Array + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Babun/index.html b/tags/Babun/index.html new file mode 100644 index 00000000..1de212ed --- /dev/null +++ b/tags/Babun/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: Babun | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

Babun + 标签 +

+
+ + +
+ 2017 +
+ + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/CD/index.html b/tags/CD/index.html new file mode 100644 index 00000000..ec1ff1a1 --- /dev/null +++ b/tags/CD/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: CD | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

CD + 标签 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/CI/index.html b/tags/CI/index.html new file mode 100644 index 00000000..71e51c29 --- /dev/null +++ b/tags/CI/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: CI | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

CI + 标签 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Notification/index.html b/tags/Notification/index.html new file mode 100644 index 00000000..a9893de2 --- /dev/null +++ b/tags/Notification/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: Notification | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

Notification + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/action/index.html b/tags/action/index.html new file mode 100644 index 00000000..b460daec --- /dev/null +++ b/tags/action/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: action | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

action + 标签 +

+
+ + +
+ 2024 +
+ + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/animation/index.html b/tags/animation/index.html new file mode 100644 index 00000000..bd302632 --- /dev/null +++ b/tags/animation/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: animation | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

animation + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/app\346\215\237\345\235\217/index.html" "b/tags/app\346\215\237\345\235\217/index.html" new file mode 100644 index 00000000..2fda8e24 --- /dev/null +++ "b/tags/app\346\215\237\345\235\217/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: app损坏 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

app损坏 + 标签 +

+
+ + +
+ 2021 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/cmd/index.html b/tags/cmd/index.html new file mode 100644 index 00000000..5d3a9219 --- /dev/null +++ b/tags/cmd/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: cmd | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

cmd + 标签 +

+
+ + +
+ 2017 +
+ + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/datepicker/index.html b/tags/datepicker/index.html new file mode 100644 index 00000000..8ab53203 --- /dev/null +++ b/tags/datepicker/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: datepicker | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

datepicker + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/for-in/index.html b/tags/for-in/index.html new file mode 100644 index 00000000..b355546e --- /dev/null +++ b/tags/for-in/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: for...in | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

for...in + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/for-of/index.html b/tags/for-of/index.html new file mode 100644 index 00000000..e32906aa --- /dev/null +++ b/tags/for-of/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: for...of | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

for...of + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/git/index.html b/tags/git/index.html new file mode 100644 index 00000000..792516e2 --- /dev/null +++ b/tags/git/index.html @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: git | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

git + 标签 +

+
+ + +
+ 2024 +
+ + +
+ 2018 +
+ + +
+ 2017 +
+ + + + + + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/github/index.html b/tags/github/index.html new file mode 100644 index 00000000..574c5e71 --- /dev/null +++ b/tags/github/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: github | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

github + 标签 +

+
+ + +
+ 2024 +
+ + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/git\345\244\232\350\272\253\344\273\275/index.html" "b/tags/git\345\244\232\350\272\253\344\273\275/index.html" new file mode 100644 index 00000000..17ee353d --- /dev/null +++ "b/tags/git\345\244\232\350\272\253\344\273\275/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: git多身份 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

git多身份 + 标签 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/gulp/index.html b/tags/gulp/index.html new file mode 100644 index 00000000..5946e480 --- /dev/null +++ b/tags/gulp/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: gulp | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

gulp + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/hexo/index.html b/tags/hexo/index.html new file mode 100644 index 00000000..9f01b989 --- /dev/null +++ b/tags/hexo/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: hexo | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

hexo + 标签 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 00000000..32864cf2 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +tags | 前端路上 + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+ +

tags +

+ + + +
+ + + +
+ +
+ + + +
+ + + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/javascript/index.html b/tags/javascript/index.html new file mode 100644 index 00000000..ea927e92 --- /dev/null +++ b/tags/javascript/index.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: javascript | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

javascript + 标签 +

+
+ + +
+ 2017 +
+ + + + + + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/kalendar/index.html b/tags/kalendar/index.html new file mode 100644 index 00000000..9eaa5f38 --- /dev/null +++ b/tags/kalendar/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: kalendar | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

kalendar + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/mac/index.html b/tags/mac/index.html new file mode 100644 index 00000000..58c19aaf --- /dev/null +++ b/tags/mac/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: mac | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

mac + 标签 +

+
+ + +
+ 2021 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/mobile/index.html b/tags/mobile/index.html new file mode 100644 index 00000000..cec2194a --- /dev/null +++ b/tags/mobile/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: mobile | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

mobile + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/node/index.html b/tags/node/index.html new file mode 100644 index 00000000..40bc9256 --- /dev/null +++ b/tags/node/index.html @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: node | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

node + 标签 +

+
+ + +
+ 2018 +
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/npm/index.html b/tags/npm/index.html new file mode 100644 index 00000000..4af78035 --- /dev/null +++ b/tags/npm/index.html @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: npm | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

npm + 标签 +

+
+ + +
+ 2024 +
+ + +
+ 2019 +
+ + +
+ 2018 +
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/nvm/index.html b/tags/nvm/index.html new file mode 100644 index 00000000..bfd9cad0 --- /dev/null +++ b/tags/nvm/index.html @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: nvm | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

nvm + 标签 +

+
+ + +
+ 2018 +
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/object/index.html b/tags/object/index.html new file mode 100644 index 00000000..4c90eaf9 --- /dev/null +++ b/tags/object/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: object | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

object + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/oh-my-zsh/index.html b/tags/oh-my-zsh/index.html new file mode 100644 index 00000000..d577dfe6 --- /dev/null +++ b/tags/oh-my-zsh/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: oh-my-zsh | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

oh-my-zsh + 标签 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/python/index.html b/tags/python/index.html new file mode 100644 index 00000000..e3fe9eb3 --- /dev/null +++ b/tags/python/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: python | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

python + 标签 +

+
+ + +
+ 2024 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/regex/index.html b/tags/regex/index.html new file mode 100644 index 00000000..5f02fdfe --- /dev/null +++ b/tags/regex/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: regex | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

regex + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/rem/index.html b/tags/rem/index.html new file mode 100644 index 00000000..8874e978 --- /dev/null +++ b/tags/rem/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: rem | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

rem + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/shell/index.html b/tags/shell/index.html new file mode 100644 index 00000000..69ac6884 --- /dev/null +++ b/tags/shell/index.html @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: shell | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

shell + 标签 +

+
+ + +
+ 2017 +
+ + + + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/sinpats/index.html b/tags/sinpats/index.html new file mode 100644 index 00000000..f99f5c8b --- /dev/null +++ b/tags/sinpats/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: sinpats | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

sinpats + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/typeof/index.html b/tags/typeof/index.html new file mode 100644 index 00000000..7c09f145 --- /dev/null +++ b/tags/typeof/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: typeof | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

typeof + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/url/index.html b/tags/url/index.html new file mode 100644 index 00000000..422cea4e --- /dev/null +++ b/tags/url/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: url | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

url + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/vue/index.html b/tags/vue/index.html new file mode 100644 index 00000000..27189cb5 --- /dev/null +++ b/tags/vue/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: vue | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

vue + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/webpack/index.html b/tags/webpack/index.html new file mode 100644 index 00000000..3a70dd5f --- /dev/null +++ b/tags/webpack/index.html @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: webpack | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

webpack + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\344\272\272\347\224\237/index.html" "b/tags/\344\272\272\347\224\237/index.html" new file mode 100644 index 00000000..f683d1a7 --- /dev/null +++ "b/tags/\344\272\272\347\224\237/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 人生 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

人生 + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\345\211\215\347\253\257\346\265\213\350\257\225/index.html" "b/tags/\345\211\215\347\253\257\346\265\213\350\257\225/index.html" new file mode 100644 index 00000000..553fe61d --- /dev/null +++ "b/tags/\345\211\215\347\253\257\346\265\213\350\257\225/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 前端测试 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

前端测试 + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\346\200\273\347\273\223/index.html" "b/tags/\346\200\273\347\273\223/index.html" new file mode 100644 index 00000000..ffd35ef3 --- /dev/null +++ "b/tags/\346\200\273\347\273\223/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 总结 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

总结 + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\346\225\260\347\273\204/index.html" "b/tags/\346\225\260\347\273\204/index.html" new file mode 100644 index 00000000..871933f9 --- /dev/null +++ "b/tags/\346\225\260\347\273\204/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 数组 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

数组 + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\346\227\245\345\216\206/index.html" "b/tags/\346\227\245\345\216\206/index.html" new file mode 100644 index 00000000..8da044bf --- /dev/null +++ "b/tags/\346\227\245\345\216\206/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 日历 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

日历 + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\346\255\243\345\210\231/index.html" "b/tags/\346\255\243\345\210\231/index.html" new file mode 100644 index 00000000..fa59b0af --- /dev/null +++ "b/tags/\346\255\243\345\210\231/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 正则 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

正则 + 标签 +

+
+ + +
+ 2016 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\346\265\213\350\257\225/index.html" "b/tags/\346\265\213\350\257\225/index.html" new file mode 100644 index 00000000..8a195ced --- /dev/null +++ "b/tags/\346\265\213\350\257\225/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 测试 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

测试 + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\347\211\210\346\234\254\347\256\241\347\220\206/index.html" "b/tags/\347\211\210\346\234\254\347\256\241\347\220\206/index.html" new file mode 100644 index 00000000..900cebe3 --- /dev/null +++ "b/tags/\347\211\210\346\234\254\347\256\241\347\220\206/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 版本管理 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

版本管理 + 标签 +

+
+ + +
+ 2018 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\350\207\252\345\212\250\345\214\226\346\265\213\350\257\225/index.html" "b/tags/\350\207\252\345\212\250\345\214\226\346\265\213\350\257\225/index.html" new file mode 100644 index 00000000..83ce83fa --- /dev/null +++ "b/tags/\350\207\252\345\212\250\345\214\226\346\265\213\350\257\225/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 自动化测试 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

自动化测试 + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\351\201\215\345\216\206/index.html" "b/tags/\351\201\215\345\216\206/index.html" new file mode 100644 index 00000000..451182e0 --- /dev/null +++ "b/tags/\351\201\215\345\216\206/index.html" @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +标签: 遍历 | 前端路上 + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ + + +

前端路上

+ +
+

万物皆有裂缝

+
+ + +
+ + + + + + + +
+ +
+ +
+ + + + + +
+ +
+ + + + + +
+
+
+

遍历 + 标签 +

+
+ + +
+ 2017 +
+ + + +
+
+ + + + +
+
+ + + + +
+ + 0% +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +