Skip to content

Commit 9be34c0

Browse files
committed
docs(proposals): edit proposals
1 parent 3bddf9c commit 9be34c0

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

docs/proposals.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,189 @@ const PS = eval("'\u2029'");
190190
根据这个提案,上面的代码不会报错。
191191
192192
注意,模板字符串现在就允许直接输入这两个字符。另外,正则表达式依然不允许直接输入这两个字符,这是没有问题的,因为 JSON 本来就不允许直接包含正则表达式。
193+
194+
## 函数的部分执行
195+
196+
### 语法
197+
198+
多参数的函数有时需要绑定其中的一个或多个函数,然后返回一个新函数。
199+
200+
```javascript
201+
function add(x, y) { return x + y; }
202+
function add7(x) { return x + 7; }
203+
```
204+
205+
上面代码中,`add7`函数其实是`add`函数的一个特殊版本,通过将一个参数绑定为`7`,就可以从`add`得到`add7`
206+
207+
```javascript
208+
// bind 方法
209+
const add7 = add.bind(null, 7);
210+
211+
// 箭头函数
212+
const add7 = x => add(x, 7);
213+
```
214+
215+
上面两种写法都有些冗余。其中,`bind`方法的局限更加明显,它必须提供`this`,并且只能从前到后一个个绑定参数,无法只绑定非头部的参数。
216+
217+
现在有一个[提案](https://github.com/tc39/proposal-partial-application),使用绑定参数然后返回一个新函数更加容器。这叫做函数的部分执行(partial application)。
218+
219+
```javascript
220+
const add = (x, y) => x + y;
221+
const addOne = add(1, ?);
222+
223+
const maxGreaterThanZero = Math.max(0, ...);
224+
```
225+
226+
根据新提案,`?`是单个参数的占位符,`...`是多个参数的占位符。以下的形式都属于函数的部分执行。
227+
228+
```javascript
229+
f(x, ?)
230+
f(x, ...)
231+
f(?, x)
232+
f(..., x)
233+
f(?, x, ?)
234+
f(..., x, ...)
235+
```
236+
237+
`?``...`只能出现在函数的调用之中,并且会返回一个新函数。
238+
239+
```javascript
240+
const g = f(?, 1, ...);
241+
// 等同于
242+
const g = (x, ...y) => f(x, 1, ...y);
243+
```
244+
245+
### 注意点
246+
247+
函数的部分执行有一些特别注意的地方。
248+
249+
(1)函数的部分执行是基于原函数的。如果原函数发生变化,部分执行生成的新函数也会立即反映这种变化。
250+
251+
```javascript
252+
let f = (x, y) => x + y;
253+
254+
const g = f(?, 3);
255+
g(1); // 4
256+
257+
// 替换函数 f
258+
f = (x, y) => x * y;
259+
260+
g(1); // 3
261+
```
262+
263+
上面代码中,定义了函数的部分执行以后,更换原函数会立即影响到新函数。
264+
265+
(2)如果预先提供的那个值是一个表达式,那么这个表达式并不会在定义时求值,而是在每次调用时求值。
266+
267+
```javascript
268+
let a = 3;
269+
const f = (x, y) => x + y;
270+
271+
const g = f(?, a);
272+
g(1); // 4
273+
274+
// 改变 a 的值
275+
a = 10;
276+
g(1); // 11
277+
```
278+
279+
上面代码中,预先提供的参数是变量`a`,那么每次调用函数`g`的时候,才会对`a`进行求值。
280+
281+
(3)如果新函数的参数多于占位符的数量,那么多余的参数将被忽略。
282+
283+
```javascript
284+
const f = (x, ...y) => [x, ...y];
285+
const g = f(?, 1);
286+
g(2, 3, 4); // [2, 1]
287+
```
288+
289+
上面代码中,函数`g`只有一个占位符,也就意味着它只能接受一个参数,多余的参数都会被忽略。
290+
291+
写成下面这样,多余的参数就没有问题。
292+
293+
```javascript
294+
const f = (x, ...y) => [x, ...y];
295+
const g = f(?, 1, ...);
296+
g(2, 3, 4); // [2, 1, 3, 4];
297+
```
298+
299+
(4)`...`只会被采集一次,如果函数的部分执行使用了多个`...`,那么每个`...`的值都将相同。
300+
301+
```javascript
302+
const f = (...x) => x;
303+
const g = f(..., 9, ...);
304+
g(1, 2, 3); // [1, 2, 3, 9, 1, 2, 3]
305+
```
306+
307+
上面代码中,`g`定义了两个`...`占位符,真正执行的时候,它们的值是一样的。
308+
309+
## 管道运算符
310+
311+
Unix 操作系统有一个管道机制(pipeline),可以把前一个操作的值传给后一个操作。这个机制非常有用,使得简单的操作可以组合成为复杂的操作。许多语言都有管道的实现,现在有一个[提案](https://github.com/tc39/proposal-partial-application),让 JavaScript 也拥有管道机制。
312+
313+
JavaScript 的管道是一个运算符,写作`|>`。它的左边是一个表达式,右边是一个函数。管道运算符把左边表达式的值,传入右边的函数进行求职。
314+
315+
```javascript
316+
x |> f
317+
// 等同于
318+
f(x)
319+
```
320+
321+
管道运算符最大的好处,就是可以把嵌套的函数,写成从左到右的链式表达式。
322+
323+
```javascript
324+
function doubleSay (str) {
325+
return str + ", " + str;
326+
}
327+
328+
function capitalize (str) {
329+
return str[0].toUpperCase() + str.substring(1);
330+
}
331+
332+
function exclaim (str) {
333+
return str + '!';
334+
}
335+
```
336+
337+
上面是三个简单的函数。如果要嵌套执行,传统的写法和管道的写法分别如下。
338+
339+
```javascript
340+
// 传统的写法
341+
exclaim(capitalize(doubleSay('hello')))
342+
// "Hello, hello!"
343+
344+
// 管道的写法
345+
'hello'
346+
|> doubleSay
347+
|> capitalize
348+
|> exclaim
349+
// "Hello, hello!"
350+
```
351+
352+
管道运算符只能传递一个值,这意味着它右边的函数必须是一个单参数函数。如果是多参数函数,就必须进行柯里化,改成单参数的版本。
353+
354+
```javascript
355+
function double (x) { return x + x; }
356+
function add (x, y) { return x + y; }
357+
358+
let person = { score: 25 };
359+
person.score
360+
|> double
361+
|> (_ => add(7, _))
362+
// 57
363+
```
364+
365+
上面代码中,`add`函数需要两个参数。但是,管道运算符只能传入一个值,因此需要事先提供另一个参数,并将其改成单参数的箭头函数`_ => add(7, _)`。这个函数里面的下划线并没有特别的含义,可以用其他符号代替,使用下划线只是因为,它能够形象地表示这里是占位符。
366+
367+
管道运算符对于`await`函数也适用。
368+
369+
```javascript
370+
x |> await f
371+
// 等同于
372+
await f(x)
373+
374+
const userAge = userId |> await fetchUserById |> getAgeFromUser;
375+
// 等同于
376+
const userAge = getAgeFromUser(await fetchUserById(userId));
377+
```
378+

docs/reflect.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,24 @@ Reflect.defineProperty(MyDate, 'now', {
387387

388388
如果`Reflect.defineProperty`的第一个参数不是对象,就会抛出错误,比如`Reflect.defineProperty(1, 'foo')`
389389

390+
这个方法可以与`Proxy.defineProperty`配合使用。
391+
392+
```javascript
393+
const p = new Proxy({}, {
394+
defineProperty(target, prop, descriptor) {
395+
console.log(descriptor);
396+
return Reflect.defineProperty(target, prop, descriptor);
397+
}
398+
});
399+
400+
p.foo = 'bar';
401+
// {value: "bar", writable: true, enumerable: true, configurable: true}
402+
403+
p.foo // "bar"
404+
```
405+
406+
上面代码中,`Proxy.defineProperty`对属性赋值设置了拦截,然后使用`Reflect.defineProperty`完成了赋值。
407+
390408
### Reflect.getOwnPropertyDescriptor(target, propertyKey)
391409

392410
`Reflect.getOwnPropertyDescriptor`基本等同于`Object.getOwnPropertyDescriptor`,用于得到指定属性的描述对象,将来会替代掉后者。

0 commit comments

Comments
 (0)