html 在body末尾追加,探究: 为什么JavaScript要在body标签尾部引入?

某天, 小枫在网上浏览前端性能优化的相关文章, 文章中指出: 将JavaScript的引入置于

尾部

小枫思索了一会, 整个人都自闭了...于是乎, 接着上网查阅资料:

为什么JavaScript的引入要置于

尾部?

查了一圈之后发现, 网上普遍的观点如下:JavaScript执行会阻塞HTML的渲染, 因此将JavaScript的引入置于

尾部, 可以使页面先呈现出来, 避免JavaScript的执行导致页面白屏;

出于钻研的精神, 小枫决定自己来验证这个结论, 请给小枫点个赞谢谢(是字体自己加粗的, 逃0.0)

大家都知道, chrome(文章使用的是版本为: 86.0.4240.80)是个神器, 自带性能调优工具, 可以记录分析网页的渲染流程: 按"F12"或是"鼠标右键+检查"打开控制台, 选择Performance栏, 如下图;

c816a8da1182c18df31500bf8090de0e.png

我们先新建一个简单的HTML文件, 使用Performance分析页面的渲染流程:

钻研精神

你好, 小枫

4cae69e23e95aea51ecdce8e55004a81.png

从上图可以看出, 网页的渲染流程大致如下:Parse HTML 该阶段生成了DOM Tree和CSSOM Tree;

Layout 将DOM Tree结合CSSOM Tree, 生成Layout Tree(又称Render Tree), 计算每个元素的尺寸和位置;

Update Layout Tree 更新Layout Tree;

Paint 生成PaintLayout Tree记录元素绘制顺序;

Composite 合成视图输出到屏幕;

简单理解网页渲染流程之后, 我们来对问题进行验证, 列举以下两种情况:置于

中:钻研精神

for (let i = 0; i < 100000; i++) {

for (let j = 0; j < 100000; j++) {}

}

你好, 小枫

在上面的html文件中, 我们将置于

中, 中是一段计算量很大的JavaScript, 值得注意的是, 这里的JavaScript是以嵌入的形式存在于HTML中, 并非使用src属性进行外部引入; 接着在浏览器打开该html文件, 并使用Performance进行分析:

9eb2a993deafa9a8f4671f6f976eee7d.png

script标签置于head标签中

如上图所示, ParseHTML阶段一直呈阻塞状态, 直到Evalute Script阶段结束才重新开始ParseHTML, 接着进行Layout以及Paint等渲染页面流程; 在这个过程中页面一直呈现白屏状态, 直到JavaScript加载完成才有内容展示; 可以看出, JavaScript的执行阻塞了HTML的解析和渲染;置于

中:钻研精神

你好, 小枫

for (let i = 0; i < 100000; i++) {

for (let j = 0; j < 100000; j++) {}

}

接着, 我们把放在

尾部, 此处的JavaScript同样为一段计算量很大的代码, 并且也以嵌入的形式引入; 接着使用Performance对网页进行分析:

b85f7abb4775c9bcd05cf88fd3eed9b5.png

script标签置于body标签尾部

如上图所示, 由于JavaScript的执行, ParseHTML阶段同样一直呈阻塞状态, 但不一样的是, 当Evalute Script阶段结束时, 直接进行Layout和Paint等渲染网页流程; 这个过程页面同样一直白屏, 直到JavaScript执行完成; 我们可以这么理解: HTML自上往下执行, 因此在执行JavaScript之前ParseHTML流程正常执行: 当script标签放在body标签尾部时, 浏览器会先解析HTML, 之后遇到script标签, 开始执行JavaScript, 这个过程将会阻塞HTML的解析渲染, 所以页面才会一直呈现白屏状态直到JavaScript执行完毕;

从上面两种情况可以看出, 当JavaScript以script标签嵌入的方式执行时, 不管script标签置于head标签中或是置于body标签尾部, 都会因为JavaScript阻塞了HTML解析渲染的原因, 导致页面白屏, 直到JavaScript加载完毕;

所以这可以证明网上搜索到的观点是错误的吗?

"不行!"

因为JavaScript的引入方式并非这一种, 还有另外一种更为常见的形式: 使用script标签的src属性引入外部JavaScript;于是乎, 以script标签引入外部JavaScript的形式, 开启下一轮验证:置于

