# 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])
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 => {...})
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)
}
}
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)
}
}
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){
// ...
}
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不同,它是双向通信模式(通信双方都可以主动发送请求)