概述
Promise
十个面试官面试九个都会问这个promise
执行完getCoachList()后再执行setTableData()方法
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject
// 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失败状态的值。
简单的说在同一个函数里面要同时调用两个或者多个不同的接口,接口的返回数据做为下一步操作的依据
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])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
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执行顺序
题目一
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
题目二
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
//跟题目一是同一个原理
经典试题
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.类型不一样:
console.log(typeOf undefined);//undefined
console.log(typeOf null);//object
2.转化为值时不一样:undefined为NaN ,null为0
console.log(Number(undefined));//NaN
console.log(Number(10+undefined));//NaN
console.log(Number(null));//0
console.log(Number(10+null));//10
undefined===null;//false
undefined==null;//true
== 和 === 有什么区别?
对于==来说,如果对方双方类型不一样的话,就会进行类型转换
1.首先会判断两者类型是否相同。相同的话就是比大小了
2.类型不相同的话,那么就会进行类型转换
3.会先判断是否在对比null和undefined,是的话就会返回true
4.判断两者类型是否为string和number,是的话就会将字符串转换为number
var, let, const用法及区别
var
作用域:函数(function)
function test(){
var x = 10;
}
console.log(x); // 错误信息:ReferenceError: x is not defined
let
作用域:区块(block) 定义变量
if (true) {
let x = 10;
}
console.log(x); // 错误信息:ReferenceError: x is not defined
const
作用域:区块(block) 定义常量
重复声明、重新赋值一个常量都会报错
const x = 10;
x = 20; // 错误信息:TypeError: Assignment to constant variable.
let x = 1; // 作用域:区块外(全域)
if (true) {
let x = 2; // 作用域:区块内
console.log(x); // 显示 2
}
console.log(x); // 显示 1
var的bug
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
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i) }, 1000);
}
//输出 0 1 2
分别在什么情况下使用
- const 适用于赋值后不会再做修改的情况
- ** let 适用于赋值后还会修改的情况,它标志着这个变量只能被用在所定义的块作用域**
- var 定义全局变量或函数级变量时可使用,但存在BUG尽量避免使用
总结
- 通过var定义的变量会提升,而let和const进行的声明不会提升
- let声明的变量作用域是外层块,而不是整个外层函数
- let,const在未声明之前是不能使用的,var可以使用
- 优先选择顺序const>let>var
i;
var i=9;
console.log(i); // 9
i;
let i=9;
console.log(i); // "ReferenceError: i is not defined
箭头函数与普通函数的区别
箭头函数是匿名函数,不能作为构造函数,不能使用new
let FunConstructor = () => {
console.log('lll');
}
let fc = new FunConstructor(); //VM84:4 Uncaught TypeError: FunConstructor is not a constructor
箭头函数不绑定arguments,取而代之用rest参数…解决
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值
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();
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 并没有影响。
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
箭头函数没有原型属性
var a = ()=>{
return 1;
}
function b(){
return 2;
}
console.log(a.prototype); // undefined
console.log(b.prototype); // {constructor: ƒ}
箭头函数不能当做Generator函数,不能使用yield关键字
js代码性能优化的几个方法
避免全局查找
使用全局变量和函数肯定要比局部的开销更大,因为要涉及作用域链上的查找,请看以下函数
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的引用保存在一个局部变量。改进代码如下:
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;
}
}
优化循环
for(var i = 0; i <=imgs.length; i++) {
//执行代码
}
上面代码每次执行循环都会重新计算imgs的长度,一旦循环次数很多,那么积少成多就会影响到代码性能,我们只需在for循环执行之前把imgs的长度保存在一个变量中即可,这样就不用每次都是计算imgs的长度,改进代码如下:
var length = imgs.length; //把imgs的长度保存在一个变量中
for(var i = 0; i <=length; i++) {
//执行代码
}
尽量使用原生方法
只要有可能,使用原生方法而不是自己用javascript重写一个。原生方法是用诸如c/c++之类的编译型语言写出来的,所以要比JavaScript的快很多很多。
使用switch替代if-else
如果有一系列复杂的if-else语句,可以转化成单个switch语句则可以得到更快的代码。还可以通过case语句按照最可能的到最不可能的顺序进行组织,来进行进一步优化switch语句。
多个变量申明
javaScript代码中的语句数量也影响所执行的操作的速度,完成多个操作的单个语句要比完成单个操作的多个语句快
//四个语句申明变量,浪费!
var name = "Bill";
var age = 10;
var sex = "man";
//一个语句申明变量,干的漂亮!
var name = "Bill",
age = 10,
sex = "man";
插入迭代值,
当使用迭代值(也就是在不同的位置进行增加或减少的值)的时候,尽可能合并语句
//两个语句,浪费!
var age = values[i];
i++;
//一个语句,干的漂亮!
var age = values[i++];
使用数组和对象字面量,
你可能看过两种创建数组和对象的方法:使用构造函数或是使用字面量,使用构造函数总是要用到很多语句来插入元素或定义属性,而字面量可以将这些操作在一个语句中完成。
//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交互越多,性能越慢。
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,虚拟节点是不会渲染出来的,只会渲染它的子节点。改进代码如下:
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属性可以知道具体事件是在那个子元素发生的。从而执行不同的行为。这样就不必每个子节点都绑定事件。
最后
以上就是淡定指甲油为你收集整理的前端常见题汇总的全部内容,希望文章能够帮你解决前端常见题汇总所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复