JavaScript冷门知识

简介: JavaScript冷门知识

看红宝书,重新梳理JavaScript的知识。这部分主要是梳理冷门的知识点(对个人来说是冷门的)

组成

  • ECMAScript(核心):提供核心功能。每门语言的根本,大同小异,会有一些特殊的地方,比如JS有变量提升。
  • DOM(文档对象模型):提供与网页内容交互的方法和接口。主要就是操作DOM元素,包括样式修改、新增节点、删除节点等。
  • BOM(浏览器对象模型):提供与浏览器交互的方法和接口。比如 location对象可以获取或设置窗口的URL等。

script元素

首先,学习过JS的话,都知道 script的使用方式有两种。

  • 直接在script标签内写JS代码
  • 在另一个文件中写JS代码,再引入

那么,如果两种方式一起用会怎样呢?

mytest.js

console.log(111)

index.html

<script src="./js/mytest.js">
  console.log(222)
</script>

image-20220413211327017

也就是说,如果两者一起使用的话,那么只有外部文件的代码会执行,会忽略行内代码。

也算可以看出是更推荐外部文件的做法。(个人想法)

使用外部文件有什么好处呢?

  1. 可维护性。如果使用的是行内代码,且一个html文件中有很多业务逻辑的话,后期维护会很困难,首先找到问题代码都要花点时间。
  2. 缓存。使用外部文件的话,如果两个页面用到同一个文件,该文件只需要下载一次,但是如果是行内代码,则会反复下载
  3. 可复用性。可以把通用的代码抽离成一个文件,实现代码复用,而不需要有大量重复代码。

异步加载

首先,script如果没有 defer async,那么浏览器会立即加载并执行脚本,也就是说,会阻塞后面的文档元素的加载和渲染。

  • defer属性的话,会异步加载js文件,即和加载渲染后续文档元素并行进行。加载完成后并不一定是立即执行,而是要等到所有元素解析完成后(图片是在之后解析完成),在 DOMContentLoaded事件触发之前完成
  • async属性的话,会异步加载js文件。加载完成会立即执行,阻塞后面的文档元素的加载和渲染。所以不一定按顺序执行,谁先加载完成就谁先执行。

异步加载

图片来源:https://www.pianshen.com/article/2104972721/

由图总结:

  • defer async都是异步加载
  • defer:加载完成后,会等到所有元素都解析完成后才执行。使用 defer js代码会按顺序执行
  • async:加载完成后,立即执行。使用 async的js代码不一定会按顺序执行

开始案例测试:下面最好在 network面板中设置 Fast 3G,让效果看得更明显。

<body>
  <p>123</p>
  <img src="https://www.clzczh.top/medias/featureimages/17.png" alt="" style="width: 400px">
  <p>456</p>

  <script>

    window.onload = () => {
      console.log('load')
    }

    document.addEventListener('DOMContentLoaded', () => {
      console.log('DomContentLoaded')
    })
  </script>
</body>

什么都没加(在 head内,js代码分别是弹出111、222、333):

<script src="./js/mytest1.js">
</script>
<script src="./js/mytest2.js">
</script>
<script src="./js/mytest3.js">

js

可以发现,任何的元素都没有加载就开始执行js代码了,也就是说js加载会阻塞元素的加载和渲染

defer属性

<script defer src="./js/mytest1.js">
</script>
<script defer src="./js/mytest2.js">
</script>
<script defer src="./js/mytest3.js">

js

先加载完所有的元素(不包括图片,图片会在 DOMContentLoaded后加载),然后才执行js代码,执行完js代码后触发 DOMContentLoaded事件,然后再加载图片。使用 defer属性的 js代码会按顺序执行

async属性

<script async src="./js/mytest1.js">
</script>
<script async src="./js/mytest2.js">
</script>
<script async src="./js/mytest3.js">

js

有点混乱,因为async是异步加载js,而且加载完就会阻塞并执行。添加 async属性的 js代码不一定按顺序执行(多刷新几次)

所以上面的图中是执行js代码前就执行完 DOMContentLoaded事件了,然后在执行js的代码途中,加载出图片

除了使用 async defer属性来实现异步加载js外,也可以通过动态创建脚本的形式来实现。

window.onload = () => {
  console.log('load')
}

document.addEventListener('DOMContentLoaded', () => {
  console.log('DomContentLoaded')
})

const fragment = document.createDocumentFragment()    // 这里使用一个小优化手段:先创建一个文档碎片,把需要新增的内容先添加到文档碎片上,最后才把文档碎片添加到真实DOM上。这样就能够减少操作DOM。

