js预解析(变量提升)导致了什么问题?

预解析

  • JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量或者函数进行提前的声明,遵循先解析后使用的原则。
  • 变量提升的表现是,在变量或函数声明之前访问变量或调用函数而不会报错。
变量提升
函数提升

原因:

JavaScript引擎在代码执行前有一个解析的过程(预编译),创建执行上下文,初始化一些代码执行时需要用到的对象。 当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性, 它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。

首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析执行

  1. 在解析阶段 JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来, 变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似, 不过函数执行上下文会多出this、arguments和函数的参数。
  2. 在执行阶段,就是按照代码的顺序依次执行。

全局上下文:变量定义,函数声明 函数上下文:变量定义,函数声明,this,arguments

为何预解析

那为什么会进行变量提升呢?主要有以下两个原因:

  1. 提高性能
  2. 容错性更好

(1)提高性能

在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。

在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、 不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数), 并且因为代码压缩的原因,代码执行也更快了。

(2)容错性更好

变量提升可以在一定程度上提高JS的容错性,看下面的代码:

javascript 复制代码
    a = 1
    var a
    console.log(a) // 1 如果没有变量提升,这段代码就会报错导致的问题
    
    var tmp = new Date();
    function fn(){
        console.log(tmp);
        if(false){
            var tmp = 'hello nanjiu';
        }
    }
    fn();  // undefined

在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部, 相当于覆盖了外层的tmp,所以打印结果为undefined。

ini 复制代码
    var tmp = 'hello nan jiu';
    for (var i = 0; i < tmp.length; i++) {
        console.log(tmp[i]);
    }
    console.log(i); // 13

由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来13

总结:

  • 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
  • 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
  • 函数是一等公民,当函数声明与变量声明冲突时,变量提升时函数优先级更高,会忽略同名的变量声明

全局预编译和局部预编译

预编译分为全局预编译和局部预编译:

  • 全局预编译‌:在页面加载完成时执行,主要创建全局对象(Global Object),将所有全局变量和函数声明添加到这个对象上‌
  • 局部预编译‌:在函数执行前执行,主要创建激活对象(Activation Object),将所有局部变量和函数声明添加到这个对象上‌

实际应用中的问题和解决方案

在实际开发中,预编译可能导致一些难以发现的错误,例如在变量初始化之前就使用它,可能会导致不可预测的行为。为了避免这些问题,可以采取以下措施:

  • 使用let和const代替var,因为let和const不会提升,这样可以减少因提升导致的错误‌1。
  • 在使用变量之前确保其已被正确初始化。
相关推荐
用户440145493141918 分钟前
Spring Boot 3.x 异步编程实战:从 @Async 到虚拟线程
javascript
wangruofeng2 小时前
终端里 rm 一下文件就没了,macOS 怎么删文件才进废纸篓
javascript·命令行·apple
小妖6662 小时前
React input 输入卡顿问题
前端·javascript·react.js
李明卫杭州3 小时前
Vue2 portal-target 组件与 Vue3 的针对性优化
前端·javascript·vue.js
你怎么知道我是队长4 小时前
JavaScript的函数介绍
开发语言·前端·javascript
weedsfly4 小时前
观察者模式 vs 发布-订阅模式:从概念到实战,一次讲清楚
前端·javascript·面试
咪饭只吃一小碗4 小时前
告别微调成本!用 RAG 本地向量库解决大模型私有领域数据接入
javascript·后端·面试
miss4 小时前
海康威视 IoT 平台前端实战:视频流、海量设备与实时数据可视化
前端·javascript·物联网
mONESY5 小时前
MCP 协议实战博客:跨进程、跨语言可复用 AI 工具标准化方案
javascript
江华森5 小时前
Vue Router 与 SPA 原理深度解析
前端·javascript·vue.js