Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

nodejs Net模块之socket建立连接

$
0
0

遇到个奇葩问题,仍是没搞明白,看有没有能人帮忙解答下。。。。

环境:WIN7 +NODEJS 8.9

用NET模块创建了一个SOCKET,然后去连接本机的服务器,服务器端口32347,但是没有开启这个服务器。 客户端SOCKET会每隔5秒连接一次服务器,当然,由于服务器没开,肯定连接失败。

但是。。。。经过1天多之后,却发现连接成功了。 然后再CMD下查看网络如下:

c:\users\admin>netstat -nao |findstr 32347
TCP      192.168.3.78:32347     192.168.3.78:32347   ESTABLISHED     10364

这是自己和自己建立成功了呀另外用C语言模拟了这种情况

#include<stdio.h>  
#include<strings.h>  
#include<unistd.h>  
#include<arpa/inet.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<netinet/in.h>  
  
#define PORT    1234  
#define BACKLOG 1  
#define MAXDATASIZE 100   
int main()  
{  
		int listenfd,connectfd;  
		struct sockaddr_in server;  
		struct sockaddr_in client;  
		int sin_size;  
char buf[MAXDATASIZE];  
		if((listenfd=(socket(AF_INET,SOCK_STREAM,0)))==-1)  
		{  
				//handle exception  
				perror("Creating socket failed");  
				exit(1);  
		}  
  
		bzero(&server,sizeof(server));  
		server.sin_family=AF_INET;  
		server.sin_port=htons(PORT);  
		server.sin_addr.s_addr=htonl(INADDR_ANY);  
  
		/*Bind socket to address*/  
		if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1)  
		{  
				perror("Bind error");  
				exit(1);  
		}  
		 if(connect(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1)  
		{  
				printf("connect() error\n");  
				exit(1);  
		}  else{
			printf("connect success.");
		}
		send(listenfd,"Welcome to my server.\n",22,0); 
		int numbytes = 0;
if((numbytes=recv(listenfd,buf,MAXDATASIZE,0))==-1)  
		{  
				printf("recv() error");  
				exit(1);  
		}  
		buf[numbytes]='\0';  
		printf("Server Message: %s\n",buf);  
} 

编译执行后

  root@iZ:/home/c# gcc ser.c 
  root@iZ:/home/c# ./a.out 
  connect success.Server Message: Welcome to my server.
  
  root@iZ:/home/c#

关于 node 多进程提高请求响应速度的问题?

$
0
0

在最开始的模型中, 我使用 async 函数阻塞一个请求 3 秒, 在单进程中, 两个发起请求时间相差 200 ms 的请求共需要近 6 秒, 但是我在使用 cluster 模块之后, 结果仍然未变, 我的想法是使用 cluster 模块, 多进程下处理多个请求, 响应速度会变快, 理论上是近 3 秒, 但是为什么结果还是 6 秒? 恳求各位解答! image.pngimage.png server.js 核心代码如下:

function sleep (second) {
  second = parseInt(second, 10);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, second * 1000);
  });
}

let allSpend = 0;

server.on('connection', (socket) => {
  console.log(`a new connection has been established. process ${process.pid}`);
  const start = Date.now();
  console.log('start time:', start);
  socket.on('data', async (chunk) => {
    console.log(`received data from client...`);
    await sleep(3);
    console.log(`wait for ${allSpend} s....`);
    console.log(chunk.toString());
    socket.end("HTTP / 1.1 200 OK\r\nContent - Length: 12\r\n\r\nhello world!");
    console.log(`request spend ${Math.floor((Date.now() - start) / 1000)} s`);
    allSpend += 3;
  });

  socket.on('end', () => {
    console.log('Closing connection with the client.\n');
  });
});

顺便想问问, node 在面对高并发的时候, 优势在哪? cluster 模块提高了 CPU 的利用率, 能提高并发响应速度吗(也就是同时处理 N 个相同请求并同时返回)?

聊一聊 URI Encode

$
0
0

前段时间公司有人反馈部分上传的文件无法下载,由于我们 CDN 用的是七牛云,而那个出问题的系统后端用的是 Node.js,因此想到有可能是七牛官方的 Node.js SDK 的 bug。首先查了一下那些无法下载的文件名字,一看都是文件名中包含了特殊字符 “#” 号。这样大体猜到问题可能出在 URL encode 上,于是查了下七牛的 nodejs-sdk 的源码发现问题出在了这里

// https://github.com/qiniu/nodejs-sdk/blob/v7.1.1/qiniu/storage/rs.js#L651
BucketManager.prototype.publicDownloadUrl = function(domain, fileName) {
  return domain + "/" + encodeURI(fileName);
}

encodeURI是不处理特殊字符 “?” 和 “#” 的,所以如果文件名中包含这种特殊字符就会导致生成的下载链接是错误的,比如文件名是 “hello#world.txt”:

publicDownloadUrl('http://example.com', 'hello#world.txt')
// => http://example.com/hello#world.txt
// 正确的应该是:http://example.com/hello%23world.txt

知道了问题出在没有转义特殊字符,那能不能提前转义呢,由于 encodeURI会转义 “%” 字符,很显然提前转义也不行。

encodeURI('hello%23world.txt')
// => hello%2523world.txt

