为什么JavaScript没有块级作用域


Posted in Javascript onMay 22, 2016

最近在看ES2015 实战,里面有句话是这么说的

JavaScript 中没有块级作用域

可能会对这个问题大家可能有点不理解,先看个例子

var a = []
for(var i = 0; i < 10; i++){
 a[i] = function(){
 console.log(i);
 }
}
a[6]();

我想很多人会觉得这个问题的结果是6,然而很不幸,答案是10.在试试别的呢.a[7]()、a[8]()、a[8]()结果都是10!!
由于JS在处理primitive的变量的时候,很多时候会把primitive变量包装成对应的对象来处理,比如对于var str = "hello world";str.slice(1).
JS真正的处理过程大概是var str = "hello world";new String(str).slice(1).这样的过程可能会对我们理解问题造成困扰.
这里为了解释这个问题,同时i属于primitive类型中的number类型,我就显式的声明为Number类型.由于基本类型的赋值过程就是重新申请内存,修改变量的指向的过程,对于这一过程我们也用重新new Number对象的过程来模拟.修改过后的代码如下:

var a = []
var i = new Number(0);
for(; i < 10; i = new Number(i+1)){
 a[i] = function(){
 console.log(i.toString());
 }
}
a[6](); // 10
a[7](); // 10
a[8](); // 10
a[9](); // 10

下面结合一段程序,我们来看看这些这变量的相对内存地址

(function() {
  var id = 0;

  function generateId() { return id++;};

  Object.prototype.id = function() {
    var newId = generateId();

    this.id = function() { return newId; };

    return newId;
  };
})();

var a = []
var i = new Number(0);
console.log(i.id());// 0
for(; i < 10; i = new Number(i+1),i.id()){

 a[i] = function(){
 console.log(i.id());
 console.log(i.toString());
 }
}
a[6](); // 10 10
a[7](); // 10 10
a[8](); // 10 10
a[9](); // 10 10
console.log(i.id())// 10

这边我们的i的整个的”赋值”的效果我们确实是模拟出来了,i的相对地址从0变到10(最后还需要加一次才可以跳出for循环).
在看i的相对地址的同时,我们发现一个问题:a[x](x:0~9)对应的函数在执行的时候,所引用的i的相对地址都为10.为什么呢?
这里就要牵扯出块级作用域问题来,这里我们引用阮一峰在ES6入门中的一段话:

ES5只有全局作用域和函数作用域,没有块级作用域.

ES5就是大家使用最广泛的JS的版本.这句话说在javascript中,是不存在块作用域的.只存在全局作用域和块级作用域.
怎么理解呢?举个例子

for(var i = 0;i < 10; i++){
 console.log(i);
}
console.log(i);//10
console.log(window.i);//10

直观的看,我们觉得for循环是一个代码块,应该属于一个块级作用域.但是这里不仅能正常的输出0~9,居然还可以在for循环的外部输出10.同时我们发现,虽然我们是在for循环上定义的i,但是似乎i是挂在了全局的window对象上(如果是nodejs的执行环境,就会挂到global对象上)

所以说在JavaScript中for循环之类的block并不会起到一个块级作用域的效果,在for循环之类的代码块中定义变量,跟在当前所在的作用域中直接定义变量没什么区别.

但是我们可以通过函数隔离出作用域出来:

(function(){
 for(var i = 0;i < 10; i++){
 console.log(i);
 }
 console.log(i);
})()
console.log(i);////i is not defined

同时如果执行console.log(window.i);会得到undefined的结果.这里我们用一个立即执行函数来形成一个作用域.起到类似于代码块的作用,出了这个函数作用域,就不再可以访问i这个变量.但是在函数作用域内可以任意访问i.
回到之前的问题,同时结合JavaScript中只有全局作用域和块级作用域再来理解一下.我们在for循环中,定义的i肯定是定义在当前作用域的,也就是window作用域.在循环体中,我们给a[i]赋值了一个函数,当我们执行这个函数时,情况如下:

为什么JavaScript没有块级作用域

function中不存在i,于是顺着作用域链去window作用域找得到了i.我们这个时候输出的i就是这个i.由于i在跳出循环最后一次的+1,使得i变成了10,所以输出结果一直都是10.但是我们真正需要的i不是最后的i,而是中间过程中的i.如果要解决这个问题,我们需要抛开i这个变量(因为最后的i不可避免的变成10).我们要让a[0]对应的function引用0这个值,让a[1]对应的function引用1这个值.如下图所示:

为什么JavaScript没有块级作用域

在回到我们之前的代码.

为什么JavaScript没有块级作用域

我们在图中的箭头出是可以正确的访问i(0~9).这里由于for循环并没有自己形成一个块级作用域.导致了我们顺着作用域链去访问i的时候就访问到了for循环定义的i.
这里我们用一个立即执行函数包裹我们的代码,就可以形成一个作用域,同时我们为其传值i.如下:

var a = []
var i = new Number(0);
console.log(i.id());// 0
for(; i < 10; i = new Number(i+1),i.id()){

 (function(i){
 a[i] = function(){
  console.log(i.id());
  console.log(i.toString());
 }
 })(i);
a[6](); // 6 6
a[7](); // 7 7
a[8](); // 8 8
a[9](); // 9 9
console.log(i.id());// 10
}

