2022-03-02
JavaScript
0

目录

HTML+CSS
1.1如何理解 HTML 语义化?
1.2 script 标签中 defer 和 async 的区别?
1.3 从浏览器地址栏输入 url 到请求返回发生了什么
2.1 盒模型介绍
2.2 CSS优先级
2.3 重排的重绘的理解
2.4 对 BFC 的理解
2.5 实现两栏布局(左侧固定 + 右侧自适应布局)
2.6 实现圣杯布局和双飞翼布局(经典三分栏布局)
2.7 水平垂直居中多种实现方式
2.8 flex 布局
2.9 line-height 如何继承?
JS 基础
1.1 基本的数据类型介绍,及值类型和引用类型的理解
1.2 数据类型的判断
1.3 手写深拷贝
1.4 根据 0.1+0.2 ! == 0.3,讲讲 IEEE 754 ,如何让其相等?
2、 原型和原型链
3、 作用域与作用域链
4、 执行上下文
5、 闭包
6、 call、apply、bind 实现
7、 new 实现
8、 异步
8.1 event loop、宏任务和微任务
8.2 Promise
8.3 async/await 和 Promise 的关系
9、 浏览器的垃圾回收机制
10、 实现一个 EventMitter 类
11、设计模式
Web 存储
1、cookie
2、localStorage 和 sessionStorage
HTTP
1、http 状态码
2、http 缓存
3、GET 和 POST 的区别
4、DNS递归迭代查询
5、TCP三次握手
6、HTTPS流程
Node
1、事件循环
参考

重新梳理前端基础知识,包括HTML、CSS、JavaScript、HTTP......

HTML+CSS

1.1如何理解 HTML 语义化?

1.有利于开发者代码维护

2.在页面没有样式的情况下,也能呈现较好的文档结构

3.有利于SEO

main 主体内容 aside 侧边栏 nav导航栏 section 文字区域 footer底部

1.2 script 标签中 defer 和 async 的区别?

defer 是异步下载完以后,按照顺序解析

async是异步加载完以后,直接解析

1.3 从浏览器地址栏输入 url 到请求返回发生了什么

首先进行缓存查询,若浏览器有缓存,则返回缓存内容,若没有缓存。

则进行DNS解析,DNS解析,会先去查询当前缓存,若缓存命中,则返回缓存,否则查询本机HOST文件,若没命中,则进行DNS递归查询解析,DNS解析后,返回对应IP地址。

若协议为https,会先进行一层tls/ssl的加密连接,接着进行TCP的三次握手,握手以后,http连接建立。

开始传输内容,接收html、js、css内容,构建DOM树、CSS树,DOM树和CSS树结合,生成Render树,Render树布局、渲染。

2.1 盒模型介绍

盒模型的构成部分由content、padding、margin、border组成。

盒模型分为IE模型和标准模型,两者的区别在于,标准模型宽高取决于content的宽高,IE模型的宽高取决于content+border+padding。

2.2 CSS优先级

!important > style > id > 类、伪类、属性选择器 > 标签选择器、伪元素选择器

2.3 重排的重绘的理解

重排一定会引起重绘,但是重绘不一定会引起重排。因为重排发生在布局阶段,布局完以后,会进行绘制。

导致重排的一些事情:新增或者删除元素、修改元素宽高或者边距、获取元素的布局属性。

仅发生重绘的一些事情:修改元素的颜色、背景颜色、透明度。

重排重绘的优化:

1.DOM批量的新增或者修改可以先在DOM碎片节点上进行修改,然后在替换回DOM。

2.多个style的修改,整合成class

3.对于复杂的动画,使用绝对定位脱离文档流

4.开启GPU加速,使用transform、will-change属性进行开启

2.4 对 BFC 的理解

BFC直译过来叫做块级格式化上下文,被BFC包裹的子元素,会对外界隔离。

BFC具有的一些特性:

1.清除浮动,解决高度塌陷的问题

2.解决margin重叠问题的

3.两栏/三栏的自适应布局

创建BFC的方式:

1.display:inline-block、flex、table-cell、grid

2.position: absolute、fixed

3.overflow: 除了visible的其它属性

4.浮动元素

2.5 实现两栏布局(左侧固定 + 右侧自适应布局)

默认理解

2.6 实现圣杯布局和双飞翼布局(经典三分栏布局)

布局相同点:

1.都是三栏布局,都使用了浮动和margin负边距

2.main的内容优先加载

区别:

圣杯布局当左边区块的宽度大于中间的区块宽度时,圣杯会破碎

