『前端面试资料』 个人面试准备手稿 2021/4

文章目录

声明

所有内容非原创,为各个技术文档爬来的,粘合到一起,给自己面试准备用的
前面加了星星图案的是指自己面试真实遇到的次数

个人情况

简历

  • 项目
  1. 进度:75%
  2. 问题:reactive ref
    完全是因为不懂Vue.set
  • 实习
  1. 改进:codemirror < uplot
  2. 负责:bug 需求 / 优化

反问

  1. 技术栈 / 前端细分领域 / 负责什么业务
  2. 对我的建议

Part A:手撕代码

  • 警告
  1. 面试题应尽量使用ES6之前的写法,arrow function对于使用this的情况要万分小心
  2. 能用闭包就用闭包

CSS

钟表

参考:利用CSS3制作一个动态时钟

轮播图

参考:纯 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*/

✨水平居中&垂直居中

  • 水平居中
  1. flex:父display: flexjustify-content: center;主轴对齐
  2. margin:父宽度已知,设置子display: block margin: 0 auto;把剩余空间分给margin,左右平均分
  3. inline:父text-align: center,子display: inline / inline-block等;转为行内元素,利用text-align
  • 垂直居中
  1. flex:父display: flexalign-items: center;交叉轴对齐
  2. 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,会拥有一系列特性
  1. 同一个BFC下,元素会垂直排列
  2. 元素的margin会重叠
  3. 参与计算高度时,浮动子元素也参与计算
  4. 不同的BFC互不相关互不影响
  5. BFC区域不会与float重叠
  • BFC-为什么
  1. 因为浮动子元素参与计算高度,可以用来防止父元素塌陷
  2. 因为不同的BFC互不影响,可以用来防止margin重叠
  3. 可以用来清除浮动
  • BFC-怎么做
  1. overflow不等于visiable
  2. float不等于none
  3. 根元素
  4. 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基础上修改样式
    触发:
  1. visibility变化
  2. 背景色、颜色、轮廓变化
  • 回流 / 重排
    重量级,必定触发重绘
    重新绘制页面
  1. 触发:
  2. 页面首次渲染
  3. 元素的位置尺寸发生改变
  4. 新增删除元素
  5. 激活伪类
  6. 查询或调用某些方法

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

  1. 关键帧
@keyframes example {
	from  { /* 样式 */ } /* from即0% */
	20% { /* 样式 */ }
	60%, 70% { /* 样式 */ }
	to { /* 样式 */ } /* to即100% */
}

可以使用transform: xxxx

  1. 属性
