漫谈ECMAScript有意思的设计
最近重读《你所不知道的 JavaScript》发现一些有趣的点,想分享下。
块级作用域
ES6 引用了 let 和 const,这实际上带来了块级作用域,在 ES6 之前只存在全局作用域和函数作用域,相信小伙伴对这句话应当不陌生,下面就来聊聊一个 ES6 之前如果实现块级作用域。
思考下面一个例子代码输出值为多少
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
这里会输出 10 个 10,因为只存在一个全局变量i
,我们可以通过立即执行函数来解决这个问题
for (var i = 0; i < 10; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 100);
})(i);
}
如果在 ES6 环境下还可以使用 let 命令解决,它会在每次循环的时候创建一个作用域,至于为什么能记住上一次的值,那是因为 js 引擎会记住上一次循环的值。
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}
上面的例子帮我们回顾一下没有块级作用域的痛苦之一,实际上我们写项目的时候可能早早的就使用了babel
,它会通过怎么样的形式将let
和const
转化为支持 ES5 的环境呢?
先看下面一个例子
try {
throw 5;
} catch (e) {
console.log(e);
}
console.log(e); //error e is not defined
这里 try 会创建一个块级作用域,在全局环境下 a 不存在,所以报错了。 聪明的小伙伴可能已经想到了,可以使用 try 来创建块级作用域,理论上是可行的,不过实际上有两点问题
- 性能太慢
- 语法丑陋,要显示的报错
我们来看下babel
怎么处理上面的例子
'use strict';
var _loop = function _loop(i) {
setTimeout(function () {
console.log(i);
}, 100);
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
可以看到与立即执行函数基本相同,不过使用立即执行函数创建块级作用域需要注意一点this
的指向。
例如这个例子
const obj = {
foo() {
for (let i = 0; i < 5; i += 1) {
setTimeout(() => {
console.log(this.arr[i]);
}, 1000);
}
},
arr: [1, 2, 3, 4, 5],
};
箭头函数没有自己的 this 对象,是定义时的对象而不是运行时的对象,在这个例子中指向的是 obj; 如果要把上面的代码转化为 ES5 环境支持的代码,我们可以通过作用域的规则来实现
(function () {
var _this = this;
setTimeout(function () {
console.log(_this.arr[i]);
}, 1000);
})();
上面就实现了所需的功能
隐藏的 global 对象
下面代码假设环境为浏览器环境,JavaScript 有许多设计缺陷,其中不使用声明声明变量,会自动将变量创建在global
上就是其一,不过下面就来漫谈特殊的global
对象
var a = 10;
b = 20;
window.a; //10
window.b; //20
index.html
创建一个带有 id 的 DOM 元素,html 内的 id 元素也会自动反映在global
对象上,例如下面
<div id="div"></div>
<script>
console.log(typeof div); //object
</script>
通过id
的 DOM 元素,可以直接使用 DOM 上的一些方法,算是一个奇淫巧技
console.log(div.nodeType); //1
console.log(div.nodeName); //DIV
//...
不过应该避免使用 id 的 DOM 元素,不易于代码的可读性,更推荐使用标准获取 dom 的方法。
最后提示一点,如果存在相同的id
dom,css 代码会作用两个相同的元素