中:

将计算量很大的代码移至js文件: index.jsfor (let i = 0; i < 100000; i++) {

for (let j = 0; j < 100000; j++) {}

}

并在html中引入 index.js:

钻研精神

你好, 小枫

接着, 使用Performance对网页进行分析:

5fd9d6dbfe4f15a3420ab4a4c5c79a7a.png

如上图, 浏览器先调用Network线程下载HTML文件, 之后对HTML进行自上往下解析工作, 直到遇到

中的script标签, 由于script标签引入的是外部js文件, 浏览器接着调用Network线程下载目标js文件, 可以看到, 这一过程中并没有进行ParseHTML;

fd44839c26b026a8dbccbe8e51efac76.png

下载完js文件之后, 浏览器开始执行JavaScript, 等待代码执行完毕之后, 开始ParseHTML, Layout以及Paint等网页解析渲染操作; 整个流程, 网页一直呈现白屏状态, 直到JavaScript执行完毕, 也就是说, 当外部引入js文件时, script标签置于head标签中时, Network线程下载js文件将会阻塞HTML的解析, 导致页面在JavaScript下载并执行完之前一直呈现白屏状态;置于

中:

将script标签移到body尾部:

钻研精神

你好, 小枫

接着使用Performance进行网页分析:

6333a35378c47009e7c9e89be38fb854.png

如图所示, HTML自上而下解析, 进行ParseHTML, 遇到引入外部js的script标签, Network线程开始下载js文件, 网页的渲染同时进行, 直到js文件下载完成, 开始执行JavaScript; 整个过程中, 网页在JavaScript执行完毕前先呈现出内容, 因为在js文件下载之前, 浏览器已经完成HTML的解析工作, 而在js文件下载过程中, 网页的渲染并没有因此阻塞, 可以得出结论:

"当使用script标签引入外部js文件时, js文件下载并不阻塞网页的渲染, 而是阻塞HTML的解析!"

为了充分验证上述结论, 我们在script标签后加多一个元素:

钻研精神

你好, 小枫

你好, 小枫

观察页面变化并使用Performance分析网页:

1190000037578914

6294b0dfe9cde8a1521dc734bf05003a.png

如上图, 网页先展示出第一行文本, 第二行文本并未出现, 此时script标签仍出于下载外部js文件阶段, 说Network线程下载js文件的过程阻塞第二行文本的解析, 而第一行文本能正常呈现出来, 说明这一过程并没有阻塞网页的渲染;

9de03864dddc003c633237f064b52879.png

26a42011b7c3466fb682c0f3ce14ac78.png

一段时间过后, JavaScript下载并执行完毕, 此时第二行文字也显示出来;

总结

结合上面的验证, 可以得出结论:JavaScript的执行会阻塞HTML的解析渲染;

当使用script标签引入外部js文件时, Network线程会阻塞HTML的解析, 但不会阻塞HTML的渲染;

因此网上大部分的观点是错误的, JavaScript执行确实会阻塞HTML的解析渲染, 若是以嵌入的方式引入JavaScript, 不管script标签是放在head标签中或是body标签尾部, 页面都会由于JavaScript的执行而持续白屏; 而在引入外部js文件的情况, 由于Network线程下载外部js文件仅阻塞HTML的解析而不会阻塞HTML的渲染, script标签置于body标签尾部可以避免由于js文件下载时间太长导致的页面持续白屏!

