JS常见基础面试题二

Promise 状态,多次链式调用以后的状态 —— 读代码

作用:解决回调地狱问题,另一种异步代码的写法

回调地狱(Callback Hell)是指在异步编程中,多个嵌套的回调函数形成的代码结构复杂、难以维护和理解的情况。

1.链式使用

当promise 的状态从pending --> resolved时会执行then函数
当promise 的状态从pending --> rejected时会执行catch函数
then、catch函数里的参数代表promise里面的值

如果没有返回promise对象的话,可以无限的点then,这个then继承上一个then的状态和值,可以一直链式下去。直到遇到return 一个promise对象才会根据这个对象的状态和值,不再继承之前的

	 var p1 = new Promise((resolve, reject)=>{
	    // 异步代码
	    // resolve({a:1})
	    setTimeout(() => {
	      reject('abc')
	    },3000)
	  })
	  // 当p1的状态从pending --> resolved时会执行
	  p1.then((obj)=>{
	    // obj就是p1的promiseValue
	    console.log('then...', obj)
	  }).catch(err => { // 当p1的状态从pending --> rejected时会执行
	     // err就是p1的promiseValue
	    console.log('catch...',err)
	  })
   // 构造函数中的 resolve 或 reject 只有第一次执行有效
	var p1 = new Promise((resolve, reject) => {
		resolve('success1')
		reject('error')
		resolve('success2') // 不执行 Promise 状态一旦改变则不能再变
	})
	p1.then((res) => {
		console.log('then: ', res) // success1
	}).catch((err) => {
		console.log('catch: ', err) // error
	})
	console.log(p1);  // then: success1

2.Promise常见面试题整理

1. promise有几种状态,什么时候会进入catch?

三个状态:pending(初始)、reslove(成功)、reject(失败)
两个过程:pending-> resloved、pending-> rejected当pending为rejectd时,会进入catch
什么时候会进入 catch 当 pending 为 rejectd 时,会进入 catch

2.Promise 构造函数是同步执行还是异步执行,那么 then 方 法呢?

Promise 构造函数是同步执行的,then 方法是异步执行

3.Promise 只有成功和失败 2 个状态,怎么让一个函数无论成 功还是失败都能被调用? 

Promise.all()。用于将多个 Promise 实例,包装成一个新的 Promise 实例

4.Promise 中 reject 和 catch 处理上有什么区别

reject 是用来抛出异常,catch 是用来处理异常
reject 是 Promise 的方法,而 catch 是 Promise 实例的方法
reject 后的东西,一定会进入 then 中的第二个回调,如果 then 中没有写第二个回调,则进入 catch
网络异常(比如断网),会直接进入 catch 而不会进入 then的第二个回调

Promise async await 的执行顺序 —— 读代码

1.Promise 的优先级高于 SetTimeout 宏任务,所以 SetTimeout 回调会在最后执行。
2.Promise 一旦被定义,就会立即执行。
3.Promise 的 resolve 与 reject 是异步执行的回调。所以,resolve 会被放到回调队列中,在主函数执行完 和 宏任务 setTimeout 执行前调用
4.async 声明的函数,返回的结果是一个Promise 对象
5.await 会立即执行同行代码,阻塞下一行代码,
(await 会暂停async后面的代码,先执行async外面的同步代码)

   // 案例1
	console.log('start')
	
	const promise1 = Promise.resolve().then(() => {
		console.log('promise1')
		const timer2 = setTimeout(() => {
			console.log('timer2')
		}, 0)
	})
	
	const timer1 = setTimeout(() => {
		console.log('timer1')
		const promise2 = Promise.resolve().then(() => {
			console.log('promise2')
		})
	}, 0)
	
	console.log('end')
    
    // 执行顺序
    1.先执行所有同步代码 输出:start end
    2.执行所有微任务。Promise立马执行 输出:promise1 
    3.执行第一个宏任务。输出: timer1
    4.执行所有新添加的微任务。输出:promise2
    5.执行下一个宏任务。输出:timer2
   
    // 案例2
	async function async1 () {
		console.log('async1 start') 
		await async2() 
		console.log('async1 end')
	}
	
	async function async2 () {
		console.log('async2') 
	}
	
	console.log('script start') 
	
	setTimeout(function() {
		console.log('setTimeout') 
	}, 0)
	
	async1()
	
	new Promise(function (resolve) {
		console.log('proimse1')
		resolve()
	}).then(function() {
		console.log('promise2')
	})
	console.log('script end')
  
   // 执行顺序
   1.主线程同步代码先执行  ~~输出:script start~~ 
   2.setTimeout宏任务,放到下一个宏任务队列中
   3.执行 async1()实质是创建了一个Promise对象,所以立即执行 ~~输出:async1 start~~ 
   4.执行await async2() 首先await 会立即执行同行代码,阻塞下一行代码,
     也会暂停async后面的代码,先执行async外面的同步代码
     执行 async2()即返回 async2.then(() => {console.log('async1 end')}) 
     .then()里面的内容会放到当前宏任务的微任务列表中,下次执行
     ~~输出:async2~~ 
   5.执行 new Promise() 立即执行 ~~输出:promise1~~   
   6. resolve() 异步微任务 所以会被放到当前宏任务的微任务队列中,下次执行
   7. 主线程同步代码执行  ~~输出:script end~~ 
   8. 此时宏任务中的同步代码已经执行完成,开始依次处理微任务队列中的代码~~输出:async1 end ; promise2~~ 
   9.在最后执行下一个宏任务队列,即setTimeout ~~输出:setTimeout~~ 
   

