# 2020 / 3 / 7

内容出自掘友「杨珏成」 入口:https://juejin.im/post/5d70ff205188253e4b2f07bd#heading-17

# Bilibili校招

# 1 / 详述es5 es6中的作用域和闭包 (es5全局+函数级,函数化闭包,es6块级)

作用域:可以理解为当前上下文中声明的变量和函数的作用范围,它规定了如何查找变量,即当前执行代码对变量的访问权限

作用域链:由当前执行环境与上层环境的一系列变量对象组成,因此可以通过作用域链访问到父级里面声明的变量和函数,保证了当前执行环境中,有权访问的变量和函数是有序的

  • Es5 作用域 > Es5 中作用域分为全局作用域和函数作用域
    • 全局作用域下声明的变量在其他作用域下也可以使用,但函数作用域下声明的变量,无法在全局作用域下使用
    • Es5 作用域存在变量提升,所谓变量提升,就是一个声明在函数体内部都是可见的(仅var),即可以先使用后声明。(函数声明提升优于变量声明提升)
  • Es6 作用域 > Es6 中新增了块级作用域
    • 由一对花括号 {} 中的语句集都属于一个块,在这个 {} 代码块中定义的所有变量和函数在这个代码块之外都是不可见的
    • 由于Es6之前没有块的概念,因此会带了很多不合理的场景
      • 变量提升导致内层变量可能覆盖外层变量
      • 用来计数的循环变量被泄漏为全局变量
    • Es6 提供了 let / const 来实现块级作用域,由 let / const 声明的变量存在暂时性死区,这一点与 var 不同
      • let 不存在变量提升,它所使用的变量必须在声明之后,否则报错
      • 暂时性死区:只要一进入当前作用域,所要使用得变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量(即 let / const 命令声明变量之前,该变量都是不可用的)

闭包

闭包就是一个函数A内部存在另一个函数B,函数B可以访问到函数A中的变量和方法,此时函数B就是闭包

· 闭包存在的意义: 在Js中,闭包存在的意义就是让我们可以间接访问到函数内部的变量(函数内部变量的引用也会在闭包中,不会因为执行完函数而被销毁,但过多的闭包会造成内存泄漏)

闭包的三个特性:

  • 闭包可以访问当前函数以外的变量
  • 闭包可以更新外部变量的值
  • 即使外部函数已经返回,闭包仍能访问外部函数定义的变量

# 2 / 详述输入url到页面渲染完成

  • 输入URL
  • 浏览器根据域名(DNS)进行解析,查询IP地址
    • DNS查询步骤:浏览器缓存 → 系统缓存 → 路由器缓存 → ISP服务器缓存 → 域名服务器递归查询
    • 域名服务器递归查询:根域名服务器 → 顶级域名服务器 → 权威DNS域名服务器 → 相应主机IP地址
  • 根据IP地址和默认端口,与服务器建立 TCP 连接(三次握手)
  • 发送Http请求,服务器处理请求,返回响应结果
  • 释放 TCP 连接(四次挥手)
  • 浏览器将得到的 Html 文本渲染出来
    • 通过解析 Html 代码构建 Dom 树,然后合并 Css 树构建 render 渲染树,最后绘制 render 树并显示

# 3 / 详述js异步机制Event Loop,MacroTask和MicroTask

从单线程说起,Javascript作为主要运行在浏览器的脚本语言,主要用途是操作Dom,如果Javascript存在多个线程,当多个线程同时操作同一个Dom,那这次浏览器应该听那一个线程?为避免这个问题,Javascript必须是单线程语言

由于Javascript是单线程的缘故,当遇到异步事件时,不可能一直等待异步执行完成后再继续往下执行,这样会造成很大的资源浪费

Javascript中存在执行栈、主线程、任务队列的概念

  • 执行栈:当执行某个函数、事件时,就会进入执行栈中 (用于管理同步任务)
  • 主线程:主线程规定现在执行执行栈中的哪个事件
    • 主线程循环:主线程会不停地从执行栈中获取事件,然后执行完所有栈中的同步代码,当主线程将执行栈中的所有代码都执行完,就会去查看任务队列中是否存在任务,如果存在,那么主线程会依次执行那些任务队列中的回调函数
  • 任务队列:当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是将这个事件挂在与执行栈不同的队列中,这个队列就较任务队列(用于管理异步任务)

