JS相关知识2.0
JavaScript中async await和Promise和generator有什么区别
在 JavaScript 中,async/await
、Promise
和 Generator
都是用于处理异步操作的机制,但它们的使用方式和特性有所不同。以下是它们的详细对比:
1. Promise
定义:
Promise
是 JavaScript 中处理异步操作的标准方式,表示一个异步操作的最终完成(或失败)及其结果值。
特点:
有三种状态:
pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)。通过
then
和catch
方法处理成功和失败的结果。
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Success'), 1000);
});
promise
.then(result => console.log(result)) // 输出: Success
.catch(error => console.error(error));
2. async/await
定义:
async/await
是基于Promise
的语法糖,用于以同步的方式编写异步代码。
特点:
async
函数返回一个Promise
。await
用于等待Promise
的结果,只能在async
函数中使用。
async function fetchData() {
try {
const result = await new Promise((resolve) => {
setTimeout(() => resolve('Success'), 1000);
});
console.log(result); // 输出: Success
} catch (error) {
console.error(error);
}
}
fetchData();
3. Generator
定义:
Generator
是一种特殊的函数,可以通过yield
关键字暂停和恢复执行。
特点:
使用
function*
定义生成器函数。通过
next()
方法控制执行,返回一个包含value
和done
属性的对象。
function* generator() {
yield 'Step 1';
yield 'Step 2';
return 'Done';
}
const gen = generator();
console.log(gen.next()); // 输出: { value: 'Step 1', done: false }
console.log(gen.next()); // 输出: { value: 'Step 2', done: false }
console.log(gen.next()); // 输出: { value: 'Done', done: true }
4. 对比
特性 | Promise | async/await | Generator |
---|---|---|---|
语法 | 链式调用 then 和 catch | 类似同步代码,使用 await | 使用 yield 暂停和恢复执行 |
返回值 | Promise 对象 | Promise 对象 | 迭代器对象 |
错误处理 | 使用 catch | 使用 try/catch | 需要手动处理 |
执行控制 | 自动执行 | 自动执行 | 手动控制(next() 方法) |
适用场景 | 简单的异步操作 | 复杂的异步操作 | 需要暂停和恢复执行的场景 |
总结
Promise
:适合处理简单的异步操作。
通过链式调用
then
和catch
处理结果。
async/await
:适合处理复杂的异步操作,代码更易读。
基于
Promise
,使用try/catch
处理错误。
Generator
:适合需要手动控制执行流程的场景。
通过
yield
暂停和恢复执行。
根据具体需求选择合适的异步处理方式,可以提高代码的可读性和可维护性。
JavaScript中手写Promise
手写一个简单的 Promise
可以帮助深入理解其工作原理。以下是 Promise
的核心功能实现:
1. Promise 的基本结构
状态:
pending
(进行中)fulfilled
(已成功)rejected
(已失败)
方法:
then
:处理成功和失败的结果。catch
:处理失败的结果。resolve
和reject
:改变状态并触发回调。
2. 手写 Promise
class MyPromise {
constructor(executor) {
this.state = 'pending'; // 初始状态
this.value = undefined; // 成功的结果
this.reason = undefined; // 失败的原因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject); // 执行 executor
} catch (error) {
reject(error); // 捕获异常
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}
3. 使用示例
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('Success'), 1000);
});
promise
.then(result => {
console.log(result); // 输出: Success
return 'Next Step';
})
.then(result => {
console.log(result); // 输出: Next Step
})
.catch(error => {
console.error(error);
});
4. 功能说明
状态管理:
pending
、fulfilled
、rejected
三种状态。
回调队列:
- 使用数组存储成功和失败的回调函数。
异步处理:
- 使用
setTimeout
模拟异步执行。
- 使用
链式调用:
then
方法返回一个新的Promise
,支持链式调用。
错误处理:
- 使用
catch
方法捕获错误。
- 使用
总结
手写 Promise
的核心在于状态管理、回调队列和链式调用。通过实现这些功能,可以更好地理解 Promise
的工作原理。
JavaScript中Promise.all作用
Promise.all
是 JavaScript 中用于处理多个异步操作的方法。它接收一个 Promise
数组作为参数,并返回一个新的 Promise
。以下是 Promise.all
的作用和使用方法:
1. 作用
并行执行:
Promise.all
会并行执行所有传入的Promise
。
全部成功:
- 当所有
Promise
都成功时,返回一个包含所有结果的数组。
- 当所有
任意失败:
- 如果任意一个
Promise
失败,立即返回失败的原因。
- 如果任意一个
2. 使用方法
Promise.all(iterable);
iterable
:一个可迭代对象(如数组),包含多个Promise
。返回值:
- 返回一个新的
Promise
,其状态由传入的Promise
决定。
- 返回一个新的
3. 示例
- 全部成功:
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log(results); // 输出: [1, 2, 3]
})
.catch(error => {
console.error(error);
});
- 任意失败:
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('Error');
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log(results);
})
.catch(error => {
console.error(error); // 输出: Error
});
4. 使用场景
并行请求:
- 同时发起多个网络请求,等待所有请求完成后再处理结果。
依赖多个异步操作:
- 需要多个异步操作都成功后才能继续执行后续逻辑。
5. 手写实现
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(result => {
results[index] = result;
completedCount++;
if (completedCount === promises.length) {
resolve(results);
}
})
.catch(error => {
reject(error);
});
});
});
}
总结
特性 | 描述 |
---|---|
并行执行 | 同时执行所有 Promise |
全部成功 | 返回所有结果的数组 |
任意失败 | 立即返回失败的原因 |
使用场景 | 并行请求、依赖多个异步操作 |
Promise.all 是处理多个异步操作的强大工具,合理使用可以提高代码的效率和可读性。
promises中的race method是什么意思
在 JavaScript 中,Promise.race()
是一个静态方法,用于将多个 Promise 包装成一个新的 Promise。这个新 Promise 的结果取决于 最先完成(无论是 fulfilled
还是 rejected
)的 Promise。
1. 语法
Promise.race(iterable);
- 参数:一个可迭代对象(如数组),包含多个 Promise 或其他值。
- 返回值:一个新的 Promise,其状态和值与第一个完成的 Promise 相同。
2. 使用场景
- 超时控制:为异步操作设置超时限制。
- 竞速任务:从多个异步任务中选择最快的结果。
3. 示例
示例 1:基本用法
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, "第一个完成"));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, "第二个完成"));
Promise.race([promise1, promise2])
.then((result) => console.log(result)) // 输出: 第二个完成
.catch((error) => console.error(error));
示例 2:超时控制
function fetchWithTimeout(url, timeout) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error("请求超时")), timeout)
);
return Promise.race([fetchPromise, timeoutPromise]);
}
fetchWithTimeout("https://api.example.com/data", 3000)
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error.message)); // 输出: 请求超时
示例 3:处理错误
const promise1 = new Promise((_, reject) => setTimeout(reject, 100, "第一个失败"));
const promise2 = new Promise((resolve) => setTimeout(resolve, 200, "第二个完成"));
Promise.race([promise1, promise2])
.then((result) => console.log(result))
.catch((error) => console.error(error)); // 输出: 第一个失败
4. 注意事项
- 非 Promise 值:如果可迭代对象中包含非 Promise 值(如数字、字符串),
Promise.race()
会将其视为已完成的 Promise。javascriptPromise.race([1, 2, 3]) .then((result) => console.log(result)); // 输出: 1
- 空数组:如果传入空数组,返回的 Promise 将永远处于
pending
状态。javascriptPromise.race([]) .then((result) => console.log(result)) // 不会执行 .catch((error) => console.error(error)); // 不会执行
5. 与 Promise.all()
的区别
方法 | 行为描述 | 示例 |
---|---|---|
Promise.race() | 返回第一个完成的 Promise(无论成功或失败) | Promise.race([p1, p2]) |
Promise.all() | 等待所有 Promise 完成,返回结果数组 | Promise.all([p1, p2]) |
总结
Promise.race()
:用于竞速任务或超时控制,返回第一个完成的 Promise。- 适用场景:
- 超时处理。
- 从多个异步任务中选择最快的结果。
- 注意事项:
- 非 Promise 值会被自动包装为已完成的 Promise。
- 空数组会导致返回的 Promise 永远处于
pending
状态。
通过合理使用 Promise.race()
,可以优化异步操作的处理逻辑。
JavaScript中defer和async的区别
在 JavaScript 中,defer
和 async
是用于控制外部脚本加载和执行行为的两个属性,通常用于 <script>
标签中。它们的主要区别在于脚本的加载和执行时机。
- 普通
<script>
标签
默认情况下,浏览器在解析 HTML 时遇到 <script>
标签会立即停止 HTML 解析,下载并执行脚本,然后再继续解析 HTML。
<script src="script.js"></script>
- 缺点:会阻塞 HTML 解析,影响页面加载性能。
defer
属性
defer
属性告诉浏览器立即下载脚本,但延迟到 HTML 解析完成后再执行脚本。
<script defer src="script.js"></script>
特点:
- 加载:立即下载脚本,但不会阻塞 HTML 解析。
- 执行:在 HTML 解析完成后,按照脚本在文档中的顺序依次执行。
- 适用场景:适用于需要操作 DOM 或依赖其他脚本的脚本文件。
示例
<!DOCTYPE html>
<html>
<head>
<script defer src="script1.js"></script>
<script defer src="script2.js"></script>
</head>
<body>
<p>Hello, World!</p>
</body>
</html>
script1.js
和script2.js
会在 HTML 解析完成后按顺序执行。
async
属性
async
属性告诉浏览器立即下载脚本,并在下载完成后立即执行脚本,同时可能会中断 HTML 解析。
<script async src="script.js"></script>
特点:
- 加载:立即下载脚本,不会阻塞 HTML 解析。
- 执行:脚本下载完成后立即执行,可能会中断 HTML 解析。
- 执行顺序:脚本的执行顺序不确定,取决于下载完成的顺序。
- 适用场景:适用于独立的脚本,如统计分析、广告脚本等。
示例
<!DOCTYPE html>
<html>
<head>
<script async src="script1.js"></script>
<script async src="script2.js"></script>
</head>
<body>
<p>Hello, World!</p>
</body>
</html>
script1.js
和script2.js
的下载和执行顺序不确定。
defer
和async
的区别
特性 | defer | async |
---|---|---|
加载时机 | 立即下载,不阻塞 HTML 解析 | 立即下载,不阻塞 HTML 解析 |
执行时机 | HTML 解析完成后,按顺序执行 | 下载完成后立即执行,顺序不确定 |
阻塞 HTML 解析 | 不会阻塞 | 可能会阻塞 |
适用场景 | 依赖 DOM 或其他脚本的脚本 | 独立的脚本 |
- 使用建议
- 如果脚本需要操作 DOM 或依赖其他脚本,使用
defer
。 - 如果脚本是独立的(如统计分析、广告脚本),使用
async
。 - 如果脚本很小且对页面加载性能影响不大,可以不使用
defer
或async
。
- 示例对比
使用 defer
<!DOCTYPE html>
<html>
<head>
<script defer src="script1.js"></script>
<script defer src="script2.js"></script>
</head>
<body>
<p>Hello, World!</p>
</body>
</html>
script1.js
和script2.js
会在 HTML 解析完成后按顺序执行。
使用 async
<!DOCTYPE html>
<html>
<head>
<script async src="script1.js"></script>
<script async src="script2.js"></script>
</head>
<body>
<p>Hello, World!</p>
</body>
</html>
script1.js
和script2.js
的下载和执行顺序不确定。
总结
defer
:延迟执行,按顺序执行,适合依赖 DOM 的脚本。async
:异步执行,顺序不确定,适合独立的脚本。- 根据脚本的具体需求选择合适的属性,可以优化页面加载性能。
JavaScript无阻塞加载的方式
在 JavaScript 中,无阻塞加载(Non-blocking Loading)是优化网页性能的关键技术,通过避免脚本阻塞页面渲染和其他资源的加载,提升用户体验。以下是实现无阻塞加载的常用方法及示例:
1. 使用 async
属性
- 作用:异步加载脚本,下载完成后立即执行,不阻塞 HTML 解析。
- 适用场景:独立脚本,不依赖其他脚本或 DOM。
- 示例:html
<script async src="script.js"></script>
2. 使用 defer
属性
- 作用:异步加载脚本,但延迟到 HTML 解析完成后按顺序执行。
- 适用场景:需要按顺序执行且依赖 DOM 的脚本。
- 示例:html
<script defer src="script1.js"></script> <script defer src="script2.js"></script> <!-- script1 先执行 -->
3. 动态脚本加载
通过 JavaScript 动态创建 <script>
标签,实现按需加载。
- 适用场景:非关键脚本或条件加载。
- 示例:javascript
function loadScript(src) { const script = document.createElement('script'); script.src = src; document.body.appendChild(script); } // 在需要时加载 window.addEventListener('DOMContentLoaded', () => { loadScript('script.js'); });
4. 使用 Promise
或 async/await
控制加载顺序
结合动态加载和 Promise
,管理脚本依赖关系。
- 示例:javascript
function loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } async function init() { await loadScript('lib.js'); await loadScript('app.js'); // 确保 lib.js 先加载 } init();
5. 使用 Intersection Observer
延迟加载
在元素进入视口时加载脚本,适用于非首屏资源。
- 示例:javascript
const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { loadScript('lazy-script.js'); observer.unobserve(entry.target); } }); }); observer.observe(document.querySelector('#lazy-element'));
6. 模块化动态导入(ES6 import()
)
按需加载 ES6 模块,适用于现代浏览器。
- 示例:javascript
button.addEventListener('click', async () => { const module = await import('./module.js'); module.doSomething(); });
7. 使用 Web Workers
处理计算密集型任务
将耗时任务移至后台线程,避免阻塞主线程。
- 示例:javascript
const worker = new Worker('worker.js'); worker.postMessage({ data: 'start' }); worker.onmessage = (e) => { console.log('Result:', e.data); };
8. 资源预加载(preload
和 prefetch
)
通过 <link>
标签提示浏览器提前加载资源。
preload
:高优先级资源,当前页面使用。html<link rel="preload" href="critical.js" as="script">
prefetch
:低优先级资源,未来页面可能使用。html<link rel="prefetch" href="next-page.js" as="script">
9. 条件加载(根据浏览器特性)
检测浏览器支持后加载特定脚本。
- 示例:javascript
if ('IntersectionObserver' in window) { loadScript('modern-script.js'); } else { loadScript('fallback-script.js'); }
10. 服务端异步加载(SSR + Hydration)
结合服务端渲染(SSR)和客户端激活(Hydration),按需加载交互逻辑。
- 示例(Next.js 框架):javascript
import dynamic from 'next/dynamic'; const DynamicComponent = dynamic(() => import('../components/HeavyComponent'));
总结
方法 | 核心原理 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|
async /defer | 异步加载脚本 | 首屏非关键脚本 | 简单易用 | async 不保证执行顺序 |
动态脚本加载 | 按需创建<script> 标签 | 非关键脚本/条件加载 | 灵活控制加载时机 | 需手动管理依赖 |
import() 动态导入 | 按需加载 ES6 模块 | 现代浏览器应用 | 模块化支持 | 需支持 ES6 模块 |
Intersection Observer | 延迟加载视口外资源 | 图片、懒加载组件 | 高性能懒加载 | 兼容性需处理 |
Web Workers | 后台线程执行任务 | 计算密集型操作 | 避免主线程阻塞 | 无法直接操作 DOM |
资源预加载 | 提前加载关键资源 | 优化关键路径 | 减少加载延迟 | 可能浪费带宽 |
注意事项:
- 依赖管理:确保异步脚本的执行顺序(如使用
defer
或Promise
)。 - 兼容性:旧版浏览器(如 IE)不支持
async
/defer
和 ES6 模块,需降级处理。 - 性能监控:使用工具(如 Lighthouse)分析加载性能,针对性优化。
通过合理组合这些方法,可显著提升页面加载速度和交互体验。
JavaScript中常用的数组方法
JavaScript 提供了丰富的数组方法,用于操作和处理数组数据。以下是常用的数组方法及其说明:
1. 修改数组的方法
push()
:
- 在数组末尾添加一个或多个元素,返回新数组的长度。
const arr = [1, 2];
arr.push(3); // arr: [1, 2, 3]
pop()
:
- 删除数组的最后一个元素,返回被删除的元素。
const arr = [1, 2, 3];
arr.pop(); // arr: [1, 2]
unshift()
:
- 在数组开头添加一个或多个元素,返回新数组的长度。
const arr = [1, 2];
arr.unshift(0); // arr: [0, 1, 2]
shift()
:
- 删除数组的第一个元素,返回被删除的元素。
const arr = [0, 1, 2];
arr.shift(); // arr: [1, 2]
splice()
:
- 添加或删除数组中的元素,返回被删除的元素数组。
const arr = [1, 2, 3];
arr.splice(1, 1, 4); // arr: [1, 4, 3]
2. 遍历数组的方法
forEach()
:
- 对数组中的每个元素执行一次回调函数。
const arr = [1, 2, 3];
arr.forEach(item => console.log(item)); // 输出: 1, 2, 3
map()
:
- 对数组中的每个元素执行一次回调函数,返回一个新数组。
const arr = [1, 2, 3];
const newArr = arr.map(item => item * 2); // newArr: [2, 4, 6]
filter()
:
- 对数组中的每个元素执行一次回调函数,返回满足条件的元素组成的新数组。
const arr = [1, 2, 3];
const newArr = arr.filter(item => item > 1); // newArr: [2, 3]
reduce()
:
- 对数组中的每个元素执行一次回调函数,将结果累积为单个值。
const arr = [1, 2, 3];
const sum = arr.reduce((acc, item) => acc + item, 0); // sum: 6
3. 查找和判断的方法
find()
:
- 返回数组中满足条件的第一个元素,如果没有则返回
undefined
。
const arr = [1, 2, 3];
const result = arr.find(item => item > 1); // result: 2
findIndex()
:
- 返回数组中满足条件的第一个元素的索引,如果没有则返回
-1
。
const arr = [1, 2, 3];
const index = arr.findIndex(item => item > 1); // index: 1
includes()
:
- 判断数组是否包含某个元素,返回布尔值。
const arr = [1, 2, 3];
const result = arr.includes(2); // result: true
some()
:
- 判断数组中是否有元素满足条件,返回布尔值。
const arr = [1, 2, 3];
const result = arr.some(item => item > 2); // result: true
every()
:
- 判断数组中的所有元素是否都满足条件,返回布尔值。
const arr = [1, 2, 3];
const result = arr.every(item => item > 0); // result: true
4. 其他常用方法
concat()
:- 合并两个或多个数组,返回一个新数组。
const arr1 = [1, 2];
const arr2 = [3, 4];
const newArr = arr1.concat(arr2); // newArr: [1, 2, 3, 4]
slice()
:
- 返回数组的浅拷贝部分,不修改原数组。
const arr = [1, 2, 3];
const newArr = arr.slice(1); // newArr: [2, 3]
join()
:
- 将数组中的所有元素连接成一个字符串。
const arr = [1, 2, 3];
const str = arr.join('-'); // str: '1-2-3'
sort()
:
- 对数组中的元素进行排序,默认按字符串顺序排序。
const arr = [3, 1, 2];
arr.sort(); // arr: [1, 2, 3]
reverse()
:
- 反转数组中的元素顺序。
const arr = [1, 2, 3];
arr.reverse(); // arr: [3, 2, 1]
总结
方法 | 描述 |
---|---|
push() | 在数组末尾添加元素 |
pop() | 删除数组的最后一个元素 |
unshift() | 在数组开头添加元素 |
shift() | 删除数组的第一个元素 |
splice() | 添加或删除数组中的元素 |
forEach() | 遍历数组中的每个元素 |
map() | 对数组中的每个元素执行回调,返回新数组 |
filter() | 返回满足条件的元素组成的新数组 |
reduce() | 将数组中的元素累积为单个值 |
find() | 返回满足条件的第一个元素 |
findIndex() | 返回满足条件的第一个元素的索引 |
includes() | 判断数组是否包含某个元素 |
some() | 判断数组中是否有元素满足条件 |
every() | 判断数组中的所有元素是否都满足条件 |
concat() | 合并两个或多个数组 |
slice() | 返回数组的浅拷贝部分 |
join() | 将数组中的元素连接成字符串 |
sort() | 对数组中的元素进行排序 |
reverse() | 反转数组中的元素顺序 |
掌握这些数组方法,可以更高效地操作和处理数组数据。
JavaScript字符串方法
JavaScript 提供了丰富的字符串方法,用于操作和处理字符串数据。以下是常用的字符串方法及其说明:
1. 字符串查找和截取
charAt(index)
:
- 返回指定索引处的字符。
const str = 'Hello';
console.log(str.charAt(1)); // 输出: e
charCodeAt(index)
:
- 返回指定索引处字符的 Unicode 值。
const str = 'Hello';
console.log(str.charCodeAt(1)); // 输出: 101
indexOf(searchValue, fromIndex)
:
- 返回指定子字符串首次出现的索引,如果没有则返回
-1
。
const str = 'Hello';
console.log(str.indexOf('l')); // 输出: 2
lastIndexOf(searchValue, fromIndex)
:
- 返回指定子字符串最后一次出现的索引,如果没有则返回
-1
。
const str = 'Hello';
console.log(str.lastIndexOf('l')); // 输出: 3
slice(start, end)
:
- 返回从
start
到end
(不包括end
)的子字符串。
const str = 'Hello';
console.log(str.slice(1, 3)); // 输出: el
substring(start, end)
:
- 返回从
start
到end
(不包括end
)的子字符串。
const str = 'Hello';
console.log(str.substring(1, 3)); // 输出: el
substr(start, length)
:
- 返回从
start
开始的指定长度的子字符串。
const str = 'Hello';
console.log(str.substr(1, 3)); // 输出: ell
2. 字符串修改
toLowerCase()
:
- 将字符串转换为小写。
const str = 'Hello';
console.log(str.toLowerCase()); // 输出: hello
toUpperCase()
:
- 将字符串转换为大写。
const str = 'Hello';
console.log(str.toUpperCase()); // 输出: HELLO
trim()
:
- 去除字符串两端的空白字符。
const str = ' Hello ';
console.log(str.trim()); // 输出: Hello
replace(searchValue, replaceValue)
:
- 替换字符串中的子字符串。
const str = 'Hello';
console.log(str.replace('H', 'h')); // 输出: hello
concat(str1, str2, ...)
:
- 连接两个或多个字符串。
const str1 = 'Hello';
const str2 = 'World';
console.log(str1.concat(' ', str2)); // 输出: Hello World
3. 字符串判断
startsWith(searchString, position)
:
- 判断字符串是否以指定子字符串开头。
const str = 'Hello';
console.log(str.startsWith('He')); // 输出: true
endsWith(searchString, length)
:
- 判断字符串是否以指定子字符串结尾。
const str = 'Hello';
console.log(str.endsWith('lo')); // 输出: true
includes(searchString, position)
:
- 判断字符串是否包含指定子字符串。
const str = 'Hello';
console.log(str.includes('el')); // 输出: true
4. 字符串分割和组合
split(separator, limit)
:
- 将字符串按指定分隔符分割成数组。
const str = 'Hello,World';
console.log(str.split(',')); // 输出: ['Hello', 'World']
repeat(count)
:
- 将字符串重复指定次数。
const str = 'Hello';
console.log(str.repeat(2)); // 输出: HelloHello
5. 其他方法
padStart(targetLength, padString)
:
- 在字符串开头填充指定字符,直到字符串达到指定长度。
const str = 'Hello';
console.log(str.padStart(10, '*')); // 输出: *****Hello
padEnd(targetLength, padString)
:
- 在字符串结尾填充指定字符,直到字符串达到指定长度。
const str = 'Hello';
console.log(str.padEnd(10, '*')); // 输出: Hello*****
match(regexp)
:
- 返回字符串中匹配正则表达式的结果。
const str = 'Hello';
console.log(str.match(/l/g)); // 输出: ['l', 'l']
search(regexp)
:
- 返回字符串中匹配正则表达式的第一个索引,如果没有则返回
-1
const str = 'Hello';
console.log(str.search(/l/)); // 输出: 2
总结
方法 | 描述 |
---|---|
charAt() | 返回指定索引处的字符 |
charCodeAt() | 返回指定索引处字符的 Unicode 值 |
indexOf() | 返回子字符串首次出现的索引 |
lastIndexOf() | 返回子字符串最后一次出现的索引 |
slice() | 返回子字符串 |
substring() | 返回子字符串 |
substr() | 返回子字符串 |
toLowerCase() | 将字符串转换为小写 |
toUpperCase() | 将字符串转换为大写 |
trim() | 去除字符串两端的空白字符 |
replace() | 替换字符串中的子字符串 |
concat() | 连接两个或多个字符串 |
startsWith() | 判断字符串是否以指定子字符串开头 |
endsWith() | 判断字符串是否以指定子字符串结尾 |
includes() | 判断字符串是否包含指定子字符串 |
split() | 将字符串分割成数组 |
repeat() | 将字符串重复指定次数 |
padStart() | 在字符串开头填充指定字符 |
padEnd() | 在字符串结尾填充指定字符 |
match() | 返回字符串中匹配正则表达式的结果 |
search() | 返回字符串中匹配正则表达式的第一个索引 |
掌握这些字符串方法,可以更高效地操作和处理字符串数据。
JavaScript中split、slice、splice函数区别
在 JavaScript 中,split
、slice
和 splice
是三个常用的函数,但它们的功能和用途完全不同。以下是它们的详细对比:
1. split
(1) 功能
- 字符串方法:将字符串按指定分隔符拆分为数组。
- 返回值:一个新的数组,包含拆分后的子字符串。
(2) 语法
str.split(separator, limit)
separator
:分隔符(可以是字符串或正则表达式)。limit
(可选):限制返回数组的最大长度。
(3) 示例
const str = 'apple,banana,orange';
const arr = str.split(','); // 按逗号拆分
console.log(arr); // 输出:['apple', 'banana', 'orange']
(4) 特点
- 仅适用于字符串。
- 不改变原字符串。
2. slice
(1) 功能
- 数组和字符串方法:提取数组或字符串的一部分。
- 返回值:一个新的数组或字符串,包含提取的元素或字符。
(2) 语法
array.slice(start, end)
str.slice(start, end)
start
:起始索引(包含)。end
(可选):结束索引(不包含)。
(3) 示例
// 数组
const arr = [1, 2, 3, 4, 5];
const slicedArr = arr.slice(1, 3); // 提取索引 1 到 2
console.log(slicedArr); // 输出:[2, 3]
// 字符串
const str = 'Hello, World!';
const slicedStr = str.slice(0, 5); // 提取前 5 个字符
console.log(slicedStr); // 输出:Hello
(4) 特点
- 适用于数组和字符串。
- 不改变原数组或字符串。
3. splice
(1) 功能
- 数组方法:删除、替换或插入数组元素。
- 返回值:被删除的元素组成的数组。
(2) 语法
array.splice(start, deleteCount, item1, item2, ...)
start
:起始索引。deleteCount
:要删除的元素个数。item1, item2, ...
(可选):要插入的元素。
(3) 示例
const arr = [1, 2, 3, 4, 5];
const removed = arr.splice(1, 2, 'a', 'b'); // 从索引 1 开始删除 2 个元素,并插入 'a' 和 'b'
console.log(arr); // 输出:[1, 'a', 'b', 4, 5]
console.log(removed); // 输出:[2, 3]
(4) 特点
- 仅适用于数组。
- 改变原数组。
4. 总结对比
函数 | 适用对象 | 功能 | 返回值 | 是否改变原对象 |
---|---|---|---|---|
split | 字符串 | 按分隔符拆分为数组 | 新数组 | 否 |
slice | 数组、字符串 | 提取部分元素或字符 | 新数组或字符串 | 否 |
splice | 数组 | 删除、替换或插入元素 | 被删除的元素组成的数组 | 是 |
5. 使用场景
split
:将字符串按特定规则拆分为数组,如解析 CSV 数据。slice
:提取数组或字符串的一部分,如分页功能。splice
:修改数组内容,如删除或插入元素。
根据需求选择合适的函数,可以更高效地处理数据。
JavaScript强制类型转换和隐式类型转换
在 JavaScript 中,类型转换分为 强制类型转换(显式类型转换) 和 隐式类型转换(自动类型转换)。理解这两种转换方式对于编写健壮的代码非常重要。
- 强制类型转换(显式类型转换)
强制类型转换是开发者通过代码明确指定将一种数据类型转换为另一种数据类型。常见的强制类型转换方法包括:
(1) 转换为字符串
- 使用
String()
或toString()
方法。
let num = 123;
let str = String(num); // "123"
let bool = true;
let str2 = bool.toString(); // "true"
(2) 转换为数字
- 使用
Number()
、parseInt()
或parseFloat()
。
let str = "123";
let num = Number(str); // 123
let str2 = "3.14";
let float = parseFloat(str2); // 3.14
let str3 = "10px";
let int = parseInt(str3); // 10
(3) 转换为布尔值
- 使用
Boolean()
。
let num = 0;
let bool = Boolean(num); // false
let str = "Hello";
let bool2 = Boolean(str); // true
- 隐式类型转换(自动类型转换)
隐式类型转换是 JavaScript 在运行时自动进行的类型转换,通常发生在操作符或上下文中。
(1) 字符串拼接
- 当使用
+
操作符时,如果其中一个操作数是字符串,另一个操作数会被隐式转换为字符串。
let num = 123;
let str = "Hello";
let result = str + num; // "Hello123"
(2) 数学运算
- 当使用
-
、*
、/
等数学操作符时,非数字类型会被隐式转换为数字。
let str = "10";
let num = str - 5; // 5
let bool = true;
let result = bool * 10; // 10(true 被转换为 1)
(3) 布尔上下文
- 在
if
、while
等条件语句中,值会被隐式转换为布尔值。
let num = 0;
if (num) {
console.log("True");
} else {
console.log("False"); // 输出 "False"(0 被转换为 false)
}
(4) 比较操作符
- 在使用
==
或!=
时,JavaScript 会尝试将操作数转换为相同类型后再比较。
console.log(1 == "1"); // true(字符串 "1" 被转换为数字 1)
console.log(true == 1); // true(true 被转换为 1)
(5) 逻辑操作符
- 在使用
&&
、||
时,会根据需要隐式转换为布尔值。
let result = "Hello" || 0; // "Hello"(返回第一个真值)
let result2 = "" && 123; // ""(返回第一个假值)
- 强制类型转换 vs 隐式类型转换
特性 | 强制类型转换 | 隐式类型转换 |
---|---|---|
开发者控制 | 开发者显式调用转换方法 | JavaScript 自动完成 |
可读性 | 代码意图明确,易于理解 | 可能隐藏潜在问题,不易调试 |
常见场景 | String() 、Number() 、Boolean() | 字符串拼接、数学运算、比较操作符等 |
- 注意事项
- 避免隐式转换的陷阱:隐式转换可能导致意外的结果,尤其是在使用
==
时。建议使用===
和!==
来避免隐式转换。 - 显式转换更安全:在需要类型转换时,尽量使用显式转换,以提高代码的可读性和可维护性。
示例代码
// 强制类型转换
let num = 123;
let str = String(num); // "123"
// 隐式类型转换
let result = "5" - 2; // 3(字符串 "5" 被隐式转换为数字 5)
通过理解强制类型转换和隐式类型转换,可以更好地掌握 JavaScript 的类型系统,避免常见的陷阱。
JavaScript对象的几种创建方式
在 JavaScript 中,对象可以通过多种方式创建,每种方法都有其适用场景和特点。以下是常见的对象创建方式及其示例:
1. 对象字面量(Object Literal)
特点:最简洁的方式,直接定义键值对。 适用场景:创建简单的单例对象,无需复用或继承。
const person = {
name: '张三',
age: 30,
greet() {
console.log(`你好,我是${this.name}`);
}
};
person.greet(); // 输出:你好,我是张三
2. 构造函数(Constructor Function)
特点:通过 new
关键字调用构造函数,实例共享原型方法。 适用场景:需要批量创建相似对象,支持继承。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`你好,我是${this.name}`);
};
const person1 = new Person('李四', 25);
person1.greet(); // 输出:你好,我是李四
3. Object.create()
特点:基于现有对象创建新对象,可指定原型。 适用场景:需要精确控制原型链,实现纯净继承。
const protoPerson = {
greet() {
console.log(`你好,我是${this.name}`);
}
};
const person = Object.create(protoPerson);
person.name = '王五';
person.greet(); // 输出:你好,我是王五
创建无原型的对象:
const bareObject = Object.create(null); // 无原型链
bareObject.key = 'value';
console.log(bareObject.toString); // undefined(无继承方法)
4. ES6 类(Class)
特点:语法糖,本质仍基于原型继承,支持 extends
继承。 适用场景:面向对象编程(OOP),需要清晰的结构和继承关系。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`你好,我是${this.name}`);
}
}
const person = new Person('赵六', 28);
person.greet(); // 输出:你好,我是赵六
5. 工厂函数(Factory Function)
特点:通过函数返回新对象,无需 new
关键字。 适用场景:灵活封装对象创建逻辑,避免 this
绑定问题。
function createPerson(name, age) {
return {
name,
age,
greet() {
console.log(`你好,我是${this.name}`);
}
};
}
const person = createPerson('孙七', 35);
person.greet(); // 输出:你好,我是孙七
6. 动态对象(动态属性名)
特点:使用计算属性名动态定义键。 适用场景:属性名需在运行时确定。
const dynamicKey = 'id';
const obj = {
[dynamicKey]: 123,
[`get${dynamicKey}`]() {
return this[dynamicKey];
}
};
console.log(obj.getId()); // 输出:123
7. 对象合并与复制
特点:通过 Object.assign()
或展开运算符 ...
创建新对象。 适用场景:合并对象或创建浅拷贝。
// 使用 Object.assign
const source = { a: 1, b: 2 };
const target = Object.assign({}, source); // 浅拷贝
// 使用展开运算符
const copied = { ...source };
总结与对比
方式 | 语法 | 原型链 | 复用性 | 典型场景 |
---|---|---|---|---|
对象字面量 | {} | Object.prototype | 无 | 简单单例对象 |
构造函数 | new Constructor() | 自定义原型 | 高 | 批量实例化,继承 |
ES6 类 | class + new | 自定义原型 | 高 | OOP,清晰继承结构 |
Object.create | Object.create(proto) | 指定原型 | 中 | 原型链控制,纯净继承 |
工厂函数 | 函数返回对象 | 默认或自定义 | 灵活 | 避免this ,灵活封装逻辑 |
选择建议
- 简单对象:优先使用 对象字面量。
- 批量实例化:选择 构造函数 或 ES6 类。
- 原型控制:使用
Object.create()
。 - 避免
this
:采用 工厂函数。 - 现代语法:推荐 ES6 类 以提升代码可读性。
JavaScript原型、原型链
在 JavaScript 中,原型(Prototype) 和 原型链(Prototype Chain) 是实现继承和共享属性和方法的机制。理解它们是掌握 JavaScript 面向对象编程的关键。
1. 原型(Prototype)
(1) 什么是原型?
- 每个 JavaScript 对象(除
null
外)都有一个内部属性[[Prototype]]
,指向其原型对象。 - 原型对象也是一个普通对象,可以包含属性和方法。
- 通过
__proto__
(非标准)或Object.getPrototypeOf()
访问原型。
(2) 原型的作用
- 共享属性和方法:所有实例共享原型上的属性和方法,节省内存。
- 实现继承:通过原型链访问父类的属性和方法。
(3) 示例
const person = {
greet() {
console.log(`你好,我是${this.name}`);
}
};
const student = {
name: '张三',
__proto__: person // 设置 student 的原型为 person
};
student.greet(); // 输出:你好,我是张三
2. 原型链(Prototype Chain)
(1) 什么是原型链?
- 当访问对象的属性或方法时,如果对象本身没有,JavaScript 会沿着
[[Prototype]]
向上查找,直到找到或到达null
。 - 这种链式查找机制称为 原型链。
(2) 原型链的终点
- 所有对象的原型链最终指向
Object.prototype
,而Object.prototype
的原型是null
。
(3) 示例
const animal = {
eat() {
console.log(`${this.name} 正在吃东西`);
}
};
const dog = {
name: '小黑',
__proto__: animal
};
dog.eat(); // 输出:小黑 正在吃东西
console.log(dog.toString()); // 输出:[object Object](来自 Object.prototype)
3. 构造函数与原型
(1) 构造函数的作用
- 构造函数用于创建对象实例。
- 每个构造函数都有一个
prototype
属性,指向原型对象。 - 实例的
__proto__
指向构造函数的prototype
。
(2) 示例
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`你好,我是${this.name}`);
};
const person1 = new Person('李四');
person1.greet(); // 输出:你好,我是李四
console.log(person1.__proto__ === Person.prototype); // true
4. 原型链的继承
(1) 实现继承
- 通过设置子类构造函数的
prototype
为父类实例,实现原型链继承。 - 问题:父类实例属性会被所有子类实例共享。
(2) 示例
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} 正在吃东西`);
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 设置 Dog.prototype 为 Animal 的实例
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复构造函数指向
const dog = new Dog('小黑', '拉布拉多');
dog.eat(); // 输出:小黑 正在吃东西
5. 原型链的查找机制
(1) 查找过程
- 检查对象自身是否有该属性或方法。
- 如果没有,沿着
__proto__
查找原型对象。 - 重复步骤 2,直到找到或到达
null
。
(2) 示例
const obj = {};
console.log(obj.toString()); // 输出:[object Object]
// 查找过程:obj -> Object.prototype -> null
6. 原型链的注意事项
(1) 原型污染
- 修改原型会影响所有实例。javascript
Array.prototype.push = function() { console.log('原型被修改了!'); }; const arr = [1, 2, 3]; arr.push(4); // 输出:原型被修改了!
(2) 性能问题
- 原型链过长会影响查找性能。
(3) 原型链的终点
- 所有对象的原型链最终指向
Object.prototype
,而Object.prototype
的原型是null
。
7. 原型链的现代替代方案
(1) ES6 类(Class)
- 语法糖,本质仍基于原型链。
- 提供更清晰的继承语法。javascript
class Animal { constructor(name) { this.name = name; } eat() { console.log(`${this.name} 正在吃东西`); } } class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } } const dog = new Dog('小黑', '拉布拉多'); dog.eat(); // 输出:小黑 正在吃东西
(2) Object.create()
- 直接创建对象并指定原型。javascript
const animal = { eat() { console.log(`${this.name} 正在吃东西`); } }; const dog = Object.create(animal); dog.name = '小黑'; dog.eat(); // 输出:小黑 正在吃东西
总结
概念 | 描述 | 核心作用 |
---|---|---|
原型 | 对象的[[Prototype]] 属性 | 共享属性和方法 |
原型链 | 通过[[Prototype]] 链式查找 | 实现继承和属性查找 |
构造函数 | 创建对象实例的函数 | 定义实例属性和原型方法 |
ES6 类 | 基于原型链的语法糖 | 提供清晰的继承语法 |
理解原型和原型链是掌握 JavaScript 继承机制的基础,同时现代语法(如 class
)提供了更简洁的实现方式。
JavaScript的数据对象有哪些属性值
在 JavaScript 中,数据对象(如普通对象、数组、函数等)具有多种属性值,这些属性值可以分为以下几类:
1. 自有属性(Own Properties)
自有属性是直接定义在对象上的属性,而非通过原型链继承的属性。
(1) 数据属性
- 值:属性的实际值。
- 特性(Attributes):
value
:属性的值。writable
:是否可修改。enumerable
:是否可枚举(如for...in
或Object.keys()
)。configurable
:是否可删除或修改特性。
(2) 访问器属性
- 特性:
get
:获取属性值的函数。set
:设置属性值的函数。enumerable
和configurable
:同上。
(3) 示例
const obj = {
name: '张三', // 数据属性
get age() { return 30; }, // 访问器属性
set age(value) { console.log('设置年龄:', value); }
};
console.log(obj.name); // 输出:张三
console.log(obj.age); // 输出:30
obj.age = 40; // 输出:设置年龄:40
2. 继承属性(Inherited Properties)
继承属性是通过原型链从原型对象继承的属性。
(1) 示例
const parent = {
greet() {
console.log('你好!');
}
};
const child = Object.create(parent);
child.greet(); // 输出:你好!(继承自 parent)
3. 内置属性(Internal Properties)
内置属性是 JavaScript 引擎内部使用的属性,通常不可直接访问。
(1) 示例
[[Prototype]]
:对象的原型(通过__proto__
或Object.getPrototypeOf()
访问)。[[Class]]
:对象的内部分类(如[object Object]
)。
4. 特殊属性
(1) length
- 数组:表示数组的长度。javascript
const arr = [1, 2, 3]; console.log(arr.length); // 输出:3
- 函数:表示函数的参数个数。javascript
function fn(a, b) {} console.log(fn.length); // 输出:2
(2) prototype
- 函数:指向函数的原型对象,用于实现基于原型的继承。javascript
function Person() {} console.log(Person.prototype); // 输出:Person {}
(3) __proto__
- 对象:指向对象的原型(非标准,推荐使用
Object.getPrototypeOf()
)。javascriptconst obj = {}; console.log(obj.__proto__ === Object.prototype); // 输出:true
5. 属性的描述符(Property Descriptor)
通过 Object.getOwnPropertyDescriptor()
获取属性的描述符。
(1) 示例
const obj = { name: '张三' };
const descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
// 输出:
// {
// value: '张三',
// writable: true,
// enumerable: true,
// configurable: true
// }
(2) 修改属性描述符
使用 Object.defineProperty()
或 Object.defineProperties()
。
Object.defineProperty(obj, 'name', {
writable: false
});
obj.name = '李四'; // 静默失败(严格模式下报错)
console.log(obj.name); // 输出:张三
6. 属性的枚举与遍历
(1) for...in
- 遍历对象及其原型链上的可枚举属性。javascript
const obj = { a: 1, b: 2 }; for (const key in obj) { console.log(key); // 输出:a, b }
(2) Object.keys()
- 返回对象自身的可枚举属性数组。javascript
console.log(Object.keys(obj)); // 输出:['a', 'b']
(3) Object.getOwnPropertyNames()
- 返回对象自身的所有属性(包括不可枚举属性)。javascript
console.log(Object.getOwnPropertyNames(obj)); // 输出:['a', 'b']
(4) Object.values()
和 Object.entries()
- 返回对象自身的值或键值对数组。javascript
console.log(Object.values(obj)); // 输出:[1, 2] console.log(Object.entries(obj)); // 输出:[['a', 1], ['b', 2]]
7. 属性的存在性检查
(1) in
操作符
- 检查属性是否在对象或其原型链上。javascript
console.log('a' in obj); // 输出:true
(2) hasOwnProperty()
- 检查属性是否为对象自身的属性。javascript
console.log(obj.hasOwnProperty('a')); // 输出:true
总结
属性类型 | 描述 | 示例方法 |
---|---|---|
自有属性 | 直接定义在对象上的属性 | Object.getOwnPropertyDescriptor |
继承属性 | 通过原型链继承的属性 | for...in |
内置属性 | 引擎内部使用的属性 | [[Prototype]] |
特殊属性 | 如length 、prototype 等 | arr.length |
属性描述符 | 描述属性的特性(如可写性、可枚举性) | Object.defineProperty |
理解这些属性值及其特性,有助于更好地操作和管理 JavaScript 对象。
JavaScript宿主对象和原生对象的区别
在 JavaScript 中,宿主对象(Host Objects) 和 原生对象(Native Objects) 是两类不同的对象,它们的来源、行为和用途有显著区别。以下是它们的详细对比:
1. 原生对象(Native Objects)
(1) 定义
- 由 JavaScript 语言本身提供的对象。
- 遵循 ECMAScript 规范定义的标准行为。
(2) 示例
- 核心对象:
Object
、Array
、Function
、String
、Number
、Boolean
、Date
、RegExp
等。 - 内置对象:
Math
、JSON
、Promise
、Proxy
等。
(3) 特点
- 标准化:所有 JavaScript 环境(如浏览器、Node.js)都支持。
- 可预测性:行为符合 ECMAScript 规范。
- 可扩展性:可以通过原型链扩展。
(4) 示例代码
const arr = new Array(1, 2, 3); // 原生对象
console.log(arr.length); // 输出:3
const str = new String('Hello');
console.log(str.toUpperCase()); // 输出:HELLO
2. 宿主对象(Host Objects)
(1) 定义
- 由 JavaScript 的运行环境(如浏览器、Node.js)提供的对象。
- 不属于 ECMAScript 规范,而是由宿主环境定义。
(2) 示例
- 浏览器环境:
window
、document
、XMLHttpRequest
、console
、localStorage
等。
- Node.js 环境:
global
、process
、Buffer
、require
等。
(3) 特点
- 环境依赖:不同宿主环境提供的对象不同。
- 行为差异:可能不符合 JavaScript 标准行为。
- 不可扩展性:部分宿主对象的行为不可修改或扩展。
(4) 示例代码
// 浏览器环境
console.log(window.location.href); // 输出当前 URL
// Node.js 环境
console.log(process.cwd()); // 输出当前工作目录
3. 主要区别
特性 | 原生对象 | 宿主对象 |
---|---|---|
来源 | JavaScript 语言本身 | JavaScript 运行环境(如浏览器、Node.js) |
标准化 | 遵循 ECMAScript 规范 | 由宿主环境定义,可能不标准化 |
可预测性 | 行为一致,可预测 | 行为可能因环境而异 |
可扩展性 | 可通过原型链扩展 | 部分宿主对象不可扩展 |
示例 | Object 、Array 、String | window 、document 、process |
4. 注意事项
- 兼容性问题:宿主对象的行为可能因环境不同而有所差异。
- 例如,
window
对象在浏览器中存在,但在 Node.js 中不存在。
- 例如,
- 性能问题:部分宿主对象(如 DOM 对象)的操作可能较慢。
- 扩展限制:某些宿主对象(如
document
)的行为不可修改。
5. 示例对比
(1) 原生对象的使用
const arr = [1, 2, 3];
arr.push(4); // 原生方法
console.log(arr); // 输出:[1, 2, 3, 4]
(2) 宿主对象的使用
// 浏览器环境
document.title = '新标题'; // 修改页面标题
// Node.js 环境
const fs = require('fs'); // 引入文件系统模块
fs.readFile('file.txt', 'utf8', (err, data) => {
console.log(data);
});
总结
- 原生对象:由 JavaScript 语言提供,行为标准化,适用于所有环境。
- 宿主对象:由运行环境提供,行为可能因环境而异,适用于特定环境。
理解二者的区别有助于编写跨平台兼容的 JavaScript 代码,并避免因环境差异导致的错误。
JavaScript中伪数组是什么,如何将伪数组转化为标准数组
在 JavaScript 中,伪数组(Array-like Object) 是一种类似数组的对象,它具有以下特征:
- 具有
length
属性:表示元素的个数。 - 通过索引访问元素:可以通过
[0]
、[1]
等方式访问元素。 - 不是真正的数组:没有数组的方法(如
push
、forEach
)。
常见的伪数组包括:
arguments
对象- DOM 元素集合(如
document.getElementsByTagName
返回的结果) - 字符串
1. 伪数组的示例
(1) arguments
对象
function example() {
console.log(arguments); // 伪数组
console.log(arguments.length); // 输出:3
console.log(arguments[0]); // 输出:1
}
example(1, 2, 3);
(2) DOM 元素集合
const divs = document.getElementsByTagName('div'); // 伪数组
console.log(divs.length); // 输出:div 元素的数量
console.log(divs[0]); // 输出:第一个 div 元素
(3) 字符串
const str = 'hello'; // 伪数组
console.log(str.length); // 输出:5
console.log(str[0]); // 输出:h
2. 将伪数组转换为标准数组
由于伪数组没有数组的方法,通常需要将其转换为标准数组。以下是几种常见的方法:
(1) 使用 Array.from()
Array.from()
是 ES6 提供的方法,可以将伪数组或可迭代对象转换为数组。
示例:
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const realArray = Array.from(arrayLike);
console.log(realArray); // 输出:['a', 'b', 'c']
(2) 使用扩展运算符(...
)
- 扩展运算符可以将伪数组展开为数组。
示例:
function example() {
const realArray = [...arguments];
console.log(realArray); // 输出:[1, 2, 3]
}
example(1, 2, 3);
(3) 使用 Array.prototype.slice.call()
- 通过
slice
方法将伪数组转换为数组。
示例:
const divs = document.getElementsByTagName('div');
const realArray = Array.prototype.slice.call(divs);
console.log(realArray); // 输出:div 元素的数组
(4) 使用 Array.prototype.concat.apply()
- 通过
concat
方法将伪数组转换为数组。
示例:
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const realArray = Array.prototype.concat.apply([], arrayLike);
console.log(realArray); // 输出:['a', 'b', 'c']
总结
方法 | 优点 | 缺点 |
---|---|---|
Array.from() | 简洁,支持所有伪数组 | 需要 ES6 支持 |
扩展运算符(... ) | 简洁,支持所有可迭代对象 | 需要 ES6 支持 |
Array.prototype.slice.call() | 兼容性好 | 代码较长 |
Array.prototype.concat.apply() | 兼容性好 | 代码较长 |
根据项目需求和兼容性要求,选择合适的方法将伪数组转换为标准数组。推荐优先使用 Array.from()
或扩展运算符。
JavaScript如何分配对象属性
在 JavaScript 中,对象属性的分配可以通过多种方式实现,具体取决于场景和需求。以下是常见的方法及示例:
- 直接赋值
通过点号 .
或方括号 []
直接为对象分配属性。
示例
const obj = {};
// 使用点号(属性名需符合标识符规则)
obj.name = "Alice";
obj.age = 25;
// 使用方括号(属性名可以是动态值或特殊字符)
obj["email"] = "alice@example.com";
const dynamicKey = "address";
obj[dynamicKey] = "New York";
console.log(obj);
// 输出: { name: "Alice", age: 25, email: "alice@example.com", address: "New York" }
- 初始化时分配
在对象字面量中直接定义属性。
示例
const obj = {
name: "Alice",
age: 25,
"email": "alice@example.com", // 属性名加引号也有效
[dynamicKey]: "New York" // 使用计算属性名(ES6+)
};
- 动态属性名(ES6+)
使用计算属性名(方括号 []
)动态设置属性。
示例
const key = "language";
const obj = {
[key]: "JavaScript", // 属性名由变量决定
[`${key}Version`]: "ES6" // 属性名可以是表达式
};
console.log(obj);
// 输出: { language: "JavaScript", languageVersion: "ES6" }
- 批量分配属性
使用 Object.assign()
或展开运算符(...
)批量合并属性。
示例
// 使用 Object.assign()
const obj1 = { a: 1 };
const obj2 = { b: 2 };
Object.assign(obj1, obj2, { c: 3 });
console.log(obj1); // 输出: { a: 1, b: 2, c: 3 }
// 使用展开运算符(ES6+)
const merged = { ...obj1, ...obj2, d: 4 };
console.log(merged); // 输出: { a: 1, b: 2, c: 3, d: 4 }
- 定义属性描述符
使用 Object.defineProperty()
或 Object.defineProperties()
定义属性的特性(如可写性、可枚举性、可配置性)。
示例
const obj = {};
// 定义单个属性
Object.defineProperty(obj, "name", {
value: "Alice",
writable: false, // 不可修改
enumerable: true // 可枚举(例如在 for...in 中出现)
});
// 尝试修改会静默失败(严格模式下报错)
obj.name = "Bob";
console.log(obj.name); // 输出: Alice
// 定义多个属性
Object.defineProperties(obj, {
age: {
value: 25,
enumerable: true
},
email: {
value: "alice@example.com",
enumerable: false // 不可枚举
}
});
console.log(Object.keys(obj)); // 输出: ["name", "age"](email 不可枚举)
- 继承属性的分配
如果对象继承自另一个对象,属性可能被分配到原型链上(需注意原型污染)。
示例
const parent = { inheritedProp: "来自父对象" };
const child = Object.create(parent); // child 继承自 parent
// 直接赋值会添加到 child 自身
child.ownProp = "子对象自身属性";
console.log(child.ownProp); // 输出: "子对象自身属性"
console.log(child.inheritedProp); // 输出: "来自父对象"
- 不可变对象的属性分配
使用 Object.freeze()
或 Object.seal()
限制对象属性的修改。
示例
const obj = { name: "Alice" };
Object.freeze(obj); // 禁止增删改属性
obj.name = "Bob"; // 静默失败(严格模式下报错)
obj.age = 25; // 无效
console.log(obj); // 输出: { name: "Alice" }
- 使用 Proxy 拦截属性分配
通过 Proxy
对象拦截属性的赋值操作(ES6+)。
示例
const target = {};
const handler = {
set: function(obj, prop, value) {
if (prop === "age" && value < 0) {
throw new Error("年龄不能为负数");
}
obj[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
proxy.age = 25; // 正常
proxy.age = -5; // 抛出错误: 年龄不能为负数
常见问题及注意事项
点号与方括号的区别:
点号要求属性名是有效的标识符(如
obj.prop
)。方括号允许动态属性名或特殊字符(如
obj["prop-name"]
)。
深浅拷贝问题:
const obj = { nested: { a: 1 } };
const copy = Object.assign({}, obj);
copy.nested.a = 2; // 原对象的 nested 也会被修改!
- 使用
JSON.parse(JSON.stringify(obj))
或工具库(如 Lodash 的_.cloneDeep
)实现深拷贝。
框架中的响应式限制:
- 在 Vue 中,直接添加新属性可能不会触发视图更新,需使用
Vue.set(obj, key, value)
。
总结
JavaScript 中对象属性的分配方式灵活多样,可根据需求选择合适的方法:
- 直接赋值:简单场景。
- 动态属性名:需要动态键名的场景。
- 属性描述符:需要控制属性行为的场景。
- Proxy:需要拦截或验证属性操作的场景。
- 批量分配:合并多个对象的属性。
根据实际场景选择最佳实践,避免因原型链、深浅拷贝或框架限制导致的问题。
JavaScript中map与foreach的区别
map
和 forEach
是 JavaScript 中用于遍历数组的两种常用方法,它们的主要区别在于返回值和使用场景。以下是它们的详细对比:
1. 返回值
map
:返回一个新数组,新数组的元素是原数组元素经过回调函数处理后的结果。
原数组不会被修改。
forEach
:没有返回值(返回
undefined
)。通常用于直接操作原数组或执行某些操作。
2. 使用场景
map
:适合需要对数组元素进行转换或映射的场景。
示例:将数组中的每个元素乘以 2。
forEach
:适合需要对数组元素执行某些操作(如打印、修改)的场景。
示例:打印数组中的每个元素。
3. 示例代码
map
:
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // 输出: [2, 4, 6]
forEach
:
const numbers = [1, 2, 3];
numbers.forEach(num => console.log(num));
// 输出:
// 1
// 2
// 3
4. 链式调用
map
:- 可以链式调用其他数组方法(如
filter
、reduce
)。
- 可以链式调用其他数组方法(如
const numbers = [1, 2, 3];
const result = numbers
.map(num => num * 2)
.filter(num => num > 3);
console.log(result); // 输出: [4, 6]
forEach
:- 不能链式调用,因为它返回
undefined
。
- 不能链式调用,因为它返回
5. 性能
map
:- 由于需要创建新数组,性能略低于
forEach
。
- 由于需要创建新数组,性能略低于
forEach
:- 性能略高于
map
,因为它不创建新数组。
- 性能略高于
总结
特性 | map | forEach |
---|---|---|
返回值 | 返回新数组 | 返回 undefined |
使用场景 | 数组元素转换或映射 | 数组元素操作 |
链式调用 | 支持 | 不支持 |
性能 | 略低 | 略高 |
根据具体需求选择合适的方法:
如果需要返回一个新数组,使用
map
。如果只需要遍历数组并执行操作,使用
forEach
。
JavaScript对象的key能是数字吗
在 JavaScript 中,对象的键(key)可以是数字,但需要注意以下几点:
1. 数字键的特性
自动转换为字符串:
- JavaScript 对象的键只能是字符串或 Symbol 类型。如果使用数字作为键,它会被自动转换为字符串。
const obj = {
1: 'One',
2: 'Two'
};
console.log(obj['1']); // 输出: One
console.log(obj[1]); // 输出: One
2. 使用数字键的注意事项
访问方式:
- 可以使用点号(
.
)或方括号([]
)访问数字键,但点号后的键会被解释为标识符,因此只能使用方括号。
- 可以使用点号(
const obj = { 1: 'One' };
console.log(obj[1]); // 输出: One
console.log(obj['1']); // 输出: One
// console.log(obj.1); // 报错
遍历顺序:
- 数字键会按照升序排列,字符串键按照插入顺序排列。
const obj = {
2: 'Two',
1: 'One',
'b': 'B',
'a': 'A'
};
console.log(Object.keys(obj)); // 输出: ['1', '2', 'b', 'a']
3. 使用数字键的场景
数组:
- 数组的索引本质上是数字键。
const arr = ['One', 'Two'];
console.log(arr[0]); // 输出: One
映射数字到值:
- 当需要将数字映射到特定值时,可以使用数字键。
const statusMap = {
0: 'Pending',
1: 'Approved',
2: 'Rejected'
};
console.log(statusMap[1]); // 输出: Approved
总结
特性 | 描述 |
---|---|
自动转换 | 数字键会被自动转换为字符串 |
访问方式 | 只能使用方括号访问数字键 |
遍历顺序 | 数字键按升序排列,字符串键按插入顺序 |
适用场景 | 数组、映射数字到值 |
虽然 JavaScript 对象的键可以是数字,但实际存储时会转换为字符串。根据具体需求合理使用数字键,可以提高代码的可读性和可维护性。
JavaScript浅拷贝和深拷贝的区别
在 JavaScript 中,浅拷贝和深拷贝是两种不同的对象复制方式,主要区别在于对嵌套对象的处理。以下是它们的详细对比:
1. 浅拷贝(Shallow Copy)
定义:
- 浅拷贝只复制对象的第一层属性,如果属性是引用类型(如对象、数组),则复制其引用,而不是实际的值。
特点:
嵌套对象与原对象共享同一引用。
修改嵌套对象会影响原对象。
实现方式:
扩展运算符:
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };
Object.assign
:
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj);
- 数组的
slice
或concat
:
const arr = [1, 2, { a: 3 }];
const shallowCopy = arr.slice();
2. 深拷贝(Deep Copy)
定义:
- 深拷贝会递归复制对象的所有层级,包括嵌套对象和数组,生成一个完全独立的新对象。
特点:
嵌套对象与原对象不共享引用。
修改嵌套对象不会影响原对象。
实现方式:
JSON.parse(JSON.stringify(obj))
:
const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
局限性:
无法复制函数、
undefined
、Symbol
和循环引用。递归实现:
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
const copy = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
const obj = { a: 1, b: { c: 2 } };
const deepCopy = deepCopy(obj);
使用库函数:
使用
lodash
的cloneDeep
方法:
const _ = require('lodash');
const obj = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(obj);
3. 示例对比
- 浅拷贝:
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };
shallowCopy.b.c = 3;
console.log(obj.b.c); // 输出: 3(原对象被修改)
- 深拷贝:
const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
deepCopy.b.c = 3;
console.log(obj.b.c); // 输出: 2(原对象未被修改)
总结
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
复制层级 | 只复制第一层属性 | 递归复制所有层级 |
嵌套对象 | 共享引用,修改会影响原对象 | 独立引用,修改不会影响原对象 |
实现方式 | 扩展运算符、Object.assign、slice | JSON.parse(JSON.stringify)、递归、库函数 |
适用场景 | 简单对象复制 | 复杂对象复制,需要完全独立的对象 |
根据具体需求选择合适的拷贝方式,可以避免意外的数据修改问题。
JavaScript是静态类型语言还是动态类型语言,二者区别
JavaScript 是一种 动态类型语言。以下是静态类型语言和动态类型语言的区别:
1. 静态类型语言
(1) 定义
- 在 编译时 确定变量的类型。
- 类型检查在编译阶段完成。
(2) 特点
- 类型声明:变量类型需显式声明。
- 类型安全:编译时检查类型错误,减少运行时错误。
- 性能优化:编译器可以根据类型信息优化代码。
(3) 示例语言
- C、C++、Java、TypeScript 等。
(4) 示例代码
// Java(静态类型语言)
int x = 10; // 显式声明类型
x = "Hello"; // 编译错误:类型不匹配
2. 动态类型语言
(1) 定义
- 在 运行时 确定变量的类型。
- 类型检查在运行时完成。
(2) 特点
- 类型推断:变量类型无需显式声明,根据赋值推断。
- 灵活性:变量可以随时改变类型。
- 运行时错误:类型错误在运行时才会被发现。
(3) 示例语言
- JavaScript、Python、Ruby、PHP 等。
(4) 示例代码
// JavaScript(动态类型语言)
let x = 10; // x 是数字类型
x = "Hello"; // x 变为字符串类型
console.log(x); // 输出:Hello
3. 静态类型与动态类型的对比
特性 | 静态类型语言 | 动态类型语言 |
---|---|---|
类型检查 | 编译时 | 运行时 |
类型声明 | 需显式声明 | 无需显式声明 |
类型安全 | 编译时检查,减少运行时错误 | 运行时检查,可能产生运行时错误 |
性能 | 编译时优化,性能更高 | 运行时推断,性能较低 |
灵活性 | 较低,类型固定 | 较高,类型可变 |
开发效率 | 较低,需关注类型声明 | 较高,无需关注类型声明 |
4. JavaScript 的动态类型特性
(1) 类型推断
- 变量类型根据赋值自动推断。javascript
let x = 10; // x 是数字类型 x = "Hello"; // x 变为字符串类型
(2) 类型转换
- JavaScript 会自动进行类型转换。javascript
console.log(10 + "20"); // 输出:1020(数字转换为字符串)
(3) 运行时类型检查
- 类型错误在运行时才会被发现。javascript
const obj = null; console.log(obj.name); // 运行时错误:TypeError
5. TypeScript:JavaScript 的静态类型扩展
- TypeScript 是 JavaScript 的超集,增加了静态类型支持。
- 在 TypeScript 中,可以显式声明变量类型,并在编译时检查类型错误。
示例:
let x: number = 10; // 显式声明类型
x = "Hello"; // 编译错误:类型不匹配
总结
语言类型 | JavaScript | TypeScript |
---|---|---|
类型系统 | 动态类型 | 静态类型 |
类型检查 | 运行时 | 编译时 |
类型声明 | 无需显式声明 | 需显式声明 |
适用场景 | 快速开发,灵活性高 | 大型项目,类型安全要求高 |
JavaScript 是动态类型语言,适合快速开发和灵活性要求高的场景;而 TypeScript 提供了静态类型支持,适合大型项目和类型安全要求高的场景。