本片文章如题主要讲解浏览器相关面试题,每道面试题都是从网上找的参考资料整理而来,侵删。部分未添加原文链接(如果想要了解面试题的更多信息,只需要点击每道面试后面的
参考链接
即可);
这一系列纯粹是为了个人面试整理的,为了圆一下二流学生的大厂梦!
双击标题可快速上下切换内容呦!
本文内容持续更新中,如果您认为不错,还请点赞收藏下哦!
目录
众所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但这并未改变 JavaScript 是单线程这一核心。
为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,用户引擎必须使用事件循环
。事件循环包含两类:一类是基于浏览器上下文
,一种是基于Worker
,二者是独立运行的。
下面本文用一个例子,着重讲解下基于 Browsing Context 的事件循环机制。
来看下面这段 JavaScript 代码:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// 结果如下:
// script start
// script end
// promise1
// promise2
// setTimeout
浏览器上下文中所有的任务可以分为同步任务和异步任务。
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 Event Queue 。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
在事件循环中,每进行一次循环操作称为tick
,通过阅读规范可知,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:
可以用一张图来说明下流程:
这里相信有人会想问,什么是 microtasks ?
规范中规定,task分为两大类, 分别是 Macro Task (宏任务)和 Micro Task(微任务), 并且每个宏任务结束后,都要清空所有的微任务,这里的 Macro Task也是我们常说的 task ,有些文章并没有对其做区分,后面文章中所提及的task皆看做宏任务( macro task)。
macrotask 主要包含:script(整体代码)
、setTimeout
、setInterval
、I/O
、UI交互事件
、setImmediate(Node.js 环境)
microtask主要包含:Promise
、MutaionObserver
、process.nextTick(Node.js 环境)
setTimeout/Promise 等API便是任务源,而进入任务队列的是由他们指定的具体执行任务。来自不同任务源的任务会进入到不同的任务队列。其中 setTimeout 与 setInterval 是同源的。
参考链接
是一个受到保护的变量空间,由内嵌函数生成。JavaScript具有函数级的作用域。这意味着定义在函数内部的变量在函数外部不能被访问。JavaScript的作用域又是词法性质的。这意味着函数运行在定义它的作用域中,而不是调用它的作用域中。把这两个因素结合起来,就能通过把变量包裹在匿名函数中而对其加以保护。
各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。简单的闭包实现:
function foo(a) {
return function(b) {
return a+b;
}
}
foo(1)(2) // 3
由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成定义在一个函数内部的函数
。
所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
闭包有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅因为它常驻内存,更重要的是,对闭包的使用不当会造成无效内存的产生。
在JS中,这三者都是用来改变函数的this对象的指向的,他们有什么样的区别呢。
在说区别之前还是先总结一下三者的相似之处:
1、都是用来改变函数this对象的指向的。
2、第一个参数都是this要指向的对象。
3、都可以利用后续参数传参
call和apply都是对函数的直接调用,而bind方法返回的仍然是一个函数,因此后面还需要()
来进行调用才可以。
call后面的参数直接放入,而apply的第二个参数是一个数组,这就是两者最大的区别。
1、javascript原型
JS中每个函数都存在有一个原型对象属性prototype。并且所有函数的默认原型都是Object的实例。
2、javascript原型链
每个继承父函数的子函数的对象都包含一个内部属性_proto_。该属性包含一个指针,指向父函数的prototype。若父函数的原型对象的_proto_属性为再上一层函数。在此过程中就形成了原型链。
3、特点
1,原型链继承 (有两种实现方式)
(1)Son.prototype = Father.prototype
弊端:Son.prototype.constructor 指向Father,需要手动更改为Son ;Son的实例化对象只能继承Father原型中的方法,无法继承Father本身的属性。
(2)Son.prototype = new Father()
弊端:Son.prototype.constructor 指向Father,需要手动更改为Son;Son的实例化对象共享Father自身的引用类型属性。
2,借助构造函数继承
弊端:Son只能继承Father自身的属性,而无法继承Father原型中的方法。
3,组合式继承
将原型链继承与构造函数结合起来
弊端:通过Father.call() 和 new Father() ,父类构造函数Father被调用了两次。
4,原型式继承
newObj继承了obj的属性和方法,但是同样出现了共享父类中引用类型属性的问题。
5,经典继承(Es5中的新语法:存在兼容性问题,需要做浏览器的能力检测)
6,寄生式继承(类似于原型式继承)
newObj继承了obj的属性和方法,但是同样出现了共享父类中引用类型属性的问题。
7,寄生组合式继承(组合继承+寄生继承)
优点:可以多重继承 解决两次调用 解决实例共享引用类型的问题 原型链保持不变
Math对象:Math.abs(x);//用来返回数的绝对值
Date对象:var date = new Date();// 通过new的方式创建一个日期对象;
Array对象:var arr=new Array();
字符串对象:var str=new String();
join(separator)
: 将数组的元素组起一个字符串,以 separator 为分隔符,省略的话则用默认用逗号为分隔符,该方法只接收一个参数:即分隔符。push()
: 可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。pop()
:数组末尾移除最后一项,减少数组的length
值,然后返回移除的项。shift()
:删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined
。unshift()
:将参数添加到原数组开头,并返回数组的长度sort()
:按升序排列数组项——即最小的值位于最前面,最大的值排在最后面reverse()
:反转数组项的顺序。let arr = [3,2,24,1,53,23,32]
// output: undefined
arr.sort((a, b) => {
return a - b
})
// output: [1, 2, 3, 23, 24, 32, 53]
concat()
:将参数添加到原数组中。这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat()方法传递参数的情况下,它只是复制当前数组并返回副本。slice()
:返回从原数组中指定开始下标到结束下标之间的项组成的新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下, slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。splice()
:很强大的数组方法,它有很多种用法,可以实现删除、插入和替换。ES5新增
indexOf()
:接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, 从数组的开头(位置 0)开始向后查找。lastIndexOf()
:接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, 从数组的末尾开始向前查找。这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1
。在比较第一个参数与数组中的每一项时,会使用全等操作符。
forEach()
:对数组进行遍历循环,对数组中的每一项运行给定函数。这个方法没有返回值。参数都是function类型,默认有传参,参数分别为:遍历的数组内容;对应的数组索引,数组本身。map()
:指映射
,对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。filter()
:“过滤”功能,数组中的每一项运行给定函数,返回满足过滤条件组成的数组。every()
:判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true。some()
:判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。reduce()
:接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。let arr = [1,2,3,4,5]
let result = arr.reduce((total, curVal) => {
return total + curVal
})
// result = 15
push
、pop
、shuift
、unshift
、sort
、reverse
、splice
会修改原数组,其他方法会复制一个新数组JS数据类型目前有八种,其中
String
、Number
、Boolean
、null
、undefined
object
,其类型分为function、Array、Date等Symbol
bigint