时常查漏补缺,巩固理论知识,无论在哪种环境下,都是很有必要的。下面是对自己javascript 基础的一些反问,一边学习一边整理,会持续更新,保持学习,共勉~
1. 创建对象的可能方法有哪些
在 javascript 中创建对象的方法有很多种,如下所述:
-
对象字面量语法:
对象字面量语法(或对象初始值设定项)是用大括号括起来的一组以逗号分隔的名称-值对。对象字面量属性值可以是任何数据类型,包括数组、函数和嵌套对象。
var object = { name: "Sudheer", age: 34 }; -
对象构造函数:
创建空对象的最简单方法是使用“Object”构造函数。目前不推荐这种方法。
var object = new Object();Object()是一个内置的构造函数,因此不需要“new”关键字。 上面的代码片段可以重写为:var object = Object(); -
Object 的 create 方法:
Object 的 create 方法用于通过传递指定的原型对象和属性作为参数来创建新对象,即这种模式有助于基于现有对象创建新对象。第二个参数是可选的,它用于在新创建的对象上创建属性。
以下代码创建一个新的空对象,其原型为 null。
var object = Object.create(null); -
函数构造函数:
在此方法中,创建任意函数并应用 new 运算符来创建对象实例。
function Person(name) { this.name = name; this.age = 21; } var object = new Person("Sudheer"); -
带原型的函数构造函数:
这类似于函数构造函数,但它使用原型作为其属性和方法。
function Person() {} Person.prototype.name = "Sudheer"; var object = new Person();这相当于使用带有函数原型的 Object.create 方法创建一个实例,然后使用实例和参数作为参数调用该函数。
function func() {} new func(x, y, z);(或者)
// 使用函数原型创建一个新实例。 var newInstance = Object.create(func.prototype) // 使用call调用函数 var result = func.call(newInstance, x, y, z), // 如果结果是非空对象,则使用它,否则仅使用新实例。 console.log(result && typeof result === 'object' ? result : newInstance); -
ES6 类语法:
ES6引入了类特性来创建对象。
class Person { constructor(name) { this.name = name; } } var object = new Person("Sudheer"); -
单例模式:
Singleton 是一种只能实例化一次的对象。 重复调用其构造函数会返回相同的实例。 这样,人们就可以确保他们不会意外创建多个实例。
var object = new (function () { this.name = "Sudheer"; })();
2. 什么是原型链
原型链用于基于现有对象构建新类型的对象。 它类似于基于类的语言中的继承。
对象实例上的原型可通过 Object.getPrototypeOf(object) 或 __proto__ 属性获得,而构造函数上的原型可通过 Object.prototype 获得。
3. call, apply 和 bind
call: call() 方法使用给定的 this 值和一一提供的参数来调用函数
var employee1 = { firstName: "John", lastName: "Rodson" };
var employee2 = { firstName: "Jimmy", lastName: "Baily" };
function invite(greeting1, greeting2) {
console.log(
greeting1 + " " + this.firstName + " " + this.lastName + ", " + greeting2
);
}
invite.call(employee1, "Hello", "How are you?"); // Hello John Rodson, How are you?
invite.call(employee2, "Hello", "How are you?"); // Hello Jimmy Baily, How are you?
apply: 使用给定的“this”值调用函数,并允许你以数组形式传入参数
var employee1 = { firstName: "John", lastName: "Rodson" };
var employee2 = { firstName: "Jimmy", lastName: "Baily" };
function invite(greeting1, greeting2) {
console.log(
greeting1 + " " + this.firstName + " " + this.lastName + ", " + greeting2
);
}
invite.apply(employee1, ["Hello", "How are you?"]); // Hello John Rodson, How are you?
invite.apply(employee2, ["Hello", "How are you?"]); // Hello Jimmy Baily, How are you?
bind: 返回一个新函数,允许你传递任意数量的参数
var employee1 = { firstName: "John", lastName: "Rodson" };
var employee2 = { firstName: "Jimmy", lastName: "Baily" };
function invite(greeting1, greeting2) {
console.log(
greeting1 + " " + this.firstName + " " + this.lastName + ", " + greeting2
);
}
var inviteEmployee1 = invite.bind(employee1);
var inviteEmployee2 = invite.bind(employee2);
inviteEmployee1("Hello", "How are you?"); // Hello John Rodson, How are you?
inviteEmployee2("Hello", "How are you?"); // Hello Jimmy Baily, How are you?
call和apply几乎可以互换。 两者都立即执行当前函数。Call 是用于逗号(分隔列表),Apply 是用于 Array。
bind 创建一个新函数,将“this”设置为传递给 bind() 的第一个参数。
4. 如何比较 Object 和 Map
1. 数据结构:
Object 是 JavaScript 中的基本数据结构,它使用大括号 {} 来表示。键值对以键值对形式存储在对象中,键是字符串或符号,值可以是任意数据类型。
const obj = {
key1: 'value1',
key2: 'value2',
};
Map 是 ES6 引入的新数据结构,它使用 Map 构造函数来创建。与 Object 不同,Map 的键可以是任意数据类型,包括对象、函数和其他原始类型数据。
const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
2. 键的顺序性:
Object 的键是无序的,无法保证键值对的遍历顺序与定义时的顺序一致。
Map 保留键的插入顺序,可以按照插入顺序进行迭代和遍历。
const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
for (let [key, value] of map) {
console.log(key, value);
}
// 输出:
// key1 value1
// key2 value2
3. 性能:
在大型数据集上进行查找和插入操作时,Map 的性能通常比 Object 更好。Map 使用了更高效的哈希算法来实现键值对的存储和检索。
4. 功能扩展性:
Object 在 JavaScript 中广泛使用,并且具有丰富的内置方法和功能。你可以使用 Object.keys()、Object.values()、Object.entries() 等方法来操作对象。
Map 提供了一些额外的方法,如 set()、get()、delete()、has() 等,用于操作键值对。它还提供了 size 属性来获取键值对的数量。
const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
console.log(map.size); // 输出:2
console.log(map.get('key1')); // 输出:value1
5. 什么是一等函数
在 Javascript 中,函数是第一类对象。 一等函数意味着该语言中的函数被像任何其他变量一样对待。
例如,函数可以作为参数传递给其他函数,可以由另一个函数返回,并且可以作为值分配给变量。 例如,在下面的示例中,分配给侦听器的处理程序函数
const handler = () => console.log("This is a click handler function");
document.addEventListener("click", handler);
6. 什么是一阶函数
一阶函数是不接受另一个函数作为参数并且不返回函数作为其返回值的函数。
const firstOrder = () => console.log("I am a first order function!");
7. 什么是高阶函数
高阶函数是接受另一个函数作为参数或返回一个函数作为返回值或两者兼而有之的函数。
const firstOrderFunc = () =>
console.log("Hello, I am a First order function");
const higherOrder = (ReturnFirstOrderFunc) => ReturnFirstOrderFunc();
higherOrder(firstOrderFunc);
8. 什么是一元函数
一元函数(即一元函数)是一种只接受一个参数的函数。 它代表函数接受的单个参数。
让我们举一个一元函数的例子,
const unaryFunction = (a) => console.log(a + 10);
9. 什么是柯里化函数
柯里化是采用具有多个参数的函数并将其转换为每个仅具有单个参数的函数序列的过程。 柯里化 (Currying) 以数学家哈斯克尔·柯里 (Haskell Curry) 的名字命名。 通过应用柯里化,n 元函数变成一元函数。
让我们举一个 n 元函数的例子以及它如何变成柯里化函数,
const multiArgFunction = (a, b, c) => a + b + c;
console.log(multiArgFunction(1, 2, 3)); // 6
const curryUnaryFunction = (a) => (b) => (c) => a + b + c;
curryUnaryFunction(1); // returns a function: b => c => 1 + b + c
curryUnaryFunction(1)(2); // returns a function: c => 3 + c
curryUnaryFunction(1)(2)(3); // returns the number 6
柯里化函数对于提高代码可重用性和函数组合非常有用。
10. 什么是纯函数
纯函数是一种返回值仅由其参数确定的函数,没有任何副作用。 即如果你在应用程序中调用具有相同参数“n”次,那么它将始终返回相同的值。
我们举个例子来看看纯函数和非纯函数的区别,
// 非纯函数
let numberArray = [];
const impureAddNumber = (number) => numberArray.push(number);
// 纯函数
const pureAddNumber = (number) => (argNumberArray) =>
argNumberArray.concat([number]);
// 展示结果
console.log(impureAddNumber(6)); // returns 1
console.log(numberArray); // returns [6]
console.log(pureAddNumber(7)(numberArray)); // returns [6, 7]
console.log(numberArray); // returns [6]
11. let 关键字的用途是什么
let 语句声明一个块作用域局部变量。 因此,使用 let 关键字定义的变量的范围仅限于使用它的块、语句或表达式。 而使用 var 关键字声明的变量用于全局定义变量,或在整个函数中局部定义变量,而不管块作用域如何。
我们举个例子来演示一下用法,
let counter = 30;
if (counter === 30) {
let counter = 31;
console.log(counter); // 31
}
console.log(counter); // 30 (因为 if 块中的变量在这里不存在)
12. let 和 var 有什么区别
| var | let |
|---|---|
| 它从 JavaScript 诞生之初就已经可用 | 作为 ES6 的一部分引入 |
| 函数作用域 | 块作用域 |
| 变量将被提升 | 已提升但未初始化 |
让我们举个例子来看看区别,
function userDetails(username) {
if (username) {
console.log(salary); // undefined, 声明提升var salary,
console.log(age); // ReferenceError,初始化前无法访问age
let age = 30;
var salary = 10000;
}
console.log(salary); //10000, 函数作用域
console.log(age); //error,age 未定义,块作用域
}
userDetails("John");
13. 在 switch 块中重新声明变量
如果你尝试在“switch block”中重新声明变量,则会导致错误,因为只有一个块。 例如,下面的代码块会抛出语法错误,如下所示:
let counter = 1;
switch (x) {
case 0:
let name;
break;
case 1:
let name; // SyntaxError,重新声明的语法错误
break;
}
为了避免此错误,你可以在 case 子句中创建嵌套块并创建新的块作用域词法环境。
let counter = 1;
switch (x) {
case 0: {
let name;
break;
}
case 1: {
let name; // 重新声明不会出现语法错误。
break;
}
}
14. 什么是“暂时性死区”
“暂时性死区”是 JavaScript 中的一种行为,当使用 let 和 const 关键字声明变量时会发生这种情况,但使用 var 则不会。 在 ECMAScript 6 中,在声明之前(在其范围内)访问“let”或“const”变量会导致引用错误。 发生这种情况的时间跨度,即变量绑定的创建和声明之间,称为暂时性死区。
让我们通过一个例子来看看这个行为,
function somemethod() {
console.log(counter1); // undefined
console.log(counter2); // ReferenceError
var counter1 = 1;
let counter2 = 2;
}
15. 什么是 IIFE
IIFE(立即调用函数表达式)是一个 JavaScript 函数,一旦定义就立即运行。 它的签名如下,
(function () {
// logic here
})();
使用 IIFE 的主要原因是获得数据隐私,因为外界无法访问 IIFE 中声明的任何变量。 即,如果你尝试从 IIFE 访问变量,则会抛出如下错误,
(function () {
var message = "IIFE";
console.log(message);
})();
console.log(message); //Error: message is not defined
17. 什么是记忆化
记忆化是一种函数式编程技术,它试图通过缓存先前计算的结果来提高函数的性能。 每次调用记忆函数时,都会使用其参数来索引缓存。 如果数据存在,则可以返回它,而无需执行整个函数。 否则,执行该函数,然后将结果添加到缓存中。
让我们以添加带有记忆功能的功能为例,
const memoizAddition = () => {
let cache = {};
return (value) => {
if (value in cache) {
console.log("Fetching from cache");
return cache[value]; // Here, cache.value cannot be used as property name starts with the number which is not a valid JavaScript identifier. Hence, can only be accessed using the square bracket notation.
} else {
console.log("Calculating result");
let result = value + 20;
cache[value] = result;
return result;
}
};
};
// 从 memoizAddition 返回的函数
const addition = memoizAddition();
console.log(addition(20)); // 输出:计算出40
console.log(addition(20)); // 输出:40缓存
18. 什么是声明提升
提升是一种 JavaScript 机制,其中变量、函数声明和类在代码执行之前被移动到其作用域的顶部。 请记住,JavaScript 仅提升声明,而不提升初始化。
让我们举一个变量提升的简单例子,
console.log(message); // 输出 : undefined
var message = "The variable Has been hoisted";
上面的代码对于解释器来说如下所示:
var message;
console.log(message);
message = "The variable Has been hoisted";
以同样的方式,函数声明也被提升
message("Good morning"); //Good morning
function message(name) {
console.log(name);
}
注意:
如果函数声明是通过变量赋值的方式进行的,那么在变量声明之前是无法访问该函数的。这是因为在 JavaScript 中,变量提升将变量声明提升到作用域的顶部,但不会提升变量的赋值操作。
foo(); // TypeError: foo is not a function
var foo = function() {
console.log("Hello, world!");
};
19. ES6 中的类是什么
在 ES6 中,Javascript 类主要是 JavaScript 现有的基于原型的继承的语法糖。
例如,基于原型的继承写成函数表达式如下:
function Bike(model, color) {
this.model = model;
this.color = color;
}
Bike.prototype.getDetails = function () {
return this.model + " bike has" + this.color + " color";
};
而 ES6 类可以定义为替代方案
class Bike {
constructor(color, model) {
this.color = color;
this.model = model;
}
getDetails() {
return this.model + " bike has" + this.color + " color";
}
}
20. 什么是闭包
闭包是函数和声明该函数的词法环境的组合。 即,它是一个内部函数,可以访问外部或封闭函数的变量。 闭包具有三个作用域链
- 自己的作用域,其中变量在大括号之间定义
- 外部函数的变量
- 全局变量
function Welcome(name) {
var greetingInfo = function (message) {
console.log(message + " " + name);
};
return greetingInfo;
}
var myFunction = Welcome("John");
myFunction("Welcome "); //Output: Welcome John
myFunction("Hello Mr."); //output: Hello Mr.John
根据上面的代码,即使外部函数返回后,内部函数(即greetingInfo)也可以访问外部函数作用域(即Welcome)中的变量。
21. 什么是模块
模块是指独立的、可重用的代码的小单元,也是许多 JavaScript 设计模式的基础。 大多数 JavaScript 模块导出对象字面量、函数或构造函数
22. 作用域是什么
作用域是运行时代码某些特定部分中变量、函数和对象的可访问性。 换句话说,范围决定了代码区域中变量和其他资源的可见性。
23. 什么是 Service Worker
Service Worker 基本上是一个在后台运行的脚本(JavaScript 文件),与网页分开,并提供不需要网页或用户交互的功能。 Service Worker 的一些主要功能包括丰富的离线体验(离线第一个 Web 应用程序开发)、定期后台同步、推送通知、拦截和处理网络请求以及以编程方式管理响应缓存。
24. 使用 Service Worker 操作 DOM
Service Worker 无法直接访问 DOM。 但它可以通过响应通过“postMessage”接口发送的消息来与其控制的页面进行通信,并且这些页面可以操作 DOM。
25. 在 Service Worker 重启过程中重用信息
Service Worker 的问题在于,它会在不使用时终止,并在下次需要时重新启动,因此你不能依赖 Service Worker 的“onfetch”和“onmessage”处理程序中的全局状态。 在这种情况下,服务工作人员将有权访问 IndexedDB API,以便在重新启动时保持和重用。
26. 什么是 IndexedDB
IndexedDB 是一种低级 API,用于客户端存储大量结构化数据(包括文件/blob)。 此 API 使用索引来实现对此数据的高性能搜索。
27. 什么是post message
Post message 消息是一种支持 Window 对象之间跨域通信的方法。(即,页面与其生成的弹出窗口之间,或者页面与嵌入其中的 iframe 之间)。 一般来说,当且仅当页面遵循同源策略(即页面共享相同的协议、端口号和主机)时,不同页面上的脚本才允许相互访问。
28. 什么是 Cookie
Cookie 是存储在你的计算机上供浏览器访问的一段数据。 Cookie 以键/值对的形式保存。 例如,你可以创建一个名为 username 的 cookie,如下所示:
document.cookie = "username=John";
29. 为什么需要 Cookie
Cookie 用于记住有关用户个人资料的信息(例如用户名)。 它基本上涉及两个步骤,
- 当用户访问网页时,用户个人资料可以存储在cookie中。
- 下次用户访问该页面时,cookie 会记住用户配置文件。
30. cookie 中有哪些选项
以下几个选项可用于 cookie,
- 默认情况下,当浏览器关闭时,cookie 将被删除,但你可以通过设置到期日期(以 UTC 时间表示)来更改此行为。
document.cookie = "username=John; expires=Sat, 8 Jun 2019 12:00:00 UTC";
- 默认情况下,cookie属于当前页面。 但是你可以使用路径参数告诉浏览器该 cookie 属于哪个路径。
document.cookie = "username=John; path=/services";
31. 如何删除 cookie
你可以通过将到期日期设置为已过日期来删除 cookie。 在这种情况下,你不需要指定 cookie 值。 例如,你可以删除当前页面中的用户名 cookie,如下所示。
document.cookie =
"username=; expires=Fri, 07 Jun 2019 00:00:00 UTC; path=/;";
注意: 你应该定义 cookie 路径选项以确保删除正确的 cookie。 除非你指定路径参数,否则某些浏览器不允许删除 cookie。
32. cookie、本地存储和会话存储
下面是cookie、本地存储和会话存储之间的一些区别,
| 特征 | Cookie | Local storage | Session storage |
|---|---|---|---|
| 在客户端或服务器端访问 | 服务器端和客户端 | 仅客户端 | 仅客户端 |
| 终身 | 使用 Expires 选项配置 | 直到删除 | 直到选项卡关闭 |
| SSL 支持 | 支持 | 不支持 | 不支持 |
| 最大数据大小 | 4KB | 5 MB | 5MB |
33. 什么是存储事件及其事件柄
StorageEvent 是当另一个文档的上下文中的存储区域发生更改时触发的事件。 而 onstorage 属性是一个用于处理存储事件的 EventHandler 。
window.onstorage = functionRef;
让我们以 onstorage 事件处理程序为例,该处理程序记录存储键及其值
window.onstorage = function (e) {
console.log(
"The " +
e.key +
" key has been changed from " +
e.oldValue +
" to " +
e.newValue +
"."
);
};
34. 为什么需要网络存储
Web存储更加安全,可以在本地存储大量数据,而不影响网站性能。 此外,信息永远不会传输到服务器。 因此,这是比 Cookie 更值得推荐的方法。
35. 如何检查网络存储浏览器支持
在使用网络存储之前,你需要检查浏览器对 localStorage 和 sessionStorage 的支持,
if (typeof Storage !== "undefined") {
// Code for localStorage/sessionStorage.
} else {
// Sorry! No Web Storage support..
}
36. 检查 Web Workers 浏览器支持
使用前需要检查浏览器对网络工作者的支持
if (typeof Worker !== "undefined") {
// code for Web worker support.
} else {
// Sorry! No Web Worker support..
}
37. 举一个web worker的例子
你需要按照以下步骤开始使用web workers进行计数示例
- 创建一个Web Worker 文件:你需要编写一个脚本来增加计数值。 我们将其命名为 counter.js
let i = 0;
function timedCount() {
i = i + 1;
postMessage(i);
setTimeout("timedCount()", 500);
}
timedCount();
这里 postMessage() 方法用于将消息发送回 HTML 页面
- 创建 Web Worker 对象:你可以通过检查浏览器支持来创建 Web Worker 对象。 我们将此文件命名为 web_worker_example.js
if (typeof w == "undefined") {
w = new Worker("counter.js");
}
我们可以接收来自web worker的消息
w.onmessage = function (event) {
document.getElementById("message").innerHTML = event.data;
};
-
终止 Web Worker: Web Worker 将继续侦听消息(即使在外部脚本完成之后),直到其终止。 你可以使用terminate()方法来终止监听消息。
w.terminate(); -
复用Web Worker:如果将worker变量设置为undefined,则可以复用代码
let w = new Worker("counter.js"); // 使用 worker 进行一些操作... // 当操作完成后,将 worker 变量设置为 undefined w = undefined; // 在之后的代码中可以重新使用 worker 变量 w = new Worker("anotherWorker.js");
38. Web Workers对DOM的限制
WebWorkers 无法访问以下 javascript 对象,因为它们是在外部文件中定义的
- Window object
- Document object
- Parent object
39. promise是什么
Promise 是一个对象,它可能会在未来某个时间生成单个值,并带有已解决的值或未解决的原因(例如,网络错误)。 它将处于 3 种可能状态之一:fulfilled, rejected, or pending。
Promise 创建的语法如下所示,
const promise = new Promise(function (resolve, reject) {
// promise description
});
Promise 的用法如下,
const promise = new Promise(
(resolve) => {
setTimeout(() => {
resolve("I'm a Promise!");
}, 5000);
},
(reject) => {}
);
promise.then((value) => console.log(value));
Promise 的操作流程如下:
40. 什么是服务器推送事件
服务器推送事件 (SSE) 是一种服务器推送技术,使浏览器能够通过 HTTP 连接从服务器接收自动更新,而无需诉诸轮询。 这是一种单向通信通道 - 事件仅从服务器流向客户端。 这已用于 Facebook/Twitter 更新、股票价格更新、新闻提要等。
下面是一个使用 SSE 的简单示例,展示了服务器端和客户端的代码:
服务器端代码(Node.js):
const http = require('http');
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
// 设置响应头,指定内容类型为 text/event-stream
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 发送初始的事件数据
res.write('data: Initial data\n\n');
// 定期发送事件数据
setInterval(() => {
const eventData = `data: ${new Date().toISOString()}\n\n`;
res.write(eventData);
}, 1000);
});
// 监听端口
server.listen(3000, () => {
console.log('Server is listening on port 3000');
});
在上述服务器端代码中,我们首先创建了一个 HTTP 服务器。当客户端发起请求时,服务器会设置响应头,包括 Content-Type: text/event-stream,指示服务器将发送 SSE 数据流。然后,服务器发送初始的事件数据,之后每隔一秒发送一个事件数据(当前时间)。
客户端代码(HTML + JavaScript):
<!DOCTYPE html>
<html>
<head>
<title>SSE Example</title>
</head>
<body>
<div id="event-data"></div>
<script>
const eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
const eventData = event.data;
const eventDataElement = document.getElementById('event-data');
eventDataElement.innerText += eventData + '\n';
};
</script>
</body>
</html>
在客户端代码中,我们创建了一个 EventSource 对象,并指定 SSE 的 URL(这里假设为 /sse)。然后,我们监听 onmessage 事件,当接收到服务器发送的事件数据时,将其追加到页面中的 event-data 元素中。
通过运行上述服务器端代码,并在浏览器中打开客户端代码,你将看到页面上不断显示来自服务器的事件数据,每秒更新一次。
41. promise的主要规则是什么
- promise 是一个提供符合标准的
.then()方法的对象 - pending状态的promise可能会转变为fulfilled或rejected状态
- fulfilled或rejected状态的promise是已完成状态,并且不得转变为任何其他状态。
- 一旦promise被完成,其价值就不能改变。
42. null与undefined的区别是什么
| Null | Undefined |
|---|---|
| 它是一个赋值值,表示变量不指向任何对象。 | 这是一个未赋值的变量声明,表示变量已经被声明但尚未被赋予一个值。 |
| null 的类型是 object | undefined 的类型是undefined |
| null 值是一个原始值,表示 null、空或不存在的引用。 | undefined是当变量尚未赋值时使用的原始值。 |
| 指示变量缺少值 | 表示变量本身不存在 |
| 执行原始操作时转换为零 (0) | 执行原始操作时转换为 NaN |
43. window与document有什么区别
| Window | Document |
|---|---|
| 它是任何网页中的根级元素 | 它是窗口对象的直接子对象。 这也称为文档对象模型 (DOM) |
| 默认情况下,窗口对象在页面中隐式可用 | 你可以通过 window.document 或 document 访问它。 |
| 它具有alert()、confirm() 等方法以及文档、位置等属性。 | 它提供了 getElementById、getElementsByTagName、createElement 等方法 |
44. 如何检测大写锁定键是否打开
mouseEvent getModifierState() 用于返回一个布尔值,指示指定的修饰键是否被激活。 CapsLock、ScrollLock 和 NumLock 等修饰键在单击时激活,再次单击时停用。
让我们以一个输入元素来检测 CapsLock 开/关行为为例,
<input type="password" onmousedown="enterInput(event)" />
<p id="feedback"></p>
<script>
function enterInput(e) {
var flag = e.getModifierState("CapsLock");
if (flag) {
document.getElementById("feedback").innerHTML = "CapsLock activated";
} else {
document.getElementById("feedback").innerHTML =
"CapsLock not activated";
}
}
</script>
45. 什么是事件流
事件流是指网页上接收事件的顺序。 当你单击嵌套在各种其他元素中的元素时,在你的单击实际到达其目的地或目标元素之前,它必须首先触发其每个父元素的单击事件,从顶部的全局窗口对象开始。 事件流有两种方式
从上到下(事件捕获) 从下到上(事件冒泡)
46. 如何查找操作系统详细信息
window.navigator 对象包含有关访问者浏览器操作系统详细信息的信息。 一些操作系统属性可在平台属性下找到,
console.log(navigator.platform);
47. document load和 DOMContentLoaded
当初始 HTML 文档完全加载和解析后,将触发 DOMContentLoaded 事件,无需等待资源(样式表、图像和子框架)完成加载。 而 load 事件在整个页面加载后触发,包括所有依赖资源(样式表、图像)。
48. 本机对象、宿主对象和用户对象有什么区别
本机对象是 ECMAScript 规范定义的 JavaScript 语言一部分的对象。 例如,ECMAScript 规范中定义的 String、Math、RegExp、Object、Function 等核心对象。 宿主对象是浏览器或运行时环境(Node)提供的对象。 例如,窗口、XmlHttpRequest、DOM 节点等都被视为宿主对象。 用户对象是在 javascript 代码中定义的对象。 例如,为配置文件信息创建的用户对象。
49. Promise 相对于回调有哪些优缺点
优点:
- 它避免了不可读的回调地狱
- 使用 .then() 轻松编写顺序异步代码
- 使用 Promise.all() 轻松编写并行异步代码
- 解决回调的一些常见问题(回调调用太晚、太早、多次以及吞掉错误/异常)
缺点:
- 它会使代码变得复杂一些。
- 如果不支持 ES6,则需要加载一个 polyfill
50. 属性(Attribute)和属性(Property)
Property是在 HTML 标记上定义的,而Attribute是在 DOM 上定义的。例如,下面的 HTML 元素有两个属性:type 和 value。
<input type="text" value="Name:">
你可以按如下方式检索属性值,
const input = document.querySelector("input");
console.log(input.getAttribute("value")); // Good morning
console.log(input.value); // Good morning
将文本字段的值更改为“晚上好”后,它会变成这样
console.log(input.getAttribute("value")); // Good evening
console.log(input.value); // Good evening
51. void 0 的目的是什么
void(0)用于防止页面刷新。 这将有助于消除不需要的副作用,因为它将返回未定义的原始值。 它通常用于使用 href="JavaScript:Void(0);" 的 HTML 文档。 在<a> 元素内。 即,当你单击链接时,浏览器会加载新页面或刷新同一页面。 但使用此表达式可以防止这种行为。 例如,下面的链接通知消息而不重新加载页面。
<a href="JavaScript:void(0);" onclick="alert('Well done!')">
Click Me!
</a>
52. 谁创建了 JavaScript
JavaScript 是由 Brendan Eich 于 1995 年在 Netscape Communications 工作期间创建的。 最初它以 Mocha 的名称开发,但后来当它首次在 Netscape 测试版中发布时,该语言被正式称为 LiveScript。
53. PreventDefault 方法有什么用
如果事件是可取消的,则 PreventDefault() 方法会取消该事件,这意味着属于该事件的默认操作或行为将不会发生。 例如,在单击提交按钮时阻止表单提交以及在单击超链接时阻止打开页面 URL 是一些常见的用例。
document
.getElementById("link")
.addEventListener("click", function (event) {
event.preventDefault();
});
54. stopPropagation方法有什么用
stopPropagation 方法用于阻止事件在事件链中向上冒泡。 例如,下面带有 stopPropagation 方法的嵌套 div 可防止单击嵌套 div(Div1) 时的默认事件传播
<p>Click DIV1 Element</p>
<div onclick="secondFunc()">DIV 2
<div onclick="firstFunc(event)">DIV 1</div>
</div>
<script>
function firstFunc(event) {
alert("DIV 1");
event.stopPropagation();
}
function secondFunc() {
alert("DIV 2");
}
</script>
55. return false使用涉及哪些步骤
事件处理程序中的 return false 语句执行以下步骤,
- 首先,它会停止浏览器的默认操作或行为。
- 它阻止事件传播 DOM
- 停止回调执行并在调用时立即返回。
56. 什么是BOM
浏览器对象模型 (BOM) 允许 JavaScript 与浏览器“对话”。 它由窗口的子对象navigator, history, screen, location 和 document组成。 浏览器对象模型不是标准化的,可以根据不同的浏览器而改变。
57. 为什么 JavaScript 被视为单线程
JavaScript 是一种单线程语言。 因为语言规范不允许程序员编写代码以便解释器可以在多个线程或进程中并行运行部分代码。 而像java、go、C++这样的语言可以编写多线程和多进程程序。
58. 什么是事件委托
事件委托是一种侦听事件的技术,你可以将父元素委托为其中发生的所有事件的侦听器。
例如,如果你想检测特定表单内的字段更改,你可以使用事件委托技术,
var form = document.querySelector("#registration-form");
// 监听表单内字段的更改
form.addEventListener(
"input",
function (event) {
console.log(event.target);
},
false
);
59. 什么是 ECMAScript
ECMAScript 是构成 JavaScript 基础的脚本语言。 ECMAScript 由 ECMA 国际标准组织在 ECMA-262 和 ECMA-402 规范中标准化。 ECMAScript 第一版于 1997 年发布。
60. 什么是 JSON
JSON(JavaScript 对象表示法)是一种用于数据交换的轻量级格式。 它基于 JavaScript 语言的一个子集,就像在 JavaScript 中构建对象的方式一样。
61. 什么是 PWA
渐进式 Web 应用程序 (PWA) 是一种通过 Web 交付的移动应用程序,使用常见的 Web 技术(包括 HTML、CSS 和 JavaScript)构建。 这些 PWA 部署到服务器,可通过 URL 访问,并由搜索引擎编制索引。
62. 在javascript中获取查询字符串值
你可以使用 URLSearchParams 在 JavaScript 中获取查询字符串值。
const urlParams = new URLSearchParams(window.location.search);
const clientCode = urlParams.get("clientCode");
63. 如何测试空对象
根据ECMAScript版本有不同的解决方案
- 使用对象条目(ECMA 7+):你可以使用对象条目长度以及构造函数类型。
Object.entries(obj).length === 0 && obj.constructor === Object; // 由于日期对象长度为0,因此还需要检查构造函数检查
- 使用对象键(ECMA 5+):你可以使用对象键长度和构造函数类型。
Object.keys(obj).length === 0 && obj.constructor === Object; // 由于日期对象长度为0,因此还需要检查构造函数检查
- 将 for-in 与 hasOwnProperty 结合使用(ECMA 5 之前的版本):你可以将 for-in 循环与 hasOwnProperty 一起使用。
function isEmpty(obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
return false;
}
}
return JSON.stringify(obj) === JSON.stringify({});
}
64. 什么是参数对象 arguments
参数对象arguments是一个类似数组的对象,可在函数内部访问,其中包含传递给该函数的参数值。 例如,让我们看看如何在 sum 函数中使用参数对象,
function sum() {
var total = 0;
for (var i = 0, len = arguments.length; i < len; ++i) {
total += arguments[i];
}
return total;
}
sum(1, 2, 3); // returns 6
注意:你不能对参数对象应用数组方法。 但你可以如下转换为常规数组。
var argsArray = Array.prototype.slice.call(arguments);
65. 什么是js标签
标签语句允许我们在 JavaScript 中命名循环和块。 然后我们可以使用这些标签稍后引用代码。 例如,下面带有标签的代码可以避免在数字相同时打印数字,
var i, j;
loop1: for (i = 0; i < 3; i++) {
loop2: for (j = 0; j < 3; j++) {
if (i === j) {
continue loop1;
}
console.log("i = " + i + ", j = " + j);
}
}
// 结果是:
// "i = 1, j = 0"
// "i = 2, j = 0"
// "i = 2, j = 1"
66. 如何生成随机整数
你可以将 Math.random() 与 Math.floor() 结合使用来返回随机整数。 例如,如果要生成 1 到 10 之间的随机整数,则乘法因子应为 10,
Math.floor(Math.random() * 10) + 1; // 返回 1 到 10 之间的随机整数
Math.floor(Math.random() * 100) + 1; // 返回 1 到 100 之间的随机整数
注意: Math.random() 返回 0(含)和 1(不含)之间的随机数
67. 什么是tree shaking
Tree Shaking 是消除无用代码的一种形式。 这意味着在构建过程中未使用的模块不会包含在捆绑包中,因此它依赖于 ES2015 模块语法的静态结构(即导入和导出)。 最初,这是通过 ES2015 模块捆绑器汇总而普及的。
68. 如何检测手机浏览器
你可以使用正则表达式,它根据用户是否使用移动设备浏览而返回 true 或 false 值。
window.mobilecheck = function () {
var mobileCheck = false;
(function (a) {
if (
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
a
) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
a.substr(0, 4)
)
)
mobileCheck = true;
})(navigator.userAgent || navigator.vendor || window.opera);
return mobileCheck;
};
69. 在没有正则表达式的情况下检测移动浏览器
你只需运行设备列表并检查用户代理是否匹配任何内容即可检测移动浏览器。 这是 RegExp 使用的替代解决方案,
function detectmob() {
if (
navigator.userAgent.match(/Android/i) ||
navigator.userAgent.match(/webOS/i) ||
navigator.userAgent.match(/iPhone/i) ||
navigator.userAgent.match(/iPad/i) ||
navigator.userAgent.match(/iPod/i) ||
navigator.userAgent.match(/BlackBerry/i) ||
navigator.userAgent.match(/Windows Phone/i)
) {
return true;
} else {
return false;
}
}
70. 使用JS获取图像的宽度和高度
你可以使用 Javascript 以编程方式获取图像并检查尺寸(宽度和高度)。
var img = new Image();
img.onload = function () {
console.log(this.width + "x" + this.height);
};
img.src = "http://www.google.com/intl/en_ALL/images/logo.gif";
71. 如何发出同步 HTTP 请求
浏览器提供 XMLHttpRequest 对象,可用于从 JavaScript 发出同步 HTTP 请求
function httpGet(theUrl) {
var xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open("GET", theUrl, false); // false 表示同步请求 request
xmlHttpReq.send(null);
return xmlHttpReq.responseText;
}
72. 在 JavaScript 中将日期转换为另一个时区
你可以使用 toLocaleString() 方法将一个时区的日期转换为另一个时区的日期。 例如,让我们将当前日期转换为英国英语时区,如下所示,
console.log(event.toLocaleString("en-GB", { timeZone: "UTC" })); //29/06/2019, 09:56:00
73. 用于获取窗口大小的属性是什么
你可以使windows, document元素 和 document body对象的innerWidth、innerHeight、clientWidth、clientHeight 属性来查找窗口的大小。 让我们使用这些属性的组合来计算窗口或文档的大小,
var width =
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth;
var height =
window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight;
74. 可以在条件运算符上应用链式吗
可以在条件运算符上应用链接,类似于 if ... else if ... else if ... else 链。 语法如下,
function traceValue(someParam) {
return condition1
? value1
: condition2
? value2
: condition3
? value3
: value4;
}
// 上面的条件运算符相当于
function traceValue(someParam) {
if (condition1) {
return value1;
} else if (condition2) {
return value2;
} else if (condition3) {
return value3;
} else {
return value4;
}
}
75. 页面加载后执行javascript的方式
可以通过多种不同的方式在页面加载后执行 javascript,
- window.onload:
window.onload = function ...
- document.onload:
document.onload = function ...
- body onload:
<body onload="script();">
76. proto和prototype有什么区别
proto 对象是在查找链中用于解析方法等的实际对象。而prototype 是在使用 new 创建对象时用于构建 proto 的对象。
new Employee().__proto__ === Employee.prototype;
new Employee().prototype === undefined;
77. 为什么建议使用分号
建议在 JavaScript 中的每个语句后使用分号。 例如,在下面的情况下,由于缺少分号,它在运行时抛出错误“..不是函数”。
// 定义一个函数
var fn = (function () {
//...
})(
// 此行缺少分号
// 然后在闭包内执行一些代码
function () {
//...
}
)();
它将被解释为
var fn = (function () {
//...
})(function () {
//...
})();
在这种情况下,我们将第二个函数作为参数传递给第一个函数,然后尝试将第一个函数调用的结果作为函数调用。 因此,第二个函数将在运行时失败并出现“...不是函数”错误。
78. freeze方法的作用
freeze() 方法用于冻结对象。 冻结对象不允许向对象添加新属性,防止删除并防止更改现有属性的可枚举性、可配置性或可写性。 即,它返回传递的对象并且不创建冻结副本。
const obj = {
prop: 100,
};
Object.freeze(obj);
obj.prop = 200; // Throws an error in strict mode
console.log(obj.prop); //100
请记住,冻结仅适用于对象中的顶级属性,而不适用于嵌套对象。 例如,
const user = {
name: "John",
employment: {
department: "IT",
},
};
Object.freeze(user);
user.employment.department = "HR"; // 此处department被修改
79. 如何检测浏览器语言首选项
可以使用navigator对象来检测浏览器语言首选项,如下所示,
var language =
(navigator.languages && navigator.languages[0]) || // Chrome / Firefox
navigator.language || // 所有浏览器
navigator.userLanguage; // IE <= 10
console.log(language);
80. 如何检测页面中禁用的 javascript
你可以使用 <noscript> 标签来检测 javascript 是否被禁用。 <noscript> 内的代码块在 JavaScript 被禁用时执行,通常用于在 JavaScript 生成页面时显示替代内容。
<script type="javascript">
// JS相关代码放在这里
</script>
<noscript>
<a href="next_page.html?noJS=true">JavaScript 在页面中被禁用。 请点击下一页</a>
</noscript>
81. javascript中可用的按位运算符
下面是 JavaScript 中使用的按位逻辑运算符列表
按位与 (&) 按位或 (|) 按位异或 (^) 按位非 (~) 左移 (<<) 符号传播右移 ( >> ) 零填充右移 ( >>> )
82. 使用对象确定两个值相同或不同
Object.is() 方法确定两个值是否是相同的值。 例如,不同类型值的用法是:
Object.is("hello", "hello"); // true
Object.is(window, window); // true
Object.is([], []); // false
如果满足以下条件之一,则两个值相同:
- 两者均未定义(undefined)
- 都为空(null)
- 均为true或均为false
- 两个字符串具有相同的长度、相同的字符且顺序相同
- 都是同一个对象(意味着两个对象具有相同的引用)
- 两个数字和两个 +0 两个 -0 两个 NaN 都非零且都不是 NaN 并且两者具有相同的值。
83. seal方法的目的是什么
Object.seal() 方法用于密封对象,方法是防止向对象添加新属性并将所有现有属性标记为不可配置。 但只要当前属性可写,它们的值仍然可以更改。 让我们看下面的例子来了解更多关于 seal() 方法的信息。
const object = {
property: "Welcome JS world",
};
Object.seal(object);
object.property = "Welcome to object world";
console.log(Object.isSealed(object)); // true
delete object.property; // 无法删除
console.log(object.property); //Welcome to object world
注意以下两点
Object.seal()允许修改已有属性的值,但不允许添加和删除属性。Object.freeze()不允许修改、添加和删除属性,对象及其属性值都是不可变的。
84. 随机数范围 [min, max]
产生随机数范围[min, max]为什么是Math.floor(Math.random() * (max + 1 - min)) + min而不是Math.floor(Math.random() * (max + 1)) + min ?
这两个表达式在功能上是相似的,它们都用于生成一个范围在 min 到 max(包括 min 和 max)之间的随机整数。
然而,它们在计算方式上有一些微小的差异:
-
Math.floor(Math.random() * (max + 1 - min)) + min;:- 这个表达式使用了
(max + 1 - min)来确定随机数的范围。 - 首先,
Math.random()生成一个范围在[0, 1)的随机数。 - 然后,将这个随机数乘以
(max + 1 - min),将范围扩大到[0, max + 1 - min)。 - 最后,使用
Math.floor()向下取整,得到一个范围在[0, max + 1 - min - 1]的整数。 - 最后,将结果加上
min,使得随机数的范围变为[min, max]。
- 这个表达式使用了
-
Math.floor(Math.random() * (max + 1)) + min;:- 这个表达式使用了
(max + 1)来确定随机数的范围。 - 首先,
Math.random()生成一个范围在[0, 1)的随机数。 - 然后,将这个随机数乘以
(max + 1),将范围扩大到[0, max + 1)。 - 最后,使用
Math.floor()向下取整,得到一个范围在[0, max]的整数。 - 最后,将结果加上
min,使得随机数的范围变为[min, max + min]。
- 这个表达式使用了
85. Object.create(func)与Object.create(func.prototype)
Object.create(func) 和 Object.create(func.prototype) 之间有一些区别,这涉及到原型链和对象创建的方式。
-
Object.create(func):- 这个语法创建一个新对象,并将
func的原型设置为新对象的原型。 - 新对象的原型链如下:新对象 ->
func.prototype->Object.prototype->null。 - 通过
new func()创建的实例将继承func.prototype上的属性和方法。 - 这种方式常用于实现经典的原型继承。
- 这个语法创建一个新对象,并将
-
Object.create(func.prototype):- 这个语法创建一个新对象,并将
func.prototype直接设置为新对象的原型。 - 新对象的原型链如下:新对象 ->
func.prototype->null。 - 与上面的方式相比,它直接将
func.prototype设置为新对象的原型,省略了Object.prototype的层级。 - 这种方式常用于创建一个仅继承自特定原型的对象。
- 这个语法创建一个新对象,并将
示例:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
const obj1 = Object.create(Person);
console.log(obj1 instanceof Person); // false
console.log(obj1 instanceof Object); // true
const obj2 = Object.create(Person.prototype);
console.log(obj2 instanceof Person); // true
console.log(obj2 instanceof Object); // false
在上面的示例中,Object.create(Person) 创建的对象 obj1 的原型链包含了 Object.prototype,因此它是 Object 的实例,但不是 Person 的实例。
而 Object.create(Person.prototype) 创建的对象 obj2 的原型链只包含了 Person.prototype,因此它是 Person 的实例,但不是 Object 的实例。
需要注意的是,Object.create() 方法创建的对象不会调用构造函数,因此在这两种方式下,新对象不会执行 Person 的构造函数。如果需要执行构造函数并初始化对象,请使用其他方式,例如使用 new 关键字调用构造函数。
86. 如何创建带有原型的对象
Object.create() 方法用于创建具有指定原型对象和属性的新对象。 即,它使用现有对象作为新创建对象的原型。 它返回一个具有指定原型对象和属性的新对象。
const user = {
name: "John",
printInfo: function () {
console.log(`My name is ${this.name}.`);
},
};
const admin = Object.create(user);
admin.name = "Nick"; // 请记住,“name”是在“admin”上设置的属性,而不是在“user”对象上设置的属性
admin.printInfo(); // My name is Nick
87. WeakSet与Set 有什么区别
WeakSet 和 Set 主要区别在于 Set 中对象的引用是强引用,而 WeakSet 中对象的引用是弱引用。 即,如果没有其他引用,WeakSet 中的对象可以被垃圾回收。 其他差异是,
- Sets 可以存储任何值,而 WeakSets 只能存储对象的集合
- WeakSet 与 Set 不同,没有 size 属性
- WeakSet没有clear、keys、values、entries、forEach等方法。
- WeakSet 是不可迭代的。
new WeakSet([iterable]);
var ws = new WeakSet();
var user = {};
ws.add(user);
ws.has(user); // true
ws.delete(user); // removes user from the set
ws.has(user); // false, user has been removed
88. WeakMap 和 Map 有什么区别
主要区别在于,Map 中关键对象的引用是强引用,而 WeakMap 中关键对象的引用是弱引用。 即,如果没有其他引用,WeakMap 中的关键对象可以被垃圾回收。 其他差异是,
- Map 可以存储任何键类型,而 WeakMap 只能存储键对象的集合
- WeakMap 与 Map 不同,没有 size 属性
- WeakMap没有clear、keys、values、entries、forEach等方法。
- WeakMap 是不可迭代的。
new WeakMap([iterable]);
var ws = new WeakMap();
var user = {};
ws.set(user);
ws.has(user); // true
ws.delete(user); // removes user from the map
ws.has(user); // false, user has been removed
89. uneval的目的是什么
uneval() 是一个内置函数,用于创建对象源代码的字符串表示形式。 它是一个顶级函数,不与任何对象关联。 让我们看下面的例子来了解更多关于它的功能,
var a = 1;
uneval(a); // returns a String containing 1
uneval(function user() {}); // returns "(function user(){})"
uneval() 函数已被弃用。 建议对函数使用 toString(),对其他情况使用 JSON.toStringify()。
function user() {}
console.log(user.toString()); // returns "(function user(){})"
90. 使用requestAnimationFrame写一个动画
当使用 requestAnimationFrame 方法执行动画时,可以根据具体的需求来更新动画的状态和绘制帧的逻辑。以下是一个示例,展示了如何更新动画状态和绘制一个简单的移动方块的帧:
// 获取动画区域的元素
const animationArea = document.getElementById('animation-area');
// 定义方块的初始位置和速度
let positionX = 0;
let velocity = 2;
function updateAnimation() {
// 更新动画状态
positionX += velocity;
// 绘制帧
animationArea.style.transform = `translateX(${positionX}px)`;
// 检查是否超出动画区域边界
const animationAreaWidth = animationArea.clientWidth;
const boxWidth = 50; // 假设方块的宽度为 50px
if (positionX + boxWidth > animationAreaWidth || positionX < 0) {
// 反转速度,使方块改变方向
velocity *= -1;
}
// 调用 requestAnimationFrame 更新下一帧
requestAnimationFrame(updateAnimation);
}
// 启动动画
requestAnimationFrame(updateAnimation);
在上述示例中,我们假设有一个具有 id 为 animation-area 的 HTML 元素,表示动画区域。通过获取该元素的引用,我们可以通过修改 transform 属性来改变方块的水平位置。
通过在 updateAnimation 函数的末尾再次调用 requestAnimationFrame(updateAnimation),我们实现了动画的循环,从而在每一帧都更新动画状态和绘制帧。
91. 什么是 JavaScript 访问器
ECMAScript 5 通过 getter 和 setter 引入了 javascript 对象访问器或计算属性。 Getters 使用 get 关键字,而 Setters 使用 set 关键字。
var user = {
firstName: "John",
lastName: "Abraham",
language: "en",
get lang() {
return this.language;
},
set lang(lang) {
this.language = lang;
},
};
console.log(user.lang); // getter访问lang返回 en
user.lang = "fr";
console.log(user.lang); // setter将lang设置为 fr
92. 原始数据类型有哪些
原始数据类型是具有原始值(没有属性或方法)的数据。 原始数据类型有 7 种。
- string
- number
- boolean
- null
- undefined
- bigint
- symbol
注意: 当说一个原始数据类型的值没有属性或方法时,是指该值不能访问或调用任何附加的属性或方法。原始数据类型是基本的数据类型,它们是简单的、不可变的值。
例如,以下是预期的结果
const num = 42;
console.log(num.toString()); // 错误,无法调用方法
const str = "Hello";
console.log(str.length); // 错误,无法获取属性
然而,JavaScript 提供了相应的包装对象(如 Number、Boolean 和 String),这些对象提供了属性和方法的访问。当你尝试在原始值上访问属性或方法时,JavaScript 会自动将原始值转换为相应的包装对象。所以,实际结果如下
const num = 42;
console.log(num.toString()); // 转换为 Number 对象,调用 toString() 方法
const str = "Hello";
console.log(str.length); // 转换为 String 对象,获取 length 属性
93. 函数参数规则是什么
JavaScript 函数遵循以下参数规则,
- 函数定义不指定参数的数据类型。
- 不要对传递的参数执行类型检查。
- 不检查收到的参数数量。
如:
function functionName(parameter1, parameter2, parameter3) {
console.log(parameter1); // 1
}
functionName(1);
94. 什么是error对象
error对象是一个内置的错误对象,它在发生错误时提供错误信息。 它有两个属性:name和message。 例如,以下函数记录错误详细信息,
try {
greeting("Welcome");
} catch (err) {
console.log(err.name + "<br>" + err.message);
}
95. 错误类型有哪些
| 错误名称 | 描述 |
|---|---|
| EvalError | eval() 函数发生错误 |
| RangeError | 发生错误,数字“超出范围” |
| ReferenceError | 由于非法引用而导致的错误 |
| SyntaxError | 由于语法错误而导致的错误 |
| TypeError | 由于类型错误而导致的错误 |
| URIError | 由于encodeURI() 导致的错误 |
96. javascript中的循环有哪两种类型
- 入口控制循环:在这种循环类型中,在进入循环体之前测试测试条件。 例如,For 循环和 While 循环就属于这一类。
- 退出受控循环:在这种循环类型中,测试条件在循环体末尾进行测试或评估。 即,无论测试条件为真还是假,循环体都将至少执行一次。 例如,do-while 循环就属于这一类。
97. 什么是 Intl 对象
Intl 是 JavaScript 中的一个内置对象,它提供了对国际化(Internationalization)和本地化(Localization)的支持。通过 Intl 对象,开发者可以处理不同语言、地区和文化的数据,包括日期和时间、数字格式化、货币、排序等。
Intl 对象提供了一系列的构造函数和静态方法,用于执行各种国际化相关的操作。以下是 Intl 对象的一些常用功能:
-
日期和时间格式化:
Intl.DateTimeFormat构造函数用于格式化日期和时间,根据指定的语言和地区显示不同的日期和时间格式。const date = new Date(); const options = { year: 'numeric', month: 'long', day: 'numeric' }; const formattedDate = new Intl.DateTimeFormat('en-US', options).format(date); console.log(formattedDate); // 输出: February 20, 2024 -
数字格式化:
Intl.NumberFormat构造函数用于格式化数字,根据指定的语言和地区显示适当的数字格式,包括千位分隔符、小数位数等。const number = 12345.6789; const formattedNumber = new Intl.NumberFormat('en-US').format(number); console.log(formattedNumber); // 输出: 12,345.6789 -
货币格式化:
Intl.NumberFormat构造函数也可以用于格式化货币,根据指定的语言和地区显示适当的货币格式。const amount = 12345.67; const currency = 'USD'; const formattedCurrency = new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount); console.log(formattedCurrency); // 输出: $12,345.67 -
排序:
Intl.Collator构造函数用于排序字符串,根据指定的语言和地区进行正确的排序。const strings = ['apple', 'banana', 'cherry', 'Date', 'Eggplant']; const sortedStrings = strings.sort(new Intl.Collator('en-US').compare); console.log(sortedStrings); // 输出: ["apple", "banana", "cherry", "Date", "Eggplant"]
98. 什么是迭代器
迭代器(Iterator)是一种对象,它提供了一种方法来访问集合或序列中的元素,一次一个。通过迭代器,我们可以遍历和访问集合中的元素,而无需暴露集合内部的实现细节。
迭代器是一种抽象的概念,它定义了两个基本方法:
-
next()方法:该方法返回迭代器中的下一个元素,并以对象形式包含两个属性:value和done。value:表示迭代器返回的当前元素的值。done:表示迭代器是否已经遍历完所有元素的布尔值。当迭代器遍历完集合中的所有元素时,done为true;否则为false。
-
return()方法:该方法用于提前终止迭代过程。它允许迭代器在遍历过程中执行清理操作,并返回一个指定的值作为迭代的结果。
迭代器可以用于各种数据结构,如数组、集合、映射和自定义的数据结构。它提供了一种统一的方式来遍历这些数据结构中的元素,无需关心其内部实现。
在 JavaScript 中,迭代器的概念由 Symbol.iterator 符号和 Iterable 接口组成。可迭代对象(Iterable)是具有实现 Symbol.iterator 方法的对象,该方法返回一个迭代器对象。
下面是一个简单的示例,展示了如何使用迭代器遍历数组中的元素:
const array = [1, 2, 3];
// 获取数组的迭代器对象
const iterator = array[Symbol.iterator]();
// 使用迭代器遍历数组元素
let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}
迭代器提供了一种灵活且通用的方式来处理集合中的元素,使得遍历和访问集合变得简单和统一。它在 JavaScript 中被广泛应用于各种迭代操作,如 for...of 循环和数组的高阶方法(如 forEach、map、filter 等)。
100. 事件循环机制(Event Loop)
事件循环(Event Loop)是 JavaScript 中处理异步操作的一种机制。在 JavaScript中,单线程的特性意味着一次只能执行一个任务。如果某个任务需要花费较长时间,那么其他任务就会被阻塞,这会导致用户界面冻结或无响应。为了解决这个问题,JavaScript 使用了事件循环机制。
事件循环的基本原理是将任务分为两类:同步任务和异步任务。同步任务是按照它们出现的顺序依次执行的,而异步任务则在将来的某个时间点执行。
事件循环的流程
- 执行同步任务队列中的任务,直到队列为空。
- 检查是否有异步任务需要执行。如果有,执行这些异步任务,并等待它们完成。
- 当异步任务完成后,将它们添加到异步任务队列。
- 如果存在微任务队列(Promise 、MutationObserver 等),执行微任务队列中的任务,直到队列为空。
- 返回步骤 1,继续执行同步任务。
如何区分宏任务与微任务
根据来源和调度方式进行判断
宏任务:
- 宏任务是由宿主环境(如浏览器或 Node.js)提供的任务。
- 常见的宏任务包括定时器回调(setTimeout、setInterval)、事件回调(DOM 事件处理器)、Ajax 请求回调等。
- 宏任务通过宿主环境的任务队列进行调度和执行。
微任务:
- 微任务是由 JavaScript 引擎提供的任务。
- 常见的微任务包括 Promise 的回调、MutationObserver 的回调等。
- 微任务通过 JavaScript 引擎内部的微任务队列进行调度和执行。
关于区分宏任务和微任务的执行顺序,请注意以下几点:
- 在每个宏任务执行完毕之后,会立即执行所有微任务,然后再执行下一个宏任务。换句话说,微任务总是在宏任务之间执行。
- 微任务具有更高的优先级,它们会在下一个宏任务执行之前执行。这意味着微任务可以在宏任务之间更新应用程序状态或进行其他重要的操作。
理解宏任务和微任务的示例代码如下:
console.log('同步任务1'); // 同步任务1
setTimeout(function() {
console.log('宏任务1'); // 宏任务1
Promise.resolve().then(function() {
console.log('微任务1'); // 微任务1
});
}, 0);
Promise.resolve().then(function() {
console.log('微任务2'); // 微任务2
});
console.log('同步任务3'); // 同步任务3
// 输出:
// 同步任务1
// 同步任务3
// 微任务2
// 宏任务1
// 微任务1
上面脚本的执行顺序是这样的:
- console.log('同步任务1'); 执行同步输出 "同步任务1"。
- setTimeout(...) 设置了一个宏任务(定时器),但不会立即执行里面的回调函数。
- Promise.resolve().then(...) 创建了一个微任务,该任务会在当前宏任务(主脚本的执行)完成后立即执行。
- console.log('同步任务3'); 执行同步输出 "同步任务3"。
- 当前宏任务执行完毕,开始执行微任务队列中的任务。此时,微任务队列中有一个任务,执行输出 "微任务2"。
- 微任务队列清空后,事件循环取出下一个宏任务,即 setTimeout(...) 的回调函数,执行输出 "宏任务2"。
- 在 setTimeout(...) 的回调函数中,Promise.resolve().then(...) 创建了另一个微任务,该任务会在这个宏任务(setTimeout 的回调)执行完之后立即执行,输出 "微任务1"。
101 什么是调用栈
调用堆栈是 JavaScript 解释器跟踪程序中函数调用(创建执行上下文)的数据结构。 它有两个主要动作,
- 每当你调用一个函数来执行它时,函数将被推入堆栈。
- 每当执行完成时,函数就会从堆栈中弹出。
如下示例:
function hungry() {
eatFruits();
}
function eatFruits() {
return "I'm eating fruits";
}
// 调用hungry函数
hungry();
上面的代码在调用堆栈中处理如下,
- 将 hungry() 函数添加到调用堆栈列表中并执行代码。
- 将 eatFruits() 函数添加到调用堆栈列表中并执行代码。
- 从我们的调用堆栈列表中删除 eatFruits() 函数。
- 从调用堆栈列表中删除 hungry() 函数
102. 写一个装饰器
装饰器是一个计算结果为函数的表达式,并将目标、名称和装饰器描述符作为参数。
装饰器定义
-
定义装饰器函数:首先,你需要定义一个装饰器函数。这个函数可以接收多个参数,其中包括被装饰的目标对象、属性或方法的名称,以及一个描述符对象等。
-
修改目标对象:在装饰器函数中,你可以对目标对象进行修改或添加额外的行为。这可以通过修改属性、拦截方法调用、替换方法实现等方式来实现。
-
返回修改后的目标对象:最后,你需要返回经过修改后的目标对象或描述符对象。这是确保装饰器能够正确应用并生效的关键步骤。
创建和应用装饰器的过程如下:
// 定义装饰器函数
function myDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 在装饰器函数中修改目标对象
console.log('Decorating', target, propertyKey);
// 返回修改后的目标对象或描述符对象
return descriptor;
}
// 定义一个类
class MyClass {
@myDecorator
myMethod() {
console.log('Executing myMethod');
}
}
// 创建类的实例并调用装饰后的方法
const instance = new MyClass();
instance.myMethod();
示例
以下是一个自定义装饰器 logTime 的示例代码,它用于记录函数的执行时间:
function logTime(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = Date.now();
const result = originalMethod.apply(this, args);
const end = Date.now();
const executionTime = end - start;
console.log(`Method ${propertyKey} executed in ${executionTime} ms`);
return result;
};
return descriptor;
}
class Example {
@logTime
calculateSum(a: number, b: number) {
return a + b;
}
}
const example = new Example();
console.log(example.calculateSum(2, 3)); // 输出: Method calculateSum executed in X ms
在上述示例中,我们定义了一个名为 logTime 的装饰器函数。它接收三个参数:target、propertyKey 和 descriptor。装饰器函数内部,我们首先保存了原始方法的引用,然后重写了 descriptor.value,即被装饰的方法。在重写的方法中,我们在函数执行前记录了当前时间 start,在函数执行后记录了当前时间 end,并计算了函数的执行时间 executionTime。最后,我们打印出了方法的名称 propertyKey 和执行时间 executionTime。
在示例中,我们将 logTime 装饰器应用于 calculateSum 方法上,当调用 example.calculateSum(2, 3) 时,装饰器会在方法执行前后记录并打印出执行时间。
103. 如何找到数组中的最小值和最大值
可以对数组变量使用 Math.min 和 Math.max 方法来查找数组中的最小和最大元素。
var marks = [50, 20, 70, 60, 45, 30];
function findMin(arr) {
return Math.min.apply(null, arr);
}
function findMax(arr) {
return Math.max.apply(null, arr);
}
console.log(findMin(marks));
console.log(findMax(marks));
104. 如何获取模块的元数据
import.meta 是 JavaScript 中的一个特殊对象,它提供了有关当前模块的元数据信息。它可以用于获取模块的URL、导入资源的URL以及其他与模块相关的信息。
import.meta 对象具有以下常用属性:
-
import.meta.url:返回当前模块的URL,即模块文件在浏览器或服务器上的绝对路径。这对于获取模块的位置信息非常有用。 -
import.meta.scriptElement:返回当前模块的<script>元素。这对于访问和操作加载当前模块的<script>标签很有用。
下面是一些 import.meta 的使用方法和应用场景:
- 动态加载资源:
import.meta.url可以用于构建基于模块位置的动态资源加载路径。例如,当你需要在模块中加载一个附带的 JSON 文件时,可以使用import.meta.url来构建正确的文件路径。
const response = await fetch(`${import.meta.url}/data.json`);
const data = await response.json();
- 模块位置信息:通过访问
import.meta.url,你可以获取到当前模块的位置信息,包括当前模块文件在服务器上的绝对路径。这对于需要了解模块位置的场景非常有用,比如调试或日志记录。
console.log(`当前模块的位置:${import.meta.url}`);
- 动态注入模块:
import.meta可以与动态导入语法 (import()) 结合使用,以动态地加载其他模块。你可以使用import.meta.url来构建动态导入的模块路径。
const modulePath = `${import.meta.url}/otherModule.js`;
const module = await import(modulePath);
- 访问当前模块的
<script>元素:通过import.meta.scriptElement,你可以访问当前模块所对应的<script>元素。这对于在运行时访问和操作当前模块的<script>标签非常有用。
const scriptElement = import.meta.scriptElement;
scriptElement.setAttribute('data-custom-attribute', 'value');
105. 什么是逗号运算符
逗号运算符用于从左到右计算每个操作数并返回最后一个操作数的值。 这与数组、对象和函数参数中的逗号用法完全不同。 例如,数字表达式的用法如下:
var x = 1;
x = (x++, x);
console.log(x); // 2
106. 逗号运算符有什么优点
它通常用于在需要单个表达式的位置包含多个表达式。 该逗号运算符的常见用法之一是在 for 循环中提供多个参数。 例如,下面的 for 循环使用逗号运算符在单个位置使用多个表达式,
for (var a = 0, b =10; a <= 10; a++, b--)
还可以在返回语句中使用逗号运算符,在返回之前进行处理。
function myFunction() {
var a = 1;
return (a += 10), a; // 11
}
107. 什么是构造方法
构造函数方法是用于创建和初始化在类中创建的对象的特殊方法。 如果不指定构造函数方法,则使用默认构造函数。 构造函数的示例用法如下,
class Employee {
constructor() {
this.name = "John";
}
}
var employeeObject = new Employee();
console.log(employeeObject.name); // John
108. 给getPrototype 方法传递字符串类型会发生什么
在 ES5 中,如果 obj 参数不是对象,则会抛出 TypeError 异常。 而在 ES2015 中,参数将被强制为对象。
// ES5
Object.getPrototypeOf("James"); // TypeError: "James" is not an object
// ES2015
Object.getPrototypeOf("James"); // String.prototype
109. 如何防止对象扩展
Object.preventExtensions() 方法用于防止将新属性添加到对象中。 换句话说,它可以防止将来对该对象进行扩展。 让我们看看这个属性的用法,
const newObject = {};
Object.preventExtensions(newObject); // 不可扩展
try {
Object.defineProperty(newObject, "newProperty", {
// Adding new property
value: 100,
});
} catch (e) {
console.log(e); // TypeError: Cannot define property newProperty, object is not extensible
}
110. 使对象不可扩展的不同方法有哪些
可以通过 3 种方式将对象标记为不可扩展,
- Object.preventExtensions
- Object.seal
- Object.freeze
var newObject = {};
Object.preventExtensions(newObject);
Object.isExtensible(newObject); // false
var sealedObject = Object.seal({});
Object.isExtensible(sealedObject); // false
var frozenObject = Object.freeze({});
Object.isExtensible(frozenObject); // false
111. 如何在一个对象上定义多个属性
Object.defineProperties() 方法用于直接在对象上定义新属性或修改现有属性并返回该对象。 让我们在一个空对象上定义多个属性,
const newObject = {};
Object.defineProperties(newObject, {
newProperty1: {
value: "John",
writable: true,
},
newProperty2: {},
});
112. 有哪些 DOM 方法可用于约束验证
以下 DOM 方法可用于对无效输入进行约束验证,
- checkValidity():如果输入元素包含有效数据,则返回 true。
- setCustomValidity():用于设置输入元素的validationMessage属性。 让我们采用带有 DOM 验证的用户登录表单
function myFunction() {
var userName = document.getElementById("uname");
if (!userName.checkValidity()) {
document.getElementById("message").innerHTML =
userName.validationMessage;
} else {
document.getElementById("message").innerHTML =
"Entered a valid username";
}
}
113. 可用的约束验证 DOM 属性有哪些
下面是一些可用的约束验证 DOM 属性的列表,
- validity:它提供与输入元素的有效性相关的布尔属性列表。
- validationMessage:有效性为 false 时显示的消息。
- willValidate:指示输入元素是否将被验证。
114. 如何从get参数中获取值
new URL() 对象接受 url 字符串,并且该对象的 searchParams 属性可用于访问 get 参数。
let urlString = "http://www.some-domain.com/about.html?x=1&y=2&z=3"; //window.location.href
let url = new URL(urlString);
let parameterZ = url.searchParams.get("z");
console.log(parameterZ); // 3
115. 如何打印以逗号作为千位分隔符的数字
可以使用 Number.prototype.toLocaleString() 方法,该方法返回一个具有语言敏感表示形式的字符串,例如该数字的千位分隔符、货币等。
function convertToThousandFormat(x) {
return x.toLocaleString(); // 12,345.679
}
console.log(convertToThousandFormat(12345.6789));
或者
const number = 1234567;
const formattedNumber = number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
console.log(formattedNumber); // 输出 "1,234,567"
116. java 和 javascript 有什么区别
两者是完全不相关的编程语言,并且它们之间没有关系。 Java 是静态类型的、编译的、在它自己的虚拟机上运行。 而 Javascript 是动态输入、解释并在浏览器和 Nodejs 环境中运行的。 让我们看看表格格式的主要区别,
| 特征 | java | JavaScript |
|---|---|---|
| 类型 | 这是一种强类型语言 | 这是一种动态类型语言 |
| 范式 | 面向对象编程 | 基于原型的编程 |
| 范围 | 块作用域 | 函数作用域 |
| 并发 | 基于线程 | 基于事件 |
| 内存 | 使用更多内存 | 使用更少的内存,用于网页 |
117. 如何声明命名空间
尽管 JavaScript 缺乏命名空间,但我们可以使用 Objects 、 IIFE 来创建命名空间。
- 使用对象字面量表示法:让我们将变量和函数包装在充当命名空间的对象字面量中。 可以使用对象表示法访问它们:
var namespaceOne = {
function func1() {
console.log("This is a first definition");
}
}
var namespaceTwo = {
function func1() {
console.log("This is a second definition");
}
}
namespaceOne.func1(); // This is a first definition
namespaceTwo.func1(); // This is a second definition
- 使用 IIFE(立即调用函数表达式):IIFE 外面的一对括号为其内部的所有代码创建了一个局部作用域,并使匿名函数成为函数表达式。 因此,可以在两个不同的函数表达式中创建相同的函数来充当命名空间。
(function () {
function fun1() {
console.log("This is a first definition");
}
fun1();
})();
(function () {
function fun1() {
console.log("This is a second definition");
}
fun1();
})();
- 使用块和 let/const 声明:在 ECMAScript 6 中,可以简单地使用块和 let 声明来将变量的范围限制为块。
{
let myFunction = function fun1() {
console.log("This is a first definition");
};
myFunction();
}
//myFunction(): ReferenceError: myFunction is not defined.
{
let myFunction = function fun1() {
console.log("This is a second definition");
};
myFunction();
}
//myFunction(): ReferenceError: myFunction is not defined.
118. 什么是 V8 JavaScript 引擎
V8 是 Google Chrome 浏览器使用的开源高性能 JavaScript 引擎,用 C++ 编写。 它也被用在 Node.js 项目中。 它实现了 ECMAScript 和 WebAssembly,并在 Windows 7 或更高版本、macOS 10.12+ 以及使用 x64、IA-32、ARM 或 MIPS 处理器的 Linux 系统上运行。 注意:它可以独立运行,也可以嵌入到任何 C++ 应用程序中。
119. 什么是空运算符
void 运算符计算给定的表达式,然后返回未定义的(即不返回值)。 语法如下,
void expression;
void expression;
在没有任何重定向或重新加载的情况下显示一条消息
<a href="javascript:void(alert('Welcome to JS world'))">
Click here to see a message
</a>
120. 如何创建无限循环
可以使用 for 和 while 循环创建无限循环,而无需使用任何表达式。
for (;;) {}
while (true) {}
121. 为什么需要避免 with 语句
JavaScript 的 with 语句旨在提供一种编写对对象的重复访问的简写方式。 因此,它可以通过减少重复冗长的对象引用的需要来帮助减小文件大小,而不会影响性能。 让我们举一个例子,它用于避免多次访问对象时的冗余。
a.b.c.greeting = "welcome";
a.b.c.age = 32;
使用with后的效果如下
with (a.b.c) {
greeting = "welcome";
age = 32;
}
但是这个 with 语句会产生性能问题,因为无法预测参数是引用实数变量还是 with 参数内的属性。
122. const 变量是否使值不可变
const 变量不会使值变得不可变。 但它不允许后续赋值(即可以使用赋值进行声明,但以后不能分配另一个值)
const userList = [];
userList.push("John"); // 不能被重新赋值,但能被改变
console.log(userList); // ['John']
123. 什么是模板标签函数
模板标签函数是一种在编程语言中用于处理模板字符串的机制。它允许你定义自定义的标签或占位符,用于在模板字符串中插入动态内容或执行特定的逻辑操作。
一般来说,模板标签函数由两个部分组成:模板字符串和标签函数。模板字符串是包含占位符的字符串,而标签函数是一个处理该模板字符串的函数。
下面是一个示例模板标签函数的基本结构:
function templateTag(strings, ...expressions) {
// 处理模板字符串的逻辑
// 返回处理后的结果
}
在上面的示例中,templateTag 是模板标签函数的名称。它接受两个参数:
strings:一个字符串数组,包含模板字符串中的原始文本部分。expressions:一个或多个表达式,表示插入到模板字符串中的动态值。
标签函数可以根据需要执行各种操作,例如替换占位符为实际的值、处理条件逻辑、生成动态内容等。它可以使用任何编程语言提供的功能和库来完成这些任务。
下面是一个示例,演示如何使用标签函数处理模板字符串:
var user1 = "John";
var skill1 = "JavaScript";
var experience1 = 15;
var user2 = "Kane";
var skill2 = "JavaScript";
var experience2 = 5;
function myInfoTag(strings, userExp, experienceExp, skillExp) {
var str0 = strings[0]; // "Mr/Ms. "
var str1 = strings[1]; // " is a/an "
var str2 = strings[2]; // "in"
var expertiseStr;
if (experienceExp > 10) {
expertiseStr = "expert developer";
} else if (skillExp > 5 && skillExp <= 10) {
expertiseStr = "senior developer";
} else {
expertiseStr = "junior developer";
}
return `${str0}${userExp}${str1}${expertiseStr}${str2}${skillExp}`;
}
var output1 = myInfoTag`Mr/Ms. ${user1} is a/an ${experience1} in ${skill1}`;
var output2 = myInfoTag`Mr/Ms. ${user2} is a/an ${experience2} in ${skill2}`;
console.log(output1); // Mr/Ms. John is a/an expert developer in JavaScript
console.log(output2); // Mr/Ms. Kane is a/an junior developer in JavaScript
124. 什么是原始字符串
ES6 使用 String.raw() 方法提供了原始字符串功能,该方法用于获取模板字符串的原始字符串形式。 此功能允许你访问输入的原始字符串,而无需处理转义序列。 例如,用法如下:
var calculationString = String.raw`The sum of numbers is \n${
1 + 2 + 3 + 4
}!`;
console.log(calculationString); // The sum of numbers is \n10!
如果不使用原始字符串,则将通过以多行显示输出来处理换行符序列
var calculationString = `The sum of numbers is \n${1 + 2 + 3 + 4}!`;
console.log(calculationString);
// The sum of numbers is
// 10!
此外,raw属性可在标签函数的第一个参数上使用
function tag(strings) {
console.log(strings.raw[0]);
}
125. ~~运算符的目的是什么
~~ 运算符称为双非按位运算符。 该运算符是 Math.floor() 的稍微快一点的替代品。
126. 如何将字符转换为 ASCII 码
可以使用 String.prototype.charCodeAt() 方法将字符串字符转换为 ASCII 数字。 例如,让我们查找“ABC”字符串的第一个字母的 ASCII 代码,
"ABC".charCodeAt(0); // returns 65
而 String.fromCharCode() 方法将数字转换为相同的 ASCII 字符。
String.fromCharCode(65, 66, 67); // returns 'ABC'
127. javascript 是否使用 mixins
Mixin 是一个通用的面向对象编程术语 - 是一个包含可以被其他类使用而无需继承的方法的类。 在 JavaScript 中,我们只能从单个对象继承。 但有时我们需要扩展多个类,为了克服这个问题,我们可以使用 Mixin,它有助于将方法复制到另一个类的原型。
举例来说,我们有两个类 User 和 CleanRoom。 假设我们需要向 User 添加 CleanRoom 功能,以便用户可以按需打扫房间。 这就是 mixin 的概念出现的地方。
// mixin
let cleanRoomMixin = {
cleanRoom() {
alert(`Hello ${this.name}, your room is clean now`);
},
sayBye() {
alert(`Bye ${this.name}`);
},
};
// 使用:
class User {
constructor(name) {
this.name = name;
}
}
// 拷贝方法
Object.assign(User.prototype, cleanRoomMixin);
// 增强后的User能使用cleanRoom方法
new User("Dude").cleanRoom(); // Hello Dude, your room is clean now!
128. 什么是thunk 函数
Thunk 函数是一种用于实现惰性求值(延迟计算)的编程技术。它是一个包装了表达式的函数,用于延迟表达式的求值,将计算推迟到需要的时候进行。
在 JavaScript 中,Thunk 函数通常是一个接受回调函数作为参数的函数。当调用这个函数时,它不会立即执行计算,而是返回一个封装了计算的函数,以及可能需要的上下文信息。该函数可以在后续需要时被调用执行,从而实现延迟计算。
Thunk 函数的主要应用场景之一是在处理异步操作时,用于控制异步操作的执行顺序和结果的处理。它可以将异步操作封装为一个函数,然后按需执行。
以下是一个简单的示例,展示了一个基本的 Thunk 函数实现:
function addAsync(x, y, callback) {
setTimeout(() => {
const result = x + y;
callback(result);
}, 1000);
}
function thunkify(fn) {
return function (...args) {
return function (callback) {
fn.apply(this, args.concat(callback));
};
};
}
const addThunk = thunkify(addAsync);
const thunk = addThunk(3, 4);
thunk(function (result) {
console.log(result); // 输出结果 7
});
129. 为什么0.1 + 0.2 不等于0.3
JavaScript使用IEEE 754标准(一种二进制浮点数算术标准,定义了浮点数的表示方法、运算规则和舍入行为)来表示和处理浮点数。在这个标准中,浮点数被表示为二进制的科学计数法。
然而,由于二进制无法精确表示一些十进制小数,例如0.1和0.2,因此在转换为二进制表示时会出现近似值。具体来说,0.1的二进制表示是一个无限循环的二进制小数,大约是0.0001100110011...,而0.2的二进制表示是另一个无限循环的二进制小数,大约是0.001100110011...。当这两个近似值相加时,产生的结果是一个近似值,大约是0.0010011001100110011...。
由于计算机内部表示的精度有限,无法存储无限循环的小数,因此结果会被截断为一定的位数,导致舍入误差。这就是为什么0.1 + 0.2在JavaScript中不等于0.3。
130. Symbol遵循哪些约定
- 从 Symbol() 返回的每个符号值都是唯一的,与可选字符串无关。
- Symbol.for() 函数在全局符号注册表列表中创建一个符号。 但它不一定在每次调用时创建一个新符号,它首先检查注册表中是否已存在具有给定键的符号,如果找到则返回该符号。 否则在注册表中创建一个新符号。
const sym1 = Symbol("one");
const sym2 = Symbol("one");
const sym3 = Symbol.for("two");
const sym4 = Symbol.for("two");
console.log(sym1 === sym2, sym3 === sym4); // false, true
- undefined、函数和Symbols不是有效的 JSON 值。 因此,这些值要么被省略(在对象中),要么更改为 null(在数组中)。
- 所有Symbol键控属性将被完全忽略。
console.log(
JSON.stringify({ myArray: ["one", undefined, function () {}, Symbol("")] })
); // {"myArray":['one', null,null,null]}
console.log(
JSON.stringify({ [Symbol.for("one")]: "one" }, [Symbol.for("one")])
); // {}
131. 生成器能被重复调用吗
一旦迭代器关闭,生成器就不应重复使用。 即,退出循环时(完成时或使用中断和返回),生成器将关闭,并且尝试再次迭代它不会产生任何更多结果。 因此,第二个循环不打印任何值。
const myGenerator = (function* () {
yield 1;
yield 2;
yield 3;
})();
for (const value of myGenerator) {
console.log(value);
break;
}
for (const value of myGenerator) {
console.log(value);
}
132. console.log(true && '' && 0)输出什么
在逻辑与(&&)运算符中,它会按照从左到右的顺序对操作数进行求值,并返回第一个被判定为假的表达式的值,或者是最后一个表达式的值(如果所有表达式都被判定为真)。
在这个例子中,首先判断 true 是否为真,它是一个布尔值,且为真。然后继续判断 '' 是否为真,空字符串被判定为假。因为逻辑与运算符要求所有操作数都为真,所以当遇到第一个被判定为假的表达式时,整个表达式的结果就确定了。
因此,最终结果为 ''(空字符串),即 console.log(true && '' && 0); 的输出结果为 ''。
133. bind, call, apply 实现
在 JavaScript 中,bind、call 和 apply 都用于改变函数的执行上下文(this 的值),但它们的实现方式略有不同。
-
bind方法:bind方法会创建一个新的函数,该函数的执行上下文(this的值)被永久地绑定到指定的对象。bind方法返回一个绑定了指定上下文的函数,而不会立即执行。当调用绑定函数时,该函数会以绑定的上下文执行。bind方法的实现通常使用了函数闭包和函数的apply方法:Function.prototype.myOwnBind = function (context) { if (typeof this !== "function") { throw new Error(this + " cannot be bound as it's not callable"); } const boundTargetFunction = this; return function (...args) { return boundTargetFunction.apply(context, args); }; }; -
call方法:call方法立即调用函数,并将指定的对象作为执行上下文(this的值)。除了第一个参数是要绑定的上下文外,后续的参数会作为函数的参数传递进去。Function.prototype.myOwnCall = function (context, ...args) { if (typeof this !== "function") { throw new Error(this + " cannot be called as it's not a function"); } context = context || window; // 如果上下文为 null 或 undefined,则默认为全局对象(浏览器中为 window) const uniqueKey = Symbol(); // 创建一个独一无二的键,用于防止上下文对象被篡改 context[uniqueKey] = this; const result = context[uniqueKey](...args); delete context[uniqueKey]; return result; }; -
apply方法:apply方法立即调用函数,并将指定的对象作为执行上下文(this的值)。除了第一个参数是要绑定的上下文外,第二个参数是一个数组(或类数组对象),其中的元素会作为函数的参数传递进去。Function.prototype.myOwnApply = function (context, args) { if (typeof this !== "function") { throw new Error(this + " cannot be called as it's not a function"); } context = context || window; // 如果上下文为 null 或 undefined,则默认为全局对象(浏览器中为 window) const uniqueKey = Symbol(); // 创建一个独一无二的键,用于防止上下文对象被篡改 context[uniqueKey] = this; const result = context[uniqueKey](...args); delete context[uniqueKey]; return result; };
134. [1, 2, 3].toString与Object.prototype.toString.call([1, 2, 3])有什么区别?
当我们直接调用对象的 toString 方法时,如果该对象对 toString 方法进行了重写(覆盖),它会返回自定义的字符串表示形式。这适用于一些内置对象和自定义对象,它们可以通过重写 toString 方法来改变默认的字符串表示。
而 Object.prototype.toString.call 方法是通过显式调用 toString 方法,并将要检测类型的对象作为调用者,以改变 this 上下文。这样做的目的是确保 toString 方法返回的字符串包含了要检测对象的正确类型信息。
因为 Object.prototype.toString 方法是一个内置方法,它不会被普通对象所覆盖。它会读取对象的内部 [[Class]] 属性,并根据该属性返回相应的字符串。
对于普通对象和内置对象来说,它们的 [[Class]] 属性会被正确地识别和返回,因此 Object.prototype.toString.call 方法可以提供准确的类型信息。
总结起来,直接调用对象的 toString 方法可能返回对象自定义的字符串表示形式,而使用 Object.prototype.toString.call 方法能够获得对象的类型信息,不受对象的重写行为影响。
135. 防抖节流的原理与实现
防抖
当短期内有大量的事件触发时,只会执行最后一次触发的事件。其原理如下:
- 定义一个计时器(timer),用于延迟执行动作。
- 当触发动作时,首先清除之前设置的计时器,以确保只有一个计时器在运行。
- 重新设置一个新的计时器,延迟指定的时间后执行动作。
- 如果在延迟时间内再次触发动作,重复步骤 2 和步骤 3,以更新计时器。
- 只有在指定的延迟时间内没有再次触发动作时,执行最终的动作。
function debounce(func, timeout = 500) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { // 每次重置计时,重新延时触发
func.apply(this, args);
}, timeout);
};
}
function fetchResults() {
console.log("Fetching input suggestions");
}
const processChange = debounce(() => fetchResults());
节流
当短期内有大量的事件触发时,只会执行第一次触发的事件。其原理如下:
- 执行动作并锁定触发状态,防止重复触发动作。
- 设置一个延时解锁触发的定时器。
- 当动作再次触发时,判断是否到达延时解锁的时机。
- 如果未到达延时解锁时机,即定时器还在运行中,则不触发动作。
- 如果已经到达延时解锁时机,即定时器已经完成,可以触发动作。
- 当延时解锁时机到达后,自动解锁触发状态,使得下一次触发动作可以执行。
const throttle = (func, limit) => {
let inThrottle;
return (...args) => {
if (!inThrottle) {
func.apply(this, args); // 立即触发
inThrottle = true;
setTimeout(() => (inThrottle = false), limit); // 延时解锁
}
};
};
window.addEventListener("scroll", () => {
throttle(handleScrollAnimation, 100);
});
136 数执行上下文
每当函数在执行时,都会创建一个执行上下文,用于跟踪函数的变量、作用域、参数和其他相关信息。
function greet(name) {
const message = `Hello, ${name}!`;
console.log(message);
}
function sayHello() {
const greeting = "Hello";
greet("Alice");
}
sayHello();
以上面的示例为例,我们有两个函数:greet 和 sayHello,每个函数的执行上下文如下,
-
全局执行上下文:
- 变量环境:包含全局变量和函数声明,如
greet和sayHello。 - 词法环境:与变量环境相同,包含全局作用域的信息。
- this 值:全局对象(在浏览器中通常是
window对象)。 - 外部变量引用:无。
- 变量环境:包含全局变量和函数声明,如
-
sayHello函数执行上下文:- 变量环境:包含变量
greeting。 - 词法环境:包含
sayHello函数作用域和全局作用域的信息。 - this 值:全局对象(在非严格模式下)。
- 外部变量引用:指向全局执行上下文。
- 变量环境:包含变量
-
greet函数执行上下文:- 变量环境:包含变量
name和message,以及console.log函数的引用。 - 词法环境:包含
greet函数作用域和全局作用域的信息。 - this 值:全局对象(在非严格模式下)。
- 外部变量引用:指向全局执行上下文。
- 变量环境:包含变量
当调用 sayHello() 时,会创建一个新的 sayHello 函数执行上下文。在该上下文中,会创建变量 greeting。然后,sayHello 函数内部调用 greet("Alice"),创建一个新的 greet 函数执行上下文。在该上下文中,会创建变量 name 和 message。每个函数都有自己的执行上下文,用于跟踪函数内部的变量和作用域。这样的嵌套结构使得函数能够访问其外部函数或全局环境中的变量和函数。
137. 全局执行上下文
当 JavaScript 引擎开始执行代码时,会首先创建全局执行上下文,并将其作为当前执行上下文。全局执行上下文只有一个,并且在整个程序执行期间都存在。当程序执行结束或页面关闭时,全局执行上下文会被销毁,从而结束程序的执行
138. 创建自定义 HTML 元素
自定义 HTML 元素的创建涉及两个主要步骤,
-
定义自定义元素类:创建一个类来定义自定义元素的行为和功能。通常,这个类会继承自
HTMLElement。 -
编写构造函数:在自定义元素类中添加构造函数,可以在其中进行一些初始化操作,如设置默认属性或状态。
-
注册生命周期方法:自定义元素类可以实现一些生命周期方法,如
connectedCallback、disconnectedCallback、attributeChangedCallback等。这些方法可以在元素与 DOM 进行交互时被调用。 -
编写渲染方法:添加一个渲染方法,用于根据元素的状态和属性生成相应的 HTML 内容,并将其插入自定义元素中。
-
注册自定义元素:使用
customElements.define方法将自定义元素类注册为一个自定义标签,以便在 HTML 中使用。
下面是一个示例,演示了创建一个简单的自定义元素 <x-greeting> 的步骤:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Element Demo</title>
</head>
<body>
<x-greeting name="Alice"></x-greeting>
<script>
class GreetingElement extends HTMLElement {// 1. 定义
constructor() { // 2. 构造函数
super();
this.attachShadow({ mode: 'open' });
this.name = this.getAttribute('name') || 'Guest';
}
connectedCallback() { // 3. 生命周期
this.render();
}
render() { // 4. 渲染
this.shadowRoot.innerHTML = `
<p>Hello, ${this.name}!</p>
`;
}
}
// 注册
customElements.define('x-greeting', GreetingElement);
</script>
</body>
</html>
通过以上步骤,我们成功创建了自定义元素 <x-greeting>,并将其渲染为 <p>Hello, Alice!</p>。这个示例展示了自定义元素的基本创建过程,并演示了如何在自定义元素中使用 Shadow DOM 来封装样式和内容。
139. 创建生成器函数的5咱方式
-
生成器函数声明:
```javascript function* myGenFunc() { yield 1; yield 2; yield 3; } const genObj = myGenFunc(); ``` -
生成器函数表达式:
const myGenFunc = function* () { yield 1; yield 2; yield 3; }; const genObj = myGenFunc(); -
对象字面量中的生成器方法定义:
const myObj = { *myGeneratorMethod() { yield 1; yield 2; yield 3; }, }; const genObj = myObj.myGeneratorMethod(); -
类中的生成器方法定义:
class MyClass { *myGeneratorMethod() { yield 1; yield 2; yield 3; } } const myObject = new MyClass(); const genObj = myObject.myGeneratorMethod(); -
生成器作为计算属性:
const SomeObj = { *[Symbol.iterator]() { yield 1; yield 2; yield 3; }, }; console.log(Array.from(SomeObj)); // [ 1, 2, 3 ]
140. 检测一个对象是否为promise
如果你不知道某个值是否是 Promise,则将该值包装为“Promise.resolve(value)”,它会返回一个 Promise
function isPromise(object) {
if (Promise && Promise.resolve) {
return Promise.resolve(object) == object;
} else {
throw "Promise not supported in your environment";
}
}
var i = 1;
var promise = new Promise(function (resolve, reject) {
resolve();
});
console.log(isPromise(i)); // false
console.log(isPromise(promise)); // true
另一种方式是检查 .then() 属性的类型
function isPromise(value) {
return Boolean(value && typeof value.then === "function");
}
var i = 1;
var promise = new Promise(function (resolve, reject) {
resolve();
});
console.log(isPromise(i)); // false
console.log(isPromise(promise)); // true
141 什么是合理的尾调用
合理的尾调用(PTC)是一种当函数调用是尾调用时程序或代码不会为递归创建额外的堆栈帧的技术。尾调用优化(Tail Call Optimization)是一种优化技术,编译器或解释器在遇到尾调用时,可以将其转换为一个无需额外调用栈空间的跳转操作,从而节省内存的使用。
尾调用的特点是:
- 尾调用是函数执行的最后一步操作,不会再有后续的计算或操作。
- 尾调用的返回值直接作为当前函数的返回值,不需要额外的处理或计算。
- 调用发生时,不需要保留当前函数的调用帧(即不需要额外的调用栈空间)。
以下为例,当不使用尾调用优化时,阶乘函数的递归实现可能会像这样:
function factorial(n) {
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
console.log(factorial(5)); // 输出: 120
在上述代码中,factorial 函数的递归调用发生在乘法操作之后,而不是作为函数的最后一步操作。因此,每次递归调用都会在调用栈中添加一个新的调用帧,记录函数的执行位置和相关信息。
相反,使用尾调用优化的上面的递归函数示例如下
function factorial(n, acc = 1) {
if (n === 0) {
return acc;
}
return factorial(n - 1, n * acc);
}
console.log(factorial(5)); // 输出: 120
在尾调用优化的情况下,当函数调用发生在函数的最后一步操作时,编译器或解释器可以优化代码,使得新的函数调用复用当前函数的调用帧,而不是添加新的调用帧到调用栈中。这样做的好处是节省了内存空间,因为只需要一个调用帧来执行整个递归过程。
142. 如何使对象可迭代
默认情况下,普通对象是不可迭代的。 但是可以通过在对象上定义 Symbol.iterator 属性来使该对象可迭代。
让我们用一个例子来证明这一点,
const collection = {
one: 1,
two: 2,
three: 3,
[Symbol.iterator]() {
const values = Object.keys(this);
let i = 0;
return {
next: () => {
return {
value: this[values[i++]],
done: i > values.length,
};
},
};
},
};
const iterator = collection[Symbol.iterator]();
console.log(iterator.next()); // → {value: 1, done: false}
console.log(iterator.next()); // → {value: 2, done: false}
console.log(iterator.next()); // → {value: 3, done: false}
console.log(iterator.next()); // → {value: undefined, done: true}
上述过程可以使用生成器函数来简化,
const collection = {
one: 1,
two: 2,
three: 3,
[Symbol.iterator]: function* () {
for (let key in this) {
yield this[key];
}
},
};
const iterator = collection[Symbol.iterator]();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}
143. 如何防止 Promise 吞噬错误
为了防止 Promise 吞噬错误,需要采取适当的措施来处理错误。以下是几种常见的方法:
在一些现代的 JavaScript 环境中,确实存在不会打印任何错误的情况。可以通过不同的方法来解决这个问题:
-
在每个 Promise 链的末尾添加 catch 块:可以在每个 Promise 链的末尾添加 catch 块来捕获错误。
Promise.resolve("promised value") .then(function () { throw new Error("error"); }) .catch(function (error) { console.error(error.stack); });但是这种方式需要为每个 Promise 链都添加 catch 块,这样会比较繁琐和冗长。
-
使用 done 方法:可以将第一种解决方案中的 then 和 catch 块替换为 done 方法。
Promise.resolve("promised value").done(function () { throw new Error("error"); });
144. async函数是什么
使用 async 关键字声明的异步函数允许以更清晰的方式编写基于 Promise 的异步代码,避免了过多的 Promise 链。这些函数可以包含零个或多个 await 表达式。
让我们看一个下面的异步函数示例:
async function fetchData() {
// 异步操作
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
在上述示例中,fetchData 是一个异步函数。它使用 async 关键字进行声明。函数体内部使用 await 关键字来等待异步操作的结果。await 表达式会暂停函数的执行,直到异步操作完成并返回结果。在等待期间,函数会暂时释放控制权,允许其他代码在该时间段内执行。一旦异步操作完成,函数将继续执行。
异步函数的另一个特性是它们总是返回一个 Promise 对象。在上述示例中,fetchData 函数将返回一个 Promise,该 Promise 在异步操作完成后会解析为数据。
145. 函数声明和类声明的主要区别
函数声明和类声明的主要区别在于"提升"(hoisting)。函数声明会被提升,但类声明不会被提升。
类声明:
const user = new User(); // ReferenceError
class User {}
构造函数声明:
const user = new User(); // 没有错误
function User() {}
在上述示例中,当使用类声明时,尝试在类声明之前创建类的实例会引发 ReferenceError,因为类声明不会被提升。这意味着在使用类之前,必须先声明它。
相比之下,使用构造函数声明时,可以在声明之前创建构造函数的实例,因为函数声明会被提升。这意味着在代码中的任何位置都可以访问和使用构造函数。
146. 什么是可观察对象
Observable(可观察对象)基本上是一个函数,它可以在一段时间内以同步或异步的方式向观察者返回一系列的值。消费者可以通过调用 subscribe() 方法来获取这些值。
让我们看一个简单的 Observable 示例:
import { Observable } from "rxjs";
const observable = new Observable((observer) => {
setTimeout(() => {
observer.next("来自 Observable 的消息!");
}, 3000);
});
observable.subscribe((value) => console.log(value));
在上述示例中,我们使用 rxjs 库中的 Observable 类创建了一个 Observable 对象。在 Observable 的构造函数中,我们定义了一个回调函数,它接收一个观察者(observer)作为参数。在这个回调函数中,我们使用 setTimeout 函数来模拟一个异步操作,并在 3 秒后通过调用观察者的 next 方法来发送一个值。
然后,我们通过调用 observable.subscribe 方法来订阅 Observable。在订阅过程中,我们传入一个回调函数,它会在 Observable 发送新值时被调用。在上述示例中,我们简单地将接收到的值打印到控制台上。
147. 函数构造函数和函数声明之间的差别
函数构造函数(Function constructor)和函数声明(function declaration)之间的区别在于作用域和闭包的处理方式。
使用函数构造函数创建的函数不会创建闭包,它们始终在全局作用域中创建。也就是说,函数只能访问自己的局部变量和全局作用域中的变量。而函数声明可以访问外部函数的变量(闭包)。
让我们通过一个例子来看看这个区别:
函数构造函数:
var a = 100;
function createFunction() {
var a = 200;
return new Function("return a;");
}
console.log(createFunction()()); // 100
函数声明:
var a = 100;
function createFunction() {
var a = 200;
return function func() {
return a;
};
}
console.log(createFunction()()); // 200
总结起来,函数构造函数创建的函数只能访问自己的局部变量和全局作用域中的变量,而函数声明创建的函数可以访问外部函数的变量(闭包)。
148. 如何判断值是原始类型还是非原始类型
在JavaScript中,原始类型包括布尔值(boolean)、字符串(string)、数字(number)、BigInt、null、Symbol和undefined。而非原始类型包括对象(Objects)。但是你可以通过以下函数轻松地区分它们:
var myPrimitive = 30;
var myNonPrimitive = {};
function isPrimitive(val) {
return Object(val) !== val;
}
isPrimitive(myPrimitive);
isPrimitive(myNonPrimitive);
如果值是一个原始数据类型,Object构造函数会为该值创建一个新的包装对象。但如果值是一个非原始数据类型(即对象),Object构造函数将返回同一个对象。
这个函数判断一个值是否是原始类型,如果是原始类型,则返回true;如果是非原始类型,则返回false。通过将值和通过Object构造函数创建的包装对象进行严格比较,我们可以判断它们是否相等。
149. shim 和 polyfill 之间有什么区别
shim 是一个库,它仅使用该环境的手段将新的 API 引入旧环境。 它不一定限于 Web 应用程序。 例如,es5-shim.js 用于在旧版浏览器(主要是 IE9 之前)上模拟 ES5 功能。 而 polyfill 是一段代码(或插件),它提供了(开发人员)期望浏览器本机提供的技术。 简而言之,polyfill 是浏览器 API 的填充程序。
150. 什么是事件表
在典型的事件驱动编程模型中,存在一个事件循环(Event Loop),它负责管理事件的处理和异步操作的执行。事件表和事件异步队列是事件循环中的两个关键组成部分,它们协同工作来处理事件和异步操作。
- 事件表(Event Table):事件表是一个数据结构,用于管理和调度事件的执行。它维护了事件的注册信息、优先级以及对应的事件处理器。当事件触发时,引擎可以通过事件表中的记录找到对应的处理器,并执行相应的代码。事件表负责管理事件的注册和调度。
- 异步队列(Async Queue):异步队列是用于管理异步操作的队列。当执行一个异步操作时,相关的回调函数会被添加到异步队列中。事件循环会定期检查异步队列,并在适当的时候执行队列中的回调函数。
事件表和异步队列之间的关系是:事件表决定是否将某个任务(可能是异步操作的回调函数)插入到异步队列中。当某个事件发生或某个异步操作完成时,事件表会检查事件的注册信息或异步操作的状态,并决定是否将相应的任务添加到异步队列中等待执行。
因此,事件表负责管理事件的注册和调度,决定何时触发事件处理器,而异步队列管理异步操作的执行,包括将异步操作的回调函数添加到队列中并在适当时执行。
151. 什么是堆
堆(或内存堆)是我们定义变量时存储对象的内存位置。 即,这是所有内存分配和解除分配发生的地方。 堆和调用栈都是 JS 运行时的两个容器。 每当运行时遇到代码中的变量和函数声明时,它将它们存储在堆中。
152. 在TypeScript文件中如何使用JavaScript库
众所周知,并非所有的JavaScript库或框架都有TypeScript声明文件。但如果你仍然希望在TypeScript文件中使用这些库或框架而不会出现编译错误,唯一的解决方案是使用declare关键字结合变量声明。例如,假设你有一个名为customLibrary的库,它没有TypeScript声明,并且在全局命名空间中有一个名为customLibrary的命名空间。你可以在TypeScript代码中如下使用这个库:
declare var customLibrary;
在运行时,TypeScript将为customLibrary变量提供any类型的类型。另一种不使用declare关键字的替代方法如下:
var customLibrary: any;
153. queueMicrotask的目的是什么
queueMicrotask函数用于调度微任务(microtask),微任务是一种在微任务队列中异步执行的函数。queueMicrotask的目的是确保在当前任务完成后,但在浏览器执行任何渲染或处理用户事件之前执行某个函数。
示例:
console.log("Start"); //1
queueMicrotask(() => {
console.log("Inside microtask"); // 3
});
console.log("End"); //2
通过使用queueMicrotask,可以确保某些任务或回调尽快在JavaScript事件循环中执行,这对于需要异步执行但优先级高于常规的setTimeout或setInterval回调的工作非常有用。
154. setTimeout(fn, 0)的执行时机
首先,使用setTimeout(fn, 0)将代码推迟到下一次事件循环开始前执行,但不属于立即执行代码,因为最小延迟大于0毫秒。
对"下一次事件循环开始前"指的是当前事件循环中的所有同步任务执行完毕,并且异步队列中的所有任务也执行完毕后的时机。
在事件循环中,首先执行当前任务队列中的同步任务,直到队列为空。然后,会检查是否有微任务(例如Promise的回调函数、MutationObserver的回调等),如果有,则依次执行它们,直到微任务队列为空。
接下来,检查是否有新的异步任务被添加到任务队列中(例如通过setTimeout、事件回调等),如果有,则将它们添加到异步队列中,以便在下一次事件循环迭代中执行。
所以,在下一次事件循环开始前,意味着当前事件循环中的同步任务已经执行完毕,微任务队列也已经清空,并且新的异步任务已经被添加到异步队列中,等待下一次事件循环迭代中执行。
这种机制确保了JavaScript代码的执行顺序和时机,同时也保持了主线程的响应性。
155. 什么是最小超时限制
无论是浏览器还是Node.js JavaScript环境,都会对延迟进行限制,最小延迟大于0毫秒。这意味着即使设置延迟为0毫秒,也不会立即发生。
浏览器: 浏览器的最小延迟为4毫秒。这种限制会在连续的回调嵌套(一定深度)或连续间隔的情况下发生。
注意:旧版本的浏览器的最小延迟为10毫秒。
Node.js: Node.js的最小延迟为1毫秒。当延迟大于2147483647或小于1时,会发生这种限制。
156. Web语音API
Web Speech API(Web语音API)用于使现代浏览器能够识别和合成语音(将语音数据转换为Web应用程序)。这个API由W3C社区于2012年引入。它有两个主要部分:
- SpeechRecognition(异步语音识别或语音转文本): 它提供了从音频输入中识别语音内容并作出相应反应的能力。通过
SpeechRecognition接口访问。下面的示例展示了如何使用该API从语音中获取文本:
window.SpeechRecognition =
window.webkitSpeechRecognition || window.SpeechRecognition; // Chrome使用webkitSpeechRecognition,Firefox使用SpeechRecognition
const recognition = new window.SpeechRecognition();
recognition.onresult = (event) => {
// SpeechRecognitionEvent类型
const speechToText = event.results[0][0].transcript;
console.log(speechToText);
};
recognition.start();
在使用该API时,浏览器会询问你是否允许使用麦克风。
- SpeechSynthesis(文本转语音): 它提供了将文本转换为语音的能力。通过
SpeechSynthesis接口访问。例如,下面的代码用于从文本获取语音:
if ("speechSynthesis" in window) {
var speech = new SpeechSynthesisUtterance("Hello World!");
speech.lang = "en-US";
window.speechSynthesis.speak(speech);
}
上述示例可以在Chrome(33+)浏览器的开发者控制台中进行测试。
注意: 这个API仍然是一个工作草案,只在Chrome和Firefox浏览器中可用(当然,Chrome是唯一实现规范的浏览器)。
157. 使用AbortController取消请求
以fetch请求为例
基本流程如下:
- 创建一个
AbortController实例。 - 获取实例的signal属性,并将该信号作为fetch选项中的signal。
- 调用AbortController的abort属性来取消使用该信号的所有fetch请求。
例如,假设将相同的信号传递给多个fetch调用,将会取消所有使用该信号的请求:
const controller = new AbortController();
const { signal } = controller;
fetch("http://localhost:8000", { signal })
.then((response) => {
console.log(`Request 1 is complete!`);
})
.catch((e) => {
if (e.name === "AbortError") {
// 我们知道它已被取消!
}
});
fetch("http://localhost:8000", { signal })
.then((response) => {
console.log(`Request 2 is complete!`);
})
.catch((e) => {
if (e.name === "AbortError") {
// 我们知道它已被取消!
}
});
// 等待2秒后取消两个请求
setTimeout(() => controller.abort(), 2000);
158. AJAX是什么
AJAX代表异步JavaScript和XML,它是一组相关的技术(包括HTML、CSS、JavaScript、XMLHttpRequest API等),用于异步显示数据。也就是说,我们可以在不重新加载网页的情况下向服务器发送数据并从服务器获取数据。
159. 包装对象是什么
原始值(Primitive Values),如字符串(string)、数字(number)和布尔值(boolean),在进行操作时不具有属性和方法,但当你尝试对它们执行操作时,它们会被临时转换或强制转换为对象(包装对象)。需要注意的是,这种包装对象只存在于方法调用期间,并不会改变原始值本身。一旦方法调用结束,包装对象就会被销毁,原始值保持不变。
以下是一个示例来说明这个过程:
var str = 'abc';
var result = str.split('');
console.log(result); // ['a', 'b', 'c']
console.log(typeof str); // string
在上面的代码中,str 是一个原始字符串。当你调用 str.split('') 方法时,str 会被自动包装成一个字符串包装对象,然后调用该对象的 split 方法。实际上,背后执行的是 Object(str).split('')。最后,返回的结果是一个数组 ['a', 'b', 'c']。但是,str 本身仍然是一个原始字符串类型,并没有被改变。
但要注意,频繁地进行自动包装和拆包装操作可能会导致性能问题,因此在性能敏感的场景中,最好直接使用原始值而不是频繁地调用其方法。
160. 常用的一些全局禁止行为(右键菜单、文本选择、拖放等)
- 禁止文本选择:可以通过设置CSS属性
user-select来禁止在网页上选择文本。例如:
body {
user-select: none;
}
- 禁用拖放操作:可以通过监听
dragstart事件并阻止其默认行为来禁用元素的拖放操作。例如:
document.addEventListener("dragstart", function(event) {
event.preventDefault();
});
- 禁用表单提交:可以通过监听表单的
submit事件并调用preventDefault()方法来阻止表单的默认提交行为。例如:
document.querySelector("form").addEventListener("submit", function(event) {
event.preventDefault();
});
- 禁用链接点击:可以通过监听链接的
click事件并调用preventDefault()方法来阻止链接的默认点击行为。例如:
document.querySelector("a").addEventListener("click", function(event) {
event.preventDefault();
});
- 禁用右键单击:可以通过在
body元素的oncontextmenu属性中返回false来禁用网页上的右键单击。
<body oncontextmenu="return false;"></body>
161. 如何捕获浏览器后退按钮
当用户点击浏览器的返回按钮时,可以使用以下方法来捕获该事件:
- 使用
beforeunload事件:当窗口、文档及其资源即将被卸载时,将触发beforeunload事件。该事件可用于警告用户即将丢失当前数据并检测返回按钮事件。
window.addEventListener('beforeunload', () => {
console.log('Clicked browser back button');
});
- 使用
popstate事件:可以使用popstate事件来检测浏览器的返回按钮。注意: 必须使用history.pushState方法激活历史记录条目。
window.addEventListener('popstate', () => {
console.log('Clicked browser back button');
box.style.backgroundColor = 'white';
// 点击浏览器的返回按钮
window.history.back();
});
const box = document.getElementById('div');
box.addEventListener('click', () => {
box.style.backgroundColor = 'blue';
window.history.pushState({}, null, null);
});
在上述代码中,当点击 box 元素时,其背景颜色会变为蓝色,并且使用 popstate 事件处理程序点击浏览器返回按钮时会恢复为白色。popstate 的 state 属性包含历史记录条目的状态对象的副本。
注意: 当在历史记录中使用history.pushState方法添加了一个新的状态后,点击浏览器的返回按钮并不会直接退出当前页面。这是我为什么在最后添加window.history.back();完成返回操作。
162. 如何展平多维数组
要扁平化多维度数组,可以采用以下方法:
使用Spread运算符可以轻松地扁平化二维数组:
const 二维数组 = [11, [22, 33], [44, 55], [66, 77], 88, 99];
const 扁平数组 = [].concat(...二维数组); // 结果为 [11, 22, 33, 44, 55, 66, 77, 88, 99]
而对于多维度数组,可以通过递归调用来实现扁平化:
function 扁平化多维数组(arr) {
const 展开后的数组 = [].concat(...arr);
return 展开后的数组.some((item) => Array.isArray(item))
? 扁平化多维数组(展开后的数组)
: 展开后的数组;
}
const 多维度数组 = [11, [22, 33], [44, [55, 66, [77, [88]], 99]]];
const 平坦数组 = 扁平化多维数组(多维度数组); // 结果为 [11, 22, 33, 44, 55, 66, 77, 88, 99]
另外,可以使用Array对象的flat方法:
const 数组 = [1, [2, 3], 4, 5, [6, 7]];
const 扁平化后的数组 = 数组.flat(); // 结果为 [1, 2, 3, 4, 5, 6, 7]
// 对于多维度数组
const 多维度数组实例 = [11, [22, 33], [44, [55, 66, [77, [88]], 99]]];
const 一步扁平化 = 多维度数组实例.flat(1); // 结果为 [11, 22, 33, 44, [55, 66, [77, [88]], 99]]
const 两步扁平化 = 多维度数组实例.flat(2); // 结果为 [11, 22, 33, 44, 55, 66, [77, [88]], 99]
const 完全扁平化数组 = 多维度数组实例.flat(Infinity); // 结果为 [11, 22, 33, 44, 55, 66, 77, 88, 99]
163. 选中与复制文本
当需要在用户选择文本后执行复制操作时,可以结合使用 DOM 事件和 Clipboard API。
// 监听文本选择事件
document.addEventListener('selectionchange', () => {
const selectedText = window.getSelection().toString();
// 检查是否有选中的文本
if (selectedText) {
copyToClipboard(selectedText);
}
});
// 复制文本到剪贴板
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
console.log('Text copied to clipboard');
})
.catch((error) => {
console.error('Failed to copy text:', error);
});
}
通过监听 selectionchange 事件来捕获文本选择的变化。当用户选择文本时,我们获取选中的文本内容,并将其传递给 copyToClipboard 函数进行复制操作。
在 copyToClipboard 函数中,我们使用了 Clipboard API 的 writeText 方法将文本内容写入剪贴板。如果复制操作成功,会在控制台打印消息;如果复制操作失败,会在控制台输出错误信息。
164. window.isNaN 和 Number.isNaN
window.isNaN 和 Number.isNaN 是用于检查一个值是否为 NaN(Not-a-Number)的两个不同的函数。
-
window.isNaN是全局函数,它将参数转换为数字,然后检查是否为 NaN。如果参数不能转换为数字,则会返回true。这个函数存在一个问题,就是它会尝试将非数字的参数转换为数字,可能导致意外的结果。 -
Number.isNaN是 ECMAScript 6 中引入的静态方法,它更可靠地检查一个值是否为 NaN。它不会尝试将参数转换为数字,只有在参数本身是数字类型且值为 NaN 时,才会返回true。
以下是示例代码来说明它们的区别:
// 使用 window.isNaN
console.log(window.isNaN('Hello')); // true,'Hello' 无法转换为数字
console.log(window.isNaN('123')); // false,'123' 可以转换为数字
// 使用 Number.isNaN
console.log(Number.isNaN('Hello')); // false,'Hello' 不是数字类型
console.log(Number.isNaN('123')); // false,'123' 不是数字类型
console.log(Number.isNaN(NaN)); // true,NaN 是数字类型且值为 NaN
console.log(Number.isNaN(123)); // false,123 是数字类型但不是 NaN
可以看到 window.isNaN 在处理非数字参数时会将其转换为数字,这可能导致意外的结果。而 Number.isNaN 更严格,只有在参数本身是数字类型且值为 NaN 时才返回 true。
因此,在现代 JavaScript 开发中,推荐使用 Number.isNaN 来检查一个值是否为 NaN,以获得更准确和可靠的结果。
165. 特殊字符创建自身字符串
自定义字符串可以由 []()!+ 字符的组合形成。为了实现这种模式,需要记住以下规则:
- 由于数组是真值,对数组取反会得到
false:![] === false。 - 根据 JavaScript 的强制转换规则,将数组相加会将它们转换为字符串:
[] + [] === ""。 - 使用加号操作符在数组之前会将数组转换为
false,对其取反会得到true,最后将结果转换为值'1':+(!(+[])) === 1。
通过应用上述规则,我们可以得出以下条件:
(![] + [] === "false" + !+[]) === 1;
现在,字符模式将如下创建:
s e l f
^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^
(![] + [])[3] + (![] + [])[4] + (![] + [])[2] + (![] + [])[0]
^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^
(![] + [])[+!+[]+!+[]+!+[]] +
(![] + [])[+!+[]+!+[]+!+[]+!+[]] +
(![] + [])[+!+[]+!+[]] +
(![] + [])[+[]]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(![]+[])[+!+[]+!+[]+!+[]]+(![]+[])[+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+!+[]+!+[]]+(![]+[])[+[]]
166. 虚假值前面添加前置加法运算符
在编程语言JavaScript中,如果在假值(null, undefined, NaN, false, "")前添加加法前置运算符(+),这些假值会被转换为数字值。具体转换规则如下:
console.log(+null); // 输出:0,因为null转换成了数字0
console.log(+undefined); // 输出:NaN,因为undefined转换成了NaN(非数字)
console.log(+false); // 输出:0,因为false转换成了数字0
console.log(+NaN); // 输出:NaN,NaN保持不变,它始终代表非数字值
console.log(+""); // 输出:0,因为空字符串""转换成了数字0