JavaScript的变量:变量提升
JavaScript代码的运行规则
在JavaScript代码运行之前其实是有一个编译阶段的。编译之后才是从上到下,一行一行解释执行。这样一来也给初学者造成很大的误解。初学者会觉得JavaScript的代码是从上到下,一行一行的解释执行的。按这样的思路,在有些情况下就会造成惨案:
name = "W3cplus"; var name; console.log(name);
按照代码从上到下一行一行解释执行的说法,有些同学可能会觉得console.log(name)
输出的值是undefined
。那是因为name = "W3cplus"
在var name
之前,变量name
被重新定义了,而且没有给其赋值,所以认为此时name
的值是undefined
。输出的也应该是undefined
。但事实上输出的值是"W3cplus"
。下图是Chrome浏览器调试器下的输出结果:======================>> // >-< \\
再来看一段代码: ================ >>
console.log(name); var name = "W3cplus";
当初我就以为它输出的结果是Uncaught ReferenceError: name is not defined(…)
。因为变量name
在没有声明的情况下就被使用了。而事实上呢,并如如此,它输出的结果是undefined
。如下图所示:
为什么会这样呢?因为JavaScript代码运行时,它把变量和函数的声明提升至作用域的顶端。而这个阶段就发生了变量提升。同时JavaScript在编译阶段的工作之一就是将变量与其作用域进行关联。那么要彻底的理解JavaScript声明提升,需要对JavaScript的变量作用域有一定的了解。
JavaScript变量作用域
对于JavaScript的初学者来说,变量作用域是最令人感到困惑的一部分。有关于JavaScript中变量的作用域,本文不做介绍,因为要说清楚它,需要大幅篇幅,而且对于我这样的新手也道不清说不明。拿张图向大家简单的展示一下:
在JavaScript中,变量有4种基本方式进入作用域:
- 语言自身定义(Language-defined):所有的作用域默认都会包含
this
和arguments
- 函数形参(Formal parameters):函数有名字的形参会进入到函数体的作用域中
- 函数声明(Function decalrations):通过
function foo() {...}
方式实现函数声明 - 变量声明(Variable declarations):通过
var foo;
形式声明变量,当然在ES6中还增加了let
和const
声明变量
可能你跟我一样,对变量作用域并没有理解透彻,但我们不能因为这个原因而不继续.
JavaScript变量提升
在JavaScript中,变量的声明可以放在它的使用之后。换句话说,变量可以先使用后声明。这主要是因为JavaScript的提升(hoisting)机制在作怪。简单点说,提升(hoisting)是JavaScript中默认就具有的一种机制,它将当用作用域内的所有声明都提升到最顶部。如此一来,可以把变量提升归纳为:
JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
为了理解上面的含义,我们来看一个简单的示例。下面的代码中定义了一个函数foo()
:
function foo () { var name = "w3cplus"; var address = "http://www.w3cplus.com"; var age = 6; }
其实它会摇身一变成这样:
function foo () { var name,address,age; name = "w3cplus"; address = "http://www.w3cplus.com"; age = 6; }
JavaScript的提升将会影响一个变量的生命周期,JavaScript中的一个变量,其生命周期主要包含三个阶段:
- 声明变量:创建一个新变量,如
var name;
- 变量初始化:给变量初始化一个值,如
name = "W3cplus";
- 变量使用: 使用变量的值,如
console.log(name);
知道这个概念之后,再回过头来看文章开头的示例代码:
name = "W3cplus"; var name; console.log(name);
在编写代码时应该这样来作处理:
var name; // 代码编译阶段 name = "W3cplus"; // 代码运行阶段 console.log(name); // 代码运行阶段
所以这段代码最终输出的结果将会是W3cplus
。
第二个示例代码:
console.log(name); var name = "W3cplus";
我们应该这样来处理:
var name; // 代码编译阶段 console.log(name); // 代码运行阶段 name = "W3cplus": // 代码运行阶段
所以代码最终结果是undefined
。
理解变量提升
变量提升就是把变量提升到函数的顶部。需要特别说明的是:变量提升只是提升变量的声明,并不会把赋值也提升上来。
其实前面的示例已经说明了这一切,咱们重新来看看这个foo()
函数:
function foo () { var name = "w3cplus"; var address = "http://www.w3cplus.com"; var age = 6; }
实际上foo()
函数是这样子:
function foo () { var name,address,age; name = "w3cplus"; address = "http://www.w3cplus.com"; age = 6; }
这个时候就把变量提升了。
上面的示例比较简单,咱们再来一个稍微复杂一点的示例:
var foo = 1; function bar () { if (!foo) { var foo = 10; } console.log(foo); } bar(); var foo = 1;
答案是10
。你一定觉得非常奇怪,foo
等于1
,if
条件!foo
应该是false
,也就是说if
代码块的代码是不会被执行。返回的值应该是undefined
呀,怎么就变成10
了呢?实际 上,正如前面所说,变量foo
被提升到了bar()
函数最顶部,那么程序就变成这样:
function bar () { console.log(foo); // => undefined if (!foo) { var foo = 10; console.log(foo); // => 10 } console.log(foo); // => 10 } bar();
bar()
函数运行后,输出的值是undefined 10 10
,如下图所示:
说明当在函数内使用var
声明变量的时候,这个变量的声明被提升到了bar()
函数的最顶部(最开始处),所以这个例子等同于:
var foo = 1; function bar () { var foo; // 定义局部变量foo if (!foo) { // foo是undefined (false),那么!foo就是true,所以会执行if语句块 foo = 10; } console.log(foo); } bar(); // => 输出的结果是10
这样一来就明白了,结果为啥会是10
了吧。
接着往下看,如果把bar()
函数内的var foo = 10;
换成foo = 10
,其结果又将是如何呢?
function bar(){ if(!foo){ foo=10; } console.log(foo); } bar(); var foo=1;
其实上面的代码变成:
var foo=1; function bar(){ if(!foo){ foo=10; } console.log(foo); } bar(); // => 1
因为bar()
中的变量foo
没有使用var
声明,变量不再提升。所以(!foo)
会到函数外寻找定义的合局变量foo
,结果是1
,那!foo
返回的值是false
,也就不会执行if
语句块内的代码。最终得到的结果是1
。如果这个时候没有定义全局变量foo
,就会报错。
function bar(){ if(!foo){ foo=10; } console.log(foo); } bar(); // => Uncaught ReferenceError: foo is not defined(…)
如下图所示:
也就是说,只有在有var
声明的变量才会被提升到函数最顶部.
函数声明提升
函数在声明时也会像变量一样被提升。不同的是,函数表达式不会被提升。
函数声明 函数声明提升
foo(); function foo() { console.log(n); var n = 2; }
实际上上面的代码将会按下面的形式执行:
function foo () { var n; console.log(n); n = 2; } foo(); // => undefined
函数foo()
的作用域内的变量n
提升到了作用域顶部,全局作用域里的foo()
函数声明民会被提前到所处的作用域顶部,即全局作用域的顶部。但是函数表达式的话只有变量被声明,但是赋值给变量的函数不会被提升。
函数表达式
函数表达式不会被提升:
var foo; foo(); // => Uncaught TypeError: foo is not a function(…) foo = function bar () { console.log(foo); }
这样会引发TypeError
异常,因为当时的foo
并没有赋值,对undefined
进行函数调用会导致非法操作抛出异常。
函数优先
foo(); // 函数声明 function foo() { console.log('1'); } // 函数表达式 var foo = function () { console.log('2'); }
上面的代码将会被理解成下面的形式:
// 函数声明 function foo() {
console.log('1'); } var foo; foo();
// 函数表达式 foo = function () { console.log('2'); }
所以实际上的输出是 1
,因为函数表达式的赋值操作会在原来的位置,而声明操作则是提升到作用域顶部,但是优先级低于函数声明。
重复声明同名变量在 JavaScript 非严格模式中将会被忽略,所以实际上函数表达式的位置并没有改变。
通过上面的介绍之后,简单的总结一下:
- 使用
var
声明的变量(包括函数)其声明会被提升到方法体最顶部,而赋值不会被提升; - 未使用
var
声明的变量,不会被提升; - 使用函数表达式
function bar(){}
定义的函数会函数声明连带函数体提升到方法体最顶部; - 注意JavaScript中的作用域问题,JavaScript中没有块作用域
事实上,变量提升在不同方面的影响也不同:
- 变量声明: 使用
var
,let
或const
关键字 - 函数声明: 使用
function () {...}
语法 - 类声明: 使用
class
关键字
总结
看到这里,是不是有点晕了,说真的,我自己都晕了。不过理解清楚下面这段话,你理解JavaScript中的变量提升会有很大的帮助:
如果变量在函数体内声明,它的作用域是函数作用域。否则,它就是全局作用域。变量将会在执行进入作用域时被创建。块不会定义新的作用域,只有函数声明和程序才可以。变量在创建的时候会被初始化为undefined
。如果变量声明语句带有赋值操作,则赋值操作只有在被执行的时候才会发生,而不是创建的时候。
如果文章中有不对之处,或者你有更好的意见欢迎在下面的评论中与我们分享。 d=====( ̄▽ ̄*)b
JavaScript的变量:变量提升的更多相关文章
- 在javascript中关于变量与函数的提升
在javascript中关于变量与函数的提升 一.简介 在javascript中声明变量与函数的执行步骤: 1.先预解析变量或函数声明代码,会把用var声明的变量或者函数声明的代码块进行提升操作 2. ...
- JavaScript 变量声明提升
JavaScript 变量声明提升 一.变量提升的部分只是变量的声明,赋值语句和可执行的代码逻辑还保持在原地不动 二.在基本的语句(或者说代码块)中(比如:if语句.for语句.while语句.swi ...
- javascript变量声明提升和函数声明提升
在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域.变量提升即将变量声明提升到它所在作用域的最开始的部分. JS的解析过程分为两个阶段:预 ...
- 解读JavaScript中的Hoisting机制(js变量声明提升机制)
hoisting机制:javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面. 知识点一:javascript是没有 ...
- 浅谈JavaScript变量声明提升
前段时间阿里实习生内推,一面就被刷了,也是郁闷.今天系统给发通知,大致意思就是内推环节不足以了解彼此,还可以参加笔试,于是赶紧再投一次.官网流程显示笔试时间3月31日,时间快到了,开始刷题.网上搜了一 ...
- JavaScript变量声明提升
JavaScript代码在被解析引擎执行前,会被“编译”把变量声明等放在合适的作用域中,如果不了解这一点,会让人产生很多疑惑. 文章:详解js变量声明提升
- JavaScript中的变量提升和严格模式
1.什么是变量提升 所谓的变量提升指的是:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体(作用域)的最顶部. //先声明后使用 var x; console.log(x) ...
- [Effective JavaScript 笔记] 第12条:理解变量声明提升
js支持词法作用域,即除了极少的例外,对变量的引用会被绑定到声明变量最近的作用域中. js不支持块级作用域,即变量定义的作用域并不是离其最近的封闭语句或代码块,而是包含它们的函数. 不了解这个会产生一 ...
- javascript基础语法——变量和标识符
× 目录 [1]定义 [2]命名规则 [3]声明[4]特性[5]作用域[6]声明提升[7]属性变量 前面的话 关于javascript,第一个比较重要的概念是变量,变量的工作机制是javascript ...
- JS高级. 05 词法作用域、变量名提升、作用域链、闭包
作用域 域,表示的是一个范围,作用域,就是作用范围. 作用域说明的是一个变量可以在什么地方被使用,什么地方不能被使用. 块级作用域 JavaScript中没有块级作用域 { var num = 123 ...
随机推荐
- Gym - 101775L SOS 博弈 找规律
题目:https://cn.vjudge.net/problem/Gym-101775L PS:训练赛中被这道题折磨的不轻,和队友反复推必胜态与必败态试图推导出公式或者规律,然后推的心态逐渐失控,,, ...
- 笔记-python-urllib
笔记-python-urllib 1. 简介 PYTHON3中将urllib,urllib2整合到URLLIB中 包括以下模块 urllib.request 请求模块(核心) urllib. ...
- Centos7重启网卡失败解决方法
service Network-Manager stop 执行命令解决,如果执行命令还是失败,则是配置文件内容的问题,检查配置文件
- SPOJ 1825 Free tour II 树分治
题意: 给出一颗边带权的数,树上的点有黑色和白色.求一条长度最大且黑色节点不超过k个的最长路径,输出最长的长度. 分析: 说一下题目的坑点: 定义递归函数的前面要加inline,否则会RE.不知道这是 ...
- Python之code对象与pyc文件(三)
上一节:Python之code对象与pyc文件(二) 向pyc写入字符串 在了解Python如何将字符串写入到pyc文件的机制之前,我们先来了解一下结构体WFILE: marshal.c typede ...
- UTV - URL Tag Validation
What`s UTV 1.URL Tag Validation 2.Special format of URL for preventing unauthorized usage and access ...
- 一张图展示:用两个栈来实现一个队列,完成队列的Push和Pop操作
一 基本思路 将s1作为存储空间,以s2作为临时缓冲区. 入队时,将元素压入s1. 出队时,将s1的元素逐个“倒入”(弹出并压入)s2,将s2的顶元素弹出作为出队元素,之后再将s2剩下的元素逐个“倒 ...
- [python学习篇][书籍学习][python standrad library][内建函数]之[all,any,basestring,isinstance,bin,bool,@classmethod,@staticmethod,cmp,enumerate
Python 解释器内置了一些函数,它们总是可用的.这里将它们按字母表顺序列出. Built-in Functions abs() divmod() input() open() st ...
- IO Streams:扫描
简介 Scanner类被用于输入的格式化中断,并将其移到Tokens中,然后对其单个的Tokens根据其数据类型进行翻译. 从input--Tokens 默认情况下,一个Scanner使用 空格 键去 ...
- 刷题总结——book of evil(codefoeces 337D)
题目: description Paladin Manao caught the trail of the ancient Book of Evil in a swampy area. This ar ...