JS有哪些假值对象

在 JavaScript 中,假值 是在布尔上下文中会被评估为 false 的值。尽管这些值本身不是布尔类型,但在需要强制转换为布尔值时(如在 if 条件或逻辑操作中),它们会被认为是 false

JavaScript 中一共有 6 种假值,它们是:

  1. false :布尔类型中的假值。
  2. 0 :数字 0 被认为是假值,包括 -0
  3. ""(空字符串):长度为 0 的字符串是假值。
  4. null :表示空值,意图上是一个不存在的对象引用。
  5. undefined :表示变量已声明但尚未赋值。
  6. NaN :表示“不是一个数字”(Not-a-Number),通常是数学计算失败的结果。

这些是假值,它们在布尔上下文中都会被强制转换为 false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (!false) {
console.log("false is falsy"); // 输出
}

if (!0) {
console.log("0 is falsy"); // 输出
}

if (!"") {
console.log("empty string is falsy"); // 输出
}

if (!null) {
console.log("null is falsy"); // 输出
}

if (!undefined) {
console.log("undefined is falsy"); // 输出
}

if (!NaN) {
console.log("NaN is falsy"); // 输出
}

除了上面提到的假值,所有其他值在布尔上下文中都被认为是 真值(truthy values),即使它们看起来为空或者没有值。例如:

  • 非空字符串(如 "0""false"
  • 非零数字(如 1, -1
  • 对象(即使是空对象 {}
  • 数组(即使是空数组 []

这些在布尔上下文中会被评估为 true

JS有什么内置对象?

JavaScript 中有许多内置对象,这些对象为开发者提供了强大的功能,可以用来处理各种常见的任务,例如操作数据、处理日期、数学计算、以及与浏览器和 DOM 交互等。

常见的 JavaScript 内置对象分类:

  1. 全局对象

    • **Object**:所有 JavaScript 对象的父对象,包含所有对象的通用属性和方法。
    • **Function**:每个函数都是 Function 的实例。可以用来创建新的函数。
    • **Boolean**:用于创建布尔值对象。
    • **Symbol**:用于创建独特的、不可变的符号值,常用于对象属性的唯一标识符。
    • **Error**:创建表示错误的对象。常用的派生错误类型有 TypeErrorRangeErrorReferenceError 等。
    • **BigInt**:用于表示和操作大整数,超出了 Number 的范围。
  2. 数字和日期处理

    • **Number**:处理数字,包括数字转换和格式化操作。
    • **Math**:提供常用的数学常数和函数(如 Math.PI, Math.random()Math.sqrt() 等)。
    • **Date**:用于处理日期和时间。
  3. 字符串处理

    • **String**:用于创建和操作字符串,提供各种字符串处理的方法。
    • **RegExp**:表示正则表达式,用于字符串模式匹配和替换。
  4. 集合类型(集合、映射)

    • **Array**:表示数组对象,用于存储有序集合的列表,提供大量数组操作方法。
    • **Set**:用于存储唯一值的集合,不允许重复的值。
    • **Map**:用于存储键值对集合,键可以是任意类型。
    • **WeakSet**:类似 Set,但其成员是弱引用,垃圾回收时不考虑其引用的对象。
    • **WeakMap**:类似 Map,但键是弱引用,键必须是对象,垃圾回收时不考虑其引用的对象。
  5. 类型化数组(用于操作二进制数据)

    • **ArrayBuffer**:用于表示原始的二进制数据缓冲区。
    • **TypedArray**:类型化数组,提供多种类型的二进制数据视图,如 Int8Array, Uint8Array, Float32Array 等。
    • **DataView**:通过特定的格式读取和写入 ArrayBuffer 中的数据。
  6. 结构化数据和对象操作

    • **JSON**:用于序列化(JSON.stringify())和反序列化(JSON.parse())JSON 数据格式。
    • **Promise**:表示异步操作的最终完成(或失败)及其结果值。
    • **Proxy**:用于定义对象的自定义行为,如属性查找、赋值、枚举、函数调用等。
    • **Reflect**:提供与 Proxy 相配合的低级别操作。
  7. Web API 相关的内置对象

    • **Window**:表示浏览器窗口的全局对象,包含 DOM、定时、导航等方法。
    • **Document**:表示整个 HTML 文档,提供访问和操作页面内容的接口。
    • **Element**:DOM 元素对象的基类,用于表示 HTML 元素。
    • **Event**:表示浏览器中发生的事件,提供对事件处理的支持。
  8. 其他常见内置对象

    • **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)

  • 作用:渲染进程通常运行在沙盒环境中,以提高浏览器的安全性,防止恶意代码通过渲染进程攻击系统。
  • 主要职责
    • 通过沙盒机制,将渲染进程的权限限制在严格的范围内,防止其访问系统的敏感资源(如文件系统等)。
    • 如果某个渲染进程受到攻击或崩溃,沙盒机制可以确保问题不会扩展到系统其他部分。

总结

浏览器是由多个进程组成的复杂应用程序,每个进程都有特定的职责,互相独立又协作工作。通过这种多进程架构,现代浏览器能够实现稳定性、安全性和性能优化。主要的进程有:

  1. 浏览器主进程:负责管理界面、页面、网络、下载等。
  2. 渲染进程:负责渲染网页内容、执行 JavaScript 和处理用户交互。
  3. GPU 进程:负责加速图形渲染。
  4. 插件进程:管理浏览器插件的加载与执行。
  5. 网络进程:负责处理网络请求和响应。
  6. 存储进程:管理本地存储、数据库等操作。
  7. 服务工作进程:负责后台任务,如离线缓存、推送通知等。
  8. 扩展进程:运行用户安装的浏览器扩展。

这种多进程架构使得浏览器更加健壮,保证即使某个进程崩溃,也不会影响其他进程的正常工作,从而提供更高的稳定性和安全性。

JavaScript有几种方式声明函数?

  1. 函数声明(Function Declaration)

    1
    2
    3
    function functionName(parameters) {
    // 函数体
    }
  2. 函数表达式(Function Expression)

    1
    2
    3
    var functionName = function(parameters) {
    // 函数体
    };
  3. 箭头函数(Arrow Function)(ES6引入):

    1
    2
    3
    var functionName = (parameters) => {
    // 函数体
    };
  4. Function构造函数

    1
    var functionName = new Function('parameters', 'function body');

这些方式各有特点,函数声明和函数表达式是最常见和推荐的方式,而箭头函数是在ES6中引入的一种更简洁的写法,而Function构造函数则较少使用,因为它会在运行时解析字符串,有一定的性能开销和安全风险。

img

原生Javascript获取DOM节点有哪些方式?

document.getElementById

获取具有指定的ID,ID唯一。

1
let a=documentgetElementById('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
2
3
var element = document.querySelector('.myClass');
var element = document.querySelector('#myId');
var element = document.querySelector('div[name="myElement"]');

document.querySelectorAll

返回一个 NodeList,其中包含文档中所有符合指定 CSS 选择器的元素。

1
2
3
var elements = document.querySelectorAll('.myClass');
var elements = document.querySelectorAll('div');
var elements = document.querySelectorAll('div.myClass');

document.forms`

这是一个比较老的方法,用来获取文档中的表单元素。

1
2
var form = document.forms[0]; // 获取第一个表单
var myForm = document.forms['myFormName']; // 通过名称获取表单

document.getElementsByName

这个方法返回一个 NodeList,其中包含所有具有指定的 name 特性的元素。

1
var elements = document.getElementsByName('name');

使用 CSS 属性选择器

你也可以使用 querySelectorquerySelectorAll 方法配合属性选择器来选择具有特定属性的元素。

1
2
var element = document.querySelector('input[type="checkbox"]');
var elements = document.querySelectorAll('input[type="text"]');

使用建议:

  • 如果你只需要选择一个元素,并且该元素具有 ID,那么 getElementById 是最快的选择。
  • 如果需要根据类名、标签名或者更复杂的 CSS 选择器选择多个元素,querySelectorAll 是非常强大的工具。
  • 对于需要兼容性考虑,所有上述方法除了较新的 querySelectorquerySelectorAll,其他都有很好的兼容性。querySelectorquerySelectorAll 主要不支持 IE8 及以下版本。

这些方法都是原生 JavaScript 提供的,无需依赖任何库或框架即可在现代浏览器中使用。

Javascript的this绑定

在JavaScript中,this 关键字是非常核心的一个概念,它引用的是函数执行时的上下文对象。理解this如何绑定到不同的上下文是理解和掌握JavaScript非常重要的一部分。this的绑定规则可以通过以下几种方式确定:

默认绑定

当函数独立调用时(即不作为某个对象的方法或未被显式绑定到某个对象),this 默认绑定到全局对象。在非严格模式下,this 指向全局对象(浏览器中的window,Node.js中的global);在严格模式下(函数或全局代码中使用'use strict';),this 会被绑定到undefined

1
2
3
4
5
function show() {
console.log(this);
}

show(); // 在非严格模式下,输出 window/global;在严格模式下,输出 undefined

隐式绑定

当函数作为对象的方法被调用时,this 隐式绑定到该对象。

1
2
3
4
5
6
7
8
const obj = {
name: 'Example',
show: function() {
console.log(this.name);
}
};

obj.show(); // 输出 'Example'

如果存在多层对象嵌套,this 将绑定到最近一层的对象上。

显式绑定

使用call()apply()bind() 方法可以显式地指定this 的绑定对象。

1
2
3
4
5
6
7
8
9
10
11
function show() {
console.log(this.name);
}

const obj = { name: 'Explicit Example' };

show.call(obj); // 输出 'Explicit Example'
show.apply(obj); // 输出 'Explicit Example'

const boundShow = show.bind(obj);
boundShow(); // 输出 'Explicit Example'

new绑定

当一个函数通过new 关键字被作为构造函数调用时,this 被绑定到新创建的对象上。

1
2
3
4
5
6
function Person(name) {
this.name = name;
}

const person = new Person('New Person');
console.log(person.name); // 输出 'New Person'

箭头函数绑定

箭头函数不使用以上四种规则,而是捕获其所在上下文的this 值,作为自己的this 值,也称为词法绑定。

1
2
3
4
5
6
7
8
9
10
const obj = {
name: 'Arrow',
show: function() {
setTimeout(() => {
console.log(this.name);
}, 100);
}
};

obj.show(); // 输出 'Arrow'

在箭头函数中,this 绑定到外围函数的上下文中,即使是在setTimeout这样的异步函数中。

总结

JavaScript的this 绑定是动态的,除了箭头函数外,其绑定取决于函数调用的上下文。理解这些绑定规则对于编写可预测的JavaScript代码非常重要。

JS主线程有哪些

在JavaScript中,特别是在浏览器环境下,存在一个称为“主线程”的概念,通常也被称为“UI线程”。JavaScript 是单线程运行的,意味着在任一时刻只能执行一个任务。主线程负责执行代码、渲染界面、处理用户的交互操作等任务。这里主要解释的是JavaScript在浏览器中的运行环境:

JavaScript 主线程的职责

  1. 执行代码

    • JavaScript 主线程执行所有的JavaScript代码。无论是从<script>标签引入的代码,还是通过事件触发的函数,都在这个单一的线程上执行。
  2. UI 渲染

    • 浏览器使用主线程来渲染页面。这包括HTML的解析、CSS的应用、DOM的操作以及最终的画面渲染。这也是为什么在执行重计算或大量DOM操作时,页面可能会感觉卡顿或冻结,因为这些操作会占用主线程的资源,影响到UI的更新。
  3. 用户交互

    • 主线程还处理来自用户的所有交互,如点击、滚动、键盘输入等。这意味着如果JavaScript代码执行耗时过长,它可以阻塞用户交互,导致不良的用户体验。

与主线程交互的其他组件

尽管JavaScript是单线程执行的,但现代浏览器和Node.js环境提供了多种机制来处理异步事件和后台任务,以避免阻塞主线程,如:

  1. Web Workers

    • 提供一种在浏览器背景下运行脚本的方法,不影响主线程。Web Workers运行在与主线程完全独立的线程上,可以执行复杂计算或处理大量数据,不会导致界面卡顿。
  2. 事件队列和事件循环

    • 浏览器维护一个事件队列,所有异步事件,如网络请求、定时器触发、用户输入等,都会被排到这个队列中。主线程在执行完当前代码后,会查看这个队列,并处理队列中的事件。
  3. 异步API

    • 浏览器提供了许多异步API,如setTimeoutsetIntervalfetch(网络请求)等,这些API的回调不会立即执行,而是在合适的时机被推入事件队列,待主线程空闲时执行。

注意事项

由于主线程的多重职责,作为开发者在编写JavaScript代码时,需要注意不要执行过于复杂或耗时的同步代码,这可能会阻塞主线程,影响到UI的渲染和用户的交互体验。在处理大量数据或复杂计算时,应考虑使用Web Workers或其他异步处理方法,以提高应用性能和响应性。

事件捕获、目标、冒泡

在Web开发中,事件冒泡是一种事件处理机制,其中从一个元素发起的事件会逐层向上传播到其父元素,直至根元素。有时候,我们可能需要阻止这种冒泡行为,以避免触发父元素上的事件处理函数。这可以通过JavaScript中的事件对象提供的方法来实现。

以下是如何在不同情况下阻止事件冒泡的方法:

使用 event.stopPropagation()

这是阻止事件冒泡的标准方法。当在事件处理函数中调用此方法时,当前事件将不会进一步传播到父元素。

1
2
3
4
element.addEventListener('click', function(event) {
event.stopPropagation();
// 事件处理代码
});

返回 false

在使用jQuery时,你可以通过在事件处理函数中返回false来同时阻止事件冒泡和默认行为。在原生JavaScript中,这种方法只适用于通过HTML属性添加的事件处理器。

1
2
3
4
5
6
7
8
9
10
11
12
// 使用 jQuery
$('#element').on('click', function(event) {
// 事件处理代码
return false; // 阻止冒泡并阻止默认行为
});

// 在HTML元素中直接使用
<button onclick="doSomething(); return false;">Click me</button>

function doSomething() {
// 事件处理代码
}

使用 event.stopImmediatePropagation()

这个方法不仅阻止事件继续冒泡到更高的父元素,还阻止任何当前元素上的其他事件监听器被调用。这是一个比stopPropagation()更强大的方法。

1
2
3
4
element.addEventListener('click', function(event) {
event.stopImmediatePropagation();
// 事件处理代码
});

应用场景

  • 阻止链接默认打开新页面:阻止<a>标签的默认行为。
  • 防止表单提交:阻止表单的自动提交行为。
  • 组织事件传播到其他元素:如在弹出菜单中阻止点击事件传播到背景页面。

注意

虽然阻止事件冒泡可以解决一些问题,但过度使用它可能会导致维护困难,因为这改变了事件的正常流动。合理使用这一特性,并确保理解其对整体交互逻辑的影响。

JavaScript的对象方法

JavaScript的对象(Object)有许多内置的方法,这些方法可以帮助我们操作和管理对象。以下是一些常见的对象方法及其示例:

Object.keys()

返回一个数组,包含对象自身可枚举属性的键。

1
2
3
4
5
6
7
8
const person = {
name: "John",
age: 30,
city: "New York"
};

const keys = Object.keys(person);
console.log(keys); // 输出: ["name", "age", "city"]

Object.values()

返回一个数组,包含对象自身可枚举属性的值。

1
2
3
4
5
6
7
8
const person = {
name: "John",
age: 30,
city: "New York"
};

const values = Object.values(person);
console.log(values); // 输出: ["John", 30, "New York"]

Object.entries()

返回一个数组,包含对象自身可枚举属性的键值对数组。

1
2
3
4
5
6
7
8
const person = {
name: "John",
age: 30,
city: "New York"
};

const entries = Object.entries(person);
console.log(entries); // 输出: [["name", "John"], ["age", 30], ["city", "New York"]]

Object.assign()

将所有可枚举的自身属性从一个或多个源对象复制到目标对象。它返回目标对象。

1
2
3
4
5
6
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);
console.log(returnedTarget); // 输出: { a: 1, b: 4, c: 5 }
console.log(target); // 输出: { a: 1, b: 4, c: 5 }

Object.freeze()

冻结对象:一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。该对象的原型也不能被修改。

1
2
3
4
5
6
7
8
9
const person = {
name: "John",
age: 30
};

Object.freeze(person);

person.age = 40; // 这行代码不会生效
console.log(person.age); // 输出: 30

Object.seal()

密封对象:一个密封的对象不能再添加新的属性,且所有现有属性将变为不可配置的。现有属性的值仍然可以修改。

1
2
3
4
5
6
7
8
9
10
const person = {
name: "John",
age: 30
};

Object.seal(person);

person.age = 40; // 这行代码会生效
person.city = "New York"; // 这行代码不会生效
console.log(person); // 输出: { name: "John", age: 40 }

Object.create()

使用指定的原型对象及其属性创建一个新对象。

1
2
3
4
5
6
7
8
9
const personPrototype = {
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};

const john = Object.create(personPrototype);
john.name = "John";
john.greet(); // 输出: Hello, my name is John

Object.defineProperty()

直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。

1
2
3
4
5
6
7
8
9
10
11
12
const person = {};

Object.defineProperty(person, 'name', {
value: 'John',
writable: false, // 不能修改
enumerable: true,
configurable: true
});

console.log(person.name); // 输出: John
person.name = 'Jane'; // 这行代码不会生效
console.log(person.name); // 输出: John

Object.getOwnPropertyDescriptor()

返回指定对象上一个自有属性对应的属性描述符。

1
2
3
4
5
6
7
8
9
10
11
12
13
const person = {
name: "John"
};

const descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor);
// 输出:
// {
// value: 'John',
// writable: true,
// enumerable: true,
// configurable: true
// }

Object.getPrototypeOf()

返回指定对象的原型(即,内部[[Prototype]]属性的值)。

1
2
3
4
5
6
7
8
9
10
const personPrototype = {
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};

const john = Object.create(personPrototype);
john.name = "John";

console.log(Object.getPrototypeOf(john) === personPrototype); // 输出: true

总结

JavaScript对象有许多内置方法,可以帮助我们操作和管理对象。上述列举的只是其中的一部分,实际开发中可以根据需要选择合适的方法来处理对象。

JavaScript的包装类型

在 JavaScript 中,包装类型(wrapper types)是指为基本数据类型(primitive types)提供对象表示的方法。基本数据类型本身是不可变且没有方法的,但 JavaScript 自动为这些类型创建对应的包装对象,使得我们可以调用它们的方法。

JavaScript 的基本数据类型包括:

  • string
  • number
  • boolean
  • symbol
  • bigint
  • null
  • undefined

包装类型为基本数据类型中的 stringnumberboolean 提供了对象包装类,使它们能暂时表现为对象,可以调用对象方法。具体包括 StringNumberBoolean 三个构造函数。

自动装箱与拆箱

JavaScript 会在某些场合自动将基本数据类型转换为对应的对象,称为自动装箱。当调用基本数据类型的属性或方法时,JavaScript 会临时创建一个包装对象,使得我们可以像操作对象一样操作基本类型的值。

例如:

1
2
let str = "hello";
console.log(str.length); // 输出: 5

在这里,str 是一个基本类型(string),但我们可以调用 .length 属性,这是因为 JavaScript 在访问 str.length 时,临时创建了一个 String 对象,从而使 str 拥有对象的属性和方法。

自动装箱的步骤如下:

  1. JavaScript 将基本类型 str 转换为其包装对象 String
  2. 查找 String 对象上的 length 属性并返回。
  3. 之后 String 包装对象被销毁,str 依然是原本的基本类型。

包装类型的例子

String 包装类型

String 对象用于包装字符串,并允许调用与字符串相关的各种方法,如 lengthtoUpperCase() 等。

1
2
3
4
5
6
7
8
let str = "hello";
console.log(str.length); // 输出: 5
console.log(str.toUpperCase()); // 输出: "HELLO"

// 直接创建 String 对象
let strObj = new String("hello");
console.log(typeof strObj); // 输出: "object"
console.log(strObj instanceof String); // 输出: true

Number 包装类型

Number 对象用于包装数字,并允许调用与数字相关的各种方法,如 toFixed()toString() 等。

1
2
3
4
5
6
7
let num = 123;
console.log(num.toFixed(2)); // 输出: "123.00"

// 直接创建 Number 对象
let numObj = new Number(123);
console.log(typeof numObj); // 输出: "object"
console.log(numObj instanceof Number); // 输出: true

Boolean 包装类型

Boolean 对象用于包装布尔值,并允许调用与布尔值相关的方法,如 toString()

1
2
3
4
5
6
7
let bool = true;
console.log(bool.toString()); // 输出: "true"

// 直接创建 Boolean 对象
let boolObj = new Boolean(false);
console.log(typeof boolObj); // 输出: "object"
console.log(boolObj instanceof Boolean); // 输出: true

包装类型与基本类型的区别

尽管包装对象和基本类型类似,它们仍然是不同的类型。使用 typeof 时可以看出:

1
2
3
4
5
let str = "hello";
let strObj = new String("hello");

console.log(typeof str); // 输出: "string"
console.log(typeof strObj); // 输出: "object"

在进行比较时,基本类型和包装对象也可能表现不同:

1
2
3
4
let str = "hello";
let strObj = new String("hello");

console.log(str === strObj); // 输出: false

str 是基本类型,而 strObj 是包装对象,它们不是同一个类型,因此返回 false

不推荐使用显式的包装类型

尽管 JavaScript 支持通过 new 关键字显式创建包装类型对象,但通常不推荐这样做。这样做会让代码变得不直观,而且可能导致一些难以察觉的错误。大多数情况下,依赖 JavaScript 的自动装箱机制就足够了。

坏例子:

1
2
3
let str = new String("hello");
console.log(str == "hello"); // 输出: true
console.log(str === "hello"); // 输出: false

推荐做法:

1
2
3
let str = "hello";
console.log(str == "hello"); // 输出: true
console.log(str === "hello"); // 输出: true

总结来说,JavaScript 的包装类型允许基本数据类型暂时表现为对象,从而调用它们的属性和方法。尽管可以显式创建包装对象,但更推荐使用隐式的自动装箱机制。

如何理解json

基本概念

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它易于人类阅读和编写,也便于机器解析和生成。JSON 的数据结构简单,通常用于在网络上进行数据传输,是现代 Web 开发中的重要数据格式之一。

特点

  1. 轻量级:JSON 采用文本格式,结构简单,便于快速传输。
  2. 易读性:JSON 语法直观,接近 JavaScript 对象的字面量表示,易于阅读和理解。
  3. 语言无关性:虽然 JSON 起源于 JavaScript,但几乎所有编程语言都支持 JSON,可以方便地在不同系统和语言之间交换数据。
  4. 可扩展性:JSON 数据结构灵活,支持嵌套对象和数组,适合描述复杂的数据结构。

JSON 的数据类型

JSON 支持以下几种数据类型:

  • 对象(Object):由键值对组成,键为字符串类型,值可以是任何有效的 JSON 数据类型。对象用花括号 {} 包裹,键值对之间用逗号分隔。
  • 数组(Array):一个有序的值的集合,使用方括号 [] 包裹,元素之间用逗号分隔。
  • 字符串(String):必须使用双引号 ""
  • 数值(Number):支持整数和浮点数,不支持 NaNInfinity
  • 布尔值(Boolean)truefalse
  • 空值(null):表示空值或无值。

示例:

1
2
3
4
5
6
7
8
9
10
11
{
"name": "Alice",
"age": 30,
"isStudent": false,
"skills": ["JavaScript", "Python", "Java"],
"address": {
"city": "New York",
"zip": "10001"
},
"projects": null
}

JSON 与 JavaScript 对象的区别

  • 键必须用双引号:JSON 中的键必须是双引号括起来的字符串,而 JavaScript 对象的键不必使用引号。
  • 不能包含函数:JSON 不支持函数或方法,而 JavaScript 对象可以包含函数作为属性。
  • 数据类型限制:JSON 不支持 undefinedNaNInfinity,而 JavaScript 对象可以。

JSON 的应用场景

  1. 数据传输:JSON 常用于客户端和服务器的数据交换。例如,使用 AJAXFetch API 从服务器请求数据时,通常会收到 JSON 格式的数据。
  2. 配置文件:许多应用和工具(如 ESLint、Babel 等)使用 JSON 文件来存储配置数据。
  3. 数据持久化:一些 NoSQL 数据库(如 MongoDB)直接使用 JSON 格式存储数据。
  4. 跨平台数据交换:JSON 是一种语言无关的格式,可以在不同语言之间交换数据,如 Java、Python、JavaScript 等。

JSON 的解析与生成

在 JavaScript 中,可以使用 JSON.parse()JSON.stringify() 这两个方法来解析和生成 JSON 数据。

解析 JSON 字符串为 JavaScript 对象

1
2
3
const jsonString = '{"name": "Alice", "age": 30}';
const jsonObj = JSON.parse(jsonString);
console.log(jsonObj.name); // 输出: Alice

将 JavaScript 对象转换为 JSON 字符串

1
2
3
const obj = { name: "Alice", age: 30 };
const jsonString = JSON.stringify(obj);
console.log(jsonString); // 输出: {"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),易于人类阅读。
  • MessagePackProtobufAvro:这些是二进制格式,体积更小、效率更高,适合在性能至关重要的场景使用。

总结

JSON 是一种轻量级、语言无关的数据交换格式,适用于客户端与服务器之间的数据传输。它在现代 Web 开发中广泛使用,且具有通用性强、格式清晰等优点,但也存在缺乏复杂数据类型、体积较大等局限性。通过合理地使用 JSON,可以在各种编程语言和环境之间实现高效的数据交换。

判断 JSON 是否为空

实际上是判断 JSON 对象是否为空对象或空数组。由于 JSON 本质上可以是对象或数组,因此我们可以分别判断对象或数组是否为空。

判断 JSON 对象为空

在 JavaScript 中,判断一个 JSON 对象是否为空,可以检查该对象是否没有任何可枚举属性。通常可以使用 Object.keys()Object.values()Object.entries() 来判断。

1
2
3
4
5
6
7
8
const obj = {}; // 空 JSON 对象

// 使用 Object.keys() 判断
if (Object.keys(obj).length === 0) {
console.log("JSON 对象为空");
} else {
console.log("JSON 对象不为空");
}

如果对象的键长度为 0,那么这个对象就是空对象。

判断 JSON 数组为空

对于 JSON 数组,可以简单地通过检查数组的长度来判断是否为空。

1
2
3
4
5
6
7
const arr = []; // 空 JSON 数组

if (arr.length === 0) {
console.log("JSON 数组为空");
} else {
console.log("JSON 数组不为空");
}

数组长度为 0 意味着数组为空。

处理 JSON 字符串

如果你有一个 JSON 字符串,首先需要将其解析为 JavaScript 对象或数组,然后再检查是否为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const jsonString = '{}'; // 或者 '[]'

// 解析 JSON 字符串
const jsonObj = JSON.parse(jsonString);

// 判断是否为空对象或数组
if (Array.isArray(jsonObj)) {
// 如果是数组
if (jsonObj.length === 0) {
console.log("空数组");
} else {
console.log("非空数组");
}
} else if (typeof jsonObj === 'object' && Object.keys(jsonObj).length === 0) {
// 如果是对象且无属性
console.log("空对象");
} else {
console.log("非空对象");
}

判断特殊情况

  • null 值:如果 JSON 数据为 null,可以直接通过比较判断。

    1
    2
    3
    4
    5
    const jsonObj = null;

    if (jsonObj === null) {
    console.log("JSON 为 null");
    }

总结

  • 对于 JSON 对象,可以使用 Object.keys().length 来判断是否为空。
  • 对于 JSON 数组,可以通过检查 array.length 来判断是否为空。
  • 如果是 JSON 字符串,需要先使用 JSON.parse() 将其转换为对象或数组,然后再根据具体类型进行判断。

DOMBOM

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
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<body>
<div id="example">Hello, DOM!</div>
<script>
// 使用 DOM 选择元素并修改内容
const div = document.getElementById('example');
div.textContent = 'DOM has changed!';
</script>
</body>
</html>

在这个例子中,document.getElementById('example') 是通过 DOM API 选择页面中的一个 div 元素,并修改其内容。document 是整个 DOM 的根对象,它代表了整个 HTML 文档。

DOM 的一些常用方法:

方法 说明 返回类型
getElementById() 根据 id 获取单个元素 Elementnull
getElementsByClassName() 根据 class 获取所有匹配的元素 HTMLCollection
getElementsByTagName() 根据标签名获取所有匹配的元素 HTMLCollection
getElementsByName() 根据 name 属性获取所有匹配的元素 NodeList
querySelector() 使用 CSS 选择器获取第一个匹配的元素 Elementnull
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
2
3
4
5
6
7
8
9
10
11
12
13
14
// 访问浏览器的 URL 信息
console.log(window.location.href); // 获取当前页面的 URL

// 重定向到新页面
window.location.href = 'https://example.com';

// 获取浏览器历史记录的长度
console.log(window.history.length);

// 获取用户的屏幕宽度
console.log(window.screen.width);

// 打开一个新窗口
window.open('https://example.com', '_blank');

在上面的例子中,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,代表整个浏览器窗口。它包含了与浏览器环境相关的对象,比如 navigatorlocationhistoryscreen 等。

这两者的功能结合起来,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 元素添加事件监听器,如 clickmouseover 等。

网络请求 API

这些 API 使开发者能够与服务器通信,发送和接收数据,通常用于异步操作。

  • fetch(): 现代的网络请求 API,用于发送 HTTP 请求(GETPOST 等),支持 Promise
    1
    2
    3
    fetch('/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 事件,如 clickscrollkeydown 等。
  • CustomEvent: 创建和触发自定义事件,用于自定义应用中的事件机制。

总结

浏览器提供的 API 覆盖了从基础的 DOM 操作到高级的设备访问、性能优化、网络请求、并发处理等多方面的功能。它们帮助开发者创建功能丰富、响应迅速的 Web 应用,同时提升用户体验。每个 API 都针对特定的需求和功能场景,使得 Web 应用能够与设备、服务器和用户之间进行强大的交互。

requestIdleCallback 是一个浏览器提供的 API,用来在浏览器的空闲时间执行一些不紧急的、后台任务或低优先级任务。这个 API 主要用于在不阻塞主线程的情况下执行非关键性操作,比如数据预加载、日志记录、UI 渲染后的性能监控等。

requestIdleCallback 是一种让开发者可以充分利用浏览器渲染空闲时间的机制,确保在用户体验不受影响的情况下处理一些后台任务,而不会影响 UI 的流畅度。

基本使用

requestIdleCallback 接收一个回调函数,当浏览器有空闲时间时,会执行这个回调函数。你可以通过可选的超时参数来确保某些任务不会无限期等待。

1
2
3
4
5
6
7
8
9
// 基本用法
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
performTask();
}
});

// 或者带有超时参数
requestIdleCallback(myCallback, { timeout: 2000 });

参数说明:

  1. **回调函数 (callback)**:回调函数接收一个 IdleDeadline 对象作为参数,该对象包含两个属性:

    • **timeRemaining()**:返回当前帧内剩余的空闲时间(以毫秒为单位),如果为 0,意味着当前没有剩余时间。
    • **didTimeout**:布尔值,表示该回调函数是否因为超时而被执行。
  2. **可选参数 (options)**:

    • **timeout**:设定一个超时时间(以毫秒为单位),即使浏览器没有空闲时间,也会在超时时间后强制执行回调函数。

示例代码:

假设我们有一些低优先级的任务需要在浏览器空闲时执行,比如在页面加载后预加载下一页的数据。这是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function preloadData() {
console.log("Preloading data for the next page...");
}

function myCallback(deadline) {
// 在空闲时间内执行任务
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
preloadData(); // 执行预加载任务
}

// 如果任务未完成,继续请求下一个空闲回调
if (tasks.length > 0) {
requestIdleCallback(myCallback);
}
}

// 开始请求空闲时间执行任务
requestIdleCallback(myCallback);

在这个例子中,requestIdleCallback 会在浏览器的空闲时间预加载数据,但不会打断主线程的 UI 渲染,保证了用户的交互体验。

主要应用场景

requestIdleCallback 常用于以下场景:

  1. 后台任务处理:适用于那些非紧急的后台任务,比如分析数据、记录日志、预加载内容、缓存更新等。
  2. 优化性能:通过利用空闲时间执行低优先级任务,减少主线程的压力,提升用户的体验。
  3. 数据预加载:页面加载完毕后,使用空闲时间提前预加载下一步的数据(比如用户未请求的部分页面资源)。
  4. 避免主线程阻塞:在不打断用户交互或影响页面流畅度的前提下,执行大量运算或者 DOM 操作。

API 原理

requestIdleCallback 的核心在于它的设计目标是让开发者可以更好地利用浏览器的“空闲时间”。它的底层工作机制是:

  1. 浏览器每一帧会进行渲染、布局、绘制等工作。这些任务是有较高优先级的,必须尽快完成,以确保用户界面流畅。
  2. 一帧完成后,浏览器如果检测到有剩余时间,才会调用通过 requestIdleCallback 注册的回调函数,这时剩余的时间就叫做空闲时间
  3. 如果浏览器没有足够的空闲时间,则会等到未来的某个空闲时间才执行回调。
  4. 如果指定了 timeout 参数,浏览器会在超时时间到了之后强制执行回调,即使没有空闲时间。

IdleDeadline 对象

在回调函数中,浏览器会传递一个 IdleDeadline 对象,提供了一些有用的信息:

  1. **timeRemaining()**:

    • 它是一个动态值,表示当前帧剩余的可用空闲时间(以毫秒为单位)。如果 timeRemaining() 的值是 0,意味着浏览器不再有空闲时间执行额外任务,任务应该被推迟到下一次空闲时间执行。
    • 通常在一帧的剩余空闲时间不超过 50 毫秒。
  2. **didTimeout**:

    • didTimeout 是一个布尔值,表示回调是否因为超时而被强制执行(即浏览器已经没有空闲时间了,但因为设置了 timeout 参数,回调仍然被执行)。

性能优化与局限性

尽管 requestIdleCallback 是一个非常有用的 API,但它并不适用于所有场景。以下是一些需要注意的事项:

  1. 不适合高优先级任务:由于 requestIdleCallback 仅在空闲时间才执行回调,它并不适用于那些需要及时响应的高优先级任务(比如用户输入响应、关键路径的渲染任务)。对于这类任务,应该优先使用常规的事件处理和同步渲染。

  2. 不保证回调执行的时间:如果浏览器没有空闲时间,回调函数可能会延迟很久,甚至不会执行。因此,如果你需要在一定时间内强制执行回调,应该设置 timeout

  3. 浏览器支持有限:虽然现代浏览器大多支持 requestIdleCallback,但一些老旧版本的浏览器并不支持。在不支持的浏览器中,可以通过 setTimeout 创建一个降级的 polyfill。

Polyfill(降级处理)

在某些不支持 requestIdleCallback 的环境中,你可以手动创建一个 Polyfill:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (!window.requestIdleCallback) {
window.requestIdleCallback = function (cb) {
return setTimeout(() => {
const start = Date.now();
cb({
timeRemaining: () => Math.max(0, 50 - (Date.now() - start))
});
}, 1);
};
}

if (!window.cancelIdleCallback) {
window.cancelIdleCallback = function (id) {
clearTimeout(id);
};
}

这个简单的 polyfill 模拟了 requestIdleCallback 的行为,虽然无法完全复制浏览器空闲时间的精确度,但至少可以确保回调函数在一定的时间间隔内执行。

总结

requestIdleCallback 是一个用于在浏览器空闲时间执行后台任务的 API,可以帮助开发者优化性能,尤其是在执行低优先级任务时,不会阻塞用户界面的响应和渲染。它的原理依赖于浏览器的任务调度系统,利用每帧的空闲时间执行额外任务。在使用时,要注意任务的优先级和潜在的执行延迟,以确保对用户体验的影响最小。

unloadDOMContentLoaded 是两个常用的浏览器事件,它们在页面的不同加载和卸载阶段被触发,分别用于处理页面资源加载完成或即将离开页面时的情况。理解它们的触发时机和使用场景,可以帮助开发者更好地管理 Web 页面资源、性能和交互行为。

DOMContentLoaded 事件

触发时机

  • DOMContentLoaded 事件会在 HTML 文档被完全解析DOM 树已构建完成 后触发。
  • 它不会等待样式表、图片、iframe 等外部资源的加载完成,只要 DOM 结构本身已经准备好,事件就会被触发。因此,它的触发时间通常比 load 事件更早。

典型场景

  • 当你只需要操作 DOM 元素或者初始化页面的基本逻辑,而不依赖于页面中的外部资源(如图片、视频)的加载时,DOMContentLoaded 是非常理想的事件。
  • 可以通过这个事件确保在脚本中可以安全地操作所有 DOM 元素。

用法

1
2
3
4
5
6
document.addEventListener("DOMContentLoaded", function() {
console.log("DOM fully loaded and parsed");
// 这里可以操作 DOM 元素
const element = document.getElementById("my-element");
element.textContent = "DOM is ready!";
});

工作原理

  • 当浏览器解析 HTML 文档时,它会构建 DOM 树。DOMContentLoaded 事件在文档解析完成后立即触发,无需等待图像、样式表或其他资源的加载完成。
  • 该事件适合于希望尽早执行 DOM 操作的场景,尤其是在需要高响应的 Web 应用中,避免等待所有资源加载的额外时间开销。

unload 事件

触发时机

  • unload 事件会在页面即将被卸载(如用户关闭窗口、刷新页面或导航到其他页面)时触发。
  • 它标志着当前页面的生命周期即将结束,页面将被移除。通常用于执行清理任务,比如释放资源、保存用户状态或发送分析数据等。

典型场景

  • 用于在用户离开页面时执行一些清理工作,比如断开 WebSocket 连接、停止媒体播放、取消未完成的网络请求或提交表单数据等。
  • 还可以在离开页面之前向服务器发送分析数据,记录用户行为。

用法

1
2
3
4
window.addEventListener("unload", function(event) {
console.log("Page is being unloaded");
// 在这里做一些清理任务,比如保存用户数据或释放资源
});

工作原理

  • 当用户即将离开页面时,unload 事件会被触发。此时,页面的所有资源仍然存在,但已经不再交互。
  • 重要的是,unload 事件中的操作通常是异步的,而浏览器不会等待这些异步操作完成。如果在 unload 中执行异步操作(如发送网络请求),则这些任务可能无法完成,因为浏览器很快就会关闭或切换到新页面。

注意事项

  • 现代浏览器在 unload 事件中有严格的限制。为了防止恶意脚本在用户离开页面时阻塞页面卸载进程,很多浏览器可能会忽略或抑制一些操作。
  • 使用 unload 事件时,不能依赖其完成所有的清理工作或数据保存。更好的选择是使用 beforeunload 事件,它允许用户在离开页面前有机会取消卸载,或者使用异步机制处理数据清理。

区别和对比

特性 DOMContentLoaded unload
触发时机 DOM 树完全构建后,但不等待外部资源(如图片、CSS)的加载 页面即将被卸载,用户离开页面或刷新页面时
用途 执行 DOM 操作,初始化脚本 页面离开前进行清理工作或保存数据
等待资源加载 不等待外部资源的加载 不适用(页面即将卸载)
适合的操作 操作 DOM 元素,初始化 UI 释放资源,保存数据,停止后台任务
执行的可靠性 始终可靠,页面加载过程中会保证触发 有些浏览器对异步操作有限制,不一定能完全执行
现代替代方案 load 事件(等待所有资源加载完毕后) beforeunload,允许用户交互,或使用现代框架中的清理机制

现代替代方案:beforeunload

beforeunload 是另一个常用的事件,它比 unload 提供更多的灵活性和控制。

  • beforeunload 触发时机:与 unload 类似,但它允许开发者阻止页面卸载,或者提醒用户即将离开页面。如果你希望在页面离开之前弹出确认对话框询问用户,可以使用这个事件。

  • 用法

1
2
3
4
5
window.addEventListener("beforeunload", function (event) {
event.preventDefault();
event.returnValue = ''; // 某些浏览器需要设置 returnValue 才能弹出对话框
return ''; // 触发确认对话框
});

使用 beforeunload 可以让用户有机会取消关闭页面或刷新页面,是保存用户数据、阻止未保存工作丢失的有效手段。

总结

  • **DOMContentLoaded**:适用于需要尽早操作 DOM 的场景,比如页面初始化或用户界面操作。这时,DOM 已经加载完毕,外部资源(如图片、样式表)可能还没有完全加载。

  • **unload**:适用于页面离开时的清理任务,如释放资源或发送离线统计数据,但现代浏览器对其使用有诸多限制。

  • **beforeunload**:在需要提示用户或阻止用户意外离开页面时可以使用,允许用户确认是否真的要关闭页面或刷新。

原型链继承的缺点

在 JavaScript 中,原型链继承是最常见的一种继承方式,借助对象的 prototype 属性实现继承关系。然而,尽管原型链继承具有一定的优势,但它也存在一些显著的缺点和局限性。以下是使用原型链继承时常见的缺点:

引用类型属性的共享问题

原型链继承的最大问题是引用类型属性的共享。如果父类(超类)构造函数中包含了引用类型(如数组或对象)的属性,那么所有子类实例都会共享这个引用类型的数据。如果一个子类修改了引用类型的数据,其他子类实例也会受到影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent() {
this.colors = ['red', 'blue', 'green'];
}

function Child() {}

Child.prototype = new Parent();

const child1 = new Child();
const child2 = new Child();

child1.colors.push('yellow');

console.log(child1.colors); // ['red', 'blue', 'green', 'yellow']
console.log(child2.colors); // ['red', 'blue', 'green', 'yellow']

解释:

  • 由于 colors 数组是定义在父类 Parent 的实例对象中的,而 Child 的原型是 Parent 的一个实例,因此 所有 Child 的实例都会共享同一个 colors 数组
  • child1 修改 colors 数组时,child2colors 数组也被修改了。

无法向父类构造函数传递参数

通过原型链继承,子类实例没有办法向父类的构造函数传递参数。因为子类的原型指向了父类的实例,而不是通过调用父类的构造函数来创建,这导致不能为父类构造函数传递参数来初始化。

示例:

1
2
3
4
5
6
7
8
9
10
11
function Parent(name) {
this.name = name;
}

function Child() {}

// 原型链继承
Child.prototype = new Parent();

const child1 = new Child();
console.log(child1.name); // undefined

解释:

  • Child 的原型对象是通过 new Parent() 创建的,但在创建时没有传递任何参数,因此父类的 name 属性无法正确初始化,子类实例的 name 属性为 undefined

子类无法独立定义自己的属性

在原型链继承中,子类的实例属性只能从父类继承,子类实例中的属性是在父类构造函数中初始化的。如果我们想要给每个子类实例独立定义一些属性,就需要额外编写逻辑。否则,子类只能继承父类的属性,且所有子类实例共用父类的引用类型属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Parent(name) {
this.name = name || 'Parent';
}

function Child(age) {
this.age = age;
}

Child.prototype = new Parent();

const child1 = new Child(10);
console.log(child1.name); // 'Parent'
console.log(child1.age); // 10
  • 在这个例子中,虽然 Child 的实例可以设置 age 属性,但是 name 属性却只能继承自 Parent,且无法在 Child 的实例中独立设置它。

无法合理区分实例属性与原型属性

在原型链继承中,父类的实例属性是被子类共享的,因此很容易出现子类中的实例属性和原型属性混淆不清的情况。特别是对于引用类型的属性,当子类实例修改这些属性时,会导致其他子类实例的行为异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Parent() {
this.name = 'Parent';
this.colors = ['red', 'blue', 'green'];
}

function Child() {}

Child.prototype = new Parent();

const child1 = new Child();
const child2 = new Child();

// 修改子类1的 name 和 colors
child1.name = 'Child1';
child1.colors.push('yellow');

console.log(child1.name); // 'Child1'
console.log(child2.name); // 'Parent'

console.log(child1.colors); // ['red', 'blue', 'green', 'yellow']
console.log(child2.colors); // ['red', 'blue', 'green', 'yellow'] (也被修改)
  • child1 中,name 属性被修改后,子类对象会首先在自身属性中查找 name,因此不会影响其他实例。
  • colors 属性是一个数组类型的引用属性,所有子类实例都共享同一个 colors,所以修改一个实例的 colors,其他实例的 colors 也会被修改。

调试困难

由于 JavaScript 使用原型链继承,调试时很容易发生属性查找混淆,尤其是当原型链很深时,子类的属性可能来自多个层级的父类,定位某个属性的定义位置就变得更加困难。

  • 例如,当你试图修改一个子类实例的属性时,你可能不知道它是定义在当前实例上,还是定义在原型链上的某个父类构造函数中。这个层级深度的增加会让调试和维护代码变得复杂。

效率问题

当使用原型链继承时,JavaScript 引擎会在原型链上逐级向上查找某个属性或方法。原型链的层级越深,查找的效率就会越低。如果链条过长,性能也会随之下降。

无多继承支持

JavaScript 的原型链继承模式只支持单继承,也就是说,子类只能从一个父类继承属性和方法。对于某些复杂的设计模式,这可能会限制代码的灵活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
function ParentA() {
this.name = 'ParentA';
}

function ParentB() {
this.age = 50;
}

function Child() {}

// 无法同时继承 ParentA 和 ParentB
Child.prototype = new ParentA();
// Child.prototype = new ParentB(); // 这将覆盖 ParentA 的原型
  • 子类只能继承一个父类的原型(最后的赋值覆盖了之前的赋值),这使得 JavaScript 原型链继承无法直接支持多继承。

过度依赖原型链,容易破坏封装

JavaScript 中过度使用原型链继承可能会破坏封装性。子类可以访问父类的所有属性和方法,即使这些属性和方法是内部实现的,或者设计时不打算暴露给外部使用的。这样容易导致子类直接依赖父类的内部实现,破坏了封装原则。

总结

虽然原型链继承在 JavaScript 中是实现继承的基本方式,但它也存在不少缺点,特别是在处理引用类型共享、参数传递以及实例独立性方面。这些缺点通常可以通过一些其他的继承模式(如组合继承寄生组合式继承)来弥补。原型链继承的主要缺点包括:

  1. 引用类型的属性共享,导致子类实例之间出现干扰。
  2. 无法向父类构造函数传参,导致父类的属性无法根据子类需求初始化。
  3. 子类无法独立定义自己的属性
  4. 难以区分实例属性和原型属性,容易导致属性覆盖和共享问题。
  5. 调试困难,特别是在复杂的原型链结构中。
  6. 性能问题,原型链层次过深时影响性能。
  7. 不支持多继承
  8. 可能破坏封装性,子类可以访问父类的内部实现。

解决这些问题的一个常见做法是结合其他继承方式(如组合继承或寄生组合式继承)来避免原型链继承的这些缺陷。