2.7 水平垂直居中多种实现方式

默认理解

2.8 flex 布局

flex布局是根据轴线布局,有主轴、垂直轴、换行轴

2.9 line-height 如何继承?

冷门

JS 基础

1.1 基本的数据类型介绍,及值类型和引用类型的理解

基本类型:StringNumberBoolean、Null、Undefined、SymbolBigInt

基本类型的值存放在栈中,引用类型的值存放在堆中

1.2 数据类型的判断

typeof:检测变量的数据类型,不可对 *null*、对象、数组进行精确判断,因为都返回 object

instancceof:能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型

1.3 手写深拷贝

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; }

1.4 根据 0.1+0.2 ! == 0.3,讲讲 IEEE 754 ,如何让其相等?

什么是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 误差范围

2、 原型和原型链

什么是原型:

每个函数上面都会有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, 将FunctionObject划清界限(因为Function.prototype是对象,原型会指向Object.prototype), 就不会有这样的问题。 然后Function属于内置对象,是运行前就存在的东西, 做的只是将Function.__proto__指回Function.prototype, 我觉得这么做,可能是想和其他函数保持统一,也可以保证函数上面也可以使用Object上的原型方法

3、 作用域与作用域链

JS的作用域,属于静态作用域,也就是说JS在创建的时候,作用域就已经确定了。

当函数创建时,就会保存父变量的对象至内部属性[[scope]],可以理解为[[scope]]就是所有父级变量对象的层级链。

当函数被激活时,会将活动对象添加到作用域链的顶端,活动对象包括当前函数的实参、arguments、定义的变量和函数,当我们在函数内部查找变量时,他会按照作用域链的顺序进行查找,先查找当前函数内部的活动对象,其次是父级变量,直到找至全局的作用域。

4、 执行上下文

可执行代码有3种,有全局代码、函数代码、eval代码。

当我们执行全局代码的时候,会首先创建全局的执行上下文,随后全局的执行上下文被压入栈中,然后全局执行上下文的初始化。

假如这个时候执行A1函数,A1函数的执行上下文会被创建,随后被压入栈中,然后进行初始化,当A1函数执行完毕后,A1的执行上下文会从栈中弹出。

5、 闭包

闭包:MDN对闭包的定义是,闭包是指那些能够访问自由变量的函数那理论上的闭包:

1.代码使用了自由变量

2.创建的执行上下文被销毁后,这个自由变量依然存在

6、 call、apply、bind 实现

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 } }

7、 new 实现

首先创建一个空对象

将空对象的__proto__的值设置为构造函数的prototype

构造函数的this指向这个对象,执行构造函数的代码

判断函数的返回类型,如果是引用类型,则返回引用类型,否则返回创建的对象

jsx
function 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

8、 异步

8.1 event loop、宏任务和微任务

执行script下的同步代码,在执行同步代码的时候,若遇到异步任务,将异步任务注册到Event Table, 若遇到微任务,将微任务加入相应的Event Table,宏任务和微任务的回调可以执行时,分别将其移到宏任务的任务队列和微任务的任务队。

等待同步代码全部执行后,先清空微任务的事件的队列,清空后,去执行宏任务的事件队列,每一个宏任务的回调结束后,都会伴随微任务的队列检查,若有,则执行微任务的回调,若没有,执行下一个宏任务的回调,以此往复,清空微任务和宏任务的事件队列。

宏任务:DOM事件、定时器、http请求

微任务:Promise、MutationObserver

8.2 Promise

Promise reject,通过.catch捕获后,状态会由rejected修改为fulfilled

Promise 内部优化,连续的多个then(3个)如果没有reject或者resolve会交替执行

jsx
class 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')

8.3 async/await 和 Promise 的关系

async/await 是消灭异步回调的终极武器。

但和 Promise 并不互斥,反而,两者相辅相成。

执行 async 函数,返回的一定是 Promise 对象。

await 相当于 Promise 的 then。

tru...catch 可捕获异常,代替了 Promise 的 catch。

9、 浏览器的垃圾回收机制

V8垃圾回收:

V8的垃圾回收,是分代式的,分为新生代和老生代,新生代存放的是生命周期短并且占用内存小的对象,老生代存放的是生命周期长并且占用内存大(25%以上)的对象,新生代又划分为使用区和闲置区,当使用区即将被写满时,会进行一次垃圾回收,垃圾回收做的事情是,对正在被使用的对象进行标记,然后将标记后的对象移动至闲置区,接着对使用区进行清理,清理后,两个分区进行转换,即使用区变为闲置区,闲置区变为空闲区。

