JavaScript变量函数声明提升(Hoisting)是在 Javascript 中执行上下文工作方式的一种认识(也可以说是一种预编译),从字面意义上看,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,在代码里的位置是不会动的,而是在编译阶段被放入内存中会和代码顺序不一样。变量函数声明提升虽然对于实际编码影响不大,特别是现在ES6的普及,但作为前端算是一个基础知识,必须掌握的,是很多大厂的前端面试必问的知识点之一。在这里分享,不是什么新鲜的内容,只是作为一个自己的学习笔记,加速对其的理解。

变量知道是ES5中的 var 和 function 中的产物,ES6中的 let 、 const 则不存在有变量提升。

变量提升

JavaScript引擎的工作方式是先解析代码,获取所有声明的变量和函数,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(Hoisting)。

这里说的变量声明,包括函数的声明,接下来看看代码:

  1. function hoistingVariable() {
  2. if (!devpoint) {
  3. var devpoint = 1;
  4. }
  5. console.log(devpoint);
  6. }
  7. hoistingVariable();
  8. // 下面是输出结果
  9. // 1

变量所处的作用域为函数体内,解析的时候查找该作用域中的声明的变量,devpoint在if虽然未声明,根据变量提升规则,变量的声明提升到函数的第一行,但未赋值。实际的效果等同于下面的代码:

  1. function hoistingVariable() {
  2. var devpoint;
  3. if (!devpoint) {
  4. devpoint = 1;
  5. }
  6. console.log(devpoint);
  7. }
  8. hoistingVariable();

接下再增加一些迷惑的代码,如下:

  1. var devpoint = "out";
  2. function hoistingVariable() {
  3. var devpoint;
  4. if (!devpoint) {
  5. devpoint = "in";
  6. }
  7. console.log(devpoint);
  8. }
  9. hoistingVariable();
  10. console.log(devpoint);
  11. // 下面是输出结果
  12. // in
  13. // out

对于同名变量声明,个人理解是先找作用域,就近原则,函数体内声明(前提是有声明 var ),就只找函数内查找,并不受函数外声明的影响。

把上面函数体内的声明语句去掉,输出情况也就不一样。

  1. var devpoint = "out";
  2. function hoistingVariable() {
  3. if (!devpoint) {
  4. devpoint = "in";
  5. }
  6. console.log(devpoint);
  7. }
  8. hoistingVariable();
  9. console.log(devpoint);
  10. // 下面是输出结果
  11. // out
  12. // out

函数体内声明语句去掉后,这是就需要去函数体外找声明,根据这一条,函数外声明并赋值了,函数体内的 if 语句就不会执行。

下面代码调整了赋值的顺序,代码如下:

  1. var devpoint;
  2. function hoistingVariable() {
  3. if (!devpoint) {
  4. devpoint = "in";
  5. }
  6. console.log(devpoint);
  7. }
  8. devpoint = "out";
  9. hoistingVariable();
  10. console.log(devpoint);
  11. // 下面是输出结果
  12. // out
  13. // out

根据上面说的,函数体内的变量是外部声明的,但未赋值,函数是提升了,并为执行。在函数执行前赋值给devpoint,再执行就变成了out

函数提升

上面介绍过,变量提升,同样包括函数的声明,不同方式的函数声明,执行也有所不同。这种问题就是直接上代码。

  1. function hoistingFun() {
  2. hello();
  3. function hello() {
  4. console.log("hello");
  5. }
  6. }
  7. hoistingFun();
  8. // 下面是输出结果
  9. // hello

上面的代码能够正常运行是因为函数声明被提升,函数 hello 被提升到顶部,运行效果跟下面代码一致:

  1. function hoistingFun() {
  2. function hello() {
  3. console.log("hello");
  4. }
  5. hello();
  6. }
  7. hoistingFun();
  8. // 下面是输出结果
  9. // hello

如果在同一个作用域中对同一个函数进行声明,后面的函数会覆盖前面的函数声明。

  1. function hoistingFun() {
  2. hello();
  3. function hello() {
  4. console.log("hello");
  5. }
  6. function hello() {
  7. console.log("hello2");
  8. }
  9. }
  10. hoistingFun();
  11. // 下面是输出结果
  12. // hello2

