
【Notes】前端基础知识(HTML/JavaScript/Vue)
前端基础知识
常见浏览器内核
- WebKit:移动端适配及动画渲染,苹果生态深度整合,如 Safari
- Blink:基于 WebKit 分支演进,现代浏览器,支持最新 HTML5/CSS3 标准,如 Chrome,Edge,360 浏览器
- Gecko:自主开源引擎,如 Firefox
- Trident:强化 IE 模拟能力,如 Edge 的兼容模式
大部分国产浏览器基本上都是极速模式和兼容模式,极速模式内核为 Blink,兼容模式是 Trident。
怎样让 HTML 在所有浏览器版本下表现一致?
使用 CSS 重置(CSS Reset)
不同的浏览器有不同的默认样式,例如:不同浏览器的 <body> 标签、表格、按钮等元素的边距、字体、颜色等有所不同。使用 CSS 重置或标准化工具可以帮助消除这些默认样式差异,确保各个浏览器中的元素显示一致。
常用的 CSS 重置方法包括:
- Eric Meyer’s CSS Reset:MeyerWeb CSS Reset
- Normalize.css:Normalize.css:比 CSS 重置更注重浏览器之间的默认样式统一。
在 HTML 文件中引入重置或标准化样式:
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"> | 
使用浏览器兼容性前缀
一些 CSS 属性(如 flexbox、grid、box-shadow 等)在不同浏览器中可能需要加上浏览器厂商前缀,尤其是旧版浏览器。常见的前缀包括 -webkit-、-moz-、-ms- 和 -o-。
自动添加前缀:可以使用 CSS 自动前缀工具,例如:
- Autoprefixer: 自动添加必要的浏览器前缀。
- Sass/SCSS、PostCSS 等工具集成自动添加前缀。
| /* Flexbox */ | 
测试并调整在不同浏览器上的表现
使用跨浏览器测试工具:
- BrowserStack:BrowserStack 提供真实设备和浏览器的在线测试环境。
- CrossBrowserTesting:CrossBrowserTesting 允许你在多个浏览器和操作系统上进行测试。
- Sauce Labs:Sauce Labs 提供跨浏览器自动化测试。
CSS 如何与 HTML 协同工作的?
内联样式 (Inline Style)
行内样式直接在 HTML 元素的 style 属性中定义 CSS 规则。这种方式优先级最高,适用于单个元素的快速样式设置。
- 语法:在元素标签内添加 style 属性,属性值为 CSS 声明(多个声明用分号分隔)。
- 优点:简单快捷,优先级最高(覆盖其他样式)。
- 缺点:不易维护(样式与内容混杂),不适用于多元素复用。
- 适用场景:临时修改或测试单个元素样式。
内部样式 (Internal Style Sheet / Embedded Style)
内部样式在 HTML 文档的 <head> 部分使用 <style> 标签定义 CSS 规则。这种方式适用于单个网页的样式定制。
- 语法:在 <head>中添加<style>标签,内容为 CSS 规则(选择器 + 声明)。
- 优点:集中管理单个页面的样式,易于维护(与内容分离)。
- 缺点:不能跨页面复用,优先级低于行内样式。
- 适用场景:小型网站或单页应用。
| 
 | 
外部样式 (External Style Sheet)
外部样式通过 <link> 标签链接到独立的 CSS 文件(扩展名为 .css)。这种方式最适合大型项目,实现样式复用。推荐使用
- 语法:在 <head>中添加<link>标签,指定href属性为 CSS 文件路径。
- 优点:最佳可维护性(样式与内容完全分离),支持多页面复用,优先级最低(便于全局覆盖)。
- 缺点:需要额外文件,加载速度略慢(需 HTTP 请求)。
- 适用场景:多页网站或团队协作项目。
index.html:
| 
 | 