很显然解决问题只能给七牛的 SDK 提 PR 了,但是如何修改呢?

  1. encodeURI 换成 encodeURIComponent —— 否决("/" 也被转义了)
  2. 写一个 encodePath —— 否决(fileName 也有可能带参数:“he#llo.jpg?imageView2/2/w/800”)
  3. 修改 API 为 publicDownloadUrl(domain, fileName, query) —— 否决(改动太大,要升级大版本)
  4. 将 encodeURI 修改为其他函数避免重复 encode —— 可行(改动不大,影响最小)

于是就提了这个 PR:Safe encode url

PS: 其实考虑 API 友好还是 3 比较好,或者干脆不做 encode 让使用者自己处理。不过这些都影响太大。

既然问题解决了,那是不是本文就收尾了呢,哈哈这只是一个开头,下面我们真正来聊一聊 URL Encode。

规范

上面一直用 URL 是因为它是一个具体的资源,既然谈到规范应该用 URI,具体规范详见:Uniform Resource Identifier

下面这个例子包含了 URI 规范定义的各个部分:

    http://example.com:8042/over/there?name=ferret#nose
     \_/   \______________/\_________/ \_________/ \__/
      |           |            |            |        |
   scheme     authority       path        query   fragment
      |   _____________________|__
     / \ /                        \
     urn:example:animal:ferret:nose

根据规范 URI 包含 5 个部分:

  1. Scheme

    scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
    
  2. Authority (包含:UserInfo, Host, Port)

    authority   = [ userinfo "@" ] host [ ":" port ]
    userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
    host        = IP-literal / IPv4address / reg-name
    port        = *DIGIT
    
  3. Path

    path          = path-abempty    ; begins with "/" or is empty
                     / path-absolute   ; begins with "/" but not "//"
                     / path-noscheme   ; begins with a non-colon segment
                     / path-rootless   ; begins with a segment
                     / path-empty      ; zero characters
    
  4. Query

    query       = *( pchar / "/" / "?" )
    
  5. Fragment

    fragment    = *( pchar / "/" / "?" )
    

实现一个 URI Encode

根据上面的规范,让我们来实现一个 URI Encode 吧,首先定义 URI 各个部分:

const TYPE = {
  SCHEME: 1,
  AUTHORITY: 2,
  USER_INFO: 3,
  HOST_IPV4: 4,
  HOST_IPV6: 5,
  PORT: 6,
  PATH: 7,
  PATH_SEGMENT: 8,
  QUERY: 9,
  QUERY_PARAM: 10,
  FRAGMENT: 11,
  URI: 12
};

然后来实现几个判别函数:

function isAlpha (c) {
  return (c >= 65 && c <= 90) || (c >= 97 && c <= 122);
}

function isDigit (c) {
  return (c >= 48 && c <= 57);
}

function isGenericDelimiter (c) {
  // :/?#[]@
  return [58, 47, 63, 35, 91, 93, 64].indexOf(c) >= 0;
}

function isSubDelimiter (c) {
  // !$&'()*+,;=
  return [33, 36, 38, 39, 40, 41, 42, 43, 44, 59, 61].indexOf(c) >= 0;
}

function isReserved (c) {
  return isGenericDelimiter(c) || isSubDelimiter(c);
}

function isUnreserved (c) {
  // -._~
  return isAlpha(c) || isDigit(c) || [45, 46, 95, 126].indexOf(c) >= 0;
}

function isPchar (c) {
  // :@
  return isUnreserved(c) || isSubDelimiter(c) || c === 58 || c === 64;
}

接下来实现判断某个字符是否需要转义,isAllow(char, type) 为 true 表示不需要转义。

function isAllow (c, type) {
  switch (type) {
    case TYPE.SCHEME:
      return isAlpha(c) || isDigit(c) || [43, 45, 46].indexOf(c) >= 0;// +-.
    case TYPE.AUTHORITY:
      return isUnreserved(c) || isSubDelimiter(c) || c === 58 || c === 64;// :@
    case TYPE.USER_INFO:
      return isUnreserved(c) || isSubDelimiter(c) || c === 58;// :
    case TYPE.HOST_IPV4:
      return isUnreserved(c) || isSubDelimiter(c);
    case TYPE.HOST_IPV6:
      return isUnreserved(c) || isSubDelimiter(c) || [91, 93, 58].indexOf(c) >= 0;// []:
    case TYPE.PORT:
      return isDigit(c);
    case TYPE.PATH:
      return isPchar(c) || c === 47;// /
    case TYPE.PATH_SEGMENT:
      return isPchar(c);
    case TYPE.QUERY:
      return isPchar(c) || c === 47 || c === 63;// /?
    case TYPE.QUERY_PARAM:
      return (c === 61 || c === 38)// =&
        ? false
        : (isPchar(c) || c === 47 || c === 63);// /?
    case TYPE.FRAGMENT:
      return isPchar(c) || c === 47 || c === 63;// /?
    case TYPE.URI:
      return isUnreserved(c);
    default: return false;
  }
}

最后实现 encode 函数:

// 前 128 个 ASCII 码我们用查表的方式转义
const hexTable = new Array(128);
for (let i = 0; i < 128; ++i) {
  hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
}

// 编码前 128 个用查表方式转义,后面的用 encodeURIComponent 处理
function encode (str, type = TYPE.URI) {
  if (!str) return str;
  let out = '';
  let last = 0;
  for (let i = 0; i < str.length; ++i) {
    const c = str.charCodeAt(i);
    // ASCII
    if (c < 0x80) {
      if (last < i) {
        out += encodeURIComponent(str.slice(last, i));
      }
      if (isAllow(c, type)) {
        out += str[i];
      } else {
        out += hexTable[c];
      }
      last = i + 1;
    }
  }
  if (last < str.length) {
    out += encodeURIComponent(str.slice(last));
  }
  return out;
}

这样一个简单的 URI Encode 就实现了,我们可以再封装几个工具函数:

function encodeScheme (scheme) {
  return encode(scheme, TYPE.SCHEME);
}
function encodeAuthority (authority) {
  return encode(authority, TYPE.AUTHORITY);
}
function encodePath (path) {
  return encode(path, TYPE.PATH);
}
function encodeQuery (query) {
  return encode(query, TYPE.QUERY);
}
// ...

来测试下:

encodePath('/foo bar#?.js')
// => /foo%20bar%23%3F.js

最后

最后找了下 npm 貌似没有相关的工具库,于是我们将其发布到了 npm 上:uri-utils,详细的源码在 Github 上:uri-utils,包含了单元测试和 Benchmark。

腾讯云 360 元买 6年半的教程

$
0
0

褥CVM的羊毛:

购买链接: https://cloud.tencent.com/act/campus/group/detail?group=18946请用微信或者其他浏览器打开。根据实际情况购买。 机房选择成都,降配时,可以返还更多时间。 机房选择成都,降配时,可以返还更多时间。 机房选择成都,降配时,可以返还更多时间。 续费链接: https://cloud.tencent.com/act/campus续费时长随意。 降低配置: https://console.cloud.tencent.com/cvm/index后台控制台 - 更多 - 云主机设置 - 调整配置。选择 1G 内存,会返还时间。 PS: 如果感觉 1C1G 够用的话,最多可以用 6年半,但是记得要选择 成都 机房。 如果感觉 1C2G 才够的话,哪个机房都可以。 续费时的学生认证,随便填都可以。 续费时间越长,返还时间越多。

NODE_ENV

$
0
0

TIM图片20180309155759.png圈中的是什么意思?做什么用的啊 ??

部署阿里的这个 rap2 后端服务的时候,经常遇到后端挂。 请问这个用 pm2 如何部署?

$
0
0

https://github.com/thx/rap2-delos

就是这个后端服务, 我直接用 pm2 start /root/rap2-delos-master/dist/dispatch.js 会直接报错

┌──────────┬─────────┬─────────┬────┬─────┬────────┐
│ Name     │ mode    │ status  │ ↺  │ cpu │ memory │
├──────────┼─────────┼─────────┼────┼─────┼────────┤
│ dispatch │ cluster │ errored │ 15 │ 0%  │ 0 B    │
│ dispatch │ cluster │ errored │ 15 │ 0%  │ 0 B    │
│ dispatch │ cluster │ errored │ 15 │ 0%  │ 0 B    │
│ dispatch │ cluster │ errored │ 15 │ 0%  │ 0 B    │
└──────────┴─────────┴─────────┴────┴─────┴────────┘

函数中间件

$
0
0

如何让数据在函数列表中流动,并且灵活的控制其流动过程 Function.prototype.addMiddle=function(fn){ if (!this._fnList){ this._fnList=[] this._fnList.push(this); } if (!this._finalFunc){ this._finalFunc=function(){} } this._fnList.push(fn); var tmpFuncArr = [] for (var i=0,len = this._fnList.length-1;i<=len;i++){ if (i==0){ tmpFuncArr.unshift(this._fnList[len].bind(this,function(){})) }else{ var func = this._fnList[len-i].bind(this,tmpFuncArr[0]) tmpFuncArr.unshift(func) }

				}
				this._finalFunc = tmpFuncArr[0];
				return this;
			}
		}

用法示例

 function step3 (next,arg){
		console.log('1111');
		next&&next(arg)
	}

	step3.addMiddle(function(next,arg){
		console.log('2222');
		next(arg);
	}).addMiddle(function(next,arg){
		console.log('3333');
		next(arg);
	}).addMiddle(function(next,arg){
		console.log('444');
		console.log(arg);
		next();
	})
	step3._finalFunc(123);

【北京】北京盛安德科技发展有限公司诚聘Node.JS全栈开发良将 (薪资:25K-35K)内有短片小亮点,可以进一步了解我们哦~

$
0
0

我们是谁,我们做什么北京盛安德科技发展有限公司(以下简称“盛安德”)成立于2001年,是国际领先的软件开发服务提供商,专注于为客户开发“快速解决问题、创造真正价值“的软件。 我们的愿景和目标是“成为最适合程序员工作和发展的公司”。成立16年以来,我们一直致力于企业管理理念的创新,以提供自由宽松、开放合作的环境让程序员有机会去成长和改变。 “自由创新、开放合作”的企业文化由此培养了越来越多的程序员成长为独立自主,有创造力的敏捷开发工程师。他们能够站在客户的立场上,立足于客户的业务模式和信息化目标,与客户共同面对问题,协同创新,开发出能快速解决客户问题,给客户带来真正价值的软件。这也是盛安德的核心优势, 在全球1000+的客户中赢得了良好的口碑。 盛安德秉承“为客户创造真正价值”的服务宗旨,根据客户(企业)要解决的问题以及差异化的业务模型,针对不同客户量身提供由2~7个敏捷开发工程师组成的敏捷小团队提供的软件研发+服务,帮助中小企业以最小的信息化投入,获得最为直接的收益。而在这个过程中,程序员在帮助客户解决问题、创造价值的同时,自身的价值也在不断提升,盛安德也因此离自己的愿景和目标越来越近。

我们拥有 1000+建立合作的客户  50+正在合作超过两年的客户  30+客户分布的国家,包括中国、美国、英国、澳大利亚、瑞士、德国、芬兰、荷兰、比利时等  400+员工在全球21个办公地点为客户提供服务  40+员工从一线工程师成长为负责交付和管理的角色  0从未和客户产生过知识产权相关的纠纷

我们的价值观和传统• 诚实,不说谎 – 这是每一个盛安德员工坚守的第一准则。 • 不做自有知识产权的软件产品 – 做纯粹的技术服务公司, 从根本上保护客户的知识产权。 • 坚持公平的商业规则 – 我们一直坚持不提供回扣等有违商业公平原则的行为, 并且时刻保持审视自身,给客户创造真正的价值。 • 敏捷 – 从2006年我们开始在整个公司倡导并实践敏捷理念,借助敏捷拉近工程师和市场的距离。 • 精简运营团队规模 – 只有这样才能把最好的资源提供给一线员工。 • 投资于员工– 为员工提供多样化的培训和发展平台。 这些价值观和传统始终指导着我们的业务、机制、品牌、员工发展等每一个业务环节。

