什么是Javascript

他是一个专门用来与网页交互的脚本语言,主要包含以下三个部分:

  • ECMAScript:由ESMA-262定义并提供的核心功能
  • 文档对象模型(BOM):提供与网页内容交互的方法与接口
  • 浏览器对象模型(DOM):提供与浏览器文档交互的方法与接口

HTML中的Javascript

要将JS引入HTML中主要使用script标签,改标签有以下八个属性

  • async:立即开始下载script脚本
  • defer:文档解析完毕过后再下载脚本
  • crossorigin:配置相关请求的CORS设置
  • integrity:允许对比接收到的资源与指定加密签名以验证资源完整性,目的是确保CDN不会提供恶意内容
  • src:要执行代码的外部文件路径

废弃或者不常用:charset、language、type

script元素

放在head标签的时候,在HTML文档加载的时候就需要把js文件下载下来(没有用defer),用defer属性,就能让js执行时间放在HTML解析完毕之后:HTML 的 script 标签中 defer 和 async 有什么区别?

如果head中的script标签用src引用了外部js文件,且在body用script标签行内js代码,那么只会执行外部的js文件,而忽略行内代码

如果用src引用了外部js文件的script标签放在body而不是head标签里面,那么加载js文件就会在页面解析之后再完成

动态加载JS

使用JS来创建<script>元素

document.createELement('script')

创建的script元素默认属性为aync,用JS来动态创建是早期懒加载,或者webpack中按需加载(Code Splitting)的原理

语言基础

声明变量:var const let的区别

  • var:
    • 全局作用域,无视块级作用域
    • 变量提升:可以在声明之前被使用,但值为undefined
    • 全局污染:全局作用域的声明会挂载到window对象上
  • let:
    • 块级作用域,只在当前{}中有效
    • 暂时性死区(TDZ):声明之前使用会报错
  • const:
    • 常量:声明后必须初始化,且后续不能修改值/引用地址

Js中的数据类型 Js的 = 有什么区别

可选链以及空值运算符

在红宝书第四版没有,只有单独记录一下

解决短路逻辑&& ||判断嵌套对象属性的痛点

// 旧的安全写法:由于太长,被戏称为“死亡金字塔” 
const street = user && user.address && user.address.street;

可选链的写法:

const street = user?.address?.street;
console.log(street); // undefined

浏览器在读到 ?. 时,会先判断 ? 前面的变量:

  • 如果是 nullundefined立即停止(短路),并返回 undefined
  • 如果是其他值:继续往后读取属性。

安全访问方法与数组

// 旧写法
if (props.onCallback) {
  props.onCallback();
}
 
// 新写法:如果 onCallback 存在则调用,不存在则啥也不干,也不报错
props.onCallback?.();
 
const arr = null;
const firstItem = arr?.[0]; // undefined,而不是报错

解决||的假值陷阱

|| 会把 0""(空字符串)、false、null、undefined 都视为假值,如果我仅仅想回退null与undefined的假值,而不是0,就可以用到空值运算符??

旧写法(Bug 源头):

const userConfig = { duration: 0 }; // 用户明确想设置为 0
 
// 旧逻辑:因为 0 被当成了 false,所以 fallback 到了 300
const time = userConfig.duration || 300; 
 
console.log(time); // 300 😱 (用户设置的 0 被吞了!)

新写法: 现在的写法:严谨的 ??

// 新逻辑:只有当 duration 是 null 或 undefined 时,才使用 300
const time = userConfig.duration ?? 300;
 
console.log(time); // 0 🎉 (正确保留了 0)

变量、作用域与内存

原始值与引用值

JS中的类型分为两种类型

  • 原始值,直接存储在栈内存当中,占据固定的大小空间,直接复制的时候,值得到一个副本
  • 引用值,按照引用地址访问,对象这类的引用值存储在堆内存当中,变量里面存储的是引用地址,复制也只是复制另一个地址,

传递参数ECMAScript 中所有函数的参数都是按值传递的。

当你把对象作为参数传给函数时,传递的是这个对象引用地址的副本

  • 如果你在函数内部修改对象的属性(如 obj.name = 'Google'),外部对象会变(因为指向同一个堆内存)。
  • 如果你在函数内部把参数重新赋值(如 obj = { name: 'New' }),外部对象不会变(因为你只是切断了局部变量 obj 与原对象的连接,指向了新对象)。

执行上下文与作用域

下面的内容解释了,为什么在函数里面访问不了那个变量

执行上下文

  • 它是 JS 中最重要的概念之一。每个上下文都有一个关联的变量对象 (Variable Object),里面存着这个上下文中定义的所有变量和函数。
  • 全局上下文:在浏览器中就是 window 对象。
  • 函数上下文:每当调用一个函数,就创建一个新上下文推入上下文栈 (Context Stack)。函数执行完,栈弹出,上下文销毁。

作用域链

  • 决定了各级上下文中的代码在访问变量和函数时的顺序。
  • 查找规则:从当前上下文找 没找到去上一级找 直到全局上下文。
  • 只能向上,不能向下:内部函数可以访问外部变量,但外部不能访问内部。

块级作用域 (Block Scope) 的再次强调

  • 在 ES6 之前,只有全局和函数作用域(由 var 主导)。
  • ES6 的 letconst 引入了块级作用域(花括号 {} 也是一个地盘)。
  • 实战意义:这极大地减少了临时变量污染全局或函数作用域的风险,比如 for (let i=0; ...) 里的 i 出了循环就销毁了,这在以前用 var 时是做不到的。

垃圾回收 (Garbage Collection):内存的自动管理

标记清理 — 主流策略

  • 原理:垃圾回收程序运行时,会“标记”内存中所有变量。然后,它会去掉那些“在环境中的变量”以及“被环境中的变量引用的变量”的标记。剩下还被标记的,就是没人用的垃圾,准备销毁。

引用计数 (Reference Counting) —— 历史的教训

  • 原理:记录每个值被引用的次数。次数为 0 就回收。
  • 致命缺陷循环引用 (Circular Reference)。如果对象 A 引用 B,B 又引用 A,它们的引用次数永远是 1,永远回收不掉。
  • 实战:早期的 IE 浏览器(IE8 及之前)DOM 对象采用引用计数,这导致了无数内存泄漏的噩梦。虽然现代浏览器解决了这个问题,但在涉及 COM 对象或非常老旧的代码维护时仍需注意。