JavaScript中标识符提升问题


Posted in Javascript onJune 11, 2015

JS 存在变量提升,这个的设计其实是低劣的,或者是语言实现时的一个副作用。它允许变量不声明就可以访问,或声明在后使用在前。新手对于此则很迷惑,甚至许多使用JS多年老手也比较迷惑。但在 ES6 加入 let/const 后,变量Hoisting 就不存在了。

一、 变量未声明,直接使用

function test() {
  alert(notDefined);
}
test(); // ?

报错是自然的

JavaScript中标识符提升问题

二. 变量声明在末尾

function test() {
  alert(declaredButNotAssigned); // undefined
  var declaredButNotAssigned;
}
test();

输出 undefined, 结果比上例有所改善,没有报错,代码可以运行,但变量值可能不是程序员所期望的。

三、 变量声明在末尾,同时给变量赋值

function test() {
  alert(declaredAndAssigned); // undefined
  var declaredAndAssigned = 1;
}
test();

结果和 二 相同, 很明显,并不会因为赋值了就输出 1。

二、三 都发生了变量提升(Hoisting),简单定义

变量提升: 在指定作用域里,从代码顺序上看是变量先使用后声明,但运行时变量的 “可访问性” 提升到当前作用域的顶部,其值为 undefined ,没有 “可用性”。

这里强调 “代码顺序” 和 “运行顺序”,是因为多数时候我们写的代码都是顺序执行的,即 “代码顺序” 和 “运行顺序” 是一致的。这也符合人的大脑的思维过程。比如有过 C语言 经验的程序员

#include <stdio.h>
int main() {
  int x = 1;
  printf("%d, ", x); // 1
}

两句代码,先声明整数型 x, 再输出。代码顺序和运行顺序是一致的,即正常运行。

如果顺序反过来

#include <stdio.h>
int main() {
  printf("%d, ", x); // error
  int x = 1;
}

此时,编译都不能通过了。但JS里可以反过来写,见二、三。

因此,有类 C语言 经验的程序员,都很清楚变量需要 先声明后使用,不然会报错。而到了JS里,有 变量提升 现象,可以 先使用后声明,C 的经验用到 JS 里迷惑便出现了。

四、 函数表达式也存在变量提升

function test() {
  alert(func); // undefined
  var func = function() {};
}
test();

但如果想使用这个 func,则无可能

function test() {
  alert(func); // undefined
  func(); // 报异常
  var func = function() {};
}
test();

结果func 是 undefined,调用 func 则会报异常。 在上面的定义中提到了 可访问性 和 可用性 对应如下语句。

可访问性:alert(func),输出 undefined,可以运行,可以访问 func。

可用性:   func(), 报异常,不能正常调用 func,表示无可用性。

二、三、四 都是使用 var 声明的变量,JS 里函数声明也会存在提升,只是这个 “变量” 比较特殊,它是一个 function 类型(可以作为函数、方法或构造器)。它的名字(标识符)也会提升到当前作用域的顶部。

五、函数声明的名也会提升到当前作用域顶部

function test() {
  alert(f1); // function
  f1(); // "called"
  function f1() {
    alert('called');
  }
}
test();

我们看到,声明 f1 在代码最末,f1 使用在前,alert(f1) 和 f1() 都正常执行,表示 可访问性 和 可用性 都有了。

前面说了,变量提升(Hoisting)没什么用,属于语言的低劣设计,好的习惯还是 “先声明后使用”。这个特性也会出现在不少大公司面试题里

题1:

// 写出以下代码的运行结果
var a = 1;
function fn() {
  if (!a) {
    var a = 2;
  }
  alert(a); // ?
}
fn();

题2:

// 写出以下代码的运行结果
var a = 1;
function fn() {
  a = 2;
  return;
  function a() {}
}
fn();
alert(a); // ?

但这一切随着 ES6 的 let/const 到来结束了,ES里除全局变量外,其它都使用 let/const,var 替换成 let 后变量提升就不复存在了。

function test() {
  alert(declaredButNotAssigned1); // 报异常
  alert(declaredButNotAssigned2); // 报异常
  alert(func); // 报异常
 
  let declaredButNotAssigned1;
  let declaredButNotAssigned2 = true;
  let func = function() {};
}
test();

这强制程序员养成好的习惯,变量需要“先声明再使用”,否则报错。

以下摘自MDN的关于let不在发生变量提升的描述

In ECMAScript 6, let does not hoist the variable to the top of the block. If you reference a variable in a block before the let declaration for that variable is encountered, this results in a ReferenceError, because the variable is in a "temporal dead zone" from the start of the block until the declaration is processed.

用 let 声明变量后,typeof 也不再安全

if (condition) {
  alert(typeof num); // Error!
  let num = 100;
}

