2016年3月

事件起因源于对我之前写的一个小工具的再优化,工具遇到的场景大概是这样的:

工具需要在用户点击完按钮后做大量的循环操作(这里暂时没有算法层的优化),大概有 6 万次,然后与此同时,界面上还需要显示出当前循环进行的程度,百分比。大概是这样的:

setInterval(function() {
    // trigger progress...
}, 16);

for(var i=0; i<60000; i++) {
    // do something...
}

这里在之前就遇到一个问题,就是当 js 去在一个函数里进行循环操作时,js 会被卡住,因为是一个同步过程,所以界面的更新事件会在循环进行完以后再触发执行,这当然不是我们想要的。然后后来,我就把它变成了下面这种递归模式:

function loop() {
    // do something ...
    
    setTimeout(loop, 0);
}

这样的话,就把每次循环变成了一个异步事件,等上次循环执行完后,再把下次循环的事件挂到事件队列上,等待下次 event loop 执行。这样就使得界面更新事件可以正常在循环进行中的时候触发了。

然而这样又出现了另一个问题,就是循环执行速度极慢(详情可脑补《疯狂动物城》的树懒。。。)

好吧,其实分析一下就可以得知,当我执行完一次函数的时候,才把下次循环挂到事件队列上,也就是下次循环的执行时间最短是下次 event loop 取事件队列的间隔时间,所以就导致速度很慢。

OK,那我把所有循环都做成异步事件,一次性都挂到事件队列上呢?

for(var i=0; i<total; i++) {
    setTimeout((function(count){
        return function() {
            // do something...
        }
    })(i), 0);
}

然后呢?我得到的结果是并不能到达我们想象的样子。这些事件会被快速执行,但是界面更新事件只会触发一次,然后就等全部循环执行完成后再次触发,有点儿类似同步循环的阻塞,但是我试了一下,要比同步的阻塞写法慢一些。

好吧,其实这里想一些也就明白了,我这里是将所有的异步事件一次性挂到事件队列上面,导致这一次的事件队列特别长,所以 js 会等这一次的事件全部处理完成后,再去取下次的队列,也就变成了类似同步的写法,只是中间应该会耗费一些事件去切换事件处理吧。

那么再然后呢?怎么样才能做到既能快速进行运算,有不影响到其他事件的触发呢?

最后我试着把循环拆成了若干个小循环去完成,大概是这样:

function loop() {
    for(var i=current; i<total && i<current+200; i++) {
        // do something...
    }
    current = i;
    
    setTimeout(loop, 0);
}

这样就是每次异步函数只执行 200 次,然后把下次的循环挂到下次的事件队列里,这样界面更新事件就有机会出现在下次的事件队列中,从而得到触发。

事情就此解决,也从这件事情中,更明白了一些关于异步和 event loop 的东西:

  • 单个函数内部的执行是同步的,影响到后面函数的执行
  • js 会在全部执行完一次 event loop 的异步事件后,再去取下次的队列,也就是单次队列里面的异步函数如果耗费大量时间的话(不管是因为单个函数耗费大还是因为所有函数个数耗费大),都会影响到下次取队列的时间
  • js 是单线程的,即使你是异步写法,到后面也是同步执行,所以要更好的优化性能的话,就尽量的让 js 在每次的 event loop 的间隔时间里,尽可能多的进行运算。如果运算时间实在太长,就合理的拆成小段儿来填充进每一个 event loop 里面

最后这里记篇文章:

How JavaScript Timers Work
是安然君推荐给我的,把 event loop 的运行机制讲的很明白,个人感觉很好,有空儿尝试翻译一下~

嗯嗯,大概就是这样吧,本文其实少了很多图示,(好吧,其实是因为我太懒了。。。),大家可以去看推荐的文章,讲的很清楚。

本文中如果有不对的地方,欢迎指正,一起学习~

嗯嗯,就这样吧,晚安~

好吧,没啥,就是他们最近在迁移服务器的时候,安装 npm 包的时候又遇到 Please try running this command again as root/Administrator. 这个问题了,然后就准备把这个问题记一下,方便为遇到同样问题的同学参考吧

原文地址: https://docs.npmjs.com/getting-started/fixing-npm-permissions

当你安装一个包到全局的时候可能会遇到一个 EACCES 错误,这是因为你没有 npm 存储全局包的目录的写权限。

你可以通过两种途径来修复这个问题:

  1. 修改 npm 默认目录的权限
  2. 修改 npm 默认目录到其他目录

在进行一下操作之前,请先备份一下你的电脑

方法1:修改 npm 默认目录的权限

  1. 找到 npm 默认目录的路径
npm config get prefix

对于大多数的系统来说,应该是 /usr/local

注意: 如果你显示的路径是只是 /usr,请使用 方法2

  1. 修改 npm 目录所有者为当前用户
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}

这将修改所有被 npm 和其他工具(lib/node_modules, bin 和 share)使用的子目录的权限

方法2:修改 npm 默认目录到其他目录

有些时候你会因为某些问题不想修改 npm 目录(像:/usr)的所有关系,比如你在跟另外的用户共享系统的时候。

我们可以通过修改配置让 npm 使用一个不同目录来代替。在下面的例子里,我们使用了一个在我们 home 目录下的一个隐藏目录

  1. 创建一个用于安装全局包的目录
mkdir ~/.npm-global
  1. 配置 npm 使用新的目录路径
npm config set prefix '~/.npm-global'
  1. 打开或者创建一个 ~/.profile 文件并且添加一行
export PATH=~/.npm-global/bin:$PATH
  1. 回到命令行,更新系统变量
source ~/.profile

测试一下,不使用 sudo 下载一个包到全局

npm install -g jshint

你也可以使用环境变量来代替 2~4 步(当你不想更改 ~/.profile

NPM_CONFIG_PREFIX=~/.npm-global npm install -g jshint

OK, 就这样啦~ 希望会有帮助吧