前端进阶:深度剖析预解析机制

一、预解析是什么?

在前端开发中,我们常常会遇到一些看似不符合常规逻辑的代码执行现象,比如为什么在变量声明之前访问它,得到的结果是undefined,而不是报错?为什么函数在声明之前就可以被调用?这些问题的答案,都与前端的预解析机制有关。那预解析究竟是什么呢?

简单来说,预解析是 JavaScript 解析器在运行代码前的一个重要处理步骤。当 JavaScript 代码被加载到浏览器中时,解析器并不会立即逐行执行代码,而是会先进行预解析。在这个阶段,解析器会将所有带有var声明的变量和function声明的函数,在内存中进行提前声明或者定义。 这样做的目的是为了让 JavaScript 引擎在正式执行代码时,能够更高效地处理变量和函数的调用,避免因为变量或函数未定义而导致的错误。

二、预解析的类型

(一)全局预解析

当我们在浏览器中打开一个包含 JavaScript 代码的页面时,全局预解析就开始了。它会对全局代码进行通读,找出所有使用var声明的变量和function声明的函数。在这个过程中,变量只是被声明,并不会被赋值,而函数则会被完整地定义。需要注意的是,函数体内的代码在全局预解析阶段不会被处理 。

例如以下代码:

console.log(num);var num = 10;function fn() {console.log('这是一个函数');}fn();

在全局预解析阶段,浏览器会先找到var num,将num声明为一个变量,但此时num的值为undefined。接着找到function fn,将fn定义为一个函数。当真正开始执行代码时,console.log(num)会输出undefined,因为此时num虽然已经声明,但还未被赋值。然后执行num = 10,给num赋值为 10。最后调用fn函数,输出 “这是一个函数”。

(二)局部预解析

局部预解析发生在函数被调用的时候。当一个函数被调用时,会创建一个私有作用域,在这个私有作用域内,会对函数内部的代码进行预解析。同样,它会查找函数内使用var声明的变量和function声明的函数,变量声明会被提前,函数也会被提前定义,解析完成后才会执行函数体里的代码。

以下面这段代码为例:

function test() {console.log(a);var a = 5;console.log(a);function inner() {console.log('这是内部函数');}inner();}test();

在调用test函数时,进入局部预解析阶段。首先,在这个私有作用域内找到var a,将a声明为局部变量,值为undefined,同时找到function inner,将inner定义为一个函数。然后开始执行函数体代码,console.log(a)会输出undefined,接着执行a = 5,给a赋值为 5,再次执行console.log(a),输出 5。最后调用inner函数,输出 “这是内部函数”。

三、预解析解析的内容

(一)var 声明

在 JavaScript 中,使用var声明变量时,会发生变量提升现象,即变量的声明会被提升到其所在作用域的顶部,但变量的赋值操作并不会被提升,仍然保留在原来的位置。这意味着,我们可以在变量声明之前访问它,只不过此时它的值是undefined。

例如:

console.log(num);var num = 10;console.log(num);

在上述代码中,第一行console.log(num)输出的结果是undefined。这是因为在预解析阶段,var num被提升到了当前作用域的顶部,相当于代码变成了:

var num;console.log(num);num = 10;console.log(num);

所以,在第一个console.log(num)执行时,num已经被声明,但还没有被赋值,其值为undefined。而在执行num = 10后,第二个console.log(num)输出的结果就是 10。

(二)函数声明

函数声明在预解析阶段也会被提升,与变量提升不同的是,函数声明是整个函数定义被提升,而不仅仅是函数名。这就使得我们可以在函数声明之前调用它。

例如声明式函数:

fn();function fn() {console.log('这是一个声明式函数');}

在这个例子中,fn()函数调用在函数声明之前,但代码依然能够正常执行,输出 “这是一个声明式函数”。这是因为在预解析阶段,函数fn的定义被提升到了作用域的顶部,所以在调用时,JavaScript 引擎已经知道了fn是一个函数。

再看赋值式函数:

fn2();var fn2 = function() {console.log('这是一个赋值式函数');};

