重新梳理前端基础知识,包括HTML、CSS、JavaScript、HTTP......
1.有利于开发者代码维护
2.在页面没有样式的情况下,也能呈现较好的文档结构
3.有利于SEO
main 主体内容 aside 侧边栏 nav导航栏 section 文字区域 footer底部
defer 是异步下载完以后,按照顺序解析
async是异步加载完以后,直接解析
首先进行缓存查询,若浏览器有缓存,则返回缓存内容,若没有缓存。
则进行DNS解析,DNS解析,会先去查询当前缓存,若缓存命中,则返回缓存,否则查询本机HOST文件,若没命中,则进行DNS递归查询解析,DNS解析后,返回对应IP地址。
若协议为https,会先进行一层tls/ssl的加密连接,接着进行TCP的三次握手,握手以后,http连接建立。
开始传输内容,接收html、js、css内容,构建DOM树、CSS树,DOM树和CSS树结合,生成Render树,Render树布局、渲染。
盒模型的构成部分由content、padding、margin、border组成。
盒模型分为IE模型和标准模型,两者的区别在于,标准模型宽高取决于content的宽高,IE模型的宽高取决于content+border+padding。
!important > style > id > 类、伪类、属性选择器 > 标签选择器、伪元素选择器
重排一定会引起重绘,但是重绘不一定会引起重排。因为重排发生在布局阶段,布局完以后,会进行绘制。
导致重排的一些事情:新增或者删除元素、修改元素宽高或者边距、获取元素的布局属性。
仅发生重绘的一些事情:修改元素的颜色、背景颜色、透明度。
重排重绘的优化:
1.DOM批量的新增或者修改可以先在DOM碎片节点上进行修改,然后在替换回DOM。
2.多个style的修改,整合成class
3.对于复杂的动画,使用绝对定位脱离文档流
4.开启GPU加速,使用transform、will-change属性进行开启
BFC直译过来叫做块级格式化上下文,被BFC包裹的子元素,会对外界隔离。
BFC具有的一些特性:
1.清除浮动,解决高度塌陷的问题
2.解决margin重叠问题的
3.两栏/三栏的自适应布局
创建BFC的方式:
1.display:inline-block、flex、table-cell、grid
2.position: absolute、fixed
3.overflow: 除了visible的其它属性
4.浮动元素
默认理解
布局相同点:
1.都是三栏布局,都使用了浮动和margin负边距
2.main的内容优先加载
区别:
圣杯布局当左边区块的宽度大于中间的区块宽度时,圣杯会破碎
默认理解
flex布局是根据轴线布局,有主轴、垂直轴、换行轴
冷门
基本类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt
基本类型的值存放在栈中,引用类型的值存放在堆中
typeof:检测变量的数据类型,不可对 *null*、对象、数组进行精确判断,因为都返回 object
instancceof:能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型
JSON.stringify 深度拷贝的劣势:
1.值为undefined、function时会消失
2.Date,regexp,NaN无法正确拷贝
3.对象循环引用的情况下会报错
jsx/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
* @param {Map} map 用于存储循环引用对象的地址
*/
function deepClone(obj = {}, map = new Map()) {
if (typeof obj !== "object") {
return obj;
}
if (map.get(obj)) {
return map.get(obj);
}
let result = {};
// 初始化返回结果
if (
obj instanceof Array ||
// 加 || 的原因是为了防止 Array 的 prototype 被重写,Array.isArray 也是如此
Object.prototype.toString(obj) === "[object Array]"
) {
result = [];
}
// 防止循环引用
map.set(obj, result);
for (const key in obj) {
// 保证 key 不是原型属性
if (obj.hasOwnProperty(key)) {
// 递归调用
result[key] = deepClone(obj[key], map);
}
}
// 返回结果
return result;
}
什么是IEEE754 ?
IEEE754是二进制浮点数的运算标准,第一位表示符号的正负,后面11位表示阶码,最后52位表示尾数,因二进制的表达使用科学计数法,所以尾数的第一位一定是1,实际尾数53位,所以JS的最大安全数是Math.pow(2,53) - 1,最大值和阶码相关,大概是Math.pow(2,1023)。
0.1 + 0.2 !== 0.3 ?
小数转换为二进制,是乘2取整,0.1 * 2,永远都无法整乘为1,直到将尾数占满,根据0舍1入的情况,0.1的二进制最终会进位1,0.2也是相同的情况,也会进位1,两个进位的相加下,得到的实际值会大于0.3
解决方案:1.大数运算2.使用 Number.EPSILON 误差范围
什么是原型:
每个函数上面都会有prototype属性,prototype就是实例原型,当我们将函数实例化时,生成的对象下就会关联prototype,目前大部分浏览器都支持对象的__proto__属性,该属性指向构造函数的prototype。
什么是原型链:
当我们获取实例的属性时,如果找不到,就会查找与该对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
jsx所有函数的__proto__,都指向Function.prototype,
包括Function ,即Function.__proto__ === Function.prototype。
这也就导致了鸡蛋问题
Object instanceof Function // true
Function instanceof Object // true
根本问题就是Function.__proto__指向了本身的Function.prototype,
如果Function.__proto__指向null,
将Function和Object划清界限(因为Function.prototype是对象,原型会指向Object.prototype),
就不会有这样的问题。
然后Function属于内置对象,是运行前就存在的东西,
做的只是将Function.__proto__指回Function.prototype,
我觉得这么做,可能是想和其他函数保持统一,也可以保证函数上面也可以使用Object上的原型方法
JS的作用域,属于静态作用域,也就是说JS在创建的时候,作用域就已经确定了。
当函数创建时,就会保存父变量的对象至内部属性[[scope]],可以理解为[[scope]]就是所有父级变量对象的层级链。
当函数被激活时,会将活动对象添加到作用域链的顶端,活动对象包括当前函数的实参、arguments、定义的变量和函数,当我们在函数内部查找变量时,他会按照作用域链的顺序进行查找,先查找当前函数内部的活动对象,其次是父级变量,直到找至全局的作用域。
可执行代码有3种,有全局代码、函数代码、eval代码。
当我们执行全局代码的时候,会首先创建全局的执行上下文,随后全局的执行上下文被压入栈中,然后全局执行上下文的初始化。
假如这个时候执行A1函数,A1函数的执行上下文会被创建,随后被压入栈中,然后进行初始化,当A1函数执行完毕后,A1的执行上下文会从栈中弹出。
闭包:MDN对闭包的定义是,闭包是指那些能够访问自由变量的函数那理论上的闭包:
1.代码使用了自由变量
2.创建的执行上下文被销毁后,这个自由变量依然存在
jsx// call apply类似
Function.prototype.customCall = function (ctx, ...args) {
if (typeof this !== 'function') {
throw new Error('Type Error')
}
const context = ctx || window
const fnKey = Symbol()
context[fnKey] = this
const result = context[fnKey](...args)
delete context[fnKey]
return result
}
Function.prototype.customBind = function (ctx, ...bindArgs) {
if (typeof this !== 'function') {
throw new Error('Type Error')
}
const context = ctx || window
const fn = this
return function (...args) {
const fnKey = Symbol()
context[fnKey] = fn
const result = context[fnKey](...bindArgs, ...args)
delete context[fnKey]
return result
}
}
首先创建一个空对象
将空对象的__proto__的值设置为构造函数的prototype
构造函数的this指向这个对象,执行构造函数的代码
判断函数的返回类型,如果是引用类型,则返回引用类型,否则返回创建的对象
jsxfunction customNew(fn, ...args) {
const obj = {}
obj.__proto__ = fn.prototype
const result = fn.call(obj, ...args)
return typeof result === 'object' ? result : obj
}
原型链式继承:
子类的原型是父类实例化后的一个实例对象
缺点:
1.来自父类原型对象的所有属性都被实例共享
2.无法向父类构造函数传参
构造函数式继承:
用call或者apply将父类函数引入子函数
缺点:
1.只能继承父类构造函数的属性和方法
2.无法复用父类原型上的属性和方法
组合式继承:
结合了两个模式的优点
缺点:
调用了两次构造函数
原型式继承:
和Object.create方法类似
缺点:来自父类原型对象的所有属性都被实例共享
寄生式继承:
和原型式继承比较相似,通过增强方式,返回对象
寄生组合式继承:
使用原型式的继承方式,抵消了一次实例化,解决了组合式继承两次实例化的问题
ES6继承:
是现在主流的继承方法缺点:并不是所有浏览器都支持class
执行script下的同步代码,在执行同步代码的时候,若遇到异步任务,将异步任务注册到Event Table, 若遇到微任务,将微任务加入相应的Event Table,宏任务和微任务的回调可以执行时,分别将其移到宏任务的任务队列和微任务的任务队。
等待同步代码全部执行后,先清空微任务的事件的队列,清空后,去执行宏任务的事件队列,每一个宏任务的回调结束后,都会伴随微任务的队列检查,若有,则执行微任务的回调,若没有,执行下一个宏任务的回调,以此往复,清空微任务和宏任务的事件队列。
宏任务:DOM事件、定时器、http请求
微任务:Promise、MutationObserver
Promise reject,通过.catch捕获后,状态会由rejected修改为fulfilled
Promise 内部优化,连续的多个then(3个)如果没有reject或者resolve会交替执行
jsxclass CustomPromise {
pending = 'pending'
fulfilled = 'fulfilled'
rejected = 'rejected'
fulfilledValue
rejectedReason
constructor(fn) {
const resolve = this.#resolve.bind(this)
const reject = this.#reject.bind(this)
this.status = this.pending
this.successCallbacks = []
this.rejectCallbackcs = []
fn(resolve, reject)
}
#resolve(value) {
this.status = this.fulfilled
this.fulfilledValue = value
this.timeout(() => {
this.successCallbacks.forEach((callback) => {
callback(value)
})
})
}
#reject(reason) {
this.status = this.rejected
this.rejectedReason = reason
this.timeout(() => {
const callback = this.rejectCallbackcs.shift()
if (callback) {
this.status = this.fulfilled
callback(reason)
}
})
}
timeout(callback) {
setTimeout(() => {
callback()
}, 0)
}
then(callback) {
if (this.status === this.fulfilled) {
this.timeout(() => {
callback(this.fulfilledValue)
})
} else {
this.successCallbacks.push(callback)
}
return this
}
catch(callback) {
if (this.status === this.rejected) {
this.status = this.fulfilled
this.timeout(() => {
callback(this.rejectedReason)
})
} else {
this.rejectCallbackcs.push(callback)
}
return this
}
}
// demo1
console.log('1')
const p = new CustomPromise((resolve) => {
console.log('2')
resolve(10)
}).then((res) => {
console.log('res1', res)
}).then((res) => {
console.log('res2', res)
})
console.log('3')
//demo2
console.log('1')
const p = new CustomPromise((resolve, rejecct) => {
console.log('2')
rejecct(10)
}).then((res) => {
console.log('res1', res)
}).catch((reason) => {
console.log('reason', reason)
}).then((res) => {
console.log('res2', res)
})
console.log('3')
async/await 是消灭异步回调的终极武器。
但和 Promise 并不互斥,反而,两者相辅相成。
执行 async 函数,返回的一定是 Promise 对象。
await 相当于 Promise 的 then。
tru...catch 可捕获异常,代替了 Promise 的 catch。
V8垃圾回收:
V8的垃圾回收,是分代式的,分为新生代和老生代,新生代存放的是生命周期短并且占用内存小的对象,老生代存放的是生命周期长并且占用内存大(25%以上)的对象,新生代又划分为使用区和闲置区,当使用区即将被写满时,会进行一次垃圾回收,垃圾回收做的事情是,对正在被使用的对象进行标记,然后将标记后的对象移动至闲置区,接着对使用区进行清理,清理后,两个分区进行转换,即使用区变为闲置区,闲置区变为空闲区。
在多次的垃圾回收中,一个对象依然存活(默认值为15次),它就会被晋升到老生代。老生代的垃圾回收采用标记整理的方式进行。
垃圾回收会占用主线程的执行,新生代使用并行回收进行优化,老生代使用增量标记和惰性清理进行优化。
jsxclass Event {
#events = {}
constructor() {
}
on(name, callback) {
if (this.#events[name]) {
this.#events[name].push(callback)
} else {
this.#events[name] = [callback]
}
}
once(name, callback) {
const that = this
const onceCallback = function (...args) {
callback(...args)
that.off(name, onceCallback)
}
this.on(name, onceCallback)
}
emit(name, ...args) {
if (this.#events[name]) {
this.#events[name].forEach(callback => {
callback(...args)
})
}
}
off(name, callback) {
if (this.#events[name]) {
const index = this.#events[name].indexOf(callback)
if (index !== -1) {
this.#events[name].splice(index, 1)
}
}
}
removeAll(name) {
this.#events[name] = undefined
}
}
// demo
const event = new Event()
const test2 = function (a, b) {
console.log('this is test2', a, b)
}
event.on('test', function (a, b) {
console.log('this is test1', a, b)
})
event.on('test', test2)
event.once('test', function (a, b) {
console.log('once', a, b)
})
event.emit('test', 1, 2)
event.off('test', test2)
event.emit('test', 1, 2)
五大基本设计原则:
单一职责原则:聚合单一功能
开放封闭原则:对扩展开放,对修改封闭
里氏替换原则:子类能覆盖父类
接口隔离原则:耦合多个接口,不如独立拆分
依赖反转原则:高层次的模块不应该依赖于低层次的模块
工厂模式:工厂模式根据提供的参数,在工厂模式内部进行实例化
jsx
function CarFactory() {
this.create = function (brand, color) {
switch (brand) {
case BRANDS.suzuki:
return new SuzukiCar(color);
case BRANDS.honda:
return new HondaCar(color);
case BRANDS.bmw:
return new BMWCar(color);
default:
break;
}
}
}
单例模式:首次访问,生成实例对象,后续每次访问,都仅返回相同的实例
jsxclass A {
static instance=null
constructor (){
console.log('create')
}
static getInstance(){
if(this.instance instanceof A)return this.instance
return this.instance=new A()
}
}
策略模式:判断条件下的策略相互独立且可复用、策略需要灵活组合
jsxvar compose1 = function() {
var validator = new Validator();
const data1 = {
role: 'juejin',
grade: 3
};
validator.add(data1.role, 'checkRole');
validator.add(data1.grade, 'checkGrade');
const result = validator.check();
return result;
};
发布订阅模式:发布订阅模式(也称为“消息队列”或“事件总线”)是一种更松散的模式,其中消息发送者(也称为“发布者”)不会直接向特定的接收者(也称为“订阅者”)发送消息,而是将消息发送到一个中心位置(也称为“主题”或“通道”)。
jsxconst EventEmit = function() {
this.events = {};
this.on = function(name, cb) {
if (this.events[name]) {
this.events[name].push(cb);
} else {
this.events[name] = [cb];
}
};
this.trigger = function(name, ...arg) {
if (this.events[name]) {
this.events[name].forEach(eventListener => {
eventListener(...arg);
});
}
};
};
观察者模式:观察者模式是一种对象间的一对多的依赖关系,其中当一个对象的状态发生改变时,它的所有依赖对象都会收到通知并自动更新。
jsxclass Subject {
constructor (){
this.state=0
this.observers=[]
}
getState(){
return this.state
}
setState(state){
this.state=state
this.notify()
}
notify(){
this.observers.forEach(observer=>{
observer.update(this.state)
})
}
addObserver(observer){
this.observers.push(observer)
}
}
class Observer{
constructor (name,subject,fn){
this.name=name
this.fn=fn
this.subject=subject
this.subject.addObserver(this)
}
update(state){
console.log(this.name,'已成功被订阅')
this.fn(state)
}
}
装饰器模式:
类似高阶组件HOC,可以增强类的某种能力
jsximport React from 'react';
import yellowHOC from './yellowHOC';
class TargetComponent extends Reac.Compoment {
render() {
return <div>66666</div>;
}
}
export default yellowHOC(TargetComponent);
适配器模式:解决数据类型不兼容的问题,把一个类的接口换成我们想要的接口
代理模式:类Proxy方法
责任链模式:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止类似函数组合
cookie特点:
http请求时会被携带
容量小,4kb
只能使用document.cookie进行修改
安全性:
secure:指定cookie必须在https请求中才能携带
httpOnly:cookie只能用于http,无法获取或者修改
samesite:限制第三方使用Cookie,从而减少安全风险
(strict:完全禁止第三方 Cookie,规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有GitHub 的 Cookie,跳转过去总是未登陆状态。
lax:规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外
none:关闭sameSite属性)
cookie和session的关系:
cookie存储于客户端,容易遭到非法获取,session存储于服务端,安全性相对cookie更好些
cookie可以长时间存储,session相对来说,生命周期较短
cookie可以和session配合,cookie存储sessionID,服务端根据sessionID查找对应的session
localStorage和sessionStorage存储容量是5M。
localStorage数据可以持久化存储,sessionStorage只用于当前窗口在
存有sessionStorage的页面通过a标签、window.open跳转至同源页面,会将当前窗口的sessionStorage复制到跳转后的页面,后续两个页面的sessionStorage没有任何联系,修改任何一方的值,都不会被同步
localStorage修改后,两个窗口会同步
200 请求成功
301 永久重定向
302 临时重定向
304 协商缓存
400 客户端错误
401 未提供有效的身份证明
403 请求被拒绝,一般用于防盗链
404 请求资源不存在
405 请求类型不匹配
500 服务器错误
502 网关错误
504 网关超时
强缓存是由Cache-Control和Expries字段控制,现在主流的都是使用Cache-Control,Cache-Control的max-age 填写资源的有效时间,在资源有效期内再次访问资源,都会直接返回缓存内容协商缓存的生效条件是Cache-Control 设置为no-cache。
协商缓存会去对比Etag和Last-Modified,若发现变更,则重新请求资源,若未发生变更,则从缓存拉取资源
。
Etag If-None-Match
Last-Modified *if*-Modified-since
get请求是幂等的,post请求是不幂等的
get请求会被缓存,post请求是不会被缓存
get请求的参数会被URL编码,post请求的参数不会被URL编码
get请求相对来说不安全,get请求的参数在URL上,post的请求在body里
客户端会先向本地的DNS服务器发起查询,本地的DNS服务器若有缓存,则返回缓存,若没有,则DNS本地服务器向根节点的服务器发起查询,这样的流程叫做递归。
查询根节点服务器返回顶级域名服务器的地址给DNS本地服务器,本地服务器接着去查询顶级域名,顶级域名返回权威域名服务器地址,就这样一直迭代,直到查询到目标的IP地址
客户端发送SYN标志位,表示建立连接,接着发送seq = x,
服务端收到信息后,回复SYN、ACK标识位,表示回复并且建立连接,接着返回ack=x+1,接着发送seq=y,
客户端收到信息后,回复ACK标志位,表示收到回复,同时发送seq=x+1,ack=y+1
客户端向服务端发送随机数、当前支持的加密套件列表,
服务端接收到加密套件列表以后,选择一个最合适的加密套件,并且返回一个随机数和当前服务端的证书,
客户端拿着服务端的证书,去获取公钥,接着在生成预主密钥,三个数通过公钥,使用加密套件进行加密,加密后,将密钥发送给服务端,服务端用私钥解密后,会从非对称加密转换为对称加密,后续的交互都使用对称密钥进行加解密。
Node的事件循环分为多个阶段,timers阶段、pending/callback阶段、idea阶段、poll阶段、check阶段、close阶段。
times阶段:执行setTimeout 和 setInterval的回调函数
pending/callback阶段:执行延迟到下一个循环迭代的I/O回调
idle:仅系统内部调用
poll:检索新的I/O事件,大部分I/O事件的回调在这里运行,清空队列以后,会去查询是否有setImmediate的调度,若有,则会等待setImmediate加入队列,然后执行,若没有setImmediate的调度,则会去检查是否有timers阶段的调度,若有,则会等待计时器加入队列,然后绕回到timers阶段进行执行,如果没有timers的调度,事件循环会阻塞在poll阶段
check:setImmediate的回调函数在这里执行
close:关闭阶段的回调函数执行,比如socket的关闭
V11后:微任务的执行顺序和浏览器保持一致,每个宏任务结束以后查询是否有微任务
V11前:阶段结束后,去查询是否有微任务
process.nextTick的优先级比微任务高
本文作者:BARM
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!