JavaScript进阶
HTMLCollection与NodeList区别
① HTMLCollection 对象
- 能够返回HTMLCollection 对象的属性和方法: getElementsByTagName()、getElementsByClassName()、children
- HTMLCollection 对象的成员只能是元素类型对象 。
- 没有 forEach 方法。
- 是动态的集合,如果文档中新增了满足条件的元素,集合会自动更新。
② NodeList
- 能够返回 NodeList 对象的属性和方法: querySelectorAll()、getElementsByName()、childNodes
- NodeList 对象的成员可以是节点类型的对象(包括元素类型、document 等)。
- 具有 forEach 方法。
- 静态的集合。
执行上下文
全局执行上下文
1.在JS代码执行前,就会创建一个window对象,这个window就是全局执行上下文对象。
2.预处理
- 变量处理:找到所有使用
var声明的全局变量,给全局执行上下文对象添加属性,但不赋值(或者说值为undefined)。 - 函数处理:找到使用
function关键字声明的全局函数,给全局执行上下文对象添加属性,值是函数。 - this赋值:将window赋值给this
3.正式执行全局代码。
4.页面关闭,全局执行上下文对象销毁。
💡生命周期:
创建:页面打开时创建
销毁:页面关闭时销毁
💡特殊说明:
所有全局变量都是window对象的属性。
预处理阶段会执行变量提升和函数提升。
函数内执行上下文
1.调用函数的时候,函数内代码执行之前,会创建该函数的执行上下文对象。
2.预处理:
① 将形参作为函数内执行上下文对象的属性,并赋值。
② 给函数内执行上下文对象添加属性arguments,并赋值。
③ 找到函数内使用 var 的变量声明语句,给函数内执行上下文对象添加属性,不赋值。
④ 找到函数内使用 function 关键字的函数声明语句,给函数内执行上下文对象添加属性,值是函数。
④ 给 this 进行赋值,将调用该函数的对象赋值给 this。
3.正式执行函数内的语句。
4.函数调用结束,函数内执行上下文对象被销毁。
💡生命周期:
创建:函数调用时创建。
销毁:函数执行结束后销毁。
💡重要区别:
与全局执行上下文不同,函数执行上下文对象不可见。
每次调用都会创建独立的新的执行上下文对象。
执行栈
栈结构: 是一种数据存储结构,特点先进后出,后进先出。
执行栈: 执行上下文对象创建之后,要放入执行栈,放入执行栈才能执行。
立即执行函数
1 | (function(){})(); |
核心特征:定义函数的同时立即执行,且函数内部的变量不会泄漏到全局作用域。
立即执行函数的使用:前端开发中常用立即执行函数(IIFE)来创建独立作用域,避免多个特效间的变量命名冲突。即使当前页面只有一个案例,也应养成这种良好的编码习惯。
for-in与for-of循环
for-in:为遍历对象的属性而生,遍历的是键名 / 索引(字符串类型),会遍历对象原型链上的可枚举属性。
for-of:为遍历可迭代对象的元素而生,遍历的是值(原始值 / 元素本身),只遍历对象自身的可迭代内容,不涉及原型链。
| 特性 | for-in 循环 | for-of 循环 |
|---|---|---|
| 遍历目标 | 所有可枚举对象(对象 / 数组 / 字符串) | 所有可迭代对象(数组 / 字符串 / Map/Set/NodeList 等) |
| 遍历内容 | 键名 / 索引(字符串类型) | 元素值(原始值 / 元素本身) |
| 原型链遍历 | ✅ 会遍历原型链上的可枚举属性 | ❌ 不遍历原型链,仅遍历自身内容 |
| 适用场景 | 遍历普通对象的属性(key) | 遍历数组 / 字符串 / 集合的元素(value) |
| 能否用 const 声明 | ✅ 可以(每次迭代创建新常量) | ✅ 可以(每次迭代创建新常量) |
| 数组遍历的坑 | 索引是字符串、会遍历非数字属性 | 无坑,直接遍历值 |
豆包讲例子: https://www.doubao.com/thread/w0ba8d42c55fa3936
BOM
window对象
BOM的核心是window对象,表示浏览器的实例。
◆弹框:
1 | // 警告框 没有返回值 |
◆打开关闭窗口:
1 | open('网页地址', '', 'width=400,height=300') // 打开新窗口指定尺寸 |
◆页面滚动
1 | // 设置两个参数作为坐标 |
◆定时器
1.延时函数
2.间歇函数
1️⃣延时函数
1 | setTimeout(回调函数, 延迟时间) |
- 延时函数需要等待,所以后面的代码先执行。
- 返回值是一个正整数,表示定时器的编号。
2️⃣间歇函数
1 | setInterval(回调函数, 时间间隔) // 时间间隔单位是毫秒,默认值是0,返回定时器标记 |
location对象
location提供了当前窗口中加载文档的信息,以及通常的导航功能。这个对象独特的地方在于,它既是window的属性,也是document的属性。也就是说,window.location和document.location指向同一个对象。
- location是一个对象,包含了许多属性,这些属性提供了关于当前URL的详细信息。
- 通过location的属性,我们可以获取到URL的各个组成部分,如协议、主机名、端口号、路径等。
1 | console.log("完整的URL:"+location.href); |
URL的组成部分:
- URL由多个组成部分构成,包括协议、主机名、端口号、路径、锚点和参数等。
- 协议部分通常为http或https,用于指定访问文件时所需的协议。
- 主机名是IP地址或域名,用于确定网页所在的计算机在互联网上的地址。
(通过ping命令可将域名(如ping github.com)解析为对应的IP地址。)- 端口号用于区分不同的网络服务,确保消息发送到正确的程序。
- 路径信息指明了网页在服务器上的具体位置。
- 锚点信息用于在页面内定位到特定位置。
- 参数信息用于传递查询参数,常以问号开头,后面跟着键值对。
reload() 刷新页面。
assign() 页面跳转,设置一个地址作为参数,留下历史记录。
repalce() 页面跳转,设置一个地址作为参数,不会留下历史记录。(浏览器行为:使用replace跳转后无法通过后退按钮返回)
history对象
history对象表示当前窗口首次使用以来用户的导航历史记录。因为history是window的属性,所以每个window都有自己的history对象。出于安全考虑,这个对象不会暴露用户访问过的URL。
1 | // 后退一页(等价于浏览器后退按钮) |
navigator对象
navigetor现在已经成为客户端标识浏览器的标准。只要浏览器启用JavaScript, navigator对象就一定存在。但是与其他BOM对象一样,每个浏览器都支持自己的属性。
1 | console.log('操作系统平台:', navigator.platform); // 例:Win32(Windows)、MacIntel(Mac) |
screen对象
window.screen(通常简写为 screen)是浏览器提供的屏幕硬件信息对象,所有属性都是只读的,无法修改。数据来自设备的屏幕硬件,而非浏览器窗口(比如浏览器窗口缩小,screen 的宽高也不会变)。
DOM
文档对象模型(DOM)将web页面与脚本或编程语言连接起来,通常指JavaScript。DOM模型用逻辑树表示文档,树的每个分支终点都是节点(node),每个节点包含对象(objects)。通过DOM方法可以操作树结构,改变文档的结构、样式或内容,节点可关联事件处理器。
任何HTML或XML文档都可以用DOM表示为一个由节点构成的层级结构。
例如:
图源:March
节点
DOM 是文档对象模型,本质是把 HTML/XML 文档转换成 JS 能操作的节点树,而 Node 就是这棵树的最小通用单元—— 所有节点(元素、文本、注释、文档等)都基于 Node 构建,因此共享 Node 的核心能力。
1 | ===== 文档节点(document) ===== |
- nodeName:节点的「名称标识」,文档节点是
#document,元素节点是大写标签名。 - nodeValue:节点的「内容值」,仅文本 / 注释节点有实际值,文档 / 元素节点为
null。 - nodeType:节点的「数字编码」,文档节点是 9,元素节点是 1,文本节点是 3(编程判断节点类型的首选)。
作用:这三个属性是识别 DOM 节点类型的通用工具,所有节点都继承自 Node 接口,因此取值规则统一、跨浏览器兼容。
获取元素
- 通过ID名
- 通过标签名。返回的是一个HTMLCollection对象,是一个伪数组,里面的成员是元素对象。
- 通过类名
- 通过name属性值(document独有)。返回的是一个NodeList对象,是一个伪数组,里面的成员是元素对象。
- 使用CSS选择器获取元素。
querySelector()返回符合选择器条件的第一个元素,没有符合条件的元素返回 null;querySelectorAll()返回所有符合选择器条件的元素组成的集合,是 NodeList 对象(有forEach),是伪数组。
1 | document.getElementById('ID名') |
伪数组(类数组 ) 是长得像数组,但不是数组的对象 —— 它有数组的外在特征(
length属性、能通过数字下标[0]/[1]访问成员),但没有数组的核心能力(forEach/map/filter等原型方法),本质是一个普通对象。
元素的属性操作
◆读写内置属性的值:先获取元素对象,然后通过.点号访问或修改属性。
◆读写设置在标签代码上的属性:
1 | 元素对象.getAttribute('属性名'); // 读取设置在标签代码上的属性(不区分内置属性和自定义属性) |
对于布尔属性如checked,使用getAttribute()返回的是字符串null而非布尔值true/false。
◆data-* 形式的自定义属性:
1 | <img data-loadpic="" data-home-address=""> |
1 | const imgElement = document.querySelector('img'); |
元素的样式操作
◆读写行内样式:通过元素对象的style属性访问。
1 | // 只能读取设置在行内的样式 |
◆读取计算样式:计算样式是指最终作用在元素上的样式,即使没有设置也有默认样式。只能读取不能设置。(读取复合属性如background会包含所有子属性的默认值)
1 | // 返回由计算样式组成的对象 |
操作元素的类名
◆className
1 | //获取元素 |
◆classList
对象获取:通过元素对象.classList可获取管理类名的对象。
核心方法:
- add():添加指定类名(不影响原有类名)
- remove():移除指定类名(不影响其他类名)
- contains():检查是否包含特定类名(返回布尔值)
- toggle():切换类名的存在状态(存在则删除,不存在则添加)
优势对比:相比直接操作className属性,classList方法不会覆盖原有类名,操作更精准。
读写元素的文本内容
元素对象.innerHTML 读写内部的html代码和文本内容
元素对象.outerHTML 读写包括元素自身在内的html代码和文本内容
元素对象.innerText 读写内部的文本内容,会剔除掉标签
元素对象.textContent 读写内部的文本内容,会剔除掉标签,读取的值保留空格
读取元素的尺寸
元素对象.offfsetWidth / offfsetHeight 获取元素的总宽度:内容宽度+内边距+边框
元素对象.clientWidth / clientHeight 获取元素的可视宽度:内容宽度+内边距
元素对象.scrollWidth / scrollHeight 获取元素的实际宽高,client加上溢出的部分
元素对象.getBoundingClientRect() 返回对象,对象包含元素的位置和尺寸信息,对象有如下属性:
元素对象.getBoundingClientRect().width 同offsetWidth
元素对象.getBoundingClientRect().height 同offsetHeihgt
获取视口的尺寸:
1 | // 会包括滚动条本身的宽度 约17px |
读取元素的位置
元素对象.offsetLeft / offsetTop 获取元素在第一个定位的祖先元素上的位置(祖先元素没有定位的,参照页面)
元素对象.clientLeft / clientTop 获取元素的左边框宽度、上边框宽度
元素对象.getBoundingClientRect() 返回对象,对象包含元素的位置和尺寸信心,对象有如下属性:
left 读取元素在视口上到位置x坐标
top 读取元素在视口上到位置y坐标
x 同 left
y 共 top
right 元素右边的x坐标
bottom 元素底部的y坐标
读写元素中内容滚动的位置
scrollLeft 内容在元素中向左滚动的距离
scrollTop 内容在元素中向上滚动的距离
元素节点的添加/删除/替换/克隆
创建元素节点:
1 | document.createElement('标签名'); |
添加子节点:
1 | 父元素.appendChild(新元素); |
删除子节点:
1 | 父元素.removeChild(要删除元素); |
替换子节点:
1 | 父元素.replaceChild(新元素, 旧元素); |
克隆节点:
1 | 元素.cloneNode(true) 返回克隆后的元素 参数设置为true表示元素和里面的内容一起克隆 |
表单相关元素
◆form元素
1 | length 获取该表单中表单控件的数量 |
表格相关元素
事件
JavaScript与HTML的交互是通过事件实现的。
事件意味着用户或浏览器执行的某种动作。比如,单击(click)、加载(load)、鼠标悬停(mouseover)。为响应事件而调用的函数被称为事件处理程序(或事件监听器)。
事件监听
(1)给元素监听事件
◆事件作为HTML标签的属性
语法格式:
1 | <标签名 on事件名="代码..."></标签名> |
特点:
- 相同事件设置多次时,只有前面的生效。
- 代码与HTML混写,不利于维护。
💡注意:行代码需要用分号分隔。不建议在实际开发中使用这种方式。
◆事件作为元素对象的方法
语法格式:
1 | 元素对象.on事件名 = 回调函数 |
特点:
- 相同事件设置多次时,后面的会覆盖前面的。
- 代码与HTML分离,便于维护。适合简单项目和独立开发,被广泛开发者接受,语法简洁。
◆使用addEventListener方法
语法格式:
1 | 元素对象.addEventListener('事件名', 回调函数); |
特点:
- 相同事件可以设置多次,都会执行。
- 事件名不带”on”前缀。
优势: - 适合多人协作的大型项目
- 不会出现事件覆盖问题
回调函数可以是匿名函数或有名函数;是W3C标准推荐的方式。
(2)解除事件的监听
第一种和第二种方式监听的事件:
1 | 元素对象.on事件名 = null; |
第三种方式监听的事件:
1 | 元素对象.removeEventListener('事件名', 函数名); |
事件流
事件流描述了页面接收事件的顺序。
事件传播分为捕获阶段(从window到目标元素)和冒泡阶段(从目标元素到window)。默认情况下事件在冒泡阶段触发。子元素事件先于父元素事件触发是因为事件首先到达目标元素(子元素)
注意: addEventListener设置第三个参数为 true,该事件会在捕获阶段触发。
DOM2 Events规范规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。
◆捕获阶段
1.路径:从window/document到目标元素。
2.目的:确定事件发生的精确位置。
◆目标阶段
1.标志:找到不可再分的目标元素。
2.作用:连接捕获和冒泡的转折点。
◆冒泡阶段
1.路径:从目标元素回到window / document。
2.特点:默认在此阶段执行事件回调函数。
addEventListener()的第三个参数:
- 默认false(冒泡阶段触发)。
- 设为true时在捕获阶段触发。
其他绑定方式只能在冒泡阶段触发。
事件回调函数中的this
在事件回调函数中,this永远指向监听事件的DOM元素,这个规则不会改变。虽然回调函数内部不知道被谁调用,但W3C规范明确规定this指向事件监听元素。
注意,使用for循环事件监听时要注意i的作用域问题,可以使用this代替通过索引访问元素。for循环中全局变量i在回调执行时已变为终值,而forEach通过函数作用域隔离每次迭代的局部变量。
1 | //foreach循环监听事件 |
这里点击的时候for循环已经结束了。
事件委托
“过多事件处理程序”的解决方案是使用事件委托。
例如:
1 | <ul id="myLinks"> |
JavaScript 中事件会遵循事件冒泡机制(从触发元素向上传播到父 / 祖先元素),因此可以不用给每个子元素单独绑定点击事件,只给父元素 list 绑定一次点击事件,通过 event.target 找到实际点击的子元素,通过判断点击目标的 id 执行不同逻辑。
1 | let list = document.getElementById("myLinks"); |
「事件委托写法」和「给每个子元素单独绑事件写法」的性能 / 内存差异—— 两者功能效果对用户无感知,但事件委托在DOM 访问次数、事件处理程序数量、内存占用上更优,且避免了先期延迟(页面初始化时的性能开销,会让页面交互响应稍慢)。
先期延迟:在原来的页面初始化时,JS 要完成 3 次 DOM 查询 + 3 次事件绑定,这些操作会占用初始化时间,若子元素数量多(如 100 个),延迟会更明显。
浏览器会为每个事件监听器分配内存(存储处理函数、绑定关系等)。使用事件委托时,无论子元素有多少,始终只有 1 个事件监听器(绑定在父元素上),内存占用固定且极少。
事件对象的原型链关系
以鼠标事件对象为例:
1 | 鼠标事件对象 -> MouseEvent.prototype -> UIEvent.prototype -> Event.prototype -> Object.prototype |
- 最底层是具体的鼠标事件对象(如
click事件的event参数)。 - 往上是「专属原型」:
MouseEvent.prototype包含鼠标事件特有的属性(如clientX/clientY)。 - 再往上是「通用原型」:
UIEvent.prototype包含所有 UI 事件(鼠标、键盘)的通用属性,Event.prototype包含所有事件的通用方法(如preventDefault)。
比如,你能从click事件的event对象中拿到clientX,是因为继承了MouseEvent.prototype的属性;能调用event.preventDefault(),是继承了Event.prototype的方法。
常用事件总结
鼠标事件
click 单击
dblclick 双击
contextmenu 右击,上下文菜单事件
mousedown 鼠标按键按下,无论左右键
mouseup 鼠标按键抬起
mousemove 鼠标在元素上移动
◆事件对象
1.获取方式:事件处理函数的第一个参数自动接收事件对象,如function(event)
2.button属性:
- 0表示左键。
- 1表示滚轮键。
- 2表示右键。
3.特性说明:
事件对象由浏览器自动传入,包含事件触发时的各种状态信息。
参数名可自定义(如event/even),但建议保持一致性。
适用于onmousedown和onmouseup事件。
键盘事件
文档事件
表单事件
图片事件
过渡事件
动画事件
垃圾回收机制
内存溢出: 需要使用内存的时候,内存空间不够。
内存泄漏: 垃圾没有回收称为内存泄漏。
JavaScript 垃圾回收的常见算法:
- 引用计数
- 标记清除
闭包
1)简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。
2)MDN 上面这么说:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
前置:要先要看懂语法
1 | // 1. 定义一个函数,赋值给变量a |
1 | function A() { |
◆闭包产生的条件
- 函数嵌套:函数 A 内部定义函数 B(内层函数)。
- 作用域访问:函数 B 访问函数 A 的局部变量 / 参数(上层作用域数据)。
- 外层访问内层:函数 B 能被函数 A 外部调用。
- 方式 1:A 返回 B(最常用);
- 方式 2:B 赋值给全局变量(如
window.func = B); - 方式 3:B 作为事件回调 / 定时器回调(如
document.onclick = B)。
◆核心原理
函数A执行结束后,其内部变量不会被垃圾回收;因为函数B仍然引用着这些变量,形成闭包;通过调试工具可以观察到闭包中保存的变量值。
◆闭包和作用域
函数在执行时,会按“自己作用域 → 外层函数作用域 → 全局作用域”的顺序查找变量,这个 “查找路径” 就是作用域链。
作用域只与函数声明的位置有关,与调用位置无关。
闭包延长了数据的生命周期。
闭包缺点:闭包会让数据常驻内存,增加了内存溢出的风险。
JS中的子类父类(原型链)
1 | var arr = []; |
原型链:arr -> Array.prototype -> Object.prototype
构造函数:Array -> Object
可以说:Array 是子类,Object 是父类。
- 对象a的原型是对象b, 对象a的构造函数是子类,对象b的构造函数是父类。
子类的实例以父类的实例为原型。 - 一个对象只能有一个原型,原型可以作为多个对象的原型。
一个父类可以有多个子类, 一个子类只能有一个父类。
题目思考
实现JS中构造函数和构造函数之间的继承:
1 | function A(){} |
💡思考过程:
实例: B的实例 -> B.prototype ->A.prototype
构造函数:B A
可以说:B作子类,A作父类
所以,B.prototype = new A()。
单线程和事件轮询机制
线程与进程
进程:程序的一次执行,,它占有一片独有的内存空间。
线程:CPU的基本调度单位, 是程序执行的一个完整流程。
- 一个进程中至少有一个运行的线程(主线程)。
- 一个进程可以同时运行多个线程(多线程运行)。
- 同一进程内的多个线程可以直接共享数据。
- 不同进程之间的数据不能直接共享。
内存分配:操作系统以进程为单位分配内存资源,同一进程内的线程共享该内存空间。
多线程运行:一个进程可以包含多个线程(如浏览器进程中包含渲染线程、JS执行线程等)。
数据隔离:不同进程间的数据不能直接共享,需要通过进程间通信(IPC)机制传输。
JavaScript是单线程运行! 所有任务在同一个线程中顺序执行。为什么这样设计?避免多线程同时修改DOM元素导致的冲突(如两个线程同时修改同一元素的宽度);资源开销,多线程需要额外的线程调度和管理开销。
虽然js是单线程,但是浏览器是多进程的。
◆同步任务与异步任务
当同步任务(如console.log)与异步任务(定时器回调)同时存在时,即使异步任务定时器延迟时间为0,也会先执行同步任务。
异步任务本质:定时器的回调函数才是真正的异步任务,创建定时器本身是同步操作。
异步任务需要满足两个条件才会执行:
- 自身条件满足(如定时器时间到);
- 主线程空闲(同步任务执行完毕)。
【定时器回调函数、DOM事件回调函数、Ajax请求的回调、Promise相关操作】
◆线程模型
1.主线程:唯一执行JS代码的线程
2.辅助线程:负责监控异步条件(如定时、事件触发),但不执行代码
3.执行流程:辅助线程发现条件满足后,将回调函数放入任务队列,等待主线程空闲时执行
事件轮询(Event Loop)机制
JavaScript通过事件轮询实现异步执行机制,同步代码直接执行,异步代码放入队列等待主线程空闲时执行。
- 执行栈(调用栈)
主线程里就是一个执行栈,所有代码都要放入执行栈才能执行。 - 异步任务管理模块
判断异步任务是否满足了执行条件。分为定时器管理模块、DOM事件管理模块、Ajax管理模块…
如果满足了异步任务管理模块,会将异步任务放入回调队列,等待执行 - 回调队列
队列是一种数据存储结构,特点是先进先出,后进后出。
回调队列存放等待执行的异步任务。 - 事件轮询模块
时刻监听主线程(执行栈)是否空闲,一旦空闲,从回调队列中取出异步任务,放入主线程执行。
1 | let v1=0,v2=0,v3=0; |
豆包分析: https://www.doubao.com/thread/w197adf33ba41e15d
JS实现多线程
通过Worker构造函数创建子线程,子线程代码需单独写在.js文件中,子线程不能操作DOM元素。
1 | //创建子线程 |
通信机制:
主线程使用worker.postMessage()发送数据
子线程通过onmessage事件接收数据
子线程使用postMessage()返回结果
主线程通过worker.onmessage接收结果
Less
开始
Less 是一门 CSS 预处理器语言(CSS Preprocessor),它在原生 CSS 语法基础上,增加了变量、嵌套、混合、运算等编程特性,让 CSS 写起来更简洁、易维护,最终会编译成标准 CSS 供浏览器识别。
注释:/* 这是 CSS 注释,会原样编译到 CSS 中 */// 这是 LESS 注释,不会编译到 CSS 中
变量的使用
test.less
1 | @len: 600px; |
test.css
1 | .box { |



