2020前端近期面试题整理

1. DIV+CSS 布局的好处
  1. 代码精简,且结构和样式分离,易于维护
  2. 代码量少了,减少大量的带宽,页面加载的更快,提升了用户体验
  3. 对 SEO 搜索引擎更加友好,且 H5增加了许多语义化的标签也是如此
  4. 可以更好的丰富页面效果
  5. 符合 W3C 标准,保证网站不会因升级而被淘汰
  6. 缺点:容易出现浏览器的兼容性问题
2. 点击一个 input 依次触发的事件
const text = document.getElementById('text');
text.onclick = function(){
	console.log('onclick');
}
text.onfocus = function(){
	console.log('onfocus');
}
text.onmousedown = function(){
	console.log('onmousedown');
}
text.onmouseenter = function(){
	console.log('onmouseenter');
}
// 结果
// 'onmouseenter'   'onmousedown'   'onfocus'   'onclicj'
3. Vue 的优点及缺点

首先 Vue 最核心的两个优点: ** 响应式 和 组件化**
响应式:这是 Vue 最大的优点,通过 MVVM 思想实现数据的双向绑定,通过虚拟 DOM 让我们可以用数据来操作 DOM,而不用去操作真实的 DOM,提升了性能,且让开发者有更好的时间去处理业务逻辑。
组件化:把一个单页应用中的各个模块拆分到一个一个组件中,或者把一些公共的部分抽离出来做成一个可复用的组件,所以组件化带来的好处就是,提高了开发效率,方便重复使用,使项目的可维护性更强。
虚拟DOM:就是把 真实的 DOM 用 JS对象映射出来,当前虚拟 DOM 也不是 Vue 中独有的。
缺点:基于对象配置的写法,也就是 options 写法,开发时不利于对一个属性的查找,另外的一些缺点,比如 vuex的一些魔法字符串, 对 ts 的支持,兼容性存在一些问题。

4. Vue 中 hash 模式和 history 模式的区别
  • 最明显的是在显示上,hash 模式的 URL 会夹杂着 # 号, 而 history 没有。
  • Vue 底层对它们的实现方式不同。 hash 模式是依靠 onhashchange 事件(监听 location.hash的改变),而 history 模式主要是依靠 HTML5 history 中新增的两个方法, pushState() 可以改变 url 地址且不会发送请求,replaceState() 可以读取历史记录栈,还可以对浏览器记录进行修改。
  • 当真正需要通过 URL 向后端发送 HTTP 请求的时候,比如常见的用户手动输入 URL 后回车,或是刷新(重启)浏览器,这时候 history模式需要后端的支持。因为 history 模式下,前端的 URL 必须和实际向后端发送请求的 URL 一致,例如有一个 URL 是带有路径 path 的(例如:www.baidu.com/images/a),如果后端没有对这个路径做处理的话,就会返回一个 404 错误,所以需要后端增加一个覆盖所有情况的候选资源,一般会配合前端给出一个 404 页面。
5. null 和 undefined 的区别

null 表示一个 “无” 的对象,也就是该出不应该有的值,而 undefined 表示 未定义。
在转换为数字时结果不同, Number(null) 为 0 ,而 Number(undefined) 为 NaN。
在使用场景上:
null :

  • 作为函数的参数,表示该函数的参数不是对象
  • 作为对象原型链的终点

undefined:

  • 变量被定义了,但是没有赋值,就等于 undefined
  • 调用函数时,应该提供的参数没有提供,该参数就为 undefined
  • 对象没有赋值属性, 该属性的值为 undefined
  • 函数没有返回值时,默认返回 undefined
6. 数组去重

有很多数组去重的方法 ,这里只列出几种,详情可以查看: JavaScript数组去重(12种方法)

【 Array.from(new Set(arr)) 或 … new Set(arr) 】:

let arr = [1,1,2,3,1,5,6,7,8,2,4,7];
console.log(Array.from(new Set(arr)));  // [ 1, 2, 3, 5, 6, 7, 8, 4 ]
// console.log([...new Set(arr)])

【 For 循环嵌套,利用 splice 去重 】:

function unique(origin){
	let arr = [].concate(origin);
	for(let i = 0; i < arr.length; i++){
		for(let j = i+1; j < arr.length; j++){
			if(arr[i] === arr[j]){
			  arr.splice(j, 1);
			  j--;
			}
		}
	}
	return arr;
}
let arr = [1,1,2,3,1,5,6,7,8,2,4,7,10];
console.log(unique(arr));  // [ 1, 2, 3, 5, 6, 7, 8, 4, 10 ]

【 新建数组,利用 indexOf 去重 】:

function unique(arr){
	let res = [];
	for(let i = 0; i < arr.length; i++){
		if(!res.includes(arr[i])){
			res.push(arr[i]);
		}
	}
	return res;
}
let arr = [1,1,2,3,1,5,6,7,8,2,4,7,10];
console.log(unique(arr));  // [ 1, 2, 3, 5, 6, 7, 8, 4, 10 ]

7. 描述一下 Promise

Promise 是一个对象,它代表一个异步操作的最终完成或失败。由于它的 then 方法和 catch、finally 方法会返回一个新的 Promise,所以可以允许我们链式调用,解决了传统的回调地狱问题。

再说一下 then 和 catch 方法:

  • Promise 的状态一经改变就不能再修改。
  • .then 和 .catch 都会返回一个新的 Promis。
  • catch 不管被链接到哪里,都能捕获上层未捕捉过得错误。
  • 在 Promise 中,返回一个任意的 非 Promise 的值 都会被包裹成 Promise 对象,例如 return 2 会被包装成 return Promise.resolve(2)。
  • Promsie 的 .then 或者 .catch 可以被调用多次,但如果 Promise 内部的状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch的时候都会直接拿到该值。
  • .then 或者 .catch 中 return 中 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。
  • .then 或 .catch 返回的值不能是 Promise 本身,否则会造成死循环。
  • .then 方法是可以接收两个参数的,第一个是处理成功的参数,第二个是处理失败的参数,再某些时候你可以认为 catch是 .then 第二个参数的简便写法。
  • .then 或 .catch 的期望参数是函数,传入非函数会发生值穿透。

另外再说说 finally 方法:

  • .finally() 方法也是返回一个 promise,他在 Promise 结束的时候,无论结果为 resolved 还是 rejected,都会执行里面的回调函数。
  • .finally() 方法的回调不接受任何的参数,也就是说在 .finally() 函数中是没法 知道 Promise 的最终态是 resolved 还 rejected。
  • 它最终返回的默认是一个 上一次的 Promise 对象值,不过如果抛出的是一个异常,则是返回 异常的 Promise 对象。

最后说说 all 以及 race 方法:

  • Promise.all() 可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值,例如一个页面要分了多个异步请求去加载数据 ,那必须要等数据全部成功返回,页面才会成功显示。
  • Promise.race() 的作用也是接收一组异步任务,然后执行异步任务,只保留第一个执行完成的异步操作结果,其他的方法仍在执行,不管结果如何会被抛弃。比如有几个 异步请求同时去访问一个 图片,最先获取的则直接返回,其他的则无用了。
  • Promise.all().then() 结果中的数组的顺序和 Promise.all() 接收到的数组顺序一致。
  • all 和 race 传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被 then 的第二个参数或者后面的 catch 捕获,但不会影响数组中其他的异步任务的执行。
8. 描述一下 EventLoop 的执行过程
  • 一开始整个脚本作为一个宏任务执行
  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完出对,检查微任务列表,有则依次执行,直到全部执行完
  • 执行浏览器 UI 线程的渲染工作
  • 检查是否有 web worker 任务,有则执行
  • 执行完本轮宏任务,回到步骤2,依此循环,直到宏任务和微任务队列都为空
9. 手写 new
function myNew(fn, ...args){
	let instance = Object.create(fn.prototype);
	let result = fn.call(instance, ...args);
	return typeof result === 'object' ? result :instance;
}
10. typeof 和 instanceof 的区别

typeof 表示对某个变量类型的检测,基本数据类型除了 null 都能显示为对应的类型,引用类型除了函数会显示为 ‘function’,其他都显示为 object。
而 instanceof 它主要是用与检测某个构造函数的原型对象在不在某一个对象的原型链上。

10. 详细介绍下 Instanceof

这里就直接代码实现

 function myInstanceof(left, right){
	let proto = Object.getPrototypeOf(left);
	while(true){
		if(proto === null) return false;
		if(proto === right.prototype) return true;
		proto = Object.getPrototypeOf(proto);
	}
 }
