浅谈JS变量声明和函数声明提升

先来两个问题

很多时候,在直觉上,我们都会认为JS代码在执行时都是自上而下一行一行执行的,但是实际上,有一种情况会导致这个假设是错误的。

a = 2;
var a;
console.log(a);

按照传统眼光,console.log(a)输出的应该是undefined,因为var a在a = 2之后。但是,输出的是2。

再看第二段代码:

console.log(a);
var a = 2;

有人会想到第一段代码,然后回答undefined。还有人会认为a在使用前未被声明,因此抛出ReferenceError异常。遗憾的是,结果是undefined。

为什么呢?

从编译器的角度看问题

JS在编译阶段,编译器的一部分工作就是找到所有声明,并用合适的作用域将他们关联起来。对于一般人来说var a = 2仅仅是一个声明,但是,JS编译器会将该段代码拆为两段,即:var a和a = 2。var a这个定义声明会在编译阶段执行,而a = 2这个赋值声明会在原地等待传统意义上的从上到下的执行。

所以,在编译器的角度来看,第一段代码实际上是这样的:

var a;  // 编译阶段执行
a = 2;
console.log(a);

所以,输出的是2。

类似的,第二个代码片段实际上是这样执行的:

var a;
console.log(a);
a = 2;

这样的话,很明显,输出的应该是undefined,因为只对a进行了定义声明,没有对a进行赋值声明。

从上面这两个例子可以看出,变量声明会从它们在代码中出现的位置被移动到当前作用域的最上方进行执行,这个过程叫做提升

函数提升

下面,再来看一段代码

foo();

function foo () {
    console.log(a);
    var a = 2;
}

在这个例子中,输出undefined而不会报错,因为,函数变量也能提升。即,实际上像如下的情况运行。

function foo () {
    var a;
    console.log(a);
    a = 2;
}

foo();

说到这里,你是不是认为提升很简单,只要把变量都放到当前作用域最上方执行就好了?

下面,我来说一种意外情况:函数表达式的提升情况。

函数表达式的提升情况
foo();

var foo = function bar () {
    console.log(a);
    var a = 2;
}

你是不是想说,这个例子不是和之前的那个差不多吗?输出的当然是undefined呀。但是,结果是,不输出,因为JS报了TypeError错误!

因为,函数表达式不会进行提升!

该例子的实际运行情况是这样的:

var foo;
foo();
foo = function bar () {
    var a;
    console.log(a);
    a = 2;
}

由于执行时,在作用域中找得到foo(该作用域最上方声明了foo),所以不会报ReferenceError错误,但是,foo此时没有进行赋值(如果foo是一个函数声明而不是函数表达式,那么就会赋值),也就是说实际上foo()是对一个值为undefined的变量进行函数调用,所以,理所应当抛出TypeError异常。

值得一提的是,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用,即:

foo();  // TypeError
bar();  // ReferenceError

var foo = function bar () {}

函数优先

函数声明和变量声明都会被提升,但是有一个值得注意的细节,那就是,函数会首先提升,然后才是变量!

看下面这一段代码:

foo();
var foo;
function foo () {
    console.log(1);
}
foo = function () {
    console.log(2);
}

这一段代码会输出1,原因就在于,函数优先。

这一段代码可以转换为以下形式:

function foo () {
    console.log(1);
}
var foo;    // 重复声明,被忽略
foo();      // 输出1
foo = function () {
    console.log(2);
}

如果,在代码的结尾再执行一次foo函数,此时,输出的是1。

function foo () {
    console.log(1);
}
var foo;    // 重复声明,被忽略
foo();      // 输出1
foo = function () {
    console.log(2);
}
foo();      // 输出2

因为,尽管重复的声明会被忽略了,但是后面的函数还是可以覆盖前面的函数。

明白了这个道理,你就可以理解下面这个问题了:

foo();
var a = true;
if (a) {
    function foo () {
        console.log("a");
    }
} else {
    function foo () {
        console.log("b");
    }
}

你猜这道题输出的结果是什么?是b!为什么?因为foo进行了两次的声明,但是,后一次函数覆盖了前一次的函数。所以调用foo时,永远调用的都是console.log("b")。

总结

1.所有声明(变量和函数)都会被移动到各自作用域的最顶端,这个过程被称为提升

2.函数表达式等各种赋值操作并不会被提升

3.函数优先原则

4.尽量避免产生提升问题

参考资料:You Dont‘t Know JS: SCope & Closures

原文地址:https://www.cnblogs.com/karthuslorin/p/8972585.html

时间: 2024-07-30 03:02:40

浅谈JS变量声明和函数声明提升的相关文章

浅谈Js中关于事件处理函数执行顺序的问题

