我是靠谱客的博主 淡定指甲油,这篇文章主要介绍前端常见题汇总,现在分享给大家,希望可以做个参考。

Promise

十个面试官面试九个都会问这个promise
执行完getCoachList()后再执行setTableData()方法
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// getCoachList()方法 getCoachList() { return new Promise((resolve, reject) => { let occupationType = '' if(this.occupationType != 'all'){ occupationType += `?occupationType=${this.occupationType}` } this.$axios.get(`${api_host}/lego/manage/coach/arrangement/overview${occupationType}`).then(res => { let data = res.data.data this.arrangementStatuses = data.arrangementStatuses this.manageDayArrangements = data.manageDayArrangements this.weekStartDate = moment(data.weekStartDate) this.arrangementStatuses.forEach(() => { this.isHighlighted.push(false) }) resolve(resolve) }).catch(err => { reject(err) }) }) } // setTableData()方法 setTableData() { let arr = [] this.timetable.forEach((item, index) => { let dayArr = this.manageDayArrangements.filter(day => day.dayDuration == item) let struct = { time: item, '0': '', '1': '', '2': '', '3': '', '4': '', '5': '', '6': '' } if(dayArr.length) { dayArr.forEach(item => { struct[item.weekDay - 1] = item.concatCoachNames }) } arr.push(struct) }) this.tableData = arr }, // 可以确保拿到getCoachList()方法执行的结果后再执行setTableData()方法 // 使用.then()方法的前提是getCoachList()需返回Promise对象 changeCoachList() { this.getCoachList().then(() => { this.setTableData() }) // 下面的方法并不能保证getCoachList()执行完后才能执行setTableData() changeCoachList() { let promise = new Promise((resolve, reject) => { this.getCoachList() }).then(res=>{ this.setTableData() }) }

Promise.all
Promise.all 接收一个 promise对象的数组作为参数,当这个数组里的所有 promise 对象全部变为 resolve 或 reject 状态的时候,它才会去调用 then 方法;
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
简单的说在同一个函数里面要同时调用两个或者多个不同的接口,接口的返回数据做为下一步操作的依据

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const promise = ['/api/type','/api/info'].map(function(item){ return httpAjax('post',item) }) Promise.all(promise).then(values =>{ console.log(values) }) //例如 created() { let timePro = this.getTimeTable() let coachPro = this.getCoachList() Promise.all([timePro, coachPro]).then(res => { this.setColumns() this.setTableData() }) }

Promise.race的使用
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') },1000) }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('failed') }, 500) }) Promise.race([p1, p2]).then((result) => { console.log(result) }).catch((error) => { console.log(error) // 打开的是 'failed' })

事件执行机制

执行一个宏任务;
遇到微任务,放到微任务列队;
宏任务执行完毕,执行微任务列队中的任务;
微任务执行完毕后,GUI 线程接管,开始渲染页面;
渲染完成后,JS线程继续接管,开启下一个宏任务。

setTimeout和Promise执行顺序

题目一
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
setTimeout(function() { console.log(1) }, 0); new Promise(function(a, b) { console.log(2); for(var i = 0; i < 10; i++) { i == 9 && a(); } console.log(3); }).then(function() { console.log(4) }); console.log(5) //最终打印结果是 2 3 5 4 1

原因:而创建Promise实例( executor )是立即执行任务,相当于任务同步执行的所以先打印2和3 ,Promise.then 是异步执行的,相当于微任务,要放在微任务列表中执行,所以先执行5之后再执行微任务4,setTimeout 函数是一个宏任务,需要等到 script 整体的宏任务执行完成之后再执行所以是最后一个打印的1