const myscript1 = document.createElement('script')
myscript1.src = './js/mytest1.js'
fragment.appendChild(myscript1)

const myscript2 = document.createElement('script')
myscript2.src = './js/mytest2.js'
fragment.appendChild(myscript2)

const myscript3 = document.createElement('script')
myscript3.src = './js/mytest3.js'
fragment.appendChild(myscript3)

document.head.appendChild(fragment)

效果就相当于添加了 async属性。

最后再来一下结论:

  • defer async都是异步加载
  • defer:加载完成后,会等到所有元素都解析完成后才执行。使用 defer js代码会按顺序执行
  • async:加载完成后,立即执行。使用 async的js代码不一定会按顺序执行

标签退出循环

说到退出循环的方法,常用的就是 break continue break是退出整个循环,而 continute是跳过当前那一个循环

// 输出0、1
for (let i = 0; i < 5; i++) {
  if (i === 2) {
    break
  }
  console.log(i)
}

// 输出0、1、3、4
for (let i = 0; i < 5; i++) {
  if (i === 2) {
    continue
  }
  console.log(i)
}

那么嵌套循环,需要跳出两层的循环怎么办呢?

很明显,使用 break只能跳出一层,而使用 continute能跳出一层的一轮。

这个时候就到我们的标签退出循环法闪亮登场了。

其实用的也是 break,只是我们使用 break一般后面不带东西而已,也就是说没带标签则默认跳一层循环。所以,我们要跳两层循环的话,首先得在要使用的位置添加上标签,然后跳出循环时使用 break 标签

let num = 0;
label:
for (let i = 0; i < 5; i++) {
  for (let j = 0; j < 5; j++) {
    if (i == 2 && j == 2) {
      break label;
    }
    num++;
  }
}
console.log(num); // 12

结果是12的原因:

  • i=0时,执行5次
  • i=1时,执行5次
  • i=2时,执行2次

with语句

作用:将代码作用域设置为特定的对象

const person = {
  name: 'clz',
  age: 21,
  job: 'frontEnd'
}

with (person) {
  console.log(name)
  console.log(age)
  console.log(job)
}

对象常量Object.freeze

对象是引用类型,所以即使使用 const来定义对象变量,它也是能够改变内容的,只是它的地址没有变化而已。

const o = {
  name: 'clz'
}

o.name = 'czh'
console.log(o)    // {name: 'czh'}

通过 Object.freeze冻结一个对象后,这个对象就不能再被修改,包括添加新属性、删除已有属性、修改属性等。

所以可以通过 Object.freeze来实现对象常量。

const o = {
  name: 'clz'
}

Object.freeze(o)

o.name = 'czh'
console.log(o)    // {name: 'clz'}

函数参数按值传递

这个一直都是这么听别人说的,但是没有证明过,其他语言倒是有看过证明,现在就来证明一波js版本的。

function mytest(o) {
  o.age = 21
}
​
const person = {
  name: 'clz'
}
​
mytest(person)
​
console.log(person)  // {name: 'clz', age: 21}

可以发现,修改函数里的对象o会导致函数外的person也发生变化。

这是什么原因呢?可能的原因有两个:

  1. 当函数参数是对象时,是按引用传递的
  2. 函数参数是按值传递的,但是对象是引用类型。所以o还是会通过引用访问对象,那么函数内部给o添加age属性时,函数外部的对象也会反映这个变化。因为o指向的对象保存在全局作用域的堆内存中。

然后,先来说一下按值传递和按引用传递的概念。

  • 按值传递:值会被复制到函数内的局部变量。也就是说,此时直接给局部变量赋新值是不会修改到函数外的变量的,但是,如果参数是对象,而且不是直接给局部变量赋新值,而是给它添加新属性,或者修改属性值的话,还是影响到函数外的变量的,因为对象是引用类型的
  • 按引用传递:值在内存的位置会被保存到函数内的局部变量中。也就是说,此时直接给局部变量赋新值是会修改到函数外的变量的,因为此时局部变量是地址,也就是说,直接给局部变量赋新值的话,就是将那个地址改变,既然修改的是地址,那么函数外部的变量肯定也会跟着变,因为它指向的地址都被别人改了。

下面对上面的例子进行修改,来证明对象也是按值传递的。

