HTMLCollection与NodeList区别

① HTMLCollection 对象

  1. 能够返回HTMLCollection 对象的属性和方法: getElementsByTagName()、getElementsByClassName()、children
  2. HTMLCollection 对象的成员只能是元素类型对象 。
  3. 没有 forEach 方法。
  4. 是动态的集合,如果文档中新增了满足条件的元素,集合会自动更新。

② NodeList

  1. 能够返回 NodeList 对象的属性和方法: querySelectorAll()、getElementsByName()、childNodes
  2. NodeList 对象的成员可以是节点类型的对象(包括元素类型、document 等)。
  3. 具有 forEach 方法。
  4. 静态的集合。

执行上下文

全局执行上下文

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
2
3
4
5
6
7
8
// 警告框  没有返回值
alert()

// 确认框 返回布尔值
confirm()

// 输入框 返回用户输入的内容(string)
prompt()

◆打开关闭窗口:

1
2
open('网页地址', '', 'width=400,height=300') // 打开新窗口指定尺寸
close() //关闭本窗口,要求该窗口必须是 open 打开的

◆页面滚动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 设置两个参数作为坐标
scrollTo(0, 0);

// 设置一个参数,该参数是对象
scrollTo({
left: 0,
top: 0,
behavior: 'smooth'
});

// 设置两个参数作为滚动的距离
scrollBy(100, 100);

// 设置一个参数,该参数是对象
scrollBy({
top: 600,
behavior: "smooth"
});

◆定时器
1.延时函数
2.间歇函数

1️⃣延时函数

1
2
setTimeout(回调函数, 延迟时间) 
clearTimeout(id)
  1. 延时函数需要等待,所以后面的代码先执行。
  2. 返回值是一个正整数,表示定时器的编号。

2️⃣间歇函数

1
2
setInterval(回调函数, 时间间隔)	// 时间间隔单位是毫秒,默认值是0,返回定时器标记
clearInterval(定时器标记) // 停止定时器,指定定时器标记

location对象

location提供了当前窗口中加载文档的信息,以及通常的导航功能。这个对象独特的地方在于,它既是window的属性,也是document的属性。也就是说,window.locationdocument.location指向同一个对象。

  1. location是一个对象,包含了许多属性,这些属性提供了关于当前URL的详细信息。
  2. 通过location的属性,我们可以获取到URL的各个组成部分,如协议、主机名、端口号、路径等。
1
2
3
4
5
6
7
8
console.log("完整的URL:"+location.href);
console.log("协议:"+location.protocol);
console.log("主机名:"+location.hostname);
console.log("端口号:"+location.port);
console.log("主机名+端口号:"+location.host);
console.log("路径信息:"+location.pathname);
console.log("锚点信息:"+location.hash);
console.log("参数信息:"+location.search);

URL的组成部分:

  1. URL由多个组成部分构成,包括协议、主机名、端口号、路径、锚点和参数等。
  2. 协议部分通常为http或https,用于指定访问文件时所需的协议。
  3. 主机名是IP地址或域名,用于确定网页所在的计算机在互联网上的地址。
    (通过ping命令可将域名(如ping github.com)解析为对应的IP地址。)
  4. 端口号用于区分不同的网络服务,确保消息发送到正确的程序。
  5. 路径信息指明了网页在服务器上的具体位置。
  6. 锚点信息用于在页面内定位到特定位置。
  7. 参数信息用于传递查询参数,常以问号开头,后面跟着键值对。

reload() 刷新页面。
assign() 页面跳转,设置一个地址作为参数,留下历史记录。
repalce() 页面跳转,设置一个地址作为参数,不会留下历史记录。(浏览器行为:使用replace跳转后无法通过后退按钮返回)

history对象

history对象表示当前窗口首次使用以来用户的导航历史记录。因为history是window的属性,所以每个window都有自己的history对象。出于安全考虑,这个对象不会暴露用户访问过的URL。

1
2
3
4
5
6
7
8
9
// 后退一页(等价于浏览器后退按钮) 
history.back();
// 前进一页(等价于浏览器前进按钮)
history.forward();
// 跳转到历史记录中的指定位置(参数是相对当前位置的偏移量)

