前端面试题汇总

313 阅读28分钟

Javascript基础

0.1 + 0.2 === 0.3 ?

计算机的底层都是由二进制来表示的,对于 0.1和 0.2 这种数字在用二进制储存的时候会有精度误差

base64编码原理

将二进制数据进行分组,以6位一组进行分组,并在每组前面都填两个高位 0,然后将 8 bit 的字节转换成十进制,对照 BASE64 编码表,得到对应编码后的字符。 一个字节是8bit,一个base64的字符6bit,24是最小公倍数,所以3个字节可以完整转化为4个base64字符; 但是我们无法控制需要编码的数据正好是3的倍数,所以要进行补零---在不足3的倍数的字符串末尾用0x00进行填充; 因为base64编码中的下标0对应的字符是'A',而末尾填充上的0x00在分组补零后同样是下标0x00,这样就无法分辨出到底是末尾填充的0x00还是二进制数据中的0x00。 所以引进了等号,这就是'='字符不在Base64字符集中,但是也出现在Base64编码的原因了。

以对6666P进行base64编码的步骤说明
每个字符转化为8bit:
6----->00110110
6----->00110110
6----->00110110
6----->00110110
P----->01010000
补位----->00000000
整体拼接结果:
0011011000110110001101100011011001010000000000006bit一组进行分割:
001101  100011  011000  110110  001101  100101  000000  000000
转化为base64编码脚标:
13  35  24  54  13  37  0  0
获取对应的base64编码:
N  j  Y  2  N  l  A  A
得到结果:string(8) "NjY2NlA="

this

var length = 10;
function fn() {
	console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1);

输出

10
2

变量提升

函数在运行的时候,会首先创建执行上下文,然后将执行上下文入栈,然后当此执行上下文处于栈顶时,开始运行执行上下文。 在创建执行上下文的过程中会做三件事:创建变量对象,创建作用域链,确定 this 指向,其中创建变量对象的过程中,首先会为 arguments 创建一个属性,值为 arguments,然后会扫码 function 函数声明,创建一个同名属性,值为函数的引用,接着会扫码 var 变量声明,创建一个同名属性,值为 undefined,这就是变量提升。 let 存在变量提升,但是与 var 不同的是,var 的变量提升的同时会默认赋值为 undefined. 而 let 仅仅发生了提升而已,并不会赋任何值给变量,在显式赋值之前,任何对变量的读写都会导致 ReferenceError 的报错。从代码块(block)起始到变量求值(包括赋值)以前的这块区域,称为该变量的暂时性死区。

问题1

var x = 21;
var girl = function () {
    console.log(x);
    var x = 20;
};
girl ();

输出

undefined

问题2

(function () {
    try {
        throw new Error();
    } catch (x) {
        var x = 1, y = 2;
        console.log(x);
    }
    console.log(x);
    console.log(y);
})();

输出

1
undefined
2

事件循环机制/JS执行机制/JS如何实现异步

首先,JavaScript是单线程,为了充分理由有限的资源,分同步任务和异步任务。事件循环就是线程中的任务执行完成后,将event queue中的任务放到线程执行,一直重复这个过程。

我们将任务区分为微任务和宏任务:

macro-task(宏任务):包括整体代码script,setTimeout,setInterval

micro-task(微任务):Promise,process.nextTick

setTimeout(function() {
    console.log('setTimeout');
})

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');

这段代码作为宏任务,进入主线程。 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。(注册过程与上同,下文不再描述) 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。 遇到console.log(),立即执行。 好啦,整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。 ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。 结束。

ES6 Static关键字

为类的函数对象直接添加方法,而非放到原型链上

箭头函数和普通函数的区别

  1. 箭头函数不能作为构造器,没有constructor
  2. 箭头函数的this声明时即确定,不受bind、call、apply影响

JS数据类型

Number String Boolean Object Function Array Symbol Null Undefined BigInt

NaN

Not a number

NaN !== NaN
typeof NaN === 'number'
Boolean(NaN) === false

判断类型

  1. typeof 对于对象,只能区分object和function;typeof null === 'object'
  2. instanceof 常用于判断具体的对象类型 都说instanceof只能判断对象类型
1 instanceof Number === false
'1' instanceof String === false

但也可以指定对象的实例判断方式

class CheckIsNumber {
  static [Symbol.hasInstance](number) {
    return typeof number === 'number'
  }
}

