Alan

此刻想举重若轻,之前必要负重前行

记录了自己疫情期间自学+复习总结的一些面试题+面试遇到的一些问题,文章将会持续更新。

背景

由于今年疫情的原因以及一些个人原因,辞去了之前的实习工作。在家待了也快5个月了(期间经历了毕业设计+毕业答辩(2个月),算下来已经有3个月没有上班了😭)。去年在公司实习错过了秋招,今年又碰上疫情,对我这个应届生来说简直是难上加难。在家这段时间自己学习了React、webpack。用React搭建了一直想做但是耽搁了半年的博客,搭建出来后考虑后期维护成本(服务器费用),就没有继续维护了。改用了Hexo+github搭建了一个静态博客。真香😂。

JS

原型及继承

红宝书中的图

组合继承

function Person(name) {
  this.name = name;
  this.features = ['eyes'];
}

function Student(name, id) {
  Person.call(this, name); //继承属性相当于super(this)
  this.id = id;
}

Student.prototype = new Person();

Student.prototype.sayHello = function () {
  console.log('hello');
};

const s1 = new Student('Alan', '001');
const s2 = new Student('Bob', '002');

console.log(s1 instanceof Person); // true
// 引用类型在各实例中不会相互影响
s1.features.push('hand');
console.log(s2.features); // ["eyes"]

更多继承方式及其优缺点可以查看红宝书第六章(讲得非常“干”)

深拷贝和浅拷贝

在JS中,变量分为了基本类型和引用类型。对基本类型进行赋值时是对值进行拷贝的,而对引用类型进行赋值则是对地址进行拷贝。

let a = 1;
let b= a;
console.log(a); // 1
a++;
console.log(a); // 2
console.log(b); // 1
const obj = {
    name: 'Alan',
    age: 22,
}
const cloneObj = obj;
obj.age = 18;
console.log(cloneObj.age); // 18

通过上面的例子我们知道,cloneObj和obj是指向同一个地址的,任何一方修改都会影响到对方,那如何创建一个独立的cloneObj?,这就要使用深拷贝和浅拷贝了。

深拷贝和浅拷贝的区别:

根据拷贝的层级进行区分,浅拷贝只进行一层拷贝,深拷贝进行多层拷贝。

  • 浅拷贝
const obj = {a:1, b:{b1:1, b2:2}, c:0};
function shallowClone(source) {
    const result= {};
    for (const key in source) {
        if (source.hasOwnProperty(key)) {
            result[key] = source[key];
        }
    }
    return result;
}
const shallowObj = shallowClone(obj);
obj.a = 10;
console.log(shallowObj.a); // 1
obj.b.b1 = 6;
console.log(shallowObj.b.b1); // 6
const obj = {a:1, b:{b1:1, b2:2}, c:0};
function shallowClone1(source) {
    return Object.assign({}, source);
}
const shallowObj = shallowClone1(obj);
obj.a = 10;
console.log(shallowObj.a); // 1
obj.b.b1 = 6;
console.log(shallowObj.b.b1); // 6
  • 深拷贝
const obj = {a:1, b:{b1:1, b2:2}, c:0};
function deepClone(source) {
    const result = {};
    for (const key in source) {
        if (source.hasOwnProperty(key)) {
            if (typeof source[key] === 'object') {
                result[key] = deepClone(source[key]);
            } else {
                result[key] = source[key];
            }
        }
    }
    return result;
}
const deepObj = deepClone(obj);
obj.a = 10;
console.log(deepObj.a); // 1
obj.b.b1 = 6;
console.log(deepObj.b.b1); // 1
const obj = {a:1, b:{b1:1, b2:2}, c:0};
function deepClone(source) {
    return JSON.parse(JSON.stringify(source));
}
const deepObj = deepClone(obj);
obj.a = 10;
console.log(deepObj.a); // 1
obj.b.b1 = 6;
console.log(deepObj.b.b1); // 1

数组去重

