Fork me on GitHub
大洋

专注于前端


  • 首页

  • 分类

  • 归档

  • 标签

微前端 乾坤

发表于 2022-03-06 |

微前端 乾坤

原理:

乾坤官网

registerMicroApps源码

loadMicroApp源码

Ts高手篇:22个示例深入讲解Ts最晦涩难懂的高级类型工具

发表于 2022-02-25 |

本文基本分为三部分:

第一部分讲解一些基本的关键词的特性(比如索引查询、索引访问、映射、extends等),但是该部分更多的讲解小伙伴们不清晰的一些特性,而基本功能则不再赘述。更多的关键词及技巧将包含在后续的例子演示中再具体讲述;

第二部分讲解Ts内置的类型工具以及实现原理,比如Pick、Omit等;

第三部分讲解自定义的工具类型,该部分也是最难的部分,将通过一些复杂的类型工具示例进行逐步剖析,对于其中的晦涩的地方以及涉及的知识点逐步讲解。此部分也会包含大量Ts类型工具的编程技巧,也希望通过此部分的讲解,小伙伴的Ts功底可以进一步提升!

第一部分 前置内容

  • keyof 索引查询

对应任何类型T,keyof T的结果为该类型上所有共有属性key的联合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Eg1 {
name: string,
readonly age: number,
}
// T1的类型实则是name | age
type T1 = keyof Eg1

class Eg2 {
private name: string;
public readonly age: number;
protected home: string;
}
// T2实则被约束为 age
// 而name和home不是公有属性,所以不能被keyof获取到
type T2 = keyof Eg2
  • T[K] 索引访问
1
2
3
4
5
6
7
8
9
10
11
12
interface Eg1 {
name: string,
readonly age: number,
}
// string
type V1 = Eg1['name']
// string | number
type V2 = Eg1['name' | 'age']
// any
type V2 = Eg1['name' | 'age2222']
// string | number
type V3 = Eg1[keyof Eg1]

T[keyof T]的方式,可以获取到T所有key的类型组成的联合类型;
T[keyof K]的方式,获取到的是T中的key且同时存在于K时的类型组成的联合类型;
注意:如果[]中的key有不存在T中的,则是any;因为ts也不知道该key最终是什么类型,所以是any;且也会报错;

  • & 交叉类型注意点
    交叉类型取的多个类型的并集,但是如果相同key但是类型不同,则该key为never。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Eg1 {
name: string,
age: number,
}

interface Eg2 {
color: string,
age: string,
}

/**
* T的类型为 {name: string; age: number; age: never}
* 注意,age因为Eg1和Eg2中的类型不一致,所以交叉后age的类型是never
*/
type T = Eg1 & Eg2
// 可通过如下示例验证
const val: T = {
name: '',
color: '',
age: (function a() {
throw Error()
})(),
}

extends关键词特性(重点)

  • 用于接口,表示继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface T1 {
name: string,
}

interface T2 {
sex: number,
}

/**
* @example
* T3 = {name: string, sex: number, age: number}
*/
interface T3 extends T1, T2 {
age: number,
}

注意,接口支持多重继承,语法为逗号隔开。如果是type实现继承,则可以使用交叉类型type A = B & C & D。

  • 表示条件类型,可用于条件判断
    表示条件判断,如果前面的条件满足,则返回问号后的第一个参数,否则第二个。类似于js的三元运算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @example
* type A1 = 1
*/
type A1 = 'x' extends 'x' ? 1 : 2;

/**
* @example
* type A2 = 2
*/
type A2 = 'x' | 'y' extends 'x' ? 1 : 2;

/**
* @example
* type A3 = 1 | 2
*/
type P<T> = T extends 'x' ? 1 : 2;
type A3 = P<'x' | 'y'>
阅读全文 »

前端知识点-前端基础

发表于 2022-01-17 |

1、列举你所了解的计算机存储设备类型?

现代计算机以存储器为中心,主要由 CPU、I / O 设备以及主存储器三大部分组成。各个部分之间通过总线进行连接通信,具体如下图所示:

线结构的示意图,CPU、主存以及 I / O 设备之间的所有数据都是通过总线进行并行传输,使用局部总线是为了提高 CPU 的吞吐量(CPU 不需要直接跟 I / O 设备通信),而使用高速总线(更贴近 CPU)和 DMA 总线则是为了提升高速 I / O 设备(外设存储器、局域网以及多媒体等)的执行效率。

