Javascript Q&A
事件循环
为什么有JS的事件循环机制? JS是单线程的,事件驱动的,需要轮询事件列表,相应事件;当事件处理足够快,轮询保持在FPS的时候,就放佛多线程的体验
为什么要引入微任务,只有宏任务不行吗?
- 宏任务是按照先进先出的原则执行的,假如说有比较紧急的事件需要快速处理或者响应,仅依靠宏任务是不够的
- MDN对于微任务的定义:微任务就是一个简单的函数,当创建该函数的函数执行以后,JS栈为空,同时控制权尚未返还给浏览器进行下一次事件循环之前执行的任务函数。需要注意,微任务都是函数创建的
NodeJs中的事件循环和浏览器中的事件循环有什么区别? 事件循环顺序不一致 NodeJs的事件循环顺序:
- timer定时器:执行已经安排的setTimeout和setInterval回调函数
- pending callback等待回调:执行延迟到下一个循环迭代的I/O回调
- idle, prepare:仅系统内部使用
- poll:检索新的I/O事件,执行与I/O相关的回调
- check:执行setImmediate()回调函数
- close callback:socket.on(‘close’, () => {})
微任务和宏任务在node的执行顺序,不同版本有区别 在Node v10及以前:
- 执行完一个阶段中的所有任务
- 执行nextTick队列里的内容
- 执行完微任务队列里的内容
- 在Node v10版本以后,和浏览器的微任务和宏任务执行顺序一致了
addEventLisener支持的参数及含义
function fn() {
console.log('hola')
}
window.addEventListener('click', fn, {
passive: true | false, // 当为true时,listener不会调用`preventDefault()`方法,即使显式调用了这个方法也无效
capture: true | false, // 当为true时,表示该事件在捕获阶段触发
once: true | false, // 如果为true,该listener会在调用一次以后自动移除
})
实现throttle和debounce
实现一个Promise.all
实现一个Promise.cache
实现一个Promise.limit
for in和for of的区别
简单理解:
- for in是枚举
- 比较全能,任何键值对结构或者可以通过键值索引的对象,都可以进行枚举
- for in会从自身属性开始,直至原型链重点,任何enumerable的属性,都可以被枚举
- 因为当Object原型存在可枚举属性时,也会枚举出来,所以当仅需要枚举自身属性时,需要结合
Object.hasOwnProperty
进行过滤 - 但是当数组添加自定义属性如
arr.words = 'hola'
时,arr.hasOwnProperty(words) === false
- arr只有一个ownProperty,就是
length
- arr只有一个ownProperty,就是
- for of是迭代
- 迭代的是元素本身,所以像对象这种键值对索引的数据结构,无法迭代
- 其余的各种,如:字符串、数组、类数组、arguments、Map、Set等,只要对象本身实现了
[Symbol.iterator]
方法的,都可以被迭代
实现一个[Symbol.iterator]迭代器,使对象可以使用for of遍历
Symbol作用和场景
- 解决属性冲突问题,构造唯一的属性名或变量名
- 给对象添加私有属性
// 唯一属性名,nameA和nameB是两个不同的key
const nameA = Symbol('name')
const nameB = Symbol('name')
// Symbol作为key,无法被遍历,所以可以作为私有属性
const obj = {}
obj[nameA] = 'nameA'
JSON.stringify会忽略哪些类型的值?
会忽略:
- Symbol
- funciton
- undefined
会视为空对象:
- RegExp
const obj = {
a: 1,
[Symbol('name')]: 'hola',
function() { },
c: new RegExp(/\d/, 'ig'),
d: undefined,
e: /\d/ig
}
console.log(JSON.stringify(obj)) // {"a":1,"c":{},"e":{}}
如果对象循环引用,JSON.stringify能处理吗?
- 不能,会报错
确定是stringify
而不是parse
报错吗?
- 确定,这是一个先有鸡还是先有蛋的问题
- 序列化不报错,等到反序列化再报错,是不是太蠢了
实现一个对象深拷贝
typeof
- typeof对于7大基本数据,除了null会判断为object,其他都准确
- 对于引用数据类型,除了function判断准确,其他都返回object
undefine
- undefined并不是保留字,在低版本浏览器中,undefined可以被赋值,所以你可以经常看到在压缩混淆的代码库中经常用void 0代替undefined
- 原理就是void后面随便跟任何值组成表达式,返回就是undefined
重绘和重排
new Map()特性
Objects
和maps
其实用法和用途都比较像:
- 他们都允许你按键存取一个值、删除一个键、检测一个键是否存储了值
- 在
Map
之前,我们通过Object
进行键值对管理、存取
核心区别:
Map
可以使用for of
进行迭代,因为Map
实现了[Symbol.iterator]
迭代器- 基于这个原因,
Map
和Objects
对于属性赋值存在了区别 - 因为
for of
无法迭代键值对索引的元素,所以Map
是通过AB映射的方式进行存储的- 即
new Map([ [A,B] ])
- 即
- 基于这个原因,
更加适合使用Map
的场景:
- Map默认不包含任何键,不会存在原型和定义键值冲突的问题
- Map的键和Value可以是任意值,函数、对象、基本类型都是允许的
- 相比较Object的键只能是string或symbol
- Map中的key是有序的,迭代时会按照插入key的顺序进行迭代
- Map的key的数量可以简单的通过
size
属性获取 - Map实现了迭代器,是可迭代的
- Map不能使用
JSON.stringigfy
和JSON.parse
进行序列化和反序列化,但是可以通过自定义replacer的方式进行序列化 - 最重要的,在频繁增删键值对的场景下,Map性能更好
Map的常用方法:
clear()
:删除所有键值对delete(key)
: 删除指定键值对,返回boolean标识是否成功get(key)
: 获取valuehas(key)
: 是否存在某属性set(key, value)
: 使用比较特殊,因为可迭代, 不能通过wrongMap['bla'] = 'blaa'
的方式进行赋值keys()
: 返回一个包含所有key的可迭代对象values()
: 返回一个包含所有values的可迭代对象forEach
: Map可以使用forEach方法进行迭代,接收的参数为value, key, sourceItem
浏览器缓存位置及响应码
缓存响应有几种状态:
200 from memory cache
强缓存命中,表示不访问服务器,直接从内存中读取缓存200 from disk cache
强缓存命中,表示不访问服务器,直接从硬盘中读取缓存200 from prefetch cache
预加载缓存命中- preload,提前加载,按需执行:
<link rel="preload" href="/path/to/style.css" as="style">
- prefecth,提前声明,空闲加载,按需执行:
<link rel="prefetch" href="/path/to/style.css" as="style">
- preload,提前加载,按需执行:
304 not modified
协商缓存命中,已经发生服务器验证,但是返回的报文中不包含具体资源,复用客户端缓存,会更快一点