// true
1 instanceof CheckIsNumber
  1. Object.prototype.toString.call 能判断的类型最完善,看似最佳方式
  2. isXXX Api Array.isArray isNaN

类型转换

强制转换

Number()
Boolean()
String()
toString()
  1. 转布尔值的规则 undefined、null、false、NaN、''、0、-0 都转为 false,其他为true
  2. 转数字的规则 false、null为0,true为1,undefined及不可转字符串NaN,symbol报错,字符串如果是数字和二进制正常转换

隐式转换

  1. 对象转基本类型
    • 调用 Symbol.toPrimitive,转成功就结束
    • 调用 valueOf,转成功就结束
    • 调用 toString,转成功就结束
    • 报错
  2. 四则运算
    • 只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型
    • 其他运算只要其中一方是数字,那么另一方就转为数字
  3. ==操作符
[] == ![] //true

x == y

  • 若x y属于同一种数据类型

页面卡顿的原因

  1. 网络请求太多,导致数据返回较慢,可以适当做一些缓存或者请求合并
  2. 某块资源的bundle太大,可以考虑拆分一下
  3. js代码,是不是某处有过多循环导致占用主线程时间过长
  4. 浏览器某帧渲染的东西太多,导致的卡顿
  5. 在页面渲染过程中,频繁触发重排重绘
  6. 内存泄漏

垃圾回收机制

  1. 通常全局状态(window)下的变量是不会被自动回收的
  2. 标记-清除,在离开局部作用域后,若该作用域内的变量没有被外部作用域所引用,则会被标记,后续会被清除
  3. 标记-整理,在清空部分垃圾数据后释放了一定的内存空间后会可能会留下大面积的不连续内存片段,导致后续可能无法为某些对象分配连续内存,此时需要整理一下内存空间
  4. 交替执行,因为JavaScript是运行在主线程上的,所以执行垃圾回收机制时会暂停js的运行,若垃圾回收执行时间过长,则会给用户带来明显的卡顿现象,所以垃圾回收机制会被分成一个个的小任务,穿插在js任务之中,即交替执行,尽可能得保证不会带来明显的卡顿感

内存泄漏

由于疏忽或者程序的某些错误造成未能释放已经不再使用的内存。

  1. 闭包使用不当引起内存泄漏
  2. 全局变量
  3. 分离的DOM节点: 假设你手动移除了某个dom节点,本应释放该dom节点所占用的内存,但却因为疏忽导致某处代码仍对该被移除节点有引用,最终导致该节点所占内存无法被释放
    let btn = document.querySelector('button')
    let child = document.querySelector('.child')
    let root = document.querySelector('#root')
    
    btn.addEventListener('click', function() {
        root.removeChild(child)
    })
    
  4. 控制台的打印: 如果浏览器不一直保存着我们打印对象的信息,我们为何能在每次打开控制的Console时看到具体的数据呢
  5. 遗忘的定时器
    function fn1() {
        let largeObj = new Array(100000)
    
        setInterval(() => {
            let myObj = largeObj
        }, 1000)
    }
    

Javascript手写题

斐波那契数列

function getFibonacci(n) {
  let fibonacci = [];
  let i = 0;
  while (i < n) {
    if (i <= 1) {
      fibonacci.push(i)
    } else {
      fibonacci.push(fibonacci[i - 1] + fibonacci[i - 2]);
    }
    i++
  }
  return fibonacci
}

回文

str === str.split('').reverse().join('')

柯里化

  1. 参数数量足够才执行
function createCurry(func, args) {
  var originArgsLen = func.length;
  var args = args || [];
  
  return function () {
    var _args = [].slice.apply(arguments);
    args.push(..._args);
    
    if (args.length < originArgsLen) {
      return createCurry.call(this, func, args);
    }
    
    return func.apply(this, args);
  }
}
  1. 传入参数即可执行

new

  1. 创建一个空对象
  2. 取得构造函数,即第一个参数
  3. 将构造函数的原型链接至实例的__proto__
  4. 使用剩余参数和当前实例作为this执行构造函数
  5. 若构造函数返回对象则返回该对象,否则返回实例
function myNew() {
    let obj = {};
    let constructor = [].shift.call(arguments);
    obj.__proto__ = constructor.prototype;
    const result = constructor.apply(obj, arguments);
    if(result && typeof result === 'object') return result;
    return obj;
}

