什么时候开始记录都不算晚,即使已经是晚了,但是再早的东西也会有淘汰的时候,收藏另说。
问题主要选取一些高频和基础的问题。(问题的回答只是本人的理解,非参考答案,有些答案只给个提示,详解可谷歌百度或在掘金内搜索相关文章、同时本人每次回顾更新文章都会重新搜一下,与时俱进同步新答案)
1、ES6新增了哪些拓展?
答:
**箭头函数(Arrow Functions):** 箭头函数提供了一种更简洁的函数定义方式,同时还改变了函数内部`this`的行为。
**let 和 const 关键字:** `let`和`const`用于报表变量。`let`报表变量具有块级作用域,而`const`报表变量是不可重新赋值关键字的常量。
**模板字符串(Template Strings):** 使用反引号(`)来创建多行字符串和插入变量,使字符串修剪更加简单。
**解构赋值(DestructuringAssignment):** 允许从数组或对象中提取值,并给出赋值的变量,简化了代码。
**默认参数(DefaultParameters):** 在函数定义中可以为参数提供默认值,当调用函数时未传递对应参数时将使用默认值。引入了关键字`import`和`export`关键字,使得标准化开发更加方便和可维护。
**展开操作(Spread Operator):** 用于在磁盘或对象面量中展开磁盘元素或对象属性,方便地进行合并和复制。
**Rest参数(RestParameter):** 允许将函数的多个参数收集成一个数组,使得函数能够处理任意数量的参数。
**类和继承(Classes and Inheritance):** 引入了类和继承的概念,更接近传统面向对象编程风格。
**(Modules):** 引入了模块化编程,使得JavaScript代码可以更好地组织和重用。
**Promise 和异步编程(Promises and Asynchronous Programming):** 引入了 Promise 对象,用于更优雅地处理异步操作,避免了回调地狱。
**生成器允许函数(Generators):** 在函数执行过程中暂停和恢复执行,可用于更复杂的异步控制流程。
**符号数据类型(Symbols):** 引入了一种新的数据类型,用于创建唯一且不可修改的属性键。
**Map 和 Set 数据结构:** 引入了更灵活和功能丰富的 Map 和 Set 数据结构,用于存储键值对和唯一值。
**阵列扩展方法:** 引入了许多新的阵列方法,如`map`、、`filter`等`reduce`,使阵列处理更加方便。
**字符串新增方法:** 引入了许多新的字符串方法,如`startsWith`、、`endsWith`等`includes`,增强了字符串处理能力。
这只是 ES6 引入的一些主要特性,还有其他更多的改进和扩展。随着 ECMAScript 版本的不断更新,JavaScript 语言不断获得新的功能和语法。
2、如何实现深拷贝?
答:
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的深拷贝方式有:
- _.cloneDeep()
- jQuery.extend()
- JSON.stringify()
- 手写循环递归
3、对一个数组去除重复项,该怎么做?
答:
**使用 Set**: 最简单的方法是将数组转换为 Set,因为 Set 只会保留唯一的值,然后再将 Set 转回为数组。
const originalArray = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = Array.from(new Set(originalArray));
console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]
**使用 filter 方法**: 使用 `filter` 方法可以通过检查数组中元素的索引来保留第一个出现的元素,从而去除重复项。
const originalArray = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = originalArray.filter((value, index, self) => {
return self.indexOf(value) === index;
});
console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]
**使用 reduce 方法**: 使用 `reduce` 方法可以构建一个新数组,逐步将不重复的元素添加到数组中。
const originalArray = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = originalArray.reduce((accumulator, currentValue) => {
if (!accumulator.includes(currentValue)) {
accumulator.push(currentValue);
}
return accumulator;
}, []);
console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]
**使用 Map**: 使用 Map 数据结构来存储数组中的元素,以元素值作为键。这样,重复元素将自动被覆盖,最后再将 Map 中的值提取为数组。
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = Array.from(new Map(array.map(value => [value, value])).values());
console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]
这些方法中,使用 Set 是最简单和高效的方式。然而,如果你需要保留原始数组的顺序,可以使用 `filter` 或 `reduce` 方法来实现去重操作。
4、写出当一个ul列表,当点击li时,li内容变成...。
答:
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li onclick="toggleContent(this)">Item 3</li>
<!-- 可以添加更多的 <li> 元素 -->
</ul>
const list = document.getElementById("list");
list.addEventListener("click", function(event) {
const clickedElement = event.target;
if (clickedElement.tagName === "LI") {
if (clickedElement.textContent === "...") {
clickedElement.textContent = clickedElement.dataset.originalContent;
} else {
clickedElement.dataset.originalContent = clickedElement.textContent;
clickedElement.textContent = "...";
}
}
});
const list = document.getElementById("list");
const items = list.getElementsByTagName("li");
for (let i = 0; i < items.length; i++) {
items[i].addEventListener("click", function() {
this.textContent = "...";
});
}
function toggleContent(element) {
if (element.textContent === "...") {
element.textContent = element.dataset.originalContent;
} else {
element.dataset.originalContent = element.textContent;
element.textContent = "...";
}
}
5、如何使用正则表达式获取地址栏的参数。
答:
function getURLParameters(url) { const queryString = url.split('?')[1]; if (!queryString) { return {}; } const paramPairs = queryString.split('&'); const params = {}; for (const pair of paramPairs) { const [key, value] = pair.split('='); params[key] = decodeURIComponent(value); } return params; } const url = window.location.search; const parameters = getURLParameters(url); console.log(parameters);
6、对数组中的元素去重,然后对去重后的元素进行排序。
答:
const originalArray = [3, 1, 2, 2, 4, 1, 5];
// 使用 Set 去重
const uniqueSet = new Set(originalArray);
// 使用 Array.from() 转换为数组,并排序
const sortedArray = Array.from(uniqueSet).sort((a, b) => a - b);
console.log(sortedArray);
7、call apply bind的区别是什么?
答:
`call`, `apply`, 和 `bind` 都是 JavaScript 中用于处理函数调用和上下文的方法。它们的作用都是改变函数的执行环境,但具体的用法和效果有一些区别。
call方法:
call方法用于调用一个函数,并将一个指定的对象作为函数的上下文(this)传递进去。此外,你可以传递任意数量的参数给函数。语法如下:
`function functionName(arg1, arg2, ...argN) {
// 函数体
}
functionName.call(context, arg1, arg2, ...argN);`
context 是函数要绑定的上下文对象,而后面的参数是函数的参数。
apply方法
apply方法与call类似,也用于调用一个函数并改变函数的上下文。然而,不同之处在于它接受一个参数数组作为参数,而不是逐个列举参数。语法如下:
functionName.apply(context, [arg1, arg2, ...argN]);
这里的 `context` 是函数要绑定的上下文对象,第二个参数是一个包含参数的数组。
bind方法:
bind方法创建一个新的函数,并将原函数的上下文(this)永久地绑定到指定的对象。返回的新函数在调用时无论在什么上下文中都会保持原来的上下文。语法如下:
const newFunction = functionName.bind(context);
这里的 `context` 是函数要绑定的上下文对象。
综上所述,三者的区别主要在于传递参数的方式和是否永久绑定上下文。`call` 和 `apply` 的主要区别在于参数传递的方式,而 `bind` 则是创建了一个新函数,并将上下文永久绑定到该函数。
8、请简述什么是广度优先遍历和深度优先遍历,它们的区别是什么?
答:
广度优先遍历(Breadth-First Traversal)和深度优先遍历(Depth-First Traversal)是两种常用的树和图遍历算法。
1. **广度优先遍历(BFS)** : 广度优先遍历是一种层次遍历算法,从根节点开始,逐层遍历树或图的节点。具体步骤是从根节点开始,先访问根节点,然后依次访问其相邻的节点,再访问相邻节点的相邻节点,依此类推。这种遍历方式类似于水波从中心向外扩散的方式。
**区别**:
- 广度优先遍历是逐层进行的,先遍历完当前层的所有节点,然后再进入下一层。
- 使用队列来实现广度优先遍历,先进先出(FIFO)的顺序。
1. **深度优先遍历(DFS)** : 深度优先遍历是一种以深度为优先级的遍历方式,从根节点开始,尽可能深地访问树或图的节点,直到达到叶子节点,然后回溯到上一层继续遍历。具体步骤是从根节点开始,先访问根节点,然后选择一个子节点继续访问,直到到达叶子节点,然后返回到父节点,再选择另一个未访问的子节点,依此类推。这种遍历方式类似于一条路走到底,然后返回走另一条路。
**区别**:
- 深度优先遍历是沿着一条路径一直深入到底部,然后再回溯到上一层继续。
- 使用递归或栈来实现深度优先遍历。
区别总结:
- 广度优先遍历逐层遍历,深度优先遍历逐个路径深入。
- 广度优先遍历使用队列,深度优先遍历使用递归或栈。
- 在树的情况下,广度优先遍历得到的是层次顺序的遍历结果,深度优先遍历可能会有不同的路径顺序。
- 深度优先遍历在一些情况下可能会更快,因为它往往可以更快地到达目标节点。
9、什么是防抖和节流,它们的区别是什么?
答:
防抖(Debounce)和节流(Throttle)是在处理频繁触发的事件时常用的两种优化技术,用于限制函数的执行频率,从而提高性能和用户体验。
**防抖**:在事件触发后,等待一段时间(例如200毫秒),如果在这段时间内没有再次触发事件,那么执行相应的处理函数。如果在等待时间内又触发了事件,则重新计时。防抖适用于处理频繁触发的事件,如窗口大小调整、搜索框输入等,以避免频繁的函数调用。
**节流**:在一定的时间间隔内(例如200毫秒),只执行一次事件的处理函数。如果事件在时间间隔内多次触发,只有第一次会执行函数,其他的会被忽略。节流适用于需要限制函数执行频率的场景,如滚动事件、鼠标移动事件等,以避免过多的计算。
区别:
**触发时机**:
- 防抖:在事件触发后的等待时间内没有再次触发时执行函数。
- 节流:在一定的时间间隔内执行函数,只执行一次。
**函数执行次数**:
- 防抖:只在事件最后一次触发后执行一次函数。
- 节流:在时间间隔内可能会执行多次函数,但每个时间间隔只会执行一次。
**适用场景**:
- 防抖:适用于需要避免频繁函数调用的场景,如窗口大小调整、搜索框输入等。
- 节流:适用于需要限制函数执行频率的场景,如滚动事件、鼠标移动事件等。
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 使用防抖处理滚动事件
const debouncedScroll = debounce(() => {
console.log("Scrolled!");
}, 200);
window.addEventListener("scroll", debouncedScroll);
function throttle(func, interval) {
let lastTime = 0;
return function(...args) {
const currentTime = Date.now();
if (currentTime - lastTime >= interval) {
func.apply(this, args);
lastTime = currentTime;
}
};
}
// 使用节流处理滚动事件
const throttledScroll = throttle(() => {
console.log("Scrolled!");
}, 200);
window.addEventListener("scroll", throttledScroll);
10、vue的生命周期钩子有哪些?,Vue组件之间的通信方式都有哪些?父子,兄弟,隔代/祖孙与后代,非关系组件。
答:
Vue.js 组件的生命周期钩子函数以及组件之间的通信方式如下:
**生命周期钩子函数**:
1. **beforeCreate**:在实例被创建之前被调用,数据观测和事件配置之前。
1. **created**:实例已经创建完成,数据观测和事件配置完成,但 DOM 尚未生成。
1. **beforeMount**:在挂载开始之前被调用,此时还没有渲染 DOM。
1. **mounted**:实例已经挂载到 DOM 上,可以访问 DOM 元素。
1. **beforeUpdate**:数据更新时调用,但是在 DOM 更新之前。
1. **updated**:数据更新时调用,DOM 已经更新。
1. **beforeDestroy**:实例销毁之前调用。
1. **destroyed**:实例已经销毁,清理工作应在这里进行。
1. **activated**(仅在 `keep-alive` 组件中可用):组件被激活时调用。
1. **deactivated**(仅在 `keep-alive` 组件中可用):组件被停用时调用。
**组件之间的通信方式**:
1. **父子组件通信**:
- 通过 `props` 将数据从父组件传递到子组件。
- 使用 `$emit` 在子组件中触发事件,由父组件监听该事件。
1. **兄弟组件通信**:
- 使用共同的父组件作为中介,通过父组件的数据传递到兄弟组件。
1. **隔代/祖孙组件通信**:
- 使用 `$attrs` 和 `$listeners` 属性,在嵌套的子组件中传递数据和事件。
- 使用 `provide` 和 `inject` 来在祖先组件中提供数据,然后在后代组件中进行注入。
- 使用 `ref` 来获取对 DOM 元素或子组件的引用。
1. **非关系组件通信**:
- 使用 Vuex 进行集中状态管理,允许多个组件共享状态。
- 使用事件总线,创建一个 Vue 实例作为事件中心,组件通过它来发送和接收事件。
- 使用 `.sync` 修饰符在父子组件之间实现双向绑定。
- 使用 `$parent` 访问父组件的实例,使用 `$root` 访问根组件的实例。
这些通信方式可以根据实际场景来选择,每种方式都有适用的情况。例如,如果需要在父子组件之间传递数据,可以使用 `props` 和 `$emit`;如果需要在兄弟组件之间通信,可以通过共同的父组件作为中介;如果需要在隔代/祖孙组件之间通信,可以使用 `$attrs` 和 `$listeners` 或者 `provide` 和 `inject`,还可以使用 `ref`;对于非关系组件通信,可以根据项目的规模和复杂性选择使用 Vuex、事件总线、`.sync`、`$parent` 或 `$root`。
11、简述如果做一个医院预约小程序该怎么做?你认为你会遇到什么问题?怎么解决?小程序的大概功能需要有预约,查看预约内容,支付,管理订单,查看报告。
答:
设计医院预约小程序需要从用户界面、交互体验、前后端分离、性能优化等方面考虑。以下是一个前端设计方案,以及前端语言和框架的选择:
**前端设计:**
1. **用户界面设计**:设计直观、易用的用户界面,确保用户可以轻松预约、查看订单、支付等操作。
1. **交互体验**:优化用户交互体验,确保界面响应迅速、操作流畅。
1. **导航结构**:设计清晰的导航结构,使用户能够无缝切换预约、支付、订单管理和报告查看等功能。
**前端语言和框架选择:**
1. **前端语言**:在小程序开发中,主要使用 JavaScript。考虑到目标平台是微信小程序,你应该使用小程序支持的语言。
1. **前端框架**:微信小程序有自己的原生框架和组件库。你可以使用原生框架,也可以选择使用小程序开发框架,如 Taro 或 uni-app,它们可以实现跨平台开发,减少重复劳动。
**页面和功能:**
1. **预约页面**:用户可以选择医生、时间,并填写个人信息进行预约。
1. **订单管理页面**:用户可以查看已预约的订单,取消订单等。
1. **支付页面**:用户确认预约后,跳转至支付页面,支付预约费用。
1. **报告查看页面**:用户可以查看已完成的检查报告。
**性能优化:**
1. **图片优化**:对页面中的图片进行压缩,减少加载时间。
1. **懒加载**:对长列表和大图像进行懒加载,提升页面加载速度。
1. **异步加载**:使用异步加载技术,避免阻塞用户界面。
1. **代码分割**:按需加载页面组件和功能,提升初始加载速度。
**用户隐私与安全:**
1. **数据加密**:对用户隐私数据进行加密存储,确保数据安全性。
1. **权限控制**:确保用户只能访问其自己的预约、订单和报告等数据。
1. **合规性**:遵循相关数据隐私保护法规,保障用户隐私。
**测试和发布:**
1. 对小程序进行全面测试,包括功能测试、性能测试和兼容性测试。
1. 准备小程序的上线材料,如图标、描述、截图等。
1. 提交小程序到微信小程序平台,进行审核和发布。
综上所述,选择前端语言和框架时,考虑项目需求、开发团队技术熟悉度和目标平台特点。确保设计能提供良好的用户体验、高效的开发流程,并与后端开发紧密配合,共同实现医院预约小程序的功能。
12、现有如下数组,请修改数组下所有massage的值。
const newArray = [
{
title: 'title-1',
message: 'message-1',
children: [
{
title: 'title-1-1',
children: [
{title: 'title-1-1-1',message: 'message-1-1-1',children...},
{title: 'title-1-1-2',message: 'message-1-1-2',children...}
]
},
{
title: 'title-1-2',
children: [
{title: 'title-1-2-1',message: 'message-1-2-1',children...},
{title: 'title-1-2-2',message: 'message-1-2-2',children...}
]
}
]
},
{
title: 'title-2',
message: 'message-2',
children: [
...
]
}
]
答:
递归遍历的方法
function modifyMessages(arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (item.message) {
// 修改 message 的值,这里假设你想将 message 加上前缀
item.message = `Modified: ${item.message}`; }
if (item.children && item.children.length > 0) {
// 递归遍历子集
modifyMessages(item.children);
}
}
}
modifyMessages(newArray);
使用栈(Stack)或队列(Queue)
function modifyMessages(arr) {
const queue = [...arr];
while (queue.length > 0) {
const item = queue.shift();
if (item.message) {
item.message = 'New Message'; // 修改 message 值
}
if (item.children) {
queue.push(...item.children); // 将子集加入队列继续处理
}
}
}
modifyMessages(newArray);
使用递归的同时利用数组的 `map` 方法来修改对象属性
function modifyMessages(arr) {
return arr.map(item => {
const newItem = { ...item }; // 创建新对象,避免修改原对象
if (newItem.message) {
newItem.message = 'New Message'; // 修改 message 值
}
if (newItem.children) {
newItem.children = modifyMessages(newItem.children); // 递归处理子集
}
return newItem;
});
}
const modifiedArray = modifyMessages(newArray);
console.log(modifiedArray);
使用生成器函数(Generator)来实现迭代修改
function* modifyGenerator(arr) {
for (const item of arr) {
const newItem = { ...item }; // 创建新对象,避免修改原对象
if (newItem.message) {
newItem.message = 'New Message'; // 修改 message 值
}
if (newItem.children) {
newItem.children = yield* modifyGenerator(newItem.children); // 递归处理子集
}
yield newItem;
}
}
const modifiedArray = [...modifyGenerator(newArray)];
console.log(modifiedArray);
基于递归和 ES6 的解构赋值的方法
function modifyMessages(arr) {
return arr.map(item => {
const { children, ...rest } = item; // 使用解构赋值获取除 children 外的其他属性
const newItem = { ...rest }; // 创建新对象,避免修改原对象
if (newItem.message) {
newItem.message = 'New Message'; // 修改 message 值
}
if (children) {
newItem.children = modifyMessages(children); // 递归处理子集
}
return newItem;
});
}
const modifiedArray = modifyMessages(newArray);
console.log(modifiedArray);