前端基础
1. js中有哪些可以提供遍历方法
(1)数组遍历方法
-
for 循环:这是最基本的遍历方法,适用于所有类型的循环遍历。
-
forEach():数组的方法,用于遍历数组的每个元素,并执行提供的函数。没有返回值(或返回
undefined
)。 -
map():数组的方法,遍历数组的每个元素,对元素执行提供的函数,并返回一个新数组,包含执行结果。
-
filter():数组的方法,遍历数组的每个元素,返回一个新数组,包含通过测试(即回调函数返回
true
)的所有元素。 -
reduce():数组的方法,对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
-
some():数组的方法,测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是布尔值。
-
every():数组的方法,测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回的是布尔值。
-
find():数组的方法,返回数组中满足提供的测试函数的第一个元素的值。否则返回
undefined
。 -
findIndex():数组的方法,返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
-
for...of 循环:ES6 引入的循环语法,用于遍历可迭代对象(包括数组、Map、Set、arguments 等)的值。
对象遍历方法
-
for...in 循环:遍历一个对象的所有可枚举属性(包括其原型链上的属性)。
-
Object.keys():返回一个由给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用
for...in
循环遍历该对象时返回的顺序一致(两者的主要区别是for...in
还会枚举其原型链上的属性)。 -
Object.values():返回一个给定对象自身的所有可枚举属性值的数组,值的排列顺序与使用
for...in
循环遍历该对象时返回的顺序一致(区别在于for...in
循环还会枚举原型链中的属性)。 -
Object.entries():返回一个给定对象自身可枚举属性的键值对数组,其排列与使用
for...in
循环遍历该对象时返回的顺序一致(同样,for...in
循环还会枚举原型链中的属性)。 -
Object.getOwnPropertyNames():返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
-
Reflect.ownKeys():返回目标对象自身的所有键名,不管键名是Symbol或字符串,也不管是否可枚举。
2. js脚本库,如何延时加载
1. 使用<script>
标签的defer
或async
属性
-
defer
属性:带有defer
属性的脚本会在文档完全解析和显示之后才会执行,且按照在文档中出现的顺序执行。这对于不依赖DOM的脚本库很有用,因为它可以确保脚本在DOM完全加载后再执行。<script src="path/to/your/script.js" defer></script>
async
属性:带有async
属性的脚本会在加载完成后立即执行,而不阻塞页面解析。不过,这些脚本不会按照在文档中出现的顺序执行。这适用于那些不依赖其他脚本或DOM顺序的独立脚本。<script src="path/to/your/script.js" async></script>
2. 动态创建
<script>
标签这种方法允许你更灵活地控制脚本的加载时机,例如,在特定事件触发后或页面上的某个元素被滚动到视口中时加载脚本。
function loadScript(url, callback) { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; // 当脚本加载并执行完成后执行回调 script.onload = script.onreadystatechange = function() { if (!script.readyState || script.readyState === "loaded" || script.readyState === "complete") { script.onload = script.onreadystatechange = null; if (typeof callback === 'function') { callback(); } } }; // 将script标签添加到DOM中 document.head.appendChild(script); } // 使用示例 loadScript('path/to/your/script.js', function() { console.log('脚本加载完成!'); });
3. 使用JavaScript库或框架的加载器
许多现代JavaScript库和框架都提供了自己的模块加载系统或集成第三方加载器(如RequireJS、Webpack等),这些工具支持更复杂的依赖管理和优化加载策略。
4. 使用服务器端渲染技术
在某些情况下,你可以通过服务器端渲染(SSR)技术来控制脚本的加载时机。例如,在Node.js环境中,你可以根据用户的交互或页面滚动来动态地生成并发送JavaScript脚本到客户端。
5. 延迟初始化
即使脚本已经加载到页面中,你也可以通过延迟其初始化过程来进一步优化性能。例如,你可以将脚本的初始化代码放入一个函数中,并在需要时才调用该函数。
3. 怎么理解原型和原型链?最后指向谁?
一、原型(Prototype)
原型是JavaScript中的一个重要概念,它是一个对象,用于定义其他对象的属性和方法。在JavaScript中,每个函数都有一个prototype
属性,这个属性是一个指针,指向一个原型对象。通过该构造函数实例化出来的对象都可以继承得到原型上的所有属性和方法。
具体来说,原型具有以下特点:
- 原型是函数的一个属性,即
function.prototype
。 - 原型对象默认有一个属性
constructor
,其值指向对应的构造函数。 - 原型对象还有一个特殊的属性
__proto__
(尽管在一些现代JavaScript环境中不推荐直接使用此属性,因为它不是ECMAScript标准的一部分,但它在某些实现中用于表示原型链的链接),它指向Object.prototype,即所有原型链的顶端。
二、原型链(Prototype Chain)
原型链是由多个原型对象通过__proto__
属性(或内部机制,因为直接访问__proto__
可能不是最佳实践)串联起来的一个链条。当试图访问一个对象的属性或方法时,JavaScript会首先在该对象自身中查找,如果找不到,则会沿着原型链向上查找,直到找到对应的属性或方法或到达原型链的顶端(即Object.prototype
,其__proto__
属性为null
,表示没有更多的原型可以查找)。
原型链的作用主要体现在以下几个方面:
- 属性共享:通过原型链,多个对象可以共享同一个原型对象上的属性和方法,从而节省内存。
- 方法继承:子类对象可以继承父类原型对象上的方法,实现面向对象编程中的继承特性。
- 灵活扩展:可以在原型链的任意位置添加或修改属性和方法,从而影响到所有继承自该原型的对象。
三、原型链的最终指向
原型链的最终指向是null
。在JavaScript中,所有对象的原型链最终都会指向Object.prototype
,而Object.prototype
的__proto__
属性为null
,表示原型链的结束。这意味着当JavaScript在原型链中向上查找属性或方法时,如果到达Object.prototype
仍未找到,则会停止查找并返回undefined
。
4. 调用接口,异地接口,跨域问题
在Web开发中,当你需要从一个前端应用调用位于不同域名或子域上的后端接口时,就会遇到跨域问题(CORS,即跨源资源共享)。跨域问题主要是出于安全考虑,浏览器会限制来自不同源的“文档”或脚本对当前“文档”读取或设置某些属性。这里的“源”指的是协议、域名和端口号的组合。
1. 后端设置CORS策略
最常见的解决方案是在后端服务中配置CORS策略,以允许来自特定源或所有源的请求。这通常在HTTP响应头中设置,包括Access-Control-Allow-Origin
、Access-Control-Allow-Methods
、Access-Control-Allow-Headers
等。
2. 使用JSONP(仅适用于GET请求)
JSONP是一种通过<script>
标签的src属性实现跨域请求的老旧技术。它允许在请求的URL中指定一个回调函数,服务器响应时会将数据作为该函数的参数调用,从而实现跨域数据交换。但是,JSONP只支持GET请求,并且存在安全风险(如XSS攻击)。
3. 代理服务器
另一个解决方案是使用代理服务器。前端应用向同源的代理服务器发送请求,代理服务器再向实际的后端接口发送请求,并将响应返回给前端应用。这样,前端应用和后端接口之间的通信就被封装在了同源请求中,从而避免了跨域问题。
-
实现方式:可以使用Nginx、Apache等Web服务器作为代理,或者在开发环境中使用Webpack的devServer等工具来配置代理。
5. 如何把两个数组合并成一个?ES6里有什么方法
使用扩展运算符合并数组
假设我们有两个数组array1
和array2
,我们可以使用扩展运算符将它们合并成一个新数组:
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
// 使用扩展运算符合并数组
let combinedArray = [...array1, ...array2];
console.log(combinedArray); // 输出: [1, 2, 3, 4, 5, 6]
使用Array.prototype.concat()
方法
虽然concat()
方法不是ES6新增的,但它也是合并数组的一种常用方法,并且在ES5及以后的版本中都是可用的。concat()
方法用于合并两个或多个数组。此方法不会改变现有的数组,而是返回一个新数组。
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
// 使用concat方法合并数组
let combinedArray = array1.concat(array2);
console.log(combinedArray); // 输出: [1, 2, 3, 4, 5, 6]
- 扩展运算符(
...
)是ES6中引入的,提供了一种更简洁的方式来合并数组,同时它也支持在函数参数、解构赋值等多种场景中使用。 concat()
方法虽然不是ES6新增的,但仍然是合并数组的一种有效方式,尤其适用于需要兼容旧版本JavaScript环境的情况。
6. 子父容器,如何让子元素垂直对齐?flex布局还有其他的方法吗?
使用Flexbox实现垂直对齐
- 基本设置:首先,你需要将父容器的
display
属性设置为flex
,然后通过align-items
属性来控制子元素在交叉轴(默认情况下是垂直轴)上的对齐方式。
.parent {
display: flex;
flex-direction: column; /* 可选,默认为column,表示主轴为垂直方向 */
align-items: center; /* 子元素在交叉轴(此时为水平方向)上居中对齐 */
/* 如果需要子元素在垂直方向上居中对齐,则不需要设置flex-direction为column,
因为默认就是垂直方向,且align-items控制的是交叉轴对齐 */
justify-content: center; /* 控制主轴上的对齐方式,如果flex-direction为column,则控制垂直对齐 */
height: 100vh; /* 示例:让父容器占满视窗高度 */
}
.child {
/* 子元素样式 */
}
注意:如果你想要子元素在垂直方向上居中对齐,并且父容器的主轴已经是垂直方向(即flex-direction: column;
),那么实际上你主要关注的是justify-content: center;
,因为它会控制子元素在主轴(此时为垂直方向)上的对齐方式。而align-items: center;
在这种情况下会控制子元素在水平方向上的对齐方式(即交叉轴上的对齐)。
其他方法
除了Flexbox之外,还有其他一些方法可以实现垂直对齐,但Flexbox通常是最简单且功能最强大的。以下是一些替代方法:
-
使用
position
属性和transform
:可以通过设置子元素的position
为absolute
,然后使用transform: translateY(-50%);
(假设你想要顶部对齐)或者transform: translateY(-100%);
(如果目标是底部对齐,但通常我们会结合父容器的高度来调整这个值)来手动调整位置。但这种方法需要知道子元素或父容器的高度,或者使用JavaScript来动态计算。 -
使用
display: table
和display: table-cell
:将父容器设置为display: table;
,子元素设置为display: table-cell;
,然后使用vertical-align: middle;
来垂直居中对齐子元素。但这种方法已经不太常用了,因为它基于表格布局模型,而现代Web开发更倾向于使用更灵活和语义化的布局方法。 -
使用
line-height
:如果子元素是行内元素(如<span>
),并且父容器只包含一个这样的子元素,你可以通过设置父容器的line-height
等于其height
来实现垂直居中对齐。但这种方法只适用于单行文本的垂直居中对齐。 -
使用
grid
布局:CSS Grid布局也提供了强大的对齐功能,包括垂直对齐。通过设置align-items
或align-self
属性,可以轻松实现子元素的垂直居中对齐。Grid布局在处理复杂网格布局时特别有用。
7. 盒子模型的组成
"盒子模型是CSS布局中的一个核心概念,它描述了HTML元素如何在页面上占据空间。一个盒子模型主要由四个部分组成,这些部分共同决定了元素的总大小和位置。
-
内容(Content):这是盒子模型的最核心部分,也是我们通常所说的元素的内容区域。它包含了元素的文本、图片或其他媒体内容。内容区域的大小可以通过设置元素的
width
和height
属性来直接控制。 -
内边距(Padding):内边距位于内容区域的外围,是内容与边框之间的空间。它不会影响到元素的大小(除非设置了
box-sizing: border-box;
),但会增加元素的可视区域大小。内边距可以是上、右、下、左四个方向上的,分别通过padding-top
、padding-right
、padding-bottom
、padding-left
来设置。 -
边框(Border):边框环绕在内边距和内容的外围,是可见的线条,用于分隔不同的元素。边框的大小、样式和颜色都可以通过CSS来设置。边框的存在也会影响到元素的总大小。
-
外边距(Margin):外边距是盒子模型最外层的空间,它位于边框之外,用于控制元素与其他元素之间的距离。外边距是透明的,不会占据背景色或背景图片的空间。外边距可以是正值也可以是负值,负值的外边距会导致元素重叠。外边距同样可以是上、右、下、左四个方向上的,分别通过
margin-top
、margin-right
、margin-bottom
、margin-left
来设置。
8. CSS布局的模式
1. 标准流布局(Normal Flow)
解释:
标准流布局是网页中默认的布局方式,也称为文档流或正常流。在这种布局下,元素按照HTML文档中的顺序依次排列,块级元素(如div)会独占一行,而行内元素(如span)则会并排排列直到遇到块级元素或容器边界。
特点:
-
简单直观,不需要额外的CSS样式来控制布局。
-
适用于简单的页面结构。
2. 浮动布局(Float Layout)
解释:
浮动布局通过设置元素的float
属性(如float: left;
或float: right;
),使元素脱离其正常的文档流,并向左或向右移动,直到它的外边缘遇到包含框或另一个浮动元素的边缘。
特点:
-
常用于图文混排、多栏布局等场景。
-
需要注意清除浮动,以避免布局问题。
3. 定位布局(Positioning Layout)
解释:
定位布局通过设置元素的position
属性(如position: relative;
、position: absolute;
、position: fixed;
等),可以精确地控制元素在页面上的位置。
特点:
-
提供了更多的布局灵活性。
-
绝对定位和固定定位会脱离文档流,相对定位则不会。
4. 弹性布局(Flexbox Layout)
解释:
弹性布局是一种更加高效的布局方式,通过设置容器的display
属性为flex
或inline-flex
,可以使其内部的子元素能够灵活地伸缩,以适应不同的屏幕尺寸和布局需求。
特点:
-
灵活性强,能够轻松实现响应式布局。
-
支持多种排列方式和对齐方式。
5. 网格布局(Grid Layout)
解释:
网格布局通过设置容器的display
属性为grid
或inline-grid
,将容器划分为多个行和列,从而创建一个二维的网格系统。子元素可以放置在网格的特定位置,并可以跨越多个网格单元。
特点:
-
适用于复杂的页面布局。
-
提供了强大的布局控制能力。
6. 多列布局(Multi-column Layout)
解释:
多列布局是一种基于CSS多列属性的布局方式,可以将文本内容分成多列并排显示,类似于报纸或杂志的排版效果。
特点:
-
适用于长文本内容的排版。
-
可以通过CSS属性控制列数、列宽、列间距等。
7. 响应式布局(Responsive Layout)
解释:
响应式布局不是一种具体的布局模式,而是一种设计理念。它通过使用媒体查询(Media Queries)、百分比、vw/vh单位、rem/em单位等技术手段,使网页能够自动适应不同尺寸的屏幕和设备。
特点:
-
提供了更好的用户体验。
-
是现代网页设计的重要趋势。
9. js数据类型?基本数据类型?引用数据类型?
基本数据类型(Primitive Types)
基本数据类型是存储在栈内存中的简单数据,它们直接包含数据值,且这些值是不可变的。当你修改一个基本数据类型的值时,实际上是创建了一个新的值,并把这个新值赋给了变量。JavaScript中的基本数据类型包括:
-
String(字符串):用于表示文本数据,如
'Hello, World!'
。 -
Number(数字):用于表示数值,可以是整数或浮点数,如
42
或3.14
。 -
Boolean(布尔值):用于表示逻辑值,只有两个值
true
和false
。 -
Undefined(未定义):当变量被声明了但没有被赋值时,它的值就是
undefined
。 -
Null(空值):表示一个空对象引用,用于显式地指示变量为空。注意,
null
和undefined
在逻辑上有所区别,但在某些情况下它们的行为可能相似。 -
Symbol(符号)(ES6新增):用于创建唯一的标识符,通常用于对象的属性名。
-
BigInt(大整数)(ES2020新增):用于表示任意精度的整数,可以安全地存储和操作大整数。
引用数据类型(Reference Types)
引用数据类型是存储在堆内存中的复杂数据,变量中保存的是对数据的引用(即内存地址),而不是数据本身。这意味着当你修改一个引用数据类型的属性时,实际上是在修改堆内存中的数据,而所有引用该数据的变量都会受到影响。JavaScript中的引用数据类型包括:
-
Object(对象):是所有引用数据类型的基础,可以包含属性和方法。
-
Array(数组):一种特殊的对象,用于存储一系列的值,可以通过索引来访问这些值。
-
Function(函数):用于执行代码块的引用数据类型,可以包含参数、局部变量和代码块。
-
Date(日期):用于处理日期和时间的对象。
-
RegExp(正则表达式):用于定义搜索文本字符串时要匹配的模式。
面试口语化解释
在面试中,可以这样解释JavaScript的数据类型:
-
基本数据类型就像是直接装在你手里的糖果,你看到的是什么就是什么,改变它就得换一个新的。比如,字符串
'Hello'
就是一个糖果,你不能改变'Hello'
里的字母,但你可以把'Hello'
换成'World'
。 -
引用数据类型则像是一个地址,这个地址指向一个房子(堆内存中的数据)。你可以通过地址进入房子,并在里面放置或修改东西。如果有多个地址指向同一个房子,那么任何人在房子里做的改动都会影响到其他人。比如,一个对象就是一个房子,你可以往里面添加属性或方法,如果多个变量都引用了这个对象,那么这些变量都会看到对象的最新状态。
10. 判断实例的类型?instanceOf
在JavaScript中,instanceof
操作符用于检测一个对象是否在其原型链原型构造函数的prototype属性所指向的原型对象上。换句话说,它用来判断一个实例是否属于某个构造函数创建的对象的类型。这在面试中是一个常见的知识点,因为它涉及到JavaScript的原型链和继承机制。
if (object instanceof Constructor) {
// 如果 object 是由 Constructor 构造的实例,或者其原型链上的某个构造函数的实例,则执行此代码块
}
“instanceof
是JavaScript中的一个操作符,用于检测一个对象是否通过指定的构造函数创建的。当我们使用instanceof
时,它会检查该对象的原型链中是否存在该构造函数的prototype
属性。如果存在,则说明这个对象是通过该构造函数创建的实例,或者它的原型链上的某个地方是这个构造函数的实例。这种机制是JavaScript原型继承的基础,它允许我们灵活地组织和复用代码。”
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
console.log(person1 instanceof Person); // true,因为person1是Person的实例
console.log(person1 instanceof Object); // true,因为Person的原型链最终指向Object.prototype
// 错误的用法示例(仅为了说明instanceof的用途)
console.log(person1 instanceof String); // false,因为person1不是String的实例
11. 如何复制一个对象
1. 使用扩展运算符(...
)
适用于对象字面量复制对象的第一层属性。
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
console.log(copy); // { a: 1, b: { c: 2 } }
console.log(copy.b === original.b); // true,说明对象b是引用复制的
2. 使用Object.assign()
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
const original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
console.log(copy); // { a: 1, b: { c: 2 } }
console.log(copy.b === original.b); // true,说明对象b是引用复制的
3. 手动递归复制
对于需要深拷贝的情况(即连嵌套的对象也要完全复制),你需要手动实现递归复制逻辑。
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return null; // null 的情况
if (obj instanceof Date) return new Date(obj); // 日期对象直接返回一个新的日期对象
if (obj instanceof RegExp) return new RegExp(obj); // 正则对象直接返回一个新的正则对象
// 如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) return hash.get(obj);
let allDesc = Object.getOwnPropertyDescriptors(obj);
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);
hash.set(obj, cloneObj);
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (typeof obj[key] === 'object' && obj[key] !== null)
? deepClone(obj[key], hash)
: obj[key];
}
return cloneObj;
}
const original = { a: 1, b: { c: 2 } };
const copy = deepClone(original);
console.log(copy); // { a: 1, b: { c: 2 } }
console.log(copy.b === original.b); // false,说明对象b是深拷贝的
12. 深拷贝和浅拷贝的区别
深拷贝和浅拷贝是JavaScript中处理对象复制时非常重要的两个概念,它们的主要区别在于复制的深度和对象内部属性的处理方式。
浅拷贝(Shallow Copy):
浅拷贝就像是给对象拍了一张照片,但照片里的内容(如果是对象的话)只是原对象的引用,而不是真正的对象本身。换句话说,浅拷贝只复制了对象的第一层属性,如果属性值是基本数据类型(如数字、字符串、布尔值),则直接复制值;但如果属性值是对象(如数组、对象字面量等),则复制的是这个对象的引用,即新对象和原对象共享这个内部对象。因此,如果通过浅拷贝得到的对象修改了内部对象的属性,原对象也会受到影响。
深拷贝(Deep Copy):
深拷贝就像是给对象做了一个完全独立的复制品,包括对象内部的所有嵌套对象都会被完整地复制一份。这样,新对象和原对象在内存中是完全独立的,修改新对象的任何属性(包括嵌套对象的属性)都不会影响到原对象。深拷贝通常需要递归地复制对象的所有层级,确保每一层级的对象都是全新的。
总结:
- 浅拷贝只复制对象的第一层属性,如果属性值是对象,则复制的是这个对象的引用。
- 深拷贝会递归地复制对象的所有层级,确保每一层级的对象都是全新的,与原对象完全独立。
在实际开发中,选择深拷贝还是浅拷贝取决于你的具体需求,比如是否需要完全独立地修改对象而不影响原对象等。不过,需要注意的是,深拷贝可能会比浅拷贝消耗更多的内存和时间,因为它需要复制更多的数据。
13. 父子嵌套,点击的触发顺序,如何只触发子的?==事件冒泡
在HTML DOM中,当事件(如点击事件)发生在某个元素(子元素)上时,这个事件会按照DOM树的结构向上传播,直到达到根节点(通常是document
对象)。这个过程被称为事件冒泡(Event Bubbling)。
父子嵌套,点击的触发顺序
在父子嵌套的DOM结构中,当你点击子元素时,首先会触发子元素上的事件处理函数(如果有的话),然后这个事件会冒泡到父元素,并触发父元素上的事件处理函数(如果父元素上也有绑定该类型的事件处理函数)。
如何只触发子的?
如果你只想触发子元素上的点击事件,而不希望这个事件冒泡到父元素,你可以使用event.stopPropagation()
方法。这个方法会阻止事件进一步传播(冒泡)。
<div id="parent" style="padding: 20px; border: 1px solid #000;">
父元素
<button id="child">子元素</button>
</div>
<script>
// 绑定父元素的点击事件
document.getElementById('parent').addEventListener('click', function(event) {
console.log('父元素被点击');
});
// 绑定子元素的点击事件,并阻止事件冒泡
document.getElementById('child').addEventListener('click', function(event) {
console.log('子元素被点击');
event.stopPropagation(); // 阻止事件冒泡
});
</script>
14. 节流和防抖
一、主要区别
-
工作原理:
-
防抖(Debounce):当事件持续触发时,只有在事件停止触发n秒后,才会执行事件函数。如果在n秒内事件被重新触发,那么之前的计时会被重置。这种技术通常用于如模糊搜索、短信验证和文本编辑器实时保存等场景。
-
节流(Throttle):当事件持续触发时,每n秒只执行一次函数。如果在n秒内事件被重复触发,那么只有第一次触发的事件会生效。这种技术常用于处理如scroll事件、浏览器播放事件和window的resize等场景。
-
-
关注点:
-
防抖:更关注最后一次触发事件的结果,适用于短时间内的大量触发。
-
节流:更关注事件触发的频率,限制在特定时间内最多触发一次,适用于持续的触发。
-
二、实现方式
防抖(Debounce)
防抖的实现通常是通过设置一个定时器,在事件触发时清除之前的定时器并重新设置,确保在事件停止触发后的延迟时间内执行函数。以下是一个简单的防抖函数示例:
function debounce(func, delay) {
let debounceTimer;
return function () {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
func.apply(this, arguments);
}, delay);
};
}
// 使用防抖函数
const searchInput = document.getElementById('searchInput');
const debouncedSearch = debounce(() => {
console.log('执行搜索操作');
}, 500);
searchInput.addEventListener('keyup', debouncedSearch);
节流(Throttle)
节流的实现则是通过设置一个标志位(或定时器),在事件触发时检查标志位(或定时器状态),如果允许则执行函数并设置下一次允许执行的时间。以下是一个简单的节流函数示例:
function throttle(func, delay) {
let canRun = true;
return function () {
if (canRun) {
func.apply(this, arguments);
canRun = false;
setTimeout(() => {
canRun = true;
}, delay);
}
};
}
// 使用节流函数
const scrollEventHandler = throttle(() => {
console.log('滚动事件触发');
}, 1000);
window.addEventListener('scroll', scrollEventHandler);
三、应用场景
-
防抖:适用于搜索框输入、按钮点击等场景,避免短时间内的频繁请求。
-
节流:适用于滚动事件、窗口大小调整、鼠标移动等场景,限制函数的执行频率,提高性能。
15. CSS如何实现淡入淡出效果
使用CSS实现淡入淡出效果,主要依赖于opacity
属性和transition
属性。
首先,我会设置一个元素的初始opacity
值为0,这样它一开始就是完全透明的,不可见的。然后,我会给这个元素添加一个类(比如.fade-in
),当这个类被添加到元素上时,它的opacity
值会变为1,变得完全不透明,从而实现了淡入效果。
为了实现这个过渡效果,我会在元素的CSS规则中使用transition
属性。transition
属性会告诉浏览器,当opacity
这个属性发生变化时,应该如何进行过渡。我会指定过渡的持续时间(比如0.5秒或1秒),以及过渡的缓动函数(如果需要的话,但很多时候默认的就足够了)。
举个例子,假设我有一个<div>
元素,我想要在点击一个按钮时让它淡入显示。我可以这样写CSS:
.fade-in {
opacity: 1;
transition: opacity 0.5s ease; /* 过渡效果设置为opacity变化持续0.5秒,缓动函数为ease */
}
/* 初始状态,元素是隐藏的 */
.my-element {
opacity: 0;
/* 可能还需要设置其他样式,比如定位、尺寸等 */
}
然后,在JavaScript中,当按钮被点击时,我会给.my-element
这个元素添加.fade-in
这个类:
document.querySelector('#myButton').addEventListener('click', function() {
var element = document.querySelector('.my-element');
element.classList.add('fade-in');
});
这样,当按钮被点击时,.my-element
的opacity
值会从0变为1,并且这个变化会在0.5秒内平滑过渡,从而实现了淡入效果。
16. 元素的定位方式?fixed定位和absoulte定位的区别
元素的定位方式啊主要有静态定位、相对定位、绝对定位、固定定位、粘性定位和Flex定位这几种。
静态定位就是元素按照正常的文档流来排列,不会被top
、bottom
、left
、right
这些属性影响。
相对定位呢,就是元素先按照正常文档流排列,然后可以通过top
、bottom
、left
、right
这些属性相对于它原来的位置进行调整,但是它还是占据原来的空间的。
绝对定位元素完全脱离文档流,不占据空间,位置相对于最近的已定位的祖先元素来确定。如果没有这样的祖先元素,就相对于<body>
元素来定位。
固定定位也很常用,元素的位置相对于浏览器窗口来定位,页面滚动时它也会停留在固定的位置,就像一些固定在顶部的导航栏一样。
粘性定位就比较特别了,它结合了相对定位和固定定位的特点,元素根据正常文档流进行定位,直到滚动到某个阈值为止,然后就固定在指定位置了。
至于Flex定位嘛,它不是通过position
属性来设置的,而是通过display: flex
或display: inline-flex
在容器上启用Flexbox布局,然后通过Flex容器的属性来控制子元素的对齐和分布。
至于fixed定位和absolute定位的区别嘛,主要就是定位参照物不同。fixed定位是相对于浏览器窗口来定位的,即使页面滚动元素也会停留在固定位置;而absolute定位是相对于其最近的已定位的祖先元素来定位的,如果祖先元素的位置发生变化,absolute定位的元素位置也可能随之变化。另外,fixed定位的元素不会随滚动条移动而移动,而absolute定位的元素则可能会随着滚动条移动而移动。使用场景也不同,fixed定位常用于需要固定在页面某个位置的元素,而absolute定位则更灵活,可以根据需要相对于任何已定位的祖先元素进行定位。
17. 如何给div加阴影效果?给文字加阴影?
给div
加阴影效果,我们通常会用CSS的box-shadow
属性。这个属性很灵活,可以让我们自定义阴影的模糊程度、扩散范围、颜色等。比如说,如果我们想给一个div
添加一个黑色的阴影,阴影模糊程度为5px,扩散距离为10px,我们就可以这样写CSS代码:
div {
box-shadow: 10px 10px 5px black;
}
至于给文字加阴影,那就要用到text-shadow
属性了。这个属性和box-shadow
类似,也是让我们自定义文字的阴影效果。比如,我们想给文字添加一个红色的阴影,阴影模糊程度为2px,扩散距离为3px,就可以这样写:
p {
text-shadow: 3px 3px 2px red;
}
18. ES6中async和await的作用?
async的作用
定义:async
是ES6(ECMAScript 2015)中引入的一个关键字,用于声明一个函数为异步函数。它实际上是对Promise的一种语法糖,使得异步代码的编写更加简洁和直观。
主要作用:
-
声明异步函数:通过在函数声明前加上
async
关键字,可以将该函数标记为异步函数。这告诉JavaScript引擎,该函数内部可能会执行异步操作。 -
自动返回Promise对象:异步函数无论是否有返回值,或者返回的是什么类型的值,都会自动返回一个Promise对象。如果函数内部没有显式的
return
语句,则会隐式地返回一个已解析(resolved)的Promise,其值为undefined
。 -
与await配合使用:
async
函数内部可以使用await
关键字来等待异步操作的完成,这极大地简化了异步代码的编写和阅读。
await的作用
定义:await
是ES6中与async
配套使用的一个关键字,用于等待一个Promise对象的解决(resolve)或拒绝(reject)。
主要作用:
-
等待异步操作完成:
await
关键字会暂停async
函数的执行,直到等待的Promise对象被解决或拒绝。一旦Promise被解决,await
表达式的结果就是Promise解决的值;如果Promise被拒绝,则会抛出异常,可以通过try...catch
语句捕获。 -
简化异步代码:在
async
函数内部使用await
,可以使异步代码看起来更像是同步代码,提高了代码的可读性和可维护性。 -
错误处理:通过
try...catch
语句,可以方便地捕获await
等待的Promise对象被拒绝时抛出的异常,从而进行错误处理。
async
和await
是ES6中引入的两个非常有用的关键字,它们主要用于简化异步代码的编写。具体来说:
-
async
用于声明一个函数为异步函数。这意味着函数内部可能会执行一些耗时的操作,比如网络请求或文件读写。通过在函数前加上async
关键字,JavaScript引擎就知道这个函数可能会返回一个Promise对象。而且,无论函数内部是否有显式的返回值,它都会自动返回一个Promise对象。 -
await
则用于等待一个Promise对象的完成。它只能在async
函数内部使用。当我们在async
函数内部遇到await
关键字时,函数会暂停执行,直到等待的Promise对象被解决或拒绝。如果Promise被解决,await
表达式的结果就是解决的值;如果Promise被拒绝,则会抛出异常,这时我们可以使用try...catch
语句来捕获并处理这个异常。
19. ES6 let 和 const 与 var的区别
1. 作用域
-
var:
var
声明的变量具有函数作用域或全局作用域,这取决于声明的位置。在函数内部声明的var
变量只能在该函数内部访问,而在函数外部声明的var
变量则成为全局变量。 -
let 和 const:
let
和const
声明的变量具有块级作用域(block scope),即它们的作用域被限制在声明它们的块({})内。这意味着在if
语句、for
循环等控制结构内部声明的变量,在外部是不可见的。
2. 声明提升(Hoisting)
-
var:使用
var
声明的变量会被提升(hoisted)到其作用域的顶部,即变量可以在声明之前被访问(尽管此时它的值是undefined
)。 -
let 和 const:
let
和const
声明的变量不会被提升,这意味着在声明之前访问它们会导致一个引用错误(ReferenceError)。
3. 重新声明
-
var:使用
var
可以重新声明同一个变量多次,而不会报错。 -
let 和 const:
let
和const
不允许在同一个作用域内重新声明同一个变量。尝试这样做会导致语法错误(SyntaxError)。
4. 常量
-
var 和 let:
var
和let
声明的变量都可以被重新赋值。 -
const:
const
声明一个只读的常量。一旦const
变量被赋值后,它的值就不能被改变(如果值是对象,则对象本身的内容可以被修改,但指向该对象的引用不能改变)。尝试重新赋值会导致类型错误(TypeError)。
5. 暂时性死区(Temporal Dead Zone, TDZ)
-
let 和 const:在
let
和const
变量被声明之前,它们都处于暂时性死区(Temporal Dead Zone, TDZ)。在这个区域内,访问这些变量会抛出引用错误(ReferenceError)。这是因为let
和const
不会像var
那样被提升。
20. DOM 和 BOM
DOM(Document Object Model,文档对象模型)和BOM(Browser Object Model,浏览器对象模型)是Web开发中两个重要的概念,它们各自承担着不同的角色和功能,但又紧密相关。
DOM(文档对象模型)
定义:
DOM是一种用于表示HTML、XML等文档结构的标准编程接口。它将文档表示为一个具有层次结构的节点树,每个节点都表示文档中的一个元素、属性、文本或者注释。通过DOM,开发者可以使用JavaScript等编程语言来访问和修改文档的内容、结构和样式。
核心对象:
DOM的核心是document
对象,它表示整个文档,包括文档的根元素、文本节点、注释、标签等。通过document
对象,开发者可以访问和操作文档中的每个元素和属性,以及它们之间的关系。
功能:
-
访问和操作文档中的元素和属性。
-
修改文档的结构,如添加、删除、修改元素。
-
响应用户事件,如点击、滚动等。
BOM(浏览器对象模型)
定义:
BOM提供了独立于内容而与浏览器窗口进行交互的对象。它包含了一系列与浏览器窗口和浏览器本身进行交互的JavaScript对象和API,用于操作浏览器窗口、文档、历史记录等。
核心对象:
BOM的核心是window
对象,它代表了浏览器窗口,提供了很多方法和属性,如打开新窗口、关闭窗口、设置窗口大小、获取窗口位置等。此外,BOM还包括了location
对象(表示当前窗口中的URL)、navigator
对象(提供有关浏览器的信息)、history
对象(表示当前窗口的浏览历史记录)等。
功能:
- 控制浏览器窗口的大小、位置等。
- 导航,如打开新窗口、跳转页面等。
- 弹出对话框。
- 获取浏览器信息,如浏览器品牌、版本、屏幕分辨率等。
21. 回流和重绘?
。回流,也叫重排,是指当DOM结构发生变化时,浏览器需要重新计算元素的几何属性,并重新构建渲染树,然后基于新的渲染树重新绘制页面的过程。这可能会导致页面的其他部分也重新布局。而重绘则是指当元素的外观发生变化,但不影响其在文档流中的位置或尺寸时,浏览器只需重新绘制该元素的过程。与回流相比,重绘的开销要小得多。
为了优化性能,我们通常会尽量避免频繁触发回流和重绘,比如通过批量修改DOM或样式,使用CSS类来修改样式,而不是直接操作DOM元素的样式属性,以及避免在循环中读取元素的几何属性等。
22. 如何隐藏DIV?display:none,hidden
“隐藏一个DIV元素主要有两种方法。第一种是使用CSS的display: none;
属性。当你给DIV设置这个属性时,它会完全从文档流中消失,就像它从未存在过一样。这意味着它不会占据任何空间,也不会影响其他元素的布局。
第二种方法是使用visibility: hidden;
属性。这也会使DIV不可见,但它仍然会占据原来的空间。换句话说,它虽然看不见了,但其他元素在布局时仍然会考虑到它的存在。”
23. CSS长度的单位?px,em,rem的区别
在CSS中,长度的单位多种多样,每种单位都有其特定的用途和场景。其中,px(像素)、em和rem是三种非常常见且重要的长度单位。下面我将用口语化的方式解释这三种单位的区别:
px(像素)
定义:px全称是pixel,中文意思是“像素”。在CSS中,px是一个相对长度单位,它是相对于显示器屏幕分辨率而言的。简单来说,px就像是屏幕上的一块块小格子,每个格子的大小是固定的,所以使用px作为单位时,元素的大小也是相对固定的。
特点:
-
稳定性:使用px设置元素大小时,尺寸非常稳定和精确,不会因为其他元素的尺寸变化而变化。
-
局限性:但是,px也有局限性。比如,在不同分辨率的设备上,相同的px值可能会显示出不同的大小效果,因为高分辨率的屏幕能够显示更多的小格子(像素)。另外,使用px设置字体大小时,如果用户在浏览器中调整了缩放比例,页面布局可能会被打乱。
em
定义:em是一个相对长度单位,它相对于当前对象内文本的字体尺寸。如果当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。
特点:
-
灵活性:使用em作为单位时,元素的大小会根据父元素的字体大小进行缩放,这使得em非常适合用于响应式布局和需要字体大小动态调整的场景。
-
继承性:em具有继承性,即子元素会继承父元素的字体大小作为自己的基准值。这意味着,如果改变了父元素的字体大小,子元素中所有使用em作为单位的属性都会相应地发生变化。
rem
定义:rem也是一个相对长度单位,但它相对于根元素(即html元素)的字体大小。与em不同,rem的值不会继承父元素的字体大小,而是始终以根元素的字体大小为基准。
特点:
-
集中控制:使用rem作为单位时,我们只需要修改根元素的字体大小,就可以成比例地调整页面上所有使用rem作为单位的元素的大小。这使得rem非常适合用于全局样式的调整。
-
兼容性:除了IE8及更早版本外,几乎所有现代浏览器都支持rem单位。
总结
-
px:适合用于需要精确控制元素大小的场景,但不太适合响应式布局。
-
em:灵活性高,适合需要根据父元素字体大小动态调整的场景,但需要注意继承性可能带来的复杂性。
-
rem:集中控制全局样式调整,适合响应式布局和主题切换等场景,且兼容性良好。
在实际开发中,选择合适的长度单位取决于具体的需求和设计要求。通常,我们会结合使用多种单位来实现最佳的布局效果。
24. JS对象的继承
JavaScript 中的对象继承可以通过多种方式实现,包括原型链(prototype chaining)、借用构造函数(constructor stealing)、组合继承(combination inheritance)、原型式继承(prototypal inheritance)、寄生式继承(parasitic inheritance)和寄生组合式继承(parasitic combination inheritance)。下面是一些主要的继承方式的解释和示例:
1. 原型链
原型链是实现继承的一种基本方式。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
function Parent() {
this.name = 'parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child() {
this.name = 'child';
}
Child.prototype = new Parent(); // Child 继承 Parent
var child = new Child();
child.sayHello(); // 输出: Hello from child
2. 借用构造函数
借用构造函数的技术,也称为伪造对象或经典继承,是在子类型构造函数的内部调用超类型构造函数。
function Parent() {
this.colors = ['red', 'blue', 'green'];
}
function Child() {
Parent.call(this); // 借用构造函数继承
}
var child1 = new Child();
child1.colors.push('black');
console.log(child1.colors); // 输出: red,blue,green,black
var child2 = new Child();
console.log(child2.colors); // 输出: red,blue,green
3. 组合继承
组合继承指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用 Parent()
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用 Parent()
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
var child = new Child('child', 10);
child.sayHello(); // 输出: Hello from child
child.sayAge(); // 输出: 10
4. 寄生组合式继承
寄生组合式继承是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思想是:不必为了指定子类型的原型而调用超类型的构造函数。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype); // 创建对象,设置原型
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
var child = new Child('child', 10);
child.sayHello(); // 输出: Hello from child
child.sayAge(); // 输出: 10
25. Call、apply、bind的作用
Call、apply、bind 是 JavaScript 中 Function 对象自带的三个方法,它们的主要作用是改变函数执行时的上下文(即函数体内部的 this
指向),从而实现函数在不同对象上的调用或继承。以下是对这三个方法作用的详细解释:
1. Call 方法
作用:
-
call
方法用于调用一个函数,其具有一个指定的this
值和分别提供的参数(参数的列表)。 -
通过
call
方法,你可以调用一个函数,同时设置该函数体内的this
指向为call
方法的第一个参数,随后的参数将作为被调用函数的参数传入。
语法:
2. Apply 方法
作用:
apply 方法的作用与 call 方法类似,也是调用一个函数,并为其指定 this 值和参数。
与 call 方法不同的是,apply 方法接受两个参数:第一个参数是 this 的值,第二个参数是一个数组或类数组对象,其中的数组元素将作为单独的参数传给被调用的函数。
语法:
func.call(thisArg, arg1, arg2, ...)
-
thisArg
:在func
函数运行时使用的this
值。注意,指定的this
值并不一定是该函数执行时所在的上下文,JavaScript 只会将this
值传递给函数,并处理函数中的this
。 -
arg1, arg2, ...
:指定要传递给函数的参数列表。
示例:
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'John' };
greet.call(person, 'Hello', '!'); // 输出: Hello, John!
2. Apply 方法
作用:
-
apply
方法的作用与call
方法类似,也是调用一个函数,并为其指定this
值和参数。 -
与
call
方法不同的是,apply
方法接受两个参数:第一个参数是this
的值,第二个参数是一个数组或类数组对象,其中的数组元素将作为单独的参数传给被调用的函数。
语法:
func.apply(thisArg, [argsArray])
-
thisArg
:在func
函数运行时使用的this
值。 -
argsArray
:一个数组或类数组对象,其中的数组元素将作为单独的参数传给func
函数。如果该参数的值为null
或undefined
,则表示不需要传入任何参数。
示例:
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Jane' };
greet.apply(person, ['Hi', '.']); // 输出: Hi, Jane.
3. Bind 方法
作用:
-
bind
方法创建一个新的函数,在bind
被调用时,这个新函数的this
被指定为bind
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。 -
与
call
和apply
不同,bind
不会立即执行函数,而是返回一个新的函数,这个新函数在被调用时,其this
被永久绑定到了bind
的第一个参数上。
语法:
const newFunc = func.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
:当新函数被调用时,this
的值。注意,即使原函数被作为构造函数使用,this
值也不会改变。arg1, arg2, ...
:当新函数被调用时,arg1, arg2, ...
将作为其前置参数,之后传入的参数将跟在arg1, arg2, ...
后面。
示例:
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Doe' };
const boundGreet = greet.bind(person, 'Hello');
boundGreet('!'); // 输出: Hello, Doe!
26. 数据缓存的方法?indexdb听说过吗?
数据缓存的方法多种多样,它们根据不同的应用场景和需求而设计。以下是一些常见的数据缓存方法:
一、数据缓存方法
-
本地缓存
-
内存缓存:将缓存数据存储在单个应用程序进程内部的内存中,通常是使用如HashMap、ConcurrentHashMap等Java集合类实现。这种方法速度快,但受限于应用程序的生命周期和内存大小。
-
文件缓存:将缓存数据存储在文件系统中,如手机的ROM或SD卡上。这种方法可以存储大量数据,但访问速度相对较慢,且可能受到操作系统权限和存储空间的限制。
-
软引用和弱引用:在Java中,软引用和弱引用是两种特殊的引用类型,用于在内存不足时自动释放对象,从而帮助管理缓存数据。
-
-
分布式缓存
-
分布式缓存将缓存数据存储在多台服务器上,通过网络传输数据实现缓存共享。常见的分布式缓存框架有Redis、Memcached、Ehcache等。分布式缓存的优点是扩展性好、支持高并发、容量大,并能提高应用程序的可靠性和可用性。
-
-
多级缓存
-
多级缓存将缓存数据同时存储在本地缓存和分布式缓存中,以加快访问速度并提高可靠性。常见的多级缓存方案包括EHCache+Redis、Guava Cache+Redis等。这种方法的优点在于兼顾了本地缓存和分布式缓存的优点,使得缓存系统更灵活、性能更强。
-
-
数据库缓存
-
数据库缓存通常指的是数据库管理系统(DBMS)内部的缓存机制,用于缓存频繁访问的数据以减少磁盘I/O操作。数据库缓存是DBMS自动管理的,对应用程序透明。
-
-
应用级缓存
-
应用级缓存指的是在应用程序层面实现的缓存机制,如使用缓存框架(如Guava Cache、Caffeine等)或自定义缓存策略来管理缓存数据。这种方法允许开发者根据具体需求定制缓存策略,以实现最佳的性能和可靠性。
-
二、IndexDB介绍
IndexedDB是一种在浏览器中用于高效存储结构化数据的API,提供对象存储、事务、索引和异步操作。它是Web标准的一部分,旨在让Web应用能够高效地存储大量数据,并支持丰富的查询功能。IndexeDB的主要特点包括:
-
面向对象的存储:数据以对象的形式存储,支持复杂的数据结构。
-
事务支持:允许对数据库进行原子性的操作,保证数据的完整性和一致性。
-
索引:支持创建索引,使得数据可以被高效地查询。
-
异步操作:IndexedDB的操作通常是异步的,可以避免阻塞主线程,提高应用的响应性。
-
不限制大小:IndexedDB的存储空间比LocalStorage大得多,理论上没有上限。
-
同源限制:IndexedDB受到同源限制,每个数据库对应创建它的域名,网页只能访问自身域名下的数据库。
IndexedDB通常用于存储大量结构化数据,如离线应用程序数据、缓存数据等。它通过与Web应用的集成,提高了应用的性能和用户体验。开发者可以通过IndexedDB API来打开或创建数据库、创建对象存储空间、添加、读取、更新和删除数据等操作。同时,由于IndexedDB的操作是异步的,开发者需要处理相应的事件来获取操作结果或处理异常情况。
27. Js模块化的机制
JavaScript的模块化机制是一种将代码组织成独立的、可重用的、可维护的单元的方式,旨在提高代码的可读性、可维护性和复用性。以下是JavaScript模块化机制的主要方面:
一、模块化的定义
模块通常指的是一个独立的可重用软件组件,它包含了一组相关的函数、方法、类、变量等,能够实现特定的功能或提供特定的服务。在JavaScript中,模块化是一种将代码组织到这些独立的单元中的方式,使得代码更易于管理和复用。
二、模块化的发展历程
JavaScript的模块化经历了多个发展阶段,从早期的全局函数模式、命名空间模式,到后来的立即执行函数模式(IIFE),再到现代的模块化规范如CommonJS、AMD、CMD和ES6模块。
-
全局函数模式:早期的JavaScript开发中,常常将可复用的函数直接赋值给全局对象(如window),这种方式容易导致全局命名空间的污染和命名冲突。
-
命名空间模式:通过将变量和函数封装到对象内部,减少了全局变量的数量,一定程度上缓解了命名冲突的问题,但数据仍然可以被外部直接修改,不够安全。
-
立即执行函数模式(IIFE):通过立即执行的匿名函数封装私有变量和函数,并通过给全局对象添加属性来暴露接口,实现了数据的私有化,提高了模块的安全性。然而,这种模式在处理模块间依赖时存在困难。
-
模块化规范:随着JavaScript应用的复杂度增加,出现了多种模块化规范,如CommonJS、AMD、CMD和ES6模块。这些规范定义了模块的定义、导入和导出方式,使得模块化的开发更加规范和高效。
三、模块化规范
-
CommonJS:主要用于服务器端JavaScript,如Node.js。它使用
require
函数来导入模块,使用module.exports
或exports
来导出模块。CommonJS的模块加载是同步的,适用于服务器环境。 -
AMD(Asynchronous Module Definition):主要用于浏览器端JavaScript,支持异步加载模块。它使用
define
函数来定义模块,使用require
函数来异步加载依赖的模块。AMD规范通过require.js等库实现。 -
CMD(Common Module Definition):类似于AMD,也是用于浏览器端的模块化规范。它强调依赖就近,即延迟执行,在真正需要的时候再去解析依赖关系。Sea.js是实现CMD规范的库之一。
-
ES6模块:ES6(ECMAScript 2015)标准中引入了原生的模块化支持,成为JavaScript的官方模块化规范。ES6模块使用
import
语句来导入模块,使用export
语句来导出模块。ES6模块支持静态结构分析,有利于代码的压缩和优化。
四、模块化的好处
-
避免命名冲突:通过将代码封装到模块中,减少了全局变量的使用,避免了命名冲突。
-
提高代码复用性:模块化的代码可以被多个项目或模块复用,提高了代码的复用性。
-
便于维护:每个模块都负责相对独立的功能,降低了代码的耦合度,便于维护和调试。
-
支持按需加载:现代模块化规范支持按需加载模块,减少了不必要的代码加载,提高了页面加载速度。
28. CSS选择器有哪些?
CSS(层叠样式表)选择器是用于选择HTML元素以应用特定样式的模式或规则。CSS选择器种类丰富,可以根据不同的需求选择适当的选择器。以下是一些常见的CSS选择器类型:
1. 元素选择器
-
定义:通过HTML元素的标签名来选择元素。
-
示例:
p
选择所有<p>
元素。
2. 类选择器
-
定义:通过元素的class属性来选择元素,以点
.
开头。 -
示例:
.classname
选择所有class属性包含"classname"的元素。
3. ID选择器
-
定义:通过元素的ID属性来选择唯一的元素,以井号
#
开头。 -
示例:
#idname
选择ID属性为"idname"的元素。
4. 后代选择器
-
定义:通过空格分隔祖先元素和后代元素来选择特定元素的后代。
-
示例:
div p
选择所有<div>
元素内部的<p>
元素。
5. 子选择器
-
定义:使用
>
符号选择直接子元素。 -
示例:
div > p
仅选择<div>
元素的直接子元素<p>
。
6. 相邻兄弟选择器
-
定义:使用
+
符号选择紧接在另一元素后的元素。 -
示例:
h1 + p
选择紧接在<h1>
元素后的<p>
元素。
7. 通用兄弟选择器
-
定义:使用
~
符号选择某一元素之后的所有兄弟元素。 -
示例:
h1 ~ p
选择所有在<h1>
元素之后的<p>
兄弟元素。
8. 属性选择器
-
定义:根据元素的属性及属性值来选择元素。
-
示例:
-
[attribute]
选择具有指定属性的元素。 -
[attribute=value]
选择属性等于特定值的元素。 -
[attribute^=value]
选择属性值以特定字符串开头的元素。 -
[attribute$=value]
选择属性值以特定字符串结尾的元素。 -
[attribute*=value]
选择属性值包含特定字符串的元素。
-
9. 伪类选择器
-
定义:用于定义元素的特殊状态,如:hover、:active、:first-child等。
-
示例:
-
:hover
鼠标悬停状态。 -
:active
元素被激活的状态(如鼠标点击)。 -
:first-child
选择父元素的第一个子元素。
-
10. 伪元素选择器
-
定义:用于创建虚拟元素,并为其设置样式,如::before、::after。
-
示例:
-
::before
在元素内容之前插入新内容。 -
::after
在元素内容之后插入新内容。
-
11. 组合选择器
-
定义:通过逗号分隔多个选择器,可以同时选择多个元素并应用相同的样式。
-
示例:
p, span, a
选择所有<p>
、<span>
和<a>
元素。
12. 交集选择器
-
定义:选择同时满足多个条件的元素,通常用于类选择器或ID选择器与元素选择器的组合。
-
示例:
div.classname
选择所有class属性为"classname"的<div>
元素。
13. 结构伪类选择器
-
定义:基于文档树的结构来选择元素,如
:nth-child()
、:nth-of-type()
等。 -
示例:
-
:nth-child(n)
选择父元素的第n个子元素。 -
:nth-of-type(n)
选择父元素中第n个指定类型的子元素。
-
CSS选择器种类繁多,每种选择器都有其特定的应用场景。了解和掌握这些选择器,可以帮助开发者更灵活地控制页面元素的样式。
29. 使用过精灵图吗?好处是什么?弊端是什么?
精灵图(Sprite),也称为雪碧图或CSS Sprite,是一种网页图片应用处理方式,主要用于优化网页性能。以下是对精灵图的好处和弊端的详细分析:
好处
-
减少HTTP请求:
-
精灵图通过将多个小图标或图片合并成一张大图,显著减少了页面加载时需要发出的HTTP请求次数。每个HTTP请求都会增加页面的加载时间,因此减少请求次数可以显著提高页面加载速度。
-
-
提高加载速度:
-
由于只需要加载一张大图,而不是多个小图,因此精灵图减少了图片加载的总时间,提高了网页的整体加载速度。这对于提高用户体验尤为重要,尤其是在网络条件不佳的情况下。
-
-
减少带宽消耗:
-
减少HTTP请求和图片加载次数也意味着减少了带宽的消耗。这对于移动设备和低速网络连接的用户来说,效果更加明显,有助于降低用户的流量成本。
-
-
简化CSS:
-
使用精灵图时,可以通过CSS的
background-image
和background-position
属性来控制每个小图标的显示。这种方式简化了CSS代码,使得样式表更加整洁和易于维护。
-
-
方便管理和维护:
-
所有图标都包含在一个文件中,使得图标的管理和维护变得更加方便。如果需要修改或更新图标,只需修改精灵图文件即可,而无需逐个更新页面中的小图标文件。
-
-
减少命名困扰:
-
当多个小图标被合并成一张大图时,只需对这张大图进行命名,而无需为每个小图标单独命名,从而减少了命名上的困扰。
-
弊端
-
开发时需要测量:
-
在制作精灵图时,开发者需要精确测量每个小图标在大图中的位置和尺寸,以便在CSS中正确设置
background-position
属性。这个过程可能比较繁琐和耗时。
-
-
维护时可能麻烦:
-
如果精灵图中的某个小图标需要修改或更新,整个精灵图文件可能都需要重新制作和上传。这意味着页面的其他部分(即使它们没有使用到修改过的小图标)也可能需要重新加载精灵图文件,从而可能影响性能。
-
-
不能随意改变大小和位置:
-
由于精灵图中的小图标是按照一定的布局排列的,因此它们的大小和位置通常不能随意改变。如果需要调整某个小图标的大小或位置,可能需要重新制作精灵图。
-
-
宽屏高分辨率屏幕下可能出现背景断裂:
-
在宽屏或高分辨率的屏幕下,如果精灵图的宽度不够宽或高度不够高,可能会出现背景断裂的情况。这通常是因为精灵图的设计没有考虑到不同屏幕尺寸和分辨率的适应性。
-
综上所述,精灵图在提高网页性能、减少带宽消耗和简化CSS等方面具有显著的优势,但在开发和维护过程中也存在一些需要注意的问题。因此,在使用精灵图时需要根据实际情况进行权衡和选择。
30. 对Promise的了解
Promise是JavaScript中用于处理异步操作的一种编程模式,它代表了一个未来才会知道结果的事件(通常是一个异步操作)。以下是对Promise的详细了解:
一、Promise的基本概念
-
定义:Promise是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,通过它可以获取异步操作的消息。
-
状态:Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果才能改变Promise的状态,且状态一旦改变就不会再变。
二、Promise的构造函数
-
使用
new Promise()
关键字创建一个Promise对象。构造函数接受一个执行器函数作为参数,该函数本身又接受两个函数作为参数:resolve和reject。-
resolve
函数用于将Promise的状态从pending变为fulfilled,并将异步操作的结果作为参数传递出去。 -
reject
函数用于将Promise的状态从pending变为rejected,并将错误信息作为参数传递出去。
-
三、Promise的实例方法
-
then():用于处理Promise成功时的回调函数。当Promise状态变为fulfilled时,会执行then方法中的回调函数,并将异步操作的结果作为参数传递给该回调函数。
-
catch():用于处理Promise失败时的回调函数。当Promise状态变为rejected时,会执行catch方法中的回调函数,并将错误信息作为参数传递给该回调函数。
-
finally():无论Promise最终状态如何,finally方法中的回调函数都会被执行。它主要用于执行一些清理工作,如关闭文件描述符、释放资源等。
四、Promise的链式调用
-
Promise支持链式调用,即可以在一个then方法中返回一个新的Promise对象,并在其后继续调用then方法。这样可以将多个异步操作按照顺序连接起来,形成一个操作链。链中的每个then方法都会等待前一个then方法中的异步操作完成后再执行。
五、Promise的优缺点
-
优点:
-
避免了传统异步编程中的回调地狱问题,使得代码更加清晰、易于维护。
-
提供了统一的异步编程接口,使得控制异步操作更加容易。
-
支持链式调用,可以清晰地表达异步操作之间的依赖关系。
-
-
缺点:
-
一旦新建就无法取消,会立即执行。
-
如果不设置回调函数,Promise内部抛出的错误不会反应到外部。
-
在pending状态下,无法得知异步操作进行到哪一步了。
-
六、Promise的应用场景
-
Promise在前端开发中有着广泛的应用场景,包括但不限于:
-
异步请求:如发送HTTP请求获取数据。
-
定时器:如延迟执行某个函数或定时轮询。
-
动画效果:管理动画效果的执行顺序和完成状态。
-
多个异步操作的并行执行:如同时发送多个请求并等待它们全部完成后再进行下一步操作。
-
异步操作的错误处理:捕获并处理异步操作中的错误。
-
复杂的异步操作流程控制:根据某个异步操作的结果来决定下一步的操作。
-
总之,Promise是JavaScript中一种强大的异步编程模式,它使得异步操作的控制变得更加容易和直观。通过合理使用Promise,可以编写出更加清晰、易于维护的异步代码。
31. 如果说有多个promise完成后再进行操作,应该用什么?
在JavaScript中,如果你想要等待多个Promise都完成后再进行操作,你可以使用Promise.all()
方法。
Promise.all()
接受一个Promise对象的数组作为参数,并返回一个新的Promise对象。这个新的Promise对象在所有的Promise都成功完成时才会触发成功,任何一个Promise失败都会立即触发新的Promise的失败。
下面是一个使用Promise.all()
的例子:
let promise1 = Promise.resolve(3);
let promise2 = 42;
let promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, 42, "foo"]
}).catch((error) => {
console.error(error);
});
在这个例子中,promise1
、promise2
和promise3
都是Promise对象。我们使用Promise.all()
等待它们都完成,然后打印出它们的值。注意,即使promise2
不是一个显式的Promise对象,它也会被Promise.all()
自动转换成一个立即解决的Promise。
如果任何一个Promise失败了,Promise.all()
会立即失败,并且catch块会捕获到这个错误。如果你想要即使某些Promise失败了也继续等待其他的Promise完成,你可以考虑使用Promise.allSettled()
,它会在所有的Promise都“结束”(无论是解决还是拒绝)后触发。
32. 箭头函数的this指向哪里?
箭头函数(Arrow Function)是ES6中新增的一种更简洁的函数写法,与传统的函数表达式相比,箭头函数在语法上更加简洁,并且没有自己的this
,arguments
,super
,或 new.target
。这些函数表达式更适合用在非方法函数的地方,并且它们不能用作构造函数。
箭头函数的this
是在定义函数的时候绑定的,而不是在执行函数的时候绑定的。这意味着,箭头函数内部的this
值与它在被创建时的上下文相同。换句话说,箭头函数没有自己的this
绑定。因此,this
的值将继承自外围作用域(即箭头函数定义时的上下文)。
例如:
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // |this| 正确地指向 person 对象
}, 1000);
}
var p = new Person();
在这个例子中,setInterval
中的箭头函数没有自己的this
,它的this
继承自外围作用域,即Person
函数的实例p
。因此,每当setInterval
调用箭头函数时,this.age
都会正确地增加。
总之,箭头函数的this
指向定义它时的上下文环境,即它的this
是在定义时绑定的,而不是在调用时绑定的。
33. 闭包概念?
闭包(Closure)是计算机编程中的一个重要概念,尤其在函数式编程和面向对象编程中广泛使用。闭包可以简单理解为能够读取其他函数内部变量的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。以下是对闭包概念的详细解释:
定义
闭包包含自由(未绑定到特定对象)变量,这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(通常是局部变量)。闭包允许一个函数访问并操作函数外部的变量,即使外部函数已经执行完毕。
特点
-
函数嵌套函数:闭包通常涉及一个函数内部定义另一个函数,内部函数可以访问外部函数的局部变量。
-
延长变量生命周期:闭包可以延长外部函数中变量的生命周期,即使外部函数已经执行完成,只要闭包仍然存在,这些变量就不会被垃圾回收机制回收。
-
封装私有变量:闭包可以用于封装私有变量,这些变量只能通过特定的函数接口访问,从而提高了代码的封装性和安全性。
用途
闭包在编程中有多种用途,包括但不限于:
-
实现回调函数:闭包是实现回调函数的一种常见方式,允许异步操作完成后执行特定的函数。
-
封装私有状态:在JavaScript等语言中,闭包可以用来封装对象的私有状态,实现模块化的代码设计。
-
实现函数防抖和节流:通过闭包,可以实现对事件的防抖和节流处理,提高页面的响应性能和用户体验。
示例
function outerFunction() {
var localVar = "I am local";
function innerFunction() {
console.log(localVar); // 可以访问外部函数的局部变量
}
return innerFunction; // 返回内部函数,形成闭包
}
var myClosure = outerFunction(); // myClosure现在是一个闭包
myClosure(); // 输出: I am local
以下是一个简单的JavaScript闭包示例:
34. 如果下拉框有上万个选项怎么处理?
处理具有上万个选项的下拉框是一个具有挑战性的任务,因为传统的下拉框在这种情况下会变得非常低效,甚至不可用。以下是一些处理这种情况的策略:
-
使用虚拟滚动:
虚拟滚动技术可以只渲染可视区域内的选项,而不是渲染所有选项。当用户滚动下拉框时,只渲染和显示新进入可视区域的选项。 -
分页或懒加载:
可以将选项分成多个页面,每次只加载和显示一个页面的选项。当用户滚动到底部时,再加载下一个页面的选项。 -
使用搜索框:
在下拉框上方添加一个搜索框,允许用户输入搜索词来过滤选项。这样,用户只需要看到与他们搜索词相关的选项,而不是所有选项。 -
使用服务器端搜索:
如果选项数据存储在服务器上,可以实现一个服务器端搜索功能。用户输入搜索词后,服务器返回匹配的选项,然后在下拉框中显示这些选项。 -
使用缓存:
如果选项数据不经常更改,可以考虑将选项数据缓存在客户端。这样,当用户再次打开下拉框时,可以从缓存中快速加载选项,而不是每次都从服务器获取。 -
优化DOM操作:
当需要动态添加或删除选项时,应该尽量优化DOM操作。例如,可以使用DocumentFragment
或requestAnimationFrame
来减少页面重绘和重排的次数。 -
考虑使用第三方库:
有一些第三方库专门用于处理大量数据的下拉框,如Select2
、React-Select
等。这些库通常提供了虚拟滚动、分页、搜索等功能,并且经过了优化,可以处理大量数据。 -
重新设计UI:
如果下拉框包含如此多的选项,可能需要重新考虑是否应该使用下拉框。也许可以使用其他UI组件,如自动完成输入框、标签页、分页列表等,来更好地满足用户需求。
综上所述,处理具有上万个选项的下拉框需要综合考虑性能、用户体验和可行性。通过选择合适的策略和技术,可以有效地解决这个问题。
35. 浏览器会不会回收闭包的内存?
浏览器会回收闭包的内存,但回收的条件和方式与闭包的具体使用场景有关。
闭包与内存管理
闭包是JavaScript中一个强大的特性,它允许一个函数访问并操作函数外部的变量。在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁。闭包中的变量和函数如果不再被需要,浏览器会通过垃圾回收机制来回收它们所占用的内存。
垃圾回收机制
现代浏览器普遍采用标记-清除(Mark-and-Sweep)或分代回收(Generational GC)等垃圾回收算法来管理内存。这些算法的基本思想是通过跟踪和标记内存中的对象,来识别出哪些对象是不再被需要的,从而回收它们的内存。
闭包内存回收的条件
对于闭包而言,其内存回收的条件主要取决于闭包及其内部变量是否还被外部引用。如果闭包或闭包中的变量不再被任何外部引用所指向,那么它们就会被视为不再需要,从而在垃圾回收时被回收。
注意事项
- 滥用闭包:如果滥用闭包,例如在全局作用域中创建大量闭包,或者闭包中引用了大量不必要的变量,就可能导致内存泄漏,即内存被占用但无法被回收。
- 循环引用:在JavaScript中,循环引用也可能导致内存泄漏。如果两个或多个对象相互引用,且没有其他外部引用指向它们,那么这些对象就可能因为循环引用而无法被垃圾回收器回收。然而,现代浏览器采用的垃圾回收算法通常能够处理循环引用的问题。
- 手动解除引用:为了避免内存泄漏,开发者可以手动解除对不再需要的闭包或变量的引用。例如,将闭包或变量设置为
null
,就可以显式地告诉垃圾回收器这些对象不再被需要。
结论
浏览器会回收闭包的内存,但回收的条件和方式与闭包的具体使用场景有关。开发者需要注意避免滥用闭包和创建循环引用,以确保内存的有效回收和应用的性能。同时,了解浏览器的垃圾回收机制也有助于更好地优化JavaScript代码的内存使用。
36. 开发一个适配不同屏幕尺寸的页面应该怎么实现?
开发一个适配不同屏幕尺寸的页面,主要依赖于响应式设计(Responsive Web Design, RWD)。响应式设计旨在使网页能够自适应不同尺寸的屏幕,从而在不同设备上都能提供良好的用户体验。以下是实现响应式设计的几个关键步骤:
- 使用流体布局(Fluid Layouts):
- 流体布局使用百分比而不是固定像素来定义容器、列和行的宽度。这样,布局可以根据屏幕尺寸的变化而自动调整。
- 媒体查询(Media Queries):
- 媒体查询允许开发者根据不同的屏幕尺寸和特性来应用不同的CSS样式。通过媒体查询,可以针对手机、平板和桌面等不同设备设置特定的样式规则。
- 灵活的图像和媒体(Flexible Images and Media):
- 图像和媒体内容也应该能够适应不同屏幕尺寸。这通常通过使用
max-width: 100%;
和height: auto;
来实现,确保图像不会超出其父容器的宽度,并且高度会根据宽度自动调整。
- 图像和媒体内容也应该能够适应不同屏幕尺寸。这通常通过使用
- 使用视口单位(Viewport Units):
- 视口单位(如vw、vh)允许元素的大小根据视口的大小来设置。例如,
width: 50vw;
将元素的宽度设置为视口宽度的50%。
- 视口单位(如vw、vh)允许元素的大小根据视口的大小来设置。例如,
- 避免使用绝对定位(Avoid Using Absolute Positioning):
- 绝对定位的元素可能不会在不同屏幕尺寸下正确显示。相反,应该使用相对定位或流式布局来确保元素的布局能够适应不同屏幕。
- 测试和调整(Testing and Tweaking):
- 在不同的设备和屏幕尺寸上测试网页,确保它在所有目标设备上都能正确显示和工作。根据需要调整样式和布局。
- 使用框架或工具(Using Frameworks or Tools):
- 考虑使用如Bootstrap、Foundation等响应式框架,它们提供了内置的响应式特性和组件,可以简化响应式网页的开发过程。
- 关注性能(Focus on Performance):
- 响应式设计不仅要考虑布局和样式,还要关注性能。优化图像、减少HTTP请求、使用浏览器缓存等技术可以提高网页在不同设备上的加载速度和性能。
综上所述,通过结合流体布局、媒体查询、灵活的图像和媒体、视口单位、相对定位以及测试和调整,可以开发出适配不同屏幕尺寸的响应式网页。同时,使用现成的响应式框架或工具可以进一步简化开发过程。
37. Css的预处理器?与css相比有什么优势?
CSS预处理器是一种工具,它扩展了CSS的功能,为CSS增加了一些编程的特性,让开发者能够用更高级、更灵活的方式来编写样式表。与原生CSS相比,CSS预处理器具有以下几个显著优势:
CSS预处理器简介
CSS预处理器使用一种专门的编程语言进行Web页面样式设计,然后再编译成正常的CSS文件,以供项目使用。常见的CSS预处理器包括Sass、LESS和Stylus等。这些预处理器为CSS提供了变量、嵌套规则、混合宏、函数等高级功能,使得样式表的编写更加灵活和高效。
CSS预处理器与原生CSS相比的优势
-
更好的可维护性:
-
变量:CSS预处理器支持变量,使得颜色、字体大小等常用样式值可以在一个地方定义,然后在整个样式表中重复使用。这大大减少了样式的重复编写,也便于在需要时统一修改样式值。
-
嵌套规则:原生CSS中,对于嵌套的元素需要重复书写父元素的选择器,而CSS预处理器支持嵌套规则,可以使得父子关系一目了然,减少代码量,提高维护性。
-
模块化:通过CSS预处理器,可以将样式表划分为多个独立的模块或组件,每个模块或组件负责一部分样式,这有助于代码的组织和管理。
-
-
更高的可读性:
-
CSS预处理器通常支持更直观的语法,例如使用缩进表示嵌套规则,以及使用括号和分号表示语句等,这使得代码更加易读。
-
通过嵌套和模块化,CSS预处理器可以帮助开发者更好地组织代码结构,提高代码的可读性。
-
-
更高的生产效率:
-
函数和混合宏:CSS预处理器提供了函数和混合宏等高级功能,使得开发者可以编写可复用的样式代码块,提高开发效率。
-
条件语句和循环:虽然CSS本身不支持条件语句和循环等编程特性,但CSS预处理器可以实现这些功能,使得开发者能够编写更加动态的样式表。
-
-
浏览器兼容性:
-
CSS预处理器通常可以处理浏览器兼容性问题,例如自动添加浏览器前缀等,减轻开发者的负担。
-
-
其他功能:
-
颜色函数:CSS预处理器内置了一些颜色处理函数,用于对颜色值进行加亮、变暗、混合等操作,使得颜色管理更加方便。
-
缓存和性能优化:预处理器还可以优化CSS文件的加载速度和性能,例如通过合并多个CSS文件、压缩CSS代码等方式来减少HTTP请求和文件大小。
-