Promise

class MyPromise {
  constructor(fn) {
    this.callbacks = [];
    this.state = "PENDING";
    this.value = null;

    fn(this._resolve.bind(this), this._reject.bind(this));
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) =>
      this._handle({
        onFulfilled: onFulfilled || null,
        onRejected: onRejected || null,
        resolve,
        reject,
      })
    );
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  _handle(callback) {
    if (this.state === "PENDING") {
      this.callbacks.push(callback);

      return;
    }

    let cb =
      this.state === "FULFILLED" ? callback.onFulfilled : callback.onRejected;
    if (!cb) {
      cb = this.state === "FULFILLED" ? callback.resolve : callback.reject;
      cb(this.value);

      return;
    }

    let ret;

    try {
      ret = cb(this.value);
      cb = this.state === "FULFILLED" ? callback.resolve : callback.reject;
    } catch (error) {
      ret = error;
      cb = callback.reject;
    } finally {
      cb(ret);
    }
  }

  _resolve(value) {
    if (value && (typeof value === "object" || typeof value === "function")) {
      let then = value.then;

      if (typeof then === "function") {
        then.call(value, this._resolve.bind(this), this._reject.bind(this));

        return;
      }
    }

    this.state === "FULFILLED";
    this.value = value;
    this.callbacks.forEach((fn) => this._handle(fn));
  }

  _reject(error) {
    this.state === "REJECTED";
    this.value = error;
    this.callbacks.forEach((fn) => this._handle(fn));
  }
}

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error("fail")), 3000);
});

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000);
});

p2.then((result) => console.log(result)).catch((error) => console.log(error));

call

Function.prototype.myCall = function() {
    const context = [].shift.call(arguments) || windows;
    context.fn = this;
    context.fn(...arguments);
    delete context.fn
}

bind

Function.prototype.myBind = function() {
    const context = [].shift.call(arguments) || windows;
    const _this = this;
    const args = arguments
    return function() {
        _this.call(context, ...args, ...arguments)
    }
}
    

apply

Function.prototype.myApply = function() {
    const context = [].shift.call(arguments) || windows;
    context.fn = this;
    context.fn(...arguments[0]);
    delete context.fn
}

拷贝

浅拷贝

const a = {...b}
const a = Object.assign({}, b)

深拷贝

function deepCopy(obj) {
    let copy = obj instanceof Array ? [] : {};
    for(let i in obj) {
        if(obj.hasOwnProperty(i)) {
            if(typeof obj[i] === 'object') {
                copy[i] = deepCopy(obj[i]);
            } else {
                copy[i] = obj[i]
            }
        }
    }
    return copy;
}

debounce

某个函数在规定的时间没有触发第二次则执行

function debounce(fn, time) {
    let timer = null;
    return function() {
        const args = [...arguments];
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
            fn(...args)
        }, time)
    }
}

throttle

某个函数在规定的时间内只能触发一次

function throttle(fn, time) {
    let timer = null;
    let lastTriggerTime = Date.now();
    return function() {
        const span = Date.now() - lastTriggerTime;
        if(span >=time) {
            fn(...arguments)
        }
    }
}

instanceof

function instanceOf(left, right) {
    let leftProto = left.__proto__;
    let rightPrototype = right.prototype;
    while(true) {
        if(leftProto === null) {
            return false;
        }
        if(leftProto === rightPrototype) return true
        leftProto = leftProto.__proto__
    }
}

数组去重

Array.from(new Set([1,2,3,3]))

数组扁平化

function flatArray(arr) {
    let result = [];
    arr.forEach(item => {
        if(Array.isArray(item)) {
            result = result.concat(flatArray(item))
        } else {
            result = result.concat(item)
        }
    })
    return result;
}

字符串解析——CSV

目标

  1. '1,2,3' => [['1', '2', '3']]
  2. 'a, b, "c, d" => [['a', 'b', 'c, d']]
  3. '"he,said""I'll,stay,here""",2,3,abc=-.,"abc",""""\n4,5,""' => [['he,said"I'll, stay, here"', '2', '3', 'abc=-.', 'abc', '"'], ['4', '5', '']]
function parse(csv) {
    if(!csv) return
    const rows = csv.split('\n');
    return rows.map(row => row.trim().split(",(?=([^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*$)",-1);)
}

字符串解析——URL Params 为对象

