『前端面试资料』 个人面试准备手稿 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
    评论
慕课哈工大C语言第五周马克思手稿中的趣味数学题是一道有趣且具有挑战性的数学问题。这个问题是这样描述的:给定一个三位数n,将其个位数、十位数和百位数分别记作a、b和c,然后按照以下公式进行计算:n = a^3 + b^3 + c^3。如果计算结果等于n本身,则称该数为完全立方数。 这个问题需要我们编写程序,将所有的三位数都进行计算,并判断它们是否为完全立方数。代码的实现思路是使用三层循环,分别遍历所有的个位数、十位数和百位数的可能取值,然后计算并判断是否为完全立方数。具体代码如下: ```c #include <stdio.h> int main() { int n, a, b, c; for (n = 100; n <= 999; n++) { a = n % 10; // 取个位数 b = (n / 10) % 10; // 取十位数 c = n / 100; // 取百位数 if(n == (a * a * a + b * b * b + c * c * c)) { printf("%d 是完全立方数\n", n); } } return 0; } ``` 这段代码使用了一个for循环,从100到999遍历所有的三位数。在每次循环中,通过取余和除法操作分别得到该数的个位数、十位数和百位数,并计算它们的立方和。然后,通过if语句判断计算结果是否等于原数n,如果相等,则输出该数是完全立方数。 通过运行这段代码,我们可以得到所有的完全立方数,即153和370。这个问题展示了编程在解决数学问题中的应用,同时也让我们了解到了完全立方数的概念和计算方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大熊软糖M

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

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

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

打赏作者

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

抵扣说明:

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

余额充值