// 笨方法
const arr = [1,3,45,6,3,2,0];
const newArr = [];
let isRepeat;
for (let i = 0; i < arr.length; i++) {
    isRepeat = false;
    for (let j = 0; j < newArr.length; j++) {
        if (arr[i] === newArr[j]) {
            isRepeat = true;
            break;
        }
    }
    if (!isRepeat) {
        newArr.push(arr[i]);
    }
}
console.log(arr); // [1, 3, 45, 6, 3, 2, 0]
console.log(newArr); // [1, 3, 45, 6, 2, 0]
const arr = [1,3,45,6,3,2,0];
const newArr = arr.filter((item,index) => {
    return arr.indexOf(item) === index
})
console.log(arr); // [1, 3, 45, 6, 3, 2, 0]
console.log(newArr); // [1, 3, 45, 6, 2, 0]
const arr = [1,3,45,6,3,2,0];
const newArr = [...new Set(arr)];
console.log(arr); // [1, 3, 45, 6, 3, 2, 0]
console.log(newArr); // [1, 3, 45, 6, 2, 0]

for in 和 for of

  • for in 会遍历数组中的可枚举属性,包括原型。可以遍历对象,遍历的是key值
  • for of只是遍历数组的元素或者可以迭代的对象,不包括原型。遍历的是value值。
Array.prototype.testMethod = function () {
    console.log('testMethod');
}
const mArr = [1,2,3,7];
const mObject = {
    name: 'Alan',
    age: 1
}
for (const key in mArr) {
    console.log(mArr[key]);
    // 1 2 3 7 
    /*       ƒ () {
        console.log('testMethod');
      } */
}
// 解决方案
for (const key in mArr) {
    if (mArr.hasOwnProperty(key)) {
        console.log(mArr[key]);
        // 1 2 3 7
    }
}
for (const key in mObject) {
    console.log(key);
    // name
    // age
}
try {
    for (const iterator of mObject) {
        console.log(iterator);
    }
} catch (error) {
    console.log(error);
    //mObject is not iterable
}

事件委托

事件委托利用了事件冒泡,只指定了一个事件处理程序,就可以管理某一类型的所有事件。

例如,click事件会一直冒泡到document层次。例如下面例子中我们无需对所有li元素添加onclick事件,只需使用事件冒泡的特性来实现事件委托。

<body>
  <ul id="myList">
    <li id="sayName">Name</li>
    <li id="sayHello">Hello</li>
    <li id="sayAge">Age</li>
  </ul>
  <script>    
    const myList = document.getElementById('myList');
    myList.addEventListener('click', function (e) {
      const target = e.target;
      if (target.id === 'sayName') {
        alert('Alan');
      } else if(target.id === 'sayHello') {
        alert('Hello');
      } else {
        alert('sayAge');
      }
    })
  </script>
</body>

实现滑动动画

<div id="myDiv"></div>

<script>
    const myDiv = document.getElementById('myDiv');
    const time = Date.now(); //时间戳

    const transition = setInterval(() => {
    const timeLength = Date.now() - time;
    const step = (5000 - timeLength) / 1000;
    console.log(step);

    if (timeLength > 5000) {
        clearInterval(transition);
        // 5s后结束
    }
    myDiv.style.left = myDiv.offsetLeft + step + 'px';
}, 50);
</script>

数组扁平化

// 笨方法:遍历
const arr = [1,2,7,[2,[2,3],6]];
function flat(arr) {
    let result = [];
    for (let i = 0; i < arr.length; i++) {
        if(Array.isArray(arr[i])) {
            result = result.concat(flat(arr[i]));
        } else {
            result.push(arr[i]);
        }
    }
    return result;
}
console.log(flat(arr));
// 使用for of
let arr = [1,2,7,[2,[2,3],6]];
function flat(arr) {
    let newArr =[];
    for (const item of arr) {
        if(Array.isArray(item)){
            newArr = newArr.concat(flat(item));
        } else {
            newArr.push(item);
        }
    }
    return newArr;
}
console.log(flat(arr));
const arr = [1,2,7,[2,[2,3],6]];
function flat(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}
console.log(flat(arr));
// 骚操作
const arr = [1,2,7,[2,[2,3],6]];
function flat(arr) {
    const result = arr.toString().split(',').map(item => {
        return +item;
    });
    return result;
}
console.log(flat(arr));

eventLoop

JS是单线程的,那为什么不是多线程的呢,设想一个场景,一个线程修改了body的background为red,另一个线程修改了body的background为green。那最终浏览器就不知道background到底为什么。由此可以看到多线程会为浏览器的DOM操作带来很多同步问题。参考资料