两个函数声明都被提升了,按照声明的顺序,后面的声明覆盖前面的声明。

函数声明常见的方式有两种,还有一种是匿名函数表达式声明方式,这种方式可以视为是变量的声明来处理,当作用域中有函数声明和变量声明时,函数声明的优先级最高,将上面的代码更改后,结果就不一样了,如下:

  1. function hoistingFun() {
  2. hello();
  3. function hello() {
  4. console.log("hello");
  5. }
  6. var hello = function () {
  7. console.log("hello2");
  8. };
  9. }
  10. hoistingFun();
  11. // 下面是输出结果
  12. // hello

上面的代码,编译逻辑如下:

  1. function hoistingFun() {
  2. function hello() {
  3. console.log("hello");
  4. }
  5. hello();
  6. hello = function () {
  7. console.log("hello2");
  8. };
  9. }
  10. hoistingFun();
  11. // 下面是输出结果
  12. // hello

接下来再来看下,外部使用变量声明,函数体内使用函数声明的示例:

  1. var hello = 520;
  2. function hoistingFun() {
  3. console.log(hello);
  4. hello = 521;
  5. console.log(hello);
  6. function hello() {
  7. console.log("hello");
  8. }
  9. }
  10. hoistingFun();
  11. console.log(hello);
  12. // 下面是输出结果
  13. // [Function: hello]
  14. // 521
  15. // 520

上面说过,在函数体内声明过的变量或者函数,只作用于函数体内,受限于函数体内,不受外部声明的影响,相当于函数体内作用域与外部隔离。上面代码的编译后的逻辑如下:

  1. var hello = 520;
  2. function hoistingFun() {
  3. function hello() {
  4. console.log("hello");
  5. }
  6. console.log(hello);
  7. hello = 521;
  8. console.log(hello);
  9. }
  10. hoistingFun();
  11. console.log(hello);

在变量声明中,函数的优先权最高,永远提升到作用域最顶部,然后才是函数表达式和变量的执行顺序。

来看下面的代码:

  1. var hello = 520;
  2. function hello() {
  3. console.log("hello");
  4. }
  5. console.log(hello);
  6. // 下面是输出结果
  7. // 520

根据函数声明优先级最高的原则,上面代码的执行逻辑如下:

  1. function hello() {
  2. console.log("hello");
  3. }
  4. hello = 520;
  5. console.log(hello);

为什么要提升?

至于为什么要提升,这里不做详细介绍,提供一些参考文章,有兴趣的可以去查阅

最佳实践

现代JavaScript中,已经有很多方式避免变量提升带来的问题,使用letconst替代var,使用eslint等工具避免变量重复定义,在一些前端开发团队中,可以针对团队做一些规范化的脚手架,如项目初始化强制项目的目录、eslint的最佳配置等,用程序规范的过程比人督促要靠谱。

下面的代码可以看到const 和 var 声明的变量的区别,const 声明的变量不会提升,具体的区别可以查阅《细说javascript中变量声明var、let、const的区别

  1. console.log("1a", myTitle1);
  2. if (1) {
  3. console.log("1b", myTitle1);
  4. var myTitle1 = "devpoint";
  5. }
  6. if (1) { // 这里的代码是有错误无法执行
  7. console.log("3c", myTitle2);
  8. const myTitle2 = "devpoint";
  9. }
  10. // 下面是输出结果
  11. // 1a undefined
  12. // 1b undefined

总结

通过自我学习变量函数提升,加深了对其理解,对于前端面试所涉及的类似问题可以自信的给出答案,算是一种收获。