上述代码中,fn2()函数调用会报错,提示fn2 is not a function。这是因为在预解析阶段,只有var fn2被提升,此时fn2只是一个普通变量,值为undefined,还没有被赋值为函数。当执行到fn2()时,fn2还不是一个函数,所以会报错 。只有在执行到var fn2 = function() {... }时,fn2才被赋值为一个函数。

四、预解析原理揭秘

了解了预解析的类型和内容后,我们来深入探究一下预解析的原理。在浏览器中,预解析是与 HTML 解析并行进行的一个重要过程 。当浏览器接收到 HTML 文档后,会开启一个主线程来解析 HTML,同时启动一个预解析线程。预解析线程的主要任务是扫描 HTML 文档,寻找其中的外部资源引用,如<link>标签引用的 CSS 文件、<script>标签引用的 JavaScript 文件以及<img>标签引用的图片等,并提前下载这些资源。

在解析 HTML 时,假设遇到了如下代码:

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><link rel="stylesheet" href="styles.css"><script src="script.js"></script></head><body><img src="image.jpg" alt="示例图片"></body></html>

主线程在解析到<link rel="stylesheet" href="styles.css">时,会继续解析后续的 HTML 内容,而预解析线程则会立即开始下载styles.css文件。同样,当解析到<script src="script.js"></script>时,预解析线程也会下载script.js,以及解析到<img src="image.jpg" alt="示例图片">时,下载image.jpg。这样,当主线程后续需要这些资源时,它们可能已经被下载完成,从而减少了等待时间,提高了页面的加载速度和渲染效率。

需要注意的是,对于<script>标签,如果没有使用async或defer属性,浏览器会按照默认行为暂停 HTML 解析,并等待脚本下载和执行完毕。因为 JavaScript 代码的执行可能会修改 DOM 树结构,所以为了保证 DOM 树的一致性,在执行 JavaScript 时,HTML 解析会被暂停。但在预解析阶段,预解析线程可以提前开始下载这些脚本文件,即使尚未到达需要执行它们的部分。

五、预解析常见问题及解决

(一)变量提升与作用域

在 JavaScript 中,变量提升和作用域的概念紧密相关,这也导致了一些容易让人困惑的问题。

  • 局部变量遮蔽全局变量:当局部作用域中声明了与全局变量同名的变量时,就会发生局部变量遮蔽全局变量的情况。在局部作用域内,对该变量的访问和操作都将针对局部变量,而不会影响到全局变量。

例如:

var num = 10;function test() {var num = 20;console.log(num);}test();console.log(num);

在上述代码中,全局作用域中声明了变量num并赋值为 10。在test函数内部,又声明了一个同名的局部变量num并赋值为 20。在test函数内,console.log(num)输出的是局部变量num的值 20,因为局部变量遮蔽了全局变量。而在函数外部,console.log(num)输出的依然是全局变量num的值 10 。

  • 函数参数与变量提升:当函数参数与函数内部使用var声明的变量同名时,会出现一些特殊的情况。函数参数会被优先提升,并且函数内部的var声明会被忽略,但赋值操作仍然会执行。

例如:

function fun(param) {console.log(param);var param = function () {console.log(1);};console.log(param);}fun(5);

在这个例子中,调用fun(5)时,参数param被赋值为 5。在函数内部,虽然有var param的声明,但由于参数param已经存在,这个声明会被忽略。所以,第一个console.log(param)输出的是参数param的值 5。接着,param被赋值为一个函数,此时第二个console.log(param)输出的就是这个函数 。

为了避免这些问题,我们在编写代码时,应该遵循一些最佳实践:

  • 尽量避免在不同作用域中使用相同的变量名,以减少变量遮蔽带来的困惑。
  • 养成良好的变量命名习惯,使变量名具有描述性,能够清晰地表达其用途。
  • 在函数内部,明确区分函数参数和局部变量,避免同名冲突。

(二)函数声明与变量声明的优先级

在 JavaScript 中,函数声明和变量声明都存在提升现象,但函数声明的优先级高于变量声明。这意味着在预解析阶段,函数声明会先被提升到作用域的顶部,然后才是变量声明。