10. 一句话描述一下 this

指向最后调用函数的那个对象,是函数运行时内部自动生成的一个内部对象,只能在函数内部使用。

11. webpack 中的 loader 和 plugin 有神区别

loader: 它是一个转换器,只专注于转换文件这一个领域,完成压缩、打包、语言编译,它仅仅是为了打包,并且运行在打包之前。
**plugin:**是一个扩展器,它丰富了 webpack 本身,为其进行了一些其他功能的扩展,它不局限于打包,资源的加载,还包括其他的功能,所以它是在整个编译周期都起作用。

12. 介绍一下虚拟 DOM

虚拟 DOM 的本质就是一个原生的 Javascript 对象去描述一个 DOM 节点,是对真实 DOM 的一种抽象。
由于在浏览器中的 DOM 是非常昂贵的,频繁的操作 DOM,将会产生一定的性能问题,因为我们需要将这一层抽象,在 patch 过程中尽可能地一次性将差异更新到 DOM 中,这样保证了 DOM 不会出现性能很差的情况。

另外还有重要的一点,也是它设计的初衷,为了更好的跨平台,比如 Node.js 就没有 DOM,如果要实现 SSR (服务端渲染),那么一个方式就是借助 Virtual DOM,因为 Virtual DOM 本身就是 Javascript 对象。

Vue2.x 中虚拟 DOM 主要借鉴了 snabbdom.js, Vue3 中借鉴了 inferno.js 算法进行优化。

13. 浏览器为什么要跨域?如果是因为安全的话,那小程序或是其他的为什么没有跨域?

跨域的产生来源于现代浏览器所通用的同源策略,所谓的同源策略,是指只有在地址的:协议名、域名、端口名都一样的情况下,才允许访问相同的 cookie、localStorage,以及访问页面的 DOM 或是发送 Ajax 请求,如在不同源的情况下访问,就称为跨域。
为什么浏览器会禁止跨域?

  • 首先,跨域只存在于浏览器端,因为我们知道浏览器的形态是很开放的,所以我们需要对它有所限制
  • 其次,同源策略主要是为了保证用户的信息安全,可分为两种:Ajax 同源策略和 DOM 同源策略
  • Ajax 同源策略主要是使不同源的页面不能获取 cookie 且不能发起 Ajax 请求,这样在一定程度上防止了 CSRF 攻击。
  • DOM 同源策略也一样,它限制了不同源页面不能获取 DOM,这样可以防止一些恶意网站在自己的网站上利用 iframe 嵌入正规网站并迷惑用户,以此来达到窃取用户信息。
14. CORS 跨域的原理

跨域资源共享(CORS)是一种机制,是 W3C 标准,它允许浏览器向跨源服务器,发出 XMLHttpRequest 或 Fetch 请求,并且整个 CORS 通信过程都是浏览器自动完成的,不需要用户参与。
而使用这种 跨域资源共享的前提是,浏览器必须支持这个功能,并且服务器端也必须同意这种 “域” 请求,因此实现 CROS 的关键是服务器,通常是需要有以下几个配置

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Credentials
  • Access-Control-Max-Age

过程分析:

  • 浏览器先根据同源策略对前端页面和后台交换地址做匹配,若同源,则直接发送数据请求,若不同源,则发送跨域请求。
  • 服务器收到浏览器跨域请求后,根据自身配置返回对应文件头,若为配置任何跨域,则文件头里不包含 Access-Control-Allow-origin 字段,若配置过域名,则返回 Access-Control-Allow-origin + 对应配置规则里的域名的方式。
  • 浏览器根据接受到的响应头里的 Access-Control-Allow-origin 字段做匹配,若无该字段,说明不允许跨域,从而抛出一个错误,若有该字段,则对字段内容和当前域名做匹配,如果同源,则说明可以跨域,浏览器接受该响应,若不同源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。

在 CORS 中有简单请求非简单请求,简单请求是不会触发 CORS 的预检请求的,而非简单请求则会触发预检。
“需预检的请求” 要求必须首先要使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。“预检请求” 的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

