前端面试题
一些面试题
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 跨域