2、一般代码存储在计算机的哪个设备中?代码在 CPU 中是如何运行的?

高级程序设计语言不能直接被计算机理解并执行,需要通过翻译程序将其转换成特定处理器上可执行的指令,计算机 CPU 的简单工作原理如下所示:

CPU 主要由控制单元、运算单元和存储单元组成(注意忽略了中断系统),各自的作用如下:

  • 控制单元:在节拍脉冲的作用下,将程序计数器(Program Counter,PC)指向的主存或者多级高速缓存中的指令地址送到地址总线,接着获取指令地址所对应的指令并放入指令寄存器 (Instruction Register,IR)中,然后通过指令译码器(Instruction Decoder,ID)分析指令需要进行的操作,最后通过操作控制器(Operation Controller,OC)向其他设备发出微操作控制信号。
  • 运算单元:如果控制单元发出的控制信号存在算术运算(加、减、乘、除、增 1、减 1、取反等)或者逻辑运算(与、或、非、异或),那么需要通过运算单元获取存储单元的计算数据进行处理。
  • 存储单元:包括片内缓存和寄存器组,是 CPU 中临时数据的存储地方。CPU 直接访问主存数据大概需要花费数百个机器周期,而访问寄存器或者片内缓存只需要若干个或者几十个机器周期,因此会使用内部寄存器或缓存来存储和获取临时数据(即将被运算或者运算之后的数据),从而提高 CPU 的运行效率。

3、什么是指令和指令集?

上图右侧主存中的指令是 CPU 可以支持的处理命令,一般包含算术指令(加和减)、逻辑指令(与、或和非)、数据指令(移动、输入、删除、加载和存储)、流程控制指令以及程序结束指令等,由于 CPU 只能识别二进制码,因此指令是由二进制码组成。除此之外,指令的集合称为指令集(例如汇编语言就是指令集的一种表现形式),常见的指令集有精简指令集(ARM)和复杂指令集(Inter X86)。一般指令集决定了 CPU 处理器的硬件架构,规定了处理器的相应操作。

5、JavaScript 是如何运行的?解释型语言和编译型语言的差异是什么?

解释器和编译器有很多相似之处,都需要对源程序进行分析,并转换成目标机器可识别的机器语言进行执行。
编译器:先把源程序全部转换成机器语言并产生目标文件,然后将目标文件写入相应的程序存储器进行执行(转换和执行的过程分离)
解释器:在转换源程序的同时立马执行对应的机器语言(转换和执行的过程不分离)
①JS代码->解析成 AST (期间伴随词法分析、语法分析)->生成字节码(V8)->生成机器码(编译器)

电脑:CPU+I/O设备+主存储器组成,通过总线进行连接并行传输
CPU:控制单元+存储单元+运算单元
主存:指令,CPU可以支持的处理命令,一般包含算数指令(加减)、逻辑指令(与、或和非)、数据指令(移动、输入、删除、加载和存储)、流程控制指令、程序结束指令等

5.简单描述一下 Babel 的编译过程?

答: 首先,Babel的作用是 从一种源码到另一种源码,充当转换编译器的作用,可以简述为 解析(解析JS代码)->转换(解析和修改AST)->重建(将修改后的AST转换成另一种JS代码)

7.浏览器和 Node.js 中的事件循环机制有什么区别?

在浏览器里,每当一个被监听的事件发生时,事件监听器绑定的相关任务就会被添加进回调队列。通过事件产生的任务是异步任务,常见的事件任务包括:

  • 用户交互事件产生的事件任务,比如输入操作
  • 计时器产生的事件任务,比如setTimeout;
  • 异步请求产生的事件任务,比如 HTTP 请求。
    主线程运行的时候,会产生堆(heap)和栈(stack),其中堆为内存、栈为函数调用栈。我们能看到,Event Loop 负责执行代码、收集和处理事件以及执行队列中的子任务,具体包括以下过程。
  • JavaScript 有一个主线程和调用栈,所有的任务最终都会被放到调用栈等待主线程执行。
  • 同步任务会被放在调用栈中,按照顺序等待主线程依次执行。
  • 主线程之外存在一个回调队列,回调队列中的异步任务最终会在主线程中以调用栈的方式运行。
  • 同步任务都在主线程上执行,栈中代码在执行的时候会调用浏览器的 API,此时会产生一些异步任务。
  • 异步任务会在有了结果(比如被监听的事件发生时)后,将异步任务以及关联的回调函数放入回调队列中。
  • 调用栈中任务执行完毕后,此时主线程处于空闲状态,会从回调队列中获取任务进行处理。
  • 上述过程会不断重复,这就是 JavaScript 的运行机制,称为事件循环机制(Event Loop)