JS的任务可以分为同步任务和异步任务。

1.同步任务优先在主线程上执行,会形成一个执行栈。

2.异步任务会被放入任务队列中,当执行栈清空时会读取任务队列中的任务丢进执行栈中。

1、2两步反复执行形成了eventLoop。

如果将任务细分的话还可以分成宏任务和微任务

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise.then(),process.nextTick

优先级:process.nextTick>Promise.then()

当执行栈中没有任务时,微任务总是优先于宏任务执行

详细查看文章这一次,彻底弄懂 JavaScript 执行机制

this

  • 指向调用它的那个对象,函数运行时获得的。
  • 箭头函数的this指向取决于定义时最近一层的非箭头函数的this值。取决于外部的上下文。

闭包

  • 当前函数的执行上下文中的内容被该上下文以外的内容占用,导致当前上下文无法释放。
  • 闭包是指有权访问另一个函数作用域中的变量的函数。
  • 创建闭包的常见方式,就是在一个函数内部创建另一个函数

涉及到函数作用域链,词法作用域,后期有空再补充

求数组最大值

const arr = [1,2,1,4,2,10];
console.log(Math.max.apply(null, arr));
const arr = [1,2,1,4,2,10];
console.log(arr.sort((a,b) => (a-b))[arr.length-1]);
const arr = [1,2,1,4,2,10];
console.log(Math.max(...arr));

Promise

// new Promise(executor),当new Promise被创建,executor自动执行
let promise = new Promise(function (resolve, reject) {
    resolve('finished');
    // reject(new Error);
});
// Promise.then(f1,f2) f1在resolve后运行(参数为resolve结果),f2在reject后运行(参数为reject错误)
promise.then(
    result => console.log(result), //finished
    error => console.log(error) //输出错误
);
  • promise.all()同时执行多个promise,只要有一个promise被reject,那么将不再执行
  • promise.allSettled()和promise.all()类似,只是会等所有promise执行完。
  • promise.race()返回最快执行完的promise结果。

实现一个sleep

function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

(async () => {
  console.log('2s后输出内容...');
  await sleep(2000);
  console.log('666');
})();

防抖和节流

  • 节流

函数在一定时间内只执行一次,比如点击按钮后回去服务器获取数据,使用节流可以防止短时间内请求多次,减少服务器的压力

  • 防抖

在一定时间后才执行(触发多次只会执行一次)。应用场景:input搜索框在wait秒后再发送请求

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      height: 4000px;
      width: 100px;
      background: rgb(241, 165, 165);
    }
  </style>
</head>
<body>
  <button id="fetchBtn">fetch data</button>
  <span>searchBar</span><input id="inputBar"/>
  <script>
    // 节流
    function throttle(func, wait) {
      let last = 0;
      return function (...args) {
        let now = new Date();
        if (now - last > wait) {
          last = now;
          func.apply(this, args);
        }
      }
    }

    // 防抖
    function debounce(func, wait) {
      let timer = 0;
      return function (...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          func.apply(this, args);
        }, wait)
      }
    }

    document.getElementById('fetchBtn').addEventListener('click', throttle(function (numb) {
      console.log('fetching');
    },1000));
    document.getElementById('inputBar').addEventListener('input', debounce(function (numb) {
      console.log('searching');
    },1000));
  </script>
</body>
</html>

XMLHttpRequest

常用方法:

  • open(method, url, [async, user, password])初始化,async(false/true)控制同步/异步
  • send([body])发送请求
  • abort()中止请求
  • setRequestHeader(name, value)设置请求头

常用属性:

status: 404/200…

statusText: Not Found/OK…

responseType: 响应格式

readyState: 状态

withCredentials: 跨域设置

UNSENT = 0; // 初始状态
OPENED = 1; // open 被调用
HEADERS_RECEIVED = 2; // 接收到 response header
LOADING = 3; // 响应正在被加载(接收到一个数据包)
DONE = 4; // 请求完成

常用监听事件:

  • onload
  • onerror
  • onprogress

题目