输入

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';

输出

{ user: 'anonymous',
  id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
  city: '北京', // 中文需解码
  enabled: true, // 未指定值得 key 约定为 true
}
*/

方法

function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1];
  const paramsArr = paramsStr.split('&');
  let paramsObj = {};
  paramsArr.forEach(param => {
    if (/=/.test(param)) {
      let [key, val] = param.split('=');
      val = decodeURIComponent(val);
      val = /^\d+$/.test(val) ? parseFloat(val) : val;

      if (paramsObj.hasOwnProperty(key)) {
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else {
        paramsObj[key] = val;
      }
    } else {
      paramsObj[param] = true;
    }
  })

  return paramsObj;
}

字符串解析-模版引擎实现

输入

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}

输出

我是姓名,年龄18,性别

方法

function render(template, data) {
  const reg = /\{\{(\w+)\}\}/;
  if (reg.test(template)) {
    const name = reg.exec(template)[1];
    template = template.replace(reg, data[name]);
    return render(template, data);
  }
  return template;
}

字符串转化——驼峰命名

输入

var s1 = "get-element-by-id"

输出

getElementById

方法

var f = function(s) {
    return s.replace(/-\w/g, function(x) {
        return x.slice(1).toUpperCase();
    })
}

字符串中出现最多的字符和个数

输入

"abcabcabcbbccccc"

输出

c 8

方法

let num = 0;
let char = '';
str = str.split('').sort().join('');
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {
    if(num < $0.length){
        num = $0.length;
        char = $1;        
    }
})

字符串转化——千位分隔符

目标

12345.78 => 12,345.78

123456890 => 123,456,890

字符串 stringObject 的 replace() 方法执行的是查找并替换的操作。它将在 stringObject 中查找与 regexp 相匹配的子字符串,然后用 replacement 来替换这些子串。如果 regexp 具有全局标志 g,那么 replace() 方法将替换所有匹配的子串。否则,它只替换第一个匹配子串。

function parseToMoney(num) {
  num = parseFloat(num.toFixed(3));
  let [integer, decimal] = String.prototype.split.call(num, '.');
  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
  return integer + '.' + (decimal ? decimal : '');
}

字符串转化——调换姓名

输入

Sue,Zhao

输出

Zhao Sue

方法

str.replace(/(\s*\w+\s*), (\s*\w+\s*)/, '$2 $1')

html

datalist

h5中的datalist属性可以给文本输入框提供自动完成特性

<input type="search" maxlength="32" placeholder="探索掘金" value="" class="search-input" data-v-1a3f6ed6="" list="search">
<datalist id="search">
    <option value="123"></option>
    <option value="456"></option>
</datalist>

image.png

CSS

css3新特性

border-radius box-shadow rgba text-shadow gradien ::selection transform border-image column-count/rule/gap

link import

  1. link 是html标签,无兼容问题; import 不兼容IE5-
  2. link 页面加载时同时加载; import 页面加载完后再加载
  3. link 可以JS动态引入; import 不支持
  4. link引入的样式优先级大于import

内联元素和块级元素的区别

块级元素总是独占一行,可设置宽、高、padding、margin 内联元素不换行,不可设置宽高,不可设置margin-bottom margin-top padding-top padding-bottom

精灵图

把一堆小的图片整合到一张大的图片上。然后利用CSS的background-position的组合进行背景定位 优势:

  1. 减少网络请求
  2. 减小图片大小
  3. 便于更换风格
  4. 免去命名烦恼 劣势:
  5. 定位图片难度
  6. 维护麻烦,背景少许改动有可能影响整张图片

margin 和 padding 使用场景

margin

  1. 需要在border外侧添加间隔
  2. 间隔处不需要背景
  3. 上下相连的两个盒子的间隔,需要相互抵消

padding

  1. 需要在border内侧添加间隔
  2. 间隔处需要背景
  3. 上下相连的两个盒子的间隔,不能相互抵消

作者:Coderfei 链接:juejin.cn/post/684490… 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

元素竖向的百分比设定是相对于容器的高度吗

当按百分比设定一个元素的宽度时,它是相对于父容器的宽度计算的,但是,对于一些表示竖向距离的属性,例如 padding-top , padding-bottom , margin-top , margin-bottom 等,当按百分比设定它们时,依据的也是父容器的宽度,而不是高度。

css伪类和伪元素的区别