/* 全部 */
#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灵活按索引切片,负数会从后数,但是不会转换范围
参数substrsubstringslice
(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?

参考:下面代码a在什么情况中打印出1

Vue

✨vue-router原理

  • 本质
    SPA单页面应用状态管理器,根据切换渲染不同的组件
  • 模式
  1. hash:使用hash值作为URL,为默认模式
    URL改变时,页面不会重新加载
    通过锚点值#的改变,根据不同的值渲染不同的组件和数据
    每次操作都会保存在历史记录,可以使用“后退”、“前进”
  2. history:依赖H5的history API和环境
    利用history.pushState()history.replaceState(),在不刷新页面的情况下,更改URL
  3. abstract:支持所有运行环境,比如node.js服务端
    没有浏览器API的情况下会强制转abstract模式

route和router的区别

  • route
    当前对象的路由信息
    pathparamsquery等等
  • router
    全局路由的实例
    push()go()

vue和react的区别

参考:vue和react的区别及各自优点
主要看图

✨生命周期

参考:超详细vue生命周期解析(详解)

✨组件通信

参考:vue组件间通信六种方式(完整版)

网络

✨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状态码

statusCodedescription
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
  • 解决
  1. JSONP,动态插入script标签,document.createElement('script'),缺点是只支持GET
  2. Nginx反代,将请求反向代理
  3. CORS,跨域资源共享策略,在后端设置res.setHeader('Access-control-allow-xxxx'),xxx可以是originheader
  4. websocket
  5. postmessage

✨✨输入URL到看到页面的过程

  • DNS解析
    将域名解析为IP地址
  • 发送HTTP请求
    请求包含头部,请求资源参数等
  • 传输层TCP
    三次握手,建立连接
  • 网络层、链路层
    网络层IP协议查询MAC地址,数据进入链路层传输
  • 接受请求,返回资源
    拿到数据后,后端返回,传输过程中从下往上解包
    返回资源和状态码,200,404等
  • 渲染
    DOM树
    CSS渲染数
    JS脚本加载

✨OSI七层模型

层次描述代表
物理层物理传输,为链路层提供了物理连接铜线、双绞线
链路层解决两个节点之间的数据传输MAC协议
网络层为传输层提供服务,数据单元称为数据包或分组IP协议
传输层为上层提供可靠和透明的数据传输服务TCP、UDP
会话层管理和协调不同主机进程之间的通信/会话-
表示层处理数据编码的表示方式、数据格式化、加密-
应用层用户与网络的接口HTTP、FTP、SMTP等

✨TCP、UDP

属性TCPUDP
面向连接有,三次握手
按序交付
可靠数据传输
超时重传
拥塞控制
流量控制
速度
应用场景需要可靠数据传输只需要速度
例子HTTPDNS

三次握手四次挥手

  • 三次握手
  1. 客户端向服务端发起请求,SYN=1,生成序列号seq=m,进入SYN_SENT状态
  2. 服务端收到客户端请求,回应,SYN=1,ACK=1,生成序列号seq=n,确认号ack=m+1,进入SYN_RECEIVED状态
  3. 客户端收到服务端回应,回应ACK=1,生成序列号seq=m+1,确认号ack=n+1,完成三次握手

为什么不能是两次:如果客户端多次请求,前面请求失败了,两次握手的情况下,后续请求到达也会建立连接,浪费资源
SYN洪泛:就是不断向服务端发起SYN的请求,使其不能回应正常的请求

  • 四次挥手
  1. 客户端向服务端发起请求,FIN=1,生成序列号seq=m,进入FIN_WAIT状态
  2. 服务端收到请求,回应ACK=1,生成序列号seq=n,确认号ack=m+1,进入CLOSE_WAIT状态
  3. 进行最终的数据传输
  4. 服务端再次发起请求,FIN=1,ACK=1,生成序列号seq=M,确认号ack=m+1,进入LAST_ACK状态
  5. 客户端收到请求,进入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

参考:IPV4与IPV6的区别(史上最详细)

优化

✨浏览器缓存策略

  • 缓存的位置
    磁盘

  • 强制缓存

  1. cache-control
    http1.0不支持
    优先级大于empires
    格式为:max-age: 1234,数字表示秒
    是缓存有效时间
  2. empires
    优先级较低,但支持版本更早
    格式为时间
  • 协商缓存
    去服务器端询问,是否要用缓存,还是获得新资源
  1. Etag -> if-none-match
    根据Etag来判断修改时间,如果修改过,需要重新缓存
    if-none-match就为true,否则为false
    优先级较高
  2. Last-Modified-> if-modified-since
    根据此字段判定是否需要修改
    缺点:周期性修改但内容未变,会失效;粒度只到秒
  • 过程
  1. 没有缓存:去后台请求
    客户端去向服务器端发送请求, 获得资源,同时服务器端会在请求头部写empires/cache-control等,客户端根据请求头部,保留缓存并设置到期时间
  2. 有缓存,但是到期了:协商缓存
    向服务器端请求,服务器端根据Etag/Last-Modified头部判断是否需要更新缓存
    如果不需要,返回304,客户端去获得缓存资源
    需要更新,返回200,客户端获得新的资源,并且重设缓存
  3. 有缓存,没到期:强制缓存
    直接命中,使用缓存

✨前端性能优化

  • 使用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不修改资源

服务端

✨进程、线程

属性进程线程
从属关系程序运行=进程轻量级进程,从属于进程
单位操作系统调度的基本单位处理器调度资源的最小单位
资源有独立的地址空间和内存共享进程的地址空间和内存,但是有自己独立的资源比如程序计数器
切换开销
崩溃影响不会对其他进程产生影响会影响整个进程

设计模式-单例

  • 单例模式
  1. 前端写法
    module,上下文相关单例
class MyClass {
	// ...
}
export const myClassSingleSample = new MyClass();
export default new MyClass();
  1. 后端写法:懒汉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获取页面长宽,做等列栅格布局

  • 长宽
  1. js获取
document.body.clientWidth; // 可见区域
document.body.offsetWidth; // 可见区域 + 边线
document.body.scrollWidth; // 正文全文
  1. 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
解决方法

  • 外层元素
  1. padding代替margin设置
  2. overflow: hidden // 取值:scroll滚动 hidden隐藏 visiablity可见 auto自动滚动 inhert继承
  • 内层元素
  1. position: absolute
  2. float: left
  3. 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

正则部分

  • 匹配模式
  1. 贪婪匹配:匹配成功继续向右匹配
  2. 非贪婪/惰性匹配:匹配成功就不做事了
  • 匹配结果输出
    利用字符串的match,正则本身有testexec
    字符串可以用replacesearrchmatch
	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等等方式跨域

服务端&插件

webpack(wait)

Node中间件(wait)

SSR(wait)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大熊软糖M

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值