JS面试题汇总(三)

1.generator 原理

Generator 是 ES6 中新增的语法,和 Promise ⼀样,都可以⽤来异步编 程

// 使⽤ * 表示这是⼀个 Generator 函数
// 内部可以通过 yield 暂停代码
// 通过调⽤ next 恢复执⾏
function* test() {
    
    
	 let a = 1 + 2;
	 yield 2;
	 yield 3; 
 }
let b = test();
console.log(b.next()); // > { value: 2, done: false }
console.log(b.next()); // > { value: 3, done: false }
console.log(b.next()); // > { value: undefined, done: true }

从以上代码可以发现,加上 * 的函数执⾏后拥有了 next 函数,也就是说 函数执⾏后返回了⼀个对象。每次调⽤ next 函数可以继续执⾏被暂停的代 码。以下是 Generator 函数的简单实现

// cb 也就是编译过的 test 函数
function generator(cb) {
    
    
	 return (function() {
    
    
		 var object = {
    
    
			 next: 0,
			 stop: function() {
    
    }
		 };
		 return {
    
    
			 next: function() {
    
    
 				var ret = cb(object);
				if (ret === undefined) return {
    
     value: undefined, done: true };
	 			return {
    
    
 					value: ret,
 					done: false
		 		};
	 		 }
 			};
 		})(); 
 }
// 如果你使⽤ babel 编译后可以发现 test 函数变成了这样
function test() {
    
    
	var a;
 	return generator(function(_context) {
    
    
 		while (1) {
    
    
 		switch ((_context.prev = _context.next)) {
    
    
 // 可以发现通过 yield 将代码分割成⼏块
 // 每次执⾏ next 函数就执⾏⼀块代码
 // 并且表明下次需要执⾏哪块代码
 			case 0:
 				a = 1 + 2;
 				_context.next = 4;
 				return 2;
 			case 4:
 				_context.next = 6;
 				return 3;
			// 执⾏完毕
 			case 6:
 				case "end":
 				return _context.stop();
  			}
 		}
 	}); 
 }

2. Promise

  • PromiseES6 新增的语法,解决了回调地狱的问题
  • 可以把 Promise 看成⼀个状态机。初始是 pending 状态,可以通过函数 resolvereject ,将状态转变为 resolved或者 rejected 状态,状态⼀旦改变就不能再次变 化。
  • then 函数会返回⼀个 Promise 实例,并且该返回值是⼀个新的实例⽽不是之前的实 例。因为Promise 规范规定除了pending 状态,其他状态是不可以改变的,如果返回 的是⼀个相同实例的话多个 then 调⽤就失去意义了。 对于 then来说,本质上可以 把它看成是flatMap

3.== 和 ===区别,什么情况⽤ ==

这⾥来解析⼀道题⽬ [] == ![] // -> true ,下⾯是这个表达式为何为true 的步骤

// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出 [] == ToNumber(false) [] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出 0 == 0 // -> true

=== ⽤于判断两者类型和值是否相同。 在开发中,对于后端返回的 code , 可以通过 == 去判断

4. 基本数据类型和引⽤类型在存储上的差别

前者存储在栈上,后者存储在堆上

5. setTimeout 倒计时误差

JS 是单线程的,所以setTimeout的误差其实是⽆法被完全解决的,原因 有很多,可能是回调的,有可能是浏览器中的各种事件导致。这也是为什么⻚⾯开久了,定时器会不准的原因,当然我们可以通过⼀定的办法去减少这个 误差。

// 以下是⼀个相对准备的倒计时实现
var period = 60 * 1000 * 60 * 2
var startTime = new Date().getTime();
var count = 0
var end = new Date().getTime() + period
var interval = 1000
var currentInterval = interval
function loop() {
    
    
 	count++
 	var offset = new Date().getTime() - (startTime + count * interval); // 代码
 	var diff = end - new Date().getTime()
 	var h = Math.floor(diff / (60 * 1000 * 60))
 	var hdiff = diff % (60 * 1000 * 60)
 	var m = Math.floor(hdiff / (60 * 1000))
 	var mdiff = hdiff % (60 * 1000)
 	var s = mdiff / (1000)
 	var sCeil = Math.ceil(s)
 	var sFloor = Math.floor(s)
 	currentInterval = interval - offset // 得到下⼀次循环所消耗的时间
 	console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执⾏时间:
 	setTimeout(loop, currentInterval) 
 	}
	setTimeout(loop, currentInterval)

6.数组降维

[1, [2], 3].flatMap(v => v)
// -> [1, 2, 3]

如果想将⼀个多维数组彻底的降维,可以这样实现

const flattenDeep = (arr) => Array.isArray(arr)
 ? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , [])
 : [arr]
flattenDeep([1, [[2], [3, [4]], 5]])

7.深拷⻉

这个问题通常可以通过 JSON.parse(JSON.stringify(object))来解决

let a = {
    
    
 age: 1,
 jobs: {
    
    
 first: 'FE'
 } }
let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native'
console.log(b.jobs.first) // FE

但是该⽅法也是有局限性的

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引⽤的对象
let obj = {
    
    
 a: 1,
 b: {
    
    
 c: 2,
 d: 3,
 }, 
 }
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)

在遇到函数、 undefined 或者 symbol 的时候,该对象也不能正常的序列 化

let a = {
    
    
 age: undefined,
 sex: Symbol('male'),
 jobs: function() {
    
    },
 name: 'yck'
 }
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "yck"}

但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决⼤部 分问题,并且该函数是内置函数中处理深拷⻉性能最快的。当然如果你的数据中含有以上三种情况下,可以使⽤ lodash的深拷⻉函数

猜你喜欢

转载自blog.csdn.net/qq_44880095/article/details/113418452