之前看过汤姆大叔的博客翻译的js the core
- 变量对象 http://www.cnblogs.com/TomXu/archive/2012/01/16/2309728.html
- 闭包 http://www.cnblogs.com/TomXu/archive/2012/01/31/2330252.html
- 原文 http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
对for循环中出现的问题可以做出解释
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
为什么直接data[k] = function(){ alert(k) }这样做k总是3, 因为闭包 data[0] data[1] data[2]的 [[scope]]属性是一样的, 他们是在同一个上下文中创建出的闭包, 他们
- data[0].scope = [ data0Context.VO, globalContextVO ]
- data[1].scope = [ data1Context.VO, globalContextVO ]
- data[2].scope = [ data2Context.VO, globalContext.VO ] 可以看到除了第一个是它们自己的VO外, 后面都是一样的, 他们引用的k不存在于他们自己的VO对象中, 由于外面k修改, 他们都跟着变了.
为什么加一层自执行函数可以.
for (var k = 0; k < 3; k++) {
(function temp(k){
data[k] = function () {
alert(k);
};
})(k);
}
此时他们的scope属性如下
- data[0].scope = [ data0Context.VO, tempContext.VO={ k:0}, globalContext.VO ]
- data[1].scope = [ data1Context.VO, tempContext.VO={ k:1}, globalContext.VO ]
- data[2].scope = [ data2Context.VO, tempContext.VO={ k:2}, globalContext.VO ] 可以看到他们的scope chain中引用到k的地方是没有重合的, 后修改for循环中的k是不会影响到他们scope chain中的k值的.
问题来了 http://dmitrysoshnikov.com/ecmascript/javascript-the-core/comment-page-3/#comment-173907
var data = [];
for (let k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
data[0](); // 0
data[1](); // 1
data[2](); // 2
阮一峰对ES6 let类似的问题有个解释, 块级作用域, 这个我也知道, 但解决不了我的问题 http://es6.ruanyifeng.com/#docs/let
我的想法是:
- data[0].scope = [ data0Context.VO, blockContext.VO={ k }, globalContext.VO ]
- data[1].scope = [ data1Context.VO, blockContext.VO={ k }, globalContext.VO ]
- data[2].scope = [ data2Context.VO, blockContext.VO={ k }, globalContext.VO ]
k存在于 let-for的块级作用域中, 而且是公共的, 就是引用的同一个k, 结果应该也是与 var 相同, 但是测试结果不是这样的, 遂求解答?
回复 (15)
@reverland
for of基本在任何平台对异步异常处理都不太对劲
怎么说?
若有所思... 此时的blockContext 应该与 前面的 tempContext 一样, 不重合~
看了下原文说的Evaluation context部分, 应该是for循环每次都会创建, blockContext中的k 值每次都不一样, 就起到了跟var+自执行函数一样的效果
简单地说, 如果for括号里面有let或者const, 那么for的body每次执行的时候都会新建一个词法环境, 并且把let和const的声明的变量名和值绑定到这个新的词法环境, 所以有上面的结果.
不是很明白,for let应该给每个循环一个单独的作用域,虽然没看过标准,但感觉标准应该做符合直觉的事情。来填坑了无数人的坑。
另外,现在各种环境的es6支持称不上完美。for of基本在任何平台对异步异常处理都不太对劲。最好babel转下……
然而最近好像碰到了个babel的错误。。。
@William17
yeah, 类似用var加一个自执行函数每次都创建一个作用域...
我问JavaScript the core的作者这个问题有回复了 http://dmitrysoshnikov.com/ecmascript/javascript-the-core/comment-page-3/#comment-173985
Per ES6 let is a block-scoped. So every time we enter the block of the for loop, a new environment is created, and the function created inside the block, > is created relatively to this environment.
Roughly it’s similar to (although at engine level much more optimized than this):
var data = [];
for (var k = 0; k < 3; k++) {
(function(x) {
data[x] = function () {
alert(x);
};
})(k);
}
@magicdawn 建一个函数的话,会进入一个新的执行上下文。 而含有词法声明的for只会新建一个词法环境对象。
@William17
一个是 Lexical Environment http://www.ecma-international.org/ecma-262/6.0/index.html#sec-lexical-environments 一个是 Execution Context http://www.ecma-international.org/ecma-262/6.0/index.html#sec-execution-contexts
Soga~
@magicdawn 指这个例子,不知道表述有什么问题没。只是想说珍爱生命,慎用原生es6。
参见:https://hacks.mozilla.org/2015/07/es6-in-depth-generators-continued/
function setup() {
console.log("setup")
}
function cleanup() {
console.log("cleanup")
}
function work(value) {
console.log(value)
}
function* produceValues() {
setup();
try {
yield "work1";
yield "work2";
// ... yield some values ...
} finally {
cleanup();
}
}
for (var value of produceValues()) {
work(value);
break;
}
这个例子,我这里node v5.0.0
➜ test node forof.js
setup
work1
firefox for ubuntu 43.0.4
setup
work1
babel转换后的
➜ test cat forof-es5.js
"use strict";
require("babel-polyfill");
var _marked = [produceValues].map(regeneratorRuntime.mark);
function setup() {
console.log("setup");
}
function cleanup() {
console.log("cleanup");
}
function work(value) {
console.log(value);
}
function produceValues() {
return regeneratorRuntime.wrap(function produceValues$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
setup();
_context.prev = 1;
_context.next = 4;
return "work1";
case 4:
_context.next = 6;
return "work2";
case 6:
_context.prev = 6;
cleanup();
return _context.finish(6);
case 9:
case "end":
return _context.stop();
}
}
}, _marked[0], this, [[1,, 6, 9]]);
}
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = produceValues()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var value = _step.value;
work(value);
break;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
➜ test node forof-es5.js
setup
work1
cleanup
@reverland
Note that the .return() is not called automatically by the language in all contexts, only in cases where the language uses the iteration protocol. So it is possible for a generator to be garbage collected without ever running its finally block.
这个不就是在用这个 iteration protocol么, 为啥没有调用 return? 发现node v4的generator没有return... 怎么回事
哈哈,不明觉厉...
@wangchi ......