Javascript Q&A

2021/05/01 Javascript 共 3192 字,约 10 分钟

Javascript Q&A

事件循环

为什么有JS的事件循环机制? JS是单线程的,事件驱动的,需要轮询事件列表,相应事件;当事件处理足够快,轮询保持在FPS的时候,就放佛多线程的体验

为什么要引入微任务,只有宏任务不行吗?

  • 宏任务是按照先进先出的原则执行的,假如说有比较紧急的事件需要快速处理或者响应,仅依靠宏任务是不够的
  • MDN对于微任务的定义:微任务就是一个简单的函数,当创建该函数的函数执行以后,JS栈为空,同时控制权尚未返还给浏览器进行下一次事件循环之前执行的任务函数。需要注意,微任务都是函数创建的

NodeJs中的事件循环和浏览器中的事件循环有什么区别? 事件循环顺序不一致 NodeJs的事件循环顺序:

  1. timer定时器:执行已经安排的setTimeout和setInterval回调函数
  2. pending callback等待回调:执行延迟到下一个循环迭代的I/O回调
  3. idle, prepare:仅系统内部使用
  4. poll:检索新的I/O事件,执行与I/O相关的回调
  5. check:执行setImmediate()回调函数
  6. close callback:socket.on(‘close’, () => {})

微任务和宏任务在node的执行顺序,不同版本有区别 在Node v10及以前:

  1. 执行完一个阶段中的所有任务
  2. 执行nextTick队列里的内容
  3. 执行完微任务队列里的内容
  4. 在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
  • 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()特性

Objectsmaps其实用法和用途都比较像:

  • 他们都允许你按键存取一个值、删除一个键、检测一个键是否存储了值
  • Map之前,我们通过Object进行键值对管理、存取

核心区别:

  • Map可以使用for of进行迭代,因为Map实现了[Symbol.iterator]迭代器
    • 基于这个原因,MapObjects对于属性赋值存在了区别
    • 因为for of无法迭代键值对索引的元素,所以Map是通过AB映射的方式进行存储的
      • new Map([ [A,B] ])

更加适合使用Map的场景:

  • Map默认不包含任何键,不会存在原型和定义键值冲突的问题
  • Map的键和Value可以是任意值,函数、对象、基本类型都是允许的
    • 相比较Object的键只能是string或symbol
  • Map中的key是有序的,迭代时会按照插入key的顺序进行迭代
  • Map的key的数量可以简单的通过size属性获取
  • Map实现了迭代器,是可迭代的
  • Map不能使用JSON.stringigfyJSON.parse进行序列化和反序列化,但是可以通过自定义replacer的方式进行序列化
  • 最重要的,在频繁增删键值对的场景下,Map性能更好

Map的常用方法:

  • clear():删除所有键值对
  • delete(key): 删除指定键值对,返回boolean标识是否成功
  • get(key): 获取value
  • has(key): 是否存在某属性
  • set(key, value): 使用比较特殊,因为可迭代, 不能通过wrongMap['bla'] = 'blaa'的方式进行赋值
  • keys(): 返回一个包含所有key的可迭代对象
  • values(): 返回一个包含所有values的可迭代对象
  • forEach: Map可以使用forEach方法进行迭代,接收的参数为value, key, sourceItem

浏览器缓存位置及响应码

nginx etag

缓存响应有几种状态:

  • 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">
  • 304 not modified 协商缓存命中,已经发生服务器验证,但是返回的报文中不包含具体资源,复用客户端缓存,会更快一点

[1]

Search

    Table of Contents