当函数声明和变量声明同名时,函数声明会覆盖变量声明,但变量的赋值操作会在执行阶段覆盖函数的定义。例如:

console.log(foo);function foo() {console.log('这是一个函数');}var foo = 10;console.log(foo);

在上述代码中,第一个console.log(foo)输出的是函数foo的定义。这是因为在预解析阶段,函数声明function foo()被提升到了作用域的顶部,此时foo是一个函数。接着,var foo的声明也被提升,但它不会覆盖已经存在的函数声明。在执行阶段,foo = 10将foo赋值为 10,所以第二个console.log(foo)输出的是 10 。

再看下面这个例子:

function bar() {console.log(a);var a = 5;function a() {console.log('这是内部函数');}console.log(a);}bar();

在bar函数中,预解析时,函数声明function a()先被提升,然后是var a的声明(这个声明会被忽略,因为已经有同名的函数声明)。所以第一个console.log(a)输出的是函数a。接着,执行a = 5,将a赋值为 5,此时第二个console.log(a)输出的就是 5。如果此时再调用a(),就会报错,因为a已经被赋值为 5,不再是一个函数 。

理解函数声明和变量声明的优先级,有助于我们写出更准确、可维护的代码。在实际开发中,要避免函数声明和变量声明同名,以免造成不必要的错误和困惑。

六、预解析对前端性能的影响

预解析在前端性能优化方面发挥着重要作用,尤其是在网络资源加载和页面渲染速度上。

(一)缩短 DNS 解析时间

DNS 解析是将域名转换为 IP 地址的过程,这个过程通常会消耗一定的时间,而 DNS 预解析(dns-prefetch)技术可以提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,从而缩短 DNS 解析时间。当浏览器解析 HTML 文档时,会遇到各种资源的引用,如<script>标签引用的 JavaScript 文件、<link>标签引用的 CSS 文件以及<img>标签引用的图片等,这些资源可能来自不同的域名,每次访问不同域名都需要进行 DNS 解析。例如,在一个电商网站的页面中,不仅有来自主域名的商品信息展示,还引用了第三方 CDN 上的图片资源和字体文件,以及其他合作平台的广告脚本,这些不同来源的资源都需要进行 DNS 解析。通过 DNS 预解析,浏览器可以在后台提前完成这些域名的解析工作,当真正需要加载这些资源时,就可以直接使用已经解析好的 IP 地址,减少了等待 DNS 解析的时间。

(二)提高页面加载速度

通过提前解析域名,浏览器能够更快地建立与服务器的连接,从而加快资源的下载速度。在一个复杂的前端应用中,可能会有大量的 JavaScript、CSS 和图片等资源需要加载。以一个在线新闻网站为例,页面上除了文章内容,还包含各种配图、广告、推荐文章链接等,这些资源分布在不同的域名下。如果没有预解析,浏览器在加载这些资源时,需要逐个进行 DNS 解析,这会导致页面加载时间延长。而使用预解析后,在页面解析的同时,DNS 解析已经在后台完成,资源可以更快地被下载和加载,大大提高了页面的加载速度,用户能够更快地看到完整的页面内容,提升了用户体验。

(三)减少资源加载阻塞

在浏览器解析 HTML 文档时,如果遇到<script>标签,会暂停 HTML 解析,先去加载和执行 JavaScript 代码,这是因为 JavaScript 代码可能会修改 DOM 树结构,为了保证 DOM 树的一致性,需要先执行 JavaScript。而在加载 JavaScript 文件时,又会涉及到 DNS 解析、建立连接、下载文件等过程,如果 DNS 解析时间过长,就会阻塞页面的渲染。预解析可以提前完成 DNS 解析,减少了这一过程对页面渲染的阻塞。例如,在一个视频播放网站中,视频播放器的初始化脚本可能需要从不同的域名获取配置信息和资源,通过预解析,这些域名的解析工作可以提前完成,当解析到<script>标签时,能够更快地加载和执行脚本,减少了视频播放前的等待时间,让用户能够更流畅地观看视频,避免了因资源加载阻塞而导致的页面卡顿或白屏现象。

七、最后小结

