文章目录
-
-
- 33. JavaScript 脚本延迟加载(异步加载)的方式
- 34. JavaScript 的类数组对象
- 35. JavaScript 数组的原生方法
- 36 . 为什么 JavaScript 函数的 `arguments` 参数是类数组而不是数组?
- 37. JavaScript 数组的遍历方法
- 38. 如何遍历类数组?
- 39. JavaScript 数组的 `map` 和 `forEach` 函数中能否通过 `break` 等语法结束循环?
- 40. 什么是 DOM 和 BOM?
- 41. `encodeURI` 和 `encodeURIComponent` 的区别是什么?
- 42. 什么是 AJAX?如何实现一个 AJAX 请求?
-
33. JavaScript 脚本延迟加载(异步加载)的方式
JavaScript 脚本延迟加载可以提高页面加载速度,以下是几种常见的方法:
(1) 使用 async
属性
async
属性让脚本尽可能地异步加载,不会阻塞 HTML 解析。脚本一旦下载完成就会立即执行,但多个 async
脚本的执行顺序不确定。适用于独立性较高的脚本,如第三方统计代码、广告代码等。
<script src="example.js" async></script>
(2) 使用 defer
属性
defer
属性用于延迟脚本加载,保证所有 defer
脚本会按文档中出现的顺序执行,并且在 HTML 解析完成后才执行。适用于依赖 HTML 结构的脚本,如需要操作 DOM 的脚本。
<script src="example.js" defer></script>
(3) 动态创建脚本元素
通过 JavaScript 动态创建 <script>
标签并插入到文档中,可以灵活控制脚本的加载和执行时机,常用于某些条件触发时才加载的场景。
var script = document.createElement('script');
script.src = 'example.js';
document.head.appendChild(script);
(4) 使用模块化加载工具
前端模块化工具(如 Webpack 的动态加载)提供了强大的依赖管理和延迟加载功能,适用于大型项目,解决代码拆分和按需加载的问题。
// 以 Webpack 的动态加载为例
import('example.js').then(module => {
// 使用加载的模块
});
34. JavaScript 的类数组对象
JavaScript 的类数组对象(Array-like Object)是指具有类似数组特性但不是数组的对象。它们通常具备 length
属性和按索引存储的元素,例如 arguments
对象、DOM 方法返回的集合(如 NodeList
)。要将类数组对象转换为真正的数组,可以使用以下方法:
(1) 使用 Array.prototype.slice.call()
方法
这种方法在 ES5 之前非常常见,利用 slice
方法将类数组对象切片成真正的数组,但需要写得较为复杂,效率稍低。
const arrayLike = {0: 'a', 1: 'b', length: 2};
const realArray = Array.prototype.slice.call(arrayLike);
(2) 使用 Array.from()
方法(ES6 引入)
这种方法简洁直观,Array.from
还可以接受第二个参数用来处理每一个元素。
const arrayLike = {0: 'a', 1: 'b', length: 2};
const realArray = Array.from(arrayLike);
(3) 使用扩展运算符(ES6 引入)
这是 ES6 引入的语法糖,最为简洁且可读性强,但需要确保类数组对象的结构完整(包括 length
属性)。
const arrayLike = {0: 'a', 1: 'b', length: 2};
const realArray = [...arrayLike];
注意: 类数组对象和数组的主要区别在于类数组对象没有数组的方法(如 push
、pop
等)。转换为数组后,可以更方便地使用这些方法对数据进行操作。
35. JavaScript 数组的原生方法
JavaScript 数组提供了多种原生方法,用于操作和处理数组数据。以下是一些常见的方法:
(1) 数组转成字符串
toString()
:将数组转换为字符串,元素之间用逗号分隔。join()
:将数组转换为字符串,可以指定分隔符。
const fruits = ["apple", "banana", "orange"];
const fruitString = fruits.toString(); // "apple,banana,orange"
const colors = ["red", "green", "blue"];
const colorString = colors.join(" - "); // "red - green - blue"
(2) 数组尾部操作
pop()
:删除数组最后一个元素。push()
:向数组末尾添加一个或多个元素。
let arr = [1, 2, 3];
arr.push(4); // arr 现在是 [1, 2, 3, 4]
arr.pop(); // 返回 4,arr 现在是 [1, 2, 3]
(3) 数组开头操作
unshift()
:向数组开头添加一个或多个元素。shift()
:删除数组第一个元素。
arr.unshift(0); // arr 现在是 [0, 1, 2, 3]
arr.shift(); // 返回 0,arr 现在是 [1, 2, 3]
(4) 数组连接
concat()
:返回拼接好的数组,不影响原数组。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinedArray = arr1.concat(arr2); // [1, 2, 3, 4, 5, 6]
(5) 遍历数组
forEach()
:为每个数组元素执行一次回调函数,没有返回值。map()
:创建一个新数组,其结果是原数组中每个元素调用回调函数后的返回值。
arr.forEach(item => console.log(item));
let doubled = arr.map(item => item * 2);
(6) 查找和测试
find()
:返回数组中满足测试函数的第一个元素。findIndex()
:返回数组中满足测试函数的第一个元素的索引。some()
:测试是否至少有一个元素通过测试函数。every()
:测试是否所有元素都通过测试函数。flat()
:将多维数组扁平化。flatMap()
:先对数组中的每个元素执行回调函数,然后将结果扁平化。
const arr = [1, 2, 4];
arr.find(item => item > 2); // 返回 4
arr.some(item => item > 2); // 返回 true
let nested = [1, [2, 3]];
nested.flat(); // 返回 [1, 2, 3]
36 . 为什么 JavaScript 函数的 arguments
参数是类数组而不是数组?
arguments
是一个类数组对象,而不是真正的数组,原因如下:
- 历史原因:
arguments
出现得比数组早。如果将其改为真正的数组,需要对现有代码进行大规模修改,因此这种设计被延续下来。 - 性能考虑:如果
arguments
是一个真正的数组,每次函数调用时都需要创建一个完整的数组对象,这会增加内存使用和时间开销。
arguments
是一个对象,它的属性是从 0 开始依次递增的数字,还有 length
等属性,与数组相似,但没有数组的常见方法(如 push
、pop
等),因此被称为类数组。
37. JavaScript 数组的遍历方法
JavaScript 提供了多种数组遍历方法,适用于不同的场景:
(1) for
循环
最传统的方法,通过索引访问数组的每个元素,可以提前终止循环。
const arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
(2) forEach()
更现代、更简洁的方法,为每个数组元素执行一次回调函数,没有返回值。
arr.forEach((item, index) => {
console.log(item, index);
});
(3) for...of
ES6 引入的新语法,可以直接遍历数组的值,不能用于普通对象。
for (const item of arr) {
console.log(item);
}
(4) map()
返回一个新数组,其结果是原数组中每个元素调用回调函数后的返回值。
const newArr = arr.map(item => item * 2);
(5) filter()
过滤数组,返回包含符合条件的元素的数组,可链式调用。
const evenNumbers = arr.filter(item => item % 2 === 0);
(6) reduce()
和 reduceRight()
用于对数组元素进行累积操作,reduce
从左到右处理数组,reduceRight
从右到左处理数组。
const sum = arr.reduce((acc, cur) => acc + cur, 0);
const sumRight = arr.reduceRight((acc, cur) => acc + cur, 0);
(7) some()
和 every()
some()
:测试是否至少有一个元素通过测试函数。every()
:测试是否所有元素都通过测试函数。
const hasEven = arr.some(item => item % 2 === 0);
const allPositive = arr.every(item => item > 0);
38. 如何遍历类数组?
虽然 arguments
是类数组对象,但它具有数组的一些特性,例如可以通过索引访问元素,并且有 length
属性。因此,可以使用以下几种方式来遍历 arguments
:
(1) 使用传统的 for
循环
function myFunction() {
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(2) 使用 for...of
循环(需要先将类数组转换为数组)
function myFunction() {
for (const arg of Array.from(arguments)) {
console.log(arg);
}
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(3) 使用 forEach
方法(需要先将类数组转换为数组)
function myFunction() {
Array.from(arguments).forEach(arg => {
console.log(arg);
});
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(4) 使用扩展运算符(...
)将类数组转换为数组
function myFunction() {
const args = [...arguments];
for (const arg of args) {
console.log(arg);
}
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(5) 使用 map
方法(需要先将类数组转换为数组)
function myFunction() {
const args = Array.from(arguments);
args.map(arg => console.log(arg));
}
myFunction(1, 2, 3); // 输出:1, 2, 3
(6) 使用 Array.prototype.forEach.call
或 apply
方法
function foo() {
Array.prototype.forEach.call(arguments, arg => console.log(arg));
}
拓展:使用剩余参数(Rest Parameters)
在现代 JavaScript 中,推荐使用剩余参数来替代 arguments
,因为剩余参数提供了一个真正的数组,更易于使用:
function example(...args) {
args.forEach(arg => console.log(arg));
}
example(1, 2, 3); // 输出:1, 2, 3
39. JavaScript 数组的 map
和 forEach
函数中能否通过 break
等语法结束循环?
在 JavaScript 中,map
和 forEach
方法中不能直接使用 break
或 continue
语句来结束循环,因为它们是高阶函数,设计初衷是遍历整个数组。
不推荐的实现方式:
使用异常来终止循环:
try {
[1, 2, 3, 4, 5].forEach(item => {
if (item > 3) throw new Error("Break");
console.log(item);
});
} catch (e) {
if (e.message !== "Break") throw e;
}
推荐的实现方式:
使用
Array.prototype.some()
或Array.prototype.every()
- 这两个方法允许在满足某个条件时提前结束循环,更加优雅且符合函数式编程思想。
[1, 2, 3, 4, 5].some(item => { if (item > 3) return true; console.log(item); return false; });
使用普通的
for
循环- 虽然不够简洁,但在需要更细粒度控制循环的情况下很有用。
for (let item of [1, 2, 3, 4, 5]) { if (item > 3) break; console.log(item); }
使用
Array.prototype.reduce()
- 比较灵活,可以根据需要存储和传递更多信息。
[1, 2, 3, 4, 5].reduce((acc, item) => { if (acc.break) return acc; if (item > 3) return { break: true }; console.log(item); return acc; }, {});
40. 什么是 DOM 和 BOM?
DOM(Document Object Model)
- 定义:文档对象模型,将网页内容转换为 JavaScript 可以操作的对象,允许程序和脚本动态地访问和更新文档的内容、结构和样式。
- 常见操作:
- 获取元素:
document.getElementById()
、document.querySelector()
等。 - 修改元素内容:
element.innerHTML
、element.textContent
等。 - 修改元素样式:
element.style.property
。 - 添加或删除元素:
document.createElement()
、element.appendChild()
等。
- 获取元素:
BOM(Browser Object Model)
- 定义:浏览器对象模型,是浏览器提供的用于操作浏览器的接口。
- 主要组成部分:
window
对象:表示浏览器窗口。navigator
对象:包含有关浏览器的信息。location
对象:包含当前 URL 的信息。history
对象:包含浏览器的历史记录。screen
对象:包含有关用户屏幕的信息。
常见操作:
- 打开新窗口:
window.open()
- 移动、调整窗口大小:
window.moveTo()
、window.resizeTo()
- 导航到其他 URL:
window.location.href = "http://example.com"
- 获取浏览器信息:
navigator.userAgent
- 操作浏览历史:
history.back()
、history.forward()
DOM 和 BOM 的主要区别:
- DOM 主要处理网页内容,而 BOM 处理浏览器窗口和功能。
- DOM 是 W3C 标准,而 BOM 没有相关标准。
- DOM 可以在任何支持 XML 的环境中使用,而 BOM 只能在浏览器环境中使用。
41. encodeURI
和 encodeURIComponent
的区别是什么?
这两个函数都用于对 URI(统一资源标识符)进行编码,但它们的编码范围和适用场景有所不同。
encodeURI()
用途:用于编码完整的 URI。
特点:会编码所有对 URI 有特殊含义的字符,但不会编码以下字符:
/, ? : @ & = + $ #
。示例:
console.log(encodeURI("https://example.com/path?name=张三&age=18")); // 输出: https://example.com/path?name=%E5%BC%A0%E4%B8%89&age=18
encodeURIComponent()
用途:用于编码 URI 的组成部分。
特点:会编码所有对 URI 有特殊含义的字符,包括
/, ? : @ & = + $ #
。示例:
console.log(encodeURIComponent("https://example.com/path?name=张三&age=18")); // 输出: https%3A%2F%2Fexample.com%2Fpath%3Fname%3D%E5%BC%A0%E4%B8%89%26age%3D18
主要区别:
encodeURI
不会编码 URI 中的功能字符(如&
,=
),而encodeURIComponent
会编码这些字符。encodeURIComponent
编码的字符范围更广,通常用于编码 URL 的参数。
拓展:
在实际开发中:
- 当需要编码整个 URI 时,使用
encodeURI
。 - 当需要编码 URI 参数时,使用
encodeURIComponent
。
42. 什么是 AJAX?如何实现一个 AJAX 请求?
定义
AJAX(Asynchronous JavaScript and XML)是一种通过 JavaScript 进行异步通信的技术,允许从客户端向服务器发送请求,获取数据并更新页面的特定部分,而无需刷新整个页面。
创建 AJAX 请求的步骤
创建
XMLHttpRequest
对象:const xhr = new XMLHttpRequest();
通过
open
方法设置请求方式并建立连接:xhr.open(method, url, true);
构建请求数据并发送:
if (method.toUpperCase() === 'POST') { xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(JSON.stringify(data)); } else { xhr.send(); }
监听通信状态并处理响应:
xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { callback(null, xhr.responseText); } else { callback(new Error('请求失败: ' + xhr.status)); } } };
示例代码
function makeAjaxRequest(url, method, data, callback) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
callback(null, xhr.responseText);
} else {
callback(new Error('请求失败: ' + xhr.status));
}
}
};
xhr.open(method, url, true);
if (method.toUpperCase() === 'POST') {
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
} else {
xhr.send();
}
}
// 使用示例
makeAjaxRequest('https://api.example.com/data', 'GET', null, function(error, response) {
if (error) {
console.error('出错了:', error);
} else {
console.log('收到响应:', response);
}
});
使用 Fetch 实现 AJAX 请求(更现代的方法)
function makeAjaxRequest(url, method, data) {
const options = {
method: method,
headers: {
'Content-Type': 'application/json'
}
};
if (method.toUpperCase() !== 'GET' && data) {
options.body = JSON.stringify(data);
}
return fetch(url, options)
.then(response => {
if (!response.ok) {
throw new Error('请求失败: ' + response.status);
}
return response.json();
});
}
// 使用示例
makeAjaxRequest('https://api.example.com/data', 'GET')
.then(data => console.log('收到数据:', data))
.catch(error => console.error('出错了:', error));