style.css:
| body { | 
CSS 选择器优先级
- CSS Hack:CSS hack 是通过在 CSS 样式中加入一些特殊的符号,让不同的浏览器识别不同的符号,以达到应用不同的 CSS 样式的目的。比如 hack1{width:300px;_width:200px;},一般浏览器会先给元素使用width:300px;的样式,紧接着后面还有个_width:200px;由于下划线_width只有 IE6 可以识别,所以此样式在 IE6 中实际设置对象的宽度为 200px,而其他浏览器不识别_width不会执行_width:200px;这句样式。
- 内嵌 CSS
- id 选择器
- class 选择器
- 标签选择器
CSS 常用长度单位
CSS 中有两种类型的长度——相对长度和绝对长度。
绝对长度单位
| 单位 | 名称 | 等价换算 | 
|---|---|---|
| cm | 厘米 | 1cm = 37.8px | 
| mm | 毫米 | 1mm = 3.78px | 
| Q | 四分之一毫米 | 1Q = 0.945px = 0.25mm | 
| in | 英寸 | 1in = 96px = 2.54cm | 
| pc | 派卡 | 1pc = 16px ≈ 0.4233cm | 
| pt | 磅 | 1pt = 4/3 px ≈ 0.3527mm | 
| px | 像素 | 1px = 1/96in ≈ 0.2646mm | 
这些单位大多在用于印刷而非屏幕输出时更有用。例如,我们通常不在屏幕上使用 cm(厘米),唯一应该常用的值是 px(像素) ,1px 被定义为 1/96 英寸,也就是说,在标准的 96 DPI(点每英寸)的显示设备上,1px 应该精确对应屏幕上的一个物理像素点。
px 实际渲染中存在着相对性:
- 设备像素密度 (PPI/DPI): 这是关键点。现代设备(尤其是智能手机、平板电脑、高清/视网膜显示器)的像素密度 远高于 96 PPI。在 iPhone 的 Retina 显示屏上,PPI 可能高达 400 多。
- CSS 像素 vs 设备像素: 为了保持网页元素在不同设备上视觉尺寸的相对一致性(避免在高 PPI 屏幕上元素变得极小),浏览器会进行 映射。在高 PPI 屏幕上:- 1 个 px(CSS 像素)不再对应 1 个物理设备像素。
- 1 个 px会被放大显示,占据多个物理设备像素的位置。
 
- 1 个 
- 用户缩放: 当用户在浏览器中放大或缩小时,浏览器会重新计算 px的实际显示大小。放大页面时,1px 会占据更多的屏幕空间(更多的物理像素),使其看起来更大;缩小则反之。
- 操作系统缩放 (Windows/Mac 缩放设置): 如果用户在操作系统层面设置了显示缩放(例如放大到 125%, 150%),浏览器会将 px值按比例放大或缩小,以匹配用户的系统偏好。
相对长度单位
相对长度单位是相对于其他某些东西的。例如:
- em相对于本元素的字体大小,或者在用于- font-size时相对于父元素的字体大小。- rem相对于根元素的字体大小。
- vh和- vw分别相对于视口的高度和宽度。如果你更改浏览器窗口的宽度,那么框的大小应该会更改。示例:https://developer.mozilla.org/zh-CN/docs/Web/CSS/length
使用相对单位的好处是,通过一些精心的规划,你可以使文本或其他元素的大小相对于页面上的任何指定的东西进行缩放。要获取可用的相对单位的完整列表,请参阅 <length> 类型的参考页面。
em 与 rem
em 单位在用于 font-size 时表示“父元素的字体大小”(而在用于其他属性时则表示“自身的字体大小”)。类为 ems 的 <ul> 元素内部的 <li> 元素的尺寸是从它们的父元素继承的。因此,每一层嵌套都会逐渐变大,因为每个元素的字体大小都被设置为 1.3em —— 即其父元素字体大小的 1.3 倍。
rem 单位的意思是“根元素的字体大小”(rem 代表“root em”)。类为 rems 的 <ul> 内部的 <li>,其字体大小取决于根元素(<html>)。这意味着每层嵌套不会让字体越变越大。低于 IE8(含 IE8)不支持。
Flex 布局
Flex 布局,即 Flex Box(弹性布局),是一种较为灵活、强大的页面 CSS 布局方式。在深入学习 Flex 布局前,我们需要了解一些 Flex 相关的概念和术语。
Flex 布局的优点:
- 简便、完整、响应式
- 浏览器支持良好
- 传统盒状模型中,难以实现的垂直等布局等 ,Flex 布局可以完美解决。
注意,设为 Flex 布局以后,子元素的 float、clear 和 vertical-align 属性将失效。
基础概念
- flex 容器(flex container)
- flex 项目(flex item)
- 主轴(main axis)
- 交叉轴(cross axis)
- 占用主轴空间(main size)
- 占用交叉轴空间(cross size)
- 线轴起止点(main start、main end、cross start、cross end)
容器与项目
使用 Flex 布局的元素(display: flex),称之为 Flex 容器(Flex Container),简称 “容器”。Flex 的布局发生在父容器和子容器之间,因此,元素一旦被申明为 Flex 布局后,它的所有子元素自动成为容器成员。 通常,我们将容器内的成员统称为 Flex 项目(Flex item),简称 “项目”。
容器属性:
| 属性 | 取值 | 说明 | 
|---|---|---|
| display | flex、inline-flex | flex:定义一个 Flex 容器,弹性盒子 inline-flex:定义一个内联元素为 Flex 容器 | 
| flex-direction | row (默认)、row-reverse、column、column-reverse | 定义主轴的方向,分别是从左到右、从右到左、从上到下、从下到上  | 
| flex-wrap | nowrap(默认,不换行)、wrap(换行)、wrap-reverse(反向换行) | 默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap 属性定义,如果一条轴线排不下,如何换行。  | 
| flex-flow | <flex-direction><flex-wrap> | flex-direction 与 flex-wrap 的缩写形式。 | 
| justify-content | flex-start(默认,左对齐)、flex-end(右对齐)、center(居中)、space-between、space-around | flex 项目在主轴上的对齐方式。  | 
| align-items | flex-start、flex-end、center、stretch、baseline | 定义项目在交叉轴上对齐方式  | 
| align-content | flex-start、flex-end、center、space-between、space-around、stetch | 定义多条线轴对齐方式,若只有一条线轴。若仅有一条线轴,该属性无效。  | 
主轴与交叉轴
主轴由 flex-direction 定义,交叉轴垂直于主轴,所以如果 flex-direction(主轴)设成了 row 或者 row-reverse 的话,交叉轴的方向就是沿着上下方向延伸的。
Grid 布局
栅格布局(Grid Layout)是一种基于网格系统的布局方式,将页面划分为多个网格单元,通过对网格单元的组合和排列,实现各种复杂的页面布局。栅格布局具有良好的灵活性和可扩展性,可以帮助我们快速地实现响应式设计。
示例:
| 
 | 
与 Flex 布局的区别
| Flex 布局 | Grid 布局 | |
|---|---|---|
| 布局维度 | 一维布局(单行或单列),只处理水平或垂直方向的排列 | 二维布局(同时处理行和列),可以精确定义行和列的结构 | 
| 响应式设计 | 通过 flex-wrap 和 flex-basis 实现简单的换行和自适应宽度 | 通过 repeat()、auto-fit、auto-fill 和 minmax()) 实现更复杂的响应式网络 | 
| 使用场景 | 导航栏、面包屑、按钮组 垂直居中某个元素 等分布局(如等分宽度的卡片) | 网页整体布局(页眉、侧边栏、内容区、页脚) 图片网格、产品展示 需要精确控制行列的复杂排版 | 
JavaScript 简介
JavaScript 和 Java 的区别
| 分类 | JavaScript | Java | 
|---|---|---|
| 起源 | Netscape 公司的产品,其目的是为了扩展 Netscape Navigator 功能而开发的一种可以嵌入 Web 页面中的基于对象和事件驱动的解释性语言,最初的名字并不是 JavaScript,而是 LiveScript,名字中的“Java”是经过 SUN Microsystems 公司授权的。 | SUN Microsystems 公司推出的新一代面向对象的程序设计语言,特别适合于 Internet 应用程序开发。 | 
| 对象模型/范式 | 基于对象,是一种基于对象和事件驱动的编程语言,自身具有已创建完毕的对象。 | 面向对象,对象必须从类中创建。 | 
| 代码嵌入方式 | JavaScript 的代码以字符的形式嵌入在 HTML 文档中,使用 <script></script>来标识 | Java Applets 则是由文档引用,其代码以字节代码的形式保存在另一个独立的文件中,用标识 <applet>来标明 | 
| 编译/执行方式 | 源代码在发往客户端执行之前不需经过编译,而是将文本格式的字符代码发送给客户,即 JavaScript 语句本身随 Web 页面一起下载下来,由浏览器解释执行 | 代码在传递到客户端执行之前,必须经过编译,因而客户端上必须具有相应平台上的仿真器或解释器,它可以通过编译器或解释器实现独立于某个特定的平台编译代码 | 
| 变量声明 | 采用弱类型,即变量在使用前不需作声明,而是解释器在运行时检查其数据类型 | 采用强类型变量检查,即所有变量在编译之前必须作声明 | 
| 联编方式 | 采用动态联编,即 JavaScript 的对象引用在运行时进行检查 | 采用静态联编,即 Java 的对象引用必须在编译时进行,以使编译器能够实现强类型检查 | 
核心
JavaScript 是一种在浏览器中运行的脚本语言,用于增强网页交互性。它由三个核心部分组成:ECMAScript、DOM(文档对象模型)和 BOM(浏览器对象模型)。
ECMAScript
ECMAScript 是 JavaScript 的核心语言规范,定义了 JavaScript 的语法、类型、语句、关键字、保留字、运算符、对象等基本构成部分,规定了 JavaScript 运行的基础环境,使得 JavaScript 可以在各种浏览器和平台(Node.js 等环境)上运行。
ECMAScript 6(ES6)是 JavaScript 语言的新一代标准,在 2015 年 6 月正式发布。2015 年后,ECMAScript 改为每年发布一个新版本,命名规则为 ES + 年份(如 ES2020、ES2021),都是增量更新。
变量定义
| 特性 | var | let | const | 
|---|---|---|---|
| 重新赋值 | ✅ | ✅ | ❌ | 
| 必须初始化 | ❌ | ❌ | ✅ | 
| 作用域 | 函数/全局 | 块级 | 块级 | 
| 变量提升 | 是(值为 undefined) | 是(存在 TDZ) | 是(存在 TDZ) | 
| 重复声明 | ✅ | ❌ | ❌ | 
TDZ 是 Temporal Dead Zone(暂时性死区)的缩写,是 JavaScript 中 let 和 const 变量的一个概念。在 ES6 中,let 和 const 引入了块级作用域(block scope),而它们声明的变量在声明之前的作用域中是不可访问的,这段不可访问的区域就是 暂时性死区。
对象
(1)扩展运算符
拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象。
| let age = {age: 15}; | 
注意:自定义的属性和拓展运算符对象里面属性的相同的时候,自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉; 自定义的属性在拓展运算度前面,则变成设置新对象默认属性值。
(2)复制对象
使用:Object.assign(target, source_1, ···),可以将源对象的所有可枚举属性复制到目标对象中。
| let target = {a: 1}; | 
如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
| Object.assign(3); // Number {3} | 
因为 null 和 undefined 不能转化为对象,所以会报错:
| Object.assign(null); // TypeError: Cannot convert undefined or null to object | 
当参数不止一个时,null 和 undefined 不放第一个,即不为目标对象时,会跳过 null 和 undefined ,不报错
| Object.assign(1,undefined); // Number {1} | 
注意:assign 的属性拷贝是浅拷贝
| let sourceObj = { a: { b: 1}}; | 
(3)解构
解构是 ES6 新加的 解构 功能,可以使得我们获取数据更方便,从而提高日常编码效率。解构可以用于对象,也可以用于数组。
| let node = { | 
(4)箭头函数
| 特性 | 普通函数 | 箭头函数 | 
|---|---|---|
| 声明语法 | function sum(a, b) {return a+b;} | const sum =(a, b)=> a+b; | 
| 简写形式 | 无 | 单参数可省略括号:const func = x => x*2; | 
| 多行体 | function(){…} | ()=>{const x = 1; return x;} | 
| this 绑定 | 动态(调用者) | 静态(继承父级) | 
| arguments 对象 | 有 | 无(需用…args) | 
| 构造函数 | 可使用 new | 不可使用 new | 
| 隐式返回 | 不支持 | 支持(单行表达式) | 
| yield 关键字 | 支持(生成器) | 不支持 | 
| 方法定义 | 可作为对象方法 | 不推荐(this 问题) | 
DOM
DOM(Document Object Model,文档对象模型)是 JavaScript 的一部分,提供了一种程序访问和操作 HTML 和 XML 文档的方式。DOM 将文档结构化为一个对象树,开发者可以通过 JavaScript 动态地访问和更新文档的内容、结构和样式。通过 DOM,开发者可以创建、修改、删除页面元素,改变页面样式,实现页面的动态交互效果。依赖浏览器环境,操作会触发页面重排(reflow)和重绘(repaint)。 核心功能:
- 访问元素:document.getElementByld()、querySelector()
- 修改内容:element.textContent、innerHTML
- 变更样式:element.style.color = ‘red’
- 事件监听:element.addEventListener(‘click’, callback)
| // 获取元素 | 
BOM
BOM(Browser Object Model,浏览器对象模型)也是 JavaScript 的重要组成部分,提供了与浏览器交互的对象和方法。BOM 主要用于处理浏览器窗口和框架,控制导航、窗口大小和位置、弹出窗口等操作。通过 BOM,开发者可以编写 JavaScript 代码来控制浏览器行为,实现更加丰富的交互效果。
没有标准化规范(由 W3C 和 WHATWG 逐步统一),不同浏览器可能存在兼容性问题。核心对象:
- window:全局对象,包含 DOM 和 BOM 的其他对象
- navigator:浏览器信息(如 navigator.userAgent)
- location:URL 控制(如 location.href)
- history:浏览历史(如 history.back())
- screen:屏幕信息(如 screen.width)
- setTimeout、setInterval:定时器
关联
- ECMAScript 是基础:DOM 和 BOM 的 API 基于 ECMAScript 实现。
- DOM 和 BOM 依赖浏览器环境:离开浏览器,ECMAScript 仍可运行(如 Node.js),但 DOM 和 BOM 会报错。
- DOM 和 BOM 通过 window 关联:window 是 BOM 的顶层对象,也是全局变量的宿主。
- 事件处理贯穿三者:ECMAScript 提供函数作为事件回调。DOM 提供事件绑定 API(如 addEventListener)。BOM 提供窗口级事件(如 window.onload)
宏任务与微任务
JavaScript 是单线程语言,这意味着它一次只能执行一个任务。为了处理耗时操作(如网络请求、定时器),JavaScript 引入了 异步机制,允许任务在后台执行,而主线程继续执行后续代码。异步任务分为两类:宏任务(MacroTask) 和 微任务(MicroTask),它们的执行顺序由 JavaScript 的事件循环(Event Loop)机制控制。
宏任务
宏任务是事件循环的基本执行单元,每次循环从宏任务队列中取出一个任务执行。
特点:每个宏任务执行时会形成完整的执行栈;宏任务执行完毕后,会立即执行所有微任务队列中的任务。
微任务
微任务是在当前宏任务执行过程中产生的异步任务,会在当前宏任务结束后、下一个宏任务开始前立即执行。
特点:微任务队列在每个宏任务结束后清空。微任务可以在执行过程中添加新的微任务,形成链式执行常见微任务。
| 特性 | 宏任务 | 微任务 | 
|---|---|---|
| 执行时机 | 每个事件循环周期开始时执行 | 在当前宏任务结束后立即执行 | 
| 队列数量 | 多个队列(如定时器队列、I/O 队列) | 单个队列 | 
| 优先级 | <微任务 | > 下一个宏任务 | 
| 执行栈状态 | 执行前执行栈为空 | 执行前执行栈已清空当前宏任务的同步代码 | 
| 应用场景 | 定时任务、I/O 操作、UI 渲染 | Promise 链式调用、DOM 变化监听 | 
事件绑定方法
EventTarget.addEventListener() 方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Document 本身,或者任何其他支持事件的对象 (比如 XMLHttpRequest)。它允许 为一个事件添加多个监听器。
语法
| addEventListener(type, listener); | 
参数:
- type:表示监听 事件类型 的大小写敏感的字符串。
- listener:当所监听的事件类型触发时,会接收到一个事件通知(实现了- Event接口的对象)对象。- listener必须是一个实现了- EventListener接口的对象,或者是一个 函数。有关回调本身的详细信息,请参阅 事件监听回调。
- options(可选):- capture|- once|- passive|- single
- useCapture(可选):一个布尔值,表示在 DOM 树中注册了- listener的元素,是否要先于它下面的- EventTarget调用该- listener。当 useCapture(设为 true)时,沿着 DOM 树向上冒泡的事件不会触发 listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可以查看 DOM Level 3 事件 及 JavaScript 事件顺序 文档。如果没有指定,- useCapture默认为- false。
直接绑定
在目标元素上就不会遵守先发生捕获后发生冒泡这一规则,而是先绑定的事件先发生。
- el.onclick = function(e){};优:简单稳定,兼容性好;缺:冒泡阶段,一次只能绑定一个处理事件
- <div onclick="clickEventHandler"></div>
在 JavaScript 中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的 DOM 访问次数,会延迟整个页面的交互就绪时间。
对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click 事件会一直冒泡到 document 层次。也就是说,我们可以为整个页面指定一个 onclick 事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
事件流的三个阶段
事件流描述了事件在 DOM(文档对象模型)中传播的顺序,包括三个阶段:
- 事件捕获(Capturing Phase):事件从文档的根节点开始,向目标元素传播。在这个阶段,事件沿着从外到内的路径传播。
- 目标阶段(Target Phase):事件到达目标元素。这个阶段是事件处理的关键部分,因为事件已经捕获到目标。
- 事件冒泡(Bubbling Phase):事件从目标元素开始,向文档的根节点传播。在这个阶段,事件沿着从内到外的路径传播。
JavaScript 代理与反射
JavaScript 中的 Proxy 与 Reflect 是 ES6 中引入的新特性,它们可以帮助我们更高效地控制对象。
代理(Proxy)是一种设计模式,它允许我们在访问对象的同时,添加一些额外的操作。代理对象与被代理对象实现相同的接口,代理对象会接受并处理所有对被代理对象的访问请求。
代理是对象通过一个代理对象来控制对原对象的读取、设置、调用及其他操作,并对这些操作进行预处理或附加操作,主要用于拦截对象
反射(Reflection)是指程序可以在运行时获取、操作、修改它本身的状态或行为。反射是一种动态获取类型信息和操作类型的能力,可以在运行时动态调用类中的方法或属性。
反射可以使我们获取对象详情,操控对象的成员(属性),在调用对象的方法时加入额外的逻辑,主要用于操作对象
JavaScript 原型与原型链
为其他对象提供共享属性的对象。每个对象都有一个 __proto__(又名:[[Prototype]])属性,该属性指向原型对象,并从中继承数据、结构和行为。每个函数(函数也是对象)都有一个 prototype 属性,它指向的也是一个原型对象。
先看一个示例:
| let obj={} | 
可以看到 obj 上确实多了一个 sayHello 的属性,值为一个函数,但是问题来了,obj 上面并没有 hasOwnProperty 这个方法,为什么我们可以调用呢?这就引出了 原型。
每一个对象从被创建开始就和另一个对象关联,从另一个对象上继承其属性,这个 另一个对象 就是 原型。
当访问一个对象的属性时,先在对象的本身找,找不到就去对象的原型上找,如果还是找不到,就去对象的原型(原型也是对象,也有它自己的原型)的原型上找,如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回 undefined。这条由对象及其原型组成的链就叫做原型链。
__proto__ 属性虽然在 ECMAScript 6 语言规范中标准化,但是不推荐被使用,现在更推荐使用 Object.getPrototypeOf,Object.getPrototypeOf(obj) 也可以获取到 obj 对象的原型。本文中使用 __proto__ 只是为了便于理解。
| Object.getPrototypeOf(person) === person.__proto__ // true | 
javascript: void(0)
javascript:void(0); 是一个 JavaScript 表达式,用于防止链接在被点击时执行其默认行为(即 导航到一个新页面 或 刷新当前页面 或 丢失当前滚动位置)。由于有些浏览器对 undefined 不支持,或者会被默认修改成其他值,可以使用 javascript:void(0) 代替。
- javascript:是一个伪 URL。一种将 JavaScript 代码直接嵌入 HTML 文档的方法。它可以用作超链接的 href 属性的值或用作事件处理程序(如 onclick)的值。例如,如果有一个链接需要在单击时执行特定的 JavaScript 功能,而不是导航到不同的页面,可以使用- javascript:
| <a href="javascript:myFunction()">Click here</a> | 
- void(0):void 运算符是 JavaScript 中一个很有价值的工具,它计算表达式并返回未定义的值。它经常用于通过使用- void(0)或- void 0来简单地获取 undefined 原始值。
| <a href="JavaScript:void(0)">Click me, nothing will happen</a> | 
点击时执行 JavaScript 函数 void,返回 undefined,对页面没有影响。使用 javascript:void(0) 作为 href 值的目的是防止页面在点击链接时刷新和更改 URL。它通常在需要链接但不需要执行任何操作时使用。
JavsScript 常用对象
Date
创建日期
有四种方式初始化日期:
| new Date(); | 
设置日期
在下面的例子中,我们将日期对象设置为 5 天后的日期:
| var myDate=new Date(); | 
注意: 如果增加天数会改变月份或者年份,那么日期对象会自动完成这种转换。
日期比较
| var x=new Date(); | 
Array
**JavaScript 数组复制操作 创建 浅拷贝*。(所有* JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是 深拷贝)。
常用方法:
- concat():连接两个或更多的数组,并返回结果 
- join():把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔 
- pop():删除并返回数组的最后一个元素 
- push():向数组的末尾添加一个或更多元素,并返回新的长度 
- reverse():颠倒数组中元素的顺序 
- shift():删除并返回数组的第一个元素 
- slice(start, end):从某个已有的数组返回选定的元素,start 必须有,如果参数为负数则从末尾开始选取 
- sort():对数组的元素进行排序 
- splice():删除元素,并向数组添加新元素,可用于插入、删除、替换元素。 
- unshift():向数组的开头添加一个或更多元素,并返回新的长度 
- indexOf(element):在数组中查找 element,返回值为索引,如果没有该元素返回-1 
- sort(function):排序,function 为一个函数(字符串对象(String)的方法与 Array 的方法类似) 
Window
浏览器对象模型(B rowser O bject M odel,BOM),是 BOM 中所有对象的核心,除了是 BOM 中所有对象的父对象外,还包含一些窗口控制函数。
所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员。全局变量是 window 对象的属性,全局函数是 window 对象的方法,甚至 HTML DOM 的 document 也是 window 对象的属性之一。
| window.document.getElementById("header"); | 
常用方法:
- alert():显示带有一段消息和一个确认按钮的警告框
- close():关闭浏览器窗口
- clearInterval()/setInterval():按照指定的周期(以毫秒计)来调用函数或计算表达式
- clearTimeout()/setTimeout():在指定的毫秒数后调用函数或计算表达式
- confirm():显示带有一段消息以及确认按钮和取消按钮的对话框
- moveBy():可相对窗口的当前坐标把它移动指定的像素
- moveTo():把窗口的左上角移动到一个指定的坐标
- open():打开一个新的浏览器窗口或查找一个已命名的窗口
- print():打印当前窗口的内容
- prompt();显示可提示用户输入的对话框
- resizeBy():按照指定的像素调整窗口的大小
- resizeTo():把窗口的大小调整到指定的宽度和高度
- scrollBy():按照指定的像素值来滚动内容
- scrollTo():把内容滚动到指定的坐标
RegExp
RegExp 对象用于将文本与一个模式匹配。
构建正则表达式
| var = /ab+c/i; //字面量形式 | 
后面的 i 是修饰符,下面是几个修饰符的作用:
- i:执行对大小写不敏感的匹配
- g:执行全局匹配(查找 所有 匹配)
- m:执行多行匹配
正则表达式对象元字符
| 字符 | 含义 | 
|---|---|
| \b | 匹配一个词的边界。一个词的边界就是一个词不被另外一个“字”字符跟随的位置或者前面跟其他“字”字符的位置,例如在字母和空格之间。注意,匹配中不包括匹配的字边界。换句话说,一个匹配的词的边界的内容的长度是 0。(不要和 [\b] 混淆了) 使用 “moon” 举例:  /\bm/匹配“moon”中的‘m’;/oo\b/并不匹配 “moon” 中的’oo’,因为’oo’被一个“字”字符’n’紧跟着。/oon\b/匹配 “moon” 中的’oon’,因为’oon’是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着。/\w\b\w/将不能匹配任何字符串,因为在一个单词中间的字符永远也不可能同时满足没有“字”字符跟随和有“字”字符跟随两种情况。备注: JavaScript 的正则表达式引擎将 特定的字符集 定义为“字”字符。不在该集合中的任何字符都被认为是一个断词。这组字符相当有限:它只包括大写和小写的罗马字母,十进制数字和下划线字符。不幸的是,重要的字符,例如“é”或“ü”,被视为断词。 | 
| \B | 匹配一个非单词边界。匹配如下几种情况: 字符串第一个字符为非“字”字符 字符串最后一个字符为非“字”字符 两个单词字符之间 两个非单词字符之间 空字符串 例如, /\B../匹配 “noonday” 中的’oo’, 而/y\B../匹配 “possibly yesterday” 中的’yes‘ | 
| \cX | 当 X 是处于 A 到 Z 之间的字符的时候,匹配字符串中的一个控制符。 例如, /\cM/匹配字符串中的 control-M (U+000D)。 | 
| \d | 匹配一个数字。等价于 [0-9]。例如, /\d/或者/[0-9]/匹配 “B2 is the suite number.” 中的’2’。 | 
| \D | 匹配一个非数字字符。等价于 [^0-9]。例如, /\D/或者/[^0-9]/匹配 “B2 is the suite number.” 中的’B’ 。 | 
| \f | 匹配一个换页符 (U+000C)。 | 
| \n | 匹配一个换行符 (U+000A)。 | 
| \r | 匹配一个回车符 (U+000D)。 | 
| \s | 匹配一个空白字符,包括空格、制表符、换页符和换行符。等价于 [\f\n\r\t\v\u0020\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如, /\s\w*/匹配 “foo bar.” 中的’ bar’。经测试,\s 不匹配 “\u180e“,在当前版本 Chrome(v80.0.3987.122) 和 Firefox(76.0.1) 控制台输入/\s/.test(“\u180e”) 均返回 false。 | 
| \S | 匹配一个非空白字符。等价于 [^\f\n\r\t\v\u0020\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。例如, /\S\w*/匹配 “foo bar.” 中的’foo’。 | 
| \t | 匹配一个水平制表符 (U+0009)。 | 
| \v | 匹配一个垂直制表符 (U+000B)。 | 
| \w | 匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]。例如, /\w/匹配 “apple,” 中的 ‘a’,”$5.28,” 中的 ‘5’ 和 “ 3D.” 中的 ‘3’。 | 
| \W | 匹配一个非单字字符。等价于 [^A-Za-z0-9_]。例如, /\W/或者/[^A-Za-z0-9_]/匹配 “50%.” 中的 ‘%’。 | 
| \n(数字) | 在正则表达式中,它返回最后的第 n 个子捕获匹配的子字符串 (捕获的数目以左括号计数)。 比如 /apple(,)\sorange\1/匹配 “apple, orange, cherry, peach.” 中的’apple, orange,’ 。 | 
| \0 | 匹配 NULL(U+0000)字符,不要在这后面跟其他小数,因为 \0<digits>是一个八进制转义序列。 | 
| \xhh | 匹配一个两位十六进制数(\x00-\xFF)表示的字符。 | 
| \uhhhh | 匹配一个四位十六进制数表示的 UTF-16 代码单元。 | 
| \u{hhhh}或\u{hhhhh} | (仅当设置了 u 标志时)匹配一个十六进制数表示的 Unicode 字符。 | 
正则表达式对象量词
| 字符 | 含义 | 
|---|---|
| \ | 依照下列规则匹配: 在非特殊字符之前的反斜杠表示下一个字符是特殊字符,不能按照字面理解。例如,前面没有 “\“ 的 “ b “ 通常匹配小写字母 “ b “,即字符会被作为字面理解,无论它出现在哪里。但如果前面加了 “\“,它将不再匹配任何字符,而是表示一个 字符边界。 在特殊字符之前的反斜杠表示下一个字符不是特殊字符,应该按照字面理解。详情请参阅下文中的 “转义(Escaping)” 部分。 如果你想将字符串传递给 RegExp 构造函数,不要忘记在字符串字面量中反斜杠是转义字符。所以为了在模式中添加一个反斜杠,你需要在字符串字面量中转义它。 /[a-z]\s/i和new RegExp("[a-z]\\s", "i")创建了相同的正则表达式:一个用于搜索后面紧跟着空白字符(\s可看后文)并且在 a-z 范围内的任意字符的表达式。为了通过字符串字面量给 RegExp 构造函数创建包含反斜杠的表达式,你需要在字符串级别和正则表达式级别都对它进行转义。例如/[a-z]:\\/i和new RegExp("[a-z]:\\\\","i")会创建相同的表达式,即匹配类似 “ C:" 字符串。 | 
| ^ | 匹配输入的开始。如果多行标志被设置为 true,那么也匹配换行符后紧跟的位置。 例如, /^A/并不会匹配 “an A” 中的 ‘A’,但是会匹配 “An E” 中的 ‘A’。当 ‘ ^‘ 作为第一个字符出现在一个字符集合模式时,它将会有不同的含义。反向字符集合 一节有详细介绍和示例。 | 
| $ | 匹配输入的结束。如果多行标志被设置为 true,那么也匹配换行符前的位置。 例如, /t$/并不会匹配 “eater” 中的 ‘t’,但是会匹配 “eat” 中的 ‘t’。 | 
| * | 匹配前一个表达式 0 次或多次。等价于 {0,}。例如, /bo*/会匹配 “A ghost boooooed” 中的 ‘booooo’ 和 “A bird warbled” 中的 ‘b’,但是在 “A goat grunted” 中不会匹配任何内容。 | 
| + | 匹配前面一个表达式 1 次或者多次。等价于 {1,}。例如, /a+/会匹配 “candy” 中的 ‘a’ 和 “caaaaaaandy” 中所有的 ‘a’,但是在 “cndy” 中不会匹配任何内容。 | 
| ? | 匹配前面一个表达式 0 次或者 1 次。等价于 {0,1}。例如, /e?le?/匹配 “angel” 中的 ‘el’、”angle” 中的 ‘le’ 以及 “oslo’ 中的 ‘l’。如果 紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为 非贪婪(匹配尽量少的字符),和缺省使用的 贪婪模式(匹配尽可能多的字符)正好相反。例如,对 “ 123abc “ 使用/\d+/将会匹配 “ 123 “,而使用/\d+?/则只会匹配到 “ 1 “。还用于先行断言中,如本表的 x(?=y)和x(?!y)条目所述。 | 
| . | (小数点)默认匹配除换行符之外的任何单个字符。 例如, /.n/将会匹配 “nay, an apple is on the tree” 中的 ‘an’ 和 ‘on’,但是不会匹配 ‘nay’。如果 s(“dotAll”) 标志位被设为 true,它也会匹配换行符。 | 
| (x) | 像下面的例子展示的那样,它会匹配 ‘x’ 并且记住匹配项。其中括号被称为 捕获括号。 模式 /(foo) (bar) \1 \2/中的 ‘(foo)‘ 和 ‘(bar)‘ 匹配并记住字符串 “foo bar foo bar” 中前两个单词。模式中的\1和\2表示第一个和第二个被捕获括号匹配的子字符串,即foo和bar,匹配了原字符串中的后两个单词。注意\1、\2、…、\n是用在正则表达式的匹配环节,详情可以参阅后文的 \n 条目。而在正则表达式的替换环节,则要使用像$1、$2、…、$n这样的语法,例如,'bar foo'.replace(/(...) (...)/, '$2 $1')。$&表示整个用于匹配的原字符串。 | 
| (?:x) | 匹配 ‘x’ 但是不记住匹配项。这种括号叫作 非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。 看看这个例子 /(?:foo){1,2}/。如果表达式是/foo{1,2}/,{1,2}将只应用于 ‘foo’ 的最后一个字符 ‘o’。如果使用非捕获括号,则{1,2}会应用于整个 ‘foo’ 单词。更多信息,可以参阅下文的 使用括号的子字符串匹配 条目。 | 
| x(?=y) | 匹配’x’仅仅当’x’后面跟着’y’.这种叫做先行断言。 例如,/Jack(?= Sprat)/会匹配到’Jack’仅当它后面跟着’Sprat’。/Jack(?= Sprat|Frost)/匹配‘Jack’仅当它后面跟着’Sprat’或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。 | 
| (?<=y)x | 匹配’x’仅当’x’前面是’y’.这种叫做后行断言。 例如,/(?<= Jack)Sprat/会匹配到’ Sprat ‘仅仅当它前面是’ Jack ‘。/(?<= Jack|Tom)Sprat/匹配‘Sprat ’仅仅当它前面是’Jack’或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。 | 
| x(?!y) | 仅仅当’x’后面不跟着’y’时匹配’x’,这被称为正向否定查找。 例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。正则表达式/\d+(?!.)/.exec(“3.141”) 匹配‘141’而不是‘3.141’ | 
| (?<!*y*)*x* | 仅仅当’x’前面不是’y’时匹配’x’,这被称为反向否定查找。 例如,仅仅当这个数字前面没有负号的时候, /(?<!-)\d+/匹配一个数字。/(?<!-)\d+/.exec('3')匹配到 “3”./(?<!-)\d+/.exec('-3')因为这个数字前有负号,所以没有匹配到。 | 
| [`x | y`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions#special-or) | 
| {n} | n 是一个正整数,匹配了前面一个字符刚好出现了 n 次。 比如, /a{2}/ 不会匹配“candy”中的’a’, 但是会匹配“caandy”中所有的 a,以及“caaandy”中的前两个’a’。 | 
| {n,} | n 是一个正整数,匹配前一个字符至少出现了 n 次。 例如,/a{2,}/ 匹配 “aa”, “aaaa” 和 “aaaaa” 但是不匹配 “a”。 | 
| {n,m} | n 和 m 都是整数。匹配前面的字符至少 n 次,最多 m 次。如果 n 或者 m 的值是 0,这个值被忽略。 例如,/a{1, 3}/ 并不匹配“cndy”中的任意字符,匹配“candy”中的 a,匹配“caandy”中的前两个 a,也匹配“caaaaaaandy”中的前三个 a。注意,当匹配”caaaaaaandy“时,匹配的值是“aaa”,即使原始的字符串中有更多的 a。 | 
| [xyz] | 一个字符集合。匹配方括号中的任意字符,包括 转义序列。你可以使用破折号(-)来指定一个字符范围。对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。 例如,[abcd] 和 [a-d] 是一样的。他们都匹配 “brisket” 中的‘b’, 也都匹配“city”中的‘c’。/[a-z.]+/ 和/[\w.]+/与字符串“test.i.ng”匹配。 | 
| [^xyz] | 一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。任何普通字符在这里都是起作用的。 例如, [^abc]和[^a-c] 是一样的。他们匹配 “brisket” 中的‘r’,也匹配“chop”中的‘h’。 | 
| [\b] | 匹配一个退格 (U+0008)。(不要和\b 混淆了。) | 
Location
window.location 对象用于获得当前页面的地址 (URL),并把浏览器重定向到新的页面。
方法:
- reload():重新加载当前文档
属性:
- hash:设置或返回从井号 (#) 开始的 URL(锚)。
- host:设置或返回主机名和当前 URL 的端口号。
- hostname:设置或返回当前 URL 的主机名。
- href:设置或返回完整的 URL。
- pathname:设置或返回当前 URL 的路径部分。
- port:设置或返回当前 URL 的端口号。
- protocol:设置或返回当前 URL 的协议。
- search:设置或返回从问号 (?) 开始的 URL(查询部分)
Function
回调函数
在一个函数当中,另一个函数作为参数传入该函数中,另一个的这个函数即为回调函数。
| function atack(callback){ | 
自执行函数(IIFE)
IIFE(Imdiately Invoked Function Expression,立即执行的函数表达式)在函数定义的结束最后写入一个(),该函数定义完成后直接被调用执行。
| (function func2(){ | 
短路运算
防止传入函数的数据不足,造成无法运行。
| # 通常使用逻辑与的短路来决定是否执行回调函数 | 
本地缓存
| Cookie | localStorage | sessionStorage | IndexedDB | |
|---|---|---|---|---|
| 存储大小 | 4KB | 5-10MB | 5-10MB | 无明确上限 | 
| 数据有效期 | 可设置过期时间 | 永久 | 会话结束 | 永久 | 
| 自动发送至服务器 | ✅ | ❌ | ❌ | ❌ | 
| 数据类型 | 字符串 | 字符串 | 字符串 | 结构化数据 | 
| 访问方式 | 同步 | 同步 | 同步 | 异步 | 
| API 复杂度 | 简单 | 简单 | 简单 | 复杂 | 
| 使用场景 | 身份验证(如会话 ID)、用户偏好设置 | 长期保存不敏感数据(如用户浏览历史、离线缓存) | 临时保存表单数据、多页面会话状态(如购物车) | 大量结构化数据存储(如图像、离线数据库) | 
闭包(closure)
闭包(closure)是 JavaScript 中一个重要的概念,它允许函数访问并操作函数外部的变量。闭包在函数创建时就会形成,使得内部函数即使在其外部作用域执行时,仍能访问到定义时的词法作用域。
示例:
| function init() { | 
init() 创建了一个名为 name 的局部变量和一个名为 displayName() 的函数。displayName() 是在 init() 内定义的内部函数,并且仅在 init() 函数的函数体内可用。请注意,displayName() 没有自己的局部变量。然而,因为内部函数能访问外部作用域的变量,所以 displayName() 能访问在 init() 父函数中声明的 name 变量。
用途
(1)匿名自执行函数
我们知道所有的变量,如果不加上 var 关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。除了每次使用变量都是用 var 关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护。
(2)结果缓存
我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
跨域(CORS)
跨域报错关键字:‘Access-Control-Allow-Origin’ 是一种解决资源跨域的策略,如果请求跨域访问,那么浏览器将报错。跨域是因为浏览器的‘同源策略’所导致的,那么,“同源策略”如何理解呢?所谓同源是指:访问地址的协议、域名/IP、端口三者都相同,浏览器则判定访问为同源访问,否则就是“不同源”,就会产生我们常说的跨域现象。
例如:相对于 http://localhost: 8102 的同源检测结果如下:
| 地址 | 差异点 | 是否同源 | 
|---|---|---|
| http://localhost: 8102/index.html | 域名、协议、端口均相同 | ✅ | 
| https://localhost: 8102 | 协议不同 | ❌ | 
| http://10.51.83.173:8102 | 域名/IP 不同 | ❌ | 
| http://10.51.83.173:8103 | 域名、端口不同 | ❌ | 
解决方案
后端支持跨域
适用于本地开发调试,连接后端开发机。
Access-Control-Allow-Origin:  这个头部信息由服务器返回,用来明确指定那些客户端的域名允许访问这个资源。它的值可以是: * (允许任意域名);- (一个完整的域名名字,比如:https://example.com)
Access-Control-Allow-Headers:提供一个逗号分隔的列表表示服务器支持的请求数据类型。假如你使用自定义头部(比如:x-authentication-token 服务器需要在返回 OPTIONS 请求时,要把这个值放到这个头部里,否则请求会被阻止)
Access-Control-Allow-Methods:一个逗号分隔的列表,表明服务器支持的请求类型(比如:GET, POST)
反向代理
通过配置 dev-service 或者 nginx 反向代理服务。涉及文件:
- src\config\constant\app.data.service.js 请求服务配置
- src\config\micro.js 微前端相对路径
Vue
生命周期
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。
常用的钩子:
- onCreated:vue 实例创建完成后被立即调用
- onMounted:组件完成初始渲染并创建 DOM 节点后运行
- onActivated:被 keep-alive 缓存的组件激活时调用
- onDestoryed:实例销毁后调用
自定义指令
除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。下面是一个自定义指令的例子,当 Vue 将元素插入到 DOM 中后,该指令会将一个 class 添加到元素中:
| <script setup> | 
在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以当作自定义指令使用。在上述例子中,vHighlight 可以在模板中以 v-highlight 的形式使用。
在不使用 <script setup> 的情况下,自定义指令需要通过 directives 选项注册:
| export default { | 
将一个自定义指令全局注册到应用层级也是一种常见的做法:
| const app = createApp({}) | 
参考链接
如何确保 HTML 文件在不同浏览器中显示的效果一致? – 来学习啦 – 编程乐园,实战驱动的编程学习平台
(史上最详细易懂)CSS hack 大全&详解(什么是 Css Hack?各个浏览器的 Hack 分析)-CSDN 博客
flex 布局的基本概念 - CSS:层叠样式表 | MDN
三分钟读懂 Java 与 JavaScript 的区别,让小白摘帽 - 知乎
JavaScript 的三大核心组成部分:ECMAScript、DOM 和 BOM-百度开发者中心
EventTarget.addEventListener() - Web API | MDN
深入理解 JavaScript 中的事件冒泡与事件捕获_js 事件捕获-CSDN 博客 v
精读 JavaScript 中的代理(Proxy)与反射(Reflect)-阿里云开发者社区
Javascript 基础:代理器(proxy)_javascript proxy-CSDN 博客
javascript - 彻底搞懂 JS 原型与原型链 - 个人文章 - SegmentFault 思否
什么是 TDZ?在 JavaScript 当中怎么避免?_js tdz-CSDN 博客