我们的网站:英文网站:http://www.shinetechchina.com; 中文网站:http://www.shinetechchina.com.cn

下面奉献上一段小片,帮助你更加了解我们:Shinetech 有你更精彩

我们的福利 我们是开放的管理文化,不介意您之前的工作经历和学历背景,只要您爱生活,爱交流,就可以加入我们;  加入我们,您不必担心迟到,我们实行弹性办公,无需打卡,上下班悠闲自得;  加入我们,休假宽松,第一年5天年假,第二年10天年假,比国家规定多出好多呦;  加入我们,您可享用营养的健康水果,休息室里有不限时不限量的茶点及饮料,还有香浓的咖啡;  加入我们,五险一金齐全,并增加午餐补助、笔记本补助;  加入我们,可享受每年一次的健康体检,并组织其他形式丰富的运动或者活动;  加入我们,您可在图书室借阅各类热门图书,在工作之余让身心得到放松;  加入我们,您可参加公司定期举行的敏捷或者技术方面的培训,时刻给自己的技术充电;

岗位职责及任职要求:岗位职责: 1,负责项目前端、后端的开发,完成整体业务模块的研发工作,具备快速解决问题的能力; 2,参与系统需求分析与设计,负责完成核心代码编写和单元测试( Jest、Mocha) ,并撰写相关技术文档; 3,熟悉 Html5、CSS3,能够根据 Zeplin 设计编写结构良好的响应式布局 Html、CSS 及动效; 4,扎实的 ES6 功底 ,能够快速设计、研发具备可扩展性的 RESTful 接口; 5,参与性能效率优化,所开发的应用需能满足千万级的访问量;

任职要求: 1,具备持续学习能力,对各种新技术、编程语言及框架有学习热情,开发过开源框架者优先; 2,熟悉主流前端框架及其生态链(Vue/React/Angular 以及对应的路由和状态管理方案) ,熟练使用各种工具包 ; 3,丰富的 Node.js 后端开发经验,良好的算法/数据结构基础,精通 Express/Koa/PM2/Bluebird/Mongoose 等框架及工具; 4,熟悉各种常用数据库及缓存产品中的一种或多种,如: MongoDB 、PostgreSQL、 Mysql 、Redis 、Memcache 等; 5,对 CSS/Javascript 性能优化、解决多浏览器兼容性问题具备一定的经验; 6,熟练掌握 Logstash、Elasticsearch、Kibana 者优先; 7,良好的编程习惯,追求极致的代码质量, 能按时、独立、高质量地完成工作 ; 8,英文读写熟练、口语熟练者优先;

薪资标准: 25K-35K(根据个人能力确定)

工作地址:北京市西城区新德街甲20号中影器材大厦705

联系方式:邮箱:wangy@shinetechsoftware.com 电话:010-62370842 联系人:王女士

感兴趣的话就请尽快发送简历过来哦!Shinetech 欢迎你!

shinetech Logo.jpg


【北京】TrustNote 招聘 Node.js程序员 &区块链技术研究员

$
0
0

这是程序员主宰世界的时代

程序员是人类历史上同时具有最大创造力、最高智商、最强活力的优秀群体,中本聪给了我们再一次改变世界的希望!区块链是拥有价值交换能力的基础设施,是前所未有的、纯技术的、去中心化的信任机制和方法论,通过公平的正向激励模型充分释放生产力,是未来互联网和物联网的重要基础,甚至可能成为未来社会的底层代码。更幸运的是,你我不仅能见证区块链技术为社会带来变革,还能亲历其中。TrustNote正在为独具慧眼并勇于掌控未来的资深专家和优质新人,提供充满挑战、乐趣无穷、前景旷阔的工作机会!拒绝浑浑噩噩,拒绝中年油腻,加入TrustNote披荆斩棘,成为主宰世界的区块链技术大咖吧!

TrustNote 简介

TrustNote基金会是创立于2017年的国际化区块链技术社区,总部位于澳大利亚,创始人是知名区块链技术和软件技术专家Jeff Zhou。TrustNote拥有强大的顾问团队和顶尖的技术专家,以打造世界领先的“极轻、极速、极趣”的新一代公有链为目标,引领区块链技术发展,构建可真正落地应用的区块链技术体系和可持续创新的生态。TrustNote资金充足,能够支持技术团队和基金会长期发展。

了解更多关于TrustNote的信息,请访问官网GitHub

招聘–Node.js高级工程师

工作职责:

  • 区块链技术设计方案概念验证,功能模块设计与实现;
  • 撰写技术方案文档和报告;
  • 参与区块链软件架构设计和安全性设计。

任职资格:

  • 211大学计算机相关专业本科及以上学历;
  • 5年以上软件产品开发经验;
  • 熟悉C和Node.js编程语言;
  • 英语水平较好;
  • 了解区块链、P2P等技术;
  • 逻辑思维能力强,乐观开朗,有责任心。

招聘–区块链技术研究员

工作职责:

  • 研究国内外区块链项目的技术方案;
  • 撰写中英文技术文档;
  • 参与区块链技术方案的概念验证;
  • 参与区块链开发者社区维护。

任职资格:

  • 211大学计算机相关专业研究生及以上学历;
  • 英语水平较好;
  • 对数字加密货币非常感兴趣;
  • 逻辑思维能力强,乐观开朗,有责任心。

请将简历发送至 horsen.li@trustnote.org

soket.io提示跨域问题

$
0
0

今天在公司官网,控制台发现这个: image.png

文字如下: Failed to load https://www.xxx.com:7001/socket.io/?EIO=3&transport=polling&t=1520584510239-0: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. Origin ‘https://www.xx.com’ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

这是之前的人做的,我只是维护,soket这部分从来没有动过,以前也没发现问题。网上搜了一下,相关信息不多,感觉都没在点子上。请问这种情况是怎么回事?怎么修复?

【北京/深圳/武汉】-今日头条- 25k—50k 招 前端(node) 、后端、APP、产品、运营、设计。。。史上最强安利贴

$
0
0

你没看错,我们什么都招

我们是谁

字节跳动(今日头条母公司)成立于2012年,是一家技术驱动的移动互联网公司。通过推荐算法和大数据挖掘技术,字节跳动给用户推荐感兴趣的信息,是全球数亿用户的选择。 目前产品矩阵包含今日头条、抖音、西瓜视频、火山小视频、内涵段子、悟空问答、懂车帝、TopBuzz、Musically和Faceu等。

我们可以提供

25k-50k,当然你也可以优秀到超出这个范围

六险一金,大平台,免费三餐下午茶,住房补贴,免费健身房

此处可能需要收简历邮箱了:weijianli@bytedance.com :)

我们做些什么

这些都是我们的产品 851520597578_.pic.jpg821520597577_.pic.jpg

我们有什么要求

谈什么要求,讲什么技术,难道我要求高,你就被唬住了么~ 有追求的人从来不会被唬住 和优秀的人做有挑战的事; 梦想总是要有的,关键是如何迈开第一步; 第一波筛简历靠自信,没有自信的人就只能被刷掉了,sorry~ 不要怕,慌啥,觉得自己OK,觉得薪资符合你的预期,就来

此处又可能需要收简历邮箱了:weijianli@bytedance.com :)

在这里,你还可能得到这些

随便吃的食堂a.jpgWX20180309-211850.png

同事朋友圈截图: 881520598499_.pic.jpg901520598628_.pic.jpg

百万英雄听说过木有呀,嘘!这是内部场~ 831520597577_.pic.jpg现场看着两个人撒狗粮(人家会不会告我乱传绯闻,手动 「双手捂脸」) 861520597579_.pic.jpg

抖音尬舞来一波 871520597579_.pic.jpg

此处可能双需要收简历邮箱了:weijianli@bytedance.com :)

再来一波大图

841520597578_.pic.jpg109244330_5_20170825054646535.jpg109244330_6_20170825054646629.jpg109244330_10_2017082505464735.jpg

尾巴(下面才是真正想说的)

看到这里的同学,我觉得基本稳了。 当年,桑德伯格考虑Facebook的机会时,曾详细的列了一张试算表,评估是否符合她的预期。Google当时的CEO施密特给了她一句话,「如果有人给你一个火箭上的座位,别问位子在哪里,上就是了」二级引擎已经启动,错过今年,后悔一生! 此处可能需要收简历邮箱了:weijianli@bytedance.com :) 对于不好意思发简历给我的同学,也可以访问下面的连接自己填写: https://job.toutiao.com/2018/spring_referral/?token=dbUmhLBaKsTDVIcy7osFiQ%3D%3D&from=timeline&isappinstalled=0