history.go(-1); // 等价于 back()
history.go(1); // 等价于 forward()
history.go(0); // 刷新当前页面

navigetor现在已经成为客户端标识浏览器的标准。只要浏览器启用JavaScript, navigator对象就一定存在。但是与其他BOM对象一样,每个浏览器都支持自己的属性。

1
2
3
console.log('操作系统平台:', navigator.platform); // 例:Win32(Windows)、MacIntel(Mac) 
console.log('浏览器默认语言:', navigator.language); // 例:zh-CN
console.log('浏览器支持的语言:', navigator.languages); // 例:["zh-CN", "zh", "en-US", "en"]

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
===== 文档节点(document) ===== 
nodeName: #document
nodeValue: null
nodeType: 9
===== 元素节点(div) =====
nodeName: DIV
nodeValue: null
nodeType: 1
===== 文本节点(div内部文字) =====
nodeName: #text
nodeValue: Hello
DOM Node! nodeType: 3
===== 注释节点 =====
nodeName: #comment
nodeValue: 这是一个注释节点
nodeType: 8
  • nodeName:节点的「名称标识」,文档节点是 #document,元素节点是大写标签名。
  • nodeValue:节点的「内容值」,仅文本 / 注释节点有实际值,文档 / 元素节点为 null
  • nodeType:节点的「数字编码」,文档节点是 9,元素节点是 1,文本节点是 3(编程判断节点类型的首选)。

作用:这三个属性是识别 DOM 节点类型的通用工具,所有节点都继承自 Node 接口,因此取值规则统一、跨浏览器兼容。

获取元素

  1. 通过ID名
  2. 通过标签名。返回的是一个HTMLCollection对象,是一个伪数组,里面的成员是元素对象。
  3. 通过类名
  4. 通过name属性值(document独有)。返回的是一个NodeList对象,是一个伪数组,里面的成员是元素对象。
  5. 使用CSS选择器获取元素。querySelector()返回符合选择器条件的第一个元素,没有符合条件的元素返回 null;querySelectorAll()返回所有符合选择器条件的元素组成的集合,是 NodeList 对象(有forEach),是伪数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
document.getElementById('ID名')

// 从文档中获取所有指定标签名的元素
document.getElementsByTagName('标签名');

// 从某个元素的后代中获取所有指定标签名的元素
元素.getElementsByTagName('标签名');

// 从文档中获取所有指定类名的元素
document.getElementsByClassName('标签名');

// 从某个元素的后代中获取所有指定类名的元素
元素.getElementsByClassName('标签名');

// 只有document才有getElementsByName方法
document.getElementsByName('name属性值');

// 从整个文档中获取
document.querySelector('CSS选择器')
document.querySelectorAll('CSS选择器')

// 从指定元素的后代中获取
元素.querySelector('CSS选择器')
元素.querySelectorAll('CSS选择器')

伪数组(类数组 ) 是长得像数组,但不是数组的对象 —— 它有数组的外在特征(length 属性、能通过数字下标 [0]/[1] 访问成员),但没有数组的核心能力(forEach/map/filter 等原型方法),本质是一个普通对象。

元素的属性操作

◆读写内置属性的值:先获取元素对象,然后通过.点号访问或修改属性。

◆读写设置在标签代码上的属性:

1
2
元素对象.getAttribute('属性名');		// 读取设置在标签代码上的属性(不区分内置属性和自定义属性)
元素对象.setAttribute('属性名', '值'); // 将属性值设置在标签的文档结构中,如果不存在属性会添加

对于布尔属性如checked,使用getAttribute()返回的是字符串null而非布尔值true/false。

data-* 形式的自定义属性:

1
<img data-loadpic="" data-home-address="">
1
2
3
4
const imgElement = document.querySelector('img');

imgElement.dataset.loadpic; // 可读可写
imgElement.dataset.homeAddress; // 可读可写 自动转为小驼峰

元素的样式操作

读写行内样式:通过元素对象的style属性访问。

1
2
3
4
5
6
7
8
9
// 只能读取设置在行内的样式
元素对象.style.属性名;
元素对象.style.color;
元素对象.style.backgroundColor;

// 设置样式 如果行内设置过则修改,如果行内没有则添加
元素对象.style.属性名 = 新值;
元素对象.style.color = '#f00';
元素对象.style.backgroundColor = '#099';

