面试知识javascript篇

[toc]

从浏览器地址栏输入 url 到显示页面的步骤

* 先检查搜索关键字是否符合url规则,然后将其组装成完成url进行访问
* 检查缓存,浏览器检查本地强缓存是否可用,如果命中强缓存就直接从缓存中返回资源
  - 根据http header中的expires、cache-control来判断是否命中强缓存
* DNS解析,如果未命中强缓存,则向服务器发起请求,通过递归查询和迭代查询解析域名来获取对应IP地址
  - 浏览器IP缓存
  - 操作系统IP缓存
  - 本地hosts文件
  - 路由器缓存
  - 本地DNS服务器以递归方式进行查询缓存记录
  - 若没有缓存记录就向根DNS服务器查询。根DNS服务器查询到结果后会把域名和IP地址告诉本地DNS服务器,
  本地DNS服务器把对应关系暂存在缓存中(以便下次用户查询,加快网络访问),然后再发给浏览器客户端的解析服务器。
* 客户端发送HTTP请求
* 建立TCP连接,三次握手
* 发起http请求
* 负载均衡:服务端网关收到http请求后,可能会进行一系列负载均衡处理,通过反向代理分配给对应集群中的服务器去执行
* 服务端返回响应:服务端收到请求后,根据请求头中缓存标识(If-Modified-Since/If-None-Match)来判断缓存是否生效,
生效返回304状态码,未命中缓存返回200状态码和标识(Last-Modified/Etag)
* 浏览器接收到http响应后,根据connection: keep-alive判断保持连接或者四次挥手断开TCP连接
* 浏览器缓存响应头中缓存标识字段(Last-Modified/Etag)
* 解析HTML文档,此时document.readystate为loading
* 构建DOM树,浏览器从上到下解析html文档生成DOM节点树
* 构建CSSOM树,浏览器解析遇到样式进行异步下载,构建CSSOM树(不会阻塞DOM树构建,但是会阻塞渲染,防止css规则不断变化)
* 构建渲染树,根据DOM节点树和CSSOM树构建渲染树Render
* 遇到图片异步下载,遇到不带async和defer的script时,阻塞html的解析并下载且执行
* 带async的script标签,不会中断html解析并行下载脚本,下载完成后中断html解析并执行脚本,优先级高于defer,但是无序
* 带defer的script标签,不会中断html解析并行下载脚本,当浏览器解析完html时,DOMContentLoaded事件即将触发时执行脚本
* 文档解析完成,document.readystate变为interactive,触发DOMContentLoaded事件
* 等待图片加载或所有异步脚本加载执行完成,document.readystate变为complete,window触发load事件
* 布局Layout,根据Render树计算每个节点在屏幕上的位置布局
* 绘制Paint,绘制节点到屏幕上,涉及到构建图层树、绘制列表、光栅化(合成线程)和显示等。

其他:
dns-prefetch:
前端网络性能优化的一种措施,提前解析之后可能遇到的域名,使解析结果缓存到系统缓存中,缩短 DNS 解析时间,进而提高网站的访问速度

dns-prefetch 原理:
浏览器缓存->系统缓存->路由器缓存->ISP(运行商)DNS 缓存->根域名服务器->顶级域名服务器->主域名服务器

dns-prefetch 将解析后的 IP 缓存放在系统缓存中
dns-prefetch 与 preconnect 预连接提示配对

1
2
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
<link rel="dns-prefetch" href="https://fonts.gstatic.com/">

Note:如果页面需要建立与许多第三方域的连接,则将它们预先连接会适得其反。 preconnect 提示最好仅用于最关键的连接。对于其他的,只需使用 即可节省第一步的时间 DNS 查找。

扩展:
收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式、什么顺序、建立了多少连接、使用什么协议被下载下来的呢?

你知道哪些前端攻击?该如何预防?

  • xss

    • Cross Site Script 跨站脚本攻击
    • 手段:将 js 代码插入到网页内容中,渲染时执行 js 代码
    • 预防:特殊字符替换(前端或后端)
    • 输入检查,对于用户输入进行格式检查。
  • csrf

    • Cross Site Request Forgery 跨站请求伪造

    • 手段:诱导用户去访问另一个网站的接口,伪造请求

    • 预防:严格的跨域限制 + 验证码机制

    • csrf 详细过程

      • 用户登录 A 网站,有了 A 网站的 cookie
      • 诱导用户到 B 网站,并发起 A 网站的请求
      • A 网站的 API 发现有 cookie,认为是用户自己操作的
    • csrf 预防手段

      • 严格的跨域请求限制,如判断 referer(请求来源)
      • 为 cookie 设置 SameSite,禁止跨域传递 cookie
        • Chrome 51 开始,浏览器的 Cookie 新增加了一个 SameSite 属性,用来防止 CSRF 攻击 和用户追踪(第三方恶意获取 cookie),限制第三方 Cookie,从而减少安全风险。
      • 关键接口使用短信验证码
      • token 验证
  • 点击劫持

    • click jacing
    • 手段:诱导界面上蒙一个透明 iframe,诱导用户点击
    • 预防:让 iframe 不能跨域加载
      • X-FRAME-OPTIONS 响应头是用来给浏览器指示允许一个页面可否在<frame>,
        <iframe> 或者 <object> 中展现的标记。网站可以使用此功能,来确保自己网站
        内容没有被嵌到别人的网站中去,也从而避免点击劫持的攻击。
  • DDos

    • Distribute denial-of-service 分布式拒绝服务
    • 手段:分布式的、大规模的流量访问,使服务器瘫痪
    • 预防:软件层不好做,需硬件预防(如阿里云 WAF)
  • SQL 注入

    • 手段:提交内容时写入 SQL 语句,破环数据库
    • 预防:处理输入的内容,替换特殊字符