对于Javascript异步执行,它的机制如下

  • 所有同步任务依次存入执行栈中,遇到异步任务,则将回调放入任务队列中
  • 当执行栈中所有同步任务执行完毕,就会去查看任务队列,那些异步任务对应的回调,结束等待状态,进入执行栈并开始执行
  • 主线程不断重复第二点

关于异步任务,可以分为两类 - 宏任务 与 微任务。不同类型的API注册的任务会依次进入到各自对应的队列中,然后等待事件循环将它们依次压入执行栈

  • 宏任务 MacroTask :script、setTimeout、setInterval、setImmediate、UI渲染、I/O流操作、postMessage、MessageChannel
  • 微任务 MicroTask :Promise、MutationObserver、process.nextTick

在事件循环EventLoop中,每一次循环称为tick,每一次tick的任务如下(宏任务 → 微任务 → 下一个宏任务)

  • 执行栈选择最先进入队列的宏任务(一般为script),如果有则执行
  • 执行完当前宏任务后,检查是否存在微任务,如果存在则不停执行,直至清空微任务队列
  • 更新渲染(每一次事件循环,浏览器都可能会去更新渲染)
  • 重复以上步骤

# 4 / Promise.all的用法

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

const p = Promise.all([p1,p2,p3])
1

Promise.all()接受一个数组作为参数,p1,p2,p3都是Promise实例,如果不是,就会先调用Promise.resolve(),将参数转为Promise实例,再进一步处理。

另外Promise.all()的参数可以不是数组,但必须是具有Iterator接口,且返回的每个成员都是Promise实例

上面的代码中,p的状态由p1,p2,p3决定,分为两种情况

  • 只要p1,p2,p3的状态都变成 fulfilled,p 的状态才会变成 fulfilled,并且 p1,p2,p3 的返回值组成一个数组,传递给 p 的回调函数
  • 只要p1,p2,p3之中有一个被 rejected,p的状态就变成 rejected,并且第一个被 rejected 的实例的返回值,会传递给 p 的回调函数

# 5 / 如何让Promise.all在抛出异常后依然有效

可以把异常转换为普通的输出,然后在 Promise.all().then()里面分析所有的输出,再决定哪些是异常输出,哪些是正常输出

Promise.all([p1,p2,p3].map(p => {p.catch(e => {...})}))
    .then(res => {...})
    .catch(er => {...})
1
2
3

# 6 / 什么是VueX

vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证以一种可预测的方法发生变化

追问:VueX中action和mutation的区别

两者功能大致相同,都是用于提交更新数据,mutation是同步提交,而action是异步提交(提交的是mutation,而非直接变更状态),因此action可以包含任意异步操作

# 7 / 详述Vue的双向数据绑定原理

Vue 主要通过以下4个步骤实现数据双向绑定

  • 实现一个监听器(Observer):对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()在属性上都加上getter和setter,这样之后,给对象的某个值赋值,就会触发setter,那么就能监听到数据的变化
  • 实现一个解析器(Compile):解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定上更新函数,添加监听数据的订阅者,一旦数据发送变化,就会收到通知,调用绑定好的更新函数进行数据更新
  • 实现一个订阅者(Watcher):订阅者是监听器和解析器之间通信的桥梁,主要任务是订阅Observer(监听器)中的属性值变化的消息,当收到属性值变化的消息后,就会去通知解析器,解析器收到通知触发更新函数
  • 实现一个订阅器(Dep):订阅器采用发布订阅的设计模式,用来收集订阅者,对监听器Observer和订阅者Watcher进行统一管理

# 8 / Vue的优势

Vue.js是一个轻巧,高性能,可组件化的MVVM框架,采用SPA单页面,页面切换速度快,但首屏加载稍慢

简而言之:Vue.js是一个构建数据驱动的web界面的渐进式框架。Vue.js的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统

vue的优势:

  • 轻量级框架
  • 双向数据绑定
  • 组件化
  • 视图
  • 数据和结构分离
  • 虚拟Dom
  • 运行速度快

# 9 / 如何实现SEO优化 ?