https://juejin.cn/post/6987549240436195364#heading-0
https://juejin.cn/post/6987070062490288165?share_token=3904e7be-48e7-44bf-a27c-66ab95dd598c#heading-65
https://juejin.cn/post/6844903843197616136#heading-3

手写Promise源码

发表于 2021-12-20 |

https://juejin.cn/post/6994594642280857630

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
class MyPromise {
constructor(fn) {
this.PromiseResult = null; // 终值
this.PromiseState = 'pending'; // 状态

this.onFulfiledCallbacks = []; // 保存成功回调
this.onRejectedCallbacks = []; // 保存失败回调
// 绑定this
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
try {
fn(this.resolve, this.reject);
} catch(e) {
this.reject(e)
}
}

resolve (value) {
// 状态不可变
if(this.PromiseState !== 'pending') return
this.PromiseState = 'fulfiled';
this.PromiseResult = value;

while (this.onFulfiledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult)
}
}
reject (reason) {
// 状态不可变
if(this.PromiseState !== 'pending') return
this.PromiseState = 'rejected';
this.PromiseResult = reason;

while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.PromiseResult)
}
}

// 接受两个参数
// 1、then方法本身会返回一个新的Promise对象
// 2、如果返回值是promise对象,返回值为成功,新promise就是成功
// 3、如果返回值是promise对象,返回值为失败,新promise就是失败
// 4、如果返回值非promise对象,新promise对象就是成功,值为此返回值
then (onFulfilled, onRejected) {
// 返回 Promise 对象
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

let thenPromise = new MyPromise((resolve, reject)=>{

const resolvePromise = cb => {

setTimeout(()=>{
try {
const x = cb(this.PromiseResult);
if(x === thenPromise) {
throw new Error('不能返回自身')
}
if(x instanceof MyPromise) {
// 如果返回值是Promise
// 如果返回值是promise对象,返回值为成功,新promise就是成功
// 如果返回值是promise对象,返回值为失败,新promise就是失败
// 谁知道返回的promise是失败成功?只有then知道
x.then(resolve, reject)
} else {
// 非Promise就直接成功
resolve(x)
}
} catch(err) {
reject(err)
throw new Error(err)
}
})
}

if(this.PromiseState == 'fulfiled') {
// 如果当前为成功状态,执行第一个回调

// let x = onFulfilled(this.PromiseResult);
// if(x instanceof MyPromise) {
// x.then(resolve, reject);
// } else {
// resolve(x)
// }
resolvePromise(onFulfilled)
} else if(this.PromiseState == 'rejected') {
// 如果当前为失败状态,执行第二个回调

// let x = onRejected(this.PromiseResult);
// if(x instanceof MyPromise) {
// x.then(reject, reject)
// } else {
// reject(x);
// }
resolvePromise(onRejected)
} else if(this.PromiseState == 'pending') {
// 如果状态为待定状态,暂时保存两个回调
// this.onFulfilledCallbacks.push(onFulfilled.bind(this))
// this.onRejectedCallbacks.push(onRejected.bind(this))
this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
}
})

return thenPromise;
}
}

阿里云服务器搭建前端服务流程

发表于 2021-11-15 |

重置实例密码

重置实例密码之后可以在阿里云后台进行远程连接Workbench,或者试用ssh进行远程连接

这里也可以重置VPC连接,这样就可以在阿里云后台进行远程连接VPC

安装git 和 node

https://help.aliyun.com/document_detail/50775.html

1
yum -y install git

注意:使用yum安装的git在/usr/bin/git下

安装nginx

https://help.aliyun.com/document_detail/173042.html?spm=5176.21213303.J_6028563670.7.572a3edaz0dICA&scm=20140722.S_help%40%40%E6%96%87%E6%A1%A3%40%40173042.S_hot%2Bos0.ID_173042-RL_centos%E5%AE%89%E8%A3%85nginx-OR_helpmain-V_2-P0_0

绑定域名,进行访问