性能优化(空间换时间)

  • 性能优化原则

    • 多使用内存、缓存
    • 减少 CPU 计算,减少网络加载耗时
    • 适用于所有编程的性能优化-空间换时间
  • 减少资源体积:压缩代码

  • 减少访问次数:合并代码,SSR 服务器渲染,缓存,精灵图

    • 缓存(webpack contenthash)
      • 静态资源加 hash 后缀,根据文件内容计算 hash
      • 文件内容不变,则 hash 不变则 url 不变
      • url 和文件不变,则会自动触发 http 的缓存机制,返回 304
    • SSR
      • 服务器端渲染:将网页和数据一起加载,一起渲染
      • 非 SSR(前后端分离):先加载网页,后加载数据,再渲染数据
  • DNS 预解析

  • 减少 cookie 大小,http 请求会携带 cookie

  • 使用 http2 头部压缩

  • 避免重定向:当页面发生了重定向,就会延迟整个 HTML 文档的传输。在 HTML 文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载。

  • 渲染优化

    • css 放在 head,js 放在 body 最下面
    • 尽早开始执行 js,用 DOMContentLoaded 触发
    • 懒加载(图片懒加载-IntersectionObserver,下滑更多等)
    • 对 DOM 查询进行缓存
    • 减少 DOM 数量,大数据量分页、虚拟列表
    • 合并频繁的 DOM 操作,document.createDocumentFragment(),减少 dom 操作次数
    • 节流 throttle、防抖 debounce
    • 使用 loading 图,提高用户视觉体验
    • 使用 GPU 加速:使用 transform、opacity,要慎用低端机 GPU 差,占用较多内存,因此是否开启硬件加速,要用测试结果决定
    • 使用 requestAnimationFrame 来实现视觉变化
  • 三方资源

    • 第三方资源、库使用 CDN
    • 压缩图片体积,减少图片大小
    • 图片使用 webp 格式,减少图片体积
    • preload 预先加载 css 文件或者字体文件、js 文件等
      • 浏览器需要先把 html 页面加载回来,才能知道下一步去加载那些 js、css 或字体文件,中间时间就被浪费掉了
      • 可以在等待 html 响应的同时把重要的静态资源文件也加载回来

你对闭包了解多少?

解释一下作用域链是如何产生的
解释一下js执行山下文的创建、执行过程
解释一下闭包所产生的变量放在哪里
  • 闭包的定义:闭包就是能够读取其他函数内部变量的函数。
  • 闭包的底层实现原理
  • js 执行上下文:
  • image
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
js运行三部曲
1.语法分析
2.预编译
3.解释执行

js代码需要经过浏览器V8引擎进行预编译,里面涉及到变量提升、函数提升。
预编译的环境需要个环境,这个环境就是执行上下文。

js执行上下文分为三种:
1.全局执行上下文:代码开始执行时首先进入的环境
2.函数执行上下文:函数调用时,会开始执行函数中的代码
3.eval执行上下文:不建议使用

执行上下文的周期,分为两个阶段
1.创建阶段
创建词法环境
生成变量对象VO,建立作用域链
确认this指向,并绑定this
2.执行阶段
进行变量赋值,函数引用以及执行代码

预编译发生在函数执行前,预编译四部曲:
1.创建AO对象
2.找形参和变量声明,将变量和形参作为AO的属性名,值为undefined
3.将形参和实参相统一
4.在函数体中找到函数声明,值赋予函数体(函数名相同,后者覆盖前者)
5.最后程序输出变量值的时候,就是从AO对象中拿
(此时就产生了外部作用域的引用,js查找变量的规则就是如果在函数执行上下文中找不到变量,
就在调用上下文中寻找他,如果还没有就一直往上一级,直到全局执行上下文,如果还没有,就是undefined,js形成闭包)

js 的执行机制

js 是单线程的,处理 js 任务只能一个一个顺序执行,js 中把任务分为了同步任务和异步任务,
同步任务进入主线程先执行,异步任务进入 Event Table 并注册函数,指定事情完成后,Event Table 就会将函数移入到事件队列 Event Queque 中,等待主线程任务执行完毕,就会从事件队列中取出对应事件进入主线程执行。

macro-task(宏任务):包括整体代码 script、setTimeout、setInterval、IO 操作、UI 交互、postMessage 等
micro-task(微任务):Promise.then、process.nextTick、MutationObserve 等
微任务先于宏任务先执行(除了 script)执行过程不同任务进入不同的 event queue

js 先执行整体的同步任务代码,遇到微任务就会将其放入到微任务事件队列,遇到宏任务放到宏任务事件队列中。

然后整体的同步任务代码执行完之后,就会先执行微任务队列中的任务,
等待微任务队列中的所有任务执行完毕之后,再去从宏任务队列中找到第一个任务进行执行,
执行过程中,如果遇到微任务就会放到微任务队列中,等到该宏任务执行完毕之后,
就会查看微任务队列中有没有微任务,如果有就先执行微任务队列中的任务,否则执行第二个宏任务,
以此类推。

简单说下原型链

  • 原型链是由原型对象组成的,每个对象都有proto属性,指向了创建该对象的构造函数的
    原型,proto将对象连接起来组成了原型链。

  • 原型链:用来实现继承和共享属性的有限对象链。

  • 每个对象都有proto(隐式原型)属性,指向创建该对象的构造函数的原型。其实
    这个属性指向了 [[prototype]],但是 [[prototype]] 是内部属性,我们并不能
    访问到,所以使用 proto 来访问。

  • 对象的隐式原型等于对象的构造函数的显式原型:obj.proto === Object.prototype

  • 访问属性的时候,js 引擎会调用内部的默认[[Get]]操作,[[Get]]操作首先会检查对象本身是否包含这个属性
    如果对象没有这个属性,则对象可以通过proto来寻找不属于该对象的属性,proto
    将对象和原型连接起来形成原型链

  • Function.prototype 和 Object.prototype 是两个特殊的对象,他们由引擎创建。

defer 和 async 有什么区别?

  • 区别 区别

如何监听未处理的异常

  • try…catch
    • 无法捕捉到语法错误,只能捕捉运行时错误
    • 可以拿到出错的信息(出错的文件,行号,列号)
  • window.onerror
    • 由于 try…catch 只能捕获块里面的错误,全局的一些错误可以用 window.onerror
  • window.addEventListener(‘error’,callback):捕获资源错误
  • window.addEventListener(‘unhandledrejection’,callback):捕获 promise 类型错误
  • vue.config.errorHandler: vue 错误
  • componentDidCatch:错误边界函数

