水平居中、垂直居中的方法。
水平居中和垂直居中是在网页设计和布局中经常需要解决的问题。下面是几种实现水平居中和垂直居中的方法:
水平居中
使用
text-align: center;
对于内联元素或块级元素,可以将其父元素的
text-align
属性设置为center
来实现水平居中。
.parent { text-align: center; }
使用
margin: 0 auto;
对于具有固定宽度的块级元素,可以将其左右外边距设置为
auto
,使其在父容器中水平居中。
.child { width: 200px; margin: 0 auto; }
Flexbox 布局
使用 Flexbox 布局是实现水平居中最简单和灵活的方法。
.parent { display: flex; justify-content: center; }
垂直居中
使用
line-height
对于单行文本,可以将行高设置为与父容器高度相等来实现垂直居中。
.parent { height: 100px; line-height: 100px; }
使用
display: flex;
使用 Flexbox 布局也可以轻松实现垂直居中。
.parent { display: flex; align-items: center; }
使用
position
和transform
将子元素的定位设置为绝对定位,然后使用
transform
属性来调整其位置。
.child { position: absolute; top: 50%; transform: translateY(-50%); }
使用 Grid 布局
使用 CSS Grid 布局也可以实现垂直居中。
.parent { display: grid; place-items: center; }
这些方法可以单独使用或者组合使用,根据具体的布局需求选择合适的方法。
align-item实现的是水平居中还是垂直居中。flex-direction为column的时候,是什么居中。
align-items
属性用于在 Flexbox 布局中定义项目在交叉轴(与主轴垂直的轴)上的对齐方式。当flex-direction
设置为row
时,align-items
控制的是项目在垂直方向上的对齐方式,因此可以用于垂直居中。而当flex-direction
设置为column
时,align-items
控制的是项目在水平方向上的对齐方式,因此可以用于水平居中。具体来说:
- 当
flex-direction: row;
时,align-items: center;
将项目在垂直方向上居中对齐。- 当
flex-direction: column;
时,align-items: center;
将项目在水平方向上居中对齐。所以,
align-items: center;
可以实现垂直居中(在flex-direction: row;
下)和水平居中(在
flex-direction: column;
下)的效果。
简单数据类型和复杂数据类型的区别
简单数据类型和复杂数据类型是编程语言中常用的概念,它们通常指的是数据在内存中的存储方式以及对数据进行操作时的行为。
简单数据类型(也称为基本数据类型或原始数据类型):
- 简单数据类型是存储单个值的数据类型,每个变量通常只能容纳一个值。
- 简单数据类型的值在内存中被直接存储,它们的大小固定,分配在栈(stack)内存中。
- 简单数据类型包括整数(如整数、浮点数、布尔值)、字符、枚举等。
- 简单数据类型的赋值是按值传递的,即将值本身复制给目标变量。
在许多编程语言中,简单数据类型是不可变的,意味着一旦创建就不能修改其值。每次对简单数据类型进行操作时,实际上是在创建一个新的值。
复杂数据类型:
- 复杂数据类型可以存储多个值或者其他数据类型的集合。
- 复杂数据类型的值在内存中被存储为引用,这些引用指向内存中的实际数据,这些实际数据可以是简单数据类型或其他复杂数据类型。
- 复杂数据类型包括数组、对象、集合、字典、字符串等。
- 复杂数据类型的赋值是按引用传递的,即将引用指向的内存地址复制给目标变量,因此对目标变量的修改会影响原始数据。
与简单数据类型不同,复杂数据类型通常是可变的,可以通过改变其内部结构来修改其值。因为复杂数据类型的大小可能会动态改变,它们通常在堆(heap)内存中分配空间。
总的来说,简单数据类型适用于存储单个简单值,而复杂数据类型适用于组织和存储大量数据或多个值的集合。
深拷贝和浅拷贝的区别
深拷贝和浅拷贝是在编程中经常遇到的两种拷贝数据的方式,它们有着重要的区别:
浅拷贝(Shallow Copy):
- 浅拷贝是指将原始对象的顶层属性复制到新对象中,而不会复制嵌套对象的引用。
- 当进行浅拷贝时,新对象与原始对象的顶层属性相同,但嵌套对象的引用仍然是相同的。如果修改新对象中的嵌套对象,那么原始对象中相应的嵌套对象也会被修改,因为它们共享相同的引用。
- 浅拷贝通常使用对象的一些内置方法实现,例如
Object.assign()
或扩展运算符...
。深拷贝(Deep Copy):
- 深拷贝是指创建一个新对象,并递归地将原始对象及其所有嵌套对象的属性都复制到新对象中,而不共享任何引用。
- 当进行深拷贝时,新对象中的所有嵌套对象都是原始对象的副本,而不是引用。因此,对新对象或原始对象的任何修改都不会影响对方。
- 实现深拷贝可能需要递归地复制对象的所有属性,并处理循环引用等特殊情况,因此相对于浅拷贝而言,深拷贝的实现更为复杂。
区别总结:
- 浅拷贝只复制对象的顶层属性,而深拷贝会递归复制对象的所有属性,包括嵌套对象。
- 浅拷贝创建的新对象与原始对象共享嵌套对象的引用,而深拷贝创建的新对象包含所有嵌套对象的副本,不存在引用关系。
- 浅拷贝的性能通常比深拷贝要高,但对于包含嵌套对象的复杂数据结构,深拷贝是更安全和更可靠的选择。
JSON.stringify有什么弊端
无法处理循环引用: 如果 JavaScript 对象包含循环引用(即对象的属性之间存在循环引用关系),则
JSON.stringify
会抛出错误。const obj = {}; obj.circularReference = obj; JSON.stringify(obj); // TypeError: Converting circular structure to JSON
无法处理特定对象的属性:
JSON.stringify
在转换对象时会忽略某些属性,例如函数、Symbol
类型的属性、以及值为undefined
的属性。这些属性在结果字符串中将被省略。const obj = { func: function() { /* ... */ }, sym: Symbol('symbol'), undef: undefined }; JSON.stringify(obj); // "{}"
无法处理包含不可枚举属性的对象:
JSON.stringify
只会序列化对象的可枚举属性,而忽略不可枚举属性。const obj = {}; Object.defineProperty(obj, 'nonEnumerable', { value: 'value', enumerable: false }); JSON.stringify(obj); // "{}"
对日期对象和正则表达式的处理:
JSON.stringify
将日期对象和正则表达式对象转换为字符串。日期对象会转换为 ISO 格式的字符串,而正则表达式对象会被转换为空对象{}
。const date = new Date(); JSON.stringify(date); // "\"2022-01-01T00:00:00.000Z\"" const regex = /test/; JSON.stringify(regex); // "{}"
丢失对象特有的方法和原型链: 在将对象转换为 JSON 字符串时,对象的方法和原型链信息都会丢失,因为 JSON 只是一种数据格式,不包含方法和原型链信息。
虽然
JSON.stringify
存在这些弊端,但对于大多数简单的 JavaScript 对象来说,它仍然是一种非常方便的序列化方法。如果需要处理上述问题,可以考虑使用第三方库或自定义序列化函数来实现更复杂的需求。
怎么判断数组类型
在 JavaScript 中,判断一个变量是否为数组类型有几种方法,最常见的方法包括使用
Array.isArray()
方法和使用instanceof
运算符。使用
Array.isArray()
方法:
Array.isArray()
方法是最简单和推荐的方法,它会返回一个布尔值,表示传入的参数是否为数组类型。const arr = [1, 2, 3]; Array.isArray(arr); // true const notArr = 'not an array'; Array.isArray(notArr); // false
使用
instanceof
运算符:
instanceof
运算符用于检测构造函数的prototype
属性是否出现在对象的原型链中的任何位置。注意:
instanceof
只能用于检查对象是否是某个特定类型的实例,而不能用于原始数据类型。const arr = [1, 2, 3]; arr instanceof Array; // true const notArr = 'not an array'; notArr instanceof Array; // false
Map和weakMap的区别
Map
和WeakMap
是 JavaScript 中的两种数据结构,它们都用于存储键值对,并且具有一些相似之处,但也有一些重要的区别。Map:
Map
是一种普通的 JavaScript 对象,它的键可以是任意类型的值,包括基本类型和引用类型。Map
中的键值对是强引用的,即使键被删除了,相应的值仍然存在于Map
中,不会被垃圾回收器回收。Map
可以使用forEach
方法来遍历键值对,也可以通过size
属性获取键值对的数量。WeakMap:
WeakMap
是一种弱引用的数据结构,它的键必须是对象(或者继承自对象的类型),而且值必须是对象。WeakMap
中的键值对是弱引用的,如果某个键不再被引用,那么它对应的键值对会被自动从WeakMap
中删除,并且被垃圾回收器回收。WeakMap
没有提供像Map
那样的遍历方法,也没有size
属性。因为键是弱引用的,所以无法获得键的数量。总结区别:
- 键类型:
Map
的键可以是任意类型的值,而WeakMap
的键必须是对象。- 引用强度:
Map
中的键值对是强引用的,而WeakMap
中的键值对是弱引用的。- 自动回收:
Map
中的键值对不会被自动回收,而WeakMap
中的键值对会在键不再被引用时自动删除并被回收。- 功能:
Map
提供了遍历方法和获取键值对数量的属性,而WeakMap
没有提供这些功能。综上所述,
Map
和WeakMap
在使用场景和功能上有所不同,需要根据具体的需求选择合适的数据结构。通常情况下,如果需要使用任意类型的键或者需要遍历键值对,可以使用Map
;如果需要存储对象之间的关联关系,并且希望在对象被回收时自动删除关联关系,可以使用WeakMap
。
Vue生命周期钩子,activated和deactivated用过吗
activated
和deactivated
是 Vue 组件的生命周期钩子函数,它们通常与 Vue Router 中的动态路由组件一起使用。这两个钩子函数在组件被激活(进入路由)和停用(离开路由)时被调用。
activated:
- 当组件被激活时调用。这意味着组件在其父路由组件被激活时,也会被激活。
activated
生命周期钩子是在动态路由组件每次被导航到时调用的。- 可以在这个钩子函数中执行一些需要在组件被激活时立即执行的操作,比如数据加载、订阅事件等。
- 这个钩子函数可以用来恢复组件之前状态的操作,或者在进入路由时进行一些初始化工作。
deactivated:
- 当组件被停用时调用。这意味着组件在其父路由组件被停用时,也会被停用。
deactivated
生命周期钩子是在动态路由组件离开时调用的。- 可以在这个钩子函数中执行一些在组件被停用时需要立即执行的操作,比如清理工作、取消订阅等。
- 这个钩子函数通常用于保存组件状态的操作,或者在离开路由时进行一些清理工作。
这两个生命周期钩子在使用 Vue Router 动态路由时非常有用,可以用来控制路由切换时组件的行为和状态,例如在组件进入和离开时加载和释放资源、启用和禁用功能等。
Vue里keep-alive的理解
在Vue中,
<keep-alive>
组件是用于缓存组件的一种特殊组件。它可以将动态组件缓存到内存中,而不是每次切换都销毁并重新创建组件实例,从而提高了组件的性能。使用
<keep-alive>
组件的主要目的是在保留组件的状态和避免重新渲染的同时,节省资源并提高用户体验。通常在以下场景下使用<keep-alive>
组件是很有用的:
频繁切换的组件:如果某个组件在多个路由之间频繁切换,使用
<keep-alive>
可以避免每次切换都销毁和重新创建组件实例,从而减少资源开销。包含表单数据或输入状态的组件:如果一个组件包含用户输入的表单数据或者其他状态信息,使用
<keep-alive>
可以确保在组件被缓存时,用户的输入状态得到保留,不会因为组件的销毁而丢失。包含代价较高的组件:如果一个组件的创建代价较高(例如组件需要进行大量的计算或者请求数据),使用
<keep-alive>
可以避免重复的代价高昂的初始化过程,提高页面的加载速度和性能。使用
<keep-alive>
组件非常简单,只需将需要缓存的组件包裹在<keep-alive>
标签中即可:<template> <div> <keep-alive> <component :is="currentComponent"></component> </keep-alive> <button @click="toggleComponent">Toggle Component</button> </div> </template> <script> export default { data() { return { currentComponent: 'ComponentA' }; }, methods: { toggleComponent() { this.currentComponent = this.currentComponent === 'ComponentA' ? 'ComponentB' : 'ComponentA'; } } }; </script>
在上面的示例中,
<component :is="currentComponent"></component>
中的组件会根据currentComponent
的值动态渲染,而<keep-alive>
组件将缓存这个动态组件,以避免重复的销毁和创建。
怎么解决跨域问题,讲讲nginx
跨域问题是由于浏览器的同源策略所导致的,当浏览器发起跨域请求时,如果目标地址与当前页面的地址不符合同源策略,请求就会被浏览器拦截。为了解决跨域问题,常用的方法之一是通过配置反向代理服务器,例如 Nginx。
Nginx 是一个高性能的开源的 HTTP 和反向代理服务器,可以用于搭建静态服务器、负载均衡、反向代理等。下面是使用 Nginx 解决跨域问题的简要步骤:
安装和配置 Nginx:
- 首先,需要在服务器上安装 Nginx,并确保配置文件正确。
- 配置文件通常位于
/etc/nginx/nginx.conf
或/usr/local/nginx/conf/nginx.conf
,根据实际安装位置可能会有所不同。- 在配置文件中添加反向代理的配置。
配置反向代理:
- 在 Nginx 的配置文件中,使用
location
块配置反向代理,将需要跨域访问的请求转发到目标地址。- 使用
proxy_pass
指令指定目标地址。例如,以下是一个简单的 Nginx 配置示例:
server { listen 80; server_name example.com; location /api { proxy_pass http://api.example.com; # 其他配置参数 } }
重启 Nginx:
- 在修改了配置文件后,需要重启 Nginx 服务器,以使配置生效。可以使用命令
sudo systemctl restart nginx
(适用于使用 systemd 管理的系统)来重启 Nginx。通过配置 Nginx 反向代理,可以实现对跨域请求的转发,从而解决跨域问题。需要根据具体的项目和需求来配置 Nginx,确保配置的准确性和安全性。
webpack中loader和plugin有哪些?
在Webpack中,Loader和Plugin是两个重要的概念,它们用于处理和扩展Webpack构建过程中的不同方面。
Loader(加载器)
Loader用于将非JavaScript文件转换为Webpack可处理的模块,并且可以应用于
import
或require
语句中。Webpack在处理模块时,会从右到左地应用Loader,即先应用最右边的Loader,然后将结果传递给下一个Loader,依次类推,直到所有Loader执行完毕。一些常见的Loader包括:
babel-loader
:用于将ES6/ES7/ES8代码转换为ES5代码。css-loader
:用于解析CSS文件,并处理CSS中的import
和url()
等路径。style-loader
:将CSS代码以<style>
标签的形式注入到HTML页面中。file-loader
:用于处理文件,例如图片、字体等,将文件复制到输出目录,并返回文件路径。url-loader
:类似于file-loader
,但可以设置阈值,将小于指定大小的文件转换为base64编码。Plugin(插件)
Plugin用于扩展Webpack的功能,它可以在Webpack的构建过程中执行一些任务,例如打包优化、资源管理、注入环境变量等。Plugin在Webpack配置中被实例化,并在整个构建过程中生效。
一些常见的Plugin包括:
HtmlWebpackPlugin
:用于生成HTML文件,并自动将打包后的JS文件注入到HTML中。MiniCssExtractPlugin
:用于将CSS提取为单独的文件,并与HTML分离。CleanWebpackPlugin
:用于在每次构建前清理输出目录。DefinePlugin
:用于定义全局变量,例如在打包过程中注入环境变量。HotModuleReplacementPlugin
:用于实现热模块替换(HMR)。除了上述常见的Loader和Plugin之外,Webpack还提供了大量的第三方Loader和Plugin,以满足各种不同的需求和场景。通过Loader和Plugin的组合,Webpack能够实现更加灵活和强大的功能,从而适应各种复杂的项目需求。
ref()的使用
ref()
函数用于创建一个响应式的引用对象,这个引用对象包装了一个普通的JavaScript值,并且可以在模板中直接使用。使用
ref()
创建响应式引用对象:import { ref } from 'vue'; const a = ref(1); console.log(a.value); // 输出: 1 a.value = 2; console.log(a.value); // 输出: 2
在这个例子中,我们使用
ref()
创建了一个名为a
的引用对象,并且初始化值为1
。在创建引用对象后,我们可以通过访问a.value
属性来获取或修改引用对象的值。在模板中使用
ref()
创建的引用对象:<template> <div> <p>{{ a.value }}</p> <button @click="increment">Increment</button> </div> </template> <script> import { ref } from 'vue'; export default { setup() { const a = ref(1); const increment = () => { a.value++; }; return { a, increment }; } }; </script>
在Vue组件中,我们可以在
setup()
函数中使用ref()
创建引用对象。在模板中,我们可以直接访问a.value
来获取引用对象的值,并且可以通过a.value
来修改引用对象的值。
实现深拷贝
使用递归实现深拷贝:
function deepCopy(obj) { if (typeof obj !== 'object' || obj === null) { return obj; // 如果是基本数据类型或者null,直接返回 } let copy = Array.isArray(obj) ? [] : {}; // 创建新的对象或数组 for (let key in obj) { if (obj.hasOwnProperty(key)) { // 排除原型链上的属性 copy[key] = deepCopy(obj[key]); // 递归地拷贝对象的每个属性 } } return copy; }
使用JSON序列化/反序列化实现深拷贝:
function deepCopy(obj) { return JSON.parse(JSON.stringify(obj)); }
需要注意的是,使用JSON序列化/反序列化实现深拷贝的方法存在一些限制:
- 无法处理函数、正则表达式、
undefined
等特殊类型,因为它们在JSON序列化过程中会被忽略掉。- 无法处理循环引用,因为JSON序列化会抛出错误。
循环引用陷入死循环的解决方法:
循环引用是指对象的属性之间形成了循环依赖关系,导致在深拷贝时陷入死循环,无法正常完成拷贝操作。为了避免循环引用导致的死循环,我们可以使用一些方法来检测和处理循环引用的情况:
使用WeakMap进行循环引用检测:在拷贝过程中,使用WeakMap来记录已经拷贝过的对象,当检测到重复访问同一个对象时,直接返回该对象的拷贝。
function deepCopy(obj, map = new WeakMap()) { if (map.has(obj)) { return map.get(obj); // 如果已经拷贝过该对象,则直接返回拷贝后的对象 } if (typeof obj !== 'object' || obj === null) { return obj; // 如果是基本数据类型或者null,直接返回 } let copy = Array.isArray(obj) ? [] : {}; // 创建新的对象或数组 map.set(obj, copy); // 记录已拷贝过的对象 for (let key in obj) { if (obj.hasOwnProperty(key)) { // 排除原型链上的属性 copy[key] = deepCopy(obj[key], map); // 递归地拷贝对象的每个属性 } } return copy; }
限制拷贝的层级:设置一个最大递归深度,在达到最大深度时停止递归拷贝。
function deepCopy(obj, depth = 0, maxDepth = 10) { if (depth > maxDepth) { return obj; // 如果达到最大递归深度,则直接返回原对象 } // 其他拷贝逻辑... }
封装一个简单的vue组件,实现数字增减:左边一个按钮,右边一个输入框显示数字,每点击一次左边按钮,右方输入框的数字加1
<template> <div> <button @click="decrease">-</button> <input type="number" v-model="count" /> <button @click="increase">+</button> </div> </template> <script> export default { data() { return { count: 0 }; }, methods: { decrease() { this.count--; }, increase() { this.count++; } } }; </script> <style scoped> /* 样式可以根据实际需求自行调整 */ button { width: 30px; height: 30px; cursor: pointer; } input { width: 50px; height: 30px; text-align: center; } </style>
二分查询 输入数组,返回下标,没查到返回-1
function binarySearch(arr, target) { let left = 0; let right = arr.length - 1; while (left <= right) { let mid = Math.floor((left + right) / 2); if (arr[mid] === target) { return mid; // 找到目标值,返回下标 } else if (arr[mid] < target) { left = mid + 1; // 目标值在右侧 } else { right = mid - 1; // 目标值在左侧 } } return -1; // 没有找到目标值,返回-1 }
这个算法首先将数组的左右边界设为数组的起始位置和结束位置,然后在循环中通过计算中间位置找到中间元素,将中间元素与目标值进行比较。如果中间元素等于目标值,则返回中间元素的下标;如果中间元素小于目标值,则说明目标值在右侧,将左边界移动到中间元素的右侧;如果中间元素大于目标值,则说明目标值在左侧,将右边界移动到中间元素的左侧。重复这个过程直到找到目标值或者左边界大于右边界为止。如果循环结束时仍然没有找到目标值,则返回-1。
函数缓存
函数缓存是一种优化技术,它用于缓存函数的计算结果,当函数被调用时,首先检查缓存中是否已经存在相应的计算结果,如果存在则直接返回缓存的结果,否则执行函数的计算过程,并将结果存入缓存中。
实现函数缓存的一种常见方法是使用闭包和对象字面量来存储计算结果。下面是一个简单的例子:
在这个例子中,我们定义了一个
memoize
函数,它接受一个函数作为参数,并返回一个新的函数。这个新的函数在执行时,会首先检查缓存中是否存在计算结果,如果存在则直接返回缓存的结果,否则执行原始的函数计算过程,并将结果存入缓存中。这样就实现了函数的缓存功能,可以提高函数的性能和效率。function memoize(func) { const cache = {}; // 缓存对象 return function(...args) { const key = JSON.stringify(args); // 将函数的参数转换为字符串作为缓存的键 if (!cache[key]) { cache[key] = func.apply(this, args); // 如果缓存中不存在计算结果,则执行函数计算,并存入缓存 } return cache[key]; // 返回缓存的计算结果 }; } // 示例函数,计算斐波那契数列的第n项 function fibonacci(n) { if (n <= 1) { return n; } return fibonacci(n - 1) + fibonacci(n - 2); } // 使用函数缓存优化斐波那契数列函数 const memoizedFibonacci = memoize(fibonacci); console.log(memoizedFibonacci(10)); // 第10项斐波那契数列的值 console.log(memoizedFibonacci(20)); // 第20项斐波那契数列的值 console.log(memoizedFibonacci(10)); // 直接从缓存中获取第10项斐波那契数列的值,无需重新计算