const a = ['1', '2', '3'].map(parseInt);
// 数组a中的'1'转化为10进制。
console.log(a); // [1, NaN, NaN]
// map的三个参数(item,index,array)
/* parseInt(string, radix)
当radix等于0或者undefined或者没有指定时,如果string以'0x'或者''0X'开头,则radix=16
以'0'开头,根据实际情况radix=10/8。 */
const users = [
    {
        name: 'Alan',
        age: 19
    },
    {
        name: 'Bob',
        age: 25
    }
];
const userList = users.sort((a, b) => b.age-a.age);
// 根据年龄进行排序,注意:sort会改变原来的数组
function isSameLetter(a, b) {
    a = a.toString().toLowerCase();
    b = b.toString().toLowerCase();
    return a.split('').sort().join('') === b.split('').sort().join('');
}
console.log(isSameLetter('176as', 'a17s6'));
//判断两者是否是由相同的字母组成,顺序可以不一样

CSS

css单位

  • rem:相对于根元素的字体大小(html)css3

    如果html的font-size为16px(默认),那么1rem=16px

  • em:如果该元素有设置font-size,那么相对于该元素。如果没有设置则相对于父元素。

    例如,div设置了font-size为10px,那么该div中使用em时,1em为10px

    如果该元素没有设置font-size且父元素设置font-size为20px,那么1em为20px

  • vh/vw相对于视窗,10vh=1/10的屏幕高

translate和使用绝对定位的区别

translate会占据原来的位置,绝对定位会脱离文档流。

BFC

概念:很模糊抽象,是页面上一个隔离的独立容器,容器中的子元素不会影响到外面的元素。

试用场景:清理浮动,解决margin重叠

一个元素不能同时存在两个BFC中