<think>好的,用户现在想了解如何使用JavaScript向数组添加值。之前他们问过关于前端for循环的使用方法,现在转向数组操作。我需要先回顾之前的对话,确保回答的一致性。根据用户提供的引用,他们提到了push方法和concat方法,但可能还有其他方法需要补充。首先,用户可能知道一些基本方法,但需要全面的指导。我需要列出所有常用的添加元素的方法,并详细说明每个方法的用途和示例。引用中提到的push和concat是基本的,但还有unshift、splice、展开运算符等。需要确保覆盖这些内容,并正确引用用户提供的资料。 接着,用户可能关心这些方法的使用场景和区别。比如,push和unshift的区别在于添加到末尾还是开头,splice可以在任意位置添加,而concat用于合并数组。同时,ES6的展开运算符也是一个重要点,应该包括在内。还要注意用户提到的系统级指令,比如数学公式的格式,但在这个问题中可能不太相关。不过要确保代码示例正确,使用中文回答,并生成相关问题。需要检查引用标记是否正确,比如在提到push和concat时引用用户提供的[^1]和[^2]。 另外,用户可能在实际应用中有不同的需求,比如动态添加元素或合并多个数组,需要举例说明。同时,要注意回答的结构清晰,分点列出,方便用户理解。后,生成的相关问题要围绕数组操作的其他方面,如删除元素、方法比较、性能等,帮助用户深入学习。 需要确保所有方法都有示例代码,并说明输出结果,这样用户可以直接测试。同时,指出每个方法是否会修改原数组,因为这是开发中常关心的问题。比如push、unshift、splice会修改原数组,而concat和展开运算符会返回新数组,原数组不变。后,验证所有信息是否正确,特别是引用部分,确保push和concat的示例与用户提供的引用一致。可能还需要检查是否有其他方法被遗漏,比如使用方括号直接赋值,但这种情况通常用于已知索引的情况,可能不属于添加值的常用方法,所以可能不需要提及。 总结,回答需要涵盖多种添加值的方法,每种方法的说明和示例,引用正确,结构清晰,并生成相关问题引导用户进一步学习。</think>### JavaScript 向数组添加值的 8 种方法详解 #### 一、尾部追加元素 1. **push() 方法** 常用的数组追加方法,可一次性添加多个元素,直接修改原数组: ```javascript const fruits = ['apple']; fruits.push('banana', 'orange'); console.log(fruits); // 输出: ["apple", "banana", "orange"][^1] ``` 2. **索引直接赋值** 适用于已知数组长度的情况: ```javascript const nums = [1, 2]; nums[nums.length] = 3; console.log(nums); // 输出: [1, 2, 3] ``` #### 二、头部插入元素 3. **unshift() 方法** 在数组开头插入元素,时间复杂度为 $O(n)$: ```javascript const colors = ['blue']; colors.unshift('red'); console.log(colors); // 输出: ["red", "blue"] ``` #### 三、任意位置插入 4. **splice() 方法** 灵活的数组操作方法,可在指定索引位置插入元素: ```javascript const letters = ['a', 'c']; letters.splice(1, 0, 'b'); // 在索引1处插入,0表示不删除元素 console.log(letters); // 输出: ["a", "b", "c"] ``` #### 四、数组合并 5. **concat() 方法** 合并多个数组并返回新数组,原数组不受影响: ```javascript const arr1 = [1, 2]; const arr2 = [3]; const merged = arr1.concat(arr2, [4]); console.log(merged); // 输出: [1, 2, 3, 4][^2] ``` 6. **展开运算符(ES6)** 更直观的数组拼接方式: ```javascript const parts = ['head']; const full = [...parts, 'body', 'tail']; console.log(full); // 输出: ["head", "body", "tail"] ``` #### 五、动态构建数组 7. **动态生成数组** 配合循环语句动态添加元素: ```javascript const squares = []; for(let i=1; i<=3; i++) { squares.push(i**2); } console.log(squares); // 输出: [1, 4, 9] ``` 8. **Array.from()(ES6)** 通过类数组对象创建新数组: ```javascript const chars = Array.from('hello'); chars.push('!'); console.log(chars); // 输出: ["h", "e", "l", "l", "o", "!"] ``` #### 六、方法对比表 | 方法 | 修改原数组 | 插入位置 | 时间复杂度 | |-------------|------------|------------|------------| | push() | ✔️ | 尾部 | $O(1)$ | | unshift() | ✔️ | 头部 | $O(n)$ | | splice() | ✔️ | 任意位置 | $O(n)$ | | concat() | ❌ | 尾部合并 | $O(n)$ | | 展开运算符 | ❌ | 任意位置 | $O(n)$ | #### 七、实际应用场景 ```javascript // 动态表单字段添加 const formFields = ['username']; function addField(newField) { return [...formFields, newField]; } console.log(addField('email')); // 输出: ["username", "email"] // 实时数据流处理 const sensorData = []; setInterval(() => { sensorData.push(Math.random().toFixed(2)); }, 1000); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值