ES6 新的数据结构Set Map

介绍下 Set和Map的区别

应用场景:Set用于数据重组,Map用于数据储存
Set: 
(1)成员不能重复
(2)只有键值没有键名,类似数组
(3)可以遍历
Map:
(1)本质上是健值对的集合,类似集合
(2)可以遍历,可以跟各种数据格式转换

Set 数据结构:相当于是一种叫集合的数据结构,集合的元素都是唯一的,是构造函数。
可以轻松地实现数组去重

1.声明方式

	let s = new Set();
	
	// 唯一的
	let s = new Set([1, 2, 3, 2]);
	console.log('唯一性',s) // Set(3) {1, 2, 3}
	
	// 常规方法
	s.add('kaka').add('Dorothy') // 添加新的值 支持链式操作
	s.delete(2) // 删除2
	s.clear() // 清空
	console.log(s.has('kaka')) //true,判断是否包含某个值
	console.log(s.size) // 输出Set的长度 
	

2.遍历方法

    // 遍历的4种方式 
    
	// 1.forEach()
	   s.forEach(item => console.log(item));
	// 2.keys()  返回键名的遍历器
	   console.log('keys---',s.keys());
	// 3.values()  返回键值的遍历器
	   console.log('values---',s.values());	
	// 4.entries() 返回键值对的遍历器
	   console.log('entries---',s.entries());

3.实现数组去重和Set的交集,并集,差集

    // 数组去重
    let set = new Set([1,2,3,3])
	console.log(set) // {1,2,3}
	let [...arr] = set //将set转换为数组
	comsole.log(arr) // [1,2,3]
    
    let set11Arr = new Set([1, 2, 3])
	let set12Arr = new Set([2, 3, 4])
	//交集
	let jiao = new Set([...set11Arr].filter(x => set12Arr.has(x)));
	console.log("jiao", jiao) //Set(2) {2, 3}
	//并集
	console.log(new Set([...set11Arr, ...set12Arr])) //Set(4) {1, 2, 3, 4}
	//差集
	let cha = new Set([...set11Arr].filter(x => !set12Arr.has(x)));
	console.log("cha", cha) // Set(1) {1} 

Map 数据结构:类似于对象,Object 结构提供了“字符串—值”的对应,但普通对象的 key 必须是字符串或是数字,而Map 结构提供了“值—值”的对应,Map 的 key 可以是任何数据类型,是一种更完善的 Hash 结构实现。Map 的用法和普通对象基本一致,如果你需要“键值对”的数据结构,Map 比 Object 更合适。可以更加全面的描述对象的属性

  1. 创建Map实例
    const map = new Map();
    
	const map = new Map([
		[1, 'a'],
		[2, 'b'],
		[3, 'c']
	]); //Map(3) Map(3) {1 => 'a', 2 => 'b', 3 => 'c'}
  1. Map实例属性和操作方法
    map.size; // size属性
	// 操作方法
	map.set('a',1); // 设置某个值(键,值) 
    map.get('a');  // 读取key对应的键值
	map.has('a'); // 某个键是否存在当前的Map对象之中
	map.delete('a'); // 删除某个键。删除失败返回false
	map.clear(); // 清楚所有成员
  1. 遍历的4种方式
    // 1.forEach()
	   map.forEach(item => console.log(item));
	// 2.keys()  返回键名的遍历器
	   console.log('keys---',map.keys());
	// 3.values()  返回键值的遍历器
	   console.log('values---',map.values());	
	// 4.entries() 返回键值对的遍历器
	   console.log('entries---',map.entries());
  1. Map与数组的互相转换
    //  1.Map 转为数组
	    [...map]; // 扩展运算符
		Array.from(map); // 将一个类数组对象或者可遍历对象转成一个真正的数组
	//  2.数组转为Map
	    let map = new Map(数组);