从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理

发表于 2021-10-20 |

浏览器包含哪些进程

  1. Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
    • 负责浏览器界面显示,与用户交互。如前进,后退等
    • 负责各个页面的管理,创建和销毁其他进程
    • 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
    • 网络资源的管理,下载等
  2. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  3. GPU进程:最多一个,用于3D绘制等
  4. 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为
    • 页面渲染,脚本执行,事件处理等

重点是浏览器内核(渲染进程)

请牢记,浏览器的渲染进程是多线程的

  1. GUI渲染线程
    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
    • 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
  2. JS引擎线程
    • 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
    • JS引擎线程负责解析Javascript脚本,运行代码。
    • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
    • 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
  3. 事件触发线程
    • 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
    • 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
    • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
  4. 定时触发器线程
    • 传说中的setInterval与setTimeout所在线程
    • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
    • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
    • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
  5. 异步http请求线程
    • 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
    • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

https://segmentfault.com/a/1190000012925872

前端监控 SDK 的一些技术要点原理分析

发表于 2021-10-14 |

字节-前端监控 SDK 的一些技术要点原理分析

一文摸清前端监控自研实践(三)错误监控

浅谈前端AST的概念与实际应用

发表于 2021-10-14 |

回想日常开发中使用的框架,脚手架,打包工具,再到编辑器的代码补全,代码格式化等功能,用一句话概括他们做的事那就是批量修改源代码,再精确一点即代码转换。既然要转换,那么首先第一步一定是理解源程序。如何能阅读和理解源程序?这就要引出一个关键概念-AST,本文将依次介绍AST的概念,生成过程,基本结构,节点类型,如何操作AST等,最后通过一个AST的实际应用来进行总结。

AST的概念

抽象语法树(Abstract Syntax Tree)简称AST,顾名思义,它是一棵树,用分支和节点的组合来描述代码结构。他可以让计算机理解我们写的代码,我们不妨先试着按自己的理解来想象一下这棵树的构造。例如下面这段代码

1
2
3
4
function foo(a) {
const b = a + 1;
return b;
}

分析,首先这是一个函数,有名字(foo),参数(a),函数体(body)三个基本属性。再来看body,他有两条语句,分别是声明语句和return语句。先看声明语句,他由变量b和一条表达式语句组成,表达式语句由三个元素:a,+,1组成。而return语句则由元素b组成。我们可以依照上述并按照节点与分支的组合描绘出这段代码的AST的大致结构如下。

阅读全文 »

git放弃本地文件修改

发表于 2021-10-09 |

1. 未使用git add 缓存代码

使用git checkout – filename,注意中间有–

1
git checkout -- filename

放弃所有文件修改 git checkout .

1
git checkout .

此命令用来放弃掉所有还没有加入到缓存区(就是 git add 命令)的修改:内容修改与整个文件删除
此命令不会删除新建的文件,因为新建的文件还没加入git管理系统中,所以对git来说是未知,只需手动删除即可

2. 已使用git add 缓存代码,未使用git commit

使用 git reset HEAD filename

1
git reset HEAD filename

放弃所有文件修改 git reset HEAD

1
git reset HEAD

此命令用来清除 git 对于文件修改的缓存。相当于撤销 git add 命令所在的工作。在使用本命令后,本地的修改并不会消失,而是回到了第一步1. 未使用git add 缓存代码,继续使用用git checkout – filename,就可以放弃本地修改

3. 已经用 git commit 提交了代码

使用 git reset –hard HEAD^ 来回退到上一次commit的状态

1
git reset --hard HEAD^

或者回退到任意版本git reset –hard commit id ,使用git log命令查看git提交历史和commit id

1
git reset --hard commit id

React深入

发表于 2021-09-27 |
  • 【React深入】setState 的执行机制
  • 【React深入】React事件机制
  • 【React深入】深入分析虚拟DOM的渲染过程和特性
  • 【React深入】从Mixin到HOC再到Hook
  • React 运行时优化方案的演进

卡颂 - React 技术揭秘

7kms - 图解React源码

123…12
大洋

大洋

Stay Hungry! Stay Young!

113 日志
57 标签
RSS
GitHub Weibo QQ Mail
友链
  • moxhuis
  • indexof
  • xiaoqiang
© 2016 - 2023 大洋
由 Hexo 强力驱动
主题 - NexT.Muse