前端常见题汇总

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

作用域:函数(functionfunction 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 

分别在什么情况下使用

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

总结

  1. 通过var定义的变量会提升,而let和const进行的声明不会提升
  2. let声明的变量作用域是外层块,而不是整个外层函数
  3. let,const在未声明之前是不能使用的,var可以使用
  4. 优先选择顺序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属性可以知道具体事件是在那个子元素发生的。从而执行不同的行为。这样就不必每个子节点都绑定事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值