1、如何理解闭包
(1)什么是闭包
- 闭包是一个函数里可以返回另一个函数,
闭包是指有权访问另一个函数作用域中变量的函数
(2)闭包的作用
- 保护函数的私有变量不受外部的干扰。形成不销毁的栈内存。
- 保存,把一些函数内的值保存下来。闭包可以实现方法和属性的私有化
(3)闭包的缺陷
- 当他内部函数的引用对象被他的外部所接收的时候会形成一个不被销毁的作用域,会存放到浏览器的内存当中,容易造成内存泄漏
2、分析 Promise 实现细节
(1) Promise的作用
- promise是一个异步队列的任务(微任务),解决了我们之前前端回调地狱的问题
(2)promise的基本原理
-
Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
-
Promise 会有三种状态
Pending 等待Fulfilled 完成Rejected 失败
-
状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
-
Promise 中使用 resolve 和 reject 两个函数来更改状态;
-
then 方法内部做但事情就是状态判断
- 如果状态是成功,调用成功回调函数
- 如果状态是失败,调用失败回调函数
3、js的事件代理(事件委托)
(1)什么是事件代理
- 事件代理是指通过事件冒泡和事件捕获把子元素的属性方法代理到他的父元素上
(2)举例说明
- 比如一个ul父级标签和一个li子级标签,我们可以通过事件冒泡把li标签的属性和方法冒泡到ul签上,这样当我们在点击ul标签的时候我们能够获取到某个li标签的属性
4、js的事件循环机制
(1)什么是事件循环
- 首先js是一个执行顺序是一个单线程的,然后事件循环机制主要由主线程和任务队列组成,主线程负责同步任务,任务队列异步任务,在事件循环过程中,当主线程的同步任务执行完后,会去检查事件队列中是否存在待处理的事物,如果有主线程会取出事件执行对应的回调函数,整个过程就称为事件循环
5、从输入浏览器的url地址到结束会发生了些什么
- 首先输入url地址后,浏览器不会直接通过url地址直接去访问服务器,而是先会通过DNS转换,获取到当前服务器的IP地址,在通过获取的IP地址去向服务发生http请求,这个时候就会与服务器端建立3次握手与4次挥手的过程,当服务器端收到我们客户端的请求后就会在后台进行处理,然后返回给我们客户端需要的字符串数据,客户端收到服务器端的回调后,就会对页面进行加载,渲染,绘制,在加载的过程中会执行渲染与绘制,首先会对页面的图片资源,静态资源,css样式,同时会执行js的代码,由于js是单线程的,这个时候就会执行同步任务与异步任务,之后开始渲染页面,对页面生成新dom树,通过dom树的节点进行页面的渲染,在到最后对页面进行绘制
6、原型与原型链
(1)什么是原型
- 首先每个函数的实例化上都有着一个属性prototype(原型),默认情况下它是一个普通
Object对象,这个对象是调用该构造函数所创建的实例的原型。所有的对象中(除了null)都会有一个_proto_属性,实例的__proto__属性会指向这个构造函数的原型,如果多个实例的__proto__都指向构造函数的原型,那么实例如果能通过一种方式,访问原型上的方法,属性等,就可以在原型进行编程,实现继承的效果。
(2)什么是原型链
- 实例对象在查找属性时,如果查找不到,就会沿着
__proto__去与对象关联的原型上查找,如果还查找不到,就去找原型的原型,直至查到最顶层,这也就是原型链的概念。原型链的好处是可以让我们对属性与方法实现资源共享,我们可以直接访问该原型链的属性与方法, - 举例说明:比如jquery的
$符就是通过原型链的形式可以实现拿取到这个原型队列里的所有属性与方法,还有$axios,等等
7、http、https、websoket
(1)http
- HTTP 协议是超文本传输协议,基于
TCP/IP协议传输数据,他能够允许传输任意类型的数据对象。传输的类型由Content-Type以标记,客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有 GET、HEAD、POST。缺点是数据完全肉眼可见,能够方便地研究和分析,但也容易被窃听,无法验证通信双方的身份
(2)https
- HTTPS是一种通过计算机网络进行安全通信的传输协议,经由Http进行通信,利用SSL/TLS建立安全信道,加密数据包。HTTPS使用的主要目的是提供对网站服务器的身份认证,同时保护数据的隐私与完整性,防止传输的内容被中间人冒充或者篡改。缺点是HTTPS 协议多次握手,导致页面的加载时间延长近 50%;HTTPS 连接缓存不如 HTTP 高效,会增加数据开销和功耗,SSL 涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大
(2)websoket
- WebSocket实现了客户端与服务器进行通信,WebSocket协议是一种长链接,只需要通过一次请求来初始化链接,然后所有的请求和响应都是通过这个TCP链接进行通讯。
- 实现步骤,首先客户端需要拿到服务器端的WebSocket建立连接的地址,通过这个地址new一个WebSocket对象,在这个对象里我们可以拿到一些方法,比如send,可以通过这个方法向客户端发送指令,当服务器端接收到这个客户端的指令后,会广播到其他成员,客户端可以通过message这个方法去接收服务端他们传过来的信息,error代表失败。这样就实现了一个简单的通讯,
- websoket也有不稳定的情况,比如我们的用户网络不稳定的情况下,就会容易导致websocket断开的情况,出现这种情况我们可以在js上做一个心跳(定时器)每隔多久检测一次websocket的连接状态,当发现连接断开的时候,这个时候我们在进行重新连接,保证连接的稳定性
8、this指向
(1)普遍this指向
- 默认情况下会指向window,
- 严格模式下this为undefined
- 当使用call,apply,bind(ES5新增)绑定的,this指向绑定对象
(2)call,apply,bind
- call 方法第一个参数是this的指向,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,默认指向window。如:getColor.call(obj, ‘yellow’, ‘blue’, ‘red’)
- apply 方法接受两个参数,第一个参数是this的指向,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。如:getColor.apply(obj, [‘yellow’, ‘blue’, ‘red’])
- bind 方法和 call 方法很相似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。低版本浏览器没有该方法,需要自己手动实现
(3) ES6箭头函数中this
- 箭头函数里取消了this,他会直接指向这个函数的上下文对象,没有的话会指向window,不需要去通过call,apply,bind去绑定他的对象,可以直接去绑定
8、作用域与作用链
(1)作用域
- 全局作用域:全局作用域中定义的变量可以被任何地方访问。一般有如下几种属于全局作用域
- 1、定义的函数和最外层函数之外定义的变量拥有全局作用域。2、未经声明直接赋值的变量。3、
window上的属性都具有全局作用域 - 局部作用域(函数作用域):函数作用域里的变量和函数只能由函数内部访问,作用域是分层的,内部函数可以访问外部函数作用域里的变量
- 块级作用域:
ES6新增了let和const两种声明变量的命令,所声明的变量只能在指定块的作用域内被访问。
(2)作用域链
JavaScript函数执行时,首先会在自身的AO中查找对应属性值,若查找不到,则会去父级的AO上找,在查找不到则再去父级的父级查找,直到找到根window对象,这样形成的“AO链”就是作用域链。
9、深拷贝与浅拷贝
(1)浅拷贝
- 浅拷贝是对对象地址的复制,并没有开辟新的栈,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变。如object.assign/...语法糖/push/数组语法等等
(2)深拷贝
- 深拷贝则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
如何实现深拷贝
唯一的作法就是克隆这个对象。
对于简单的JSON对象,最简单的方法是
var objectIsNew = JSON.parse(JSON.stringify(objectIsOld));
//如果使用jQuery,可以使用:
// 浅拷贝
var objectIsNew = jQuery.extend({}, objectIsOld);
// 深拷贝
var objectIsNew = jQuery.extend(true, {}, objectIsOld);
纯JS方法来深拷贝对象(并非最佳方法)
function keepCloning(objectpassed) {
if (objectpassed=== null || typeof objectpassed!== 'object') {
return objectpassed;
}
// 临时存储原始的obj的构造
var temporary_storage = objectpassed.constructor();
for (var key in objectpassed) {
temporary_storage[key] = keepCloning(objectpassed[key]);
}
return temporary_storage;
}
var employeeDetailsOriginal = {
name: '前端智',
age: 18,
Profession: '前端开发'
};
var employeeDetailsDuplicate = (keepCloning(employeeDetailsOriginal));
employeeDetailsOriginal.name = "前端";
console.log(employeeDetailsOriginal);
console.log(employeeDetailsDuplicate);
10、防抖与节流
(1)防抖
- 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
- 个人理解 函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条。
(2)节流
- 定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
- 个人理解 函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹。
结合应用场景
- 防抖
-
search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
-
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
-
- 节流
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
11、前端跨域问题
(1)为什么会出现跨域
- 浏览器有一个重要的安全策略,称之为「同源策略」,若页面的源和页面运行过程中加载的源不一致时,出于安全考虑,浏览器会对跨域的资源访问进行一些限制
(2)解决跨域的方法
- nginx的反向代理
- JSONP
- CORS(跨域资源共享):他允许浏览器向跨源服务器发送
XMLHttpRequest请求,从而克服啦 AJAX 只能同源使用的限制,axios配置、设置请求头 - vue项目与reatc项目:可以通过proxy进行域名代理
12、垃圾回收机制
js的垃圾回收是由浏览器引擎来做的,不同浏览器的垃圾回收机制在细节上略有不同,但回收算法大体上是通用的。V8引擎将堆内存分为新生代和老生代两个区域
(1)标记清除
- 区分活动对象和非活动对象,并对活动对象进行标记。(活动对象就是指还在使用的对象,非活动对象就是需要清理的对象)
- 标记完成之后将使用区的活动对象复制进空闲区,并进行内存整理排序,以避免产生内存碎片。
- 清理使用区的非活动对象,释放垃圾内存。
- 把使用区和活动区进行互换,以达到内存清理和整理的目的,当新的使用区即将被占满时会执行一次新的内存清理。
(2)标记整理
- 标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据(这一点跟新生代的标记是一致的)
- 清理阶段就是清理掉垃圾数据
- 由于标记清除对象后,内存会产生内存碎片,会导致大对象无法分配内存,从而造成内存不足。所以标记整理算法此时就排上了用场,标记整理算法会将活动对象向一端移动排序,从而避免产生内存碎片。
(3)引用计数算法
- 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1
- 如果同一个值又被赋给另一个变量,那么引用数加 1
- 如果该变量的值被其他的值覆盖了,则引用次数减 1
- 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存
13、前端性能优化
主要细节就不一一介绍了
- CDN引入
- 使用懒加载
- 避免重绘与回流,优化动画
- 图片优化:图片压缩、图片格式
- webpack优化
14、重绘与回流
(1)浏览器渲染机制
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上
(2)回流
- 布局引擎会根据各种样式计算每个盒子在页面上的大小与位置,如以下情况
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
- 页面一开始渲染的时候(这避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
(3)重绘
- 当计算好盒模型的位置、大小及其他属性后,浏览器根据每个盒子特性进行绘制,如以下情况
- 触发回流
- 颜色的修改
- 文本方向的修改
- 阴影的修改
(4)如何避免重绘与回流
- 操作DOM时,尽量在低层级的DOM节点进行操作
- 不要使用
table布局, 一个小的改动可能会使整个table进行重新布局 - 使用CSS的表达式
- 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
- 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
- 避免频繁操作DOM,可以创建一个文档片段
documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中 - 将元素先设置
display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
- 浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。
15、虚拟DOM
(1)什么是虚拟DOM
JavaScript按照DOM的结构来创建的虚拟树型结构对象
(2)为什么要使用虚拟DOM
-
当然是前端优化方面,避免频繁操作
DOM,频繁操作DOM会可能让浏览器回流和重绘,性能也会非常低,还有就是手动操作DOM还是比较麻烦的,要考虑浏览器兼容性问题,当前jQuery等库简化了DOM操作,但是项目复杂了,DOM操作还是会变得复杂,数据操作也变得复杂 -
并不是所有情况使用虚拟
DOM都提高性能,是针对在复杂的的项目使用。如果简单的操作,使用虚拟DOM,要创建虚拟DOM对象等等一系列操作,还不如普通的DOM操作 -
虚拟
DOM可以实现跨平台渲染,服务器渲染 、小程序、原生应用都使用了虚拟DOM -
使用虚拟
DOM改变了当前的状态不需要立即的去更新DOM而且更新的内容进行更新,对于没有改变的内容不做任何操作,通过前后两次差异进行比较 -
虚拟 DOM 可以维护程序的状态,跟踪上一次的状态
总结
可能写的有很多不对的地方,还希望各位大佬多指导指导,整理一些常用的js的面试题,希望能帮到各位。