function mytest(o) {
  o.age = 21
​
  o = new Object()
  o.age = 999
}
​
const person = {
  name: 'clz'
}
​
mytest(person)
​
console.log(person)  // {name: 'clz', age: 21}

如果person是按引用传递的,那么当我们将o重新定义为一个新对象时,person也应该会自动将指针改成指向age为999的对象。

但是最后的结果是{name: 'clz', age: 21},也就是说,函数中参数的值改变之后,原始的引用还是没有改变。即函数参数按值传递

参考链接:

\

目录
相关文章
|
8月前
|
SQL 数据可视化 BI
VeryReport和FastReport两款报表软件深度分析对比
VeryReport和FastReport两款报表软件深度分析对比
|
12月前
|
缓存 JavaScript Java
vue2知识点:组件自定义事件
vue2知识点:组件自定义事件
182 3
|
12月前
|
tengine 应用服务中间件 Linux
Tengine、Nginx安装PHP命令教程
要在阿里云Linux上安装PHP,请先更新YUM源并启用PHP 8.0仓库,然后安装PHP及相关扩展。通过`php -v`命令验证安装成功后,需修改Nginx配置文件以支持PHP,并重启服务。最后,创建`phpinfo.php`文件测试安装是否成功。对于CentOS系统,还需安装EPEL源和Remi仓库,其余步骤类似。完成上述操作后,可通过浏览器访问`http://IP地址/phpinfo.php`测试安装结果。
|
10月前
|
Java 数据库连接 Maven
最新版 | SpringBoot3如何自定义starter(面试常考)
在Spring Boot中,starter是一种特殊的依赖,帮助开发人员快速引入和配置特定功能模块。自定义starter可以封装一组特定功能的依赖和配置,简化项目中的功能引入。其主要优点包括模块化、简化配置、提高代码复用性和实现特定功能。常见的应用场景有短信发送模块、AOP日志切面、分布式ID生成等。通过创建autoconfigure和starter两个Maven工程,并编写自动配置类及必要的配置文件,可以实现一个自定义starter。最后在测试项目中验证其有效性。这种方式使开发者能够更便捷地管理和维护代码,提升开发效率。
1143 1
最新版 | SpringBoot3如何自定义starter(面试常考)
|
弹性计算 运维 负载均衡
构建高可用性的分布式系统:技术与策略
【7月更文挑战第1天】构建高可用分布式系统涉及负载均衡、容错处理和数据一致性等关键技术,遵循冗余、模块化及异步设计原则,并通过监控告警、自动化运维和弹性伸缩策略确保稳定性。
|
存储 监控 数据挖掘
飞轮科技携手观测云亮相云栖大会,全方位展示阿里云数据库 SelectDB 版核心优势
飞轮科技技术副总裁姜国强于「数据分析与洞察」专场分享[阿里云数据库 SelectDB 版在日志存储分析、实时报表生成、用户行为分析及 Lakehouse 场景应用方案
300 1
飞轮科技携手观测云亮相云栖大会,全方位展示阿里云数据库 SelectDB 版核心优势
|
Java 数据库连接 API
seata回滚问题之全局异常如何解决
Seata是一款开源的分布式事务解决方案,旨在提供高效且无缝的分布式事务服务;在集成和使用Seata过程中,开发者可能会遇到不同的异常问题,本合集针对Seata常见异常进行系统整理,为开发者提供详细的问题分析和解决方案,助力高效解决分布式事务中的难题。
1863 106
|
弹性计算 数据可视化 Ubuntu
ECS如何安装可视化桌面
【1月更文挑战第10天】ECS如何安装可视化桌面
657 5
|
弹性计算 关系型数据库 MySQL
阿里云服务器申请试用并快速搭建网站教程(图文教程)
阿里云提供云服务器1个月-3个月免费试用,可申请的试用配置有2核4GB 3个月、2核8GB 3个月、4核8GB 1个月、4核16GB 1个月,本文为大家介绍如何申请这些试用云服务器及在云服务器上快速搭建网站教程,以图文形式展示给大家,以供参考。
阿里云服务器申请试用并快速搭建网站教程(图文教程)
|
域名解析 运维 负载均衡
【运维知识进阶篇】Tomcat集群实战之部署zrlog博客(Tomcat服务安装+静态资源挂载NFS+Nginx负载均衡+HTTPS证书+Redis会话保持)
【运维知识进阶篇】Tomcat集群实战之部署zrlog博客(Tomcat服务安装+静态资源挂载NFS+Nginx负载均衡+HTTPS证书+Redis会话保持)
545 1