(d:\code\yideng\yideng-test\e2e\github.spec.js:9:4)
163 | at Module._compile (module.js:635:30)
164 | at Object.Module._extensions..js (module.js:646:10)
165 | at Module.load (module.js:554:32)
166 | at tryModuleLoad (module.js:497:12)
167 | at Function.Module._load (module.js:489:3)
168 | at Function.Module.runMain (module.js:676:10)
169 | at startup (bootstrap_node.js:187:16)
170 | npm ERR! code ELIFECYCLE
171 | npm ERR! errno 1
172 | npm ERR! yideng-test@1.0.0 e2e: `node ./e2e/github.spec.js`
173 | npm ERR! Exit status 1
174 | npm ERR!
175 | npm ERR! Failed at the yideng-test@1.0.0 e2e script.
176 | npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
177 |
178 | npm ERR! A complete log of this run can be found in:
179 | npm ERR! C:\Users\shaowei\AppData\Roaming\npm-cache\_logs\2019-01-09T05_05_03_648Z-debug.log
180 | ```
181 |
182 | 介绍f2etest
183 | [https://github.com/alibaba/f2etest](https://github.com/alibaba/f2etest)
184 | [http://shaofan.org/f2etest/](http://shaofan.org/f2etest/)
185 | [http://shaofan.org/ui-recorder/](http://shaofan.org/ui-recorder/)
186 | [https://f2etest.net/](https://f2etest.net/)
187 |
188 | UI自动化测试
189 |
190 | 最早做UI走查的框架PhantomCSS
191 | 现在我们要用的backstopJS工具,首先要安装这个工具
192 |
193 | 接口测试
194 | 要先安装axios
195 | 要安装mocha
196 | 要安装express
197 |
--------------------------------------------------------------------------------
/es5.1-new-feature/article/ASI.md:
--------------------------------------------------------------------------------
1 | # Automatic Semicolon Insertion (ASI)
2 |
3 | [source](https://www.ecma-international.org/ecma-262/5.1/#sec-7.9)
4 |
5 | Certain ECMAScript statements (empty statement, variable statement, expression statement, **do-while** statement, **continue** statement, **break** statement, **return** statement, and **throw** statement) must be terminated with semicolons. Such semicolons may always appear explicitly in the source text. For convenience, however, such semicolons may be omitted from the source text in certain situations. These situations are described by saying that semicolons are automatically inserted into the source code token stream in those situations.
6 |
7 | ## 1. Rules of Automatic Semicolon Insertion
8 |
9 | There are three basic rules of semicolon insertion:
10 |
11 | 1. When, as the program is parsed from left to right, a token (called the offending token) is encountered that is not allowed by any production of the grammar, then a semicolon is automatically inserted before the offending token if one or more of the following conditions is true:
12 | * The offending token is separated from the previous token by at least one LineTerminator.
13 | * The offending token is }.
14 | 2. When, as the program is parsed from left to right, the end of the input stream of tokens is encountered and the parser is unable to parse the input token stream as a single complete ECMAScript Program, then a semicolon is automatically inserted at the end of the input stream.
15 | 3. When, as the program is parsed from left to right, a token is encountered that is allowed by some production of the grammar, but the production is a restricted production and the token would be the first token for a terminal or nonterminal immediately following the annotation “[no LineTerminator here]” within the restricted production (and therefore such a token is called a restricted token), and the restricted token is separated from the previous token by at least one LineTerminator, then a semicolon is automatically inserted before the restricted token.
16 |
17 | However, there is an additional overriding condition on the preceding rules: a semicolon is never inserted automatically if the semicolon would then be parsed as an empty statement or if that semicolon would become one of the two semicolons in the header of a for statement ([see 12.6.3](https://www.ecma-international.org/ecma-262/5.1/#sec-12.6.3)).
18 |
19 | **NOTE** The following are the only restricted productions in the grammar:
20 |
21 | > *PostfixExpression* :
22 | > LeftHandSideExpression [no LineTerminator here] ++
23 | > LeftHandSideExpression [no LineTerminator here] --
24 | >
25 | > *ContinueStatement* :
26 | > **continue** [no LineTerminator here] Identifier ;
27 | >
28 | > *BreakStatement* :
29 | > **break** [no LineTerminator here] Identifier ;
30 | >
31 | > *ReturnStatement* :
32 | > **return** [no LineTerminator here] Expression ;
33 | >
34 | > *ThrowStatement* :
35 | > **throw** [no LineTerminator here] Expression ;
36 |
37 | The practical effect of these restricted productions is as follows:
38 |
39 | When a ++ or -- token is encountered where the parser would treat it as a postfix operator, and at least one LineTerminator occurred between the preceding token and the ++ or -- token, then a semicolon is automatically inserted before the ++ or -- token.
40 |
41 | When a continue, break, return, or throw token is encountered and a LineTerminator is encountered before the next token, a semicolon is automatically inserted after the continue, break, return, or throw token.
42 |
43 | The resulting practical advice to ECMAScript programmers is:
44 |
45 | A postfix ++ or -- operator should appear on the same line as its operand.
46 |
47 | An Expression in a return or throw statement should start on the same line as the return or throw token.
48 |
49 | An Identifier in a break or continue statement should be on the same line as the break or continue token.
50 |
51 | ## 2. Examples of Automatic Semicolon Insertion
52 |
53 | The source
54 |
55 | `{ 1 2 } 3`
56 |
57 | is not a valid sentence in the ECMAScript grammar, even with the automatic semicolon insertion rules. In contrast, the source
58 |
59 | ```js
60 | { 1
61 | 2 } 3
62 | ```
63 |
64 | is also not a valid ECMAScript sentence, but is transformed by automatic semicolon insertion into the following:
65 |
66 | ```js
67 | { 1
68 | ;2 ;} 3;
69 | ```
70 |
71 | which is a valid ECMAScript sentence.
72 |
73 | The source
74 |
75 | ```js
76 | for (a; b
77 | )
78 | ```
79 |
80 | is not a valid ECMAScript sentence and is not altered by automatic semicolon insertion because the semicolon is needed for the header of a for statement. Automatic semicolon insertion never inserts one of the two semicolons in the header of a for statement.
81 |
82 | The source
83 |
84 | ```js
85 | return
86 | a + b
87 | ```
88 |
89 | is transformed by automatic semicolon insertion into the following:
90 |
91 | ```js
92 | return;
93 | a + b;
94 | ```
95 |
96 | NOTE The expression a + b is not treated as a value to be returned by the return statement, because a LineTerminator separates it from the token return.
97 |
98 | The source
99 |
100 | ```js
101 | a = b
102 | ++c
103 | ```
104 |
105 | is transformed by automatic semicolon insertion into the following:
106 |
107 | ```js
108 | a = b;
109 | ++c;
110 | ```
111 |
112 | NOTE The token ++ is not treated as a postfix operator applying to the variable b, because a LineTerminator occurs between b and ++.
113 |
114 | The source
115 |
116 | ```js
117 | if (a > b)
118 | else c = d
119 | ```
120 |
121 | is not a valid ECMAScript sentence and is not altered by automatic semicolon insertion before the else token, even though no production of the grammar applies at that point, because an automatically inserted semicolon would then be parsed as an empty statement.
122 |
123 | The source
124 |
125 | ```js
126 | a = b + c
127 | (d + e).print()
128 | ```
129 |
130 | is not transformed by automatic semicolon insertion, because the parenthesised expression that begins the second line can be interpreted as an argument list for a function call:
131 |
132 | `a = b + c(d + e).print()`
133 |
134 | In the circumstance that an assignment statement must begin with a left parenthesis, it is a good idea for the programmer to provide an explicit semicolon at the end of the preceding statement rather than to rely on automatic semicolon insertion.
--------------------------------------------------------------------------------
/functional-programming/article/2019-1-13.md:
--------------------------------------------------------------------------------
1 | # JavaScript函数式编程
2 |
3 | ## 一、函数式编程基础——范畴论
4 |
5 | 函数式编程的理论基础就是范畴论,虽然不得不成为我们对于数学,总是保佑复杂的心态,但是不可否认的,他确实是科学之母。
6 |
7 | * 范畴论的目的是:规范化数学构造。
8 | * 方法为:使用带标签的有向图。
9 | * 研究内容:各种数学结构之间的关系。
10 |
11 | ### 什么是范畴
12 |
13 | 范畴就是一系列之间存在关系的对象所组成的一个“集合”。这里对象之间的关系就是态射。范畴由以下部分组成:
14 |
15 | 1. 一系列的对象(object).
16 | 2. 一系列的态射(morphism).
17 | 3. 一个组合(composition)操作符,用点(.)表示,用于将态射进行组合。
18 |
19 | ### 函子(functor)
20 |
21 | 函子是范畴之间的map关系。可以理解为范畴之间的态射。
22 |
23 | ## 二、纯函数
24 |
25 | 我们先用数学概念去理解对于函数的定义:
26 | 函数 f 的概念就是,对于输入 x 产生一个输出 y = f(x)。这便是一种最简单的纯函数。引出纯函数的定义就是:
27 | **纯函数的定义是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。**
28 |
29 | 对于上面的总结我们可以拆分成三部分用代码去理解:
30 | 1. 只要每次给定相同的输入值,就一定会得到相同的输出值;
31 | 2. 不会改变原始输入参数,或是外部的环境,所以没有副作用;
32 | 3. 不依頼其他外部的状态,变量或常量。
33 |
34 | 下面我们通过代码的例子去逐一解释上面的内容
35 |
36 | ```js
37 | // 理解1:只要每次给定相同的输入值,就一定会得到相同的输出值
38 |
39 | var arr = [1,2,3,4,5];
40 |
41 | // Array.slice是纯函数,对于固定的输入,输出总是固定的
42 | // 这很函数式
43 | arr.slice(0,3); //=> [1,2,3]
44 | arr.slice(0,3); //=> [1,2,3]
45 |
46 | // Array.splice是不纯的,对于固定的输入,输出不是固定的
47 | // 这不函数式
48 | arr.splice(0,3); //=> [1,2,3]
49 | arr.splice(0,3); //=> [4,5]
50 | arr.splice(0,3); //=> []
51 |
52 | //它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
53 | ```
54 |
55 | ```js
56 | // 理解2:不会改变原始输入参数,或是外部的环境,所以没有副作用;
57 |
58 | ```
59 |
60 | ```js
61 | // 理解3:不依頼其他外部的状态,变量或常量
62 | //不是纯函数
63 | var min = 18;
64 | var checkage = age => age > min;
65 |
66 | //这很函数式
67 | var checkage = age => age > 18;
68 | ```
69 |
70 | 在不纯的版本中,checkage 这个函数的行为不仅取决于输入的参数 age,还取决于一个外部的变量 min,换句话说,这个函数的行为需要由外部的系统环境决定。对于大型系统来说,这种对于外部状态的依赖是造成系统复杂性大大提高的主要原因。
71 |
72 | 可以注意到,纯的 checkage 把关键数字 18 硬编码在函数内部,扩展性比较差,我们可以在后面的柯里化中看到如何用优雅的函数式解决这种问题。
73 |
74 | 纯函数不仅可以有效降低系统的复杂度,还有很多很棒的特性,比如可缓存性:
75 |
76 | ```js
77 | import _ from 'lodash';
78 | var sin = _.memorize(x => Math.sin(x));
79 |
80 | //第一次计算的时候会稍慢一点
81 | var a = sin(1);
82 |
83 | //第二次有了缓存,速度极快
84 | var b = sin(1);
85 | ```
86 |
87 | ## 三、函数的柯里化
88 |
89 | 函数柯里化(curry)的定义很简单:传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
90 |
91 | 比如对于加法函数 var add = (x, y) => x + y ,我们可以这样进行柯里化:
92 |
93 | ```js
94 | //比较容易读懂的ES5写法
95 | var add = function(x){
96 | return function(y){
97 | return x + y
98 | }
99 | }
100 |
101 | //ES6写法,也是比较正统的函数式写法
102 | var add = x => (y => x + y);
103 |
104 | //试试看
105 | var add2 = add(2);
106 | var add200 = add(200);
107 |
108 | add2(2); // =>4
109 | add200(50); // =>250
110 | ```
111 |
112 | 对于加法这种极其简单的函数来说,柯里化并没有什么大用处。
113 |
114 | 还记得上面那个 checkage 的函数吗?我们可以这样柯里化它:
115 |
116 | ```js
117 | var checkage = min => (age => age > min);
118 | var checkage18 = checkage(18);
119 | checkage18(20);
120 | // =>true
121 | ```
122 |
123 | 事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的“缓存”,是一种非常高效的编写函数的方法:
124 |
125 | ```js
126 | import { curry } from 'lodash';
127 |
128 | //首先柯里化两个纯函数
129 | var match = curry((reg, str) => str.match(reg));
130 | var filter = curry((f, arr) => arr.filter(f));
131 |
132 | //判断字符串里有没有空格
133 | var haveSpace = match(/\s+/g);
134 |
135 | haveSpace("ffffffff");
136 | //=>null
137 |
138 | haveSpace("a b");
139 | //=>[" "]
140 |
141 | filter(haveSpace, ["abcdefg", "Hello World"]);
142 | //=>["Hello world"]
143 | ```
144 |
145 | ## 四、函数组合
146 |
147 | 学会了使用纯函数以及如何把它柯里化之后,我们会很容易写出这样的“包菜式”代码:
148 |
149 | ```js
150 | h(g(f(x)));
151 | ```
152 |
153 | 虽然这也是函数式的代码,但它依然存在某种意义上的“不优雅”。为了解决函数嵌套的问题,我们需要用到“函数组合”:
154 |
155 | ```js
156 | //两个函数的组合
157 | var compose = function(f, g) {
158 | return function(x) {
159 | return f(g(x));
160 | };
161 | };
162 |
163 | //或者
164 | var compose = (f, g) => (x => f(g(x)));
165 |
166 | var add1 = x => x + 1;
167 | var mul5 = x => x * 5;
168 |
169 | compose(mul5, add1)(2);
170 | // =>15
171 | ```
172 |
173 | 我们定义的compose就像双面胶一样,可以把任何两个纯函数结合到一起。当然你也可以扩展出组合三个函数的“三面胶”,甚至“四面胶”“N面胶”。
174 |
175 | 这种灵活的组合可以让我们像拼积木一样来组合函数式的代码:
176 |
177 | ```js
178 | var first = arr => arr[0];
179 | var reverse = arr => arr.reverse();
180 |
181 | var last = compose(first, reverse);
182 |
183 | last([1,2,3,4,5]);
184 | // =>5
185 | ```
186 |
187 | ## 五、Point Free
188 |
189 | 有了柯里化和函数组合的基础知识,下面介绍一下Point Free这种代码风格。
190 |
191 | 细心的话你可能会注意到,之前的代码中我们总是喜欢把一些对象自带的方法转化成纯函数:
192 |
193 | ```js
194 | var map = (f, arr) => arr.map(f);
195 |
196 | var toUpperCase = word => word.toUpperCase();
197 |
198 | // 这就是有参的,因为有word
199 | var snakeCase = word => word.toLowerCase().replace(/\s+/ig, '_');
200 |
201 | // 这是pointfree
202 | var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);
203 |
204 | ```
205 |
206 | 从另一个角度看,有参的函数的目的是得到一个数据,而pointfree的函数的目的是得到另一个函数。 所以,如下的方程,虽然也有参,也可以认为是pointfree的。
207 |
208 | 这种做法是有原因的。
209 |
210 | ```js
211 | const titlesForYear = year =>
212 | pipe(
213 | filter(publishedInYear(year)),
214 | map(book => book.title)
215 | )
216 | ```
217 |
218 | Point Free这种模式现在还暂且没有中文的翻译,有兴趣的话可以看看这里的英文解释:
219 |
220 | 用中文解释的话大概就是,不要命名转瞬即逝的中间变量,比如:
221 |
222 | ```js
223 | //这不Piont free
224 | var f = str => str.toUpperCase().split(' ');
225 | ```
226 |
227 | 这个函数中,我们使用了 str 作为我们的中间变量,但这个中间变量除了让代码变得长了一点以外是毫无意义的。下面改造一下这段代码:
228 |
229 | ```js
230 | var toUpperCase = word => word.toUpperCase();
231 | var split = x => (str => str.split(x));
232 |
233 | var f = compose(split(' '), toUpperCase);
234 |
235 | f("abcd efgh");
236 | // =>["ABCD", "EFGH"]
237 | ```
238 |
239 | 这种风格能够帮助我们减少不必要的命名,让代码保持简洁和通用。当然,为了在一些函数中写出Point Free的风格,在代码的其它地方必然是不那么Point Free的,这个地方需要自己取舍。
240 |
241 | 六、声明式与命令式代码
242 | 命令式代码的意思就是,我们通过编写一条又一条指令去让计算机执行一些动作,这其中一般都会涉及到很多繁杂的细节。
243 |
244 | 而声明式就要优雅很多了,我们通过写表达式的方式来声明我们想干什么,而不是通过一步一步的指示。
245 |
246 | ```js
247 | //命令式
248 | var CEOs = [];
249 | for(var i = 0; i < companies.length; i++){
250 | CEOs.push(companies[i].CEO)
251 | }
252 |
253 | //声明式
254 | var CEOs = companies.map(c => c.CEO);
255 | ```
256 |
257 | 命令式的写法要先实例化一个数组,然后再对 companies 数组进行for循环遍历,手动命名、判断、增加计数器,就好像你开了一辆零件全部暴露在外的汽车一样,虽然很机械朋克风,但这并不是优雅的程序员应该做的。
258 |
259 | 声明式的写法是一个表达式,如何进行计数器迭代,返回的数组如何收集,这些细节都隐藏了起来。它指明的是做什么,而不是怎么做。除了更加清晰和简洁之外,map 函数还可以进一步独立优化,甚至用解释器内置的速度极快的 map 函数,这么一来我们主要的业务代码就无须改动了。
260 |
261 | 函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。
262 |
263 | 相反,不纯的不函数式的代码会产生副作用或者依赖外部系统环境,使用它们的时候总是要考虑这些不干净的副作用。在复杂的系统中,这对于程序员的心智来说是极大的负担。
264 |
265 | ## 七、尾声
266 |
267 | 任何代码都是要有实际用处才有意义,对于JS来说也是如此。然而现实的编程世界显然不如范例中的函数式世界那么美好,实际应用中的JS是要接触到ajax、DOM操作,NodeJS环境中读写文件、网络操作这些对于外部环境强依赖,有明显副作用的“很脏”的工作。
268 |
269 | 这对于函数式编程来说也是很大的挑战,所以我们也需要更强大的技术去解决这些“脏问题”。我会在下一篇文章中介绍函数式编程的更加高阶一些的知识,例如Functor、Monad等等概念。
270 |
271 | 浏览器对尾调用有优化但是浏览器没有给实践
272 |
273 | 所以尾递归减少了栈侦记录,手动我们进行了优化,但是最终优化是覆盖当前侦,可惜浏览器并没有。
--------------------------------------------------------------------------------
/es5-senior/essence1.md:
--------------------------------------------------------------------------------
1 | # JavaScript语言精粹
2 |
3 | ## 数据类型
4 |
5 | JavaScript是弱类型语言,但并不是没有类型,JavaScript可以识别7种不同的类型:
6 | 1. boolean
7 | 2. number
8 | 3. string
9 | 4. null
10 | 5. underfined
11 | 6. Symbol
12 | 7. object
13 |
14 | typeof false
15 | typeof .2
16 | typeof NaN
17 | typeof ' '
18 | typeof undefined
19 | typeof Symbol()
20 | typeof new Date()
21 | typeof [ ]
22 |
23 | typeof alert
24 |
25 | typeof null
26 | typeof not_defined_var
27 |
28 | ## 变量
29 |
30 | 在应用程序中,使用变量来为值命名。变量的名称称为 identifiers
31 |
32 | ### 声明
33 |
34 | 1. 使用关键字 var :函数作用域
35 | 2. 使用关键字 let :块作用域(block scope local variable)
36 | 3. 直接使用:全局作用域
37 |
38 | ``` js
39 | var global_var = 1;
40 |
41 | function fn() {
42 | var fn_var = 2;
43 |
44 | if(fn_var > 10) {
45 | let block_var = 3;
46 | global__var2 = 4;
47 | }
48 | }
49 | ```
50 |
51 | 只声明不赋值,变量的默认值是 underfined
52 |
53 | const 关键字可以声明不可变变量,同样是块作用域。对不可变的理解在对象上的理解需要注意。
54 |
55 | ``` js
56 | const num = 1;
57 | const obj = {
58 | prop: 'value'
59 | };
60 |
61 | num = 2; //
62 | obj['prop'] = 'value2';
63 |
64 | obj = [ ]
65 | ```
66 |
67 | ### 变量提升
68 |
69 | JavaScript中可以引用稍后声明的变量,而不会引发异常,这一概念称为变量声明提升(hoisting)
70 |
71 | ``` js
72 | console.log(a);
73 | var a = 2;
74 |
75 | // 等同于
76 |
77 | var a;
78 | console.log(a);
79 | a = 2;
80 | ```
81 |
82 | ## 函数
83 |
84 | 一个函数就是一个可以被外部代码调用(或者函数本身递归调用)的子程序
85 |
86 | ### 定义函数
87 |
88 | 1. 函数声明
89 | 2. 函数表达式
90 | 3. Function构造函数
91 | 4. 箭头函数
92 |
93 | ``` js
94 |
95 | function fn( ) {
96 | // ...
97 | }
98 |
99 | var fn = function( ) {
100 | // ...
101 | }
102 |
103 | var fn = new Function(arg1, arg2, arg3, ... arg, funcBody)
104 |
105 | var fn = (param) => { }
106 | ```
107 |
108 | ### arguments
109 |
110 | arguments对象存放着函数的所有参数,类似数组但不是数组
111 |
112 | ``` js
113 | function foo() {
114 | return arguments;
115 | }
116 | foo(1, 2, 3);
117 | ```
118 |
119 | ### rest
120 |
121 | ``` js
122 | function foo(...args) {
123 | return args;
124 | }
125 |
126 | foo(1, 2, 3);
127 |
128 | function fn(a, b, ...args) {
129 | return args;
130 | }
131 |
132 | fn(1, 2, 3, 4, 5);
133 | ```
134 |
135 | ### default
136 |
137 | ``` js
138 | function fn(a = 2, b = 3) {
139 | return a + b;
140 | }
141 |
142 | fn(2, 3);
143 | fn(2);
144 | fn();
145 | ```
146 |
147 | ## 对象
148 |
149 | JavaScript中对象是可变 键值集合 (keyed collections)
150 |
151 | ### 定义对象
152 |
153 | 1. 字面量
154 | 2. 构造函数
155 |
156 | var obj = {
157 | prop: 'value',
158 | fn: function() {}
159 | };
160 |
161 | var date = new Date();
162 |
163 | ## 构造函数
164 |
165 | 构造函数和普通函数并没有区别,使用new关键字调用就是构造函数,使用构造函数可以实例化一个对象。
166 |
167 | 函数的返回值有两种可能
168 | 1. 显示调用return 返回return后表达式的求值
169 | 2. 没有调用return返回undefined
170 |
171 | ``` js
172 | function People(name, age) {
173 | this.name = name;
174 | this.age = age;
175 | }
176 |
177 | var people = new People('Byron', 26);
178 | ```
179 |
180 | 构造函数返回值
181 | 1. 没有返回值
182 | 2. 返回简单数据类型
183 | 3. 对象类型
184 |
185 | 前两种情况构造函数返回构造对象的实例,实例化对象正是利用的这个特性
186 |
187 | 第三种构造函数和普通函数表现一致,返回return后表达式的结果。
188 |
189 | ### prototype
190 |
191 | 1. 每个函数都有一个prototype 的对象属性,对象内有一个constructor属性,默认指向函数本身
192 | 2. 每个对象都有一个__proto__的属性,属相指向其父类型的prototype
193 |
194 | 继承
195 |
196 | label statement
197 |
198 | ``` js
199 | loop:
200 | for (var i = 0; i < 10; i++) {
201 | for () {
202 | console.log(j);
203 | if (j === 1) {
204 | break loop;
205 | }
206 | }
207 | }
208 |
209 | console.log(i);
210 | ```
211 |
212 | 语句与表达式
213 |
214 | ``` js
215 | var x = {a: 1};
216 |
217 | {a: 1}
218 |
219 | {a:1, b: 2}
220 | ```
221 |
222 | 语句优先原则,JavaScript进行语法分析时,当遇到语法冲突,优先解释为语句(注意不是表达式),
223 |
224 | `{a: 1}` 可以解释成一个对象的语法树,也可以理解成这是一个语句块,
225 |
226 | 立即执行函数
227 |
228 | ``` js
229 | !function(){}()
230 | +function(){}()
231 | -function(){}()
232 |
233 | (function(){})()
234 |
235 | ```
236 |
237 | ## 高阶函数
238 |
239 | 高阶函数是把函数当做参数或者返回值是函数的函数
240 |
241 | ### 回调函数
242 |
243 | ```js
244 | [1, 2, 3, 4].forEach(function(item) {
245 | console.log(item);
246 | });
247 | ```
248 |
249 | ## 闭包
250 |
251 | 闭包由两部分组成
252 | 1. 函数
253 | 2. 环境:函数创建时作用域内的局部变量
254 |
255 | ```js
256 | function makeCounter(init) {
257 | var init = init || 0;
258 |
259 | return function() {
260 | return ++init;
261 | }
262 | }
263 |
264 | var counter = makeCounter(10);
265 | console.log(counter());
266 | console.log(counter());
267 | ```
268 |
269 | 典型错误
270 |
271 | ```js
272 | // 并不能得出想要的结果
273 | // 因为闭包,i值被保留
274 | // 事件绑定是在for循环执行之后的
275 | for (var i = 0; i < doms.length; i++) {
276 | doms.eq(i).on('click', function (ev) {
277 | console.log(i);
278 | });
279 | }
280 |
281 | for (var i = 0; i < doms.length; i++) {
282 | (function (i) {
283 | doms.eq(i).on('click', function (ev) {
284 | console.log(i);
285 | });
286 | })(i);
287 | }
288 | ```
289 |
290 | ## 惰性函数
291 |
292 | ```js
293 | function eventBinderGenerator() {
294 | if(window.addEventListener) {
295 | return function(element, type, handler) {
296 | element.addEventListener(type, handler, false);
297 | }
298 | } else {
299 | return function(element, type, handler) {
300 | element.attanchEvent('on' + type, handler.bind(element, window.event));
301 | }
302 | }
303 | }
304 |
305 | var addEvent = eventBinderGenerator();
306 | ```
307 |
308 | ## 柯里化
309 |
310 | 一种允许使用部分参数生成函数的方式
311 |
312 | ```js
313 | function isType(type) {
314 | return function(obj) {
315 | return Object.prototype.toString.call(obj) === '[object' + type +']';
316 | }
317 | }
318 |
319 | var isNumber = isType('Number');
320 |
321 | console.log(isNumber(1));
322 | console.log(isNumber('s'));
323 |
324 | var isArray = isType('Array');
325 |
326 | console.log(isArray(1));
327 | console.log(isArray([1, 2, 3]));
328 |
329 | function f(n) {
330 | return n * n;
331 | }
332 |
333 | function g(n) {
334 | return n * 2;
335 | }
336 |
337 | console.log(f(g(5)));
338 |
339 | function pipe(f, g) {
340 | return function() {
341 | return f.call(null, g.apply(null, arguments));
342 | }
343 | }
344 |
345 | var fn = pipe(f, g);
346 |
347 | console.log(fn(5));
348 | ```
349 |
350 | 你可能会问,Underscore 和 Lodash 已经这么流行了,为什么还要学习好像雷同的 Ramda 呢?
351 |
352 | ## 尾递归
353 |
354 | 1. 尾调用是指某个函数的最后一步是调用另一个函数
355 | 2. 函数调用自身,称为递归
356 | 3. 如果尾调用自身,就称为尾递归
357 |
358 | 递归很容易发生"栈溢出”错误(stack overflow)
359 |
360 | ```js
361 | function factorial(n) {
362 | if(n === 1) return 1;
363 | return n * factorial(n - 1);
364 | }
365 |
366 | factorial(5)
367 | ```
368 |
369 | ## 反柯里化
370 |
371 | ### 柯里化减少参数
372 |
--------------------------------------------------------------------------------
/functional-programming/article/2019-1-22.md:
--------------------------------------------------------------------------------
1 | # 函数式编程——原来你也是编程范式
2 |
3 | ## 编程范式有点抽象
4 |
5 | 在学习面向对象编程或者是函数式编程的时候,总会提到“编程范式”这四个字,这篇文章正式为了解释“编程范式”,以及它和函数式编程的关系。
6 |
7 | 首先,**编程范式(Programming Paradigm)是某种编程语言的典型编程风格或者说是编程方式。**
8 |
9 | 随着编程方法学和软件工程学的深入,特别是OO思想的普及,范式(Paradigm)以及编程范式等术语渐渐出现在人们面前。面向对象编程(OOP)常常被誉为是一种革命性的思想,正因为它不同于其他的各种编程范式。编程范式也许是学习任何一门编程语言时要理解的最重要的术语。
10 | 托马斯.库尔提出“科学的革命”的范式论后,Robert Floyd在1979年图灵奖的颁奖演说中使用了编程范式一词。编程范式一般包括三个方面,以OOP为例:
11 | 1. 学科的逻辑体系——规则范式:如 类/对象、继承、动态绑定、方法改写、对象替换等等机制。
12 | 2. 心理认知因素——心理范式:按照面向对象编程之父Alan Kay的观点,“计算就是模拟”。OO范式极其重视隐喻(metaphor)的价值,通过拟人化,按照自然的方式模拟自然。
13 | 3. 自然观/世界观——观念范式:强调程序的组织技术,视程序为松散耦合的对象/类的组合,用继承机制将类组织成一个层次结构,把程序运行视为相互服务的对象之间的对话。
14 |
15 | 需要再次提醒的是:编程范式是编程语言的一种分类方式,它并不针对某种编程语言。就编程语言而言,一种语言可以适用多种编程范式。例如JavaScript、Java对于面向对象编程和函数式编程都是支持的。
16 |
17 | ## 主流编程范式
18 |
19 | 按照不同的分类标准,对于编程范式的分类也略有不同。
20 | 计算机科学中主流的:
21 | * 面向对象编程
22 | * 面向过程编程
23 | * 泛型编程
24 |
25 | 工程业务框架中特有的:
26 | * 事件驱动编程
27 | * 并发编程,分布式编程。
28 |
29 | 三个主流
30 | * 声明式编程
31 | * 命令式编程
32 | * 函数式编程
33 |
34 | **备注:**面向过程编程也就是命令式编程。
35 |
36 | 下面我们将着重介绍面向过程编程、面向对象编程、函数式编程
37 |
38 | ## 面向过程编程(Procedure Oriented)
39 |
40 | 简单的理解:面向过程编程以过程为中心的编程思想,是具体化的,流程化的。解决一个问题,需要一步一步分析需要怎样,然后需要怎样,一步一步实现的。
41 |
42 | 面向过程编程,也称为命令式编程,应该是最原始的、也是我们最熟悉的一种传统编程方式。从本质上讲,它是“冯.诺依曼机”运行机制的抽象,它的编程思想方式源于计算机指令的顺序排列。(也就是说:过程化语言模拟的是计算机机器的系统构造,而并不是基于语言的使用者的个人能力和倾向。这一点我们应该都很清楚,比如我们最早曾经使用过的单片机的汇编语言。)
43 |
44 | ### 过程化编程的步骤是:
45 |
46 | 首先,我们必须将待解问题的解决方案抽象为一系列概念化的步骤。
47 |
48 | 然后通过编程的方式将这些步骤转化为程序指令集(算法),而这些指令按照一定的顺序排列,用来说明如何执行一个任务或解决一个问题。这就意味着,程序员必须要知道程序要完成什么,并且告诉计算机如何来进行所需的计算工作,包括每个细节操作。简言之,就是将计算机看作一个善始善终服从命令的装置。
49 |
50 | 所以在过程化编程中,把待解问题规范化、抽象为某种算法是解决问题的关键步骤。其次,才是编写具体算法和完成相应的算法实现问题的正确解决。当然,程序员对待解问题的抽象能力也是非常重要的因素,但这本身已经与编程语言无关了。程序流程图是过程化语言进行程序编写的有效辅助手段。
51 | 尽管现存的计算机编程语言很多,但是人们把所有支持过程化编程范式的编程语言都被归纳为过程化编程语言。例如机器语言、汇编语言、BASIC、COBOL、C 、FORTRAN等等许多第三代编程语言都被归纳为过程化语言。
52 |
53 | 过程化语言特别适合解决线性(或者说按部就班)的算法问题。它强调“自上而下(自顶向下)”、“精益求精”的设计方式。这种方式非常类似我们的工作和生活方式,因为我们的日常活动都是按部就班的顺序进行的。
54 |
55 | 过程化语言趋向于开发运行较快且对系统资源利用率较高的程序。过程化语言非常的灵活并强大,同时有许多经典应用范例,这使得程序员可以用它来解决多种问题。
56 |
57 | 过程化语言的不足之处就是它不适合某些种类问题的解决,例如那些非结构化的具有复杂算法的问题。问题出现在,过程化语言必须对一个算法加以详尽的说明,并且其中还要包括执行这些指令或语句的顺序。实际上,给那些非结构化的具有复杂算法的问题给出详尽的算法是极其困难的。
58 |
59 | 广泛引起争议和讨论的地方是:无条件分支,或goto语句,它是大多数过程式编程语言的组成部分,反对者声称:goto语句可能被无限地滥用;它给程序设计提供了制造混 乱的机会。目前达成的共识是将它保留在大多数语言中,对于它所具有的危险性,应该通过程序设计的规定将其最小化。
60 |
61 | ## 面向对象编程
62 |
63 | 在面向对象编程范式没有出现,面向过程编程大行其道。上面我们介绍过:面向过程编程就是分析出解决问题所需要的步骤,然后采用分支循环用函数把这些步骤一步一步实现,使用的时候一个一个一次调用就可以了。面向过程编程虽然也可以解决问题,但是也存在着重用性差、可维护性差、开发过程复杂等缺点。
64 | 尤其是随着时间的发展,软件开发中出现的问题越加突出:
65 |
66 | * 软件复杂庞大
67 | * 需求的不断变更
68 | * 很多软件维护阶段困难重重
69 |
70 | 为解决以上问题,面向对象编程应运而生。面向对象编程具有很好的可读性、可维护性和可扩展性,并且保证了代码的高内聚低耦合。具备以上优点的面向对象编程具备四大特性:
71 | * 抽象
72 | * 封装
73 | * 继承
74 | * 多态
75 |
76 | 面向对象的程序设计包括了三个基本概念:封装性、继承性、多态性。面向对象的程序语言通过类、方法、对象和消息传递,来支持面向对象的程序设计范式。
77 | 1. 对象
78 | 世间万事万物都是对象。
79 | 面向对象的程序设计的抽象机制是将待解问题抽象为面向对象的程序中的对象。利用封装使每个对象都拥有个体的身份。程序便是成堆的对象,彼此通过消息的传递,请求其它对象 进行工作。
80 | 2. 类
81 | 每个对象都是其类中的一个实体。
82 | 物以类聚——就是说明:类是相似对象的集合。类中的对象可以接受相同的消息。换句话说:类包含和描述了“具有共同特性(数据元素)和共同行为(功能)”的一组对象。
83 | 比如:苹果、梨、橘子等等对象都属于水果类。
84 | 3. 封装
85 | 封装(有时也被称为信息隐藏)就是把数据和行为结合在一个包中,并对对象的使用者隐藏数据的实现过程。信息隐藏是面向对象编程的基本原则,而封装是实现这一原则的一种方 式。
86 | 封装使对象呈现出“黑盒子”特性,这是对象再利用和实现可靠性的关键步骤。
87 | 4. 接口
88 | 每个对象都有接口。接口不是类,而是对符合接口需求的类所作的一套规范。接口说明类应该做什么但不指定如何作的方法。一个类可以有一个或多个接口。
89 | 5. 方法
90 | 方法决定了某个对象究竟能够接受什么样的消息。面向对象的设计有时也会简单地归纳为“将消息发送给对象”。
91 | 6. 继承
92 | 继承的思想就是允许在已存在类的基础上构建新的类。一个子类能够继承父类的所有成员,包括属性和方法。
93 | 继承的主要作用:通过实现继承完成代码重用;通过接口继承完成代码被重用。继承是一种规范的技巧,而不是一种实现的技巧。
94 | 7. 多态
95 | 多态提供了“接口与实现分离”。多态不但能改善程序的组织架构及可读性,更利于开发出“可扩充”的程序。
96 | 继承是多态的基础。多态是继承的目的。
97 | 合理的运用基于类继承的多态、基于接口继承的多态和基于模版的多态,能增强程序的简洁性、灵活性、可维护性、可重用性和可扩展性。
98 |
99 | 面向对象技术一方面借鉴了哲学、心理学、生物学的思考方式,另一方面,它是建立在其他编程技术之上的,是以前的编程思想的自然产物。
100 | 如果说结构化软件设计是将函数式编程技术应用到命令式语言中进行程序设计,面向对象编程不过是将函数式模型应用到命令式程序中的另一途径,此时,模块进步为对象,过程龟缩到class的成员方法中。OOP的很多技术——抽象数据类型、信息隐藏、接口与实现分离、对象生成功能、消息传递机制等等,很多东西就是结构化软件设计所拥有的、或者在其他编程语言中单独出现。但只有在面向对象语言中,他们才共同出现,以一种独特的合作方式互相协作、互相补充。
101 |
102 | ## 函数式编程
103 |
104 | 函数式编程属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
105 |
106 | ### 特点
107 |
108 | 1. 闭包和高阶函数
109 | 函数编程支持函数作为第一类对象,有时称为闭包或者仿函数(functor)对象。实质上,闭包是起函数的作用并可以像对象一样操作的对象。与此类似,FP 语言支持高阶函数。高阶函数可以用另一个函数(间接地,用一个表达式) 作为其输入参数,在某些情况下,它甚至返回一个函数作为其输出参数。这两种结构结合在一起使得可以用优雅的方式进行模块化编程,这是使用 FP 的最大好处。
110 |
111 | 2. 惰性计算
112 | 除了高阶函数和仿函数(或闭包)的概念,FP 还引入了惰性计算的概念。在惰性计算中,表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值时进行计算。延迟的计算使您可以编写可能潜在地生成无穷输出的函数。因为不会计算多于程序的其余部分所需要的值,所以不需要担心由无穷计算所导致的 out-of-memory 错误。一个惰性计算的例子是生成无穷 Fibonacci 列表的函数,但是对第n个Fibonacci 数的计算相当于只是从可能的无穷列表中提取一项。
113 |
114 | 3. 递归
115 | FP 还有一个特点是用递归做为控制流程的机制。例如,Lisp 处理的列表定义为在头元素后面有子列表,这种表示法使得它自己自然地对更小的子列表不断递归。
116 | 函数式编程具有五个鲜明的特点。
117 |
118 | 4. 函数是"第一等公民"
119 | 所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
120 |
121 | 5. 只用"表达式",不用"语句"
122 | "表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
123 | 原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。
124 | 当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。
125 |
126 | 6. 没有"副作用"
127 | 所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
128 | 函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
129 |
130 | 7. 不修改状态
131 | 上一点已经提到,函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。
132 | 在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。下面的代码是一个将字符串逆序排列的函数,它演示了不同的参数如何决定了运算所处的"状态"。
133 |
134 | 8. 引用透明性
135 | 函数程序通常还加强引用透明性,即如果提供同样的输入,那么函数总是返回同样的结果。就是说,表达式的值不依赖于可以改变值的全局状态。这使您可以从形式上推断程序行为,因为表达式的意义只取决于其子表达式而不是计算顺序或者其他表达式的副作用。这有助于验证正确性、简化算法,甚至有助于找出优化它的方法。
136 |
137 | 9. 副作用
138 | 副作用是修改系统状态的语言结构。因为 FP 语言不包含任何赋值语句,变量值一旦被指派就永远不会改变。而且,调用函数只会计算出结果 ── 不会出现其他效果。因此,FP 语言没有副作用。
139 |
140 | ### 优点
141 |
142 | 1. 代码简洁,开发快速
143 | 函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
144 | Paul Graham在《黑客与画家》一书中写道:同样功能的程序,极端情况下,Lisp代码的长度可能是C代码的二十分之一。
145 | 如果程序员每天所写的代码行数基本相同,这就意味着,"C语言需要一年时间完成开发某个功能,Lisp语言只需要不到三星期。反过来说,如果某个新功能,Lisp语言完成开发需要三个月,C语言需要写五年。"当然,这样的对比故意夸大了差异,但是"在一个高度竞争的市场中,即使开发速度只相差两三倍,也足以使得你永远处在落后的位置。"
146 |
147 | 2. 接近自然语言,易于理解
148 | 函数式编程的自由度很高,可以写出很接近自然语言的代码。
149 | 前文曾经将表达式 `(1 + 2) * 3 - 4` ,写成函数式语言:
150 | `subtract(multiply(add(1,2), 3), 4)`
151 | 对它进行变形,不难得到另一种写法:
152 | `add(1,2).multiply(3).subtract(4)`
153 | 这基本就是自然语言的表达了。再看下面的代码,大家应该一眼就能明白它的意思吧:
154 | `merge([1,2],[3,4]).sort().search("2")`
155 | 因此,函数式编程的代码更容易理解。
156 |
157 | 3. 更方便的代码管理
158 | 函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
159 |
160 | 4. 代码的热升级
161 | 函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。Erlang语言早就证明了这一点,它是瑞典爱立信公司为了管理电话系统而开发的,电话系统的升级当然是不能停机的。
162 |
163 | 5. 易于"并发编程"
164 | 函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。
165 | 请看下面的代码:
166 |
167 | ```js
168 | var s1 = Op1();
169 | var s2 = Op2();
170 | var s3 = concat(s1, s2);
171 | ```
172 |
173 | 由于s1和s2互不干扰,不会修改变量,谁先执行是无所谓的,所以可以放心地增加线程,把它们分配在两个线程上完成。其他类型的语言就做不到这一点,因为s1可能会修改系统状态,而s2可能会用到这些状态,所以必须保证s2在s1之后运行,自然也就不能部署到其他线程上了。
174 | 多核CPU是将来的潮流,所以函数式编程的这个特性非常重要。
--------------------------------------------------------------------------------
/question-bank/JavaScript/2019-01-02.md:
--------------------------------------------------------------------------------
1 | # 2019年1月2日 JavaScript第一次检测
2 |
3 | ## 1.请写出弹出值,并解释为什么。
4 |
5 | ```js
6 | console.info(a);
7 | a();
8 | var a = 3;
9 | function a() {
10 | console.info(10)
11 | }
12 | console.info(a);
13 | a = 6;
14 | a();
15 | ```
16 |
17 | ```js
18 | var a = 3;
19 | function a() {
20 | console.info(10)
21 | }
22 | a();
23 | ```
24 |
25 | 变量提升,提升到当前作用域的顶端。
26 |
27 | 涉及知识点:
28 | 1. JavaScript中存在变量提升和函数提升现象;
29 | 2. 函数提升的优先级大于变量提升。
30 | 3. 函数提升的过程中会把函数声明转为函数表达式。
31 |
32 | ```js
33 | function test() {
34 | console.info(1);
35 | }
36 | function init() {
37 | if(false) {
38 | function test() {
39 | console.info(2)
40 | }
41 | }
42 | test();
43 | }
44 | init();
45 | ```
46 |
47 | 变量提升的过程的理解,以及每个版本浏览器对于变量提升不同的解释。
48 |
49 | ## 2.请写出如下输出值,并写出把注释掉的代码取消注释的值,并解释为什么。
50 |
51 | ```js
52 | this.a = 20;
53 | var test = {
54 | a: 40,
55 | init: () => {
56 | console.info(this.a);
57 | function go() {
58 | // this.a = 60;
59 | console.info(this.a);
60 | }
61 | go.prototype.a = 50;
62 | return go;
63 | }
64 | }
65 | // var p = test.init();
66 | // p();
67 | new (test.init())();
68 | ```
69 |
70 | 基础知识:
71 | this的指向问题,先看下面这个代码
72 |
73 | 自动补全分号问题
74 | 逻辑运算符和小括号
75 |
76 | ```js
77 | function test() {
78 | console.info(this);
79 | }
80 | var obj = {
81 | f: test
82 | }
83 | (obj.f)();
84 | (false || obj.f)();
85 | ```
86 |
87 | ## 3.请写出如下点击li的输出值,并用三种正确输出li里的数字。
88 |
89 | ```html
90 |
91 |
92 | - 1
93 | - 2
94 | - 3
95 | - 4
96 | - 5
97 | - 6
98 |
99 |
108 | ```
109 |
110 | 答案:
111 |
112 | JavaScript异步队列和同步执行栈
113 |
114 | ```js
115 | // 这种做法比较巧妙的运用this
116 | var list_li = document.getElementsByTagName("li");
117 | console.info(list_li);
118 | for (var i = 0; i < list_li.length; i++) {
119 | list_li[i].onclick = function () {
120 | console.info(this.innerHTML);
121 | }
122 | }
123 | // 闭包的正经运用
124 | var list_li = document.getElementsByTagName("li");
125 | console.info(list_li);
126 | for (var i = 0; i < list_li.length; i++) {
127 | (function (i) {
128 | list_li[i].onclick = function () {
129 | console.info(i + 1);
130 | }
131 | })(i);
132 | }
133 |
134 | // let、const的块级作用域
135 | var list_li = document.getElementsByTagName("li");
136 | console.info(list_li);
137 | for (let i = 0; i < list_li.length; i++) {
138 | list_li[i].onclick = function () {
139 | console.info(i + 1);
140 | }
141 | }
142 |
143 | // 事件代理,但是有点偏题
144 | window.onclick = function (event) {
145 | var event = event || window.event;
146 | var target = event.target || event.srcElement;
147 | if (target.nodeName.toLowerCase() === 'li') {
148 | console.info(target.innerHTML)
149 | }
150 | }
151 | ```
152 |
153 | ## 4.写出输出值,并解释为什么。
154 |
155 | ```js
156 | function test(m) {
157 | m = { v: 5 }
158 | }
159 | var m = { k: 5 };
160 | test(m);
161 | console.info(m.v);
162 | ```
163 |
164 | 这道题目的变种是这样的:
165 |
166 | ```js
167 | function test(m) {
168 | m.v = 5
169 | }
170 | var m = { k: 5 };
171 | test(m);
172 | console.info(m.v);
173 | ```
174 |
175 | 理解按值传递和重新开辟内存空间
176 |
177 | ## 5.请写出代码执行结果,并解释为什么。
178 |
179 | ```js
180 | function yideng() {
181 | console.info(1);
182 | }
183 | (function () {
184 | if (false) {
185 | function yideng() {
186 | console.info(2);
187 | }
188 | }
189 | yideng();
190 | })();
191 | ```
192 |
193 | 涉及知识点:
194 | 1. JavaScript深刻理解函数提升
195 | 2. 函数提升的过程中会把函数声明转为函数表达式
196 |
197 | ## 6.(编程题)请用一句话算出0~100分数之间学生的学生等级,如90~100输出为1等生、80~90为2等生以此类推。不允许使用if switch等。
198 |
199 | ## 7.请用一句话遍历变量a。(禁止用for已知var a = "abc")
200 |
201 | ```js
202 | var a = "abcd";
203 | Object.keys(a).forEach(function (key) {
204 | console.log(key, a[key]);
205 | });
206 |
207 | Array.prototype.slice.call
208 |
209 | [...a].forEach
210 | ```
211 |
212 | ## 8.(编程题)请在下面写出JavaScript面向对象编程的混合式继承。并写出ES6版本的继承。要求:汽车是父类,Cruze是子类。父类有颜色、价格属性,有售卖的方法。Cruze子类实现父类颜色是红色,价格是140000,售卖方法实现输出如下语句:将红色的Cruze卖给了小王,价格是14万。
213 |
214 | ```js
215 | function Car(color, sale) {
216 | this.color = color;
217 | this.sale = sale;
218 | }
219 | Car.prototype.sell = function () {
220 | console.info(`There a ${this.color} car is selling.`);
221 | }
222 |
223 | function Cruze(color, sale) {
224 | Car.call(this, color, sale);
225 | }
226 |
227 | function inherite(surperType, subType) {
228 | let prototype = Object.create(surperType.prototype);
229 | prototype.constructor = subType;
230 | subType.prototype = prototype;
231 | }
232 |
233 | inherite(Car, Cruze);
234 |
235 | var cruze = new Cruze('red', 140000);
236 | cruze.sell();
237 | ```
238 |
239 | ## 9.请你写出如何利用ECMAScript6/7(小demo)优化多步异步嵌套的代码?
240 |
241 | ``` js
242 | function runAsync1() {
243 | var p = new Promise(function (resolve, reject) {
244 | //做一些异步操作
245 | setTimeout(function () {
246 | console.log('异步任务1执行完成');
247 | resolve('随便什么数据1');
248 | }, 1000);
249 | });
250 | return p;
251 | }
252 | function runAsync2() {
253 | var p = new Promise(function (resolve, reject) {
254 | //做一些异步操作
255 | setTimeout(function () {
256 | console.log('异步任务2执行完成');
257 | resolve('随便什么数据2');
258 | }, 2000);
259 | });
260 | return p;
261 | }
262 | function runAsync3() {
263 | var p = new Promise(function (resolve, reject) {
264 | //做一些异步操作
265 | setTimeout(function () {
266 | console.log('异步任务3执行完成');
267 | resolve('随便什么数据3');
268 | }, 2000);
269 | });
270 | return p;
271 | }
272 |
273 | runAsync1()
274 | .then(function (data) {
275 | console.log(data);
276 | return runAsync2();
277 | })
278 | .then(function (data) {
279 | console.log(data);
280 | return runAsync3();
281 | })
282 | .then(function (data) {
283 | console.log(data);
284 | });
285 | ```
286 |
287 | ## 10.(仔细思考)写出如下代码的执行结果,并解释为什么。
288 |
289 | ```js
290 | var length = 10;
291 | function fn() {
292 | console.info(this.length);
293 | }
294 | var yideng = {
295 | length: 5,
296 | method: function (fn) {
297 | fn();
298 | arguments[0]();
299 | }
300 | };
301 | yideng.method(fn, 1);
302 | ```
--------------------------------------------------------------------------------
/es5-senior/article/javascript-iife.md:
--------------------------------------------------------------------------------
1 | # JavaScript中立即执行函数详解
2 |
3 | 本文来自网上一篇很好的译文。
4 | 译文 :[http://www.cnblogs.com/zichi/p/4401755.html](http://www.cnblogs.com/zichi/p/4401755.html)
5 | 原文 :[http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife](http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife)
6 |
7 | ## 我们要说的到底是什么?
8 |
9 | 在javascript中,每一个函数在被调用的时候都会创建一个执行上下文,在该函数内部定义的变量和函数只能在该函数内部被使用,而正是因为这个上下文,使得我们在调用函数的时候能创建一些私有变量。
10 |
11 | ``` js
12 | // makeCounter函数返回的是一个新的函数,该函数对makeCounter里的局部变量i享有使用权
13 | function makeCounter() {
14 | // i只是makeCounter函数内的局部变量
15 | var i = 0;
16 | return function () {
17 | console.log(++i);
18 | };
19 | }
20 | // 注意counter和counter2是不同的实例,它们分别拥有自己范围里的i变量
21 | // 注意这里有闭包,变量没有被回收。
22 | var counter = makeCounter();
23 | counter(); // 1
24 | counter(); // 2
25 | var counter2 = makeCounter();
26 | counter2(); // 1
27 | counter2(); // 2
28 | i;
29 | // Uncaught ReferenceError: i is not defined
30 | // 报错,i没有定义,它只是makeCounter内部的局部变量
31 | ```
32 |
33 | 很多情况下我们并不需要像以上代码一样初始化很多实例,甚至有时候并不需要返回值。
34 |
35 | ### 问题的核心
36 |
37 | 现在我们定义了一个函数(`function foo(){}` 或者 `var foo = function(){}`),函数名后加上一对小括号即可完成对该函数的调用,比如下面的代码:
38 |
39 | ``` js
40 | var foo = function(){ /* code */ };
41 | foo();
42 | ```
43 |
44 | 接着我们来看下面的代码:
45 |
46 | ``` js
47 | function(){ /* code */ }(); // SyntaxError: Unexpected token (
48 | ```
49 |
50 | 报错了,这是为何?这是因为在javascript代码解释时,当遇到function关键字时,会默认把它当做是一个函数声明,而不是函数表达式,如果没有把它显视地表达成函数表达式,就报错了,因为函数声明需要一个函数名,而上面的代码中函数没有函数名。(以上代码,也正是在执行到第一个左括号(时报错,因为(前理论上是应该有个函数名的。)
51 |
52 | ### 一波未平一波又起
53 |
54 | 有意思的是,如果我们给它函数名,然后加上()立即调用,同样也会报错,而这次报错原因却不相同:
55 |
56 | ``` js
57 | function foo(){ /* code */ }(); // SyntaxError: Unexpected token )
58 | ```
59 |
60 | 为什么会这样?在一个表达式后面加上括号,表示该表达式立即执行;而如果是在一个语句后面加上括号,该括号完全和之前的语句不搭嘎,而只是一个分组操作符,用来控制运算中的优先级(小括号里的先运算)。所以以上代码等价于
61 |
62 | ```js
63 | function foo(){ /* code */ }
64 | (); // SyntaxError: Unexpected token )
65 | ```
66 |
67 | 相当于先声明了一个叫foo的函数,之后进行()内的表达式运算,但是()(分组操作符)内的表达式不能为空,所以报错。(以上代码,也就是执行到右括号时,发现表达式为空,所以报错)。
68 |
69 | 如果想要了解更多,可以参考[ECMA-262-3 in detail. Chapter 5. Functions](http://dmitrysoshnikov.com/ecmascript/chapter-5-functions/#question-about-surrounding-parentheses)
70 |
71 | ## 立即执行函数
72 |
73 | 看到这里,相信你一定迫不及待地想知道究竟如何做了吧,其实很简单,只需要用括号全部括起来即可,比如下面这样:
74 |
75 | ```js
76 | (function(){ /* code */ }());
77 | ```
78 |
79 | 为什么这样就能立即执行并且不报错呢?因为在javascript里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(),然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。
80 |
81 | 而立即执行函数并非只有上面的一种写法,写法真是五花八门:
82 |
83 | ``` js
84 | // 最常用的两种写法
85 | (function () { /* code */ }()); // 老道推荐写法
86 | (function () { /* code */ })(); // 当然这种也可以
87 | [function () { /* code */ }()];
88 |
89 | // 括号和JS的一些操作符(如 = && || ,等)可以在函数表达式和函数声明上消除歧义
90 | // 如下代码中,解析器已经知道一个是表达式了,于是也会把另一个默认为表达式
91 | // 但是两者交换则会报错
92 | var i = function () { return 10; }();
93 | true && function () { /* code */ }();
94 | 0, function () { /* code */ }();
95 |
96 | // 如果你不怕代码晦涩难读,也可以选择一元运算符
97 | ! function () { /* code */ }();
98 | ~ function () { /* code */ }();
99 | - function () { /* code */ }();
100 | + function () { /* code */ }();
101 |
102 | // 还有一些关键字
103 | delete function( ) { } ( );
104 | typeof function( ) { } ( );
105 | void function( ) { } ( );
106 |
107 | // 你也可以这样
108 | new function () { /* code */ }
109 | new function () { /* code */ }() // 带参数
110 | ```
111 |
112 | 无论何时,给立即执行函数加上括号是个好习惯
113 | 通过以上的介绍,我们大概了解通过()可以使得一个函数表达式立即执行。
114 |
115 | 有的时候,我们实际上不需要使用()使之变成一个函数表达式,啥意思?比如下面这行代码,其实不加上()也不会保错:
116 |
117 | ```js
118 | var i = function(){ return 10; }();
119 | ```
120 |
121 | 但是我们依然推荐加上():
122 |
123 | ```js
124 | var i = (function(){ return 10; }());
125 | ```
126 |
127 | 为什么?因为我们在阅读代码的时候,如果function内部代码量庞大,我们不得不滚动到最后去查看function(){}后是否带有()来确定i值是个function还是function内部的返回值。所以为了代码的可读性,请尽量加上()无论是否已经是表达式。
128 |
129 | 立即执行函数与闭包的暧昧关系
130 | 立即执行函数能配合闭包保存状态。
131 |
132 | 像普通的函数传参一样,立即执行函数也能传参数。如果在函数内部再定义一个函数,而里面的那个函数能引用外部的变量和参数(闭包),利用这一点,我们能使用立即执行函数锁住变量保存状态。
133 |
134 | ```js
135 | // 并不会像你想象那样的执行,因为i的值没有被锁住
136 | // 当我们点击链接的时候,其实for循环已经执行完了
137 | // 于是在点击的时候i的值其实已经是elems.length了
138 | var elems = document.getElementsByTagName( 'a' );
139 |
140 | for ( var i = 0; i < elems.length; i++ ) {
141 |
142 | elems[ i ].addEventListener( 'click', function(e){
143 | e.preventDefault();
144 | alert( 'I am link #' + i );
145 | }, 'false' );
146 |
147 | }
148 |
149 |
150 | // 这次我们得到了想要的结果
151 | // 因为在立即执行函数内部,i的值传给了lockedIndex,并且被锁在内存中
152 | // 尽管for循环结束后i的值已经改变,但是立即执行函数内部lockedIndex的值并不会改变
153 | var elems = document.getElementsByTagName( 'a' );
154 |
155 | for ( var i = 0; i < elems.length; i++ ) {
156 |
157 | (function( lockedInIndex ){
158 |
159 | elems[ i ].addEventListener( 'click', function(e){
160 | e.preventDefault();
161 | alert( 'I am link #' + lockedInIndex );
162 | }, 'false' );
163 |
164 | })( i );
165 |
166 | }
167 |
168 |
169 | // 你也可以这样,但是毫无疑问上面的代码更具有可读性
170 | var elems = document.getElementsByTagName( 'a' );
171 |
172 | for ( var i = 0; i < elems.length; i++ ) {
173 |
174 | elems[ i ].addEventListener( 'click', (function( lockedInIndex ){
175 | return function(e){
176 | e.preventDefault();
177 | alert( 'I am link #' + lockedInIndex );
178 | };
179 | })( i ), 'false' );
180 |
181 | }
182 | ```
183 |
184 | 其实上面代码的lockedIndex也可以换成i,因为两个i是在不同的作用域里,所以不会互相干扰,但是写成不同的名字更好解释。以上便是立即执行函数+闭包的作用。
185 |
186 | 我为什么更愿意称它是“立即执行函数”而不是“自执行函数”
187 | IIFE的称谓在现在似乎已经得到了广泛推广(不知道是不是原文作者的功劳?),而原文写于10年,似乎当时流行的称呼是自执行函数(Self-executing anonymous function),接下去作者开始为了说明立即执行函数的称呼好于自执行函数的称呼开始据理力争,有点咬文嚼字,不过也蛮有意思的,我们来看看作者说了些什么。
188 |
189 | ```js
190 | // 这是一个自执行函数,函数内部执行的是自己,递归调用
191 | function foo() { foo(); }
192 |
193 | // 这是一个自执行匿名函数,因为它没有函数名
194 | // 所以如果要递归调用自己的话必须用arguments.callee
195 | var foo = function() { arguments.callee(); };
196 |
197 | // 这可能也算是个自执行匿名函数,但仅仅是foo标志引用它自身
198 | // 如果你将foo改变成其它的,你将得到一个used-to-self-execute匿名函数
199 | var foo = function() { foo(); };
200 |
201 | // 有些人叫它自执行匿名函数,尽管它没有执行自己,只是立即执行而已
202 | (function(){ /* code */ }());
203 |
204 | // 给函数表达式添加了标志名称,可以方便debug
205 | // 但是一旦添加了标志名称,这个函数就不再是匿名的了
206 | (function foo(){ /* code */ }());
207 |
208 | // 立即执行函数也可以自执行,不过不常用罢了
209 | (function(){ arguments.callee(); }());
210 | (function foo(){ foo(); }());
211 | ```
212 |
213 | 我的理解是作者认为自执行函数是函数内部调用自己(递归调用),而立即执行函数就如字面意思,该函数立即执行即可。其实现在也不用去管它了,就叫IIFE好了。
214 |
215 | 最后的旁白:模块模式
216 | 立即执行函数在模块化中也大有用处。用立即执行函数处理模块化可以减少全局变量造成的空间污染,构造更多的私有变量。
217 |
218 | ```js
219 | // 创建一个立即执行的匿名函数
220 | // 该函数返回一个对象,包含你要暴露的属性
221 | // 如下代码如果不使用立即执行函数,就会多一个属性i
222 | // 如果有了属性i,我们就能调用counter.i改变i的值
223 | // 对我们来说这种不确定的因素越少越好
224 |
225 | var counter = (function(){
226 | var i = 0;
227 |
228 | return {
229 | get: function(){
230 | return i;
231 | },
232 | set: function( val ){
233 | i = val;
234 | },
235 | increment: function() {
236 | return ++i;
237 | }
238 | };
239 | }());
240 |
241 | // counter其实是一个对象
242 |
243 | counter.get(); // 0
244 | counter.set( 3 );
245 | counter.increment(); // 4
246 | counter.increment(); // 5
247 |
248 | counter.i; // undefined i并不是counter的属性
249 | i; // ReferenceError: i is not defined (函数内部的是局部变量)
250 | ```
251 |
--------------------------------------------------------------------------------
/es5-senior/essence2.md:
--------------------------------------------------------------------------------
1 | # 闭包、作用域、原型链
2 |
3 | ```js
4 | if(!('userName' in window)) {
5 | var userName = 'shaogucheng';
6 | }
7 | console.log(userName);
8 | ```
9 |
10 | ```js
11 | var obj = {
12 | user: 'shaogucheng',
13 | getName: function() {
14 | return this.user;
15 | }
16 | }
17 | var getNameFn = obj.getName;
18 |
19 | console.log(getNameFn());
20 | console.log(obj.getName());
21 | ```
22 |
23 | ## 作用域
24 |
25 | 作用域有大有小:
26 | 1. 程序级
27 | 2. 文件级
28 | 3. 函数级
29 | 4. 块级
30 |
31 | ## JavaScript的作用域
32 |
33 | * 全局作用域
34 | * 函数作用域
35 | * 块级作用域(ES6)
36 |
37 | ```js
38 | var global = 1;
39 | function doSomething() {
40 | var inner = 2;
41 | globalA = 3;
42 | }
43 | doSomething();
44 | console.log(global);
45 | console.log(globalA);
46 | console.log(inner);
47 | ```
48 |
49 | ## JavaScript的作用域链
50 |
51 | 什么是作用域链?
52 |
53 | 执行环境(execution context,为简单起见,有时也称为“环境”)是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
54 |
55 | 全局执行环境是最外围的一个执行环境。根据ECMAscript实现所在的宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或者浏览器——时才会销毁)。
56 |
57 | 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAscript程序中的执行流正是由这个方便的机制控制着。
58 | 当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的是保证执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在的环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。
59 |
60 | 在JavaScript中,函数也是对象,函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性[[scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
61 |
62 | ```js
63 | var x = 1;
64 | function foo() {
65 | var y = 1 + x;
66 | return function() {
67 | var z = 1 + y;
68 | return z;
69 | }
70 | }
71 | foo()();
72 | ```
73 |
74 | ```js
75 | var test = 'aaa';
76 | function doSomething() {
77 | console.log(test); // 内部变量提升,覆盖全局变量
78 | var test = 'bbb'; // 变量声明且赋值,这里覆盖了全局变量
79 | console.log(test) // 有值的变量
80 | }
81 | doSomething();
82 | console.log(test); // 访问的是全局变量
83 | ```
84 |
85 | 执行顺序
86 | 1. 声明函数
87 | 2. 调用函数
88 | 3. 声明变量
89 | 4. console.log(test)
90 | 5. test变量赋值为bbb
91 | 6. console.log(test);
92 |
93 | ## JavaScript中的this关键字
94 |
95 | this指向哪里?
96 |
97 | 在JavaScript中,this指向函数执行时的当前对象。
98 |
99 | this的使用场景
100 |
101 | * 普通函数中:
102 | * 严格模式:undefined
103 | * 非严格模式:全局对象(window)
104 | * 构造函数中:对象的实例
105 | * 对象方法:对象本身
106 |
107 | ### call apply bind
108 |
109 | ## 原型对象是什么?
110 |
111 | * 每个函数都有一个prototype的对象属性
112 | * 每个对象都有一个__proto__属性,该属性指向其父类的prototype对象
113 |
114 | ### 原型对象中的constructor
115 |
116 | 默认指向本身
117 |
118 | ```js
119 | Person.prototype.constructor === Person;
120 | Function.prototype.constructor === Function;
121 | Object.prototype.constructor === Object;
122 | Object,constructor === Function;
123 | ```
124 |
125 | ```js
126 | function make(num) {
127 | return function() {
128 | return num;
129 | }
130 | }
131 | var arr = [make(0), make(1), make(2)];
132 | console.log(arr[0]());
133 | console.log(arr[1]());
134 | console.log(arr[2]());
135 | ```
136 |
137 | ```js
138 | var name = 'global';
139 | function A(name) {
140 | console.log(name);
141 | this.name = name;
142 | var name = '1';
143 | }
144 | A.prototype.name = '2';
145 | var a = new A('3');
146 | console.log(a.name);
147 | delete a.name;
148 | console.log(a.name);
149 | ```
150 |
151 | 下面这段代码太有意思了。
152 |
153 | ```js
154 | function fun(n, o) {
155 | console.log(o);
156 | return {
157 | fun: function(m) {
158 | return fun(m, n)
159 | }
160 | }
161 | }
162 |
163 | var a = fun(0);
164 | a.fun(1);
165 | a.fun(2);
166 | var b = fun(0).fun(1).fun(2).fun(3);
167 | var c = fun(0).fun(1);
168 | c.fun(2);
169 | c.fun(3);
170 | ```
171 |
172 | ```js
173 | window.Glog = function(msg){
174 | console.log(msg)
175 | }
176 | // this was added before the main closure.
177 |
178 | (function(win){
179 | //the former closure that contains the main javascript logic;
180 | })(window)
181 | ```
182 |
183 | ```js
184 | this.m = 100;
185 | var obj = {
186 | m: 1000,
187 | test: function () {
188 | console.log(this.m);
189 | return function () {
190 | console.log(this.m);
191 | }
192 | }
193 | }
194 | (obj.test())();
195 | ```
196 |
197 | ```js
198 | var s = {
199 | p: function() {
200 | return function() {
201 | console.log('enen')
202 | }
203 | }
204 | }
205 | (s.p())()
206 | ```
207 |
208 | The ECMAScript specification has specific rules for automatic semicolon insertion, however in this case a semicolon isn't automatically inserted because the parenthesised expression that begins on the next line can be interpreted as an argument list for a function call.
209 |
210 | This means that without that semicolon, the anonymous window.Glog function was being invoked with a function as the msg parameter, followed by (window) which was subsequently attempting to invoke whatever was returned.
211 |
212 | This is how the code was being interpreted:
213 |
214 | ```js
215 | window.Glog = function(msg) {
216 | console.log(msg);
217 | }; // <--- Add this semicolon
218 |
219 | (function(win) {
220 | // ...
221 | })(window);
222 |
223 | window.Glog = function(msg) {
224 | console.log(msg);
225 | }(function(win) {
226 | // ...
227 | })(window);
228 | ```
229 |
230 | Everything works fine when I wrote the js logic in a closure as a single js file, as:
231 |
232 | ``` js
233 | (function(win){
234 | //main logic here
235 | win.expose1 = ....
236 | win.expose2 = ....
237 | })(window)
238 | ```
239 |
240 | but when I try to insert a logging alternative function before that closure in the same js file,
241 |
242 | ```js
243 | window.Glog = function(msg){
244 | console.log(msg)
245 | }
246 | // this was added before the main closure.
247 |
248 | (function(win){
249 | //the former closure that contains the main javascript logic;
250 | })(window)
251 | ```
252 |
253 | it complains that there is a TypeError:
254 |
255 | ``` console
256 | Uncaught TypeError: (intermediate value)(...) is not a function
257 | ```
258 |
259 | ```js
260 | window.Glog = function(msg) {
261 | console.log(msg);
262 | }; // <--- Add this semicolon
263 |
264 | (function(win) {
265 | // ...
266 | })(window);
267 | ```
268 |
269 | The ECMAScript specification has specific rules for automatic semicolon insertion, however in this case a semicolon isn't automatically inserted because the parenthesised expression that begins on the next line can be interpreted as an argument list for a function call.
270 |
271 | This means that without that semicolon, the anonymous window.Glog function was being invoked with a function as the msg parameter, followed by (window) which was subsequently attempting to invoke whatever was returned.
272 |
273 | This is how the code was being interpreted:
274 |
275 | ```js
276 | window.Glog = function(msg) {
277 | console.log(msg);
278 | }(function(win) {
279 | // ...
280 | })(window);
281 | ```
282 |
283 | ```js
284 | **Error Case:**
285 | var handler = function(parameters) {
286 | console.log(parameters);
287 | }
288 |
289 | (function() { //IIFE
290 | // some code
291 | })();
292 | ```
293 | Output: TypeError: (intermediate value)(intermediate value) is not a function *How to Fix IT -> because you are missing semi colan(;) to separate expressions;
294 |
295 | ```js
296 | **Fixed**
297 | var handler = function(parameters) {
298 | console.log(parameters);
299 | }; // <--- Add this semicolon(if you miss that semi colan ...
300 | //error will occurs )
301 |
302 | (function() { //IIFE
303 | // some code
304 | })();
305 | ```
--------------------------------------------------------------------------------
/functional-programming/article/2019-1-18.md:
--------------------------------------------------------------------------------
1 | # 函数编程基础之——范畴论
2 |
3 | ## 基本概念
4 |
5 | ### 范畴论
6 |
7 | * 数学构造(Mathematical structure)
8 | 在数学上,在集合上的一个构造是一个附加的数学对象,赋予这个集合某种意义。
9 |
10 | * 范畴论(category theory)
11 | 范畴论的目的是:规范化数学构造。
12 | 方法为:使用带标签的有向图。
13 | 研究内容:各种数学结构之间的关系。
14 |
15 | ### 范畴(category)
16 |
17 | 一个范畴是一个带标签的有向图,其节点为对象(object),带有标签的有向边为箭头(arrow or morphism)。
18 |
19 | 一个范畴C包含3个数学实体:
20 |
21 | * 对象集合:ob(C)
22 | 每个元素都是一个对象,一个对象又可以认为是一个集合。
23 |
24 | * 态射集合: hom(C)
25 | 态射集合的每个元素是一个态射, `f:a→b`,每个态射f有一个源对象 (source object) a和目标对象(target object)b。
26 | `hom(a,b)` 表示从a到b的所有态射。
27 |
28 | * 性质:二元操作:态射结合(composition of morphisms): ∘
29 | f:a→b,g:b→c 的结合是 g∘f。具有
30 | * 满足结合律(Associativity): h∘(g∘f)=(h∘g)∘f
31 | * 存在恒等态射(identity):
32 | 对于每个对象x,存在一个恒等态射(identity morphism) 1x:x→x,
33 | 其性质为,对于任何态射f:a→b,有1b∘f=f=f∘1a
34 | 恒等态射的含义是:定义了相同关系(equality relation A = B)。
35 | 可以简单的认为是;f(x)=x。
36 |
37 | ### 态射(morphism)
38 |
39 | 态射可以理解为一个函数,在范畴论中,往往表示为一个对象和另一个对象的map关系。
40 | 态射作为函数理解的时候,不用纠结于参数的个数。
41 |
42 | 态射的种类 `(f:a→b)` :
43 |
44 | * 单态射(monomorphism or monic)
45 | 如果f∘g1=f∘g2⟹g1=g2,∀g1,g2:x→a。
46 | 其含义是:不存在两个a中元素 map 到同一个b中的元素。
47 | ∀a1,a2∈A,a1≠a2⟹f(a1)≠f(a2)
48 |
49 | * 满态射(epimorphism or epic)
50 | 如果g1∘f=g2∘f⟹g1=g2,∀g1,g2:b→x。
51 | 其含义是:每一个b中的元素,都在a中有至少一个 source mapper。
52 |
53 | * 双态射(bimorphism)
54 | 即是单态射,有时满态射。
55 |
56 | * 同构(isomorphism)
57 | 如果存在一个同构g:b→a,有f∘g=1b,g∘f=1a。
58 | 其含义是:a,b两个对象的元素存在一对一的 map 关系。
59 | 同构 = 双态射 + 存在逆态射。
60 | g称为逆态射,也是一个同构,g 的逆态射是 f。
61 | 比如:f是加法,g是减法。
62 |
63 | * 自态射(endomorphism)
64 | 表示一个态射源对象和目标对象是同一个, f:a→a。记为:end(a)。
65 |
66 | * 自同构(automorphism)
67 | 如果f既是一种自态射,又是具有同构性。记为:aut(a)。
68 |
69 | * 撤回射(retraction)
70 | 如果存在一个f的右逆,也就是说,如果存在: $g : b \to a, f \circ g = 1_b。
71 | f 是另一个态射g的撤回射,其含义是:g 可以通过f找到 source element。f必定是一个满态射(epimorphism)。
72 |
73 | * 部分射(section)
74 | 如果f的左逆是存在的,也就是说,如果存在: $g : b \to a, g \circ f = 1_a。
75 | f 是另一个态射g的部分射,其含义是:f 确定了g的同构部分。f必定是一个单态射(monomorphism)。
76 | 是不是可以理解为f的g应用的一个条件???
77 |
78 | * 同态(homomorphism)
79 | 同态(homomorphism)是一个态射,表示一个数学结构\mathcal{A}(C, , e)到另一个数学结构\mathcal{B}(C', ', e')的map关系,并且维持了数学结构上的的每一种操作*。
80 | 同态(homomorphism) f:A→B,有:
81 | f(x∗y)=f(x)∗′f(y)
82 |
83 | 同一种操作在不同的数学结构上定义可以不同。
84 | 比如:指数函数是一个同态。
85 |
86 | f:R→R
87 | f(x)=ex
88 | ex+y=exey→f(x∗y)=f(x)∗f(y)
89 | where
90 | f exponential function is a homomorphism
91 | the source is A=R
92 | the target is B=R
93 | ∗=+ in A
94 | ∗=× in B(1)
95 |
96 | 域(domain)/协域(codomain)
97 | 对于一个态射(morphism) f:S→T。 域(domain)是这个态射的源,协域(codomain)是这个态射目标。
98 |
99 | 范畴的表达
100 | 表达1
101 | C=(Ob(C),HomC(x,y),idx,∘)whereOb(C)∈SetobjectHomC(x,y)∈Setmorphism | x,y∈Ob(C)idx∈HomC(x,x) : identity morphism of x∘:HomC(y,z)×HomC(x,y)→HomC(x,z) : composition formulaIdentity Law:∀x,y∈Ob(C),f:x→yf∘idx=fidy∘f=fAssociative Law:∀w,x,y,z∈Ob(C),h:w→x,g:x→y,f:y→z(h∘g)∘f=h∘(g∘f)∈HomC(w,z)(2)
102 |
103 | ### 函子(functor)
104 |
105 | 函子是范畴之间的map关系。可以理解为范畴之间的态射。
106 |
107 | * 协变(covariant)函子
108 | F:C→D, F包含:
109 | 对于每一个 C 中的对象x,对应一个 D 中的F(x)。
110 | 对于每一个C中的态射f:x→y,对应D的态射F(f):F(x)→F(y)。
111 |
112 | * 逆变(contravariant)函子
113 | 和协变函子类似,只不过态射的方向是从D到C。
114 |
115 | ### 自然转换(Natural transformation)
116 |
117 | 自然转换是两个函子之间的关系。函子描述“自然构造(natural constructions)”,自然转换描述两个这样构造的"自然同态(natural homomorphisms)"。
118 | homomorphism的意思是相同的形状。
119 |
120 | ### 其它
121 |
122 | 本体(olog)
123 | 交换图(commutative diagram)
124 | 通用性质(universal property)
125 | 如果,两个数学结构是同构(isomorphism),那么它们之间就存在通用性质。
126 | 同构意味着两个数学结构X和Y中的元素是存在一一对应。
127 | 那么,Y上的性质(态射)意味着X上存在一个对应的性质(态射)。比如:
128 | X = {A, B, C}
129 | Y = {1, 2, 3}
130 | A --> 1
131 | B --> 2
132 | C --> 3
133 | 如果+(1, 2) = 3,我们也可以认为存在 +(A, B) = C。起点性质。
134 | 如果+(1, 2),我们也可以认为存在 +(C) = (A, B)。起点性质。
135 | 通用性质(universal property)要么是一个起点性质(initial property),要么是一个终点性质(terminal property)。
136 |
137 | 起点性质(initial property)
138 | 唯一存在
139 | U:D→CD{A,Y}C{X,U(A),U(Y),ϕ:X→U(A)}∴initial property:∀f:X→U(Y)∃g,g:A→Y∃U(g),g:U(A)→U(Y)(3)
140 | 终点性质(terminal property)
141 | 唯一存在
142 | U:D→CD{A,Y}C{X,U(A),U(Y),ϕ:U(A)→X}∴terminal property:∀f:U(Y)→X∃g,g:Y→A∃U(g),g:U(Y)→U(A)(4)
143 | 起点性质示例
144 | X=(L:(a,b,c),A:L×L,B:L)Y=(N:(1,2,3),Ay:N×N,By:N,Cy:Ay)ϕ:Cy→Ay=ce.g. (1,2)=(1,2)f:Cy→By=c1+c2e.g. 1+2=3∴∃g:A→Be.g. a+b=c(5)
145 | 终点性质示例
146 | X=(L:(a,b,c),A:L×L,B:L)Y=(N:(1,2,3),Ay:N×N,By:N,Cy:By)ϕ:Ay→Cy=a1+a2e.g. 1+2=3f:By→Cy=be.g. 3=3∴∃g:B→Ae.g. c=a+b(6)
147 | 拉回(pullback)
148 | 推出(pushout)
149 | limit/colimit
150 |
151 | 关系
152 | 二元关系(binary relation)
153 | 一个基于集合X的二元关系,是一个R⊆X×X的子集。
154 |
155 | 等价关系(equivalence relations)
156 | 一个集合X上的等价关系∼,是一个R⊆X×X的子集,具有
157 | 自反性(Reflexivity)
158 | (x,x)∈R
159 | 对称性(Symmetry)
160 | $(x, y) \in R \text{, iff } (y,x)∈R$
161 | 传递性(Transitivity)
162 | $\text{if } (x, y) \in R, (y, z) \in R \text{, then } (x,z)∈R$
163 | 幺半群(monoid)
164 | 幺半群可以代表一个序列或者列表。
165 |
166 | 幺半群(monoid)
167 | 一个幺半群是一个序列(sequence)(M,e,⋆)。M是一个集合,
168 | e∈M是一个元素,成为单位元素(identity element)。
169 | ⋆:M×M→M是一个函数,称为乘法公式(multiplication formula)。
170 | 幺半群具有以下属性:
171 | 同一律(identity law)
172 | m⋆e=m
173 | e⋆m=m
174 | 结合律(associativity law)
175 | (m⋆n)⋆p=m⋆(n⋆p)
176 | 比如:字符串就是一个幺半群,e = "", ⋆ = +。
177 | List in set
178 | 集合X,在X上的List是
179 | (n,f)wheren∈N : the length of the listf:n––→Xn––={1,2,⋯,n}(7)
180 |
181 | 记做:
182 | (n,f)=[f(1),f(2),⋯,f(n)](8)
183 |
184 | 列表单体(free monoid generated by X)
185 | M:=(List(X),[],++)
186 | List(X);集合X的元素列表集合。
187 | []是一个空列表。
188 | ++是连接操作(concatenation)。
189 |
190 | 显示幺半群(presented monoid)
191 |
192 | 显示幺半群的作用是提供了替换方法。
193 | 由有限集合G和等价关系产生的显示幺半群(the monoid presented by generators G and relation (mi,m′i)|1≤i≤n)
194 |
195 | M={M,e,⋆}where{(xmiy∼xm′iy)|x,y∈List(G),1≤i≤n} : equivalence relationM=List(G)/∼e=[]⋆ : concatenating operation(9)
196 |
197 | 循环(cyclic)幺半群
198 |
199 | 循环幺半群的作用是提供了一个环形列表的定义方法。
200 | 循环(cyclic)幺半群是只有一个等价关系的显示幺半群。
201 |
202 | 幺半群行动(monoid actions)
203 | 在集合S上的幺半群(M,e,⋆)的行动为函数:
204 | ↪(10)
205 |
206 | 符号
207 | 术语 English Notation
208 | 单态射 monomorphisms ↪
209 | 满态射 epimorphisms ↠
210 | 同构 isomorphisms →∼
211 | 群(group)
212 | group是一个monoid,并且每个元素都有一个倒数(inverse)。
213 | 推论:倒数具有唯一性。
214 |
215 | 图形(graphs)
216 | 图形(graphs)是由多个顶点(vertex)和顶点之间的箭头(arrow)定义而成。
217 | 路径(path)
218 |
219 | 顺序(order)
220 | 预次序关系(preorder)
221 | 预次序关系(preorder)(S,≤)是一个基于集合S的二元关系R⊆S×S,
222 | R的关系:if (s,s′)∈R,then s≤s′
223 | 并且有
224 | 自反性(Reflexivity): s≤s
225 | 传递性(Transitivity): if s≤s′and s′≤s",then s≤s"
226 | 部分有序(partial order)
227 | 部分有序(partial order)是预次序关系,并且
228 | 反对称性(Antisymmetry)
229 | 如果s <= s', 并且 s' <= s, 则 s = s'。
230 | 线性有序(linear order)
231 | 线性有序(linear order)是部分有序,并且
232 | 可比较性(Comparability)
233 | 要么 s <= s', 要么 s' <= s。
234 | 派系(clique)
235 | 派系(clique)中的每两个点都是毗邻的(adjacent)。
236 | clique≐S′where(S,⩽) is a preorderS′⊆Sa⩽b,∀a,b∈S′(11)
237 |
238 | meet 和 join
239 | (S,⩽)是一个preorder。s,t∈S
240 | s和t的meet(the biggest thing smaller than both)是一个元素w∈S,
241 | 表示为:w≅s∧t
242 | 具有:
243 | w⩽sw⩽tx⩽w | ∀x∈S,x⩽s & x⩽t(12)
244 |
245 | s和t的join(the smallest thing bigger than both)是一个元素w∈S,
246 | 表示为:w≅s∨t
247 | 具有:
248 | s⩽wt⩽ww⩽x | ∀x∈S,s⩽x & t⩽x(13)
249 |
250 | meet 和 join 不一定是唯一的。任何两个meet一定在同一个派系内。
251 |
252 | digraph finite_state_machine {
253 | rankdir=LR;
254 | size="8,5"
255 |
256 | node [shape = doublecircle]; S;
257 | node [shape = point ]; qi
258 |
259 | node [shape = circle];
260 | qi -> S;
261 | S -> q1 [ label = "a" ];
262 | S -> S [ label = "a" ];
263 | q1 -> S [ label = "a" ];
264 | q1 -> q2 [ label = "ddb" ];
265 | q2 -> q1 [ label = "b" ];
266 | q2 -> q2 [ label = "b" ];
267 | }
268 | 反顺序(Opposite order)
269 | S:=(S,⩽)是一个预次序(preorder),则反顺序Sop:=(s,⩽op),
270 | 有:
271 | s⩽s′⟺s′⩽s
272 | 次序的态射(morphism of orders)
273 | 从S:=(S,⩽)到S′:=(S′,⩽′)次序的态射f,表示为f:S→S′。
274 | if s1⩽s2, then f(s1)⩽f(s2)(14)
275 |
276 | Database vs Graph
277 | 路径等价声明(path equivalence declaration)
278 | 图形G:=(V,A,src,tgt), PathG为G中所有路径的集合。
279 | p≃q|p,g∈PathG为路径等价声明,表示p,g有相同的起点和终点。
280 | 在G上的一个一致(congruence) 是一个在PathG上的 等价关系≃,具有:
281 | ≃是一个等价关系.
282 | 如果 p≃q,则src(p) = src(q).
283 | 如果 p≃q,则tgt(p) = tgt(q).
284 | 假设 p,q:b→c是路径,并且m:a→b。如果 p≃q,则mp≃mq.
285 | 假设 p,q:b→c是路径,并且n:b→c。如果 p≃q,则pn≃qn.
286 |
287 | ≃ 是一个集合,定义了图形上的所有约束。
288 |
289 | 引理:假设psimeqq:a→b,r≃s:b→c,则pr≃qs。
290 |
291 | Database Schema
292 | Database Schema C:=(G,≃),G是一个图形,≃是G上的一致。
293 |
294 | Olog = Database Schema
295 |
296 | 实例(instance)
297 | 一个顶点对应的集合,和出入箭头的所有路径上的节点集合。
298 | (PK,FK):C→Set is an instancewhereC=(G,≃)G=(V,A,src,tgt)PK:V→Set, one set for one vertexFK(a):PK(v)→PK(w) | v=src(a),w=tgt(a),∀a∈A(15)
299 |
300 | 路径法则(Law 1 - Path through a database)
301 | FK(am)∘⋯∘FK(a1)(x)=FK(a′n)∘⋯∘FK(a′1)(x)=PK(w),∀x∈PK(v)wherep=va1a2…am:v→wq=va′1a′2…a′n:v→wp≃q(16)
302 |
303 | 范畴化
304 | 如何将一个monoid/order group/group index通过一个函子转化成一个范畴。
305 | Database Schema present categories
--------------------------------------------------------------------------------
/es5-senior/article/this.md:
--------------------------------------------------------------------------------
1 | # JavaScript探究this奥秘之旅
2 |
3 | ## 一、this的理解
4 |
5 | > 我们知道,关于this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。
6 | > --------《JavaScript高级程序设计》
7 | > **核心提炼:函数中的this总指向调用它的对象。**
8 |
9 | ## 二、错误认识
10 |
11 | **1、指向自身**
12 | 人们很容易把this理解成指向函数自身,其实this的指向在函数定义阶段是无法确定的,只有函数执行时才能确定this到底指向谁,实际上this的最终指向是调用它的那个对象。
13 |
14 | **2、指向函数的作用域**
15 | 对this的第二种误解就是this指向函数的作用域,
16 | window,在chrome console环境里是没有问题的,全局声明的函数放在了window下,foo函数里面的this代指的是window对象,在全局环境中并没有声明变量a,因此在bar函数中的this.a自然没有定义,输出undefined。
17 |
18 | nodejs,在node环境下,声明的function不会放在global全局对象下,因此在foo函数里调用this.bar函数会报 TypeError: this.bar is not a function错误,调用bar函数,要省去前面的this。
19 |
20 | ## 三、this绑定规则
21 |
22 | this有四种绑定规则
23 | * 默认绑定
24 | * 隐式绑定
25 | * 显示绑定
26 | * new绑定
27 |
28 | ### 3.1默认绑定
29 |
30 | 当函数调用属于独立调用(不带函数引用的调用),无法调用其他的绑定规则,我们给它一个称呼“默认绑定”,在非严格模式下绑定到全局对象,在使用了严格模式(use strict)下绑定到undefined。
31 | 当一个函数没有明确的调用对象的时候,也就是单纯作为独立函数调用的时候,将对函数的this使用默认绑定:绑定到全局的window对象
32 |
33 | 默认绑定引发一个很深的疑问!?
34 | 函数的调用必须绑定对象,默认绑定就是绑定到window对象。
35 |
36 | ``` js
37 | function default() {
38 | console.log(this === window)
39 | }
40 | default(); // 输出true
41 |
42 | function default() {
43 | function inner() {
44 | console.log(this === window)
45 | }
46 | inner(); // 独立函数调用
47 | }
48 | default(); // 输出true
49 |
50 | var obj = {
51 | outer: function () {
52 | function inner() {
53 | console.log(this === window)
54 | }
55 | inner(); // 独立函数调用
56 | }
57 | }
58 | obj.outer(); //输出 true
59 | ```
60 |
61 | 【总结】 凡事函数作为独立函数调用,无论它的位置在哪里,它的行为表现,都和直接在全局环境中调用无异
62 |
63 | ### 3.2隐式绑定
64 |
65 | 当函数被一个对象“包含”的时候,我们称函数的this被隐式绑定到这个对象里面了,这时候,通过this可以直接访问所绑定的对象里面的其他属性,
66 |
67 | ``` js
68 |
81 | ```
82 |
83 | outer函数和inner函数被定义在obj对象的内部和外部而有任何区别,也就是说在上述隐式绑定的两种形式下,outer通过this还是可以访问到obj内的a属性,这告诉我们:
84 |
85 | 1. this是动态绑定的,或者说是在代码运行期绑定而不是在书写期
86 | 2. 函数于对象的独立性, this的传递就存在丢失问题
87 |
88 | > 隐式绑定下,作为对象属性的函数,对于对象来说是独立的。基于this动态绑定的特点,写在对象内部,作为对象属性的函数,对于这个对象来说是独立的。(函数并不被这个外部对象所“完全拥有”)
89 |
90 | 在上文中,函数虽然被定义在对象的内部中,但它和“在对象外部声明函数,然后在对象内部通过属性名称的方式取得函数的引用”,这两种方式在性质上是等价的(而不仅仅是效果上)
91 |
92 | 在一串对象属性链中,this绑定的是最内层的对象
93 |
94 | **隐式绑定的问题!!!**
95 | 当进行隐式绑定时,如果进行一次引用赋值或者传参操作,会造成this的丢失,使this绑定到全局对象中去。
96 |
97 | 1引用赋值丢失
98 |
99 | ``` js
100 | function thisTo() {
101 | console.log(this.a);
102 | }
103 | var data = {
104 | a: 2,
105 | foo: thisTo //通过属性引用this所在函数
106 | };
107 | var a = 3;//全局属性
108 |
109 | var newData = data.foo; //这里进行了一次引用赋值
110 | newData(); // 3
111 | ```
112 |
113 | 原理:因为newData实际上引用的是foo函数本身,这就相当于:var newData = thisTo;data对象只是一个中间桥梁,data.foo只起到传递函数的作用,所以newData跟data对象没有任何关系。而newData本身又不带a属性,最后a只能指向window。
114 |
115 | 2传参丢失
116 |
117 | ```js
118 | function thisTo() {
119 | console.log(this.a);
120 | }
121 | var data = {
122 | a: 2,
123 | foo: thisTo //通过属性引用this所在函数
124 | };
125 | var a = 3;//全局属性
126 |
127 | setTimeout(data.foo, 100);// 3
128 | ```
129 |
130 | 这就是本文开始的那个题目。所谓传参丢失,就是在将包含this的函数作为参数在函数中传递时,this指向改变。setTimeout函数的本来写法应该是setTimeout(function(){......},100);100ms后执行的函数都在“......”中,可以将要执行函数定义成var fun = function(){......},即:setTimeout(fun,100),100ms后就有:fun();所以此时此刻是data.foo作为一个参数,是这样的:setTimeout(thisTo,100);100ms过后执行thisTo(),实际道理还跟1.1差不多,没有调用thisTo的对象,this只能指向window。
131 |
132 | 3隐式丢失解决方法
133 | 为了解决隐式丢失(隐式丢失专用)的问题,ES5专门提供了bind方法,bind()会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。(这个bind可跟$(selector).bind('click',function(){......})的用法不同)
134 |
135 | ``` js
136 | function thisTo() {
137 | console.log(this.a);
138 | }
139 | var data = {
140 | a: 2
141 | };
142 | var a = 3;
143 | var bar = thisTo.bind(data);
144 | console.log(bar()); //2
145 | ```
146 |
147 | 2.间接引用
148 | 间接引用是指一个定义对象的方法引用另一个对象存在的方法,这种情况下会使得this指向window:
149 |
150 | ``` js
151 | function thisTo() {
152 | console.log(this.a);
153 | }
154 | var data = {
155 | a: 2,
156 | foo: thisTo
157 | };
158 | var newData = {
159 | a: 3
160 | }
161 | var a = 4;
162 | data.foo(); //2
163 | (newData.foo = data.foo)() //4
164 | newData.foo(); //3
165 | ```
166 |
167 | 这里为什么`(newData.foo=data.foo)()`的结果是4,与newData.foo()的结果不一样呢?按照正常逻辑的思路,应该是先对newData.foo赋值,再对其进行调用,也就是等价于这样的写法:newData.foo=data.foo;newData.foo();然而这两句的输出结果就是3,这说明两者不等价。
168 |
169 | 接着,当我们console.log(newData.foo=data.foo)的时候,发现打印的是thisTo这个函数,函数后立即执行括号将函数执行。这句话中,立即执行括号前的括号中的内容可单独看做一部本,该部分虽然完成了赋值操作,返回值却是一个函数,该函数没有确切的调用者,故而立即执行的时候,其调用对象不是newData,而是window。下一句的newData.foo()是在给newData添加了foo属性后,再对其调用foo(),注意这次的调用对象为newData,即我们上面说的隐式绑定的this,结果就为3。
170 |
171 | 1、为函数调用创建别名
172 |
173 | ``` js
174 | function foo() {
175 | console.log(this.a);
176 | }
177 | var obj = {
178 | a: 2,
179 | foo: foo
180 | }
181 | var bar = obj.foo;
182 | var a = "window";
183 | bar()//window
184 | ```
185 |
186 | 虽然bar是obj.foo的一个引用,但是bar引用的是foo函数的本身,此时的bar()其实就是一个不带任何修饰的函数调用,所以应用了默认绑定,this为全局
187 |
188 | 2、传入回调函数
189 |
190 | ``` js
191 | function foo() {
192 | console.log(this.a);
193 | }
194 | function doFoo(fn) {
195 | fn();
196 | }
197 | var obj = {
198 | a: 2,
199 | foo: foo
200 | }
201 | var a = "window";
202 | doFoo(obj.foo)//window
203 | ```
204 | 参数传递其实就是隐式赋值。相当于var fn=obj.foo,与创建别名的结果一样,应用了默认绑定,应该注意的是,return返回一个函数时,也是应用了默认绑定
205 |
206 | 3、传入语言内置的函数
207 |
208 | ``` js
209 | function foo() {
210 | console.log(this.a);
211 | }
212 | var obj = {
213 | a: 2,
214 | foo: foo
215 | }
216 | var a = "window";
217 | setTimeout(obj.foo, 100)//window
218 | ```
219 |
220 | 在JavaScript内部,内置函数setTimeout函数实现可以看作
221 |
222 | ``` js
223 | function setTimeout(fn, delay) {
224 | //等待delay毫秒
225 | fn();
226 | }
227 | ```
228 |
229 | 最后,还剩下的疑问就是:为什么会出现隐式丢失的问题?
230 |
231 | ### 3.3显式绑定
232 |
233 | 上面我们提到了this的隐式绑定所存在的this绑定丢失的问题,也就是对于 “ fireInGrobal = obj.fire”fireInGrobal调用和obj.fire调用的结果是不同的,
234 | 因为这个函数赋值的过程无法把fire所绑定的this也传递过去。这个时候,call函数就派上用场了
235 |
236 | call的基本使用方式: fn.call(object)
237 | fn是你调用的函数,object参数是你希望函数的this所绑定的对象。
238 | fn.call(object)的作用:
239 | 1.即刻调用这个函数(fn)
240 | 2.调用这个函数的时候函数的this指向object对象
241 |
242 | ``` js
243 | var obj = {
244 | a: 1, // a是定义在对象obj中的属性
245 | fire: function () {
246 | console.log(this.a)
247 | }
248 | }
249 |
250 | var a = 2; // a是定义在全局环境中的变量
251 | var fireInGrobal = obj.fire;
252 | fireInGrobal(); // 输出2
253 | fireInGrobal.call(obj); // 输出1
254 | ```
255 |
256 | 原本丢失了与obj绑定的this参数的fireInGrobal再次重新把this绑回到了obj
257 |
258 | 但是,我们其实不太喜欢这种每次调用都要依赖call的方式,我们更希望:能够一次性 返回一个this被永久绑定到obj的fireInGrobal函数,这样我们就不必每次调用fireInGrobal都要在尾巴上加上call那么麻烦了。
259 |
260 | 怎么办呢? 聪明的你一定能想到,在fireInGrobal.call(obj)外面包装一个函数不就可以了嘛!
261 |
262 | ``` js
263 | var obj = {
264 | a: 1, // a是定义在对象obj中的属性
265 | fire: function () {
266 | console.log(this.a)
267 | }
268 | }
269 | var a = 2; // a是定义在全局环境中的变量
270 | var fn = obj.fire;
271 | var fireInGrobal = function () {
272 | fn.call(obj) //硬绑定
273 | }
274 | fireInGrobal(); // 输出1
275 | ```
276 |
277 | 如果使用bind的话会更加简单
278 | var fireInGrobal = function () {
279 | fn.call(obj) //硬绑定
280 | }
281 | 可以简化为:
282 | var fireInGrobal = fn.bind(obj);
283 | call和bind的区别是:在绑定this到对象参数的同时:
284 |
285 | 1.call将立即执行该函数
286 | 2.bind不执行函数,只返回一个可供执行的函数
287 |
288 | 【其他】:至于apply,因为除了使用方法,它和call并没有太大差别,这里不加赘述
289 |
290 | 在这里,我把显式绑定和隐式绑定下,函数和“包含”函数的对象间的关系比作买房和租房的区别。
291 |
292 | 因为this的缘故
293 | 在隐式绑定下:函数和只是暂时住在“包含对象“的旅馆里面,可能过几天就又到另一家旅馆住了
294 | 在显式绑定下:函数将取得在“包含对象“里的永久居住权,一直都会”住在这里“
295 |
296 | 显示绑定和隐士绑定从字面意思理解,有一个相反的对比,一个表现的更直接,一个表现的更委婉,下面在看下两个规则各自的含义:
297 |
298 | 隐士绑定 在一个对象的内部通过属性间接引用函数,从而把this隐士绑定到对象内部属性所指向的函数(例如上例中的对象parent的child属性引用函数function child(){})。
299 |
300 | 显示绑定 需要引用一个对象时进行强制绑定调用,js有提供call()、apply()方法,ES5中也提供了内置的方法 Function.prototype.bind。
301 |
302 | call、apply这两个函数的第一个参数都是设置this对象,关于两个个函数的区别可以查看 [函数] call和apply的使用与区别?
303 | ``` js
304 | // 水果对象
305 | function fruit() {
306 | console.log(this.name, arguments);
307 | }
308 | var apple = {
309 | name: '苹果'
310 | }
311 | var banana = {
312 | name: '香蕉'
313 | }
314 | fruit.call(banana, banana, apple) // 香蕉 {'0': {name: '香蕉'}, '1': { name: '苹果'}}
315 | fruit.apply(apple, [banana, apple]) // 苹果 {'0': {name: '香蕉'}, '1': { name: '苹果'}}
316 | ```
317 |
318 | ### 3.4 new绑定
319 |
320 | 执行new操作的时候,将创建一个新的对象,并且将构造函数的this指向所创建的新对象
321 |
322 | ``` js
323 | function foo(a) {
324 | this.a = a;
325 | }
326 |
327 | var a1 = new foo(1);
328 | var a2 = new foo(2);
329 | var a3 = new foo(3);
330 | var a4 = new foo(4);
331 |
332 | console.log(a1.a); // 输出1
333 | console.log(a2.a); // 输出2
334 | console.log(a3.a); // 输出3
335 | console.log(a4.a); // 输出4
336 | ```
337 |
338 | ## 四、优先级
339 |
340 | new绑定
341 | 显示绑定
342 | 隐式绑定
343 | 默认绑定(严格模式下会绑定到undefined)
344 | 箭头函数
345 | 箭头函数并非使用function关键字进行定义,也不会使用上面所讲解的this四种标准规范,箭头函数会继承自外层函数调用的this绑定。
346 |
347 | 执行 fruit.call(apple)时,箭头函数this已被绑定,无法再次被修改。
348 |
349 | ``` js
350 | function fruit() {
351 | return () => {
352 | console.log(this.name);
353 | }
354 | }
355 | var apple = {
356 | name: '苹果'
357 | }
358 | var banana = {
359 | name: '香蕉'
360 | }
361 | var fruitCall = fruit.call(apple);
362 | fruitCall.call(banana); // 苹果
363 | ```
364 |
365 | this在项目中使用问题总结,React中使用this注意的问题
366 |
367 | ``` js
368 | class App extends React.Component {
369 | componentDidMount() {
370 | console.log(this);
371 | //注意this在setTimeout函数外代表的是App这个对象
372 | setTimeout(function () {
373 | this.setState({ //这里的this指的是windows对象
374 | initDone: true
375 | })
376 | }, 1000);
377 | //两种解决方案:
378 | //1. var that = this
379 | setTimeout(() => {
380 | that.setState({
381 | initDone: true
382 | })
383 | }, 1000);
384 | //2.使用es6的箭头函数
385 | setTimeout(() => {
386 | this.setState({
387 | initDone: true
388 | })
389 | }, 1000);
390 | }
391 | }
392 | ```
--------------------------------------------------------------------------------
/functional-programming/article/2019-1-19.md:
--------------------------------------------------------------------------------
1 | # FP编程进阶之——Point-Free
2 |
3 | ## 引言
4 |
5 | 在学习point-free之前,我先谷歌了一下关于point-free的定义,wiki上是这样说的:
6 |
7 | [wiki Tacit_programming](https://en.wikipedia.org/wiki/Tacit_programming)
8 |
9 | >Tacit programming, also called point-free style, is a programming paradigm in which function definitions do not identify the arguments (or "points") on which they operate. Instead the definitions merely compose other functions, among which are combinators that manipulate the arguments. Tacit programming is of theoretical interest, because the strict use of composition results in programs that are well adapted for equational reasoning.[1] It is also the natural style of certain programming languages, including APL and its derivatives,[2] and concatenative languages such as Forth. The lack of argument naming gives point-free style a reputation of being unnecessarily obscure, hence the epithet "pointless style".[1]
10 | > UNIX scripting uses the paradigm with pipes.
11 |
12 | 翻译如下:
13 |
14 | `Tacit programming` 也叫做 `point-free style` ,是一种编程范式,其中函数定义不用定义它们所操作的参数(或“点”)。相反,定义只是组成其他函数,其中包括操作参数的组合器。隐性编程在理论上很有意义,因为严格使用组合会产生适合于等式推理的程序[1]它也是某些编程语言的自然风格,包括APL及其派生语言[2]和连接语言,如FORTH。缺乏参数命名使无点风格享有不必要模糊的声誉,因此绰号“无点风格”。
15 |
16 | ```python
17 | def example(x):
18 | y = foo(x)
19 | z = bar(y)
20 | w = baz(z)
21 | return w
22 | ```
23 |
24 | ## 一、程序的本质
25 |
26 | 为了理解 Pointfree,请大家先想一想,什么是程序?
27 | 
28 | 上图是一个编程任务,左侧是数据输入(input),中间是一系列的运算步骤,对数据进行加工,右侧是最后的数据输出(output)。一个或多个这样的任务,就组成了程序。
29 |
30 | 输入和输出(统称为 I/O)与键盘、屏幕、文件、数据库等相关,这些跟本文无关。这里的关键是,中间的运算部分不能有 I/O 操作,应该是纯运算,即通过纯粹的数学运算来求值。否则,就应该拆分出另一个任务。
31 |
32 | I/O 操作往往有现成命令,大多数时候,编程主要就是写中间的那部分运算逻辑。现在,主流写法是过程式编程和面向对象编程,但是我觉得,最合适纯运算的是函数式编程。
33 |
34 | ## 二、函数的拆分与合成
35 |
36 | 上面那张图中,运算过程可以用一个函数fn表示。
37 | 
38 |
39 | fn的类型如下。
40 |
41 | ```c++
42 | fn :: a -> b
43 | ```
44 |
45 | 上面的式子表示,函数fn的输入是数据a,输出是数据b。
46 |
47 | 如果运算比较复杂,通常需要将fn拆分成多个函数。
48 | 
49 |
50 | f1、f2、f3的类型如下。
51 |
52 | ```c++
53 | f1 :: a -> m
54 | f2 :: m -> n
55 | f3 :: n -> b
56 | ```
57 |
58 | 上面的式子中,输入的数据还是a,输出的数据还是b,但是多了两个中间值m和n。
59 |
60 | 我们可以把整个运算过程,想象成一根水管(pipe),数据从这头进去,那头出来。
61 |
62 | 
63 |
64 | 函数的拆分,无非就是将一根水管拆成了三根。
65 |
66 | 
67 |
68 | 进去的数据还是a,出来的数据还是b。fn与f1、f2、f3的关系如下。
69 |
70 | ```c++
71 | fn = R.pipe(f1, f2, f3);
72 | ```
73 |
74 | 上面代码中,我用到了 Ramda 函数库的pipe方法,将三个函数合成为一个。Ramda 是一个非常有用的库,后面的例子都会使用它,如果你还不了解,可以先读一下教程。
75 |
76 | ## 三、Pointfree 的概念
77 |
78 | ```c++
79 | fn = R.pipe(f1, f2, f3);
80 | ```
81 |
82 | 这个公式说明,如果先定义f1、f2、f3,就可以算出fn。整个过程,根本不需要知道a或b。
83 |
84 | 也就是说,我们完全可以把数据处理的过程,定义成一种与参数无关的合成运算。不需要用到代表数据的那个参数,只要把一些简单的运算步骤合成在一起即可。
85 |
86 | 这就叫做 Pointfree:不使用所要处理的值,只合成运算过程。中文可以译作"无值"风格。
87 |
88 | 请看下面的例子。
89 |
90 | ```js
91 | var addOne = x => x + 1;
92 | var square = x => x * x;
93 | ```
94 |
95 | 上面是两个简单函数addOne和square。
96 |
97 | 把它们合成一个运算。
98 |
99 | ```js
100 | var addOneThenSquare = R.pipe(addOne, square);
101 |
102 | addOneThenSquare(2) // 9
103 | ```
104 |
105 | 上面代码中,addOneThenSquare是一个合成函数。定义它的时候,根本不需要提到要处理的值,这就是 Pointfree。
106 |
107 | ## 四、Pointfree 的本质
108 |
109 | Pointfree 的本质就是使用一些通用的函数,组合出各种复杂运算。上层运算不要直接操作数据,而是通过底层函数去处理。这就要求,将一些常用的操作封装成函数。
110 |
111 | 比如,读取对象的role属性,不要直接写成obj.role,而是要把这个操作封装成函数。
112 |
113 | ```js
114 | var prop = (p, obj) => obj[p];
115 | var propRole = R.curry(prop)('role');
116 | ```
117 |
118 | 上面代码中,prop函数封装了读取操作。它需要两个参数p(属性名)和obj(对象)。这时,要把数据obj要放在最后一个参数,这是为了方便柯里化。函数propRole则是指定读取role属性,下面是它的用法(查看完整代码)。
119 |
120 | ```js
121 | var isWorker = s => s === 'worker';
122 | var getWorkers = R.filter(R.pipe(propRole, isWorker));
123 |
124 | var data = [
125 | {name: '张三', role: 'worker'},
126 | {name: '李四', role: 'worker'},
127 | {name: '王五', role: 'manager'},
128 | ];
129 | getWorkers(data)
130 | // [
131 | // {"name": "张三", "role": "worker"},
132 | // {"name": "李四", "role": "worker"}
133 | // ]
134 | ```
135 |
136 | 上面代码中,data是传入的值,getWorkers是处理这个值的函数。定义getWorkers的时候,完全没有提到data,这就是 Pointfree。
137 |
138 | 简单说,Pointfree 就是运算过程抽象化,处理一个值,但是不提到这个值。这样做有很多好处,它能够让代码更清晰和简练,更符合语义,更容易复用,测试也变得轻而易举。
139 |
140 | ## 五、Pointfree 的示例一
141 |
142 | ```js
143 | var str = 'Lorem ipsum dolor sit amet consectetur adipiscing elit';
144 | ```
145 |
146 | 上面是一个字符串,请问其中最长的单词有多少个字符?
147 |
148 | 我们先定义一些基本运算。
149 |
150 | ```js
151 | // 以空格分割单词
152 | var splitBySpace = s => s.split(' ');
153 | // 每个单词的长度
154 | var getLength = w => w.length;
155 | // 词的数组转换成长度的数组
156 | var getLengthArr = arr => R.map(getLength, arr);
157 | // 返回较大的数字
158 | var getBiggerNumber = (a, b) => a > b ? a : b;
159 | // 返回最大的一个数字
160 | var findBiggestNumber =
161 | arr => R.reduce(getBiggerNumber, 0, arr);
162 | ```
163 |
164 | 然后,把基本运算合成为一个函数(查看完整代码)。
165 |
166 | ```js
167 | var getLongestWordLength = R.pipe(
168 | splitBySpace,
169 | getLengthArr,
170 | findBiggestNumber
171 | );
172 |
173 | getLongestWordLength(str) // 11
174 |
175 | ```
176 |
177 | 可以看到,整个运算由三个步骤构成,每个步骤都有语义化的名称,非常的清晰。这就是 Pointfree 风格的优势。
178 |
179 | Ramda 提供了很多现成的方法,可以直接使用这些方法,省得自己定义一些常用函数(查看完整代码)。
180 |
181 | ```js
182 | // 上面代码的另一种写法
183 | var getLongestWordLength = R.pipe(
184 | R.split(' '),
185 | R.map(R.length),
186 | R.reduce(R.max, 0)
187 | );
188 | ```
189 |
190 | ## 六、Pointfree 示例二
191 |
192 | 最后,看一个实战的例子,拷贝自 Scott Sauyet 的文章《Favoring Curry》。那篇文章能帮助你深入理解柯里化,强烈推荐阅读。
193 |
194 | 下面是一段服务器返回的 JSON 数据。
195 |
196 | ```js
197 | var data = {
198 | result: "SUCCESS",
199 | interfaceVersion: "1.0.3",
200 | requested: "10/17/2013 15:31:20:",
201 | lastUpdated: "10/16/2013 10:52:39"
202 | tasks: [
203 | {id: 104, complete: false, priority: "high",
204 | dueDate: "2013-11-29", username: "Scott",
205 | title: "Do something", created: "9/22/2013"},
206 | {id: 105, complete: false, priority: "medium",
207 | dueDate: "2013-11-22", username: "Lena",
208 | title: "Do something else", created: "9/22/2013"},
209 | {id: 107, complete: true, priority: "high",
210 | dueDate: "2013-11-29", username: "Mike",
211 | title: "Fix the foo", created: "9/22/2013"},
212 | {id: 108, complete: false, priority: "low",
213 | dueDate: "2013-11-15", username: "Punam",
214 | title: "Adjust the bar", created: "9/25/2013"},
215 | {id: 110, complete: false, priority: "medium",
216 | dueDate: "2013-11-15", username: "Scott",
217 | title: "Rename everything", created: "10/2/2013"},
218 | {id: 112, complete: true, priority: "high",
219 | dueDate: "2013-11-27", username: "Lena",
220 | title: "Alter all quuxes", created: "10/5/2013"},
221 | ]
222 | }
223 | ```
224 |
225 | 现在要求是,找到用户 Scott 的所有未完成任务,并按到期日期升序排列。
226 |
227 | ```js
228 | [
229 | {id: 110, complete: false, priority: "medium",
230 | dueDate: "2013-11-15", username: "Scott",
231 | title: "Rename everything", created: "10/2/2013"},
232 | {id: 104, complete: false, priority: "high",
233 | dueDate: "2013-11-29", username: "Scott",
234 | title: "Do something", created: "9/22/2013"}
235 | ]
236 | ```
237 |
238 | ```js
239 | getIncompleteTaskSummaries = function(membername) {
240 | return fetchData()
241 | .then(function(data){
242 | return data.tasks;
243 | })
244 | .then(function(tasks){
245 | var results = [];
246 | for (var i = 0, len = tasks.length; i < len; i++) {
247 | if (tasks[i].username == membername) {
248 | results.push(tasks[i]);
249 | }
250 | }
251 | return results;
252 | })
253 | .then(function(tasks){
254 | var results = [];
255 | for (var i = 0, len = tasks.length; i < len; i++) {
256 | if (!tasks[i].complete) {
257 | results.push(tasks[i]);
258 | }
259 | }
260 | return results;
261 | })
262 | .then(function(tasks){
263 | var results = [], task;
264 | for (var i = 0, len = tasks.length; i < len; i++) {
265 | task = tasks[i];
266 | results.push({
267 | id: task.id,
268 | dueDate: task.dueDate,
269 | title: task.title,
270 | priority: task.priority
271 | })
272 | }
273 | return results;
274 | })
275 | .then(function(tasks){
276 | tasks.sort(function(first, second){
277 | var a = first.dueDate, b = second.dueDate;
278 | return a < b ? -1 : a > b ? 1: 0;
279 | })
280 | return tasks;
281 | })
282 | }
283 | ```
284 |
285 | 上面代码不易读,出错的可能性很大。
286 |
287 | 现在使用 Pointfree 风格改写(查看完整代码)。
288 |
289 | ```js
290 | var getIncompleteTaskSummaries = function(membername) {
291 | return fetchData()
292 | .then(R.prop('tasks'))
293 | .then(R.filter(R.propEq('username', membername)))
294 | .then(R.reject(R.propEq('complete', true)))
295 | .then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
296 | .then(R.sortBy(R.prop('dueDate')));
297 | };
298 | ```
299 |
300 | 上面代码已经清晰很多了。
301 |
302 | 另一种写法是,把各个then里面的函数合成起来(查看完整代码)。
303 |
304 | ```js
305 | // 提取 tasks 属性
306 | var SelectTasks = R.prop('tasks');
307 | // 过滤出指定的用户
308 | var filterMember = member => R.filter(
309 | R.propEq('username', member)
310 | );
311 | // 排除已经完成的任务
312 | var excludeCompletedTasks = R.reject(R.propEq('complete', true));
313 | // 选取指定属性
314 | var selectFields = R.map(
315 | R.pick(['id', 'dueDate', 'title', 'priority'])
316 | );
317 | // 按照到期日期排序
318 | var sortByDueDate = R.sortBy(R.prop('dueDate'));
319 | // 合成函数
320 | var getIncompleteTaskSummaries = function(membername) {
321 | return fetchData().then(
322 | R.pipe(
323 | SelectTasks,
324 | filterMember(membername),
325 | excludeCompletedTasks,
326 | selectFields,
327 | sortByDueDate,
328 | )
329 | );
330 | };
331 | ```
332 |
333 | 上面的代码跟过程式的写法一比较,孰优孰劣一目了然。
--------------------------------------------------------------------------------
/es5-senior/exercises.md:
--------------------------------------------------------------------------------
1 | ## ES5核心技术
2 |
3 | > **主要通过例题的方式讲解一些ES5的核心知识**
4 |
5 | ### 1.第一题 以下代码运算结果
6 |
7 | ``` html
8 |
17 | ```
18 |
19 | 讲解:
20 |
21 | 该题涉及的知识点有:
22 | 变量、作用域、函数声明、函数表达式、立即执行函数(IIFE)
23 | * 我们看到了一个立即执行函数
24 |
25 | ``` js
26 | (function(){...})()
27 | (function(){...}())
28 | ```
29 |
30 | > IIFE的作用
31 | > * 一是不必为函数命名,避免了污染全局变量
32 | > * 二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取 的私有变量。
33 |
34 | javascript中没用私有作用域的概念,如果在多人开发的项目上,你在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉,根据javascript函数作用域链的特性,可以使用IIFE可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。
35 |
36 | 立即执行函数体内的变量 `var a` ,覆盖了全局声明的变量的 `var a` ,另外IIFE内部的 `var a` 因为变量提升的关系,在 `console.log(a)` 访问前就已经声明完毕(此时只是执行`var a`的过程,所以变量处于 **undefined** 状态),在 `var a=30` 时完成赋值的过程。
37 |
38 | 所以最后的执行结果是:`undefined`
39 |
40 | 另外这道题简单做一些修改,使上边所说的特性更加清晰:
41 |
42 | ``` html
43 |
56 | ```
57 |
58 | ### 2.第二题 以下代码运算结果
59 |
60 | ``` js
61 | this.a = 20;
62 | var test = {
63 | a: 40,
64 | init: ()=> {
65 | console.info(this.a);
66 | function go() {
67 | // this.a = 60;
68 | console.info(this.a);
69 | }
70 | go.prototype.a = 50;
71 | return go;
72 | }
73 | };
74 | // var p = test.init();
75 | // p();
76 | new (test.init())();
77 | ```
78 |
79 | 这道题目相当复杂,所以在解析这道题目的时候,我们可以尝试着把复杂的问题解析成小问题,逐个击破。
80 |
81 | ``` js
82 |
94 | ```
95 |
96 | 这个例子主要就是分析this的指向,关于这个我有详细的文章介绍。请参考。
97 | [this问题详解][1]
98 |
99 | [1]: https://github.com/Martin-Shao/yideng-note/blob/master/es5-senior/article/this.md
100 |
101 | ``` js
102 |
119 | ```
120 |
121 | 上面的例子中,`this.a` 的值和P变量是完全没有关系的。`(p.test())();`这段代码和下面注释掉的两段代码是一样,这样就能很明白的看出来,`s();` 函数时挂载在window对象的,所以函数中的this就是指向window对象的。
122 |
123 | 第二点就是要注意`this.a = 60;` 和 `this.a = 20;` 中的this都是指向window的,所以前者的 `a` 的值要覆盖后者。
124 |
125 | ``` js
126 |
141 | ```
142 |
143 | 这段代码和前面的代码得出的结果是一样,另一方面也证明了,在浏览器中,凡是没有指明调用对象的,都会默认绑定window对象。
144 |
145 | ### 3.第二题 以下代码运算结果
146 |
147 | ``` js
148 |
163 | ```
164 |
165 | 这个例题就是典型的闭包,造成的问题就是N这个变量永驻内存,其实就会造成内存泄露问题。得出的结果是
166 | > 1 2 3
167 |
168 | 解决这个问题的办法是在逻辑结束之后,给变量赋值 `null` ,这是闭包带来不好的问题,另外一方面,闭包还能给JavaScript这么语言带来私有变量。
169 |
170 | ``` js
171 |
186 | ```
187 |
188 | ### 4. 面向对象与继承
189 |
190 | 第四部分开始,就要涉及到JavaScript面向对象编程的问题了。
191 |
192 | 下面代码简单演示了什么是对象、构造器和以及最简单的原型继承实例对象。
193 |
194 | 1. 面向对象编程范式的概念介绍
195 | 2. JavaScript语言支持面向对象的基石
196 | 3. JavaScript之原型、构造函数、继承解析
197 | 4. JavaScript按值传递和按引用传递在继承中问题
198 | 5. 原型继承、构造继承、组合继承、寄生组合继承优缺点解析
199 |
200 | ``` js
201 |
217 | ```
218 |
219 | 从这里开始,算是接触到JavaScript面相对象编程的知识了。
220 | 关于这部分知识,会单独做一个专题,请移步。
221 |
222 | ### 5. 变量提升和函数提升
223 |
224 | ``` js
225 | (function() {
226 | var a = 20;
227 | function a() {}
228 | console.info(a); // console => 20
229 | })();
230 | ```
231 |
232 | ``` js
233 | (function() {
234 | var a = 20;
235 | var b = c = a;
236 | })();
237 | console.info(c); // console => 20
238 | ```
239 |
240 | ``` js
241 | (function() {
242 | function a() {
243 | var a = 20;
244 | var b = c = a;
245 | }
246 | })();
247 | console.info(c); // Uncaught ReferenceError: c is not defined
248 | ```
249 |
250 | ``` js
251 | (function() {
252 | var a = 20;
253 | var b, c = a;
254 | })();
255 | console.info(c); // Uncaught ReferenceError: c is not defined
256 | ```
257 |
258 | ``` js
259 | function test() {
260 | this.a = 20;
261 | }
262 | test.prototype.a = 30;
263 | var q = new test();
264 | console.info(q.a);
265 | ```
266 |
267 | ``` js
268 | var user = {
269 | age: 20,
270 | init: function() {
271 | console.info(this.age);
272 | }
273 | }
274 | var data = {age: 40};
275 | var u = user.init.bind(data);
276 | u.init();
277 | ```
278 |
279 | ``` js
280 | function test(m) {
281 | m.v = 20;
282 | }
283 | var m = {age: 30};
284 | test(m);
285 | console.info(m);
286 | console.info(m.v); // console => 20
287 | ```
288 |
289 | ```js
290 | var bo = 10;
291 | function foo() {
292 | console.log(bo);
293 | }
294 | foo();
295 |
296 | (function() {
297 | var bo = 20;
298 | foo();
299 | })()
300 |
301 | (function (func) {
302 | var bo = 30;
303 | func();
304 | })(foo)
305 | ```
306 |
307 | ## 大总结
308 |
309 | 1. 理解执行函数
310 | 2. 闭包,内部函数可以访问外部函数的变量,把函数返回出去
311 | 闭包可以保护内部的变量,闭包造成内存泄露 == null
312 | 3. 原型链
313 | 3.1. 构造函数里的属性的优先级比原型链的要高
314 | 3.2. 面向对象编程的时候,js没有类的概念,可以用函数替代
315 | 3.3. constructor实际就是对应的那个函数
316 | 3.4. prototype按引用传递的,Object.create原型链的副本
317 | 4. 数值 字符串 布尔类型按值传递
318 | 5. 改变this的方法 call apply bind
319 | 6. 函数提升,变量提升 函数提升的级别要比变量提升高
320 | 7. jquery内部有很多经典的写法 模块化编程的概念 闭包
321 |
322 | ## ES5核心知识提炼
323 |
324 | JavaScript基础之变量类型
325 | JavaScript探究this奥秘之旅
326 | 面向对象编程范式简介
327 | JavaScript面向对象编程基础
328 | JavaScript之理解“一等公民”--函数
329 | Function原型方法call、apply、bind全方位解析
330 | JavaScript深度理解原型链
331 | JavaScript全方位理解闭包
332 | JavaScript继承深度解析
333 |
334 | 问题是这样的:有这样的一个代码:
335 |
336 | ``` js
337 | var s = {
338 | p: function () {
339 | return function () {
340 | console.log('enna')
341 | }
342 | }
343 | }
344 | (s.p())()
345 | ```
346 |
347 | 志佳老师运行的结果是这样的:
348 |
349 | 
350 | 但是我的结果是这样的:
351 | 
352 |
353 | 报错很清楚呀 {} 对象不是一个函数 直接跟() 就等于执行了 不是函数执行不了 es5可执行的代码只有全局代码 函数 和 eval呀
354 |
355 | 这个问题的报错分为两个部分:
356 |
357 | ``` js
358 | // 1. Cannot read property 'xxx' of undefined
359 | // 2. {(intermediate value)} is not a function
360 | ```
361 |
362 | ```js
363 | function Car(color, price) {
364 | this.color = color;
365 | this.price = price;
366 | }
367 | Car.prototype.sail = function() {
368 | console.info(this.color + ' car is sailing ' + this.price);
369 | }
370 |
371 | function inherite(subType, SuperType) {
372 | var prototype = Object.create(SuperType.prototype);
373 | prototype.constructor = subType;
374 | subType.prototype = prototype;
375 | }
376 |
377 | function Cruze(color, price, seller) {
378 | Car.call(this, color, price);
379 | this.seller = seller;
380 | }
381 |
382 | inherite(Cruze, Car);
383 |
384 | var cruze = new Cruze('red', 140000);
385 | cruze.sail();
386 | ```
387 |
388 | ```js
389 | var s = [];
390 | var arr = s;
391 | var pusher;
392 | var tmp;
393 | for (var i = 0; i < 3; i++) {
394 | pusher = {
395 | value: 'item' + i
396 | };
397 | if (i !== 2) {
398 | tmp = []
399 | pusher.children = tmp
400 | }
401 | arr.push(pusher);
402 | arr = tmp;
403 | }
404 | console.log(s[0]);
405 | ```
406 |
407 | ```js
408 | var Container = function (x) {
409 | this._value = x;
410 | }
411 | Container.of = x => new Container(x);
412 | Container.prototype.map = function (f) {
413 | return Container.of(f(this._value))
414 | }
415 | Container.of(3)
416 | .map(x => x + 1)
417 | .map(x => 'Result is' + x);
418 | ```
419 |
420 | ```js
421 | const timeout = ms =>
422 | new Promise((resolve, reject) => {
423 | setTimeout(() => {
424 | resolve();
425 | }, ms);
426 | });
427 | const ajax1 = () => //#endregion
428 | timeout(2000).then(() => {
429 | console.log('1');
430 | return 1;
431 | });
432 | const ajax2 = () => //#endregion
433 | timeout(1000).then(() => {
434 | console.log('2');
435 | return 2;
436 | });
437 | const ajax3 = () => //#endregion
438 | timeout(2000).then(() => {
439 | console.log('3');
440 | return 3;
441 | });
442 | const mergePromise = (ajaxArray) => {
443 | var data = [];
444 | var sequence = Promise.resolve();
445 | ajaxArray.forEach(function (item) {
446 | sequence = sequence.then(item).then(function (res) {
447 | data.push(res);
448 | return data;
449 | });
450 | })
451 |
452 | return sequence;
453 | }
454 | mergePromise([ajax1, ajax2, ajax3]).then(data => {
455 | console.info('done');
456 | console.log(data);
457 | })
458 | ```
--------------------------------------------------------------------------------
/functional-programming/article/2019-1-20.md:
--------------------------------------------------------------------------------
1 | # FP编程进阶之——Functor、Applicative 和 Monad
2 |
3 | 本文转自: [雷纯锋的技术博客-Functor、Applicative 和 Monad](http://blog.leichunfeng.com/blog/2015/11/08/functor-applicative-and-monad/)
4 |
5 | 
6 |
7 | ## 引言
8 |
9 | 首先这篇文章是写的十分好的,对于函数式编程中Functor、Applicative 和 Monad的理解比较深刻,但是阅读此文的同学最好知道一点Haskell语言的知识,将提升不少阅读体验。如果实在不想触碰Haskell,也是能够勉强理解的。最后补充一点:Haskell是一种标准化的、通用纯函数式编程语言,通过学习Haskell,我们将对于函数式编程有着更加深刻的理解。通过降维打击的学习JavaScript的函数式编程,将不再那么困难。
10 |
11 | 我们在学习JavaScript函数式编程的时候,通常所说的Functor函子、AP函子、Monad函子的概念就来源此:Haskell函数式编程中Functor、Applicative 和 Monad。笔者也是受到这篇文章的启发,开始启动Haskell学习计划。这里推荐: [Haskell 趣學指南](https://legacy.gitbook.com/book/mno2/learnyouahaskell-zh/details/zh-cn)
12 |
13 | `Functor` `、Applicative` 和 `Monad` 是函数式编程语言中三个非常重要的概念,尤其是 `Monad` ,难倒了不知道多少英雄好汉。事实上,它们的概念是非常简单的,但是却很少有文章能够将它们描述清楚,往往还适得其反,越描越黑。与其它文章不同的是,本文将从结论出发,层层深入,一步步为你揭开它们的神秘面纱。
14 |
15 | **说明**:本文中的主要代码为 Haskell 语言,它是一门纯函数式的编程语言。其中,具体的语法细节,我们不需要太过关心,因为这并不影响你对本文的理解。
16 |
17 | 结论
18 | 关于 `Functor` `、Applicative` 和 `Monad` 的概念,其实各用一句话就可以概括:
19 |
20 | 1. 一个 `Functor` 就是一种实现了 `Functor typeclass` 的数据类型;
21 | 2. 一个 `Applicative` 就是一种实现了 `Applicative typeclass` 的数据类型;
22 | 3. 一个 `Monad` 就是一种实现了 `Monad typeclass` 的数据类型。
23 | 当然,你可能会问那什么是 `typeclass` 呢?我想当你在看到**实现**二字的时候,就应该已经猜到了:
24 |
25 | > A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes. A lot of people coming from OOP get confused by typeclasses because they think they are like classes in object oriented languages. Well, they’re not. You can think of them kind of as Java interfaces, only better.
26 |
27 | 是的, `typeclass` 就类似于 `Java` 中的接口,或者 `Objective-C` 中的协议。在 `typeclass` 中定义了一些函数,实现一个 `typeclass` 就是要实现这些函数,而所有实现了这个 `typeclass` 的数据类型都会拥有这些共同的行为。
28 |
29 | 那 `Functor` `、Applicative` 和 `Monad` 三者之间有什么联系吗,为什么它们老是结队出现呢?其实, `Applicative` 是增强型的 `Functor` ,一种数据类型要成为 `Applicative` 的前提条件是它必须是 `Functor` ;同样的, `Monad` 是增强型的 `Applicative` ,一种数据类型要成为 `Monad` 的前提条件是它必须是 `Applicative` 。注:这个联系,在我们看到 `Applicative typeclass` 和 `Monad typeclass` 的定义时,自然就会明了。
30 |
31 | ## Maybe
32 |
33 | 在正式开始介绍 `Functor` 、 `Applicative` 和 `Monad` 的定义前,我想先介绍一种非常有意思的数据类型, **`Maybe`** 类型(可类比 `Swift` 中的 `Optional` ):
34 |
35 | > The Maybe type encapsulates an optional value. A value of type Maybe a either contains a value of type a (represented as Just a), or it is empty (represented as Nothing). Using Maybe is a good way to deal with errors or exceptional cases without resorting to drastic measures such as error.
36 |
37 | `Maybe` 类型封装了一个可选值。一个 `Maybe a` 类型的值要么包含一个 `a` 类型的值(用 `Just a` 表示);要么为空(用 `Nothing` 表示)。我们可以把 `Maybe` 看作一个盒子,这个盒子里面可能装着一个 `a` 类型的值,即 `Just a` ;也可能是一个空盒子,即 `Nothing` 。或者,你也可以把它理解成泛型,比如 `Objective-C` 中的 `NSArray` 。不过,最正确的理解应该是把 `Maybe` 看作一个上下文,这个上下文表示某次计算可能成功也可能失败,成功时用 `Just a` 表示,`a` 为计算结果;失败时用 `Nothing` 表示,这就是 `Maybe` 类型存在的意义:
38 |
39 | 
40 |
41 | ```haskell
42 | > :i Maybe
43 | data Maybe a = Nothing | Just a -- Defined in ‘GHC.Base’
44 | ```
45 |
46 | 下面,我们来直观地感受一下 `Maybe` 类型:
47 |
48 | ```haskell
49 | ghci> Nothing
50 | Nothing
51 | ghci> Just 2
52 | Just 2
53 | ```
54 |
55 | 我们可以用盒子模型来理解一下,Nothing 就是一个空盒子;而 Just 2 则是一个装着 2 这个值的盒子:
56 |
57 | 
58 |
59 | 提前剧透: `Maybe` 类型实现了 `Functor typeclass`、`Applicative typeclass` 和 `Monad typeclass` ,所以它同时是 `Functor` 、 `Applicative` 和 `Monad` ,具体实现细节将在下面的章节进行介绍。
60 |
61 | ## Functor
62 |
63 | 在正式开始介绍 `Functor` 前,我们先思考一个这样的问题,假如我们有一个值 2 :
64 |
65 | 
66 |
67 | 我们如何将函数 `(+3)` 应用到这个值上呢?我想上过小学的朋友应该都知道,这就是一个简单的加法运算:
68 |
69 | 
70 |
71 | ``` shell
72 | ghci> (+3) 2
73 | 5
74 | ```
75 |
76 | 分分钟搞定。那么问题来了,如果这个值 2 是在一个上下文中呢?比如 `Maybe` ,此时,这个值 `2` 就变成了 `Just 2` :
77 |
78 | 
79 |
80 | 这个时候,我们就不能直接将函数 `(+3)` 应用到 `Just 2` 了。那么,我们如何将一个函数应用到一个在上下文中的值呢?
81 |
82 | 
83 |
84 | 是的,我想你应该已经猜到了, `Functor` 就是干这事的,欲知后事如何,请看下节分解。
85 |
86 | ## Functor typeclass
87 |
88 | 首先,我们来看一下 `Functor typeclass` 的定义:
89 |
90 | ```haskell
91 | class Functor f where
92 | fmap :: (a -> b) -> f a -> f b
93 | ```
94 |
95 | 在 `Functor typeclass` 中定义了一个函数 `fmap` ,它将一个函数 `(a -> b)` 应用到一个在上下文中的值 `f a` ,并返回另一个在相同上下文中的值 `f b` ,这里的 `f` 是一个类型占位符,表示任意类型的 `Functor` 。
96 |
97 | 注: `fmap` 函数可类比 `Swift` 中的 `map` 方法。
98 |
99 | ## Maybe Functor
100 |
101 | 我们知道 `Maybe` 类型就是一个 `Functor` ,它实现了 `Functor typeclass` 。我们将类型占位符 `f` 用具体类型 `Maybe` 代入可得:
102 |
103 | ```haskell
104 | class Functor Maybe where
105 | fmap :: (a -> b) -> Maybe a -> Maybe b
106 | ```
107 |
108 | 因此,对于 `Maybe` 类型来说,它要实现的函数 `fmap` 的功能就是将一个函数 `(a -> b)` 应用到一个在 `Maybe` 上下文中的值 `Maybe a` ,并返回另一个在 `Maybe` 上下文中的值 `Maybe b` 。接下来,我们一起来看一下 `Maybe` 类型实现 `Functor typeclass` 的具体细节:
109 |
110 | ```haskell
111 | instance Functor Maybe where
112 | fmap func (Just x) = Just (func x)
113 | fmap func Nothing = Nothing
114 | ```
115 |
116 | 这里针对 `Maybe` 上下文的两种情况分别进行了处理:如果盒子中有值,即 `Just x` ,那么就将 `x` 从盒子中取出,然后将函数 `func` 应用到 `x` ,最后将结果放入一个相同类型的新盒子中;如果盒子为空,那么直接返回一个新的空盒子。
117 |
118 | 看到这里,我想你应该已经知道如何将一个函数应用到一个在上下文中的值了。比如前面提到的将函数 `(+3)` 应用到 `Just 2` :
119 |
120 | 
121 |
122 | ```haskell
123 | ghci> fmap (+3) (Just 2)
124 | Just 5
125 | ```
126 |
127 | 另外,值得一提的是,当我们将函数 `(+3)` 应用到一个空盒子,即 `Nothing` 时,我们将会得到一个新的空盒子:
128 |
129 | 
130 |
131 | ```haskell
132 | ghci> fmap (+3) Nothing
133 | Nothing
134 | ```
135 |
136 | ## Applicative
137 |
138 | 现在,我们已经知道如何将函数 `(+3)` 应用到 `Just 2` 了。那么问题又来了,如果函数 `(+3)` 也在上下文中呢,比如 `Maybe` ,此时,函数 `(+3)` 就变成了 `Just (+3)` :
139 |
140 | 那么,我们如何将一个在上下文中的函数应用到一个在上下文中的值呢?
141 |
142 | 这就是 `Applicative` 要干的事,详情请看下节内容。
143 |
144 | ## Applicative typeclass
145 |
146 | 同样的,我们先来看一下 `Applicative typeclass` 的定义:
147 |
148 | ```haskell
149 | class Functor f => Applicative f where
150 | pure :: a -> f a
151 | (<*>) :: f (a -> b) -> f a -> f b
152 | ```
153 |
154 | 我们注意到,与 `Functor typeclass` 的定义不同的是,在 `Applicative typeclass` 的定义中多了一个类约束 `Functor f` ,表示的意思是数据类型 `f` 要实现 `Applicative typeclass` 的前提条件是它必须要实现 `Functor typeclass` ,也就是说它必须是一个 `Functor` 。
155 |
156 | 在 `Applicative typeclass` 中定义了两个函数:
157 |
158 | * `pure` :将一个值 `a` 放入上下文中;
159 | * `(<*>)` :将一个在上下文中的函数 `f (a -> b)` 应用到一个在上下文中的值 `f a` ,并返回另一个在上下文中的值 `f b` 。
160 | 注:`<*>` 函数的发音我也不知道,如果有同学知道的话还请告之,谢谢。
161 |
162 | ## Maybe Applicative
163 |
164 | 同样的,我们将类型占位符 `f` 用具体类型 `Maybe` 代入,可得:
165 |
166 | ```haskell
167 | class Functor Maybe => Applicative Maybe where
168 | pure :: a -> Maybe a
169 | (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
170 | ```
171 |
172 | 因此,对于 `Maybe` 类型来说,它要实现的 `pure` 函数的功能就是将一个值 `a` 放入 `Maybe` 上下文中。而 `(<*>)` 函数的功能则是将一个在 `Maybe` 上下文中的函数 `Maybe (a -> b)` 应用到一个在 `Maybe` 上下文中的值 `Maybe a` ,并返回另一个在 `Maybe` 上下文中的值 `Maybe b` 。接下来,我们一起来看一下 `Maybe` 类型实现 `Applicative typeclass` 的具体细节:
173 |
174 | ```haskell
175 | instance Applicative Maybe where
176 | pure = Just
177 | Nothing <*> _ = Nothing
178 | (Just func) <*> something = fmap func something
179 | ```
180 |
181 | `pure` 函数的实现非常简单,直接等于 `Just` 即可。而对于 `(<*>)` 函数的实现,我们同样需要针对 `Maybe` 上下文的两种情况分别进行处理:当装函数的盒子为空时,直接返回一个新的空盒子;当装函数的盒子不为空时,即 `Just func` ,则取出 `func` ,使用 `fmap` 函数直接将 `func` 应用到那个在上下文中的值,这个正是我们前面说的 `Functor` 的功能。
182 |
183 | 好了,我们接下来看一下将 `Just (+3)` 应用到 `Just 2` 的具体过程:
184 |
185 | 
186 |
187 | ```haskell
188 | ghci> Just (+3) <*> Just 2
189 | Just 5
190 | ```
191 |
192 | 同样的,当我们将一个空盒子,即 `Nothing` 应用到 `Just 2` 的时候,我们将得到一个新的空盒子:
193 |
194 | ```haskell
195 | ghci> Nothing <*> Just 2
196 | Nothing
197 | ```
198 |
199 | ## Monad
200 |
201 | 截至目前,我们已经知道了 `Functor` 的作用就是应用一个函数到一个上下文中的值:
202 | 
203 |
204 | 而 `Applicative` 的作用则是应用一个上下文中的函数到一个上下文中的值:
205 | 
206 |
207 | 那么 `Monad` `又会是什么呢?其实,Monad` 的作用跟 `Functor` 类似,也是应用一个函数到一个上下文中的值。不同之处在于, `Functor` 应用的是一个接收一个普通值并且返回一个普通值的函数,而 `Monad` 应用的是一个接收一个普通值但是返回一个在上下文中的值的函数:
208 |
209 | 
210 |
211 | ## Monad typeclass
212 |
213 | 同样的,我们先来看一下 `Monad typeclass` 的定义:
214 |
215 | ```haskell
216 | class Applicative m => Monad m where
217 | return :: a -> m a
218 |
219 | (>>=) :: m a -> (a -> m b) -> m b
220 |
221 | (>>) :: m a -> m b -> m b
222 | x >> y = x >>= \_ -> y
223 |
224 | fail :: String -> m a
225 | fail msg = error msg
226 | ```
227 |
228 | 哇,这什么鬼,完全看不懂啊,太复杂了。兄台莫急,且听我细说。在 `Monad typeclass` 中定义了四个函数,分别是 `return` 、`(>>=)`、`(>>)` 和 `fail` ,且后两个函数 `(>>)` 和 `fail` 给出了默认实现,而在绝大多数情况下,我们都不需要去重写它们。因此,去掉这两个函数后,`Monad typeclass` 的定义可简化为:
229 |
230 | ```haskell
231 | class Applicative m => Monad m where
232 | return :: a -> m a
233 | (>>=) :: m a -> (a -> m b) -> m b
234 | ```
235 |
236 | 怎么样?现在看上去就好多了吧。跟 `Applicative typeclass` 的定义一样,在 `Monad typeclass` 的定义中也有一个类约束 `Applicative m` ,表示的意思是一种数据类型 `m` 要成为 `Monad` 的前提条件是它必须是 `Applicative` 。另外,其实 `return` 函数的功能与 `Applicative` 中的 `pure` 函数的功能是一样的,只不过换了一个名字而已,它们的作用都是将一个值 `a` 放入上下文中。而 `(>>=)` 函数的功能则是应用一个(接收一个普通值 `a` 但是返回一个在上下文中的值 `m b` 的)函数 `(a -> m b)` 到一个上下文中的值 `m a` ,并返回另一个在相同上下文中的值 `m b` 。
237 |
238 | 注:`>>=` 函数的发音为 `bind` ,学习 `ReactiveCocoa` 的同学要注意啦。另外,`>>=` 函数可类比 `Swift` 中的 `flatMap` 方法。
239 |
240 | ## Maybe Monad
241 |
242 | 同样的,我们将类型占位符 `m` 用具体类型 `Maybe` 代入,可得:
243 |
244 | ```haskell
245 | class Applicative Maybe => Monad Maybe where
246 | return :: a -> Maybe a
247 | (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
248 | ```
249 |
250 | 相信你用盒子模型已经能够轻松地理解上面两个函数了,因此不再赘述。接下来,我们一起来看一下 `Maybe` 类型实现 `Monad typeclass` 的具体细节:
251 |
252 | ```haskell
253 | instance Monad Maybe where
254 | return x = Just x
255 | Nothing >>= func = Nothing
256 | Just x >>= func = func x
257 | ```
258 |
259 | 正如前面所说, `return` 函数的实现跟 `pure` 函数一样,直接等于 `Just` 函数即可,功能就是将一个值 `x` 放入 `Maybe` 盒子中,变成 `Just x` 。同样的,对于 `(>>=)` 函数的实现,我们需要针对 `Maybe` 上下文的两种情况分别进行处理,当盒子为空时,直接返回一个新的空盒子;当盒子不为空时,即 `Just x` ,则取出 `x` ,直接将 `func` 函数应用到 `x` ,而我们知道 `func x` 的结果就是一个在上下文中的值。
260 |
261 | 下面,我们一起来看一个具体的例子。我们先定义一个 `half` 函数,这个函数接收一个数字 `x` 作为参数,如果 `x` 是偶数,则将 `x` 除以 `2` ,并将结果放入 `Maybe` 盒子中;如果 `x` 不是偶数,则返回一个空盒子:
262 |
263 | 
264 |
265 | ```haskell
266 | half x = if even x
267 | then Just (x `div` 2)
268 | else Nothing
269 | ```
270 |
271 | 接下来,我们使用 `(>>=)` 函数将 `half` 函数应用到 `Just 20` ,假设得到结果 `y` ;然后继续使用 `(>>=)` 函数将 `half` 函数应用到上一步的结果 `y` ,以此类推,看看会得到什么样的结果:
272 |
273 | ```shell
274 | ghci> Just 20 >>= half
275 | Just 10
276 | ghci> Just 10 >>= half
277 | Just 5
278 | ghci> Just 5 >>= half
279 | Nothing
280 | ```
281 |
282 | 看到上面的运算过程,不知道你有没有看出点什么端倪呢?上一步的输出作为下一步的输入,并且只要你愿意的话,这个过程可以无限地进行下去。我想你可能已经想到了,是的,就是链式操作。所有的操作链接起来就像是一条生产线,每一步的操作都是对输入进行加工,然后产生输出,整个操作过程可以看作是对最初的原材料 `Just 20` 进行加工并最终生产出成品 `Nothing` 的过程:
283 |
284 | 
285 |
286 | ```shell
287 | ghci> Just 20 >>= half >>= half >>= half
288 | Nothing
289 | ```
290 |
291 | 注:链式操作只是 `Monad` 为我们带来的主要好处之一;另一个本文并未涉及到的主要好处是, `Monad` 可以为我们自动处理上下文,而我们只需要关心真正的值就可以了。
292 |
293 | ## 总结
294 |
295 | `Functor` `、Applicative` 和 `Monad` 是什么:
296 |
297 | 一个 `Functor` 就是一种实现了 `Functor typeclass` 的数据类型;
298 | 一个 `Applicative` 就是一种实现了 `Applicative typeclass` 的数据类型;
299 | 一个 `Monad` 就是一种实现了 `Monad typeclass` 的数据类型。
300 |
301 | `Functor` `、Applicative` 和 `Monad` 三者之间的联系:
302 | `Applicative` 是增强型的 `Functor` ,一种数据类型要成为 `Applicative` 的前提条件是它必须是 `Functor` ;
303 | `Monad` 是增强型的 `Applicative` ,一种数据类型要成为 `Monad` 的前提条件是它必须是 `Applicative` 。
304 |
305 | `Functor` `、Applicative` 和 `Monad` 三者之间的区别:
306 | `Functor` :使用 `fmap` 应用一个函数到一个上下文中的值;
307 | `Applicative` :使用 `<*>` 应用一个上下文中的函数到一个上下文中的值;
308 | `Monad` :使用 `>>=` 应用一个接收一个普通值但是返回一个在上下文中的值的函数到一个上下文中的值。
309 | 此外,我们还介绍了一种非常有意思的数据类型 `Maybe` ,它实现了 `Functor typeclass`、`Applicative typeclass` 和 `Monad typeclass` ,所以它同时是 `Functor`、`Applicative` 和 `Monad` 。
310 |
311 | 以上就是本文的全部内容,希望可以对你有所帮助,Good luck !
--------------------------------------------------------------------------------
/es5-senior/article/img/JavaScript-object.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/es5-senior/article/prototype-chain.md:
--------------------------------------------------------------------------------
1 | # JavaScript深度理解原型链
2 |
3 | ## 原型链知识思维导图
4 |
5 | 
6 |
7 | ## 序言
8 |
9 | 为什么要有原型和原型链,因为这是JavaScript这门语言实现面向对象编程的基石之一。进一步理解,构造函数、原型和原型链是JavaScript实现继承的基础,而继承是面向对象编程重要特性。原型与原型链是JavaScript这门语言一个重要的特性,深刻理解非常重要。
10 |
11 | ## 什么是原型?
12 |
13 | 我们创建的每个函数都有一个 `prototype` (原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。换言之, `prototype` 就是通过调用构造函数而创建的那个对象实例的原型对象。
14 | (每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
15 | 原型对象、实例原型是一回事,都是不同人的叫法不同)
16 |
17 | ```js
18 | // 通常构造函数默认使用大写开头
19 | function Person(name, age, job) {
20 | this.name = name;
21 | this.age = age;
22 | this.sayName = function () {
23 | console.info(this.name);
24 | }
25 | }
26 |
27 | console.info(Person);
28 | ```
29 |
30 | 备注:chrome浏览器的控制台不好打印函数属性,Firefox是可以的,下图Firefox打印。
31 | 
32 |
33 | 总结:
34 | 1. JavaScript中,凡是函数都会有 `prototype` 属性,该属性是一个指针,指针指向原型对象。
35 | 2. 原型对象是一个实实在在的对象,原型属性则是一个指针。
36 |
37 | ```js
38 | console.info(typeof Person.prototype) // => object
39 | ```
40 |
41 | ## 理解原型对象、prototype属性、constructor构造函数
42 |
43 | JavaScript中,**无论什么时候**,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 `prototype` 属性,这个属性指向函数的原型对象。
44 | 在默认情况下,所有原型对象都会自动获得一个 `constructor` (构造函数)属性,这个属性是一个指向 `prototype` 属性所在函数的指针。
45 | 构造函数、原型对象、实例之间关系图
46 |
47 | 
48 |
49 | 函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1 和 person2 的原型。
50 |
51 | ``` js
52 | function Person(name, age) {
53 | this.name = name;
54 | this.age = age;
55 |
56 | this.selfIntroduction = function () {
57 | console.log(this.name + " is " + this.age + " years old.", 'color:red;');
58 | };
59 | }
60 |
61 | var person = new Person("shaogucheng", 18);
62 | console.info(Person.prototype);
63 | ```
64 |
65 | 控制台可以看到清晰的结构
66 | 
67 |
68 | 创建了自定义的构造函数之后,其原型对象默认只会取得 `constructor` 属性;至于其他方法,则都是从 `Object` 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。**ECMA-262**第5版中管这个指针叫 `[[Prototype]]` 。虽然在脚本中没有标准的方式访问 `[[Prototype]]` ,但Firefox、Safari和Chrome在每个对象上都支持一个属性 `__proto__` ;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,**这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间**。
69 |
70 | ### 确定对象之间关系(确立构造函数与实例对象间关系)
71 |
72 | 方法一:简单粗暴图示法
73 | 直接撸出这样一段代码,执行过后:
74 |
75 | ```js
76 | var Lily = new Person('Lily', 18);
77 | console.info(Lily);
78 | ```
79 |
80 | Firefox浏览器中对于 `[[Prototype]]` 指针的实现是 `` ,这里通过 `[[Prototype]]` 指针就可以找到构造函数原型对象,找到构造函数,那么实例化对象的构造函数就一目了然。
81 | 
82 |
83 | chrome浏览器对于 `[[Prototype]]` 指针的实现是 `__proto__` ,这里通过 `[[Prototype]]` 指针就可以找到构造函数原型对象,找到构造函数,那么实例化对象的构造函数就一目了然。
84 | 
85 |
86 | 方法二:科学严谨代码法
87 | 一种简单的判断方式是:我们知道实例的 `[[Prototype]]` 指针是指向构造函数原型对象的,所以有这样的代码;
88 |
89 | ```js
90 | var person = new Person("shaogucheng", 18);
91 | console.info(person.__proto__ === Person.prototype); // => true
92 | // 或者更加过分一点
93 | console.info(person.__proto__.constructor === Person); // => true
94 | ```
95 |
96 | 另外一种方法是利用Object对象函数的API,`isPrototypeOf()` 还有 `getPrototypeOf()` 。
97 |
98 | 很遗憾chrome访问不到Object API
99 | 
100 | FireFox提供了API
101 | 
102 |
103 | 然后撸出这样的代码:
104 |
105 | ```js
106 | console.info(Person.prototype.isPrototypeOf(person)); // => true
107 | console.info(Object.getPrototypeOf(person) === Person.prototype); // => true
108 | ```
109 |
110 | ------------------
111 |
112 | ## 原型的用途
113 |
114 | 之前一直介绍原型,各种概念纷繁复杂搞得人头昏脑涨,我一直认为学以致用是一种更加有趣的学习方式,那么接下里就要重点介绍原型的用处了。
115 | > 使用原型最大的好处就是:可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
116 |
117 | ```js
118 | function Person() { }
119 |
120 | Person.prototype.language = "Chinese"
121 | Person.prototype.gender = "unknown"
122 | Person.prototype.name = "Legend of the Dragon"
123 | Person.prototype.sayName = function () {
124 | console.info(this.name)
125 | }
126 |
127 | var person1 = new Person();
128 | var person2 = new Person();
129 | person1.sayName(); // => Legend of the Dragon
130 | person2.sayName(); // => Legend of the Dragon
131 | console.info(person1.sayName === person2.sayName); // => true
132 | ```
133 |
134 | ------------------
135 |
136 | ## 分步理解原型链构成
137 |
138 | 在讲解原型链之前,我希望先给大家铺垫一下基础知识。一方面是加深对于JavaScript数据类型的了解,另一方面更有助于对于整个原型链每个环节有个清晰的认识。
139 |
140 | ### 重新认识数据类型
141 |
142 | 首先在以往大家学习JavaScript数据类型的时候,都会有这样的认识:
143 | > JavaScript只有七种数据类型(注意都是首字母小写):
144 | > 基本类型:number、string 、boolean、null、undefined、symbol、
145 | > 引用类型:object
146 |
147 | 这里我想做一个更正,更正以往大家对JavaScript数据类型的认识。
148 |
149 | > JavaScript只有七种数据类型(注意都是首字母小写):
150 | > 基本类型(值类型):number、string 、boolean、undefined、symbol、
151 | > 引用类型:object、function
152 |
153 | 这里之所以把null类型去掉,是因为null只表示数据值,null的实际数据类型是object。
154 |
155 | 然后,object和Object,function和Function表示的意义是不同的,function与object是数据类型,Function与Object是两个函数对象的标识符(简称**函数标识对象**,等价于两个函数对象),Function与Object的数据类型都是function。数据类型为object的数据对象(除了null)外其实例类型是Object,而数据类型为function的实例类型既是Function,也是Object,原因在于 `Function.prototype.__proto__ == Object.prototype` ,所以所有的object和funtion数据类型的数据对象的实例类型都是Object。因为实例类型是按原型链查找的。
156 |
157 | 数据类型为object的数据对象,内置__proto__属性,而数据类型为function的数据对象(函数),内置__proto__,还有scope,prototype,length等属性。关键在于object类型的只有声明创建时,而function类型的除了声明创建时,还有函数运行时。
158 |
159 | 示例代码(typeof 表示数据类型,instanceof表示实例类型---用原型链查找):
160 |
161 | ``` js
162 | var a = function(){};
163 | var b = {};
164 | var c = 1;
165 | var d;
166 | var e = null;
167 | var f = false;
168 | var g = "";
169 |
170 | console.log( typeof a);//function
171 | console.log( a instanceof Function);//true
172 | console.log( a instanceof Object);//true
173 |
174 | console.log( typeof b);//object
175 | console.log( b instanceof Function);//true
176 | console.log( b instanceof Object);//true
177 |
178 | console.log( typeof c);//number
179 | console.log( c instanceof Function);//false
180 | console.log( c instanceof Object);//false
181 |
182 | console.log( typeof d);//undefined
183 | console.log( d instanceof Function);//false
184 | console.log( d instanceof Object);//false
185 |
186 | console.log( typeof e);//object
187 | console.log( e instanceof Function);//false
188 | console.log( e instanceof Object);//false
189 |
190 | console.log( typeof f);//boolean
191 | console.log( f instanceof Function);//false
192 | console.log( f instanceof Object);//false
193 |
194 | console.log( typeof g);//string
195 | console.log( g instanceof Function);//false
196 | console.log( g instanceof Object);//false
197 |
198 | console.log(typeof Object);//function
199 | console.log(typeof Function);//function
200 |
201 | console.log(Object instanceof Object); // true
202 | console.log(Function instanceof Object);// true
203 | console.log(Object instanceof Function);// true
204 | console.log(Function instanceof Function);// true
205 |
206 | var Fn = function(){}; // 这是函数声明时
207 | Fn(); // 这是函数运行时.
208 | ```
209 |
210 | ### Function和Object
211 |
212 | 常在JavaScript群子里混的同学可能都会听到这样两句话:
213 | > 1. 一切都是对象(万物皆对象)
214 | > 2. 函数是一等公民
215 |
216 | 我们今天抛开JavaScript函数式编程,但从面向对象的角度来理解这两句话。
217 |
218 | ```js
219 | console.info(Object);
220 | console.info(Function)
221 |
222 | console.log(Object instanceof Object); // true
223 | console.log(Function instanceof Object);// true
224 | console.log(Object instanceof Function);// true
225 | console.log(Function instanceof Function);// true
226 |
227 | console.log( Object.__proto__ == Function.prototype);//true
228 | console.log( Function.__proto__ == Function.prototype);//true
229 | console.log( Function.prototype.__proto__ == Object.prototype);//true
230 | // 关键在这一步: Function.prototype.__proto__ == Object.prototype
231 | ```
232 |
233 | 我们的chrome照例是不支持native code显示的。
234 | 
235 | Firefox展示的就比较清楚了
236 | 
237 | 
238 |
239 | 由此可见,Object继承自己,Funtion继承自己,Object和Function互相是继承对方,也就是说Object和Function都既是函数也是对象。这一点很特别。所有的函数都是对象,可是并不是所有的对象都是函数。证明如下:
240 |
241 | ``` js
242 | function foo(){};
243 | console.log(foo instanceof Function); // true
244 | console.log(foo instanceof Object); // true
245 | console.log(new foo() instanceof Function); // false
246 | ```
247 |
248 | 我们看到由new + function的构造器实例化出来的对象不是函数,仅仅是Object的子类。接下来,我们会想所有的对象都有一个Function的构造器存储在原型链的constructor属性中,那么Object的构造器是什麽呢? 证明:
249 |
250 | ``` js
251 | console.log(Object.constructor); // function Function(){ [native code] }
252 | console.log(Function.constructor); // function Function(){ [native code] }
253 | ```
254 |
255 | 由此我们可以确定Object是由Function这个函数为原型的,而Function是由它自己为原型的。Function函数是由native code构成的,我们不用去深究了。存在function Function(){...}的定义,我们明白了Function其实就是函数指针,也看作函数变量。就相当于function foo(){}中的foo。连Object的构造器都是指向Function的,可以想象Function是一个顶级的函数定义,大大的区别于自定义的如function foo(){}这样的函数定义。
256 |
257 | 看这样一个语句,new Function();以Function为原型来实例化一个对象,按照刚才Object.constructor 为 functon Function(){}来说,new Function()产生的对象应该是一个Object啊,我们来验证一下:
258 |
259 | ``` js
260 | console.log(new Function() instanceof Object); // true
261 | console.log(Object instanceof new Function());// false
262 | console.log(typeof new Function());// function
263 | console.log(typeof Object);// function
264 | ```
265 |
266 | 其实new Function();将产生一个匿名函数,由于Function是顶级函数所以可以产生自定义函数,我们可以把所有的函数看作Function的子类。但所有的一切包括函数都是Object的子类这点是不变的,也得到了体现。typeof Object的结果说明Object也是一个函数。继续做实验:
267 |
268 | ``` js
269 | alert(new Object().constructor); // function Object(){ [native code] }
270 | ```
271 |
272 | 一个情理之中的疑惑。可以这么说凡是可以放在new后面的都是一个函数构造器,那么Object确实也像其它函数一样,是一个函数的指针或者是函数变量,但Function是顶级的所以Object要由Function来构造。可以这么理解,Function和Object一个是上帝,一个是撒旦同时诞生于宇宙的最开始,拥相当的力量。但是上帝更为光明,所以高高在上。Object要由Function来构造,Function属于顶级函数。但是撒旦并没有绝对的输给上帝,否则上帝就会消灭撒旦。于是或所有的对象都要继承Object包括Function(Object和Function既是对象又是函数)。就相当于所有的人包括上帝都有邪念一样。
273 |
274 | 拓展一下,由Object我们会想到Array,Number,String等这些内置对象。有理由相信这些都是Object的子类。如下:
275 |
276 | ``` js
277 | console.log(Array instanceof Object) // true
278 | console.log(String instanceof Object) // true
279 | console.log(Number instanceof Object) // true
280 | console.log(Object instanceof Array) // false
281 | console.log(Object instanceof String) // false
282 | console.log(Object instanceof Number) // false
283 | ```
284 |
285 | 当然他们也都会有Object的特性就像魔鬼和撒旦的关系一样,也是Function的子类,由Function构造。那么有Array,String,Number构造的对像如:new Array();new Number();new String()的构造器是function Array(){...};function String(){...};function Number(){...};
286 |
287 | ``` js
288 | alert(Array instanceof Function) // true
289 | alert(String instanceof Function) // true
290 | alert(Number instanceof Function) // true
291 | alert(Array.constructor) // function Function(){ [native code] }
292 | alert(String.constructor) // function Function(){ [native code] }
293 | alert(Number.constructor) // function Function(){ [native code] }
294 | ```
295 |
296 | 总结一下,像内置的函数或说对象把如:Object,String,Array等等和自定义的function关键字定义的函数,都是Function的子类。new Function()相当于function关键字定义。这里可以引出,Function.prototype原型链上的属性所有函数共享,Object.prototype原型链上的属性所有对象共享。
297 |
298 | QAQ:真心想要吐槽一句,如此复杂的数据类型设计,完完全全了解其中复杂的关系,也是颇为费力的一件事情,也正是JavaScript令人吐槽的地方,设计糟糕的一种体现,可同样也是巧妙的让人绝望。
299 |
300 | 说了这么多,做一个简单的总结:
301 | 首先第一个概念:**function与object是数据类型,Function与Object是两个函数对象的标识符(等价于两个函数对象),Function与Object的数据类型都是function。**
302 | 其次第二个概念:**JavaScript中所有的对象都继承自Object原型,而Function又充当了对象(Object)的构造器。**
303 | 然后第三个概念:**一切都是对象**
304 |
305 | 另外附送一张鄙人惊喜绘制的关系图,以供参考理解。
306 | 关系图理解基础(在阅读下图之前,希望牢记下面两点):
307 | 1. 所有函数都会有prototype属性,该属性是个指针,指向prototype实例原型。
308 | 2. 所有对象都会有__proto__属性,该属性是个指针,指向父类prototype原型对象。
309 | 3. prototype是个真真实实,如假包换的对象。
310 |
311 | 这张图可能很好的看到Function和Object的内在联系。
312 | 
313 |
314 | * `Object` 是所有对象的爸爸,所有对象都可以通过 `__proto__` 找到它
315 | * `Function` 是所有函数的爸爸,所有函数都可以通过 `__proto__` 找到它
316 | * 函数的 `prototype` 是一个对象
317 | * 对象的 `__proto__` 属性指向原型, `__proto__` 将对象和原型连接起来组成了原型链
318 |
319 | 另外附上Function和Object的API文档以供参考:
320 |
321 | [MDN Object API](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object) 请点击链接访问
322 | [MDN Function API](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function)请点击链接访问
323 |
324 | 这里的Person就是构造函数本尊,它本身是对象,也是函数。当Person这个函数新建之后,就会有一个属性(其实是一个指针),指向原型对象(实例原型),原型对象是客观存在的对象。
325 |
326 | ``` js
327 | function Person(name, age) {
328 | this.name = name;
329 | this.age = age;
330 |
331 | this.selfIntroduction = function () {
332 | console.log(this.name + " is " + this.age + " years old.", 'color:red;');
333 | };
334 | }
335 |
336 | var person = new Person("shaogucheng", 18);
337 | console.info(Person.prototype);
338 | console.info(person);
339 | console.info(person.prototype);
340 | ```
341 | 
342 | 
343 |
344 | > 我们创建的每个函数都有一个 `prototype` (原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。 ——《JavaScript高级程序设计》
345 |
346 | 正如图中展示的那样,Person函数天生就会有一个属性,prototype,这个属性是一个指针,指向原型对象(实例原型)
347 | 
348 |
349 | 在控制台运行 `var person = new Person("shaogucheng", 18);` 根据构造函数new出新的对象,这个其实就是面向对象中,根据模板实例化对象的过程(Person --> person)。
350 |
351 | 
352 |
353 | > 这里要稍微介绍一下new的过程:
354 | > 1. 创建一个新对象;
355 | > 2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
356 | > 3. 执行构造函数中的代码(为这个新对象添加属性);
357 | > 4. 返回新对象。
358 |
359 | 为了更好的理解上图,我们在控制台输入 `console.info(person);` ,输出相关信息:
360 | 
361 | 这样,无论使用代码 `console.info(Person.prototype === person.__proto__);` 结果为 `true` 还是看日志中两者的结构,都能得出的结论。实例上的 `__proto__` 属性是指向实例原型,这大概也就是为什么原型对象也叫实例原型的原因吧。
362 |
363 | 
364 |
365 | 我们可以在控制台执行 `console.info(Person.prototype);` 根据打印的日志信息,可以直观看到原型对象,并且看到原型对象上的属性。在原型对象上有一个构造属性**constructor**,并且这个构造属性也是一个指针,指向构造函数本身,这样就可以得出上图中实例原型中的constructor属性指向构造函数本身的线。
366 | 
367 |
368 | 构造函数的原型对象的原型又是谁呢?
369 | 
370 |
371 | 喜大普奔的是chrome浏览器支持显示 `__proto__` 属性,所以在控制台输入:`console.info(Person.prototype.__proto__);` 我们就能愉快的观察到Person构造函数的原型对象的原型是谁。
372 | 
373 | 这样,真相就已经很明显了。
374 |
375 | 
376 | 上图就已经很明显的给出了原型链的雏形。
377 |
378 | 最后的最后我们祭出经典原型链图。
379 | 
380 |
381 | the constructor property of an instance of a function object "specifies the function that creates an object's prototype". This is confusing, so Object.constructor is "the function that creates an object's prototype"? What object is "an object" exactly?
382 |
383 | I'm trying to understand why is Object.constructor's constructor property itself?
384 |
385 | as such: Object.constructor===Object.constructor.constructor // why?
386 |
387 | Edit: i find T.J. Crowder's answer good but the phrasing of his words is pretty vague (making it hard to understand at first read, at least for me). Here's the rephrased answer:
388 |
389 | 1) Object is an instance of Function
390 |
391 | 2) Object does not have a property called constructor so when we call Object.constructor, it actually gives us Object.[[prototype]].constructor (aka Object.__proto__.constructor).
392 |
393 | 3) Object.constructor (aka Object.__proto__.constructor) is an instance of Function.
394 |
395 | 4) Since both Object and Object.constructor (aka Object.__proto__.constructor) are instances of Function therefore they both have a __proto__ property which refer to the same object. In other words Object.__proto__ === Object.constructor.__proto__ (aka Object.__proto__.constructor._proto_)
396 |
397 | 5) The line Object.constructor===Object.constructor.constructor is actually equal to the line Object.__proto__.constructor===Object.constructor.__proto__.constructor
398 |
399 | 6) combining steps 4 and 5 give us Object.constructor===Object.constructor.constructor
400 |
401 | 7) goto step 4)
--------------------------------------------------------------------------------