读取计算样式:计算样式是指最终作用在元素上的样式,即使没有设置也有默认样式。只能读取不能设置。(读取复合属性如background会包含所有子属性的默认值)

1
2
3
4
5
6
7
8
9
// 返回由计算样式组成的对象
getComputedStyle(元素);

const computedStyle = getComputedStyle(box);
console.log(computedStyle.background);
console.log(computedStyle.backgroundColor);

console.log(getComputedStyle(box).backgroundColor);
console.log(getComputedStyle(box).fontSize);

操作元素的类名

◆className

1
2
3
4
//获取元素
var itemBoxs = document.querySelectorAll('.item');
//切换类名
itemBox.className = 'item active';

◆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
2
3
4
5
6
7
// 会包括滚动条本身的宽度 约17px
window.innerWidth
window.innerHeight

// 不会包括滚动条本身的宽度
document.documentElenment.clientWidth
document.documentElenment.clientHeight

读取元素的位置

元素对象.offsetLeft / offsetTop 获取元素在第一个定位的祖先元素上的位置(祖先元素没有定位的,参照页面)
元素对象.clientLeft / clientTop 获取元素的左边框宽度、上边框宽度

元素对象.getBoundingClientRect() 返回对象,对象包含元素的位置和尺寸信心,对象有如下属性:
left 读取元素在视口上到位置x坐标
top 读取元素在视口上到位置y坐标
x 同 left
y 共 top
right 元素右边的x坐标
bottom 元素底部的y坐标

读写元素中内容滚动的位置

scrollLeft 内容在元素中向左滚动的距离
scrollTop 内容在元素中向上滚动的距离

元素节点的添加/删除/替换/克隆

创建元素节点:

1
document.createElement('标签名');

添加子节点:

1
2
父元素.appendChild(新元素);
父元素.insertBefore(新元素, 旧元素);

删除子节点:

1
父元素.removeChild(要删除元素);

替换子节点:

1
父元素.replaceChild(新元素, 旧元素);

克隆节点:

1
元素.cloneNode(true)  返回克隆后的元素 参数设置为true表示元素和里面的内容一起克隆

表单相关元素

◆form元素

1
2
3
4
5
length		获取该表单中表单控件的数量
elements 获取该表单中表单控件元素的集合

submit() 执行该方法表单会提交
reset() 执行该方法表单会重置

表格相关元素

事件

JavaScript与HTML的交互是通过事件实现的。
事件意味着用户或浏览器执行的某种动作。比如,单击(click)​、加载(load)​、鼠标悬停(mouseover)​。为响应事件而调用的函数被称为事件处理程序(或事件监听器)​。

事件监听

(1)给元素监听事件
事件作为HTML标签的属性
语法格式:

1
2
3
<标签名 on事件名="代码..."></标签名>
//示例:
<button onclick="console.log('您点击了按钮!')">按钮</button>

特点:

  • 相同事件设置多次时,只有前面的生效。
  • 代码与HTML混写,不利于维护。

    💡注意:行代码需要用分号分隔。不建议在实际开发中使用这种方式。

事件作为元素对象的方法
语法格式:

1
2
3
4
5
6
元素对象.on事件名 = 回调函数
//示例:
const btn01 = document.querySelector('#btn01');
btn01.onclink = function(){
console.log('您点击了按钮!');
}

特点:

  • 相同事件设置多次时,后面的会覆盖前面的。
  • 代码与HTML分离,便于维护。适合简单项目和独立开发,被广泛开发者接受,语法简洁。

使用addEventListener方法
语法格式:

1
2
3
4
5
6
元素对象.addEventListener('事件名', 回调函数);
//示例:
const btn01 = document.querySelector('#btn01');
btn01.addEventListener(‘click’, function(){
console.log('您点击了按钮!');
});

特点:

  • 相同事件可以设置多次,都会执行。
  • 事件名不带”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
2
3
4
5
6
7
8
9
10
11
12
13
14
//foreach循环监听事件
var items = document.querySelectorAll('#box li');
items.foreach(function(item){
item.onclick = function(){
item.classList.toggle('active');
}
})
//for循环监听事件
for (var i; i < items.length; i++){
items[i].onclick = function(){
//items[i].classList.toggle(`active`); ❌错误
this.classList.toggle(`active`);
}
}

