一、其它
1、前端性能优化的方式有哪些
前端性能优化是确保网页在加载速度、交互性能和资源利用方面表现出色的关键。以下是一些前端性能优化的常见方式:
减少HTTP请求:
- 合并和压缩CSS和JavaScript文件,减少HTTP请求次数。
- 使用CSS Sprites或图像雪碧图将多个小图标合并到一个图像文件中。
- 使用图像懒加载,仅在用户需要时加载图像。
使用CDN(内容分发网络):
- 托管静态资源(如CSS、JavaScript、图像)在CDN上,以加速资源加载。
- CDNs通常具有全球分布,可减少资源加载时间。
优化图像:
- 使用适当的图像格式,如WebP,以减小图像文件大小。
- 使用图像压缩工具来减少图像文件大小。
- 使用响应式图片,根据设备分辨率提供不同大小的图像。
使用浏览器缓存:
- 设置适当的缓存控制头,如
Cache-Control
和Expires
,以减少重复加载资源。 - 使用浏览器本地缓存(LocalStorage和SessionStorage)来存储数据,减少服务器请求。
- 设置适当的缓存控制头,如
压缩和优化代码:
- 使用工具(如UglifyJS或Terser)来压缩JavaScript代码。
- 优化JavaScript性能,避免不必要的循环和操作。
- 删除未使用的CSS和JavaScript代码。
减少重绘和回流:
- 使用CSS硬件加速来减少重绘和回流。
- 使用
requestAnimationFrame
来执行动画,以最大程度减少性能问题。
懒加载和分页加载:
- 使用懒加载技术,延迟加载页面上不可见区域的内容。
- 分页加载长列表或大数据集,减少一次性加载的内容量。
这些优化技术和策略可以帮助提高前端性能,加快页面加载速度,提升用户体验。根据项目需求和情况,可以选择合适的优化方法。需要注意的是,性能优化是一个持续的过程,随着项目的发展和变化,需要不断地进行监测和改进。
2、浏览器本地存储方式及使用场景
浏览器本地存储提供了多种方式来在客户端存储数据,每种方式都有不同的使用场景和特点。以下是常见的浏览器本地存储方式及其使用场景:
Cookies(Cookie):
- 使用场景:Cookies 最常用于存储小量数据,通常是文本,用于跟踪用户会话状态、身份验证令牌和一些偏好设置。
- 特点:Cookies 可以设置过期时间,可以在同一域名下的不同页面之间共享,但大小有限制(通常几 KB)。
LocalStorage:
- 使用场景:LocalStorage 适用于需要在客户端永久性存储数据的情况,例如保存用户首选项、离线应用数据和缓存大量数据。
- 特点:数据以键值对的形式存储,容量较大(通常在 5MB 到 10MB 之间),可以在同一域名下的不同页面之间共享。
SessionStorage:
- 使用场景:SessionStorage 用于存储会话级别的数据,只在会话期间有效,通常用于暂时保存用户在单个页面或标签页上的数据。
- 特点:数据以键值对的形式存储,容量较大(与LocalStorage相似),仅在当前窗口或标签页中共享。
选择合适的本地存储方式取决于你的具体需求。需要考虑数据大小、生命周期、共享需求、数据类型和复杂性等因素。同时,要注意保护用户隐私和敏感信息,避免存储敏感数据在客户端。
3、git 和 svn 的区别
Git 和 SVN(Subversion)是两种不同的版本控制系统,它们在设计和工作方式上有一些重要的区别:
分布式 vs 集中式:
- Git 是一种分布式版本控制系统,每个开发者都拥有完整的代码库的副本,可以在本地进行工作,不需要持续连接到中央服务器。
- SVN 是一种集中式版本控制系统,所有代码存储在中央服务器上,开发者需要从中央服务器获取代码并提交更改。
性能:
- Git 通常被认为比 SVN 更快速和高效。因为每个开发者都有完整的本地仓库,大多数操作都可以在本地执行,减少了网络通信的需要。
- SVN 需要频繁与中央服务器通信,因此在大型项目中可能会出现性能问题。
分支和合并:
- Git 对分支和合并提供了强大的支持,创建和合并分支相对容易,这使得并行开发和实验性工作更加方便。
- SVN 也支持分支和合并,但相对复杂,操作不如 Git 那么容易。
历史记录:
- Git 保存完整的项目历史记录,每个提交都是一个快照,这使得查看历史记录和回滚更加容易。
- SVN 保存的是每个文件的变更历史,而不是整个项目的快照。
离线工作:
- Git 允许开发者在离线情况下继续工作,因为他们拥有完整的本地仓库。
- SVN 需要连接到中央服务器,因此在离线情况下无法提交更改或获取代码。
综上所述,Git 和 SVN 在分布式性能、分支管理、历史记录、离线工作和生态系统等方面存在明显的区别。选择哪种版本控制系统应取决于项目的需求和团队的工作流程。现代开发中,Git 更为流行,特别是在开源社区和大型协作项目中。
4、webpack的构建流程
Webpack 的构建流程包括以下关键步骤:
入口文件(Entry):Webpack 从一个或多个入口文件开始构建,入口文件定义了整个应用程序的起点。
依赖分析(Dependency Analysis):Webpack 分析入口文件及其依赖的模块,构建整个依赖关系图。
加载器(Loaders):在分析过程中,Webpack 使用加载器处理各种文件类型,将它们转换为可打包的模块。
插件(Plugins):Webpack 插件用于执行各种构建任务,如代码压缩、文件拷贝、环境变量注入等。
输出(Output):Webpack 将构建后的模块打包成一个或多个输出文件,通常是 JavaScript 文件,这些文件将在浏览器中运行。
以下是一个简单的示例,演示了一个基本的 Webpack 配置文件和构建流程:
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', // 入口文件
output: {
filename: 'bundle.js', // 输出文件名
path: path.resolve(__dirname, 'dist'), // 输出目录
},
module: {
rules: [
{
test: /\.js$/, // 匹配以 .js 结尾的文件
exclude: /node_modules/, // 排除 node_modules 目录
use: {
loader: 'babel-loader', // 使用 Babel 加载器处理 .js 文件
options: {
presets: ['@babel/preset-env'], // Babel 预设
},
},
},
{
test: /\.css$/, // 匹配以 .css 结尾的文件
use: ['style-loader', 'css-loader'], // 使用 style-loader 和 css-loader 处理 .css 文件
},
],
},
plugins: [
// 插件配置
// 例如:可以添加 HtmlWebpackPlugin 以生成 HTML 文件
],
};
在上面的示例中,Webpack 的配置文件指定了入口文件、输出文件、加载器规则和插件配置。当运行 webpack
命令时,Webpack 将根据配置文件执行构建流程,处理入口文件及其依赖,应用加载器和插件,最终生成 bundle.js
文件并输出到 dist
目录。
请注意,上述示例是一个简化的 Webpack 配置,实际项目中可能需要更复杂的配置以满足项目需求。配置文件中的加载器和插件也可以根据项目的特定需求进行扩展和定制。
4、重绘和重排是什么
重绘(Repainting)和重排(Reflow)是与浏览器渲染页面元素相关的两个概念,它们在前端性能优化中非常重要。
重绘(Repainting):
- 重绘指的是在不改变文档布局的情况下,重新绘制元素的外观。这通常发生在某个元素的样式属性(如颜色、背景、边框等)发生改变时。
- 重绘不会导致文档的几何属性(如位置和大小)发生改变,因此不需要重新计算布局。
重排(Reflow):
- 重排是指在改变了 DOM 结构或者元素的布局属性(如宽度、高度、边距、填充等)时,浏览器需要重新计算元素的几何属性,重新布局页面。
- 重排是一项计算密集型的操作,因为它会影响整个文档的布局,可能导致其他元素的位置和大小也需要重新计算。
重绘和重排的关系:
- 重排必然会导致重绘。因为在元素的几何属性发生改变时,浏览器需要重新计算元素的样式和外观。
- 重绘不一定导致重排。如果只改变了元素的样式属性而没有影响到布局属性,就只会触发重绘,不会触发重排。
- 避免不必要的重排和重绘是前端性能优化的关键,因为它们消耗了大量计算资源,特别是在大型复杂的页面上。
- 使用技术手段如合并多次重绘、使用 CSS3 动画代替 JavaScript 动画、使用
transform
属性等可以降低重排和重绘的开销。
总之,了解重绘和重排的概念并采取相应的优化措施有助于提高前端应用的性能,减少不必要的资源浪费。
5、jQuery的基本语法
jQuery是一个广泛用于处理DOM操作和简化JavaScript编程的JavaScript库。它提供了一组简洁而强大的API,使得在处理HTML文档、处理事件、执行动画等方面更加方便。
jQuery的基本语法主要涉及选择器和操作方法。以下是一些常用的jQuery语法示例:
选择器:使用选择器来选择一个或多个DOM元素。
javascript// 选择ID为myElement的元素 $("#myElement") // 选择所有段落元素 $("p") // 选择所有类名为myClass的元素 $(".myClass") // 选择所有带有data属性的元素 $("[data-attribute]")
操作方法:对选定的元素执行操作。
javascript// 隐藏元素 $("#myElement").hide(); // 显示元素 $("#myElement").show(); // 添加CSS类 $("#myElement").addClass("highlight"); // 移除CSS类 $("#myElement").removeClass("highlight"); // 获取或设置元素的文本内容 var text = $("#myElement").text(); $("#myElement").text("新文本内容"); // 获取或设置元素的HTML内容 var html = $("#myElement").html(); $("#myElement").html("<p>新的HTML内容</p>");
事件处理:为元素绑定事件处理程序。
javascript// 点击事件处理 $("#myButton").click(function() { alert("按钮被点击了!"); }); // 鼠标移入事件处理 $("#myElement").mouseenter(function() { // 在鼠标移入时执行的操作 });
遍历和过滤:对多个元素进行遍历或过滤操作。
javascript// 遍历所有段落元素并隐藏它们 $("p").each(function() { $(this).hide(); }); // 筛选出具有特定类名的元素 $("div").filter(".special"); // 查找后代元素 $("#container").find("p");
属性操作:获取或设置元素的属性值。
javascript// 获取属性值 var src = $("img").attr("src"); // 设置属性值 $("a").attr("href", "https://example.com");
这些示例演示了一些基本的jQuery语法,但jQuery提供了丰富的功能和方法,用于处理DOM元素、执行动画、处理事件等。要深入了解jQuery的语法和功能,请参阅官方文档:jQuery官方文档。
6、用户权限问题
在使用Vue全家桶(Vue.js、Vue Router、Vuex)开发项目时,处理不同角色的权限问题是一个重要的考虑因素,以下是一些常见的方法来处理权限问题:
路由守卫: 使用Vue Router的路由守卫可以在路由跳转之前进行权限检查。你可以根据用户角色来控制路由的访问。例如,你可以创建一个全局前置守卫,检查用户是否有权限访问某个路由,如果没有权限则将其重定向到一个错误页面或默认页面。
javascript// main.js router.beforeEach((to, from, next) => { const userRole = getUserRole(); // 获取用户角色 const requiredRole = to.meta.requiredRole; // 获取路由需要的角色 if (requiredRole && requiredRole !== userRole) { next('/403'); // 重定向到无权限页面 } else { next(); // 继续路由跳转 } });
在路由配置中,你可以使用
meta
字段来设置路由需要的角色:javascriptconst routes = [ { path: '/admin', component: AdminComponent, meta: { requiredRole: 'admin' } // 设置需要管理员权限 }, // ... ];
动态渲染页面元素: 根据用户角色动态渲染页面元素,只显示用户有权限访问的部分。你可以在Vue组件中使用
v-if
或v-show
指令来控制元素的显示与隐藏。vue<template> <div> <button v-if="userRole === 'admin'">管理功能</button> <button v-if="userRole === 'user'">普通用户功能</button> </div> </template> <script> export default { data() { return { userRole: getUserRole() // 获取用户角色 }; } }; </script>
Vuex状态管理: 使用Vuex来管理应用程序的状态,包括用户的角色信息。然后,根据用户的角色状态来决定应用程序的行为和显示。
javascript// store.js const store = new Vuex.Store({ state: { userRole: getUserRole() // 获取用户角色 }, // ... }); // 在组件中使用 userRole
服务端验证: 尽量避免将权限检查完全依赖于前端,因为前端的代码可以被绕过。在服务端进行权限验证是非常重要的一层安全措施,前端的权限检查仅作为用户友好的界面提示。
动态路由: 如果你的应用有大量角色和权限组合,你可以考虑使用动态路由,根据用户角色动态生成可访问的路由。
以上是处理不同角色权限问题的一些常见方法,具体的实现方式会根据项目需求和架构的复杂性而变化。最重要的是确保安全性,并根据具体情况来灵活应对权限控制的需求。同时,你可能需要考虑如何在后端服务器上进行权限验证,以确保安全性。
7、微前端及使用
微前端是一种软件架构模式,旨在帮助大型应用程序团队更有效地构建和维护前端应用。它基于将前端单体应用程序分解为小的、相对独立的微前端模块,每个模块都有自己的开发团队、技术栈和生命周期。这些微前端模块可以独立开发、构建、部署和升级,然后通过组合它们来构建完整的前端应用。
微前端的核心概念包括以下内容:
模块化:前端应用被分解为多个小模块,每个模块都有自己的功能和职责。这些模块可以是独立的子应用,例如单页应用 (SPA)、组件、微服务等。
独立开发:每个微前端模块由不同的开发团队负责,他们可以使用不同的技术栈和编程语言,这使得团队能够更自主地开发和维护自己的模块。
独立部署:每个微前端模块可以独立构建和部署,因此不需要整个应用的重新部署。这有助于降低风险,减少故障对整个应用的影响。
基于组合:微前端应用可以通过组合不同的模块来构建完整的前端应用。这通常通过路由和动态加载实现,使得用户可以无缝地访问各个模块。
独立升级:各个模块可以独立升级,而不会影响整个应用的稳定性。这有助于保持前端应用的可维护性和可扩展性。
微前端的目标是减少前端开发的复杂性,提高团队的协作能力,同时提供更灵活的部署和升级策略。微前端可以在大型、复杂的前端应用中特别有用,例如企业级应用程序,以便更好地管理代码库和开发流程。它还有助于实现前后端分离、跨团队协作和持续交付。不同的微前端框架和工具可用于实施微前端架构,例如Single-spa、qiankun等。
要在Vue.js项目中使用微前端,你可以使用一些库和工具来实现微前端架构。以下是一种可能的方法,使用single-spa
作为微前端的框架来在Vue项目中实现微前端:
创建微前端模块: 将您的Vue项目拆分成多个微前端模块。每个模块可以是一个独立的Vue应用,它包含自己的组件、路由、状态管理等。
安装
single-spa
: 使用npm或yarn安装single-spa
库。bashnpm install single-spa
配置和注册微前端应用: 在您的Vue项目中,您需要配置和注册每个微前端应用,以便
single-spa
可以管理它们。通常,这是通过创建一个主应用(容器应用)来实现的。主应用会在其中加载并协调所有微前端模块。javascript// main.js 主应用入口文件 import Vue from "vue"; import singleSpaVue from "single-spa-vue"; import App from "./App.vue"; const vueLifecycles = singleSpaVue({ Vue, appOptions: { el: "#app", render: (h) => h(App), }, }); export const bootstrap = vueLifecycles.bootstrap; export const mount = vueLifecycles.mount; export const unmount = vueLifecycles.unmount;
配置路由: 每个微前端模块都应该有自己的路由配置,以确保它们可以正确地加载和卸载。通常,主应用的路由会根据URL来动态加载不同的微前端应用。
构建和部署: 每个微前端模块需要单独构建和部署。确保每个模块都有自己的构建脚本,并可以独立部署到合适的位置。
集成共享状态: 如果需要在微前端模块之间共享状态,可以使用一些状态管理库(如Vuex)来管理全局状态,以便多个微前端应用之间可以访问和修改这些状态。
测试和调试: 确保您的微前端应用可以在独立部署的情况下正常工作,同时进行集成测试和调试,以确保各个模块协作正常。
部署容器应用: 部署主应用(容器应用),并确保它能够加载和协调所有微前端模块。
这是一个基本的微前端在Vue项目中的实现示例。请注意,还有其他微前端框架和工具可供选择,具体实现方式可能会有所不同。在实际项目中,您需要更多的配置和细节来满足特定需求。此外,确保查看single-spa
和Vue的官方文档以获取更多详细信息。
8、百度地图api如何实现地图下钻
要在百度地图API中实现地图下钻,你可以通过在地图上添加事件监听器来捕获用户的交互,并在特定的用户操作时更改地图的级别(缩放级别)或中心点。下面是一个基本的步骤:
引入百度地图API:首先,在你的网页中引入百度地图API,确保你拥有API密钥,以便进行地图操作。你可以在百度地图开放平台注册并获取API密钥。
html<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=YOUR_API_KEY"></script>
创建地图实例:在页面加载时,创建一个百度地图实例,并设置地图的初始属性(中心点和初始缩放级别)。
javascriptvar map = new BMap.Map("mapContainer"); // "mapContainer" 是包含地图的 HTML 元素的 ID var point = new BMap.Point(经度, 纬度); // 初始中心点的坐标 map.centerAndZoom(point, 初始缩放级别);
监听地图事件:使用百度地图API的
addEventListener
函数来监听用户的交互事件,例如点击或双击。你可以选择适当的事件来触发地图下钻操作。javascriptmap.addEventListener("click", function(e) { // 处理地图点击事件 // 在这里可以更改地图的中心点和缩放级别 });
更改地图状态:在事件处理程序中,你可以使用地图的方法来更改地图的状态,例如更改中心点或缩放级别。下钻时,通常你会增加缩放级别以放大地图。
javascriptmap.centerAndZoom(new BMap.Point(新的经度, 新的纬度), 新的缩放级别);
通过这些步骤,你可以实现在用户与地图交互时进行地图下钻操作。你还可以根据你的需求添加其他功能,如标记点、信息窗口等,以提供更丰富的地图体验。确保详细阅读百度地图API文档以获取更多有关地图功能和事件的信息。
二、手写代码
1、编写一个数组去重的方法
有多种方法可以使用 JavaScript 编写一个数组去重的函数。以下是其中一种简单的方法,使用 Set 数据结构:
function uniqueArray(arr) {
// 方法一
// 使用 Set 数据结构来自动去重
const uniqueSet = new Set(arr);
// 将 Set 转换回数组
const uniqueArray = Array.from(uniqueSet);
return uniqueArray;
// 方法二 使用展开运算符+Set
return [...new Set(arr)];
}
// 示例用法:
const myArray = [1, 2, 2, 3, 4, 4, 5];
const resultArray = uniqueArray(myArray);
console.log(resultArray); // [1, 2, 3, 4, 5]
在上面的示例中,我们首先创建了一个 Set 数据结构 uniqueSet
,它会自动去除重复项,然后使用 Array.from()
方法将 Set 转换回数组,得到去重后的数组。
另外,还可以使用循环遍历数组并手动去重的方式,例如:
function uniqueArray(arr) {
const uniqueArray = [];
for (let i = 0; i < arr.length; i++) {
if (uniqueArray.indexOf(arr[i]) === -1) {
uniqueArray.push(arr[i]);
}
}
return uniqueArray;
}
这个方法遍历数组,检查每个元素是否已经存在于 uniqueArray
中,如果不存在,则将其添加到 uniqueArray
中,从而实现去重。
注意,使用 Set 更为简洁和高效,因为 Set 内部的数据结构是哈希表,查找和去重的操作都是 O(1) 复杂度,而使用数组的 indexOf
方法则需要线性搜索,效率较低。因此,建议使用 Set 来进行数组去重。
2、将字符串转为json格式
以下是一个 JavaScript 程序示例,用于提取 URL 中的各个 GET 参数并将其以 key-value 形式存储在一个 JSON 结构中:
function getURLParameters(url) {
const parameters = {};
const searchParams = new URL(url).searchParams;
for (const [key, value] of searchParams) {
parameters[key] = value || undefined;
}
return parameters;
}
const url = "http://example.com/page?a=1&b=2&c=&d=xxx&e";
const parameters = getURLParameters(url);
console.log(parameters);
在这个示例中,我们定义了一个 getURLParameters
函数,它接受一个 URL 字符串作为输入。我们使用 URL
构造函数创建一个 URL
对象,然后从该对象的 searchParams
属性中获取 URL 中的参数。
接下来,我们遍历 searchParams
对象的键值对,将参数名和参数值存储在 parameters
对象中。如果参数值为空字符串,则我们将其设置为 undefined
,以满足你的要求。
运行示例代码后,parameters
对象将包含以下内容:
{
"a": "1",
"b": "2",
"c": undefined,
"d": "xxx",
"e": undefined
}
这样,你就可以轻松提取 URL 中的各个 GET 参数并以 key-value 形式存储在一个 JSON 结构中。注意,如果参数值为 undefined,它将被表示为 undefined
。
3、冒泡排序算法
冒泡排序(Bubble Sort)是一种简单的排序算法,它的基本思想是通过比较相邻元素的大小,如果它们的顺序不正确就交换它们,重复这个过程直到整个数组有序为止。它属于比较排序算法中的一种,虽然不是最高效的排序算法,但容易理解和实现。
以下是使用 JavaScript 实现冒泡排序的示例代码:
function bubbleSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
// 标记是否发生过交换,如果没有发生交换,表示数组已经有序,可以提前退出循环
let swapped = false;
for (let j = 0; j < n - i - 1; j++) {
// 比较相邻元素,如果前一个元素比后一个元素大,则交换它们
if (arr[j] > arr[j + 1]) {
const temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果没有发生交换,表示数组已经有序,提前退出循环
if (!swapped) {
break;
}
}
return arr;
}
// 示例用法
const unsortedArray = [64, 34, 25, 12, 22, 11, 90];
const sortedArray = bubbleSort(unsortedArray);
console.log(sortedArray); // 输出 [11, 12, 22, 25, 34, 64, 90]
在上面的示例中,bubbleSort
函数接受一个待排序的数组作为输入,并使用嵌套的循环来执行冒泡排序。外层循环控制每一轮的排序,内层循环比较相邻元素并进行交换。如果在某一轮内没有发生交换,就提前退出循环,因为数组已经有序。这个优化可以提高冒泡排序的效率。最后,函数返回已排序的数组。
需要注意的是,冒泡排序的时间复杂度为 O(n^2),因此在大型数据集上不是最优的选择。但它对于小型数组或部分有序的数组来说是一个简单而有效的排序算法。
4、快速排序
快速排序(Quick Sort)是一种高效的、基于比较的排序算法,它采用了分治(Divide and Conquer)的策略。快速排序的基本思想是选择一个基准元素,将数组分成两个子数组,一个子数组中的元素小于基准元素,另一个子数组中的元素大于基准元素,然后递归地对子数组进行排序。
以下是使用 JavaScript 实现快速排序的示例代码:
function quickSort(arr) {
if (arr.length <= 1) {
return arr; // 如果数组长度小于等于1,无需排序,直接返回
}
const pivot = arr[Math.floor(Math.random() * arr.length)]; // 随机选择基准元素
const left = [];
const right = [];
const equal = [];
for (const element of arr) {
if (element < pivot) {
left.push(element);
} else if (element > pivot) {
right.push(element);
} else {
equal.push(element);
}
}
return [...quickSort(left), ...equal, ...quickSort(right)];
}
// 示例用法
const unsortedArray = [64, 34, 25, 12, 22, 11, 90];
const sortedArray = quickSort(unsortedArray);
console.log(sortedArray); // 输出 [11, 12, 22, 25, 34, 64, 90]
在上面的示例中,quickSort
函数接受一个待排序的数组作为输入。首先,它检查数组的长度是否小于等于 1,如果是,则无需排序,直接返回原数组。
然后,函数随机选择一个基准元素 pivot
,并创建三个空数组 left
、right
和 equal
来分别存放小于、大于和等于基准元素的元素。接下来,它遍历原数组,将元素按照与基准元素的比较结果分别放入这三个数组中。
最后,函数使用递归对 left
和 right
两个子数组进行快速排序,并将排序好的结果与 equal
数组合并,得到最终的排序结果。
快速排序是一种高效的排序算法,其平均时间复杂度为 O(n log n),但最坏情况下的时间复杂度为 O(n^2),因此通常情况下表现非常出色。此外,它是一个原地排序算法,不需要额外的内存空间。在实际应用中,快速排序常常被用来排序大型数据集。
5、实现浅拷贝和深拷贝
深拷贝和浅拷贝是两种不同的方式来复制对象或数组的内容,它们有不同的行为和用途。
浅拷贝: 浅拷贝仅复制对象或数组的第一层结构,而不会递归复制嵌套对象或数组的内部元素。这意味着新对象的直接属性指向的是原对象的属性引用,如果原对象的属性值是对象或数组,那么新对象中的对应属性也会引用相同的对象或数组。
深拷贝: 深拷贝会递归地复制对象或数组及其嵌套结构中的所有元素,以确保生成一个完全独立的副本,不共享任何引用。
下面分别演示如何使用 JavaScript 来实现浅拷贝和深拷贝:
浅拷贝示例:
- 使用扩展运算符
...
(对象):
const originalObject = { a: 1, b: 2 };
const shallowCopy = { ...originalObject };
- 使用
Object.assign()
方法(对象):
const originalObject = { a: 1, b: 2 };
const shallowCopy = Object.assign({}, originalObject);
深拷贝示例:
- 使用递归函数:
// 定义一个深拷贝函数,它接受一个对象或值作为参数
function deepCopy(obj) {
// 检查传入的参数是否不是对象或为 null,如果是,则直接返回该参数
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 检查传入的参数是否为数组,如果是,则进行数组的深拷贝
if (Array.isArray(obj)) {
// 使用 map 方法遍历数组,对数组的每个元素递归调用 deepCopy 函数进行深拷贝
return obj.map(item => deepCopy(item));
}
// 如果传入的参数是一个普通对象(不是数组),则创建一个新的空对象
const newObj = {};
// 遍历原对象的所有属性
for (let key in obj) {
// 检查属性是否是对象自身的属性,而不是继承来的属性
if (obj.hasOwnProperty(key)) {
// 递归调用 deepCopy 函数对属性值进行深拷贝,并将结果存储在新对象中
newObj[key] = deepCopy(obj[key]);
}
}
// 返回新对象,包含了原对象的深拷贝
return newObj;
}
// 创建一个原始对象,包含了一些属性和嵌套对象
const originalObject = { a: 1, b: { c: 2 } };
// 使用 deepCopy 函数对原始对象进行深拷贝
const deepCopyObject = deepCopy(originalObject);
- 使用 JSON 序列化和反序列化(仅适用于可以被 JSON 序列化的对象,不包括函数等):
const originalObject = { a: 1, b: { c: 2 } };
const deepCopyObject = JSON.parse(JSON.stringify(originalObject));
需要注意的是,使用 JSON 序列化和反序列化方法时,会忽略不能被 JSON 序列化的属性,如函数、undefined
等。因此,深拷贝方法的选择应根据实际需求和数据结构来决定。
6、实现一个数组扁平化函数
你可以使用递归来实现一个数组扁平化函数,将多维数组变成一维数组。下面是一个JavaScript函数示例:
function flattenArray(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
// 如果当前元素是数组,递归调用flattenArray并合并结果
result = result.concat(flattenArray(arr[i]));
} else {
// 如果不是数组,直接将元素添加到结果数组中
result.push(arr[i]);
}
}
return result;
}
// 示例用法
const nestedArray = [1, [2, [3, 4], 5], 6];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // 输出 [1, 2, 3, 4, 5, 6]
这个flattenArray
函数接受一个多维数组作为参数,然后使用递归遍历数组的每个元素。如果当前元素是数组,它会递归调用flattenArray
来处理内部数组,并将结果合并到结果数组中。如果当前元素不是数组,它将直接将元素添加到结果数组中。最终,返回一个一维数组,其中包含了多维数组中的所有元素。
7、毫秒值转换指定日期格式
要将毫秒值转换为指定日期格式,你可以使用JavaScript的Date
对象和一些内置方法。以下是一个示例函数,它将毫秒值转换为指定的日期格式:
function formatDateFromMilliseconds(milliseconds, format) {
const date = new Date(milliseconds);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
if (format === 'datetime') {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
} else if (format === 'date') {
return `${year}-${month}-${day}`;
} else if (format === 'time') {
return `${hours}:${minutes}:${seconds}`;
} else {
return 'Invalid Format';
}
}
// 示例用法
const milliseconds = 1632787200000; // 毫秒值
const formattedDatetime = formatDateFromMilliseconds(milliseconds, 'datetime');
const formattedDate = formatDateFromMilliseconds(milliseconds, 'date');
const formattedTime = formatDateFromMilliseconds(milliseconds, 'time');
console.log('Date and Time:', formattedDatetime); // 输出 "2021-09-28 00:00:00"
console.log('Date:', formattedDate); // 输出 "2021-09-28"
console.log('Time:', formattedTime); // 输出 "00:00:00"
这个示例函数首先使用Date
对象创建一个日期对象,然后提取年份、月份、日期、小时、分钟和秒,并根据指定的格式选项创建一个格式化的日期字符串。你可以根据需要传递不同的格式选项('datetime'、'date'、'time')来获取日期和时间的不同部分或完整的日期时间字符串。
二、代码结果
1、代码一
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
- 输出结果
1
2
4
3
解释如下:
创建了一个Promise对象,传入的执行函数(executor)是一个同步操作,其中包含两个
console.log
语句,分别输出1和2。这两个语句会立即执行。然后,通过
.then()
方法注册了一个回调函数,该回调函数会在Promise状态变为已解决(resolved)时执行,但是目前Promise的状态还没有改变,因此这个回调函数尚未执行。接着,同步代码块中的
console.log(4)
被执行。最后,虽然Promise的状态尚未改变,但是在JavaScript中,
.then()
中的回调函数会被添加到一个微任务队列中,并在当前执行栈执行完毕后立即执行。所以,console.log(3)
会在console.log(4)
之后被输出。