本地存储

  • 客户端的本地存储:

    • localStorage - 生命周期永久生效,除非手动删除,否则关闭页面也会存在 - 可以在同一浏览器多窗口共享 - 以键值对的形式存储使用

    • 方法:

    • 存储数据:localStorage.setItem(key, value)

    • 获取数据:localStorage.getItem(key)

    • 删除数据:localStorage.removeItem(key)

    • 删除所有数据:localStorage.clear()

    • sessionStorage

      • 声明周期为关闭浏览器窗口

      • 在同一个窗口中数据可以共享

      • 以键值对的形式存储

      • 方法:

        • 存储数据:sessionStorage.setItem(key, value)
        • 获取数据:sessionStorage.getItem(key)
        • 删除数据:sessionStorage.removeItem(key)
        • 删除所有数据:sessionStorage.clear()

        localStorage 和 sessionStorage

        cookie 和 session

    • IndexDB 离线存储,当网络断开,可以从浏览器中读取数据,用来做一些离线应用

    • Cookie

      • 包含字段
        • name: cookie 名称
        • value: 值
        • domain: cookie 生效的域名
        • path: cookie 生效的路径
        • expires/max-age: cookie 过期时间
        • size: 大小
        • HttpOnly: 用户端不可更改
      • 存储用户信息,通过在客户端记录信息确定用户身份,最大为 4kb
      • 会话 Cookie,若不设置过期时间,表示这个 cookie 的生命周期为浏览器会话期间,浏览器
        关闭,cookie 就消失,会话 cookie 会保存在内存中而不是硬盘上。
      • 持久 Cookie,若设置了过期时间,浏览器会把 cookie 保存在硬盘上,关闭浏览器仍然有效直到
        超过设定的过期时间。
      • cookie 数据始终在同源的 http 请求中携带(即使不需要),即会在浏览器和服务器之间来回传递。
      • Cookie 具有不可跨域名性,例如浏览器访问百度不会带上谷歌的 cookie
  • 监测是否支持 web Storage

    • 1.通过 window.sessionStorage,window.localStorage 判断浏览器是否支持
    • 2.通过 try{}catch{} 执行一下 storage.setItem(‘key’, value);storage.removeItem(‘key’),判断接口方法是否可用。
  • 服务端的存储:

    • Session
      • Session 服务器端一种记录客户端状态的机制
      • cookie 数据存放在客户的浏览器上,session 将数据存放在服务器端
      • Session 相对 Cookie 来说比较安全,别人可以分析本地存放的 cookie 进行 cookie 欺骗
      • Session 会在一定时间内保存在服务器上,访问量较多的时候会比较占用服务器的性能,
        考虑到减轻服务器性能方面的时候,应当使用 cookie
      • 可以将登陆等重要信息保存在 session,其他信息放在 cookie 中

localStorage 超过最大限制(5M)怎么处理?

  • localstorage 一般最大容量为 5M,意思是每个域名(假如为 a.com)下最大 localstorage 容量为 5M,我们可以通过 iframe 创建 b.com 域框架用于存储 a.com 剩下的数据,然后通过 postMessage 读写数据。

    • 通常对于不同页面的脚本,只有在同源策略下才能通信,但是 window.postMessage(message,targetOrigin)方法提供了一种受控机制来规避此限制。
    • 而且 localStorage 本身定位也不是大数据量的存储方案
  • 浏览器提供了大数据量的本地存储的方案:IndexedDB

    • 一般存储数据大小在 250M 以上
    • 可以使用 localforage 插件(yarn add localforage),api 基本和 localStorage 类似,学习成本低
  • cookie 可用于传递少量数据,是一个再服务器和客户端之间来回传送文本值的内置机制,服务器可以根据 cookie 追踪用户在不同页面的访问信息。

  • cookie 特点

    • 1.大小限制,cookie 大小限制在 4KB 以内
    • 2.宽带限制,cookie 数据会在服务器和浏览器之间来回传送,所以访问页面会消耗宽带。
    • 3.安全风险,cookie 会频繁的在网络中传送,不加密的情况下是有安全风险的。
    • 4.操作复杂
    1
    2
    3
    4
    5
    6
    function setCookie(name: string, value: string) {
    const exp = new Date();
    //过期时间设置为一天
    exp.setTime(exp.getTime() + 24 * 60 * 60 * 1000);
    document.cookie = `${name}=${escape(value);expires=${exp.toString()}}`;
    }

fetch 和 axios

fetch取消发送
1.创建一个AbortController实例
2.该实例具有signal属性
3.将signal传递给fetch option
4.调用AbortController的abort属性来取消所有使用该信号的fetch

axios取消发送
1.const cancelToken = axios.CancelToken
2.const source = CancelToken.source()
3.axios.get('/xxx',{cancelToken: source.token})

箭头函数中的 this

  • 箭头函数中的 this 是在定义函数的时候绑定的(继承自父执行上下文中的 this),而不是执行函数时绑定。
  • 箭头函数没有 this,所以不能用作构造函数。

Map 和 Set 两种新的数据结构的区别?

  • Map 类似 Object 是一种键值对集合,区别在于 Map 的键不仅限于字符串,其他各种类型的值都可以作为 Map 的键

  • Set 是类似数组的一种数据结构,不同点在于 Set 中没有重复的值

js 的 new 操作符都做了些什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1. 创建一个空的js对象{}
2. 将空对象的隐式原型__proto__指向构造函数的原型
3. 将空对象作为构造函数的上下文(改变this指向)
4. 对构造函数返回值做判断

实现:
function newFn(fn, ...args) {
const obj = Object.create(fn.prototype);
const result = fn.apply(obj, args);
return typeof result === 'object' && result !== null ? result : obj;
}

function Person(name) {
this.name = name;
}

const p = newFn(Person, 'Jerome');

console.log('p.name :>> ', p.name); // p.name :>> Jerome

补充:
在new的时候,会对构造函数的返回值做一些判断
1. 如果返回值是基础类型数据,则忽略返回值
2. 如果返回值是引用数据类型,则使用return的返回,也就是new操作符无效

es6 新特性

1.let和const
2.模版字符串
3.箭头函数
4.函数可以设置默认参数值
5.扩展运算符
6.对象和数组的解构
7.class

图片懒加载原理

  • 浏览器是否发起请求是根据标签的 src 属性
  • 所以懒加载的关键是:在图片没有进入可视区域时,先不给的 src 属性赋值,等到图片进入可是区域再给 data-src -> src 赋值。
  • // 方法一:offsetTop - scrollTop <= 视口高度
  • // 方法二:getBoundingClientRect().top <= 视口高度 screenHeight
  • // 方法三:IntersectionObserver

为什么 try/catch 不能捕获到 promise 的错误?

  • try-catch 主要用于捕获同步函数的异常,如果 try 里面的异步方法出现了异常,此时 catch 是无法捕获到异常的。ES6 中 Promise 对象的实例提供了 catch() 方法,表示异步捕获异常。

  • 原因:当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在 try-catch 所在的栈,所以 try-catch 并不能捕获到错误。对于微任务而言,比如 promise,promise 的构造函数的异常只能被自带的 reject 也就是.catch 函数捕获到。

requestIdleCallback 和 requestAnimationFrame 有什么区别?

requestIdleCallback(callback, timeout):
· 低优先级
· 兼容性不好
· 执行时机:浏览器空闲时被调用
· 指定timeout,回调任务就会被放进事件循环队列,强制执行,但是会影响性能

requestAnimationFrame(callback):
· 高优先级
· 执行时机:下次重绘前执行传入的回调函数

