深入解读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中的变量是传值还是传址的?
Apr 19 Javascript
jquery多行滚动/向左或向上滚动/响应鼠标实现思路及代码
Jan 23 Javascript
一个页面元素appendchild追加到另一个页面元素的问题
Jan 27 Javascript
jquery序列化form表单使用ajax提交后处理返回的json数据
Mar 03 Javascript
chrome下判断点击input上标签还是其余标签的实现方法
Sep 18 Javascript
BootStrap表单验证实例代码
Jan 13 Javascript
vue下跨域设置的相关介绍
Aug 26 Javascript
React-Native使用Mobx实现购物车功能
Sep 14 Javascript
Vue项目History模式404问题解决方法
Oct 31 Javascript
vue中使用props传值的方法
May 08 Javascript
BootStrap表单验证中的非Submit类型按钮点击时触发验证的坑
Sep 05 Javascript
Vue的Options用法说明
Aug 14 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 和 MySQL 基础教程(二)
2006/10/09 PHP
php 求质素(素数) 的实现代码
2011/04/12 PHP
PHP实现定时执行任务的方法
2014/10/05 PHP
PHP利用header跳转失效的解决方法
2014/10/24 PHP
PHP实现的线索二叉树及二叉树遍历方法详解
2016/04/25 PHP
Yii2中多表关联查询hasOne hasMany的方法
2017/02/15 PHP
laravel框架邮箱认证实现方法详解
2019/11/22 PHP
PHP call_user_func和call_user_func_array函数的简单理解与应用分析
2019/11/25 PHP
jquery 插件 web2.0分格的分页脚本,可用于ajax无刷新分页
2008/12/25 Javascript
Dom 学习总结以及实例的使用介绍
2013/04/24 Javascript
用JS实现3D球状标签云示例代码
2013/12/01 Javascript
使用js操作css实现js改变背景图片示例
2014/03/10 Javascript
谈谈javascript中使用连等赋值操作带来的问题
2015/11/26 Javascript
JavaScript使用DeviceOne开发实战(四)仿优酷视频应用
2015/12/02 Javascript
学习JavaScript设计模式之策略模式
2016/01/12 Javascript
JS显示日历和天气的方法
2016/03/01 Javascript
JQuery之proxy实现绑定代理方法
2016/08/01 Javascript
js 简易版滚动条实例(适用于移动端H5开发)
2017/06/26 Javascript
基于jquery trigger函数无法触发a标签的两种解决方法
2018/01/06 jQuery
Vue源码探究之状态初始化
2018/11/14 Javascript
分享一个vue项目“脚手架”项目的实现步骤
2019/05/26 Javascript
layui自定义验证,用ajax查询后台是否有重复数据,form.verify的例子
2019/09/06 Javascript
小程序自定义模板实现吸顶功能
2020/01/08 Javascript
Python获取系统默认字符编码的方法
2015/06/04 Python
pygame游戏之旅 python和pygame安装教程
2018/11/20 Python
对python 调用类属性的方法详解
2019/07/02 Python
Python 脚本拉取 Docker 镜像问题
2019/11/10 Python
Jupyter安装拓展nbextensions及解决官网下载慢的问题
2021/03/03 Python
彻底弄明白CSS3的Media Queries(跨平台设计)
2010/07/27 HTML / CSS
瑞典廉价机票预订网站:Seat24
2018/06/19 全球购物
货代行业个人求职简历的自我评价
2013/10/22 职场文书
煤矿机修工岗位职责
2014/02/07 职场文书
《颐和园》教学反思
2014/02/26 职场文书
群众路线教育实践活动的心得体会
2014/09/03 职场文书
革命英雄事迹演讲稿
2014/09/13 职场文书
浅谈Java父子类加载顺序
2021/08/04 Java/Android