声明
所有内容非原创,为各个技术文档爬来的,粘合到一起,给自己面试准备用的
前面加了星星图案的是指自己面试真实遇到的次数
个人情况
简历
- 项目
- 进度:75%
- 问题:reactive ref
完全是因为不懂Vue.set
- 实习
- 改进:codemirror < uplot
- 负责:bug 需求 / 优化
反问
- 技术栈 / 前端细分领域 / 负责什么业务
- 对我的建议
Part A:手撕代码
- 警告
- 面试题应尽量使用ES6之前的写法,arrow function对于使用this的情况要万分小心
- 能用闭包就用闭包
CSS
钟表
轮播图
参考:纯 CSS 实现图片轮播
原生JS
组合继承
function Father(name) {
this.name = name;
}
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = Object.create(Father.prototype); // 注意不能直接引用
Son.prototype.constructor = Son;
new
function myNew(fn) { // 不需要this,创建新对象返回即可
if (typeof fn !== 'function') {
throw new Error('fn is not function!');
}
const obj = {}; // 创建新对象
const args = Array.from(arguments).slice(1); // 获取参数
fn.apply(obj, args); // 借用
obj.__proto__ = fn.prototype; // 原型
fn.prototype.constructor = fn; // 构造函数指回
return obj;
}
/* 测试 */
function Person(name) {
this.name = name;
this.say = function() {
console.log(`my name is ${this.name}`);
}
}
const myPerson = myNew(Person, 'hahaha');
myPerson.say();
call、apply
核心在于给传进去的上下文绑定当前使用的函数,用完再删掉
参数处理注意去掉第一个,而apply只有第二个参数有用
Function.prototype.myCall = function(context) { // 需要参数context上下文绑定
context.fn = this;
const args = [...arguments].slice(1);
const result = context.fn(...args);
delete context.fn;
return result;
}
Function.prototype.myApply = function(context) {
context.fn = this;
const result = context.fn(...arguments[1]); // 只有参数1的展开是有用的
delete context.fn;
return result;
}
/* 测试 */
function getName(name) {
this.name = name;
}
const obj1 = {};
getName.myCall(obj1, 'hello');
console.log(obj1.name); // hello
const obj2 = {};
getName.myApply(obj2, ['world']); // 注意传参是一个数组
console.log(obj2.name); // world
bind-基于call/apply
核心是借用,保留this,保留参数
返回一个函数,内部返回借用的结果
Function.prototype.myBind = function(context) {
const args = [...arguments].slice(1);
return () => this.apply(context, args); // 可以用箭头函数,所以不保留this
}
/* 测试 */
function Person(name) {
this.name = name;
}
const obj = {};
Person.myBind(obj, 'hello')(); // 参数在第一个调用里,第二个必须执行必须为空
console.log(obj.name); // hello
curry化
const curry = function(fn, args = []) { // 注意参数默认值
return function() { // 直接返回函数
const newArgs = args.concat(Array.from(arguments)); // 拼接即可
if (newArgs.length < fn.length) {
return curry.call(this, fn, newArgs); // 虽然是call,但不用展开
}
return fn.call(this, ...newArgs); // 必须展开
}
};
/* 测试 */
const add = function(a, b, c) {
return a + b + c;
};
const curryAdd = curry(add);
console.log(curryAdd(1)(4)(7)); // 12
console.log(curryAdd(1, 4)(7)); // 12
console.log(curryAdd(1, 4, 7)); // 12
console.log(curryAdd(1, 4)(7)); // 12
自定义事件
参考:前端如何自定义事件
监听页面滑动到底部
window.onscroll = function(){
console.log(document.documentElement.scrollHeight); // 滚动条高度
console.log(document.documentElement.scrollTop); // 滚动条到顶部距离
console.log(document.documentElement.scrollTopMax); // 滚动条到顶部最大距离
}
后两者相同时,代表滚动到了底部
ES6
promise
function myPromise() {
this.status = 'pending'; // 初始化
this.fulfilledData = null; // fulfilled状态数据
this.rejectedData = null; // rejected状态数据
this.resolve = function(resolve) {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.fulfilledData = resolve;
}
}
this.reject = function(reject) {
if (this.status === 'pending') {
this.status = 'rejected';
this.rejectedData = reject;
}
}
this.then = function(onFulfilledCallback, onRejectedCallback) {
switch(this.status) {
case 'fulfilled':
onFulfilledCallback(this.fulfilledData);
break;
case 'rejected':
onRejectedCallback(this.rejectedData);
break;
default:
break;
}
}
}
/* 测试 */
const myPromiseTest = new myPromise();
console.log(myPromiseTest.status); // pending
myPromiseTest.resolve('yes');
myPromiseTest.then((res) => console.log(res)); // yes
promise.all-基于promise
主要思想是用一个promise,在内部调用调用Promise.resolve()
,再模拟数组来完成
记得返回一个new Promise
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
if (!(promises instanceof Array)) {
throw new TypeError('\'promises\' is not Array!');
} else {
const resolvedLength = 0;
const requiredLength = promises.length;
const resolvedArray = [];
for (let i = 0; i < requiredLength; i += 1) {
(function handlePromise(index) {
Promise.resolve(promises[index]).then((res) => {
resolvedLength += 1;
resolvedArray[index] = res;
if (resolvedLength === requiredLength) {
resolve(resolvedArray);
}
}, (rej) => {
reject(rej);
}).catch((e) => {
reject(e);
});
})(i);
}
}
});
};
Vue
✨✨✨Vue双向绑定
<input type="text" id="myInput">
<span id="mySpan"></span>
const myInput = document.getElementById('myInput');
const mySpan = document.getElementById('mySpan'); // 非必须
const obj = {};
myInput.addEventListener("input", (e) => {
obj.key = e.target.value;
});
Object.defineProperty(obj, "key", {
set: (newVal) => { // 注意defineProperty是属性后接函数,而proxy是set(),且无参数2
mySpan.innerHTML = newVal;
myInput.value = newVal;
},
});
数据劫持:以上
订阅者-发布者:get收集订阅,set派发发布
vue3使用proxy,可以不用深度监听,而且可以处理数组
vue2可以使用Vue.set或vm.$set来设置数组
算法
✨归并排序
思想在于首先有一个两个有序数组合并的函数merge
然后把数组拆分两半,每部分再依次用拆分两半的函数递归,最终传给merge
const merge = (arrLeft, arrRight) => {
let ans = [];
while (arrLeft.length && arrRight.length) {
ans.push(arrLeft[0] < arrRight[0] ? arrLeft.shift() : arrRight.shift());
}
ans = ans.concat(arrLeft.length ? arrLeft : arrRight);
return ans;
};
const mergeSort = (arr) => {
const LEN = arr.length;
if (LEN < 2) {
return arr;
}
const MID = LEN >> 1;
const arrLeft = arr.slice(0, MID);
const arrRight = arr.slice(MID);
return merge(mergeSort(arrLeft), mergeSort(arrRight));
};
/* 测试 */
const test = [7, 1, 9, 3, 5, 4, 6, 2, 8, 0];
console.log(mergeSort(test));
✨快速排序
根本思想在于在右面找一个小的,在左面找一个大的,不断替换,找到正确位置
然后分区间递归
const quickSort = (arr, left, right) => {
if (left < right) {
const temp = arr[left];
let l = left, r = right;
while (l < r) {
// 从右往左找一个小于temp的值
while (l < r && arr[r] >= temp) { // 注意这里也有 l < r
r -= 1;
}
if (l < r) {
arr[l] = arr[r];
}
// 从左往右找一个大于temp的值
while (l < r && arr[l] <= temp) {
l += 1;
}
if (l < r) {
arr[r] = arr[l];
}
}
// 空出来的位置就是temp的
arr[l] = temp;
quickSort(arr, left, l - 1);
quickSort(arr, l + 1, right);
}
};
堆排序
参考:堆排序
二叉树后序遍历相关
网络
✨JSONP+数据处理
- 基础
const url = "https://www.xxx.com/file?a=1&b=2";
const myScript = document.createElement('script');
myScript.src = url;
document.appendChild(myScript);
- 获取数据:原生
const callBackFunc = (data) => {
// do something
};
window.callBackFunc = callBackFunc; // 注意:一定挂到window上
const url = "https://www.xxx.com/file?a=1&b=2&callback=callBackFunc";
const myScript = document.createElement('script');
myScript.src = url;
document.appendChild(myScript);
- 获取数据:jQurey
const handleData = (data) => {
// do something
};
$.ajax({
url: "https://www.xxx.com/file?a=1&b=2",
dataType: "jsonp",
jsonpCallback: "handleData" //指定回调函数
}).done((data) => {
// do something
}).fail((data) => {
// do something
});
AJAX
const xhr = new XMLHttpRequest();
const url = 'http://localhost:3000'
xhr.open('GET', url); // 第三个参数boolean表示是否异步
xhr.send();
xhr.onreadystatechange = function() { // 注意都是小写
if (this.readyState === 4 && this.status === 200) {
console.log(this.responseText);
}
};
- AJAX readystate
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
优化
✨防抖
const debounce = function(fn, wait) {
let timer;
const self = this;
return function() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => fn.apply(self, Array.from(arguments)), wait);
}
}
节流
const throttle = function(fn, wait) {
let timer;
const self = this;
return function() {
if (!timer) {
timer = setTimeout(() => fn.apply(self, Array.from(arguments)), wait);
}
}
};
Part B:理论题
CSS
CSS选择器优先级
- 优先顺序
!important
行内/内联样式
id选择器
class选择器
标签选择器
通配符*
继承
默认 - 优先级权重
行内样式1000
id选择器100
class选择器10
标签选择器1
宽高比固定
基于padding和margin设置百分比是按照宽度计算的原理
height设置为0,内容自然溢出
width: 50%;
height: 0;
padding-bottom: 30%;
/*宽高比为5:3*/
✨水平居中&垂直居中
- 水平居中
- flex:父
display: flex
,justify-content: center
;主轴对齐 - margin:父宽度已知,设置子
display: block
margin: 0 auto
;把剩余空间分给margin,左右平均分 - inline:父
text-align: center
,子display: inline / inline-block
等;转为行内元素,利用text-align
- 垂直居中
- flex:父
display: flex
,align-items: center
;交叉轴对齐 - absolute + transform:父高度已知,父
display不为static
,子display: absolute
top: 50%
transform: translate(0, -50%)
✨三栏布局
- flex
父display: flex
flex-flow: row wrap
左右flex-basis: 200px
中flex: 1
- absolute + margin
左右position: absolute
left/right: 0
width: 200px
中margin: 0 200px
- float + margin
左右float: left/right
width: 200px
中margin: 0 200px
✨Flex
-
容器属性
flex-direction:column row
flex-wrap:nowrap wrap
flex-flow:flex-direction flex-wrap
justify-content:主轴对齐
align-items:交叉轴对齐
align-content:自己的对齐方式 -
项目属性
flex-grow:放大比例
flex-shrink:缩小比例
flex-basis:基准大小
flex:flex-grow flex-shrink flex-basis 默认0 1 auto
order:顺序
align-self:继承align-items
✨Position
- absolute
绝对布局,相对于第一个非static父元素进行定位 - fixed
固定布局,关于浏览器窗口定位 - relative
相对布局,关于原位置进行偏移,设置top,left,right,bottom等 - static
默认布局,没有布局,以正常流显示 - inherit
继承父级position - sticky
粘性,一开始relative,到了一定高度fixed
BFC与IFC
- BFC-是什么
根据英文联想,B=Block,F=format,C=Context,块级格式化上下文
将一个盒子转化为BFC,会拥有一系列特性
- 同一个BFC下,元素会垂直排列
- 元素的margin会重叠
- 参与计算高度时,浮动子元素也参与计算
- 不同的BFC互不相关互不影响
- BFC区域不会与float重叠
- BFC-为什么
- 因为浮动子元素参与计算高度,可以用来防止父元素塌陷
- 因为不同的BFC互不影响,可以用来防止margin重叠
- 可以用来清除浮动
- BFC-怎么做
- overflow不等于visiable
- float不等于none
- 根元素
- display为flex,inline-block,inline-flex,table相关
- IFC:内联格式化上下文
横向排列
垂直方向的样式不会被计算
子元素垂直对齐的方式不同
float元素被优先排列
✨清除浮动
- 触发父级BFC
- 父级加一个class,设置其
:after
伪类中clear: both
- 后面加一个兄弟元素,其class中
clear: both
响应式布局 em、rem、vh、vw
em是根据当前元素字体font-size等比例缩放
rem是根据根元素font-size等比例缩放
vh是每个页面高度恰好100vh
vw是每个页面宽度恰好100vw
重绘/重排&回流
- 重绘
轻量级,不一定会触发回流重排
简单的在已有DOM基础上修改样式
触发:
- visibility变化
- 背景色、颜色、轮廓变化
- 回流 / 重排
重量级,必定触发重绘
重新绘制页面
- 触发:
- 页面首次渲染
- 元素的位置尺寸发生改变
- 新增删除元素
- 激活伪类
- 查询或调用某些方法
CSS动画 - 字节重点
- 和JS动画区别
JS做的是帧动画,CSS是补间动画 - transform(动画效果)
transform-origin: (left, bottom); /* 效果基点,可以是百分比,em,left之类 */
transform: rotate(30deg); /* 旋转角度,单位deg */
transform: translate(1px, 2px); /* 平移:单位为长度 */
transform: translateX(1em); /* 平移:可以分为X和Y */
transform: skew(30deg, 40deg); /* 扭曲:单位为角度 */
transform: skewX(30deg); /* 扭曲:可以分为X和Y */
transform: scale(2, 2); /* 缩放:单位为比例 */
transform: scaleX(2); /* 缩放:可以分为X和Y */
- transition
主要涉及到样式的过渡,可以通过伪类触发
/* 全部 */
#a {
width: 100px;
}
#a:hover {
width: 300px;
transition-property: all; /* all / none / 属性名 */
transition-duration: 4s; /* 持续时间 */
transition-timing-function: linear; /* 时间函数 */
transition-delay: 1s; /* 延迟 */
}
/* 简写 */
#a:hover {
/* transform动画效果 or 简单的属性变化 */
transtion: all 4s linear 2s;
}
可以使用transform: xxx
- animation
参考:【前端面试系列】CSS Animations
- 关键帧
@keyframes example {
from { /* 样式 */ } /* from即0% */
20% { /* 样式 */ }
60%, 70% { /* 样式 */ }
to { /* 样式 */ } /* to即100% */
}
可以使用transform: xxxx
- 属性
/* 全部 */
#a {
animation-name: example; /* 名称,对应keyframes */
animation-duration: 5s; /* 持续时间,单位s,默认为0(无动画) */
animation-timing-function: linear; /* 速度曲线,有ease(默认,中间快)、ease-in(开始快)、ease-out(结尾快)、ease-in-out(开始结尾慢)、linear(均匀)、cubic-bezier(n,n,n,n) (自定义曲线) */
animation-delay: 2s; /* 延迟时间,单位s,可以为负数(即duration-delay) */
animation-iteration-count: infinite; /* 循环次数,可以为数字 or infinite */
animation-direction: alternate; /* 方向,有normal(默认)、reverse(反转)、alternate(先正后反)、alternate-reverse(先反后正) */
animation-fill-mode: both; /* 填充模式,有none、both(都有)、backwards(起始)、forwards(结束) */
}
/* 简写 */
#b {
animation: example 5s linear 2s infinite alternate; // 没有fill-mode
}
原生JS&ES6
判断转化
Speaking Javascript的说法
===
undefined === undefined
null === null
NaN !== NaN // Object.is()结果相反
+0 === -0 // Object.is()结果相反
==
undefined == null
1 == true // boolean与其他比较会转化为数字,1或0
"2" == 2 // string和数字比较会转数字,空字符串会转0
"" == 0 // ""转数字为0
"abc" != true // true转1 "abc"变NaN 和任何都不想等
"\t\n123" == 123 // ps:字符串的parseInt和Number严格度递增
{} == "[object Object]" // 按照方括号内第二个值判断
['123'] == 123 // true
[] == 0 // true
Object.is()
类似于 ===,但是有一些细微差别,如下:
- NaN 和 NaN 相等
- -0 和 +0 不相等
super
子类调用父类对象先处理,Father.call的过程
await
- 知道执行结果
try catch - await返回什么
promise
字符串截断方法
概括:
- substr第二位是长度,为负时返回undefined
- substring会把负值和NaN转0,范围灵活转换从小到大
- slice灵活按索引切片,负数会从后数,但是不会转换范围
参数 | substr | substring | slice |
---|---|---|---|
(1) | 无区别 | 无区别 | 无区别 |
(-1) | 从后往前 | 参数是负数或NaN,变0 | 从后往前 |
(1, 2) | 参数2代表长度 | 参数2代表索引 | 参数2代表索引 |
(2, 1) | 参数2代表长度 | 会自动从小到大 | 返回undefined |
(2, -1) | 参数2代表长度,undefined | 会自动从小到大,转0,截取0-2 | 返回2位到倒数第1位 |
weakMap和weakSet
key必须是对象
而且是弱引用,不计入垃圾回收机制计数
link和@import
link可以加载多种资源,@import只能加载css
link是同步加载,@import是在页面加载完毕后加载
link不存在兼容性问题,@import在老浏览器版本不可用
link可以通过js来操作,@import不可以
结论:尽量使用link来引入
✨✨require和import
- require属于commonJS
动态加载(运行时)
同步
值 - import属于ESmodule
静态加载(编译时就可以确定依赖关系)
异步
引用
requestAnimationFrame
参考:requestAnimationFrame 和 setTimeout/setInterval 有什么区别?使用 requestAnimationFrame 有哪些好处?
下面代码a在什么情况中打印出1?
Vue
✨vue-router原理
- 本质
SPA单页面应用状态管理器,根据切换渲染不同的组件 - 模式
- hash:使用hash值作为URL,为默认模式
URL改变时,页面不会重新加载
通过锚点值#
的改变,根据不同的值渲染不同的组件和数据
每次操作都会保存在历史记录,可以使用“后退”、“前进” - history:依赖H5的history API和环境
利用history.pushState()
和history.replaceState()
,在不刷新页面的情况下,更改URL - abstract:支持所有运行环境,比如node.js服务端
没有浏览器API的情况下会强制转abstract模式
route和router的区别
- route
当前对象的路由信息
有path
,params
,query
等等 - router
全局路由的实例
有push()
,go()
等
vue和react的区别
参考:vue和react的区别及各自优点
主要看图
✨生命周期
✨组件通信
网络
✨get、post
- 参数
get的参数直接放在url中,post使用params传递,放在request body里
所以post相对安全 - 长度
get请求有长度限制,post没有 - 编码和字符
get只能进行url编码,post支持多种编码格式
get只能接受ASCII字符,post支持其他字符 - 缓存和记录
get请求会被浏览器缓存,post不会,除非设置
get请求的参数会被记录在历史记录里,post不会 - 回退
get在浏览器回退时无害,post回退会重新提交 - TCP
get是直接发送header+body
post是先发header,服务器返回100-continue,再发body,服务器返回200等
不同浏览器有区别,FireFox就是只发送一次
在网络比较差的时候,发送两次更有利于验证数据完整性
put、delete
get、post、head属于简单请求,对服务器影响不大
其他有影响的属于非简单请求
- put:修改资源
- delete:删除资源
options
- 获取服务器支持的HTTP请求方法;
- 用来检查服务器的性能
http状态码
statusCode | description |
---|---|
100 | 已接受,正在处理 |
200 | 成功,并返回数据 |
301 | 永久重定向,资源已移动 |
302 | 临时重定向,可以使用原有URL |
304 | 协商缓存:资源未修改,可以使用缓存 |
400 | 请求语法错误 |
401 | 需要授权 |
403 | 被拒绝 |
404 | 不存在 |
500 | 服务器错误 |
request Header 有哪些
- Accept
- Accept-Language
- Content-Type
- Content-Language
- Host
- Origin
- keep-alive
✨跨域
- 触发
不同域名:www.baidu.com
->www.google.com
不同端口:www.baidu.com
->www.baidu:81.com
不同协议:http://www.baidu.com
->https://www.baidu.com
不同子域:www.baidu.com
->baidu.com
- 解决
- JSONP,动态插入script标签,
document.createElement('script')
,缺点是只支持GET - Nginx反代,将请求反向代理
- CORS,跨域资源共享策略,在后端设置
res.setHeader('Access-control-allow-xxxx')
,xxx可以是origin
、header
等 - websocket
- postmessage
✨✨输入URL到看到页面的过程
- DNS解析
将域名解析为IP地址 - 发送HTTP请求
请求包含头部,请求资源参数等 - 传输层TCP
三次握手,建立连接 - 网络层、链路层
网络层IP协议查询MAC地址,数据进入链路层传输 - 接受请求,返回资源
拿到数据后,后端返回,传输过程中从下往上解包
返回资源和状态码,200,404等 - 渲染
DOM树
CSS渲染数
JS脚本加载
✨OSI七层模型
层次 | 描述 | 代表 |
---|---|---|
物理层 | 物理传输,为链路层提供了物理连接 | 铜线、双绞线 |
链路层 | 解决两个节点之间的数据传输 | MAC协议 |
网络层 | 为传输层提供服务,数据单元称为数据包或分组 | IP协议 |
传输层 | 为上层提供可靠和透明的数据传输服务 | TCP、UDP |
会话层 | 管理和协调不同主机进程之间的通信/会话 | - |
表示层 | 处理数据编码的表示方式、数据格式化、加密 | - |
应用层 | 用户与网络的接口 | HTTP、FTP、SMTP等 |
✨TCP、UDP
属性 | TCP | UDP |
---|---|---|
面向连接 | 有,三次握手 | 无 |
按序交付 | 有 | 无 |
可靠数据传输 | 有 | 无 |
超时重传 | 有 | 无 |
拥塞控制 | 有 | 无 |
流量控制 | 有 | 无 |
速度 | 慢 | 快 |
应用场景 | 需要可靠数据传输 | 只需要速度 |
例子 | HTTP | DNS |
三次握手四次挥手
- 三次握手
- 客户端向服务端发起请求,SYN=1,生成序列号seq=m,进入SYN_SENT状态
- 服务端收到客户端请求,回应,SYN=1,ACK=1,生成序列号seq=n,确认号ack=m+1,进入SYN_RECEIVED状态
- 客户端收到服务端回应,回应ACK=1,生成序列号seq=m+1,确认号ack=n+1,完成三次握手
为什么不能是两次:如果客户端多次请求,前面请求失败了,两次握手的情况下,后续请求到达也会建立连接,浪费资源
SYN洪泛:就是不断向服务端发起SYN的请求,使其不能回应正常的请求
- 四次挥手
- 客户端向服务端发起请求,FIN=1,生成序列号seq=m,进入FIN_WAIT状态
- 服务端收到请求,回应ACK=1,生成序列号seq=n,确认号ack=m+1,进入CLOSE_WAIT状态
- 进行最终的数据传输
- 服务端再次发起请求,FIN=1,ACK=1,生成序列号seq=M,确认号ack=m+1,进入LAST_ACK状态
- 客户端收到请求,进入TIME_WAIT状态,发送ACK=1,序列号=m+1,确认号ack=M+1,等待2个MSL,进入CLOSE状态
✨✨http1.0、1.1、2.0、https
-
http 1.0 -> http 1.1
无状态连接 到 keep-alive
断点续传
状态管理
身份认证
缓存:cache-control、expires、Etag、Last-Modified -
http 1.1 -> http 2
http 1.1使用文本传送,http 2 使用二进制传送
http 2 支持多路复用,因为使用二进制传送,通过流,所以可以通过同一个http请求实现多个请求
http 2 支持头部压缩
http 2 可以在客户端未经许可的情况下推送内容 -
https
端口443
SSL安全套接字层
CA证书
websocket
- 定义
持久化协议,基于H5
基于http握手的基础上,仅进行一次握手就可以建立websocket连接 - 好处
改变服务器被动局面,可以主动向客户端推送内容,代替轮询机制
避免了非状态性,防止重复建立连接
ipv4、v6
优化
✨浏览器缓存策略
-
缓存的位置
磁盘 -
强制缓存
- cache-control
http1.0不支持
优先级大于empires
格式为:max-age: 1234
,数字表示秒
是缓存有效时间 - empires
优先级较低,但支持版本更早
格式为时间
- 协商缓存
去服务器端询问,是否要用缓存,还是获得新资源
- Etag -> if-none-match
根据Etag来判断修改时间,如果修改过,需要重新缓存
if-none-match就为true,否则为false
优先级较高 - Last-Modified-> if-modified-since
根据此字段判定是否需要修改
缺点:周期性修改但内容未变,会失效;粒度只到秒
- 过程
- 没有缓存:去后台请求
客户端去向服务器端发送请求, 获得资源,同时服务器端会在请求头部写empires/cache-control等,客户端根据请求头部,保留缓存并设置到期时间 - 有缓存,但是到期了:协商缓存
向服务器端请求,服务器端根据Etag/Last-Modified头部判断是否需要更新缓存
如果不需要,返回304,客户端去获得缓存资源
需要更新,返回200,客户端获得新的资源,并且重设缓存 - 有缓存,没到期:强制缓存
直接命中,使用缓存
✨前端性能优化
- 使用CDN
- 图片懒加载,利用事件触发
- 减少HTTP请求数
- 尽量使用JSON格式进行传输
- 不常更新的资源使用缓存,304
- 将js、css文件等打包
- 尽量减少DOM操作,CSS操作最好使用class,极限优化时display先设置为none
✨vue首屏加载优化
- 使用CDN,将自己代码和第三方代码分离,缩短加载时间
- 路由使用懒加载
- 第三方库可以按需加载,减少体积
- webpack配合nginx服务器,开启gzip压缩
- SSR服务端渲染
✨✨✨SSR
- 概念
Server Side Render:服务端渲染
将组件or页面通过服务器端生成html字符串,发给前端 - 优点
利于SEO,利于爬虫抓数据
利于SPA首屏渲染,不依赖js,可以更快看到页面 - 缺点
服务端压力较大
条件受限
PWA
- PWA:渐进式web应用
目的:使用现有的web技术提供用户更优的使用体验
基本要求:可靠 快速响应 粘性 - web-worker
可以脱离js主线程,单独执行,可以与主线程交互
H5提出的,JS的多线程,但是子线程不能脱离主线程,而且不能操作DOM
数据交互接口是postMessage和onMessage
var worker = new Worker('./worker.js'); //创建一个子线程
worker.postMessage('Hello');
worker.onmessage = function (e) {
console.log(e.data); //Hi
worker.terminate(); //结束线程
};
- service-worker
要求https,基于promise实现
离线缓存,周期同步,推送和管理资源
安全
✨XSS、CSRF
- XSS:跨站脚本攻击
描述:在输入框输入诸如<script
等进行攻击
防范:不相信用户输入,对用户输入进行过滤,对<
进行转义;cookie设置http/only - CSRF:跨站伪装请求攻击
描述:在用户已经登陆的网站,伪装用户身份发起请求,窃取信息等
防范:使用token,或者设置请求头,总之二次确认;get不修改资源
服务端
✨进程、线程
属性 | 进程 | 线程 |
---|---|---|
从属关系 | 程序运行=进程 | 轻量级进程,从属于进程 |
单位 | 操作系统调度的基本单位 | 处理器调度资源的最小单位 |
资源 | 有独立的地址空间和内存 | 共享进程的地址空间和内存,但是有自己独立的资源比如程序计数器 |
切换开销 | 大 | 小 |
崩溃影响 | 不会对其他进程产生影响 | 会影响整个进程 |
设计模式-单例
- 单例模式
- 前端写法
module,上下文相关单例
class MyClass {
// ...
}
export const myClassSingleSample = new MyClass();
export default new MyClass();
- 后端写法:懒汉vs饿汉
- 懒汉:赋值为null,第一次调用的时候赋值
- 饿汉:直接赋值,以后每次取已经赋值的结果
class MyClass {
private MyClass() { // 私有化
// do something
}
private static MyClass mySample = null;
public static getInstance() {
if (MyClass.mySample === null) {
MyClass.mySample = new MyClass();
}
return MyClass.mySample;
}
}
session
session是在服务器端保持登录状态的方法,有唯一id,有有效期
在请求时检查
工具
✨✨✨webpack
loader: css-loader、eslint-loader
plugin
Part C:面试题纪实
CSS
css获取页面长宽,做等列栅格布局
- 长宽
- js获取
document.body.clientWidth; // 可见区域
document.body.offsetWidth; // 可见区域 + 边线
document.body.scrollWidth; // 正文全文
- css获取
calc()
参考CSS3 使用 calc() 计算高度 vh px
- 栅格
<div class="father">
<div class="son"></div>
<div class="son"></div>
<div class="son"></div>
<div class="son"></div>
<div class="son"></div>
<div class="son"></div>
</div>
通过设置宽度百分比控制一行的数量
通过flex-flow控制展示方向
margin重叠
Father div中 Son div设置margin-top为100px
实际结果是顶部重合,因为Son和Father整体margin-top变成了100px
解决方法
- 外层元素
- padding代替margin设置
- overflow: hidden // 取值:scroll滚动 hidden隐藏 visiablity可见 auto自动滚动 inhert继承
- 内层元素
- position: absolute
- float: left
- display: inline-block
JS
setInterVal实现时钟&精准定时
- 实现时钟
3个变量,时分秒,模拟时间运转和进位
用setInterval定时 - 精准定时
用Date做差得到dis,setInterval参数变为1000 - dis
或者用requestAnimationFrame
var汇总
- hoist
var a = 1;
{
console.log(a); // 1
var a = 2;
}
var是函数作用域的,其他情况大括号不形成作用域
如果这是个函数,那就是定义提前,赋值在当前行做,中间为undefined,类似于暂时性死区
- 闭包hoist
var a = 1;
(function() {
console.log(a); // undefined
if (false) {
var a = 2;
}
})();
本来应该是1 但是闭包形成了函数作用域
- 同名var优先级
赋值>函数>声明
var a = 1;
var a = function() {};
console.log(a); // 1
var a;
var a = function() {};
a = 1;
console.log(a); // f
this汇总
const obj = {
f: function() {
console.log(this);
},
};
obj.f(); // 可以视为window在调用
浏览器事件
- 点击li获取数字
dom - li特别多
委托给父元素 - 不想要某一个
阻止冒泡
事件循环与node环境
参考:浏览器与Node的事件循环(Event Loop)有何区别?
前端渲染的时候有什么performance能看到的事件
借鉴自:浏览器渲染机制
当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片。
(譬如如果有async加载的脚本就不一定完成)
当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了。(渲染完毕了)
所以,顺序是:DOMContentLoaded -> load
正则部分
- 匹配模式
- 贪婪匹配:匹配成功继续向右匹配
- 非贪婪/惰性匹配:匹配成功就不做事了
- 匹配结果输出
利用字符串的match
,正则本身有test
,exec
字符串可以用replace
、searrch
和match
const s = "abc123def456";
/\d+/.test(s); // true
/\d+/.exec(s); // ["123"]
/\d+/g.exec(s); // ["123"] // g无效
s.search(/\d+/); // 3 // 返回索引
s.search(/\d+/g); // 3 // g无效
s.match(/\d+/); // ["123"]
s.match(/\d+/g); // ["123", "456"]; // g有效
s.replace(/\d+/, "hello"); // 替换1次
s.replace(/\d+/g, "hello"); // 替换全部
Set部分
- weakSet和Set
Set本身可以给引用类型去重
weakSet弱引用
数组部分
-
数组不可变
Object.freeze()
proxy
Object.defineProperty()
writable
是不可以的,想象一下vue原理 -
数组API返回值
push和unshift返回长度
pop和shift返回移除值 -
判断是不是数组
Array.isArray()
instanceof
。 注意,typeof
不行
constructor
是否为Array
import循环引用
- 报错形式
不会报错
因为 import 是在编译阶段执行的,这样就使得程序在编译时就能确定模块的依赖关系,一旦发现循环依赖,ES6 本身就不会再去执行依赖的那个模块了,所以程序可以正常结束。这也说明了 ES6 本身就支持循环依赖,保证程序不会因为循环依赖陷入无限调用
- 怎么解决
webpack 插件 circular-dependency-plugin
肉眼解决
Vue
created获取dom
- nextTick()
在下次dom更新循环结束之后执行延迟回调,可用于获取更新后的dom状态
vue2响应式缺点
- 数组单位元素变化的监听
数组中某个对象or值被监听到了,Model变了,但v-for不刷新
Vue.set(this.arr, 0, 100); // array, index, value
vm.$set(this.arr, 0, 100);
- 对象增删的监听
Vue.set(this.obj, 'x', 100); // object, key, value
vm.$set(this.obj, 'x', 100);
算法
连续1的区域
算法题,在一个矩阵中寻找连续1的区域总共有多少个
只有上下左右算连续
思路:用visited做一个等大小的矩阵,记录是否被访问过
每次循环寻找矩阵从左往右从上往下第一个1
然后对其进行上下左右的递归
每次递归判断,如果是合法地址,如果没被访问过,如果是1,就继续递归到上下左右,同时翻转访问情况并记录当前节点
单次循环结束后将这些节点全部从1变0
count++
继续寻找下一个起始的1
最长公共子序列
二维dp,匹配到了dp=左上角+1,否则=0
安全
cookie部分
- cookie在请求中携带是谁做的
http协议做的,同源自带
所以需要后端设置前端只读的cookie - 为何设置为httponly有利于安全性
js脚本将无法读取到cookie,可以防止XSS
跨域对CSRF的影响
同源限制策略只针对AJAX方式的CSRF
还可以通过form,通过iframe等等方式跨域