由于公司内部有规定,一些能够『激发大家积极涌向头条的数据』不能随便外漏。但是,我可以说我的总结呀~

在此立碑,欢迎挖坟

字节跳动(今日头条母公司)很有可能成为中国史上第一家 全球化互联网公司(国内营收处于行业领先水平,海外多国家营收 > 国内营收,比肩facebook,amazon ps:华为不算互联网哈) 。 对于该结论: 不解释不论证不负责不要笑,不要笑,不要笑,曾经马云在讲他的设想与规划时,周围的人都在笑;我们张一鸣童鞋每年讲公司的下一年目标时,大家也在笑。 我觉得,有爸爸的公司永远长不大,头条没有爸爸~

请问前端是如何像growingio那样子做无痕埋点的?

$
0
0

最近想研究下,但是不知道是一个怎么样的思路实现

Ckeditor将内容中的大写字母自动转换成小写的问题

$
0
0

博客后台使用的是ckeditor,但是最近发现一个问题。发表的文章中的大写字母总是自动转换成了小写。很郁闷,不知道是哪里的问题。

从零开始写一个Javascript解析器

$
0
0

最近在研究 AST, 之前有一篇文章 面试官: 你了解过 Babel 吗?写过 Babel 插件吗? 答: 没有。卒为什么要去了解它? 因为懂得 AST 真的可以为所欲为

简单点说,使用 Javascript 运行Javascript代码。

这篇文章来告诉你,如何写一个最简单的解析器。

前言(如果你很清楚如何执行自定义 js 代码,请跳过)

在大家的认知中,有几种执行自定义脚本的方法?我们来列举一下:

Web

创建 script 脚本,并插入文档流

function runJavascriptCode(code) {
  const script = document.createElement("script");
  script.innerText = code;
  document.body.appendChild(script);
}

runJavascriptCode("alert('hello world')");

eval

无数人都在说,不要使用eval,虽然它可以执行自定义脚本

eval("alert('hello world')");

参考链接: Why is using the JavaScript eval function a bad idea?

setTimeout

setTimeout 同样能执行,不过会把相关的操作,推到下一个事件循环中执行

setTimeout("console.log('hello world')");
console.log("I should run first");

// 输出
// I should run first
// hello world'

new Function

new Function("alert('hello world')")();

参考链接: Are eval() and new Function() the same thing?

NodeJs

require

可以把 Javascript 代码写进一个 Js 文件,然后在其他文件 require 它,达到执行的效果。

NodeJs 会缓存模块,如果你执行 N 个这样的文件,可能会消耗很多内存. 需要执行完毕后,手动清除缓存。

Vm

const vm = require("vm");

const sandbox = {
  animal: "cat",
  count: 2
};

vm.runInNewContext('count += 1; name = "kitty"', sandbox);

以上方式,除了 Node 能优雅的执行以外,其他都不行,API 都需要依赖宿主环境。

解释器用途

在能任何执行 Javascript 的代码的平台,执行自定义代码。

比如小程序,屏蔽了以上执行自定义代码的途径

那就真的不能执行自定义代码了吗?

非也

工作原理

基于 AST(抽象语法树),找到对应的对象/方法, 然后执行对应的表达式。

这怎么说的有点绕口呢,举个栗子console.log("hello world");

原理: 通过 AST 找到console对象,再找到它log函数,最后运行函数,参数为hello world

准备工具

  • Babylon, 用于解析代码,生成 AST
  • babel-types, 判断节点类型
  • astexplorer, 随时查看抽象语法树

开始撸代码

我们以运行console.log("hello world")为例

打开astexplorer, 查看对应的 AST

1

由图中看到,我们要找到console.log("hello world"),必须要向下遍历节点的方式,经过FileProgramExpressionStatementCallExpressionMemberExpression节点,其中涉及到IdentifierStringLiteral节点

我们先定义visitors, visitors是对于不同节点的处理方式

const visitors = {
  File(){},
  Program(){},
  ExpressionStatement(){},
  CallExpression(){},
  MemberExpression(){},
  Identifier(){},
  StringLiteral(){}
};

再定义一个遍历节点的函数

/**
 * 遍历一个节点
 * @param {Node} node 节点对象
 * @param {*} scope 作用域
 */
function evaluate(node, scope) {
  const _evalute = visitors[node.type];
  // 如果该节点不存在处理函数,那么抛出错误
  if (!_evalute) {
    throw new Error(`Unknown visitors of ${node.type}`);
  }
  // 执行该节点对应的处理函数
  return _evalute(node, scope);
}

下面是对各个节点的处理实现

const babylon = require("babylon");
const types = require("babel-types");

const visitors = {
  File(node, scope) {
    evaluate(node.program, scope);
  },
  Program(program, scope) {
    for (const node of program.body) {
      evaluate(node, scope);
    }
  },
  ExpressionStatement(node, scope) {
    return evaluate(node.expression, scope);
  },
  CallExpression(node, scope) {
    // 获取调用者对象
    const func = evaluate(node.callee, scope);

    // 获取函数的参数
    const funcArguments = node.arguments.map(arg => evaluate(arg, scope));

    // 如果是获取属性的话: console.log
    if (types.isMemberExpression(node.callee)) {
      const object = evaluate(node.callee.object, scope);
      return func.apply(object, funcArguments);
    }
  },
  MemberExpression(node, scope) {
    const { object, property } = node;

    // 找到对应的属性名
    const propertyName = property.name;

    // 找对对应的对象
    const obj = evaluate(object, scope);

    // 获取对应的值
    const target = obj[propertyName];

    // 返回这个值,如果这个值是function的话,那么应该绑定上下文this
    return typeof target === "function" ? target.bind(obj) : target;
  },
  Identifier(node, scope) {
    // 获取变量的值
    return scope[node.name];
  },
  StringLiteral(node) {
    return node.value;
  }
};

function evaluate(node, scope) {
  const _evalute = visitors[node.type];
  if (!_evalute) {
    throw new Error(`Unknown visitors of ${node.type}`);
  }
  // 递归调用
  return _evalute(node, scope);
}

const code = "console.log('hello world')";

// 生成AST树
const ast = babylon.parse(code);

// 解析AST
// 需要传入执行上下文,否则找不到``console``对象
evaluate(ast, { console: console });

在 Nodejs 中运行试试看

$ node ./index.js
hello world

然后我们更改下运行的代码 const code = "console.log(Math.pow(2, 2))";

因为上下文没有Math对象,那么会得出这样的错误 TypeError: Cannot read property 'pow' of undefined

记得传入上下文evaluate(ast, {console, Math});

再运行,又得出一个错误Error: Unknown visitors of NumericLiteral

原来Math.pow(2, 2)中的 2,是数字字面量

2

节点是NumericLiteral, 但是在visitors中,我们却没有定义这个节点的处理方式.

那么我们就加上这么个节点:

NumericLiteral(node){
    return node.value;
  }

再次运行,就跟预期结果一致了

$ node ./index.js
4

到这里,已经实现了最最基本的函数调用了

进阶

既然是解释器,难道只能运行 hello world 吗?显然不是

我们来声明个变量吧

var name = "hello world";
console.log(name);

先看下 AST 结构

3

visitors中缺少VariableDeclarationVariableDeclarator节点的处理,我们给加上

VariableDeclaration(node, scope) {
    const kind = node.kind;
    for (const declartor of node.declarations) {
      const {name} = declartor.id;
      const value = declartor.init
        ? evaluate(declartor.init, scope)
        : undefined;
      scope[name] = value;
    }
  },
  VariableDeclarator(node, scope) {
    scope[node.id.name] = evaluate(node.init, scope);
  }

运行下代码,已经打印出hello world

我们再来声明函数

function test() {
  var name = "hello world";
  console.log(name);
}
test();

根据上面的步骤,新增了几个节点

BlockStatement(block, scope) {
    for (const node of block.body) {
      // 执行代码块中的内容
      evaluate(node, scope);
    }
  },
  FunctionDeclaration(node, scope) {
    // 获取function
    const func = visitors.FunctionExpression(node, scope);

    // 在作用域中定义function
    scope[node.id.name] = func;
  },
  FunctionExpression(node, scope) {
    // 自己构造一个function
    const func = function() {
      // TODO: 获取函数的参数
      // 执行代码块中的内容
      evaluate(node.body, scope);
    };

    // 返回这个function
    return func;
  }

然后修改下CallExpression