前端预解析作为 JavaScript 解析过程中的重要环节,对代码的执行顺序和结果有着深远的影响。它通过提前声明变量和函数,为代码的顺利执行奠定了基础。同时,DNS 预解析等技术的应用,也在前端性能优化方面发挥着关键作用,显著提升了页面的加载速度和用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/11372.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Baklib赋能企业提升内容中台构建效率的全新路径解析

内容概要 在当今数字化转型的大潮中&#xff0c;企业面临着前所未有的挑战与机遇。为了顺应市场的发展趋势&#xff0c;提高运营能力&#xff0c;搭建高效的内容中台已成为企业迫在眉睫的任务。内容中台不仅仅是一个技术架构的集合&#xff0c;它更是企业实现数据共享、资源整…

计算机网络——流量控制

流量控制的基本方法是确保发送方不会以超过接收方处理能力的速度发送数据包。 通常的做法是接收方会向发送方提供某种反馈&#xff0c;如&#xff1a; &#xff08;1&#xff09;停止&等待 在任何时候只有一个数据包在传输&#xff0c;发送方发送一个数据包&#xff0c;…

游戏引擎 Unity - Unity 设置为简体中文、Unity 创建项目

Unity Unity 首次发布于 2005 年&#xff0c;属于 Unity Technologies Unity 使用的开发技术有&#xff1a;C# Unity 的适用平台&#xff1a;PC、主机、移动设备、VR / AR、Web 等 Unity 的适用领域&#xff1a;开发中等画质中小型项目 Unity 适合初学者或需要快速上手的开…

MySQL基础-多表查询

多表查询-多表关系 多表查询-概述 例如执行下行sql语句就会出现笛卡尔积&#xff1a; select *from emp,dept; --消除笛卡尔积 select * from emp,dept where emp.dept_id dept.id; 多表查询-查询分类 多表查询-连接查询-内连接 --内连接演示 --1.查询每一个员工的姓名,及关…

[权限提升] Wdinwos 提权 维持 — 系统错误配置提权 - Trusted Service Paths 提权

关注这个专栏的其他相关笔记&#xff1a;[内网安全] 内网渗透 - 学习手册-CSDN博客 0x01&#xff1a;Trusted Service Paths 提权原理 Windows 的服务通常都是以 System 权限运行的&#xff0c;所以系统在解析服务的可执行文件路径中的空格的时候也会以 System 权限进行解析&a…

【01】共识机制

BTF共识 拜占庭将军问题 拜占庭将军问题是一个共识问题 起源 Leslie Lamport在论文《The Byzantine Generals Problem》提出拜占庭将军问题。 核心描述 军中可能有叛徒&#xff0c;却要保证进攻一致&#xff0c;由此引申到计算领域&#xff0c;发展成了一种容错理论。随着…

本地部署DeepSeek教程(Mac版本)

第一步、下载 Ollama 官网地址&#xff1a;Ollama 点击 Download 下载 我这里是 macOS 环境 以 macOS 环境为主 下载完成后是一个压缩包&#xff0c;双击解压之后移到应用程序&#xff1a; 打开后会提示你到命令行中运行一下命令&#xff0c;附上截图&#xff1a; 若遇…

【Redis】Redis 经典面试题解析:深入理解 Redis 的核心概念与应用

Redis 是一个高性能的键值存储系统&#xff0c;广泛应用于缓存、消息队列、排行榜等场景。在面试中&#xff0c;Redis 是一个高频话题&#xff0c;尤其是其核心概念、数据结构、持久化机制和高可用性方案。 1. Redis 是什么&#xff1f;它的主要特点是什么&#xff1f; 答案&a…

JavaWeb入门-请求响应(Day3)

(一)请求响应概述 请求(HttpServletRequest):获取请求数据 响应(HttpServletResponse):设置响应数据 BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器就可访问,应用程序的逻辑和数据都存储在服务端(维护方便,响应速度一般) CS架构:Client/ser…

基于UKF-IMM无迹卡尔曼滤波与交互式多模型的轨迹跟踪算法matlab仿真,对比EKF-IMM和UKF

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于UKF-IMM无迹卡尔曼滤波与交互式多模型的轨迹跟踪算法matlab仿真,对比EKF-IMM和UKF。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 3.核心程序 .…