手写 debounce throttle EventBus

节流函数(throttle):需要频繁触发的函数,在设定规定时间内,函数只触发一次,后面的触发要等间隔时间。应用场景:鼠标点击、滚轮事件

    //fn:要被节流的函数  delay:间隔时间
   function throttle(fn, delay) {
        let lastTime = 0; //记录上一次的函数触发时间
 
        return function() {
            let nowTime = Date.now(); //记录本次的函数触发时间
            if (nowTime - lastTime > delay) {
                lastTime = nowTime;
                return fn.apply(this); //修正this指向
                
            }
        }
    }
    document.onscroll = throttle(function() {
        console.log('throttle函数执行了' + Date.now());
    }, 600)

防抖函数(debounce):在规定时间内,需要频繁触发的函数,每次触发会重新计时,只让函数最后一次的触发生效
应用场景:用户在input框不断输入值时、不断的调整浏览器窗口大小时

   function debounce(fn, delay) {
        var timer = null;
        return function() {
            clearTimeout(timer);
            timer = setTimeout(function() {
                fn.apply(this);
            }, delay);
        }
    }
    document.onscroll = debounce(function() {
        console.log('debounce函数执行了' + Date.now());
    }, 1200)

EvenBus:事件总线,通常作为多个模块间的通信机制,相当于一个事件管理中心,一个模块发送消息,其它模块接受消息,就达到了通信的作用。本质上是采用了发布-订阅的设计模式

	class EventBus {
	  constructor() {	
	    // 初始化事件列表
	    this.eventObject = {};
	    // 回调函数列表的id
	    this.callbackId = 0;
	  }
	  // 发布事件
	  publish(eventName, ...args) {
	    // 取出当前事件所有的回调函数
	    const callbackObject = this.eventObject[eventName];
	
	    if (!callbackObject) return console.warn(eventName + " not found!");
	
	    // 执行每一个回调函数
	    for (let id in callbackObject) {
	      // 执行时传入参数
	      callbackObject[id](...args);
	    }
	  }
	  // 订阅事件
	  subscribe(eventName, callback) {
	    // 初始化这个事件
	    if (!this.eventObject[eventName]) {
	      // 使用对象存储,注销回调函数的时候提高删除的效率
	      this.eventObject[eventName] = {};
	    }
	
	    const id = this.callbackId++;
	
	    // 存储订阅者的回调函数
	    // callbackId使用后需要自增,供下一个回调函数使用
	    this.eventObject[eventName][id] = callback;
	
	    // 每一次订阅事件,都生成唯一一个取消订阅的函数
	    const unSubscribe = () => {
	      // 清除这个订阅者的回调函数
	      delete this.eventObject[eventName][id];
	
	      // 如果这个事件没有订阅者了,也把整个事件对象清除
	      if (Object.keys(this.eventObject[eventName]).length === 0) {
	        delete this.eventObject[eventName];
	      }
	    };
	
	    return { unSubscribe };
	  }
	}
	
	// 测试
	const eventBus = new EventBus();
	
	// 订阅事件eventX
	eventBus.subscribe("eventX", (obj, num) => {
	  console.log("模块A", obj, num);
	});
	eventBus.subscribe("eventX", (obj, num) => {
	  console.log("模块B", obj, num);
	});
	const subscriberC = eventBus.subscribe("eventX", (obj, num) => {
	  console.log("模块C", obj, num);
	});
	
	// 发布事件eventX
	eventBus.publish("eventX", { msg: "EventX published!" }, 1);
	
	// 模块C取消订阅
	subscriberC.unSubscribe();
	
	// 再次发布事件eventX,模块C不会再收到消息了
	eventBus.publish("eventX", { msg: "EventX published again!" }, 2);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值