前端必会面试题
亦.落 2024-08-08 09:03:07 阅读 78
不足之处评论我进行修改,或者有更好的面试题评论告诉我,我添加上,后续我还会继续添加面试题
1、什么是闭包
必须有一个内嵌函数。内嵌函数必须引用外部函数中的变量。外部函数的返回值必须是内嵌函数。
1.闭包的概念
闭包是这样的一种机制:函数嵌套函数,内部函数可以引用外部函数的参数和变量。参数和变量不会被垃圾回收机制收回。
2.闭包的作用
数据封装:闭包可以用于封装私有数据,只暴露有限的接口供外界访问。保持变量状态:闭包允许函数记住和访问其词法作用域中的变量,即使函数在其作用域之外执行。延迟计算:通过闭包可以推迟计算的执行,直到真正需要结果时。
相比全局变量和局部变量,闭包有两大特点:
1.闭包拥有全局变量的不被释放的特点
2.闭包拥有局部变量的无法被外部访问的特点
闭包的好处:
1.可以让一个变量长期在内存中不被释放
2.避免全局变量的污染,和全局变量不同,闭包中的变量无法被外部使用
3.私有成员的存在,无法被外部调用,只能直接内部调用
3.闭包的使用场景
装饰器:在不修改原有函数代码的情况下,增加额外的功能。回调函数:封装了状态的函数可以作为回调函数传递给某些操作。函数工厂:根据输入参数的不同返回不同行为的函数。
4.闭包语法规范
定义外部函数。在外部函数内定义内部函数。内部函数引用外部函数的变量。外部函数返回内部函数。
5.闭包的注意事项
1.内存泄漏
由于闭包可以访问外部函数的作用域,如果不小心,可能导致内存泄漏。在不再需要使用闭包时,及时释放对它的引用是很重要的。
造成内存泄漏的情况(意外的全局变量、定时器)
2.陷阱:异步操作
当在循环或迭代中创建闭包时,可能会遇到与预期不符的问题,尤其是在涉及异步操作时。
<code>for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出: 5 5 5 5 5
在上面的例子中,由于 JavaScript 中的事件循环机制,setTimeout 中的闭包在循环结束后执行,此时 i 的值已经变成了 5。为了解决这个问题,可以使用块级作用域或立即执行函数。
for (var i = 0; i < 5; i++) {
(function(index) {
setTimeout(function() {
console.log(index);
}, 1000);
})(i);
}
// 输出: 0 1 2 3 4
2.垃圾回收机制有两种:1.标记清除, 2.引用计数
1.标记清除:js会对变量做一个标记yes or no的标签以供js引擎来处理,当变量在某个环境下被使用则标记为yes,当超出改环境(可以理解为超出作用域)则标记为no,然后对有no的标签进行释放。
2.引用计数:对于js中引用类型的变量, 采用引用计数的内存回收机制,当一个引用类型的变量赋值给另一个变量时, 引用计数会+1, 而当其中有一个变量不再等于值时,引用计数会-1, 如果引用计数为0, 则js引擎会将其释放掉。
3.防抖节流的概念
防抖 是指在一定时间内,在动作被连续频繁触发的情况下,动作只会被执行一次,也就是说当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间,所以短时间内的连续动作永远只会触发一次,比如说用手指一直按住一个弹簧,它将不会弹起直到你松手为止。
节流 是指一定时间内执行的操作只执行一次,也就是说即预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期,一个比较形象的例子是人的眨眼睛,就是一定时间内眨一次。
1.作用
节流(throttle)与 防抖(debounce)都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象。
2. 区别
防抖: 任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行;
节流: 指定时间间隔内只会执行一次任务;
3.原理
函数节流与函数防抖巧妙地使用 setTimeout 来存放待执行的函数,这样可以很方便的利用 clearTimeout 在合适的时机来清除待执行的函数。
4.应用场景
函数节流应用的实际场景,多数在监听页面元素滚动事件的时候会用到。因为滚动事件,是一个高频触发的事件。
函数防抖的应用场景,最常见的就是用户注册时候的手机号码验证和邮箱验证了。只有等用户输入完毕后,前端才需要检查格式是否正确,如果不正确,再弹出提示语。
4.重绘与回流的区别
1、 重绘:元素样式的改变(但宽高、大小、位置等不变)
如:outline、visibility、color、background-color等
只改变自身样式,不会影响到其他元素
2、 回流:元素的大小或者位置发生改变(当页面布局和几何信息发生改变的时候),触发了重新布局导致渲染树重新计算布局和渲染
如添加或删除可见的DOM元素;元素的位置发生变化;元素的尺寸发生变化、内容发生变化(如文本变化或图片被另一个不同尺寸的图片所代替);页面一开始渲染的时候(无法避免);
因为回流是根据视口大小来计算元素的位置和大小的,所以浏览器窗口尺寸变化也会引起回流
注意:回流一定会触发重绘,而重绘不一定会回流
3.如何避免(减少)回流
css
避免设置多层内联样式。
如果需要设置动画效果,最好将元素脱离正常的文档流。
避免使用CSS表达式(例如:calc())。
JavaScript
避免频繁操作样式,最好将样式列表定义为class并一次性更改class属性。
避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
可以先为元素设置为不可见:display: none,操作结束后再把它显示出来。
5.什么是原型和原型链
原型就是个对象
每个对象都有他自己对应的原型对象,每个对象都可以使用他对象对应的原型对象上面的所有的属性和方法
原型链:查找对象实例的方法和属性时,先在自身找,找不到则沿着__proto__向上查找,我们把__proto__形成的链条关系称原型链
6.数组去重的方式
1、new Set() + Array.from
Set对象:是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即Set中的元素是唯一的。Set本身是一个构造函数,用来生成 Set 数据结构。类似于数组,不是真正的数组,不能使用 length 方法
Array.from() 方法:对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
注意:以上去方式对NaN和undefined类型去重也是有效的,是因为NaN和undefined都可以被存储在Set中, NaN之间被视为相同的值(尽管在js中:NaN !== NaN)
对 {} 无效
const newArr = Array.from(new Set(arr))
console.log(newArr) // [9, 2, '123', true, NaN, false, undefined, {…}, {…}]
2、双层循环,外层循环元素,内层循环时比较值如果有相同的值则跳过,不相同则push进数组
function getDisArray2(arr) {
let newArr = [];
let len = arr.length
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[i] === arr[j]) {
j++
}
}
newArr.push(arr[i]);
}
return newArr
}
getDisArray2(arr) // [9, 2, '123', true, NaN, false, undefined, NaN, {…}, {…}]
3、
利用两层循环+数组的splice方法
function getDisArray3(arr) {
let len = arr.length
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1)
len-- // 减少循环次数提高性能
j-- // 保证j的值自加后不变
}
}
}
return arr
}
getDisArray3(arr) // [9, 2, '123', true, NaN, false, undefined, NaN, {…}, {…}]
4、利用数组的indexOf方法 + forEach
function getDisArray4(arr) {
const newArr = []
arr.forEach(item => {
if (newArr.indexOf(item) === -1) {
newArr.push(item)
}
})
return newArr // 返回一个新数组
}
console.log(getDisArray4(arr))
//[9, 2, '123', true, NaN, false, undefined, NaN, {…}, {…}]
5、利用filter
function getDisArray7(arr) {
return arr.filter(function (item, index, arr) {
return arr.indexOf(item, 0) === index;
});
}
console.log(getDisArray7(arr)) // [9, 2, '123', true, false, undefined, {…}, {…}]
当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
6、利用Map()
function getDisArray4(arr) {
const map = new Map()
const newArr = []
arr.forEach(item => {
if (!map.has(item)) { // has()用于判断map是否包为item的属性值
map.set(item, true) // 使用set()将item设置到map中,并设置其属性值为true
newArr.push(item)
}
})
return newArr
}
getDisArray4(arr) // [9, 2, '123', true, NaN, false, undefined, {…}, {…}]
7.Let、const、var的区别
1.作用域
var声明变量的作用域
var声明的变量不存在块级作用域,属于全局作用域。但是存在函数作用域,在函数中用var声明的变量在函数外不能使用,也不能跨函数使用(在其他函数里使用)。
let声明变量的作用域
let声明的变量存在块级作用域,由{ }包裹,不能跨块访问。也存在存在函数作用域,在函数中用let声明的变量在函数外不能使用,也不能跨函数使用(在其他函数里使用)。
const声明变量的作用域
const声明的变量存在块级作用域,由{ }包裹,不能跨块访问。也存在存在函数作用域,在函数中用const声明的变量在函数外不能使用,也不能跨函数使用(在其他函数里使用)。
2.变量提升
let和const声明的变量和函数不存在变量提升,var声明的变量和函数存在变量提升
3.暂时性死区
暂时性死区是指在代码块内,使用let或const命令声明变量之前的区域,该变量在此区域内是不可用的,即属于该变量的“死区”。
因为let,const不存在变量提升所以导致死区的形成,但是var不存在暂时性死区的情况。
在ES6中,暂时性死区的概念是为了防止在变量声明前就使用这个变量,从而导致意料之外的行为。具体来说,当在代码块内尝试访问一个使用let或const声明的变量,但在该声明之前就已经引用了这个变量时,会触发一个ReferenceError错误。可以看如下代码方便理解:
let a = 1
{
//死区开始
console.log(a) //死区:因为存在块级作用域无法访问外部的a,也无法访问到内部的a
//死区结束
let a = 2 //此区域块内创建了a并绑定了值
console.log(a)//此时获取到内部的a
}
4.重复声明
var在同一作用域内,相同的变量可以重复声明变量,let和const不可以
5、全局属性
var关键字声明的变量会挂载到window全局属性上,但是let和const声明的则不会。
6.初始值
1、使用const定义变量时必须赋予其初始值,而var和let则不用
2、const声明的常量必须进行初始化,不允许对常量重新赋值
3、如果const定义的是一个复杂数据类型,可以添加、删除、修改值,但不能改变原有类型
8.Es6新增了哪些新特性
1、let和const
let var const的区别
let 是代码块有效 var是全局有效
let 是不能重复声明的 var是可以多次声明
let不存在变量的提升 var存在变量的提升
const存储简单数据类型存储的是常量
2、symbol
Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值,不能与其他数据类型进行运算。它是JavaScript中的第七种数据类型,与undefined、null、Number(数值)、String(字符串)、Boolean(布尔值)、Object(对象)并列。
你可以这样创建一个Symbol值:
const a = Symbol();
console.log(a); //Symbol()
//因为Symbol是基本数据类型,而不是对象,不能 new 。
const a = new Symbol();//报错,Symbol is not a constructor
3、模板字符串
在ES6之前,处理模板字符串:
通过“\”和“+”来构建模板对ES6来说:
用${}
来界定;
反引号(``)
直接搞定;
<script>
url="xxxxxx"code>
// es6之前
let html="<div>"+code>
" <a>"+url+"</a>"+
"</div>";
//es6
let eshtml=`<div>
<a>${url}</a>
</div>`
</script>
4、解构表达式
结构赋值是对赋值运算符的扩展。它是一种针对数组或者对象进行匹配模式,然后对其中的变量进行赋值。
字符串、以及ES6新增的Map和Set 都可以使用解构表达式
5、函数方面
参数默认值
<script>
function add(a = 0, b = 0) {
return a + b;
}
let x=add();
let y=add(2);
let z=add(3, 4);
console.log(x,y,z); //x=0, y=2, z=7
</script>
5.2 箭头函数
箭头函数实现了一种更加简洁的书写方式。箭头函数内部没有arguments
,也没有prototype
属性,所以不能用new
关键字调用箭头函数。
箭头函数和普通函数最大的区别在于其内部this永远指向其父级对象的this。(重点)
6、运算符
...
扩展运算符
可选链 ?.
函数绑定运算符::
7、模块化
ES6使用关键字 import
导入模块(文件),有两种常用的方式:
import ‘模块名称’ from ‘路径’;
import ‘路径’;
导出
let name = 'ren',age = 12;
export {name,age};
//注意:变量需要用大括号包裹,然后才能向外输出
模块化优点: 1.防止命名冲突, 2.复用性强
8.数据类型判断 (得准确的知道每一种数据类型判断缺点)
typeof 能判断基本的数据类型,返回基本数据类型小写字符串形式 除了null,用typeof判断null 返回Object
instanceof 可以判断引用数据类型 正常的判断A是B的实例是没有问题的,但是所有引用数据类型的对象用instanceof判断都是Object的实例
constructor 构造函数可以判断除了undefined 和null之外的任何数据类型,页解决了instanceof的问题
最完美的解决方案 Object.prototype.toString.call()返回的是[ Object 数据类型]
9.普通函数和箭头函数的区别
1. 箭头函数比普通函数更加简洁
如果没有参数,就直接写一个空括号即可
如果只有一个参数,可以省去参数的括号
如果有多个参数,用逗号分割
如果函数体的返回值只有一句,可以省略大括号
2. 箭头函数没有自己的this
箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
3. 箭头函数继承来的this指向永远不会改变
4. call()、apply()、bind()等方法不能改变箭头函数中this的指向
5. 箭头函数不能作为构造函数使用
由于箭头函数时没有自己的this,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
6. 箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
7. 箭头函数没有prototype
8. 箭头函数的this指向哪⾥?
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
9.箭头函数没有arguments,要接受所有的参数用...rest
10.New操作符都做了哪些事情
new 在执行时,会做下面这四件事:
(1)开辟内存空间,在内存中创建一个新的空对象。
(2)让 this 指向这个新的对象。
(3)执行构造函数里面的代码,给这个新对象添加属性和方法。
(4)返回这个新对象(所以构造函数里面不需要 return)。
因为 this 指的是 new 一个 Object 之后的对象实例。于是,下面这段代码:
// 创建一个函数
function Student(name) {
this.name = name; //this指的是构造函数中的对象实例
}
11、对象的浅拷贝与深拷贝
由于引用数据类型的数据是存储在堆空间中,在栈空间中存储的是是数据的引用地址。
对象的浅拷贝就是将栈空间中的地址复制一份,两个地址指向的同一个数据
浅拷贝可以使用Object.assign()来实现 深拷贝可以使用JSON.stringify()先转换为json的串复制,然后再通过JSON.parse()转换回来
12、说一下js的事件机制
js中存在两种事件机制,一个是ie提出的冒泡事件机制 还有一个是网景提出的捕获型事件机制
冒泡事件机制是先触发事件的的直接元素,然后向外扩散就像冒泡一样。捕获型就是事件从外向里执行。js中的事件监听addEventListener的第三个参数默认的为false 是冒泡 为true是捕获
我们可以通过event.stopPropagation()来实现
13、rem布局的原理
1rem的大小就是根元素<html>的font-size的值,通过设置 根元素<html>的font-size的大小,来控制整个html文档内的字体大小、元素宽高、内外边距等
14、$route和$router的区别
$route是一个跳转的路由对象,每一个路由都会有一个$route对象,是一个局部的对象,可以获取对应的name,path,params,query等
$router是VueRouter的一个对象,通过Vue.use(VueRouter)和Vue构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由,包含了许多关键的对象和属性。
15、params和query传参的区别
params传值的参数是路由的一部分,所以调转必须加参数值才能调转 query传参和路由配置没有关系
获取方式是不一样的 query this.$route.query.参数名 params是 this.$route.params.参数名
16.什么是事件循环?
概念
事件循环是JavaScript中一种异步执行机制,他的作用是协调和管理各种异步任务的执行顺序,保证JavaScript代码的执行顺序和预期一致,提高JavaScript代码的可靠性和稳定性
JavaScript代码执行时会先执行同步任务,而异步任务则被存放任务队列中等待执行。
事件循环由三部分组成:
1.调用栈:用来管理代码的执行顺序
2.任务队列:用来存放异步任务
3.事件循环线程:是一个循环,不断从任务队列中取出任务,放入调用栈中执行,直到任务
队列为空为止
任务队列分两种类型:
宏任务和微任务。宏任务包括setTimeout()、setlnterval()、XMLHttpRequest等异步任务
微任务包括Promise、MutationObserver等异步任务,在事件循环过程中,当一个宏任务执行完成之后会先执行所有微任务,然后再从任务队列中取出下一个宏任务执行
17.Promise的理解?
Promise是ES6引入的异步编程的处理方案,有三种状态:pending(进行中)、resolved(已完成)、rejected(已失败)。从语法上说,Promise是一个构造函数,可以实例化对象。封装异步操作,获取成功和失败的结果。
优点:支持链式调用,可以解决回调地狱的问题。
缺点:无法取消Promise,一旦新建他就会立即执行,无法中途取消。
其次,如果不设置回调函数,Promise内部跑出的错误无法反应到外部。当pending的时候,无法知道进展到了哪一步。
应用场景(图片的懒加载、预加载)
18.sessionStorage、localStorage和cookie的区别
1)相同点是都是保存在浏览器端、且同源的
2)cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
3)存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
4)数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
5)作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
6)web Storage支持事件通知机制,可以将数据更新的通知发送给监听者
7)web Storage的api接口使用更方便
19.Call、apply、bind的区别?
一、参数区别
1、call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表,当第一个参数为null、undefined的时候,默认指向window。
2、apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。
3、bind接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组,这一点与apply相同。
二、调用区别
1、call、apply都是立即调用。bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数,便于稍后调用。而原函数 中的 this 并没有被改变,依旧指向原来该指向的地方。
bind应用场景:给参数指定默认参数、绑定构造函数
20.前端跨域的解决方案?
JSONP
JSONP是用于解决跨域的一种常见方法,它利用了HTML的<script>
标签的特性,可以跨域加载一个JavaScript文件。下面是一个JSONP解决跨域的案例
CORS(Cross-Origin Resource Sharing)
CORS(Cross-Origin Resource Sharing),即跨源资源共享,是一种现代的跨域解决方案,可用于安全地在不同源之间共享资源。
CORS
规定了在浏览器和服务器之间如何进行跨域通信。它通过HTTP请求标头来进行控制和配置,具体包括以下几个方面:
简单请求:对于一些符合特定要求的请求(如请求方式为GET、POST、HEAD,且没有自定义头信息等),浏览器会自动发送一个CORS请求头(Origin),服务器端通过判断这个头部,决定是否返回Access-Control-Allow-Origin头部,来控制是否允许跨域访问。
预检请求:对于非简单请求(如请求方式为PUT、DELETE、PATCH、Content-Type不为text/plain等),浏览器会先发送一个OPTIONS的预检请求(Preflight),服务器端需要返回Access-Control-Allow-Headers、Access-Control-Allow-Methods和Access-Control-Allow-Origin等头部信息来进行配置,来决定是否允许跨域访问。
Access-Control-Allow-Origin头部:用于指定允许访问的域名,其值可以为*,表示允许任何域名请求。
Access-Control-Allow-Methods头部:用于指定允许访问的HTTP方法。
Access-Control-Allow-Headers头部:用于指定允许访问的HTTP头部字段。
代理设置
1.使用代理服务器对请求进行转发也是一种可行的跨域解决方案,可以在我们的服务器上搭建一个代理服务器,然后将请求发送到代理服务器上。代理服务器再将请求转发给真正的服务端,最后再将响应结果返回给前端。
21正向代理和反向代理的区别
虽然正向代理服务器和反向代理服务器所处的位置都是客户端和真实服务器之间,所做的事情也都是把客户端的请求转发给服务器,再把服务器的响应转发给客户端,但是二者之间还是有一定的差异的。
1、正向代理其实是客户端的代理,帮助客户端访问其无法访问的服务器资源。反向代理则是服务器的代理,帮助服务器做负载均衡,安全防护等。
2、正向代理一般是客户端架设的,比如在自己的机器上安装一个代理软件。而反向代理一般是服务器架设的,比如在自己的机器集群中部署一个反向代理服务器。
3、正向代理中,服务器不知道真正的客户端到底是谁,以为访问自己的就是真实的客户端。而在反向代理中,客户端不知道真正的服务器是谁,以为自己访问的就是真实的服务器。
4、正向代理和反向代理的作用和目的不同。正向代理主要是用来解决访问限制问题。而反向代理则是提供负载均衡、安全防护等作用。二者均能提高访问速度
22.封装一个map方法并且条件搜索
如果你想要封装一个 map
方法,并且在遍历数组的同时进行条件筛选,你可以稍微修改上面的 myMap
方法,使其接受一个额外的条件函数。这个条件函数将决定是否对当前元素应用回调函数。下面是一个示例实现:
function myMapWithFilter(array, callback, condition) {
const result = [];
for (let i = 0; i < array.length; i++) {
if (condition(array[i], i, array)) {
result.push(callback(array[i], i, array));
}
}
return result;
}
// 使用示例
const numbers = [1, 2, 3, 4, 5];
const evenDoubled = myMapWithFilter(numbers, function(num) {
return num * 2;
}, function(num) {
return num % 2 === 0;
});
console.log(evenDoubled); // 输出: [4, 8]
23.Promise.all 和 Promise.race的区别?
解决条件:
Promise.all
:所有输入的 Promise 都解决后才解决。Promise.race
:只要有一个输入的 Promise 解决或拒绝,就立即解决或拒绝。
用途:
Promise.all
:适用于所有异步操作都必须成功的情况。Promise.race
:适用于只要有一个异步操作完成就可以继续下一步操作的情况,或者用于设置超时。
错误处理:
Promise.all
:如果其中一个 Promise 被拒绝,那么整个 Promise.all
也会被拒绝,并且会返回第一个被拒绝的 Promise 的理由。Promise.race
:只会返回第一个解决或拒绝的 Promise 的结果,不管它是解决还是拒绝。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。