伪类 :link :visited hover active focus 关注元素状态 伪元素 :before :after :first-letter :first-line 虚拟元素

隐藏元素

  1. display: none
  2. visibility: hidden
  3. width: 0; height: 0;
  4. opacity: 0
  5. z-index: -1000

清除浮动

什么是浮动

要知道在css中一些元素是块级元素,他们会自动启用新的一行,还有另一种内联元素也就是行内元素,它们会与之前的内容保持显示在“一行”;但是有时候我们需要改变这种布局方式,这就需要利用css浮动来实现。css浮动允许给定的元素挪动到它那一行的一侧,并且其他内容向下流动。一个右浮动的元素将被推动直到它的容器的右侧,并且内容会沿着它的左侧向下流动,一个有浮动的元素会被挪动到左侧,内容会沿着它的右侧向下流动。当元素浮动之后,不会影响块级元素的布局,只会影响内联元素布局。

为什么要清除浮动

  1. 内容重叠
  2. 父容器高度塌陷

如何清除浮动

外间距折叠

在CSS中,两个或多个毗邻的普通流中的盒子(可能是父子元素,也可能是兄弟元素)在垂直方向上的外边距会发生叠加,这种形成的外边距称之为外边距叠加。

  1. 两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
  2. 两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
  3. 两个外边距一正一负时,折叠结果是两者的相加的和。

rgba opacity的不同

opacity是css属性,可以继承;rgba是属性值,不会被继承

消除display: inline-block 元素间的换行符空格间隙

同行显示的inline-block元素之间会有一个换行符,一般占据4px位置(例如Chrome浏览器 字体默认大小16px,行内块元素间隔4px,字体大小越大间隔越大)

image.png

  1. 父元素字体大小设置为0,自元素字体重新设置。
  2. 去掉换行符或者注释换行符
  3. letter-spacing缩进

列布局

column-count: 列数 column-gap: 每列之间的间隙 column-rule: 两列之间的分割线宽度 样式 颜色

水平垂直居中背景图

background-position: center

oblique italic的区别

前者是倾斜后者是斜体,oblique可以使没有斜体属性的字体倾斜,如果有斜体属性,则表现为斜体。

HTTP

TCP/IP 五层模型

物理层 链路层 mac 网络层 ip arp 传输层 tcp udp 应用层 http ftp

HTTPS & HTTP

HTTPS 的全称是 Hypertext Transfer Protocol Secure,从名称我们可以看出 HTTPS 要比 HTTPS 多了 secure 安全性这个概念,实际上, HTTPS 并不是一个新的应用层协议,它其实就是 HTTP + TLS/SSL 协议组合而成,而安全性的保证正是 TLS/SSL 所做的工作。

  1. 访问方式不一样 一个是https前缀一个是http前缀
  2. 默认端口号不一样 http默认端口80 https默认端口443
  3. HTTP 是未经安全加密的协议,它的传输过程容易被攻击者监听、数据容易被窃取、发送方和接收方容易被伪造;而 HTTPS 是安全的协议,它通过 密钥交换算法 - 签名算法 - 对称加密算法 - 摘要算法 能够解决上面这些问题。

GET & POST

  1. get方法一般用来请求资源,post方法一般用来提交表单修改资源
  2. get方法的请求参数直接暴露在url中,容易被伪造攻击,post方法的请求参数在请求体body中,对于用户不可见
  3. get方法对于URL有长度限制,POST请求题body没有长度限制
  4. 浏览器会默认cache get请求
  5. 对于 get 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据);而对于 post,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)

无状态协议

HTTP是一种无状态协议,即服务器不保留与客户交易时的任何状态 如何变成有状态

Cookie

Cookie 会根据从服务器端发送的响应报文内的一个叫做 Set-Cookie 的首部字段信息,通知客户端保存 Cookie,当下次客户端再往该服务器发送请求时,客户端会自动在请求报文中加入 Cookie 值后发送出去。

也就是 Cookie 是服务器生成的,但是发送给客户端,并且由客户端来保存。每次请求加上 Cookie就行了。服务器端发现客户端发送过来的 Cookie 后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息。

Session

Session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 Session 保存在服务器上。

客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是 Session。客户端浏览器再次访问时,只需要从该 Session 中查找该客户的状态就可以了。