题目二
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
console.log(1) setTimeout(function(){ console.log(2); let promise = new Promise(function(resolve, reject) { console.log(7); resolve() }).then(function(){ console.log(8) }); },1000); setTimeout(function(){ console.log(10); let promise = new Promise(function(resolve, reject) { console.log(11); resolve() }).then(function(){ console.log(12) }); },0); let promise = new Promise(function(resolve, reject) { console.log(3); resolve() }).then(function(){ console.log(4) }).then(function(){ console.log(9) }); console.log(5) //最终打印结果是: 1 3 5 4 9 10 11 12 2 7 8 //跟题目一是同一个原理

经典试题

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function Foo(){ getName = function(){ console.log(1); }; return this; } Foo.getName = function(){ console.log(2); } Foo.prototype.getName = function(){ console.log(3); } var getName = function(){ console.log(4); } function getName(){ console.log(5); } //请写出以下输出结果: //函数Foo的静态方法 Foo.getName(); //2 //function getName有提前声明的规则,声明后被var getName= 。。覆盖,则getName为4 getName(); //4 //Foo()的return this为window,window.getName 在Foo里面被覆盖,则输出1 Foo().getName(); //1 //同上,因调用了Foo();window的getName被覆盖 getName(); //1 //依然只是调用了Foo对象上的getName,又因为Foo.getNname,所以相当于 /** * function a(){console.log(2)}; * new a(); * **/ new Foo.getName(); //2 //先执行了new Foo();返回一个对象,这个对象的getName为prototype上的getName,相当于(new Foo()).getName(); new Foo().getName(); //3 new new Foo().getName() //3

null和undefined的区别

Null:

null是js中的关键字,表示空值,null可以看作是object的一个特殊的值,如果一个object值为空,表示这个对象不是有效对象。

Undefined:

undefined不是js中的关键字,其是一个全局变量,是Global的一个属性,以下情况会返回undefined:

1)使用了一个未定义的变量;var i;

2)使用了已定义但未声明的变量;

3)使用了一个对象属性,但该属性不存在或者未赋值;

4)调用函数时,该提供的参数没有提供:
function func(a){
console.log(a);
}
func();//undefined
5)函数没有返回值时,默认返回undefined
var aa=func();
aa;//undefined

相同点:
都是原始类型的值,保存在栈中变量本地
1.类型不一样:

复制代码
1
2
3
console.log(typeOf undefined);//undefined console.log(typeOf null);//object

2.转化为值时不一样:undefined为NaN ,null为0

复制代码
1
2
3
4
5
console.log(Number(undefined));//NaN console.log(Number(10+undefined));//NaN console.log(Number(null));//0 console.log(Number(10+null));//10
复制代码
1
2
3
undefined===null;//false undefined==null;//true
== 和 === 有什么区别?

对于==来说,如果对方双方类型不一样的话,就会进行类型转换
1.首先会判断两者类型是否相同。相同的话就是比大小了
2.类型不相同的话,那么就会进行类型转换
3.会先判断是否在对比null和undefined,是的话就会返回true
4.判断两者类型是否为string和number,是的话就会将字符串转换为number

var, let, const用法及区别

var

复制代码
1
2
3
4
5
6
作用域:函数(functionfunction test(){ var x = 10; } console.log(x); // 错误信息:ReferenceError: x is not defined

let
作用域:区块(block) 定义变量

复制代码
1
2
3
4
5
if (true) { let x = 10; } console.log(x); // 错误信息:ReferenceError: x is not defined

const
作用域:区块(block) 定义常量
重复声明、重新赋值一个常量都会报错

复制代码
1
2
3
const x = 10; x = 20; // 错误信息:TypeError: Assignment to constant variable.
复制代码
1
2
3
4
5
6
7
let x = 1; // 作用域:区块外(全域) if (true) { let x = 2; // 作用域:区块内 console.log(x); // 显示 2 } console.log(x); // 显示 1

var的bug

复制代码
1
2
3
4
5
6
for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i) }, 1000); } //输出 3 3 3