// 如果是获取属性的话: console.log
if (types.isMemberExpression(node.callee)) {
  const object = evaluate(node.callee.object, scope);
  return func.apply(object, funcArguments);
} else if (types.isIdentifier(node.callee)) {
  // 新增
  func.apply(scope, funcArguments); // 新增
}

运行也能过打印出hello world

完整示例代码

其他

限于篇幅,我不会讲怎么处理所有的节点,以上已经讲解了基本的原理。

对于其他节点,你依旧可以这么来,其中需要注意的是: 上文中,作用域我统一用了一个 scope,没有父级/子级作用域之分

也就意味着这样的代码是可以运行的

var a = 1;
function test() {
  var b = 2;
}
test();
console.log(b); // 2

处理方法: 在递归 AST 树的时候,遇到一些会产生子作用域的节点,应该使用新的作用域,比如说functionfor in

最后

以上只是一个简单的模型,它连玩具都算不上,依旧有很多的坑。比如:

  • 变量提升, 作用域应该有预解析阶段
  • 作用域有很多问题
  • 特定节点,必须嵌套在某节点下。比如 super()就必须在 Class 节点内,无论嵌套多少层
  • this 绑定

连续几个晚上的熬夜之后,我写了一个比较完善的库vm.js,基于jsjs修改而来,站在巨人的肩膀上。

与它不同的是:

  • 重构了递归方式,解决了一些没法解决的问题
  • 修复了多项 bug
  • 添加了测试用例
  • 支持 es6 以及其他语法糖

目前正在开发中, 等待更加完善之后,会发布第一个版本。

欢迎大佬们拍砖和 PR.

小程序今后变成大程序,业务代码通过 Websocket 推送过来执行,小程序源码只是一个空壳,想想都刺激.

项目地址: https://github.com/axetroy/vm.js

在线预览: http://axetroy.github.io/vm.js/

原文: http://axetroy.xyz/#/post/172

2018 刑侦科推理题的暴力破解

$
0
0

这应该算一个 demo 吧, 利用 NodeJs 做的暴力破解, 并没有网上说的需要二十多个分钟, 虽然有百万级的数据量, 但是也是极速破解的. 然而并没有过多优化代码, 如果喜欢就点个 star, 跟帖来发布你 的破解法, 贴上传送门 开始你的表演test.jpg


zsh命令行里的命令历史有接口可以获取或者添加么?

$
0
0

在命令行里按键盘上下键,可以查看历史命令。请问这个历史有接口可以获取么?

webpack详解

$
0
0

webpack是现代前端开发中最火的模块打包工具,只需要通过简单的配置,便可以完成模块的加载和打包。那它是怎么做到通过对一些插件的配置,便可以轻松实现对代码的构建呢?

webpack的配置

const path = require('path');
module.exports = {
  entry: "./app/entry", // string | object | array
  // Webpack打包的入口
  output: {  // 定义webpack如何输出的选项
    path: path.resolve(__dirname, "dist"), // string
    // 所有输出文件的目标路径
    filename: "[chunkhash].js", // string
    // 「入口(entry chunk)」文件命名模版
    publicPath: "/assets/", // string
    // 构建文件的输出目录
    /* 其它高级配置 */
  },
  module: {  // 模块相关配置
    rules: [ // 配置模块loaders,解析规则
      {
        test: /\.jsx?$/,  // RegExp | string
        include: [ // 和test一样,必须匹配选项
          path.resolve(__dirname, "app")
        ],
        exclude: [ // 必不匹配选项(优先级高于test和include)
          path.resolve(__dirname, "app/demo-files")
        ],
        loader: "babel-loader", // 模块上下文解析
        options: { // loader的可选项
          presets: ["es2015"]
        },
      },
  },
  resolve: { //  解析模块的可选项
    modules: [ // 模块的查找目录
      "node_modules",
      path.resolve(__dirname, "app")
    ],
    extensions: [".js", ".json", ".jsx", ".css"], // 用到的文件的扩展
    alias: { // 模块别名列表
      "module": "new-module"
	  },
  },
  devtool: "source-map", // enum
  // 为浏览器开发者工具添加元数据增强调试
  plugins: [ // 附加插件列表
    // ...
  ],
}

从上面我萌可以看到,webpack配置中需要理解几个核心的概念EntryOutputLoadersPluginsChunk

  • Entry:指定webpack开始构建的入口模块,从该模块开始构建并计算出直接或间接依赖的模块或者库
  • Output:告诉webpack如何命名输出的文件以及输出的目录
  • Loaders:由于webpack只能处理javascript,所以我萌需要对一些非js文件处理成webpack能够处理的模块,比如sass文件
  • Plugins:Loaders将各类型的文件处理成webpack能够处理的模块,plugins有着很强的能力。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。但也是最复杂的一个。比如对js文件进行压缩优化的UglifyJsPlugin插件
  • Chunk:coding split的产物,我萌可以对一些代码打包成一个单独的chunk,比如某些公共模块,去重,更好的利用缓存。或者按需加载某些功能模块,优化加载时间。在webpack3及以前我萌都利用CommonsChunkPlugin将一些公共代码分割成一个chunk,实现单独加载。在webpack4 中CommonsChunkPlugin被废弃,使用SplitChunksPlugin

webpack详解

读到这里,或许你对webpack有一个大概的了解,那webpack 是怎么运行的呢?我萌都知道,webpack是高度复杂抽象的插件集合,理解webpack的运行机制,对于我萌日常定位构建错误以及写一些插件处理构建任务有很大的帮助。

不得不说的tapable

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。在Tapable1.0之前,也就是webpack3及其以前使用的Tapable,提供了包括

  • plugin(name:string, handler:function)注册插件到Tapable对象中
  • apply(…pluginInstances: (AnyPlugin|function)[])调用插件的定义,将事件监听器注册到Tapable实例注册表中
  • applyPlugins*(name:string, …)多种策略细致地控制事件的触发,包括applyPluginsAsyncapplyPluginsParallel等方法实现对事件触发的控制,实现

(1)多个事件连续顺序执行 (2)并行执行 (3)异步执行 (4)一个接一个地执行插件,前面的输出是后一个插件的输入的瀑布流执行顺序 (5)在允许时停止执行插件,即某个插件返回了一个undefined的值,即退出执行 我们可以看到,Tapable就像nodejs中EventEmitter,提供对事件的注册on和触发emit,理解它很重要,看个栗子:比如我萌来写一个插件

function CustomPlugin() {}
CustomPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', pluginFunction);
}

在webpack的生命周期中会适时的执行

this.apply*("emit",options)

当然上面提到的Tapable都是1.0版本之前的,如果想深入学习,可以查看Tapable 和 事件流那1.0的Tapable又是什么样的呢?1.0版本发生了巨大的改变,不再是此前的通过plugin注册事件,通过applyPlugins*触发事件调用,那1.0的Tapable是什么呢?

暴露出很多的钩子,可以使用它们为插件创建钩子函数

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");

我萌来看看 怎么使用。

class Order {
    constructor() {
        this.hooks = { //hooks
            goods: new SyncHook(['goodsId', 'number']),
            consumer: new AsyncParallelHook(['userId', 'orderId'])
        }
    }

    queryGoods(goodsId, number) {
        this.hooks.goods.call(goodsId, number);
    }

    consumerInfoPromise(userId, orderId) {
        this.hooks.consumer.promise(userId, orderId).then(() => {
            //TODO
        })
    }

    consumerInfoAsync(userId, orderId) {
        this.hooks.consumer.callAsync(userId, orderId, (err, data) => {
            //TODO
        })
    }
}

对于所有的hook的构造函数均接受一个可选的string类型的数组

const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// 调用tap方法注册一个consument
order.hooks.goods.tap('QueryPlugin', (goodsId, number) => {
    return fetchGoods(goodsId, number);
})
// 再添加一个
order.hooks.goods.tap('LoggerPlugin', (goodsId, number) => {
    logger(goodsId, number);
})

// 调用
order.queryGoods('10000000', 1)

对于一个 SyncHook,我们通过tap来添加消费者,通过call来触发钩子的顺序执行。

对于一个非sync*类型的钩子,即async*类型的钩子,我萌还可以通过其它方式注册消费者和调用

// 注册一个sync 钩子
order.hooks.consumer.tap('LoggerPlugin', (userId, orderId) => {
   logger(userId, orderId);
})

order.hooks.consumer.tapAsync('LoginCheckPlugin', (userId, orderId, callback) => {
    LoginCheck(userId, callback);
})

order.hooks.consumer.tapPromise('PayPlugin', (userId, orderId) => {
    return Promise.resolve();
})

