中高级前端高频面试题分享(二)

简介: 中高级前端高频面试题分享

事件循环


从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue

简要介绍:谈谈promise.resove,setTimeout,setImmediate,process.nextTick在EvenLoop队列中的执行顺序

1.问题的引出 event loop都不陌生,是指主线程从“任务队列”中循环读取任务,比如

例1:

setTimeout(function(){console.log(1)},0);
console.log(2)
//输出2,1

在上述的例子中,我们明白首先执行主线程中的同步任务,当主线程任务执行完毕后,再从event loop中读取任务,因此先输出2,再输出1。

event loop读取任务的先后顺序,取决于任务队列(Job queue)中对于不同任务读取规则的限定。比如下面一个例子:

例2:

setTimeout(function () {
  console.log(3);
}, 0);
Promise.resolve().then(function () {
  console.log(2);
});
console.log(1);
//输出为  1  2 3

先输出1,没有问题,因为是同步任务在主线程中优先执行,这里的问题是setTimeout和Promise.then任务的执行优先级是如何定义的。

2 . Job queue中的执行顺序 在Job queue中的队列分为两种类型:macro-task和microTask。我们举例来看执行顺序的规定,我们设

macro-task队列包含任务: a1, a2 , a3 micro-task队列包含任务: b1, b2 , b3

执行顺序为,首先执行marco-task队列开头的任务,也就是 a1 任务,执行完毕后,在执行micro-task队列里的所有任务,也就是依次执行b1, b2 , b3,执行完后清空micro-task中的任务,接着执行marco-task中的第二个任务,依次循环。

了解完了macro-task和micro-task两种队列的执行顺序之后,我们接着来看,真实场景下这两种类型的队列里真正包含的任务(我们以node V8引擎为例),在node V8中,这两种类型的真实任务顺序如下所示:

macro-task(宏任务)队列真实包含任务: script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering

micro-task(微任务)队列真实包含任务: process.nextTick, Promises, Object.observe, MutationObserver

由此我们得到的执行顺序应该为:

script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering

在ES6中macro-task队列又称为ScriptJobs,而micro-task又称PromiseJobs

3 . 真实环境中执行顺序的举例

(1) setTimeout和promise

例3:

setTimeout(function () {
  console.log(3);
}, 0);
Promise.resolve().then(function () {
  console.log(2);
});
console.log(1);

我们先以第1小节的例子为例,这里遵循的顺序为:

script(主程序代码)——>promise——>setTimeout 对应的输出依次为:1 ——>2————>3 (2) process.nextTick和promise、setTimeout

例子4:

setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){
   console.log(2);
   resolve();
}).then(function(){console.log(3)
}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
//输出2,6,5,3,4,1

这个例子就比较复杂了,这里要注意的一点在定义promise的时候,promise构造部分是同步执行的,这样问题就迎刃而解了。

首先分析Job queue的执行顺序:

script(主程序代码)——>process.nextTick——>promise——>setTimeout

I) 主体部分: 定义promise的构造部分是同步的, 因此先输出2 ,主体部分再输出6(同步情况下,就是严格按照定义的先后顺序)

II)process.nextTick: 输出5

III)promise: 这里的promise部分,严格的说其实是promise.then部分,输出的是3,4

IV) setTimeout : 最后输出1

综合的执行顺序就是: 2——>6——>5——>3——>4——>1

(3)更复杂的例子

setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){
   console.log(2);
   setTimeout(function(){resolve()},0)
}).then(function(){console.log(3)
}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
//输出的是  2 6 5 1 3 4

这种情况跟我们(2)中的例子,区别在于promise的构造中,没有同步的resolve,因此promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,因此3,4最后输出。


typeof和instanceof


ECMAScript是松散类型的,一次需要一种手段来检测给定变量的数据类型,typeof操作符(注意不是函数哈!)就是负责提供这方面信息的

typeof 可以用于检测基本数据类型和引用数据类型。

语法格式如下:

typeof variable

返回6种String类型的结果:

  • "undefined" - 如果这个值未定义
  • "boolean" - 如果这个值是布尔值
  • "string" - 如果这个值是字符串
  • "number" - 如果这个值是数值
  • "object" - 如果这个值是对象或null
  • "function" - 如果这个值是函数 示例:
console.log(typeof 'hello'); // "string"
console.log(typeof null); // "object"
console.log(typeof (new Object())); // "object"
console.log(typeof(function(){})); // "function"

typeof主要用于检测基本数据类型:数值、字符串、布尔值、undefined, 因为typeof用于检测引用类型值时,对于任何Object对象实例(包括null),typeof都返回"object"值,没办法区分是那种对象,对实际编码用处不大。

instanceof 用于判断一个变量是否某个对象的实例

在检测基本数据类型时typeof是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大,通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。此时我们可以使用ECMAScript提供的instanceof操作符。

语法格式如下:

1. result = variable instanceof constructor

返回布尔类型值:

  • true - 如果变量(variable)是给定引用类型的实例,那么instanceof操作符会返回true
  • false - 如果变量(variable)不是给定引用类型的实例,那么instanceof操作符会返回false 示例:
function Person(){}
function Animal(){}
var person1 = new Person();
var animal1 = new Animal();
console.log(person1 instanceof Person); // true
console.log(animal1 instanceof Person); // false
console.log(animal1 instanceof Object); // true
console.log(1 instanceof Person);   //false
var oStr =  new String("hello world");
console.log(typeof(oStr));      // object
console.log(oStr instanceof String);
console.log(oStr instanceof Object);
// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo);
// instanceof 在继承中关系中的用法
console.log('instanceof 在继承中关系中的用法');
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();
var fo = new Foo();
console.log(fo instanceof Foo);
console.log(fo instanceof Aoo)

根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符会始终返回true。如果使用instanceof 操作符检测基本类型值时,该操作符会始终返回false,因为基本类型不是对象。

console.log(Object.prototype.toString.call(null));
// [object Null]
undefined
console.log(Object.prototype.toString.call([1,2,3]));
//[object Array]
undefined
console.log(Object.prototype.toString.call({}));
// [object Object]


常见的继承的几种方法


原型链继承

定义 利用原型让一个引用类型继承另外一个引用类型的属性和方法 代码

function SuperType(){
    this.property = 'true';
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}
function SubType(){
    this.subProperty = 'false';
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
    return this.subProperty;
}
var instance = new SubType();
alert(instance.getSuperValue());//true
  • 优点 简单明了,容易实现,在父类新增原型属性和方法,子类都能访问到。
  • 缺点 包含引用类型值的函数,所有的实例都指向同一个引用地址,修改一个,其他都会改变。不能像超类型的构造函数传递参数

构造函数继承 定义 在子类型构造函数的内部调用超类型的构造函数

代码

function SuperType(){
    this.colors = ['red','yellow'];
}
function SubType(){
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('black');
var instance2 = new SubType();
instance2.colors.push('white');
alert(instance1.colors);//'red','yellow','black'
alert(instance2.colors);//'red','yellow','white'
  • 优点 简单明了,直接继承了超类型构造函数的属性和方法
  • 缺点 方法都在构造函数中定义,因此函数复用就无从谈起了,而且超类型中的原型的属性和方法,对子类型也是不可见的,结果所有的类型只能使用构造函数模式。

组合继承

定义 使用原型链实现多原型属性和方法的继承,使用构造函数实现实例的继承

代码

function SuperType(name){
    this.name = name;
    this.colors = ['red','black'];
}
SuperType.prototype.sayName = function()
{
   alert(this.name); 
}
function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
SubType.protptype = new SuperType();
SubType.protptype.sayAge = function(){
    alert(this.age);
}
  • 优点 解决了构造函数和原型继承中的两个问题
  • 缺点 无论什么时候,都会调用两次超类型的构造函数

目录
相关文章
|
4月前
|
缓存 前端开发 中间件
[go 面试] 前端请求到后端API的中间件流程解析
[go 面试] 前端请求到后端API的中间件流程解析
|
1月前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
66 1
|
3月前
|
Web App开发 前端开发 Linux
「offer来了」浅谈前端面试中开发环境常考知识点
该文章归纳了前端开发环境中常见的面试知识点,特别是围绕Git的使用进行了详细介绍,包括Git的基本概念、常用命令以及在团队协作中的最佳实践,同时还涉及了Chrome调试工具和Linux命令行的基础操作。
「offer来了」浅谈前端面试中开发环境常考知识点
|
4月前
|
存储 XML 移动开发
前端大厂面试真题
前端大厂面试真题
|
2月前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
|
4月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
【8月更文挑战第18天】
58 2
|
4月前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
45 0
|
4月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
44 个 React 前端面试问题
|
4月前
|
存储 JavaScript 前端开发
|
4月前
|
Web App开发 存储 缓存