diff --git a/docs/function.md b/docs/function.md index 4b44ec9..355bc4b 100644 --- a/docs/function.md +++ b/docs/function.md @@ -730,7 +730,7 @@ var f = function(v) { ```javascript var f = () => 5; // 等同于 -var f = function (){ return 5 }; +var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 @@ -739,7 +739,7 @@ var sum = function(num1, num2) { }; ``` -如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。 +如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用`return`语句返回。 ```javascript var sum = (num1, num2) => { return num1 + num2; } @@ -757,7 +757,7 @@ var getTempItem = id => ({ id: id, name: "Temp" }); const full = ({ first, last }) => first + ' ' + last; // 等同于 -function full( person ){ +function full( person ) { return person.first + ' ' + person.last; } ``` @@ -787,7 +787,7 @@ const square = n => n * n; ```javascript // 正常函数写法 -var result = values.sort(function(a, b) { +var result = values.sort(function (a, b) { return a - b; }); @@ -826,8 +826,8 @@ headAndTail(1, 2, 3, 4, 5) ```javascript function foo() { setTimeout( () => { - console.log("id:", this.id); - },100); + console.log('id:', this.id); + }, 100); } var id = 21; @@ -838,37 +838,49 @@ foo.call( { id: 42 } ); 上面代码中,`setTimeout`的参数是一个箭头函数,这个箭头函数的定义生效是在`foo`函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时`this`应该指向全局对象`window`,这时应该输出`21`。但是,箭头函数导致`this`总是指向函数定义生效时所在的对象(本例是`{id: 42}`),所以输出的是`42`。 -下面是另一个例子。 +箭头函数可以让`setTimeout`里面的`this`,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。 + +```javascript +function Timer () { + this.s1 = 0; + this.s2 = 0; + // 箭头函数 + setInterval(() => this.s1++, 1000); + // 普通函数 + setInterval(function () { + this.s2++; + }, 1000); +} + +var timer = new Timer(); + +setTimeout(() => console.log('s1: ', timer.s1), 3100); +setTimeout(() => console.log('s2: ', timer.s2), 3100); +// s1: 3 +// s2: 0 +``` + +上面代码中,`Timer`函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的`this`绑定定义时所在的作用域(即`Timer`函数),后者的`this`指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,`timer.s1`被更新了3次,而`timer.s2`一次都没更新。 + +箭头函数可以让`this`指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM事件的回调函数封装在一个对象里面。 ```javascript var handler = { - id: "123456", + id: '123456', init: function() { - document.addEventListener("click", + document.addEventListener('click', event => this.doSomething(event.type), false); }, doSomething: function(type) { - console.log("Handling " + type + " for " + this.id); + console.log('Handling ' + type + ' for ' + this.id); } }; ``` 上面代码的`init`方法中,使用了箭头函数,这导致这个箭头函数里面的`this`,总是指向`handler`对象。否则,回调函数运行时,`this.doSomething`这一行会报错,因为此时`this`指向`document`对象。 -```javascript -function Timer () { - this.seconds = 0 - setInterval(() => this.seconds++, 1000) -} -var timer = new Timer() -setTimeout(() => console.log(timer.seconds), 3100) -// 3 -``` - -上面代码中,`Timer`函数内部的`setInterval`调用了`this.seconds`属性,通过箭头函数让`this`总是指向`Timer`的实例对象。否则,输出结果是0,而不是3。 - `this`指向的固定化,并不是因为箭头函数内部有绑定`this`的机制,实际原因是箭头函数根本没有自己的`this`,导致内部的`this`就是外层代码块的`this`。正是因为它没有`this`,所以也就不能用作构造函数。 所以,箭头函数转成ES5的代码如下。 @@ -877,7 +889,7 @@ setTimeout(() => console.log(timer.seconds), 3100) // ES6 function foo() { setTimeout( () => { - console.log("id:", this.id); + console.log('id:', this.id); },100); } @@ -886,12 +898,12 @@ function foo() { var _this = this; setTimeout(function () { - console.log("id:", _this.id); + console.log('id:', _this.id); }, 100); } ``` -上面代码中,箭头函数转成ES5代码时,内部的`this`需要改为引用外部的`this`。 +上面代码中,转换后的ES5版本清楚地说明了,箭头函数里面根本没有自己的`this`,而是引用外层的`this`。 请问下面的代码之中有几个`this`? @@ -900,7 +912,7 @@ function foo() { return () => { return () => { return () => { - console.log(`id:`, this.id); + console.log('id:', this.id); }; }; }; @@ -913,18 +925,18 @@ var t2 = f().call({id: 3})(); // id: 1 var t3 = f()().call({id: 4}); // id: 1 ``` -上面代码之中,只有一个`this`,就是函数`foo`的`this`,所以`t1`、`t2`、`t3`都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的`this`,所以它们的`this`其实都是最外层`foo`函数的`this`。 +上面代码之中,只有一个`this`,就是函数`foo`的`this`,所以`t1`、`t2`、`t3`都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的`this`,它们的`this`其实都是最外层`foo`函数的`this`。 除了`this`,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:`arguments`、`super`、`new.target`。 ```javascript function foo() { - setTimeout( () => { - console.log("args:", arguments); - },100); + setTimeout(() => { + console.log('args:', arguments); + }, 100); } -foo( 2, 4, 6, 8 ); +foo(2, 4, 6, 8) // args: [2, 4, 6, 8] ``` diff --git a/docs/generator.md b/docs/generator.md index ed490ab..f2f76b2 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -1041,87 +1041,87 @@ obj.a // undefined 上面代码中,Generator函数`g`在`this`对象上面添加了一个属性`a`,但是`obj`对象拿不到这个属性。 +Generator函数也不能跟`new`命令一起用,会报错。 ```javascript -function* F(){ +function* F() { yield this.x = 2; yield this.y = 3; } + +new F() +// TypeError: F is not a constructor ``` -上面代码中,函数F是一个构造函数,又是一个Generator函数。这时,使用new命令就无法生成F的实例了,因为F返回的是一个内部指针。 +上面代码中,`new`命令跟构造函数`F`一起使用,结果报错,因为`F`不是构造函数。 + +那么,有没有办法让Generator函数返回一个正常的对象实例,既可以用`next`方法,又可以获得正常的`this`? + +下面是一个变通方法。首先,生成一个空对象,使用`bind`方法绑定Generator函数内部的`this`。这样,构造函数调用以后,这个空对象就是Generator函数的实例对象了。 ```javascript -'next' in (new F()) -// true -``` - -上面代码中,由于`new F()`返回的是一个Iterator对象,具有next方法,所以上面的表达式为true。 - -如果要把Generator函数当作正常的构造函数使用,可以采用下面的变通方法。首先,生成一个空对象,使用`bind`方法绑定Generator函数内部的`this`。这样,构造函数调用以后,这个空对象就是Generator函数的实例对象了。 - -```javascript -function* F(){ - yield this.x = 2; - yield this.y = 3; +function* F() { + this.a = 1; + yield this.b = 2; + yield this.c = 3; } var obj = {}; -var f = F.bind(obj)(); +var f = F.call(obj); f.next(); // Object {value: 2, done: false} f.next(); // Object {value: 3, done: false} f.next(); // Object {value: undefined, done: true} -obj // { x: 2, y: 3 } +obj.a // 1 +obj.b // 2 +obj.c // 3 ``` 上面代码中,首先是`F`内部的`this`对象绑定`obj`对象,然后调用它,返回一个Iterator对象。这个对象执行三次`next`方法(因为`F`内部有两个`yield`语句),完成F内部所有代码的运行。这时,所有内部属性都绑定在`obj`对象上了,因此`obj`对象也就成了`F`的实例。 -## Generator函数推导 +上面代码中,执行的是遍历器对象`f`,但是生成的对象实例是`obj`,有没有办法将这两个对象统一呢? -ES7在数组推导的基础上,提出了Generator函数推导(Generator comprehension)。 +一个办法就是将`obj`换成`F.prototype`。 ```javascript -let generator = function* () { - for (let i = 0; i < 6; i++) { - yield i; - } +function* F() { + this.a = 1; + yield this.b = 2; + yield this.c = 3; } +var f = F.call(F.prototype); -let squared = ( for (n of generator()) n * n ); -// 等同于 -// let squared = Array.from(generator()).map(n => n * n); +f.next(); // Object {value: 2, done: false} +f.next(); // Object {value: 3, done: false} +f.next(); // Object {value: undefined, done: true} -console.log(...squared); -// 0 1 4 9 16 25 +f.a // 1 +f.b // 2 +f.c // 3 ``` -“推导”这种语法结构,不仅可以用于数组,ES7将其推广到了Generator函数。for...of循环会自动调用遍历器的next方法,将返回值的value属性作为数组的一个成员。 - -Generator函数推导是对数组结构的一种模拟,它的最大优点是惰性求值,即直到真正用到时才会求值,这样可以保证效率。请看下面的例子。 +再将`F`改成构造函数,就可以对它执行`new`命令了。 ```javascript -let bigArray = new Array(100000); -for (let i = 0; i < 100000; i++) { - bigArray[i] = i; +function* gen() { + this.a = 1; + yield this.b = 2; + yield this.c = 3; } -let first = bigArray.map(n => n * n)[0]; -console.log(first); -``` - -上面例子遍历一个大数组,但是在真正遍历之前,这个数组已经生成了,占用了系统资源。如果改用Generator函数推导,就能避免这一点。下面代码只在用到时,才会生成一个大数组。 - -```javascript -let bigGenerator = function* () { - for (let i = 0; i < 100000; i++) { - yield i; - } +function F() { + return gen.call(gen.prototype); } -let squared = ( for (n of bigGenerator()) n * n ); +var f = new F(); -console.log(squared.next()); +f.next(); // Object {value: 2, done: false} +f.next(); // Object {value: 3, done: false} +f.next(); // Object {value: undefined, done: true} + +f.a // 1 +f.b // 2 +f.c // 3 ``` ## 含义 diff --git a/docs/reference.md b/docs/reference.md index 4f4d746..167229f 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -76,6 +76,7 @@ - Ragan Wald, [Destructuring and Recursion in ES6](http://raganwald.com/2015/02/02/destructuring.html): rest参数和扩展运算符的详细介绍 - Axel Rauschmayer, [The names of functions in ES6](http://www.2ality.com/2015/09/function-names-es6.html): 函数的name属性的详细介绍 - Kyle Simpson, [Arrow This](http://blog.getify.com/arrow-this/): 箭头函数并没有自己的this +- Derick Bailey, [Do ES6 Arrow Functions Really Solve “this” In JavaScript?](http://derickbailey.com/2015/09/28/do-es6-arrow-functions-really-solve-this-in-javascript/):使用箭头函数处理this指向,必须非常小心 - Mark McDonnell, [Understanding recursion in functional JavaScript programming](http://www.integralist.co.uk/posts/js-recursion.html): 如何自己实现尾递归优化 ## 对象