- 补充
  - 两者都是宏任务(其实也算不上,重要的是执行时机)

如何阻止冒泡?

W3C的方法e.stopPropagation(),IE使用e.cancelBubble = true;

封装:
    //阻止冒泡行为
    function stopBubble(e) {
        //如果提供了事件对象,则这是一个非IE浏览器
        if (e && e.stopPropagation) e.stopPropagation()
        //IE的方法
        else window.event.cancelBubble = true
    }

如何阻止默认事件?

W3C的方法是e.preventDefault(),IE使用e.returnValue = false

封装:
    //阻止浏览器的默认行为
    function stopDefault (e) {
        if (e && e.preventDefault) e.preventDefault()
        //IE中阻止默认事件的方法
        else window.event.returnValue = false
        return false
    }

补充:事件绑定的封装

function addEvent(element,type,handle) {
    if(element.addEventListener){
        element.addEventListener(type,handle,false);
    }else if(element.attachEvent){
        element.attachEvent('on'+type,function () {
            handle.call(element);
        })
    }else {
        element['on'+type] = handle;
    }
}

如何判断一个对象是否为数组

1. Array.prototype.isPrototypeOf(obj)方法,判断Array.prototype是不是在obj的原型链中,
如果在,则返回true,否则返回false。

2. obj instanceof Array

3. Object.prototype.toString.call(obj); //(==="[object Array]")

4.Array.isArray(obj)

Http 的持久连接和管线化

1. 什么是持久连接?
    HTTP1.1规定了默认保持持久连接,数据传输完成也保持TCP连接不断开,等待同域名下
    继续使用这个通道传输数据,在一个TCP连接上传输多个HTTP请求和响应。

    持久连接避免了重新建立连接,大大减少了建立和关闭连接的消耗和延迟,HTTP的连接是建立
    在TCP协议之上的,建立一条TCP连接需要三次握手,TCP连接关闭需要四次挥手,这些都需要时间。

2. 什么是管线化?
    持久连接:
        请求1 ——> 响应1 ——> 请求2 ——> 响应2
    管线化:
        请求1 ——> 请求2 ——> 响应1 ——> 响应2

    管线化机制需要通过持久化连接完成。

    持久连接的一个缺点是请求和响应式是顺序执行的,只有在请求1的响应收到之后,
    才会发送请求2,而管线化不需要等待上一次请求得到响应就可以进行下一次请求。
    实现并行发送请求。

    只有GET和HEAD请求可以进行管线化,而POST有所限制。

    初次建立连接不应启动管线机制,因为对方服务器不一定支持HTTP1.1版本的协议。

为什么利用多个域名来存储网站资源会更有效?

1.CDN 是构建在网络之上的内容分发网络,可以使用户就近获取资源,减低网络拥塞,提高用户
访问的响应效率以及命中率。

2.突破浏览器的并发限制,同一时间针对同一域名下的请求有一定的数量限制,超过限定数目
的请求会被阻塞。

3.节约cookie带宽

4.减少主域名的连接数,优化页面响应速度

5.防止不必要的安全问题

基本数据类型

基本数据类型:Null、Undefined、String、Boolean、Number
ES6:Symbol
ES10:Bigint(可以突破安全整数限制,安全的存储和操作最大整数之外的整数,不会损失精度)

Object.seal 和 Object.freeze

Object.seal

  1. 当前属性的值只要原来可写就可以改变
  2. 不能向对象新增属性
  3. 已有属性都变得不可配置,也就是不可删除

Object.freeze

  1. 不能向对象新增属性
  2. 不能删除已有属性
  3. 不能修改已有属性的 enumable、writable、configurable
  4. 不能修改已有属性的值
  5. 该对象的原型也不能修改

牛客学习

  • 超链接

    • a 标签的 href 的属性值是 url,里面必须包含协议,没有协议就会解析成相对路径。
  • HTTP 状态码分类:

    • 1** 信息,服务器收到请求,需要请求者继续执行操作
    • 2** 成功,操作被成功接受并处理。
      • 200 服务端成功处理了请求并返回内容
    • 3** 重定向,需要进一步的操作以完成请求
      • 301 永久重定向
      • 302 临时重定向
      • 304 资源未被修改,返回一个 304 状态吗然后从本地缓存中加载请求的资源
    • 4** 客户端错误,请求包含语法错误或无法完成请求。
      • 404 (页面丢失)未找到资源
      • 403 服务器拒绝请求
      • 408 (请求超时) 服务器等候请求时发生超时
    • 5** 服务器错误,服务器在处理请求的过程中发生错误
      • 503 服务器暂时不可用
      • 504 服务器内部错误
  • HTTP 协议的特征:

    • C/S(客户端/服务器)模式:只要客户端和服务器知道如何处理数据内容,任何类型的数据
      都可以通过 HTTP 来发送,客户端和服务器指定合适的 MIME-type 内容类型。
    • 简单快速
    • 灵活
    • 无连接:限制每次连接只处理一个请求,收到客户的应答后即断开连接,可以节省传输时间。
    • 无状态:对于事物处理没有记忆能力,意味着如果后续处理需要前面的信息,则必须重传,
      这样就会导致每次连接传送的数据量增大,另一方面,在服务器不需要先前信息时它的应答就会很快。
  • get 和 post 的请求区别?

    • 区别一:
      • get 重点是从服务器上获取资源
      • post 重点是向服务器发送数据
    • 区别二:
      • get 传输数据通过 url 请求,以 field(字段)=value 的形式放在 url 后,用“?”连接,
        多个请求数据间用“&”连接,过程用户可见。
      • post 传输数据放在请求体(request body)中发送给服务器,用户不可见。
    • 区别三:
      • get 传输数据大小有限制,但效率较高
      • post 可以传输大量数据,所以上传文件用 post 方式
    • 区别四:
      • get 请求不安全,因为参数直接暴露在 url 上,不能用来传递敏感信息。
      • post 较 get 安全性较高。
    • 区别五:
      • get 方式只能支持 ASCII 字符,向服务传的中文字符可能会乱码。
      • post 支持标准字符集,可以正确传递中文字符。
    • 区别六:
      • get 在浏览器回退是无害的,而 post 会再次提交请求。
    • 区别七
      • get 请求会被浏览器主动 cache(缓存),而 post 不会除非手动设置。
  • iframe 有哪些缺点?

    • iframe 会阻塞主页面的 onload 事件

    • 通过 oIframe.contentWindow 寻找子 window 对象

    • 通过 window.parent 寻找父级窗体

    • 通过 window.top 寻找顶级窗体

    • window.location.hash 解决父页面向子页面传值

    • window.name 解决子页面向父页面传值

    • 不利于 SEO,搜索引擎的检索程序无法解读这种页面(百度 spider 不收录,浏览器对 iframe 框架的兼容问题)

    • iframe 和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载

    • 如果需要使用 iframe,最好通过 javascript 动态给 iframe 添加 src 属性值。

  • xhtml 和 html 有什么区别?

    • 性能方面

      • XHTML 兼容性好,兼容各大浏览器、手机以及 PDA,使浏览器可以快速正确编译网页
    • 书写习惯方面

      • HTML 标签不区分大小写,XHTML 所有标签必须小写

      • XHTML 必须成双成对

      • HTML 对标签顺序要求不严格,XHTML 标签顺序必须正确

      • 等等

  • html 和 xml 的区别?

    • xml 被设计用来传输和存储数据,其焦点是数据的内容

    • html 被设计用来显示数据,其焦点是数据的外观

    • html 旨在显示信息,而 xml 旨在传输信息

    • xml 在定义标记时区分大小写,而 html 不区分大小写

  • link 和@import 的区别:
    两者都是外部引用 CSS 的方式,但有一定的区别

    + link是XHTML标签,除了加载CSS外,还可以加载其他文件;@import只能加载CSS。
    
    + 解析到link时,页面会同步加载Css;@import在页面加载完后加载Css。
    
    + link是XHTML标签,无兼容问题;@import是在**CSS2.1**之后提出的,只有再IE5以上才能识别。
    
    + link可以js动态引入,@import不行
    
    + @import的最佳写法: @import url(style.css),其他写法:@import 'style.css'、
    @import "style.css"、@import url('style.css')、@import url("style.css")
    
  • viewport