// 调用
// 返回Promise
order.consumerInfoPromise('user007', '1024');

//回调函数
order.consumerInfoAsync('user007', '1024')

通过上面的栗子,你可能已经大致了解了Tapable的用法,它的用法

  • 插件注册数量
  • 插件注册的类型(sync, async, promise)
  • 调用的方式(sync, async, promise)
  • 实例钩子的时候参数数量
  • 是否使用了interception

Tapable详解

Alt text对于Sync*类型的钩子来说。

  • 注册在该钩子下面的插件的执行顺序都是顺序执行。
  • 只能使用tap注册,不能使用tapPromisetapAsync注册
// 所有的钩子都继承于Hook
class Sync* extends Hook { 
	tapAsync() { // Sync*类型的钩子不支持tapAsync
		throw new Error("tapAsync is not supported on a Sync*");
	}
	tapPromise() {// Sync*类型的钩子不支持tapPromise
		throw new Error("tapPromise is not supported on a Sync*");
	}
	compile(options) { // 编译代码来按照一定的策略执行Plugin
		factory.setup(this, options);
		return factory.create(options);
	}
}

对于Async*类型钩子

  • 支持taptapPromisetapAsync注册
class AsyncParallelHook extends Hook {
	constructor(args) {
		super(args);
		this.call = this._call = undefined;
	}

	compile(options) {
		factory.setup(this, options);
		return factory.create(options);
	}
}
class Hook {
	constructor(args) {
		if(!Array.isArray(args)) args = [];
		this._args = args; // 实例钩子的时候的string类型的数组
		this.taps = []; // 消费者
		this.interceptors = []; // interceptors
		this.call = this._call =  // 以sync类型方式来调用钩子
		this._createCompileDelegate("call", "sync");
		this.promise = 
		this._promise = // 以promise方式
		this._createCompileDelegate("promise", "promise");
		this.callAsync = 
		this._callAsync = // 以async类型方式来调用
		this._createCompileDelegate("callAsync", "async");
		this._x = undefined; // 
	}

	_createCall(type) {
		return this.compile({
			taps: this.taps,
			interceptors: this.interceptors,
			args: this._args,
			type: type
		});
	}

	_createCompileDelegate(name, type) {
		const lazyCompileHook = (...args) => {
			this[name] = this._createCall(type);
			return this[name](...args);
		};
		return lazyCompileHook;
	}
	// 调用tap 类型注册
	tap(options, fn) {
		// ...
		options = Object.assign({ type: "sync", fn: fn }, options);
		// ...
		this._insert(options);  // 添加到 this.taps中
	}
	// 注册 async类型的钩子
	tapAsync(options, fn) {
		// ...
		options = Object.assign({ type: "async", fn: fn }, options);
		// ...
		this._insert(options); // 添加到 this.taps中
	}
	注册 promise类型钩子
	tapPromise(options, fn) {
		// ...
		options = Object.assign({ type: "promise", fn: fn }, options);
		// ...
		this._insert(options); // 添加到 this.taps中
	}
	
}

每次都是调用taptapSynctapPromise注册不同类型的插件钩子,通过调用callcallAsyncpromise方式调用。其实调用的时候为了按照一定的执行策略执行,调用compile方法快速编译出一个方法来执行这些插件。

const factory = new Sync*CodeFactory();
class Sync* extends Hook { 
	// ...
	compile(options) { // 编译代码来按照一定的策略执行Plugin
		factory.setup(this, options);
		return factory.create(options);
	}
}

class Sync*CodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}

compile中调用HookCodeFactory#create方法编译生成执行代码。


class HookCodeFactory {
	constructor(config) {
		this.config = config;
		this.options = undefined;
	}

