前端面试八股文
# JavaScript
# var let const1
# var、let、const异同
- 共同点:都能声明变量
- 不同点:var 在ECMAScript 的所有版本中都可以使用,而const和let只能在ECMAScript6及更晚中使用
# var 1
- var存在变量提升
- 函数作用域
- 可以重复声明
- 全局声明时会变成windows对象的属性
# let 1
- 块级作用域
- 全局声明时不会变成windows对象的属性
- 不能进行条件式声明(判断是否声明然后再确定是否声明),因为会声明在条件符合的块级作用域里
- let没有变量提升
- 不允许重复声明
- 暂时性死区:js引擎会注意到后面的let声明,但并没有变量提升,会抛出ReferenceError,而不是not defined
# const 1
- 块级作用域
- 全局声明时不会变成windows对象的属性
- 没有变量提升
- 不允许重复声明
- 声明变量时必须赋值
- 变量不能修改,但仅限于变量引用的对象,对内部属性的修改不报错
# 变量提升的理解 1
var变量的时候会把所有变量声明都拉到函数作用域的顶部
# 箭头函数1
# 箭头函数与普通函数的区别 1
- 箭头函数没有this,所以需要通过查找作用域链来确定this的值,这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this
- 箭头函数没有自己的arguments对象,但是可以访问外围函数的arguments对象
- 不能通过new关键字调用,同样也没有new.target值和原型
- 语法更加简洁、清晰
- 箭头函数继承而来的this指向永远不变
- .call()/.apply()/.bind()无法改变箭头函数中this的指向
- 箭头函数不能作为构造函数使用
- 箭头函数没有自己的arguments,可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表
- 箭头函数没有原型prototype
- 箭头函数不能用作Generator函数,不能使用yeild关键字
- 箭头函数不具有super,不具有new.target
# 事件代理1
- 事件代理(Event Delegation),又称之为事件委托。是JavaScript中常用绑定事件的常用技巧
- 事件传播
- 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件
- 目标阶段:在目标节点上触发,称为“目标阶段”
- 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层
- 事件代理优点
- 可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒
- 可以实现当新增子对象时无需再次对其绑定(动态绑定事件)
- 事件代理的实现
var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");
document.addEventListener("click", function (event) {
var target = event.target;
switch (target.id) {
case "doSomething":
document.title = "事件委托";
break;
case "goSomewhere":
location.href = "http://www.baidu.com";
break;
case "sayHi": alert("hi");
break;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# cookie sessionStorage localStorage 1
# cookie
- 存储的数据量小,每个不超过4096字节
- 每个域有cookie个数限值,具体个数与浏览器相关
- 可以通过HTTP-only限值浏览器通过js访问
- 通过设置 domain、path 等参数可以随着请求发送到服务器端
- 可以通过 expires 设置有效期
# localStorage 1
- 永久存储机制:将数据保存在客户端本地的硬件设备(通常指硬盘,也可以是其他硬件设备)中,
- 浏览器被关闭了,该数据仍然存在,下次打开浏览器访问网站时仍然可以继续使用
# sessionStorage 1
- 跨会话存储机制:将数据保存在session对象中。
- session是指用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间
- 存储的数据不受页面刷新的影响,但只能在最初存储数据的页面使用,在多页应用程序中的用处有限
# js数据类型1
- 简单数据类型:String、Number、boolean、null、undefined、bigInt、symbol
- 引用数据类型:object里面包括function、Array、Date
# js数据类型判断1
- typeof,只能判断简单数据类型,不能具体区分object
- 对于基本类型,除 null 以外,均可以返回正确的结果。
- 对于引用类型,除 function 以外,一律返回 object 类型。
- 对于 null ,返回 object 类型。
- 对于 function 返回 function 类型
- instanceof:instanceof 检测的是原型
- constructor:当一个函数F被创建时,JS引擎会为其添加prototype原型,然后在原型上添加一个constructor属性,并让其指向F的引用。也就是说F.prototype.constructor === F // ---> true
- toString() :toString 方法默认返回其调用者的具体类型,更严格的讲是toString运行时,this指向的对象类型
# instanceof的实现
function instanceof(obj, classType) {
let classProto = classType.prototype;
obj = obj.__proto__;
while (true) {
if (obj === null) {
return false;
}
if (obj === classProto) {
return true;
}
obj = obj.__proto__;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# Promise
- Promise 是一种为了避免回调地狱的异步解决方案
- Promise 是一种状态机: pending(进行中)、fulfilled(已成功)和rejected(已失败) 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 回调地狱:回调函数中嵌套回调函数的情况就叫做回调地狱
- 回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护
# Promise是什么?
- Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大
- ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
- ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
# Promise是为解决什么问题而产生的?
promise是为解决异步处理回调地狱问题而产生的
# Promise的两个特点
- Promise对象的状态不受外界影响
- pending 初始状态
- fulfilled 成功状态
- rejected 失败状态
- Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled或者由pending变成rejected
# Promise的三个缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
- 当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成
# Promise在哪存放成功回调序列和失败回调序列
- onResolvedCallbacks 成功后要执行的回调序列 是一个数组
- onRejectedCallbacks 失败后要执行的回调序列 是一个数组
- 以上两个数组存放在Promise 创建实例时给Promise这个类传的函数中,默认都是空数组。
- 每次实例then的时候 传入 onFulfilled 成功回调 onRejected 失败回调,如果此时的状态是pending 则将onFulfilled和onRejected push到对应的成功回调序列数组和失败回调序列数组中,如果此时的状态是fulfilled 则onFulfilled立即执行,如果此时的状态是rejected则onRejected立即执行
- 上述序列中的回调函数执行的时候 是有顺序的,即按照顺序依次执行
# ES6新特性
- let与const
- let 允许创建块级作用域(最靠近的一个花括号内有效),不具备变量提升,不允许重复声明 2.const 允许创建块级作用域(最靠近的一个花括号内有效)、变量声明不提升、const 在声明时必须被赋值、声明时大写变量(默认规则)、block作用域
- 箭头函数:ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体:
- 函数默认参数值:ES6 中允许你对函数参数设置默认值
- 对象超类:ES6 允许在对象中使用 super 方法来调用父类的构造函数
- Map VS WeakMap
- ES6 中两种新的数据结构集:Map 和 WeakMap。事实上每个对象都可以看作是一个 Map
- 一个对象由多个 key-val 对构成,在 Map 中,任何类型都可以作为对象的 key
- 类
- ES6 中有 class 语法。值得注意是,这里的 class 不是新的对象继承模型,它只是原型链的语法糖表现形式
- 函数中使用 static 关键词定义构造函数的的方法和属性
# 原型链
# 什么是原型链
简单理解就是原型组成的链,对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了。
# 原型对象和实例之间有什么作用
通过一个构造函数创建出来的多个实例,如果都要添加一个方法,给每个实例去添加并不是一个明智的选择。这时就该用上原型了。
实例的原型上添加一个方法,这个原型的所有实例便都有了这个方法
# prototype原型对象
- prototype属性,它是函数所独有的,它是从一个函数指向一个对象。
- 每一个构造函数都一个prototype属性,指向另一个对象。这个对象的所有属性和方法都会被构造函数所拥有
- 构造函数通过原型分配的函数是所有对象所共享的
# __proto__对象原型
- 对象都会有一个属性__proto__指向构造函数的prototype原型对象,这让对象可以使用构造函数原型对象的属性和方法
- 对象都会有一个属性__proto__指向构造函数的prototype原型对象,这让对象可以使用构造函数原型对象的属性和方法
- __proto__是一个非标准属性,不能对其赋值
- 对象方法查找规则
- 先查找对象是否存在该方法,有就执行对象上的方法
- 通过__proto__去构造函数原型对象里查找执行
# constructor构造函数
- 对象原型__proto__和构造函数prototype原型对象里面都有一个属性constructor,被称为构造函数,因为它指回构造函数本身
- constructor用于记录该对象引用哪个构造函数,它可以让原型对象重新指向原来的构造函数
- 如果修改了原来的原型对象,给原型对象幅值的是一个对象,则必须手动地使用constructor指回原来的构造函数
# 继承
# 原型链继承
- 父类的实例作为子类的原型
function Father() {
}
Father.prototype.money = function(){
console.log(100000)
}
Son.prototype = new Father();
//如果利用对象的形式修改了原型对象,要用constructor指回原来的构造函数
Son.prototype.constructor = Son;
function Son() {
}
2
3
4
5
6
7
8
9
10
- 优点:简单易于实现,父类的新增的实例与属性子类都能访问
- 缺点:
- 可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
- 无法实现多继承
- 创建子类实例时,不能向父类构造函数中传参数
# 借用构造函数继承(伪造对象、经典继承)
- 复制父类的实例属性给子类
function Father(uname, age) {
//此处的this指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
function Son(uname, age) {
//将父构造函数的this改成子构造函数的this
Father.call(this, uname, age);
}
2
3
4
5
6
7
8
9
- 优点
- 解决了子类构造函数向父类构造函数中传递参数
- 可以实现多继承(call或者apply多个父类)
- 缺点
- 方法都在构造函数中定义,无法复用
- 不能继承原型属性/方法,只能继承父类的实例属性和方法
# 组合继承
- 这种方式关键在于:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
function Father (name, age) {
this.name = name,
this.age = age,
this.setAge = function () { }
}
function Son (name, age, price) {
Father.call(this, name, age)
this.price = price
this.setScore = function () { }
}
Son.prototype = new Father()
Son.prototype.constructor = Son
2
3
4
5
6
7
8
9
10
11
12
- 优点
- 可以继承实例属性/方法,也可以继承原型属性/方法
- 不存在引用属性共享问题
- 可传参
- 函数可复用
- 缺点
- 调用了两次父类构造函数,生成了两份实例
# ES6class继承
class Father {
constructor(name, age) {
this.name = name
this.age = age
}
howName () {
console.log("调用父类的方法")
console.log(this.name, this.age);
}
})
Son extends Father {
constructor(name, age, salary) {
super(name, age)
this.salary = salary
}
showName () {
console.log("调用子类的方法")
console.log(this.name, this.age, this.salary);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 优点:语法简单易懂,操作更方便
- 缺点:并不是所有的浏览器都支持class关键字
# Null undefined
# Null
- 空指针对象
- typeof一个null会返回object
- 使用
==
判断null与undefined始终返回true
# undefined
- 表示一个变量声明了但没有赋值
onsole.log(未声明的变量)
会报错,但typeof(未声明的变量)
则会返回undefined
# call bind apply
# apply方法
- 可以调用函数,也可以改变this指向
- 语法
函数名.apply(this指向,[实参])
- 实参的传递必须使用数组的方式
- 当第一个参数为null、undefined的时候,默认指向window
- 使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
# call方法
- 可以调用函数和改变this指向
- 语法
函数名.call(this指向,实参)
- 实参是一个参数列表
- 主要作用是实现继承。在子构造函数里用call调用父构造函数
父构造函数名.call(this,参数)
- 当第一个参数为null、undefined的时候,默认指向window
- call也只是临时改变一次this指向,并立即执行
# bind方法
- 不会调用函数,但能改变函数内部this指向
- 语法
函数名.bind(this指向,实参)
- 实参是一个参数列表,但是这个参数列表可以分多次传入,call则必须一次性传入所有参数
- 返回由指定的this值和初始化参数改造的原函数拷贝
- 改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
# 闭包
# 变量作用域
- js函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量
# 从外部读取函数内部的局部变量
function f1(){
var n =999
function f2(){
console.log(n)
}
return f2
}
2
3
4
5
6
7
- 函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。
- "链式作用域"结构:子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立
- 把 f2 作为返回值,我们就可以在 f1 外部读取它的内部变量了
# 闭包的概念
- 闭包就是能够读取其他函数内部变量的函数
- 在本质上,闭包是将函数内部和函数外部连接起来的桥梁
- 就是上面的f2
# 闭包的用途
- 读取函数内部的变量
- 让这些变量的值始终保持在内存中,不会在 f1 调用后被自动清除
# 使用闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值
# 数组去重
# 利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(new Set(arr))
}
2
3
- 不考虑兼容性
- 法去掉“{}”空对象
# 利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
2
3
4
5
6
7
8
9
10
11
# 利用indexOf去重
function unique(arr) {
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
2
3
4
5
6
7
8
9
# 利用sort()
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort()
var array= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrray;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 利用includes
function unique(arr) {
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
2
3
4
5
6
7
8
9
# 利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
2
3
4
5
6
# 利用Map数据结构去重
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有该key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果没有该key值
array .push(arr[i]);
}
}
return array ;
}
2
3
4
5
6
7
8
9
10
11
12
13
# 深浅拷贝
- 深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
- 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
# require import
- import在代码编译时被加载,所以必须放在文件开头,require在代码运行时被加载,所以require理论上可以运用在代码的任何地方,所以import性能更好
- import引入的对象被修改时,源对象也会被修改,相当于浅拷贝,require引入的对象被修改时,源对象不会被修改,官网称值拷贝,我们可以理解为深拷贝
- import有利于tree-shaking(移除JavaScript上下文中未引用的代码),require对tree-shaking不友好
- import会触发代码分割(把代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件),require不会触发
- import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法,require 是 AMD规范引入方式
- 目前所有的引擎都还没有实现import,import最终都会被转码为require,在webpack打包中,import和require都会变为_webpack_require_
# 浏览器原理
# 跨域1
浏览器限制了脚本发起的资源请求 (一般是 XHR 或 Fetch API) 只能发生在同源网址之间,如果本网页想要访问非同源网址的资源就会产生跨域请求
# 同源策略
# 同源的定义
- 同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略来对脚本和请求进行校验,若不同源,则禁止使用
- 域名,协议,端口三个都相同才算同源
# 同源策略的作用
- 无法用js读取非同源的Cookie、LocalStorage 和 IndexDB,防止恶意网站通过js获取用户其他网站的cookie等用户信息
- 无法用js获取非同源的DOM,防止恶意网站通过iframe获取页面dom,从而窃取页面的信息
- 无法用js发送非同源的AJAX请求,防止恶意的请求攻击服务器窃取数据信息
# 跨域请求发送出去了吗
- 对于简单请求,跨域请求发送出去了,只是结果被浏览器拦截了
- 对于非简单请求,由于预检过程的存在,真正的跨域请求没有发送出去
# 跨域请求的解决方案 1
- 目前最常用的 CORS:服务器端可以在 HTTP 响应头上通过 Access-Control-Allow-(Origin/Headers/Methods/Credentials/) 等响应头字段设置允许访问该资源的请求源,请求头,请求方法,是否允许携带 cookies 等
- webpack 提供的 proxy:仅适用于开发阶段
- Nginx 添加请求头:允许的源很难动态更改
- JSONP
- 只允许 GET 请求,因此 URL 长度受限,不安全
- 需要服务器端进行配合
# cors 方案中预检过程 1
- 提到简单请求和非简单请求,只有非简单请求才会触发预检过程
- 简单请求必须同时满足下面三个条件
- 请求方式只能是:GET、POST、HEAD
- HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
- Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
- 简单请求必须同时满足下面三个条件
- 浏览器首先发出一个 OPTIONS 请求,包含非简单请求中存在的请求信息,如请求方法,请求头,源等等
- 服务器端返回它所允许的上述内容,并且如果预检请求能够通过就返回 200 响应
- 浏览器接收到响应后,再判断是否真正发出非简单请求
# jsonp 方案的大致流程 1
- 浏览器端声明需要执行的 callback 函数,比如叫 run
- 浏览器端动态生成 script 标签,将 src 属性指向目标 url 同时携带上参数 callback=run
- 服务器端接收到请求后将结果包装成 json 格式,同时返回字符串 run(json 格式的结果)
- 浏览器端接收到数据后就会执行相应的 run 函数
# nginx 和 webpack 代理方式的区别
- nginx 是通过为每一个 HTTP 响应添加上满足 CORS 要求的请求头实现的跨域,本质上请求还是通过浏览器发出的
- webpack 是通过内部发送请求的方式绕过浏览器获取到结果之后再返回给浏览器,本质上请求不是通过浏览器发出的
# HTTP请求
# GET方法
- GET方法用于使用给定的URI从给定服务器中检索信息,即从指定资源中请求数据
/test/demo_form.php?name1=value1&name2=value2
- GET请求是可以缓存的,我们可以从浏览器历史记录中查找到GET请求,还可以把它收藏到书签中
- GET请求有长度限制,仅用于请求数据(不修改)
- 由于参数显示再地址栏所以不安全,一般需要保密的请求不使用GET
# POST方法
- POST方法用于将数据发送到服务器以创建或更新资源,它要求服务器确认请求中包含的内容作为由URI区分的Web资源的另一个下属
- POST请求永远不会被缓存,且对数据长度没有限制
- 我们无法从浏览器历史记录中查找到POST请求
# GET和POST的区别
- GET提交的数据放在URL中,POST则不会。这是最显而易见的差别。这点意味着GET更不安全(POST也不安全,因为HTTP是明文传输抓包就能获取数据内容,要想安全还得加密)
- GET回退浏览器无害,POST会再次提交请求(GET方法回退后浏览器再缓存中拿结果,POST每次都会创建新资源)
- GET提交的数据大小有限制(是因为浏览器对URL的长度有限制,GET本身没有限制),POST没有
- GET可以被保存为书签,POST不可以
- GET能被缓存,POST不能
- GET只允许ASCII字符,POST没有限制
# HEAD方法
- HEAD方法与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容
# PUT方法
- PUT方法用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容
- 它会将包含的元素放在所提供的URI下,如果URI指示的是当前资源,则会被改变。如果URI未指示当前资源,则服务器可以使用该URI创建资源
# DELETE方法
- DELETE方法用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容
# CONNECT方法
- CONNECT方法用来建立到给定URI标识的服务器的隧道;它通过简单的TCP / IP隧道更改请求连接,通常实使用解码的HTTP代理来进行SSL编码的通信(HTTPS)
# OPTIONS方法
- OPTIONS方法用来描述了目标资源的通信选项,会返回服务器支持预定义URL的HTTP策略
# TRACE方法
- TRACE方法用于沿着目标资源的路径执行消息环回测试;它回应收到的请求,以便客户可以看到中间服务器进行了哪些(假设任何)进度或增量
# URL输出到页面的全过程
# 浏览器构建HTTP Request请求
- 应用层进行DNS解析
通过DNS将域名解析成IP地址。在解析过程中,按照浏览器缓存、系统缓存、路由器缓存、ISP(运营商)DNS缓存、根域名服务器、顶级域名服务器、主域名服务器的顺序,逐步读取缓存,直到拿到IP地址 - 应用层生成HTTP请求报文
- 应用层生成针对目标WEB服务器的HTTP请求报文,HTTP请求报文包括起始行、首部和主体部分
- 首部包括域名host、keep-alive、User-Agent、Accept-Encoding、Accept-Language、Cookie等信息
- 传输层建立TCP连接
- 由于HTTP协议使用的是TCP协议,为了方便通信,将HTTP请求报文按序号分为多个报文段(segment),并对每个报文段进行封装。
- 使用本地一个大于1024以上的随机TCP源端口(这里假设是1030)建立到目的服务器TCP80号端口(HTTPS协议对应的端口号是443)的连接
- TCP源端口和目的端口被加入到报文段中,学名叫协议数据单元(Protocol Data Unit, PDU)
- 因TCP是一个可靠的传输控制协议,传输层还会加入序列号、确认号、窗口大小、校验和等参数,共添加20字节的头部信息
- TCP协议是面向连接的,所以它在开始传输数据之前需要先建立连接。要建立或初始化一个连接,两端主机必须同步双方的初始序号。同步是通过交换连接建立数据分段和初始序号来完成的,在连接建立数据分段中包含一个SYN(同步)的控制位。同步需要双方都发送自己的初始序号,并且发送确认的ACK。此过程就是三次握手
- 第一次握手:主机A发往主机B,主机A的初始序号是X,设置SYN位,未设置ACK位
- 第二次握手:主机B发往主机A,主机B的初始序号是Y,确认号(ACK)是X+1,X+1确认号暗示己经收到主机A发往主机B的同步序号。设置SYN位和ACK位
- 第三次握手:主机A发往主机B,主机A的序号是X+1,确认号是Y+1,Y+1确认号暗示已经收到主机B发往主机A的同步序号。设置ACK位,未设置SYN位
- 网络层使用IP协议来选择路线
- 处理来自传输层的数据段segment,将数据段segment装入数据包packet,填充包头,主要就是添加源和目的IP地址,然后发送数据
- 在数据传输的过程中,IP协议负责选择传送的路线,称为路由功能
- 数据链路层实现网络相邻结点间可靠的数据通信
- 为了保证数据的可靠传输,把数据包packet封装成帧(Frame),并按顺序传送各帧。由于物理线路的不可靠,发出的数据帧有可能在线路上出错或丢失,于是为每个数据分块计算出CRC(循环冗余检验),并把CRC添加到帧中,这样接收方就可以通过重新计算CRC来判断数据接收的正确性。一旦出错就重传
- 将数据包packet封装成帧(Frame),包括帧头和帧尾。帧尾是添加被称做CRC的循环冗余校验部分。帧头主要是添加数据链路层的地址,即数据链路层的源地址和目的地址,即网络相邻结点间的源MAC地址和目的MAC地址
- 物理层传输数据
- 数据链路层的帧(Frame)转换成二进制形式的比特(Bit)流,从网卡发送出去,再把比特转换成电子、光学或微波信号在网络中传输
# 网络传输
# 服务器处理及反向传输
- 服务器接收到这个比特流,把比特流转换成帧格式,上传到数据链路层,服务器发现数据帧中的目的MAC地址与本网卡的MAC地址相同,服务器拆除数据链路层的封装后,把数据包上传到网络层。服务器的网络层比较数据包中的目的IP地址,发现与本机的IP地址相同,服务器拆除网络层的封装后,把数据分段上传到传输层。传输层对数据分段进行确认、排序、重组,确保数据传输的可靠性。数据最后被传到服务器的应用层
- HTTP服务器,如nginx通过反向代理,将其定位到服务器实际的端口位置,如8080。比如,8080端口对应的是一个NodeJS服务,生成响应报文,报文主体内容是google首页的HTML页面
# 浏览器渲染
- 网络线程安全校验
- 谷歌浏览器通过SafeBrowsing检查站点是否是恶意站点,如果是会跳转警告页面阻止你的访问
- UI线程创建渲染器进程(Renderer Thread)渲染页面
- 浏览器进程通过IPC管道将数据(HTML)传送给渲染器进程
- 渲染器进程主线程解析HTML,构建DOM数据结构
- tokeniser标记化,将输入的html内容解析成多个标记
- 根据识别后的标记进行DOM树构造
- CSS、image等不会阻塞html解析,不会影响DOM的生成
- 遇到script标签会停止html解析过程,转而去加载解析并且执行js
- 主线程解析CSS样式
- 如果没有CSS就查找浏览器自带的CSS样式
- 主线程layout布局
- 主线程通过遍历DOM和计算好的样式来生成layout tree
- layout tree上的每个节点都记录了x y坐标和边框尺寸
- DOM tree和layout tree不是一一对应的
- 设置了display:none的节点不会出现在layout tree上
- 伪类元素的content里的内容会出现在layout tree上
- 主线程绘制
- 遍历layout tree创建一个绘制记录表(Paint Record)
- 该表记录绘制的顺序
- 遍历layout tree生成layer tree
- 主线程将layer tree和绘制记录表传递给合成器线程
- 合成器线程将图层栅格化
- 合成器线程将图层切分成图块
- 合成器线程将图块发送给栅格线程(Raster Thread)
- 栅格线程栅格化每个图块,并存储到GPU内存中
- 合成器线程收集draw quads的图块信息,并生成合成器帧
- 将合成器帧通过IPC传送给浏览器进程
- 浏览器进程将合成器帧传到GPU进行渲染
- 重排:改变元素尺寸位置属性时,会重新进行样式计算,并生成layout tree后面的所有流程
- 重绘:改变元素的颜色属性时,不会重新触发布局,但会触发样式计算和绘制
- js也运行在主线程上
- 优化
- requestAnimationFrame()
- 可以将js运行任务分成更小的任务块分到每一帧
- 在每一帧时间用万千暂停js执行归还主线程
- CSS的transform属性
- 通过该属性实现的动画不会经过布局和绘制,而是直接运行在合成器线程和栅格线程
- requestAnimationFrame()
# 事件循环
TODO:
# VUE
# 组件间传递数据1
- 父子组件通信
- 父组件向子组件传递数据,使用props属性
- 子组件向父组件中传递数据,在子组件中使用$emit派发事件,父组件中使用v-on监听事件
- 缺点:组件嵌套层次多的话,传递数据比较麻烦
- 祖孙组件通信
- 通过依赖注入(inject / provide)的方式,向其所有子孙后代传递数据
- 缺点:无法监听数据修改的来源,不支持响应式
- 通过属性$root / $parent / $children /ref,访问根组件、父级组件、子组件中的数据;缺点:要求组件之间要有传递性
- 事件总线(event bus)
- 可以实现任意两个组件间进行数据传递
- 缺点:不支持响应式,这个概念是vue1.0版本中的,现在已经废弃
- 状态管理模式 Vue
- 实现多个组件进行数据共享,推荐使用这种方式进行项目中各组件间的数据传递
# VUE2和VUE3的区别1
- 响应式原理的重写
- vue2 响应式是基于
Object.defineProperty
实现的,存在较大的问题- 对数组等集合类型的支持不佳
- 对嵌套属性的深层响应支持不佳
- 对新增属性的支持不佳)
- vue3 响应式是基于新的 API
proxy
实现的,功能更加强大
- vue2 响应式是基于
- 组合式 API 的引入,使 vue 可以写出更加解耦的代码
- vue3 各模块之间更加解耦,响应式相关封装成了 reactivity 包,组合式 API 相关封装成了
composition-api
包,因此可以按需引入了 - 对 typescript 的支持更好
# 响应式1
# MVC模式
以往的MVC模式是单向绑定,即Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新
# MVVM模式
MVVM模式就是Model–View–ViewModel模式。它实现了View的变动,自动反映在 ViewModel,反之亦然。对于双向绑定的理解,就是用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。再说细点,就是在单向绑定的基础上给可输入元素input、textare等添加了change(input)事件,(change事件触发,View的状态就被更新了)来动态修改model
# 双向绑定原理 1
- vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的
- 我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。
- 因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的
- 我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图
# VUE2.x怎么实现复杂数据结构的数据监听 1
虽然 Vue 中确实能检测到数组数据的变化,但是其实是使用了 hack 的办法
# 状态管理1
# vuex
# pinia 1
# 生命周期函数
- 又名生命周期回调函数、生命周期函数、生命周期钩子
- 生命周期函数是Vue在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的 this 指向是vm或组件实例对象
# VUE2生命周期
beforeCreate() {console.log('初始化但没有数据代理')},
created() {console.log('初始化且完成数据监测和数据代理')},
beforeMount() {console.log('虚拟DOM已经生成,但还没有转换为真实DOM')},
mounted() {console.log('VUE解析并把真实DOM挂载到页面上')},
beforeUpdate() {console.log('数据已经更新,但页面还未更新')},
updated() {console.log('页面和数据都更新完毕')},
beforeDestroy() {console.log('马上要销毁VUE实例')},
destroyed() {console.log('destroyed')},
2
3
4
5
6
7
8
# VUE3生命周期
- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
# CSS
# 选择器 1
# 标签选择器
- HTML标签名作为选择器
- 权重1
# 类选择器
- 用class属性进行选择
- 一个标签可以使用多个类名,多个类名之间用空格隔开
- 权重10
# ID选择器
- 通过#定义id,以id属性选择
- 只能调用一次,具有唯一性
- 权重100
# 通配符选择器
- 用*,表示选取页面所有标签
- 权重0
# 后代选择器
- 后代可以是子类也可以是子类的子类
- 两个都可以是任意的基础选择器
- 符号是空格
# 子代选择器
- 只能选择某元素最近一级子代
- 符号是>
# 并集选择器
- 选择多组标签,同时定义相同的样式,用于集体声明
- 语法:用逗号分隔
# 链接伪类选择器
a:link
选择所有未访问的连接a:visited
选择所有已访问的连接a:hover
选择鼠标指针位于其上的连接a:active
选择活动连接(鼠标按下未弹起的连接)- 为了确保能够生效,必须使用LVHA的顺序写
- 浏览器中a有固定样式,要改变必须用a来设定
# :focus伪类选择
- 用于获取光标所在的表单元素
- 语法
input:focus{样式说明}
# 水平垂直居中 1
# flex布局
- 利用flex的
alignItems:center
垂直居中 justifycontent:center
水平居中
# margin:auto
相对定位下,使用绝对定位将上下左右都设置为0,再设置margin:auto
即可实现居中
# 利用相对定位和绝对定位,再加上外边距和平移的配合
相对定位下,使用绝对定位,利用margin偏移外容器的50%,再利用translate平移回补自身宽高的50%即可
# 利用textAlign和verticalAlign
- 利用
textAlign:center
实现行内元素的水平居中 - 再利用
verticalAlign:middle
实现行内元素的垂直居中 - 前提是要先加上伪元素并给设置高度为100%
# 盒子模型 1
- 盒模型包括margin、border、padding、content四个部分,主要的设置属性是margin、border、padding
- 正常设置盒子模型的width时不包括padding和margin,当box-sizing设置为border-box时则包括padding和border
# 块级元素 行内元素 行内块元素
# 块级元素
- 霸占一行,不能与其他任何元素并列
- 能接受宽高,如果不设置宽度,那么宽度将默认变为父级的100%
- 常见块级元素:div、ul、li、form、tale、h系列、hr、p、dl、dt、dd
# 行内元素
- 与其他行内元素并排
- 不能设置宽高,默认的宽度就是文字的宽度
- 常见行内元素:a、span
# 行内块元素
- 与其他行内元素并排
- 默认宽度是本身内容的宽度
- 宽度、高度、内外边距都可以控制
- 常见行内块元素:img、input、td
# HTML
# HTML5新特性
# 语义标签
header、footer、nav、section、article、details、summary、dialog、figure、main、mark、time
# 增强表单
- input属性修改:color、date、datetime、datetime-local、month、week、time、email、number、url、tel、search、range
- 新增表单元素
# 视频和音频
支持直接播放视频和音频,并提供了相应的控制 api
# Canvas绘图
# SVG绘图
# 地理定位
# 拖放API
# WebWorker
让 Web 应用程序具备后台处理能力,对多线程的支持性非常好
# WebStorage
增加了 localstorage, sessionstorage
# WebSocket
基于 tcp 的全双工通讯