1
2
3
4
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"
/>

// width 设置 viewport 宽度,为一个正整数,或字符串‘device-width’
// device-width 设备宽度
// height 设置 viewport 高度,一般设置了宽度,会自动解析出高度,可以不用设置
// initial-scale 默认缩放比例(初始缩放比例),为一个数字,可以带小数
// minimum-scale 允许用户最小缩放比例,为一个数字,可以带小数
// maximum-scale 允许用户最大缩放比例,为一个数字,可以带小数
// user-scalable 是否允许手动缩放

  • 单行文本溢出省略号

    overflow: hidden;

    text-overflow:ellipsis;

    white-space: nowrap;

  • 多行文本溢出省略号

    display: -webkit-box;

    -webkit-box-orient: vertical;

    -webkit-line-clamp: 3;

    overflow: hidden;

  • 换行标签

    word-wrap: break-word

浏览器是怎么对 HTML5 的离线存储资源进行管理和加载的?

  • 在线的情况下:浏览器发现 html 头部有 manifest 属性,会请求 manifest 文件,如果是
    第一次访问 app,浏览器会根据 manifest 文件的内容下载相应的资源并进行离线缓存。如果
    已经离线存储了,浏览器会使用离线的资源加载页面,然后浏览器会比对新的 manifest 文件
    与旧的 manifest 文件,如果文件没有发生改变,就不要做任何操作,如果文件改变,就会重新
    下载文件中的资源并进行离线存储。

  • 离线的情况下:浏览器就直接使用离线存储的资源。

如何清除 token

  • 浏览器关闭会出发 beforeunloadunload 这两个事件。

  • 浏览器刷新也会触发,还会触发load事件

  • 方案一

    1
    2
    3
    window.onbeforeunload = function () {
    localStorage.removeItem("token")
    }

    缺点: 刷新也会清空 token

  • 方案二

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    window.onunload = function() {
    localStorage.setItem("lastTime",new Date().getTime())
    }

    window.onload = function() {
    let lastTime = localStorage.getItem("lastTime");
    const interval = 3 * 1000;
    // 如果时间间隔大于3s,则清除token
    if (!lastTime || new Date().getTime() - lastTime > interval) {
    localStorage.remove("token");
    console.log("remove token");
    } else {
    console.log("time is less than not remove token");
    }
    }
  • 补充
    可以使用 sessionStorage 代替 localStorage 来存储 token,当关闭浏览器时会自动清除 token,sessionStorage 不是持久化的本地存储,而是会话级别的存储,而 localStorage 是持久化的本地存储,除非主动删除数据,否则数据是不会过期的。

npm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 举个例子:
"dependencies": {
"jquery": "^13.4.6", // 只锁定主版本号 major
"jquery": "~13.4.6", // 锁定主版本号和次版本号 major + minor
"jquery": "13.4.6", // 锁定版本
"jquery": "*", // 最新版本
}
// major: 13, minor: 4, patch: 6

$ npm info jquery // 查看 jquery 信息
$ npm view jquery versions // 查看 jquery 所有版本
$ npm list | grep gulp // 过滤 gulp
$ npm outdated // 查看过期版本
$ npm update //更新版本
$ npm cache clean --force // 清楚缓存
$ npm ls // 查看项目引用了哪些包
$ npm unpublish --force // 从npm卸载包
// 执行顺序
$ npm run script1 & npm run script2 //并行执行
$ npm run script1 && npm run script2 // 继发执行

// cross-env: 运行跨平台设置和使用环境变量的脚本
// cross-env可以设置NODE_ENV环境变量,process.env.NODE_ENV === 'production'
$ npm install --save-dev cross-env
// {
// "scripts": {
// "build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
// }
// }

$ npm config get registry // 查看当前源
$ npm config set registry https://registry.npm.taobao.org //切换镜像源

// npx
// --no-install: 让npx强制使用本地模块,不下载远程模块,如果本地不存在,就会报错
$ npx --no-install http-server
// --ignore-existing: 忽略本地的同名模块,强制安装远程模块
$ npx --ignore-existing http-server

URI 和 URL 有什么区别?

  • URL(Uniform Resource Identifier): 统一资源定位符

  • URI(Uniform Resource Locator): 统一资源标识符

    • 如:164203142_30_1.jpg
  • URN(Uniform Resource Name): 统一资源名称

    • 如:urn:isbn:9787115318893 (国际标准图书编号),类似身份证
  • URL 是 URI 的子集

日期

Date.now()  //获取当前时间毫秒数
var dt = new Date() //构造一个实例对象
dt.getTime()    //获取毫秒数
dt.getFullYear() //年
dt.getMonth()   //月(0-11)
dt.getDate()    //日 (0-31)
dt.getHours()   //小时(0-23)
dt.getMinutes() //分钟(0-59)
dt.getSeconds() //秒(0-59)
dt.getDay()     //星期几(0-6)