虽然 Session 保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为 Session 需要使用Cookie 作为识别标志。HTTP协议是无状态的,Session 不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为 JSESSIONID 的 Cookie,它的值为该 Session 的 id(即放在HTTP响应报文头部信息里的Set-Cookie)。Session依据该 Cookie 来识别是否为同一用户。 如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

二者区别

  1. Cookie 数据存放在客户的浏览器上,Session 数据放在服务器上,过期与否服务端决定;
  2. Cookie 不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用 Session ;
  3. Session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE;
  4. 单个Cookie 在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能超过3K;
  5. Cookie 和 Session 的方案虽然分别属于客户端和服务端,但是服务端的 Session 的实现对客户端的 Cookie 有依赖关系的,上面我讲到服务端执行 Session 机制时候会生成 Session 的 id 值,这个 id 值会发送给客户端,客户端每次请求都会把这个 id 值放到 http 请求的头部发送给服务端,而这个 id 值在客户端会保存下来,保存的容器就是 Cookie,因此当我们完全禁掉浏览器的Cookie的时候,服务端的Session也会不能正常使用。
  6. session会话机制:session会话机制是一种服务器端机制,它使用类似于哈希表(可能还有哈希表)的结构来保存信息。 cookies会话机制:cookie是服务器存储在本地计算机上的小块文本,并随每个请求发送到同一服务器。 Web服务器使用HTTP标头将cookie发送到客户端。在客户端终端,浏览器解析cookie并将其保存为本地文件,该文件自动将来自同一服务器的任何请求绑定到这些cookie

UDP & TCP

UDP 的全称是 User Datagram Protocol,用户数据报协议。它不需要所谓的握手操作,从而加快了通信速度,允许网络上的其他主机在接收方同意通信之前进行数据传输。 TCP 的全称是Transmission Control Protocol ,传输控制协议。它能够帮助你确定计算机连接到 Internet 以及它们之间的数据传输。通过三次握手来建立 TCP 连接,三次握手就是用来启动和确认 TCP 连接的过程。一旦连接建立后,就可以发送数据了,当数据传输完成后,会通过关闭虚拟电路来断开连接。

TCPUDP
建立连接后才可传输无需连接即可传输
数据包之间有顺序数据包之间无顺序
速度较慢速度较快
错误检查 + 错误修复错误检查+直接丢弃
头部20字节头部8字节

TCP三次握手(建立连接)

  1. 客户端发送SYN(同步序列编号)sequenceNumber = x
  2. 服务端打开客户端链接,发送SYN(sequenceNumber = y)+ ACK(acknowledgeNumber = x + 1)
  3. 客户端确认收到,发送ACK(acknowledgeNumber = y + 1 为什么不是两次握手?

因为两次握手的话,客户端知道了服务端可以收发报文,但是服务端只知道客户端可以发送报文,但不知道客户端是否可以接受报文

TCP四次握手(终止连接)

  1. 客户端发送FIN,进入FIN_WAIT_1阶段
  2. 服务端确认,发送ACK,客户端收到ACK,进入FIN_WAIT_2阶段
  3. 服务端发送FIN,客户端进入TIME_WAIT阶段
  4. 处于 TIME_WAIT 状态的客户端允许重新发送 ACK 到服务器为了防止信息丢失。客户端在 TIME_WAIT 状态下花费的时间取决于它的实现,在等待一段时间后,连接关闭,客户端上所有的资源(包括端口号和缓冲区数据)都被释放。

HTTP Code

1XX

用于信息提示

  1. 100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分
  2. 101 Switching Protocols 服务器理解并愿意服从客户端含有Upgrade信息头字段的请求,以改变当前连接使用的应用层协议。

2XX

成功

  1. 200 一切正常
  2. 201 Created 请求成功,并且服务器创建了新的资源,Location头给出了它的URL
  3. 202 Accepted 已请求成功,但服务器尚未处理(完成)
  4. 203 Non-Authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝,非权威性信息
  5. 204 No Content 服务器已正确处理请求,但没有返回任何内容(文档没有更新,不需要返回)
  6. 205 Reset Content 没有新的内容,浏览器应该强制清楚表单项
  7. 206 Partial Content 服务端完成了客户端发送的一个带Range头的请求

3XX

重定向,客户端浏览器必须采取更多操作来实现请求

  1. 300 Multiple Choices 客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出。如果服务器要提出优先选择,则应该在Location应答头指明。
  2. 301 Moved Permanently 客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL
  3. 302 Found 类似于301,但新的URL应该被视为临时性的替代,而不是永久性的。出现该状态代码时,浏览器能够自动访问新的URL。
  4. 303 See Other 类似于301/302,不同之处在于,如果原来的请求是POST,Location头指定的重定向目标文档应该通过GET提取
  5. 304 Not Modified 客户端有缓存的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)
  6. 305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取
  7. 307 Temporary Redirect 类似303 但浏览器只能跟随对GET请求的重定向

