├── .gitignore ├── README ├── http ├── NProxy-Mac-和-Linux下的Fiddler.md └── 如何判断页面的编码.md ├── javascript ├── JavaScript内部原理实践——真的懂JavaScript吗?.md ├── 动态修改script标签中的src属性存在的问题.md ├── 说说为什么 [] == ![] 为true.md └── 通过什么途径能够深入了解JavaScript解析引擎是如何工作的?.md └── nodejs ├── npm中本地安装命令行类型的模块是不注册Path的.md ├── 使用node-instector来调试node.md ├── 如何调试Node本身.md └── 安装node后node的js代码都跑哪里去了.md /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings/ 3 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ____ ____ U ___ u ____ _____ ____ 2 | U /"___|uU| _"\ u \/"_ \/ / __"| u |_ " _|/ __"| u 3 | \| | _ /\| |_) |/ | | | |<\___ \/ | | <\___ \/ 4 | | |_| | | __/.-,_| |_| | u___) | /| |\ u___) | 5 | \____| |_| \_)-\___/ |____/>> u |_|U |____/>> 6 | _)(|_ ||>>_ \\ )( (__)_// \\_ )( (__) 7 | (__)__) (__)__) (__) (__) (__) (__)__) 8 | 9 | -------------------------------------------------------------------------------- /http/NProxy-Mac-和-Linux下的Fiddler.md: -------------------------------------------------------------------------------- 1 | [Fiddler](http://www.fiddler2.com/fiddler2/) 相信大家,尤其是前端工程师们都知道。 2 | 用它的文件替换功能,将线上的静态资源文件(JS、CSS、图片)替换为本地相应的文件,来调试线上(代码都被压缩过)UI的问题。的确是一神器。(相比,它的HTTP请求的inspector功能因为各大主流浏览器都内置有这功能,反而现在用的不多)。 3 | 4 | 但是,Fiddler最大的问题就是只支持Windows,这对于Mac党和Linux党来说,有些遗憾。 5 | 6 | 以往,总是得开个虚拟机来用Fiddler。后来也有了跨平台的类似Fiddler的工具,如:[Charles](http://www.charlesproxy.com/)、[Rythem](http://www.alloyteam.com/2012/05/web-front-end-tool-rythem-1/)以及[Tinyproxy](https://banu.com/tinyproxy/)。 7 | 8 | 尽管这些各有优势,但是,都没有办法满足我的需求: 9 | 10 | * 支持Mac、Linux以及Windows 11 | * 支持HTTP和HTTPS(很重要) 12 | * 支持单文件替换 13 | * 支持combo文件替换(即多个文件合并为一个文件的替换) 14 | * 支持目录替换 15 | 16 | 下面这张图显示了,根据我的需求,罗列出的各工具的支持情况: 17 | 18 | +---+---------+---------+---------+-----------+ 19 | | R | Fiddler | Charles | Rythem | TinyProxy | 20 | |---------------------------------------------| 21 | | A | Win | Y | Mac&Win | Mac&Linux | 22 | |---------------------------------------------| 23 | | B | Y | Y | Y/N | Y | 24 | |---------------------------------------------| 25 | | C | Y | Y | Y | N | 26 | |---------------------------------------------| 27 | | D | N | N | Y/N | N | 28 | |---------------------------------------------| 29 | | E | Y/N | N | Y | N | 30 | +---------------------------------------------+ 31 | 32 | A: 支持Mac、Linux以及Windows 33 | B: 支持HTTP和HTTPS 34 | C: 支持单文件替换 35 | D: 支持combo文件替换(即多个文件合并为一个文件的替换) 36 | E: 支持目录替换 37 | Y: 支持 38 | N: 不支持 39 | Y/N: 不完全支持 40 | 41 | 这就是为什么会有[NProxy](http://goddyzhao.me),它满足所有上述我的需求。这里并不表示Nproxy就比其他这4个工具优秀,只是NProxy在文件替换上更胜一筹。 42 | 它不提供HTTP Inspector功能,只专注在文件替换功能上。 43 | 44 | 目前,NProxy发布了1.3.0, 据我所知,除了我自己所在的公司——[SuccessFactors(An SAP Company)](http://www.successfactors.com/homepage.html)在使用之外,部分[天猫](http://tmall.com)的前端也在使用。如果你也在用,麻烦请告诉[我](http://weibo.com/goddyzhao)。 45 | 46 | 因此,各位平时用Mac和Linux的朋友(当然windows也完全可以),可以使用NProxy!具体的安装和使用非常简单,可以参考[官网介绍](http://goddyzhao.me) -------------------------------------------------------------------------------- /http/如何判断页面的编码.md: -------------------------------------------------------------------------------- 1 | 在写爬虫程序(web页面)的时候,经常需要去解析页面的内容,而在解析前就必须要知道该页面是用何种字符集来编码的,这样才能有效的避免乱码的问题。 2 | 3 | 那么如何才能得知目的页面的编码呢?让我们来看看来自“[W3C](http://www.w3.org/TR/html4/charset.html)”的官方解释: 4 | 5 | 1. An HTTP "charset" parameter in a "Content-Type" field 6 | 2. A META declaration with "http-equiv" set to "Content-Type" and a value set for "charset" 7 | 3. The charset attribute set on an element that designates an external resource 8 | 9 | 上述描述中表现了检测页面编码的优先级,也就是说首先会看http头信息中的“Content-Type”字段、如果没有,就去看Meta信息,还没有的话,对于一些外链(如css、JavaScript)就会看这种元素专门的charset字段。如果检查完上述三种方式之后还是无法确定呢?那就采用默认的*ISO-8859-1*字符集来解析。 10 | 11 | 顺便提一句,既然是W3C的标准,那就说明标准浏览器都是这么工作的哦! 12 | 13 | -------------------------------------------------------------------------------- /javascript/JavaScript内部原理实践——真的懂JavaScript吗?.md: -------------------------------------------------------------------------------- 1 | 通过翻译了[Dmitry A.Soshnikov](http://dmitrysoshnikov.com/)的关于ECMAScript-262-3 [JavaScript内部原理](http://goddyzhao.tumblr.com/JavaScript-Internal)的文章, 2 | 从理论角度对JavaScript中部分特性的内部工作机制有了一定的了解。 3 | 但是,邓爷爷说过:**“实践才是检验真理的唯一标准”**。 4 | 所以,我打算通过**从内部原理来解释一些经常在笔试或者面试中遇到的关于JavaScript语言层面的题目**来进一步学习和掌握JavaScript内部工作原理。 5 | 6 | 那么,首先就是要去找那些题目,google了一圈终于找到了来自**Dmitry Baranovskiy**的非常著名的[5个问题](http://dmitry.baranovskiy.com/post/91403200), 7 | 这5个问题,NCZ给出了[非常清楚的解释](http://www.nczonline.net/blog/2010/01/26/answering-baranovskiys-javascript-quiz/)。 8 | 不过,我还是想尝试下从low-level——JavaScript内部工作机制的角度去解释下这些问题。 9 | 10 | 好吧,我承认我废话很多,那就开始吧。 11 | 12 | 问题 #1 13 | -------- 14 | * * * 15 |
if (!("a" in window)) {
16 | var a = 1;
17 | }
18 | alert(a);
19 |
20 | * **正确答案:** undefined
21 |
22 |
23 | **解释:**
24 | 这个问题,初一看感觉答案很自然是1。因为从上到下执行下去,_if_语句中的条件应该为 _true_,因为"a"的确是没有定义啊。
25 | 随后,顺理成章地进入 _var a = 1;_,最后,alert出来就应该是1。
26 |
27 | 而事实上,从JavaScript内部工作原理去看,在[变量对象](http://goddyzhao.tumblr.com/post/11141710441/variable-object)中讲过,
28 | JavaScript处理上下文分为两个阶段:
29 |
30 | * 进入执行上下文
31 | * 执行代码
32 |
33 | 可以理解为,第一个阶段是静态处理阶段,第二个阶段为动态处理阶段。
34 |
35 | 而在静态处理阶段,就会创建 _变量对象(variable object)_,并且将变量申明作为属性进行填充。
36 | 到了执行阶段,才会根据执行情况,来对变量对象中属性(就是申明的变量)的值进行更新。
37 |
38 | 针对这个问题,其实际过程如下:
39 |
40 | * **进入执行上下文**: 创建VO,并填充变量申明 _a_,VO如下所示:
41 | VO(global) = {
42 | a: undefined
43 | }
44 | 所以,这个时候,a其实已经存在了。
45 |
46 | * **执行代码**: 进入 _if_语句,发现条件判断 _"a" in window_ 为**true**。于是就不会进入if代码块,直接执行alert语句,因此,最终为**undefined**。
47 |
48 |
49 | 问题 #2
50 | --------
51 | * * *
52 | var a = 1,
53 | b = function a(x) {
54 | x && a(--x);
55 | };
56 | alert(a);
57 |
58 | * **正确答案:** 1
59 |
60 | **解释:**
61 | 这个问题,第一反应可能会是将 function a打印出来。因为明明就看到了function a了。看似,也顺其自然。
62 |
63 | 但是,事实并非如此。还是和此前一个问题一样。从两个阶段来分析:
64 |
65 | * **进入执行上下文**: 这个时候要注意了, _b = function a(){}_,这里的 _function a_并非函数申明,因为整个这个句话属于**赋值语句(assignment statement)**,所以,这里的 _function a_会被看作是函数表达式。
66 | 函数表达式是不会对VO造成影响的。所以,这个时候VO中其实只有 **a和x(函数形参)**:
67 | VO(global) = {
68 | a: undefined,
69 | b: undefined
70 | }
71 |
72 | * **执行代码**: 这个时候a的值修改为1:
73 | VO(global) = {
74 | x: undefined,
75 | a: 1
76 | }
77 |
78 | 所以,最后alert(a)的结果是1。
79 |
80 |
81 | 问题 #3
82 | --------
83 | * * *
84 | function a(x) {
85 | return x * 2;
86 | }
87 | var a;
88 | alert(a);
89 |
90 | * **正确答案:** 函数a
91 |
92 | **解释:**
93 | 这个问题,很多人可能会以为是: undefined。理由可能是,明明看到了 _var a_定义在了function a的后面,感觉应该会覆盖之前a的申明。
94 |
95 | 事实又是怎样的呢? 老套路,从两个阶段来分析:
96 |
97 | * **进入执行上下文**: 这里出现了名字一样的情况,一个是函数申明,一个是变量申明。那么,根据[变量对象](http://goddyzhao.tumblr.com/post/11141710441/variable-object)
98 | 介绍的,填充VO的顺序是: 函数的形参 -> 函数申明 -> 变量申明。
99 | 上述例子中,变量a在函数a后面,那么,变量a遇到函数a怎么办呢?还是根据 _变量对象_中介绍的,当变量申明遇到VO中已经有同名的时候,不会影响已经存在的属性。因此,VO如下所示:
100 | VO(global) = {
101 | a: 引用了函数申明“x”
102 | }
103 |
104 | * **执行代码**:啥也没变化
105 |
106 | 所以,最终的结果是:函数a。
107 |
108 |
109 | 问题 #4
110 | --------
111 | * * *
112 | function b(x, y, a) {
113 | arguments[2] = 10;
114 | alert(a);
115 | }
116 | b(1, 2, 3);
117 |
118 | * **正确答案:** 10
119 |
120 | **解释:**
121 | 个人感觉这个问题其实不是很复杂。这里也不需要从两个阶段去分析了。根据 _变量对象_中介绍的,**arguments对象的properties-indexes和实际传递的参数是共享的**
122 | 也就是说,通过arguments\[2\]修改的参数,也会影响到a,所以,这里的值是10。但是,要注意的是和**实际传递的值**,所以,如果把上述问题改成如下形式:
123 | function b(x, y, a) {
124 | arguments[2] = 10;
125 | alert(a);
126 | }
127 | b(1, 2);
128 |
129 | 结果就会是: **undefined**。因为,并没有传递a的值。
130 |
131 |
132 | 问题 #5
133 | --------
134 | * * *
135 | function a() {
136 | alert(this);
137 | }
138 | a.call(null);
139 |
140 | * **正确答案:** 全局对象(window)
141 |
142 | **解释:**
143 | 这个问题,可能会比较困惑。因为懂call的童鞋都会觉得,call的时候把null传递为了当前的上下文了。里面的this应该是null才对啊。
144 |
145 | 事实却是: 前面都没错,this会是null。但是,[this](http://goddyzhao.tumblr.com/post/11218727474/this)中介绍过,null是没有任何意义的,因此,最终会变成全局对象。
146 | 所以,这里结果就变成了全局对象。 这里还有ECMAScript-262-3标准文档中的一句话作为证据:
147 | _“If thisArg is null or undefined, the called function is passed the global object as the this value. Otherwise, the called function is passed ToObject(thisArg) as the this value.”_
148 |
149 |
150 |
151 | 总结
152 | --------
153 | * * *
154 | 上面这5个问题其实也只是牵涉到了JavaScript内部原理中的部分知识点,要想了解更多,还是建议读完[JavaScript内部原理系列](http://goddyzhao.tumblr.com/JavaScript-Internal)
155 | 以及去看[Dmitry A.Soshnikov](http://dmitrysoshnikov.com/)的文章。
156 |
--------------------------------------------------------------------------------
/javascript/动态修改script标签中的src属性存在的问题.md:
--------------------------------------------------------------------------------
1 | 今天某个同事遇到一个诡异的问题,问题描述如下:
2 |
3 | **需求**:通过脚本动态修改script标签的src来载入一段外部脚本并执行
4 | **实现方式(#1)**:
5 |
6 |
8 |
11 |
12 | **url2的内容如下**:
13 |
14 | alert('I am dynamic');
15 |
16 | **结果**:
17 |
18 | - Chrome: 什么事都没发生(没有请求url2)
19 | - Firefox: 什么事都没发生(没有请求url2)
20 | - IE9:什么事都没发生(请求url2但不执行url2的脚本)
21 | - IE(6,7,8): I am dynamic(请求并执行了url2的脚本)
22 |
23 | 注意实现方式中,第一段的script标签中间是有内容的(空格、换行符以及回车符)。
24 |
25 | 如何来解释这个问题呢?要解释这个问题,我们来看两个变种的例子,第一个例子(明确内联内容),如下所示(#2):
26 |
27 |
30 |
33 |
34 | 结果如下:
35 |
36 | - Chrome: I am inline(没有请求url2)
37 | - Firefox: I am inline(没有请求url2)
38 | - IE9:I am inline(请求url2但不执行url2的脚本)
39 | - IE(6,7,8): I am inline I am dynamic(请求并执行了url2的脚本)
40 |
41 | 再来看看第二个变种的例子(#3):
42 |
43 |
46 |
49 |
50 | 其中url1的内容如下:
51 |
52 | alert('I am url1');
53 |
54 | 结果如下:
55 |
56 | - Chrome: I am url1(没有请求url2)
57 | - Firefox: I am url1(没有请求url2)
58 | - IE9:I am url1(请求url2但不执行url2的脚本)
59 | - IE(6,7,8): I am url1 I am dynamic(请求并执行了url2的脚本)
60 |
61 | 首先这里肯定的是src属性是修改成功的,可以通过看dom的变化看到src已经设置进去了。这个时候我们比对这三个例子,思考几十秒。分析下这三个例子,其实#2和#1是一样的,这里用#2是为了说明#1中的空格、换行符以及回车符会被浏览器认为是内联的脚本。通过比对#2和#3,是不是会让你想到什么?没错,我们第一个会想到的就是:_当script标签既有src属性又有内联脚本的时候浏览器该如何处理?_ , 先来解释这个问题。
62 |
63 | 一谈到浏览器应该怎样处理,就不得不翻出各种宝典,这次不再是葵花宝典了,而是九阴真经([W3C的HTML4标准](http://www.w3.org/TR/1999/REC-html401-19991224/interact/scripts.html#h-18.2.4)),标准中关于script标签的src部分有如下一段话:
64 | > If the src attribute is not set, user agents must interpret the contents of the element as the script. If the src has a URI value, user agents must ignore the element's contents and retrieve the script via the URI
65 |
66 | 上面这段话的意思就是说:_如果src没有设置,那么就执行内联脚本,如果src设置了浏览器就必须忽略内敛脚本而要去请求src指定的url的内容_
67 |
68 | 这解释了为什么#3中标准浏览器(甚至IE6,7,8)都没有执行内联脚本(因为src设置了url1)。
69 |
70 | 搞清楚了这个基础问题之后,接下来问题就定位到了_动态修改script的src属性的时候浏览器如何处理?_ ,从结果来看,标准的浏览器都没有去请求url2(更改src无效),这回IE6,7,8终于犯傻了。当然了,咱们也不能随随便便说人家犯傻,要拿出证据,这个时候继续拿出九阴真经[W3C的HTML5标准](http://www.w3.org/TR/2012/WD-html5-20120329/the-script-element.html#the-script-element),其中有这样一句话:
71 | > Changing the src, type, charset, async, and defer attributes dynamically has no direct effect; these attribute are only used at specific times described below.
72 |
73 | 意思就是说:_修改src是没用的,对src的处理只会在特定的时候进行(个人猜测就是第一次看到这个属性的时候浏览器会去做相应处理,之后就无视它了)。_
74 |
75 | 好了,这下真相大白了:这解释了为啥#3和#1中除了IE6,7,8之外其他浏览器都没有去请求url2(IE9请求了,但没执行),而且实验发现IE6,7,8对动态修改src都会做请求执行处理。
76 |
77 | 最后,这个故事至少告诉我们:写script标签的时候千万别手贱打回车。
78 |
79 |
80 |
81 | 参考文档:
82 |
83 | - [HTML5标准文档](http://www.w3.org/TR/2012/WD-html5-20120329/the-script-element.html#the-script-element)
84 | - [HTML4标准文档](http://www.w3.org/TR/1999/REC-html401-19991224/interact/scripts.html#h-18.2.4)
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/javascript/说说为什么 [] == ![] 为true.md:
--------------------------------------------------------------------------------
1 | 此前在微博上无意中看到有人问**“为什么alert([] == ![])会是true?”**,
2 | 刚看到这个问题我也说不上来究竟是什么原因,只知道这个肯定又是和**==**操作相关的类型转换问题。
3 | 于是,就翻开了[**“葵花宝典(ECMA-262-5th)”**](http://www.ecma-international.org/publications/standards/Ecma-262.htm),你懂的。
4 |
5 | 在宝典的帮助下,我尝试着来解释下该问题的原因:
6 |
7 | * 首先看看**==**这个操作内部是如何工作的
8 |
9 | 宝典中的关于**==**操作的工作描述如下(11.9.1):
10 | > The production EqualityExpression: _EqualityExpression_ == _RelationalExpression_ is evaluated as follows:
11 | > 1. Let _lref_ be the result of evaluating _EqualityExpression_
12 | > 2. Let _lval_ be GetValue(_lref_)
13 | > 3. Let _rref_ be the result of evaluating _RelationalExpression_
14 | > 4. Let _rval_ be GetValue(_rref_)
15 | > 5. Return the result of performing abstract equality comparison _rval==lval_
16 |
17 | * 根据上面的步骤,我们来对问题作如下解析:
18 |
19 | 1. 先求GetValue([])
20 | 2. 再求GetValue(![])
21 | 3. 最后求 GetValue([]) == GetValue(![])
22 |
23 | 先要搞清楚GetValue方法是干嘛的,继续看宝典关于GetValue的描述(8.7.1):
24 | > 1. If Type(V) is not Reference, return V
25 | > 2. ....
26 |
27 | 对于解释我们的问题,看到这里就足够了,因为[]和![]都不属于Reference,所以,GetValue([])和GetValue(![])都返回自身。
28 | 这里关于什么是Reference不想再赘述了,要详细了解的可以看宝典(8.7)。
29 | 那么,上述问题进一步转化成了如下问题:
30 |
31 | 1. GetValue([])为 []
32 | 2. GetValue(![])为 false, (这里!会使得[]强制转化为Boolean类型)
33 | 3. 这里就成了求 **[] == false**的问题
34 |
35 | 也就是说: **[] == ![]** 现在转化为了 **[] == false**。
36 |
37 | * 根据"abstract equality comparison"算法来求结果:
38 |
39 | 宝典中第五步就提到了根据"abstract equality comparison"来求最后的结果。
40 | 现在先来看看[]和false的类型,两者类型显而易见,前者是Object,后者Boolean。
41 | 然后,我们进一步来看看这个算法是如何的(11.9.3),以下只列出了和我们这个问题相关的算法步骤,其中有这么一条:
42 | > The comparison x == y, where x and y are values, produces **true** or **false**. Such a comparison is performed as follows:
43 | > 7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y)
44 |
45 | 这句话很容易理解,就是要把y转化类型为数值,也就是说false变为0。
46 | 这样以来,问题有变成了求: **[] == 0**
47 |
48 | 继续看宝典中这个算法(11.9.3),其中有这么一条:
49 | > The comparison x == y, where x and y are values, produces **true** or **false**. Such a comparison is performed as follows:
50 | > 9. If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
51 |
52 | 于是,问题有变成了求: **ToPrimitive([]) == 0**。
53 |
54 | * 查看ToPrimitive的工作机制(9.1)
55 |
56 | 其中对于Object有这种转换描述:
57 | > Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object,
58 | > passing the optional hint PreferredType. The behaviour of the
59 | > [[DefaultValue]] internal method is defined by this specification for all native
60 | > ECMAScript objects in 8.12.8.
61 |
62 | 继续顺藤摸瓜,看\[\[DefaultValue\]\](hint),我们的例子中hint是Number,因为它是和0去做比较。
63 | 根据宝典(8.12.8)描述:
64 | > When the [[DefaultValue]] internal method of O is called with hint Number, the following steps are taken:
65 | > 1. Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".
66 | > 2. If IsCallable(valueOf) is true then
67 | > a. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
68 | > b. If val is a primitive value, return val.
69 | > 3. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".
70 | > a. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
71 | > b. If str is a primitive value, return str.
72 | > ...
73 |
74 | 这里不对valueOf再去做赘述了,MDN上面有很[简短的说明](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/ValueOf),大致意思如下:
75 | > 默认,每个对象都有从Object继承下来的valueOf方法。其中每个内置的核心对象都会重载该方法来返回正确的值,对于没有基础类型值的对象,则返回对象自身。
76 |
77 | 那么,对于我们的情况来说,进入了算法中的2,但是,因为val是对象不是基础类型,所以继续进入第3步,这个时候关键来了:
78 | **开始调用[]的toString方法,这个时候会返回\"\",一个空的字符串,因此ToPrimitive([])为\"\"**
79 | 因此,问题又转化成了:
80 | **\"\" == 0**
81 |
82 | 现在答案就很明显了,根据宝典的==工作原理如下描述(11.9.3):
83 | > The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
84 | > 5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
85 |
86 | 好了,根据算法,会将\"\"转化为数值类型,那么自然就变成了0,于是 0 == 0 是很自然而然的。
87 |
88 | **总结:**
89 | 最终问题就从: **[] == ![]** 变成了 **0 == 0**。答案自然是**true**了。
90 | 其实遇到这种语言层面的问题,直接看宝典即可。
91 |
92 | **说明:**
93 | 以上诸如 11.9.3 这样的数字均表示葵花宝典中的章节。
94 |
95 | 参考资料
96 | --------
97 | * * *
98 |
99 | * [葵花宝典(ECMA-262-5th)](http://www.ecma-international.org/publications/standards/Ecma-262.htm)
100 | * [MDN](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/ValueOf)
101 |
--------------------------------------------------------------------------------
/javascript/通过什么途径能够深入了解JavaScript解析引擎是如何工作的?.md:
--------------------------------------------------------------------------------
1 | 昨天收到一封来自深圳的一位前端童鞋的邮件,邮件内容如下(很抱歉,未经过他的允许,公开邮件内容,不过我相信其他人肯定也有同样的问题,所以,直接把问题原文抛出来):
2 |
3 | “读了你的几篇关于JS(变量对象、作用域、上下文、执行代码)的文章,我个人觉得有点抽象,难以深刻理解。我想请教下通过什么途径能够深入点的了解javascript解析引擎在执行代码前后是怎么工作的,ecma英文版实在看不下去呵呵。”
4 |
5 | 其实这个问题个人觉得太笼统了,直接回答很难回答,所以,我打算先把他的问题拆解成如下几个子问题,并对其表达个人的观点,希望对有同样困惑的童鞋能够有所帮助。
6 |
7 | ## 1. 什么是JavaScript解析引擎?
8 |
9 | 简单地说,JavaScript解析引擎就是能够“读懂”JavaScript代码,并准确地给出代码运行结果的一段程序。比方说,当你写了 *var a = 1 + 1;* 这样一段代码,JavaScript引擎做的事情就是看懂(解析)你这段代码,并且将a的值变为2。
10 |
11 | 学过编译原理的人都知道,对于静态语言来说(如Java、C++、C),处理上述这些事情的叫**编译器(Compiler)**,相应地对于JavaScript这样的动态语言则叫**解释器(Interpreter)**。这两者的区别用一句话来概括就是:**编译器是将源代码编译为另外一种代码(比如机器码,或者字节码),而解释器是直接解析并将代码运行结果输出**。 比方说,firebug的console就是一个JavaScript的解释器。
12 |
13 | 但是,现在很难去界定说,JavaScript引擎它到底算是个解释器还是个编译器,因为,比如像V8(Chrome的JS引擎),它其实为了提高JS的运行性能,在运行之前会先将JS编译为本地的机器码(native machine code),然后再去执行机器码(这样速度就快很多),相信大家对**JIT(Just In Time Compilation)**一定不陌生吧。
14 |
15 | 我个人认为,不需要过分去强调JavaScript解析引擎到底是什么,了解它究竟做了什么事情我个人认为就可以了。对于编译器或者解释器究竟是如何看懂代码的,翻出大学编译课的教材就可以了。
16 |
17 | 这里还要强调的就是,JavaScript引擎本身也是程序,代码编写而成。比如V8就是用C/C++写的。
18 |
19 | ## 2. JavaScript解析引擎与ECMAScript是什么关系?
20 |
21 | JavaScript引擎是一段程序,我们写的JavaScript代码也是程序,如何让程序去读懂程序呢?这就需要定义规则。比如,之前提到的*var a = 1 + 1;*,它表示:
22 |
23 | - 左边var代表了这是申明(declaration),它申明了a这个变量
24 | - 右边的+表示要将1和1做加法
25 | - 中间的等号表示了这是个赋值语句
26 | - 最后的分号表示这句语句结束了
27 |
28 | 上述这些就是规则,有了它就等于有了衡量的标准,JavaScript引擎就可以根据这个标准去解析JavaScript代码了。那么这里的ECMAScript就是定义了这些规则。其中ECMAScript 262这份文档,就是对JavaScript这门语言定义了一整套完整的标准。其中包括:
29 |
30 | - var,if,else,break,continue等是JavaScript的关键词
31 | - abstract,int,long等是JavaScript保留词
32 | - 怎么样算是数字、怎么样算是字符串等等
33 | - 定义了操作符(+,-,>,<等)
34 | - 定义了JavaScript的语法
35 | - 定义了对表达式,语句等标准的处理算法,比如遇到**==**该如何处理
36 | - ⋯⋯
37 |
38 | **标准**的JavaScript引擎就会根据这套文档去实现,注意这里强调了**标准**,因为也有不按照标准来实现的,比如IE的JS引擎。这也是为什么JavaScript会有兼容性的问题。至于为什么IE的JS引擎不按照标准来实现,就要说到浏览器大战了,这里就不赘述了,自行Google之。
39 |
40 | 所以,简单的说,ECMAScript定义了语言的标准,JavaScript引擎根据它来实现,这就是两者的关系。
41 |
42 | ## 3. JavaScript解析引擎与浏览器又是什么关系?
43 |
44 | 简单地说,JavaScript引擎是浏览器的组成部分之一。因为浏览器还要做很多别的事情,比如解析页面、渲染页面、Cookie管理、历史记录等等。那么,既然是组成部分,因此一般情况下JavaScript引擎都是浏览器开发商自行开发的。比如:IE9的Chakra、Firefox的TraceMonkey、Chrome的V8等等。
45 |
46 | 从而也看出,不同浏览器都采用了不同的JavaScript引擎。因此,**我们只能说要深入了解哪个JavaScript引擎**。
47 |
48 | ## 4. 深入了解其内部原理的途径有哪些?
49 |
50 | 搞清楚了前面三个问题,那这个问题就好回答了。个人认为,主要途径有如下几种(依次由浅入深):
51 |
52 | - 看讲JavaScript引擎工作原理的书
53 | 这种方式最方便,不过我个人了解到的这样的书几乎没有,但是[Dmitry A.Soshnikov](http://dmitrysoshnikov.com/)博客上的文章真的是非常的赞,建议直接看英文,实在英文看起来吃力的,可以看我翻译的[译本](http://blog.goddyzhao.me/JavaScript-Internal)
54 | - 看ECMAScript的标准文档
55 | 这种方式相对直接,原汁原味,因为引擎就是根据标准来实现的。目前来说,可以看[第五版](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf)和[第三版](http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf),不过要看懂也是不容易的。
56 | - 看JS引擎源代码
57 | 这种方式最直接,当然也最难了。因为还牵涉到了如何实现词法分析器,语法分析器等等更加底层的东西了,而且并非所有的引擎代码都是开源的。
58 |
59 | ## 5. 以上几种方式中第一种都很难看明白怎么办?
60 |
61 | 其实第一种方式中的文章,作者已经将文档中内容提炼出来,用通俗易懂的方式阐述出来了。如果,看起来还觉得吃力,那说明还缺少两块的东西:
62 |
63 | - 对JavaScript本身还理解的不够深入
64 | 如果你刚刚接触JavaScript,或者说以前甚至都没有接触过。那一下子就想要去理解内部工作原理,的确是很吃力的。首先应该多看看书,多实践实践,从知识和实践的方式来了解JavaScript预言特性。这种情况下,你只需要了解现象。比方说,*(function(){})()* 这样可以直接调用该匿名函数、用闭包可以解决循环中的延迟操作的变量值获取问题等等。要了解这些,都是需要多汲取和实践的。实践这里就不多说了,而知识汲取方面可以多看看书和博客。这个层面的书就相对比较多了,[《Professional JavaScript for Web Developers》](http://www.amazon.com/Professional-JavaScript-Developers-Nicholas-Zakas/dp/1118026691/)就是本很好的书(中文版请自行寻找)。
65 | - 缺乏相应的领域知识
66 | 当JavaScript也达到一定深度了,但是,还是看不大明白,或者没法很深入到内部去一探究竟。那就意味着缺少对应的领域知识。这里明显的就是编译原理相关的知识。不过,其实对这块了解个大概基本看起来就没问题了。要再继续深入,那需要对编译原理了解的很深入,比如说词法分析采用什么算法,一般怎么处理。会有什么问题,如何解决,AST生成算法一般有哪几种等等。那要看编译原理方面的书,也有基本经典的书,比如[《Compilers: Principles, Techniques, and Tools》](http://www.amazon.com/Compilers-Principles-Techniques-Tools-2nd/dp/0321486811/)这本也是传说中的龙书,还有非常著名的[《SICP》](http://mitpress.mit.edu/sicp/full-text/book/book.html)和[《PLAI》](http://www.cs.brown.edu/~sk/Publications/Books/ProgLangs/)。不过其实根据个人经验,对于Dmitry的文章,要看懂它,只要你对JavaScript有一定深度的了解,同时你大学计算机的课程都能大致掌握了(尤其是操作系统),也就是说基础不错,理解起来应该没问题。因为这些文章基本没有涉及底层编译相关的,只是在解释文档的内容,并且其中很多东西都是相通的,比如:context的切换与CPU的进程切换、函数相关的的局部变量的栈存储、函数退出的操作等等都是一致的。
67 |
68 | 以上就是个人对这个问题的看法,除此之外,我觉得,学习任何技术都不能操之过急,要把基础打扎实了,这样学什么都会很快。
69 |
--------------------------------------------------------------------------------
/nodejs/npm中本地安装命令行类型的模块是不注册Path的.md:
--------------------------------------------------------------------------------
1 | 首先有必要解释下什么是**命令行(Command Line)类型的模块**。
2 | npm的模块一共分为三类:
3 | 1. **绑定型(Binding)**:本地模块,C++书写,如[node-png](https://github.com/pkrumins/node-png)
4 | 2. **库型(Library)**:JS书写,直接使用require('module')这种方式,如[Socket.IO-node](https://github.com/learnboost/Socket.IO-node)
5 | 3. **命令行型(Command Line)**: 以命令行形式使用,如[json-command](https://github.com/zpoley/json-command)
6 |
7 | 显而易见,提供命令行形式调用方式的模块就属于命令行型的。那么如何来申明自己的模块以什么命令调用,调用执行代码又如何制定呢?
8 |
9 | 这里就要说到npm模块的核心文件:**package.json**,此文件是npm模块的配置信息(npm help json可以获取详细信息)。
10 |
11 | 其中有一个key就叫**“bin”**,具体形式为**“{ "bin": {"command-name" : "command-file"}}”**,意思是申明用户可以直接使用**“command-name”**这个命令,而输入该命令后就会去执行**“command-file”**这个文件。
12 | 比如json-command这个模块,安装好后就可以看到它的package.json文件中bin配置项为**“{"bin" : {"json" : "./bin/json.js"}}”**。这就申明了命令行名字为**“json”**,对应地会去执行**“bin目录下的json.js”**这个文件。
13 |
14 | 其次还要解释下npm中安装模块的两种模式:
15 | 1. **全局模式**: npm -g install module-name,这种模式模块会被安装在node安装记录的lib所在目录的node_modules文件夹中,全局使用
16 | 2. **本地模式**: npm install module-name,这种模式模块只会被安装在当前目录的node_modules文件夹中,非全局使用
17 |
18 | 理解了什么叫**“命令行型的模块”**和**“npm模块的安装模式”**之后,再来看看什么叫**“npm中本地安装命令行类型的模块是不注册Path的”**?
19 | 这句话的意思是说:比如像json-command这样的命令行类型的模块,如果是“全局安装”,安装成功之后,就可以直接使用**“json”**的命令了,而如果是本地模式安装,则没法使用**“json”**命令。只能手动找到执行文件,然后调用./command-file这样调用。很是不爽!
20 |
21 | 后来仔细想了下,npm这样设计也是有道理的,因为命令行的模块,通过命令行使用,一般也都会认为是全局的,就像压缩文件,合并文件之类的命令,一般是不会随着应用的发布而发布的,都只是开发过程中的中间工具。所以,npm对这种类型的模块只有当全局安装的时候才会可以直接使用命令。
22 |
23 | 那么,**“npm到底是如何实现全局直接使用,而本地就不能使用的呢?”**
24 |
25 | 通过查看npm的源码,稍作调试就能发现其中的奥妙了:
26 |
27 | * 通过npm的package.json配置文件就能知道npm的命令行入口是**“bin/npm.js”**
28 | * npm.js有会去调用**“模块根目录的npm.js”**,并会调用相关的命令,依据来自如下代码:
29 | npm = require("../npm")
30 | npm.load(conf, function (er) {
31 | if (er) return errorHandler(er)
32 | npm.commands[npm.command](npm.argv, errorHandler)
33 | })
34 |
35 | * **“npm.commands...(npm.argv,errorHandler)”**是一行典型的command模式代码,npm.js中的commands对象肯定维护了所有command的列表。依据来自如下代码:
36 | var cmd = require(__dirname+"/lib/"+a+".js")
37 | * 那么install命令就会去调用**“lib/install.js”**的install方法,install方法会去做一些**模块查询**,**模块依赖关系处理**,**模块下载**,**模块安装**等工作,之后会去区分本地模式和全局模式,依据来自如下代码:
38 | var fn = npm.config.get("global") ? installMany : installManyTop
39 | * 之后会去调用lib/build.js中的build函数,依据来自于如下代码:
40 | npm.commands.build([where], false, true, function (er) {
41 | return cb_(er, d)
42 | })
43 | * 然后,再看看build函数的申明中,其**第二个参数**正是表示模块是否全局安装,依据来自如下代码:
44 | function build (args, global, didPre, didRB, cb) {
45 | * 最后,global参数被赋予给gtop变量,最后判断是否全局,如果是则注册Path否则直接调用回调函数返回,依据来自如下代码:
46 | if (er || !gtop) return cb(er)
47 | var dest = path.resolve(binRoot, b)
48 | , src = path.resolve(folder, pkg.bin[b])
49 | , out = npm.config.get("parseable")
50 | ? dest + "::" + src + ":BINFILE"
51 | : dest + " -> " + src
52 | 这里很清楚,如果**“!gtop”**,即本地模式就直接调用cb并返回,否则,就去注册Path,其中**“dest->src”**会发现很眼熟,通常全局安装好一个命令行模块之后,都会显示这行log。这行代码其实就做了如下这件事情:
53 | **将命令执行文件json.js link到node/bin/json命令中**
54 | 其中node的bin路径命令已经被加入到PATH中了,这样输入json的时候,系统自动去PATH中查找对应的执行文件,这样就能够找到了!
55 |
56 | ---
57 | **参考文献**
58 | * [http://howtonode.org/how-to-module](http://howtonode.org/how-to-module)
59 | * [https://github.com/isaacs/npm](https://github.com/isaacs/npm)
60 |
--------------------------------------------------------------------------------
/nodejs/使用node-instector来调试node.md:
--------------------------------------------------------------------------------
1 | 大部分程序员(比如我),开发的过程中,其实只有20%的时间在写代码,另外80%的时间都在调试代码。完全符合著名的[80/20法则](http://en.wikipedia.org/wiki/80/20)。
2 | 好吧,我承认我在滥用伟大的法则,废话很多。其实,我就想说明**我们大部分时间都在调试(Debug)而不是在写代码**。
3 |
4 | 所以,要想尽一切办法提高调试的效率,这样有助于提高开发效率。
5 | 对于node的调试,官方wiki中有[在eclipse中调试node](https://github.com/joyent/node/wiki/Using-Eclipse-as-Node-Applications-Debugger)的文章。
6 |
7 | 但是,不知为何,我在eclipse中总是不成功(eclipse 3.7 + ubuntu 11.04),况且,要是不用eclipse那咋办呢?
8 | 于是,我就另谋出路,在github上一番寻觅之后,发现了:[node-inspector](https://github.com/dannycoates/node-inspector)。
9 | 用了一把之后,感觉神清气爽,遂推荐给大家。
10 |
11 | 那么如何使用呢?其实官方有很详细的教程,不过这里我还是要赘述下,并且以joyent官方的例子来作为调试代码:
12 | // dbgtest.js
13 |
14 | var sys=require('sys');
15 | var count = 0;
16 |
17 | sys.debug("Starting ...");
18 |
19 | function timer_tick() {
20 | count = count+1;
21 | sys.debug("Tick count: " + count);
22 | if (count === 10) {
23 | count += 1000;
24 | sys.debug("Set break here");
25 | }
26 | setTimeout(timer_tick, 1000);
27 | }
28 |
29 | timer_tick();
30 |
31 | 具体调试步骤如下:
32 |
33 | * **以debug模式来运行上述代码**: node默认运行方式是直接 node filename。node还提供了两种debug模式运行:
34 | 1. node --debug\[=port\] filename (这种方式,其实就是在指定的端口(默认为 5858)监听远程调试连接)
35 | 2. node --debug-brk\[=port\] filename (这种方式在监听的同时,会在代码执行的时候,强制断点在第一行,这样有个好处就是:**可以debug到node内部的是如何运行的**)
36 | 这里采用第二种方式运行: _node --debug-brk dbgtest.js_。
37 | 这个时候终端就会出现这样一个log:
38 | debugger listening on port 5858
说明已经开始监听了。
39 |
40 | * **启动node-inspector**: 首 info - socket.io started
41 | visit http://0.0.0.0:8080/debug?port=5858 to start debugging先,我们要**全局**安装node-inspector模块,如果你真不知道如何安装模块,或者不知到为啥要全局安装,可以参看[这篇文章](http://goddyzhao.tumblr.com/post/9835631010/no-direct-command-for-local-installed-command-line-modul)),
42 | 然后,直接输入命令: _node-inspector_ 就运行起来了。起来后,终端可以看到如下log:
43 | info - socket.io started
44 | visit http://0.0.0.0:8080/debug?port=5858 to start debugging
45 |
46 | 通过看log也大致能够猜到了,node-inspector就是利用socket.io来监听5858端口实现的。
47 |
48 | * **使用浏览器调试工具来调试node(如: chrome developer tool)**: 在chrome中输入: _http://localhost:8080/debug?port=5858_,就会出现如下页面:
49 | 
50 |
51 | 这里很明显看到,直接代码就停在第一行,那么,接下来如何断点来debug相信身为前端的童鞋就不用我再赘述了吧。
--------------------------------------------------------------------------------
/nodejs/如何调试Node本身.md:
--------------------------------------------------------------------------------
1 | 虽然对C和C++都不怎么熟悉,但是总忍不住想“扒掉Node的衣服,一探究竟”。今天算是有了初步的成功。下面就分享给大家如何来Debug Node本身,注意是Node本身哦,不是Node应用。(对GDB很熟悉的人来说肯定是小菜一碟)
2 |
3 | 步骤如下:
4 |
5 | 1. 从[Node官网](http://nodejs.org/download/),下载源代码
6 | 2. 解压代码后,到根目录下运行
7 |
8 | ./configure --prefix 安装路径 --gdb (这里要加上--gdb标志)
9 |
10 | 3. make
11 | 4. 编译好以后,要到out目录下,运行
12 |
13 | gdb ./Release/node (这里很重要,不能跑到Release目录中,gdb ./node,这样会找不到源代码)
14 |
15 | 5. 如果,你要运行一个外部node文件的话,别忘记通过gdb传递参数给node
16 |
17 | (gdb) set args path/of/node/file.js
18 |
19 | 就这么简单!
--------------------------------------------------------------------------------
/nodejs/安装node后node的js代码都跑哪里去了.md:
--------------------------------------------------------------------------------
1 | [Node](http://nodejs.org)的[源代码](https://github.com/joyent/node)有两部分组成:
2 | 1. C/C++代码(包括Google的V8引擎代码): 位于src目录下
3 | 2. JavaScript代码:node.js位于src目录下,其余全部位于lib目录下,**而且数量很多**
4 |
5 | 但是,奇怪的是,按照node wiki上的[安装文档](https://github.com/joyent/node/wiki/Installation)安装好Node之后,却发现在安装目录中并没有任何JavaScript文件,于是我就很想知道为什么(我犯贱)
6 |
7 | 因为node运行的时候这些js代码都是需要的,而且基本是node的核心代码,
8 | 所以**大致猜测**:JS文件在make的过程中可能被翻译然后合并到C代码中一同编译成了二进制代码
9 | 通过粗略地看了下node的几个和安装相关的脚本文件后,大概知道了真正的原因,如下:
10 |
11 | **首先,回忆下node的安装过程**:
12 | 1. ./configure --prefex=node_install_path: 配置node安装,指定安装目录
13 | 2. make:编译代码
14 | 3. make install: 安装
15 |
16 | 那么,按照这个顺序首先去看下**configure**文件,发现其代码非常少:
17 | if [ ! -z "`echo $CC | grep ccache`" ]; then
18 | echo "Error: V8 doesn't like ccache. Please set your CC env var to 'gcc'"
19 | echo " (ba)sh: export CC=gcc"
20 | exit 1
21 | fi
22 |
23 | CUR_DIR=$PWD
24 |
25 | #possible relative path
26 | WORKINGDIR=`dirname $0`
27 | cd "$WORKINGDIR"
28 | #abs path
29 | WORKINGDIR=`pwd`
30 | cd "$CUR_DIR"
31 |
32 | "${WORKINGDIR}/tools/waf-light" --jobs=1 configure $*
33 |
34 | exit $?
35 |
36 | 没啥发现,继续去看make执行的**makefile**,makefile里面一堆代码,没怎么看懂,感觉没啥发现
37 |
38 | 这个时候,心想这个思路不好,不一定能够找到原因,于是决定换个思路:
39 | 既然JavaScript文件都是在lib目录下,那直接搜索lib看看哪些文件用到了,这个方法果然奏效,搜索发现根目录下的**node.gyp**文件非常可疑,
40 | 其文件内容就感觉是个build文件,其中有如下这段代码:
41 | 'library_files': [
42 | 'src/node.js',
43 | 'lib/_debugger.js',
44 | 'lib/_linklist.js',
45 | 'lib/assert.js',
46 | 'lib/buffer.js',
47 | ...
48 |
49 | 这里就是把所有JavaScript文件都罗列出来了,这时已经看到光明了,在此文件中找**library_files**这个变量,又发现如下代码:
50 | 'actions': [
51 | {
52 | 'action_name': 'node_js2c',
53 |
54 | 'inputs': [
55 | './tools/js2c.py',
56 | '<@(library_files)',
57 | ],
58 |
59 | 'outputs': [
60 | '<(SHARED_INTERMEDIATE_DIR)/node_natives.h',
61 | ],
62 | ...
63 |
64 | 猜测就是一个build任务,其输入源就是这些JS文件,输出是一个C的头文件,这里有个文件极其可疑**./tools/js2c.py**,继续追看该文件
65 | 终于找到真凶了:
66 | # This is a utility for converting JavaScript source code into C-style
67 | # char arrays. It is used for embedded JavaScript code in the V8
68 | # library.
69 |
70 | 这三行注释就大致等于告诉我们了,node在安装过程中会把JavaScript代码翻译成C语言风格的字符数组,在V8引擎中运行,这下终于豁然开朗了。
71 |
72 | **说明:**以上过程纯属自己YY,仅供大家一起YY。
73 |
--------------------------------------------------------------------------------