搜索引擎优化,简称SEO。是按照搜索引擎给出的优化建议,以增强网站核心价值为目标,从网站结构,内容建设方案,用户互动传播等角度进行合理规划,以改善网站在搜索引擎中的表现。

前端实现SEO优化,可以从3个方面入手

  • Html
    • 避免冗余代码,例如去除空格字符
    • 合理利用语义化标签
    • 合理的嵌套规则,避免行元素内嵌块元素
    • img标签提供title属性和alt属性
    • a标签提供title属性
    • 优化Meta标签
    • <title>页面标题优化
    • 合理使用注释
    • 尽量减少 iframe 的使用
  • Css
    • 避免将 css 代码写在标签内(内联样式)
    • 避免使用通配符选择器
    • 尽量使用css代码缩写
    • 利用继承,减少相同代码
    • 避免产生大量类选择器
    • 适当的代码注释
    • 谨慎使用display:none
  • Javascript
    • 采用外部引入的方式
    • 合理合并Js代码,减少服务器压力
    • 减少页面重绘,减少作用域链上的查找次数
    • 减少冗余代码

# 10 / 详述回流和重绘优化?

  • 重绘:当一个元素自身的宽高,布局,及显示或隐藏没有改变,而只是改变了元素的外观风格时,就产生了重绘
  • 回流(重排):当渲染树的一部分或者全部元素因改变了自身的宽高,布局,显示和隐藏,或元素内部的文字结构发生了变化,导致需要重新构建页面的时候,就产生了回流

优化:减少回流和重绘的次数,将元素移出Dom树再变更

  • 减少回流和重绘次数,可以通过合并多次对Dom和样式的修改,然后一次性处理掉
  • 批量修改Dom:当我们需要对Dom做一系列修改的时候,可以通过以下步骤减少回流和重绘
    • 是元素脱离文档流
    • 对其进行多次修改
    • 将元素带回到文档流中
  • 避免触发同步布局事件
  • 对于复杂动画效果,使用绝对定位让其脱离文档流

# 11 / 详述防抖和节流优化

如果事件处理函数调用频率无限制,会加重浏览器的负担,导致用户体验非常糟糕,可以通过 debounce 和 throttle 的方式来减少调用频率,同时又不影响实际效果

  • 防抖:当持续触发事件时,一定时间段内没有再次触发事件,事件才执行,如果设定的时间段内又触发了事件,就重新开始延时(即设定的时间内触发事件将无效)
function debounce(handle, delay = 500){
    let timeout = null // 存放定时器
    return function(...args){
        clearTimeout(timeout) // 清除定时器
        timeout = setTimeout( () => {
            handle.apply(this,args)
        }, delay)
    }
}
1
2
3
4
5
6
7
8
9
  • 节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数(通过判断是否到达一定条件来触发函数)