浏览器如何缓存

  • html meta 标签控制缓存
    • <meta http-equiv="Pragma" content="no-cahce"> //告诉浏览器当前页面不被缓存
  • http 头信息控制缓存
    • Expires ——> 过期时间
    • Cache-Control 响应头信息(no-cache、no-store、max-age、public)

Chrome 打开一个页面需要启动多少线程?分别有哪些线程?

最新Chrome浏览器包括:一个浏览器(Browser)主进程、一个GPU进程、一个网络(NetWork)
进程、多个渲染进程和多个插件进程
  • 进程:

    • 浏览器进程:主要负责界面显示、用户交互、子进程管理、同时提供存储等功能。

    • 渲染进程:核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,
      排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个
      Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。

    • GPU 进程:Chrome 刚开始的时候是没有 GPU 进程的,而 GPU 使用的使用初衷是为了实现
      3D CSS 效果,之后随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 称为
      浏览器普遍的需求,最后,Chrome 在其多进程架构上也引入了 GPU 进程。

    • 网络进程:主要负责网页的网络资源加载,之前是作为一个模块运行在浏览器进程里面
      的,最近才独立出来,称为一个单独的进程。

    • 插件进程:主要负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以
      保证插件进程崩溃不会对浏览器和页面造成影响。

  • 线程:

    • js 线程
    • UI 渲染线程
    • 事件线程
    • 定时器触发线程
    • http 请求线程
    • 插件线程

[js] 请说说写一个拖拽组件的思路及注意事项?

1
2
3
4
5
6
7
8
9
首先,其实拖拽效果的思路是很简单的。主要就是三个步骤:

1.onmousedown的时候,启动可拖拽事件,记录被拖拽元素的原始坐标参数。

2.onmousemove的时候,实时记录鼠标移动的距离,结合被拖拽元素第一阶段的坐标参数,计算并设置新的坐标值。

3.onmouseup的时候,关闭可拖拽事件,记录新的坐标值。

注意:这里主要是通过绝对定位的top和left来确定元素的位置的,因此被拖拽元素的css一定要设置绝对定位。

箭头函数

  • 箭头函数的特点

    1. 没有 arguments
    2. 无法通过 apply、call、bind 改变 this
    3. 某些箭头函数代码难以阅读

for…in 和 for…of 有什么区别

  • key 和 value

    1. for…in 遍历得到 key
    2. for…of 遍历得到 value
  • 适用于不同的数据类型

    1. 遍历对象: for…in 可以,for…of 不可以
    2. 遍历 Map、Set:for…of 可以,for…in 不可以
    3. 遍历 generator:for…of 可以,for…in 不可以
  • 可枚举 vs 可迭代

    1. for…in 用于可枚举(Object.getOwnPropertyDescriptors(obj))数据,如对象、数组、字符串
    2. for…of 用于可迭代(arr[Symbol.iterator])数据,如数组、字符串、Map、Set

for await…of 有什么作用?

  • for await…of 用于遍历多个 Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function createPromise(value) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(value);
}, 1000);
});
}

(async function () {
const p1 = createPromise(100);
const p2 = createPromise(200);
const p3 = createPromise(300);

const list = [p1, p2, p3];
const list2 = [100, 200, 300];

// ---------------同时调用----------------
// 方式一
// const res1 = await p1;
// console.log(res1);
// const res2 = await p2;
// console.log(res2);
// const res3 = await p3;
// console.log(res3);

// 方式二
// Promise.all(list).then((res) => console.log(res));

// 方式三
// for await (let res of list) {
// console.log(res);
// }

// -----------------逐步调用---------------

for (let value of list2) {
const res = await createPromise(value);
console.log(res);
}
})();

JS 严格模式有什么特点?

  • 特点(’use strict’)
    1. 全局变量必须先声明
    2. 禁止使用 with
    3. this 指向 undefined 而不是 window
    4. 函数参数不能重名
    5. eval 有单独作用域,不推荐使用

HTTP 跨域请求时为什么发送 options 请求?

options 请求是对 CORS 跨域请求之间的一次预检查,获取服务器是否允许本次请求,检查成功才会正式发起请求,是浏览器自行处理的

JS 内存泄漏如何检测?场景有哪些?

  • 垃圾回收 GC

    1. 引用计数
    2. 标记清除
  • 场景

    1. 意外的全局变量
    2. 遗忘的定时器
    3. 使用不当的闭包
    4. 遗漏的 DOM 元素
    5. 网络回调
  • 内存泄漏属于非预期的,闭包是主动行为,闭包非内存泄漏

  • 可以使用 chrome devtools 的 performance 和 memory 工具类检测 js 内存

  • 参考

vdom 真的很快吗?

  • js 直接操作 dom 才是最快的,vdom 并不快

  • 但是 vdom 是最合适“数据驱动视图”的技术方案

遍历数组,for 和 forEach 哪个快?

  • 时间复杂度都是 O(n)

  • 结论:

    1. for 更快
    2. forEach 每次都要创建一个函数来调用,而 for 不会创建函数
    3. 函数需要独立的作用域,会有额外的开销

请描述 JS Bridge 的原理

  • 什么是 JS Bridge?

    • js 无法直接调用 native API
    • 需要通过一些特定的“格式”来调用
    • 这些“格式”就统称 JS-Bridge,例如微信 JSSDK
  • JS Bridge 的常见实现方式

    • 注册全局 API
    • URL Scheme

移动端 H5 click 有 300ms 延迟,如何解决?

  • 背景:double tap to zoom

  • 初期解决方案 FastClick

    • 监听 touchend 事件(touchstart touchend 会先于 click 触发)
    • 使用自定义 DOM 事件模拟一个 click 事件
    • 把默认的 click 事件(300ms 之后触发)禁止掉
    1
    2
    3
    4
    5
    6
    7
    window.addEventListener(
    'load',
    function () {
    FastClick.attach(document.body);
    },
    false
    );
  • 现代浏览器的改进(width=device-width)

    1
    2
    3
    4
    5
    6
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="id=edge">
    <title>title</title>
    </head>
  • cookie

    • http 无状态,每次请求都要带 cookie,以帮助识别身份
    • 服务端也可以向客户端 set-cookie,cookie 大小限制 4kb
    • 默认有跨域限制:不可跨域共享、传递 cookie
  • token vs cookie

    • cookie 是 http 规范,而 token 是自定义传递
    • cookie 会默认被浏览器存储,而 token 需自己存储
    • token 默认没有跨域限制
  • JWT(JSON Web Token)

    • 前端发起登录,后端验证成功之后,返回一个加密的 token
    • 前端自行存储这个 token(其中包含了用户信息,加密了)
    • 以后访问服务端的接口,都带着这个 token,作为用户信息