由于这个立即执行函数引用着数值0~9,当我们执行函数a[i]的时候,会顺着作用域链先找到这个立即执行函数的作用域.立即执行函数维护着0~9的数值引用,我们就可以在函数a[i]中正确的输出i的值.通过执行结果,我们可以看到,不光执行结果是对的,同时我们引用的值的相对内存地址也都是对的.接着我们把原来为了测试显式声明的Number对象改回去.如下:

var a = [];
for(var i = 0; i < 10; i++){
 (function(i){
 a[i] = function(){
  console.log(i);
 }
 })(i);
}

最后我们再来看看ES6的语法中推荐用let代替var以及经过bable编译生成ES5的代码是如何的:

//ES6代码
var a = []
for(let i = 0; i < 10; i++){
 a[i] = function(){
 console.log(i);
 }
}
a[6]();
//babel编译生成的ES5代码
"use strict";
var a = [];
var _loop = function _loop(i) {
 a[i] = function () {
 console.log(i);
 };
};
for (var i = 0; i < 10; i++) {
 _loop(i);
}
a[6]();

为什么JavaScript没有块级作用域

看~我们的解决方法和ES6的解决方法是不是很像.这里我们的立即执行函数相当于生成的ES5代码中的_loop函数以及_loop(i)的执行.

Javascript 相关文章推荐
setInterval 和 setTimeout会产生内存溢出
Feb 15 Javascript
JavaScript 事件参考手册
Dec 24 Javascript
比较简单的异步加载JS文件的代码
Jul 18 Javascript
Extjs列表详细信息窗口新建后自动加载解决方法
Apr 02 Javascript
为jquery的ajaxfileupload增加附加参数的方法
Mar 04 Javascript
Javascript中的delete操作符详细介绍
Jun 06 Javascript
JS基于myFocus库实现各种功能的tab选项卡切换效果
Sep 19 Javascript
js轮盘抽奖实例分析
Apr 17 Javascript
JavaScript中各数制转换全面总结
Aug 21 Javascript
JavaScript数据结构与算法之队列原理与用法实例详解
Nov 22 Javascript
vue-cli 目录结构详细讲解总结
Jan 15 Javascript
JavaScript进阶(二)词法作用域与作用域链实例分析
May 09 Javascript
全面解析Bootstrap中nav、collapse的使用方法
May 22 #Javascript
全面解析bootstrap格子布局
May 22 #Javascript
Bootstrap模块dropdown实现下拉框响应
May 22 #Javascript
基于Bootstrap实现图片轮播效果
May 22 #Javascript
基于Vue.js的表格分页组件
May 22 #Javascript
js正则表达式replace替换变量方法
May 21 #Javascript
深入解析JavaScript中的立即执行函数
May 21 #Javascript
You might like
PHP实现多条件查询实例代码
2010/07/17 PHP
PHP项目开发中最常用的自定义函数整理
2010/12/02 PHP
Fatal error: session_start(): Failed to initialize storage module: files问题解决方法
2014/05/04 PHP
phpmyadmin提示The mbstring extension is missing的解决方法
2014/12/17 PHP
基于jQuery的简单的列表导航菜单
2011/03/02 Javascript
jquery选择符快速提取web表单数据示例
2014/03/27 Javascript
详解 javascript中offsetleft属性的用法
2015/11/11 Javascript
javascript匀速运动实现方法分析
2016/01/08 Javascript
JavaScript简单实现鼠标移动切换图片的方法
2016/02/23 Javascript
Vue表单验证插件的制作过程
2017/04/01 Javascript
jQuery条件分页 代替离线查询(附代码)
2017/08/17 jQuery
jQuery实现弹窗下底部页面禁止滑动效果
2017/12/19 jQuery
vue vue-Router默认hash模式修改为history需要做的修改详解
2018/09/13 Javascript
详解Nuxt.js 实战集锦
2019/11/19 Javascript
简介JavaScript错误处理机制
2020/08/04 Javascript
基于NodeJS开发钉钉回调接口实现AES-CBC加解密
2020/08/20 NodeJs
[02:44]DOTA2英雄基础教程 魅惑魔女
2014/01/07 DOTA
python使用opencv进行人脸识别
2017/04/07 Python
python文件特定行插入和替换实例详解
2017/07/12 Python
对Python 获取类的成员变量及临时变量的方法详解
2019/01/22 Python
python中import与from方法总结(推荐)
2019/03/21 Python
python基于pdfminer库提取pdf文字代码实例
2019/08/15 Python
Python 获取项目根路径的代码
2019/09/27 Python
什么是python类属性
2020/06/10 Python
浅析Python面向对象编程
2020/07/10 Python
美国宠物商店:Wag.com
2016/10/25 全球购物
Arti-shopping中文官网:大型海外商品一站式直邮平台
2020/03/23 全球购物
写给女生的道歉信
2014/01/08 职场文书
分层教学实施方案
2014/03/19 职场文书
松材线虫病防治方案
2014/06/15 职场文书
基层党员对照检查材料
2014/09/24 职场文书
房地产销售助理岗位职责
2015/04/14 职场文书
汤姆索亚历险记读书笔记
2015/06/29 职场文书
小学音乐课教学反思
2016/02/18 职场文书
剑指Offer之Java算法习题精讲二叉树专项训练
2022/03/21 Java/Android
Golang jwt身份认证
2022/04/20 Golang