什么是Javascript
他是一个专门用来与网页交互的脚本语言,主要包含以下三个部分:
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:
- 常量:声明后必须初始化,且后续不能修改值/引用地址
可选链以及空值运算符
在红宝书第四版没有,只有单独记录一下
解决短路逻辑&& ||判断嵌套对象属性的痛点
// 旧的安全写法:由于太长,被戏称为“死亡金字塔”
const street = user && user.address && user.address.street;可选链的写法:
const street = user?.address?.street;
console.log(street); // undefined浏览器在读到 ?. 时,会先判断 ? 前面的变量:
- 如果是
null或undefined:立即停止(短路),并返回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 的
let和const引入了块级作用域(花括号{}也是一个地盘)。 - 实战意义:这极大地减少了临时变量污染全局或函数作用域的风险,比如
for (let i=0; ...)里的i出了循环就销毁了,这在以前用var时是做不到的。
垃圾回收 (Garbage Collection):内存的自动管理
标记清理 — 主流策略:
- 原理:垃圾回收程序运行时,会“标记”内存中所有变量。然后,它会去掉那些“在环境中的变量”以及“被环境中的变量引用的变量”的标记。剩下还被标记的,就是没人用的垃圾,准备销毁。
引用计数 (Reference Counting) —— 历史的教训:
- 原理:记录每个值被引用的次数。次数为 0 就回收。
- 致命缺陷:循环引用 (Circular Reference)。如果对象 A 引用 B,B 又引用 A,它们的引用次数永远是 1,永远回收不掉。
- 实战:早期的 IE 浏览器(IE8 及之前)DOM 对象采用引用计数,这导致了无数内存泄漏的噩梦。虽然现代浏览器解决了这个问题,但在涉及 COM 对象或非常老旧的代码维护时仍需注意。