这里点击的时候for循环已经结束了。

事件委托

“过多事件处理程序”的解决方案是使用事件委托。

例如:

1
2
3
4
<ul id="myLinks"> 
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li> </ul>

JavaScript 中事件会遵循事件冒泡机制(从触发元素向上传播到父 / 祖先元素),因此可以不用给每个子元素单独绑定点击事件,只给父元素 list 绑定一次点击事件,通过 event.target 找到实际点击的子元素,通过判断点击目标的 id 执行不同逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let list = document.getElementById("myLinks");

list.addEventListener("click", (event) => {
//解释:event.target 是用户真正点击的子元素
let target = event.target;

//根据点击元素的id执行不同逻辑
switch(target.id){
case "doSomething":
document.title = "change title";
break; // 终止switch,避免执行后续case
case "goSomewhere":
location.href = "https://www.baidu.com";
break;
case "sayHi":
console.log("Hello World!");
break;
}
})

「事件委托写法」和「给每个子元素单独绑事件写法」的性能 / 内存差异—— 两者功能效果对用户无感知,但事件委托在DOM 访问次数、事件处理程序数量、内存占用上更优,且避免了先期延迟(页面初始化时的性能开销,会让页面交互响应稍慢)。

先期延迟:在原来的页面初始化时,JS 要完成 3 次 DOM 查询 + 3 次事件绑定,这些操作会占用初始化时间,若子元素数量多(如 100 个),延迟会更明显。

浏览器会为每个事件监听器分配内存(存储处理函数、绑定关系等)。使用事件委托时,无论子元素有多少,始终只有 1 个事件监听器(绑定在父元素上),内存占用固定且极少。

事件对象的原型链关系

以鼠标事件对象为例:

1
鼠标事件对象 -> MouseEvent.prototype -> UIEvent.prototype -> Event.prototype -> Object.prototype
  1. 最底层是具体的鼠标事件对象(如 click 事件的 event 参数)。
  2. 往上是「专属原型」:MouseEvent.prototype 包含鼠标事件特有的属性(如 clientX/clientY)。
  3. 再往上是「通用原型」: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
2
3
4
5
6
7
// 1. 定义一个函数,赋值给变量a 
var a = function() {
console.log("我是一个函数");
};

// 2. 变量a现在保存了函数的引用,要执行这个函数,必须加()
a(); // 输出:我是一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
function A() { 
var num = 100;
function B() {
console.log(num);
}
return B; // 返回的是函数B的引用,不是执行B(执行B是B()) 同时返回B才会去使用B
//window.xixi = B;
}

var fn = A();
fn();
//A();
//xiix();

闭包产生的条件

  1. 函数嵌套:函数 A 内部定义函数 B(内层函数)。
  2. 作用域访问:函数 B 访问函数 A 的局部变量 / 参数(上层作用域数据)。
  3. 外层访问内层:函数 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 是父类。

  1. 对象a的原型是对象b, 对象a的构造函数是子类,对象b的构造函数是父类。
    子类的实例以父类的实例为原型。
  2. 一个对象只能有一个原型,原型可以作为多个对象的原型。
    一个父类可以有多个子类, 一个子类只能有一个父类。

题目思考

实现JS中构造函数和构造函数之间的继承:

1
2
3
4
function A(){}
function B(){}

//A作父类,B作子类

💡思考过程:
实例: B的实例 -> B.prototype ->A.prototype
构造函数:B A
可以说:B作子类,A作父类

所以,B.prototype = new A()

单线程和事件轮询机制

线程与进程

进程:程序的一次执行,,它占有一片独有的内存空间。
线程:CPU的基本调度单位, 是程序执行的一个完整流程。

  1. 一个进程中至少有一个运行的线程(主线程)。
  2. 一个进程可以同时运行多个线程(多线程运行)。
  3. 同一进程内的多个线程可以直接共享数据。
  4. 不同进程之间的数据不能直接共享。

内存分配:操作系统以进程为单位分配内存资源,同一进程内的线程共享该内存空间。
多线程运行:一个进程可以包含多个线程(如浏览器进程中包含渲染线程、JS执行线程等)。
数据隔离:不同进程间的数据不能直接共享,需要通过进程间通信(IPC)机制传输。