15. CommonJS 和 ES6 模块的区别
  • CommonJS 模块时运行时加载的, ES6 Modules 是编译时输出接口
  • CommonJS 输出是值得拷贝, ES6 Modules 输出的是值得引用,被输出模块的内部的改变会影响引用的改变。
  • CommonJS 导入的模块路径是一个表达式,因为它使用的是 require() 方法,而 ES6 Modules 只能是字符串
  • CommonJS this 指向当前模块,ES6 Modules this 指向 undefined
  • 且 ES6 Modules中没有这些顶层变量: arguments、require、module、exports、__filename、 __dirname

关于第一个差异,是因为 CommonJS 加载的是一个对象,即 module.exports 属性,该对象只有在有脚本运行完才会生成,而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

16. 说一下重绘和回流

回流:
触发条件:当我们对 DOM 结构的修改引发了 DOM 几何尺寸发生变化的时候,就会发生回流的过程。
例如一下几个操作:

  1. 一个 DOM 元素的几何变化,常见的几何属性 width、height、padding、margin、left、top、border 等等
  2. 使 DOM 节点发生 增减 或 移动。
  3. 读写 offset 族,scroll 族 和 client 族属性的时候,浏览器为了获取这些值,需要进行回流操作。
  4. 调用 window.getComputedStyle 方法。

回流过程:由于 DOM 的结构发生了改变,所以需要从生成 DOM 这一步开始,重新经过 样式计算、生成布局树、建立图层树、再到 生成绘制列表 以及之后的显示器显示这一整个渲染过程走一遍,开销是非常大的。

重绘:
触发条件:当 DOM 的修改导致了样式的变化,并且没有影响几何属性的时候,会导致 重绘。
重绘过程:由于没有导致 DOM 几何属性的变化,因此元素的位置信息不需要更新,所以当发生重绘的时候,会跳过 生成布局树 和建立图层树的阶段,直到生成绘制列表,然后继续进行分块、生成位图等后面一系列操作。

如何避免触发回流和重绘:

  1. 避免频繁的使用 style,而是采用修改 class 的方式。
  2. 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上
  3. 也可以先为元素设置 display:none,操作结束后再把它显示出来,因为在 display 属性为 none 的元素上进行 DOM 操作不会引发回流和重绘
  4. 使用 createDocumentFragment 进行批量的 DOM 操作。
  5. 对于 resize、scroll 等进行防抖/节流处理
  6. 避免频繁的读取会引发回流/ 重绘的属性,如果确实要多次使用,就用一个变量缓存起来
  7. 利用 CSS3 的 transform 、opacity、filter 这些属性可以实现合成的效果,也就是 CPU 加速。
17. 实现水平垂直居中的几种方式

Flex布局1(子元素是块级元素)

.box{
	display:flex;
	width:100px;
	width:100px;
	background-color:pink;
}
.box-center{
	margin:auto;
	background-color:green;
}

Flex布局2

.box{
	display:flex;
	width:100px;
	height:100px;
	background-color:pink;
	justify-content:center;
	align-items:center;
}
.box-center{
	background-color:green;
}

绝对定位

.box{
	position:relative;
	height:100px;
	width:100px;
	background-color:pink;
}
.box-center{
	position:absolute;
	left:0;
	right:0;
	bottom:0;
	top:0;
	width:50px;
	height:50px;
	background-color:green;
}
18. em 和 rem 的区别

em:定义字体大小时以父级字体大小为准,定义长度单位时以当前字体的大小为基准,
rem:以根元素的字体大小为基准。

19. setTimeout 执行原理

setTimeout的运行机制,执行该语句时,是立即把当前定时器代码推入事件队列,当定时器在事件列表中满足设置的时间值将传入的函数加入任务队列,之后的执行就交给任务队列负责,但是如果此时任务队列不为空,则需等待,所以执行定时器内代码的时间可能会大于设置的时间。

20. 平时 工作中 ES6+ 主要用到了哪些?
  1. 模块 import 和 export
  2. 箭头函数
  3. 函数的默认参数
  4. … 扩展运算符允许展开数组
  5. 解构赋值
  6. Promise
  7. let const
  8. Porxy、Map、Set
  9. 对象属性同名能简写
  10. includes
  11. async / await
  12. Object.values() 和 Object.entries()
  13. padStart() 和 padEnd()
    能想起多少写多少
    还不了解的小伙伴可以去看一下 阮一峰老师的博客
  • 7
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值