Js给dom元素绑定事件的处理函数总的来说有两种方式:在html文档中绑定,在js代码中绑定. 然而,并不推荐在html标签上绑定事件. 在js代码中也可以分两种方式绑定事件: 1:通过dom元素的onclick等属性,直接绑定: 2: a.在ie下使用attachEvent/detachEvent函数的方式进行事件的绑定和取消: b.使用W3C标准的addEventListener和removeEventListener,给dom添加事件监听者和移除. 第一种方式只能绑定一个事件处理函数,后面

浅谈js中的this关键字

浅谈js中的this关键字 全局作用域中的this 函数作用域中的this 不同函数调用方法下的this 直接调用 作为对象的方法调用 作为构造函数调用 通过call或apply方法调用 嵌套函数作用域中的this 浅谈js中的this关键字 this是JavaScript中的关键字之一,在编写程序的时候经常会用到,正确的理解和使用关键字this尤为重要.接下来,笔者就从作用域的角度粗谈下自己对this关键字的理解,希望能给到大家一些启示,权当交流之用. 全局作用域中的this 本文将以作用域由

浅谈 js 语句块与标签

原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象字面量么?怎么成了语句块了?如果在赋值语句或者表达式里用的时候,确实是对象字面量,如: var a = {}; ({toString:function(){return "hehe"}}) + "..."; 是不是很有意思..但是直接使用如: {toString: fu

javaScript的函数(Function)对象的声明(@包括函数声明和函数表达式)

平时再用js写函数的时候,一般都是以惯例 function fn () {} 的方式来声明一个函数,在阅读一些优秀插件的时候又不免见到 var fn = function () {} 这种函数的创建,究竟他们用起来有什么区别呢,今天就本着打破砂锅问到底的精神,好好来说说这个让人神魂颠倒的--函数声明.  函数声明 函数声明示例代码 function fn () { console.log('fn 函数执行..'); // code.. } 这样我们就声明了一个名称为fn的函数,这里出个思考,你认

浅谈JS之AJAX

0x00:什么是Ajax? Ajax是Asynchronous Javascript And Xml 的缩写(异步javascript及xml),Ajax是使用javascript在浏览器后台操作HTTP和web服务器进行数据交换(用户不知道也感觉不出来,就跟桌面应用程序似的进行数据交互),它不会导致页面重新加载,这样才有更好的用户体验. Ajax是基于以下开放标准: javascript(DOM) css html xml(json) 通俗的说就是使用了javascript(DOM)的XMLH

从window.console&amp;&amp;console.log(123)浅谈JS的且运算逻辑(&amp;&amp;)

从window.console&&console.log(123)浅谈JS的且运算逻辑(&&) 作者:www.cnblogs.com  来源:www.cnblogs.com  发布日期:2015-03-01 一.JS的且运算记得最开始看到window.console&&console.log(123),当时知道能起什么作用但是没有深入研究,最近在研究后总算弄明白了.要理解这个,首先得明白三个知识点第一:短路原则这个大家都非常清楚的了,在做且运算的时候,“同真

浅谈 js 对象 toJSON 方法

前些天在<浅谈 JSON.stringify 方法>说了他的正确使用姿势,今天来说下 toJSON 方法吧.其实我觉得这货跟 toString 一个道理,他是给 stringify 方法字符串化的时候调用的.看下 MDN 官方文档吧<toJSON behavior>.非常简单,但是要注意的是他和 stringify 方法第二个参数稍微有点不同.因为 stringify 第二个参数是回调函数时,只是对当前 key 对应的值进行修改.而 toJSON 则是对当前对象进行修改.例如: v

浅谈 PHP 变量可用字符

原文:浅谈 PHP 变量可用字符 先来说说php变量的命名规则,百度下一抓一大把:(1) PHP的变量名区分大小写;(2) 变量名必须以美元符号$开始;(3) 变量名开头可以以下划线开始;(4) 变量名不能以数字字符开头. 其实所有编程都类似的命名规范就是:1. 变量第一个字符最好是 字母或_,不能以数字开头2. 第二个字符开始允许 数字,字母,_ 好了,差不多就是这样了,但是这不是我们要说的重点.今天我们说说 PHP 变量的可用字符,不仅仅是 数字,字母,_ 哦. 前几天QQ上一朋友发我一个s

37.浅谈js原型的理解

浅谈Js原型的理解 一.js中的原型毫无疑问一个难点,学习如果不深入很容易就晕了!    在参考了多方面的资料后,发现解释都太过专业,对于很多还没有接触过面向对象    语言的小白来说,有理解不了里面的专有名词!如果你没学过c++或者Java之类的更接触底层的语言,那就不要太深究,理解会用自然可以了,当接触到更多语言时慢慢的会理解越来越深刻! 下面我就举例分享一下prototype的概念!知道对于初学者知道这些就足够了! 分析一下,上面这个例子!我们可以知道 People的类型是一个对象!按照j