JS 会有变量提升和函数提升的更多相关文章

  1. JS 变量提升与函数提升

    JS 变量提升与函数提升 JS变量提升 变量提升是指:使用var声明变量时,JS会将变量提升到所处作用域的顶部.举个简单的例子: 示例1 console.log(foo); // undefined ...

  2. js变量提升与函数提升的详细过程

    大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...

  3. JS逻辑题 技术点: 1). 变量提升 2). 函数提升 3). 预处理 4). 调用顺序

    考查的技术点:  1). 变量提升 2). 函数提升  3). 预处理  4). 调用顺序 var c = 1; function c(c) { console.log(c); var c = 3; ...

  4. js中的变量提升和函数提升

    从上周开始,我所在的学习小组正式开始了angular的学习,angular是全面支持es6的,所以语法上和以前的angular有了很大的不同,比如变量声明时就抛弃了var,而选择了let和const: ...

  5. JS——变量提升和函数提升

    一.引入 在了解这个知识点之前,我们先来看看下面的代码,控制台都会输出什么 var foo = 1; function bar() { if (!foo) { var foo = 10; } aler ...

  6. js中变量提升和函数提升

    变量提升和函数提升的总结 我们在学习JavaScript时,会遇到变量提升和函数提升的问题,为了理清这个问题,现做总结如下,希望对初学者能有所帮助 我们都知道 var 声明的变量有变量提升,而 let ...

  7. JavaScript系列文章:变量提升和函数提升

    第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函 ...

  8. JavaScript:变量提升和函数提升

    第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函 ...

  9. JavaScript: 变量提升和函数提升

    第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函 ...

  10. ES6-LET,变量提升,函数提升

    1:let命令 ①类似var,但只在let所在代码块内有效 ②不存在变量提升 ③暂时性死区(TDZ)—有let命令时,在此命令前都没法使用此变量 ④不允许重复声明 ⑤ES6允许块级作用域任意嵌套 ⑥E ...

随机推荐

  1. 得到一个a(10)到b(20)的随机数。包括10和20

  2. HCIE-SEC笔记-EVENG模拟器安装

    EVEng模拟器安装: 准备:Vmware 16.0 EVEng EVE-NG-Win-Client-Pack.exe [抓包,内置wireshark,内置vnc,用来打开windows系统] Win ...

  3. vue动态路由实现原理 addRoute

    vue新版router.addRoute基础用法 新版Vue Router中用router.addRoute来替代原有的router.addRoutes来动态添加路由.子路由 在添加子路由的时候 比如 ...

  4. python练习-20200826

    1:L = [ ['Apple', 'Google', 'Microsoft'], ['Java', 'Python', 'Ruby', 'PHP'], ['Adam', 'Bart','Lisa'] ...

  5. Spring 源码(7)Spring的注解是如何解析的?

    上一篇 https://www.cnblogs.com/redwinter/p/16196359.html 介绍了BeanFactoryPostProcessor的执行过程,这篇文章介绍Spring中 ...

  6. 类变量_main方法_代码块

    需要解决的问题: 统计一共创建了多少个对象 类变量(静态变量) 被所有对象共享 可以通过 类名(推荐)|对象名.变量名 方式来访问 main main 方法 是虚拟机在调用  args 传值灵活 结果 ...

  7. 劳动节快乐!手写个核心价值观编码工具 - Python实现

    前言 今天是五一劳动节,祝各位无产阶级劳动者节日快乐! 然后来整活分享一些有趣的东西~ 这个小工具是我大学时做着玩的,对于各位接班人来说,12个词的核心价值观这东西,大家都非常熟悉了,这工具可以实现将 ...

  8. 前端面试 -Vue2系列

    vue 1为啥用Vue? 1MVVM 数据的双向绑定 2指令系统 不需要操作DOM 3组件化 2v-show和v-if.v-for v-show 通过 display:none 隐藏元素,DOM还在. ...

  9. KMP算法学习以及小结(好马不吃回头草系列)

    首先请允许我对KMP算法的三位创始人Knuth,Morris,Pratt致敬,这三位优秀的算法科学家发明的这种匹配模式可以大大避免重复遍历的情况,从而使得字符串的匹配的速度更快,效率更高. 首先引入对 ...

  10. 过早的给方法中 引用对象 设为 null 可被 GC提前回收吗?

    经常在代码中看到有人将 null 赋值给引用类型,来达到让 GC 提前回收的目的,这样做真的有用吗?今天我们就来研究一下. 为了方便讲解,来一段测试代码,提前将 test1=null ,然后调用 GC ...