yliu

时来天地皆同力,运去英雄不自由

漫谈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,它会通过怎么样的形式将letconst转化为支持 ES5 的环境呢?

先看下面一个例子

try {
  throw 5;
} catch (e) {
  console.log(e);
}
console.log(e); //error e is not defined

这里 try 会创建一个块级作用域,在全局环境下 a 不存在,所以报错了。 聪明的小伙伴可能已经想到了,可以使用 try 来创建块级作用域,理论上是可行的,不过实际上有两点问题

  1. 性能太慢
  2. 语法丑陋,要显示的报错

我们来看下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 的方法。 最后提示一点,如果存在相同的iddom,css 代码会作用两个相同的元素