为什么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 相关文章推荐
JavaScript 页面坐标相关知识整理
Jan 09 Javascript
jQuery Validation插件remote验证方式的Bug解决
Jul 01 Javascript
jquery focus(fn),blur(fn)方法实例代码
Dec 16 Javascript
jQuery用unbind方法去掉hover事件及其他方法介绍
Mar 18 Javascript
Jquery实现仿京东商城省市联动菜单
Nov 19 Javascript
AngularJS中使用HTML5手机摄像头拍照
Feb 22 Javascript
node.js微信公众平台开发教程
Mar 04 Javascript
JS中用三种方式实现导航菜单中的二级下拉菜单
Oct 31 Javascript
JS 音频可视化插件Wavesurfer.js的使用教程
Oct 31 Javascript
Vue对象赋值视图不更新问题及解决方法
Jun 03 Javascript
vue如何实现自定义底部菜单栏
Jul 01 Javascript
H5实现手机拍照和选择上传功能
Dec 18 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
global.php
2006/12/09 PHP
MySQL 日期时间函数常用总结
2012/06/12 PHP
PHP json_encode中文乱码问题的解决办法
2013/09/09 PHP
codeigniter上传图片不能正确识别图片类型问题解决方法
2014/07/25 PHP
PHP实现递归无限级分类
2015/10/22 PHP
PHP编程中的__clone()方法使用详解
2015/11/27 PHP
PHP类的自动加载机制实现方法分析
2019/01/10 PHP
在js(jquery)中获得文本框焦点和失去焦点的方法
2012/12/04 Javascript
使用jquery选择器如何获取父级元素、同级元素、子元素
2014/05/14 Javascript
JavaScript是如何实现继承的(六种方式)
2016/03/31 Javascript
AngularJS 遇到的小坑与技巧小结
2016/06/07 Javascript
indexedDB bootstrap angularjs之 MVC DOMO (应用示例)
2016/06/20 Javascript
详解Vue-cli代理解决跨域问题
2017/09/27 Javascript
JS实现点击链接切换显示隐藏内容的方法
2017/10/19 Javascript
js实现简单分页导航栏效果
2019/06/28 Javascript
VUE兄弟组件传值操作实例分析
2019/10/26 Javascript
python 中文字符串的处理实现代码
2009/10/25 Python
Python深入学习之内存管理
2014/08/31 Python
Python中的__slots__示例详解
2017/07/06 Python
Python实现的自定义多线程多进程类示例
2018/03/23 Python
PyQt5实现简易计算器
2020/05/30 Python
浅谈pyqt5在QMainWindow中布局的问题
2019/06/21 Python
pytorch ImageFolder的覆写实例
2020/02/20 Python
python调用百度API实现人脸识别
2020/11/17 Python
python 通过pip freeze、dowload打离线包及自动安装的过程详解(适用于保密的离线环境
2020/12/14 Python
html2canvas把div保存图片高清图的方法示例
2018/03/05 HTML / CSS
享受加州生活方式的时尚舒适:XCVI
2018/07/09 全球购物
英国领先的鞋类零售商:Shoe Zone
2018/12/13 全球购物
美体小铺波兰官方网站:The Body Shop波兰
2019/09/03 全球购物
武汉世纪畅想数字传播有限公司.NET笔试题
2014/07/22 面试题
讲文明树新风公益广告宣传方案
2014/02/25 职场文书
司机职责范本
2014/03/08 职场文书
党支部反对四风思想汇报
2014/10/10 职场文书
失恋33天观后感
2015/06/11 职场文书
python xlwt模块的使用解析
2021/04/13 Python
MySql中的json_extract函数处理json字段详情
2022/06/05 MySQL