笔灵ai写作技术浅析(三):深度学习

笔灵AI写作的深度学习技术主要基于Transformer架构,尤其是GPT(Generative Pre-trained Transformer)系列模型。 1. Transformer架构 Transformer架构由Vaswani等人在2017年提出,是GPT系列模型的基础。它摒弃了传统的循环神经网络(RNN)和卷积神经网络(CNN),完全依赖自…

FFmpeg(7.1版本)在Ubuntu18.04上的编译

一、从官网上下载FFmpeg源码 官网地址:Download FFmpeg 点击Download Source Code 下载源码到本地电脑上 二、解压包 tar -xvf ffmpeg-7.1.tar.xz 三、配置configure 1.准备工作 安装编译支持的软件 ① sudo apt-get install nasm //常用的汇编器,用于编译某些需要汇编…

何谓共赢?

A和B是人或组织&#xff0c;他们怎样的合作才是共赢呢&#xff1f; 形态1:A提供自己的身份证等个人信息&#xff0c;B用来作贷款等一些事务&#xff0c;A每月得到一笔钱。 A的风险远大于收益&#xff0c;或者B从事的是非法行为&#xff1b; 形态2:A单方面提前终止了与B的合作…

项目练习:重写若依后端报错cannot be cast to com.xxx.model.LoginUser

文章目录 一、情景说明二、解决办法 一、情景说明 在重写若依后端服务的过程中 使用了Redis存放LoginUser对象数据 那么&#xff0c;有存就有取 在取值的时候&#xff0c;报错 二、解决办法 方法1、在TokenService中修改如下 getLoginUser 方法中&#xff1a;LoginUser u…

2 MapReduce

2 MapReduce 1. MapReduce 介绍1.1 MapReduce 设计构思 2. MapReduce 编程规范3. Mapper以及Reducer抽象类介绍1.Mapper抽象类的基本介绍2.Reducer抽象类基本介绍 4. WordCount示例编写5. MapReduce程序运行模式6. MapReduce的运行机制详解6.1 MapTask 工作机制6.2 ReduceTask …

ASP.NET Core与配置系统的集成

目录 配置系统 默认添加的配置提供者 加载命令行中的配置。 运行环境 读取方法 User Secrets 注意事项 Zack.AnyDBConfigProvider 案例 配置系统 默认添加的配置提供者 加载现有的IConfiguration。加载项目根目录下的appsettings.json。加载项目根目录下的appsettin…

Clion开发STM32时使用stlink下载程序与Debug调试

一、下载程序 先创建一个文件夹&#xff1a; 命名&#xff1a;stlink.cfg 写入以下代码: # choose st-link/j-link/dap-link etc. #adapter driver cmsis-dap #transport select swdsource [find interface/stlink.cfg]transport select hla_swdsource [find target/stm32f4x.…

低成本、高附加值,具有较强的可扩展性和流通便利性的行业

目录 虚拟资源类 1. 网课教程 2. 设计素材 3. 软件工具 服务类 1. 写作服务 2. 咨询顾问 3. 在线教育 4. 社交媒体管理 虚拟资源类 1. 网课教程 特点&#xff1a;高附加值&#xff0c;可复制性强&#xff0c;市场需求大。 执行流程&#xff1a; 选择领域&#xff1a…

54. 螺旋矩阵

【题目】&#xff1a;54. 螺旋矩阵 class Solution { public:vector<int> spiralOrder(vector<vector<int>>& matrix) {int startx 0, starty 0; // 起始坐标vector<int> res;int m matrix.size(), n matrix[0].size();int count min(m, n) …

elasticsearch8.15 高可用集群搭建(含认证Kibana)

文章目录 1.资源配置2.系统参数优化3.JDK17安装4.下载&安装ES 8.155.生成ES的证书(用于ES节点之间进行安全数据传输)6.修改ES 相关配置文件7.创建es用户并启动8.配置ES的账号和密码(用于ES服务端和客户端)9.下载和安装Kibana10.编辑Kibana配置文件11.启动Kiabana12.访问Kia…