深入解读JavaScript中的Hoisting机制


Posted in Javascript onAugust 12, 2015

hoisting机制

javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面。

先看一段代码

var v = "hello";
(function(){
 console.log(v);
 var v = "world";
})();

这段代码运行的结果是什么呢?
答案是:undefined
这段代码说明了两个问题,
第一,function作用域里的变量v遮盖了上层作用域变量v。代码做少些变动

var v = "hello";
if(true){
 console.log(v);
 var v = "world";
}

输出结果为”hello”,说明javascript是没有块级作用域的。函数是JavaScript中唯一拥有自身作用域的结构。

第二,在function作用域内,变量v的声明被提升了。所以最初的代码相当于:

var v = "hello";
(function(){
 var v; //declaration hoisting
 console.log(v);
 v = "world";
})();

声明、定义与初始化

声明宣称一个名字的存在,定义则为这个名字分配存储空间,而初始化则是为名字分配的存储空间赋初值。
用C++来表述这三个概念

extern int i;//这是声明,表明名字i在某处已经存在了
int i;//这是声明并定义名字i,为i分配存储空间
i = 0;//这是初始化名字i,为其赋初值为0
javascript中则是这样

var v;//声明变量v
v = "hello";//(定义并)初始化变量v
因为javascript为动态语言,其变量并没有固定的类型,其存储空间大小会随初始化与赋值而变化,所以其变量的“定义”就不像传统的静态语言一样了,其定义显得无关紧要。

声明提升

当前作用域内的声明都会提升到作用域的最前面,包括变量和函数的声明

(function(){
 var a = "1";
 var f = function(){};
 var b = "2";
 var c = "3";
})();

 
变量a,f,b,c的声明会被提升到函数作用域的最前面,类似如下:

(function(){
 var a,f,b,c;
 a = "1";
 f = function(){};
 b = "2";
 c = "3";
})();

请注意函数表达式并没有被提升,这也是函数表达式与函数声明的区别。进一步看二者的区别:

(function(){
 //var f1,function f2(){}; //hoisting,被隐式提升的声明

 f1(); //ReferenceError: f1 is not defined
 f2();

 var f1 = function(){};
 function f2(){}
})();

上面代码中函数声明f2被提升,所以在前面调用f2是没问题的。虽然变量f1也被提升,但f1提升后的值为undefined,其真正的初始值是在执行到函数表达式处被赋予的。所以只有声明是被提升的。

名字解析顺序

javascript中一个名字(name)以四种方式进入作用域(scope),其优先级顺序如下:
1、语言内置:所有的作用域中都有 this 和 arguments 关键字
2、形式参数:函数的参数在函数作用域中都是有效的
3、函数声明:形如function foo() {}
4、变量声明:形如var bar;

名字声明的优先级如上所示,也就是说如果一个变量的名字与函数的名字相同,那么函数的名字会覆盖变量的名字,无论其在代码中的顺序如何。但名字的初始化却是按其在代码中书写的顺序进行的,不受以上优先级的影响。看代码:

(function(){
  var foo;
  console.log(typeof foo); //function

  function foo(){}

  foo = "foo";
  console.log(typeof foo); //string
})();

如果形式参数中有多个同名变量,那么最后一个同名参数会覆盖其他同名参数,即使最后一个同名参数并没有定义。

以上的名字解析优先级存在例外,比如可以覆盖语言内置的名字arguments。

命名函数表达式

可以像函数声明一样为函数表达式指定一个名字,但这并不会使函数表达式成为函数声明。命名函数表达式的名字不会进入名字空间,也不会被提升。

f();//TypeError: f is not a function
foo();//ReferenceError: foo is not defined
var f = function foo(){console.log(typeof foo);};
f();//function
foo();//ReferenceError: foo is not defined
命名函数表达式的名字只在该函数的作用域内部有效。

再来看看下面例子:

var myval = "my global var";
(function() {
 console.log(myval); // log "my global var"
})();

以上代码很显然会输出 "my global var",但是如果我们把以上代码按如下方式稍加修改:

var myval = "my global var";
(function() {
 console.log(myval); // log "undefined"
 var myval = "my local var";
})();

执行结果是输出了一个 undefined,出现这个结果的原因就是变量的声明被提升了,以上代码等同如下:

var myval = "my global var";
(function() {
 var myval;
 console.log(myval); // log "undefined"
 myval = "my local var";
})();

被提升的仅仅是变量的声明部分,并没有立即初始化,所以会输出 undefined。

然而这种提升机制,不仅仅表现于在普通的变量,同时也表现在函数上。例如下面这段代码并不能被正确执行:

(function() {
 fun(); // Uncaught TypeError: undefined is not a function
 var fun = function() {
 console.log("Hello!");
 }
})();

因为它等价于:

(function() {
 var fun;
 fun(); // Uncaught TypeError: undefined is not a function
 fun = function() {
 console.log("Hello!");
 }
})();

因为函数的声明同样被提升而没有立即初始化,所以会出错。

当然,这种定义函数的方式称之为“函数表达式”,会有提升机制,如果是如下的这种“函数声明”方式,则完全没有提升机制方面的问题:

(function() {
 fun();
 function fun() {
 console.log("Hello!"); // log "Hello!"
 }
})();

这也是函数声明与函数表达式的主要区别。

Javascript 相关文章推荐
javascript 词法作用域和闭包分析说明
Aug 12 Javascript
js文件缓存之版本管理详解
Jul 05 Javascript
jQuery实现图片文字淡入淡出效果
Dec 21 Javascript
JS控制页面跳转时未请求要跳转的地址怎么回事
Oct 14 Javascript
vue2 自定义动态组件所遇到的问题
Jun 08 Javascript
老生常谈JavaScript面向对象基础与this指向问题
Oct 16 Javascript
关于echarts在节点显示动态数据及添加提示文本所遇到的问题
Apr 20 Javascript
Vue实现点击当前元素以外的地方隐藏当前元素(实现思路)
Dec 04 Javascript
js实现三角形粒子运动
Sep 22 Javascript
swiper4实现移动端导航栏tab滑动切换
Oct 16 Javascript
11个Javascript小技巧帮你提升代码质量(小结)
Dec 28 Javascript
mustache.js实现首页元件动态渲染的示例代码
Dec 28 Javascript
Jquery代码实现图片轮播效果(一)
Aug 12 #Javascript
javascript表单验证大全
Aug 12 #Javascript
JavaScript实现动态删除列表框值的方法
Aug 12 #Javascript
jQuery实现文件上传进度条特效
Aug 12 #Javascript
XML文件转化成NSData对象的方法
Aug 12 #Javascript
javascript实现点击单选按钮链接转向对应网址的方法
Aug 12 #Javascript
jQuery实现动态添加和删除一个div
Aug 12 #Javascript
You might like
PHP页面间传递参数实例代码
2008/06/05 PHP
一个不易被发现的PHP后门代码解析
2014/07/05 PHP
php程序内部post数据的方法
2015/03/31 PHP
Linux操作系统安装LAMP环境
2015/06/26 PHP
PHP使用preg_split()分割特殊字符(元字符等)的方法分析
2017/02/04 PHP
Ajax请求PHP后台接口返回信息的实例代码
2018/08/21 PHP
Extjs学习过程中新手容易碰到的低级错误积累
2010/02/11 Javascript
JavaScript 题型问答有答案参考
2010/02/17 Javascript
Jquery AutoComplete自动完成 的使用方法实例
2010/03/19 Javascript
js中使用DOM复制(克隆)指定节点名数据到新的XML文件中的代码
2011/07/27 Javascript
js中各种类型的变量在if条件中是true还是false
2014/07/16 Javascript
javascript异步处理工作机制详解
2015/04/13 Javascript
Jquery实现select multiple左右添加和删除功能的简单实例
2016/05/26 Javascript
bootstrap基本配置_动力节点Java学院整理
2017/07/14 Javascript
jQuery实现动态生成年月日级联下拉列表示例
2019/05/11 jQuery
使用layui实现的左侧菜单栏以及动态操作tab项方法
2019/09/10 Javascript
详解vue-template-admin三级路由无法缓存的解决方案
2020/03/10 Javascript
[53:15]Mineski vs iG 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
Python网页解析利器BeautifulSoup安装使用介绍
2015/03/17 Python
编写Python脚本来获取mp3文件tag信息的教程
2015/05/04 Python
python从sqlite读取并显示数据的方法
2015/05/08 Python
Python基于百度AI的文字识别的示例
2018/04/21 Python
详解如何用django实现redirect的几种方法总结
2018/11/22 Python
实例介绍Python中整型
2019/02/11 Python
python使用matplotlib画柱状图、散点图
2019/03/18 Python
Python 生成VOC格式的标签实例
2020/03/10 Python
来自世界各地的饮料:Flavourly
2019/05/06 全球购物
SQL Server 2000数据库的文件有哪些,分别进行描述。
2015/11/09 面试题
九年级英语教学反思
2014/01/31 职场文书
六查六看心得体会
2014/10/14 职场文书
暑假生活随笔
2015/08/15 职场文书
2016年综治和平安建设宣传月活动总结
2016/04/01 职场文书
如何用python绘制雷达图
2021/04/24 Python
springboot 启动如何排除某些bean的注入
2021/08/02 Java/Android
使用redis生成唯一编号及原理示例详解
2021/09/15 Redis
Nginx配置根据url参数重定向
2022/04/11 Servers