JavaScript是单线程运行! 所有任务在同一个线程中顺序执行。为什么这样设计?避免多线程同时修改DOM元素导致的冲突(如两个线程同时修改同一元素的宽度);资源开销,多线程需要额外的线程调度和管理开销。
虽然js是单线程,但是浏览器是多进程的。

同步任务与异步任务
当同步任务(如console.log)与异步任务(定时器回调)同时存在时,即使异步任务定时器延迟时间为0,也会先执行同步任务
异步任务本质:定时器的回调函数才是真正的异步任务,创建定时器本身是同步操作。

异步任务需要满足两个条件才会执行:

  1. 自身条件满足(如定时器时间到);
  2. 主线程空闲(同步任务执行完毕)。
    【定时器回调函数、DOM事件回调函数、Ajax请求的回调、Promise相关操作】

线程模型
1.主线程:唯一执行JS代码的线程
2.辅助线程:负责监控异步条件(如定时、事件触发),但不执行代码
3.执行流程:辅助线程发现条件满足后,将回调函数放入任务队列,等待主线程空闲时执行

事件轮询(Event Loop)机制

JavaScript通过事件轮询实现异步执行机制,同步代码直接执行,异步代码放入队列等待主线程空闲时执行

  1. 执行栈(调用栈)
    主线程里就是一个执行栈,所有代码都要放入执行栈才能执行。
  2. 异步任务管理模块
    判断异步任务是否满足了执行条件。分为定时器管理模块、DOM事件管理模块、Ajax管理模块…
    如果满足了异步任务管理模块,会将异步任务放入回调队列,等待执行
  3. 回调队列
    队列是一种数据存储结构,特点是先进先出,后进后出。
    回调队列存放等待执行的异步任务。
  4. 事件轮询模块
    时刻监听主线程(执行栈)是否空闲,一旦空闲,从回调队列中取出异步任务,放入主线程执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
let v1=0,v2=0,v3=0;
//最终v1、v2、v3的值是多少?12、9、6
for(let i=1;i<=3;i++){
var i2=i;//全局的,没在函数里面,i2=3
(function(){
var i3=i;
setTimeout(function(){
v1+=i;
v2+=i2;
v3+=i3;
})
})();
}

豆包分析: https://www.doubao.com/thread/w197adf33ba41e15d

JS实现多线程

通过Worker构造函数创建子线程,子线程代码需单独写在.js文件中,子线程不能操作DOM元素。

1
2
//创建子线程
new Worker(./zixiancheng.js);

通信机制:
主线程使用worker.postMessage()发送数据
子线程通过onmessage事件接收数据
子线程使用postMessage()返回结果
主线程通过worker.onmessage接收结果

Less

开始

Less 是一门 CSS 预处理器语言(CSS Preprocessor),它在原生 CSS 语法基础上,增加了变量、嵌套、混合、运算等编程特性,让 CSS 写起来更简洁、易维护,最终会编译成标准 CSS 供浏览器识别。

注释:
/* 这是 CSS 注释,会原样编译到 CSS 中 */
// 这是 LESS 注释,不会编译到 CSS 中

变量的使用

test.less

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@len: 600px;
@master-red: #900;
@prop: background-position;
@sel01: .item;
//当变量值有特殊符号时,用~“ ”包裹
@min768:~"min-width:768px";
@min992:~"min-width:992px";
@sel02:~".item li";

.box{
//变量做属性值,直接用
width: @len;
color: @master-red;
//变量做属性名,加上{}
@{prop}:10px 10px;
}
//变量做选择器,加上{}
@{sel01}{
width: 100px;
height: 200px;
}
@media(@min768){
.container{
width: 100%;
}
}
@media (@min992){
.container{
width: 970px;
}
}

test.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.box {
width: 600px;
color: #900;
background-position: 10px 10px;
}
.item {
width: 100px;
height: 200px;
}
@media (min-width:768px) {
.container {
width: 100%;
}
}
@media (min-width:992px) {
.container {
width: 970px;
}
}

Less混合

JS八股

资料
万恶的八股文 - 飞书云文档