创建方式:

  • 根元素
  • float不为none
  • position: absolute/fixed
  • display: inline-block/table-cell
  • overflow不为visible
  • 弹性盒子(display: flex/inline-flex

清理浮动

  • 伪类元素

    .clearfix::after {
        content: '';
        display: blcok;
        clear: both;
    }
  • 创建BFC

    overflow: hidden;
    
    overflow: auto;
  • 空盒子

    <div style="clear: both;"></div>

选择器优先级

!important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承

垂直居中

<div class="outer">
    <div class="inner"></div>
</div>
  • table-cell
.outer {
    width: 400px;
    height: 400px;
    background: aqua;
    display: table-cell;
    vertical-align: middle;
    text-align: center;
}
.inner {
    width: 100px;
    height: 100px;
    background: brown;
    display: inline-block;
}
  • flex布局

  • 绝对定位

    .outer {
        position: relative;
    }
    .inner {
        position: absolute;
        left: 50%;
        top: 50%;
        margin-left: -50px; /* 宽度一半 */
        margin-top: -50px; /* 高度一半 */
    }
    .outer {
        position: relative;
    }
    .inner {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
    }
    .outer {
        position: relative;
    }
    .inner {
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        margin: auto;
    }

HTML

meta标签

<meta name="viewport" content="width=device-width, initial-scale=1.0">

网络

从输入URL到页面加载发生了什么

  1. 浏览器查找当前URL是否存在缓存,并比较缓存是否过期
  2. 根据DNS解析得到IP地址
  3. 建立TCP连接(3次握手)
  4. HTTP发请求
  5. 服务器处理请求,返回数据
  6. 渲染页面,构建DOM树
  7. 关闭TCP连接(4次挥手)

常用状态码

分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误
状态码 描述
200 请求成功
204 无内容。服务器成功处理,但未返回内容
301 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
304 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
400 客户端请求的语法错误,服务器无法理解
401 请求要求用户的身份认证
403 服务器理解请求客户端的请求,但是拒绝执行此请求
404 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置”您所请求的资源无法找到”的个性页面
500 服务器内部错误,无法完成请求

网络七层协议

引用网上的一张图,如侵权请联系我删除😂

image-20200601083205320

引自维基百科TCP/IP协议族是一个网络通信模型,以及一整个网络传输协议家族,为互联网的基础通信架构。该协议家族的两个核心协议:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准。这个协议族由互联网工程任务组负责维护。

TCP(Transmission Control Protocol 传输控制协议):通过三次握手与服务器建立一个全双工的通信,在数据传送之前把数据分割成IP包,在到达服务器时再将他们重组。

IP(Internet Protocol 网际协议):IP负责客户端与服务端之间的通信,IP负责在因特网上发送和接受数据包。通过IP,数据被分割成小的独立的包,然后通过互联网在计算机之间传送。

HTTP

HTTP(HyperText Transfer Protocol),基于TCP实现的应用层协议。HTTP是一个无状态的协议,即客户端和服务端之间不用建立持久的链接,当客户端发送一个请求,服务端返回响应时,连接就被关闭了。

当请求一个url时www.baidu.com,首先DNS解析出该地址对应的IP地址,然后将相关信息封装成一个HTTP请求数据包。然后TCP(三次握手)建立连接,连接成功后发送HTTP请求,服务端响应回来后,关闭TCP连接。如果浏览器或服务器在头部加入了Connection:keep-alive,那么TCP在发送完信息后仍然保存连接,这样可以减少TCP连接次数,能提升性能和服务器吞吐率。

缺点:

  • 通行使用明文,不安全
  • 不验证身份,可能遭遇伪装
  • 无法验证报文完整性,可能遭遇篡改

前面提到了HTTP是一个无状态的协议,那如何让它拥有状态?

session和cookie

cookie(key/value形式):

客户端返回cookie附在响应头中的Set-Cookie或者Set-Cookie2,cookie保存在客户端,下次访问时连同cookie发送给服务端

cookie的同源和跨域:

cookie只关注域名,忽略协议和端口,意思是协议或者端口不同不属于跨域

session机制(服务端维护的),基于cookie工作:

session和sessionId(返回给客户端)

当客户端访问服务端时,首先检查请求中是否包括sessionId。如果有,通过sessionId检索出对应session进行一系列操作。如果没有sessionId,创建一个session以此对应的sessionId通过cookie存在客户端,客户端之后访问服务端时通过这个sessionId维护HTTP状态。

HTTPS

HTTPS = HTTP + TLS/SSL

经过加密的HTTP,更加安全,利用SSL/TLS加密数据包;

TLS/SSL:安全传输层协议Transport Layer Security

TSL/SSL涉及到三种算法:

  • 散列函数Hash:MD5、SHA1、SHA256(函数单向不可逆,加密传输信息以及信息摘要)
  • 对称加密:AES-CBC、DES、3DES、AES-GCM
  • 非对称加密:RSA(分公钥和私钥)

TSL工作方式:

首先使用非对称加密算法加密进行通信,服务端返回公钥并协商使用对称加密后的密钥对通信进行加密。

RSA存在的隐患:

无法确保服务器身份的合法性(因为公钥不包含服务器信息):

例如:

C(客户端)和S(服务端)通信时,H(劫持者)截获了S与C的通信,H将自己的公钥pub_H给了C,当C向S发送请求时,实际上是使用pub_H加密的,这样H就能获取到C发送的消息了。这样原本属于C和S的通信现在就变成了C和H的通行了。

解决方案:引入第三方认证机构,验证公钥拥有者的信息然后颁发证书,简称PKI体系

- S向第三方机构提交公钥和公司相关信息
- CA(即三方认证机构)通过一系列渠道验证审核S信息的正确性
- 审核通过后,CA向S签发证书,证书信息包括:S的公钥、S公司相关信息、签发机构CA的信息、有效时间、证书序列号(前面这些信息全是明文显示)。签名(使用散列函数计算公开的明文信息的信息摘要,然后用CA的私钥对信息摘要加密)
- C向S发送请求,S返回证书
- C读取证书中的明文信息,采用相同的散列函数计算得到信息摘要,然后利用CA对应的公钥对签名进行解密,对比证书中的信息摘要来验证证书/公钥的合法性。

跨域

非同源请求,均为跨域。名词解释:同源 —— 如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(origin)。

image-20200403163435103

jsonp:

通过script标签的src属性来实现

CORS:

服务端设置Access-Control-Allow-Origin和Access-Control-Allow-Credentials为true

Vue设置跨域:

vue.http.options.credentials = true

Nginx代理

框架

单页应用优缺点

优点:

  • 基于ajax加载数据,无需刷新页面,用户体验好
  • 前后端分离,后端代码可以应用到多端
  • 减轻服务器压力

缺点:

  • 不利于SEO,可以使用SSR解决
  • 首屏加载速度慢
  • 不支持浏览器前进后退功能

评论