前端面试题
一些面试题
css 部分
css 盒模型
对一个文档进行布局时候, 浏览器渲染引擎会根据css 基础框盒模型
将所有元素都表示为一个矩形盒子, css 决定了盒子的大小位置以及其他一些属性.
盒子由四个部分或称区域组成 内容边界
内边距边界
边框边界
外边框边界
通过下面的 css 来改变应该如何计算一个元素的总宽度和总高度
1 | box-sizing: content-box; 默认值 |
绝对定位和相对定位的区别
- 绝对定位相对于元素最近的已定位的祖先元素进行定位
- 相对定位是根据元素在文档中的初始位置进行定位
水平垂直居中
- flex
1 | display: flex; |
0.5px 的线
1 | height: 1px; |
flex
是一个简写属性, 规定了 flex-grow flex-shrink flex-basis
- flex-grow 他指定了 flex 容器中剩余的多少空间应该分配给项目
- flex-shrink 制定了 flex 元素的收缩规则, flex 元素仅在默认宽度和大于容器的是才会发生收缩,其收缩的大小一句是 flex-shrink
- flex-basis 指定了 flex 元素在主轴方向上的初始大小, 如果不是用 box-sizing 改变盒模型的话,那么这个属性就决定了 flex 元素的内容盒的尺寸
javascript
call apply bind
三个函数都是重新指定函数内部的 this
- call 方法接收的是一个包含等多个参数的
列表
- apply 方法接收的是一个包含税多个参数的
数组
- bind 方法和 call 类似,但是 bind 是返回一个新的函数
闭包
函数和对其周围状态的引用捆绑在一起构成的闭包, 从而实现让你在可以从内部函数访问外部函数作用域.
this
- 无论严格模式还是非严格模式,在全局环境中 this 都指向全局对象
1 | // 在控制台输入 |
- 在函数内执行, 严格模式和非严格模式会有差别
1 | function t1() { |
在 ES 模块中所有代码运行于严格模式之下
- this 的值取决于函数/对象被调用的方式
1 | const a = { |
- bind this
一般可以使用 函数原型上的 bind 方法来为一个方法指定其中的 this
1 | function a() { |
简易实现 bind 方法
1 | function myBind(self, fn, ...args) { |
var let const
var 会有变量提升
let const 会有临时性死区
loop 里面 var 和 let 的差异, var 始终是一个变量, let 每次循环的变量都是新的
JavaScript 原型, 原型链
JavaScript 只有一种结构, 对象, 每个实例对象又有一个私有属性 __proto__
, 它指向他的构造函数的原型对象 prototype
, 而该原型对象也有一个自己的原型对象 __proto__
, 一直层层向上,直到找到 null, 而根据定义 null 没有原型,到此为止.
而所有的 JavaScript 对象都是位于顶端的 Object 的实例.
1 | class A { |
事件委托 冒泡 捕获
对于比较多的元素进行绑定事件,可以不用一个一个的为元素进行绑定,将原本的事件委托绑定给父元素, 让父元素利用事件冒泡的特性从而可以处理, 在事件内可以通过 event.target
可以知道实际发生事件的位置和元素.
冒泡和捕获
事件冒泡是由微软提出的, 主张事件应该从目标元素一直向上触发,一直传播到 document 之上 例如
div -> body -> html -> document
而事件捕获是由王景公司提出的,主张事件从最外层一直向最内侧进行传播 例如
document -> html -> body -> div
之后 w3c 委员会进行了折中处理, 先捕获然后再冒泡
1 | target.addEventListener(type, listener, useCapture); |
第三个参数 useCapture 如果是 true 那么就会在捕获阶段触发, 默认是 false
JavaScript 异步处理方法 async/await 的实现原理
异步处理方法:
- 使用回调函数
- 事件监听, 通过创建一个自定义事件, 来监听指定事件,监听到之后就运行代码
- Promise
async await 是 generator 的语法糖. 内置了执行器.不需要在手动 next()
简单的说 async/await === Promise + generator + yield
防抖和节流的区别是什么
防抖是将多次执行变为指定事件内的最后一次执行, 而节流是变成固定时间只执行一次.
React
React 的 key 的作用
React 更新主要基于下面三种策略:
- 如果层级相同的节点 DOM 结构发生了变化 例如从 div 变为了 p 那么直接卸载重新创建
- 如果层级相同的节点 DOM 结构没有发生变化, 但是属性变了 例如 1变成了1那么 React 只会更新这个属性
- 所有同一级的子节点,他们都会通过 key 来区分. 如果 key 相同那么他就会更新属性,如果 key 不同, 那么他就 卸载重新创建.
Vue React 的差别
首先 Vue 是一个渐进式的框架, 你可以只用 Vue 做 View 也可以使用 Vue 全家桶, 而 React 只是一个 View 的库.
React 基于 jsx 语法, Vue 基于 html 模板. 两者都是 虚拟 dom
Vue 通过代理 data 可以检测出哪些数据发生了变化从而进行更新, 并且更新时同步的
React 通过基于时间的调度器, 对 Fiber 对象链表遍历对比更新.
执行栈
执行栈可以理解为储存函数调用的栈结构,单向的(只能从一侧进栈或者出栈)遵循先进后出的原则.
运行一个函数就会将函数和相关的变量储存在栈内, 当函数运行完毕之后会从栈中弹出. 栈的大小是有限制的,根据浏览器不同限制也不尽相同.如果超过最大栈深度,那么就会抛出错误.
Event loop
JavaScript 在执行的过程中会产生执行环境,这些执行环境会被顺序价值入到执行栈中, 如果遇到异步代码,会被挂起并加入到执行队列中, 一旦执行栈为空, Event Loop 就会从 Task 队列中拿出要执行的代码并放入执行栈中进行执行. 所以从本质上来讲 异步也是同步行为.
不同的任务会被放在不同的队列
中, 任务可以分为微任务和宏任务, 在 ES6 规范中, 微任务被称为 jobs, 宏任务被称为 task
如果当前宏任务执行完毕, 那么会查询当前是否有微任务需要执行, 如果有就执行没有就开始下一轮 Event loop
举例说明
1 | console.log(1); // 执行1 |
常见微任务:
- process.nextTick
- Promise
- Object.observe
- MutaionObserver
宏任务:
- JavaScript 同步代码
- setTimeout
- setTnterval
- setImmediate
- I/O
- UI render
浏览器相关
浏览器渲染流程
浏览器收到 document 响应之后,会对文本进行解析, 首先解析成 AST ,之后再将语法树解析成实际 Node ,从而构建出一颗 DOM 树
浏览器在解析的同时也会一边处理资源,对资源进行加载和运行,遇到 css 就开始请求 css 文件,调用布局引擎开始解析 css,类似也是解析语法,生成树,树生成实际 css Node 树.
在这两者解析的过程也会开始处理布局 js 等. 这也就是为什么 js 会堵塞渲染, 和 js 放在文档末尾比较好.
重绘 和 重排
重绘是指当前节点需要更改外观,而不需要更改布局, 比如字体颜色 div 背景颜色之类的.
重排就是当布局或者几何属性发生变化的时候就称为重排
重排必定会发生重绘, 重绘则不一定会引发重排, 重排所需要的城北比重绘高很多, 改变父节点内的子节点很可能回调熬制父节点一系列重排.
所以以下操作会影响性能:
- 改变窗口大小
- 改变字体
- 添加或者删除样式
- 改变布局
JavaScript 引起的重绘重排
JavaScript 在事件循环的时候执行完微任务列表之后,就会判断 document 是否需要更新, 因为浏览器是 60Hz 刷新率,所以每 16.6ms 才会更新一次
判断有没有 resize 或者 scroll 事件, 有就执行, 所以这两个事件也是 16.6 ms 以上才会执行一次
更新动画
判断是否有全屏操作事件
执行 requsetAnimationFram 回调
执行 IntersectionObserver 回调
如果还有剩余时间 执行 requestIdleCallback 回调
同时在 JavaScript 使用一些获取大小位置信息的 api 都会引起强制重绘,以获得最新值 说明文档, 举例说明
1 | elementA.className = "a"; |
那么可以通过如下修改来避免重复重绘
1 | elementA.className = "a"; |
HTTP
强缓存和协议缓存
- 浏览器会在加载资源时,根据请求头的过期时间以及 cache-control 判断是否命中缓存, 是则直接从缓存读取资源
- 如果没有命中缓存,浏览器会发送一个请求到服务器,通过 last-modified 和 etag 验证资源是否命中缓存. 如果命中.服务器将返回这个请求,但是不会反悔资源的数据.
- 如果两者都没严明中,则直接从服务器加载资源
强缓存
expires
expores 是一个 HTTP 响应头.标识资源过期时间, 但是 expires 受限于本地时间,如果修改了时间,则可能造成缓存失效cache-control
cache-control 优先级高于 expires 表示的是相对时间.
协商缓存
如果某个资源没有命中强缓存, 就会发送一个请求到服务器,验证协商请求是否命中,如果命中协商缓存,请求响应返回的 HTTP 状态为 304,并显示一个 not modified
- last-modified if-modified-since
last-modified 标识本地文件最后修改日期, 浏览器会在请求头上加上 if-modified-sice (返回上次的 last-modified) 访问服务器在该日期后资源是否有更新,有更新的话就会将新资源发送回来
- Etag if-none-match
Etag 就像一个指纹,每次资源变化都会导致 etag 变化,和最后修改时间没关系, etag 可以保证每个资源都是唯一的
if-none-match 和头会将上次返回的 etag 发送给服务器, 询问该资源的 etag 是否有更新,有变动就发回新的资源回来
etag 优先级比 last-modified 高
- 一些文件也许会周期性的改变,但是他的内容官兵不改变,这个时候并不希望客户端认为这个文件被修改了,而工薪 get
- 某些文件修改非常频繁, if modifled since 能检查到的是秒级的而 etag 可以
- 某些服务器不能精确的得到文件的最后修改时间
几种状态码的区别
- 200 强缓存 expires cache-control 失效,返回新的资源文件
- 200 from cache 强缓存都在,未过期,从本地读取
- 304 last modified 和 etag 没有过期,服务端返回 304
SCRF 和 XSS
XSS 指攻击者对客户端网页注入一些恶意脚本的方式进行攻击.通常是获取用户隐私数据. 防范方式也很简单.转义用户输入,不相信任何用户输入. 设置 cookie 的 HTTPOnly 禁止 JavaScript 访问 cookie
SCRF 是指跨站请求伪造, cookie 有个 donmain 属性指定, 如果 cookie 的域和页面的域相同,那么就是第一方 cookie, 如果 cookie 的域和页面不同,则称为第三方域, 页面上包含图片或者其他域上的资源时, 第一方 cookie 也会发送给他们的服务器
http referer 头
http 会有一个 Referer 头用来表明这个请求是从哪来的.但是网站可以选择不发送任何 referer
添加 token 头
可以要求请求添加一个单独的 token 来防止攻击
跨域
跨域是指一个域的脚本请求另一个域的资源或者请求.同源就是 协议 + 域名 + 端口必须一致,否则非同域
同源限制一下行为:
- cookie loacalstorage 和 indexDB 无法读取
- DOM 和 Js 无法获得
- 请求无法发送或者被拦截
跨域解决方法:
- 跨域资源共享 CORS 跨域
- nginx 代理
- JSONP 跨域