在多次的垃圾回收中,一个对象依然存活(默认值为15次),它就会被晋升到老生代。老生代的垃圾回收采用标记整理的方式进行。

垃圾回收会占用主线程的执行,新生代使用并行回收进行优化,老生代使用增量标记和惰性清理进行优化。

10、 实现一个 EventMitter 类

jsx
class 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)

11、设计模式

五大基本设计原则:

单一职责原则:聚合单一功能

开放封闭原则:对扩展开放,对修改封闭

里氏替换原则:子类能覆盖父类

接口隔离原则:耦合多个接口,不如独立拆分

依赖反转原则:高层次的模块不应该依赖于低层次的模块

工厂模式:工厂模式根据提供的参数,在工厂模式内部进行实例化

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; } } }

单例模式:首次访问,生成实例对象,后续每次访问,都仅返回相同的实例

jsx
class A { static instance=null constructor (){ console.log('create') } static getInstance(){ if(this.instance instanceof A)return this.instance return this.instance=new A() } }

策略模式:判断条件下的策略相互独立且可复用、策略需要灵活组合

jsx
var 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; };

发布订阅模式:发布订阅模式(也称为“消息队列”或“事件总线”)是一种更松散的模式,其中消息发送者(也称为“发布者”)不会直接向特定的接收者(也称为“订阅者”)发送消息,而是将消息发送到一个中心位置(也称为“主题”或“通道”)。

jsx
const 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); }); } }; };

观察者模式:观察者模式是一种对象间的一对多的依赖关系,其中当一个对象的状态发生改变时,它的所有依赖对象都会收到通知并自动更新。

jsx
class 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,可以增强类的某种能力

jsx
import React from 'react'; import yellowHOC from './yellowHOC'; class TargetComponent extends Reac.Compoment { render() { return <div>66666</div>; } } export default yellowHOC(TargetComponent);

适配器模式:解决数据类型不兼容的问题,把一个类的接口换成我们想要的接口

代理模式:类Proxy方法

责任链模式:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止类似函数组合

Web 存储

1、cookie

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

2、localStorage 和 sessionStorage

localStorage和sessionStorage存储容量是5M。

localStorage数据可以持久化存储,sessionStorage只用于当前窗口在

存有sessionStorage的页面通过a标签、window.open跳转至同源页面,会将当前窗口的sessionStorage复制到跳转后的页面,后续两个页面的sessionStorage没有任何联系,修改任何一方的值,都不会被同步

localStorage修改后,两个窗口会同步

HTTP

1、http 状态码

200 请求成功

301 永久重定向

302 临时重定向

304 协商缓存

400 客户端错误

401 未提供有效的身份证明

403 请求被拒绝,一般用于防盗链

404 请求资源不存在

405 请求类型不匹配

500 服务器错误

502 网关错误

504 网关超时

2、http 缓存

强缓存是由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

3、GET 和 POST 的区别

get请求是幂等的,post请求是不幂等的

get请求会被缓存,post请求是不会被缓存

get请求的参数会被URL编码,post请求的参数不会被URL编码

get请求相对来说不安全,get请求的参数在URL上,post的请求在body里

4、DNS递归迭代查询

客户端会先向本地的DNS服务器发起查询,本地的DNS服务器若有缓存,则返回缓存,若没有,则DNS本地服务器向根节点的服务器发起查询,这样的流程叫做递归。

查询根节点服务器返回顶级域名服务器的地址给DNS本地服务器,本地服务器接着去查询顶级域名,顶级域名返回权威域名服务器地址,就这样一直迭代,直到查询到目标的IP地址

5、TCP三次握手

客户端发送SYN标志位,表示建立连接,接着发送seq = x,

服务端收到信息后,回复SYN、ACK标识位,表示回复并且建立连接,接着返回ack=x+1,接着发送seq=y,

客户端收到信息后,回复ACK标志位,表示收到回复,同时发送seq=x+1,ack=y+1

6、HTTPS流程

客户端向服务端发送随机数、当前支持的加密套件列表,

服务端接收到加密套件列表以后,选择一个最合适的加密套件,并且返回一个随机数和当前服务端的证书,

客户端拿着服务端的证书,去获取公钥,接着在生成预主密钥,三个数通过公钥,使用加密套件进行加密,加密后,将密钥发送给服务端,服务端用私钥解密后,会从非对称加密转换为对称加密,后续的交互都使用对称密钥进行加解密。

Node

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 许可协议。转载请注明出处!