以前可以用 typeof == 'undefined',来判断是否引入了某lib,比如jQuery

// 判断jQuery是否引入了
if (typeof $ !== 'undefined') {
  // do something
}...

jQuery没有引入,$ 没有声明,这句也不会报错而影响到下面的代码执行,但如果是 let 声明的就会报错了。

以上所述就是本文的全部内容了,希望大家能够喜欢。

Javascript 相关文章推荐
Google排名中的10个最著名的 JavaScript库
Apr 27 Javascript
JavaScript中清空数组的三种方法分享
Apr 07 Javascript
firefox下jquery iframe刷新页面提示会导致重复之前动作
Dec 17 Javascript
javascript 中__proto__和prototype详解
Nov 25 Javascript
node.js中使用socket.io的方法
Dec 15 Javascript
javascript实现全局匹配并替换的方法
Apr 27 Javascript
javascript实现根据iphone屏幕方向调用不同样式表的方法
Jul 13 Javascript
Vue2.x中的父子组件相互通信的实现方法
May 02 Javascript
vue使用element-ui的el-input监听不了回车事件的解决方法
Jan 12 Javascript
vue如何解决循环引用组件报错的问题
Sep 22 Javascript
JavaScript设计模式之责任链模式实例分析
Jan 16 Javascript
WebGL学习教程之Three.js学习笔记(第一篇)
Apr 25 Javascript
JQuery删除DOM节点的方法
Jun 11 #Javascript
JQuery插入DOM节点的方法
Jun 11 #Javascript
JQuery创建DOM节点的方法
Jun 11 #Javascript
JQuery查找DOM节点的方法
Jun 11 #Javascript
JQuery鼠标移到小图显示大图效果的方法
Jun 10 #Javascript
JQuery实现超链接鼠标提示效果的方法
Jun 10 #Javascript
jquery序列化方法实例分析
Jun 10 #Javascript
You might like
php discuz 主题表和回帖表的设计
2009/03/13 PHP
全局记录程序片段的运行时间 正确找到程序逻辑耗时多的断点
2011/01/06 PHP
ThinkPHP之getField详解
2014/06/20 PHP
php实现过滤表单提交中html标签的方法
2014/10/17 PHP
php 解决substr()截取中文字符乱码问题
2016/07/18 PHP
Yii2 中实现单点登录的方法
2018/03/09 PHP
PHP whois查询类定义与用法示例
2019/04/03 PHP
jQuery EasyUI API 中文文档 - TreeGrid 树表格使用介绍
2011/11/21 Javascript
jquery中的ajax方法怎样通过JSONP进行远程调用
2014/05/04 Javascript
JQuery标签页效果的两个实例讲解(4)
2015/09/17 Javascript
React实现点击删除列表中对应项
2017/01/10 Javascript
JavaScript中的this陷阱的最全收集并整理(没有之一)
2017/02/21 Javascript
Bootstrap超大屏幕的实现代码
2017/03/22 Javascript
jQuery插件FusionWidgets实现的AngularGauge图效果示例【附demo源码】
2017/03/23 jQuery
新版vue-cli模板下本地开发环境使用node服务器跨域的方法
2018/04/03 Javascript
vue填坑之webpack run build 静态资源找不到的解决方法
2018/09/03 Javascript
js实现继承的方法及优缺点总结
2019/05/08 Javascript
JavaScript中将值转换为字符串的五种方法总结
2019/06/06 Javascript
8 个有用的JS技巧(推荐)
2019/07/03 Javascript
vue结合el-upload实现腾讯云视频上传功能
2020/07/01 Javascript
[01:52]DOTA2完美大师赛Vega战队趣味视频——kpii老师小课堂
2017/11/25 DOTA
Python求两个list的差集、交集与并集的方法
2014/11/01 Python
Python的Flask框架中web表单的教程
2015/04/20 Python
Python入门_浅谈for循环、while循环
2017/05/16 Python
python实现flappy bird小游戏
2018/12/24 Python
利用python实现凯撒密码加解密功能
2020/03/31 Python
python中加背景音乐如何操作
2020/07/19 Python
Python模拟登录和登录跳转的参考示例
2020/10/30 Python
localStorage 设置过期时间的方法实现
2018/12/21 HTML / CSS
特罗佩亚包官方网站:Tropea
2017/01/03 全球购物
美国最便宜的旅游网站:CheapTickets
2017/07/09 全球购物
优秀体育委员自荐书
2014/01/31 职场文书
小班下学期幼儿评语
2014/12/30 职场文书
亲属关系公证书样本
2015/01/23 职场文书
幼儿园教师师德表现自我评价
2015/03/05 职场文书
MySQL中distinct与group by之间的性能进行比较
2021/05/26 MySQL