JavaScript面试题
JS有哪些假值对象
在 JavaScript 中,假值 是在布尔上下文中会被评估为 false
的值。尽管这些值本身不是布尔类型,但在需要强制转换为布尔值时(如在 if
条件或逻辑操作中),它们会被认为是 false
。
JavaScript 中一共有 6 种假值,它们是:
false
:布尔类型中的假值。0
:数字0
被认为是假值,包括-0
。""
(空字符串):长度为 0 的字符串是假值。null
:表示空值,意图上是一个不存在的对象引用。undefined
:表示变量已声明但尚未赋值。NaN
:表示“不是一个数字”(Not-a-Number),通常是数学计算失败的结果。
这些是假值,它们在布尔上下文中都会被强制转换为 false
。
1 | if (!false) { |
除了上面提到的假值,所有其他值在布尔上下文中都被认为是 真值(truthy values),即使它们看起来为空或者没有值。例如:
- 非空字符串(如
"0"
或"false"
) - 非零数字(如
1
,-1
) - 对象(即使是空对象
{}
) - 数组(即使是空数组
[]
)
这些在布尔上下文中会被评估为 true
。
JS有什么内置对象?
JavaScript 中有许多内置对象,这些对象为开发者提供了强大的功能,可以用来处理各种常见的任务,例如操作数据、处理日期、数学计算、以及与浏览器和 DOM 交互等。
常见的 JavaScript 内置对象分类:
全局对象
- **
Object
**:所有 JavaScript 对象的父对象,包含所有对象的通用属性和方法。 - **
Function
**:每个函数都是Function
的实例。可以用来创建新的函数。 - **
Boolean
**:用于创建布尔值对象。 - **
Symbol
**:用于创建独特的、不可变的符号值,常用于对象属性的唯一标识符。 - **
Error
**:创建表示错误的对象。常用的派生错误类型有TypeError
、RangeError
、ReferenceError
等。 - **
BigInt
**:用于表示和操作大整数,超出了Number
的范围。
- **
数字和日期处理
- **
Number
**:处理数字,包括数字转换和格式化操作。 - **
Math
**:提供常用的数学常数和函数(如Math.PI
,Math.random()
,Math.sqrt()
等)。 - **
Date
**:用于处理日期和时间。
- **
字符串处理
- **
String
**:用于创建和操作字符串,提供各种字符串处理的方法。 - **
RegExp
**:表示正则表达式,用于字符串模式匹配和替换。
- **
集合类型(集合、映射)
- **
Array
**:表示数组对象,用于存储有序集合的列表,提供大量数组操作方法。 - **
Set
**:用于存储唯一值的集合,不允许重复的值。 - **
Map
**:用于存储键值对集合,键可以是任意类型。 - **
WeakSet
**:类似Set
,但其成员是弱引用,垃圾回收时不考虑其引用的对象。 - **
WeakMap
**:类似Map
,但键是弱引用,键必须是对象,垃圾回收时不考虑其引用的对象。
- **
类型化数组(用于操作二进制数据)
- **
ArrayBuffer
**:用于表示原始的二进制数据缓冲区。 - **
TypedArray
**:类型化数组,提供多种类型的二进制数据视图,如Int8Array
,Uint8Array
,Float32Array
等。 - **
DataView
**:通过特定的格式读取和写入ArrayBuffer
中的数据。
- **
结构化数据和对象操作
- **
JSON
**:用于序列化(JSON.stringify()
)和反序列化(JSON.parse()
)JSON 数据格式。 - **
Promise
**:表示异步操作的最终完成(或失败)及其结果值。 - **
Proxy
**:用于定义对象的自定义行为,如属性查找、赋值、枚举、函数调用等。 - **
Reflect
**:提供与Proxy
相配合的低级别操作。
- **
Web API 相关的内置对象
- **
Window
**:表示浏览器窗口的全局对象,包含 DOM、定时、导航等方法。 - **
Document
**:表示整个 HTML 文档,提供访问和操作页面内容的接口。 - **
Element
**:DOM 元素对象的基类,用于表示 HTML 元素。 - **
Event
**:表示浏览器中发生的事件,提供对事件处理的支持。
- **
其他常见内置对象
- **
Symbol
**:用于创建独特且不可改变的符号,常用于对象属性的唯一标识。 - **
Intl
**:用于国际化(Internationalization),提供字符串的本地化、时间格式化等支持。 - **
console
**:用于访问浏览器的控制台,提供调试输出功能(如console.log()
)。 setTimeout
/ **setInterval
**:用于定时器设置,setTimeout
用于延时执行,setInterval
用于周期性执行。
- **
常见 JavaScript 内置对象简表:
类型 | 对象 |
---|---|
基本对象 | Object , Function , Boolean , Symbol , Error , BigInt |
数字和日期 | Number , Math , Date |
字符串和正则表达式 | String , RegExp |
集合和键值对 | Array , Set , Map , WeakSet , WeakMap |
类型化数组和缓冲区 | ArrayBuffer , TypedArray , DataView |
结构化数据 | JSON , Promise , Proxy , Reflect |
Web API | Window , Document , Element , Event |
其他 | Intl , console , setTimeout , setInterval |
浏览器常见的进程都有哪些
浏览器作为一种复杂的应用程序,涉及多个进程的协同工作以提供高效、安全的用户体验。现代浏览器(如 Chrome、Firefox、Edge 等)采用多进程架构,将不同的功能模块分离到不同的进程中运行,从而提高稳定性、安全性和性能。以下是浏览器中常见的几种进程:
浏览器主进程(Browser Process)
- 作用:浏览器的核心控制进程,负责管理浏览器的各个窗口和标签页,协调和调度其他进程。
- 主要职责:
- 控制用户界面(如地址栏、书签栏、前进/后退按钮等)。
- 管理浏览器的多个标签页和窗口。
- 处理网络请求的代理,下载管理。
- 负责安全机制,比如处理权限请求(摄像头、麦克风等)。
- 管理各个渲染进程的生命周期,创建和销毁渲染进程。
- 特点:一般只有一个主进程,无论有多少个标签页和窗口。
渲染进程(Renderer Process)
- 作用:负责一个或多个标签页的内容渲染。每个标签页或框架通常都有自己的渲染进程,用于处理 HTML、CSS、JavaScript 等。
- 主要职责:
- 解析 HTML、CSS,构建 DOM 树和 CSSOM 树,并渲染页面。
- 执行 JavaScript 代码(JavaScript 是单线程在渲染进程中运行)。
- 处理页面的布局、绘制以及将内容展示在屏幕上。
- 特点:
- 通常每个标签页都有一个独立的渲染进程,以实现页面的隔离。某个页面崩溃不会影响其他页面。
- 如果页面内包含
iframe
,浏览器可能会为每个iframe
分配一个渲染进程。 - 渲染进程是多进程架构的核心,它直接与用户的网页交互。
GPU 进程(GPU Process)
- 作用:负责处理图形相关的任务,主要是加速页面的绘制和 WebGL 相关的操作。
- 主要职责:
- 管理和协调 GPU 的硬件加速。
- 负责页面中的 2D/3D 绘图、视频解码、WebGL 等图形处理任务。
- 渲染进程将需要绘制的内容交给 GPU 进程,然后通过 GPU 将内容加速绘制到屏幕上。
- 特点:
- 现代浏览器使用 GPU 加速绘制页面,提升渲染性能和动画效果的流畅度。
- 通常,GPU 进程是独立的,所有渲染进程共享同一个 GPU 进程。
插件进程(Plugin Process)
- 作用:用于管理浏览器中的插件,比如 Adobe Flash 等。插件进程负责加载、运行插件,并处理与插件相关的任务。
- 主要职责:
- 加载并执行浏览器支持的插件(如 Flash、PDF 查看器等)。
- 确保插件的崩溃不会导致浏览器的主进程或渲染进程崩溃。
- 特点:
- 每个插件通常有独立的进程,插件的崩溃不会影响浏览器的其他部分。
- 随着现代 Web 技术的发展,传统插件(如 Flash)的使用逐渐减少,因此插件进程的作用越来越弱。
网络进程(Network Process)
- 作用:负责处理所有与网络相关的任务,包括发起 HTTP/HTTPS 请求、处理 WebSocket 连接、处理缓存和资源加载等。
- 主要职责:
- 处理 HTTP、HTTPS、WebSocket 等网络请求。
- 管理浏览器的缓存系统,加速资源加载。
- 处理下载任务,管理网络状态等。
- 特点:
- 网络进程可以为多个渲染进程提供网络服务,从而减少重复的资源请求。
- 这一进程独立于渲染进程,确保即使网络请求出错也不会导致页面崩溃。
存储进程(Storage Process)—(在某些浏览器中)
- 作用:负责处理浏览器的本地存储任务,比如 IndexedDB、LocalStorage、SessionStorage、Cookie 等。
- 主要职责:
- 管理和存储用户数据,包括浏览器缓存、用户登录凭证等。
- 提供页面和插件访问本地存储的接口,确保存储操作安全。
- 特点:
- 某些浏览器将存储相关的操作独立为一个进程,以确保数据操作的安全性和隔离性。
- 它与渲染进程进行通信以提供和存储数据。
服务工作进程(Service Worker Process)
- 作用:专门用于处理后台任务,比如
Service Workers
,它们在后台运行,支持离线功能、推送通知等。 - 主要职责:
- 管理
Service Workers
,处理离线缓存、后台同步、推送通知等功能。 Service Workers
可以拦截网络请求,提供离线支持。- 即使页面关闭,
Service Worker
仍然可以继续执行任务。
- 管理
- 特点:
Service Worker
是独立于页面的进程,在没有打开页面时也能执行任务。
扩展进程(Extension Process)
- 作用:管理浏览器的扩展(插件),为浏览器安装的扩展提供独立的运行环境。
- 主要职责:
- 执行和管理用户安装的浏览器扩展。
- 确保扩展的错误不会影响浏览器其他进程的运行。
- 特点:
- 每个扩展可以在自己的独立进程中运行,避免扩展崩溃影响浏览器主进程或渲染进程。
- 浏览器扩展的权限相对较高,因此浏览器会对扩展进行权限和安全管理。
沙盒进程(Sandbox Process)
- 作用:渲染进程通常运行在沙盒环境中,以提高浏览器的安全性,防止恶意代码通过渲染进程攻击系统。
- 主要职责:
- 通过沙盒机制,将渲染进程的权限限制在严格的范围内,防止其访问系统的敏感资源(如文件系统等)。
- 如果某个渲染进程受到攻击或崩溃,沙盒机制可以确保问题不会扩展到系统其他部分。
总结
浏览器是由多个进程组成的复杂应用程序,每个进程都有特定的职责,互相独立又协作工作。通过这种多进程架构,现代浏览器能够实现稳定性、安全性和性能优化。主要的进程有:
- 浏览器主进程:负责管理界面、页面、网络、下载等。
- 渲染进程:负责渲染网页内容、执行 JavaScript 和处理用户交互。
- GPU 进程:负责加速图形渲染。
- 插件进程:管理浏览器插件的加载与执行。
- 网络进程:负责处理网络请求和响应。
- 存储进程:管理本地存储、数据库等操作。
- 服务工作进程:负责后台任务,如离线缓存、推送通知等。
- 扩展进程:运行用户安装的浏览器扩展。
这种多进程架构使得浏览器更加健壮,保证即使某个进程崩溃,也不会影响其他进程的正常工作,从而提供更高的稳定性和安全性。
JavaScript有几种方式声明函数?
函数声明(Function Declaration):
1
2
3function functionName(parameters) {
// 函数体
}函数表达式(Function Expression):
1
2
3var functionName = function(parameters) {
// 函数体
};箭头函数(Arrow Function)(ES6引入):
1
2
3var functionName = (parameters) => {
// 函数体
};Function构造函数:
1
var functionName = new Function('parameters', 'function body');
这些方式各有特点,函数声明和函数表达式是最常见和推荐的方式,而箭头函数是在ES6中引入的一种更简洁的写法,而Function构造函数则较少使用,因为它会在运行时解析字符串,有一定的性能开销和安全风险。
原生Javascript获取DOM节点有哪些方式?
document.getElementById
获取具有指定的ID,ID唯一。
1 | let a=document。getElementById('id'); |
document.getElementByTagName
返回一个包含所有给定标签名元素的HTML
1 | let a=document.getElementByTagName('div'); |
1document.getElementByClassName
返回一个包含所有给定类名的元素的
HTMLCollection
。
1 | let a=document.getElementByClassName('myclass'); |
在原生 JavaScript 中,有多种方式可以获取或选择 DOM (Document Object Model) 元素。这些方法提供了从简单到复杂的不同级别的选择功能,可以让你根据 ID、类名、标签名、CSS 选择器等多种方式选择元素。以下是一些最常用的方法:
document.getElementById
获取具有指定 ID 的一个元素。ID 在一个文档中应该是唯一的。
1 | var element = document.getElementById('myId'); |
document.getElementsByTagName
返回一个包含所有给定标签名的元素的 HTMLCollection
。
1 | var elements = document.getElementsByTagName('div'); |
document.getElementsByClassName
返回一个包含所有给定类名的元素的 HTMLCollection
。
1 | var elements = document.getElementsByClassName('myClass'); |
document.querySelector
返回文档中匹配指定 CSS 选择器的第一个元素。如果没有找到匹配项,返回 null
。
1 | var element = document.querySelector('.myClass'); |
document.querySelectorAll
返回一个 NodeList
,其中包含文档中所有符合指定 CSS 选择器的元素。
1 | var elements = document.querySelectorAll('.myClass'); |
document.forms`
这是一个比较老的方法,用来获取文档中的表单元素。
1 | var form = document.forms[0]; // 获取第一个表单 |
document.getElementsByName
这个方法返回一个 NodeList
,其中包含所有具有指定的 name 特性的元素。
1 | var elements = document.getElementsByName('name'); |
使用 CSS 属性选择器
你也可以使用 querySelector
和 querySelectorAll
方法配合属性选择器来选择具有特定属性的元素。
1 | var element = document.querySelector('input[type="checkbox"]'); |
使用建议:
- 如果你只需要选择一个元素,并且该元素具有 ID,那么
getElementById
是最快的选择。 - 如果需要根据类名、标签名或者更复杂的 CSS 选择器选择多个元素,
querySelectorAll
是非常强大的工具。 - 对于需要兼容性考虑,所有上述方法除了较新的
querySelector
和querySelectorAll
,其他都有很好的兼容性。querySelector
和querySelectorAll
主要不支持 IE8 及以下版本。
这些方法都是原生 JavaScript 提供的,无需依赖任何库或框架即可在现代浏览器中使用。
Javascript的this绑定
在JavaScript中,
this
关键字是非常核心的一个概念,它引用的是函数执行时的上下文对象。理解this
如何绑定到不同的上下文是理解和掌握JavaScript非常重要的一部分。this
的绑定规则可以通过以下几种方式确定:
默认绑定
当函数独立调用时(即不作为某个对象的方法或未被显式绑定到某个对象),this
默认绑定到全局对象。在非严格模式下,this
指向全局对象(浏览器中的window
,Node.js中的global
);在严格模式下(函数或全局代码中使用'use strict';
),this
会被绑定到undefined
。
1 | function show() { |
隐式绑定
当函数作为对象的方法被调用时,this
隐式绑定到该对象。
1 | const obj = { |
如果存在多层对象嵌套,this
将绑定到最近一层的对象上。
显式绑定
使用call()
、apply()
或 bind()
方法可以显式地指定this
的绑定对象。
1 | function show() { |
new绑定
当一个函数通过new
关键字被作为构造函数调用时,this
被绑定到新创建的对象上。
1 | function Person(name) { |
箭头函数绑定
箭头函数不使用以上四种规则,而是捕获其所在上下文的this
值,作为自己的this
值,也称为词法绑定。
1 | const obj = { |
在箭头函数中,this
绑定到外围函数的上下文中,即使是在setTimeout
这样的异步函数中。
总结
JavaScript的this
绑定是动态的,除了箭头函数外,其绑定取决于函数调用的上下文。理解这些绑定规则对于编写可预测的JavaScript代码非常重要。
JS主线程有哪些
在JavaScript中,特别是在浏览器环境下,存在一个称为“主线程”的概念,通常也被称为“UI线程”。JavaScript 是单线程运行的,意味着在任一时刻只能执行一个任务。主线程负责执行代码、渲染界面、处理用户的交互操作等任务。这里主要解释的是JavaScript在浏览器中的运行环境:
JavaScript 主线程的职责
执行代码:
- JavaScript 主线程执行所有的JavaScript代码。无论是从
<script>
标签引入的代码,还是通过事件触发的函数,都在这个单一的线程上执行。
- JavaScript 主线程执行所有的JavaScript代码。无论是从
UI 渲染:
- 浏览器使用主线程来渲染页面。这包括HTML的解析、CSS的应用、DOM的操作以及最终的画面渲染。这也是为什么在执行重计算或大量DOM操作时,页面可能会感觉卡顿或冻结,因为这些操作会占用主线程的资源,影响到UI的更新。
用户交互:
- 主线程还处理来自用户的所有交互,如点击、滚动、键盘输入等。这意味着如果JavaScript代码执行耗时过长,它可以阻塞用户交互,导致不良的用户体验。
与主线程交互的其他组件
尽管JavaScript是单线程执行的,但现代浏览器和Node.js环境提供了多种机制来处理异步事件和后台任务,以避免阻塞主线程,如:
Web Workers:
- 提供一种在浏览器背景下运行脚本的方法,不影响主线程。Web Workers运行在与主线程完全独立的线程上,可以执行复杂计算或处理大量数据,不会导致界面卡顿。
事件队列和事件循环:
- 浏览器维护一个事件队列,所有异步事件,如网络请求、定时器触发、用户输入等,都会被排到这个队列中。主线程在执行完当前代码后,会查看这个队列,并处理队列中的事件。
异步API:
- 浏览器提供了许多异步API,如
setTimeout
、setInterval
、fetch
(网络请求)等,这些API的回调不会立即执行,而是在合适的时机被推入事件队列,待主线程空闲时执行。
- 浏览器提供了许多异步API,如
注意事项
由于主线程的多重职责,作为开发者在编写JavaScript代码时,需要注意不要执行过于复杂或耗时的同步代码,这可能会阻塞主线程,影响到UI的渲染和用户的交互体验。在处理大量数据或复杂计算时,应考虑使用Web Workers或其他异步处理方法,以提高应用性能和响应性。
事件捕获、目标、冒泡
在Web开发中,事件冒泡是一种事件处理机制,其中从一个元素发起的事件会逐层向上传播到其父元素,直至根元素。有时候,我们可能需要阻止这种冒泡行为,以避免触发父元素上的事件处理函数。这可以通过JavaScript中的事件对象提供的方法来实现。
以下是如何在不同情况下阻止事件冒泡的方法:
使用 event.stopPropagation()
这是阻止事件冒泡的标准方法。当在事件处理函数中调用此方法时,当前事件将不会进一步传播到父元素。
1 | element.addEventListener('click', function(event) { |
返回 false
在使用jQuery时,你可以通过在事件处理函数中返回false
来同时阻止事件冒泡和默认行为。在原生JavaScript中,这种方法只适用于通过HTML属性添加的事件处理器。
1 | // 使用 jQuery |
使用 event.stopImmediatePropagation()
这个方法不仅阻止事件继续冒泡到更高的父元素,还阻止任何当前元素上的其他事件监听器被调用。这是一个比stopPropagation()
更强大的方法。
1 | element.addEventListener('click', function(event) { |
应用场景
- 阻止链接默认打开新页面:阻止
<a>
标签的默认行为。 - 防止表单提交:阻止表单的自动提交行为。
- 组织事件传播到其他元素:如在弹出菜单中阻止点击事件传播到背景页面。
注意
虽然阻止事件冒泡可以解决一些问题,但过度使用它可能会导致维护困难,因为这改变了事件的正常流动。合理使用这一特性,并确保理解其对整体交互逻辑的影响。
JavaScript的对象方法
JavaScript
的对象(Object
)有许多内置的方法,这些方法可以帮助我们操作和管理对象。以下是一些常见的对象方法及其示例:
Object.keys()
返回一个数组,包含对象自身可枚举属性的键。
1 | const person = { |
Object.values()
返回一个数组,包含对象自身可枚举属性的值。
1 | const person = { |
Object.entries()
返回一个数组,包含对象自身可枚举属性的键值对数组。
1 | const person = { |
Object.assign()
将所有可枚举的自身属性从一个或多个源对象复制到目标对象。它返回目标对象。
1 | const target = { a: 1, b: 2 }; |
Object.freeze()
冻结对象:一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。该对象的原型也不能被修改。
1 | const person = { |
Object.seal()
密封对象:一个密封的对象不能再添加新的属性,且所有现有属性将变为不可配置的。现有属性的值仍然可以修改。
1 | const person = { |
Object.create()
使用指定的原型对象及其属性创建一个新对象。
1 | const personPrototype = { |
Object.defineProperty()
直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
1 | const person = {}; |
Object.getOwnPropertyDescriptor()
返回指定对象上一个自有属性对应的属性描述符。
1 | const person = { |
Object.getPrototypeOf()
返回指定对象的原型(即,内部[[Prototype]]属性的值)。
1 | const personPrototype = { |
总结
JavaScript对象有许多内置方法,可以帮助我们操作和管理对象。上述列举的只是其中的一部分,实际开发中可以根据需要选择合适的方法来处理对象。
JavaScript的包装类型
在 JavaScript 中,包装类型(wrapper types)是指为基本数据类型(primitive types)提供对象表示的方法。基本数据类型本身是不可变且没有方法的,但 JavaScript 自动为这些类型创建对应的包装对象,使得我们可以调用它们的方法。
JavaScript 的基本数据类型包括:
string
number
boolean
symbol
bigint
null
undefined
包装类型为基本数据类型中的 string
、number
和 boolean
提供了对象包装类,使它们能暂时表现为对象,可以调用对象方法。具体包括 String
、Number
和 Boolean
三个构造函数。
自动装箱与拆箱
JavaScript 会在某些场合自动将基本数据类型转换为对应的对象,称为自动装箱。当调用基本数据类型的属性或方法时,JavaScript 会临时创建一个包装对象,使得我们可以像操作对象一样操作基本类型的值。
例如:
1 | let str = "hello"; |
在这里,str
是一个基本类型(string
),但我们可以调用 .length
属性,这是因为 JavaScript 在访问 str.length
时,临时创建了一个 String
对象,从而使 str
拥有对象的属性和方法。
自动装箱的步骤如下:
- JavaScript 将基本类型
str
转换为其包装对象String
。 - 查找
String
对象上的length
属性并返回。 - 之后
String
包装对象被销毁,str
依然是原本的基本类型。
包装类型的例子
String
包装类型
String
对象用于包装字符串,并允许调用与字符串相关的各种方法,如 length
、toUpperCase()
等。
1 | let str = "hello"; |
Number
包装类型
Number
对象用于包装数字,并允许调用与数字相关的各种方法,如 toFixed()
、toString()
等。
1 | let num = 123; |
Boolean
包装类型
Boolean
对象用于包装布尔值,并允许调用与布尔值相关的方法,如 toString()
。
1 | let bool = true; |
包装类型与基本类型的区别
尽管包装对象和基本类型类似,它们仍然是不同的类型。使用 typeof
时可以看出:
1 | let str = "hello"; |
在进行比较时,基本类型和包装对象也可能表现不同:
1 | let str = "hello"; |
str
是基本类型,而 strObj
是包装对象,它们不是同一个类型,因此返回 false
。
不推荐使用显式的包装类型
尽管 JavaScript 支持通过 new
关键字显式创建包装类型对象,但通常不推荐这样做。这样做会让代码变得不直观,而且可能导致一些难以察觉的错误。大多数情况下,依赖 JavaScript 的自动装箱机制就足够了。
坏例子:
1 | let str = new String("hello"); |
推荐做法:
1 | let str = "hello"; |
总结来说,JavaScript 的包装类型允许基本数据类型暂时表现为对象,从而调用它们的属性和方法。尽管可以显式创建包装对象,但更推荐使用隐式的自动装箱机制。
如何理解json
基本概念
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它易于人类阅读和编写,也便于机器解析和生成。JSON 的数据结构简单,通常用于在网络上进行数据传输,是现代 Web 开发中的重要数据格式之一。
特点
- 轻量级:JSON 采用文本格式,结构简单,便于快速传输。
- 易读性:JSON 语法直观,接近 JavaScript 对象的字面量表示,易于阅读和理解。
- 语言无关性:虽然 JSON 起源于 JavaScript,但几乎所有编程语言都支持 JSON,可以方便地在不同系统和语言之间交换数据。
- 可扩展性:JSON 数据结构灵活,支持嵌套对象和数组,适合描述复杂的数据结构。
JSON 的数据类型
JSON 支持以下几种数据类型:
- 对象(Object):由键值对组成,键为字符串类型,值可以是任何有效的 JSON 数据类型。对象用花括号
{}
包裹,键值对之间用逗号分隔。 - 数组(Array):一个有序的值的集合,使用方括号
[]
包裹,元素之间用逗号分隔。 - 字符串(String):必须使用双引号
""
。 - 数值(Number):支持整数和浮点数,不支持
NaN
、Infinity
。 - 布尔值(Boolean):
true
或false
。 - 空值(null):表示空值或无值。
示例:
1 | { |
JSON 与 JavaScript 对象的区别
- 键必须用双引号:JSON 中的键必须是双引号括起来的字符串,而 JavaScript 对象的键不必使用引号。
- 不能包含函数:JSON 不支持函数或方法,而 JavaScript 对象可以包含函数作为属性。
- 数据类型限制:JSON 不支持
undefined
、NaN
和Infinity
,而 JavaScript 对象可以。
JSON 的应用场景
- 数据传输:JSON 常用于客户端和服务器的数据交换。例如,使用
AJAX
或Fetch API
从服务器请求数据时,通常会收到 JSON 格式的数据。 - 配置文件:许多应用和工具(如 ESLint、Babel 等)使用 JSON 文件来存储配置数据。
- 数据持久化:一些 NoSQL 数据库(如 MongoDB)直接使用 JSON 格式存储数据。
- 跨平台数据交换:JSON 是一种语言无关的格式,可以在不同语言之间交换数据,如 Java、Python、JavaScript 等。
JSON 的解析与生成
在 JavaScript 中,可以使用 JSON.parse()
和 JSON.stringify()
这两个方法来解析和生成 JSON 数据。
解析 JSON 字符串为 JavaScript 对象
1 | const jsonString = '{"name": "Alice", "age": 30}'; |
将 JavaScript 对象转换为 JSON 字符串
1 | const obj = { name: "Alice", age: 30 }; |
JSON 的优缺点
优点
- 通用性强:几乎所有编程语言都支持 JSON,使得跨平台、跨语言的数据交换变得简单。
- 格式清晰:结构直观,易于阅读和编写。
- 解析效率高:解析 JSON 的成本较低,尤其是在 Web 环境中性能表现较好。
缺点
- 缺乏数据类型:JSON 仅支持基本数据类型,不支持日期、时间、二进制数据等复杂类型。
- 文件体积较大:与二进制格式(如 Protobuf)相比,JSON 文件体积较大,不适合传输非常大量的数据。
- 安全风险:在 Web 环境中,如果 JSON 直接在 HTML 中进行解析,可能导致跨站脚本攻击(XSS),因此应使用
JSON.parse()
进行解析,而非eval()
。
JSON 的替代格式
JSON 是广泛使用的,但在某些特定场景下,也有一些替代格式:
- XML:曾广泛用于 Web 服务的数据交换,但比 JSON 更冗长。
- YAML:结构更加简洁,主要用于配置文件(如
.yaml
),易于人类阅读。 - MessagePack、Protobuf、Avro:这些是二进制格式,体积更小、效率更高,适合在性能至关重要的场景使用。
总结
JSON 是一种轻量级、语言无关的数据交换格式,适用于客户端与服务器之间的数据传输。它在现代 Web 开发中广泛使用,且具有通用性强、格式清晰等优点,但也存在缺乏复杂数据类型、体积较大等局限性。通过合理地使用 JSON,可以在各种编程语言和环境之间实现高效的数据交换。
判断 JSON 是否为空
实际上是判断 JSON 对象是否为空对象或空数组。由于 JSON 本质上可以是对象或数组,因此我们可以分别判断对象或数组是否为空。
判断 JSON 对象为空
在 JavaScript 中,判断一个 JSON 对象是否为空,可以检查该对象是否没有任何可枚举属性。通常可以使用 Object.keys()
、Object.values()
或 Object.entries()
来判断。
1 | const obj = {}; // 空 JSON 对象 |
如果对象的键长度为 0,那么这个对象就是空对象。
判断 JSON 数组为空
对于 JSON 数组,可以简单地通过检查数组的长度来判断是否为空。
1 | const arr = []; // 空 JSON 数组 |
数组长度为 0 意味着数组为空。
处理 JSON 字符串
如果你有一个 JSON 字符串,首先需要将其解析为 JavaScript 对象或数组,然后再检查是否为空。
1 | const jsonString = '{}'; // 或者 '[]' |
判断特殊情况
null 值:如果 JSON 数据为
null
,可以直接通过比较判断。1
2
3
4
5const jsonObj = null;
if (jsonObj === null) {
console.log("JSON 为 null");
}
总结
- 对于 JSON 对象,可以使用
Object.keys().length
来判断是否为空。 - 对于 JSON 数组,可以通过检查
array.length
来判断是否为空。 - 如果是 JSON 字符串,需要先使用
JSON.parse()
将其转换为对象或数组,然后再根据具体类型进行判断。
DOM
和BOM
DOM
(Document Object Model)和 BOM
(Browser Object Model)是 JavaScript 在浏览器中的两个重要概念,它们帮助开发者与网页和浏览器进行交互。虽然两者都与浏览器有关,但它们的作用和涵盖范围不同。
DOM(Document Object Model)
DOM
是浏览器将网页(HTML 或 XML 文档)结构化成的一棵树形结构。它将 HTML 文档表示为对象,允许 JavaScript 与网页的结构、内容和样式进行动态交互。通过 DOM
,开发者可以获取、修改、添加或删除页面中的元素。
特点:
- 表示网页结构:
DOM
将 HTML 文档映射成一个树形结构,树中的每个节点都是页面的一部分,如元素、属性或文本。 - 动态操作文档:JavaScript 可以使用
DOM API
操作页面元素,例如更改元素内容、修改样式、添加/删除节点。 - 事件处理:
DOM
还支持事件机制,允许 JavaScript 响应用户的交互,例如点击、输入、滚动等。
1 |
|
在这个例子中,document.getElementById('example')
是通过 DOM
API 选择页面中的一个 div
元素,并修改其内容。document
是整个 DOM
的根对象,它代表了整个 HTML 文档。
DOM
的一些常用方法:
方法 | 说明 | 返回类型 |
---|---|---|
getElementById() |
根据 id 获取单个元素 |
Element 或 null |
getElementsByClassName() |
根据 class 获取所有匹配的元素 |
HTMLCollection |
getElementsByTagName() |
根据标签名获取所有匹配的元素 | HTMLCollection |
getElementsByName() |
根据 name 属性获取所有匹配的元素 |
NodeList |
querySelector() |
使用 CSS 选择器获取第一个匹配的元素 | Element 或 null |
querySelectorAll() |
使用 CSS 选择器获取所有匹配的元素 | NodeList |
document.forms[] |
获取所有表单元素 | HTMLCollection |
document.images[] |
获取所有 <img> 元素 |
HTMLCollection |
document.links[] |
获取所有包含 href 属性的链接元素 (<a> ) |
HTMLCollection |
document.anchors[] |
获取所有带有 name 属性的 <a> 标签(较为过时) |
HTMLCollection |
备注:
- **
HTMLCollection
**:是一个动态集合,实时反映 DOM 的变化。 - **
NodeList
**:是一种静态集合,通常是querySelectorAll()
返回的结果,不会随着 DOM 的变化而自动更新。
BOM(Browser Object Model)
BOM
是浏览器提供的一组对象,它们用于与浏览器窗口进行交互。BOM
主要涉及与浏览器环境相关的功能,比如控制浏览器窗口、导航、历史记录、屏幕大小等。BOM
并不是与页面内容相关,而是与浏览器本身的操作有关。
特点:
- 表示浏览器环境:
BOM
允许 JavaScript 访问和操作浏览器的各种功能,如窗口、导航、历史等。 - 不依赖于网页结构:
BOM
提供的是与浏览器相关的功能,不像DOM
直接操作页面元素。 - 管理浏览器窗口和导航:通过
BOM
,可以控制窗口的打开/关闭、调整大小、获取和修改 URL、处理历史记录等。
常用的 BOM
对象:
window
:表示浏览器窗口的全局对象,所有BOM
对象都是window
的属性。navigator
:包含浏览器的信息,如浏览器名称、版本、操作系统等。screen
:表示用户屏幕的信息,如分辨率和颜色深度等。location
:表示当前文档的 URL 信息,可以用来重定向页面或获取当前页面的 URL。history
:用于操作浏览器的历史记录。
1 | // 访问浏览器的 URL 信息 |
在上面的例子中,window.location.href
获取当前页面的 URL,window.history.length
返回浏览器的历史记录长度,而 window.screen.width
获取用户屏幕的宽度。这些都是 BOM
提供的功能。
DOM 和 BOM 的区别
特性 | DOM | BOM |
---|---|---|
定义 | 文档对象模型,表示网页的结构 | 浏览器对象模型,表示浏览器环境 |
作用 | 操作网页内容和结构 | 操作浏览器窗口、导航、历史等功能 |
核心对象 | document |
window |
API 示例 | document.getElementById() 、innerHTML |
window.open() 、navigator.userAgent |
关联范围 | 与网页内容相关 | 与浏览器功能相关 |
总结
- DOM:用于操作页面内容,帮助开发者通过 JavaScript 动态修改网页结构、内容和样式。核心对象是
document
,它代表整个 HTML 页面。 - BOM:用于操作浏览器窗口和相关功能,核心对象是
window
,代表整个浏览器窗口。它包含了与浏览器环境相关的对象,比如navigator
、location
、history
、screen
等。
这两者的功能结合起来,JavaScript 能够同时控制页面内容和浏览器的行为,使得网页可以动态且与用户交互。
- 表示网页结构:
DOM
将 HTML 文档映射成一个树形结构,树中的每个节点都是页面的一部分,如元素、属性或文本。 - 动态操作文档:JavaScript 可以使用
DOM API
操作页面元素,例如更改元素内容、修改样式、添加/删除节点。 - 事件处理:
DOM
还支持事件机制,允许 JavaScript 响应用户的交互,例如点击、输入、滚动等。
浏览器相关API
浏览器提供了丰富的 Web API,这些 API 使开发者可以通过 JavaScript 与浏览器进行交互,操作 DOM、管理数据存储、处理网络请求、响应用户交互等。这些 API 大致可以分为几个主要的类别,涵盖了不同的功能领域。
DOM 操作 API
这些 API 允许开发者访问、修改和操控 HTML 和 CSS,从而改变页面的外观和交互行为。
document.querySelector()
/document.getElementById()
: 查找和获取 DOM 元素。document.createElement()
: 动态创建新 DOM 元素。element.appendChild()
/element.removeChild()
: 操作 DOM 树,插入或删除元素。element.classList
: 操作元素的 CSS 类。element.style
: 动态设置或修改内联样式。innerHTML
/textContent
: 操作元素的 HTML 或文本内容。- 事件相关 API:
addEventListener()
: 为 DOM 元素添加事件监听器,如click
、mouseover
等。
网络请求 API
这些 API 使开发者能够与服务器通信,发送和接收数据,通常用于异步操作。
fetch()
: 现代的网络请求 API,用于发送 HTTP 请求(GET
、POST
等),支持Promise
。1
2
3fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));XMLHttpRequest
: 旧的网络请求方式,但仍然在一些场景中被使用,特别是为了兼容性。WebSocket
: 实现客户端和服务器之间的双向通信,适用于实时数据传输,如聊天应用或游戏。Server-Sent Events (SSE)
: 单向通信,服务器向浏览器推送数据。
存储 API
这些 API 允许在客户端存储数据,支持不同类型的存储机制。
localStorage
: 本地存储,存储键值对,数据在页面刷新或浏览器关闭后仍然存在。sessionStorage
: 会话存储,存储键值对,数据在会话期间有效,一旦关闭页面或标签页,数据即失效。IndexedDB
: 基于事务的 NoSQL 数据库,允许存储大量结构化数据。Cookies
: 小量的数据存储在浏览器中,用于跟踪用户会话,通常用于身份验证等。
图形和多媒体 API
这些 API 允许开发者创建、操作图形和处理音视频,丰富 Web 应用的表现能力。
Canvas API
: 允许在 HTML<canvas>
元素上绘制 2D 图形,适用于图形渲染和游戏开发。WebGL
: 基于 Canvas 的 3D 图形 API,允许通过 JavaScript 访问 GPU,加速 3D 渲染。<audio>
/<video>
元素及 API: 控制音视频播放、暂停、调整音量等。MediaRecorder API
: 用于录制音频或视频流,特别是在直播或视频会议等场景下使用。
工作线程和并发处理 API
这些 API 提供了浏览器中的并发能力,允许执行多线程任务以提升性能。
Web Workers
: 提供多线程执行的能力,允许在后台线程运行 JavaScript,不会阻塞主线程。Service Workers
: 允许在后台运行的脚本,用于缓存和推送通知,提升离线访问和性能。requestIdleCallback()
: 用于在浏览器空闲时间执行非紧急任务,优化主线程性能。requestAnimationFrame()
: 在下一个浏览器重绘之前执行高效的动画或图形渲染代码。
设备访问 API
浏览器提供了多种 API 以访问设备硬件和传感器,使 Web 应用能够与用户设备进行交互。
Geolocation API
: 获取用户的位置信息,适用于基于位置的服务。DeviceOrientation API
: 访问设备的方向传感器(如陀螺仪),通常用于移动设备上的游戏或增强现实。Battery Status API
: 获取设备的电池状态,如充电状态、电量等信息。Camera/Media API (getUserMedia)
: 允许访问设备的摄像头和麦克风,常用于视频会议或拍照应用。Vibration API
: 允许控制设备振动,常用于移动设备。Web Bluetooth API
: 允许 Web 应用与蓝牙设备通信。Web USB API
: 允许 Web 应用直接与 USB 设备进行通信。
安全与加密 API
提供了一些安全功能,以确保数据的加密和身份验证。
Web Crypto API
: 提供加密、解密、签名和哈希功能,支持加密操作和数据签名,确保安全的 Web 通信。Credentials Management API
: 允许 Web 应用管理用户的登录信息,例如自动填充密码和身份验证。
推送通知和后台同步 API
这些 API 用于在 Web 应用中实现后台通信和通知功能。
Push API
: 允许 Web 应用通过服务器向用户发送推送通知,哪怕用户当前不活跃或页面已关闭。Notification API
: 允许应用在用户设备上显示桌面通知。Background Sync API
: 在网络连接恢复时,允许 Web 应用后台同步数据,常用于离线应用。
性能与资源管理 API
提供一些工具来分析和优化 Web 应用的性能。
Performance API
: 提供性能监控功能,用于测量页面加载时间、资源获取时间等。Navigator API
: 获取用户代理信息、在线状态、硬件能力(如 CPU 核心数)、语言设置等。Memory Management API
: 提供关于内存使用的统计信息,帮助开发者优化内存管理。
文件操作 API
这些 API 允许 Web 应用与用户的本地文件系统进行交互。
File API
: 允许用户上传文件,并使用 JavaScript 读取文件的内容(如文本、图片等)。FileReader API
: 用于异步读取文件的内容,可以读取为文本、Data URL 或 ArrayBuffer。File System Access API
: 允许 Web 应用访问和管理本地文件系统,提供直接的文件读写操作(尚未广泛支持)。
历史和导航 API
这些 API 用于管理浏览器历史记录和页面导航。
History API
: 允许操作浏览器的历史记录,使用pushState()
和replaceState()
修改 URL 而不会刷新页面。Location API
: 提供有关当前页面 URL 的信息,并允许更改 URL。
事件处理 API
提供事件模型和机制,允许开发者监听并响应用户输入事件。
addEventListener()
: 绑定 DOM 事件,如click
、scroll
、keydown
等。CustomEvent
: 创建和触发自定义事件,用于自定义应用中的事件机制。
总结
浏览器提供的 API 覆盖了从基础的 DOM 操作到高级的设备访问、性能优化、网络请求、并发处理等多方面的功能。它们帮助开发者创建功能丰富、响应迅速的 Web 应用,同时提升用户体验。每个 API 都针对特定的需求和功能场景,使得 Web 应用能够与设备、服务器和用户之间进行强大的交互。
requestIdleCallback
是一个浏览器提供的 API,用来在浏览器的空闲时间执行一些不紧急的、后台任务或低优先级任务。这个 API 主要用于在不阻塞主线程的情况下执行非关键性操作,比如数据预加载、日志记录、UI 渲染后的性能监控等。
requestIdleCallback
是一种让开发者可以充分利用浏览器渲染空闲时间的机制,确保在用户体验不受影响的情况下处理一些后台任务,而不会影响 UI 的流畅度。
基本使用
requestIdleCallback
接收一个回调函数,当浏览器有空闲时间时,会执行这个回调函数。你可以通过可选的超时参数来确保某些任务不会无限期等待。
1 | // 基本用法 |
参数说明:
**回调函数 (
callback
)**:回调函数接收一个IdleDeadline
对象作为参数,该对象包含两个属性:- **
timeRemaining()
**:返回当前帧内剩余的空闲时间(以毫秒为单位),如果为 0,意味着当前没有剩余时间。 - **
didTimeout
**:布尔值,表示该回调函数是否因为超时而被执行。
- **
**可选参数 (
options
)**:- **
timeout
**:设定一个超时时间(以毫秒为单位),即使浏览器没有空闲时间,也会在超时时间后强制执行回调函数。
- **
示例代码:
假设我们有一些低优先级的任务需要在浏览器空闲时执行,比如在页面加载后预加载下一页的数据。这是一个简单的例子:
1 | function preloadData() { |
在这个例子中,requestIdleCallback
会在浏览器的空闲时间预加载数据,但不会打断主线程的 UI 渲染,保证了用户的交互体验。
主要应用场景
requestIdleCallback
常用于以下场景:
- 后台任务处理:适用于那些非紧急的后台任务,比如分析数据、记录日志、预加载内容、缓存更新等。
- 优化性能:通过利用空闲时间执行低优先级任务,减少主线程的压力,提升用户的体验。
- 数据预加载:页面加载完毕后,使用空闲时间提前预加载下一步的数据(比如用户未请求的部分页面资源)。
- 避免主线程阻塞:在不打断用户交互或影响页面流畅度的前提下,执行大量运算或者 DOM 操作。
API 原理
requestIdleCallback
的核心在于它的设计目标是让开发者可以更好地利用浏览器的“空闲时间”。它的底层工作机制是:
- 浏览器每一帧会进行渲染、布局、绘制等工作。这些任务是有较高优先级的,必须尽快完成,以确保用户界面流畅。
- 一帧完成后,浏览器如果检测到有剩余时间,才会调用通过
requestIdleCallback
注册的回调函数,这时剩余的时间就叫做空闲时间。 - 如果浏览器没有足够的空闲时间,则会等到未来的某个空闲时间才执行回调。
- 如果指定了
timeout
参数,浏览器会在超时时间到了之后强制执行回调,即使没有空闲时间。
IdleDeadline
对象
在回调函数中,浏览器会传递一个 IdleDeadline
对象,提供了一些有用的信息:
**
timeRemaining()
**:- 它是一个动态值,表示当前帧剩余的可用空闲时间(以毫秒为单位)。如果
timeRemaining()
的值是 0,意味着浏览器不再有空闲时间执行额外任务,任务应该被推迟到下一次空闲时间执行。 - 通常在一帧的剩余空闲时间不超过 50 毫秒。
- 它是一个动态值,表示当前帧剩余的可用空闲时间(以毫秒为单位)。如果
**
didTimeout
**:didTimeout
是一个布尔值,表示回调是否因为超时而被强制执行(即浏览器已经没有空闲时间了,但因为设置了timeout
参数,回调仍然被执行)。
性能优化与局限性
尽管 requestIdleCallback
是一个非常有用的 API,但它并不适用于所有场景。以下是一些需要注意的事项:
不适合高优先级任务:由于
requestIdleCallback
仅在空闲时间才执行回调,它并不适用于那些需要及时响应的高优先级任务(比如用户输入响应、关键路径的渲染任务)。对于这类任务,应该优先使用常规的事件处理和同步渲染。不保证回调执行的时间:如果浏览器没有空闲时间,回调函数可能会延迟很久,甚至不会执行。因此,如果你需要在一定时间内强制执行回调,应该设置
timeout
。浏览器支持有限:虽然现代浏览器大多支持
requestIdleCallback
,但一些老旧版本的浏览器并不支持。在不支持的浏览器中,可以通过setTimeout
创建一个降级的 polyfill。
Polyfill(降级处理)
在某些不支持 requestIdleCallback
的环境中,你可以手动创建一个 Polyfill:
1 | if (!window.requestIdleCallback) { |
这个简单的 polyfill 模拟了 requestIdleCallback
的行为,虽然无法完全复制浏览器空闲时间的精确度,但至少可以确保回调函数在一定的时间间隔内执行。
总结
requestIdleCallback
是一个用于在浏览器空闲时间执行后台任务的 API,可以帮助开发者优化性能,尤其是在执行低优先级任务时,不会阻塞用户界面的响应和渲染。它的原理依赖于浏览器的任务调度系统,利用每帧的空闲时间执行额外任务。在使用时,要注意任务的优先级和潜在的执行延迟,以确保对用户体验的影响最小。
unload
和 DOMContentLoaded
是两个常用的浏览器事件,它们在页面的不同加载和卸载阶段被触发,分别用于处理页面资源加载完成或即将离开页面时的情况。理解它们的触发时机和使用场景,可以帮助开发者更好地管理 Web 页面资源、性能和交互行为。
DOMContentLoaded
事件
触发时机:
DOMContentLoaded
事件会在 HTML 文档被完全解析 且 DOM 树已构建完成 后触发。- 它不会等待样式表、图片、iframe 等外部资源的加载完成,只要 DOM 结构本身已经准备好,事件就会被触发。因此,它的触发时间通常比
load
事件更早。
典型场景:
- 当你只需要操作 DOM 元素或者初始化页面的基本逻辑,而不依赖于页面中的外部资源(如图片、视频)的加载时,
DOMContentLoaded
是非常理想的事件。 - 可以通过这个事件确保在脚本中可以安全地操作所有 DOM 元素。
用法:
1 | document.addEventListener("DOMContentLoaded", function() { |
工作原理:
- 当浏览器解析 HTML 文档时,它会构建 DOM 树。
DOMContentLoaded
事件在文档解析完成后立即触发,无需等待图像、样式表或其他资源的加载完成。 - 该事件适合于希望尽早执行 DOM 操作的场景,尤其是在需要高响应的 Web 应用中,避免等待所有资源加载的额外时间开销。
unload
事件
触发时机:
unload
事件会在页面即将被卸载(如用户关闭窗口、刷新页面或导航到其他页面)时触发。- 它标志着当前页面的生命周期即将结束,页面将被移除。通常用于执行清理任务,比如释放资源、保存用户状态或发送分析数据等。
典型场景:
- 用于在用户离开页面时执行一些清理工作,比如断开 WebSocket 连接、停止媒体播放、取消未完成的网络请求或提交表单数据等。
- 还可以在离开页面之前向服务器发送分析数据,记录用户行为。
用法:
1 | window.addEventListener("unload", function(event) { |
工作原理:
- 当用户即将离开页面时,
unload
事件会被触发。此时,页面的所有资源仍然存在,但已经不再交互。 - 重要的是,
unload
事件中的操作通常是异步的,而浏览器不会等待这些异步操作完成。如果在unload
中执行异步操作(如发送网络请求),则这些任务可能无法完成,因为浏览器很快就会关闭或切换到新页面。
注意事项:
- 现代浏览器在
unload
事件中有严格的限制。为了防止恶意脚本在用户离开页面时阻塞页面卸载进程,很多浏览器可能会忽略或抑制一些操作。 - 使用
unload
事件时,不能依赖其完成所有的清理工作或数据保存。更好的选择是使用beforeunload
事件,它允许用户在离开页面前有机会取消卸载,或者使用异步机制处理数据清理。
区别和对比
特性 | DOMContentLoaded | unload |
---|---|---|
触发时机 | DOM 树完全构建后,但不等待外部资源(如图片、CSS)的加载 | 页面即将被卸载,用户离开页面或刷新页面时 |
用途 | 执行 DOM 操作,初始化脚本 | 页面离开前进行清理工作或保存数据 |
等待资源加载 | 不等待外部资源的加载 | 不适用(页面即将卸载) |
适合的操作 | 操作 DOM 元素,初始化 UI | 释放资源,保存数据,停止后台任务 |
执行的可靠性 | 始终可靠,页面加载过程中会保证触发 | 有些浏览器对异步操作有限制,不一定能完全执行 |
现代替代方案 | load 事件(等待所有资源加载完毕后) |
beforeunload ,允许用户交互,或使用现代框架中的清理机制 |
现代替代方案:beforeunload
beforeunload
是另一个常用的事件,它比 unload
提供更多的灵活性和控制。
beforeunload
触发时机:与unload
类似,但它允许开发者阻止页面卸载,或者提醒用户即将离开页面。如果你希望在页面离开之前弹出确认对话框询问用户,可以使用这个事件。用法:
1 | window.addEventListener("beforeunload", function (event) { |
使用 beforeunload
可以让用户有机会取消关闭页面或刷新页面,是保存用户数据、阻止未保存工作丢失的有效手段。
总结
**
DOMContentLoaded
**:适用于需要尽早操作 DOM 的场景,比如页面初始化或用户界面操作。这时,DOM 已经加载完毕,外部资源(如图片、样式表)可能还没有完全加载。**
unload
**:适用于页面离开时的清理任务,如释放资源或发送离线统计数据,但现代浏览器对其使用有诸多限制。**
beforeunload
**:在需要提示用户或阻止用户意外离开页面时可以使用,允许用户确认是否真的要关闭页面或刷新。
原型链继承的缺点
在 JavaScript 中,原型链继承是最常见的一种继承方式,借助对象的 prototype
属性实现继承关系。然而,尽管原型链继承具有一定的优势,但它也存在一些显著的缺点和局限性。以下是使用原型链继承时常见的缺点:
引用类型属性的共享问题
原型链继承的最大问题是引用类型属性的共享。如果父类(超类)构造函数中包含了引用类型(如数组或对象)的属性,那么所有子类实例都会共享这个引用类型的数据。如果一个子类修改了引用类型的数据,其他子类实例也会受到影响。
1 | function Parent() { |
解释:
- 由于
colors
数组是定义在父类Parent
的实例对象中的,而Child
的原型是Parent
的一个实例,因此 所有Child
的实例都会共享同一个colors
数组。 - 当
child1
修改colors
数组时,child2
的colors
数组也被修改了。
无法向父类构造函数传递参数
通过原型链继承,子类实例没有办法向父类的构造函数传递参数。因为子类的原型指向了父类的实例,而不是通过调用父类的构造函数来创建,这导致不能为父类构造函数传递参数来初始化。
示例:
1 | function Parent(name) { |
解释:
Child
的原型对象是通过new Parent()
创建的,但在创建时没有传递任何参数,因此父类的name
属性无法正确初始化,子类实例的name
属性为undefined
。
子类无法独立定义自己的属性
在原型链继承中,子类的实例属性只能从父类继承,子类实例中的属性是在父类构造函数中初始化的。如果我们想要给每个子类实例独立定义一些属性,就需要额外编写逻辑。否则,子类只能继承父类的属性,且所有子类实例共用父类的引用类型属性。
1 | function Parent(name) { |
- 在这个例子中,虽然
Child
的实例可以设置age
属性,但是name
属性却只能继承自Parent
,且无法在Child
的实例中独立设置它。
无法合理区分实例属性与原型属性
在原型链继承中,父类的实例属性是被子类共享的,因此很容易出现子类中的实例属性和原型属性混淆不清的情况。特别是对于引用类型的属性,当子类实例修改这些属性时,会导致其他子类实例的行为异常。
1 | function Parent() { |
- 在
child1
中,name
属性被修改后,子类对象会首先在自身属性中查找name
,因此不会影响其他实例。 - 但
colors
属性是一个数组类型的引用属性,所有子类实例都共享同一个colors
,所以修改一个实例的colors
,其他实例的colors
也会被修改。
调试困难
由于 JavaScript 使用原型链继承,调试时很容易发生属性查找混淆,尤其是当原型链很深时,子类的属性可能来自多个层级的父类,定位某个属性的定义位置就变得更加困难。
- 例如,当你试图修改一个子类实例的属性时,你可能不知道它是定义在当前实例上,还是定义在原型链上的某个父类构造函数中。这个层级深度的增加会让调试和维护代码变得复杂。
效率问题
当使用原型链继承时,JavaScript 引擎会在原型链上逐级向上查找某个属性或方法。原型链的层级越深,查找的效率就会越低。如果链条过长,性能也会随之下降。
无多继承支持
JavaScript 的原型链继承模式只支持单继承,也就是说,子类只能从一个父类继承属性和方法。对于某些复杂的设计模式,这可能会限制代码的灵活性。
1 | function ParentA() { |
- 子类只能继承一个父类的原型(最后的赋值覆盖了之前的赋值),这使得 JavaScript 原型链继承无法直接支持多继承。
过度依赖原型链,容易破坏封装
JavaScript 中过度使用原型链继承可能会破坏封装性。子类可以访问父类的所有属性和方法,即使这些属性和方法是内部实现的,或者设计时不打算暴露给外部使用的。这样容易导致子类直接依赖父类的内部实现,破坏了封装原则。
总结
虽然原型链继承在 JavaScript 中是实现继承的基本方式,但它也存在不少缺点,特别是在处理引用类型共享、参数传递以及实例独立性方面。这些缺点通常可以通过一些其他的继承模式(如组合继承、寄生组合式继承)来弥补。原型链继承的主要缺点包括:
- 引用类型的属性共享,导致子类实例之间出现干扰。
- 无法向父类构造函数传参,导致父类的属性无法根据子类需求初始化。
- 子类无法独立定义自己的属性。
- 难以区分实例属性和原型属性,容易导致属性覆盖和共享问题。
- 调试困难,特别是在复杂的原型链结构中。
- 性能问题,原型链层次过深时影响性能。
- 不支持多继承。
- 可能破坏封装性,子类可以访问父类的内部实现。
解决这些问题的一个常见做法是结合其他继承方式(如组合继承或寄生组合式继承)来避免原型链继承的这些缺陷。