GithubHelp home page GithubHelp logo

web_accumulate's Introduction

about OBKoro1

Hi,大家好,我是OBKoro1,大家可以叫koro(扣肉)。

关于我的其他信息有兴趣的朋友可以去前端进阶积累-关于了解。

下面是我在github上的几个开源项目的演示gif,自认为还是比较好用的工具。

开源项目演示

  1. VSCode插件: 用于一键生成文件头部注释并自动更新最后编辑人和编辑时间、函数注释自动生成和参数提取。
  2. 插件可以帮助用户养成良好的编码习惯,规范整个团队风格。
  3. 经过多版迭代后,插件支持所有主流语言,灵活方便,文档齐全,食用简单!
  4. 从2018年5月维护至今, 3K+ Star,关闭issue 300+
  5. 目前拥有250K+的用户,VSCode图表统计日安装用户100多-400多人,

头部注释

函数注释

减少摸鱼的时间和频率的Chrome插件:在上班/学习期间很容易下意识的打开摸鱼网站,插件帮助我们减少摸鱼的时间和频率,提高我们上班和学习的效率,节省时间用于学习提升自己或者享受生活

  • 这是一个可以在任意时间范围自动提交commit的VSCode插件
  • 它可以自由控制每天的commit次数, 随机commit次数,使你的commit提交看起来更加逼真。
  • 它在平常不用运行,需要的时候花十几分钟一键刷commit,填满你的github首页绿色格子

收集和整理了一个大厂前端需要掌握能力的仓库。

其中分为JS基础能力,大厂场景题、大厂面试真题。

希望能够帮助大家提升自己的能力,在面试的时候能够游刃有余,轻松拿到高薪offer。

大厂前端需要掌握的能力

用爱发电,求赞助 😭

开源不易,本插件的开发与维护全都是利用业余时间。

开源工作对我来说就是用爱发电,从18年开始在社区开源到现在,可以说基本没有收益。

如果觉得这个效率工具还不错, 对你有所帮助,就赞助支持一下我的工作吧。

赞助

联系方式:

web_accumulate's People

Contributors

obkoro1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

web_accumulate's Issues

推箱子 | amateur

博客链接

# 推箱子

demo图片

# demo:推箱子小游戏

# 步骤解析:

本文代码已经放在了github上面了,里面也进行了很详细的代码注释,可以copy下来,在本地运行一下看看

# 1. 渲染地图

  1. html结构:

     html结构十分简单,只要弄一堆div,来放置地图的class就可以了,我这里初始化了12*9个div,地图里最多九行高度。   
     这些div都是同样大小,地图渲染出来区别的只是颜色的不同。
    
  2. 地图函数:

var box=$('.box div'); //地图使用的div集合
function create(){ //创建地图函数
box.each(function(index){ //index的数量是固定的,是box div下面div的数量
        // 每次创建地图初始化div
    box.eq(index).removeClass();
});
box.each(function(index,element){ //循环整个div的数量 二维数组里数量不够的 默认为空白
//level为关卡数 根据关卡渲染地图 builder为二维数组,为地图关卡
    if(builder[level][index]){ //过滤0
        box.eq(index).addClass('type'+builder[level][index]);
    }
});
box.eq(origin[level]).addClass("pusher"); //推箱人 皮卡丘位置
}

//第一关的地图长这样(下面只是栗子,不是代码),0代表不可抵达区域,1代表目标(要被推到的地方),
//2代表普通路径(可以走的),3代表墙,4代表箱子
[0,0,0,0,3,3,3,0,0,0,0,0,
0,0,0,0,3,1,3,0,0,0,0,0,
0,0,0,0,3,2,3,3,3,3,0,0,
0,0,3,3,3,4,2,4,1,3,0,0,
0,0,3,1,2,4,2,3,3,3,0,0,
0,0,3,3,3,3,4,3,0,0,0,0,
0,0,0,0,0,3,1,3,0,0,0,0,
0,0,0,0,0,3,3,3,0,0,0,0]

# 2. 捕获键盘事件,判断是否可以移动

使用$(document).keydown()jqery事件,捕获键盘事件。

  1. 捕获键盘事件,上下左右以及wsad。
$(document).keydown(function (e) {
    var key=e.which;
    switch(key){
        //col 的值为12,上下移动要12个div为一个周期
        //方向键上或者w
        case 87:
        case 38:
            move(-col);//判断移动函数
        break;
        //方向键下或者s
        case 83:
        case 40:
            move(col);
        break;
        //方向键左或者a
        case 65:
        case 37:
            move(-1);
        break;
        //方向键右或者d
        case 68:
        case 39:
            move(1);
        break;
    }
    setTimeout(win,500); //按键之后调判断是否过关
});
  1. 判断是否可以移动。

分为两个判断条件:一个是推箱子,一个是不推箱子 自然移动,否则不移动皮卡丘。

function move(step){ //是否移动判断
        // 分为两个判断条件一个是推箱子,一个是不推箱子 自然移动。 否则不移动皮卡丘
        //step 上下是12个div一个周期,左右是1个div positin是皮卡丘的原来位置
    var pikaqiu1=box.eq(position);//皮卡丘现在的地方
    var pikaqiu2=box.eq(position+step);//皮卡丘要去的下一个地方
    var pushBox=box.eq(position+step*2);//箱子要去的下一个地方
    if(!pikaqiu2.hasClass('type4')&&( pikaqiu2.hasClass('type1')||pikaqiu2.hasClass('type2'))){ //自然移动
        //判断:如果下一个div的class不包含type4(箱子),并且 下一个div含有type1(目标位置),或者type2(普通路径)
        //这一步和下一步判断是否有type4的原因是普通路径会变成有type4的路径,这时候就会出现问题
        pikaqiu1.removeClass("pusher"); //移除当前皮卡丘
        pikaqiu2.addClass("pusher");//移动皮卡丘到下一个位置
        position=position+step;//增加position值
    }
    else if((pikaqiu2.hasClass('type4'))&&(!pushBox.hasClass('type4'))&&(pushBox.hasClass('type1')|| pushBox.hasClass('type2')) ) {
        //推箱子    
        //如果下一个div的class包含type4(箱子)并且 不包含重叠type4(箱子) 并且 包含class type1(目标位置)或者 包含type2(空路)
        pikaqiu2.removeClass('type4');//移除当前箱子
        pikaqiu1.removeClass("pusher");//移除当前皮卡丘
        pushBox.addClass('type4');//移动箱子到下一个位置
        pikaqiu2.addClass("pusher").addClass("type2");//
        //本来是type4 移除之后,这里没有class了,要变成普通路径
        position=position+step;//增加position值 
    }
}

# 3.胜利判断:

每次移动都要调用这个胜利判断。

function win(){ //胜利条件判断
    if($(".type1.type4").length===goal){ //推的箱子与关卡设置通过箱子的数量对比
        if(level<9) {
            alert("666,挑战下一关吧--OBKoro1");
            level++; //关卡+1
            goal = goalList[level];
            position = origin[level];
            create();
        }else {
            alert("厉害啊 大佬 通关了都");
        }
    }
}

# 代码地址demo地址

以上2018.1.7

# 点个Star支持我一下~

博客链接

*注释工具: 一键添加佛祖保佑永无bug和神兽护体等*注释 | tool

博客链接

# *注释工具: 一键添加佛祖保佑永无bug和神兽护体等*注释

很早之前就见过各种佛祖保佑永无BUG神兽护体等形式的注释,感觉很有趣,蛮*的😉。

然后最近有人在我开源的VSCode插件:**koroFileHeader**里面给我提issue,希望能够支持这种类型的注释。

现在开发完成了,大家可以根据下面的使用方式试用一下。

# koroFileHeader插件简介

  1. 这个插件目前维护两年多了,有1300+Star,支持所有主流语言,支持自定义语言(不支持的语言可以自行设置)。
  2. 作用: 在文件开头添加注释,记录文件信息/文件的传参/出参等,让人对文件的功能一目了然。
  3. 如果觉得还不错的话,就给我点个Star⭐️吧~

插件示例:

example.gif

# 一键添加佛祖保佑永无BUG、神兽护体等注释图案

# 使用方式

  1. 在VSCode插件市场下载安装koroFileHeader

  2. 通过快捷键shift+command+p 输入注释图案/codeDesign,就可以选择注释图案了, 如下图所示。

# 注释图案GIF示例

# 支持各种语言的注释

注释图案不仅支持目前世面主流的注释形式,还支持自定义语言的注释形式。url

# 注释图案和头部注释结合

"fileheader.configObj": {
    "designAddHead": false // 默认关闭
}

设为true效果如下:

/*
 *                        .::::.
 *                      .::::::::.
 *                     :::::::::::
 *                  ..:::::::::::'
 *               '::::::::::::'
 *                 .::::::::::
 *            '::::::::::::::..
 *                 ..::::::::::::.
 *               ``::::::::::::::::
 *                ::::``:::::::::'        .:::.
 *               ::::'   ':::::'       .::::::::.
 *             .::::'      ::::     .:::::::'::::.
 *            .:::'       :::::  .:::::::::' ':::::.
 *           .::'        :::::.:::::::::'      ':::::.
 *          .::'         ::::::::::::::'         ``::::.
 *      ...:::           ::::::::::::'              ``::.
 *     ````':.          ':::::::::'                  ::::..
 *                        '.:::::'                    ':'````..
 * 
 * Author       : OBKoro1
 * Date         : 2020-04-30 15:51:08
 * LastEditors  : OBKoro1
 * LastEditTime : 2020-05-13 17:24:47
 * FilePath     : \fileHead\test.js
 * Description  : 
 * https://github.com/OBKoro1
 */

# 注释图案

# 佛祖

/*
 *                        _oo0oo_
 *                       o8888888o
 *                       88" . "88
 *                       (| -_- |)
 *                       0\  =  /0
 *                     ___/`---'\___
 *                   .' \\|     |// '.
 *                  / \\|||  :  |||// \
 *                 / _||||| -:- |||||- \
 *                |   | \\\  - /// |   |
 *                | \_|  ''\---/''  |_/ |
 *                \  .-\__  '-'  ___/-. /
 *              ___'. .'  /--.--\  `. .'___
 *           ."" '<  `.___\_<|>_/___.' >' "".
 *          | | :  `- \`.;`\ _ /`;.`/ - ` : | |
 *          \  \ `_.   \_ __\ /__ _/   .-` /  /
 *      =====`-.____`.___ \_____/___.-`___.-'=====
 *                        `=---='
 * 
 * 
 *      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * 
 *            佛祖保佑       永不宕机     永无BUG
 */

# 佛曰

/*
 *           佛曰:  
 *                   写字楼里写字间,写字间里程序员;  
 *                   程序人员写程序,又拿程序换酒钱。  
 *                   酒醒只在网上坐,酒醉还来网下眠;  
 *                   酒醉酒醒日复日,网上网下年复年。  
 *                   但愿老死电脑间,不愿鞠躬老板前;  
 *                   奔驰宝马贵者趣,公交自行程序员。  
 *                   别人笑我忒疯癫,我笑自己命太贱;  
 *                   不见满街漂亮妹,哪个归得程序员?
 */

# 佛祖+佛曰

/*
 *                        _oo0oo_
 *                       o8888888o
 *                       88" . "88
 *                       (| -_- |)
 *                       0\  =  /0
 *                     ___/`---'\___
 *                   .' \\|     |// '.
 *                  / \\|||  :  |||// \
 *                 / _||||| -:- |||||- \
 *                |   | \\\  - /// |   |
 *                | \_|  ''\---/''  |_/ |
 *                \  .-\__  '-'  ___/-. /
 *              ___'. .'  /--.--\  `. .'___
 *           ."" '<  `.___\_<|>_/___.' >' "".
 *          | | :  `- \`.;`\ _ /`;.`/ - ` : | |
 *          \  \ `_.   \_ __\ /__ _/   .-` /  /
 *      =====`-.____`.___ \_____/___.-`___.-'=====
 *                        `=---='
 * 
 * 
 *      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * 
 *            佛祖保佑       永不宕机     永无BUG
 * 
 *        佛曰:  
 *                写字楼里写字间,写字间里程序员;  
 *                程序人员写程序,又拿程序换酒钱。  
 *                酒醒只在网上坐,酒醉还来网下眠;  
 *                酒醉酒醒日复日,网上网下年复年。  
 *                但愿老死电脑间,不愿鞠躬老板前;  
 *                奔驰宝马贵者趣,公交自行程序员。  
 *                别人笑我忒疯癫,我笑自己命太贱;  
 *                不见满街漂亮妹,哪个归得程序员?
 */

# 美女

/*
 *                        .::::.
 *                      .::::::::.
 *                     :::::::::::
 *                  ..:::::::::::'
 *               '::::::::::::'
 *                 .::::::::::
 *            '::::::::::::::..
 *                 ..::::::::::::.
 *               ``::::::::::::::::
 *                ::::``:::::::::'        .:::.
 *               ::::'   ':::::'       .::::::::.
 *             .::::'      ::::     .:::::::'::::.
 *            .:::'       :::::  .:::::::::' ':::::.
 *           .::'        :::::.:::::::::'      ':::::.
 *          .::'         ::::::::::::::'         ``::::.
 *      ...:::           ::::::::::::'              ``::.
 *     ````':.          ':::::::::'                  ::::..
 *                        '.:::::'                    ':'````..
 */

# 龙图腾

/*
 * ......................................&&.........................
 * ....................................&&&..........................
 * .................................&&&&............................
 * ...............................&&&&..............................
 * .............................&&&&&&..............................
 * ...........................&&&&&&....&&&..&&&&&&&&&&&&&&&........
 * ..................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..............
 * ................&...&&&&&&&&&&&&&&&&&&&&&&&&&&&&.................
 * .......................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&.........
 * ...................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...............
 * ..................&&&   &&&&&&&&&&&&&&&&&&&&&&&&&&&&&............
 * ...............&&&&&@  &&&&&&&&&&..&&&&&&&&&&&&&&&&&&&...........
 * ..............&&&&&&&&&&&&&&&.&&....&&&&&&&&&&&&&..&&&&&.........
 * ..........&&&&&&&&&&&&&&&&&&...&.....&&&&&&&&&&&&&...&&&&........
 * ........&&&&&&&&&&&&&&&&&&&.........&&&&&&&&&&&&&&&....&&&.......
 * .......&&&&&&&&.....................&&&&&&&&&&&&&&&&.....&&......
 * ........&&&&&.....................&&&&&&&&&&&&&&&&&&.............
 * ..........&...................&&&&&&&&&&&&&&&&&&&&&&&............
 * ................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&............
 * ..................&&&&&&&&&&&&&&&&&&&&&&&&&&&&..&&&&&............
 * ..............&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&....&&&&&............
 * ...........&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&......&&&&............
 * .........&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&.........&&&&............
 * .......&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...........&&&&............
 * ......&&&&&&&&&&&&&&&&&&&...&&&&&&...............&&&.............
 * .....&&&&&&&&&&&&&&&&............................&&..............
 * ....&&&&&&&&&&&&&&&.................&&...........................
 * ...&&&&&&&&&&&&&&&.....................&&&&......................
 * ...&&&&&&&&&&.&&&........................&&&&&...................
 * ..&&&&&&&&&&&..&&..........................&&&&&&&...............
 * ..&&&&&&&&&&&&...&............&&&.....&&&&...&&&&&&&.............
 * ..&&&&&&&&&&&&&.................&&&.....&&&&&&&&&&&&&&...........
 * ..&&&&&&&&&&&&&&&&..............&&&&&&&&&&&&&&&&&&&&&&&&.........
 * ..&&.&&&&&&&&&&&&&&&&&.........&&&&&&&&&&&&&&&&&&&&&&&&&&&.......
 * ...&&..&&&&&&&&&&&&.........&&&&&&&&&&&&&&&&...&&&&&&&&&&&&......
 * ....&..&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...........&&&&&&&&.....
 * .......&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..............&&&&&&&....
 * .......&&&&&.&&&&&&&&&&&&&&&&&&..&&&&&&&&...&..........&&&&&&....
 * ........&&&.....&&&&&&&&&&&&&.....&&&&&&&&&&...........&..&&&&...
 * .......&&&........&&&.&&&&&&&&&.....&&&&&.................&&&&...
 * .......&&&...............&&&&&&&.......&&&&&&&&............&&&...
 * ........&&...................&&&&&&.........................&&&..
 * .........&.....................&&&&........................&&....
 * ...............................&&&.......................&&......
 * ................................&&......................&&.......
 * .................................&&..............................
 * ..................................&..............................
 */

# 程序员之歌

/*
 *                   江城子 . 程序员之歌
 * 
 *               十年生死两茫茫,写程序,到天亮。
 *                   千行代码,Bug何处藏。
 *               纵使上线又怎样,朝令改,夕断肠。
 * 
 *               领导每天新想法,天天改,日日忙。
 *                   相顾无言,惟有泪千行。
 *               每晚灯火阑珊处,夜难寐,加班狂。
 * 
 */

# 耶稣

/*
 *                                |~~~~~~~|
 *                                |       |
 *                                |       |
 *                                |       |
 *                                |       |
 *                                |       |
 *     |~.\\\_\~~~~~~~~~~~~~~xx~~~         ~~~~~~~~~~~~~~~~~~~~~/_//;~|
 *     |  \  o \_         ,XXXXX),                         _..-~ o /  |
 *     |    ~~\  ~-.     XXXXX`)))),                 _.--~~   .-~~~   |
 *      ~~~~~~~`\   ~\~~~XXX' _/ ';))     |~~~~~~..-~     _.-~ ~~~~~~~
 *               `\   ~~--`_\~\, ;;;\)__.---.~~~      _.-~
 *                 ~-.       `:;;/;; \          _..-~~
 *                    ~-._      `''        /-~-~
 *                        `\              /  /
 *                          |         ,   | |
 *                           |  '        /  |
 *                            \/;          |
 *                             ;;          |
 *                             `;   .       |
 *                             |~~~-----.....|
 *                            | \             \
 *                           | /\~~--...__    |
 *                           (|  `\       __-\|
 *                           ||    \_   /~    |
 *                           |)     \~-'      |
 *                            |      | \      '
 *                            |      |  \    :
 *                             \     |  |    |
 *                              |    )  (    )
 *                               \  /;  /\  |
 *                               |    |/   |
 *                               |    |   |
 *                                \  .'  ||
 *                                |  |  | |
 *                                (  | |  |
 *                                |   \ \ |
 *                                || o `.)|
 *                                |`\\) |
 *                                |       |
 *                                |       |
 */

#

/*
 *                        ::
 *                       :;J7, :,                        ::;7:
 *                       ,ivYi, ,                       ;LLLFS:
 *                       :iv7Yi                       :7ri;j5PL
 *                      ,:ivYLvr                    ,ivrrirrY2X,
 *                      :;[email protected]:                :ivu@kexianli.
 *                     :iL7::,:::iiirii:ii;::::,,irvF7rvvLujL7ur
 *                    ri::,:,::i:iiiiiii:i:irrv177JX7rYXqZEkvv17
 *                 ;i:, , ::::iirrririi:i:::iiir2XXvii;L8OGJr71i
 *               :,, ,,:   ,::[email protected]:i:::j1jri7ZBOS7ivv,
 *                  ,::,    ::rv77iiiriii:iii:i::,[email protected]
 *              ,,      ,, ,:ir7ir::,:::i;ir:::i:i::rSGGYri712:
 *            :::  ,v7r:: ::rrv77:, ,, ,:i7rrii:::::, ir7ri7Lri
 *           ,     2OBBOi,iiir;r::        ,irriiii::,, ,iv7Luur:
 *         ,,     i78MBBi,:,:::,:,  :7FSL: ,iriii:::i::,,:rLqXv::
 *         :      iuMMP: :,:::,:ii;2GY7OBB0viiii:i:iii:i:::iJqL;::
 *        ,     ::::i   ,,,,, ::LuBBu BBBBBErii:i:i:i:i:i:i:r77ii
 *       ,       :       , ,,:::rruBZ1MBBqi, :,,,:::,::::::iiriri:
 *      ,               ,,,,::::i:  @arqiao.       ,:,, ,:::ii;i7:
 *     :,       rjujLYLi   ,,:::::,:::::::::,,   ,:i,:,,,,,::i:iii
 *     ::      BBBBBBBBB0,    ,,::: , ,:::::: ,      ,,,, ,,:::::::
 *     i,  ,  ,8BMMBBBBBBi     ,,:,,     ,,, , ,   , , , :,::ii::i::
 *     :      iZMOMOMBBM2::::::::::,,,,     ,,,,,,:,,,::::i:irr:i:::,
 *     i   ,,:;u0MBMOG1L:::i::::::  ,,,::,   ,,, ::::::i:i:iirii:i:i:
 *     :    ,iuUuuXUkFu7i:iii:i:::, :,:,: ::::::::i:i:::::iirr7iiri::
 *     :     :[email protected]:::::, ,:ii:::::::i:::::i::,::::iirrriiiri::,
 *      :      5BMBBBBBBSr:,::rv2kuii:::iii::,:i:,, , ,,:,:i@petermu.,
 *           , :r50EZ8MBBBBGOBBBZP7::::i::,:::::,: :,:,::i;rrririiii::
 *               :jujYY7LS0ujJL7r::,::i::,::::::::::::::iirirrrrrrr:ii:
 *            ,:  :@kevensun.:,:,,,::::i:i:::::,,::::::iir;ii;7v77;ii;i,
 *            ,,,     ,,:,::::::i:iiiii:i::::,, ::::[email protected];7:i,
 *         , , ,,,:,,::::::::iiiiiiiiii:,:,:::::::::iiir;ri7vL77rrirri::
 *          :,, , ::::::::i:::i:::i:i::,,,,,:,::i:i:::iir;@Secbone.ii:::
 */

# 喷火龙

/*
 *                                                     __----~~~~~~~~~~~------___
 *                                    .  .   ~~//====......          __--~ ~~
 *                    -.            \_|//     |||\\  ~~~~~~::::... /~
 *                 ___-==_       _-~o~  \/    |||  \\            _/~~-
 *         __---~~~.==~||\=_    -_--~/_-~|-   |\\   \\        _/~
 *     _-~~     .=~    |  \\-_    '-~7  /-   /  ||    \      /
 *   .~       .~       |   \\ -_    /  /-   /   ||      \   /
 *  /  ____  /         |     \\ ~-_/  /|- _/   .||       \ /
 *  |~~    ~~|--~~~~--_ \     ~==-/   | \~--===~~        .\
 *           '         ~-|      /|    |-~\~~       __--~~
 *                       |-~~-_/ |    |   ~\_   _-~            /\
 *                            /  \     \__   \/~                \__
 *                        _--~ _/ | .-~~____--~-/                  ~~==.
 *                       ((->/~   '.|||' -_|    ~~-/ ,              . _||
 *                                  -_     ~\      ~~---l__i__i__i--~~_/
 *                                  _-~-__   ~)  \--______________--~~
 *                                //.-~~~-~_--~- |-------~~~~~~~~
 *                                       //.-~~~--\
 *                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * 
 *                               神兽保佑            永无BUG
 */

# 蝙蝠

/*
 *                   ___====-_  _-====___
 *             _--^^^#####//      \\#####^^^--_
 *          _-^##########// (    ) \\##########^-_
 *         -############//  |\^^/|  \\############-
 *       _/############//   (@::@)   \############\_
 *      /#############((     \\//     ))#############\
 *     -###############\\    (oo)    //###############-
 *    -#################\\  / VV \  //#################-
 *   -###################\\/      \//###################-
 *  _#/|##########/\######(   /\   )######/\##########|\#_
 *  |/ |#/\#/\#/\/  \#/\##\  |  |  /##/\#/  \/\#/\#/\#| \|
 *  `  |/  V  V  `   V  \#\| |  | |/#/  V   '  V  V  \|  '
 *     `   `  `      `   / | |  | | \   '      '  '   '
 *                      (  | |  | |  )
 *                     __\ | |  | | /__
 *                    (vvv(VVV)(VVV)vvv)
 * 
 *      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * 
 *                神兽保佑            永无BUG
 */

# Auto Commit 一键补充commit记录

本人还开源了另外一个VSCode插件:Auto Commit

这是一个用于Git自动commit的VSCode插件,它可以用来补充之前忘记提交commit,帮助你把首页的绿色格子填满,感兴趣的可以试用一下~

autoCommit演示

# 最后

如果觉得还不错的话,就给个 Star ⭐️ 鼓励一下我吧~

前端进阶积累公众号GitHub、wx:OBkoro1、邮箱:[email protected]

# 点个Star支持我一下~

博客链接

论普通函数和箭头函数的区别以及箭头函数的注意事项和不适用场景 | ES6

博客链接

# 论普通函数和箭头函数的区别以及箭头函数的注意事项和不适用场景

箭头函数是ES6的API,相信很多人都知道,因为其语法上相对于普通函数更简洁,深受大家的喜爱。就是这种我们日常开发中一直在使用的API,大部分同学却对它的了解程度还是不够深...

# 普通函数和箭头函数的区别:

# 箭头函数的this指向规则:

# 1. 箭头函数没有prototype(原型),所以箭头函数本身没有this

let a = () =>{};
console.log(a.prototype); // undefined

# 2. 箭头函数的this指向在定义的时候继承自外层第一个普通函数的this。

下面栗子中在一个函数中定义箭头函数,然后在另一个函数中执行箭头函数。

let a,
  barObj = { msg: 'bar的this指向' };
fooObj = { msg: 'foo的this指向' };
bar.call(barObj); // 将bar的this指向barObj
foo.call(fooObj); // 将foo的this指向fooObj
function foo() {
  a(); // 结果:{ msg: 'bar的this指向' }
}
function bar() {
  a = () => {
    console.log(this, 'this指向定义的时候外层第一个普通函数'); // 
  }; // 在bar中定义 this继承于bar函数的this指向
}

从上面栗子中可以得出两点

  1. 箭头函数的this指向定义时所在的外层第一个普通函数,跟使用位置没有关系
  2. 被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变

# 3. 不能直接修改箭头函数的this指向

上个栗子中的foo函数修改一下,尝试直接修改箭头函数的this指向。

let fnObj = { msg: '尝试直接修改箭头函数的this指向' };
function foo() {
  a.call(fnObj); // 结果:{ msg: 'bar的this指向' }
}

很明显,call显示绑定this指向失败了,包括aaply、bind都一样。

它们(call、aaply、bind)会默认忽略第一个参数,但是可以正常传参。

然后我又通过隐式绑定来尝试同样也失败了,new 调用会报错,这个稍后再说。

SO,箭头函数不能直接修改它的this指向

幸运的是,我们可以通过间接的形式来修改箭头函数的指向:

去修改被继承的普通函数的this指向,然后箭头函数的this指向也会跟着改变,这在上一个栗子中有演示。

bar.call(barObj); // 将bar普通函数的this指向barObj 然后内部的箭头函数也会指向barObj

# 4. 箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象)

唔,这个问题实际上是面试官提出来的,当时我认为的箭头函数规则就是:箭头函数的this指向继承自外层第一个普通函数的this,现在看来真是不严谨(少说一个定义的时候),要是面试官问我:定义和执行不在同一个普通函数中,它又指向哪里,肯定歇菜...

既然箭头函数的this指向在定义的时候继承自外层第一个普通函数的this,那么:

当箭头函数外层没有普通函数,它的this会指向哪里

这里跟我之前写的this绑定规则不太一样(不懂的可以点进去看一下),普通函数的默认绑定规则是:

在非严格模式下,默认绑定的this指向全局对象,严格模式下this指向undefined

如果箭头函数外层没有普通函数继承,它this指向的规则

经过测试,箭头函数在全局作用域下,严格模式和非严格模式下它的this都会指向window(全局对象)

Tip:测试的时候发现严格模式在中途声明无效,必须在全局/函数的开头声明才会生效

a = 1;
'use strict'; // 严格模式无效 必须在一开始就声明严格模式
b = 2; // 不报错

# 箭头函数的

# 箭头函数的arguments

# 箭头函数的this指向全局,使用arguments会报未声明的错误

如果箭头函数的this指向window(全局对象)使用arguments会报错,未声明arguments

let b = () => {
  console.log(arguments);
};
b(1, 2, 3, 4); // Uncaught ReferenceError: arguments is not defined

PS:如果你声明了一个全局变量为arguments,那就不会报错了,但是你为什么要这么做呢?

# 箭头函数的this指向普通函数时,它的argumens继承于该普通函数

上面是第一种情况:箭头函数的this指向全局对象,会报arguments未声明的错误。

第二种情况是:箭头函数的this如果指向普通函数,它的argumens继承于该普通函数。

function bar() {
  console.log(arguments); // ['外层第二个普通函数的参数']
  bb('外层第一个普通函数的参数');
  function bb() {
    console.log(arguments); // ["外层第一个普通函数的参数"]
    let a = () => {
      console.log(arguments, 'arguments继承this指向的那个普通函数'); // ["外层第一个普通函数的参数"]
    };
    a('箭头函数的参数'); // this指向bb
  }
}
bar('外层第二个普通函数的参数');

那么应该如何来获取箭头函数不定数量的参数呢?答案是:ES6的rest参数(...扩展符)

# rest参数获取函数的多余参数

这是ES6的API,用于获取函数不定数量的参数数组,这个API是用来替代arguments的,API用法如下:

let a = (first, ...abc) => {
  console.log(first, abc); // 1 [2, 3, 4]
};
a(1, 2, 3, 4);

上面的栗子展示了,获取函数除第一个确定的参数,以及用一个变量接收其他剩余参数的示例。

也可以直接接收函数的所有参数,rest参数的用法相对于arguments的优点:

  1. 箭头函数和普通函数都可以使用。

  2. 更加灵活,接收参数的数量完全自定义。

  3. 可读性更好

    参数都是在函数括号中定义的,不会突然出现一个arguments,以前刚见到的时候,真的好奇怪了!

  4. rest是一个真正的数组,可以使用数组的API。

    因为arguments是一个类数组的对象,有些人以为它是真正的数组,所以会出现以下场景:

    arguments.push(0); // arguments.push is not a function
    

    如上,如果我们需要使用数组的API,需要使用扩展符/Array.from来将它转换成真正的数组:

    arguments = [...arguments]; 或者 :arguments = Array.from(arguments);
    

rest参数有两点需要注意

  1. rest必须是函数的最后一位参数:

    let a = (first, ...rest, three) => {
      console.log(first, rest,three); // 报错:Rest parameter must be last formal parameter
    };
    a(1, 2, 3, 4);
    
  2. 函数的length属性,不包括 rest 参数

    (function(...a) {}).length  // 0
    (function(a, ...b) {}).length  // 1
    

扩展运算符还可以用于数组,这里是阮一峰老师的文档

PS:感觉这里写多了,但比较喜欢把一个知识点讲清楚...

# 使用new调用箭头函数会报错

无论箭头函数的thsi指向哪里,使用new调用箭头函数都会报错,因为箭头函数没有constructor

let a = () => {};
let b = new  a(); // a is not a constructor

# 箭头函数不支持new.target

new.target是ES6新引入的属性,普通函数如果通过new调用,new.target会返回该函数的引用。

此属性主要:用于确定构造函数是否为new调用的。

  1. 箭头函数的this指向全局对象,在箭头函数中使用箭头函数会报错

    let a = () => {
      console.log(new.target); // 报错:new.target 不允许在这里使用
    };
    a();
    
  2. 箭头函数的this指向普通函数,它的new.target就是指向该普通函数的引用。

    new bb();
    function bb() {
      let a = () => {
        console.log(new.target); // 指向函数bb:function bb(){...}
      };
      a();
    }
    

更多关于new.target可以看一下阮一峰老师关于这部分的解释

# 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名

如下示例,普通函数的函数参数支持重命名,后面出现的会覆盖前面的,箭头函数会抛出错误:

function func1(a, a) {
  console.log(a, arguments); // 2 [1,2]
}

var func2 = (a,a) => {
console.log(a); // 报错:在此上下文中不允许重复参数名称
};
func1(1, 2); func2(1, 2);

# 箭头函数相对于普通函数语法更简洁优雅:

讲道理,语法上的不同,也属与它们两个的区别!

  1. 箭头函数都是匿名函数,并且都不用写function

  2. 只有一个参数的时候可以省略括号:

    var f = a => a; // 传入a 返回a
    
  3. 函数只有一条语句时可以省略{}return

    var f = (a,b,c) => a; // 传入a,b,c 返回a
    
  4. 简化回调函数,让你的回调函数更优雅:

[1,2,3].map(function (x) {
  return x * x;
}); // 普通函数写法 
[1,2,3].map(x => x * x); // 箭头函数只需要一行

# 箭头函数的注意事项及不适用场景

# 箭头函数的注意事项

  1. 一条语句返回对象字面量,需要加括号,或者直接写成多条语句的return形式,

    否则像func中演示的一样,花括号会被解析为多条语句的花括号,不能正确解析

var func1 = () => { foo: 1 }; // 想返回一个对象,花括号被当成多条语句来解析,执行后返回undefined
var func2 = () => ({foo: 1}); // 用圆括号是正确的写法
var func2 = () => {
  return {
    foo: 1 // 更推荐直接当成多条语句的形式来写,可读性高
  };
};
  1. 箭头函数在参数和箭头之间不能换行!
var func = ()
           => 1;  // 报错: Unexpected token =>
  1. 箭头函数的解析顺序相对靠前

MDN: 虽然箭头函数中的箭头不是运算符,但箭头函数具有与常规函数不同的特殊运算符优先级解析规则

let a = false || function() {}; // ok
let b = false || () => {}; // Malformed arrow function parameter list
let c = false || (() => {}); // ok

# 箭头函数不适用场景:

围绕两点:箭头函数的this意外指向和代码的可读性。

  1. 定义字面量方法,this的意外指向。

因为箭头函数的简洁

const obj = {
  array: [1, 2, 3],
  sum: () => {
    // 根据上文学到的:外层没有普通函数this会指向全局对象
    return this.array.push('全局对象下没有array,这里会报错'); // 找不到push方法
  }
};
obj.sum();

上述栗子使用普通函数或者ES6中的方法简写的来定义方法,就没有问题了:

// 这两种写法是等价的
sum() {
  return this.array.push('this指向obj');
}
sum: function() {
  return this.array.push('this指向obj');
}

还有一种情况是给普通函数的原型定义方法的时候,通常会在普通函数的外部进行定义,比如说继承/添加方法的时候。

这时候因为没有在普通函数的内部进行定义,所以this会指向其他普通函数,或者全局对象上,导致bug!

  1. 回调函数的动态this

下文是一个修改dom文本的操作,因为this指向错误,导致修改失败:

const button = document.getElementById('myButton');
button.addEventListener('click', () => {
    this.innerHTML = 'Clicked button'; // this又指向了全局
});

相信你也知道了,改成普通函数就成了。

  1. 考虑代码的可读性,使用普通函数

    • 函数体复杂:

      具体表现就是箭头函数中使用多个三元运算符号,就是不换行,非要在一行内写完,非常恶心!

    • 行数较多

    • 函数内部有大量操作

# 文章内容小结:

# 普通函数和箭头函数的区别:

  1. 箭头函数没有prototype(原型),所以箭头函数本身没有this
  2. 箭头函数的this在定义的时候继承自外层第一个普通函数的this。
  3. 如果箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象)
  4. 箭头函数本身的this指向不能改变,但可以修改它要继承的对象的this。
  5. 箭头函数的this指向全局,使用arguments会报未声明的错误。
  6. 箭头函数的this指向普通函数时,它的argumens继承于该普通函数
  7. 使用new调用箭头函数会报错,因为箭头函数没有constructor
  8. 箭头函数不支持new.target
  9. 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
  10. 箭头函数相对于普通函数语法更简洁优雅

# 箭头函数的注意事项及不适用场景

箭头函数的注意事项

  1. 箭头函数一条语句返回对象字面量,需要加括号
  2. 箭头函数在参数和箭头之间不能换行
  3. 箭头函数的解析顺序相对||靠前

不适用场景:箭头函数的this意外指向和代码的可读性。


# 结语

呕心沥血,可以说是很全了,反正第一次问到我的时候只能想到箭头函数的this是继承而来的,以及语法上的简洁性,其他的我都不知道,希望这篇文章能够帮助各位同学学到知识。

PS:目前找工作中,求大佬们内推,中高级前端,偏JS,Vue,上海杨浦。

博客前端积累文档公众号GitHub、wx:OBkoro1、邮箱:[email protected]

以上2019.03.22

参考资料:

MDN 箭头函数

阮一峰-ES6入门

什么时候你不能使用箭头函数?

# 点个Star支持我一下~

博客链接

博客外链失效一键替换 | tool

博客链接

# 博客外链失效一键替换

大约一个月前,微博的图片外链失效了,以及掘金因为盗链问题也于2019/06/06决定开启防盗链,造成的影响是:个人博客网站的引用了这些图片外链都不能显示

目前微博和掘金的屏蔽,在CSDN和segmentfault都是可以正常显示的,只影响个人博客

比如前段时间我的博客:http://obkoro1.com上引用的微博图片都不能显示了。

因为我写博客比较频繁,被屏蔽的图片不在少数,肯定不能一个个手动的替换,查了一番没有找到现成的解决方案,做了个脚本工具,并且写了文档把它开源出来了。

# markdown-img-down-site-change(下载/替换markdown中的图片)

搜索目标文件夹中的markdown文件,找到目标图片,提供下载图片,替换图片链接的功能-通常用于markdown 图片失效。

# 简介

这是一个极为轻量的脚本,引用包,设置好参数,通过API即可轻松上手。

# 解决什么问题?

  1. 集中下载markdown文件中某个域名下的图片到一个文件夹下。
  2. 用新的图片链接替换markdown文件中某个域名的图片链接。
// 1. 下载这两个图片
// ![](https://user-gold-cdn.xitu.io/2019/5/20/图片名字?w=2024&h=1240&f=png&s=339262)
// ![](https://user-gold-cdn.xitu.io/2018/6/16/图片名字)
// 2. 替换成:github的链接
![](https://raw.githubusercontent.com/OBKoro1/articleImg_src/master/juejin/图片名字?w=2024&h=1240&f=png&s=339262)
![](https://raw.githubusercontent.com/OBKoro1/articleImg_src/master/juejin/图片名字)

# 安装:

npm i markdown-img-down-site-change -S

# 文档:

Github

API

更新日志

# 数据安全:

刚上手可能不了解脚本的功能,需要调试一番,这时候万一把markdown文件给改坏了,岂不是要哭死?

脚本有两种形式来防止这种情况发生:

  1. 脚本会默认备份你的文件。
  2. 默认开启测试模式,等到调试的差不多了,可以关闭测试模式。
  3. 建议:再不放心的话,可以先用一两个文件来测试一下脚本

# 使用:20行代码不到

在项目中有一个使用栗子,里面加了蛮多注释和空行的,实际代码20行都不到,可以说很简单了,如下:

// npm i markdown-img-down-site-change -S 
const markdownImageDown = require('markdown-img-down-site-change'); // 文件模块

// 传参: 这也是脚本的默认参数,根据情况可以自行修改
let option = {
replace_image_url: 'https://user-gold-cdn.xitu.io/',
read_markdown_src: './source', // 要查找markdown文件的文件夹地址
down_img_src: './juejin', // 下载图片到这个文件夹
var_number: 3 // url前半部分的变量数量 比如上面的日期: /2019/5/20/、/2018/6/16/
}

// 初始化
const markdownImage = new markdownImageDown(option)

// 下载外链
markdownImage.checkDownImg();

// 上传下载下来的图片文件夹到云端 用户自己操作

// 上传图片之后
// 脚本会把以前的外链替换成云端地址+拼接一个图片名
markdownImage.updateOption({
new_image_url: 'https://xxx.com/目录地址/', // 图片上传的地址
add_end: '?raw=true' // github图片地址有后缀 直接进去是仓库
})

// 替换外链
// 把replace_image_url的字符串换成new_image_url字符串
markdownImage.replaceMarkdown();

# 运行:

仔细阅读文本,配置好参数之后

在项目根节点新建一个handleImg.js文件,安装一下脚本,然后用node运行该文件:

npm i markdown-img-down-site-change -S
node handleImg.js

# 功能/参数简介:

  • checkDownImg(): 下载查找到的图片
  • replaceMarkdown(): 替换图片链接为新的图片链接
  • replace_image_url:要替换的图片地址
  • new_image_url:图片的新地址
  • test: 测试模式。
  • var_number: 匹配图片链接的图片名之前的url,值为变量数量
  • is_link: 匹配链接。
  • write_file_time: 间隔多久修改markdown图片链接
  • read_markdown_src:要查找markdown文件的文件夹地址
  • down_img_src:下载图片到这个地址下
  • copy_item_data: 备份项目
  • filter_item: 过滤某些文件夹,不查找markdown。
  • add_end:在图片链接后面添加后缀添加后缀

# 欢迎试用

有需要的小伙伴,赶紧来试试吧!文档写的很全,上手非常轻松,项目将会持续维护,有什么问题,欢迎给我提issue~

如果觉得这个脚本还不错的话,就给项目点个Star吧!

以上2019.06.04

# 点个Star支持我一下~

博客链接

前端er怎样操作复制粘贴 | effect

博客链接

# 前端er怎样操作复制粘贴

# API 介绍:

# 复制、剪切、粘贴事件:

  1. copy 发生复制操作时触发;
  2. cut 发生剪切操作时触发;
  3. paste 发生粘贴操作时触发;
  4. 每个事件都有一个 before 事件对应:beforecopybeforecutbeforepaste;

这几个 before 一般不怎么用,所以我们把注意力放在另外三个事件就可以了。

触发条件:

  1. 鼠标右键菜单的复制粘贴剪切;

  2. 使用了相应的键盘组合键,比如:command+ccommand+v;

    就算你是随便按的,也会触发事件。高程中介绍在ChormeFirefoxSafari中,这几个 before 事件只会在真实会发生剪贴板事件的情况下触发,IE 上则可以触发 before。我实际测试的时候最新版chorme也会乱按也会触发,所以限制应该是在早一点的版本上。

    so 想说的是:before 这几个事件最好不要用,有关于剪切板的处理最好放在copycutpaste上面。

使用姿势:

以 copy 为例:

document.body.oncopy = e => {
  // 监听全局复制 做点什么
};
// 还有这种写法:
document.addEventListener('copy', e => {
  // 监听全局复制 做点什么
});

上面是在document.body上全局监听的,然而很多人不知道的是,我们还可以为某些 dom 单独添加剪切板事件:

// html结构
<div id="test1"></div>
<div id="test2"></div>
// 写法一样:
let test1 = document.querySelector('#test1');
test1.oncopy = e => {
    // 监听test1发生的复制事件 做点什么
    // test1发生的复制事件会触发回调,其他地方不会触发回调
}

其他事件也是一样的,这里就不赘述了。

# clipboardData 对象:用于访问以及修改剪贴板中的数据

兼容:

不同浏览器,所属的对象不同:在 IE 中这个对象是window对象的属性,在ChromeSafariFirefox中,这个对象是相应的event对象的属性。所以我们在使用的时候,需要做一下如下兼容:

document.body.oncopy = e => {
  let clipboardData = e.clipboardData || window.clipboardData;
  // 获取clipboardData对象 + do something
};

对象方法:

对象有三个方法: getData()setData()clearData()

  • getData() 访问剪切板中的数据

    参数: getData()接受一个'text'参数,即要取得的数据的格式。

    在复制、剪切、粘贴触发的事件的数据:

    实际上在 chorme 上测试只有paste粘贴的时候才能用getData()访问到数据,用法如下:

    要粘贴的数据:

document.body.onpaste = e => {
  let clipboardData = e.clipboardData || window.clipboardData; // 兼容处理
  console.log('要粘贴的数据', clipboardData.getData('text'));
};

被复制/剪切的数据:

在复制和剪切中的数据,需要通过window.getSelection(0).toString()来访问:

document.body.oncopy = e => {
  console.log('被复制的数据:', window.getSelection(0).toString());
};
  • setData(): 修改剪切板中的数据

    参数:第一个参数也是'text',第二个参数是要放在剪切板中的文本。

    剩下的留在下面仿知乎/掘金复制大段文本添加版权信息那里再说。

  • clearData() :

    这个方法就不太知道了,试了好久不知道怎么用(如果有大佬知道,可以在评论区指点一下)。

    如果只是为了禁止复制,或者禁止粘贴,在下面还有另外的方法可以做到,并且可以细粒化操作。


# 应用:

如果学习不是为了装 X,那么一切将毫无意义,来看这个东西可以在哪些场景使用:

# 实现类知乎/掘金复制大段文本添加版权信息:

实现很简单:取消默认复制之后,主要是在被复制的内容后面添加信息,然后根据 clipboardData 的 setData()方法将信息写入剪贴板。

可以直接复制这段代码到本地去试试。

// 掘金这里不是全局监听,应该只是监听文章的dom范围内。
document.body.oncopy = event => {
  event.preventDefault(); // 取消默认的复制事件
  let textFont,
    copyFont = window.getSelection(0).toString(); // 被复制的文字 等下插入
  // 防知乎掘金 复制一两个字则不添加版权信息 超过一定长度的文字 就添加版权信息
  if (copyFont.length > 10) {
    textFont =
      copyFont +
      '\n' +
      '作者:OBKoro1\n' +
      '链接:https://juejin.im/user/58714f0eb123db4a2eb95372/posts\n' +
      '来源:掘金\n' +
      '著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。';
  } else {
    textFont = copyFont; // 没超过十个字 则采用被复制的内容。
  }
  if (event.clipboardData) {
    return event.clipboardData.setData('text', textFont); // 将信息写入粘贴板
  } else {
    // 兼容IE
    return window.clipboardData.setData('text', textFont);
  }
};

然后 command+c、command+v,输出:

你复制的内容
作者:OBKoro1
链接:https://juejin.im/user/58714f0eb123db4a2eb95372/posts
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

# 实现类起点网的防复制功能:

  • 禁止复制+剪切
  • 禁止右键,右键某些选项:全选,复制,粘贴等。
  • 禁用文字选择,能选择却不能复制,体验很差。
  • user-select 用 css 禁止选择文本。

可以把代码拷到本地玩一玩:

// 禁止右键菜单
document.body.oncontextmenu = e => {
    console.log(e, '右键');
    return false;
    // e.preventDefault();
};
// 禁止文字选择。
document.body.onselectstart = e => {
    console.log(e, '文字选择');
    return false;
    // e.preventDefault();
};
// 禁止复制
document.body.oncopy = e => {
    console.log(e, 'copy');
    return false;
    // e.preventDefault();
}
// 禁止剪切
document.body.oncut = e => {
    console.log(e, 'cut');
    return false;
    // e.preventDefault();
};
// 禁止粘贴
document.body.onpaste = e => {
    console.log(e, 'paste');
    return false;
    // e.preventDefault();
};
// css 禁止文本选择 这样不会触发js
body {
    user-select: none;
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
}

PS:

  • 使用e.preventDefault()也可以禁用,但建议使用return false这样就不用去访问ee的方法了。
  • 示例中document.body全局都禁用了,也可以对 dom(某些区域)进行禁用。

破解防复制

上面的防复制方法通过js+css实现的,所以思路就是:禁用js+取消user-select样式。

Chrome浏览器的话:打开浏览器控制台,按F1进入Setting,勾选Disable JavaScript(禁止 js)。

此时如果还不能复制的话,就要去找user-select样式,取消这个样式就可以了。

所以那些盗版小说手打的,我真的不太能理解,Excuse me???

# 点击复制功能:

不能使用 clipboardData:

在 IE 中可以用window.clipboardData.setData('text','内容')实现。

上文提到过,在 IE 中clipboardDatawindow的属性。

而其他浏览器则是相应的event对象的属性,这实际上是一种安全措施,防止未经授权的访问,为了兼容其他浏览器,所以我们不能通过clipboardData来实现这种操作。

具体做法:

  • 创建一个隐藏的input

  • 点击的时候,将要复制的内容放进input框中

  • 选择文本内容input.select()

    这里只能用input或者textarea才能选择文本。

  • document.execCommand("copy"),执行浏览器的复制命令。

function copyText() {
  var text = document.getElementById('text').innerText; // 获取要复制的内容也可以传进来
  var input = document.getElementById('input'); // 获取隐藏input的dom
  input.value = text; // 修改文本框的内容
  input.select(); // 选中文本
  document.execCommand('copy'); // 执行浏览器复制命令
  alert('复制成功');
}

点击复制内容的 demo 在这里,可以点进去看看。

# 点个Star支持我一下~

博客链接

var和let/const的区别 | ES6

博客链接

# var和let/const的区别

letconst是 ES6 新增的命令,用于声明变量,这两个命令跟 ES5 的var有许多不同,并且letconst也有一些细微的不同,再认真阅读了�阮一峰老师的文档后,发现还是有一些不知道的细节...

博客前端积累文档公众号GitHub

# 内容:

varlet/const的区别

  1. 块级作用域

  2. 不存在变量提升

  3. 暂时性死区

  4. 不可重复声明

  5. let、const声明的�全局变量不会挂在顶层对象下面

const命令两个注意点:

  1. const 声明之后必须马上赋值,否则会报错

  2. const 简单类型一旦声明就不能再更改,�复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改。

# 为什么需要块级作用域?

ES5只有全局作用域和函数作用域,没有块级作用域。

这带来很多不合理的场景:

  1. 内层变量可能覆盖外层变量
  2. 用来计数的循环变量泄露为全局变量
var tmp = new Date();
function f() {
  console.log(tmp); // 想打印外层的时间作用域
  if (false) {
    var tmp = 'hello world'; // 这里声明的作用域为整个函数
  }
}
f(); // undefined

var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]); // i应该为此次for循环使用的变量
}
console.log(i); // 5 全局范围都可以读到

# 块级作用域

  1. 作用域
function f1() {
  let n = 5;
  if (true) {
    let n = 10;
    console.log(n); // 10 内层的n
  }
  console.log(n); // 5 当前层的n
}
  1. 块级作用域任意嵌套
{{{{
  {let insane = 'Hello World'}
  console.log(insane); // 报错 读不到子作用域的变量
}}}};
  1. 块级作用域真正使代码分割成块了
{
let a = ...;
...
}
{
let a = ...;
...
}

以上形式,可以用于测试一些想法,不用担心变量重名,也不用担心外界干扰

# 块级作用域声明函数:

在块级作用域声明函数,因为浏览器的要�兼容老代码,会产生一些问题

在块级作用域声明函数,最好使用匿名函数的形式

if(true){
  let a = function () {}; // 作用域为块级 令声明的函数作用域范围更清晰
}

ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错

// 报错
'use strict';
if (true)
  function f() {} // 我们需要给if加个{}

# 不存在变量提升

变量提升的现象:在同一作用域下,变量可以在声明之前使用,值为 undefined

ES5 时使用var声明变量,经常会出现变量提升的现象。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

# 暂时性死区:

只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量

var tmp = 123; // 声明
if (true) {
  tmp = 'abc'; // 报错 因为本区域有tmp声明变量
  let tmp; // 绑定if这个块级的作用域 不能出现tmp变量
}

暂时性死区和不能变量提升的意义在于:

为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。

# 不允许重复声明变量

在测试时出现这种情况:var a= '声明';const a = '不报错',这种情况是因为babel在转化的时候,做了一些处理,在浏览器的控制台中测试,就成功报错

letconst不允许在相同作用域内,重复声明同一个变量

function func(arg) {
  let arg; // 报错
}

function func(arg) {
{
let arg; // 不报错
}
}

# let、const声明的�全局变量不会挂在顶层对象下面

  1. 浏览器环境顶层对象是: window
  2. node环境顶层对象是: global
  3. var声明的全局变量会挂在顶层对象下面,而let、const不会挂在顶层对象下面。如下面这个栗子
var a = 1;
// 如果在 Node环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

# const命令

  1. 一旦声明,必须马上赋值

    let p; var p1; // 不报错
    const p3 = '马上赋值'
    const p3; // 报错 没有赋值
    
  2. const一旦声明值就不能改变

    # 简单类型:不能改动

    const p = '不能改变';
    p = '报错'
    

    # �复杂类型:变量指针不能变

    考虑如下情况:

    const p = ['不能改动']
    const p2 = {
      name: 'OBKoro1'
    }
    p[0] = '不报错'
    p2.name = '�不报错'
    p = ['报错']
    p2 = {
      name: '报错'
    }
    

    const所说的一旦声明值就不能改变,实际上指的是:变量指向的那个内存地址所保存的数据不得改动

    • 简单类型(number、string、boolean):内存地址就是值,即常量(一变就报错).

    • 复杂类型(对象、数组等):地址保存的是一个指针,const只能保证指针是固定的(总是指向同一个地址),它内部的值是可以改变的(不要以为const就安全了!)

      所以只要不重新赋值整个数组/对象, 因为保存的是一个指针,所以对数组使用的pushshiftsplice等方法也是允许的�,你就是把值一个一个全都删光了都不会报错。

    复杂类型还有函数,正则等,这点也要注意一下。

# 总结:

再总结一下,看到这些名词,脑子里应该会有对应的理解,如果没有的话,那可以再看看对应的内容。

# varlet/const的区别:

  1. 块级作用域
  2. 不存在变量提升
  3. 暂时性死区
  4. 不可重复声明
  5. let、const声明的�全局变量不会挂在顶层对象下面

# const命令两个注意点:

  1. let可以先声明稍后再赋值,而const在 声明之后必须马上赋值,否则会报错

  2. const 简单类型一旦声明就不能再更改,�复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改。

# let、const使用场景:

  1. let使用场景:变量,用以替代var
  2. const使用场景:常量、声明匿名函数、箭头函数的时候。

参考资料:

let 和 const 命令

# 点个Star支持我一下~

博客链接

CSS 权重规则 | CSS

博客链接

# CSS 权重规则

日常开发中,或多或少都会遇到css规则不生效的问题,为了让我们能够减少调试css规则的时间,深刻理解css权重,就十分关键了.

# 权重规则总结:

  1. !important 优先级最高,但也会被权重高的 important 所覆盖
  2. 行内样式总会覆盖外部样式表的任何样式(除了!important)
  3. 单独使用一个选择器的时候,不能跨等级使 css 规则生效
  4. 如果两个权重不同的选择器作用在同一元素上,权重值高的 css 规则生效
  5. 如果两个相同权重的选择器作用在同一元素上:以后面出现的选择器为最后规则.
  6. 权重相同时,与元素距离近的选择器生效

# css 权重优先级用来干嘛?

在同一个元素使用不同的方式,声明了相同的一条或多条 css 规则,浏览器会通过权重来判断哪一种方式的声明,与这个元素最为相关,从而在该元素上应用这个声明方式声明的所有 css 规则

# 权重的五个等级及其权重

  • !important;

  • 行内样式;

  • ID 选择器, 权重:100;

  • class,属性选择器和伪类选择器,权重:10;

    属性选择器指的是:根据元素的属性及属性值来选择元素,比如button的type属性等。 伪类选择器: :active :focus等选项.

  • 标签选择器和伪元素选择器,权重:1;

    伪元素选择器: :before :after

# 等级关系:

!important>行内样式>ID选择器 > 类选择器 | 属性选择器 | 伪类选择器 > 元素选择器

# 权重规则:

# 1.不推荐使用!important

不推荐使用!important,因为!important根本没有结构与上下文可言,并且很多时候权重的问题,就是因为不知道在哪里定义了一个!important而导致的。

# 覆盖 important:

虽然我们应该尽量避免使用!important,但你应该知道如何覆盖 important,加点权重就可以实现,codepen 的demo

//!important 优先级最高,但也会被权重高的important所覆盖
<button id="a" class="a">aaa</button>
#a{
    background: blue  !important;  /* id的important覆盖class的important*/
}
.a{
    background: red  !important;
}

# 2.行内样式总会覆盖外部样式表的任何样式,会被!important覆盖

# 3.单独使用一个选择器的时候,不能跨等级使 css 规则生效

无论多少个 class 组成的选择器,都没有一个 ID 选择器权重高。类似的,无论多少个元素组成的选择器,都没有一个 class 选择器权重高、无论多少个 ID 组成的选择器,都没有行内样式权重高。

codepen 的demo;

    在demo中使用了11个class组成一个选择器,最后还是一个ID选择器,设置的样式生效。

# 可以想象在玄幻小说的那种等级制度,没有突破那个等级,就没有可比性。

所以权重是在双方处于同一等级的情况下,才开始对比。

# 4.如果两个权重不同的选择器作用在同一元素上,权重值高的 css 规则生效

选择器可能会包含一个或者多个与权重相关的计算点,若经过权重值计算得到的权重值越大,则认为这个选择器的权重高。举一个简单的栗子:

.test #test{ } // id 100+class 10=110;
.test #test span{} // id 100+class 10+span 1=111;
.test #test .sonClass{} // id 100+class 10+class 10=120; //生效

# 5.如果两个相同权重的选择器作用在同一元素上:以后面出现的选择器为最后规则.

demo

<div id="app">
    <div class="test" id="test">
    <span >啦啦啦</span>
    </div>
</div>
#test span{
    color:blue;
}
#app span{ // 生效 因为后面出现
    color: red;
}

# 6.权重相同时,与元素距离近的选择器生效

比如不同的 style 表,head 头部等,来看下面的栗子:

#content h1 { // css样式表中
    padding: 5px;
}
<style type="text/css">
    #content h1 { // html头部 因为html头部离元素更近一点,所以生效
    padding: 10px;
    }
</style>

# 建议:

  1. 避免使用!important;
  2. 利用 id 增加选择器权重;
  3. 减少选择器的个数(避免层层嵌套);

# 点个Star支持我一下~

博客链接

JS忍者秘籍中的定时器机制详解 | tool

博客链接

# JS忍者秘籍中的定时器机制详解

前段时间刚看完《JS忍者秘籍》,虽说是15年出版的,有些东西是过时了,但像对原型链、闭包、正则、定时器之类的机制却是不会过时的,里面很多东西都讲的很细,还是值得一读的,本文将对这本书中对定时器机制的部分进行详细的解析,如果喜欢的话可以点波赞/关注,支持一下,希望大家看完本文可以有所收获。

# 准备

阅读本文之前,推荐先阅读Js 的事件循环(Event Loop)机制以及实例讲解这篇文章来理解背后发生的事情,本文对事件循环机制不会很仔细的讲解。

# 定时器解决的问题:

由于JS的单线程特性,定时器提供了一种跳出单线程限制的方法,即让一段代码在一定毫秒之后,再异步执行。

# 设置和清除定时器:

直接引用忍者秘籍中的图片:

# 注意:

  1. 定时器的时间间隔设为0,也会有几毫秒的延迟。
  2. 在使用setTimeoutsetInterval的时候最好将其赋值给一个变量,以便取消定时器。
  3. 在使用Vue的时候,setTimeoutsetInterval的this指向的是window对象,访问不到组件数据以及方法。
  4. 在使用Vue的时候,路由跳转并不会销毁setInterval,但是组件已经销毁了,这会导致问题。
  5. 在执行线程中setTimeout/setInterval无法保证准时执行回调函数的。
  6. setInterval调用有可能会被废弃以及setInterval的连续执行

第三点和第四点的解决方法可以参考我之前写的Vue 实践过程中的几个问题

接下来要讲的是第五点和第六点,这两点是最重要,也是本文要重点解析的内容。

# 执行线程中的定时器执行

下面来看忍者秘籍中的栗子:

让我们看看这里发生了什么事情:

  1. 首先在0毫秒的时候有一个持续18毫秒的js代码块要执行。
  2. 然后在0毫秒的时候设了两个10毫秒延迟的定时器,setTimeout以及setInterval,setTimeout先设定。
  3. 在第6毫秒的时候有一个发生了鼠标单击事件。

# 事件排队。

同时发生了这么多事情,由于js的单线程特性,当线程正在执行状态,有异步事件触发时,它就会排队,并且在线程空闲时才进行执行

    这里的异步事件包括:鼠标单击,定时器触发,ajax请求、promise等事件。

让我们回到栗子中:

栗子中首先有一个18毫秒的代码块要执行,在这18毫秒中只能执行这段代码块,其他事件触发了之后只能排队等待执行

在代码块还在运行期间,第6毫秒的时候,发生了一个鼠标单击事件,以及第10毫秒时的setTimeoutsetInterval两个处理程序,这三个事件不能立即执行,而是被添加到等待执行的队列中。

# 先进先出(先排队的先执行)

18毫秒的时候代码块结束执行,有三个任务在排队等待执行,根据先进先出的原则,此时会先执行click事件setTimeoutsetInterval将继续排队等待执行。

# setInterval调用被废弃

在click事件执行时,第20毫秒处,第二个setInterval也到期了,因为此时已经click事件占用了线程,所以setInterval还是不能被执行,并且因为此时队列中已经有一个setInterval正在排队等待执行,所以这一次的setInterval的调用将被废弃

浏览器不会对同一个setInterval处理程序多次添加到待执行队列。

# setTimeout/setInterval无法保证准时执行回调函数

click事件在第28毫秒处结束执行,有两个任务(setTimeoutsetInterval)正在等待执行,遵循先进先出的原则,setTimeout早于setInterval设定,所以先执行setTimeout

so:我们期望在第10毫秒处执行的setTimeout处理程序,最终会在第28毫秒处才开始执行,这就是上文提到的setTimeout/setInterval无法保证准时执行回调函数。

在30毫秒处,setInterval又触发了,因为队列中已经有setInterval在排队,所以这次的触发又作废了。

# setInterval的连续执行

setTimeout执行结束,在第36毫秒处,队列中的setInterval处理程序才开始执行,setInterval需要执行6毫秒。

在第40毫秒的时候setInterval再次触发,因为此时上一个setInterval正在执行期间,队列中并没有setInterval在排队,这次触发的setInterval将进入队列等候

所以:setInterval的处理时长不能比设定的间隔长,否则setInterval将会没有间隔的重复执行

第42毫秒的时候,第一个setInterval结束,然后队列中的setInterval立即开始执行,在48毫秒的时候完成执行。然后50毫秒的时候再次触发setInterval,此时没有任务在排队,将会立即执行。

# setTimeout按照一定的间隔周期性的触发定时器。

上文说了,setInterval的处理时长不能比设定的间隔长,否则setInterval将会没有间隔的重复执行

但是对这个问题,很多情况下,我们并不能清晰的把控处理程序所消耗的时长,为了我们能按照一定的间隔周期性的触发定时器,忍者秘籍中提供了下面这种使用方法:

// 实际上我不止在忍者秘籍中见过,在很多地方都见过这种技术。
setTimeout(function repeatMe(){
    // do something
    setTimeout(repeatMe,10); 
    // 执行完处理程序的内容后,在末尾再间隔10毫秒来调用该程序,这样就能保证一定是10毫秒的周期调用
},10)

# 忍者秘籍中关于定时器的其他知识:

  • 定时器不能非常细粒化的控制执行的时间,书中建议在15ms以上。
  • 可以使用定时器来分解长时间运行的任务,这里可以自行谷歌。

# 任务队列只有排队这么简单吗?

事实上,关于任务队列并不是只有简单的排队而已,忍者秘籍中提到为了方便,使用了这个概念,如果希望能更清晰的了解背后的机制,再次推荐阅读一下:Js 的事件循环(Event Loop)机制以及实例讲解

# 小结

这上面所有一切,都是由js单线程特性导致的,所以会有事件排队、先进先出、setInterval调用被废弃、定时器无法保证准时执行回调函数以及出现setInterval的连续执行,时刻记住这一特性,很多关于事件执行顺序的问题都能想的通,并且找出解决方法。

# 点个Star支持我一下~

博客链接

我用来阻止你摸鱼看直播、知乎和微博的Chrome插件 | tool

博客链接

# 我用来阻止你摸鱼看直播、知乎和微博的Chrome插件

chrome插件通过强制的手段禁止大家浪费时间摸鱼,在上班/学习期间下意识的打开摸鱼网站, 自动检测摸鱼网站, 提示激励信息后, 关闭摸鱼网站。

# Github地址

stop-mess-around

# 插件解决的问题: 停止下意识的摸鱼

在工作、学习期间,如果事情不是太忙,或者说在学习/忙碌一小段时间之后。

就会下意识的打开或者输入知乎、掘金沸点、微博等网站,开始了摸鱼时光

可能是摸鱼奶头乐太快乐了,时间很快就过去两三个小时,而忘记自己的工作、学习初衷了

事后我们通常会为之内疚,觉得很浪费时间

但无奈摸鱼网站深谙人性弱点,仅靠我们自身的自律还是很难去抵抗这种诱惑。

作者本人曾经也深受其害,后来我想到可以用工具来限制这种下意识的行为。

于是很喜欢写工具的我, 写了一个脚本用来检测摸鱼网站、检测到了就自动关闭摸鱼网站

我在经过一段时间的使用之后,我就再也没有在电脑上打开摸鱼网站了,工作学习效率也提高了很多

很奇怪,每次打开之后就被插件提示我不要摸鱼,然后被关闭摸鱼网站。

久而久之我就戒掉了在休息的间隙没事做就打开摸鱼网站的习惯了

就我个人而言,真的很有用,很有效果,为我节省了很多时间

所以我将它做成chrome插件,开发了可视化的界面,方便不懂技术的人也可以直接上手使用。

希望这个开源工具可以有效的帮助大家减少摸鱼时间,提高工作和学习的效率。

# 使用示例

  1. 添加摸鱼网站以及匹配摸鱼网站弹窗提示、关闭网页 start
  2. 一键开启/关闭以及批量添加摸鱼网站

addCheckout

  1. 匹配摸鱼网站后出现的提示信息 - 随机激励语录

mottoSetting 4. 一些设置提示。

setting

# 安装插件

# chrome应用商店

审核中,暂不支持。

# 本地安装

只需要6步即可安装,非常方便,快捷。

  1. 打开仓库的releases, 下载最新版本的zip包到本地。

downZip 2. 解压刚刚下载的zip包变成一个文件夹。 3. 进入chrome扩展程序管理页面

选项(右上角)-> 更多工具 -> 扩展程序

  1. 打开开发者模式
  2. 加载已解压的扩展程序
  3. 选择刚才解压的那个文件夹。 setPlugin

# 插件文档

快速上手以及使用说明

插件节省下来的时间如何合理利用

# 点个Star吧

如果觉得这个插件还不错,对你有所帮助的话,就给我点个Star吧~

# 我的其他开源推荐

# koroFileHeader

  1. 它是用于生成文件头部注释以及函数注释的,帮助我们养成良好的编码习惯,规范整个团队风格。
  2. 插件从18年5月维护至今, 2.7K+ Star,插件支持所有主流语言,功能强大,灵活方便,文档齐全,食用简单! headTip functionParams

# AutoCommit

这是一个用于Git自动commit的VSCode插件,它可以用来补充之前忘记提交commit,帮助你把首页的绿色格子填满。 autoCommit

# License

MIT

# 欢迎赞助

十块八块不嫌多,三块五块也是爱 😘

# 联系我

掘金前端进阶积累公众号GitHub微信:OBkoro1、邮箱:[email protected]

# 点个Star支持我一下~

博客链接

js 调用栈机制与ES6尾调用优化介绍 | ES6

博客链接

# js 调用栈机制与ES6尾调用优化介绍

调用栈的英文名叫做Call Stack,大家或多或少是有听过的,但是对于js调用栈的工作方式以及如何在工作中利用这一特性,大部分人可能没有进行过更深入的研究,这块内容可以说对我们前端来说就是所谓的基础知识,咋一看好像用处并没有很大,但掌握好这个知识点,就可以让我们在以后可以走的更远,走的更快!

博客前端积累文档公众号GitHub


# 目录

  1. 数据结构:栈
  2. 调用栈是什么?用来做什么?
  3. 调用栈的运行机制
  4. 调用栈优化内存
  5. 调用栈debug大法

# 数据结构:栈

栈是一种遵从后进先出(LIFO)原则的有序集合,新元素都靠近栈顶,旧元素都接近栈底。

生活中的栗子,帮助一下理解:

餐厅里面堆放的盘子(栈),一开始放的都在下面(先进),后面放的都在上面(后进),洗盘子的时候先从上面开始洗(先出)。

# 调用栈是什么?用来做什么?

  1. 调用栈是一种栈结构的数据,它是由调用侦组成的
  2. 调用栈记录了函数的执行顺序和函数内部变量等信息

# 调用栈的运行机制

机制

程序运行到一个函数,它就会将其添加到调用栈中,当从这个函数返回的时候,就会将这个函数从调用栈中删掉。

看一下例子帮助理解:

// 调用栈中的执行步骤用数字表示
printSquare(5); // 1 添加
function printSquare(x) {
    var s = multiply(x, x); // 2 添加 => 3 运行完成,内部没有再调用其他函数,删掉
    console.log(s); // 4 添加 => 5 删掉
    // 运行完成 删掉printSquare
}
function multiply(x, y) {
    return x * y;
}

调用栈中的执行步骤如下(删除multiply的步骤被省略了):

调用侦

每个进入到调用栈中的函数,都会分配到一个单独的栈空间,称为“调用侦”。

在调用栈中每个“调用侦”都对应一个函数,最上方的调用帧称为“当前帧”,调用栈是由所有的调用侦形成的。

找到一张图片,调用侦:

# 调用栈优化内存

调用栈的内存消耗

如上图,函数的变量等信息会被调用侦保存起来,所以调用侦中的变量不会被垃圾收集器回收

当函数嵌套的层级比较深了,调用栈中的调用侦比较多的时候,这些信息对内存消耗是非常大的。

针对这种情况除了我们要尽量避免函数层级嵌套的比较深之外,ES6提供了“尾调用优化”来解决调用侦过多,引起的内存消耗过大的问题。

何谓尾调用

尾调用指的是:函数的最后一步是调用另一个函数

function f(x){
  return g(x); // 最后一步调用另一个函数并且使用return
}
function f(x){
  g(x); // 没有return 不算尾调用 因为不知道后面还有没有操作
  // return undefined; // 隐式的return
}

尾调用优化优化了什么?

尾调用用来删除外层无用的调用侦,只保留内层函数的调用侦,来节省浏览器的内存。

下面这个例子调用栈中的调用侦一直只有一项,如果不使用尾调用的话会出现三个调用侦:

a() // 1 添加a到调用栈
function a(){
    return b(); // 在调用栈中删除a 添加b
}
function b(){
    return c() // 删除b 添加c
}

防止爆栈

浏览器对调用栈都有大小限制,在ES6之前递归比较深的话,很容易出现“爆栈”问题(stack overflow)。

现在可以使用“尾调用优化”来写一个“尾递归”,只保存一个调用侦,来防止爆栈问题。

注意

  1. 只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧。

如果要使用外层函数的变量,可以通过参数的形式传到内层函数中

function a(){
    var aa = 1;
    let b = val => aa + val // 使用了外层函数的参数aa
    return b(2) // 无法进行尾调用优化
}
  1. 尾调用优化只在严格模式下开启,非严格模式是无效的。
  2. 如果环境不支持“尾调用优化”,代码还可以正常运行,是无害的!

更多

关于尾递归以及更多尾调用优化的内容,推荐查阅ES6入门-阮一峰

# 调用栈debug大法

查看调用栈有什么用

  1. 查看函数的调用顺序是否跟预期一致,比如不同判断调用不同函数。

  2. 快速定位问题/修改三方库的代码。

    当接手一个历史项目,或者引用第三方库出现问题的时候,可以先查看对应API的调用栈,找到其中涉及的关键函数,针对性的修复它。

    通过查看调用栈的形式,帮助我快速定位问题,修改三方库的源码。

如何查看调用栈

  1. 只查看调用栈:console.trace
a()
function a() {
    b();
}
function b() {
    c()
}
function c() {
    let aa = 1;
    console.trace()
}

如图所示,点击右侧还能查看代码位置:

  1. bugger打断点形式,这也是我最喜欢的调试方式:

# 结语

本文主要讲了这几个方面的内容:

  1. 理解调用栈的运行机制,对代码背后的一些执行机制也可以更加了解,帮助我们在百尺竿头更进一步。
  2. 我们应该在日常的code中,有意识的使用ES6的“尾调用优化”,来减少调用栈的长度,节省客户端内存。
  3. 利用调用栈,对第三方库或者不熟悉的项目,可以更快速的定位问题,提高我们debug速度。

最后:之前写过一篇关于垃圾回收机制与内存泄露的文章,感兴趣的同学可以扩展一下。

以上2019/5/19

参考资料:

JS垃圾回收机制与常见内存泄露的解决方法

ES6入门-阮一峰

JavaScript 如何工作:对引擎、运行时、调用堆栈的概述

浅析javascript调用栈

# 点个Star支持我一下~

博客链接

JS基础-深入浅出继承 | JS

博客链接

# JS基础-深入浅出继承

上篇文章详细解析了原型、原型链的相关知识点,这篇文章讲的是和原型链有密切关联的继承,它是前端基础中很重要的一个知识点,它对于代码复用来说非常有用,本篇将详细解析JS中的各种继承方式和优缺点进行,希望看完本篇文章能够对继承以及相关概念理解的更为透彻。

# 本篇文章需要先理解原型、原型链以及call的相关知识:

JS基础-函数、对象和原型、原型链的关系

js基础-面试官想知道你有多理解call,apply,bind?

# 何为继承?

维基百科:继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。

继承是一个类从另一个类获取方法和属性的过程

PS:或者是多个类

# JS实现继承的原理

记住这个概念,你会发现JS中的继承都是在实现这个目的,差异是它们的实现方式不同。

复制父类的属性和方法来重写子类原型对象

# 原型链继承(new):

function fatherFn() {
  this.some = '父类的this属性';
}
fatherFn.prototype.fatherFnSome =  '父类原型对象的属性或者方法';
// 子类
function sonFn() {
  this.obkoro1 = '子类的this属性';
}
// 核心步骤:重写子类的原型对象
sonFn.prototype = new fatherFn(); // 将fatherFn的实例赋值给sonFn的prototype
sonFn.prototype.sonFnSome = '子类原型对象的属性或者方法' // 子类的属性/方法声明在后面,避免被覆盖
// 实例化子类
const sonFnInstance = new sonFn();
console.log('子类的实例:', sonFnInstance);

# 原型链子类实例

原型链子类实例

# 原型链继承获取父类的属性和方法

  1. fatherFn通过this声明的属性/方法都会绑定在new期间创建的新对象上。
  2. 新对象的原型是father.prototype,通过原型链的属性查找到father.prototype的属性和方法。

# 理解new做了什么:

new在本文出现多次,new也是JS基础中很重要的一块内容,很多知识点会涉及到new,不太理解的要多看几遍。

  1. 创建一个全新的对象。
  2. 这个新对象的原型(__proto__)指向函数的prototype对象。
  3. 执行函数,函数的this会绑定在新创建的对象上。
  4. 如果函数没有返回其他对象(包括数组、函数、日期对象等),那么会自动返回这个新对象。
  5. 返回的那个对象为构造函数的实例。

# 构造调用函数返回其他对象

返回其他对象会导致获取不到构造函数的实例,很容易因此引起意外的问题

我们知道了fatherFnthisprototype的属性/方法都跟new期间创建的新对象有关系

如果在父类中返回了其他对象(new的第四点),其他对象没有父类的thisprototype,因此导致原型链继承失败

我们来测试一下,修改原型链继承中的父类fatherFn

function fatherFn() {
  this.some = '父类的this属性';
  console.log('new fatherFn 期间生成的对象', this)
  return [ '数组对象', '函数对象', '日期对象', '正则对象', '等等等', '都不会返回new期间创建的新对象' ]
}

原型链继承返回其他对象,将导致原型链继承失败

PS: 本文中构造调用函数都不能返回其他函数,下文不再提及该点。

# 不要使用对象字面量的形式创建原型方法:

这种方式很容易在不经意间,清除/覆盖了原型对象原有的属性/方法,不该为了稍微简便一点,而使用这种写法。

有些人在需要在原型对象上创建多个属性和方法,会使用对象字面量的形式来创建:

sonFn.prototype = new fatherFn();
// 子类的prototype被清空后 重新赋值, 导致上一行代码失效
sonFn.prototype = {
    sonFnSome: '子类原型对象的属性',
    one: function() {},
    two: function() {},
    three: function() {}
}

还有一种常见的做法,该方式会导致函数原型对象的属性constructor丢失:

function test() {}
test.prototype = {
    ...
}

# 原型链继承的缺点

  1. 父类使用this声明的属性被所有实例共享

    原因是:实例化的父类(sonFn.prototype = new fatherFn())是一次性赋值到子类实例的原型(sonFn.prototype)上,它会将父类通过this声明的属性也在赋值到sonFn.prototype上。

值得一提的是:很多博客中说,引用类型的属性被所有实例共享,通常会用数组来举例,实际上数组以及其他父类通过this声明的属性也只是通过原型链查找去获取子类实例的原型(sonFn.prototype)上的值。

  1. 创建子类实例时,无法向父类构造函数传参,不够灵活。

这种模式父类的属性、方法一开始就是定义好的,无法向父类传参,不够灵活。

sonFn.prototype = new fatherFn()

# 借用构造函数继承(call)

 function fatherFn(...arr) {
  this.some = '父类的this属性';
  this.params = arr // 父类的参数
}
fatherFn.prototype.fatherFnSome = '父类原型对象的属性或者方法';
function sonFn(fatherParams, ...sonParams) {
  fatherFn.call(this, ...fatherParams); // 核心步骤: 将fatherFn的this指向sonFn的this对象上
  this.obkoro1 = '子类的this属性';
  this.sonParams = sonParams; // 子类的参数
}
sonFn.prototype.sonFnSome = '子类原型对象的属性或者方法'
let fatherParamsArr = ['父类的参数1', '父类的参数2']
let sonParamsArr = ['子类的参数1', '子类的参数2']
const sonFnInstance = new sonFn(fatherParamsArr, ...sonParamsArr); // 实例化子类
console.log('借用构造函数子类实例', sonFnInstance)

# 借用构造函数继承的子类实例

借用构造函数继承的子类实例

# 借用构造函数继承做了什么?

声明类,组织参数等,只是辅助的上下文代码,核心是借用构造函数使用call做了什么:

一经调用call/apply它们就会立即执行函数,并在函数执行时改变函数的this指向

fatherFn.call(this, ...fatherParams); 
  1. 在子类中使用call调用父类,fatherFn将会被立即执行,并且将fatherFn函数的this指向sonFnthis
  2. 因为函数执行了,所以fatherFn使用this声明的函数都会被声明到sonFnthis对象下。
  3. 实例化子类,this将指向new期间创建的新对象,返回该新对象。
  4. fatherFn.prototype没有任何操作,无法继承。

该对象的属性为:子类和父类声明的this属性/方法,它的原型是

PS: 关于call/apply/bind的更多细节,推荐查看我的博客:js基础-面试官想知道你有多理解call,apply,bind?[不看后悔系列]

# 借用构造函数继承的优缺点

优点:

  1. 可以向父类传递参数
  2. 解决了原型链继承中:父类属性使用this声明的属性会在所有实例共享的问题。

缺点:

  1. 只能继承父类通过this声明的属性/方法,不能继承父类prototype上的属性/方法。
  2. 父类方法无法复用:因为无法继承父类的prototype,所以每次子类实例化都要执行父类函数,重新声明父类this里所定义的方法,因此方法无法复用。

# 组合继承(call+new)

原理:使用原型链继承(new)将thisprototype声明的属性/方法继承至子类的prototype上,使用借用构造函数来继承父类通过this声明属性和方法至子类实例的属性上。

function fatherFn(...arr) {
  this.some = '父类的this属性';
  this.params = arr // 父类的参数
}
fatherFn.prototype.fatherFnSome = '父类原型对象的属性或者方法';
function sonFn() {
  fatherFn.call(this, '借用构造继承', '第二次调用'); // 借用构造继承: 继承父类通过this声明属性和方法至子类实例的属性上
  this.obkoro1 = '子类的this属性';
}
sonFn.prototype = new fatherFn('原型链继承', '第一次调用'); // 原型链继承: 将`this`和`prototype`声明的属性/方法继承至子类的`prototype`上
sonFn.prototype.sonFnSome = '子类原型对象的属性或者方法'
const sonFnInstance = new sonFn();
console.log('组合继承子类实例', sonFnInstance)

# 组合继承的子类实例

组合继承的子类实例

从图中可以看到fatherFn通过this声明的属性/方法,在子类实例的属性上,和其原型上都复制了一份,原因在代码中也有注释:

  1. 原型链继承: 父类通过thisprototype声明的属性/方法继承至子类的prototype上。
  2. 借用构造继承: 父类通过this声明属性和方法继承至子类实例的属性上。

# 组合继承的优缺点

优点:

完整继承(又不是不能用),解决了:

  1. 父类通过this声明属性/方法被子类实例共享的问题(原型链继承的问题) 每次实例化子类将重新初始化父类通过this声明的属性,实例根据原型链查找规则,每次都会
  2. 父类通过prototype声明的属性/方法无法继承的问题(借用构造函数的问题)。

缺点:

  1. 两次调用父类函数(new fatherFn()fatherFn.call(this)),造成一定的性能损耗。
  2. 因调用两次父类,导致父类通过this声明的属性/方法,生成两份的问题。
  3. 原型链上下文丢失:子类和父类通过prototype声明的属性/方法都存在于子类的prototype上

# 原型式继承(Object.create())

# 继承对象原型-Object.create()实现

以下是Object.create()的模拟实现,使用Object.create()可以达成同样的效果,基本上现在都是使用Object.create()来做对象的原型继承。

function cloneObject(obj){
  function F(){}
  F.prototype = obj; // 将被继承的对象作为空函数的prototype
  return new F(); // 返回new期间创建的新对象,此对象的原型为被继承的对象, 通过原型链查找可以拿到被继承对象的属性
}

PS:上面Object.create()实现原理可以记一下,有些公司可能会让你讲一下它的实现原理。

# 例子:

let oldObj = { p: 1 };
let newObj = cloneObject(oldObj)
oldObj.p = 2
console.log('oldObj newObj', oldObj, newObj)

原型式继承

# 原型式继承优缺点:

优点: 兼容性好,最简单的对象继承。

缺点:

  1. 因为旧对象(oldObj)是实例对象(newObj)的原型,多个实例共享被继承对象的属性,存在篡改的可能。
  2. 无法传参

# 寄生式继承(封装继承过程)

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。

function createAnother(original){
  var clone = cloneObject(original); // 继承一个对象 返回新函数
  // do something 以某种方式来增强对象
  clone.some = function(){}; // 方法
  clone.obkoro1 = '封装继承过程'; // 属性
  return clone; // 返回这个对象
}

使用场景:专门为对象来做某种固定方式的增强。

# 寄生组合式继承(call+寄生式封装)

# 寄生组合式继承原理:

  1. 使用借用构造函数(call)来继承父类this声明的属性/方法
  2. 通过寄生式封装函数设置父类prototype为子类prototype的原型来继承父类的prototype声明的属性/方法
function fatherFn(...arr) {
  this.some = '父类的this属性';
  this.params = arr // 父类的参数
}
fatherFn.prototype.fatherFnSome = '父类原型对象的属性或者方法';
function sonFn() {
  fatherFn.call(this, '借用构造继承'); // 核心1 借用构造继承: 继承父类通过this声明属性和方法至子类实例的属性上
  this.obkoro1 = '子类的this属性';
}
// 核心2 寄生式继承:封装了son.prototype对象原型式继承father.prototype的过程,并且增强了传入的对象。
function inheritPrototype(son, father) {
  const fatherFnPrototype = Object.create(father.prototype); // 原型式继承:浅拷贝father.prototype对象 father.prototype为新对象的原型
  son.prototype = fatherFnPrototype; // 设置father.prototype为son.prototype的原型
  son.prototype.constructor = son; // 修正constructor 指向
}
inheritPrototype(sonFn, fatherFn)
sonFn.prototype.sonFnSome = '子类原型对象的属性或者方法'
const sonFnInstance = new sonFn();
console.log('寄生组合式继承子类实例', sonFnInstance)

# 寄生组合式继承子类实例

寄生组合式继承子类实例

# 寄生组合式继承是最成熟的继承方法:

寄生组合式继承是最成熟的继承方法, 也是现在最常用的继承方法,众多JS库采用的继承方案也是它。

寄生组合式继承相对于组合继承有如下优点:

  1. 只调用一次父类fatherFn构造函数。

  2. 避免在子类prototype上创建不必要多余的属性。

  3. 使用原型式继承父类的prototype,保持了原型链上下文不变。

    子类的prototype只有子类通过prototype声明的属性/方法和父类prototype上的属性/方法泾渭分明。

# ES6 extends继承:

ES6继承的原理跟寄生组合式继承是一样的。

ES6 extends核心代码:

这段代码是通过babel在线编译成es5, 用于子类prototype原型式继承父类prototype的属性/方法。

// 寄生式继承 封装继承过程
function _inherits(son, father) {
  // 原型式继承: 设置father.prototype为son.prototype的原型 用于继承father.prototype的属性/方法
  son.prototype = Object.create(father && father.prototype);
  son.prototype.constructor = son; // 修正constructor 指向
  // 将父类设置为子类的原型 用于继承父类的静态属性/方法(father.some)
  if (father) {
    Object.setPrototypeOf
      ? Object.setPrototypeOf(son, father)
      : son.__proto__ = father;
  }
}

另外子类是通过借用构造函数继承(call)来继承父类通过this声明的属性/方法,也跟寄生组合式继承一样。

# ES5继承与ES6继承的区别:

本段摘自阮一峰-es6入门文档

  • ES5的继承实质上是先创建子类的实例对象,再将父类的方法添加到this上

  • ES6的继承是先创建父类的实例对象this,再用子类的构造函数修改this

    因为子类没有自己的this对象,所以必须先调用父类的super()方法。

# 扩展:

# 为什么要修正construct指向?

在寄生组合式继承中有一段如下一段修正constructor 指向的代码,很多人对于它的作用以及为什么要修正它不太清楚。

son.prototype.constructor = son; // 修正constructor 指向

# construct的作用

MDN的定义:返回创建实例对象的Object构造函数的引用

即返回实例对象的构造函数的引用,例如:

let instance = new sonFn()
instance.constructor // sonFn函数

# construct的应用场景:

当我们只有实例对象没有构造函数的引用时

某些场景下,我们对实例对象经过多轮导入导出,我们不知道实例是从哪个函数中构造出来或者追踪实例的构造函数,较为艰难。

这个时候就可以通过实例对象的constructor属性来得到构造函数的引用:

let instance = new sonFn() // 实例化子类
export instance;
// 多轮导入+导出,导致sonFn追踪非常麻烦,或者不想在文件中再引入sonFn
let  fn = instance.construct
// do something: new fn() / fn.prototype / fn.length / fn.arguments等等

# 保持construct指向的一致性:

因此每次重写函数的prototype都应该修正一下construct的指向,以保持读取construct行为的一致性。

# 小结

继承也是前端的高频面试题,了解本文中继承方法的优缺点,有助于更深刻的理解JS继承机制。除了组合继承和寄生式继承都是由其他方法组合而成的,分块理解会对它们理解的更深刻。

建议多看几遍本文,建个html文件试试文中的例子,两相结合更佳!

对prototype还不是很理解的同学,可以再看看:JS基础-函数、对象和原型、原型链的关系

# 觉得我的博客对你有帮助的话,就给我点个Star吧!

前端进阶积累公众号GitHub、wx:OBkoro1、邮箱:[email protected]

以上2019/9/22

作者:OBKoro1

参考资料:

JS高级程序设计(红宝书)6.3继承

JavaScript常用八种继承方案

# 点个Star支持我一下~

博客链接

一份超级详细的Vue-cli3.0使用教程 | tool

博客链接

# 一份超级详细的Vue-cli3.0使用教程

在vue-cli 2.X的时候,也写过一篇类似的文章,在八月份的时候vue-cli已经更新到了3.X,新版本的脚手架,功能灰常强大,试用过后非常喜欢,写篇教程来帮助各位踩一下坑。

游泳、健身了解一下:博客前端积累文档公众号GitHub

# 主要内容:

  1. 零配置启动/打包一个.vue文件
  2. 详细的搭建过程
  3. 重点推荐:使用图形化界面创建/管理/运行项目

# 安装:

# 卸载旧版本:

如果你事先已经全局安装了旧版本的vue-cli(1.x 或 2.x),你需要先卸载它:

npm uninstall vue-cli -g

# Node版本要求:

3.x需要在Node.js8.9或更高版本(推荐8.11.0+),点击这里可以安装node

大多数人都安装过了node,使用下面的命令行查询你的node版本:

node -v

如果你的版本不够,可以使用下面的命令行来把Node版本更新到最新的稳定版

npm install -g n // 安装模块 这个模块是专门用来管理node.js版本的
n stable // 更新你的node版本

mac下,更新版本的时候,如果提示你权限不够:

sudo n stable // 我就遇到了

# 安装vue-cli:

npm install -g @vue/cli // 安装cli3.x
vue --version // 查询版本是否为3.x

如果cli3.x用的不舒服,cli3也能使用2.x模板

npm install -g @vue/cli-init // 安装这个模块
// 就可以使用2.x的模板:vue init webpack my-project

# 零配置启动/打包一个.vue文件:

# 安装扩展:

npm install -g @vue/cli-service-global

安装完扩展之后,可以随便找个文件夹建一个如下方示例的.vue文件,然后跑起来:

vue serve App.vue // 启动服务
vue build App.vue // 打包出生产环境的包并用来部署

# 如下图,只需一个.vue文件,就能迅速启动一个服务:

如图所示,服务启动的时候回生成一个node_modules包,稍微测试了一下,服务支持ES6语法和热更新,打包的时候会生成一个dist文件夹。(新建一个test.vue文件也只有一个node_modules/dist文件夹)

这是个很棒的功能,用于开发一个库、组件,做一些小demo等都是非常适合的


# 第一次创建项目:

# 1. 命令行:

vue create hello-cli3 
  • hello-cli3是文件夹名字,如果不存在会自动创建文件夹,如果存在会安装到那个文件夹中。

  • 相比2.x的时候需要自己手动创建一个文件夹,这里也算是一个小优化吧。

# 2. 选择模板:

  • 一开始只有两个选项: default(默认配置)和Manually select features(手动配置)

    默认配置只有babeleslint其他的都要自己另外再配置,所以我们选第二项手动配置。

  • 在每次选择手动配置之后,会询问你是否保存配置,也就是图片中的koro选项,这样以后我们在进行创建项目的时候只需使用原先的配置就可以了,而不用再进行配置。

# 3. 选择配置:

  • 根据你的项目需要来选择配置,空格键是选中与取消,A键是全选

      ? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection) 
      // 检查项目所需的功能:(按<space>选择,<a>切换所有,<i>反转选择)
      >( ) TypeScript                                 // 支持使用 TypeScript 书写源码
       ( ) Progressive Web App (PWA) Support          // PWA 支持
       ( ) Router                                     // 支持 vue-router
       ( ) Vuex                                       // 支持 vuex
       ( ) CSS Pre-processors                         // 支持 CSS 预处理器。
       ( ) Linter / Formatter                         // 支持代码风格检查和格式化。
       ( ) Unit Testing                               // 支持单元测试。
       ( ) E2E Testing  
    

# 4. 选择css预处理器:

  • 如果你选择了Css预处理器选项,会让你选择这个

      ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
      // 选择CSS预处理器(默认支持PostCSS,Autoprefixer和CSS模块):
      > SCSS/SASS
        LESS
        Stylus
    

# 5. 是否使用路由的history模式:

  • 这里我建议选No,这样打包出来丢到服务器上可以直接使用了,后期要用的话,也可以自己再开起来。

  • 选yes的话需要服务器那边再进行设置。

      Use history mode for router? (Requires proper server setup for index fallback in production) 
      // 路由使用history模式?(在生产环境中需要适当的服务器设置以备索引)
    

# 6. 选择Eslint代码验证规则:

> ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier

# 7. 选择什么时候进行代码规则检测:

  • 建议选保存就检测,等到commit的时候,问题可能都已经积累很多了。

  • 之前写了篇VsCode保存时自动修复Eslint错误推荐一下。

      ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
      >( ) Lint on save // 保存就检测
       ( ) Lint and fix on commit // fix和commit时候检查
    

# 8. 选择e2e测试:

? Pick a E2E testing solution: (Use arrow keys)
❯ Cypress (Chrome only) 
  Nightwatch (Selenium-based) 

# 9. 把babel,postcss,eslint这些配置文件放哪:

  • 通常我们会选择独立放置,让package.json干净些

      ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)
      > In dedicated config files // 独立文件放置
        In package.json // 放package.json里
    

# 10. 是否保存配置:

Save this as a preset for future projects? (Y/n) // 是否记录一下以便下次继续使用这套配置
// 选保存之后,会让你写一个配置的名字:
Save preset as:  name // 然后你下次进入配置可以直接使用你这次的配置了

# 11. 下载依赖

# 12. webpack配置的目录不见了:

一起来看一下新项目的结构(下图),会发现2.x的webpack配置的目录不见了,也就是没有build、config这两个文件夹了:

  • 这种方式的优势对小白来说非常友好,不会一上来就两个文件夹,一堆文件,看着脑袋都大了。

  • 然后在引用别人的配置的时候,也非常方便,直接将文件复制过来就好了。

  • 在自定义一下webpack的配置,我们需要在根目录新建一个vue.config.js文件,文件中应该导出一个对象,然后进行配置,详情查阅官方文档

      // vue.config.js
      module.exports = {
        // 选项...
      }
    
  • 还有一些小变动像:static文件夹改为public了,router文件夹变成了单个文件之类的(我之前一直这么做,嘿嘿)。

# 13.启动项目:

  • 启动项目:npm run serve // 不是之前的 npm run dev

  • 打开http://localhost:8080


# 使用图形化界面创建/管理/运行项目:

# 启动图形化界面

vue ui 
  • 这是个全局的命令 在哪个文件夹都可以打开

  • 界面(下图),重要的项目可以收藏起来(置顶):

# 创建项目和导入项目:

  1. 目录选中之后,导入项目点击下面的导入就可以了。

  2. 创建项目,填一个文件夹名字:

  3. 然后选一下预先保存好的设置就可以了,非常方便,建议采用图形界面来创建项目:

# 项目管理:

当我们点击hello -cli3项目,就会进入项目管理的界面

# 1. 仪表盘:

  • 这个仪表盘,主要是为了我们操作方便而设置的

  • 可以点击右上角的按钮,来添加/移动这些功能选项。

# 2. vue-cli3.x插件:

  • vue-cli3的插件功能,详情了解官方文档

  • cli3插件安装的过程:

    cli3插件安装的过程

# 3. 项目依赖

  • 直接在图形界面管理依赖很舒服了!

  • 安装依赖的时候,要记得选择开发依赖/运行依赖!

# 4. 项目配置

  • 可以对cli进行一些配置、Eslint规则修改:

# 5. 任务:

  • serve 运行项目,点击直接运行,再也不用输入命令了!

  • 可以清楚的看到各个模块用了多久,方便我们针对性的进行优化

  • build 打包项目:这里主要展示了图表的功能,比以前2.x生成报告更加直观,超级棒!

# 6. 其他

  • 夜间风格界面,我更喜欢这个界面

  • 直接打开编辑器,很棒了!

  • 还有一些乱七八糟的按钮


# 结语

可以说很认真了,希望大家看完能够有些收获,赶紧试试新版的vue-cli吧

以上2018.11.10

参考资料:

vue-cli3官方文档

vue-cli3.0搭建与配置

# 点个Star支持我一下~

博客链接

全屏模式轻松掌握[局部元素全屏展示] | effect

博客链接

# 全屏模式轻松掌握[局部元素全屏展示]

我第一次对网页全屏模式有概念,是那种网页播放视频的全屏播 放的那种。感觉很强,前几个星期有个需求也是关于全屏模式的,接触之后才知道全屏模式并不神秘,是个很容易掌握的技能...

博客前端积累文档公众号GitHub

# CodePen Demo

地址:演示code

进去看看,玩一下,本文将结合这个demo一起进行讲解。

# 全屏功能封装在一个类里面:

我把全屏模式封装在一个类里面,在代码中有详细的注释,如果有需要的话,直接把类拿出来,根据栗子和注释使用即可。

代码在codepen的demo里。

# 何谓全屏?

MDN介绍

使用提供的API,让一个元素与其子元素,可以占据整个屏幕,并在此期间,从屏幕上隐藏所有的浏览器用户界面以及其他应用。

chrome下的全屏表现

  1. 全屏会隐藏标签栏,书签栏

  2. 如果网页一开始不是全部撑开的形式,全屏下,也会将要全屏的元素充满整个屏幕

  3. 可以多层全屏,如栗子中一样,可以先左边全屏,然后红色全屏。

    在这种情况下退出全屏,只会退出红色全屏,退回到左边全屏的形式,所以页面依然是全屏模式。

  4. 进入全屏时,有一个默认的提示:'按esc即可退出全屏模式',如下图显示:

  5. 当按Esc或调用退出全屏方法,退出全屏。标签栏和书签栏依然是隐藏的,网页上的元素恢复成原本的尺寸

    要显示书签栏和标签栏,需要刷新一下页面。


# 全屏API:

总共用到6个API

  1. 浏览器是否支持全屏模式:document.fullscreenEnabled
  2. 使元素进入全屏模式:Element.requestFullscreen()
  3. 退出全屏:document.exitFullscreen()
  4. 检查当前是否有节点处于全屏状态:document.fullscreenElement
  5. 进入全屏/离开全屏,触发事件:document.fullscreenchange
  6. 无法进入全屏时触发: document.fullscreenerror

# 浏览器前缀:

目前并不是所有的浏览器都实现了API的无前缀版本,所以我们需要针对不同浏览器,做一下API的兼容:

这是我在demo中做的浏览器兼容:

/**
 * @description: 是否支持全屏+判断浏览器前缀
 * @param {Function} fn 不支持全屏的回调函数 这里设了一个默认值
 */
isFullscreen(fn) {
  let fullscreenEnabled;
  // 判断浏览器前缀
  if (document.fullscreenEnabled) {
    fullscreenEnabled = document.fullscreenEnabled;
  } else if (document.webkitFullscreenEnabled) {
    fullscreenEnabled = document.webkitFullscreenEnabled;
    this.prefixName = 'webkit';
  } else if (document.mozFullScreenEnabled) {
    fullscreenEnabled = document.mozFullScreenEnabled;
    this.prefixName = 'moz';
  } else if (document.msFullscreenEnabled) {
    fullscreenEnabled = document.msFullscreenEnabled;
    this.prefixName = 'ms';
  }
  if (!fullscreenEnabled) {
    if (fn !== undefined) fn(); // 执行不支持全屏的回调
    this.isFullscreenData = false;
  }
}

我在实例化的时候进行一次判断浏览器是否支持全屏,然后保存浏览器前缀

推荐这么做,因为如果每个API都要这样重复的判断浏览器前缀,那也太恶心了!

# 1. 浏览器是否支持全屏模式:document.fullscreenEnabled

document.fullscreenEnabled属性返回一个布尔值,表示当前文档是否可以切换到全屏状态。

代码在上方浏览器前缀代码中给出了。

如果没有保存浏览器前缀的话,注意做一下不同浏览器前缀的兼容!下面不再强调

# 2. 使元素进入全屏模式:Element.requestFullscreen()

/**
 * @description: 将传进来的元素全屏
 * @param {String} domName 要全屏的dom名称
 */
Fullscreen(domName) {
  const element = document.querySelector(domName); // 获取dom
  const methodName =
    this.prefixName === ''
      ? 'requestFullscreen'
      : `${this.prefixName}RequestFullScreen`; // API前缀
  element[methodName](); // 调用全屏
}

这就是我们实现全屏的API,是不是超简单?

值得注意的是:调用此API并不能保证元素一定能够进入全屏模式

  1. MDN:例如<iframe> 元素具有 allowfullscreen 属性,可选择是否将其内容以全屏模式显示

    这种不被允许全屏的元素属于极少数情况,我试过可以将button全屏。

  2. 全屏请求必须在事件处理函数(点击事件等)中调用,否则将会被拒绝。

    demo中有演示,初始化直接全屏,会触发进入全屏失败回调。

# 3. 退出全屏:document.exitFullscreen()

介绍

exitFullscreen() {
  const methodName =
    this.prefixName === ''
      ? 'exitFullscreen'
      : `${this.prefixName}ExitFullscreen`; // API 前缀
  document[methodName](); // 调用
}

调用这个方法会让文档回退到上一个调用Element.requestFullscreen()方法进入全屏模式之前的状态。

多层全屏

demo中,先进入左边全屏,再进入红色全屏,即为:多层全屏的情况(虽然这种情况并不多)。

当出现多层全屏的情况,需要一层层的退出到页面最初始的情况,并不是调用一次document.exitFullscreen()就恢复到页面最初始的样子。

# 4. 检查当前是否有节点处于全屏状态:document.fullscreenElement

fullscreenElement属性返回正处于全屏状态的Element节点,如果当前没有节点处于全屏状态,则返回null

/**
 * @description: 检测有没有元素处于全屏状态
 * @return 布尔值
 */
isElementFullScreen() {
  const fullscreenElement =
    document.fullscreenElement ||
    document.msFullscreenElement ||
    document.mozFullScreenElement ||
    document.webkitFullscreenElement; // 有前缀的f是大写,没前缀是小写
  if (fullscreenElement === null) {
    return false; // 当前没有元素在全屏状态
  } else {
    return true; // 有元素在全屏状态
  }
}

事实上,还有一个属性document.fullscreen,返回一个布尔值,表示文档是否处于全屏模式。

两个方法效果是一样,但因为IE不支持这个属性,所以这里用的是document.fullscreenElement

# 5. 进入全屏/离开全屏,触发事件:document.fullscreenchange

当我们进入全屏和离开全屏的时候,都会触发一个fullscreenchange事件。

MDN注意:此事件不会提供任何信息,表明是进入全屏或退出全屏

看了好久事件返回的信息,确实找不到一个值,表明这是在进入全屏,或者离开全屏!

可以说相当不人性化了!但我们可以通过检查当前是否有节点处于全屏状态,判断当前是否处于全屏模式。

/**
 * @description: 监听进入/离开全屏
 * @param {Function} enter 进入全屏的回调
 *  @param {Function} quit 离开全屏的回调
 */
screenChange(enter,quit) {
  if (!this.isFullscreenData) return;
  const methodName = `on${this.prefixName}fullscreenchange`;
  document[methodName] = e => {
    if (this.isElementFullScreen()) {
      enter && enter(e); // 进入全屏回调
    } else {
      quit && quit(e); // 离开全屏的回调
    }
  };
}

注意:多层全屏的情况

  1. 先进入左边全屏(进入全屏回调),再进入红色全屏(进入全屏回调)
  2. 退出全屏,此时退出红色全屏,左边仍是全屏(触发进入全屏回调)
  3. 出现这种情况,可以在点击按钮的时候,做一些状态限制。或者根据全屏事件返回的dom信息来进行判断。

# 6. 无法进入全屏时触发: document.fullscreenerror

进入全屏并不总是成功的,可能是技术原因,也可能是用户拒绝,我们在上文进入全文的APIElement.requestFullscreen()部分讲过了。

比如全屏请求不是在事件处理函数中调用,会在这里拦截到错误

/**
 * @description: 浏览器无法进入全屏时触发
 * @param {Function} enterErrorFn 回调
 */
screenError(enterErrorFn) {
  const methodName = `on${this.prefixName}fullscreenerror`;
  document[methodName] = e => {
    enterErrorFn && enterErrorFn(e)
  };
}

# Css: 全屏模式下的样式

chorme 70 下的默认会为正在全屏的dom添加两个class:稍微看一下

  1. 默认设置黑色背景
:not(:root):-webkit-full-screen::backdrop {
  position: fixed;
  top: 0px;
  right: 0px;
  bottom: 0px;
  left: 0px;
  background: black; // 会将背景设为黑色的 如果你没为你的dom设置背景的话,全屏下会为黑色
}
  1. 默认样式:
:not(:root):-webkit-full-screen {
  object-fit: contain;
  position: fixed !important;
  top: 0px !important;
  right: 0px !important;
  bottom: 0px !important;
  left: 0px !important;
  box-sizing: border-box !important;
  min-width: 0px !important;
  max-width: none !important;
  min-height: 0px !important;
  max-height: none !important;
  width: 100% !important;
  height: 100% !important;
  transform: none !important;
  margin: 0px !important;
}

全屏状态的CSS:

全屏状态下,大多数浏览器的CSS支持:full-screen伪类,只有IE11支持:fullscreen伪类。使用这个伪类,可以对全屏状态设置单独的CSS属性。

以下css摘自阮一峰老师的Fullscreen API:全屏操作

/* 针对dom的全屏设置 */
.div:-webkit-full-screen {
  background: #fff;
}
/* 全屏属性 */
:-webkit-full-screen {}
:-moz-full-screen {}
:-ms-fullscreen {}
/* 全屏伪类 当前chrome:70 不支持 */
:full-screen {
}
:fullscreen {
  /* IE11支持 */
}

# 结语

我们可以把全屏技术应用在H5游戏、信息流网站、视频等地方,下次再有全屏需求时,记住不要慌,回头看看过本文的栗子,把我封装的类拿出来直接用就可以啦!

以上2018.12.1

参考资料:

Fullscreen API:全屏操作

MDN

# 点个Star支持我一下~

博客链接

JS基础系列-重新认识call apply bind | JS

博客链接

# JS基础系列-重新认识call apply bind

函数原型链中的 apply,call 和 bind 方法是 JavaScript 中相当重要的概念,与 this 关键字密切相关,相当一部分人对它们的理解还是比较浅显,所谓js基础扎实,绕不开这些基础常用的API,这次让我们来彻底掌握它们吧!

# 目录

  1. call,apply,bind的基本介绍
  2. call/apply/bind的核心理念:借用方法
  3. call和apply的应用场景
  4. bind的应用场景
  5. 中高级面试题:手写call/apply、bind

# call,apply,bind的基本介绍

# 语法:

fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)

# 返回值:

call/apply:fun执行的结果 bind:返回fun的拷贝,并拥有指定的this值和初始参数

# 参数

thisArg(可选):

  1. funthis指向thisArg对象
  2. 非严格模式下:thisArg指定为null,undefined,fun中的this指向window对象.
  3. 严格模式下:funthisundefined
  4. 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean

param1,param2(可选): 传给fun的参数。

  1. 如果param不传或为 null/undefined,则表示不需要传入任何参数.
  2. apply第二个参数为数组,数组内的值为传给fun的参数。

# 调用call/apply/bind的必须是个函数

call、apply和bind是挂在Function对象上的三个方法,只有函数才有这些方法。

只要是函数就可以,比如: Object.prototype.toString就是个函数,我们经常看到这样的用法:Object.prototype.toString.call(data)

# 作用:

改变函数执行时的this指向,目前所有关于它们的运用,都是基于这一点来进行的。

# 如何不弄混call和aaply

弄混这两个API的不在少数,不要小看这个问题,记住下面的这个方法就好了。

apply是以a开头,它传给fun的参数是Array,也是以a开头的。

# 区别:

# call与apply的唯一区别

传给fun的参数写法不同:

  • apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。
  • call从第2~n的参数都是传给fun的。

# call/apply与bind的区别

执行

  • call/apply改变了函数的this上下文后马上执行该函数
  • bind则是返回改变了上下文后的函数,不执行该函数

返回值:

  • call/apply 返回fun的执行结果
  • bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。

返回值这段在下方bind应用中有详细的示例解析。

# call/apply/bind的核心理念:借用方法

看到一个非常棒的例子

生活中:

平时没时间做饭的我,周末想给孩子炖个腌笃鲜尝尝。但是没有适合的锅,而我又不想出去买。所以就问邻居借了一个锅来用,这样既达到了目的,又节省了开支,一举两得。

程序中:

A对象有个方法,B对象因为某种原因也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?

当然是借用 A 对象的方法啦,既达到了目的,又节省了内存。

这就是call/apply/bind的核心理念:借用方法

借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。

# call和apply的应用场景:

这些应用场景,多加体会就可以发现它们的理念都是:借用方法

  1. 判断数据类型:

Object.prototype.toString用来判断类型再合适不过,借用它我们几乎可以判断所有类型的数据:

function isType(data, type) {
    const typeObj = {
        '[object String]': 'string',
        '[object Number]': 'number',
        '[object Boolean]': 'boolean',
        '[object Null]': 'null',
        '[object Undefined]': 'undefined',
        '[object Object]': 'object',
        '[object Array]': 'array',
        '[object Function]': 'function',
        '[object Date]': 'date', // Object.prototype.toString.call(new Date())
        '[object RegExp]': 'regExp',
        '[object Map]': 'map',
        '[object Set]': 'set',
        '[object HTMLDivElement]': 'dom', // document.querySelector('#app')
        '[object WeakMap]': 'weakMap',
        '[object Window]': 'window',  // Object.prototype.toString.call(window)
        '[object Error]': 'error', // new Error('1')
        '[object Arguments]': 'arguments',
    }
    let name = Object.prototype.toString.call(data) // 借用Object.prototype.toString()获取数据类型
    let typeName = typeObj[name] || '未知类型' // 匹配数据类型
    return typeName === type // 判断该数据类型是否为传入的类型
}
console.log(
    isType({}, 'object'), // true
    isType([], 'array'), // true
    isType(new Date(), 'object'), // false
    isType(new Date(), 'date'), // true
)
  1. 类数组借用数组的方法:

类数组因为不是真正的数组所有没有数组类型上自带的种种方法,所以我们需要去借用数组的方法。

比如借用数组的push方法:

var arrayLike = {
  0: 'OB',
  1: 'Koro1',
  length: 2
}
Array.prototype.push.call(arrayLike, '添加元素1', '添加元素2');
console.log(arrayLike) // {"0":"OB","1":"Koro1","2":"添加元素1","3":"添加元素2","length":4}
  1. apply获取数组最大值最小值:

apply直接传递数组做要调用方法的参数,也省一步展开数组,比如使用Math.maxMath.min来获取数组的最大值/最小值:

const arr = [15, 6, 12, 13, 16];
const max = Math.max.apply(Math, arr); // 16
const min = Math.min.apply(Math, arr); // 6
  1. 继承

ES5的继承也都是通过借用父类的构造方法来实现父类方法/属性的继承:

// 父类
function supFather(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green']; // 复杂类型
}
supFather.prototype.sayName = function (age) {
    console.log(this.name, 'age');
};
// 子类
function sub(name, age) {
    // 借用父类的方法:修改它的this指向,赋值父类的构造函数里面方法、属性到子类上
    supFather.call(this, name);
    this.age = age;
}
// 重写子类的prototype,修正constructor指向
function inheritPrototype(sonFn, fatherFn) {
    sonFn.prototype = Object.create(fatherFn.prototype); // 继承父类的属性以及方法
    sonFn.prototype.constructor = sonFn; // 修正constructor指向到继承的那个函数上
}
inheritPrototype(sub, supFather);
sub.prototype.sayAge = function () {
    console.log(this.age, 'foo');
};
// 实例化子类,可以在实例上找到属性、方法
const instance1 = new sub("OBKoro1", 24);
const instance2 = new sub("小明", 18);
instance1.colors.push('black')
console.log(instance1) // {"name":"OBKoro1","colors":["red","blue","green","black"],"age":24}
console.log(instance2) // {"name":"小明","colors":["red","blue","green"],"age":18} 

类似的应用场景还有很多,就不赘述了,关键在于它们借用方法的理念,不理解的话多看几遍。

# call、apply,该用哪个?、

call,apply的效果完全一样,它们的区别也在于

  • 参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply
  • 考虑可读性:参数数量不多就用apply,参数数量比较多的话,把参数整合成数组,使用apply。
  • 参数集合已经是一个数组的情况,用apply,比如上文的获取数组最大值/最小值。

参数数量/顺序不确定的话就用apply,比如以下示例:

const obj = {
    age: 24,
    name: 'OBKoro1',
}
const obj2 = {
    age: 777
}
callObj(obj, handle)
callObj(obj2, handle)
// 根据某些条件来决定要传递参数的数量、以及顺序
function callObj(thisAge, fn) {
    let params = []
    if (thisAge.name) {
        params.push(thisAge.name)
    }
    if (thisAge.age) {
        params.push(thisAge.age)
    }
    fn.apply(thisAge, params) // 数量和顺序不确定 不能使用call
}
function handle(...params) {
    console.log('params', params) // do some thing
}

# bind的应用场景:

# 1. 保存函数参数:

首先来看下一道经典的面试题:

for (var i = 1; i <= 5; i++) {
   setTimeout(function test() {
        console.log(i) // 依次输出:6 6 6 6 6
    }, i * 1000);
}

造成这个现象的原因是等到setTimeout异步执行时,i已经变成6了。

关于js事件循环机制不理解的同学,可以看我这篇博客:Js 的事件循环(Event Loop)机制以及实例讲解

那么如何使他输出: 1,2,3,4,5呢?

方法有很多:

  • 闭包, 保存变量
for (var i = 1; i <= 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log('闭包:', i); // 依次输出:1 2 3 4 5
        }, i * 1000);
    }(i));
}

在这里创建了一个闭包,每次循环都会把i的最新值传进去,然后被闭包保存起来。

  • bind
for (var i = 1; i <= 5; i++) {
    // 缓存参数
    setTimeout(function (i) {
        console.log('bind', i) // 依次输出:1 2 3 4 5
    }.bind(null, i), i * 1000);
}

实际上这里也用了闭包,我们知道bind会返回一个函数,这个函数也是闭包

它保存了函数的this指向、初始参数,每次i的变更都会被bind的闭包存起来,所以输出1-5。

具体细节,下面有个手写bind方法,研究一下,就能搞懂了。

  • let

let声明i也可以输出1-5: 因为let是块级作用域,所以每次都会创建一个新的变量,所以setTimeout每次读的值都是不同的,详解

# 2. 回调函数this丢失问题:

这是一个常见的问题,下面是我在开发VSCode插件处理webview通信时,遇到的真实问题,一开始以为VSCode的API哪里出问题,调试了一番才发现是this指向丢失的问题。

class Page {
    constructor(callBack) {
        this.className = 'Page'
        this.MessageCallBack = callBack // 
        this.MessageCallBack('发给注册页面的信息') // 执行PageA的回调函数
    }
}
class PageA {
    constructor() {
        this.className = 'PageA'
        this.pageClass = new Page(this.handleMessage) // 注册页面 传递回调函数 问题在这里
    }
    // 与页面通信回调
    handleMessage(msg) {
        console.log('处理通信', this.className, msg) //  'Page' this指向错误
    }
}
new PageA()

# 回调函数this为何会丢失?

显然声明的时候不会出现问题,执行回调函数的时候也不可能出现问题。

问题出在传递回调函数的时候:

this.pageClass = new Page(this.handleMessage)

因为传递过去的this.handleMessage是一个函数内存地址,没有上下文对象,也就是说该函数没有绑定它的this指向。

那它的this指向于它所应用的绑定规则

class Page {
    constructor(callBack) {
        this.className = 'Page'
        // callBack() // 直接执行的话 由于class 内部是严格模式,所以this 实际指向的是 undefined
        this.MessageCallBack = callBack // 回调函数的this 隐式绑定到class page
        this.MessageCallBack('发给注册页面的信息')
    }
}

既然知道问题了,那我们只要绑定回调函数的this指向为PageA就解决问题了。

回调函数this丢失的解决方案

  1. bind绑定回调函数的this指向:

这是典型bind的应用场景, 绑定this指向,用做回调函数。

this.pageClass = new Page(this.handleMessage.bind(this)) // 绑定回调函数的this指向

PS: 这也是为什么reactrender函数在绑定回调函数的时候,也要使用bind绑定一下this的指向,也是因为同样的问题以及原理。

  1. 箭头函数绑定this指向

箭头函数的this指向定义的时候外层第一个普通函数的this,在这里指的是class类:PageA

这块内容,可以看下我之前写的博客:详解箭头函数和普通函数的区别以及箭头函数的注意事项、不适用场景

this.pageClass = new Page(() => this.handleMessage()) // 箭头函数绑定this指向

# 中高级面试题-手写call/apply、bind:

在大厂的面试中,手写实现call,apply,bind(特别是bind)一直是比较高频的面试题,在这里我们也一起来实现一下这几个函数。

# 你能手写实现一个call吗?

思路

  1. 根据call的规则设置上下文对象,也就是this的指向。
  2. 通过设置context的属性,将函数的this指向隐式绑定到context上
  3. 通过隐式绑定执行函数并传递参数。
  4. 删除临时属性,返回函数执行结果
Function.prototype.myCall = function (context, ...arr) {
    if (context === null || context === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        context = window 
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    context.testFn = this; // 函数的this指向隐式绑定到context上
    let result = context.testFn(...arr); // 通过隐式绑定执行函数并传递参数
    delete context.testFn; // 删除上下文对象的属性
    return result; // 返回函数执行结果
};

# 判断函数的上下文对象:

很多人判断函数上下文对象,只是简单的以context是否为false来判断,比如:

// 判断函数上下文绑定到`window`不够严谨
context = context ? Object(context) : window; 
context = context || window; 

经过测试,以下三种为false的情况,函数的上下文对象都会绑定到window上:

// 网上的其他绑定函数上下文对象的方案: context = context || window; 
function handle(...params) {
    this.test = 'handle'
    console.log('params', this, ...params) // do some thing
}
handle.elseCall('') // window
handle.elseCall(0) // window
handle.elseCall(false) // window

call则将函数的上下文对象会绑定到这些原始值的实例对象上:

原始值的实例对象

所以正确的解决方案,应该是像我上面那么做:

// 正确判断函数上下文对象
    if (context === null || context === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        context = window 
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }

# 你能手写实现一个apply吗?

思路:

  1. 传递给函数的参数处理,不太一样,其他部分跟call一样。
  2. apply接受第二个参数为类数组对象, 这里用了JavaScript权威指南中判断是否为类数组对象的方法。
Function.prototype.myApply = function (context) {
    if (context === null || context === undefined) {
        context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    // JavaScript权威指南判断是否为类数组对象
    function isArrayLike(o) {
        if (o &&                                    // o不是null、undefined等
            typeof o === 'object' &&                // o是对象
            isFinite(o.length) &&                   // o.length是有限数值
            o.length >= 0 &&                        // o.length为非负值
            o.length === Math.floor(o.length) &&    // o.length是整数
            o.length < 4294967296)                  // o.length < 2^32
            return true
        else
            return false
    }
    context.testFn = this; // 隐式绑定this指向到context上
    const args = arguments[1]; // 获取参数数组
    let result
    // 处理传进来的第二个参数
    if (args) {
        // 是否传递第二个参数
        if (!Array.isArray(args) && !isArrayLike(args)) {
            throw new TypeError('myApply 第二个参数不为数组并且不为类数组对象抛出错误');
        } else {
            args = Array.from(args) // 转为数组
            result = context.testFn(...args); // 执行函数并展开数组,传递函数参数
        }
    } else {
        result = context.testFn(); // 执行函数
    }
    delete context.testFn; // 删除上下文对象的属性
    return result; // 返回函数执行结果
};

# 你能手写实现一个bind吗?

划重点

手写bind是大厂中的一个高频的面试题,如果面试的中高级前端,只是能说出它们的区别,用法并不能脱颖而出,理解要有足够的深度才能抱得offer归!

思路

  1. 拷贝源函数:
    • 通过变量储存源函数
    • 使用Object.create复制源函数的prototype给fToBind
  2. 返回拷贝的函数
  3. 调用拷贝的函数:
    • new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
    • 绑定this+传递参数
    • 返回源函数的执行结果
Function.prototype.myBind = function (objThis, ...params) {
    const thisFn = this; // 存储源函数以及上方的params(函数参数)
    let fToBind = function () {
        const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
        const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
        return thisFn.apply(context, params); // 用apply调用源函数绑定this的指向并传递参数,返回执行结果
    };
    fToBind.prototype = Object.create(thisFn.prototype); // 复制源函数的prototype给fToBind
    return fToBind; // 返回拷贝的函数
};

# 小结

本来以为这篇会写的很快,结果断断续续的写了好几天,终于把这三个API相关知识介绍清楚了,希望大家看完之后,面试的时候再遇到这个问题,就可以海陆空全方位的装逼了^_^

以上2019/8/30

# 点个Star支持我一下~

博客链接

谈论HTTP缓存时我们在谈论什么 | tool

博客链接

# 谈论HTTP缓存时我们在谈论什么

在浏览器众多缓存中的HTTP缓存可能很多人对这个的概念并没有很清晰,每个人都知道进入一次网页之后再刷新一次页面,加载速度会比首次加载快非常多,每个人都知道这是浏览器缓存的magic,但是对此背后的原因可能不甚了解...

# 当我们在谈论HTTP缓存时我们在谈论什么:

我们实际上是在谈论下面这两种情况:

如上图,浏览器对静态资源的HTTP缓存有两种情况,一种是强缓存(本地缓存),另一种是弱缓存(协商缓存)。


# 缓存流程:

# 浏览器第一次请求资源时:

图片出自网络

浏览器第一次请求资源时,必须下载所有的资源,然后根据响应的header内容来决定,如何缓存资源。可能采用的是强缓存,也可能是弱缓存

# 浏览器后续请求资源时的匹配流程:

由上图可以知道当浏览器请求一个静态资源时的HTTP流程:

  1. 强缓存阶段:先在本地查找该资源,如果发现该资源,并且其他限制也没有问题(比如:缓存有效时间),就命中强缓存,返回200,直接使用强缓存,并且不会发送请求到服务器
  2. 弱缓存阶段:在本地缓存中找到该资源,发送一个http请求到服务器,服务器判断这个资源没有被改动过,则返回304,让浏览器使用该资源。
  3. 缓存失败阶段(重新请求):当服务器发现该资源被修改过,或者在本地没有找到该缓存资源,服务器则返回该资源的数据。

# 强缓存与弱缓存的区别:

获取资源形式: 都是从缓存中获取资源的。

状态码: 强缓存返回200(from cache),弱缓存返回304状态码

请求(最大区别)

强缓存不发送请求,直接从缓存中取。

弱缓存需要发送一个请求,验证这个文件是否可以使用(有没有被改动过)。


# 强缓存:

强缓存是利用Expires或者Cache-Control,让原始服务器为文件设置一个过期时间,在多长时间内可以将这些内容视为最新的。

若时间未过期,则命中强缓存,使用缓存文件不发送请求。

# Cache-Control

Cache-Control 是http1.1中为了弥补Expires的缺陷而加入的,当Expires和Cache-Control同时存在时,Cache-Control优先级高于Expires。

选项

可缓存性:

public: 服务器端和浏览器端都能缓存

private: 只能浏览器端缓存

no-cache: 强制浏览器在使用cache拷贝之前先提交一个http请求到源服务器进行确认。http请求没有减少,会减少一个响应体(文件内容),这种个选项类似弱缓存。

only-if-cached: 表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝。

到期设置:

max-age=60:设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。 这里是60秒

其他设置:

no-store: 不缓存,使用协商缓存

must-revalidate: 缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。

更多设置,移动MDN

// 示例
Cache-Control: no-cache, no-store, must-revalidate
Cache-Control:public, max-age=31536000
Cache-Control: max-age=3600, must-revalidate

# http1.0时代的缓存 Expires+Pragma

Expires用于设置缓存到期时间

指定缓存到期GMT的绝对时间,如果设了max-age,max-age就会覆盖expires,如果expires到期需要重新请求。

Expires:Sat, 09 Jun 2018 08:13:56 GMT

有一个问题是由于使用具体时间,如果时间表示出错或者没有转换到正确的时区都可能造成缓存生命周期出错。

Pragma禁用缓存:

Pragma : no-cache 表示防止客户端缓存,需要强制从服务器获取最新的数据;

Pragma : no-cache  //只有这一个用法 禁用缓存,强制从服务器获取最新的数据; 

# 强缓存命中 from memory cache & from disk cache

在测试的时候,看到命中强缓存时,有两种状态,200 (from memory cache) cache & 200 (from disk cache),于是去找了一下这两者的区别:

memory cache: 将资源存到内存中,从内存中获取。

disk cache:将资源缓存到磁盘中,从磁盘中获取。

二者最大的区别在于:当退出进程时,内存中的数据会被清空,而磁盘的数据不会

更详细的介绍推荐这篇文章


# 弱缓存:

如果强缓存时间过期,或者没有设置,导致未命中的话。就进入到了弱缓存的阶段了,

Last-Modified & if-modified-since:

Last-Modified与If-Modified-Since是一对报文头,属于http 1.0。

last-modified是web服务器认为文件的最后修改时间,last-modified是第一次请求文件的时候,服务器返回的一个属性。

Last-Modified: Sat, 09 Jun 2018 08:13:56 GMT 

第二次请求这个文件时,浏览器把If-Modified-Since发送给服务器,询问该时间之后文件是否被修改过。

If-Modified-Since: Sat, 09 Jun 2018 08:13:56 GMT // 跟Last-Modified的值一样

ETag & If-None-Match

ETag与If-None-Match是一对报文,属于http 1.1。

ETag是一个文件的唯一标志符。就像一个哈希或者指纹,每个文件都有一个单独的标志,只要这个文件发生了改变,这个标志就会发生变化。

ETag机制类似于乐观锁机制,如果请求报文的ETag与服务器的不一致,则表示该资源已经被修改过来,需要发最新的内容给浏览器。

ETag也是首次请求的时候,服务器返回的:

ETag: "8F759D4F67D66A7244638AD249675BE2" // 长这样

If-None-Match也是浏览器发送到服务器验证,文件是否改变的:

If-None-Match: "8F759D4F67D66A7244638AD249675BE2" // 跟ETag的值一样

# Etag/lastModified过程如下:

  1. 客户端第一次向服务器发起请求,服务器将附加Last-Modified/ETag到所提供的资源上去
  2. 当再一次请求资源,如果没有命中强缓存,在执行在验证时,将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器
  3. 服务器检查该Last-Modified或ETag,并判断出该资源页面自上次客户端请求之后还未被修改,返回响应304和一个空的响应体

# 同时使用两个报文头:

同时使用这两个报文头,两个都匹配才会命中弱缓存,否则将重新请求资源。

# Etag 主要为了解决 Last-Modified 无法解决的一些问题:

  1. 一些文件也许内容并不改变(仅仅改变的修改时间),这个时候我们不希望文件重新加载。(Etag值会触发缓存,Last-Modified不会触发)
  2. If-Modified-Since能检查到的粒度是秒级的,当修改非常频繁时,Last-Modified会触发缓存,而Etag的值不会触发,重新加载。
  3. 某些服务器不能精确的得到文件的最后修改时间。

# 用户操作行为与缓存

F5刷新导致强缓存失效。

ctrl+F5强制刷新页面强缓存,弱缓存都会失效。

图片出自网络

# 如何设置?

一般是服务器端设置这些请求头的,我自己试了用阿里云服务器设置Cache-Control,设置一下很方便的。


# 小结

通过网络重复请求资源既缓慢,成本又高,缓存和重用以前获取的资源的能力成为优化性能很关键的一个方面,也是大厂面试时很频繁出现的内容,掌握好这块知识点是非常重要的,希望本文能给你带来些收获。

文章如有不正确的地方欢迎各位路过的大佬鞭策!喜欢的话,赶紧点波订阅关注/喜欢。

# 点个Star支持我一下~

博客链接

前端er来学习一下webWorker吧 | JS

博客链接

# 前端er来学习一下webWorker吧

我们都知道,JavaScript 是单线程的,在同一时刻只能处理一个任务,我们会通过 setTimeout()、setInterval()、ajax 和事件处理程序等技术模拟“并行”。但都不是真正意义上的并行:

Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,它允许一段 JavaScript 程序运行在主线程之外的另外一个线程中。

这在很大程度上利用了现在不断升级的电脑计算能力:能够在同一时间平行处理两个任务。

游泳、健身了解一下:博客前端积累文档公众号GitHub

# 场景

当我们有些任务需要花费大量的时间,进行复杂的运算,就会导致页面卡死:用户点击页面需要很长的时间才能响应,因为前面的任务还未完成,后面的任务只能排队等待。对用户来说,这样的体验无疑是糟糕的,web worker 就是为了解决这种花费大量时间的复杂运算而诞生的!

# WebWorker 的作用:创建 worker 线程

WebWorker 允许在主线程之外再创建一个 worker 线程,在主线程执行任务的同时,worker 线程也可以在后台执行它自己的任务,互不干扰

这样就让 JS 变成多线程的环境了,我们可以把高延迟、花费大量时间的运算,分给 worker 线程,最后再把结果返回给主线程就可以了,因为时间花费多的任务被 web worker 承担了,主线程就会很流畅了!


# 主线程

# 我们先来看一下栗子:

codepen,这里我写了一个 class,里面有详细注释,可以参考一下。

# 创建 worker 对象:

主线程调用new Worker()构造函数,新建一个 worker 线程,构造函数的参数是一个 url,生成这个 url 的方法有两种:

  1. 脚本文件:

    const worker = new Worker('https://~.js');
    

    因为 worker 的两个限制:

    1. 分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源

    2. worker 不能读取本地的文件(不能打开本机的文件系统file://),它所加载的脚本必须来自网络。

    可以看到限制还是比较多的,如果要使用这种形式的话,在项目中推荐把文件放在静态文件夹中,打包的时候直接拷贝进去,这样我们就可以拿到固定的链接了,

  2. 字符串形式:

    const data = `
        //  worker线程 do something
        `;
    // 转成二进制对象
    const blob = new Blob([data]);
    // 生成url
    const url = window.URL.createObjectURL(blob);
    // 加载url
    const worker = new Worker(url);
    

    栗子中就是使用这种形式的,方便我们演示。

    在项目中:我们可以把worker线程的逻辑写在js文件里面,然后字符串化,然后再export、import,配合webpack进行模块化管理,这样就很容易使用了。

# 主线程的其他 API:

# 1. 主线程与 worker 线程通信:

worker.postMessage({
  hello: ['hello', 'world']
});

它们相互之间的通信可以传递对象和数组,这样我们就可以根据相互之间传递的信息来进行一些操作,比如可以设置一个type属性,当值为hello时执行什么函数,当值为world的时候执行什么函数。

值得注意的是:它们之间通信是通过拷贝的形式来传递数据的,进行传递的对象需要经过序列化,接下来在另一端还需要反序列化。这就意味着:

  1. 我们不能传递不能被序列化的数据,比如函数,会抛出错误的。
  2. 在一端改变数据,另外一端不会受影响,因为数据不存在引用,是拷贝过来的。

# 2. 监听 worker 线程返回的信息

worker.onmessage = function (e) {
    console.log('父进程接收的数据:', e.data);
    // doSomething();
}

# 3. 主线程关闭 worker 线程

Worker 线程一旦新建成功,就会始终运行,这样有利于随时响应主线程的通信。

这也是 Worker 比较耗费计算机的计算资源(CPU)的原因,一旦使用完毕,就应该关闭 worker 线程。

worker.terminate(); // 主线程关闭worker线程

# 4. 监听错误

// worker线程报错
worker.onerror = e => {
    // e.filename - 发生错误的脚本文件名;e.lineno - 出现错误的行号;以及 e.message - 可读性良好的错误消息
    console.log('onerror', e);
};

也可以像我给出的栗子一样,把两个报错放在一起写,有报错把信息传出来就好了。


# Worker 线程

# self 代表 worker 进程自身

worker 线程的执行上下文是一个叫做WorkerGlobalScope的东西跟主线程的上下文(window)不一样。

我们可以使用self/WorkerGlobalScope来访问全局对象。

# 监听主线程传过来的信息:

self.onmessage = e => {
    console.log('主线程传来的信息:', e.data);
    // do something
};

# 发送信息给主线程

self.postMessage({
    hello: [ '这条信息', '来自worker线程' ]
});

# worker 线程关闭自身

self.close()

# worker 线程加载脚本:

Worker 线程能够访问一个全局函数 imprtScripts()来引入脚本,该函数接受 0 个或者多个 URI 作为参数。

importScripts('http~.js','http~2.js');
  1. 脚本中的全局变量都能被 worker 线程使用。

  2. 脚本的下载顺序是不固定的,但执行时会按照传入 importScripts() 中的文件名顺序进行,这个过程是同步的。

# Worker 线程限制

因为 worker 创造了另外一个线程,不在主线程上,相应的会有一些限制,我们无法使用下列对象:

  1. window 对象
  2. document 对象
  3. DOM 对象
  4. parent 对象

我们可以使用下列对象/功能

  1. 浏览器:navigator 对象

  2. URL:location 对象,只读

  3. 发送请求:XMLHttpRequest 对象

  4. 定时器:setTimeout/setInterval,在 worker 线程轮询也是很棒!

  5. 应用缓存:Application Cache


# 多个 worker 线程

  1. 在主线程内可以创建多个 worker 线程

    栗子最下方有。

  2. worker 线程内还可以新建 worker 线程,使用同源的脚本文件创建

    在 worker 线程内再新建 worker 线程就不能使用window.URL.createObjectURL(blob),需要使用同源的脚本文件来创建新的 worker 线程,因为我们无法访问到window对象。

    这里不方便演示,跟在主线程创建 worker 线程是一个套路,只是改成了脚本文件形式创建 worker 线程。

# 线程间转移二进制数据

因为主线程与 worker 线程之间的通信是拷贝关系,当我们要传递一个巨大的二进制文件给 worker 线程处理时(worker 线程就是用来干这个的),这时候使用拷贝的方式来传递数据,无疑会造成性能问题。

幸运的是,Web Worker 提供了一中转移数据的方式,允许主线程把二进制数据直接转移给子线程。这种方式比原先拷贝的方式,有巨大的性能提升。

一旦数据转移到其他线程,原先线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面

下方栗子出自浅谈 HTML5 Web Worker

// 创建二进制数据
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
    uInt8Array[i] = i;
}
console.log(uInt8Array.length); // 传递前长度:33554432
// 字符串形式创建worker线程
var myTask = `
    onmessage = function (e) {
        var data = e.data;
        console.log('worker:', data);
    };
`;

var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));

// 使用这个格式(a,[a]) 来转移二进制数据
myWorker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]); // 发送数据、转移数据

console.log(uInt8Array.length); // 传递后长度:0,原先线程内没有这个数据了

二进制数据有:File、Blob、ArrayBuffer 等类型,也允许在 worker 线程之间发送,这对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担

# 应用场景:

  1. 数学运算

  2. 图像、影音等文件处理

  3. 大量数据检索

    比如用户输入时,我们在后台检索答案,或者帮助用户联想,纠错等操作.

  4. 耗时任务都丢到 webworker 解放我们的主线程。

# 兼容:

没有找到具体的制定日期,有篇博客是在 10 年的 7 月份写的,也就是说 web worker 至少出现了八年了,以下兼容摘自MDN

Chrome:4, Firefox:3.5, IE:10.0, Opera:10.6, Safari:4

现在兼容还是做的比较好的,如果实在不放心的话:

if (window.Worker) {
    ...
}else{
    ...
}

# 结语:

Web Worker的出现,给浏览器带来了后台计算的能力,把耗时的任务分配给worker线程来做,在很大程度上缓解了主线程UI渲染阻塞的问题,提升页面性能。

使用起来也不复杂,以后有复杂的问题,记得要丢给我们浏览器的后台(web worker)来处理

看完之后,一定要研究一下文中的栗子,自己鼓捣鼓捣,实践出真知!

PS: 推荐一下我上个月写的手摸手教你使用WebSocket,感兴趣的可以看一下。

以上2018.11.25

参考资料:

MDN

Web Worker 使用教程

浅谈HTML5 Web Worker

# 点个Star支持我一下~

博客链接

JS基础系列-重新认识call apply bind | JS

博客链接

# JS基础系列-重新认识call apply bind

函数原型链中的 apply,call 和 bind 方法是 JavaScript 中相当重要的概念,与 this 关键字密切相关,相当一部分人对它们的理解还是比较浅显,所谓js基础扎实,绕不开这些基础常用的API,这次让我们来彻底掌握它们吧!

# 目录

  1. call,apply,bind的基本介绍
  2. call/apply/bind的核心理念:借用方法
  3. call和apply的应用场景
  4. bind的应用场景
  5. 中高级面试题:手写call/apply、bind

# call,apply,bind的基本介绍

# 语法:

fun.call(thisArg, param1, param2, ...)
fun.apply(thisArg, [param1,param2,...])
fun.bind(thisArg, param1, param2, ...)

# 返回值:

call/apply:fun执行的结果 bind:返回fun的拷贝,并拥有指定的this值和初始参数

# 参数

thisArg(可选):

  1. funthis指向thisArg对象
  2. 非严格模式下:thisArg指定为null,undefined,fun中的this指向window对象.
  3. 严格模式下:funthisundefined
  4. 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 String、Number、Boolean

param1,param2(可选): 传给fun的参数。

  1. 如果param不传或为 null/undefined,则表示不需要传入任何参数.
  2. apply第二个参数为数组,数组内的值为传给fun的参数。

# 调用call/apply/bind的必须是个函数

call、apply和bind是挂在Function对象上的三个方法,只有函数才有这些方法。

只要是函数就可以,比如: Object.prototype.toString就是个函数,我们经常看到这样的用法:Object.prototype.toString.call(data)

# 作用:

改变函数执行时的this指向,目前所有关于它们的运用,都是基于这一点来进行的。

# 如何不弄混call和aaply

弄混这两个API的不在少数,不要小看这个问题,记住下面的这个方法就好了。

apply是以a开头,它传给fun的参数是Array,也是以a开头的。

# 区别:

# call与apply的唯一区别

传给fun的参数写法不同:

  • apply是第2个参数,这个参数是一个数组:传给fun参数都写在数组中。
  • call从第2~n的参数都是传给fun的。

# call/apply与bind的区别

执行

  • call/apply改变了函数的this上下文后马上执行该函数
  • bind则是返回改变了上下文后的函数,不执行该函数

返回值:

  • call/apply 返回fun的执行结果
  • bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。

返回值这段在下方bind应用中有详细的示例解析。

# call/apply/bind的核心理念:借用方法

看到一个非常棒的例子

生活中:

平时没时间做饭的我,周末想给孩子炖个腌笃鲜尝尝。但是没有适合的锅,而我又不想出去买。所以就问邻居借了一个锅来用,这样既达到了目的,又节省了开支,一举两得。

程序中:

A对象有个方法,B对象因为某种原因也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?

当然是借用 A 对象的方法啦,既达到了目的,又节省了内存。

这就是call/apply/bind的核心理念:借用方法

借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。

# call和apply的应用场景:

这些应用场景,多加体会就可以发现它们的理念都是:借用方法

  1. 判断数据类型:

Object.prototype.toString用来判断类型再合适不过,借用它我们几乎可以判断所有类型的数据:

function isType(data, type) {
    const typeObj = {
        '[object String]': 'string',
        '[object Number]': 'number',
        '[object Boolean]': 'boolean',
        '[object Null]': 'null',
        '[object Undefined]': 'undefined',
        '[object Object]': 'object',
        '[object Array]': 'array',
        '[object Function]': 'function',
        '[object Date]': 'date', // Object.prototype.toString.call(new Date())
        '[object RegExp]': 'regExp',
        '[object Map]': 'map',
        '[object Set]': 'set',
        '[object HTMLDivElement]': 'dom', // document.querySelector('#app')
        '[object WeakMap]': 'weakMap',
        '[object Window]': 'window',  // Object.prototype.toString.call(window)
        '[object Error]': 'error', // new Error('1')
        '[object Arguments]': 'arguments',
    }
    let name = Object.prototype.toString.call(data) // 借用Object.prototype.toString()获取数据类型
    let typeName = typeObj[name] || '未知类型' // 匹配数据类型
    return typeName === type // 判断该数据类型是否为传入的类型
}
console.log(
    isType({}, 'object'), // true
    isType([], 'array'), // true
    isType(new Date(), 'object'), // false
    isType(new Date(), 'date'), // true
)
  1. 类数组借用数组的方法:

类数组因为不是真正的数组所有没有数组类型上自带的种种方法,所以我们需要去借用数组的方法。

比如借用数组的push方法:

var arrayLike = {
  0: 'OB',
  1: 'Koro1',
  length: 2
}
Array.prototype.push.call(arrayLike, '添加元素1', '添加元素2');
console.log(arrayLike) // {"0":"OB","1":"Koro1","2":"添加元素1","3":"添加元素2","length":4}
  1. apply获取数组最大值最小值:

apply直接传递数组做要调用方法的参数,也省一步展开数组,比如使用Math.maxMath.min来获取数组的最大值/最小值:

const arr = [15, 6, 12, 13, 16];
const max = Math.max.apply(Math, arr); // 16
const min = Math.min.apply(Math, arr); // 6
  1. 继承

ES5的继承也都是通过借用父类的构造方法来实现父类方法/属性的继承:

// 父类
function supFather(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green']; // 复杂类型
}
supFather.prototype.sayName = function (age) {
    console.log(this.name, 'age');
};
// 子类
function sub(name, age) {
    // 借用父类的方法:修改它的this指向,赋值父类的构造函数里面方法、属性到子类上
    supFather.call(this, name);
    this.age = age;
}
// 重写子类的prototype,修正constructor指向
function inheritPrototype(sonFn, fatherFn) {
    sonFn.prototype = Object.create(fatherFn.prototype); // 继承父类的属性以及方法
    sonFn.prototype.constructor = sonFn; // 修正constructor指向到继承的那个函数上
}
inheritPrototype(sub, supFather);
sub.prototype.sayAge = function () {
    console.log(this.age, 'foo');
};
// 实例化子类,可以在实例上找到属性、方法
const instance1 = new sub("OBKoro1", 24);
const instance2 = new sub("小明", 18);
instance1.colors.push('black')
console.log(instance1) // {"name":"OBKoro1","colors":["red","blue","green","black"],"age":24}
console.log(instance2) // {"name":"小明","colors":["red","blue","green"],"age":18} 

类似的应用场景还有很多,就不赘述了,关键在于它们借用方法的理念,不理解的话多看几遍。

# call、apply,该用哪个?、

call,apply的效果完全一样,它们的区别也在于

  • 参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply
  • 考虑可读性:参数数量不多就用apply,参数数量比较多的话,把参数整合成数组,使用apply。
  • 参数集合已经是一个数组的情况,用apply,比如上文的获取数组最大值/最小值。

参数数量/顺序不确定的话就用apply,比如以下示例:

const obj = {
    age: 24,
    name: 'OBKoro1',
}
const obj2 = {
    age: 777
}
callObj(obj, handle)
callObj(obj2, handle)
// 根据某些条件来决定要传递参数的数量、以及顺序
function callObj(thisAge, fn) {
    let params = []
    if (thisAge.name) {
        params.push(thisAge.name)
    }
    if (thisAge.age) {
        params.push(thisAge.age)
    }
    fn.apply(thisAge, params) // 数量和顺序不确定 不能使用call
}
function handle(...params) {
    console.log('params', params) // do some thing
}

# bind的应用场景:

# 1. 保存函数参数:

首先来看下一道经典的面试题:

for (var i = 1; i <= 5; i++) {
   setTimeout(function test() {
        console.log(i) // 依次输出:6 6 6 6 6
    }, i * 1000);
}

造成这个现象的原因是等到setTimeout异步执行时,i已经变成6了。

关于js事件循环机制不理解的同学,可以看我这篇博客:Js 的事件循环(Event Loop)机制以及实例讲解

那么如何使他输出: 1,2,3,4,5呢?

方法有很多:

  • 闭包, 保存变量
for (var i = 1; i <= 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log('闭包:', i); // 依次输出:1 2 3 4 5
        }, i * 1000);
    }(i));
}

在这里创建了一个闭包,每次循环都会把i的最新值传进去,然后被闭包保存起来。

  • bind
for (var i = 1; i <= 5; i++) {
    // 缓存参数
    setTimeout(function (i) {
        console.log('bind', i) // 依次输出:1 2 3 4 5
    }.bind(null, i), i * 1000);
}

实际上这里也用了闭包,我们知道bind会返回一个函数,这个函数也是闭包

它保存了函数的this指向、初始参数,每次i的变更都会被bind的闭包存起来,所以输出1-5。

具体细节,下面有个手写bind方法,研究一下,就能搞懂了。

  • let

let声明i也可以输出1-5: 因为let是块级作用域,所以每次都会创建一个新的变量,所以setTimeout每次读的值都是不同的,详解

# 2. 回调函数this丢失问题:

这是一个常见的问题,下面是我在开发VSCode插件处理webview通信时,遇到的真实问题,一开始以为VSCode的API哪里出问题,调试了一番才发现是this指向丢失的问题。

class Page {
    constructor(callBack) {
        this.className = 'Page'
        this.MessageCallBack = callBack // 
        this.MessageCallBack('发给注册页面的信息') // 执行PageA的回调函数
    }
}
class PageA {
    constructor() {
        this.className = 'PageA'
        this.pageClass = new Page(this.handleMessage) // 注册页面 传递回调函数 问题在这里
    }
    // 与页面通信回调
    handleMessage(msg) {
        console.log('处理通信', this.className, msg) //  'Page' this指向错误
    }
}
new PageA()

# 回调函数this为何会丢失?

显然声明的时候不会出现问题,执行回调函数的时候也不可能出现问题。

问题出在传递回调函数的时候:

this.pageClass = new Page(this.handleMessage)

因为传递过去的this.handleMessage是一个函数内存地址,没有上下文对象,也就是说该函数没有绑定它的this指向。

那它的this指向于它所应用的绑定规则

class Page {
    constructor(callBack) {
        this.className = 'Page'
        // callBack() // 直接执行的话 由于class 内部是严格模式,所以this 实际指向的是 undefined
        this.MessageCallBack = callBack // 回调函数的this 隐式绑定到class page
        this.MessageCallBack('发给注册页面的信息')
    }
}

既然知道问题了,那我们只要绑定回调函数的this指向为PageA就解决问题了。

回调函数this丢失的解决方案

  1. bind绑定回调函数的this指向:

这是典型bind的应用场景, 绑定this指向,用做回调函数。

this.pageClass = new Page(this.handleMessage.bind(this)) // 绑定回调函数的this指向

PS: 这也是为什么reactrender函数在绑定回调函数的时候,也要使用bind绑定一下this的指向,也是因为同样的问题以及原理。

  1. 箭头函数绑定this指向

箭头函数的this指向定义的时候外层第一个普通函数的this,在这里指的是class类:PageA

这块内容,可以看下我之前写的博客:详解箭头函数和普通函数的区别以及箭头函数的注意事项、不适用场景

this.pageClass = new Page(() => this.handleMessage()) // 箭头函数绑定this指向

# 中高级面试题-手写call/apply、bind:

在大厂的面试中,手写实现call,apply,bind(特别是bind)一直是比较高频的面试题,在这里我们也一起来实现一下这几个函数。

# 你能手写实现一个call吗?

思路

  1. 根据call的规则设置上下文对象,也就是this的指向。
  2. 通过设置context的属性,将函数的this指向隐式绑定到context上
  3. 通过隐式绑定执行函数并传递参数。
  4. 删除临时属性,返回函数执行结果
Function.prototype.myCall = function (context, ...arr) {
    if (context === null || context === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        context = window 
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    context.testFn = this; // 函数的this指向隐式绑定到context上
    let result = context.testFn(...arr); // 通过隐式绑定执行函数并传递参数
    delete context.testFn; // 删除上下文对象的属性
    return result; // 返回函数执行结果
};

# 判断函数的上下文对象:

很多人判断函数上下文对象,只是简单的以context是否为false来判断,比如:

// 判断函数上下文绑定到`window`不够严谨
context = context ? Object(context) : window; 
context = context || window; 

经过测试,以下三种为false的情况,函数的上下文对象都会绑定到window上:

// 网上的其他绑定函数上下文对象的方案: context = context || window; 
function handle(...params) {
    this.test = 'handle'
    console.log('params', this, ...params) // do some thing
}
handle.elseCall('') // window
handle.elseCall(0) // window
handle.elseCall(false) // window

call则将函数的上下文对象会绑定到这些原始值的实例对象上:

原始值的实例对象

所以正确的解决方案,应该是像我上面那么做:

// 正确判断函数上下文对象
    if (context === null || context === undefined) {
       // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
        context = window 
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }

# 你能手写实现一个apply吗?

思路:

  1. 传递给函数的参数处理,不太一样,其他部分跟call一样。
  2. apply接受第二个参数为类数组对象, 这里用了JavaScript权威指南中判断是否为类数组对象的方法。
Function.prototype.myApply = function (context) {
    if (context === null || context === undefined) {
        context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
    } else {
        context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    // JavaScript权威指南判断是否为类数组对象
    function isArrayLike(o) {
        if (o &&                                    // o不是null、undefined等
            typeof o === 'object' &&                // o是对象
            isFinite(o.length) &&                   // o.length是有限数值
            o.length >= 0 &&                        // o.length为非负值
            o.length === Math.floor(o.length) &&    // o.length是整数
            o.length < 4294967296)                  // o.length < 2^32
            return true
        else
            return false
    }
    context.testFn = this; // 隐式绑定this指向到context上
    const args = arguments[1]; // 获取参数数组
    let result
    // 处理传进来的第二个参数
    if (args) {
        // 是否传递第二个参数
        if (!Array.isArray(args) && !isArrayLike(args)) {
            throw new TypeError('myApply 第二个参数不为数组并且不为类数组对象抛出错误');
        } else {
            args = Array.from(args) // 转为数组
            result = context.testFn(...args); // 执行函数并展开数组,传递函数参数
        }
    } else {
        result = context.testFn(); // 执行函数
    }
    delete context.testFn; // 删除上下文对象的属性
    return result; // 返回函数执行结果
};

# 你能手写实现一个bind吗?

划重点

手写bind是大厂中的一个高频的面试题,如果面试的中高级前端,只是能说出它们的区别,用法并不能脱颖而出,理解要有足够的深度才能抱得offer归!

思路

  1. 拷贝源函数:
    • 通过变量储存源函数
    • 使用Object.create复制源函数的prototype给fToBind
  2. 返回拷贝的函数
  3. 调用拷贝的函数:
    • new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
    • 绑定this+传递参数
    • 返回源函数的执行结果
Function.prototype.myBind = function (objThis, ...params) {
    const thisFn = this; // 存储源函数以及上方的params(函数参数)
    let fToBind = function () {
        const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
        const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
        return thisFn.apply(context, params); // 用apply调用源函数绑定this的指向并传递参数,返回执行结果
    };
    fToBind.prototype = Object.create(thisFn.prototype); // 复制源函数的prototype给fToBind
    return fToBind; // 返回拷贝的函数
};

# 小结

本来以为这篇会写的很快,结果断断续续的写了好几天,终于把这三个API相关知识介绍清楚了,希望大家看完之后,面试的时候再遇到这个问题,就可以海陆空全方位的装逼了^_^

以上2019/8/30

# 点个Star支持我一下~

博客链接

Vue的钩子函数[路由导航守卫、keep-alive、生命周期钩子] | Vue

博客链接

# Vue的钩子函数[路由导航守卫、keep-alive、生命周期钩子]

说到Vue的钩子函数,可能很多人只停留在一些很简单常用的钩子(created,mounted),而且对于里面的区别,什么时候该用什么钩子,并没有仔细的去研究过,且Vue的生命周期在面试中也算是比较高频的考点,那么该如何回答这类问题,让人有眼前一亮的感觉呢...

# Vue-Router导航守卫:

有的时候,我们需要通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。

为此我们有很多种方法可以植入路由的导航过程:全局的, 单个路由独享的, 或者组件级的,推荐优先阅读路由文档

# 全局守卫

vue-router全局有三个守卫:

  1. router.beforeEach 全局前置守卫 进入路由之前
  2. router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用
  3. router.afterEach 全局后置钩子 进入路由之后

使用方法

// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => { 
    next();
});
router.beforeResolve((to, from, next) => {
    next();
});
router.afterEach((to, from) => {
    console.log('afterEach 全局后置钩子');
});

# to,from,next 这三个参数:

to和from是将要进入和将要离开的路由对象,路由对象指的是平时通过this.$route获取到的路由对象。

next:Function 这个参数是个函数,且必须调用,否则不能进入路由(页面空白)。

  • next() 进入该路由。
  • next(false): 取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)。
  • next 跳转新路由,当前的导航被中断,重新开始一个新的导航。
        我们可以这样跳转:next('path地址')或者next({path:''})或者next({name:''})
        且允许设置诸如 replace: true、name: 'home' 之类的选项
        以及你用在router-link或router.push的对象选项。

# 路由独享守卫

如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫:

const router = new VueRouter({
    routes: [
    {
        path: '/foo',
        component: Foo,
        beforeEnter: (to, from, next) => { 
        // 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
        // ...
        }
    }
    ]
})

# 路由组件内的守卫:

  1. beforeRouteEnter 进入路由前
  2. beforeRouteUpdate (2.2) 路由复用同一个组件时
  3. beforeRouteLeave 离开当前路由时

文档中的介绍:

beforeRouteEnter (to, from, next) {
// 在路由独享守卫后调用 不!能!获取组件实例 `this`,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 `this`
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用,可以访问组件实例 `this`
}

beforeRouteEnter访问this

因为钩子在组件实例还没被创建的时候调用,所以不能获取组件实例 this,可以通过传一个回调给next来访问组件实例 。

但是回调的执行时机在mounted后面,所以在我看来这里对this的访问意义不太大,可以放在created或者mounted里面。

beforeRouteEnter (to, from, next) {
    console.log('在路由独享守卫后调用');
    next(vm => {
        // 通过 `vm` 访问组件实例`this` 执行回调的时机在mounted后面,
    })
}

beforeRouteLeave:

导航离开该组件的对应路由时调用,我们用它来禁止用户离开,比如还未保存草稿,或者在用户离开前,将setInterval销毁,防止离开之后,定时器还在调用。

beforeRouteLeave (to, from , next) {
    if (文章保存) {
    next(); // 允许离开或者可以跳到别的路由 上面讲过了
    } else {
    next(false); // 取消离开
    }
}

# 关于钩子的一些知识:

# 路由钩子函数的错误捕获

如果我们在全局守卫/路由独享守卫/组件路由守卫的钩子函数中有错误,可以这样捕获:

router.onError(callback => { 
// 2.4.0新增 并不常用,了解一下就可以了 
    console.log(callback, 'callback');
});

在路由文档中还有更多的实例方法:动态添加路由等,有兴趣可以了解一下。

# 跳转死循环,页面永远空白

我了解到的,很多人会碰到这个问题,来看一下这段伪代码:

router.beforeEach((to, from, next) => {
    if(登录){
        next()
    }else{
        next({ name: 'login' }); 
    }
});

看逻辑貌似是对的,但是当我们跳转到login之后,因为此时还是未登录状态,所以会一直跳转到login然后死循环,页面一直是空白的,所以:我们需要把判断条件稍微改一下。

if(登录 || to.name === 'login'){ next() } // 登录,或者将要前往login页面的时候,就允许进入路由

# 全局后置钩子的跳转:

文档中提到因为router.afterEach不接受next函数所以也不会改变导航本身,意思就是只能当成一个钩子来使用,但是我自己在试的时候发现,我们可以通过这种形式来实现跳转:

// main.js 入口文件
import router from './router'; // 引入路由
router.afterEach((to, from) => {
    if (未登录 && to.name !== 'login') {
    router.push({ name: 'login' }); // 跳转login
    }
});

额,通过router.beforeEach 也完全可以实现且更好,我就*一下。

# 完整的路由导航解析流程(不包括其他生命周期):

  1. 触发进入其他路由。
  2. 调用要离开路由的组件守卫beforeRouteLeave
  3. 调用局前置守卫:beforeEach
  4. 在重用的组件里调用 beforeRouteUpdate
  5. 调用路由独享守卫 beforeEnter
  6. 解析异步路由组件。
  7. 在将要进入的路由组件中调用beforeRouteEnter
  8. 调用全局解析守卫 beforeResolve
  9. 导航被确认。
  10. 调用全局后置钩子的 afterEach 钩子。
  11. 触发DOM更新(mounted)。
  12. 执行beforeRouteEnter 守卫中传给 next 的回调函数

# 你不知道的keep-alive[我猜你不知道]

在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive缓存组件内部状态,避免重新渲染文档在这里

文档:和 <transition>相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。

# 用法:

缓存动态组件:

<keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,此种方式并无太大的实用意义。

<!-- 基本 -->
<keep-alive>
    <component :is="view"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
    <comp-a v-if="a > 1"></comp-a>
    <comp-b v-else></comp-b>
</keep-alive>

缓存路由组件:

使用keep-alive可以将所有路径匹配到的路由组件都缓存起来,包括路由组件里面的组件,keep-alive大多数使用场景就是这种。

<keep-alive>
    <router-view></router-view>
</keep-alive>

# 生命周期钩子:

这篇既然是Vue钩子函数的专场,那肯定要扣题呀~

在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activateddeactivated

文档:在 2.2.0 及其更高版本中,activated 和 deactivated 将会在 <keep-alive> 树内的所有嵌套组件中触发。

activated在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用

activated调用时机:

第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用:

        beforeMount=> 如果你是从别的路由/组件进来(组件销毁destroyed/或离开缓存deactivated)=>
        mounted=> activated 进入缓存组件 => 执行 beforeRouteEnter回调

因为组件被缓存了,再次进入缓存路由/组件时,不会触发这些钩子

// beforeCreate created beforeMount mounted 都不会触发。

所以之后的调用时机是:

        组件销毁destroyed/或离开缓存deactivated => activated 进入当前缓存组件 
        => 执行 beforeRouteEnter回调
        // 组件缓存或销毁,嵌套组件的销毁和缓存也在这里触发

deactivated:组件被停用(离开路由)时调用

使用了keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了

这个钩子可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做一些事情,你可以放在这个钩子里。

如果你离开了路由,会依次触发:

        组件内的离开当前路由钩子beforeRouteLeave =>  路由前置守卫 beforeEach =>
        全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)
        // 如果离开的组件没有缓存的话 beforeDestroy会替换deactivated 
        // 如果进入的路由也没有缓存的话  全局后置钩子afterEach=>销毁 destroyed=> beforeCreate等

那么,如果我只是想缓存其中几个路由/组件,那该怎么做?

# 缓存你想缓存的路由:

# Vue2.1.0之前:

想实现类似的操作,你可以:

  1. 配置一下路由元信息
  2. 创建两个keep-alive标签
  3. 使用v-if通过路由元信息判断缓存哪些路由。
        <keep-alive>
            <router-view v-if="$route.meta.keepAlive">
                <!--这里是会被缓存的路由-->
            </router-view>
        </keep-alive>
        <router-view v-if="!$route.meta.keepAlive">
            <!--因为用的是v-if 所以下面还要创建一个未缓存的路由视图出口-->
        </router-view>
        //router配置
        new Router({
          routes: [
            {
              path: '/',
              name: 'home',
              component: Home,
              meta: {
                keepAlive: true // 需要被缓存
              }
            },
            {
              path: '/:id',
              name: 'edit',
              component: Edit,
              meta: {
                keepAlive: false // 不需要被缓存
              }
            }
          ]
        });

# Vue2.1.0版本之后:

使用路由元信息的方式,要多创建一个router-view标签,并且每个路由都要配置一个元信息,是可以实现我们想要的效果,但是过于繁琐了点。

幸运的是在Vue2.1.0之后,Vue新增了两个属性配合keep-alive来有条件地缓存 路由/组件。

新增属性:

  • include:匹配的 路由/组件 会被缓存
  • exclude:匹配的 路由/组件 不会被缓存

includeexclude支持三种方式来有条件的缓存路由:采用逗号分隔的字符串形式,正则形式,数组形式。

正则和数组形式,必须采用v-bind形式来使用。

缓存组件的使用方式

        <!-- 逗号分隔字符串 -->
        <keep-alive include="a,b">
          <component :is="view"></component>
        </keep-alive>
    <span class="token comment">&lt;!-- 正则表达式 (使用 `v-bind`) --></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>keep-alive</span> <span class="token attr-name">:include</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/a|b/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>component</span> <span class="token attr-name">:is</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>view<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>component</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>keep-alive</span><span class="token punctuation">></span></span>
    
    <span class="token comment">&lt;!-- 数组 (使用 `v-bind`) --></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>keep-alive</span> <span class="token attr-name">:include</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>[<span class="token punctuation">'</span>a<span class="token punctuation">'</span>, <span class="token punctuation">'</span>b<span class="token punctuation">'</span>]<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>component</span> <span class="token attr-name">:is</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>view<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>component</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>keep-alive</span><span class="token punctuation">></span></span>

但更多场景中,我们会使用keep-alive来缓存路由

    <keep-alive include='a'>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>router-view</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>router-view</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>keep-alive</span><span class="token punctuation">></span></span>

匹配规则:

  1. 首先匹配组件的name选项,如果name选项不可用。
  2. 则匹配它的局部注册名称。 (父组件 components 选项的键值)
  3. 匿名组件,不可匹配

比如路由组件没有name选项,并且没有注册的组件名。

  1. 只能匹配当前被包裹的组件,不能匹配更下面嵌套的子组件

比如用在路由上,只能匹配路由组件的name选项,不能匹配路由组件里面的嵌套组件的name选项。

  1. 文档:<keep-alive>不会在函数式组件中正常工作,因为它们没有缓存实例。
  2. exclude的优先级大于include

也就是说:当includeexclude同时存在时,exclude生效,include不生效。

      <keep-alive include="a,b" exclude="a">
        <!--只有a不被缓存-->
        <router-view></router-view>
      </keep-alive>

当组件被exclude匹配,该组件将不会被缓存,不会调用activateddeactivated


# 组件生命周期钩子:

关于组件的生命周期,是时候放出这张图片了:

这张图片已经讲得很清楚了,很多人这部分也很清楚了,大部分生命周期并不会用到,这里提一下几点:

  1. ajax请求最好放在created里面,因为此时已经可以访问this了,请求到数据就可以直接放在data里面。

    这里也碰到过几次,面试官问:ajax请求应该放在哪个生命周期。

  2. 关于dom的操作要放在mounted里面,在mounted前面访问dom会是undefined

  3. 每次进入/离开组件都要做一些事情,用什么钩子:

  • 不缓存:

    进入的时候可以用createdmounted钩子,离开的时候用beforeDestorydestroyed钩子,beforeDestory可以访问thisdestroyed不可以访问this

  • 缓存了组件:

    缓存了组件之后,再次进入组件不会触发beforeCreatecreatedbeforeMountmounted如果你想每次进入组件都做一些事情的话,你可以放在activated进入缓存组件的钩子中

    同理:离开缓存组件的时候,beforeDestroydestroyed并不会触发,可以使用deactivated离开缓存组件的钩子来代替。


# 触发钩子的完整顺序:

将路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件:

  1. beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
  2. beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
  3. beforeEnter: 路由独享守卫
  4. beforeRouteEnter: 路由组件的组件进入路由前钩子。
  5. beforeResolve:路由全局解析守卫
  6. afterEach:路由全局后置钩子
  7. beforeCreate:组件生命周期,不能访问this
  8. created:组件生命周期,可以访问this,不能访问dom。
  9. beforeMount:组件生命周期
  10. deactivated: 离开缓存组件a,或者触发a的beforeDestroydestroyed组件销毁钩子。
  11. mounted:访问/操作dom。
  12. activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
  13. 执行beforeRouteEnter回调函数next。

# 小结

Vue提供了很多钩子,但很多钩子我们几乎不会用到,只有清楚这些钩子函数的触发顺序以及背后的一些限制等,这样我们才能够正确的使用这些钩子,希望看了本文的同学,能对这些钩子有更加清晰的认识,使用起来更加得心应手。

# 点个Star支持我一下~

博客链接

H5 notification浏览器桌面通知 | effect

博客链接

# H5 notification浏览器桌面通知

Notification是HTML5新增的API,用于向用户配置和显示桌面通知。上次在别的网站上看到别人的通知弹窗,好奇之余也想知道如何实现的。实际去查一下发现并不复杂,且可以说比较简单,故写篇博客分享给大家,希望能帮你们了解这个API。

# npm包:

我还发了一个npm包:notification-Koro1,非常轻量简洁,觉得不错的话,点个Star吧~

# chrome下Notification的表现:

  1. 以谷歌为例,一开始需要用户允许通知:

  1. 允许通知之后,显示的通知长这样:

# Notification特性

  1. 该通知是脱离浏览器的,即使用户没有停留在当前标签页,甚至最小化了浏览器,也会在主屏幕的右上角显示通知,然后在一段时间后消失

  2. 我们可以监听通知的显示,点击,关闭等事件,比如点击通知打开一个页面。

博客前端积累文档公众号GitHub

# 栗子:去各个网站里面的控制台去运行

API的具体细节,等下再说,先试试这个API~

下面是一个简单的栗子,大家可以先在各个网站的控制台里面运行查看Notification的效果

var options = {
  dir: "auto", // 文字方向
  body: "通知:OBKoro1评论了你的朋友圈", // 通知主体
  requireInteraction: true, // 不自动关闭通知
  // 通知图标 
  icon: "https://upload-images.jianshu.io/upload_images/5245297-818e624b75271127.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
};
notifyMe('这是通知的标题', options);
function notifyMe(title, options) {
  // 先检查浏览器是否支持
  if (!window.Notification) {
    console.log('浏览器不支持通知');
  } else {
    // 检查用户曾经是否同意接受通知
    if (Notification.permission === 'granted') {
      var notification = new Notification(title, options); // 显示通知
    } else if (Notification.permission === 'default') {
      // 用户还未选择,可以询问用户是否同意发送通知
      Notification.requestPermission().then(permission => {
        if (permission === 'granted') {
          console.log('用户同意授权');
          var notification = new Notification(title, options); // 显示通知
        } else if (permission === 'default') {
          console.warn('用户关闭授权 未刷新页面之前 可以再次请求授权');
        } else {
          // denied
          console.log('用户拒绝授权 不能显示通知');
        }
      });
    } else {
      // denied 用户拒绝
      console.log('用户曾经拒绝显示通知');
    }
  }
}

# 浏览器支持:

MDN:目前Notification除了IE浏览器不支持外, 其他浏览器都已支持桌面通知,移动端浏览器基本都未支持。

因为兼容性问题,所以在使用Notification之前,我们需要查看浏览器是否支持Notification这个API:

if(window.Notification){
  // 桌面通知的逻辑
}

# 通知权限:

为了避免网站滥用通知扰民,在向用户显示通知之前,需要经过用户同意。

Notification.permission 用于表明当前通知显示的授权状态,它有三个值:

  1. default: 默认值,用户还未选择
  2. granted: 用户允许该网站发送通知
  3. denied: 用户拒绝该网站发送通知

# 检测权限:

检测浏览器是否支持Notification之后,需要检测一下用户通知权限。

  if (Notification.permission === 'granted') {
    console.log('用户曾经同意授权');
     // 随时可以显示通知
  } else if (Notification.permission === 'default') {
    console.log('用户还未选择同意/拒绝');
    // 下一步请求用户授权
  } else {
    console.log('用户曾经拒绝授权 不能显示通知');
  }

# 请求权限

Notification.permissiondefault的时候,我们需要使用Notification.requestPermission()来请求用户权限。

Notification.requestPermission()基于promise语法,then的回调函数参数是用户权限的状态Notification.permission的值。

Notification.requestPermission().then(permission => {
  if (permission === 'granted') {
    console.log('用户同意授权');
     // 随时可以显示通知
  } else if (permission === 'default') {
    console.log('用户关闭授权 可以再次请求授权');
  } else {
    console.log('用户拒绝授权 不能显示通知');
  }
});
// 老版本使用的是回调函数机制:Notification.requestPermission(callback); 参数一样

# 推送通知

Notification.permissiongranted时,请求到用户权限之后,不必立即发送通知,可以在任意时刻,以任意形式来发送通知。

const options = {}; // 传空配置
const title = '这里是标题';
const notification = new Notification(title, options) // 显示通知

上面这段代码就可以显示一个简单的通知了,只要用户允许你弹窗。

# Notification的参数:

  • title:通知的标题
  • options:通知的设置选项(可选)。
    • body:字符串。通知的body内容。
    • tag:代表通知的一个识别标签,相同tag时只会打开一个通知窗口
    • icon:字符串。要在通知中显示的图标的URL。
    • data:想要和通知关联的数据,可以在new Notification返回的实例中找到。
    • renotify: 布尔值。相同tag,新通知出现的时候是否替换之前的(开启此项,tag必须设置)。
    • requireInteraction:布尔值。通知不自动关闭,默认为false(自动关闭)。
    • 还有一些不太重要的配置可以看张鑫旭老师的博客MDN的介绍

requireInteraction: 保持通知不自动关闭

默认值为false,通知会在三四秒之后自动关闭。

当设置为true,并且当有超过两个通知(new Notification(title, options))时,会出现如下图的通知叠加状态。

这种情况显然,我们只能默认操作最后一个通知,除非你把每个通知返回的实例都保存下来。

我发布的npm包:notification-koro1,可以自定义一定的时间间隔自动关闭不自动关闭的通知,也可以一次性关闭所有通知

PS:如果没有触发叠加,很可能是因为你两次通知的tag配置项是相同的(相同tag只能出现一个弹窗)。

PS: safari下不支持该选项,默认自动关闭

renotify:相同

默认值为false,chorme下相同tag的通知不替换,还是老的通知

设置为true, 两个相同tag的通知,新通知替换之前旧的通知。

注意:使用renotify必须要同时设置tag选项,否则将会报错

PS: safari下不支持该选项,默认两个相同tag的通知,新通知替换之前旧的通知。

# Notification的实例:

生成通知,会返回一个实例,如下:

const instanceNotification = new Notification(title, options)

instanceNotification就是当前通知的实例,在该实例上,我们可以查询该通知的配置,监听事件,调用实例方法

下文都以instanceNotification指代通知返回的实例。

# 通知的配置:

在通知实例上可以读取到设置通知时的所有配置,比如:

通知标题:instanceNotification. title、通知内容:instanceNotification. body、通知图标:instanceNotification. icon等。

PS: 这些属性都是只读的,不能删除,不能修改,不能遍历。

# 事件处理:

我们可以使用通知的实例来监听通知的事件:

  • click: 用户点击通知时被触发
  • show: 通知显示的时候被触发
  • error: 通知遇到错误时被触发
  • close: 用户关闭通知时被触发
instanceNotification.onclick = e => {
  // do something 可以是:打开网址,发请求,关闭通知等
}

注意:最好是一发出通知就立即监听事件,否则有些事件可能一开始没被触发或永远不会触发。

例如:用定时器5秒后才监听通知的点击和显示事件,则永远不会触发通知显示的回调,点击事件在5秒后才可以正常起作用但会错误五秒之前用户的点击。

# 关闭通知

instanceNotification.close()

没有设置不自动关闭的话,chrome通知将会在4.5秒左右自动关闭通知,safari则是5秒钟(无法设置不自动关闭)。

notification没有定时控制通知多久后消失的功能,当出现多个通知,也无法统一关闭。

这两个问题,在我发布的NPM包:notification-koro1中,都解决掉了,并提供更清晰的回调

# 应用场景

  • 即时通讯软件(邮件、聊天室)
  • 体育赛事结果彩票/抽奖结果
  • 新闻网站重大新闻通知
  • 网站的重大更新,重大新闻等。

# notification其他

这里是一些API/浏览器细节,以及可能会遇到的问题,可以先不看,等真正遇到了,回头再来看。

# 用户拒绝显示通知:

一旦用户禁止网站显示通知,网站就不能再请求用户授权显示通知,需要用户去设置中更改。

chrome浏览器的通知设置位置:设置>高级>内容设置>通知

saafari浏览器:偏好设置>网站>通知>找到网站>修改权限/恢复默认

# 关闭请求权限:

在chorme浏览器中:当用户关闭请求权限的弹窗(右上角的叉叉),页面还没刷新,我们可以再次向用户请求权限。页面刷新过后,浏览器默认用户拒绝

在safari浏览器下,没有关闭请求权限的选项,用户必须选择同意/拒绝。

# icon不显示问题:

可能是网站进行了同源限制(比如github),不是域名下面的图片,会报错,不能调用。

# tag:

  1. tag相同的通知,同时只能出现一个,老通知是否会被覆盖取决于:renotify配置和浏览器。
  2. chrome下:当通知关闭之后,上次出现过的tag在一段时间内,不能再出现,比如刷新页面再请求相同tag的通知。(在safari下正常出现)

# safari下面不能显示icon

在safari下面,同一个网站(比如谷歌),同样的代码,chorme可以正常显示icon,safari却没有icon,也没有报错。

谷歌之后发现,在stack overflow里面看到safari只支持body和tag选项,并不支持icon选项

# 连续触发

在safari和chrome下短时间内连续触发通知(不设tag,不设requireInteraction),会出现如下表现:

notification 连续触发

这个表现,通知没有icon、标题、内容,就显得没有意义了,浏览器以这种形式,限制开发者不要频繁打扰用户。

# notification-Koro1:

试一下notification-Koro1啦, 持续维护,简单方便~


# 结语

本文写的比较细,可以先mark一下,然后以后真正用到这个API了,可以先通过文中的栗子,然后再查找对应的内容。

还有就是注意浏览器间的差异,我自己就试了chrome和safari,然后这两个浏览器在实现细节上有很多不一样的地方,开发的时候注意一下。

博客前端积累文档公众号GitHub

参考资料:

notification-Koro1

简单了解HTML5中的Web Notification桌面通知

Notification MDN

HTML5 桌面通知:Notification API

# 点个Star支持我一下~

博客链接

手摸手教你写个ESLint插件以及了解ESLint的运行原理 | tool

博客链接

# 手摸手教你写个ESLint插件以及了解ESLint的运行原理

这篇文章目的是介绍如何创建一个ESLint插件和创建一个ESLint rule,用以帮助我们更深入的理解ESLint的运行原理,并且在有必要时可以根据需求创建出一个完美满足自己需求的Lint规则。

# 插件目标

禁止项目中setTimeout的第二个参数是数字。

PS: 如果是数字的话,很容易就成为魔鬼数字,没有人知道为什么是这个数字, 这个数字有什么含义。

# 使用模板初始化项目:

# 1. 安装NPM包

ESLint官方为了方便开发者开发插件,提供了使用Yeoman模板(generator-eslint)。

对于Yeoman我们只需知道它是一个脚手架工具,用于生成包含指定框架结构的工程化目录结构。

npm install -g yo generator-eslint

# 2. 创建一个文件夹:

mkdir eslint-plugin-demo
cd eslint-plugin-demo

# 3. 命令行初始化ESLint插件的项目结构:

yo eslint:plugin

下面进入命令行交互流程,流程结束后生成ESLint插件项目框架和文件。

? What is your name? OBKoro1
? What is the plugin ID? korolint   // 这个插件的ID是什么
? Type a short description of this plugin: XX公司的定制ESLint rule // 输入这个插件的描述
? Does this plugin contain custom ESLint rules? Yes // 这个插件包含自定义ESLint规则吗?
? Does this plugin contain one or more processors? No // 这个插件包含一个或多个处理器吗
// 处理器用于处理js以外的文件 比如.vue文件
   create package.json
   create lib/index.js
   create README.md

现在可以看到在文件夹内生成了一些文件夹和文件,但我们还需要创建规则具体细节的文件。

# 4. 创建规则

上一个命令行生成的是ESLint插件的项目模板,这个命令行是生成ESLint插件具体规则的文件。

yo eslint:rule // 生成 eslint rule的模板文件

创建规则命令行交互:

? What is your name? OBKoro1
? Where will this rule be published? (Use arrow keys) // 这个规则将在哪里发布?
❯ ESLint Core  // 官方核心规则 (目前有200多个规则)
  ESLint Plugin  // 选择ESLint插件
? What is the rule ID? settimeout-no-number  // 规则的ID
? Type a short description of this rule: setTimeout 第二个参数禁止是数字  // 输入该规则的描述
? Type a short example of the code that will fail:  占位  // 输入一个失败例子的代码
   create docs/rules/settimeout-no-number.md
   create lib/rules/settimeout-no-number.js
   create tests/lib/rules/settimeout-no-number.js

# 加了具体规则文件的项目结构

.
├── README.md
├── docs // 使用文档
│   └── rules // 所有规则的文档
│       └── settimeout-no-number.md // 具体规则文档
├── lib // eslint 规则开发
│   ├── index.js 引入+导出rules文件夹的规则
│   └── rules // 此目录下可以构建多个规则
│       └── settimeout-no-number.js // 规则细节
├── package.json
└── tests // 单元测试
    └── lib
        └── rules
            └── settimeout-no-number.js // 测试该规则的文件

# 4. 安装项目依赖

npm install

以上是开发ESLint插件具体规则的准备工作,下面先来看看AST和ESLint原理的相关知识,为我们开发ESLint rule 打一下基础。

# AST——抽象语法树

AST是: Abstract Syntax Tree的简称,中文叫做:抽象语法树。

# AST的作用

将代码抽象成树状数据结构,方便后续分析检测代码。

# 代码被解析成AST的样子

astexplorer.net是一个工具网站:它能查看代码被解析成AST的样子。

如下图:在右侧选中一个值时,左侧对应区域也变成高亮区域,这样可以在AST中很方便的选中对应的代码

# AST 选择器:

下图中被圈起来的部分,称为AST selectors(选择器)。

AST 选择器的作用:使用代码通过选择器来选中特定的代码片段,然后再对代码进行静态分析。

AST 选择器很多,ESLint官方专门有一个仓库列出了所有类型的选择器: estree

下文中开发ESLint rule就需要用到选择器,等下用到了就懂了,现在知道一下就好了。

将代码解析成AST


# ESLint的运行原理

在开发规则之前,我们需要ESLint是怎么运行的,了解插件为什么需要这么写。

# 1. 将代码解析成AST

ESLint使用JavaScript解析器Espree把JS代码解析成AST。

PS:解析器:是将代码解析成AST的工具,ES6、react、vue都开发了对应的解析器所以ESLint能检测它们的,ESLint也是因此一统前端Lint工具的。

# 2. 深度遍历AST,监听匹配过程。

在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。

# 3. 触发监听选择器的rule回调

在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。

# 4. 具体的检测规则等细节内容。


# 开发规则

# 规则默认模板

打开rule生成的模板文件lib/rules/settimeout-no-number.js, 清理一下文件,删掉不必要的选项:

module.exports = {
    meta: {
        docs: {
            description: "setTimeout 第二个参数禁止是数字",
        },
        fixable: null,  // 修复函数
    },
   // rule 核心
    create: function(context) {
       // 公共变量和函数应该在此定义
        return {
            // 返回事件钩子
        };
    }
};

删掉的配置项,有些是ESLint官方核心规则才是用到的配置项,有些是暂时不必了解的配置,需要用到的时候,可以自行查阅ESLint 文档

# create方法-监听选择器

上文ESLint原理第三部中提到的:在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。

create返回一个对象,对象的属性设为选择器,ESLint会收集这些选择器,在AST遍历过程中会执行所有监听该选择器的回调。

// rule 核心
create: function(context) {
    // 公共变量和函数应该在此定义
    return {
        // 返回事件钩子
        Identifier: (node) => {
            // node是选中的内容,是我们监听的部分, 它的值参考AST
        }
    };
}

# 观察AST:

创建一个ESLint rule需要观察代码解析成AST,选中你要检测的代码,然后进行一些判断。

以下代码都是通过astexplorer.net在线解析的。

setTimeout(()=>{
	console.log('settimeout')
}, 1000)

setTimeout第二个参数为数字时的AST

# rule完整文件

lib/rules/settimeout-no-number.js:

module.exports = {
    meta: {
        docs: {
            description: "setTimeout 第二个参数禁止是数字",
        },
        fixable: null,  // 修复函数
    },
    // rule 核心
    create: function (context) {
        // 公共变量和函数应该在此定义
        return {
            // 返回事件钩子
            'CallExpression': (node) => {
                if (node.callee.name !== 'setTimeout') return // 不是定时器即过滤
                const timeNode = node.arguments && node.arguments[1] // 获取第二个参数
                if (!timeNode) return // 没有第二个参数
                // 检测报错第二个参数是数字 报错
                if (timeNode.type === 'Literal' && typeof timeNode.value === 'number') {
                    context.report({
                        node,
                        message: 'setTimeout第二个参数禁止是数字'
                    })
                }
            }
        };
    }
};

context.report():这个方法是用来通知ESLint这段代码是警告或错误的,用法如上。在这里查看contextcontext.report()的文档。

规则写完了,原理就是依据AST解析的结果,做针对性的检测,过滤出我们要选中的代码,然后对代码的值进行逻辑判断

可能现在会有点懵逼,但是不要紧,我们来写一下测试用例,然后用debugger来看一下代码是怎么运行的。

# 测试用例:

测试文件tests/lib/rules/settimeout-no-number.js:

/**
 * @fileoverview setTimeout 第二个参数禁止是数字
 * @author OBKoro1
 */
"use strict";
var rule = require("../../../lib/rules/settimeout-no-number"), // 引入rule
    RuleTester = require("eslint").RuleTester;

var ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 7, // 默认支持语法为es5
},
});
// 运行测试用例
ruleTester.run("settimeout-no-number", rule, {
// 正确的测试用例
valid: [
{
code: 'let someNumber = 1000; setTimeout(()=>{ console.log(11) },someNumber)'
},
{
code: 'setTimeout(()=>{ console.log(11) },someNumber)'
}
],
// 错误的测试用例
invalid: [
{
code: 'setTimeout(()=>{ console.log(11) },1000)',
errors: [{
message: "setTimeout第二个参数禁止是数字", // 与rule抛出的错误保持一致
type: "CallExpression" // rule监听的对应钩子
}]
}
]
});

下面来学习一下怎么在VSCode中调试node文件,用于观察rule是怎么运行的。

实际上打console的形式,也是可以的,但是在调试的时候打console实在是有点慢,对于node这种节点来说,信息也不全,所以我还是比较推荐通过debugger的方式来调试rule

# 在VSCode中调试node文件

  1. 点击下图中的设置按钮, 将会打开一个文件launch.json
  2. 在文件中填入如下内容,用于调试node文件。
  3. rule文件中打debugger或者在代码行数那里点一下小红点。
  4. 点击图中的开始按钮,进入debugger

vscode 设置

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "启动程序", // 调试界面的名称
            // 运行项目下的这个文件:
            "program": "${workspaceFolder}/tests/lib/rules/settimeout-no-number.js",
            "args": [] // node 文件的参数
        },
        // 下面是用于调试package.json的命令 之前可以用,貌似vscode出了点bug导致现在用不了了
        {
            "name": "Launch via NPM",
            "type": "node",
            "request": "launch",
            "runtimeExecutable": "npm",
            "runtimeArgs": [
                "run-script", "dev"    //这里的dev就对应package.json中的scripts中的dev
            ],
            "port": 9229    //这个端口是调试的端口,不是项目启动的端口
        },
    ]
}

# 运行测试用例进入断点

  1. lib/rules/settimeout-no-number.js中打一些debugger
  2. 点击开始按钮,以调试的形式运行测试文件tests/lib/rules/settimeout-no-number.js
  3. 开始调试rule

# 发布插件

eslint插件都是以npm包的形式来引用的,所以需要把插件发布一下:

  1. 注册:如果你还未注册npm账号的话,需要去注册一下。

  2. 登录npm: npm login

  3. 发布npm包: npm publish即可,ESLint已经把package.json弄好了。

# 集成到项目:

安装npm包:npm i eslint-plugin-korolint -D

  1. 常规的方法: 引入插件一条条写入规则
// .eslintrc.js
module.exports = {
  plugins: [ 'korolint' ],
  rules: { 
    "korolint/settimeout-no-number": "error"
 }
}
  1. extends继承插件配置:

当规则比较多的时候,用户一条条去写,未免也太麻烦了,所以ESLint可以继承插件的配置

修改一下lib/rules/index.js文件:

'use strict';
var requireIndex = require('requireindex');
const output = {
  rules: requireIndex(__dirname + '/rules'), // 导出所有规则
  configs: {
    // 导出自定义规则 在项目中直接引用
    koroRule: {
      plugins: ['korolint'], // 引入插件
      rules: {
        // 开启规则
        'korolint/settimeout-no-number': 'error'
      }
    }
  }
};
module.exports = output;

使用方法:

使用extends来继承插件的配置,extends不止这种继承方式,即使你传入一个npm包,一个文件的相对路径地址,eslint也能继承其中的配置。

// .eslintrc.js
module.exports = {
  extends: [ 'plugin:korolint/koroRule' ] // 继承插件导出的配置
}

PS : 这种使用方式, npm的包名不能为eslint-plugin-xx-xx,只能为eslint-plugin-xx否则会有报错,被这个问题搞得头疼o(╥﹏╥)o

# 扩展:

以上内容足够开发一个插件,这里是一些扩展知识点。

# 遍历方向:

上文中说过: 在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。

我们所监听的选择器默认会在"从上至下"的过程中触发,如果需要在"从下至上"的过程中执行则需要添加:exit,在上文中CallExpression就变为CallExpression:exit

注意:一段代码解析后可能包含多次同一个选择器,选择器的钩子也会多次触发。

# fix函数:自动修复rule错误

修复效果

// 修复前
setTimeout(() => {

}, 1000)
// 修复后 变量名故意写错 为了让用户去修改它
const countNumber1 = 1000
setTimeout(() => {

}, countNumber2)

  1. 在rule的meta对象上打开修复功能:
// rule文件
module.exports = {
  meta: {
    docs: {
      description: 'setTimeout 第二个参数禁止是数字'
    },
    fixable: 'code' // 打开修复功能
  }
}
  1. context.report()上提供一个fix函数:

把上文的context.report修改一下,增加一个fix方法即可,更详细的介绍可以看一下文档

context.report({
    node,
    message: 'setTimeout第二个参数禁止是数字',
    fix(fixer) {
        const numberValue = timeNode.value;
        const statementString = `const countNumber = ${numberValue}\n`
        return [
        // 修改数字为变量
        fixer.replaceTextRange(node.arguments[1].range, 'countNumber'),
        // 在setTimeout之前增加一行声明变量的代码 用户自行修改变量名
        fixer.insertTextBeforeRange(node.range, statementString),
        ];
    }
});

# 项目地址:

eslint-plugin-korolint


呼~ 这篇博客断断续续,写了好几周,终于完成了!

大家有看到这篇博客的话,建议跟着博客的一起动手写一下,动手实操一下比你mark一百篇文章都来的有用,花不了很长时间的,希望各位看完本文,都能够更深入的了解到ESLint的运行原理。

# 觉得我的博客对你有帮助的话,就关注一下/点个赞吧!

前端进阶积累公众号GitHub、wx:OBkoro1、邮箱:[email protected]

# 基友带我飞

ESLint插件是向基友yeyan1996学习的,在遇到问题的时候,也是他指点我的,特此感谢。

参考资料:

创建规则 ESLint 工作原理探讨

# 点个Star支持我一下~

博客链接

数组API解析合集 | JS

博客链接

# 数组API解析合集

数组的使用场景非常多,平日中也涉及到很多数组的api/相关操作,一直也没有对这块内容进行一块整理总结,所以这次对这块内容做一个较为系统的总结,方便自己、也方便他人。

# 创建一个数组:

// 字面量方式:
// 这个方法也是我们最常用的,在初始化数组的时候 相当方便
let a = [3, 11, 8];  // [3,11,8];
// 构造器:
// 实际上 new Array === Array,加不加new 一点影响都没有。
let a = Array(); // []
let a = Array(3); // [,,]
let a = Array(3,11,8); // [ 3,11,8 ]

# ES6 Array.of() 返回由所有参数值组成的数组

定义:返回由所有参数值组成的数组,如果没有参数,就返回一个空数组。

目的:Array.of() 出现的目的是为了解决上述构造器因参数个数不同,导致的行为有差异的问题。

let a = Array.of(3, 11, 8); // [3,11,8]
let a = Array.of(3); // [3]

# ES6 Arrary.from() 将两类对象转为真正的数组

定义:用于将两类对象转为真正的数组(不改变原对象,返回新的数组)。

参数:

第一个参数(必需):要转化为真正数组的对象。

第二个参数(可选): 类似数组的map方法,对每个元素进行处理,将处理后的值放入返回的数组。

第三个参数(可选): 用来绑定this。

// 1. 对象拥有length属性
let obj = {0: 'a', 1: 'b', 2:'c', length: 3};
let arr = Array.from(obj); // ['a','b','c'];
// 2. 部署了 Iterator接口的数据结构 比如:字符串、Set、NodeList对象
let arr = Array.from('hello'); // ['h','e','l','l','o']
let arr = Array.from(new Set(['a','b'])); // ['a','b']

# 方法:

数组原型提供了非常多的方法,这里分为三类来讲,一类会改变原数组的值,一类是不会改变原数组,以及数组的遍历方法。

# 改变原数组的方法(9个):

let a = [1,2,3];
ES5:
    a.splice()/ a.sort() / a.pop()/ a.shift()/  a.push()/ a.unshift()/ a.reverse()
ES6:
a.copyWithin() / a.fill

对于这些能够改变原数组的方法,要注意避免在循环遍历中改变原数组的选项,比如: 改变数组的长度,导致遍历的长度出现问题。

# splice() 添加/删除数组元素

定义: splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目

语法: array.splice(index,howmany,item1,.....,itemX)

参数:

  1. index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
  2. howmany:可选。要删除的项目数量。如果设置为 0,则不会删除项目。
  3. item1, ..., itemX: 可选。向数组添加的新项目。

返回值: 如果有元素被删除,返回包含被删除项目的新数组。

eg1:删除元素

let a = [1, 2, 3, 4, 5, 6, 7];
let item = a.splice(0, 3); // [1,2,3]
console.log(a); // [4,5,6,7]
// 从数组下标0开始,删除3个元素
let item = a.splice(-1, 3); // [7]
// 从最后一个元素开始删除3个元素,因为最后一个元素,所以只删除了7

eg2: 删除并添加

let a = [1, 2, 3, 4, 5, 6, 7];
let item = a.splice(0,3,'添加'); // [1,2,3]
console.log(a); // ['添加',4,5,6,7]
// 从数组下标0开始,删除3个元素,并添加元素'添加'
let b = [1, 2, 3, 4, 5, 6, 7];
let item = b.splice(-2,3,'添加1','添加2'); // [6,7]
console.log(b); // [1,2,3,4,5,'添加1','添加2']
// 从数组最后第二个元素开始,删除3个元素,并添加两个元素'添加1'、'添加2'

eg3: 不删除只添加:

let a = [1, 2, 3, 4, 5, 6, 7];
let item = a.splice(0,0,'添加1','添加2'); // [] 没有删除元素,返回空数组
console.log(a); // ['添加1','添加2',1,2,3,4,5,6,7]
let b = [1, 2, 3, 4, 5, 6, 7];
let item = b.splice(-1,0,'添加1','添加2'); // [] 没有删除元素,返回空数组
console.log(b); // [1,2,3,4,5,6,'添加1','添加2',7] 在最后一个元素的前面添加两个元素

从上述三个栗子可以得出:

  1. 数组如果元素不够,会删除到最后一个元素为止
  2. 操作的元素,包括开始的那个元素
  3. 可以添加很多个元素
  4. 添加是在开始的元素前面添加的

# sort() 数组排序

定义: sort()方法对数组元素进行排序,并返回这个数组。

参数可选: 规定排序顺序的比较函数。

默认情况下sort()方法没有传比较函数的话,默认按字母升序,如果不是元素不是字符串的话,会调用toString()方法将元素转化为字符串的Unicode(万国码)位点,然后再比较字符。

// 字符串排列 看起来很正常
let a = ["Banana", "Orange", "Apple", "Mango"];
a.sort(); // ["Apple","Banana","Mango","Orange"]
// 数字排序的时候 因为转换成Unicode字符串之后,有些数字会比较大会排在后面 这显然不是我们想要的
let	a = [10, 1, 3, 20,25,8];
console.log(a.sort()) // [1,10,20,25,3,8];

比较函数的两个参数:

sort的比较函数有两个默认参数,要在函数中接收这两个参数,这两个参数是数组中两个要比较的元素,通常我们用 a 和 b 接收两个将要比较的元素:

  • 若比较函数返回值<0,那么a将排到b的前面;
  • 若比较函数返回值=0,那么a 和 b 相对位置不变;
  • 若比较函数返回值>0,那么b 排在a 将的前面;

对于sort()方法更深层级的内部实现以及处理机制可以看一下这篇文章深入了解javascript的sort方法

sort排序常见用法

  1. 数组元素为数字的升序、降序:
let array =  [10, 1, 3, 4,20,4,25,8];
// 升序 a-b < 0   a将排到b的前面,按照a的大小来排序的 
// 比如被减数a是10,减数是20  10-20 < 0   被减数a(10)在减数b(20)前面   
array.sort(function(a,b){
    return a-b;
});
console.log(array); // [1,3,4,4,8,10,20,25];
// 降序 被减数和减数调换了  20-10>0 被减数b(20)在减数a(10)的前面
array.sort(function(a,b){
    return b-a;
});
console.log(array); // [25,20,10,8,4,4,3,1];
  1. 数组多条件排序
let array = [{id:10,age:2},{id:5,age:4},{id:6,age:10},{id:9,age:6},{id:2,age:8},{id:10,age:9}];
array.sort(function(a,b){
    if(a.id === b.id){// 如果id的值相等,按照age的值降序
        return b.age - a.age
    }else{ // 如果id的值不相等,按照id的值升序
        return a.id - b.id
    }
})
// [{"id":2,"age":8},{"id":5,"age":4},{"id":6,"age":10},{"id":9,"age":6},{"id":10,"age":9},{"id":10,"age":2}]
  1. 自定义比较函数,天空才是你的极限

类似的:运用好返回值,我们可以写出任意符合自己需求的比较函数

let array = [{name:'Koro1'},{name:'Koro1'},{name:'OB'},{name:'Koro1'},{name:'OB'},{name:'OB'}];
array.sort(function(a,b){
    if(a.name === 'Koro1'){// 如果name是'Koro1' 返回-1 ,-1<0 a排在b的前面
        return -1
    }else{ // 如果不是的话,a排在b的后面
        return 1
    }
})
// [{"name":"Koro1"},{"name":"Koro1"},{"name":"Koro1"},{"name":"OB"},{"name":"OB"},{"name":"OB"}]

# pop() 删除一个数组中的最后的一个元素

定义: pop() 方法删除一个数组中的最后的一个元素,并且返回这个元素。

参数: 无。

let  a =  [1,2,3];
let item = a.pop();  // 3
console.log(a); // [1,2]

# shift() 删除数组的第一个元素

定义: shift()方法删除数组的第一个元素,并返回这个元素。

参数: 无。

let  a =  [1,2,3];
let item = a.shift();  // 1
console.log(a); // [2,3]

# push() 向数组的末尾添加元素

定义:push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。

参数: item1, item2, ..., itemX ,要添加到数组末尾的元素

let  a =  [1,2,3];
let item = a.push('末尾');  // 4
console.log(a); // [1,2,3,'末尾']

# unshift()

定义:unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。

参数: item1, item2, ..., itemX ,要添加到数组开头的元素

let  a =  [1,2,3];
let item = a.unshift('开头');  // 4
console.log(a); // ['开头',1,2,3]

# reverse() 颠倒数组中元素的顺序

定义: reverse() 方法用于颠倒数组中元素的顺序。

参数: 无

let  a =  [1,2,3];
a.reverse();
console.log(a); // [3,2,1]

# ES6: copyWithin() 指定位置的成员复制到其他位置

定义: 在当前数组内部,将指定位置的成员复制到其他位置,并返回这个数组。

语法:

array.copyWithin(target, start = 0, end = this.length)

参数:

三个参数都是数值,如果不是,会自动转为数值.

  1. target(必需):从该位置开始替换数据。如果为负值,表示倒数。
  2. start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
  3. end(可选):到该位置前停止读取数据,默认等于数组长度。使用负数可从数组结尾处规定位置。

浏览器兼容(MDN): chrome 45,Edge 12,Firefox32,Opera 32,Safari 9, IE 不支持

eg:

// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]
let a=['OB1','Koro1','OB2','Koro2','OB3','Koro3','OB4','Koro4','OB5','Koro5']
// 2位置开始被替换,3位置开始读取要替换的 5位置前面停止替换
a.copyWithin(2,3,5)
// ["OB1","Koro1","Koro2","OB3","OB3","Koro3","OB4","Koro4","OB5","Koro5"] 

从上述栗子:

  1. 第一个参数是开始被替换的元素位置
  2. 要替换数据的位置范围:从第二个参数是开始读取的元素,在第三个参数前面一个元素停止读取
  3. 数组的长度不会改变
  4. 读了几个元素就从开始被替换的地方替换几个元素

# ES6: fill() 填充数组

定义: 使用给定值,填充一个数组。

参数:

第一个元素(必须): 要填充数组的值

第二个元素(可选): 填充的开始位置,默认值为0

第三个元素(可选):填充的结束位置,默认是为this.length

MDN浏览器兼容

['a', 'b', 'c'].fill(7)
// [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

# 不改变原数组的方法(8个):

ES5:
slice、join、toLocateString、toStrigin、cancat、indexOf、lastIndexOf、
ES7:
includes

# slice() 浅拷贝数组的元素

定义: 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象,且原数组不会被修改。

注意:字符串也有一个slice() 方法是用来提取字符串的,不要弄混了。

语法:

array.slice(begin, end);

参数:

begin(可选): 索引数值,接受负值,从该索引处开始提取原数组中的元素,默认值为0。

end(可选):索引数值(不包括),接受负值,在该索引处前结束提取原数组元素,默认值为数组末尾(包括最后一个元素)。

let a= ['hello','world'];
let b=a.slice(0,1); // ['hello']
a[0]='改变原数组';
console.log(a,b); // ['改变原数组','world'] ['hello']
b[0]='改变拷贝的数组';
console.log(a,b); // ['改变原数组','world'] ['改变拷贝的数组']

如上:新数组是浅拷贝的,元素是简单数据类型,改变之后不会互相干扰

如果是复杂数据类型(对象,数组)的话,改变其中一个,另外一个也会改变

let a= [{name:'OBKoro1'}];
let b=a.slice();
console.log(b,a); // [{"name":"OBKoro1"}]  [{"name":"OBKoro1"}]
// a[0].name='改变原数组';
// console.log(b,a); // [{"name":"改变原数组"}] [{"name":"改变原数组"}]
// b[0].name='改变拷贝数组',b[0].koro='改变拷贝数组';
//  [{"name":"改变拷贝数组","koro":"改变拷贝数组"}] [{"name":"改变拷贝数组","koro":"改变拷贝数组"}]

原因在定义上面说过了的:slice()是浅拷贝,对于复杂的数据类型浅拷贝,拷贝的只是指向原数组的指针,所以无论改变原数组,还是浅拷贝的数组,都是改变原数组的数据。

# join() 数组转字符串

定义: join() 方法用于把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串。

语法:

array.join(str)

参数:

str(可选): 指定要使用的分隔符,默认使用逗号作为分隔符。

let a= ['hello','world'];
let str=a.join(); // 'hello,world'
let str2=a.join('+'); // 'hello+world'

使用join方法或者下文说到的toString方法时,当数组中的元素也是数组或者是对象时会出现什么情况?

let a= [['OBKoro1','23'],'test'];
let str1=a.join(); // OBKoro1,23,test
let b= [{name:'OBKoro1',age:'23'},'test'];
let str2 = b.join(); // [object Object],test
// 对象转字符串推荐JSON.stringify(obj);

所以,join()/toString()方法在数组元素是数组的时候,会将里面的数组也调用join()/toString(),如果是对象的话,对象会被转为[object Object]字符串。

# toLocaleString() 数组转字符串

定义: 返回一个表示数组元素的字符串。该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成。

语法:

array.toLocaleString()

参数:无。

let a=[{name:'OBKoro1'},23,'abcd',new Date()];
let str=a.toLocaleString(); // [object Object],23,abcd,2018/5/28 下午1:52:20 

如上述栗子:调用数组的toLocaleString方法,数组中的每个元素都会调用自身的toLocaleString方法,对象调用对象的toLocaleString,Date调用Date的toLocaleString

# toString() 数组转字符串 不推荐

定义: toString() 方法可把数组转换为由逗号链接起来的字符串。

语法:

array.toString()

参数: 无。

该方法的效果和join方法一样,都是用于数组转字符串的,但是与join方法相比没有优势,也不能自定义字符串的分隔符,因此不推荐使用。

值得注意的是:当数组和字符串操作的时候,js 会调用这个方法将数组自动转换成字符串

let b= [ 'toString','演示'].toString(); // toString,演示
let a= ['调用toString','连接在我后面']+'啦啦啦'; // 调用toString,连接在我后面啦啦啦

# cancat

定义: 方法用于合并两个或多个数组,返回一个新数组。

语法:

let newArr =oldArray.concat(arrayX,arrayX,......,arrayX)

参数:

arrayX(必须):该参数可以是具体的值,也可以是数组对象。可以是任意多个。

eg1:

let a = [1, 2, 3];
let b = [4, 5, 6];
//连接两个数组
let newVal=a.concat(b); // [1,2,3,4,5,6]
// 连接三个数组
let c = [7, 8, 9]
let newVal2 = a.concat(b, c); // [1,2,3,4,5,6,7,8,9]
// 添加元素
let newVal3 = a.concat('添加元素',b, c,'再加一个'); 
// [1,2,3,"添加元素",4,5,6,7,8,9,"再加一个"]
// 合并嵌套数组  会浅拷贝嵌套数组
let d = [1,2 ];
let f = [3,[4]];
let newVal4 = d.concat(f); // [1,2,3,[4]]

ES6扩展运算符...合并数组

因为ES6的语法更简洁易懂,所以现在合并数组我大部分采用...来处理,...运算符可以实现cancat的每个栗子,且更简洁和具有高度自定义数组元素位置的效果。

let a = [2, 3, 4, 5]
let b = [ 4,...a, 4, 4]
console.log(a,b); //  [2, 3, 4, 5] [4,2,3,4,5,4,4]

更多关于扩展符的详细内容移步阮一峰大神的ECMAScript 6 入门

# indexOf() 查找数组是否存在某个元素,返回下标

定义: 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

语法:

array.indexOf(searchElement,fromIndex)

参数:

searchElement(必须):被查找的元素

fromIndex(可选):开始查找的位置(不能大于等于数组的长度,返回-1),接受负值,默认值为0。

严格相等的搜索:

数组的indexOf搜索跟字符串的indexOf不一样,数组的indexOf使用严格相等===搜索元素,即数组元素要完全匹配才能搜索成功。

注意:indexOf()不能识别NaN

eg:

let a=['啦啦',2,4,24,NaN]
console.log(a.indexOf('啦'));  // -1 
console.log(a.indexOf('NaN'));  // -1 
console.log(a.indexOf('啦啦')); // 0

使用场景:

  1. 数组去重
  2. 根据获取的数组下标执行操作,改变数组中的值等。
  3. 判断是否存在,执行操作。

# lastIndexOf() 查找指定元素在数组中的最后一个位置

定义: 方法返回指定元素,在数组中的最后一个的索引,如果不存在则返回 -1。(从数组后面往前查找)

语法:

arr.lastIndexOf(searchElement,fromIndex)

参数:

searchElement(必须): 被查找的元素

fromIndex(可选): 逆向查找开始位置,默认值数组的长度-1,即查找整个数组。

关于fromIndex有三个规则:

  1. 正值。如果该值大于或等于数组的长度,则整个数组会被查找。
  2. 负值。将其视为从数组末尾向前的偏移。(比如-2,从数组最后第二个元素开始往前查找)
  3. 负值。其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
let a=['OB',4,'Koro1',1,2,'Koro1',3,4,5,'Koro1']; // 数组长度为10
// let b=a.lastIndexOf('Koro1',4); // 从下标4开始往前找 返回下标2
// let b=a.lastIndexOf('Koro1',100); //  大于或数组的长度 查找整个数组 返回9
// let b=a.lastIndexOf('Koro1',-11); // -1 数组不会被查找
let b=a.lastIndexOf('Koro1',-9); // 从第二个元素4往前查找,没有找到 返回-1

# ES7 includes() 查找数组是否包含某个元素 返回布尔

定义: 返回一个布尔值,表示某个数组是否包含给定的值

语法:

array.includes(searchElement,fromIndex=0)

参数:

searchElement(必须):被查找的元素

fromIndex(可选):默认值为0,参数表示搜索的起始位置,接受负值。正值超过数组长度,数组不会被搜索,返回false。负值绝对值超过长数组度,重置从0开始搜索。

includes方法是为了弥补indexOf方法的缺陷而出现的:

  1. indexOf方法不能识别NaN
  2. indexOf方法检查是否包含某个值不够语义化,需要判断是否不等于-1,表达不够直观

eg:

let a=['OB','Koro1',1,NaN];
// let b=a.includes(NaN); // true 识别NaN
// let b=a.includes('Koro1',100); // false 超过数组长度 不搜索
// let b=a.includes('Koro1',-3);  // true 从倒数第三个元素开始搜索 
// let b=a.includes('Koro1',-100);  // true 负值绝对值超过数组长度,搜索整个数组

兼容性(MDN): chrome47, Firefox 43,Edge 14,Opera 34, Safari 9,IE 未实现。


# 遍历方法(12个):

js中遍历数组并不会改变原始数组的方法总共有12个:

ES5:
forEach、every 、some、 filter、map、reduce、reduceRight、
ES6:
find、findIndex、keys、values、entries

# 关于遍历:

  • 关于遍历的效率,可以看一下这篇详解JS遍历
  • 尽量不要在遍历的时候,修改后面要遍历的值
  • 尽量不要在遍历的时候修改数组的长度(删除/添加)

# forEach

定义: 按升序为数组中含有效值的每一项执行一次回调函数。

语法:

array.forEach(function(currentValue, index, arr), thisValue)

参数:

function(必须): 数组中每个元素需要调用的函数。

// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

关于forEach()你要知道

  • 无法中途退出循环,只能用return退出本次回调,进行下一次回调。
  • 它总是返回 undefined值,即使你return了一个值。

# 下面类似语法同样适用这些规则

  1. 对于空数组是不会执行回调函数的
  2. 对于已在迭代过程中删除的元素,或者空元素会跳过回调函数
  3. 遍历次数再第一次循环前就会确定,再添加到数组中的元素不会被遍历。
  4. 如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值。

eg:

let a = [1, 2, ,3]; // 最后第二个元素是空的,不会遍历(undefined、null会遍历)
let obj = { name: 'OBKoro1' };
let result = a.forEach(function (value, index, array) { 
    a[3] = '改变元素';
    a.push('添加到尾端,不会被遍历')
    console.log(value, 'forEach传递的第一个参数'); // 分别打印 1 ,2 ,改变元素
    console.log(this.name); // OBKoro1 打印三次 this绑定在obj对象上
    // break; // break会报错
    return value; // return只能结束本次回调 会执行下次回调
    console.log('不会执行,因为return 会执行下一次循环回调')
}, obj);
console.log(result); // 即使return了一个值,也还是返回undefined
// 回调函数也接受接头函数写法

# every 检测数组所有元素是否都符合判断条件

定义: 方法用于检测数组所有元素是否都符合函数定义的条件

语法:

array.every(function(currentValue, index, arr), thisValue)

参数:(这几个方法的参数,语法都类似)

function(必须): 数组中每个元素需要调用的函数

  1. currentValue(必须),数组当前元素的值
  2. index(可选), 当前元素的索引值
  3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

方法返回值规则:

  1. 如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测。
  2. 如果所有元素都满足条件,则返回 true。=

eg:

function isBigEnough(element, index, array) { 
    return element >= 10; // 判断数组中的所有元素是否都大于10
}
let result = [12, 5, 8, 130, 44].every(isBigEnough);   // false
let result = [12, 54, 18, 130, 44].every(isBigEnough); // true
// 接受箭头函数写法 
[12, 5, 8, 130, 44].every(x => x >= 10); // false
[12, 54, 18, 130, 44].every(x => x >= 10); // true

# some 数组中的是否有满足判断条件的元素

定义:数组中的是否有满足判断条件的元素

语法:

array.some(function(currentValue, index, arr), thisValue)

参数:(这几个方法的参数,语法都类似)

function(必须): 数组中每个元素需要调用的函数。

  1. currentValue(必须),数组当前元素的值
  2. index(可选), 当前元素的索引值
  3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

方法返回值规则

  1. 如果有一个元素满足条件,则表达式返回true, 剩余的元素不会再执行检测。
  2. 如果没有满足条件的元素,则返回false
function isBigEnough(element, index, array) {
    return (element >= 10); //数组中是否有一个元素大于 10
}
let result = [2, 5, 8, 1, 4].some(isBigEnough); // false
let result = [12, 5, 8, 1, 4].some(isBigEnough); // true

# filter 过滤原始数组,返回新数组

定义: 返回一个新数组, 其包含通过所提供函数实现的测试的所有元素。

语法:

let new_array = arr.filter(function(currentValue, index, arr), thisArg)

参数:(这几个方法的参数,语法都类似)

function(必须): 数组中每个元素需要调用的函数。

  1. currentValue(必须),数组当前元素的值
  2. index(可选), 当前元素的索引值
  3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

eg:

let a = [32, 33, 16, 40];
let result = a.filter(function (value, index, array) {
    return value >= 18; // 返回a数组中所有大于18的元素
});
console.log(result,a);// [32,33,40] [32,33,16,40]

# map 对数组中的每个元素进行处理,返回新的数组

定义:创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

语法:

let new_array = arr.map(function(currentValue, index, arr), thisArg)

参数:(这几个方法的参数,语法都类似)

function(必须): 数组中每个元素需要调用的函数。

  1. currentValue(必须),数组当前元素的值
  2. index(可选), 当前元素的索引值
  3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

eg:

let a = ['1','2','3','4'];
let result = a.map(function (value, index, array) {
return value + '新数组的新元素'
});
console.log(result, a); 
// ["1新数组的新元素","2新数组的新元素","3新数组的新元素","4新数组的新元素"] ["1","2","3","4"]

# reduce 为数组提供累加器,合并为一个值

定义:reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。

语法:

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

参数:

function(必须): 数组中每个元素需要调用的函数。

  1. total(必须),初始值, 或者上一次调用回调返回的值
  2. currentValue(必须),数组当前元素的值
  3. index(可选), 当前元素的索引值
  4. arr(可选),数组对象本身

initialValue(可选): 指定第一次回调 的第一个参数。

回调第一次执行时:

  • 如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;
  • 如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。
  • 如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回。

eg:

// 数组求和 
let sum = [0, 1, 2, 3].reduce(function (a, b) {
    return a + b;
}, 0);
// 6
// 将二维数组转化为一维 将数组元素展开
let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
    (a, b) => a.concat(b),
    []
);
// [0, 1, 2, 3, 4, 5]

# reduceRight 从右至左累加

这个方法除了与reduce执行方向相反外,其他完全与其一致,请参考上述 reduce 方法介绍。

# ES6:find()& findIndex() 根据条件找到数组成员

find()定义:用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回undefined。

findIndex()定义:返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

这两个方法

语法:

let item = arr.find(function(currentValue, index, arr), thisArg)
let index = arr.findIndex(function(currentValue, index, arr), thisArg)

参数:(这几个方法的参数,语法都类似)

function(必须): 数组中每个元素需要调用的函数。

  1. currentValue(必须),数组当前元素的值
  2. index(可选), 当前元素的索引值
  3. arr(可选),数组对象本身

thisValue(可选): 当执行回调函数时this绑定对象的值,默认值为undefined

这两个方法都可以识别NaN,弥补了indexOf的不足.

eg:

// find
let a = [1, 4, -5, 10].find((n) => n < 0); // 返回元素-5
let b = [1, 4, -5, 10,NaN].find((n) => Object.is(NaN, n));  // 返回元素NaN
// findIndex
let a = [1, 4, -5, 10].findIndex((n) => n < 0); // 返回索引2
let b = [1, 4, -5, 10,NaN].findIndex((n) => Object.is(NaN, n));  // 返回索引4

浏览器兼容(MDN):Chrome 45,Firefox 25,Opera 32, Safari 8, Edge yes,

# ES6 keys()&values()&entries() 遍历键名、遍历键值、遍历键名+键值

定义:三个方法都返回一个新的 Array Iterator 对象,对象根据方法不同包含不同的值。

语法:

array.keys()
array.values()
array.entries()

参数:无。

遍历栗子(摘自ECMAScript 6 入门):

for (let index of ['a', 'b'].keys()) {
    console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"

for..of中如果遍历中途要退出,可以使用break退出循环。

如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历:

let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

entries()浏览器兼容性(MDN):Chrome 38, Firefox 28,Opera 25,Safari 7.1

keys()浏览器兼容性(MDN):Chrome 38, Firefox 28,Opera 25,Safari 8,

# 点个Star支持我一下~

博客链接

一次弄懂 Object.defineProperty | JS

博客链接

# 一次弄懂 Object.defineProperty

# 基本用法:

let obj = {
  singer: '周杰伦'
};
let value = '青花瓷';
Object.defineProperty(obj, 'music', {
  value: value // music的值
  // configurable: false, // music默认不能删除 要删除须设置为true 设为true 可删除
  // writable: false,  // 默认不能修改music 设为true 可修改
  // enumerable: false, // music默认是不能被枚举(遍历) 设为true 可遍历
});
delete obj.music;
console.log(obj.music); // 青花瓷 删除无效
obj.music = '听妈妈的话';
console.log(obj.music); // 青花瓷 修改无效
for (let key in obj) {
  console.log(key); // singer
}
// music 没有被遍历

# 默认不能修改、不能删除、不能遍历

通过栗子可以发现:通过 defineProperty 设置的属性,默认不能修改,不能删除,不能遍历,当然你可以通过设置更改他们。

# Object.defineProperty的作用:

完全掌控对象的某个属性,增删改查全都可以设定!

# 设置getset

不能同时设置

get,set 设置时不能同时设置 writable 和 value, 他们是一对情侣的存在,交叉设置或同时存在,会报错

let obj = {
  singer: '周杰伦'
};
let value = '青花瓷';
Object.defineProperty(obj, 'music', {
  enumerable: true, // 设置可枚举
  get() {
    // 获取obj.music的时候就会调用get方法
    // let value = "强行设置get的返回值"; // 打开注释 读取属性永远都是‘强行设置get的返回值’
    return value;
  },
  set(val) {
    // value = val; // 将修改的值重新赋给song
    value = '强行设置修改的值';
  }
});

console.log(obj.music); // 青花瓷
delete obj.music; // 删除无效
console.log(obj.music); // 青花瓷
obj.music = '听妈妈的话';
console.log(obj.music); // 强行设置修改的值
for (let key in obj) {
console.log(key); // singer, music 上面设置了enumerable可枚举
}

这个Object.defineProperty的用法就是上面两个栗子中所展示的那样,可以将栗子copy到本地自己玩一下。

# 点个Star支持我一下~

博客链接

Web sendBeacon 刷新/关闭页面之前发送请求 | effect

博客链接

# Web sendBeacon 刷新/关闭页面之前发送请求

# 背景:

有一个任务非常耗时会消耗后台大量算力,所以在退出页面的时候,要求前端这边发送一个请求来杀死任务

一开始以为这个需求非常简单,就是在进入其他路由前,发送一下请求,杀死一下任务就好了。

然而现实狠狠的打了我的脸,因为退出页面的场景不止切换路由~

# 退出页面场景:

  1. 还在本网站,跳到其他路由
  2. 刷新页面/关闭页面也需要发送请求来杀死任务

# 还在本网站,跳到其他路由

这个比较简单,在Vue中可以通过路由离开的钩子beforeRouteLeave来实现:

 beforeRouteLeave(to, from, next) {
    if (任务运行中) {
        // 发送请求
    }else{
        next(true) // 用户离开
    }
 }

刷新页面/关闭页面的情况:

然而在刷新页面的时候,beforeRouteLeave并不会执行,接着想到了下面这两个API.

# beforeunloadunload

# beforeunload 当浏览器窗口关闭或者刷新时触发:

介绍

使用这个API可以阻止页面直接关闭,用户通过点击确定/取消按钮,来决定是否不关闭/刷新当前页面。

在 chrome 下长这个样子,你们肯定都见过:

如何使用

这个 API 的使用非常简单,只要在页面加载的时候监听一下此事件,在需要出现弹窗的时候return 一个可以转化为 true 的值,就可以了。

// 页面卸载之前
let killTask = false; // 是否杀死任务
window.onbeforeunload = e => {
  if (任务运行 && 对应页面) {
    killTask = true;
    return '您可能有数据没有保存'; // 在部分浏览器可以修改弹窗标题
  } else {
    killTask = false;
  }
  // 没有return一个可以转化为true的值 就不会出现弹窗
};

出现此弹窗的浏览器行为

以下行为是基于 chorme:

  1. 焦点:你没有点击取消/确定之前,焦点会一直在此弹窗上

  2. 你无法在出现弹窗的页面上执行任何操作

  3. 在其他页面也只能执行简单的点击操作,弹窗还是存在页面中间,无法使用键盘,

  4. 键盘:键盘被绑定在弹窗上,只能通过按键EscEnter来执行取消/确定操作

  5. 弹窗不是页面的 dom,是浏览器的行为

  6. 用户取消/确定,没有回调 API,无法得知

弹窗标题

chrome 中刷新页面的标题:重新加载此网站?

chrome 中关闭页面的标题:离开此网站?

现在大部分浏览器都不允许修改弹窗的标题,这个是为了安全考虑,来保证用户不受到错误信息的误导,

迷茫

一开始我以为既然可以拦截到用户的刷新/关闭页面的操作,出现了上面那个弹窗,这个需求就已经做完了的时候。

然后发现,浏览器竟然没有提供用户点击确定/取消刷新页面的回调

到这里我陷入了迷茫,盯着beforeunload这个 API 思考了起了人生的意义(其实是在发呆),盯着盯着,从beforeunloadbefore我也就想到了unload这个 API。

瞬间又燃起了斗志,何不试试这个unload

# unload当页面正在被卸载的时候触发该事件

介绍

当页面正在被卸载的时候触发该事件,该事件不可取消,为不可逆操作。

使用

直接监听该事件就可以了。

window.onunload = e => {}

结合需求:

killTaskbeforeunload时定义的变量,每次进入回调,都会给killTask赋值,使用这个值就可以判断什么时候可以发送请求杀死任务。

window.onunload = e => {
  if (killTask && 对应页面) {
    // 发送请求
  }
};

到这里大家肯定以为已经做出来了该需求,事实上,并没有!

无法发送异步请求

我使用的是axios来发送请求,请求发出去了,但是被取消了,服务器那边根本没有收到请求,如下。

经过一顿分析:发现是axios请求是异步的问题,谷歌之后发现axios不支持同步的请求

最后使用原生的XMLHttpRequest对象,让请求同步

大功告成! 实际上,上面才是我第一次要发的内容,而下面更好的解决方法!

# 缺陷与更好的建议:

当我把这篇文章发布在公众号上,被奇舞周刊转载了,上面一些大佬给我提了一些建议。

研究了一下,结果...好吧!我承认我是菜鸡。

hey~ 不过这正是我写博客的收获之一,分享经验,收获知识!

# 性能缺陷:

XHR同步请求会阻碍页面卸载,如果是刷新/跳转页面的话,页面重新展示速度会变慢,导致性能问题

毕竟向网络发送请求并获得响应可能会超级慢,有可能是用户网络环境比较差,又或者是服务器挂了,请求一直没返回回来...

基于性能问题,大佬们推荐使用Beacon代替XHR,然后经过一番搜索...

# Beacon API

  1. Beacon API用于将少量数据通过post请求发送到服务器
  2. Beacon是非阻塞请求,不需要响应

# 完美解决性能缺陷问题:

  1. 浏览器将 Beacon 请求排队让它在空闲的时候执行并立即返回控制
  2. 它在unload状态下也可以异步发送,不阻塞页面刷新/跳转等操作。

所以**Beacon可以完美解决上面提到的因XHR同步请求阻塞而引起的性能缺陷问题**。

# 使用:navigator.sendBeacon()

完整API

let result = navigator.sendBeacon(url, data);

Beacon是挂在navigator下面的,上面就是它的完整API。

result是一个布尔值,代表这次发送请求的结果:

  • 如果浏览器接受并且把请求排队了则返回 tru
  • 如果在这个过程中出现了问题就返回 false

navigator.sendBeacon接受两个参数:

  1. url: 请求的 URL。请求是 POST 请求。
  2. data: 要发送的数据。 数据类型可以是:ArrayBufferView, Blob, FormData,Sting。

来看一个用FormData来传递数据的栗子,你就懂了:

// 创建一个新的 FormData 并添加一个键值对
let data = new FormData();
data.append('hello', 'world');
let result = navigator.sendBeacon('./src', data);
if (result) { 
  console.log('请求成功排队 等待执行');
} else {
  console.log('失败');
}

# 浏览器支持:

浏览器支持:Edge:14+,Firefox:31+,Chrome:39+,Opera:26+,IE:不支持。

虽然现在浏览器对sendBeacon的支持很好,我们对其做一下兼容性处理也是有必要的:

if (navigator.sendBeacon) {
  // Beacon 代码
} else {
 // 回退到 XHR同步请求或者不做处理
}

# web wroker中使用Beacon

因为Beacon是挂在navigator下面,而web worker也有navigator,去找了一下,真的给我找到了。

这儿有一个MDN提供的栗子,可以点进去看一下。

PS:对web worker不熟悉的同学可以看我这篇文章

# Beacon其他相关

  • 客户端优化:可以将 Beacon 请求合并到其他请求上,一同处理, 尤其在移动环境下。
  • Beacon更多的情况是用于做前端埋点,监控用户活动,它的初衷也基于此。

# 小结

本文总共讲了三个API,beforeunloadunloadBeaconBeacon这个API估计知道的人比较少,以后遇到前端埋点和页面卸载前发送请求的需求,记得使用这三个API。

以上2019.02.19

博客前端积累文档公众号GitHub

参考资料:

MDN

页面跳转时,统计数据丢失问题探讨

使用 Web Beacon API 记录活动

# 点个Star支持我一下~

博客链接

JS高程中的垃圾回收机制与常见内存泄露的解决方法 | tool

博客链接

# JS高程中的垃圾回收机制与常见内存泄露的解决方法

起因是因为想了解闭包的内存泄露机制,然后想起《js高级程序设计》中有关于垃圾回收机制的解析,之前没有很懂,过一年回头再看就懂了,写篇博客与大家分享一下。

# 内存的生命周期:

  1. 分配你所需要的内存:

由于字符串、对象等没有固定的大小,js程序在每次创建字符串、对象的时候,程序都会分配内存来存储那个实体

  1. 使用分配到的内存做点什么。

  2. 不需要时将其释放回归:

在不需要字符串、对象的时候,需要释放其所占用的内存,否则将会消耗完系统中所有可用的内存,造成系统崩溃,这就是垃圾回收机制所存在的意义

所谓的内存泄漏指的是:由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。


# 垃圾回收机制:

在C和C++之类的语言中,需要手动来管理内存的,这也是造成许多不必要问题的根源。幸运的是,在编写js的过程中,内存的分配以及内存的回收完全实现了自动管理,我们不用操心这种事情。

# 垃圾收集机制的原理:

垃圾收集器会按照固定的时间间隔,周期性的找出不再继续使用的变量,然后释放其占用的内存

什么叫不再继续使用的变量?

不再使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。

全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收

# 标记清除:当前采用的垃圾收集策略

工作原理:

当变量进入环境时(例如在函数中声明一个变量),将这个变量标记为“进入环境”,当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。

工作流程:

  1. 垃圾收集器会在运行的时候会给存储在内存中的所有变量都加上标记
  2. 去掉环境中的变量以及被环境中的变量引用的变量的标记。
  3. 那些还存在标记的变量被视为准备删除的变量
  4. 最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间

到2008年为止,IE、Chorme、Fireofx、Safari、Opera 都使用标记清除式的垃圾收集策略,只不过垃圾收集的时间间隔互有不同。

# 引用计数略:被废弃的垃圾收集策略

循环引用:跟踪记录每个值被引用的技术

在老版本的浏览器中(对,又是IE),IE9以下BOM和DOM对象就是使用C++以COM对象的形式实现的。

COM的垃圾收集机制采用的就是引用计数策略,这种机制在出现循环引用的时候永远都释放不掉内存。

let element = document.getElementById('something');
let myObject = new Object();
myObject.element = element; // element属性指向dom
element.someThing = myObject; // someThing回指myObject 出现循环引用(两个对象一直互相包含 一直存在计数)。

解决方式是,当我们不使用它们的时候,手动切断链接:

myObject.element = null; 
element.someThing = null;

淘汰

IE9把BOM和DOM对象转为了真正的js对象,避免了使用这种垃圾收集策略,消除了IE9以下常见的内存泄漏的主要原因。

IE7以下有一个声明狼藉的性能问题,大家了解一下:

  1. 256个变量,4096个对象(或数组)字面或者64KB的字符串,达到任何一个临界值会触发垃圾收集器运行。
  2. 如果一个js脚本的生命周期一直保有那么多变量,垃圾收集器会一直频繁的运行,引发严重的性能问题。

IE7已修复这个问题。


# 哪些情况会引起内存泄漏?

虽然有垃圾回收机制,但我们在编写代码的时候,有些情况还是会造成内存泄漏,了解这些情况,并在编写程序的时候,注意避免,我们的程序会更具健壮性。

# 意外的全局变量:

上文我们提到了全局变量不会被当成垃圾回收,我们在编码中有时会出现下面这种情况:

function foo() {
    this.bar2 = '默认绑定this指向全局' // 全局变量=> window.bar2
    bar = '全局变量'; // 没有声明变量 实际上是全局变量=>window.bar
}
foo();

当我们使用默认绑定,this会指向全局,this.something也会创建一个全局变量,这一点可能很多人没有注意到。

解决方法:在函数内使用严格模式or细心一点

function foo() {
    "use strict"; 
    this.bar2 = "严格模式下this指向undefined"; 
    bar = "报错";
}
foo();

当然我们也可以手动释放全局变量的内存

window.bar = undefined
delete window.bar2

# 被遗忘的定时器和回调函数

不需要setInterval或者setTimeout时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
        // 定时器也没有清除
    }
    // node、someResource 存储了大量数据 无法回收
}, 1000);

解决方法: 在定时器完成工作的时候,手动清除定时器。

# 闭包:

闭包可以维持函数内局部变量,使其得不到释放,造成内存泄漏

function bindEvent() {
    var obj = document.createElement("XXX");
    var unused = function () {
        console.log(obj,'闭包内引用obj obj不会被释放');
    };
    // obj = null;
}

解决方法:手动解除引用,obj = null

# 循环引用问题

就是IE9以下的循环引用问题,上文讲过了。

# 没有清理DOM元素引用:

var refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, "refA");  // 但是还存在引用 能console出整个div 没有被回收

不信的话,可以看下这个dom

解决办法refA = null;

# console保存大量数据在内存中。

过多的console,比如定时器的console会导致浏览器卡死。

解决:合理利用console,线上项目尽量少的使用console,当然如果你要发招聘除外。


# 如何避免内存泄漏:

记住一个原则:不用的东西,及时归还,毕竟你是'借的'嘛

  1. 减少不必要的全局变量,使用严格模式避免意外创建全局变量。
  2. 在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
  3. 组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。

# 关于内存泄漏:

  1. 即使是1byte的内存,也叫内存泄漏,并不一定是导致浏览器崩溃、卡顿才能叫做内存泄漏。
  2. 一般是堆区内存泄漏,栈区不会泄漏。

基本类型的值存在内存中,被保存在栈内存中,引用类型的值是对象,保存在堆内存中。所以对象、数组之类的,才会发生内存泄漏

  1. 使用chorme监控内存泄漏,可以看一下这篇文章

# 小结

了解了内存泄漏的原因以及出现的情况,那么我们在编码过程中只要多加注意,就不会发生非常严重的内存泄漏问题。

# 点个Star支持我一下~

博客链接

JS基础-原型、原型链 | JS

博客链接

# JS基础-原型、原型链

JS的原型、原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对这个概念一知半解,碰到问题靠“猜”,却不理解它的规则!

# prototype

# 只有函数有prototype属性

let a = {}
let b = function () { }
console.log(a.prototype) // undefined
console.log(b.prototype) // { constructor: function(){...} }

# Object.prototype怎么解释?

其实Object是一个全局对象,也是一个构造函数,以及其他基本类型的全局对象也都是构造函数:

function outTypeName(data, type) {
    let typeName =  Object.prototype.toString.call(data)
    console.log(typeName)
}
outTypeName(Object) //[object Function]
outTypeName(String) // [object Function]
outTypeName(Number) // [object Function]

# 为什么只有函数有prototype属性

JS通过new来生成对象,但是仅靠构造函数,每次生成的对象都不一样。

有时候需要在两个对象之间共享属性,由于JS在设计之初没有类的概念,所以JS使用函数的prototype来处理这部分需要被共享的属性,通过函数的prototype来模拟类:

当创建一个函数时,JS会自动为函数添加prototype属性,值是一个有constructor的对象。

以下是共享属性prototype的栗子:

function People(name) {
    this.name = name
}
People.prototype.age = 23 // 岁数
// 创建两个实例
let People1 = new People('OBKoro1')
let People2 = new People('扣肉')
People.prototype.age = 24 // 长大了一岁
console.log(People1.age, People2.age) // 24 24

为什么People1People2可以访问到People.prototype.age

原因是:People1People2的原型是People.prototype,答案在下方的:构造函数是什么以及它做了什么。

# 原型链

# __proto__Object.getPrototypeOf(target): 对象的原型

__proto__是对象实例和它的构造函数之间建立的链接,它的值是:构造函数的`prototype。

也就是说:__proto__的值是它所对应的原型对象,是某个函数的prototype

Object.getPrototypeOf(target)全等于__proto__

它是ES6的标准,兼容IE9,主流浏览器也都支持,MDN,本文将以Object.getPrototypeOf(target)指代__proto__

# 不要再使用__proto__:

本段摘自阮一峰-ES6入门,具体解析请点击链接查看

  1. __proto__属性没有写入 ES6 的正文,而是写入了附录。

  2. 原因是它本质上是一个内部属性,而不是一个正式的对外的 API,只是由于浏览器广泛支持,才被加入了 ES6

  3. 标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的

  4. 所以无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,应该使用:Object.getPrototypeOf(target)(读操作)、Object.setPrototypeOf(target)(写操作)、Object.create(target)(生成操作)代替

# 构造函数是什么、它做了什么

出自《你不知道的在js》:在js中, 实际上并不存在所谓的'构造函数',只有对于函数的'构造调用'。

上文一直提到构造函数,所谓的构造函数,实际上就是通过关键字new来调用的函数:

let newObj = new someFn() // 构造调用函数

构造/new调用函数的时候做了什么

  1. 创建一个全新的对象。
  2. 这个新对象的原型(Object.getPrototypeOf(target))指向构造函数的prototype对象。
  3. 该函数的this会绑定在新创建的对象上。
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
  5. 我们称这个新对象为构造函数的实例。

原型继承就是利用构造调用函数的特性

SubType.prototype = new SuperType();  // 原型继承:SubType继承SuperType
// 挂载SuperType的this和prototype的属性和方法到SubType.prototype上
  1. �构造调用的第二点:将新对象的Object.getPrototypeOf(target)指向函数的prototype
  2. 构造调用的第三点:该函数的this会绑定在新创建的对象上。
  3. 新对象赋值给SubType.prototype

原型类型还有缺点:多个实例对引用类型的操作会被篡改,这里就不扩展了。

# 原型链是什么

来看个例子:

function foo() { }
const newObj = new foo() // 构造调用foo 返回一个新对象
const newObj__proto__ = Object.getPrototypeOf(newObj) // 获取newObj的原型对象
newObj__proto__ === foo.prototype // true 验证newObj的原型指向foo
const foo__proto__ = Object.getPrototypeOf(foo.prototype) // 获取foo.prototype的原型
foo__proto__ === Object.prototype // true foo.prototype的原型是Object.prototype

如果用以前的语法,从newObj查找foo的原型,是这样的:

newObj.__proto__.__proto__ // 这种关系就是原型链

可以用以下三句话来理解原型链

  1. 每个对象都拥有一个原型对象: newObj的原型是foo.prototype
  2. 对象的原型可能也是继承其他原型对象的: foo.prototype也有它的原型Object.prototype
  3. 一层一层的,以此类推,这种关系就是原型链

# 一个对象是否在另一个对象的原型链上

如果一个对象存在另一个对象的原型链上,我们可以说:它们是继承关系。

判断方式有两种,但都是根据构造函数的prototype是否在原型链上来判断的:

  1. instanceof: 用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置

语法:object instanceof constructor

let test = function () { }
let testObject = new test();
testObject instanceof test // true test.prototype在testObject的原型链上
 testObject instanceof Function // false Function.prototype 不在testObject的原型链上
testObject instanceof Object // true Object.prototype在testObject的原型链上
  1. isPrototypeOf:测试一个对象是否存在于另一个对象的原型链上

语法:prototypeObj.isPrototypeOf(object)

let test = function () { }
let testObject = new test();
test.prototype.isPrototypeOf(testObject) // true test.prototype在testObject的原型链上
Object.prototype.isPrototypeOf(testObject) // true Object.prototype在testObject的原型链上

# 原型链的终点: Object.prototype

Object.prototype是原型链的终点,所有对象都是从它继承了方法和属性。

Object.prototype没有原型对象

const proto = Object.getPrototypeOf(Object.prototype) // null

下面是两个验证例子,有疑虑的同学多写几个测试用例印证一下。

字符串原型链的终点Object.prototype

let test = '由String函数构造出来的'
let stringPrototype = Object.getPrototypeOf(test) // 字符串的原型
stringPrototype === String.prototype // true 字符串的原型是String对象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true String对象的原型是Object对象

函数原型链的终点:Object.prototype

let test = function () { }
let fnPrototype = Object.getPrototypeOf(test)
stringPrototype === Function.prototype // true test的原型是Function.prototype
Object.getPrototypeOf(Function.prototype) === Object.prototype // true

# 原型链用来做什么?

# 属性查找:

如果试图访问对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性,以此类推

我们用一个例子来形象说明一下:

let test = '由String函数构造出来的'
let stringPrototype = Object.getPrototypeOf(test) // 字符串的原型
stringPrototype === String.prototype // true 字符串的原型是String对象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true String对象的原型是Object对象

当你访问test的某个属性时,浏览器会进行以下查找:

  1. 浏览器首先查找test 本身
  2. 接着查找它的原型对象:String.prototype
  3. 最后查找String.prototype的原型对象:Object.prototype
  4. 一旦在原型链上找到该属性,就会立即返回该属性,停止查找。
  5. 原型链上的原型都没有找到的话,返回undefiend

这种查找机制还解释了字符串为何会有自带的方法: slice/split/indexOf等。

准确的说:

  • 这些属性和方法是定义在String这个全局对象/函数上的。
  • 字符串的原型指向了String函数的prototype
  • 之后通过查找原型链,在String函数的prototype中找到这些属性和方法。

# 拒绝查找原型链:

hasOwnProperty: 指示对象自身属性中是否具有指定的属性

语法:obj.hasOwnProperty(prop)

参数: prop 要查找的属性

返回值: 用来判断某个对象是否含有指定的属性的Boolean

let test ={ 'OBKoro1': '扣肉' }
test.hasOwnProperty('OBKoro1');  // true
test.hasOwnProperty('toString'); // false test本身没查找到toString 

这个API是挂载在object.prototype上,所有对象都可以使用,API会忽略掉那些从原型链上继承到的属性。

# 扩展:

# 实例的属性

你知道构造函数的实例对象上有哪些属性吗?这些属性分别挂载在哪个地方?原因是什么?

function foo() {
    this.some = '222'
    let ccc = 'ccc'
    foo.obkoro1 = 'obkoro1'
    foo.prototype.a = 'aaa'
}
foo.koro = '扣肉'
foo.prototype.test = 'test'
let foo1 = new foo() // `foo1`上有哪些属性,这些属性分别挂载在哪个地方
foo.prototype.test = 'test2' // 重新赋值

上面这道是考察JS基础的题,很多人都没说对,原因是没有彻底掌握this原型链函数

# 想一下再看解析:

# 想一下再看解析:

# 想一下再看解析:

# 想一下再看解析:

# 想一下再看解析:

  1. this.somefoo1对象的属性

通过构造调用foothis指向foo1,所以this.some挂载在foo1对象下。

属性查找: foo1.some

foo1.some直接读取foo1的属性。

  1. foo1.testfoo1.afoo1对象的原型

根据上文提到的:构造/new调用函数的时候会创建一个新对象(foo1),自动将foo1的原型(Object.getPrototypeOf(foo1))指向构造函数的prototype对象。

构造调用会执行函数,所以foo.prototype.a = 'aaaaa'也会执行,单就赋值这个层面来说写在foo外面和写在foo里面是一样的。

属性查找:foo1.testfoo1.a

  • foo1本身没有找到,继续查找
  • foo1的原型Object.getPrototypeOf(foo1)上找到了atest,返回它们,停止查找。
  1. foo1.obkoro1foo1.koro:返回undefined

# 静态属性: foo.obkoro1foo.koro

函数在JS中是一等公民,它也是一个对象, 用来模拟类。

这两个属性跟foo1没有关系,它是对象foo上的两个属性(类似函数的:arguments/prototype/length等属性),称为静态属性

它们只能通过foo.obkoro1foo.koro来访问。

# 原型对象改变,原型链下游获取的值也会改变

上面那个例子中的foo1.test的值是什么?

foo.prototype.test = 'test'
let foo1 = new foo() // `foo1`上有哪些属性,这些属性分别挂载在哪个地方
foo.prototype.test = 'test2' // 重新赋值

foo1.test的值是test2,原因是:foo1的原型对象是Object.getPrototypeOf(foo1)存的指针,指向foo.prototype的内存地址,不是拷贝,每次读取的值都是当前foo.prototype的最新值。

打印foo1

# 小结

写了好几天,之前网上很多图文博客,那些线指来指去,就我个人看来还是比较难以理解的,所以本文纯文字的形式来描述这些概念,相信认真看完的同学肯定都有所收获,如果没看懂的话,建议多看几遍,这部分概念真的很重要!

PS:实际上还有很多引申出来的东西没写全,准备放到其他文章中去写。

# 觉得我的博客对你有帮助的话,就给我点个Star吧!

前端进阶积累公众号GitHub、wx:OBkoro1、邮箱:[email protected]

以上2019/8/25

作者:OBKoro1

参考资料:

MDN:对象原型

JS原型链与继承别再被问倒了

从__proto__和prototype来深入理解JS对象和原型链

# 点个Star支持我一下~

博客链接

轻松掌握浏览器重绘重排原理 | tool

博客链接

# 轻松掌握浏览器重绘重排原理

很多人都知道要减少浏览器的重排和重绘,但对其中的具体原理以及如何具体操作并不是很了解,当突然提起这个话题的时候,还是会一脸懵逼。希望大家可以耐着性子阅读本文,仔细琢磨,彻底掌握这个知识点!

博客前端积累文档公众号GitHub

# 网页生成过程:

  1. HTML被HTML解析器解析成DOM 树
  2. css则被css解析器解析成CSSOM 树
  3. 结合DOM树和CSSOM树,生成一棵渲染树(Render Tree)
  4. 生成布局(flow),即将所有渲染树的所有节点进行平面合成
  5. 将布局绘制(paint)在屏幕上

第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染

网上找了一张图片,我加了注释会更直观一些:


# 渲染:

网页生成的时候,至少会渲染一次

在用户访问的过程中,还会不断重新渲染

重新渲染需要重复之前的第四步(重新生成布局)+第五步(重新绘制)或者只有第五个步(重新绘制)。

# 重排比重绘大:

大,在这个语境里的意思是:谁能影响谁?

  • 重绘:某些元素的外观被改变,例如:元素的填充颜色
  • 重排:重新生成布局,重新排列元素。

就如上面的概念一样,单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分

比如改变元素高度,这个元素乃至周边dom都需要重新绘制。

也就是说:"重绘"不一定会出现"重排","重排"必然会出现"重绘"

# 重排(reflow):

# 概念:

当DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。

重排也叫回流,重排的过程以下面这种理解方式更清晰一些:

回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流

# 常见引起重排属性和方法

任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发重排,下面列一些栗子:

  1. 添加或者删除可见的DOM元素;
  2. 元素尺寸改变——边距、填充、边框、宽度和高度
  3. 内容变化,比如用户在input框中输入文字
  4. 浏览器窗口尺寸改变——resize事件发生时
  5. 计算 offsetWidth 和 offsetHeight 属性
  6. 设置 style 属性的值
常见引起重排属性和方法
width height margin padding
display border position overflow
clientWidth clientHeight clientTop clientLeft
offsetWidth offsetHeight offsetTop offsetLeft
scrollWidth scrollHeight scrollTop scrollLeft
scrollIntoView() scrollTo() getComputedStyle()
getBoundingClientRect() scrollIntoViewIfNeeded()

# 重排影响的范围:

由于浏览器渲染界面是基于流失布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种:

  • 全局范围:从根节点html开始对整个渲染树进行重新布局。
  • 局部范围:对渲染树的某部分或某一个渲染对象进行重新布局

全局范围重排

<body>
  <div class="hello">
    <h4>hello</h4>
    <p><strong>Name:</strong>BDing</p>
    <h5>male</h5>
    <ol>
      <li>coding</li>
      <li>loving</li>
    </ol>
  </div>
</body>

当p节点上发生reflow时,hello和body也会重新渲染,甚至h5和ol都会收到影响。

局部范围重排:

用局部布局来解释这种现象:把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界。

# 尽可能的减少重排的次数、重排范围:

重排需要更新渲染树,性能花销非常大:

它们的代价是高昂的,会破坏用户体验,并且让UI展示非常迟缓,我们需要尽可能的减少触发重排的次数。

重排的性能花销跟渲染树有多少节点需要重新构建有关系:

所以我们应该尽量以局部布局的形式组织html结构,尽可能小的影响重排的范围。

而不是像全局范围的示例代码一样一溜的堆砌标签,随便一个元素触发重排都会导致全局范围的重排。

# 重绘(Repaints):

概念

当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

常见的引起重绘的属性:

color border-style visibility background
text-decoration background-image background-position background-repeat
outline-color outline outline-style border-radius
outline-width box-shadow background-size

# 浏览器的渲染队列:

思考以下代码将会触发几次渲染?

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';

根据我们上文的定义,这段代码理论上会触发4次重排+重绘,因为每一次都改变了元素的几何属性,实际上最后只触发了一次重排,这都得益于浏览器的渲染队列机制

当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。

# 强制刷新队列:

div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);

这段代码会触发4次重排+重绘,因为在console中你请求的这几个样式信息,无论何时浏览器都会立即执行渲染队列的任务,即使该值与你操作中修改的值没关联。

因为队列中,可能会有影响到这些值的操作,为了给我们最精确的值,浏览器会立即重排+重绘

强制刷新队列的style样式请求

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop, scrollLeft, scrollWidth, scrollHeight
  3. clientTop, clientLeft, clientWidth, clientHeight
  4. getComputedStyle(), 或者 IE的 currentStyle

我们在开发中,应该谨慎的使用这些style请求,注意上下文关系,避免一行代码一个重排,这对性能是个巨大的消耗

# 重排优化建议

就像上文提到的我们要尽可能的减少重排次数、重排范围,这样说很泛,下面是一些行之有效的建议,大家可以参考一下。

# 1. 分离读写操作

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);

还是上面触发4次重排+重绘的代码,这次只触发了一次重排:

在第一个console的时候,浏览器把之前上面四个写操作的渲染队列都给清空了。剩下的console,因为渲染队列本来就是空的,所以并没有触发重排,仅仅拿值而已。

# 2. 样式集中改变

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';

虽然现在大部分浏览器有渲染队列优化,不排除有些浏览器以及老版本的浏览器效率仍然低下:

建议通过改变class或者csstext属性集中改变样式

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";
// good 
el.className += " theclassname";
// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

# 3. 缓存布局信息

// bad 强制刷新 触发两次重排
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';

// good 缓存布局信息 相当于读写分离
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';

# 4. 离线改变dom

  • 隐藏要操作的dom

    在要操作dom之前,通过display隐藏dom,当操作完成之后,才将元素的display属性为可见,因为不可见的元素不会触发重排和重绘。

    dom.display = 'none'
    // 修改dom样式
    dom.display = 'block'
    
  • 通过使用DocumentFragment创建一个dom碎片,在它上面批量操作dom,操作完成之后,再添加到文档中,这样只会触发一次重排。

  • 复制节点,在副本上工作,然后替换它!

# 5. position属性为absolute或fixed

position属性为absolute或fixed的元素,重排开销比较小,不用考虑它对其他元素的影响

# 6. 优化动画

  • 可以把动画效果应用到position属性为absolute或fixed的元素上,这样对其他元素影响较小

    动画效果还应牺牲一些平滑,来换取速度,这中间的度自己衡量:

    比如实现一个动画,以1个像素为单位移动这样最平滑,但是reflow就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多。

  • 启用GPPU加速

    此部分来自优化CSS重排重绘与浏览器性能

    GPU(图像加速器):

    GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率。

    GPU 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。

    /*
     * 根据上面的结论
     * 将 2d transform 换成 3d
     * 就可以强制开启 GPU 加速
     * 提高动画性能
     */
    div {
      transform: translate3d(10px, 10px, 0);
    }
    

# 结语

重排也是导致DOM脚本执行效率低的关键因素之一,重排与重绘作为大厂经常出现的面试题,并且涉及的性能优化,这是前端必须掌握的基本概念/技能之一(敲黑板!)。

重排会不断触发这是不可避免的,但我们在开发时,应尽量按照文中的建议来组织代码,这种优化,需要平时有意识的去做,一点一滴的去做,希望大家重视一下。

以上2018.12.17

参考资料:

网页性能管理详解

优化CSS重排重绘与浏览器性能

# 点个Star支持我一下~

博客链接

JS基础-原型、原型链 | JS

博客链接

# JS基础-原型、原型链

JS的原型、原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对这个概念一知半解,碰到问题靠“猜”,却不理解它的规则!

# prototype

# 只有函数有prototype属性

let a = {}
let b = function () { }
console.log(a.prototype) // undefined
console.log(b.prototype) // { constructor: function(){...} }

# Object.prototype怎么解释?

其实Object是一个全局对象,也是一个构造函数,以及其他基本类型的全局对象也都是构造函数:

function outTypeName(data, type) {
    let typeName =  Object.prototype.toString.call(data)
    console.log(typeName)
}
outTypeName(Object) //[object Function]
outTypeName(String) // [object Function]
outTypeName(Number) // [object Function]

# 为什么只有函数有prototype属性

JS通过new来生成对象,但是仅靠构造函数,每次生成的对象都不一样。

有时候需要在两个对象之间共享属性,由于JS在设计之初没有类的概念,所以JS使用函数的prototype来处理这部分需要被共享的属性,通过函数的prototype来模拟类:

当创建一个函数时,JS会自动为函数添加prototype属性,值是一个有constructor的对象。

以下是共享属性prototype的栗子:

function People(name) {
    this.name = name
}
People.prototype.age = 23 // 岁数
// 创建两个实例
let People1 = new People('OBKoro1')
let People2 = new People('扣肉')
People.prototype.age = 24 // 长大了一岁
console.log(People1.age, People2.age) // 24 24

为什么People1People2可以访问到People.prototype.age

原因是:People1People2的原型是People.prototype,答案在下方的:构造函数是什么以及它做了什么。

# 原型链

# __proto__Object.getPrototypeOf(target): 对象的原型

__proto__是对象实例和它的构造函数之间建立的链接,它的值是:构造函数的`prototype。

也就是说:__proto__的值是它所对应的原型对象,是某个函数的prototype

Object.getPrototypeOf(target)全等于__proto__

它是ES6的标准,兼容IE9,主流浏览器也都支持,MDN,本文将以Object.getPrototypeOf(target)指代__proto__

# 不要再使用__proto__:

本段摘自阮一峰-ES6入门,具体解析请点击链接查看

  1. __proto__属性没有写入 ES6 的正文,而是写入了附录。

  2. 原因是它本质上是一个内部属性,而不是一个正式的对外的 API,只是由于浏览器广泛支持,才被加入了 ES6

  3. 标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的

  4. 所以无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,应该使用:Object.getPrototypeOf(target)(读操作)、Object.setPrototypeOf(target)(写操作)、Object.create(target)(生成操作)代替

# 构造函数是什么、它做了什么

出自《你不知道的在js》:在js中, 实际上并不存在所谓的'构造函数',只有对于函数的'构造调用'。

上文一直提到构造函数,所谓的构造函数,实际上就是通过关键字new来调用的函数:

let newObj = new someFn() // 构造调用函数

构造/new调用函数的时候做了什么

  1. 创建一个全新的对象。
  2. 这个新对象的原型(Object.getPrototypeOf(target))指向构造函数的prototype对象。
  3. 该函数的this会绑定在新创建的对象上。
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
  5. 我们称这个新对象为构造函数的实例。

原型继承就是利用构造调用函数的特性

SubType.prototype = new SuperType();  // 原型继承:SubType继承SuperType
SubType.prototype.constructor = SubType // 重新指定constructor指向 方便找到构造函数
// 挂载SuperType的this和prototype的属性和方法到SubType.prototype上
  1. �构造调用的第二点:将新对象的Object.getPrototypeOf(target)指向函数的prototype
  2. 构造调用的第三点:该函数的this会绑定在新创建的对象上。
  3. 新对象赋值给SubType.prototype

原型类型有个缺点:多个实例对引用类型的操作会被篡改。

因为每次实例化引用类型的数据都指向同一个地址,所以它们读/写的是同一个数据,当一个实例对其进行操作,其他实例的数据就会一起更改。

# 原型链是什么

来看个例子:

function foo() { }
const newObj = new foo() // 构造调用foo 返回一个新对象
const newObj__proto__ = Object.getPrototypeOf(newObj) // 获取newObj的原型对象
newObj__proto__ === foo.prototype // true 验证newObj的原型指向foo
const foo__proto__ = Object.getPrototypeOf(foo.prototype) // 获取foo.prototype的原型
foo__proto__ === Object.prototype // true foo.prototype的原型是Object.prototype
``
如果用以前的语法,从`newObj`查找`foo`的原型,是这样的:
```js
newObj.__proto__.__proto__ // 这种关系就是原型链

可以用以下三句话来理解原型链

  1. 每个对象都拥有一个原型对象: newObj的原型是foo.prototype
  2. 对象的原型可能也是继承其他原型对象的: foo.prototype也有它的原型Object.prototype
  3. 一层一层的,以此类推,这种关系就是原型链

# 一个对象是否在另一个对象的原型链上

如果一个对象存在另一个对象的原型链上,我们可以说:它们是继承关系。

判断方式有两种,但都是根据构造函数的prototype是否在原型链上来判断的:

  1. instanceof: 用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置

语法:object instanceof constructor

let test = function () { }
let testObject = new test();
testObject instanceof test // true test.prototype在testObject的原型链上
 testObject instanceof Function // false Function.prototype 不在testObject的原型链上
testObject instanceof Object // true Object.prototype在testObject的原型链上
  1. isPrototypeOf:测试一个对象是否存在于另一个对象的原型链上

语法:prototypeObj.isPrototypeOf(object)

let test = function () { }
let testObject = new test();
test.prototype.isPrototypeOf(testObject) // true test.prototype在testObject的原型链上
Object.prototype.isPrototypeOf(testObject) // true Object.prototype在testObject的原型链上

# 原型链的终点: Object.prototype

Object.prototype是原型链的终点,所有对象都是从它继承了方法和属性。

Object.prototype没有原型对象

const proto = Object.getPrototypeOf(Object.prototype) // null

下面是两个验证例子,有疑虑的同学多写几个测试用例印证一下。

字符串原型链的终点Object.prototype

let test = '由String函数构造出来的'
let stringPrototype = Object.getPrototypeOf(test) // 字符串的原型
stringPrototype === String.prototype // true 字符串的原型是String对象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true String对象的原型是Object对象

函数原型链的终点:Object.prototype

let test = function () { }
let fnPrototype = Object.getPrototypeOf(test)
fnPrototype === Function.prototype // true test的原型是Function.prototype
Object.getPrototypeOf(Function.prototype) === Object.prototype // true

# 原型链用来做什么?

# 属性查找:

如果试图访问对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性,以此类推

我们用一个例子来形象说明一下:

let test = '由String函数构造出来的'
let stringPrototype = Object.getPrototypeOf(test) // 字符串的原型
stringPrototype === String.prototype // true 字符串的原型是String对象
Object.getPrototypeOf(stringPrototype) === Object.prototype // true String对象的原型是Object对象

当你访问test的某个属性时,浏览器会进行以下查找:

  1. 浏览器首先查找test 本身
  2. 接着查找它的原型对象:String.prototype
  3. 最后查找String.prototype的原型对象:Object.prototype
  4. 一旦在原型链上找到该属性,就会立即返回该属性,停止查找。
  5. 原型链上的原型都没有找到的话,返回undefiend

这种查找机制还解释了字符串为何会有自带的方法: slice/split/indexOf等。

准确的说:

  • 这些属性和方法是定义在String这个全局对象/函数上的。
  • 字符串的原型指向了String函数的prototype
  • 之后通过查找原型链,在String函数的prototype中找到这些属性和方法。

# 拒绝查找原型链:

hasOwnProperty: 指示对象自身属性中是否具有指定的属性

语法:obj.hasOwnProperty(prop)

参数: prop 要查找的属性

返回值: 用来判断某个对象是否含有指定的属性的Boolean

let test ={ 'OBKoro1': '扣肉' }
test.hasOwnProperty('OBKoro1');  // true
test.hasOwnProperty('toString'); // false test本身没查找到toString 

这个API是挂载在object.prototype上,所有对象都可以使用,API会忽略掉那些从原型链上继承到的属性。

# 扩展:

# 实例的属性

你知道构造函数的实例对象上有哪些属性吗?这些属性分别挂载在哪个地方?原因是什么?

function foo() {
    this.some = '222'
    let ccc = 'ccc'
    foo.obkoro1 = 'obkoro1'
    foo.prototype.a = 'aaa'
}
foo.koro = '扣肉'
foo.prototype.test = 'test'
let foo1 = new foo() // `foo1`上有哪些属性,这些属性分别挂载在哪个地方
foo.prototype.test = 'test2' // 重新赋值

上面这道是考察JS基础的题,很多人都没说对,原因是没有彻底掌握this原型链函数

# 想一下再看解析:

# 想一下再看解析:

# 想一下再看解析:

# 想一下再看解析:

# 想一下再看解析:

  1. this.somefoo1对象的属性

通过构造调用foothis指向foo1,所以this.some挂载在foo1对象下。

属性查找: foo1.some

foo1.some直接读取foo1的属性。

  1. foo1.testfoo1.afoo1对象的原型

根据上文提到的:构造/new调用函数的时候会创建一个新对象(foo1),自动将foo1的原型(Object.getPrototypeOf(foo1))指向构造函数的prototype对象。

构造调用会执行函数,所以foo.prototype.a = 'aaaaa'也会执行,单就赋值这个层面来说写在foo外面和写在foo里面是一样的。

属性查找:foo1.testfoo1.a

  • foo1本身没有找到,继续查找
  • foo1的原型Object.getPrototypeOf(foo1)上找到了atest,返回它们,停止查找。
  1. foo1.obkoro1foo1.koro:返回undefined

# 静态属性: foo.obkoro1foo.koro

函数在JS中是一等公民,它也是一个对象, 用来模拟类。

这两个属性跟foo1没有关系,它是对象foo上的两个属性(类似函数的:arguments/prototype/length等属性),称为静态属性

它们只能通过foo.obkoro1foo.koro来访问。

# 原型对象改变,原型链下游获取的值也会改变

上面那个例子中的foo1.test的值是什么?

foo.prototype.test = 'test'
let foo1 = new foo() // `foo1`上有哪些属性,这些属性分别挂载在哪个地方
foo.prototype.test = 'test2' // 重新赋值

foo1.test的值是test2,原因是:foo1的原型对象是Object.getPrototypeOf(foo1)存的指针,指向foo.prototype的内存地址,不是拷贝,每次读取的值都是当前foo.prototype的最新值。

打印foo1

# 小结

写了好几天,之前网上很多图文博客,那些线指来指去,就我个人看来还是比较难以理解的,所以本文纯文字的形式来描述这些概念,相信认真看完的同学肯定都有所收获,如果没看懂的话,建议多看几遍,这部分概念真的很重要!

PS:实际上还有很多引申出来的东西没写全,准备放到其他文章中去写。

# 觉得我的博客对你有帮助的话,就给我点个Star吧!

前端进阶积累公众号GitHub、wx:OBkoro1、邮箱:[email protected]

以上2019/8/25

作者:OBKoro1

参考资料:

MDN:对象原型

JS原型链与继承别再被问倒了

从__proto__和prototype来深入理解JS对象和原型链

# 点个Star支持我一下~

博客链接

防抖函数&节流函数 | JS

博客链接

# 防抖函数&节流函数

节流函数和防抖函数是 JS 比较重要的概念,应用好了可以提高很大的性能,在面试中也是很高频的一个考点。下面一起来看看这两种方法是如何使用的:

# 防抖函数(debounce):

# 概念:

在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时

# 生活中的栗子:

如果有人进电梯(触发事件),那电梯将在 10 秒钟后出发(执行事件),这时如果又有人进电梯了(在 10 秒内再次触发该事件),我们又得等 10 秒再出发(重新计时)。

# 代码栗子:

/**
 * @description: 防抖函数:函数被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时
 * @param {Function} fn 要执行的函数
 * @param {Number} wait  wait毫秒后执行回调
 * @param {*} ...arr 传递给fn的参数
 */
function debounce(fn, wait, ...arr) {
  let timer = null;
  return () => {
    if (timer) {
      // 如果有一个函数在等待执行 清除定时器 下面重新计时
      clearTimeout(timer);
      timer = null; // 清空timer 下次重启定时器
    }
    // 设定时器/重置定时器
    timer = setTimeout(() => {
      fn(...arr); // wait时间后 执行回调 期间再触发debounce 需要重新等待
    }, wait);
  };
}
// 要防抖的函数
let actionFn = function(a, b) {
  console.log('回调', a, b);
};
setInterval(debounce(actionFn, 500, 'actionFn参数1', '参数2'), 1000); // 第一次在1500ms后触发,之后每1000ms触发一次
setInterval(debounce(actionFn, 2000), 1000); // 还没执行就一直重复触发,不会执行

可以这样理解

函数触发停止一段时间后(期间不能再触发 debounce,否则将重新计时),再执行回调函数

# 机制:

防抖函数主要利用定时器的延迟执行特性,根据是否有定时器在等待执行:

  • 触发了一个事件后:如果有一个定时任务待执行,就清除定时器,重新计时。
  • 如果没有任务待执行,就定时执行这个事件。

# 应用场景:

  1. 表单的连续点击,防止重复提交。比如重复发送一篇文章。
  2. 类百度的搜索,连续输入等输入停止后再搜索。
  3. 一直拖动浏览器窗口,只想触发一次事件等。

# 节流函数(throttle):

# 概念:

规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行(单位时间内有事件被多次触发则,只生效一次)

# 生活中的栗子:

漏水的自来水水龙头,尽管水龙头里面有很多水(一直在触发事件),但还是一滴一滴的往下滴(单位事件内只生效一次)。

# 代码栗子:

/**
 * @description: 节流函数:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行
 * @param {Function} fn 要执行的函数
 * @param {Number} gapTime  单位时间
 * @param {*} ...arr 传递给fn的参数
 */
function throttle(fn, gapTime, ...arr) {
  let last = 0; // 上次执行时间 第一次马上执行
  return () => {
    let nowTime = Date.now(); // 当前时间
    // 当前时间-上次执行的时间是否超过间隔时间 就执行回调
    if (nowTime - last > gapTime) {
      fn(...arr); // ...arr为fn的参数
      last = nowTime; // 重置上次执行时间为当前时间 方便下次执行
    }
  };
}
let actionFn = (a, b) => {
  console.log('回调', a, b); // 要执行的函数
};
setInterval(throttle(actionFn, 1000, 'actionFn参数1', '参数2'), 10);
// 每隔10毫秒都会触发一次throttle,每隔一秒触发一次actionFn回调(1秒内再次触发被丢弃)

# 机制:

节流函数根据时间差是否超过给定时间(gapTime)来决定是否触发回调。

# 应用场景:

  1. 自动保存草稿功能,当用户在输入的时候(一直触发事件),单位时间内只保存一次草稿。
  2. 游戏中的刷新率

# 作用和本质:

  • 防抖函数和节流函数主要解决的问题是:防止函数”短时间“的重复执行

  • 它们本质上是:在时间轴上控制函数的执行次数

# 应用实例,需要加个括号:

因为返回debouncethrottle返回的是一个函数,所以如果不是自动执行的事件监听回调,我们应该再后面加个(),执行返回的闭包函数。

document.onclick = () => {
  // throttle(actionFn, 1000) 这样不会执行 只返回了一个闭包函数
  throttle(actionFn, 1000, '执行函数参数1', '执行函数参数2')(); // 执行闭包函数
};

# 点个Star支持我一下~

博客链接

前端弹幕效果实现思路 | effect

博客链接

# 前端弹幕效果实现思路

这是几个月前写的文章,文章下面有很多精彩的讨论,或许能给你一点启发

更好的方式

可以利用canvas或者 CSS 动画来实现弹幕需求,不过文章中的思路,可以参考一下。


# 实现效果:

# 实现原理:

实现弹幕的原理,并不算太复杂,耗费一些时间,怼一怼应该都可以做出来。

  1. 获取弹幕数据。
  2. 将弹幕设置为四个通道,每个通道最多只能出现两条弹幕。
  3. 使用setInterval动态设置domleft属性。
  4. 使用 dom 的offsetWidth和屏幕的宽度判断元素是否滚动超出屏幕,然后移除 dom。

# 实现步骤:

# 1. 首先看一下html的结构。

<div class="detailImg">
    <img src="url"/>
    <div id="barrageDiv">
        <div id="barrageLayer1"></div>
        <div id="barrageLayer2"></div>
        <div id="barrageLayer3"></div>
        <div id="barrageLayer4"></div>
    </div>
</div>
<!--detailImg 设置relative, barrageDiv设置z-index在图片上面,以及图片的位置-->
<!-- barrageLayer1~4 主要设置了一个top属性让四个div在各自的水平线上,形成四个通道-->

关于这里的 css 样式,关键点都在上面说了,就注意一下上面通道是怎样形成的,就可以了。具体的样式也就不贴出来了,就根据各自的需求来吧。

# 2.获取弹幕所需要的数据。

要实现弹幕效果肯定需要有数据,这里就是发请求了。

获取数据时,要考虑数据量,一次不可能全部都获取,可以一次获取一部分,当数据要加载完的时候,再次请求数据。

这里要记录数据数据是否全部请求完成,如果请求完成,就可以不再发送数据,直接用之前获取的全部数据就可以了。

# 3.执行弹幕的函数。

弹幕数据获取后,就执行弹幕运行的函数,因为我在写弹幕函数的时候,设置了很多数据状态,这里就大概讲一下实现思路和关键部分代码。

# 弹幕函数包括的功能:

  1. 定时获取数据(判断数据是否加载完毕)
  2. 定时发射弹幕(判断通道是否闲置),传入弹幕所需要的内容,用户头像等。
  3. 创建 dom 内容,根据传参生成弹幕 div,设置 style 属性,根据控制弹幕数据数组的下标将 div 插入对应的 dom 中。
  4. 采用定时器移动 dom,这里是根据内容长度定义弹幕的移动速度。
  5. 移动弹幕的过程中判断四个通道是否处于闲置状态,当 dom 移动出了屏幕,移动 dom 并且清除定时器。
function barrage(){
    //第一部分先判断数据是否加载完成 这里是一个定时器,设置为15秒。
    //如果数据还未加载完毕,就再次运行请求数据的接口,请求的页数可以 数组/每次请求的条数+1
    //数据加载完毕就清除定时器。(我将定时器都保存在vue 组件的data里面) 清除的时候clearInterval(this.data);
<span class="token comment">//定时发射</span>
    _this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>divStatus<span class="token punctuation">.</span>intervalid<span class="token operator">=</span><span class="token function">setInterval</span><span class="token punctuation">(</span> selfTime<span class="token punctuation">,</span><span class="token number">1100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">function</span> <span class="token function">selfTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span>_this<span class="token punctuation">.</span>dataNum<span class="token operator">>=</span>_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>data<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token comment">//当dataNum大于等于数组的数量时,弹幕从头再来一遍</span>
        _this<span class="token punctuation">.</span>dataNum<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token comment">//设置四个通道的变量,当这几个变量为false的时候,才可发射</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span>divStatus<span class="token punctuation">.</span>div1<span class="token operator">===</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token comment">//这里只演示其中一个变量</span>
    divStatus<span class="token punctuation">.</span>div1<span class="token operator">=</span><span class="token boolean">true</span><span class="token punctuation">;</span>
    _this<span class="token punctuation">.</span>dataNum<span class="token operator">++</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token function">barrageOut</span><span class="token punctuation">(</span>_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>data<span class="token punctuation">[</span>_this<span class="token punctuation">.</span>dataNum<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>content<span class="token punctuation">,</span>_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>data<span class="token punctuation">[</span>_this<span class="token punctuation">.</span>dataNum<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>commentator<span class="token punctuation">.</span>headImgUrl<span class="token punctuation">,</span>_this<span class="token punctuation">.</span>dataNum<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">// 创建弹幕内容,自定义弹幕移动速度</span>
<span class="token keyword">function</span> <span class="token function">barrageOut</span><span class="token punctuation">(</span>text<span class="token punctuation">,</span>imgUrl<span class="token punctuation">,</span>num<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">//text:弹幕的内容,imgUrl:用户的头像,num:数组的第几个</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>num<span class="token operator">%</span><span class="token number">4</span><span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">//根据数组下标 创建对应通道的节点 这里也演示其中一个</span>
    barrageLayer<span class="token operator">=</span>document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'barrageLayer1'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 创建dom内容 定义dom style样式</span>
<span class="token keyword">let</span> divBox <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> divBoxImg<span class="token operator">=</span>document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'span'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> divBoxText<span class="token operator">=</span>document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'span'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
divBox<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'class'</span><span class="token punctuation">,</span><span class="token string">'barrageDivClass'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
divBoxText<span class="token punctuation">.</span>innerHTML<span class="token operator">=</span>text<span class="token punctuation">;</span>
divBox<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>divBoxImg<span class="token punctuation">)</span><span class="token punctuation">;</span>
divBoxImg<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'class'</span><span class="token punctuation">,</span><span class="token string">'barrageDivClass_img'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
divBoxImg<span class="token punctuation">.</span>style<span class="token punctuation">.</span>backgroundImage<span class="token operator">=</span><span class="token template-string"><span class="token string">`url(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>imgUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)`</span></span><span class="token punctuation">;</span>
divBox<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>divBoxText<span class="token punctuation">)</span><span class="token punctuation">;</span>
divBox<span class="token punctuation">.</span>style<span class="token punctuation">.</span>left<span class="token operator">=</span>document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>clientWidth<span class="token operator">+</span><span class="token number">2000</span><span class="token operator">+</span><span class="token string">'px'</span><span class="token punctuation">;</span><span class="token comment">// 初始化left位置,一开始在屏幕的右侧</span>
barrageLayer<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>divBox<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 定时器移动dom,形成弹幕</span>
<span class="token keyword">let</span> time<span class="token punctuation">,</span>l<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>
time<span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span>text<span class="token punctuation">.</span>length<span class="token operator">&lt;</span><span class="token number">15</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token comment">// 这里可以根据需求自定义弹幕加载的速度</span>
    l<span class="token operator">=</span>l<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
    l<span class="token operator">=</span>l<span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">//通过减少left属性移动这个div 从右往左移动</span>
    divBox<span class="token punctuation">.</span>style<span class="token punctuation">.</span>left <span class="token operator">=</span> document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>clientWidth<span class="token operator">+</span>l<span class="token operator">+</span><span class="token string">'px'</span><span class="token punctuation">;</span>
    <span class="token keyword">let</span> <span class="token function-variable function">delDiv</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=></span><span class="token punctuation">{</span>
        <span class="token keyword">if</span><span class="token punctuation">(</span>num<span class="token operator">%</span><span class="token number">4</span><span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
            <span class="token comment">//在移动弹幕的过程中判断四个通道是否处于闲置状态 这里只演示其中一个</span>
            barrageLayer<span class="token operator">=</span>document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'barrageLayer1'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">if</span><span class="token punctuation">(</span>barrageLayer<span class="token punctuation">.</span>childNodes<span class="token punctuation">.</span>length<span class="token operator">&lt;</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
            <span class="token comment">//判断弹幕数量,如果小于2,设为false,上面的定时器可以继续发射弹幕</span>
            divStatus<span class="token punctuation">.</span>div1<span class="token operator">=</span><span class="token boolean">false</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
            divStatus<span class="token punctuation">.</span>div1<span class="token operator">=</span><span class="token boolean">true</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span> l <span class="token operator">&lt;=</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token operator">-</span>divBox<span class="token punctuation">.</span>offsetWidth<span class="token operator">-</span><span class="token number">120</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span>_this<span class="token punctuation">.</span>barrageStatus<span class="token punctuation">.</span>divStatus<span class="token punctuation">.</span><span class="token keyword">switch</span><span class="token operator">==</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment">//弹幕开关</span>
        <span class="token function">delDiv</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span><span class="token punctuation">(</span>l <span class="token operator">&lt;=</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token operator">-</span>divBox<span class="token punctuation">.</span>offsetWidth<span class="token operator">-</span>document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>clientWidth<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">{</span>
        <span class="token comment">//不断减少left属性,当小于这个内容的宽度,并且滚了120的时候</span>
            barrageLayer<span class="token punctuation">.</span><span class="token function">removeChild</span><span class="token punctuation">(</span>divBox<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//移除dom</span>
            <span class="token function">clearInterval</span><span class="token punctuation">(</span>time<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//清除这个div的定时器</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>
        <span class="token function">clearInterval</span><span class="token punctuation">(</span>time<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//清除这个div的定时器</span>
    <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token number">20</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

}

# 点个Star支持我一下~

博客链接

手摸手教你使用WebSocket | JS

博客链接

# 手摸手教你使用WebSocket

在本篇文章之前,WebSocket很多人听说过,没见过,没用过,以为是个很高大上的技术,实际上这个技术并不神秘,可以说是个很容易就能掌握的技术,希望在看完本文之后,马上把文中的栗子拿出来自己试一试,实践出真知。

游泳、健身了解一下:博客前端积累文档公众号GitHub

# WebSocket解决了什么问题:

客户端(浏览器)和服务器端进行通信,只能由客户端发起ajax请求,才能进行通信,服务器端无法主动向客户端推送信息。

当出现类似体育赛事、聊天室、实时位置之类的场景时,客户端要获取服务器端的变化,就只能通过轮询(定时请求)来了解服务器端有没有新的信息变化。

轮询效率低,非常浪费资源(需要不断发送请求,不停链接服务器)

WebSocket的出现,让服务器端可以主动向服务器端发送信息,使得浏览器具备了实时双向通信的能力,这就是WebSocket解决的问题

# 一个超简单的栗子:

新建一个html文件,将本栗子找个地方跑一下试试,即可轻松入门WebSocket

function socketConnect(url) {
    // 客户端与服务器进行连接
    let ws = new WebSocket(url); // 返回`WebSocket`对象,赋值给变量ws
    // 连接成功回调
    ws.onopen = e => {
        console.log('连接成功', e)
        ws.send('我发送消息给服务端'); // 客户端与服务器端通信
    }
    // 监听服务器端返回的信息
    ws.onmessage = e => {
        console.log('服务器端返回:', e.data)
        // do something
    }
    return ws; // 返回websocket对象
}
let wsValue = socketConnect('ws://121.40.165.18:8800'); // websocket对象

上述栗子中WebSocket的接口地址出自:WebSocket 在线测试,在开发的时候也可以用于测试后端给的地址是否可用。

# webSocket的class类:

当项目中很多地方使用WebSocket,把它封成一个class类,是更好的选择。

下面的栗子,做了非常详细的注释,建个html文件也可直接使用,websocket的常用API都放进去了。

下方注释的代码,先不用管,涉及到心跳机制,用于保持WebSocket连接的

class WebSocketClass {
    /**
     * @description: 初始化实例属性,保存参数
     * @param {String} url ws的接口
     * @param {Function} msgCallback 服务器信息的回调传数据给函数
     * @param {String} name 可选值 用于区分ws,用于debugger
     */
    constructor(url, msgCallback, name = 'default') {
        this.url = url;
        this.msgCallback = msgCallback;
        this.name = name;
        this.ws = null;  // websocket对象
        this.status = null; // websocket是否关闭
    }
    /**
     * @description: 初始化 连接websocket或重连webSocket时调用
     * @param {*} 可选值 要传的数据
     */
    connect(data) {
        // 新建 WebSocket 实例
        this.ws = new WebSocket(this.url);
        this.ws.onopen = e => {
            // 连接ws成功回调
            this.status = 'open';
            console.log(`${this.name}连接成功`, e)
            // this.heartCheck();
            if (data !== undefined) {
                // 有要传的数据,就发给后端
                return this.ws.send(data);
            }
        }
        // 监听服务器端返回的信息
        this.ws.onmessage = e => {
            // 把数据传给回调函数,并执行回调
            // if (e.data === 'pong') {
            //     this.pingPong = 'pong'; // 服务器端返回pong,修改pingPong的状态
            // }
            return this.msgCallback(e.data);
        }
        // ws关闭回调
        this.ws.onclose = e => {
            this.closeHandle(e); // 判断是否关闭
        }
        // ws出错回调
        this.onerror = e => {
            this.closeHandle(e); // 判断是否关闭
        }
    }
    // heartCheck() {
    //     // 心跳机制的时间可以自己与后端约定
    //     this.pingPong = 'ping'; // ws的心跳机制状态值
    //     this.pingInterval = setInterval(() => {
    //         if (this.ws.readyState === 1) {
    //             // 检查ws为链接状态 才可发送
    //             this.ws.send('ping'); // 客户端发送ping
    //         }
    //     }, 10000)
    //     this.pongInterval = setInterval(() => {
    //         this.pingPong = false;
    //         if (this.pingPong === 'ping') {
    //             this.closeHandle('pingPong没有改变为pong'); // 没有返回pong 重启webSocket
    //         }
    //         // 重置为ping 若下一次 ping 发送失败 或者pong返回失败(pingPong不会改成pong),将重启
    //         console.log('返回pong')
    //         this.pingPong = 'ping'
    //     }, 20000)
    // }
    // 发送信息给服务器
    sendHandle(data) {
        console.log(`${this.name}发送消息给服务器:`, data)
        return this.ws.send(data);
    }
    closeHandle(e = 'err') {
        // 因为webSocket并不稳定,规定只能手动关闭(调closeMyself方法),否则就重连
        if (this.status !== 'close') {
            console.log(`${this.name}断开,重连websocket`, e)
            // if (this.pingInterval !== undefined && this.pongInterval !== undefined) {
            //     // 清除定时器
            //     clearInterval(this.pingInterval);
            //     clearInterval(this.pongInterval);
            // }
            this.connect(); // 重连
        } else {
            console.log(`${this.name}websocket手动关闭`)
        }
    }
    // 手动关闭WebSocket
    closeMyself() {
        console.log(`关闭${this.name}`)
        this.status = 'close';
        return this.ws.close();
    }
}
function someFn(data) {
    console.log('接收服务器消息的回调:', data);
}
// const wsValue = new WebSocketClass('ws://121.40.165.18:8800', someFn, 'wsName'); // 这个链接一天只能发送消息50次
const wsValue = new WebSocketClass('wss://echo.websocket.org', someFn, 'wsName'); // 阮一峰老师教程链接
wsValue.connect('立即与服务器通信'); // 连接服务器
// setTimeout(() => {
//     wsValue.sendHandle('传消息给服务器')
// }, 1000);
// setTimeout(() => {
//     wsValue.closeMyself(); // 关闭ws
// }, 10000)

栗子里面我直接写在了一起,可以把class放在一个js文件里面,export出去,然后在需要用的地方再import进来,把参数传进去就可以用了。

# WebSocket不稳定

WebSocket并不稳定,在使用一段时间后,可能会断开连接,貌似至今没有一个为何会断开连接的公论,所以我们需要让WebSocket保持连接状态,这里推荐两种方法。

# WebSocket设置变量,判断是否手动关闭连接:

class类中就是用的这种方式:设置一个变量,在webSocket关闭/报错的回调中,判断是不是手动关闭的,如果不是的话,就重新连接,这样做的优缺点如下:

  • 优点:请求较少(相对于心跳连接),易设置。
  • 缺点:可能会导致丢失数据,在断开重连的这段时间中,恰好双方正在通信。

# WebSocket心跳机制:

因为第一种方案的缺点,并且可能会有其他一些未知情况导致断开连接而没有触发Error或Close事件。这样就导致实际连接已经断开了,而客户端和服务端却不知道,还在傻傻的等着消息来。

然后聪明的程序猿们想出了一种叫做心跳机制的解决方法:

客户端就像心跳一样每隔固定的时间发送一次ping,来告诉服务器,我还活着,而服务器也会返回pong,来告诉客户端,服务器还活着。

具体的实现方法,在上面class的注释中,将其打开,即可看到效果

# 关于WebSocket

怕一开始就堆太多文字性的内容,把各位吓跑了,现在大家已经会用了,我们再回头来看看WebSocket的其他知识点。

# WebSocket的当前状态:WebSocket.readyState

下面是WebSocket.readyState的四个值(四种状态):

  • 0: 表示正在连接
  • 1: 表示连接成功,可以通信了
  • 2: 表示连接正在关闭
  • 3: 表示连接已经关闭,或者打开连接失败

我们可以利用当前状态来做一些事情,比如上面栗子中当WebSocket链接成功后,才允许客户端发送ping

if (this.ws.readyState === 1) {
    // 检查ws为链接状态 才可发送
    this.ws.send('ping'); // 客户端发送ping
}

# WebSocket还可以发送/接收 二进制数据

这里我也没有试过,我是看阮一峰老师的WebSocket教程才知道有这么个东西,有兴趣的可以再去谷歌,大家知道一下就可以。

二进制数据包括:blob对象和Arraybuffer对象,所以我们需要分开来处理。

    // 接收数据
ws.onmessage = function(event){
    if(event.data instanceof ArrayBuffer){
        // 判断 ArrayBuffer 对象
    }
<span class="token keyword">if</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data <span class="token keyword">instanceof</span> <span class="token class-name">Blob</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token comment">// 判断 Blob 对象</span>
<span class="token punctuation">}</span>

}

// 发送 Blob 对象的例子
let file = document.querySelector('input[type="file"]').files[0];
ws.send(file);

// 发送 ArrayBuffer 对象的例子
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
ws.send(binary.buffer);

如果你要发送的二进制数据很大的话,如何判断发送完毕:

webSocket.bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去:

var data = new ArrayBuffer(10000000);
socket.send(data);
if (socket.bufferedAmount === 0) {
    // 发送完毕
} else {
    // 发送还没结束
}

上述栗子出自阮一峰老师的WebSocket教程

# WebSocket的优点:

最后再吹一波WebSocket:

  1. 双向通信(一开始说的,也是最重要的一点)。

  2. 数据格式比较轻量,性能开销小,通信高效

    协议控制的数据包头部较小,而HTTP协议每次通信都需要携带完整的头部

  3. 更好的二进制支持

  4. 没有同源限制,客户端可以与任意服务器通信

  5. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器


# 结语

看了本文之后,如果还是有点迷糊的话,一定要把文中的两个栗子,新建个html文件跑起来,自己鼓捣鼓捣一下。不然读多少博客/教程都没有用,实践才出真知,切勿纸上谈兵。

以上2018.10.22

参考资料:

WebSocket 教程

理解WebSocket心跳及重连机制

WebSocket协议:5分钟从入门到精通

# 点个Star支持我一下~

博客链接

CSS 概念-BFC 深入浅出 | CSS

博客链接

# CSS 概念-BFC 深入浅出

好记性不如烂笔头,研究了一下 BFC,发现里面比较细的东西也是很多的!关于 BFC,很多人可能都听说过 BFC 这个东西,大概知道这是个啥东西,相信很多人对此并没有一个非常细致的了解,本文预计篇幅较长,认真,耐着性子看,应该都能够比较深入的理解 BFC 这个概念的规则、作用以及用法。希望喜欢的朋友可以点个赞,或者关注一波本人,谢谢。

# BFC 是什么鬼?

所谓的 BFC 就是 css 布局的一个概念,是一块区域,一个环境。

先稳住别懵逼,接着往下走。

# 关于 BFC 的定义:

BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有Block-level box参与(在下面有解释), 它规定了内部的 Block-level Box 如何布局,并且与这个区域外部毫不相干。

# 通俗的说:

BFC 可以简单的理解为某个元素的一个 CSS 属性,拥有这个属性的元素对内部元素和外部元素会表现出一些特性,这就是 BFC

# 触发条件

满足下列条件之一就可触发 BFC

  1. 根元素,即 HTML 元素

  2. float的值不为none

  3. overflow的值不为visible

  4. display的值为inline-blocktable-celltable-caption

  5. position的值为absolutefixed

# BFC 布局规则:

  1. 内部的 Box 会在垂直方向,一个接一个地放置。

  2. Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠

  3. 每个元素的 margin box 的左边, 与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。

  4. BFC 的区域不会与 float box 重叠。

  5. BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。

  6. 计算 BFC 的高度时,浮动元素也参与计算

# BFC 有哪些作用:

  1. 自适应两栏布局
  2. 可以阻止元素被浮动元素覆盖
  3. 可以包含浮动元素——清除内部浮动
  4. 分属于不同的 BFC 时可以阻止 margin 重叠

# BFC 的规则和作用介绍:

# BFC 布局规则 1:内部的 Box 会在垂直方向,一个接一个地放置。

上文定义中提到过的块级盒:block-level box,在这里解析一波:

这个就是我们平常操作盒子的组成

我们平常说的盒子是由 margin、border、padding、content 组成的,实际上每种类型的四条边定义了一个盒子,分别是分别是content box、padding box、border box、margin box,这四种类型的盒子一直存在,即使他们的值为 0.决定块盒在包含块中与相邻块盒的垂直间距的便是 margin-box。

提示:Box 之间的距离虽然也可以使用 padding 来控制,但是此时实际上还是属于 box 内部里面,而且使用 padding 来控制的话就不能再使用 border 属性了。

布局规则 1 就是我们平常 div 一行一行块级放置的样式,大家想一下就知道了,这里就不展开了。

# BFC 布局规则 2:Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠。

上文提到过,决定块盒在包含块中与相邻块盒的垂直间距的便是 margin-box。,上面的栗子就是这种情况。

演示中 css 属性设置:上面的 box:margin-bottom: 100px;下面的 box:margin-top: 100px;(他们是同一侧的 margin,所以会发生 margin 重叠的情况,两个 div 的距离实际上只有 100px。)

# BFC 的作用 4:阻止 margin 重叠:

当两个相邻块级子元素分属于不同的 BFC时可以阻止 margin 重叠

操作方法:给其中一个 div 外面包一个 div,然后通过触发外面这个 div 的 BFC,就可以阻止这两个 div 的 margin 重叠

下面是代码:

<div class="aside"></div>
<div class="text">
    <div class="main"></div>
</div>
<!-- css代码 -->
.aside {
  margin-bottom: 100px; //margin属性
  width: 100px;
  height: 150px;
  background: #f66;
}
.main {
  margin-top: 100px; //margin属性
  height: 200px;
  background: #fcc;
}
.text {
  /*盒子main的外面包一个div,通过改变此div的属性使两个盒子分属于两个不同的BFC,以此来阻止margin重叠*/
  overflow: hidden; //此时已经触发了BFC属性。
}

ps:触发方式可以参考上文给出的触发条件。

# 这里有一个网址可以在线演示,通过演示,可以更直观一点:

这里面也是一篇好文章,关于BFC的

# BFC 布局规则 3:每个元素的 margin box 的左边, 与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。

<div class="par">
    <div class="child"></div>
    //给这两个子div加浮动,浮动的结果,如果没有清除浮动的话,父div不会将下面两个div包裹,但还是在父div的范围之内。
    <div class="child"></div>
</div>

解析:给这两个子 div 加浮动,浮动的结果,如果没有清除浮动的话,父 div 不会将下面两个 div 包裹,但还是在父 div 的范围之内,左浮是子 div 的左边接触父 div 的 borderbox 的左边,右浮是子 div 接触父 div 的 borderbox 右边,除非设置 margin 来撑开距离,否则一直是这个规则。

# BFC 作用 3:可以包含浮动元素——清除内部浮动

给父 divpar 加上 overflow: hidden;

清除浮动原理:触发父 div 的 BFC 属性,使下面的子 div 都处在父 div 的同一个 BFC 区域之内,此时已成功清除浮动。

还可以向同一个方向浮动来达到清除浮动的目的,清除浮动的原理是两个 div 都位于同一个浮动的 BFC 区域之中。

# BFC 布局规则 4:BFC 的区域不会与 float box 重叠:

<div class="aside"></div>
<div class="text">
    <div class="main"></div>
</div>
.aside {
  width: 100px;
  height: 150px;
  float: left;
  background: #f66;
}
.main {
  height: 200px;
  overflow: hidden; //触发main盒子的BFC
  background: #fcc;
}
.text {
  width: 500px;
}

上面 aside 盒子有一个浮动属性,覆盖了 main 盒子的内容,main 盒子没有清除 aside 盒子的浮动。只做了一个动作,就是触发自身的 BFC,然后就不再被 aside 盒子覆盖了。所以:BFC 的区域不会与 float box 重叠

# BFC 作用:自适应两栏布局。

还是上面的代码,此时 BFC 的区域不会与 float box 重叠,因此会根据包含块(父 div)的宽度,和 aside 的宽度,自适应宽度。


# BFC 与 Layout

IE 作为浏览器中的奇葩,当然不可能按部就班的支持 BFC 标准,于是乎 IE 中有了 Layout 这个东西。Layout 和 BFC 基本是等价的,为了处理 IE 的兼容性,在需要触发 BFC 时,我们除了需要用触发条件中的 CSS 属性来触发 BFC,还需要针对 IE 浏览器使用 zoom: 1 来触发 IE 浏览器的 Layout。

# 有趣的文本:

.par {
  margin-top: 3rem;
  border: 5px solid #fcc;
  width: 300px;
}
.child {
  border: 5px solid #f66;
  width: 100px;
  height: 100px;
  float: left;
}

原因:

这里两个 div 被撑开,是因为父 div 被 p 标签撑开了,并不是因为清除浮动的原因,从下面这张图片可以清楚的知道。

其实以上的几个例子都体现了 BFC 布局规则第五条————

# BFC 布局规则 5:BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。

文本环绕 float

<div style="float: left; width: 100px; height: 100px; background: #000;">
</div>
<div style="height: 200px; background: #AAA;">
    <div style=" width: 30px; height: 30px; background: red;"></div>
    <p>content</p> <p>content</p> <p>content</p> <p>content</p> <p>content</p>
</div>

问题:为什么 div 的左上角被覆盖了,而文本却没有被覆盖,float 不是应该跟普通流不在一个层级吗?是因为 float 属性不生效吗?

解决

float 属性定义元素在哪个方向浮动。以往这个属性总应用于图像,使文本围绕在图像周围,不过在 CSS 中,任何元素都可以浮动。浮动元素会生成一个块级框,而不论它本身是何种元素。

从上图可以看到,float 属性确实生效,将 float 隐藏后,下面还有一个红色的 div,这个 div 是被黑色 div 所覆盖掉的。div 会被 float 覆盖,而文本却没有被 float 覆盖,是因为float 当初设计的时候就是为了使文本围绕在浮动对象的周围

以上。2017.5.4.

# 点个Star支持我一下~

博客链接

[读书笔记]《高性能 JavaScript》 | amateur

博客链接

# [读书笔记]《高性能 JavaScript》

# 缺陷

这本书是 2010 年出版的,这本书谈性能是有时效性的,现在已经是 2018 年了,这几年前端发展的速度是飞快的,书里面还有一些内容考虑 IE6、7、8 的东西,殊不知现在这些都已经不再考虑了,所以不可避免的有一些知识是比较老的。有些解决方法现在已经不是最好的解决方式,比如工具那一章。

# 前言

总的来说,这本书整体给出的性能优化建议,以及作者耐心的实践,对我们开发优化的启发和帮助还是很大的,因为它里边的很多知识,都是作者通过实践总结出来的,都是经验的积累,这在一般的教科书上是学不到的。特别是对于 js 基础比较差一点的,里面有很多知识点尽管在现在还是非常有必要的。

下面我就将各章节的一些重要的知识点总结写出来,争取将干货都提取出来


# 第一章-加载和执行

  1. js 的阻塞特性:

当浏览器在执行 js 代码的时候,不能同时做其他事情。(界面 ui 线程和 js 线程用的是同一进程,所以 js 执行越久,网页的响应时间越长。)

  1. 脚本的位置

如果把脚本<script>放在<head>中,页面会等 js 文件全部下载并执行完成后才开始渲染,在这些文件下载和执行的过程中:会导致访问网站的时候有明显的延迟,表现为:页面空白。

性能提升:推荐将所有的<script>标签尽可能的放到<body>标签的底部,优先渲染页面,减少页面空白时间。

  1. 组件脚本。

每个<script>标签初始下载的时候都会阻塞页面的渲染。性能提升做法: 减少内嵌脚本:减少内嵌的<script>标签,将代码写在一个标签中。

合并外链的 js 文件:http 请求会带来额外的性能开销,栗子:下载一个 100KB 的 js 文件比下载 4 个 25kb 的 js 文件更快。具体操作方法自行搜索。

  1. 无阻塞脚本的方法

script 标签的 aync 属性

async 属性规定一旦脚本可用,则会异步执行。async 属性仅适用于外部脚本(只有在使用 src 属性时)。如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)

script 标签的 defer 属性

js 文件在页面解析到 script 标签的时候开始下载,但并不会执行,dom 加载完成执行。这两个属性的区别在于执行时机

动态脚本元素

js 操作 dom 创建<script>标签,自定义生成标签的 type、src 属性。文件会在该元素被添加到页面的时候开始下载。ps:如果文件顺序很重要的话,最好按照顺序合成一个文件。然后再添加到页面中。这样:无论何时启动下载。文件的下载和执行过程不会阻塞页面的其他进程。

# 3. XHR ajax 脚本注入、

用 get 请求一个文件,请求好了。然后创建动态脚本,最后添加进去。 缺陷:文件要再请求页面的同一个域。不能从 cdn 下载

# 第一章加载和执行小结:

  1. 把文件放在 body 标签签名,
  2. 合并脚本,减少<script>标签。
  3. 采用无阻塞下载 js。使用 script 的 defer 和 async 属性 异步下载。动态创建 script 标签和执行代码。

# 第二章-数据存取

  1. js 四种基本的数据存取位置。 1、字面量:字符串、数字、布尔、对象、数组、函数、正则、null、undefined,字面量只代表自身,没有存储位置。 2、局部变量。 let var 声明的变量。3、数组元素。4、对象成员。

性能:访问字面量和局部变量的速度是最快的,访问数组和对象成员相对较慢

  1. 变量标识符解析过程

搜索执行环境的作用域链,查找同名标识符。搜索过程从作用域链头部开始,也就是当前运行函数的活动对象。如果找到,就使用这个标识符,对应的变量;如果没有找到,继续搜索下面的对象。搜索过程会持续进行,直到找到标识符,若无法搜索到匹配的对象,那么标识符被视为未定义、

性能问题:一个标识符所在的位置越深,它的读写速度也就越慢。因此,函数中读写局部变量总是最快的,而读写全局变量通常是最慢的。

建议:将全局变量存储到局部变量,加快读写速度。

  1. 闭包,原型,嵌套对象。

优化建议:将常用的跨作用域变量存储到局部变量,然后直接访问局部变量。理由如上,变量标识符解析过程。

# 第二章数据存取小结:

  1. 访问字面量和局部变量的速度最快,相反,访问数组元素和对象成员相对较慢。
  2. 由于局部变量存在于作用域链的起始位置,因为访问局部变量比访问跨作用域变量更快。这个道理同样适用于数组,对象,跨作用域变量
  3. 把常用的对象,数组,跨域变量保存在局部变量可以改善 js 性能,局部变量访问速度更快。

# 第三章 DOM 编程小结:

  1. dom 操作天生就慢,尽量减少 dom 操作,减少访问 dom 的次数。
  2. 使用 document.querySelect 来做选择器,比其他方式快。
  3. 需要多次访问某个 dom 节点,使用局部变量存储。
  4. html 集合,把集合长度缓存到一个变量中,然后遍历使用这个变量,如果经常操作集合,建议拷到一个数组中。
  5. 要留意浏览器的重绘和重排;批量修改样式的时候,‘离线’操作 DOM 树,使用缓存,并减少访问布局信息的次数。 重绘和重排是 DOM 编程优化的一个相当重要概念:重绘和重排
  6. 动画中使用绝对定义,使用拖放处理。
  7. 使用事件委托来减少事件处理器的数量。

# 第四章算法和流程控制小结:

  1. for、while 和 do-while 循环性能差不多,for-in 循环速度只有前面几种类型的 1/7,所以尽量避免使用 for-in 循环,除非你需要遍历一个属性数量未知的对象。

    forEach 比 for 慢,如果运行速度要求严格,不建议使用。

  2. 改善循环性能的最佳方式是减少每次迭代的工作量和减少循环迭代的次数

减少迭代工作量:减少属性查找和倒序循环,循环次数越多,性能优化越明显。

for(var i=0;i<items.length;i++){代码}//正序循环
for(var i=items.length;i--){代码}//倒序循环
//减少属性查找:查找一次属性,把值存在局部变量,在控制条件里面使用这个变量

倒序循环在i>0的时候会自动转换为true,等于0的时候为false
//倒序循环:控制条件从(迭代数少于总数吗?它是否为true?)变为(它是否为true)

减少迭代的次数:"Duff's Device"循环体展开技术,有兴趣的可以看一下,迭代工作量大于 1000 的时候适用。

  1. if-else 与 switch:条件数量越大,越倾向于使用 switch。

    优化 if-else:

    • 把最可能出现的条件放在首位.
    • 使用二分法把值域分成一系列的区间。
  2. 浏览器的调用栈大小限制了递归算法在 js 中的应用;栈溢出错误会导致其他代码中断运行。

    小心使用递归,现在es6递归可以尾递归,在es6中只要使用尾递归就不会发生栈溢出,相对节省性能。
    

# 第五章字符串和正则表达式小结:

  1. 字符串合并的时候使用简单的'+'和'+='操作符。
let str+='abc'+'efg;//2个以上的字符串拼接,会产生临时字符串
let str=str+'abc'+'efg';//推荐,提速10%~40%
  1. 书里面讲的正则原理和回溯原理,这个很重要,找个篇博客:跟书里面讲的差不多,但还是建议大家可以去找找 PDF 好好看看正则表达式这节。

  2. 提高正则表达式效率的方法:

    • 最重要的是:具体化正则表达式!具体化正则表达式!具体化正则表达式!
    • 关注如何让匹配更快失败,找出一些必需,特殊的字符
    • 正则表达式以简单的、必需的字元开始。
    • 使用量词模式,使它们后面的字元互斥。
    • 较少分支数量,缩小分支范围
    • 使用合适的量词
    • 把正则表达式赋值给变量并重用
    • 将复杂的正则拆分为简单的片段 //事实上,书里面讲的方法不止这么几个,而且每一个都有详细的解析 大佬们 还是去看看这一章节吧
  3. 正则表达式并不总是最好的解决方案,当你只是搜索字面字符串或者你事先知道字符串的哪一部分将要被查找时:

    • 使用 indexOf()和 lastIndexOf()更适合查找特定字符串的位置或者判断它们是否存在 //例如:判断当前浏览器之类。

# 第六章快速响应的用户界面小结:

js 和用户界面更新在同一个进程中运行,因此一次只能处理一件事情。高效的管理 UI 线程就是要确保 js 不能运行太长时间,以免影响用户体验。

  1. 浏览器限制了 js 任务的运行时间,这种限制很有必要,它确保某些恶意代码不能通过永不停止的密集操作锁住用户的浏览器。此限制分为两种:调用栈的大小和长时间运行脚本。

  2. 任何 js 任务都不应当执行超过 100 毫秒。过长的运行时间会导致 UI 更新出现明显延迟,从而对用户体验造成负面影响。

  3. 定时器可用来安排代码延迟执行,它使得你可以把长时间运行脚本分解成一系列的小任务。

# 第七章 AJAX 小结

这一章节貌似东西都比较老一点。。

  1. post 更适合发送大量数据到服务器。

  2. get 请求能够被浏览器缓存,Expires 头信息设置浏览器缓存请求多久。可用于从不改变的图片或者其他静态数据集(js、css 等)

  3. JSON 是一种使用 js 对象和数组直接量编写的轻量级且易于解析的数据格式,它不仅传输尺寸小,而且解析速度快。JSON 是高性能 AJAX 的基础,尤其在使用动态脚本注入时。

json 应该是近几年一直在用的。。。

  1. 减少请求数,通过合并 js 和 css 文件。
  2. 缩短页面的加载时间,页面主要内容加载完成后,用 AJAX 获取那些次要的文件。

# 第八章编程实践小结

  1. 避免双重求值:避免使用 eval()和 function()构造器来避免双重求值带来的性能消耗,同样的,给 setTimeout()和 setInterval()传递函数而不是字符串作为参数。
//双重求值:就是在js代码中执行另一段js代码,不建议使用下面这些方式
eval('代码')
function构造函数--new function('代码')
setTimeout(‘代码’,100)和setInterval(‘代码’,100)
  1. 尽量使用直接量创建对象和数组。直接量的创建和初始化都比非直接量形式要快。
  2. 避免做重复工作,能节省的步骤就节省。
  3. js 原生方法总会比你写的任何代码都要快。

# 第九章 构建并部署高性能 js 应用小结

构建和部署的过程对基于 js 的 web 应用的性能有着巨大影响。这个过程中最重要的步骤有:

  1. 合并、压缩 js 文件。可使用 Gzip 压缩,能够减少约 70%的体积

这些都是在构建过程中完成的工作,不要等到运行时去做,webpack 也是在构建过程中,完成的工作。

  1. 通过正确设置 HTTP 响应头来缓存 js 文件,通过向文件名增加时间戳来避免缓存问题。
  2. 使用 CDN 提供 js 文件;CDN 不仅可以提升性能,它也为你管理文件的压缩与缓存,。

# 第十章 工具 小结:

当网页变慢时,分析从网络下载的资源以及分析的资源以及分析脚本的运行性能能让你专注于那些最需要优化的地方。

  1. 使用网络分析工具找出加载脚本和页面中其他资源的瓶颈,这会帮助你决定那些脚本需要延迟加载,或者需要进一步分析。

    • 检查图片、样式表和脚本的加载过程,以及它们对页面整体加载和渲染的影响。
    • 针对性的做出优化
  2. 把脚本尽可能延迟加载,这样做可以加快页面渲染速度,给用户带来更好的体验。

  3. 确认脚本和其他资源文件的加载过程已经被优化

    • 这里主要是说文件从服务器的下载速度,比如服务器那边的配置问题之类的。
    • 栗子:我就被后端坑过。一个js文件就200k ,下载下来需要50秒钟!
    • 后面发现原来是后端那边nginx没有开启加速配置什么的,导致出现的问题,找问题找半天。
  4. 测试脚本的运行时间,用一个 Date 实例减去另一个 Date 实例,得到的时间差就是脚本运行消耗的时间。

let start = new Date();
//你的代码
let time = newDate() - start;
  1. chrome ,火狐 等主流浏览器的控制面板,已经能够反映很多性能问题。仔细分析就能找出很多问题。例如:资源加载,断点等

# 后话

事实上,自认为这本书最宝贵的就是一些提到的细节,比如:

1、字符串合并的时候使用简单的'+'和'+='操作符。

let str+='abc'+'efg'; //2个以上的字符串拼接,会产生临时字符串
let str=str+'abc'+'efg'; //推荐,提速10%~40%

2、避免双重求值:避免使用 eval()和 function()构造器来避免双重求值带来的性能消耗,同样的,给 setTimeout()和 setInterval()传递函数而不是字符串作为参数。

//双重求值:就是在js代码中执行另一段js代码,不建议使用下面这些方式
eval('代码')
function构造函数--new function('代码')
setTimeout(‘代码’,100)和setInterval(‘代码’,100)

# 这些东西可以让我们知道什么是更好的实践,什么样的代码可以跑得更快,让我们养成更好的开发习惯。

书不太厚,如果对里面的内容感兴趣,还是建议买一本回家看一看。

以上 2018.1.9

# 点个Star支持我一下~

博客链接

从零开始带你写一个运行命令行的终端[手把手教学] | electron

博客链接

# 从零开始带你写一个运行命令行的终端[手把手教学]

# 前言

Electron很出名,很多人可能了解过,知道它是用来开发桌面端的应用,但是一直没有在项目中实践过,缺乏练手的实践项目。

很多开源的命令行终端都是使用Electron来开发的,本文将从零开始手把手的教大家用Electron写一个命令行终端。

作为一个完整的实战项目示例,该终端demo也将集成到Electron开源学习项目electron-playground中,目前这个项目拥有700+ Star⭐️,它最大的特点是所见即所得的演示Electron的各种特性,帮助大家快速学习、上手Electron

大家跟着本文一起来试试Electron吧~

# 终端效果

开源地址: electron-terminal-demo

giit提交代码演示

# 目录

  1. 初始化项目。

  2. 项目目录结构

  3. Electron启动入口index-创建窗口

  4. 进程通信类-processMessage。

  5. 窗口html页面-命令行面板

  6. 命令行面板做了哪些事情

    • 核心方法:child_process.spawn-执行命令行监听命令行的输出
    • stderr不能直接识别为命令行执行错误
    • 命令行终端执行命令保存输出信息的核心代码
    • html完整代码
    • 命令行终端的更多细节
  7. 下载试玩

    • 项目演示
    • 项目地址
    • 启动与调试
  8. 小结

# 初始化项目

npm init
npm install electron -D

如果Electron安装不上去,需要添加一个.npmrc文件,来修改Electron的安装地址,文件内容如下:

registry=https://registry.npm.taobao.org/
electron_mirror=https://npm.taobao.org/mirrors/electron/
chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver

修改一下package.json的入口mainscripts选项, 现在package.json长这样,很简洁:

{
  "name": "electron-terminal",
  "version": "1.0.0",
  "main": "./src/index.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^11.1.1"
  }
}

# 项目目录结构

我们最终实现的项目将是下面这样子的,页面css文件不算的话,我们只需要实现src下面的三个文件即可。

.
├── .vscode // 使用vscode的调试功能启动项目
├── node_dodules
├── src
│   ├── index.js // Electron启动入口-创建窗口
│   └── processMessage.js // 主进程和渲染进程通信类-进程通信、监听时间
│   └── index.html // 窗口html页面-命令行面板、执行命令并监听输出
│   └── index.css // 窗口html的css样式 这部分不写
├── package.json
└── .npmrc // 修改npm安装包的地址
└── .gitignore

# Electron启动入口index-创建窗口

  1. 创建窗口, 赋予窗口直接使用node的能力。
  2. 窗口加载本地html页面
  3. 加载主线程和渲染进程通信逻辑
// ./src/index.js
const { app, BrowserWindow } = require('electron')
const processMessage = require('./processMessage')

// 创建窗口
function createWindow() {
// 创建窗口
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true, // 页面直接使用node的能力 用于引入node模块 执行命令
},
})
// 加载本地页面
win.loadFile('./src/index.html')
win.webContents.openDevTools() // 打开控制台
// 主线程和渲染进程通信
const ProcessMessage = new processMessage(win)
ProcessMessage.init()
}

// app ready 创建窗口
app.whenReady().then(createWindow)

# 进程通信类-processMessage

electron分为主进程和渲染进程,因为进程不同,在各种事件发生的对应时机需要相互通知来执行一些功能。

这个类就是用于它们之间的通信的,electron通信这部分封装的很简洁了,照着用就可以了。

// ./src/processMessage.js
const { ipcMain } = require('electron')
class ProcessMessage {
  /**
   * 进程通信
   * @param {*} win 创建的窗口
   */
  constructor(win) {
    this.win = win
  }
  init() {
    this.watch()
    this.on()
  }
  // 监听渲染进程事件通信
  watch() {
    // 页面准备好了
    ipcMain.on('page-ready', () => {
      this.sendFocus()
    })
  }
  // 监听窗口、app、等模块的事件
  on() {
    // 监听窗口是否聚焦
    this.win.on('focus', () => {
      this.sendFocus(true)
    })
    this.win.on('blur', () => {
      this.sendFocus(false)
    })
  }
  /**
   * 窗口聚焦事件发送
   * @param {*} isActive 是否聚焦
   */
  sendFocus(isActive) {
    // 主线程发送事件给窗口
    this.win.webContents.send('win-focus', isActive)
  }
}
module.exports = ProcessMessage

# 窗口html页面-命令行面板

在创建窗口的时候,我们赋予了窗口使用node的能力, 可以在html中直接使用node模块。

所以我们不需要通过进程通信的方式来执行命令和渲染输出,可以直接在一个文件里面完成。

终端的核心在于执行命令,渲染命令行输出,保存命令行的输出

这些都在这个文件里面实现了,代码行数不到250行。

# 命令行面板做了哪些事情

  • 页面: 引入vue、element,css文件来处理页面

  • template模板-渲染当前命令行执行的输出以及历史命令行的执行输出

  • 核心:执行命令监听命令行输出

    • 执行命令并监听执行命令的输出,同步渲染输出。
    • 执行完毕,保存命令行输出的信息。
    • 渲染历史命令行输出。
    • 对一些命令进行特殊处理,比如下面的细节处理。
  • 围绕执行命令行的细节处理

    • 识别cd,根据系统保存cd路径
    • 识别clear清空所有输出。
    • 执行成功与失败的箭头图标展示。
    • 聚焦窗口,聚焦输入。
    • 命令执行完毕滚动底部。
    • 等等细节。

# 核心方法:child_process.spawn-执行命令行监听命令行的输出

# child_process.spawn介绍

spawn是node子进程模块child_process提供的一个异步方法。

它的作用是执行命令并且可以实时监听命令行执行的输出

当我第一次知道这个API的时候,我就感觉这个方法简直是为命令行终端量身定做的。

终端的核心也是执行命令行,并且实时输出命令行执行期间的信息。

下面就来看看它的使用方式。

# 使用方式

const { spawn } = require('child_process');
const ls = spawn('ls', {
  encoding: 'utf8',
  cwd: process.cwd(), // 执行命令路径
  shell: true, // 使用shell命令
})

// 监听标准输出
ls.stdout.on('data', (data) => {
console.log(stdout: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">);
});

// 监听标准错误
ls.stderr.on('data', (data) => {
console.error(stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">);
});

// 子进程关闭事件
ls.on('close', (code) => {
console.log(子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">);
});

api的使用很简单,但是终端信息的输出,需要很多细节的处理,比如下面这个。

# stderr不能直接识别为命令行执行错误

stderr虽然是标准错误输出,但里面的信息不全是错误的信息,不同的工具会有不同的处理。

对于git来说,有很多命令行操作的输出信息都输出在stederr上。

比如git clonegit push等,信息输出在stederr中,我们不能将其视为错误。

git总是将详细的状态信息和进度报告,以及只读信息,发送给stederr

具体细节可以查看git stderr(错误流)探秘等资料。

暂时还不清楚其他工具/命令行也有没有类似的操作,但是很明显我们不能将stederr的信息视为错误的信息。

PS: 对于git如果想提供更好的支持,需要根据不同的git命令进行特殊处理,比如对下面clear命令和cd命令的特殊处理。

根据子进程close事件判断命令行是否执行成功

我们应该检测close事件的退出码code, 如果code为0则表示命令行执行成功,否则即为失败。

# 命令行终端执行命令保存输出信息的核心代码

下面这段是命令行面板的核心代码,我贴一下大家重点看一下,

其他部分都是一些细节、优化体验、状态处理这样的代码,下面会将完整的html贴上来。

const { spawn } = require('child_process') // 使用node child_process模块
// 执行命令行
actionCommand() {
  // 处理command命令 
  const command = this.command.trim()
  this.isClear(command)
  if (this.command === '') return
  // 执行命令行
  this.action = true
  this.handleCommand = this.cdCommand(command)
  const ls = spawn(this.handleCommand, {
    encoding: 'utf8',
    cwd: this.path, // 执行命令路径
    shell: true, // 使用shell命令
  })
  // 监听命令行执行过程的输出
  ls.stdout.on('data', (data) => {
    const value = data.toString().trim()
    this.commandMsg.push(value)
    console.log(`stdout: ${value}`)
  })

ls.stderr.on('data', this.stderrMsgHandle)
ls.on('close', this.closeCommandAction)
},
// 错误或详细状态进度报告 比如 git push
stderrMsgHandle(data) {
console.log(stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)
this.commandMsg.push(stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)
},
// 执行完毕 保存信息 更新状态
closeCommandAction(code) {
// 保存执行信息
this.commandArr.push({
code, // 是否执行成功
path: this.path, // 执行路径
command: this.command, // 执行命令
commandMsg: this.commandMsg.join('\r'), // 执行信息
})
// 清空
this.updatePath(this.handleCommand, code)
this.commandFinish()
console.log(
子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 运行</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'成功'</span> <span class="token punctuation">:</span> <span class="token string">'失败'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
)
}

# html完整代码

这里是html的完整代码,代码中有详细注释,建议根据上面的命令行面板做了哪些事情,来阅读源码。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>极简electron终端</title>
    <link
      rel="stylesheet"
      href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
    />
    <script src="https://unpkg.com/vue"></script>
    <!-- 引入element -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <!-- css -->
    <link rel="stylesheet" href="./index.css" />
  </head>
  <body>
    <div id="app">
      <div class="main-class">
        <!-- 渲染过往的命令行 -->
        <div v-for="item in commandArr">
          <div class="command-action">
            <!-- 执行成功或者失败图标切换 -->
            <i
              :class="['el-icon-right', 'command-action-icon', { 'error-icon': item.code !== 0  }]"
            ></i>
            <!-- 过往执行地址和命令行、信息 -->
            <span class="command-action-path">{{ item.path }} $</span>
            <span class="command-action-contenteditable"
              >{{ item.command }}</span
            >
          </div>
          <div class="output-command">{{ item.commandMsg }}</div>
        </div>
        <!-- 当前输入的命令行 -->
        <div
          class="command-action command-action-editor"
          @mouseup="timeoutFocusInput"
        >
          <i class="el-icon-right command-action-icon"></i>
          <!-- 执行地址 -->
          <span class="command-action-path">{{ path }} $</span>
          <!-- 命令行输入 -->
          <span
            :contenteditable="action ? false : 'plaintext-only'"
            class="command-action-contenteditable"
            @input="onDivInput($event)"
            @keydown="keyFn"
          ></span>
        </div>
        <!-- 当前命令行输出 -->
        <div class="output-command">
          <div v-for="item in commandMsg">{{item}}</div>
        </div>
      </div>
    </div>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script language-javascript">
  <span class="token keyword">const</span> <span class="token punctuation">{</span> ipcRenderer <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'electron'</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> spawn <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'child_process'</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span>

  <span class="token keyword">var</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    el<span class="token punctuation">:</span> <span class="token string">'#app'</span><span class="token punctuation">,</span>
    data<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      path<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 命令行目录</span>
      command<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 用户输入命令</span>
      handleCommand<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 经过处理的用户命令 比如清除首尾空格、添加获取路径的命令</span>
      commandMsg<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 当前命令信息</span>
      commandArr<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 过往命令行输出保存</span>
      isActive<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 终端是否聚焦</span>
      action<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token comment">// 是否正在执行命令</span>
      inputDom<span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token comment">// 输入框dom</span>
      addPath<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 不同系统 获取路径的命令 mac是pwd window是chdir</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function">mounted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addGetPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>inputDom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span>
        <span class="token string">'.command-action-contenteditable'</span>
      <span class="token punctuation">)</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token function">cwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 初始化路径</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">watchFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      ipcRenderer<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">'page-ready'</span><span class="token punctuation">)</span> <span class="token comment">// 告诉主进程页面准备好了</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token comment">// 回车执行命令</span>
      <span class="token function">keyFn</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>keyCode <span class="token operator">==</span> <span class="token number">13</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">actionCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 执行命令</span>
      <span class="token function">actionCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> command <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isClear</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">===</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token keyword">return</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>action <span class="token operator">=</span> <span class="token boolean">true</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cdCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span>
        <span class="token keyword">const</span> ls <span class="token operator">=</span> <span class="token function">spawn</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand<span class="token punctuation">,</span> <span class="token punctuation">{</span>
          encoding<span class="token punctuation">:</span> <span class="token string">'utf8'</span><span class="token punctuation">,</span>
          cwd<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>path<span class="token punctuation">,</span> <span class="token comment">// 执行命令路径</span>
          shell<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 使用shell命令</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token comment">// 监听命令行执行过程的输出</span>
        ls<span class="token punctuation">.</span>stdout<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">const</span> value <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
          console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stdout: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token comment">// 错误或详细状态进度报告 比如 git push、 git clone </span>
        ls<span class="token punctuation">.</span>stderr<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">const</span> value <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
          console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token comment">// 子进程关闭事件 保存信息 更新状态</span>
        ls<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'close'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>closeCommandAction<span class="token punctuation">)</span> 
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 执行完毕 保存信息 更新状态</span>
      <span class="token function">closeCommandAction</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 保存执行信息</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>commandArr<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
          code<span class="token punctuation">,</span> <span class="token comment">// 是否执行成功</span>
          path<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>path<span class="token punctuation">,</span> <span class="token comment">// 执行路径</span>
          command<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">,</span> <span class="token comment">// 执行命令</span>
          commandMsg<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'\r'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// 执行信息</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token comment">// 清空</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">updatePath</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand<span class="token punctuation">,</span> code<span class="token punctuation">)</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>
          <span class="token template-string"><span class="token string">`子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 运行</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'成功'</span> <span class="token punctuation">:</span> <span class="token string">'失败'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
        <span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// cd命令处理</span>
      <span class="token function">cdCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">let</span> pathCommand <span class="token operator">=</span> <span class="token string">''</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'cd '</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          pathCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addPath
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">' cd '</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          pathCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addPath
        <span class="token punctuation">}</span>
        <span class="token keyword">return</span> command <span class="token operator">+</span> pathCommand
        <span class="token comment">// 目录自动联想...等很多细节功能 可以做但没必要2</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 清空历史</span>
      <span class="token function">isClear</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>command <span class="token operator">===</span> <span class="token string">'clear'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>commandArr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 获取不同系统下的路径</span>
      <span class="token function">addGetPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> systemName <span class="token operator">=</span> <span class="token function">getOsInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>systemName <span class="token operator">===</span> <span class="token string">'Mac'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>addPath <span class="token operator">=</span> <span class="token string">' &amp;&amp; pwd'</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>systemName <span class="token operator">===</span> <span class="token string">'Windows'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>addPath <span class="token operator">=</span> <span class="token string">' &amp;&amp; chdir'</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 命令执行完毕 重置参数</span>
      <span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> <span class="token string">''</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>action <span class="token operator">=</span> <span class="token boolean">false</span>
        <span class="token comment">// 激活编辑器</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">$nextTick</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">scrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 判断命令是否添加过addPath</span>
      <span class="token function">updatePath</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>code <span class="token operator">!==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span>
        <span class="token keyword">const</span> isPathChange <span class="token operator">=</span> command<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>addPath<span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>isPathChange<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">[</span><span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 保存输入的命令行</span>
      <span class="token function">onDivInput</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>textContent
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 点击div</span>
      <span class="token function">timeoutFocusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 聚焦输入</span>
      <span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//解决ff不获取焦点无法定位问题</span>
        <span class="token keyword">var</span> range <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">getSelection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//创建range</span>
        range<span class="token punctuation">.</span><span class="token function">selectAllChildren</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">)</span> <span class="token comment">//range 选择obj下所有子内容</span>
        range<span class="token punctuation">.</span><span class="token function">collapseToEnd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//光标移至最后</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 滚动到底部</span>
      <span class="token function">scrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">let</span> dom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'#app'</span><span class="token punctuation">)</span>
        dom<span class="token punctuation">.</span>scrollTop <span class="token operator">=</span> dom<span class="token punctuation">.</span>scrollHeight <span class="token comment">// 滚动高度</span>
        dom <span class="token operator">=</span> <span class="token keyword">null</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 监听窗口聚焦、失焦</span>
      <span class="token function">watchFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        ipcRenderer<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'win-focus'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>event<span class="token punctuation">,</span> message<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>isActive <span class="token operator">=</span> message
          <span class="token keyword">if</span> <span class="token punctuation">(</span>message<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>

  <span class="token comment">// 获取操作系统信息</span>
  <span class="token keyword">function</span> <span class="token function">getOsInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> userAgent <span class="token operator">=</span> navigator<span class="token punctuation">.</span>userAgent<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">'Unknown'</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'win'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      name <span class="token operator">=</span> <span class="token string">'Windows'</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'iphone'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      name <span class="token operator">=</span> <span class="token string">'iPhone'</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'mac'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      name <span class="token operator">=</span> <span class="token string">'Mac'</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>
      userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'x11'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
      userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'unix'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
      userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'sunname'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
      userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'bsd'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span>
    <span class="token punctuation">)</span> <span class="token punctuation">{</span>
      name <span class="token operator">=</span> <span class="token string">'Unix'</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'linux'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'android'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        name <span class="token operator">=</span> <span class="token string">'Android'</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        name <span class="token operator">=</span> <span class="token string">'Linux'</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> name
  <span class="token punctuation">}</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

</body>
</html>

以上就是整个项目的代码实现,总共只有三个文件。

更多细节

本项目终究是一个简单的demo,如果想要做成一个完整的开源项目,还需要补充很多细节。

还会有各种各样奇奇怪怪的需求和需要定制的地方,比如下面这些:

  • command+c终止命令
  • cd目录自动补全
  • 命令保存上下键滑动
  • git等常用功能单独特殊处理。
  • 输出信息颜色变化
  • 等等

# 下载试玩

即使这个终端demo的代码量很少,注释足够详细,但还是需要上手体验一下一个Electron项目运行的细节。

# 项目演示

clear命令演示

实际上就是将历史命令行输出的数组重置为空数组。

执行失败箭头切换

根据子进程close事件,判断执行是否成功,切换一下图标。

cd命令

识别cd命令,根据系统添加获取路径(pwd/chdir)的命令,再将获取到的路径,更改为最终路径。

giit提交代码演示

# 项目地址

开源地址: electron-terminal-demo

# 启动与调试

安装

npm install

启动

  1. 通过vscode的调试运行项目,这种形式可以直接在VSCode中进行debugger调试。

  2. 如果不是使用vscode编辑器, 也可以通过使用命令行启动。

npm run start

# 小结

命令行终端的实现原理就是这样啦,强烈推荐各位下载体验一下这个项目,最好单步调试一下,这样会更熟悉Electron

文章idea诞生于我们团队开源的另一个开源项目:electron-playground, 目的是为了让小伙伴学习electron实战项目。

electron-playground是用来帮助前端小伙伴们更好、更快的学习和理解前端桌面端技术Electron, 尽量少走弯路。

它通过如下方式让我们快速学习electron。

  1. 带有gif示例和可操作的demo的教程文章。
  2. 系统性的整理了Electron相关的api和功能。
  3. 搭配演练场,自己动手尝试electron的各种特性。

前端进阶积累公众号GitHub、wx:OBkoro1、邮箱:[email protected]

以上2021/01/12

# 点个Star支持我一下~

博客链接

计算两个时间的时间差&时区转换 | effect

博客链接

# 计算两个时间的时间差&时区转换

# 计算两个时间的时间差:

  1. 获取两个时间数据,通常这两个时间是:后端给的时间数据和当前时间

    let dateBegin = '2018/08/28 04:56:38'; // 初始时间
    dateBegin = new Date(dateBegin);
    // Mon Aug 28 2017 04:56:38 GMT+0800 (**标准时间)
    // 上面是Date对象的数据形式
    let dateEnd = new Date(); // 第二个数据通常是当前时间
    
  2. 使用getTime()返回与固定的 1970 年 1 月 1 日的时间差,再用大的时间减去短的时间即可得出双方相差的毫秒数。

    let dateDiff = dateEnd.getTime() - dateBegin.getTime();
    
  3. 换算成你想要的时间单位,得出两个时间的时间差。

    // 计算出相差天数
    let dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000));
    

    // 计算天数后剩余的毫秒数 利用这个时间去计算小时
    let leave1 = dateDiff % (24 3600 1000);
    // 计算出小时数
    let hours = Math.floor(leave1 / (3600 1000));
    // 计算相差分钟数
    let leave2 = leave1 % (3600
    1000); // 计算小时数后剩余的毫秒数
    let minutes = Math.floor(leave2 / (60 1000)); // 计算相差分钟数
    // 计算相差秒数
    let leave3 = leave2 % (60
    1000); // 计算分钟数后剩余的毫秒数
    let seconds = Math.round(leave3 / 1000);

    // 结果
    console.log(相差</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dayDiff<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">天</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>hours<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">小时</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>minutes<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">分钟</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>seconds<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">秒);

如果你只想计算相差的小时数,不想管天数:

let dayDiff = Math.floor(dateDiff / (3600 * 1000)); // 其他的同理,倍数正确即可

# 时区转换

需求

拿到这么一串字符串:2018-09-17,要与当前的日期比较,求出日期差。

let time = `2018-09-17`;
let dateBegin = new Date(time);
let dateEnd = new Date(); // 这一秒的时间 需重新设置
dateEnd.setHours(0, 0, 0, 0);
// 计算时间差 参照上文

东八区的时间

上面new Date(time)之后拿到的是零时区 0 点的时间,在东八区也就是北京时间08:00:00

而我们第二个时间设置的是北京时间00:00:00,这里面就出现了八个小时的误差。

转换时区的三种方法

  1. 设置世界时为当前时区的时间:

    设置具体的时间,传进new Date()的时候会自动转成北京时间00:00:00,然后两个时间就可以同步了。

    let time = `2018-09-17`;
    time = `${time} 00:00:00`; // 后面加上具体时间
    let dateBegin = new Date(time);
    let dateEnd = new Date(); // 这一秒的时间 需重新设置
    dateEnd.setHours(0, 0, 0, 0);
    // 计算时间差 参照上文
    

    也可以使用setHours

    let time = `2018-09-17`;
    let dateBegin = new Date(time);
    dateBegin.setHours(0, 0, 0, 0); // new Date()之后 设为0点 两个时间同步
    let dateEnd = new Date(); // 这一秒的时间 需重新设置
    dateEnd.setHours(0, 0, 0, 0);
    // 计算时间差 参照上文
    
  2. 当前时区与世界时同步:

    let time = `2018-09-17`;
    let dateBegin = new Date(time);
    let dateEnd = new Date(); // 这一秒的时间 需重新设置
    dateEnd.setHours(8, 0, 0, 0); // 设置为八点与世界时一致
    // 计算时间差 参照上文
    
  3. 你可能不知道当前时区与世界时相差多少

    let time = `2018-09-17`;
    let dateBegin = new Date(time);
    let dateEnd = new Date(); // 这一秒的时间 需重新设置
    dateEnd.setUTCHours(0, 0, 0, 0); // 设置为世界时的0点 也就是北京时间八点
    // 计算时间差 参照上文
    

以上:2018.09.21

# 点个Star支持我一下~

博客链接

手摸手教你写个ESLint 插件以及了解ESLint的运行原理 | tool

博客链接

# 手摸手教你写个ESLint 插件以及了解ESLint的运行原理

这篇文章目的是介绍如何创建一个ESLint插件和创建一个ESLint rule,用以帮助我们更深入的理解ESLint的运行原理,并且在有必要时可以根据需求创建出一个完美满足自己需求的Lint规则。

# 插件目标

禁止项目中setTimeout的第二个参数是数字。

PS: 如果是数字的话,很容易就成为魔鬼数字,没有人知道为什么是这个数字, 这个数字有什么含义。

# 使用模板初始化项目:

# 1. 安装NPM包

ESLint官方为了方便开发者开发插件,提供了使用Yeoman模板(generator-eslint)。

对于Yeoman我们只需知道它是一个脚手架工具,用于生成包含指定框架结构的工程化目录结构。

npm install -g yo generator-eslint

# 2. 创建一个文件夹:

mkdir eslint-plugin-demo
cd eslint-plugin-demo

# 3. 命令行初始化ESLint插件的项目结构:

yo eslint:plugin

下面进入命令行交互流程,流程结束后生成ESLint插件项目框架和文件。

? What is your name? OBKoro1
? What is the plugin ID? korolint   // 这个插件的ID是什么
? Type a short description of this plugin: XX公司的定制ESLint rule // 输入这个插件的描述
? Does this plugin contain custom ESLint rules? Yes // 这个插件包含自定义ESLint规则吗?
? Does this plugin contain one or more processors? No // 这个插件包含一个或多个处理器吗
// 处理器用于处理js以外的文件 比如.vue文件
   create package.json
   create lib/index.js
   create README.md

现在可以看到在文件夹内生成了一些文件夹和文件,但我们还需要创建规则具体细节的文件。

# 4. 创建规则

上一个命令行生成的是ESLint插件的项目模板,这个命令行是生成ESLint插件具体规则的文件。

yo eslint:rule // 生成 eslint rule的模板文件

创建规则命令行交互:

? What is your name? OBKoro1
? Where will this rule be published? (Use arrow keys) // 这个规则将在哪里发布?
❯ ESLint Core  // 官方核心规则 (目前有200多个规则)
  ESLint Plugin  // 选择ESLint插件
? What is the rule ID? settimeout-no-number  // 规则的ID
? Type a short description of this rule: setTimeout 第二个参数禁止是数字  // 输入该规则的描述
? Type a short example of the code that will fail:  占位  // 输入一个失败例子的代码
   create docs/rules/settimeout-no-number.md
   create lib/rules/settimeout-no-number.js
   create tests/lib/rules/settimeout-no-number.js

# 加了具体规则文件的项目结构

.
├── README.md
├── docs // 使用文档
│   └── rules // 所有规则的文档
│       └── settimeout-no-number.md // 具体规则文档
├── lib // eslint 规则开发
│   ├── index.js 引入+导出rules文件夹的规则
│   └── rules // 此目录下可以构建多个规则
│       └── settimeout-no-number.js // 规则细节
├── package.json
└── tests // 单元测试
    └── lib
        └── rules
            └── settimeout-no-number.js // 测试该规则的文件

# 4. 安装项目依赖

npm install

以上是开发ESLint插件具体规则的准备工作,下面先来看看AST和ESLint原理的相关知识,为我们开发ESLint rule 打一下基础。

# AST——抽象语法树

AST是: Abstract Syntax Tree的简称,中文叫做:抽象语法树。

# AST的作用

将代码抽象成树状数据结构,方便后续分析检测代码。

# 代码被解析成AST的样子

astexplorer.net是一个工具网站:它能查看代码被解析成AST的样子。

如下图:在右侧选中一个值时,左侧对应区域也变成高亮区域,这样可以在AST中很方便的选中对应的代码

# AST 选择器:

下图中被圈起来的部分,称为AST selectors(选择器)。

AST 选择器的作用:使用代码通过选择器来选中特定的代码片段,然后再对代码进行静态分析。

AST 选择器很多,ESLint官方专门有一个仓库列出了所有类型的选择器: estree

下文中开发ESLint rule就需要用到选择器,等下用到了就懂了,现在知道一下就好了。

将代码解析成AST


# ESLint的运行原理

在开发规则之前,我们需要ESLint是怎么运行的,了解插件为什么需要这么写。

# 1. 将代码解析成AST

ESLint使用JavaScript解析器Espree把JS代码解析成AST。

PS:解析器:是将代码解析成AST的工具,ES6、react、vue都开发了对应的解析器所以ESLint能检测它们的,ESLint也是因此一统前端Lint工具的。

# 2. 深度遍历AST,监听匹配过程。

在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。

# 3. 触发监听选择器的rule回调

在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。

# 4. 具体的检测规则等细节内容。


# 开发规则

# 规则默认模板

打开rule生成的模板文件lib/rules/settimeout-no-number.js, 清理一下文件,删掉不必要的选项:

module.exports = {
    meta: {
        docs: {
            description: "setTimeout 第二个参数禁止是数字",
        },
        fixable: null,  // 修复函数
    },
   // rule 核心
    create: function(context) {
       // 公共变量和函数应该在此定义
        return {
            // 返回事件钩子
        };
    }
};

删掉的配置项,有些是ESLint官方核心规则才是用到的配置项,有些是暂时不必了解的配置,需要用到的时候,可以自行查阅ESLint 文档

# create方法-监听选择器

上文ESLint原理第三部中提到的:在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。

create返回一个对象,对象的属性设为选择器,ESLint会收集这些选择器,在AST遍历过程中会执行所有监听该选择器的回调。

// rule 核心
create: function(context) {
    // 公共变量和函数应该在此定义
    return {
        // 返回事件钩子
        Identifier: (node) => {
            // node是选中的内容,是我们监听的部分, 它的值参考AST
        }
    };
}

# 观察AST:

创建一个ESLint rule需要观察代码解析成AST,选中你要检测的代码,然后进行一些判断。

以下代码都是通过astexplorer.net在线解析的。

setTimeout(()=>{
	console.log('settimeout')
}, 1000)

setTimeout第二个参数为数字时的AST

# rule完整文件

lib/rules/settimeout-no-number.js:

module.exports = {
    meta: {
        docs: {
            description: "setTimeout 第二个参数禁止是数字",
        },
        fixable: null,  // 修复函数
    },
    // rule 核心
    create: function (context) {
        // 公共变量和函数应该在此定义
        return {
            // 返回事件钩子
            'CallExpression': (node) => {
                if (node.callee.name !== 'setTimeout') return // 不是定时器即过滤
                const timeNode = node.arguments && node.arguments[1] // 获取第二个参数
                if (!timeNode) return // 没有第二个参数
                // 检测报错第二个参数是数字 报错
                if (timeNode.type === 'Literal' && typeof timeNode.value === 'number') {
                    context.report({
                        node,
                        message: 'setTimeout第二个参数禁止是数字'
                    })
                }
            }
        };
    }
};

context.report():这个方法是用来通知ESLint这段代码是警告或错误的,用法如上。在这里查看contextcontext.report()的文档。

规则写完了,原理就是依据AST解析的结果,做针对性的检测,过滤出我们要选中的代码,然后对代码的值进行逻辑判断

可能现在会有点懵逼,但是不要紧,我们来写一下测试用例,然后用debugger来看一下代码是怎么运行的。

# 测试用例:

测试文件tests/lib/rules/settimeout-no-number.js:

/**
 * @fileoverview setTimeout 第二个参数禁止是数字
 * @author OBKoro1
 */
"use strict";
var rule = require("../../../lib/rules/settimeout-no-number"), // 引入rule
    RuleTester = require("eslint").RuleTester;

var ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 7, // 默认支持语法为es5
},
});
// 运行测试用例
ruleTester.run("settimeout-no-number", rule, {
// 正确的测试用例
valid: [
{
code: 'let someNumber = 1000; setTimeout(()=>{ console.log(11) },someNumber)'
},
{
code: 'setTimeout(()=>{ console.log(11) },someNumber)'
}
],
// 错误的测试用例
invalid: [
{
code: 'setTimeout(()=>{ console.log(11) },1000)',
errors: [{
message: "setTimeout第二个参数禁止是数字", // 与rule抛出的错误保持一致
type: "CallExpression" // rule监听的对应钩子
}]
}
]
});

下面来学习一下怎么在VSCode中调试node文件,用于观察rule是怎么运行的。

实际上打console的形式,也是可以的,但是在调试的时候打console实在是有点慢,对于node这种节点来说,信息也不全,所以我还是比较推荐通过debugger的方式来调试rule

# 在VSCode中调试node文件

  1. 点击下图中的设置按钮, 将会打开一个文件launch.json
  2. 在文件中填入如下内容,用于调试node文件。
  3. rule文件中打debugger或者在代码行数那里点一下小红点。
  4. 点击图中的开始按钮,进入debugger

vscode 设置

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "启动程序", // 调试界面的名称
            // 运行项目下的这个文件:
            "program": "${workspaceFolder}/tests/lib/rules/settimeout-no-number.js",
            "args": [] // node 文件的参数
        },
        // 下面是用于调试package.json的命令 之前可以用,貌似vscode出了点bug导致现在用不了了
        {
            "name": "Launch via NPM",
            "type": "node",
            "request": "launch",
            "runtimeExecutable": "npm",
            "runtimeArgs": [
                "run-script", "dev"    //这里的dev就对应package.json中的scripts中的dev
            ],
            "port": 9229    //这个端口是调试的端口,不是项目启动的端口
        },
    ]
}

# 运行测试用例进入断点

  1. lib/rules/settimeout-no-number.js中打一些debugger
  2. 点击开始按钮,以调试的形式运行测试文件tests/lib/rules/settimeout-no-number.js
  3. 开始调试rule

# 发布插件

eslint插件都是以npm包的形式来引用的,所以需要把插件发布一下:

  1. 注册:如果你还未注册npm账号的话,需要去注册一下。

  2. 登录npm: npm login

  3. 发布npm包: npm publish即可,ESLint已经把package.json弄好了。

# 集成到项目:

安装npm包:npm i eslint-plugin-korolint -D

  1. 常规的方法: 引入插件一条条写入规则
// .eslintrc.js
module.exports = {
  plugins: [ 'korolint' ],
  rules: { 
    "korolint/settimeout-no-number": "error"
 }
}
  1. extends继承插件配置:

当规则比较多的时候,用户一条条去写,未免也太麻烦了,所以ESLint可以继承插件的配置

修改一下lib/rules/index.js文件:

'use strict';
var requireIndex = require('requireindex');
const output = {
  rules: requireIndex(__dirname + '/rules'), // 导出所有规则
  configs: {
    // 导出自定义规则 在项目中直接引用
    koroRule: {
      plugins: ['korolint'], // 引入插件
      rules: {
        // 开启规则
        'korolint/settimeout-no-number': 'error'
      }
    }
  }
};
module.exports = output;

使用方法:

// .eslintrc.js
module.exports = {
  extends: [ 'plugin:korolint/koroRule' ] // 继承插件导出的配置
}

PS : 这种使用方式, npm的包名不能为eslint-plugin-xx-xx,只能为eslint-plugin-xx否则会有报错,被这个问题搞得头疼o(╥﹏╥)o

# 扩展:

以上内容足够开发一个插件,这里是一些扩展知识点。

# 遍历方向:

上文中说过: 在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。

我们所监听的选择器默认会在"从上至下"的过程中触发,如果需要在"从下至上"的过程中执行则需要添加:exit,在上文中CallExpression就变为CallExpression:exit

注意:一段代码解析后可能包含多次同一个选择器,选择器的钩子也会多次触发。

# fix函数:自动修复rule错误

修复效果

// 修复前
setTimeout(() => {

}, 1000)
// 修复后
const countNumber = 1000
setTimeout(() => {

}, countNumber)

  1. 在rule的meta对象上打开修复功能:
// rule文件
module.exports = {
  meta: {
    docs: {
      description: 'setTimeout 第二个参数禁止是数字'
    },
    fixable: 'code' // 打开修复功能
  }
}
  1. context.report()上提供一个fix函数:

把上文的context.report修改一下,增加一个fix方法即可,更详细的介绍可以看一下文档

context.report({
    node,
    message: 'setTimeout第二个参数禁止是数字',
    fix(fixer) {
        const numberValue = timeNode.value;
        const statementString = `const countNumber = ${numberValue}\n`
        return [
        // 修改数字为变量
        fixer.replaceTextRange(node.arguments[1].range, 'countNumber'),
        // 在setTimeout之前增加一行声明变量的代码 用户自行修改变量名
        fixer.insertTextBeforeRange(node.range, statementString),
        ];
    }
});

# 项目地址:

eslint-plugin-korolint


呼~ 这篇博客断断续续,写了好几周,终于完成了!

大家有看到这篇博客的话,建议跟着博客的一起动手写一下,动手实操一下比你mark一百篇文章都来的有用,花不了很长时间的,希望各位看完本文,都能够更深入的了解到ESLint的运行原理。

# 觉得我的博客对你有帮助的话,就关注一下/点个赞吧!

前端进阶积累公众号GitHub、wx:OBkoro1、邮箱:[email protected]

# 基友带我飞

ESLint插件是向基友yeyan1996学习的,在遇到问题的时候,也是他指点我的,特此感谢。

参考资料:

创建规则 ESLint 工作原理探讨

# 点个Star支持我一下~

博客链接

VsCode保存时自动修复Eslint错误 | tool

博客链接

# VsCode保存时自动修复Eslint错误

同一个项目,保持代码风格的一致,是非常重要的一个规范。但事实上项目小组成员的代码校验规则、格式化工具通常都不一致,为了避免项目到后期出现无法维护的问题,项目成员使用同一套校验规则,同一个格式化方式是相当好的步骤之一。

游泳、健身了解一下:博客前端积累文档公众号

# 保存时自动统一代码风格:

先通过一些简单的配置,然后:

  • Ctrl+s / command+s 时自动修复代码的格式错误
  • 自动修复的规则是读取项目根目录的Eslint规则
  • 这样就能保证项目成员都是一套验证规则的代码风格

# 配置:

# 1.安装VsCode的EsLintvetur插件

如图安装EsLint插件:

# 2.为项目安装EsLint包:

注意要安装在开发环境上,还有就是如果你使用的是脚手架的话,选了Eslint选项,会自带这些包。

# 3.在项目的根目录下添加.eslintrc.js

用于校验代码格式,根据项目情况,可自行编写校验规则:

module.exports = {
    // Eslint规则
}

# 4.首选项设置:

将下面这部分放入首选项设置中:

"eslint.autoFixOnSave": true,  //  启用保存时自动修复,默认只支持.js文件
"eslint.validate": [
    "javascript",  //  用eslint的规则检测js文件
    {
        "language": "vue",   // 检测vue文件
        "autoFix": true   //  为vue文件开启保存自动修复的功能
    },
    {
        "language": "html",
        "autoFix": true
    },
],

想了解更多的话,推荐看一下VsCode的EsLint插件

# 大功告成:

点开文件,你可能会看到如下报错,无需一个一个去改,只要保存一下文件,就可以自动修复这些代码格式上的问题了。

注意:

如果整个文件都飘红的话,不会一次性修改如果的格式问题,会一下改一部分,你可能需要多按几次保存。

# 一键修复项目格式问题:

遇到下面这两种情况:

  • 你刚刚引入这个自动修复,但你项目的文件比较多,且你又比较懒。
  • 隔一段时间,修复一下代码格式上的问题

你可以像下面这样,在package.json里面的scripts里面新增一条如下命令:

"lint": "eslint --ext .js,.vue src --fix"

--ext后面跟上的.js.vue是你要检测文件的后缀,.vue后面的src是要检测的哪个目录下面的文件。

--fix的作用是自动修复根据你配置的规则检测出来的格式问题

一键修复:

输入如下命令行,就可以自动修复你src文件夹下面的所有根据你配置的规则检测出来的格式问题

npm run lint

# .eslintignore 不检测一些文件:

在项目的根目录创建一个.eslintignore文件,用于让EsLint不检测一些文件。

比如引的一些别人的文件,插件等,比如文件中:

src/test/* 
src/test2/* 

文件中的内容像上面这样写,这里第一行是不检测src目录下的test文件夹下面的所有文件。

# 自定义规则:

// .eslintrc.js文件
module.exports = {
    "rules": { // 自定义规则
        "no-console": 0,
        "no-const-assign": 1, 
        "no-extra-bind": 2,
    }
}

0、1、2的意思:

  • "off" 或 0 - 关闭这项规则
  • "warn" 或 1 - 将规则视为一个警告
  • "error" 或 2 - 将规则视为一个错误

# 小结

使用自动VsCode+EsLint格式化代码,在团队内部相互去看别人的代码的时候,就可以更容易的看的懂,能够极大的降低团队的沟通成本和提高心情,设置这么方便,赶紧在团队中用起来吧!

# 点个Star支持我一下~

博客链接

koroFileHeader-VSCode文件头部注释生成 | tool

博客链接

# koroFileHeader-VSCode文件头部注释生成

在vscode中用于生成文件头部注释和函数注释的插件,经过多版迭代后,插件:支持所有主流语言,功能强大,灵活方便,文档齐全,食用简单!觉得插件不错的话,就给个Star⭐️吧~

# 简介

  1. 文件头部添加注释:

    • 在文件开头添加注释,记录文件信息
    • 支持用户高度自定义注释选项
    • 保存文件的时候,自动更新最后的编辑时间和编辑人
    • 快捷键:windowctrl+alt+i,macctrl+cmd+i
  2. 在光标处添加函数注释:

    • 在光标处自动生成一个注释模板,下方有栗子
    • 支持用户高度自定义注释选项
    • 快捷键:windowctrl+alt+t,macctrl+cmd+t
    • 快捷键不可用很可能是被占用了,参考这里
  3. 支持不同语言的注释格式

  4. 自定义注释符号,再也不用担心冷门语言插件不支持了!

  5. 自动添加头部注释

  6. 查看更多配置,以及有更多需求可以给我提issue

# 安装

在 Vscode 扩展商店中搜索koroFileHeader,点击安装即可。

# 使用

  1. 文件头部注释:

    在当前编辑文件中使用快捷键:windowctrl+alt+t/macctrl+cmd+t,即可生成文件头部注释。

  2. 函数注释:

    • 将光标放在函数行或者将光标放在函数上方的空白行
    • 使用快捷键windowctrl+alt+t,macctrl+cmd+t,即可生成函数注释。
    • 事实上,函数注释在文件的任意位置都可生成,这里需要自己控制。

# 注释模板的设置

设置也超方便的,传送门

# 支持功能:

  1. 自定义注释模板信息,自动更新最后编辑时间,最后编辑人。
  2. 支持几乎所有语言的注释形式
  3. 自定义注释符号,即使插件不支持的语言,也可以自己定制。
  4. 自动添加头部注释功能,配合自动添加头部注释黑名单,麻麻再也不用担心我忘记加注释了。
  5. 头部注释第几行插入,类似PHP第一行被占用了,通过设置,可以在第二行里面插入。
  6. 注释时间格式化
  7. 头部注释之前、之后插入一段内容,类似python的环境声明: #!/usr/bin/env python
  8. 特殊字段自定义,类似博客的时间字段有特殊要求。
  9. 头部注释中输出一段自定义信息,可以是版权声明、个性签名等内容。
  10. 匹配到换行自动添加注释符号, 生成头部注释自动移动光标到`Description所在行.

# 使用效果:

# wiki文档

更新日志

支持语言

插件设置/配置

常见问题

# 最后

如果觉得还不错的话,就给个 Star ⭐️ 鼓励一下我吧~

# 点个Star支持我一下~

博客链接

从零开始带你写一个运行命令行的终端[手把手教学] | electron

博客链接

# 从零开始带你写一个运行命令行的终端[手把手教学]

# 前言

Electron很出名,很多人可能了解过,知道它是用来开发桌面端的应用,但是一直没有在项目中实践过,缺乏练手的实践项目。

很多开源的命令行终端都是使用Electron来开发的,本文将从零开始手把手的教大家用Electron写一个命令行终端。

作为一个完整的实战项目示例,该终端demo也将集成到Electron开源学习项目electron-playground中,目前这个项目拥有700+ Star⭐️,它最大的特点是所见即所得的演示Electron的各种特性,帮助大家快速学习、上手Electron

大家跟着本文一起来试试Electron吧~

# 终端效果

开源地址: electron-terminal-demo

giit提交代码演示

# 目录

  1. 初始化项目。

  2. 项目目录结构

  3. Electron启动入口index-创建窗口

  4. 进程通信类-processMessage。

  5. 窗口html页面-命令行面板

  6. 命令行面板做了哪些事情

    • 核心方法:child_process.spawn-执行命令行监听命令行的输出
    • stderr不能直接识别为命令行执行错误
    • 命令行终端执行命令保存输出信息的核心代码
    • html完整代码
    • 命令行终端的更多细节
  7. 下载试玩

    • 项目演示
    • 项目地址
    • 启动与调试
  8. 小结

# 初始化项目

npm init
npm install electron -D

如果Electron安装不上去,需要添加一个.npmrc文件,来修改Electron的安装地址,文件内容如下:

registry=https://registry.npm.taobao.org/
electron_mirror=https://npm.taobao.org/mirrors/electron/
chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver

修改一下package.json的入口mainscripts选项, 现在package.json长这样,很简洁:

{
  "name": "electron-terminal",
  "version": "1.0.0",
  "main": "./src/index.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^11.1.1"
  }
}

# 项目目录结构

我们最终实现的项目将是下面这样子的,页面css文件不算的话,我们只需要实现src下面的三个文件即可。

.
├── .vscode // 使用vscode的调试功能启动项目
├── node_dodules
├── src
│   ├── index.js // Electron启动入口-创建窗口
│   └── processMessage.js // 主进程和渲染进程通信类-进程通信、监听时间
│   └── index.html // 窗口html页面-命令行面板、执行命令并监听输出
│   └── index.css // 窗口html的css样式 这部分不写
├── package.json
└── .npmrc // 修改npm安装包的地址
└── .gitignore

# Electron启动入口index-创建窗口

  1. 创建窗口, 赋予窗口直接使用node的能力。
  2. 窗口加载本地html页面
  3. 加载主线程和渲染进程通信逻辑
// ./src/index.js
const { app, BrowserWindow } = require('electron')
const processMessage = require('./processMessage')

// 创建窗口
function createWindow() {
// 创建窗口
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true, // 页面直接使用node的能力 用于引入node模块 执行命令
},
})
// 加载本地页面
win.loadFile('./src/index.html')
win.webContents.openDevTools() // 打开控制台
// 主线程和渲染进程通信
const ProcessMessage = new processMessage(win)
ProcessMessage.init()
}

// app ready 创建窗口
app.whenReady().then(createWindow)

# 进程通信类-processMessage

electron分为主进程和渲染进程,因为进程不同,在各种事件发生的对应时机需要相互通知来执行一些功能。

这个类就是用于它们之间的通信的,electron通信这部分封装的很简洁了,照着用就可以了。

// ./src/processMessage.js
const { ipcMain } = require('electron')
class ProcessMessage {
  /**
   * 进程通信
   * @param {*} win 创建的窗口
   */
  constructor(win) {
    this.win = win
  }
  init() {
    this.watch()
    this.on()
  }
  // 监听渲染进程事件通信
  watch() {
    // 页面准备好了
    ipcMain.on('page-ready', () => {
      this.sendFocus()
    })
  }
  // 监听窗口、app、等模块的事件
  on() {
    // 监听窗口是否聚焦
    this.win.on('focus', () => {
      this.sendFocus(true)
    })
    this.win.on('blur', () => {
      this.sendFocus(false)
    })
  }
  /**
   * 窗口聚焦事件发送
   * @param {*} isActive 是否聚焦
   */
  sendFocus(isActive) {
    // 主线程发送事件给窗口
    this.win.webContents.send('win-focus', isActive)
  }
}
module.exports = ProcessMessage

# 窗口html页面-命令行面板

在创建窗口的时候,我们赋予了窗口使用node的能力, 可以在html中直接使用node模块。

所以我们不需要通过进程通信的方式来执行命令和渲染输出,可以直接在一个文件里面完成。

终端的核心在于执行命令,渲染命令行输出,保存命令行的输出

这些都在这个文件里面实现了,代码行数不到250行。

# 命令行面板做了哪些事情

  • 页面: 引入vue、element,css文件来处理页面

  • template模板-渲染当前命令行执行的输出以及历史命令行的执行输出

  • 核心:执行命令监听命令行输出

    • 执行命令并监听执行命令的输出,同步渲染输出。
    • 执行完毕,保存命令行输出的信息。
    • 渲染历史命令行输出。
    • 对一些命令进行特殊处理,比如下面的细节处理。
  • 围绕执行命令行的细节处理

    • 识别cd,根据系统保存cd路径
    • 识别clear清空所有输出。
    • 执行成功与失败的箭头图标展示。
    • 聚焦窗口,聚焦输入。
    • 命令执行完毕滚动底部。
    • 等等细节。

# 核心方法:child_process.spawn-执行命令行监听命令行的输出

# child_process.spawn介绍

spawn是node子进程模块child_process提供的一个异步方法。

它的作用是执行命令并且可以实时监听命令行执行的输出

当我第一次知道这个API的时候,我就感觉这个方法简直是为命令行终端量身定做的。

终端的核心也是执行命令行,并且实时输出命令行执行期间的信息。

下面就来看看它的使用方式。

# 使用方式

const { spawn } = require('child_process');
const ls = spawn('ls', {
  encoding: 'utf8',
  cwd: process.cwd(), // 执行命令路径
  shell: true, // 使用shell命令
})

// 监听标准输出
ls.stdout.on('data', (data) => {
console.log(stdout: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">);
});

// 监听标准错误
ls.stderr.on('data', (data) => {
console.error(stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">);
});

// 子进程关闭事件
ls.on('close', (code) => {
console.log(子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">);
});

api的使用很简单,但是终端信息的输出,需要很多细节的处理,比如下面这个。

# stderr不能直接识别为命令行执行错误

stderr虽然是标准错误输出,但里面的信息不全是错误的信息,不同的工具会有不同的处理。

对于git来说,有很多命令行操作的输出信息都输出在stederr上。

比如git clonegit push等,信息输出在stederr中,我们不能将其视为错误。

git总是将详细的状态信息和进度报告,以及只读信息,发送给stederr

具体细节可以查看git stderr(错误流)探秘等资料。

暂时还不清楚其他工具/命令行也有没有类似的操作,但是很明显我们不能将stederr的信息视为错误的信息。

PS: 对于git如果想提供更好的支持,需要根据不同的git命令进行特殊处理,比如对下面clear命令和cd命令的特殊处理。

根据子进程close事件判断命令行是否执行成功

我们应该检测close事件的退出码code, 如果code为0则表示命令行执行成功,否则即为失败。

# 命令行终端执行命令保存输出信息的核心代码

下面这段是命令行面板的核心代码,我贴一下大家重点看一下,

其他部分都是一些细节、优化体验、状态处理这样的代码,下面会将完整的html贴上来。

const { spawn } = require('child_process') // 使用node child_process模块
// 执行命令行
actionCommand() {
  // 处理command命令 
  const command = this.command.trim()
  this.isClear(command)
  if (this.command === '') return
  // 执行命令行
  this.action = true
  this.handleCommand = this.cdCommand(command)
  const ls = spawn(this.handleCommand, {
    encoding: 'utf8',
    cwd: this.path, // 执行命令路径
    shell: true, // 使用shell命令
  })
  // 监听命令行执行过程的输出
  ls.stdout.on('data', (data) => {
    const value = data.toString().trim()
    this.commandMsg.push(value)
    console.log(`stdout: ${value}`)
  })

ls.stderr.on('data', this.stderrMsgHandle)
ls.on('close', this.closeCommandAction)
},
// 错误或详细状态进度报告 比如 git push
stderrMsgHandle(data) {
console.log(stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)
this.commandMsg.push(stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)
},
// 执行完毕 保存信息 更新状态
closeCommandAction(code) {
// 保存执行信息
this.commandArr.push({
code, // 是否执行成功
path: this.path, // 执行路径
command: this.command, // 执行命令
commandMsg: this.commandMsg.join('\r'), // 执行信息
})
// 清空
this.updatePath(this.handleCommand, code)
this.commandFinish()
console.log(
子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 运行</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'成功'</span> <span class="token punctuation">:</span> <span class="token string">'失败'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
)
}

# html完整代码

这里是html的完整代码,代码中有详细注释,建议根据上面的命令行面板做了哪些事情,来阅读源码。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>极简electron终端</title>
    <link
      rel="stylesheet"
      href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
    />
    <script src="https://unpkg.com/vue"></script>
    <!-- 引入element -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <!-- css -->
    <link rel="stylesheet" href="./index.css" />
  </head>
  <body>
    <div id="app">
      <div class="main-class">
        <!-- 渲染过往的命令行 -->
        <div v-for="item in commandArr">
          <div class="command-action">
            <!-- 执行成功或者失败图标切换 -->
            <i
              :class="['el-icon-right', 'command-action-icon', { 'error-icon': item.code !== 0  }]"
            ></i>
            <!-- 过往执行地址和命令行、信息 -->
            <span class="command-action-path">{{ item.path }} $</span>
            <span class="command-action-contenteditable"
              >{{ item.command }}</span
            >
          </div>
          <div class="output-command">{{ item.commandMsg }}</div>
        </div>
        <!-- 当前输入的命令行 -->
        <div
          class="command-action command-action-editor"
          @mouseup="timeoutFocusInput"
        >
          <i class="el-icon-right command-action-icon"></i>
          <!-- 执行地址 -->
          <span class="command-action-path">{{ path }} $</span>
          <!-- 命令行输入 -->
          <span
            :contenteditable="action ? false : 'plaintext-only'"
            class="command-action-contenteditable"
            @input="onDivInput($event)"
            @keydown="keyFn"
          ></span>
        </div>
        <!-- 当前命令行输出 -->
        <div class="output-command">
          <div v-for="item in commandMsg">{{item}}</div>
        </div>
      </div>
    </div>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script language-javascript">
  <span class="token keyword">const</span> <span class="token punctuation">{</span> ipcRenderer <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'electron'</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> spawn <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'child_process'</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span>

  <span class="token keyword">var</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    el<span class="token punctuation">:</span> <span class="token string">'#app'</span><span class="token punctuation">,</span>
    data<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      path<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 命令行目录</span>
      command<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 用户输入命令</span>
      handleCommand<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 经过处理的用户命令 比如清除首尾空格、添加获取路径的命令</span>
      commandMsg<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 当前命令信息</span>
      commandArr<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 过往命令行输出保存</span>
      isActive<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 终端是否聚焦</span>
      action<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token comment">// 是否正在执行命令</span>
      inputDom<span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token comment">// 输入框dom</span>
      addPath<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token comment">// 不同系统 获取路径的命令 mac是pwd window是chdir</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function">mounted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addGetPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>inputDom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span>
        <span class="token string">'.command-action-contenteditable'</span>
      <span class="token punctuation">)</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> process<span class="token punctuation">.</span><span class="token function">cwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 初始化路径</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">watchFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      ipcRenderer<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">'page-ready'</span><span class="token punctuation">)</span> <span class="token comment">// 告诉主进程页面准备好了</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token comment">// 回车执行命令</span>
      <span class="token function">keyFn</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>keyCode <span class="token operator">==</span> <span class="token number">13</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">actionCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 执行命令</span>
      <span class="token function">actionCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> command <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isClear</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">===</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token keyword">return</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>action <span class="token operator">=</span> <span class="token boolean">true</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cdCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span>
        <span class="token keyword">const</span> ls <span class="token operator">=</span> <span class="token function">spawn</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand<span class="token punctuation">,</span> <span class="token punctuation">{</span>
          encoding<span class="token punctuation">:</span> <span class="token string">'utf8'</span><span class="token punctuation">,</span>
          cwd<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>path<span class="token punctuation">,</span> <span class="token comment">// 执行命令路径</span>
          shell<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 使用shell命令</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token comment">// 监听命令行执行过程的输出</span>
        ls<span class="token punctuation">.</span>stdout<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">const</span> value <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
          console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stdout: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token comment">// 错误或详细状态进度报告 比如 git push、 git clone </span>
        ls<span class="token punctuation">.</span>stderr<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">const</span> value <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
          console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`stderr: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token comment">// 子进程关闭事件 保存信息 更新状态</span>
        ls<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'close'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>closeCommandAction<span class="token punctuation">)</span> 
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 执行完毕 保存信息 更新状态</span>
      <span class="token function">closeCommandAction</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 保存执行信息</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>commandArr<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
          code<span class="token punctuation">,</span> <span class="token comment">// 是否执行成功</span>
          path<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>path<span class="token punctuation">,</span> <span class="token comment">// 执行路径</span>
          command<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">,</span> <span class="token comment">// 执行命令</span>
          commandMsg<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'\r'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// 执行信息</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
        <span class="token comment">// 清空</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">updatePath</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>handleCommand<span class="token punctuation">,</span> code<span class="token punctuation">)</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>
          <span class="token template-string"><span class="token string">`子进程退出,退出码 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 运行</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>code <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'成功'</span> <span class="token punctuation">:</span> <span class="token string">'失败'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
        <span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// cd命令处理</span>
      <span class="token function">cdCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">let</span> pathCommand <span class="token operator">=</span> <span class="token string">''</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'cd '</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          pathCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addPath
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">' cd '</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          pathCommand <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addPath
        <span class="token punctuation">}</span>
        <span class="token keyword">return</span> command <span class="token operator">+</span> pathCommand
        <span class="token comment">// 目录自动联想...等很多细节功能 可以做但没必要2</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 清空历史</span>
      <span class="token function">isClear</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>command <span class="token operator">===</span> <span class="token string">'clear'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>commandArr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 获取不同系统下的路径</span>
      <span class="token function">addGetPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> systemName <span class="token operator">=</span> <span class="token function">getOsInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>systemName <span class="token operator">===</span> <span class="token string">'Mac'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>addPath <span class="token operator">=</span> <span class="token string">' &amp;&amp; pwd'</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>systemName <span class="token operator">===</span> <span class="token string">'Windows'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>addPath <span class="token operator">=</span> <span class="token string">' &amp;&amp; chdir'</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 命令执行完毕 重置参数</span>
      <span class="token function">commandFinish</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> <span class="token string">''</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>action <span class="token operator">=</span> <span class="token boolean">false</span>
        <span class="token comment">// 激活编辑器</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">$nextTick</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">scrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 判断命令是否添加过addPath</span>
      <span class="token function">updatePath</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> code<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>code <span class="token operator">!==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span>
        <span class="token keyword">const</span> isPathChange <span class="token operator">=</span> command<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>addPath<span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>isPathChange<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">[</span><span class="token keyword">this</span><span class="token punctuation">.</span>commandMsg<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 保存输入的命令行</span>
      <span class="token function">onDivInput</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>textContent
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 点击div</span>
      <span class="token function">timeoutFocusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 聚焦输入</span>
      <span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//解决ff不获取焦点无法定位问题</span>
        <span class="token keyword">var</span> range <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">getSelection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//创建range</span>
        range<span class="token punctuation">.</span><span class="token function">selectAllChildren</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">)</span> <span class="token comment">//range 选择obj下所有子内容</span>
        range<span class="token punctuation">.</span><span class="token function">collapseToEnd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//光标移至最后</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>inputDom<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 滚动到底部</span>
      <span class="token function">scrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">let</span> dom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'#app'</span><span class="token punctuation">)</span>
        dom<span class="token punctuation">.</span>scrollTop <span class="token operator">=</span> dom<span class="token punctuation">.</span>scrollHeight <span class="token comment">// 滚动高度</span>
        dom <span class="token operator">=</span> <span class="token keyword">null</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 监听窗口聚焦、失焦</span>
      <span class="token function">watchFocus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        ipcRenderer<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'win-focus'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>event<span class="token punctuation">,</span> message<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>isActive <span class="token operator">=</span> message
          <span class="token keyword">if</span> <span class="token punctuation">(</span>message<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">focusInput</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>

  <span class="token comment">// 获取操作系统信息</span>
  <span class="token keyword">function</span> <span class="token function">getOsInfo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> userAgent <span class="token operator">=</span> navigator<span class="token punctuation">.</span>userAgent<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">var</span> name <span class="token operator">=</span> <span class="token string">'Unknown'</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'win'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      name <span class="token operator">=</span> <span class="token string">'Windows'</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'iphone'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      name <span class="token operator">=</span> <span class="token string">'iPhone'</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'mac'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      name <span class="token operator">=</span> <span class="token string">'Mac'</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>
      userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'x11'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
      userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'unix'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
      userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'sunname'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">||</span>
      userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'bsd'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span>
    <span class="token punctuation">)</span> <span class="token punctuation">{</span>
      name <span class="token operator">=</span> <span class="token string">'Unix'</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'linux'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>userAgent<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'android'</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        name <span class="token operator">=</span> <span class="token string">'Android'</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        name <span class="token operator">=</span> <span class="token string">'Linux'</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> name
  <span class="token punctuation">}</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

</body>
</html>

以上就是整个项目的代码实现,总共只有三个文件。

更多细节

本项目终究是一个简单的demo,如果想要做成一个完整的开源项目,还需要补充很多细节。

还会有各种各样奇奇怪怪的需求和需要定制的地方,比如下面这些:

  • command+c终止命令
  • cd目录自动补全
  • 命令保存上下键滑动
  • git等常用功能单独特殊处理。
  • 输出信息颜色变化
  • 等等

# 下载试玩

即使这个终端demo的代码量很少,注释足够详细,但还是需要上手体验一下一个Electron项目运行的细节。

# 项目演示

clear命令演示

实际上就是将历史命令行输出的数组重置为空数组。

执行失败箭头切换

根据子进程close事件,判断执行是否成功,切换一下图标。

cd命令

识别cd命令,根据系统添加获取路径(pwd/chdir)的命令,再将获取到的路径,更改为最终路径。

giit提交代码演示

# 项目地址

开源地址: electron-terminal-demo

# 启动与调试

安装

npm install

启动

  1. 通过vscode的调试运行项目,这种形式可以直接在VSCode中进行debugger调试。

  2. 如果不是使用vscode编辑器, 也可以通过使用命令行启动。

npm run start

# 小结

命令行终端的实现原理就是这样啦,强烈推荐各位下载体验一下这个项目,最好单步调试一下,这样会更熟悉Electron

文章idea诞生于我们团队开源的另一个开源项目:electron-playground, 目的是为了让小伙伴学习electron实战项目。

electron-playground是用来帮助前端小伙伴们更好、更快的学习和理解前端桌面端技术Electron, 尽量少走弯路。

它通过如下方式让我们快速学习electron。

  1. 带有gif示例和可操作的demo的教程文章。
  2. 系统性的整理了Electron相关的api和功能。
  3. 搭配演练场,自己动手尝试electron的各种特性。

前端进阶积累公众号GitHub、wx:OBkoro1、邮箱:[email protected]

以上2021/01/12

# 点个Star支持我一下~

博客链接

cookie、localStorage 和 sessionStorage 的使用以及区别 | JS

博客链接

# cookie、localStorage 和 sessionStorage 的使用以及区别

# localStorage 和 sessionStorage 的增删改查:

  1. 存储数据

    sessionStorage.setItem('key', 'sessionStorage的值'); // 存储数据
    
  2. 获取指定键名数据

    let dataSession=sessionStorage.getItem('key');//获取指定键名数据
    let dataSession2=sessionStorage.key;//sessionStorage是js对象,也可以使用key的方式来获取值
    console.log(dataSession,dataSession2,'获取指定键名数据');
    
  3. 获取sessionStorage全部数据

    let dataAll = sessionStorage.valueOf(); //获取全部数据
    console.log(dataAll, '获取全部数据');
    
  4. 清空sessionStorage数据

    sessionStorage.clear();//清空
    

localStorage

只要将sessionStorage替换成localStorage即可,他们两个的使用方法完全是一样的。

  1. 保存 cookie 值:

    let dataCookie = '110';
    document.cookie = 'token' + '=' + dataCookie;
    
  2. 获取指定名称的 cookie 值

    let cookieData = getCookie('token');
    function getCookie(name) {
      // 获取指定名称的cookie值
      let arr = document.cookie.match(
        new RegExp('(^| )' + name + '=([^;]*)(;|$)')
      ); // 使用正则匹配 对应cookie,返回数组
      if (arr != null) {
        console.log(arr);
        return unescape(arr[2]);
      }
      return null;
    }
    let cookieData = getCookie('token'); // cookie赋值给变量。
    
  3. 保存 cookie 并且设置过期时间:

    setTime('token','cookie的值',10);
    function setTime(key,value,expiresDays) {
        //存储 cookie 值并且设置 cookie 过期时间
        let date=new Date();
        date.setTime(date.getTime()+expiresDays*24*3600\*1000);
            document.cookie=`${key}=${value}; expires=${date.toGMTString()}`;
        console.log(document.cookie,'存储 cookie 值并且设置 cookie 过期时间');
    }
    
  4. 删除 cookie:

    delCookie('token');
    function delCookie(cookieName1) {
      //删除cookie
      let date2 = new Date();
      date2.setTime(date2.getTime() - 10001); //把时间设置为过去的时间,会自动删除
      document.cookie = cookieName1 + '=v; expires=' + date2.toGMTString();
      console.log(document.cookie, '删除cookie');
    }
    

# 三者的异同:

这个问题其实很多大厂面试的时候也都会问到,所以可以注意一下这几个之间的区别:

生命周期

cookie:可设置失效时间,没有设置的话,默认是关闭浏览器后失效

localStorage:除非被手动清除,否则将会永久保存。

sessionStorage: 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除。

存放数据大小

cookie:4KB左右

localStorage和sessionStorage:可以保存5MB的信息。

http请求

cookie:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题

localStorage和sessionStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信

易用性

cookie:需要程序员自己封装,源生的Cookie接口不友好

localStorage和sessionStorage:源生接口可以接受,亦可再次封装来对Object和Array有更好的支持


# 应用场景:

从安全性来说,因为每次http请求都会携带cookie信息,这样无形中浪费了带宽,所以cookie应该尽可能少的使用,另外cookie还需要指定作用域,不可以跨域调用,限制比较多。但是用来识别用户登录来说,cookie还是比stprage更好用的。其他情况下,可以使用storage,就用storage。

storage在存储数据的大小上面秒杀了cookie,现在基本上很少使用cookie了,因为更大总是更好的,哈哈哈你们懂得。

localStorage和sessionStorage唯一的差别一个是永久保存在浏览器里面,一个是关闭网页就清除了信息。localStorage可以用来夸页面传递参数,sessionStorage用来保存一些临时的数据,防止用户刷新页面之后丢失了一些参数。

# 浏览器支持情况:

localStorage和sessionStorage是html5才应用的新特性,可能有些浏览器并不支持,这里要注意。

cookie的浏览器支持没有找到,可以通过下面这段代码来判断所使用的浏览器是否支持cookie:

if(navigator.cookieEnabled) {
  alert("你的浏览器支持cookie功能");//提示浏览器支持cookie  
} else {
  alert("你的浏览器不支持cookie");//提示浏览器不支持cookie   }

# 数据存放处:

Cookie、localStorage、sessionStorage数据存放处

# 番外:各浏览器Cookie大小、个数限制。

cookie 使用起来还是需要小心一点,有兴趣的可以看一下这个链接

# 点个Star支持我一下~

博客链接

前端进阶积累

博客链接

# 前端进阶积累

本项目用于记录前端进阶路上积累的知识点,希望我们能够在纷乱的前端娱乐圈里,专注js基础,打磨核心竞争力,一通百通,无惧工具/框架变迁

前端进阶之路漫漫,期待与你一起成长...

如果我的博客对你的成长有帮助的话,那就给我的点个Star吧~

# 来社区关注我,不错过最新文章:

# 点个Star支持我一下~

博客链接

事件循环(Event Loop)机制以及实例 | tool

博客链接

# 事件循环(Event Loop)机制以及实例

大家都知道js是单线程的脚本语言,在同一时间,只能做同一件事,为了协调事件、用户交互、脚本、UI渲染和网络处理等行为,防止主线程阻塞,Event Loop方案应运而生...

# 为什么js是单线程?

js作为主要运行在浏览器的脚本语言,js主要用途之一是操作DOM。

在js高程中举过一个栗子,如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪个线程的,如何判断优先级?

为了避免这种问题,js必须是一门单线程语言,并且在未来这个特点也不会改变。

# 执行栈与任务队列

因为js是单线程语言,当遇到异步任务(如ajax操作等)时,不可能一直等待异步完成,再继续往下执行,在这期间浏览器是空闲状态,显而易见这会导致巨大的资源浪费。

# 执行栈

当执行某个函数、用户点击一次鼠标,Ajax完成,一个图片加载完成等事件发生时,只要指定过回调函数,这些事件发生时就会进入执行栈队列中,等待主线程读取,遵循先进先出原则。

# 主线程

要明确的一点是,主线程跟执行栈是不同概念,主线程规定现在执行执行栈中的哪个事件。

主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。

当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。

当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。

不太理解的话,可以运行一下下面的代码,或者点击一下这个demo

结果是当a、b、c函数都执行完成之后,三个setTimeout才会依次执行。

    let a = () => {
      setTimeout(() => {
        console.log('任务队列函数1')
      }, 0)
      for (let i = 0; i < 5000; i++) {
        console.log('a的for循环')
      }
      console.log('a事件执行完')
    }
    let b = () => {
      setTimeout(() => {
        console.log('任务队列函数2')
      }, 0)
      for (let i = 0; i < 5000; i++) {
        console.log('b的for循环')
      }
      console.log('b事件执行完')
    }
    let c = () => {
      setTimeout(() => {
        console.log('任务队列函数3')
      }, 0)
      for (let i = 0; i < 5000; i++) {
        console.log('c的for循环')
      }
      console.log('c事件执行完')
    }
    a();
    b();
    c();
    // 当a、b、c函数都执行完成之后,三个setTimeout才会依次执行

# js 异步执行的运行机制。

  1. 所有任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列"。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
  4. 主线程不断重复上面的第三步

# 宏任务与微任务:

异步任务分为 宏任务(macrotask) 与 微任务 (microtask),不同的API注册的任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。

宏任务(macrotask):

script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)

微任务(microtask):

Promise、 MutaionObserver、process.nextTick(Node.js环境)

# Event Loop(事件循环):

Event Loop(事件循环)中,每一次循环称为 tick, 每一次tick的任务如下:

  • 执行栈选择最先进入队列的宏任务(通常是script整体代码),如果有则执行
  • 检查是否存在 Microtask,如果存在则不停的执行,直至清空 microtask 队列
  • 更新render(每一次事件循环,浏览器都可能会去更新渲染)
  • 重复以上步骤

宏任务 > 所有微任务 > 宏任务,如下图所示:

从上图我们可以看出:

  1. 将所有任务看成两个队列:执行队列与事件队列。
  2. 执行队列是同步的,事件队列是异步的,宏任务放入事件列表,微任务放入执行队列之后,事件队列之前。
  3. 当执行完同步代码之后,就会执行位于执行列表之后的微任务,然后再执行事件列表中的宏任务

上面提到的demo结果可以这么理解:先执行script宏任务,执行完了之后,再执行其他两个定时器宏任务。


# 面试题实践

下面这个题,很多人都应该看过/遇到过,重新来看会不会觉得清晰很多:

// 执行顺序问题,考察频率挺高的,先自己想答案**
setTimeout(function () {
    console.log(1);
});
new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
}).then(function(val){
    console.log(val);
})
console.log(4);

根据本文的解析,我们可以得到:

  1. 先执行script同步代码

     先执行new Promise中的console.log(2),then后面的不执行属于微任务
     然后执行console.log(4)
    
  2. 执行完script宏任务后,执行微任务,console.log(3),没有其他微任务了。

  3. 执行另一个宏任务,定时器,console.log(1)。

根据本文的内容,可以很轻松,且有理有据的猜出写出正确答案:2,4,3,1.

# 小结

类似上文的面试题还有很多,实则都大同小异,只要掌握了事件循环的机制,这些问题都会变得很简单。

# 点个Star支持我一下~

博客链接

简单的弹窗组件实现 | Vue

博客链接

# 简单的弹窗组件实现

最近在使用element-ui框架,用到了Dialog对话框组件,大致实现的效果,跟我之前自己在移动端项目里面弄的一个弹窗组件差不太多。然后就想着把这种弹窗组件的实现方式与大家分享一下,下面本文会带着大家手摸手实现一个弹窗组件。

本文主要内容会涉及到弹窗遮罩的实现,slot插槽的使用方式,props$emit传参,具体组件代码也传上去了。如果喜欢的话可以点波赞/关注,支持一下,希望大家看完本文可以有所收获。


# 组件最后实现的效果

# 实现步骤

  1. 先搭建组件的html和css样式,遮罩层和内容层。
  2. 定制弹窗内容:弹窗组件通过slot插槽接受从父组件那里传过来弹窗内容。
  3. 定制弹窗样式:弹窗组件通过props接收从父组件传过来的弹窗宽度,上下左右的位置。
  4. 组件开关:通过父组件传进来的props控制组件的显示与隐藏,子组件关闭时通过事件$emit触发父组件改变值。

# 1.搭建组件的html和css样式。

html结构:一层遮罩层,一层内容层,内容层里面又有一个头部title和主体内容和一个关闭按钮。

下面是组件中的html结构,里面有一些后面才要加进去的东西,如果看不懂的话可以先跳过,

<template>
   <div class="dialog">
       <!--外层的遮罩 点击事件用来关闭弹窗,isShow控制弹窗显示 隐藏的props-->
       <div class="dialog-cover back"  v-if="isShow"  @click="closeMyself"></div>
       <!-- transition 这里可以加一些简单的动画效果 -->
       <transition name="drop">
           <!--style 通过props 控制内容的样式  -->
         <div class="dialog-content" :style="{top:topDistance+'%',width:widNum+'%',left:leftSite+'%'}"  v-if="isShow">
           <div class="dialog_head back">
               <!--弹窗头部 title-->
               <slot name="header">提示信息</slot>
           </div>
           <div class="dialog_main" :style="{paddingTop:pdt+'px',paddingBottom:pdb+'px'}">
             <!--弹窗的内容-->
             <slot name="main">弹窗内容</slot>
           </div>
           <!--弹窗关闭按钮-->
           <div  class="foot_close" @click="closeMyself">
               <div class="close_img back"></div>
           </div>
         </div>
     </transition>
   </div>
</template>

下面是组件中的主要的css样式,里面都做了充分的注释,主要通过z-indexbackground达到遮罩的效果,具体内容的css可以根据自己的需求来设置。

<style lang="scss" scoped>
  /* 最外层 设置position定位  */
  .dialog {
    position: relative;
    color: #2e2c2d;
    font-size: 16px;
  }
  /* 遮罩 设置背景层,z-index值要足够大确保能覆盖,高度 宽度设置满 做到全屏遮罩 */
  .dialog-cover {
    background: rgba(0,0,0, 0.8);
    position: fixed;
    z-index: 200;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
  /* 内容层 z-index要比遮罩大,否则会被遮盖, */
  .dialog-content{
    position: fixed;
    top: 35%;
    /* 移动端使用felx布局  */
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    z-index: 300;
  }
</style>

# 2. 通过slot定制弹窗内容

这一步,只要理解了slot的作用以及用法,就没有问题了。

# 单个插槽:

<slot>这是在没有slot传进来的时候,才显示的弹窗内容</slot>

上面是单个插槽也叫默认插槽,在父组件中使用插槽的正确姿势:

<my-component>
  <!--在my-component里面的所有内容片段都将插入到slot所在的DOM位置,并且会替换掉slot标签-->
  <!--这两个p标签,将替换整个slot标签里面的内容-->
  <p>这是一些初始内容</p>
  <p>这是更多的初始内容</p>
</my-component>

ps:如果子组件里面包含slot插槽,那么上面的p标签的内容将会被丢弃。

# 具名插槽:

所谓的具名插槽,即为slot标签赋一个name属性,具名插槽可以父组件中不同的内容片段放到子组件的不同地方,具名插槽还是可以拥有一个默认插槽。下面可以看一下弹窗组件插槽的使用方式:

<div class="dialog_head back ">
 <!--弹窗头部 title-->
 <slot name="header">提示信息</slot>
 </div>
 <div class="dialog_main " :style="{paddingTop:pdt+'px',paddingBottom:pdb+'px'}">
   <!--弹窗的内容-->
 <slot name="main">弹窗内容</slot>
</div>

在父组件中的使用方式:

  1. 将弹窗组件引入要使用的组件中,并通过components注册成为组件。
  2. 父组件中弹窗组件插槽的使用方法如下。
<dialogComponent>
    <div slot="header">插入到name为header的slot标签里面</div>
      <div class="dialog_publish_main" slot="main">
        这里是内容插入到子组件的slot的name为main里面,可以在父组件中添加class定义样式,事件类型等各种操作
      </div>
</dialogComponent>

关于组件中用到的插槽的介绍就到这里了,插槽在弹窗组件中的应用是一个典型的栗子,可以看到插槽作用相当强大,而插槽本身的使用并不难,同学们爱上插槽了没有?


# 3.通过props控制弹窗显隐&&定制弹窗style

psops是Vue中父组件向子组件传递数据的一种方式,不熟悉的小伙伴们可以看一下props文档

因为弹窗组件都是引到别的组件里面去用的,为了适合不同组件场景中的弹窗,所以弹窗组件必须具备一定的可定制性,否则这样的组件将毫无意义,下面介绍一下props的使用方式,以弹窗组件为例:

  1. 首先需要在被传入的组件中定义props的一些特性,验证之类的。
  2. 然后在父组件中绑定props数据。
<script>
export default {
  props: {
    isShow: { 
    //弹窗组件是否显示 默认不显示
      type: Boolean,
      default: false,
      required:true, //必须
    },
    //下面这些属性会绑定到div上面 详情参照上面的html结构
    // 如: :style="{top:topDistance+'%',width:widNum+'%'}"
    widNum:{ 
    //内容宽度
      type: Number,
      default:86.5
    },
    leftSite:{
      // 左定位
      type: Number,
      default:6.5
    },
    topDistance: {
        //top上边距
      type: Number,
      default:35
    },
    pdt:{
      //上padding
      type: Number,
      default:22
    },
    pdb:{
      //下padding
      type: Number,
      default:47
    }
  },
}
</script>

父组件中使用方式:

<dialogComponent :is-show="status.isShowPublish" :top-distance="status.topNum">
</dialogComponent>

ps:props传递数据不是双向绑定的,而是单向数据流,父组件的数据变化时,也会传递到子组件中,这就意外着我们不应该在子组件中修改props。所以我们在关闭弹窗的时候就需要通过$emit来修改父组件的数据,然后数据会自动传到子组件中。

现在基本上弹窗组件都已实现的差不多了,还差一个弹窗的关闭事件,这里就涉及到子组件往父组件传参了。

# 4.$emit触发父组件事件修改数据,关闭弹窗

Vue中在子组件往父组件传参,很多都是通过$emit来触发父组件的事件来修改数据。

在子组件中,在点击关闭,或者遮罩层的时候触发下面这个方法:

methods: {
  closeMyself() {
    this.$emit("on-close"); 
    //如果需要传参的话,可以在"on-close"后面再加参数,然后在父组件的函数里接收就可以了。
  }
} 

父组件中的写法:

<dialogComponent :is-show="status.isShowPublish" :top-distance="status.topNum"  @on-close="closeDialog"> 
</dialogComponent>
//"on-close是监听子组件的时间有没有触发,触发的时候执行closeDialog函数
methods:{
  closeDialog(){
    // this.status.isShowPublish=false;
    //把绑定的弹窗数组 设为false即可关闭弹窗
  },
}

可以用弹窗组件实现下列这种信息展示,或者事件交互:

# 弹窗组件代码

上面是把弹窗的每个步骤拆分开来,一步步解析的,每一步都说的比较清楚了,具体连起来的话,可以看看代码,再结合文章就能理的很清楚了。

# 小结:

这个弹窗组件,实现起来一点都不难,我这里主要是提供了一个实现方式,当项目中有需要的话,大家也可以自己撸一个出来,以上就是本文的内容了,希望同学们看完能有所收获。

以上2018.4.21

# 参考资料:

Vue文档-插槽

# 点个Star支持我一下~

博客链接

如何搭建本文档 | amateur

博客链接

# 如何搭建本文档

VuePress是尤大为了支持 Vue 及其子项目的文档需求而写的一个项目,VuePress界面十分简洁,并且非常容易上手,一个小时就可以将项目架构搭好。现在已经有很多这种类型的文档,如果你有写技术文档/技术博客的需求,VuePress绝对可以成为你的备选项之一。

# VuePress特性:

  • 为技术文档而优化的 内置 Markdown 拓展
  • 在 Markdown 文件中使用 Vue 组件的能力
  • Vue 驱动的自定义主题系统
  • 自动生成 Service Worker
  • Google Analytics 集成
  • 基于 Git 的 “最后更新时间”
  • 多语言支持
  • 默认主题包含:

建议先看一下官方文档

# 效果:

可能你会搭建出一个类似这样的文档


# 搭建:

# 全局安装VuePress

    yarn global add vuepress # 或者:npm install -g vuepress

# 新建文件夹

可以手动右键新建,也可以使用下面的命令新建文件夹:

    mkdir project

# 项目初始化

进入到project文件夹中,使用命令行初始化项目:

    yarn init -y # 或者 npm init -y

将会创建一个package.json文件,长这样子:

    {
      "name": "project",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }

# 在project的根目录下新建docs文件夹:

这个文档将作为项目文档的根目录来使用:

    mkdir docs

# 在docs文件夹下创建.vuepress文件夹:

    mkdir .vuepress

所有 VuePress 相关的文件都将会被放在这里

#.vuepress文件夹下面创建config.js:

    touch config.js

config.js是VuePress必要的配置文件,它导出一个javascript对象。

你可以先加入如下配置:

    module.exports = {
      title: 'Hello VuePress',
      description: 'Just playing around'
    }

#.vuepress文件夹下面创建public文件夹:

    mkdir public

这个文件夹是用来放置静态资源的,打包出来之后会放在.vuepress/dist/的根目录。

# 首页(像VuePress文档主页一样)

在docs文件夹下面创建一个README.md

默认的主题提供了一个首页,像下面一样设置home:true即可,可以把下面的设置放入README.md中,待会儿你将会看到跟VuePress一样的主页。

    ---
    home: true
    heroImage: /logo.jpg
    actionText: 快速上手 →
    actionLink: /zh/guide/
    features:
    - title: 简洁至上
      details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。
    - title: Vue驱动
      details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。
    - title: 高性能
      details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。
    footer: MIT Licensed | Copyright © 2018-present Evan You
    ---

ps:你需要放一张图片到public文件夹中。

# 我们的项目结构已经搭好了:

    project
    ├─── docs
    │   ├── README.md
    │   └── .vuepress
    │       ├── public
    │       └── config.js
    └── package.json

#package.json 里添加两个启动命令:

    {
      "scripts": {
        "docs:dev": "vuepress dev docs",
        "docs:build": "vuepress build docs"
      }
    }

# 启动你的VuePress:

默认是localhost:8080端口。

    yarn docs:dev # 或者:npm run docs:dev

# 构建:

build生成静态的HTML文件,默认会在 .vuepress/dist 文件夹下

    yarn docs:build # 或者:npm run docs:build

# 基本配置:

最标准的当然是官方文档,可以自己的需求来配置config.js

可以参考一下我的config.js的配置:

    module.exports = {
      title: '网站标题',
      description: '网站描述',
      // 注入到当前页面的 HTML <head> 中的标签
      head: [
        ['link', { rel: 'icon', href: '/favicon.ico' }], // 增加一个自定义的 favicon(网页标签的图标)
      ],
      base: '/web_accumulate/', // 这是部署到github相关的配置 下面会讲
      markdown: {
        lineNumbers: true // 代码块显示行号
      },
      themeConfig: {
        sidebarDepth: 2, // e'b将同时提取markdown中h2 和 h3 标题,显示在侧边栏上。
        lastUpdated: 'Last Updated' // 文档更新时间:每个文件git最后提交的时间
      }
    };

# 导航栏配置:

    module.exports = {
      themeConfig: {
        nav:[
          { text: '前端算法', link: '/algorithm/' }, // 内部链接 以docs为根目录
          { text: '博客', link: 'http://obkoro1.com/' }, // 外部链接
          // 下拉列表
          {
            text: 'GitHub',
            items: [
              { text: 'GitHub地址', link: 'https://github.com/OBKoro1' },
              {
                text: '算法仓库',
                link: 'https://github.com/OBKoro1/Brush_algorithm'
              }
            ]
          }        
        ]
      }
    }

# 侧边栏配置:

侧边栏的配置相对麻烦点,我里面都做了详细的注释,仔细看,自己鼓捣鼓捣 就知道怎么搞了。

    module.exports = {
      themeConfig: {
          sidebar:{
            // docs文件夹下面的accumulate文件夹 文档中md文件 书写的位置(命名随意)
            '/accumulate/': [
                '/accumulate/', // accumulate文件夹的README.md 不是下拉框形式
                {
                  title: '侧边栏下拉框的标题1',
                  children: [
                    '/accumulate/JS/test', // 以docs为根目录来查找文件 
                    // 上面地址查找的是:docs>accumulate>JS>test.md 文件
                    // 自动加.md 每个子选项的标题 是该md文件中的第一个h1/h2/h3标题
                  ]
                }
              ],
              // docs文件夹下面的algorithm文件夹 这是第二组侧边栏 跟第一组侧边栏没关系
              '/algorithm/': [
                '/algorithm/', 
                {
                  title: '第二组侧边栏下拉框的标题1',
                  children: [
                    '/algorithm/simple/test' 
                  ]
                }
              ]
          }
      }
    }

# 其他:

# 代码块编译错误:

像下面这段代码会导致编译错误,VuePress会去找里面的变量,把它编译成text:

    {{}}{{}}

所以我们的代码块要以这种形式书写:

//```js
{{}}{{}} // 注释需要打开 这样vuepress会把这里面包裹的当成代码块而不是js
//```

并且这样也会让我们的代码高亮显示(下图第一个没有高亮,第二个有高亮),阅读体验更好:

# 自定义容器了解一下:

更改标题:

    ::: tip 替换tip的标题
    这里是内容。
    :::

其实文档里有,我这里只是提一下。

# 支持Emoji

文档中只提了支持Emoji,我在GitHub上找到了Emoji的列表,分享一下。

# 一个命令行发布到github上:

#docs/.vuepress/config.js 中设置正确的 base:

如果你打算发布到 https://<USERNAME>.github.io/,则可以省略这一步,因为 base 默认即是 "/"

如果你打算发布到 https://<USERNAME>.github.io/<REPO>/(也就是说你的仓库在 https://github.com/<USERNAME>/<REPO>),则将 base 设置为 "/<REPO>/"

module.exports = {
    base: '/test/', // 比如你的仓库是test
}

# 创建脚步文件:

project的根目录下,创建一个deploy.sh文件:

#!/usr/bin/env sh

确保脚本抛出遇到的错误

set -e

生成静态文件

npm run docs:build

进入生成的文件夹

cd docs/.vuepress/dist

如果是发布到自定义域名

echo 'www.example.com' > CNAME

git init
git add -A
git commit -m 'deploy'

如果发布到 https://<USERNAME>.github.io USERNAME=你的用户名

git push -f [email protected]:<USERNAME>/<USERNAME>.github.io.git master

如果发布到 https://<USERNAME>.github.io/<REPO> REPO=github上的项目

git push -f [email protected]:<USERNAME>/<REPO>.git master:gh-pages

cd -

# 设置package.json:

{
<span class="token property">"scripts"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token property">"d"</span><span class="token operator">:</span> <span class="token string">"bash deploy.sh"</span>
<span class="token punctuation">}</span>

}

# 部署:

然后你每次可以运行下面的命令行,来把最新更改推到github上:

npm run d

如果你对运行项目和构建项目的命令行觉得很烦,你也可以像我这么做:

"scripts": {
    "dev": "vuepress dev docs", // 本地运行项目 npm run dev
    "build": "vuepress build docs", // 构建项目 nom run build
    "d": "bash deploy.sh" // 部署项目 npm run d
},

# 更多:

实际上VuePress的配置、用法还有很多,像还可以配置PWA,以及在markdown里面使用Vue组件等,这些功能我也还在摸索,所以大家一定要去看文档!

# 小结

上面已经写得尽可能详细了,我遇到的坑都写上去了。搭建起来确实很简单,心动不如行动,随手花一两个小时搭建一下又不吃亏,何乐而不为?

# 点个Star支持我一下~

博客链接

Vue 定义全局函数 | Vue

博客链接

# Vue 定义全局函数

# 原理

通过 Vue.prototype 将函数挂载到 Vue 实例上面,在组件中通过 this.函数名,来运行函数。

# 1. 在 main.js 里面直接写函数

直接在 main.js 里面写:

Vue.prototype.test = function() {
  console.log('执行全局函数test');
};

组件中调用

this.test(); // 直接通过this运行函数

# 2. 写一个模块文件,挂载到 main.js 上面。

想要定义的全局函数比较多的话,推荐写在一个js文件里面,文件位置可以放在跟 main.js 同一级,方便引用

// base.js
exports.install = function(Vue, options) {
  Vue.prototype.text1 = function() {
    console.log('执行成功1');
  };
  Vue.prototype.text2 = function() {
    console.log('执行成功2');
  };
};

main.js 入口文件:

import Vue from 'vue'; // vue要在引文件之前
import base from './base.js'; // 引用文件
Vue.use(base); //将全局函数当做插件来进行注册

组件里面调用:

this.text1();
this.text2();

# 点个Star支持我一下~

博客链接

仿Vue极简双向绑定 | Vue

博客链接

# 仿Vue极简双向绑定

现在的前端面试不管你用的什么框架,总会问你这个框架的双向绑定机制,有的甚至要求你现场实现一个双向绑定出来,那对于没有好好研究过这方面知识的同学来说,当然是很难的,接下来本文用160行代码带你实现一个极简的双向绑定机制。如果喜欢的话可以点波赞/关注,支持一下,希望大家看完本文可以有所收获。

# 效果GIF:

# demo地址:

codepen:仿Vue极简双向绑定

Github:仿Vue极简双向绑定


# 了解Object.defineProperty():

这个API是实现双向绑定的核心,最主要的作用是重写数据的getset方法

# 使用方式:

let obj = {
    singer: "周杰伦"
};
let default_value = "青花瓷";
Object.defineProperty(obj, "music", {
    // value: '七里香', // 设置属性的值 下面设置了get set函数 所以这里不能设置
    configurable: false, // 是否可以删除属性 默认不能删除
    // writable: true,  // 是否可以修改对象 下面设置了get set函数 所以这里不能设置
    enumerable: true, // music是否可以被枚举 默认是不能被枚举(遍历)
    // ☆ get,set设置时不能设置writable和value,要一对一对设置,交叉设置/同时存在 就会报错
    get() {
    // 获取obj.music的时候就会调用get方法
    // let default_value = "强行设置get的返回值"; // 打开注释 读取属性永远都是‘强行设置get的返回值’
    return default_value;
    },
    set(val) {
    // 将修改的值重新赋给song
    default_value = val;
    }
});
console.log(obj.music); // 青花瓷
delete obj.music; // configurable设为false 删除无效
console.log(obj.music); // 青花瓷
obj.music = "听妈妈的话"; 
console.log(obj.music); // 听妈妈的话
for (let key in obj) {
    // 默认情况下通过defineProperty定义的属性是不能被枚举(遍历)的
    // 需要设置enumerable为true才可以 否则只能拿到singer 属性
    console.log(key); // singer, music
}

# 示例demo:

对,这里有个demo

# 画一下重点:

  • get,set设置时不能设置writable和value, 他们是一对情侣的存在,交叉设置或同时存在,会报错
  • 通过defineProperty设置的属性,默认不能删除,不能遍历,当然你可以通过设置更改他们。
  • get、set 是函数,可以做的事情很多

兼容性:IE 9,Firefox 4, Chorme 5,Opera 11.6,Safari 5.1

更详细的可以看一下MDN


# 实现思路:

# mvvm系列的双向绑定,关键步骤:

  1. 实现数据监听器Observer,用Object.defineProperty()重写数据的get、set,值更新就在set中通知订阅者更新数据
  2. 实现模板编译Compile,深度遍历dom树,对每个元素节点的指令模板进行替换数据以及订阅数据
  3. 实现Watch用于连接Observer和Compile,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
  4. mvvm入口函数,整合以上三者。

# 流程图:

这部分讲的很清楚,现在有点懵逼也没关系,看完代码,自己copy下来玩一玩之后,回头再看实现思路,相信会有收获的。


# 具体代码实现:

# html结构:

<div id="app">
    <input type="text" v-model="name">
    <h3 v-bind="name"></h3>
    <input type="text" v-model="testData1">
    <h3>{{ testData1 }}</h3>
    <input type="text" v-model="testData2">
    <h3>{{ testData2 }}</h3>
</div>

看到这个模板,相信用过Vue的同学都不会陌生。

# 调用方法:

采用类Vue方式来使用双向绑定:

window.onload = function () {
    var app = new myVue({
        el: '#app', // dom
        data: { // 数据
            testData1: '仿Vue',
            testData2: '极简双向绑定',
            name: 'OBKoro1'
        }
    })
}

# 创建myVue函数:

实际上这里是我们实现思路中的第四步,用于整合数据监听器this._observer()、指令解析器this._compile()以及连接Observer和Compile的_watcherTpl的watch池。

function myVue(options = {}) {  // 防止没传,设一个默认值
    this.$options = options; // 配置挂载
    this.$el = document.querySelector(options.el); // 获取dom
    this._data = options.data; // 数据挂载
    this._watcherTpl = {}; // watcher池
    this._observer(this._data); // 传入数据,执行函数,重写数据的get set
    this._compile(this.$el); // 传入dom,执行函数,编译模板 发布订阅
};

# Watcher函数:

这是实现思路中的第三步,因为下方数据监听器_observer()需要用到Watcher函数,所以这里就先讲了。

像实现思路中所说的,这里起到了连接Observer和Compile的作用:

  1. 在模板编译_compile()阶段发布订阅
  2. 在赋值操作的时候,更新视图
// new Watcher() 为this._compile()发布订阅+ 在this._observer()中set(赋值)的时候更新视图
function Watcher(el, vm, val, attr) {
    this.el = el; // 指令对应的DOM元素
    this.vm = vm; // myVue实例
    this.val = val; // 指令对应的值 
    this.attr = attr; // dom获取值,如value获取input的值 / innerHTML获取dom的值
    this.update(); // 更新视图
}
Watcher.prototype.update = function () { 
    this.el[this.attr] = this.vm._data[this.val]; // 获取data的最新值 赋值给dom 更新视图
}

没有看错,代码量就这么多,可能需要把整个代码连接起来,多看几遍才能够理解。

# 实现数据监听器_observer():

实现思路中的第一步,用Object.defineProperty()遍历data重写所有属性的get set。

然后在给对象的某个属性赋值的时候,就会触发set。

在set中我们可以监听到数据的变化,然后就可以触发watch更新视图

myVue.prototype._observer = function (obj) {
    var _this = this;
    Object.keys(obj).forEach(key => { // 遍历数据
        _this._watcherTpl[key] = { // 每个数据的订阅池()
            _directives: []
        };
        var value = obj[key]; // 获取属性值
        var watcherTpl = _this._watcherTpl[key]; // 数据的订阅池
        Object.defineProperty(_this._data, key, { // 双向绑定最重要的部分 重写数据的set get
            configurable: true,  // 可以删除
            enumerable: true, // 可以遍历
            get() {
                console.log(`${key}获取值:${value}`);
                return value; // 获取值的时候 直接返回
            },
            set(newVal) { // 改变值的时候 触发set
                console.log(`${key}更新:${newVal}`);
                if (value !== newVal) {
                    value = newVal;
                    watcherTpl._directives.forEach((item) => { // 遍历订阅池 
                        item.update();
                        // 遍历所有订阅的地方(v-model+v-bind+{{}}) 触发this._compile()中发布的订阅Watcher 更新视图  
                    });
                }
            }
        })
    });
}

# 实现Compile 模板编译

这里是实现思路中的第三步,让我们来总结一下这里做了哪些事情:

  • 首先是深度遍历dom树,遍历每个节点以及子节点。
  • 将模板中的变量替换成数据,初始化渲染页面视图。
  • 把指令绑定的属性添加到对应的订阅池中
  • 一旦数据有变动,收到通知,更新视图。
myVue.prototype._compile = function (el) {
    var _this = this, nodes = el.children; // 获取app的dom
    for (var i = 0, len = nodes.length; i < len; i++) { // 遍历dom节点
        var node = nodes[i];
        if (node.children.length) {
            _this._compile(node);  // 递归深度遍历 dom树
        }
        // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件    
        if (node.hasAttribute('v-model') && (node.tagName = 'INPUT' || node.tagName == 'TEXTAREA')) {
            node.addEventListener('input', (function (key) {
                var attVal = node.getAttribute('v-model'); // 获取v-model绑定的值
                _this._watcherTpl[attVal]._directives.push(new Watcher( // 将dom替换成属性的数据并发布订阅 在set的时候更新数据
                    node,
                    _this,
                    attVal,
                    'value'
                ));
                return function () {
                    _this._data[attVal] = nodes[key].value;  // input值改变的时候 将新值赋给数据 触发set=>set触发watch 更新视图
                }
            })(i));
        }
        if (node.hasAttribute('v-bind')) { // v-bind指令 
            var attrVal = node.getAttribute('v-bind'); // 绑定的data
            _this._watcherTpl[attrVal]._directives.push(new Watcher( // 将dom替换成属性的数据并发布订阅 在set的时候更新数据
                node,
                _this,
                attrVal,
                'innerHTML'
            ))
        }
    <span class="token keyword">var</span> reg <span class="token operator">=</span> <span class="token regex">/\{\{\s*([^}]+\S)\s*\}\}/g</span><span class="token punctuation">,</span> txt <span class="token operator">=</span> node<span class="token punctuation">.</span>textContent<span class="token punctuation">;</span>   <span class="token comment">// 正则匹配{{}} </span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>reg<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>txt<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        node<span class="token punctuation">.</span>textContent <span class="token operator">=</span> txt<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>reg<span class="token punctuation">,</span> <span class="token punctuation">(</span>matched<span class="token punctuation">,</span> placeholder<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
                <span class="token comment">// matched匹配的文本节点包括双花括号, placeholder 是双花括号中间的属性名</span>
            <span class="token keyword">var</span> getName <span class="token operator">=</span> _this<span class="token punctuation">.</span>_watcherTpl<span class="token punctuation">;</span> <span class="token comment">// 所有绑定watch的数据</span>
            getName <span class="token operator">=</span> getName<span class="token punctuation">[</span>placeholder<span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token comment">// 获取对应watch 数据的值</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>getName<span class="token punctuation">.</span>_directives<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 没有事件池 创建事件池</span>
                getName<span class="token punctuation">.</span>_directives <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            getName<span class="token punctuation">.</span>_directives<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Watcher</span><span class="token punctuation">(</span> <span class="token comment">// 将dom替换成属性的数据并发布订阅 在set的时候更新数据</span>
                node<span class="token punctuation">,</span>
                _this<span class="token punctuation">,</span>
                placeholder<span class="token punctuation">,</span>
                <span class="token string">'innerHTML'</span>
            <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">return</span> placeholder<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'.'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span>val<span class="token punctuation">,</span> key<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
                <span class="token keyword">return</span> _this<span class="token punctuation">.</span>_data<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 获取数据的值 触发get 返回当前值 </span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span> _this<span class="token punctuation">.</span>$el<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

}


# 完整代码&demo地址

GitHub完整代码

codepen:仿Vue极简双向绑定

Github:仿Vue极简双向绑定

如果觉得还不错的话,就给个Star⭐️鼓励一下我吧~


# 小结

本文只是一个简单的实现双向绑定的方法,主要目的是帮助各位同学理解mvvm框架的双向绑定机制,也并没有很完善,这里还是有很多缺陷,比如:没有实现数据的深度对数据进行getset等。希望看完本文,大家能有所收获。

以上2018.6.24

# 参考资料:

剖析Vue原理&实现双向绑定MVVM

面试题:你能写一个Vue的双向数据绑定吗?

不好意思!耽误你的十分钟,让MVVM原理还给你

# 点个Star支持我一下~

博客链接

input 的一些坑点分享 | effect

博客链接

# input 的一些坑点分享

# 本文内容包括:

  1. 移动端底部 input 被弹出的键盘遮挡。
  2. 控制 input 显/隐密码。
  3. 在 input 中输入 emoji 表情导致请求失败。
  4. input 多行输入显示换行。
  5. 输入框首尾清除空格-trim()
  6. 在 input 中监听键盘事件

# 移动端底部 input 被弹出的键盘遮挡

input 输入框是通过position:fixed一直放在页面底部,当点击 input 进行输入的时候,就会出现如下图片情况(有的机型会遮挡一些)。

当时这个问题是去年在 ios 中遇到的,在最新版的 ios 系统中,貌似解决了这个 bug,但是为了向下兼容以及防止其他其他机型也出现这个问题,大家可以稍微记一下这个解决方法。

在解决这个问题的时候,有试过下面这种方法:

在 input 的 focus 事件中,开启一个定时器,然后每隔 300 毫秒进行一次 document.body.scrollTop=document.body.scrollHeight 的调整,运行 3 次即可。

当时还以为解决了,但是当你底部评论区还有很多内容,你每次点击 input,想要输入的时候,整个页面通过scrollTop就会不断的向下滚动,这个体验不用说自己也知道是相当失败的,然后就再去找解决方法,结果就有了下面这个。

# Element.scrollIntoView()

Element.scrollIntoView():方法让当前的元素滚动到浏览器窗口的可视区域内

document.querySelector('#inputId').scrollIntoView();
//只要在input的点击事件,或者获取焦点的事件中,加入这个api就好了

这个 api 还可以设置对齐方法,选择将 input 放在屏幕的上方/下方,类似的 api 还有:Element.scrollIntoViewIfNeeded(),这两个是解决同一个问题的,选择一个用就可以了。


# 控制 input 显/隐密码

这个就很简单了,只需更改 input 的 type 属性值就可以了。可以看一下 codepen 的demo

//点击函数,获取dom,判断更改属性。
show(){
    let input=document.getElementById("inputId");
    if(input.type=="password"){
        input.type='text';
    }else{
        input.type='password';
    }
}

# 在 input 中输入 emoji 表情导致请求失败

现在用户输入 emoji 简直已经成为了习惯,如果前后端没有对 emoji 表情进行处理,那么用户在上传的时候,就会请求失败。

通常这个问题是后端那边处理比较合适的,前端是做不了这件事的,或者说很难做这件事。

之前看过一篇文章,这个文章里面讲了怎么在上传和拿数据下来的时候不会报错,但是不能在显示的时候转换为表情。

ps:之前拿微信用户名的时候,有些人可能在微信昵称上面就会包含表情,如果后端没对表情处理转换,那么普通请求也会出错

之所以说这个,当表单请求错误的时候各位如果实在找不到问题可以往这方面考虑一下,我真的被坑过的 o(╥﹏╥)o。


# textarea 多行回车换行,显示的时候换行设置:

在使用textarea标签输入多行文本的时候,如果没有对多行文本显示处理,会导致没有换行的情况,就比如下面这种情况,用户在textarea是有换行的。

# Css 属性:white-space

white-space 属性用于设置如何处理元素内的空白,其中包括空白符和换行符。

只要在显示内容的地方将该属性设置为white-space: pre-line或者white-space:pre-wrap,多行文本就可以换行了

# 设置之后,显示效果:


# 输入框首尾清除空格-trim()

输入框清除首尾空格是 input 较为常见的需求,通常在上传的时候将首尾空格去除掉。一般使用:字符串的原生方法trim() 从一个字符串的两端删除空白字符。

trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。

# 原生清除方法:

//原生方法获取值,清除首尾空格上传str2
let str2 = document.getElementById('inputId').trim();

# Vue 清除方法:

Vue 提供了修饰符删除首尾空格, 加了修饰符.trim会自动过滤用户输入的首尾空白字符

<input v-model.trim="msg">

貌似 angular 也提供了类似过滤的方法,感兴趣的可以自己去查一下。


# 在 input 中监听键盘事件

在用户登录或者搜索框的时候,一般都会监听键盘事件绑定回车按键,来执行登录/搜索 等操作。

# 原生绑定:

<input onkeydown="keydownMsg(event)" type="text" />;
function keydownMsg(key) {
  keyCode = key.keyCode; //获取按键代码
  if (keyCode == 13) {
    //判断按下的是否为回车键
    // 在input上监听到回车 do something
  }
}

# Vue 按键修饰符

Vue 为监听键盘事件,提供了按键修饰符,并且为常用的按键提供了别名,使用方法如下:当回车按键在 input 中被按下的时候,会触发里面的函数。

<input @keyup.enter="enterActive">

# 点个Star支持我一下~

博客链接

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.