4XX

客户端错误

  1. 400 Bad Request 语法错误
  2. 401 Unauthorized 访问被拒绝,客户试图未经授权访问受密码保护的页面
  3. 403 Forbidden 资源不可用。服务器理解客户的请求,但拒绝处理它
  4. 404 Not Found 无法找到指定位置的资源。这也是一个常用的应答
  5. 405 Method Not Allowed 请求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)对指定的资源不适用
  6. 406 Not Acceptable 指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容
  7. 407 Proxy Authentication Required 要求进行代理身份验证,类似于401,表示客户必须先经过代理服务器的授权
  8. 408 Request Timeout 在服务器许可的等待时间内,客户一直没有发出任何请求
  9. 409 Conflict 通常和PUT请求有关。由于请求和资源的当前状态相冲突,因此请求不能成功
  10. 410 Gone 所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址
  11. 411 Length Required 请求缺失Content-Length头。
  12. 412 Precondition Failed 请求头中指定的一些前提条件失败
  13. 413 Request Entity Too Large 目标文档的大小超过服务器当前愿意处理的大小。如果服务器认为自己能够稍后再处理该请求,则应该提供一个Retry-After头
  14. 414 Request URI Too Long URI太长
  15. 415 不支持的MIME类型
  16. 416 Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头。

5XX

  1. 500 Internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求
  2. 501 Not Implemented 服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码
  3. 502 Bad Gateway 服务器作为网关或代理,从上游服务器收到无效响应
  4. 503 Service Unavailable 服务不可用,服务器由于维护或者负载过重未能应答
  5. 504 Gateway Timeout 网关超时,由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答
  6. 505 HTTP Version Not Supported 服务器不支持请求中所指明的HTTP版本

HTTP缓存

blog.csdn.net/u012375924/… www.cnblogs.com/ranyonsue/p… blog.csdn.net/ws9029/arti…

缓存控制

控制缓存的开关,用于标识请求或访问中是否开启了缓存,使用了哪种缓存方式

  1. Pragma & Expires Pragma 为 no-cache表示禁用缓存 Expires设置了时间 表示 缓存的过期时间
  2. Cache-Control

缓存校验

如何校验缓存,比如怎么定义缓存的有效期,怎么确保缓存是最新的

HTTP请求头

通用标头

Cache-Control

  1. 作为请求头

  2. 作为响应头

Pragma

Connection

  1. keep-alive 一次事务完成后不关闭网络连接
  2. close 一次事务完成后关闭网络连接

实体标头

Content-Length

实体主体的大小,以字节为单位,发送到接收方

Content-Language

客户端或者服务端能够接受的语言

Content-Encoding

这个实体报头用来压缩媒体类型。Content-Encoding 指示对实体应用了何种编码。 常见的内容编码有这几种: gzip、compress、deflate、identity ,这个属性可以应用在请求报文和响应报文中

Content-Type

实体主体的媒体类型

地址栏输入url到页面显示

DNS域名解析

  1. 浏览器缓存
  2. 系统缓存
  3. 路由器缓存
  4. 宽带运营商DNS服务器缓存
  5. 若以上均未找到,则进行DNS服务器查找,拿到IP和端口号将结果保存在缓存中

TCP连接

TCP连接三次握手四次挥手

浏览器渲染

构建DOM树

字节 -> 字符串 -> Token -> Node -> DOM

构建CSSOM树

字节 -> 字符串 -> Token -> Node -> CSSOM

构建渲染树

在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。 也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性 浏览器在构建DOM时,如果遇到Style,构建完CSSOM之后才会去执行JS,执行完JS之后才会继续构建DOM 通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个不带defer或async属性的script标签时,DOM构建将暂停,如果此时又恰巧浏览器尚未完成CSSOM的下载和构建,由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS,最后才重新DOM构建 juejin.cn/post/695307…

绘制