Session 和 JWT 哪个更好?

  • session

    • 优点
      • 原理简单,易于学习
      • 用户信息存储在服务端,可以快速封禁某个用户
    • 缺点
      • 占用服务端内存,硬件成本高
      • 多进程,多服务器时,不好同步-需要使用第三方缓存,如 redis
      • 默认有跨域限制
  • JWT

    • 优点
      • 不占用服务器内存
      • 多进程、多服务器不受影响
      • 没有跨域限制
    • 缺点
      • 用户信息存储在客户端,无法快速封禁某用户
      • 万一服务器密钥被泄漏,则用户信息全部丢失
      • token 体积一般大于 cookie,会增加请求的数据量
  • 答案

    • 如有严格管理用户信息的需求(保密、快速封禁),推荐 session
    • 如没有特殊要求,则使用 JWT

如何实现 SSO 单点登录?

  • 基于 cookie(主域名相同)

    • cookie 默认不可跨域共享,但有些情况下可设置共享
    • 主域名相同,如www.baidu.com,image.baidu.com
    • 设置 cookie domain 为主域名,即可共享 cookie
  • SSO(主域名不相同,cookie 无法共享)

    • sso

Map 和 WeakMap 的区别和应用场景?

  • Map:

    • key 可以是任意数据类型
    • key 是强引用,只要键不释放,就会一直占着内存不会被 GC
    • 能轻易转化为数据(扩展运算符),weakmap 做不到
  • WeakMap:

    • key 只能是非 null 的对象引用
    • key 是弱引用,没有其他引用存在时会被 GC
    • key 随时会被回收,所以 key 不可枚举,没有 size 等属性
  • Map 场景:

    • 频繁的读写和查询
    • 键值复杂的情况

重绘 repaint 重排 reflow(回流) 有什么区别?

  • 重绘 repaint

    • 元素外观改变,如颜色、背景色
    • 但元素的尺寸、定位不变,不会影响到其他元素的位置
  • 重排 relfow

    • 重新计算尺寸和布局,可能会影响其他元素的位置
    • 如元素高度增加,可能会使相邻元素位置下移
  • 区别

    • 重排比重绘影响更大,消耗更大
    • 所以,要尽量避免无意义的重排
  • 减少重排的方法

    • 集中修改样式,或直接切换 css、class
    • 修改之前先设置 display: none,脱离文档流
    • 使用 BFC 特性,不影响其他元素位置
    • 频发触发(resize、scroll)使用节流和防抖
    • 使用 createDocumentFragment 批量操作 DOM
    • 优化动画,使用 CSS3 和 requestAnimationFrame

如何实现网页多标签通讯?

  • 使用 WebSocket

    • 无跨域限制
    • 需要服务端支持,成本高
  • localStorage(跨域不共享)

    • 同域的 A 和 B 两个页面
    • A 页面设置 localStorage
    • B 页面可监听到 localStorage 值的修改
  • SharedWorker(必须同域)

    • SharedWorker 是 WebWorker 的一种
    • WebWorker 可开启子进程执行 JS,但不能操作 DOM
    • SharedWorker 可单独开启一个进程,用于同域页面通讯

网页和 iframe 如何通讯?

  • 使用 postMessage 通讯
  • 注意跨域的限制和判断
1
2
3
4
5
6
7
8
9
// 父传子
window.iframe1.contentWindow.postMessage('hello', '*');
// 子传父
window.parent.postMessage('world', '*');
// 接收
window.addEventListener('message', (event) => {
console.log(event.origin);
console.log(event.data);
});

请描述 koa2 洋葱圈模型?

  • koa2

    • 一个简约、流行的 nodejs 框架
    • 通过中间件组织代码
    • 多个中间件以“洋葱圈模型”执行
  • 代码执行过程

    • koa1
  • 洋葱圈模型(类似捕获冒泡)

    • koa2

H5 页面如何进行首屏优化?

  • 路由懒加载

    • 适用于 SPA
    • 路由拆分,优先保证首页加载
  • 服务端渲染 SSR

    • 传统前后端分离(SPA)渲染页面的过程复杂
    • SSR 渲染页面过程简单,所有性能好
    • 如果是纯 H5 页面,SSR 是性能优化的终极方案
  • App 预取

    • 如果 H5 在 App WebView 中展示,可使用 App 预取
    • 用户访问列表页面时,App 预加载文章首屏内容
    • 用户进入 H5 页面,直接从 App 中获取内容,瞬间展示首屏
  • 分页

    • 针对列表页
    • 默认只展示第一页内容
    • 上滑加载更多
  • 图片懒加载 lazyload

    • 针对详情页
    • 默认只展示文本内容,然后出发图片懒加载
    • 注意:提前设置图片尺寸,尽量只重绘不重排
  • Hybrid

    • 提前将 HTML、JS、CSS 下载到 App 内部
    • 在 App webview 中使用 file:// 协议加载页面文件
    • 再用 Ajax 获取内容并展示(也结合 App 预取)

后端一次性返回 10w 条数据,你该如何渲染?

  • 设计不合理,后端调整(分页)

  • 自定义中间层

    • 自定义 nodejs 中间层,获取并拆分这 10w 条数据
    • 前端对接 nodejs 中间层,而不是服务端
    • 成本比较高
  • 虚拟列表

    • 只渲染可视区域

如果一个 H5 很慢,你该如何排查性能问题?

  • 前端性能指标

    • First Paint(FP)
    • First ContentFul Paint(FCP)
    • DomContentLoaded(DCL)
    • Largest Contentful Paint(LCP)
    • Load(L)
  • Chrome devtools

    • Performance 可查看上述性能指标,并有网页快照
    • Network 可以查看各个资源的加载时间
  • lighthouse(第三方性能评测工具)

    1
    2
    // terminal
    lighthouse https://www.imooc.com/ --view --preset=desktop
  • 通过以上工具来判读是加载慢还是渲染慢

    • 加载慢
      • 优化服务端硬件配置,使用 CDN
      • 路由懒加载,大组件异步加载-减少主包的体积
      • 优化 http 缓存策略
    • 渲染慢
      • 优化服务端接口(如 ajax 获取数据慢)
      • 优化全段组件内部逻辑
      • 服务端渲染 SSR

BOM API

  • navigator
  • screen
  • location
  • history