分析:循环本身及三次 timeout 回调均共享唯一的变量 i,当循环结束执行时,i 的值为3,所以当第一个 timeout 执行时,调用的 i 也为 3,如果用let定义循环,多次循环保持了一个闭包,那么每个闭包将捕捉一个循环变量的不同值作为副本,而不是所有闭包都捕捉循环变量的同一个值。所以示例中,可以通过将var替换为let修复bug

复制代码
1
2
3
4
5
6
for (let i = 0; i < 3; i++) { setTimeout(function () { console.log(i) }, 1000); } //输出 0 1 2

分别在什么情况下使用

  1. const 适用于赋值后不会再做修改的情况
  2. ** let 适用于赋值后还会修改的情况,它标志着这个变量只能被用在所定义的块作用域**
  3. var 定义全局变量或函数级变量时可使用,但存在BUG尽量避免使用

总结

  1. 通过var定义的变量会提升,而let和const进行的声明不会提升
  2. let声明的变量作用域是外层块,而不是整个外层函数
  3. let,const在未声明之前是不能使用的,var可以使用
  4. 优先选择顺序const>let>var
复制代码
1
2
3
4
5
6
7
8
i; var i=9; console.log(i); // 9 i; let i=9; console.log(i); // "ReferenceError: i is not defined

箭头函数与普通函数的区别

箭头函数是匿名函数,不能作为构造函数,不能使用new

复制代码
1
2
3
4
5
let FunConstructor = () => { console.log('lll'); } let fc = new FunConstructor(); //VM84:4 Uncaught TypeError: FunConstructor is not a constructor

箭头函数不绑定arguments,取而代之用rest参数…解决

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
function A(a){ console.log(arguments); } A(1,2,3,4,5,8); // [1, 2, 3, 4, 5, 8, callee: ƒ, Symbol(Symbol.iterator): ƒ] let B = (b)=>{ console.log(arguments); } B(2,92,32,32); // Uncaught ReferenceError: arguments is not defined let C = (...c) => { console.log(c); } C(3,82,32,11323); // [3, 82, 32, 11323]

箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = { a: 10, b: () => { console.log(this.a); // undefined console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} }, c: function() { console.log(this.a); // 10 console.log(this); // {a: 10, b: ƒ, c: ƒ} } } obj.b(); obj.c();
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = { a: 10, b: function(){ console.log(this.a); //10 }, c: function() { return ()=>{ console.log(this.a); //10 } } } obj.b(); obj.c()();

箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let obj2 = { a: 10, b: function(n) { let f = (n) => n + this.a; return f(n); }, c: function(n) { let f = (n) => n + this.a; let m = { a: 20 }; return f.call(m,n); } }; console.log(obj2.b(1)); // 11 console.log(obj2.c(1)); // 11

箭头函数没有原型属性

复制代码
1
2
3
4
5
6
7
8
9
10
11
var a = ()=>{ return 1; } function b(){ return 2; } console.log(a.prototype); // undefined console.log(b.prototype); // {constructor: ƒ}

箭头函数不能当做Generator函数,不能使用yield关键字

js代码性能优化的几个方法

避免全局查找

使用全局变量和函数肯定要比局部的开销更大,因为要涉及作用域链上的查找,请看以下函数

复制代码
1
2
3
4
5
6
7
function demo1() { var imgs = document.getElementByTagName("img"); //获取页面所有img标签 for(var i = 0; i <= imgs.length; i++) { imgs[i].title = document.title + "image" + i; } }

上面的代码每执行一次for循环都会在全局寻找document,一旦循环次数很多,那么就严重影响了性能,我们可以在for循环开始之前就把document的引用保存在一个局部变量。改进代码如下:

复制代码
1
2
3
4
5
6
7
8
function demo1() { var imgs = document.getElementByTagName("img"); //获取页面所有img标签 var doc = document; //局部引用全局变量document for(var i = 0; i <= imgs.length; i++) { imgs[i].title = doc.title + "image" + i; } }
优化循环
复制代码
1
2
3
4
for(var i = 0; i <=imgs.length; i++) { //执行代码 }

上面代码每次执行循环都会重新计算imgs的长度,一旦循环次数很多,那么积少成多就会影响到代码性能,我们只需在for循环执行之前把imgs的长度保存在一个变量中即可,这样就不用每次都是计算imgs的长度,改进代码如下:

复制代码
1
2
3
4
5
var length = imgs.length; //把imgs的长度保存在一个变量中 for(var i = 0; i <=length; i++) { //执行代码 }
尽量使用原生方法
复制代码
1
2
只要有可能,使用原生方法而不是自己用javascript重写一个。原生方法是用诸如c/c++之类的编译型语言写出来的,所以要比JavaScript的快很多很多。
使用switch替代if-else
复制代码
1
2
如果有一系列复杂的if-else语句,可以转化成单个switch语句则可以得到更快的代码。还可以通过case语句按照最可能的到最不可能的顺序进行组织,来进行进一步优化switch语句。
多个变量申明

javaScript代码中的语句数量也影响所执行的操作的速度,完成多个操作的单个语句要比完成单个操作的多个语句快

复制代码
1
2
3
4
5
6
7
8
9
//四个语句申明变量,浪费! var name = "Bill"; var age = 10; var sex = "man"; //一个语句申明变量,干的漂亮! var name = "Bill", age = 10, sex = "man";
插入迭代值,

当使用迭代值(也就是在不同的位置进行增加或减少的值)的时候,尽可能合并语句

复制代码
1
2
3
4
5
6
7
//两个语句,浪费! var age = values[i]; i++; //一个语句,干的漂亮! var age = values[i++];
使用数组和对象字面量,

你可能看过两种创建数组和对象的方法:使用构造函数或是使用字面量,使用构造函数总是要用到很多语句来插入元素或定义属性,而字面量可以将这些操作在一个语句中完成。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//4个语句创建和初始化数组,浪费! var values = new Array(); values[0] = 123; values[1] = 456; values[2] = 789; //4个语句创建和初始化对象,浪费! var person = new Object(); person.name = "Bill"; person.age = 10; person.sayName = function () { console.log(this.name); } //1个语句创建和初始化数组,干得漂亮! var values = [123, 456, 789]; //一个语句创建和初始化对象,干的漂亮! var person = { name : "bill", age : 10, sayName : function () { console.log(this.name) } };
使用文档碎片减少DOM交互次数

DOM交互越多,性能越慢。

复制代码
1
2
3
4
5
6
7
8
9
10
var list = document.getElementById("myList"), item, i; for (i = 0; i <= 10; i++) { item.document.createElement("li"); list.appendChild(item); item.appendChild(document.createTextNode(" Item" + i)); }

上面代码每执行一次for循环都会向DOM插入新的元素,一旦for循环次数很多,那么将严重影响代码性能,所以解决办法就是减少DOM交互,于是我们使用createDocumentFragment方法创建虚拟节点,把要插入DOM的元素先插入该虚拟节点,循环完之后再把虚拟节点插入DOM,虚拟节点是不会渲染出来的,只会渲染它的子节点。改进代码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
var list = document.getElementById("myList"); fragment = document.createDocumentFragment(), i; for (i = 0; i < 10; i++) { item = document.createElement("li"); fragment.appendChild(item); item.appendChild(document.createTextNode("Item" + i)); } list.appendChild(fragment);
使用innerHTML。

有两种在页面上创建DOM节点的方法:诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用,由于内部方式是编译好的而非解释执行的,所以执行快的多。

使用事件委托

把事件绑定在祖先节点,由于有事件冒泡,当事件触发时根据event对象的target属性可以知道具体事件是在那个子元素发生的。从而执行不同的行为。这样就不必每个子节点都绑定事件。

最后

以上就是淡定指甲油最近收集整理的关于前端常见题汇总的全部内容,更多相关前端常见题汇总内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部