function throttle(handle, delay = 500){
    let flag = true
    return function(...args){
        if(!flag) return
        flag = false
        
        setTimeout( () => {
            handle.apply(this, args)
            flag = true
        }, delay)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 12 / 简述ES6新特性

  • 箭头函数
  • Class
  • 模板字符串
  • 加强的对象字面量
  • 对象的解构赋值
  • Promise
  • Generator
  • Es6模块
  • Symbol类型
  • Proxy代理
  • Set & Map
  • 函数默认参数
  • rest & 展开运算符...
  • 块级作用域

# 13 / 简述箭头函数特性

  • 箭头函数没有自己的this,函数体内的this值,指向定义时所在的作用域的this(即与外层环境的this一致)
  • 不可以当作构造函数使用
  • 不可以使用 arguments 对象

# 14 / webpack打包如何优化

  • 使用CDN资源,减少服务器压力
  • 按需加载第三方资源(例如Ui库)
  • 使用各类loader对不同类型的文件进行压缩

# 阿里校招一面

# 1 / 列举几个css中可继承和不可继承的元素

  • 可继承
    • font-size
    • font-weight
    • color 不可继承
    • margin
    • padding
    • border
    • width
    • height

# 2 / 用css选中列表第二项

li:nth-child(2){
    // ...
}
1
2
3

# 3 / 伪类和伪元素的区别

  • 伪类选择元素基于的是当前元素处于的状态,或者说元素当前所具有的特性
    • 由于状态是动态的,所以一个元素达到一个特定状态时,它可能得到伪类的样式,当状态变更时,它又会失去这个伪类样式
    • 伪类的功能与class类似,但它是基于文档之外的抽象,所以叫做伪类(:first-child, :link, :visited, :hover, :focus ···)
  • 伪元素是对元素中的特定内容进行操作,它所操作的层次比伪类更深一层,也因此它的动态性比伪类低得多,它控制的内容实际上与元素是相同的
    • 伪元素也是基于元素的抽象,并不存在于文档中,所以叫伪元素
    • ( :first-line, :first-letter, :before, :after)

# 4 / h5字体如何自适应屏幕

  • 媒体查询(设置 meta 的 name 为 viewport)
  • 采用css3新增的 rem 作为单位

# 5 / rpx是什么? rem是什么? vw、vh又是什么?

  • rpx:可以根据屏幕宽度进行自适应(rpx是小程序中使用的单位)
  • rem:相对于根元素的字体大小的单位(区别em,em为相对于父元素的字体大小的单位)
  • vw:是一种视窗单位,也是相对单位。相对于视口的宽度,视口被均分为100单位的vw(1vw永远等于当前视口宽度的百分之一)
  • vh:是一种视窗单位,也是相对单位。相对于视口的高度,视口被均分为100单位的vh(1vh永远等于当前视口高度的百分之一)

vw指向宽度,vh指向高度

# 6 / 什么情况下css会使用gpu加速

当使用 css 动画时,会使用gpu加速

# 7 / css filter是什么?

filter(滤镜),定义元素的可视效果(模糊/饱和度),通常用在img上

# 8 / 详述meta标签的作用

<meta>元素提供有关页面的元信息,比如针对搜索引擎和更新频度的描述和管检测,有利于SEO

<meta>的具体作用

  • 指定 名/值对 定义元数据
  • 声明字符编码
  • 模拟 Http 标头字段(http-equiv)

# 9 / position默认值和所有可能值

position 默认值为 static,其所有可能值如下:

  • relative
  • absolute
  • fixed
  • inherit
  • static

# 10 / 什么是 Scss & Less?

Scss和Less都属于Css预编译器,是一种CSS的开发工具,提供了许多便利的写法,大大节省了设计者的时间,使得CSS的开发,变得简单和可维护

# 11 / css动画最小间隔是多少?

多数显示器默认频率是60Hz,即1秒刷新60次,所以理论上最小间隔为1/60*1000ms = 16.7ms

# 12 / shadow Dom 是什么?

Shadow DOM是HTML的一个规范 ,它允许浏览器开发者封装自己的HTML标签、CSS样式和特定的javascript代码

# 13 / Svg 和 Canvas 的区别是什么?

两者都是属于2D作图,其中 Svg 属于矢量图,而 Canvas 属于位图, Canvas是逐像素进行渲染的,比较适合游戏

  • Canvas
    • 通过 JavaScript 来绘制2D图形
    • 逐像素进行渲染
    • 其位置发送改变,就会重新进行绘制
  • Svg
    • 一种通过 Xml 描述2D图型的语言
    • Svg基于Xml意味着,Svg Dom中的每个元素都是可以操作的,可以为某个元素附加 Javascript 事件
    • 在 Svg 中,每个被绘制的图形均被视为对象。如果 Svg 对象的属性发生变化,那么浏览器会自动重现图形

# 14 / dom渲染的性能损耗在哪里

操作 Dom 引起浏览器的 重绘回流(重排)

# 15 / 如何监听img加载完成?

通过img标签的 onload 事件,我们可以在onload事件的回调函数里处理需要在图片加载完成之后的动作

# 16 / 详述vue都能解决什么问题?

主观题,建议大家自行组织语言

# 17 / vue中data为什么是函数,而不是对象?

在vue中,组件都是需要复用的,一个组件创建好后,可以在很多地方重复使用,而不管复用多少次,组件内的data都必须是相互隔离,互不影响的,如果data以对象的形式存在,由于Javascript中对象是引用类型,作用域没有隔离,因此各组件间data会相互影响,所以必须以函数形式存在

而根组件不需要复用,因此它可以以对象形式存在

# 18 / 为什么需要深拷贝?

浅拷贝只能解决第一层的问题,如果对象的属性还是对象的话,该属性两者会共享相同的地址,那么当我们修改其中一个对象的对象属性,另一个对象对应的对象属性也会被修改

实现深拷贝

  • JSON.parse(JSON.stringify(obj))
  • lodash库中的cloneDeep()

# 19 / 进程与线程的区别

  • 进程:一个内存中运行的应用程序,每一个进程都有自己独立的一块内存空间,一个进程可以拥有多个线程
  • 线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行,一个进程至少拥有一个线程,一个进程可以运行多个线程,多个线程之间可以共享数据

区别:

  • 调度:进程是拥有资源的基本单位,线程是独立调度的基本单位
  • 资源:进程拥有资源,线程不拥有资源,但线程可以共享其隶属于进程的资源
  • 并发:进程可以并发执行,同一进程的多个线程也可以并发执行,大大提高吞吐量
  • 系统开销:线程之间的同步与通信比较容易实现
  • 各进程的地址空间之间相互独立,而同一进程的各个线程之间可以共享进程的资源,某进程内的线程对于其他进程则不可见

# 20 / 详述http 1.0, 1.1, 2.0的区别

  • Http1.0 与 Http1.1 的区别
    • 长连接:Http1.1支持长连接和请求的流水线处理,在一个TCP连接上可以传送多个Http请求,减少了TCP连接和关闭的消耗和延迟,一定程度上弥补了Http1.0每次请求都要创建连接的缺点
    • 节约带宽:Http1.0中存在一些浪费带宽的现象,例如客户端只需要某个对象的一部分,而服务器却将整个对象返回,并且不支持断点续传。而Http1.1支持只发送Header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,客户端接收到100后才开始把请求body发送到服务器,而如果返回401,客户端就可以不用发送请求body,从而节约带宽
    • Host域:在Http1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中URL并没有传递主机名,Http1.0没有host域;而Http1.1的请求消息和响应消息都支持host域,且请求消息中如果没有host域会报告一个错误(400 Bad Request)
    • 错误通知管理:在Http1.1中新增24个错误状态码
  • Http1.1 与 Http2.0 的区别
    • 多路复用:Http2.0使用多路复用的技术,做到同一个连接并发处理多个请求。而Http1.1想要做到并发处理多个请求,则需要创建多个TCP
    • 头部数据压缩:在Http1.1中,请求和响应都是由状态行,请求/响应头部,消息主体三部分组成。一般而言,消息主体都会经过压缩,但状态行和头部却不会经过任何压缩,以纯文本传输。而Http2.0对header的数据进行压缩,使数据体积更小,上传更快
    • 服务器推送:Http2.0引入了服务器推送的功能,它允许服务器推送资源给浏览器,在浏览器明确请求之前,避免客户端再次创建连接发送请求到服务器获取。

# 21 / 详述TCP如何保证传输完整性?

Tcp通过以下方式来提供可靠性

  • 传输数据被分割成Tcp认为最适合发送的数据块
  • 当Tcp发出一个段后,启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段(超时重发)
  • 当Tcp收到发自Tcp连接另一端的数据,它将发送一个确认,这个确认不是立即发送,通常推迟几分之一秒的时间用来对包的完整性进行校验
  • 对两端数据进行检验,目的是检测数据在传输过程中是否发生变化,如果收到段的检验和有差错。Tcp将丢弃这个报文段和不确认收到此报文段。
  • (校验出包有错,丢弃报文段,不返回响应,Tcp发送数据端,超时会重发)
  • 对于失序数据,会进行重新排序,然后才交给应用层
  • 对于重复数据,会丢弃重复的数据

# 22 / UDP和TCP有什么区别?

  • TCP面向连接,而UDP是无连接的(即发送数据之前,TCP需先建立连接,而UDP不用)
  • TCP提供可靠的数据(TCP传送的数据无差错,不丢失,不重复,并且是有序的)
  • UDP具有更好的实时性,工作效率高,适合高传输率和实时性较高的传输
  • TCP仅支持点到点,而UDP支持一对一,一对多,多对一,多对多的传输
  • TCP对系统资源要求较多

# 23 / WebSocket是基于什么协议连接?

webSocket基于TCP的应用层协议,与Http不同,它是双向通信模式(通信双方都可以主动发送请求)