为何要将 css 文件放在 head 标签中呢?

  • css 放在 body 标签尾部时,DOMTree 构建完成之后便开始构建 RenderTree,并计算布局渲染网页,等加载解析完 css 之后,开始构建 CSSOMTree,并和 DOMTree 重新构建 RenderTree,重新计算布局渲染网页
  • css 放在 head 中,先加载 css,之后解析 css 构建 CSSOMTree,同时构建 DOMTree,CSSOMTree 和 DOMTree 都构建完成之后开始构建 Render Tree,计算布局网页
  • 两者对比,css 放在 head 中比放在 body 标签尾部少了一次构建 RenderTree,一次计算布局和一次渲染网页,因此性能会更好,并且 css 放在 body 标签尾部会在网页中短暂出现裸奔的 html,不利于用户体验

为什么建议把 script 标签放在 body 最后?

  • js 的下载和执行会阻塞 DOMTree 的构建,即会中断 DOMTree 的更新,所以如果把 script 标签放在首屏范围内的 HTML 代码中会截断首屏的内容。
  • 普通 script 标签放在 body 底部,做与不做 async 或者 defer 处理都不会影响首屏时间,但是会影响 DomContentLoad 和 load 的时间,进而影响依赖他们的代码的执行的开始时间
1
2
3
4
5
6
window.addEventListener('load', function () {
// 页面的全部资源加载完成后才会执行,包括图片、视频等
});
document.addEventListener('DOMContentLoaded', function () {
// DOM 渲染完成后即可执行,此时图片、视频可能还没有加载完
});

ES6 新增的声明方式

  • let、const
    • 不属于顶层对象 window
    • 不允许重复声明
    • 不存在变量提升
    • 暂时性死区(不能在变量声明之前去使用)
    • 块级作用域

如何给所有 async 函数添加 try/catch?

babel 插件:

  1. 借助 AST 抽象语法树,遍历查找代码中的 await 关键字
    · 词法分析、语法分析生成 AST 抽象语法树

  2. 找到 await 节点后,从父路径中查找声明的 async 函数,获取该函数的 body(函数中包含的代码)

  3. 创建 try/catch 语句,将原来的 async 的 body 放入其中
    · 通过 bebel-template 插件以字符串形式的代码构建 AST 树节点,生成 try/catch 节点

  4. 最后将 try/catch 语句替换 async 的 body

全局捕获 Promise 类型错误:

  1. window.addEventListener(‘unhandledrejection’, callback)

async/await 和 promise 的区别?

  • promise 的出现是为了解决传统 callback 函数导致的回调地狱问题,但是它本身的语法造成纵向的回调链,遇到复杂的业务场景语法也不美观。

  • async await 可以说是改良版的 promise,或者说是 promise 的语法糖,可以让异步代码同步化,语法更加美观。

  • async/await 本质也是 Generator 函数的语法糖,使得异步操作更加方便。就是 Generator 函数(*)的替换为 async、yield 替换为 await

    • Generator 需要调用 next 执行,而 async 函数内置执行器,函数调用后自动执行
    • async/await = Generator 函数 + 内置自动执行器

Object.create(proto, propertiesObject)、new Object()和{}有什么区别?

  1. {}创建对象时,不会调用构造函数,js 引擎会创建一个空对象,然后改变 this 指向新创建的对象
  2. new 则需要经历:
    2.1 先创建空对象
    2.2 设置原型链,设置新对象的 constructor 属性为构造函数的名称,设置新对象的proto指向构造函数的 prototype 对象
    2.3 调用新对象并调整 this 指向新对象
    2.4 最后如果返回值为非 null 对象直接返回该返回值,否则返回新对象
  3. Object.create 方法支持两个参数,第一个参数作为新创建对象的原型,第二个为可选参数作为新创建对象的属性
  4. {}字面量创建对象的性能比 new 和 Object.create 要好
  5. {} 和 new 所创建的对象继承 Object 原型链的属性和方法,Object.create 通过第一个参数指定原型,可以通过 Object.create(Object.prototype)
  6. Object.create(null)创建的对象没有任何属性和方法

document.ready 和 window.onload 的区别?

  1. document.ready 表示 dom 文档解析完成,但是不包含异步脚本和图片等的加载,onload 需要所有文件都加载完成
  2. ready 可以执行多次,onload 只会后者覆盖前者
  3. ready 快于 onload

为什么 setTimeout 有最小 4ms 延迟?

原因在于如果浏览器允许 0ms,会导致 javascript 引擎过度循环,因为浏览器本身也是建立在 event loop 之上的,如果 js 引擎通过 0ms timer 不断的唤起系统,那么 event loop 就会阻塞,导致 CPU spining(快速旋转),有个故事就是英特尔团队发现 chrome 不正常的电量消耗,就是因为 0ms timer 导致 CPU spining,后果就是计算机没办法进入低功耗模式,所以耗电特别快。后来 chrome 将 timer 的时间间隔做了限制为 1ms。

不同浏览器的最低延迟不一样,比如 chrome 的最低时延是 1ms,而如果 timer 嵌套层级很多,那么最低是 4ms,具体嵌套层级的阈值不同浏览器也不一致,HTML Standard 规定是 >5, chrome 是 >=5;

如果判断是 PC 端还是移动端

  • 屏幕宽度:window.screen、window.innerWidth
  • 屏幕方向:window.orientation pc 端为 undefined
  • navigator.userAgent 正则去匹配

常见移动端适配方案?

  • 媒体查询@media
    • 分别为不同屏幕尺寸的移动设备编写不同尺寸的 css 属性
  • rem 适配方案
    • 使用 flexible 阿里早期开源的移动端适配解决方案
    • postcss-pxtorem 插件实现 px 到 rem 的转换
  • viewport 适配方案
    • 设置 meta 标签
    • 使用 postcss-px-to-viewport 插件将 px 自动转换为 vw
    • 可以通过插件的 ignoring 特性标注不需要转换的属性

缺点:就是转换的时候如不能完全整除就会产生像素差

如何判断JS对象是否为空?

  1. 结合getOwnPropertySymbols 和 getOwnPropertyNames
1
2
3
4
5
6
const a = {[Symbol()]: 'a'}
const b = {a: 'a'}
const c = {}
console.log(Object.getOwnPropertyNames(a).length === 0 && Object.getOwnPropertySymbols(a).length === 0 ); // false
console.log(Object.getOwnPropertyNames(b).length === 0 && Object.getOwnPropertySymbols(b).length === 0 ); // false
console.log(Object.getOwnPropertyNames(c).length === 0 && Object.getOwnPropertySymbols(c).length === 0 ); // true
  1. Reflect.ownKeys()
1
2
3
4
5
6
7

const a = {[Symbol()]: 'a'}
const b = {a: 'a'}
const c = {}
console.log(Reflect.ownKeys(a).length === 0) // false
console.log(Reflect.ownKeys(b).length === 0) // false
console.log(Reflect.ownKeys(c).length === 0) // true

评论