根据渲染树计算每个节点在屏幕上的大小和位置 重绘repaint:当我们对 DOM 的修改导致了样式的变化(比如修改了颜色或背景色)、却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式。 回流relfow:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)

算法

冒泡排序

function bubbleSort(arr) {
    if(!Array.isArray(arr)) return arr;
    if(arr.length < 2) return arr;
    for(let i = 1; i<arr.length ; i++) {console.log(i);
        const max = arr[0];
        for(let j = 0; j< arr.length - i; j++) {console.log(arr[j], arr[j+1])
            if(arr[j] > arr[j+1]) {
                const temp = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

归并排序

function _mergeSort(arr, result, start, end) {
  if (start >= end) return;
  let len = end - start,
    mid = start + Math.floor(len / 2);
  let start1 = start,
    end1 = mid,
    start2 = mid + 1,
    end2 = end;
  _mergeSort(arr, result, start1, end1);
  _mergeSort(arr, result, start2, end2);
  let k = start;
  while (start1 <= end1 && start2 <= end2) {
    result[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
  }
  while (start1 <= end1) {
    result[k++] = arr[start1++];
  }
  while (start2 <= end2) {
    result[k++] = arr[start2++];
  }
  for (let i = start; i <= end; i++) {
    arr[i] = result[i];
  }
  return arr;
}

function mergeSort(arr) {
  if (!Array.isArray(arr) || arr.length < 1) return arr;
  return _mergeSort(arr, new Array(arr.length).fill(null), 0, arr.length - 1);
}

快速排序

function selectionSort(arr) {
  if (!Array.isArray(arr) || arr.length < 2) return arr;
  for (let i = 0; i < arr.length; i++) {
    let minIndex = i;
    for (let j = i; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    const temp = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
  }
  return arr;
}

插入排序

堆排序

数字反转

int reversal(int n) {
    int m;
    while(n) {
        m = m * 10 + n % 10;
        n = n / 10; 
    }
    return m;
}

数组合并

字符串解析数组

输入 '[1,2,3,[4,5,[6,7],8],9]'

输出 [1,2,3,[4,5,[6,7],8],9]

function parse(str, arr = []) {
  if (
    String.prototype.startsWith.call(str, "[") &&
    String.prototype.endsWith.call(str, "]")
  ) {
    str = String.prototype.substring.call(str, 1, str.length-1)
    let temp = [];
    let strList = String.prototype.split.call(str, ',').map(item => item.trim());
    let foundRight = 0;
    for (let i = 0; i < strList.length; i++) {
      if (temp.length && !strList[i].endsWith("]")) {
        temp.push(strList[i]);
      } else if (strList[i].startsWith( "[")) {
        temp.push(strList[i]);
      } else if (strList[i].endsWith("]")) {
        foundRight++;
        if(foundRight === temp.filter(item => item.startsWith('[')).length) {
          temp.push(strList[i]);
          let ctx = [];
          arr.push(ctx)
          parse(temp.join(','), ctx);
          temp = [];
          foundRight = 0;
        } else {
          temp.push(strList[i])
        }
        
      } else {
        arr.push(strList[i]);
      }
    }
  }
  return arr;
}
console.log(parse('[1,2,3,[4,5, [6,7],8],9]'))

全排列

const permute = (nums) => {
  const res = [];
  const used = {};

  function dfs(path) {
    if (path.length == nums.length) { // 个数选够了
      res.push(path.slice()); // 拷贝一份path,加入解集res
      return;                 // 结束当前递归分支
    }
    for (const num of nums) { // for枚举出每个可选的选项
      // if (path.includes(num)) continue; // 别这么写!查找的时间是O(n),增加时间复杂度
      if (used[num]) continue; // 使用过的,跳过
      path.push(num);         // 选择当前的数,加入path
      used[num] = true;       // 记录一下 使用了
      dfs(path);              // 基于选了当前的数,递归
      path.pop();             // 上一句的递归结束,回溯,将最后选的数pop出来
      used[num] = false;      // 撤销这个记录
    }
  }

  dfs([]); // 递归的入口,空path传进去
  return res;
};

React

SSR

juejin.cn/post/684490…

key

juejin.cn/post/684490…

diff算法

juejin.cn/post/684490…

Fiber

juejin.cn/post/684490…

setState

juejin.cn/post/684490…

hooks

juejin.cn/post/684490…