	create(options) {
		this.init(options);
		switch(this.options.type) {
			case "sync":  // 编译生成sync, 结果直接返回
				return new Function(this.args(), 
				"\"use strict\";\n" + this.header() + this.content({
					// ...
					onResult: result => `return ${result};\n`,
					// ...
				}));
			case "async": // async类型, 异步执行,最后将调用插件执行结果来调用callback,
				return new Function(this.args({
					after: "_callback"
				}), "\"use strict\";\n" + this.header() + this.content({
					// ...
					onResult: result => `_callback(null, ${result});\n`,
					onDone: () => "_callback();\n"
				}));
			case "promise": // 返回promise类型,将结果放在resolve中
				// ...
				code += "return new Promise((_resolve, _reject) => {\n";
				code += "var _sync = true;\n";
				code += this.header();
				code += this.content({
					// ...
					onResult: result => `_resolve(${result});\n`,
					onDone: () => "_resolve();\n"
				});
			    // ...
				return new Function(this.args(), code);
		}
	}
	// callTap 就是执行一些插件,并将结果返回
	callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
		let code = "";
		let hasTapCached = false;
		// ...
		code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
		const tap = this.options.taps[tapIndex];
		switch(tap.type) {
			case "sync":
				// ...
				if(onResult) {
					code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
						before: tap.context ? "_context" : undefined
					})});\n`;
				} else {
					code += `_fn${tapIndex}(${this.args({
						before: tap.context ? "_context" : undefined
					})});\n`;
				}
				
				if(onResult) { // 结果透传
					code += onResult(`_result${tapIndex}`);
				}
				if(onDone) { // 通知插件执行完毕,可以执行下一个插件
					code += onDone();
				}
				break;
			case "async": //异步执行,插件运行完后再将结果通过执行callback透传
				let cbCode = "";
				if(onResult)
					cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
				else
					cbCode += `_err${tapIndex} => {\n`;
				cbCode += `if(_err${tapIndex}) {\n`;
				cbCode += onError(`_err${tapIndex}`);
				cbCode += "} else {\n";
				if(onResult) {
					cbCode += onResult(`_result${tapIndex}`);
				}
				
				cbCode += "}\n";
				cbCode += "}";
				code += `_fn${tapIndex}(${this.args({
					before: tap.context ? "_context" : undefined,
					after: cbCode //cbCode将结果透传
				})});\n`;
				break;
			case "promise": // _fn${tapIndex} 就是第tapIndex 个插件,它必须是个Promise类型的插件
				code += `var _hasResult${tapIndex} = false;\n`;
				code += `_fn${tapIndex}(${this.args({
					before: tap.context ? "_context" : undefined
				})}).then(_result${tapIndex} => {\n`;
				code += `_hasResult${tapIndex} = true;\n`;
				if(onResult) {
					code += onResult(`_result${tapIndex}`);
				}
			// ...
				break;
		}
		return code;
	}
	// 按照插件的注册顺序,按照顺序递归调用执行插件
	callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {
		// ...
		const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
		const next = i => {
			// ...
			const done = () => next(i + 1);
			// ...
			return this.callTap(i, {
				// ...
				onResult: onResult && ((result) => {
					return onResult(i, result, done, doneBreak);
				}),
				// ...
			});
		};
		return next(0);
	}

	callTapsLooping({ onError, onDone, rethrowIfPossible }) {
		
		const syncOnly = this.options.taps.every(t => t.type === "sync");
		let code = "";
		if(!syncOnly) {
			code += "var _looper = () => {\n";
			code += "var _loopAsync = false;\n";
		}
		code += "var _loop;\n";
		code += "do {\n";
		code += "_loop = false;\n";
		// ...
		code += this.callTapsSeries({
			// ...
			onResult: (i, result, next, doneBreak) => { // 一旦某个插件返回不为undefined,  即一只调用某个插件执行,如果为undefined,开始调用下一个
				let code = "";
				code += `if(${result} !== undefined) {\n`;
				code += "_loop = true;\n";
				if(!syncOnly)
					code += "if(_loopAsync) _looper();\n";
				code += doneBreak(true);
				code += `} else {\n`;
				code += next();
				code += `}\n`;
				return code;
			},
			// ...
		})
		code += "} while(_loop);\n";
		// ...
		return code;
	}
	// 并行调用插件执行
	callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) => run() }) {
		// ...
		// 遍历注册都所有插件,并调用
		for(let i = 0; i < this.options.taps.length; i++) {
			// ...
			code += "if(_counter <= 0) break;\n";
			code += onTap(i, () => this.callTap(i, {
				// ...
				onResult: onResult && ((result) => {
					let code = "";
					code += "if(_counter > 0) {\n";
					code += onResult(i, result, done, doneBreak);
					code += "}\n";
					return code;
				}),
				// ...
			}), done, doneBreak);
		}
		// ...
		return code;
	}
}

HookCodeFactory#create中调用到content方法,此方法将按照此钩子的执行策略,调用不同的方法来执行编译 生成最终的代码。

  • SyncHook中调用`callTapsSeries`编译生成最终执行插件的函数,`callTapsSeries`做的就是将插件列表中插件按照注册顺序遍历执行。
    
class SyncHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}
  • SyncBailHook中当一旦某个返回值结果不为undefined便结束执行列表中的插件
 class SyncBailHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			// ...
			onResult: (i, result, next) => `if(${result} !== undefined) {\n${onResult(result)};\n} else {\n${next()}}\n`,
			// ...
		});
	}
}
  • SyncWaterfallHook中上一个插件执行结果当作下一个插件的入参
class SyncWaterfallHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			// ...
			onResult: (i, result, next) => {
				let code = "";
				code += `if(${result} !== undefined) {\n`;
				code += `${this._args[0]} = ${result};\n`;
				code += `}\n`;
				code += next();
				return code;
			},
			onDone: () => onResult(this._args[0]),
		});
	}
}
  • AsyncParallelHook调用callTapsParallel并行执行插件
class AsyncParallelHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone }) {
		return this.callTapsParallel({
			onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),
			onDone
		});
	}
}

webpack流程篇

本文关于webpack 的流程讲解是基于webpack4的。

webpack 入口文件

从webpack项目的package.json文件中我萌找到了入口执行函数,在函数中引入webpack,那么入口将会是lib/webpack.js,而如果在shell中执行,那么将会走到./bin/webpack.js,我萌就以lib/webpack.js为入口开始吧!

{
  "name": "webpack",
  "version": "4.1.1",
  ...
  "main": "lib/webpack.js",
  "web": "lib/webpack.web.js",
  "bin": "./bin/webpack.js",
  ...
  }

webpack入口

const webpack = (options, callback) => {
    // ...
    // 验证options正确性
    // 预处理options
    options = new WebpackOptionsDefaulter().process(options); // webpack4的默认配置
	compiler = new Compiler(options.context); // 实例Compiler
	// ...
    // 若options.watch === true && callback 则开启watch线程
	compiler.watch(watchOptions, callback);
	compiler.run(callback);
	return compiler;
};

webpack 的入口文件其实就实例了Compiler并调用了run方法开启了编译,webpack的编译都按照下面的钩子调用顺序执行。

  • before-run 清除缓存
  • run 注册缓存数据钩子
  • before-compile
  • compile 开始编译
  • make 从入口分析依赖以及间接依赖模块,创建模块对象
  • build-module 模块构建
  • seal 构建结果封装, 不可再更改
  • after-compile 完成构建,缓存数据
  • emit 输出到dist目录

编译&构建流程

webpack中负责构建和编译都是Compilation

class Compilation extends Tapable {
	constructor(compiler) {
		super();
		this.hooks = {
			// hooks
		};
		// ...
		this.compiler = compiler;
		// ...
		// template
		this.mainTemplate = new MainTemplate(this.outputOptions);
		this.chunkTemplate = new ChunkTemplate(this.outputOptions);
		this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
			this.outputOptions
		);
		this.runtimeTemplate = new RuntimeTemplate(
			this.outputOptions,
			this.requestShortener
		);
		this.moduleTemplates = {
			javascript: new ModuleTemplate(this.runtimeTemplate),
			webassembly: new ModuleTemplate(this.runtimeTemplate)
		};

		// 构建生成的资源
		this.chunks = [];
		this.chunkGroups = [];
		this.modules = [];
		this.additionalChunkAssets = [];
		this.assets = {};
		this.children = [];
		// ...
	}
	// 
	buildModule(module, optional, origin, dependencies, thisCallback) {
		// ...
		// 调用module.build方法进行编译代码,build中 其实是利用acorn编译生成AST
		this.hooks.buildModule.call(module);
		module.build(/**param*/);
	}
	// 将模块添加到列表中,并编译模块
	_addModuleChain(context, dependency, onModule, callback) {
		    // ...
		    // moduleFactory.create创建模块,这里会先利用loader处理文件,然后生成模块对象
		    moduleFactory.create(
				{
					contextInfo: {
						issuer: "",
						compiler: this.compiler.name
					},
					context: context,
					dependencies: [dependency]
				},
				(err, module) => {
					const addModuleResult = this.addModule(module);
					module = addModuleResult.module;
					onModule(module);
					dependency.module = module;
					
					// ...
					// 调用buildModule编译模块
					this.buildModule(module, false, null, null, err => {});
				}
		});
	}
	// 添加入口模块,开始编译&构建
	addEntry(context, entry, name, callback) {
		// ...
		this._addModuleChain( // 调用_addModuleChain添加模块
			context,
			entry,
			module => {
				this.entries.push(module);
			},
			// ...
		);
	}

	
	seal(callback) {
		this.hooks.seal.call();

		// ...
		const chunk = this.addChunk(name);
		const entrypoint = new Entrypoint(name);
		entrypoint.setRuntimeChunk(chunk);
		entrypoint.addOrigin(null, name, preparedEntrypoint.request);
		this.namedChunkGroups.set(name, entrypoint);
		this.entrypoints.set(name, entrypoint);
		this.chunkGroups.push(entrypoint);

		GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
		GraphHelpers.connectChunkAndModule(chunk, module);

		chunk.entryModule = module;
		chunk.name = name;

		 // ...
		this.hooks.beforeHash.call();
		this.createHash();
		this.hooks.afterHash.call();
		this.hooks.beforeModuleAssets.call();
		this.createModuleAssets();
		if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
			this.hooks.beforeChunkAssets.call();
			this.createChunkAssets();
		}
		// ...
	}


	createHash() {
		// ...
	}
	
	// 生成 assets 资源并 保存到 Compilation.assets 中 给webpack写插件的时候会用到
	createModuleAssets() {
		for (let i = 0; i < this.modules.length; i++) {
			const module = this.modules[i];
			if (module.buildInfo.assets) {
				for (const assetName of Object.keys(module.buildInfo.assets)) {
					const fileName = this.getPath(assetName);
					this.assets[fileName] = module.buildInfo.assets[assetName]; 
					this.hooks.moduleAsset.call(module, fileName);
				}
			}
		}
	}

	createChunkAssets() {
	 // ...
	}
}

在webpack make钩子中, tapAsync注册了一个DllEntryPlugin, 就是将入口模块通过调用compilation.addEntry方法将所有的入口模块添加到编译构建队列中,开启编译流程。

compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => {
		compilation.addEntry(
			this.context,
			new DllEntryDependency(
				this.entries.map((e, idx) => {
					const dep = new SingleEntryDependency(e);
					dep.loc = `${this.name}:${idx}`;
					return dep;
				}),
				this.name
			),
			// ...
		);
	});

随后在addEntry中调用_addModuleChain开始编译。在_addModuleChain首先会生成模块,最后构建。

class NormalModuleFactory extends Tapable {
	// ...
	create(data, callback) {
		// ...
		this.hooks.beforeResolve.callAsync(
			{
				contextInfo,
				resolveOptions,
				context,
				request,
				dependencies
			},
			(err, result) => {
				if (err) return callback(err);

				// Ignored
				if (!result) return callback();
				// factory 钩子会触发 resolver 钩子执行,而resolver钩子中会利用acorn 处理js生成AST,再利用acorn处理前,会使用loader加载文件
				const factory = this.hooks.factory.call(null);

				factory(result, (err, module) => {
					if (err) return callback(err);

					if (module && this.cachePredicate(module)) {
						for (const d of dependencies) {
							d.__NormalModuleFactoryCache = module;
						}
					}

					callback(null, module);
				});
			}
		);
	}
}

在编译完成后,调用compilation.seal方法封闭,生成资源,这些资源保存在compilation.assets, compilation.chunk, 在给webpack写插件的时候会用到

class Compiler extends Tapable {
	constructor(context) {
		super();
		this.hooks = {
			beforeRun: new AsyncSeriesHook(["compilation"]),
			run: new AsyncSeriesHook(["compilation"]),
			emit: new AsyncSeriesHook(["compilation"]),
			afterEmit: new AsyncSeriesHook(["compilation"]),
			compilation: new SyncHook(["compilation", "params"]),
			beforeCompile: new AsyncSeriesHook(["params"]),
			compile: new SyncHook(["params"]),
			make: new AsyncParallelHook(["compilation"]),
			afterCompile: new AsyncSeriesHook(["compilation"]),
			// other hooks
		};
		// ...
	}

	run(callback) {
		const startTime = Date.now();

		const onCompiled = (err, compilation) => {
			// ...

			this.emitAssets(compilation, err => {
				if (err) return callback(err);

				if (compilation.hooks.needAdditionalPass.call()) {
					compilation.needAdditionalPass = true;

					const stats = new Stats(compilation);
					stats.startTime = startTime;
					stats.endTime = Date.now();
					this.hooks.done.callAsync(stats, err => {
						if (err) return callback(err);

						this.hooks.additionalPass.callAsync(err => {
							if (err) return callback(err);
							this.compile(onCompiled);
						});
					});
					return;
				}
				// ...
			});
		};

		this.hooks.beforeRun.callAsync(this, err => {
			if (err) return callback(err);
			this.hooks.run.callAsync(this, err => {
				if (err) return callback(err);

				this.readRecords(err => {
					if (err) return callback(err);

					this.compile(onCompiled);
				});
			});
		});
	}
	// 输出文件到构建目录
	emitAssets(compilation, callback) {
		// ...
		this.hooks.emit.callAsync(compilation, err => {
			if (err) return callback(err);
			outputPath = compilation.getPath(this.outputPath);
			this.outputFileSystem.mkdirp(outputPath, emitFiles);
		});
	}
	
	newCompilationParams() {
		const params = {
			normalModuleFactory: this.createNormalModuleFactory(),
			contextModuleFactory: this.createContextModuleFactory(),
			compilationDependencies: new Set()
		};
		return params;
	}

	compile(callback) {
		const params = this.newCompilationParams();
		this.hooks.beforeCompile.callAsync(params, err => {
			if (err) return callback(err);
			this.hooks.compile.call(params);
			const compilation = this.newCompilation(params);

			this.hooks.make.callAsync(compilation, err => {
				if (err) return callback(err);
				compilation.finish();
				// make 钩子执行后,调用seal生成资源
				compilation.seal(err => {
					if (err) return callback(err);
					this.hooks.afterCompile.callAsync(compilation, err => {
						if (err) return callback(err);
						// emit, 生成最终文件
						return callback(null, compilation);
					});
				});
			});
		});
	}
}

最后输出

seal执行后,便会调用emit钩子,根据webpack config文件的output配置的path属性,将文件输出到指定的path.******

最后

腾讯IVWEB团队的工程化解决方案feflow已经开源:Github主页:https://github.com/feflow/feflow

如果对您的团队或者项目有帮助,请给个Star支持一下哈~

node 的高并发多进程模型的性能体现在什么地方?

$
0
0

最近在学习 node 的多进程相关与 cluster 模块, 然后创建了一个单进程版的服务器与多进程版的服务器, 代码如下: 单进程版: https://github.com/zhangxiang958/ComputerNetworkLab/blob/master/Socket1_WebServer/source/server.js多进程版: https://github.com/zhangxiang958/ComputerNetworkLab/blob/master/Socket1_WebServer/source/muti_process/server.js单进程请直接使用 node 启动, 多进程版使用 cluster.js 来启动. 每个请求我会使用 sleep 函数阻塞 3 秒. 我的问题是: node 多进程下的服务器能否将响应时间缩短, 比如是 4 个请求同时处理, 一起返回, 而单进程只能一个一个请求地处理, 处理时间为多进程的 4 倍 ? 如果能, 如何解释使用 loadtest, 测试命令为 loadtest -c 10 -t 60 ${URL} 单进程与多进程的差别很小? 如果不能, cluster 到底真正用途是什么?

untitled1.png

左图为单进程, 右图为多进程 测试命令为: 单进程 loadtest -c 10 -t 60 http://localhost:8081多进程 loadtest -c 10 -t 60 http://localhost:8080/

恳求回答与斧正!

腾讯云360元 6年时长最新完整方案

$
0
0

腾讯云新用户认证学生身份可享受1核CPU、2G内存、1M带宽的云服务器10元/月的价格。

同时买1年(120元)送4个月,特价续费两年(120元*2),共支出360元。

得到3年的特价学生云服务器,然后在控制面板降配,会获得5年左右的时长。

具体步骤:

1、准备一个未购买过腾讯云的新账号,新注册QQ也可。

2、进入参团链接:https://cloud.tencent.com/act/campus/group/detail?group=20561

3、登陆腾讯云账号,进行实名认证(这个带参团性质,貌似需要参团才可以参与)

4、进行学生认证,(随便填就可以):https://cloud.tencent.com/act/campus/student_verify

5、配置机器参数,支付120元参团购买一年,建议新手使用WINDOWS2008的机器,区域选成都或广州

6、然后在:https://cloud.tencent.com/act/campus/特价续费两次(120元*2)

7、进入控制台,将机器配置由1核2G降低为1核G,将会增加2年左右的时长,共5年。

8、选择成都节点,可以自动增加3年时长,共6年。

bio: 一站式前端开发工具

$
0
0

本文发表在 微店前端团队 blog

bio 是什么

注意:bio 目前只兼容 Mac 平台

github 地址:bio-cli

npm 地址:bio-cli

前端开发一站式解决方案。

使用 bio,您将只需关注业务逻辑,无需关注脚手架配置信息,即可快速完成前端开发。

额外的,bio 提供了 eslint、styleint 检测、mock 服务。

bio 使用前后

安装

快速使用

  • 第 1 步:创建项目目录

    mkdir demo
    
    cd demo
    
  • 第 2 步:初始化各类项目

    • bio init bio-scaffold-vue: 初始化 vue 项目
    • bio init bio-scaffold-react:初始化 react 项目
    • bio init bio-scaffold-pure: 初始化 非 vue / 非 react 项目
  • 第 3 步:调试

    bio run dev-daily
    

命令集

  • bio init <脚手架在 npm 源上的名称>

    • 功能

      初始化项目目录。

      该命令会完成以下动作:

      • 在本地安装脚手架,以确保脚手架存在。脚手架安装在 bio 缓存目录(/Users/用户名/.bio/
      • 如果当前目录是空目录(或只有 README.md),该命令会为生成 demo 文件。
      • 执行 npm install
    • 脚手架

      bio 目前内置了三个脚手架(bio-scaffold-vuebio-scaffold-reactbio-scaffold-pure

      bio 使用 npm 托管脚手架,默认托管在 npm 官方源,您可自行设置托管源,代码地址

    • 脚手架昵称

      bio 为内置的三个脚手架都取了昵称:

      bio-scaffold-vue --> vue
      bio-scaffold-react --> react
      bio-scaffold-pure --> pure
      

      所以所有涉及脚手架名称的命令,均可以用昵称代替。

      您也可以自行添加昵称,代码地址

  • bio run <脚手架支持的任务> [-n, --no-watch]

    • 功能

      启动脚手架任务。

      bio 会启动脚手架,并透传任务名称到脚手架,以完成各类任务。

      所以,任务名称是可变的,只要脚手架支持就可以。

      我们默认提供的三个脚手架都提供了以下 6 种任务:

      dev-daily
      dev-pre
      dev-prod
      build-daily
      build-pre
      build-prod
      

      详细信息可查看:bio 内置脚手架任务名称

      举例:初始化完 bio-scaffold-vue项目后,启动它的 dev-daily任务,命令即为:

      bio run dev-daily
      
    • 选项 -n, --no-watch介绍:

      bio 默认会 启动一个文件监听服务,同步当前目录文件到脚手架目录,保证脚手架目录与业务目录始终是父子关系,供脚手架编译。(资料:(为什么要保证父子关系?))

      -n, --no-watch关闭同步当前目录到脚手架目录的文件监听服务。

      举例:

      bio run dev-daily -n
      
  • bio scaffold show <脚手架在 npm 源上的名称>

    打开脚手架所在的本地目录。

  • bio scaffold create

    创建脚手架,会提示你新的脚手架名称

  • bio mock [端口]

    启动本地 mock 服务,默认端口是 7000

    如果希望指定端口号,可以直接指定,如:bio mock 8000

  • bio lint init [-t, --type [value]]

    • 功能

      初始化 lint,会自动在 git commit 前挂载 lint 执行钩子

    • 选项 [-t, --type [value]]介绍

      默认初始化 es6 规则,如果希望在某个目录初始化 es5 功能,可以进入该目录,执行:

      bio lint init -t es5
      

      目前支持两种类型:es5、es6

  • bio lint [--fix] [-w, --watch]

    执行 lint 检查,bio 会为你生成 lint 结果页面进行查看

    • --fix:自动修正源码中的代码格式。
    • -w, --watch:启动文件监听,文件一旦有变化,会触发 lint 检查
  • bio help

    help 信息

bio 的特点

链接

TODO

  • 完善单元测试
  • 持续集成
  • English Docs
  • 完善脚手架项目 demo

开发者

LICENSE

MIT

Viewing all 14821 articles
Browse latest View live


Latest Images