---
url: /笔记/02.前端/00.图谱.md
---
# 前端

---
---
url: /笔记/02.前端/01.JS手写.md
---
# 前端常见功能实现
### 01. 防抖 debounce
当需要在事件频繁触发时,只执行最后一次操作,可以使用防抖函数来控制函数的执行频率,比如窗口 resize 事件和输入框 input 事件。
这段代码定义了一个名为 `debounce` 的函数,它接收两个参数:`fn`(一个需要被防抖处理的函数)和 `delay`(一个延迟时间,单位是毫秒)。防抖(debounce)技术的主要目的是限制某个函数在一定时间内只执行一次,即使在这段时间内被频繁调用。这对于优化性能特别有用,比如避免因快速连续触发事件(如窗口调整大小、输入验证等)而造成的不必要的计算或 DOM 操作。
逐行解析:
* `let timer = null;`:在这个函数作用域内声明一个变量 `timer`,并初始化为 `null`。这个变量将用来存储 setTimeout 的返回值,即一个可以被清除的计时器标识。
* `return function () { ... };`:`debounce` 函数返回一个新的匿名函数。这样做是因为我们希望返回一个经过防抖处理的新函数,而不是直接修改原函数。这种设计模式称为“闭包”,返回的函数能够访问外部函数(`debounce`)中的局部变量,如 `timer`。
* `if (timer) clearTimeout(timer);`:每次新的返回函数被调用时,首先检查 `timer` 是否存在且不为 `null`。如果存在,这意味着之前已经设置了一个定时器但尚未执行。此时,通过 `clearTimeout` 清除这个定时器,从而取消即将执行的 `fn` 调用。
* `timer = setTimeout(() => { fn.apply(this, arguments); }, delay);`:这里设置一个新的定时器。当过了 `delay` 毫秒后,内部的箭头函数会被执行,它通过 `apply` 方法调用原始函数 `fn`,并确保 `this` 的上下文以及传给防抖函数的所有参数都能正确传递给 `fn`。`apply` 的第一个参数 `this` 保证了在 `fn` 被调用时能保留正确的上下文环境,特别是当 `fn` 是对象的方法时;第二个参数 `arguments` 是一个类数组对象,包含了所有传入的参数。
```html
function debounce(fn, delay) { let timer = null; return function () { const
context = this; // 保存函数被调用时的上下文(this) const args = arguments; //
保存函数被调用时的参数 // 如果有之前的定时器,先清除它 if (timer)
clearTimeout(timer); // 设置新的定时器 timer = setTimeout(() => {
fn.apply(context, args); // 使用保存的上下文和参数来调用原始函数 timer = null;
// 确保在函数执行后将timer置为null }, delay); }; }
```
应用示例:
```html
Document
```
简洁版代码:
```html
function debounce(fn,delay){ let timer=null; return function(){ if(timer)
clearTimeout(timer); timer=setTimeout(()=>fn.apply(this,arguments),delay) } }
```
### 02. 节流 throttle
```html
Throttle Scroll Event Example
Scroll down to see the throttled scroll event handler in action.
```
时间戳版:
* **原理**:通过比较当前时间和上一次执行函数的时间戳,来判断是否达到指定的时间间隔。
* **示例代码**:
```javascript
function throttle(func, wait) {
let previous = 0;
return function () {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
};
}
// 使用方法
content.onmousemove = throttle(count, 1000); // 每1秒执行一次count函数
```
使用 lodash 库:
* **示例代码**:
```html
```
节流(throttle)指定的 delay 时间内只被调用一次,即使它被频繁触发;防抖(debounce),只要触发,就重新计算 delay。
### 03. 手写原生Ajax
1. **打开请求**:
```javascript
xhr.open('GET', '/api/lian', true);
```
这里使用`open`方法初始化一个请求。参数分别为:
\- `'GET'`:请求的方法,这里是GET方法,用于从服务器获取数据。
\- `'/api/lian'`:请求的URL,表示将向服务器的`/api/lian`端点发送请求。
\- `true`:表示这个请求应该是异步的(async)。如果为`false`,请求将是同步的,但在这里设置为`true`意味着当请求正在进行时,JavaScript代码将继续执行,不会阻塞。
2. **设置onreadystatechange事件处理程序**:
```javascript
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
let data = JSON.parse(xhr.responseText);
}
};
```
这段代码为`xhr`对象设置了`onreadystatechange`事件处理函数。当请求的状态改变时,这个函数会被调用。`readyState`属性表示请求的不同阶段(0到4),其中4表示请求已完成且响应已就绪。`status`属性是HTTP状态码,200表示请求成功。
\- 当`xhr.readyState == 4`且`xhr.status == 200`时,说明请求已经完成并且成功了。此时,可以通过`xhr.responseText`获取到服务器返回的文本内容,并尝试将其解析成JSON对象,存储在变量`data`中。
**注意**:在实际应用中,你可能还需要考虑错误处理,以及确保在调用`JSON.parse`之前检查响应内容是否为空或不是有效的JSON格式,以避免运行时错误。此外,如果是跨域请求,还需确保服务器支持CORS(跨源资源共享)。
以上代码是一个典型的AJAX GET请求的实现,用于向服务器请求数据并在客户端处理这些数据。
```javascript
let xhr=new XMLHttpRequest();
xhr.open('get','/api/lian',true);
xhr.onreadystatechange=function(){
if(xhr.readyState==4&&xhr.status==200){
let data=JSON.parse(xhr.responseText);
}
}
```
### 04. 手写冒泡排序
以下是使用JavaScript编写的冒泡排序算法:
```javascript
function bubbleSort(arr) {
let len = arr.length;
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
// 返回排序后的数组
return arr;
}
// 使用示例
let array = [64, 34, 25, 12, 22, 11, 90];
let sortdArray = bubbleSort(array);
console.log(sortdArray);
```
在上面的代码中,我们定义了一个名为`bubbleSort`的函数,它接受一个数组作为参数。我们使用两个嵌套的for循环来遍历数组。外层循环负责控制排序的轮数,内层循环则负责在每一轮中进行相邻元素的比较和可能的交换。如果前一个元素大于后一个元素,则交换它们的位置。通过这个过程,每一轮都会将当前未排序部分的最大值“冒泡”到其最终的位置。最终,数组将被完全排序。
其他排序算法:
是的,除了冒泡排序之外,还有多种排序算法。以下是一些常见的排序算法及其简要描述(按照不同的分类方式):
简单排序
1. **冒泡排序**:
* 原理:通过重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
* 时间复杂度:最好情况O(n),平均情况和最坏情况O(n^2)。
* 空间复杂度:O(1)。
2. **选择排序**:
* 原理:在序列中找到最小元素,存放起始位置,然后再剩余序列中依次选择最小的元素排列。
* 时间复杂度:O(n^2)。
* 空间复杂度:O(1)。
插入排序
1. **直接插入排序**:
* 原理:将元素一个一个的插入正确的位置。
* 时间复杂度:最好情况O(n),平均情况和最坏情况O(n^2)。
* 空间复杂度:O(1)。
2. **希尔排序**:
* 原理:是插入排序的一种优化版本,通过比较相距一定间隔的元素来工作,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。
* 时间复杂度:平均情况O(n log n)至O(n2),最坏情况O(n2)。
* 空间复杂度:O(1)。
递归排序
1. **快速排序**:
* 原理:采用分治法策略来排序,选择一个基准元素,通过一趟排序将待排记录分隔成独立的两部分,其中一部分的所有记录的关键字均比另一部分的所有记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
* 时间复杂度:最好情况O(n log n),平均情况O(n log n),最坏情况O(n^2)。
* 空间复杂度:递归实现时为O(log n)至O(n),迭代实现时为O(1)。
非比较类排序
1. **计数排序**:
* 原理:是一种线性时间复杂度的排序算法。它使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数,然后根据数组C来将A中的元素排到正确的位置。
* 时间复杂度:O(n+k),其中k是待排序数的范围。
* 空间复杂度:O(n+k)。
2. **基数排序**:
* 原理:是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。
* 时间复杂度:O(nk),其中n是待排序数组长度,k是数字的位数。
* 空间复杂度:O(n)。
3. **桶排序**:
* 原理:是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:首先,要使得数据分散得尽可能均匀;其次,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
* 时间复杂度:O(n+n^2/k+k),其中k为桶的数量。
* 空间复杂度:O(n+k)。
4. **堆排序**:
* 原理:是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
* 时间复杂度:O(n log n)。
* 空间复杂度:O(1)。
这些排序算法各有优缺点,适用于不同的场景和数据类型。在实际应用中,应根据具体需求和数据特性来选择合适的排序算法。
### 05. 实现异步操作sleep
`sleep` 和 `delay` 都是使用 JavaScript 的 Promise 和 `setTimeout` 来实现异步操作的。不过它们的功能略有不同,下面详细解释:
`sleep` 函数接受一个秒数 `seconds`,并返回一个 Promise,该 Promise 在指定的秒数后解析。它主要用于在异步操作中模拟等待或延迟。
```javascript
const sleep = (seconds) => new Promise(resolve => setTimeout(resolve, seconds));
// 使用示例
async function demo() {
console.log('开始');
await sleep(2); // 等待2秒
console.log('2秒后');
}
demo();
```
`delay` 函数接受一个函数 `func`、一个秒数 `seconds` 和一些额外的参数 `...args`。这个函数返回一个 Promise,该 Promise 在指定的秒数后调用传入的函数 `func` 并将其结果作为 Promise 的解析值。如果 `func` 函数抛出异常,那么 `delay` 返回的 Promise 会被拒绝。
```javascript
function delay (func, seconds, ...args) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Promise.resolve(func(...args)).then(resolve, reject); // 注意这里也处理了reject
}, seconds);
});
}
// 使用示例
function greet(name) {
return `Hello, ${name}!`;
}
delay(greet, 2000, 'World').then(result => console.log(result)); // 等待2秒后输出 "Hello, World!"
```
注意事项:
* 在 `delay` 函数中,`Promise.resolve(func(...args))` 是为了确保即使 `func` 不是一个返回 Promise 的函数,我们也能以统一的方式处理其返回值或抛出的异常。
* 如果 `func` 是一个返回 Promise 的函数,那么 `delay` 也会等待这个 Promise 解析或拒绝后再继续。
* 在 `delay` 的实现中,我们同时处理了 `resolve` 和 `reject` 的情况,以确保即使 `func` 抛出异常,`delay` 返回的 Promise 也能正确地拒绝。
* `delay` 函数提供了更高级的功能,因为它允许你在延迟后执行任意函数并处理其结果或异常。而 `sleep` 函数则更简单,只是简单地等待一段时间。
扩展实现:
* 带取消功能的 sleep:
```javascript
function sleepWithCancel(ms) {
let timeoutId;
const promise = new Promise(resolve => {
timeoutId = setTimeout(resolve, ms);
});
// 添加取消方法
promise.cancel = () => clearTimeout(timeoutId);
return promise;
}
// 使用示例
async function testCancel() {
console.log('开始');
const sleepPromise = sleepWithCancel(5000);
// 2秒后取消
setTimeout(() => {
console.log('取消等待');
sleepPromise.cancel();
}, 2000);
try {
await sleepPromise;
console.log('等待完成');
} catch (error) {
console.log('等待被取消');
}
}
testCancel();
```
* 带返回值的 sleep:
```javascript
const sleepWithValue = (ms, value) =>
new Promise(resolve => setTimeout(() => resolve(value), ms));
// 使用示例
async function testReturnValue() {
const result = await sleepWithValue(2000, '延迟后返回的值');
console.log(result); // 2秒后输出: "延迟后返回的值"
}
testReturnValue();
```
* 错误处理增强版 delay:
```javascript
async function delayEnhanced(func, ms, ...args) {
try {
// 等待指定时间
await new Promise(resolve => setTimeout(resolve, ms));
// 执行函数并返回结果
return await func(...args);
} catch (error) {
// 捕获并处理错误
console.error('执行出错:', error);
throw error; // 重新抛出以便外部处理
}
}
// 使用示例
async function testErrorHandling() {
function riskyOperation(value) {
if (value < 0) {
throw new Error('值不能为负数');
}
return value * 2;
}
try {
const result = await delayEnhanced(riskyOperation, 1000, -5);
console.log('结果:', result);
} catch (error) {
console.log('捕获到错误:', error.message);
}
}
testErrorHandling();
```
应用场景:
* `sleep` 适用于需要单纯等待一段时间的场景,如模拟网络延迟、控制操作顺序等
* `delay` 适用于需要延迟执行特定函数并处理其结果的场景,如定时任务、延迟回调等
* 带取消功能的版本适合需要根据条件中断等待的场景,如用户操作取消了某个等待过程
* 在测试异步代码、实现动画效果、控制API请求频率等场景中都能发挥重要作用
### 06.手写apply
模拟 JavaScript 原生 Function.prototype.apply 方法
当调用 greet.myApply(person, \['Hello']) 时,greet 函数内部的 this 被绑定到了 person 对象,并且用 \['Hello'] 作为参数列表来调用这个函数,因此输出了 "Hello, Alice!"
```
Function.prototype.myApply = function (context, argsArray) {
// 如果 context 为 null 或 undefined,则将 context 指向全局对象(浏览器环境下为 window)
context = context || window;
// 生成一个唯一的 Symbol 作为临时属性名,避免和 context 原有属性冲突
const uniqueKey = Symbol();
// 将当前函数(即调用 myApply 的函数)作为临时属性赋给 context 对象
context[uniqueKey] = this;
// 调用函数
const result = context[uniqueKey](...argsArray);
// 删除临时属性,确保不会影响原始对象
delete context[uniqueKey];
//返回函数执行结果
return result;
};
// 测试
function greet(greeting) {
return `${greeting}, ${this.name}!`;
}
const person = {
name: 'Alice'
};
console.log(greet.myApply(person, ['Hello'])); // 输出:Hello, Alice!
```
### 07. 手写call
基本概念
在JavaScript中,`call` 方法是 `Function` 的原型方法,它允许你调用一个函数,其 `this` 的值被设置为你提供的第一个参数。
实现代码
```javascript
Function.prototype.myCall = function(context, ...args) {
// 如果context非对象,则转化为对象
context = context ? Object(context) : window;
// 临时给context添加一个唯一的函数属性,并设置为要调用的函数
const fnSymbol = Symbol('fn');
context[fnSymbol] = this;
// 使用context来调用这个方法,并收集结果
const result = context[fnSymbol](...args);
// 删除刚才添加的临时函数
delete context[fnSymbol];
// 返回结果
return result;
};
```
使用示例
```javascript
function test(a, b) {
console.log(this.value); // 输出:Hello, World!
console.log(a, b); // 输出:1 2
}
const obj = { value: 'Hello, World!' };
test.myCall(obj, 1, 2); // 输出:Hello, World! 1 2
```
代码解析
这个手写的 `myCall` 方法模拟了 `Function.prototype.call` 的基本行为。它接受一个上下文对象 `context` 和一系列参数 `args`。
方法内部实现逻辑:
1. 首先将 `context` 转化为一个对象(如果它原本不是),若为`null`或`undefined`则默认使用`window`
2. 在 `context` 上临时添加一个唯一的函数属性(使用`Symbol`确保不会覆盖原有属性),这个属性的值就是要调用的函数(即`this`,指向当前函数)
3. 使用 `context` 来调用这个方法,并收集结果
4. 删除刚才添加的临时函数,避免污染原对象
5. 返回函数执行的结果
请注意,这个实现是一个简化的版本,主要用于教学目的。在生产环境中,建议使用原生的 `Function.prototype.call` 方法。
### 08.手写bind
```
Function.prototype.myBind = function (context, ...bindArgs) {
/** self保存了当前函数
(即调用 myBind 的函数)的引用。
这样做是为了在稍后返回的函数中能够访问到原始函数。
)*/
const self = this;
/**
* 返回一个新的函数。当这个新函数被调用时,它将使用 apply 方法来调用原始函数,
* 同时将 this 上下文设置为 myBind 方法接收到的 context 参数。
*/
return function (...args) {
const newArgs = bindArgs.concat(args);
return self.apply(context, newArgs);
}
}
const person = { name: 'Alice' };
function greet(greeting, punctuation) {
return greeting + ' ' + this.name + punctuation;
}
// 使用myBind
const boundGreet = greet.myBind(person, 'Hello');
console.log(boundGreet('!')); // 输出: Hello, Alice!
```
### 09. call、apply 和 bind 的区别与联系
基本概念
`call`、`apply` 和 `bind` 是 JavaScript 中三个常用的函数方法,它们的主要用途是调用函数(或返回可调用的函数)并显式地指定该函数的 `this` 值,以及传递参数。
相同点
1. **改变函数执行时的上下文**:三者都可以用来修改函数执行时的 `this` 指向。
2. **不修改原函数**:都不会改变原函数的 `this` 指向和自身逻辑,只是在调用时临时改变。
3. **参数传递**:都支持向目标函数传递参数。
不同点
执行方式
* `call` 和 `apply`:会**立即调用**原函数,并将指定的对象绑定为原函数的执行上下文。
* `bind`:**不会立即调用**原函数,而是返回一个新的函数,这个新函数在被调用时,会将指定的对象作为 `this` 值。
传参方式
* `call`:接受一个参数列表,参数之间用逗号分隔,按顺序传递给原函数。\
语法:`func.call(thisArg, arg1, arg2, ...)`
* `apply`:接受两个参数,第一个是 `this` 值,第二个是**数组或类数组对象**,数组中的元素会按顺序传递给原函数。\
语法:`func.apply(thisArg, [arg1, arg2, ...])`
* `bind`:接受一个参数列表,这些参数会**预先绑定**为原函数的参数前缀,新函数被调用时可补充后续参数。\
语法:`func.bind(thisArg, arg1, arg2, ...)`
返回值
* `call` 和 `apply`:直接返回原函数的**执行结果**。
* `bind`:返回一个**新的函数**(绑定了 `this` 和部分参数),需手动调用才会执行原函数。
使用场景
* `call`/`apply`:适合需要**立即执行函数**且动态改变 `this` 指向的场景,例如借用其他对象的方法(如 `Array.prototype.slice.call(arguments)` 将类数组转为数组)。
* `bind`:适合需要**延迟执行函数**,或预先绑定上下文和参数的场景,例如事件回调、定时器函数中固定 `this` 指向。
示例
假设有以下函数和对象:
```javascript
function greet(greeting, punctuation) {
return greeting + ', ' + this.name + punctuation;
}
const person = { name: 'Alice' };
```
call 示例
```javascript
// 传递参数列表:'Hello' 和 '!'
const callResult = greet.call(person, 'Hello', '!');
console.log(callResult); // 输出:Hello, Alice!
```
apply 示例
```javascript
// 传递参数数组:['Hi' 和 '?']
const applyResult = greet.apply(person, ['Hi', '?']);
console.log(applyResult); // 输出:Hi, Alice?
```
bind 示例
```javascript
// 预先绑定 this 和第一个参数 'Hey'
const boundGreet = greet.bind(person, 'Hey');
// 调用新函数时补充第二个参数 '!!!'
const bindResult = boundGreet('!!!');
console.log(bindResult); // 输出:Hey, Alice!!!
```
特殊情况处理
* 当 `thisArg` 为 `null` 或 `undefined` 时,非严格模式下 `this` 会指向全局对象(浏览器中为 `window`,Node.js 中为 `global`),严格模式下 `this` 为 `undefined`。
* `bind` 返回的新函数不能再次通过 `bind` 修改 `this` 指向(绑定后固定)。
* `apply` 适合参数已存在于数组中的场景,例如 `Math.max.apply(null, [1, 2, 3])` 可快速求数组最大值。
总结
`call`、`apply` 和 `bind` 核心功能是改变函数的 `this` 指向,区别主要在于执行时机、传参方式和返回值。实际开发中需根据是否立即执行、参数格式(列表/数组)等场景选择合适的方法。
### 10.发布/订阅模式
基本概念
发布/订阅模式(Publish/Subscribe Pattern)是一种常见的设计模式,其核心思想是通过一个中介者(发布者)管理所有订阅者,当特定事件触发时,由发布者通知所有订阅者执行相应操作。这种模式能实现组件间的解耦,提升代码的灵活性和可扩展性。
实现思路
1. **创建发布者对象**:包含订阅者列表和管理方法(订阅、发布、取消订阅)。
2. **订阅事件**:订阅者通过方法将回调函数注册到发布者的事件列表中。
3. **发布事件**:发布者在事件触发时,遍历订阅列表并调用所有回调函数,传递事件数据。
4. **取消订阅**:订阅者可通过方法从列表中移除自己的回调函数,不再接收通知。
基础实现代码
```javascript
// 发布者对象
const publisher = {
// 存储订阅者列表(键:事件类型,值:回调函数数组)
subscribers: {},
// 订阅方法:注册事件和回调
subscribe: function(eventType, callback) {
// 若事件类型不存在,初始化空数组
if (!this.subscribers[eventType]) {
this.subscribers[eventType] = [];
}
// 添加回调函数到对应事件的列表中
this.subscribers[eventType].push(callback);
},
// 取消订阅:移除指定事件的回调
unsubscribe: function(eventType, callback) {
const callbacks = this.subscribers[eventType];
if (callbacks) {
// 过滤掉目标回调函数
this.subscribers[eventType] = callbacks.filter(cb => cb !== callback);
}
},
// 发布方法:触发事件并通知所有订阅者
publish: function(eventType, data) {
const callbacks = this.subscribers[eventType];
if (callbacks && callbacks.length > 0) {
// 遍历回调列表并执行,传递数据
callbacks.forEach(callback => callback(data));
}
}
};
```
使用示例
```javascript
// 定义订阅者回调函数
function user1ReceiveNews(data) {
console.log(`用户1收到新闻:${data}`);
}
function user2ReceiveNews(data) {
console.log(`用户2收到新闻:${data}`);
}
function user1ReceiveNotice(data) {
console.log(`用户1收到通知:${data}`);
}
// 订阅事件
publisher.subscribe('news', user1ReceiveNews); // 用户1订阅新闻
publisher.subscribe('news', user2ReceiveNews); // 用户2订阅新闻
publisher.subscribe('notice', user1ReceiveNotice); // 用户1订阅通知
// 发布事件
publisher.publish('news', '前端框架Vue 3.4发布!');
// 输出:
// 用户1收到新闻:前端框架Vue 3.4发布!
// 用户2收到新闻:前端框架Vue 3.4发布!
publisher.publish('notice', '您有一条新消息');
// 输出:
// 用户1收到通知:您有一条新消息
// 取消订阅
publisher.unsubscribe('news', user2ReceiveNews);
publisher.publish('news', 'JavaScript新特性提案通过');
// 输出:
// 用户1收到新闻:JavaScript新特性提案通过
```
扩展功能
1. 支持一次性订阅
```javascript
// 扩展发布者,添加一次性订阅方法
publisher.once = function(eventType, callback) {
// 包装回调,执行后自动取消订阅
const wrapper = (data) => {
callback(data);
this.unsubscribe(eventType, wrapper);
};
this.subscribe(eventType, wrapper);
};
// 使用示例
publisher.once('sale', (data) => {
console.log(`限时活动:${data}`);
});
publisher.publish('sale', '全场5折'); // 输出:限时活动:全场5折
publisher.publish('sale', '满100减50'); // 无输出(已自动取消订阅)
```
2. 批量移除事件订阅
```javascript
// 扩展:移除某类事件的所有订阅者
publisher.removeAll = function(eventType) {
if (eventType) {
this.subscribers[eventType] = [];
} else {
// 若不指定事件类型,清空所有订阅
this.subscribers = {};
}
};
// 使用示例
publisher.removeAll('news'); // 移除所有新闻订阅者
```
现实场景应用
1. **社交媒体平台**:用户订阅话题/博主,新内容发布时推送给所有订阅者。
2. **前端框架事件系统**:如Vue的`$on`/`$emit`、Node.js的`EventEmitter`,均基于发布/订阅模式实现组件通信。
3. **消息通知系统**:APP中的推送通知(如订单状态更新、活动提醒)。
4. **状态管理库**:如Redux,通过`subscribe`监听状态变化,`dispatch`发布状态更新。
优点与注意事项
* **优点**:解耦发布者和订阅者,支持多对多通信,易于扩展。
* **注意事项**:
* 避免过度使用导致逻辑分散,难以追踪。
* 及时取消无用订阅,防止内存泄漏(尤其是在SPA应用中)。
* 复杂场景需考虑事件命名规范,避免冲突。
### 11.手写 reduce 方法
基本概念
`reduce` 是 JavaScript 数组的核心方法,用于对数组元素逐个处理并汇总为单个值。它通过迭代数组,将每个元素与累加器进行运算,最终返回累加器的最终状态。常用于求和、求积、数组转换、数据聚合等场景。
语法结构
```javascript
array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)
```
* **`callback`**:处理每个元素的函数,接收4个参数:
* `accumulator`:累加器,存储上一次回调的返回值(或初始值)。
* `currentValue`:当前正在处理的数组元素。
* `currentIndex`:当前元素的索引(可选)。
* `array`:调用 `reduce` 的原数组(可选)。
* **`initialValue`**:累加器的初始值(可选)。若不提供,默认使用数组第一个元素作为初始值。
手写实现代码
```javascript
Array.prototype.myReduce = function (callback, initialValue) {
// 检查回调是否为函数
if (typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
// 处理累加器和起始索引
let accumulator = initialValue;
let startIndex = 0;
// 无初始值的情况
if (accumulator === undefined) {
// 空数组且无初始值时抛出错误
if (this.length === 0) {
throw new TypeError('Reduce of empty array with no initial value');
}
// 以数组第一个元素作为初始累加器
accumulator = this[0];
startIndex = 1; // 从第二个元素开始遍历
}
// 遍历数组并执行回调
for (let i = startIndex; i < this.length; i++) {
accumulator = callback(accumulator, this[i], i, this);
}
// 返回最终累加结果
return accumulator;
}
```
代码逻辑解析
1. 参数校验
* 首先检查 `callback` 是否为函数,若不是则抛出 `TypeError`,确保方法调用符合规范。
2. 累加器初始化
* **有初始值(`initialValue` 存在)**:
* `accumulator` 直接赋值为 `initialValue`。
* `startIndex` 设为 `0`(从数组第一个元素开始遍历)。
* **无初始值(`initialValue` 不存在)**:
* 若数组为空,抛出 `TypeError`(无法确定初始累加器)。
* 若数组非空,`accumulator` 赋值为数组第一个元素(`this[0]`)。
* `startIndex` 设为 `1`(从第二个元素开始遍历,避免重复处理第一个元素)。
3. 数组遍历与回调执行
* 使用 `for` 循环从 `startIndex` 遍历至数组末尾。
* 每次迭代调用 `callback`,传入当前累加器、当前元素、索引和原数组。
* 将 `callback` 的返回值重新赋值给 `accumulator`,实现“累加”效果。
4. 返回结果
* 遍历结束后,返回 `accumulator` 的最终值,即数组处理的汇总结果。
使用示例
示例 1:基本求和
```javascript
const arr = [1, 2, 3, 4, 5];
// 有初始值
const sum1 = arr.myReduce((acc, cur) => acc + cur, 0);
console.log(sum1); // 输出:15
// 无初始值
const sum2 = arr.myReduce((acc, cur) => acc + cur);
console.log(sum2); // 输出:15
```
示例 2:数组转对象
```javascript
const fruits = [
{ name: 'apple', count: 2 },
{ name: 'banana', count: 3 },
{ name: 'orange', count: 5 }
];
// 将数组转为 { 水果名: 数量 } 格式的对象
const fruitObj = fruits.myReduce((acc, cur) => {
acc[cur.name] = cur.count;
return acc;
}, {}); // 初始值为 empty object
console.log(fruitObj);
// 输出:{ apple: 2, banana: 3, orange: 5 }
```
示例 3:嵌套数组扁平化
```javascript
const nestedArr = [1, [2, [3, 4], 5], 6];
// 递归扁平化数组
const flatArr = nestedArr.myReduce((acc, cur) => {
return acc.concat(Array.isArray(cur) ? cur.myReduce((a, b) => a.concat(b), []) : cur);
}, []);
console.log(flatArr); // 输出:[1, 2, 3, 4, 5, 6]
```
注意事项
1. **初始值的重要性**:
* 若数组可能为空,必须提供 `initialValue`,否则会抛出错误。
* 若处理对象类型(如示例 2),初始值建议设为 `{}`,避免类型错误。
2. **回调函数的返回值**:
* 回调必须返回一个值作为下一次的累加器,否则累加器会变为 `undefined`,导致结果异常。
3. **与原生 `reduce` 的差异**:
* 手写版本未处理稀疏数组(原生 `reduce` 会跳过空元素),实际使用中需根据需求补充。
* 未实现对 `this` 指向的绑定(原生 `reduce` 可通过第三个参数指定 `callback` 的 `this`)。
应用场景
* 数据聚合(求和、平均值、最大值)。
* 数组转换(扁平化、去重、映射)。
* 复杂数据处理(对象数组分组、属性提取)。
* 函数式编程中的管道(Pipeline)模式(多步处理串联)。
通过 `reduce`,可以用简洁的代码实现复杂的数组操作,是 JavaScript 中极具灵活性的方法之一。
### 12.手写数组去重
方法1:判断相邻元素(排序对比法)
```javascript
function _deleteRepeat(arr) {
if (!Array.isArray(arr)) {
throw new Error('参数必须是数组');
}
let res = [];
// 复制数组并排序(将相同元素相邻排列)
let sortArr = arr.slice().sort((a, b) => a - b);
for (let i = 0; i < sortArr.length; i++) {
// 若为最后一个元素或与下一个元素不同,则添加到结果
if (i === sortArr.length - 1 || sortArr[i] !== sortArr[i + 1]) {
res.push(sortArr[i]);
}
}
return res;
}
// 示例
let arr = [1, 2, 1, 2, 3, 4];
console.log(_deleteRepeat(arr)); // 输出: [1, 2, 3, 4]
```
原理分析
1. **参数校验**:检查输入是否为数组,非数组则抛出错误。
2. **数组处理**:通过 `slice()` 复制原数组(避免修改原数组),并使用 `sort()` 排序,使相同元素相邻。
3. **去重逻辑**:遍历排序后的数组,仅保留与下一个元素不同的元素(或最后一个元素),确保每个元素唯一。
4. **优缺点**:
* 优点:实现简单,无需额外数据结构。
* 缺点:排序会改变元素原始顺序,且时间复杂度为 O(n log n)(受排序算法影响)。
方法2:使用 Set 数据结构
```javascript
function quChong(arr) {
if (!Array.isArray(arr)) {
throw new Error('参数必须是数组');
}
// 利用Set自动去重特性,再转回数组
return [...new Set(arr)];
}
// 等价实现(使用Array.from)
function quChong2(arr) {
if (!Array.isArray(arr)) {
throw new Error('参数必须是数组');
}
return Array.from(new Set(arr));
}
// 示例
let arr = [1, 2, 1, 2, 3, 4];
console.log(quChong(arr)); // 输出: [1, 2, 3, 4]
```
原理分析
1. **Set 特性**:ES6 中的 `Set` 是一种无序集合,其元素具有唯一性(自动去重)。
2. **转换逻辑**:
* 将数组传入 `Set` 构造函数,得到去重后的集合。
* 通过扩展运算符(`...`)或 `Array.from()` 将集合转回数组。
3. **优缺点**:
* 优点:代码简洁,时间复杂度为 O(n),且保留元素原始顺序。
* 缺点:依赖 ES6 特性,若需兼容旧环境需转译。
方法3:使用对象记录已出现元素
```javascript
function unique(arr) {
if (!Array.isArray(arr)) {
throw new Error('参数必须是数组');
}
let res = [];
let obj = {}; // 用于记录已出现的元素
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
// 检查元素是否已存在(处理数字和字符串类型)
if (!obj.hasOwnProperty(item)) {
obj[item] = true; // 标记为已出现
res.push(item);
}
}
return res;
}
// 示例
let arr = [1, '1', 2, 2, 3];
console.log(unique(arr)); // 输出: [1, '1', 2, 3]
```
原理分析
1. **去重逻辑**:使用对象 `obj` 的键存储已出现的元素,利用对象键的唯一性实现去重。
2. **类型处理**:由于对象键会自动转换为字符串(如 `1` 和 `'1'` 会被视为同一个键),上述代码能区分数字和字符串类型。
3. **优缺点**:
* 优点:时间复杂度 O(n),兼容性好(支持 ES5 及以下)。
* 缺点:无法处理复杂类型(如对象、数组),需额外处理。
方法4:处理复杂类型的去重(基于 JSON 序列化)
```javascript
function uniqueComplex(arr) {
if (!Array.isArray(arr)) {
throw new Error('参数必须是数组');
}
let res = [];
let obj = {};
for (let i = 0; i < arr.length; i++) {
// 将元素序列化为字符串,便于对象键存储
const key = JSON.stringify(arr[i]);
if (!obj[key]) {
obj[key] = true;
res.push(arr[i]);
}
}
return res;
}
// 示例
let arr = [
{ id: 1 },
{ id: 1 },
[1, 2],
[1, 2],
3
];
console.log(uniqueComplex(arr));
// 输出: [ { id: 1 }, [1, 2], 3 ]
```
原理分析
1. **复杂类型处理**:通过 `JSON.stringify()` 将对象、数组等复杂类型转换为字符串,作为对象的键进行去重。
2. **局限性**:
* 无法处理函数、正则等特殊类型(`JSON.stringify` 会忽略或转换)。
* 相同内容的不同引用对象会被视为重复(如 `{a:1}` 和 `{a:1}` 会被去重)。
各种方法对比
| 方法 | 时间复杂度 | 保留原始顺序 | 处理复杂类型 | 兼容性 |
|--------------------|------------|--------------|--------------|--------------|
| 排序对比法 | O(n log n) | 否 | 基本类型 | ES3+ |
| Set 转换法 | O(n) | 是 | 基本类型 | ES6+ |
| 对象记录法 | O(n) | 是 | 基本类型 | ES3+ |
| JSON 序列化法 | O(n) | 是 | 部分复杂类型 | ES5+ |
应用场景
* **简单场景**:优先使用 `Set` 转换法(简洁高效)。
* **兼容性要求**:使用对象记录法(支持旧浏览器)。
* **复杂数据**:使用 JSON 序列化法(需评估数据类型是否适用)。
* **禁止改变顺序**:避免使用排序对比法。
数组去重是前端常见需求,选择合适的方法需结合数据类型、性能要求和环境兼容性综合考虑。
### 13.浅拷贝深拷贝
在编程中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两个重要的概念,特别是在处理对象或数组时。它们的主要区别在于如何处理对象或数组中的引用类型(如对象、数组等)。
浅拷贝会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
在JavaScript中,我们可以使用`Object.assign()`方法或展开运算符(`...`)来进行浅拷贝。
**使用**`**Object.assign()**`**进行浅拷贝**:
```javascript
let obj1 = {
a: 1,
b: { c: 2 }
};
let obj2 = Object.assign({}, obj1);
obj2.b.c = 3;
console.log(obj1); // { a: 1, b: { c: 3 } }
console.log(obj2); // { a: 1, b: { c: 3 } }
```
深拷贝(Deep Copy)
深拷贝会创建一个新的对象,并递归地复制对象的所有属性及其子对象,直到它们都是基本类型为止。这样,新对象与原始对象没有任何关联。
在JavaScript中,没有内置的函数可以直接进行深拷贝,但我们可以使用JSON方法(但这种方法有局限性,比如不能处理函数和循环引用)或者手动实现一个深拷贝函数。
**使用JSON方法进行深拷贝(有局限性)**:
```javascript
let obj1 = {
a: 1,
b: { c: 2 }
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 3;
console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 1, b: { c: 3 } }
```
在 `JSON.stringify()` 和 `JSON.parse()` 的上下文中,不能处理函数和循环引用指的是:
1. **函数(Functions)**:\
当您尝试使用 `JSON.stringify()` 将一个包含函数的 JavaScript 对象转换为 JSON 字符串时,该函数将不会被转换为字符串的一部分。在 JSON 规范中,函数不是有效的数据类型,因此它们会被忽略。如果您在对象中有一个函数属性,并且您尝试将其转换为 JSON 字符串,那么这个函数属性将不会出现在生成的字符串中。\
例如:
```javascript
const obj = {
name: "John",
greet: function() {
console.log("Hello, " + this.name);
}
};
const jsonString = JSON.stringify(obj);
console.log(jsonString); // 输出: {"name":"John"}
```
如上所示,`greet` 函数没有出现在输出的 JSON 字符串中。
2. **循环引用(Circular References)**:\
在 JavaScript 中,对象可以通过属性相互引用,形成循环引用。当您尝试使用 `JSON.stringify()` 转换包含循环引用的对象时,它会抛出一个错误,因为 JSON 格式不支持循环引用。\
例如:
```javascript
const obj1 = {};
const obj2 = { ref: obj1 };
obj1.alsoRef = obj2;
try {
const jsonString = JSON.stringify(obj1);
} catch (error) {
console.error(error); // 抛出错误,因为存在循环引用
}
```
在这个例子中,`obj1` 和 `obj2` 通过 `ref` 和 `alsoRef` 属性相互引用,形成了一个循环。当您尝试使用 `JSON.stringify()` 转换这个对象时,它会抛出一个错误。\
为了避免循环引用的问题,您可以使用一个 `replacer` 函数来排除或处理循环引用。但是,请注意,即使您使用 `replacer` 函数,您也无法在 JSON 字符串中保留循环引用的结构,因为 JSON 格式本身不支持这种结构。\
对于 `JSON.parse()` 来说,它不会遇到循环引用的问题,因为它只是将有效的 JSON 字符串转换回 JavaScript 对象。但是,如果您从某个源(如服务器)接收到包含无效循环引用的 JSON 字符串,并且尝试使用 `JSON.parse()` 解析它,那么它将抛出一个 `SyntaxError`。在正常的 JSON 字符串中,您不会遇到循环引用,因为 JSON 格式不支持它们。
***
**手动实现深拷贝(递归方法)**:
```javascript
function deepCopy(obj, hash = new WeakMap()) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (hash.has(obj)) {
return hash.get(obj);
}
let copy = Array.isArray(obj) ? [] : {};
hash.set(obj, copy);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key], hash);
}
}
return copy;
}
let obj1 = {
a: 1,
b: { c: 2 }
};
let obj2 = deepCopy(obj1);
obj2.b.c = 3;
console.log(obj1); // { a: 1, b: { c: 2 } }
console.log(obj2); // { a: 1, b: { c: 3 } }
```
这个手动实现的深拷贝函数可以处理对象和数组,并且可以处理循环引用。它使用了一个`WeakMap`来存储已经拷贝过的对象,以便在遇到循环引用时能够返回正确的拷贝。
手写深拷贝
`deepClone` 函数是一个实现了深拷贝功能的函数,它递归地遍历对象并复制其属性,包括数组和嵌套对象。同时,添加了递归深度的打印以及属性的克隆过程,这有助于理解函数是如何工作的。
简要分析:
1. `deepClone` 函数接受两个参数:`obj`(要克隆的对象)和 `depth`(当前递归的深度,默认为0)。
2. 使用 `console.log` 打印当前递归的深度和被克隆的对象,这对于调试和理解函数执行过程非常有用。
3. 检查 `obj` 是否不是对象或是否为 `null`,如果是,则直接返回 `obj` 本身(基本数据类型和 `null` 的值传递)。
4. 初始化 `result` 变量,根据 `obj` 的类型(数组或对象)来创建一个新的空数组或空对象。
5. 使用 `for...in` 循环遍历 `obj` 的所有可枚举属性。
6. 使用 `hasOwnProperty` 方法检查属性是否是 `obj` 自身的属性(而不是继承自原型链的属性)。
7. 对于每个属性,递归调用 `deepClone` 函数来克隆属性的值,并将结果存储在新的 `result` 对象中。
8. 返回 `result`,即克隆后的对象。
在示例使用部分,您创建了一个包含各种类型属性的对象 `original`,并使用 `deepClone` 函数克隆了它。然后,您打印了原始对象和克隆后的对象,以验证深拷贝是否成功。
执行这段代码,您应该会在控制台看到递归深度和对象被克隆的过程,以及原始对象和克隆后的对象的内容。由于 `deepClone` 函数实现了深拷贝,所以原始对象和克隆后的对象在内存中是独立的,修改其中一个不会影响另一个。
**基础版本**
会写基础版本的就够了,对于有工作经验的人还要考虑Map,Sety以及循环引用
```javascript
function deepClone(obj, depth = 0) {
console.log('depth:', depth, 'value:', obj);
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let result;
if (Array.isArray(obj)) {
result = [];
} else {
result = {};
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
console.log(`Cloning property ${key}`)
result[key] = deepClone(value, depth + 1)
}
}
return result;
}
// 示例使用
const original = {
number: 1,
bool: true,
str: 'string',
array: [1, 2, 3],
obj: { child: 'child', father: { child_1: 'father_1' } }
};
const cloned = deepClone(original);
// console.log('Original:', original);
console.log('Cloned:', cloned);
```
```javascript
PS D:\练\js\手写\13-深拷贝> node .\lian.js\
depth: 0 value: {
number: 1,
bool: true,
str: 'string',
array: [ 1, 2, 3 ],
obj: { child: 'child', father: { child_1: 'father_1' } }
}
Cloning property number
depth: 1 value: 1
Cloning property bool
depth: 1 value: true
Cloning property str
depth: 1 value: string
Cloning property array
depth: 1 value: [ 1, 2, 3 ]
Cloning property 0
depth: 2 value: 1
Cloning property 1
depth: 2 value: 2
Cloning property 2
depth: 2 value: 3
Cloning property obj
depth: 1 value: { child: 'child', father: { child_1: 'father_1' } }
Cloning property child
depth: 2 value: child
Cloning property father
depth: 2 value: { child_1: 'father_1' }
Cloning property child_1
depth: 3 value: father_1
Cloned: {
number: 1,
bool: true,
str: 'string',
array: [ 1, 2, 3 ],
obj: { child: 'child', father: { child_1: 'father_1' } }
}
```
提供的 `deepClone` 函数虽然能够处理大部分基本的深拷贝场景,但它确实有一些潜在的缺陷和限制:
1. **循环引用**:该函数没有处理循环引用的逻辑。如果对象中存在循环引用(即对象属性直接或间接地引用了自己),函数会陷入无限递归,导致栈溢出错误。
2. **特殊类型**:该函数没有处理如 `Date`、`RegExp`、`Function`、`Error`、`Map`、`Set`、`BigInt`、`Symbol` 等特殊类型的对象。这些类型的对象在直接复制时可能无法保持其原始状态或行为。
3. **性能**:对于非常大的对象或深度嵌套的对象,递归可能会导致性能问题。虽然现代JavaScript引擎对递归做了优化,但在某些情况下,使用循环而非递归可能会更有效。
4. **不可枚举属性**:`for...in` 循环只遍历对象自身的可枚举属性。如果对象有不可枚举的属性(例如通过 `Object.defineProperty` 定义的属性),这些属性将不会被复制到新对象中。
5. **getter/setter**:如果对象的属性是通过 getter/setter 方法定义的,那么简单地复制属性值可能不是您想要的行为。您可能希望在新对象上也保持这些 getter/setter 方法。
6. **Buffer 和其他类型化数组**:如果对象包含 Node.js 中的 `Buffer` 或其他类型化数组(如 `Uint8Array`),则简单的复制可能不会按预期工作。
7. **原型链**:该函数只复制了对象自身的属性,而没有复制原型链上的属性。在某些情况下,您可能希望保持原型链的完整性。
8. **未考虑 `null` 和 `undefined` 作为对象属性的值**:虽然函数处理了 `null` 和 `undefined` 作为整体输入的情况,但如果它们作为对象的属性值出现(例如 `{ prop: null }` 或 `{ prop: undefined }`),则会被正常复制。但在某些情况下,您可能希望对这些值进行特殊处理。
为了解决这些问题,您可能需要扩展 `deepClone` 函数以包含额外的逻辑来处理上述特殊情况。另外,也有一些现成的库(如 `lodash` 的 `_.cloneDeep` 方法)提供了更强大和灵活的深拷贝功能。
手写浅拷贝
手写浅拷贝(Shallow Copy)通常指的是复制对象的顶层属性,而不是递归地复制对象的所有子属性。在 JavaScript 中,浅拷贝可以通过多种方式实现,包括使用扩展运算符(`...`)、`Object.assign()` 方法,或者通过循环遍历对象的属性。
以下是几种实现浅拷贝的方法:
1. 使用扩展运算符(Spread Operator)
```javascript
function shallowCopy1(obj) {
return {...obj};
}
```
2. 使用 `Object.assign()` 方法
```javascript
function shallowCopy2(obj) {
return Object.assign({}, obj);
}
```
3. 使用循环遍历属性
```javascript
function shallowCopy3(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
return copy;
}
```
请注意,这些方法在处理数组、对象以及基本类型时表现良好,但它们都是浅拷贝,这意味着如果对象的属性值是另一个对象或数组,那么新对象和原对象将引用相同的子对象或数组。
**示例**
```javascript
const original = {
a: 1,
b: { c: 2 },
d: [3, 4]
};
const copy1 = shallowCopy1(original);
const copy2 = shallowCopy2(original);
const copy3 = shallowCopy3(original);
// 修改原始对象的子对象或数组
original.b.c = 3;
original.d.push(5);
// 浅拷贝的对象也会受到影响,因为它们引用了相同的子对象或数组
console.log(copy1.b.c); // 输出 3
console.log(copy2.d); // 输出 [3, 4, 5]
console.log(copy3.b.c); // 输出 3
```
在这个示例中,由于浅拷贝,`copy1`、`copy2` 和 `copy3` 的 `b` 属性和 `d` 属性分别引用了与 `original` 相同的对象和数组。因此,当修改 `original` 的这些属性时,浅拷贝的对象也会受到影响。
### 14.实现new操作符
在JavaScript中,`new`关键字的作用是创建一个构造函数的实例对象,而`myNew`函数则是对这一过程的手动实现,它完整模拟了`new`操作符的核心逻辑。
当使用`myNew`函数时,首先需要确保传入的第一个参数是一个有效的函数,因为只有函数才能作为构造函数来创建实例。如果传入的不是函数,就会抛出类型错误,提示“constructor must be a function”,这一步是为了保证后续操作的合法性。
接着,`myNew`会创建一个新的空对象`obj`,并通过`Object.create(constructor.prototype)`让这个空对象的原型指向构造函数的`prototype`属性。这一步非常关键,它使得新创建的对象能够继承构造函数原型上的所有属性和方法,这也是实现原型链继承的基础。
然后,函数会调用构造函数,并通过`constructor.apply(obj, args)`将`obj`作为`this`的上下文传入,同时将后续的参数传递给构造函数。这样一来,构造函数内部通过`this`设置的属性和方法,都会被绑定到`obj`上,完成对新对象的初始化。
最后,`myNew`会检查构造函数的返回值。如果返回的是一个非`null`的对象,那么`myNew`就返回这个对象;否则,就返回之前创建的`obj`。这是因为在JavaScript中,如果构造函数显式返回一个对象,`new`操作符的结果就会是这个返回的对象,而不是新创建的实例,`myNew`函数遵循了这一规则。
下面通过一个示例来具体说明:
```javascript
function Animal(type, sound) {
this.type = type;
this.sound = sound;
}
Animal.prototype.makeSound = function() {
console.log(this.sound);
};
const dog = myNew(Animal, 'dog', 'woof');
dog.makeSound(); // 输出:woof
console.log(dog.type); // 输出:dog
```
在这个示例中,`myNew`函数创建了`Animal`的实例`dog`,`dog`继承了`Animal.prototype`上的`makeSound`方法,并且拥有通过构造函数初始化的`type`和`sound`属性,与使用`new`关键字创建的实例行为一致。
再看一个构造函数返回对象的情况:
```javascript
function Car(brand) {
this.brand = brand;
return {
brand: 'Tesla',
drive: function() {
console.log(`${this.brand} is driving`);
}
};
}
const myCar = myNew(Car, 'BMW');
myCar.drive(); // 输出:Tesla is driving
console.log(myCar.brand); // 输出:Tesla
```
这里,`Car`构造函数返回了一个新对象,所以`myNew`函数最终返回的是这个新对象,而不是原本创建的`obj`,因此`myCar`的品牌是`Tesla`,而不是传入的`BMW`,这符合JavaScript中`new`操作符的特性。
### 15.手写Eventbus(事件总线)
上述简化版的EventBus虽然能满足基本的发布/订阅需求,但在实际应用中,可能还需要考虑更多细节,比如为每个订阅者提供唯一标识,方便更精准地取消订阅,或者支持一次性订阅等功能。
下面是一个功能更完善的EventBus实现:
```javascript
class EventBus {
constructor() {
this.events = {}; // 存储事件,键为事件名,值为对象,该对象的键为订阅ID,值为回调函数
this.subId = 0; // 用于生成唯一的订阅ID
}
// 订阅事件,返回订阅ID,用于取消订阅
subscribe(eventName, callback, isOnce = false) {
if (typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
if (!this.events[eventName]) {
this.events[eventName] = {};
}
const id = this.subId++;
this.events[eventName][id] = { callback, isOnce };
return id;
}
// 一次性订阅,事件触发后自动取消订阅
once(eventName, callback) {
return this.subscribe(eventName, callback, true);
}
// 发布事件
publish(eventName, ...args) {
if (!this.events[eventName]) {
return;
}
// 复制一份当前的订阅者列表,防止在回调中取消订阅导致的遍历问题
const currentSubscribers = { ...this.events[eventName] };
for (const id in currentSubscribers) {
const { callback, isOnce } = currentSubscribers[id];
callback(...args);
if (isOnce) {
delete this.events[eventName][id];
}
}
// 如果事件的订阅者为空,删除该事件
if (Object.keys(this.events[eventName]).length === 0) {
delete this.events[eventName];
}
}
// 根据订阅ID取消订阅某个事件
unsubscribe(eventName, subId) {
if (!this.events[eventName] || !this.events[eventName][subId]) {
return false;
}
delete this.events[eventName][subId];
if (Object.keys(this.events[eventName]).length === 0) {
delete this.events[eventName];
}
return true;
}
// 取消某个事件的所有订阅
unsubscribeAll(eventName) {
if (this.events[eventName]) {
delete this.events[eventName];
}
}
// 清空所有事件的订阅
clear() {
this.events = {};
this.subId = 0;
}
}
// 使用示例
const eventBus = new EventBus();
// 普通订阅
const sub1 = eventBus.subscribe('message', (content) => {
console.log('收到消息:', content);
});
// 一次性订阅
const sub2 = eventBus.once('notice', (info) => {
console.log('收到通知:', info);
});
// 发布事件
eventBus.publish('message', '今天天气不错'); // 输出:收到消息:今天天气不错
eventBus.publish('notice', '明天有雨'); // 输出:收到通知:明天有雨
eventBus.publish('notice', '后天晴天'); // 由于是一次性订阅,此次发布不会触发回调
// 取消订阅
eventBus.unsubscribe('message', sub1);
eventBus.publish('message', '明天会更好'); // 由于已取消订阅,此次发布不会触发回调
// 取消某个事件的所有订阅
eventBus.subscribe('test', () => { console.log('test1'); });
eventBus.subscribe('test', () => { console.log('test2'); });
eventBus.unsubscribeAll('test');
eventBus.publish('test'); // 不会触发任何回调
// 清空所有订阅
eventBus.subscribe('demo', () => { console.log('demo'); });
eventBus.clear();
eventBus.publish('demo'); // 不会触发任何回调
```
在这个实现中,每个事件对应的订阅者被存储为一个对象,以订阅ID作为键,这样可以通过订阅ID精准地取消某个特定的订阅。同时,增加了`once`方法用于一次性订阅,事件触发后会自动取消该订阅。另外,在发布事件时,复制了当前的订阅者列表,避免在回调函数中取消订阅导致的遍历异常。还提供了`unsubscribeAll`方法取消某个事件的所有订阅,以及`clear`方法清空所有订阅,使EventBus的功能更加完善。
这种发布/订阅模式在前端开发中应用广泛,比如在组件通信中,不同组件可以通过EventBus来传递消息,而不需要直接引用对方,降低了组件之间的耦合度。
上述简化版的EventBus虽然能满足基本的发布/订阅需求,但在实际应用中,可能还需要考虑更多细节,比如为每个订阅者提供唯一标识,方便更精准地取消订阅,或者支持一次性订阅等功能。
下面是一个功能更完善的EventBus实现:
```javascript
class EventBus {
constructor() {
this.events = {}; // 存储事件,键为事件名,值为对象,该对象的键为订阅ID,值为回调函数
this.subId = 0; // 用于生成唯一的订阅ID
}
// 订阅事件,返回订阅ID,用于取消订阅
subscribe(eventName, callback, isOnce = false) {
if (typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
if (!this.events[eventName]) {
this.events[eventName] = {};
}
const id = this.subId++;
this.events[eventName][id] = { callback, isOnce };
return id;
}
// 一次性订阅,事件触发后自动取消订阅
once(eventName, callback) {
return this.subscribe(eventName, callback, true);
}
// 发布事件
publish(eventName, ...args) {
if (!this.events[eventName]) {
return;
}
// 复制一份当前的订阅者列表,防止在回调中取消订阅导致的遍历问题
const currentSubscribers = { ...this.events[eventName] };
for (const id in currentSubscribers) {
const { callback, isOnce } = currentSubscribers[id];
callback(...args);
if (isOnce) {
delete this.events[eventName][id];
}
}
// 如果事件的订阅者为空,删除该事件
if (Object.keys(this.events[eventName]).length === 0) {
delete this.events[eventName];
}
}
// 根据订阅ID取消订阅某个事件
unsubscribe(eventName, subId) {
if (!this.events[eventName] || !this.events[eventName][subId]) {
return false;
}
delete this.events[eventName][subId];
if (Object.keys(this.events[eventName]).length === 0) {
delete this.events[eventName];
}
return true;
}
// 取消某个事件的所有订阅
unsubscribeAll(eventName) {
if (this.events[eventName]) {
delete this.events[eventName];
}
}
// 清空所有事件的订阅
clear() {
this.events = {};
this.subId = 0;
}
}
// 使用示例
const eventBus = new EventBus();
// 普通订阅
const sub1 = eventBus.subscribe('message', (content) => {
console.log('收到消息:', content);
});
// 一次性订阅
const sub2 = eventBus.once('notice', (info) => {
console.log('收到通知:', info);
});
// 发布事件
eventBus.publish('message', '今天天气不错'); // 输出:收到消息:今天天气不错
eventBus.publish('notice', '明天有雨'); // 输出:收到通知:明天有雨
eventBus.publish('notice', '后天晴天'); // 由于是一次性订阅,此次发布不会触发回调
// 取消订阅
eventBus.unsubscribe('message', sub1);
eventBus.publish('message', '明天会更好'); // 由于已取消订阅,此次发布不会触发回调
// 取消某个事件的所有订阅
eventBus.subscribe('test', () => { console.log('test1'); });
eventBus.subscribe('test', () => { console.log('test2'); });
eventBus.unsubscribeAll('test');
eventBus.publish('test'); // 不会触发任何回调
// 清空所有订阅
eventBus.subscribe('demo', () => { console.log('demo'); });
eventBus.clear();
eventBus.publish('demo'); // 不会触发任何回调
```
在这个实现中,每个事件对应的订阅者被存储为一个对象,以订阅ID作为键,这样可以通过订阅ID精准地取消某个特定的订阅。同时,增加了`once`方法用于一次性订阅,事件触发后会自动取消该订阅。另外,在发布事件时,复制了当前的订阅者列表,避免在回调函数中取消订阅导致的遍历异常。还提供了`unsubscribeAll`方法取消某个事件的所有订阅,以及`clear`方法清空所有订阅,使EventBus的功能更加完善。
这种发布/订阅模式在前端开发中应用广泛,比如在组件通信中,不同组件可以通过EventBus来传递消息,而不需要直接引用对方,降低了组件之间的耦合度。
### 16.数组扁平化
除了递归方式,数组扁平化还有其他多种实现方式,各有其特点:
**使用 reduce 方法**
利用 reduce 方法的累加器特性,遍历数组并处理每个元素:
```javascript
function flatten(arr) {
return arr.reduce((acc, item) => {
return acc.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
const arr = [1, 2, [3, 4, [5, 6, [7, 8]]]];
console.log(flatten(arr)); // [1, 2, 3, 4, 5, 6, 7, 8]
```
**使用扩展运算符**
通过扩展运算符的展开特性,结合 some 方法判断是否还有嵌套数组:
```javascript
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
const arr = [1, 2, [3, 4, [5, 6, [7, 8]]]];
console.log(flatten(arr)); // [1, 2, 3, 4, 5, 6, 7, 8]
```
**使用 toString 方法**
先将数组转为字符串,再分割成数组并转换为数字(仅适用于数组元素都是数字的情况):
```javascript
function flatten(arr) {
return arr.toString().split(',').map(item => Number(item));
}
const arr = [1, 2, [3, 4, [5, 6, [7, 8]]]];
console.log(flatten(arr)); // [1, 2, 3, 4, 5, 6, 7, 8]
```
**指定扁平化深度**
类似数组的 flat 方法,可以指定扁平化的层级:
```javascript
function flatten(arr, depth = Infinity) {
if (depth <= 0) return arr.slice();
return arr.reduce((acc, item) => {
if (Array.isArray(item)) {
acc.push(...flatten(item, depth - 1));
} else {
acc.push(item);
}
return acc;
}, []);
}
const arr = [1, 2, [3, 4, [5, 6, [7, 8]]]];
console.log(flatten(arr, 1)); // [1, 2, 3, 4, [5, 6, [7, 8]]]
console.log(flatten(arr, 2)); // [1, 2, 3, 4, 5, 6, [7, 8]]
```
这些方法各有适用场景,递归和 reduce 方法适用性较广,扩展运算符方式实现简洁,toString 方法有一定局限性,指定深度的方法则更灵活,可根据实际需求选择使用。
### 17.手写instanceof运算符
上述两个`myInstanceof`函数的实现,核心逻辑一致,只是获取对象原型的方式不同,一个使用非标准的`__proto__`,另一个使用标准的`Object.getPrototypeOf()`方法,两者作用相同,都是为了获取对象的原型。
以下是使用示例的代码运行逻辑:
```javascript
// 定义Animal构造函数
function Animal(name) {
this.name = name; // 给实例添加name属性
}
// 在Animal的原型上添加speak方法
Animal.prototype.speak = function () {
return this.name + 'speak' // 返回拼接后的字符串
}
// 定义Dog构造函数
function Dog(name) {
Animal.call(this, name); // 调用Animal构造函数,将this指向Dog的实例,实现属性继承
}
// 让Dog的原型继承Animal的原型
Dog.prototype = Object.create(Animal.prototype);
// 修正Dog原型的constructor指向,使其指向自身构造函数
Dog.prototype.constructor = Dog;
// 在Dog的原型上添加bark方法
Dog.prototype.bark = function () {
return this.name + 'bark' // 返回拼接后的字符串
}
// 创建一个Dog的实例,name为'小黑'
let myDog = new Dog('小黑');
// 调用myDog的speak方法(继承自Animal原型)
console.log(myDog.speak()) // 输出:小黑speak
// 检查myDog是否是Dog的实例
console.log(myInstanceof(myDog, Dog)) // 输出:true
// 检查myDog是否是Animal的实例(因Dog继承自Animal)
console.log(myInstanceof(myDog, Animal)) // 输出:true
// 检查myDog是否是Object的实例(所有对象最终都继承自Object)
console.log(myInstanceof(myDog, Object)) // 输出:true
```
### 18.手写curry函数(柯里化)
```
function curry(fn) {
const fnArgsLength = fn.length;
let args = [];
function calc(...newArgs) {
args = [
...args,
...newArgs
]
if (args.length < fnArgsLength) {
return calc;
} else {
return fn.apply(this, args.slice(0, fnArgsLength));
}
}
return calc;
}
function add (a, b, c) {
return a+b+c;
}
const curryAdd = curry(add);
console.log(curryAdd(10)(20)(30));
console.log(curryAdd(10,20,30));
```
---
---
url: /笔记/01.运维/03.Docker/04.Dockerfile.md
---
## 一. 什么是 Dockerfile
* **Dockerfile** 是用来构建 **Docker** 镜像的构建文件, 是由一系列的命令和参数构成的脚本
* 通过指令的方式构建镜像
## 二. 构建 Dockerfile 步骤
* 编写 **Dockerfile** 文件
* **docker built** 构建镜像
* **docker run** 创建容器
## 三.Dockfile 文件的注意事项
* 每条保留字指令都必须是大写字母, 并且后面要跟随至少一个参数
* 指令按照从上到下的顺序执行
* 每条指令可用 **#** 添加注释
* 每条指令都会创建一个新镜像层, 并对镜像进行提交
## 四.Dockerfile 的保留字指令
```plain
🍉主要保留字指令:
1. FROM
2. RUN
3. ADD
4. COPY
5. WORKDIR
6. CMD
🍉一般用以上保留字指令就可以完成容器想要的功能
```
### 1.**FROM**
* 基础 (依赖) 镜像, 就是当前要创建的镜像是基于那个镜像
```plain
🍉格式:FROM
FROM :
FROM @
🔰示例:" FROM mysql:5.6"
🔰注:"tag" 或 "digest" 是可选的,如果不使用这两个值时,会使用 "latest" 版本的基础镜像
```
### 2.**MAINTAINER**
* 镜像维护者的姓名和邮箱
```plain
🍉格式:MAINTAINER
🔰示例:MAINTAINER Jasper Xu
MAINTAINER sorex@163.com
MAINTAINER Jasper Xu
```
### 3.**RUN**
* 容器构建时需要运行的命令
```plain
🍉shell 执行格式:RUN
🔰示例:RUN yum install python3 -y
🍉exec 执行格式:RUN ["executable", "param1", "param2"]
🔰示例:RUN ["executable", "param1", "param2"]
RUN ["/etc/execfile", "arg1", "arg1"]
🔰注:RUN 指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定 "--no-cache" 参数,如:docker --no-cache
```
### 4.**EXPOSE**
* 当前容器对外暴露出的端口
```plain
🍉格式:EXPOSE [...]
🔰示例:EXPOSE 80 443
EXPOSE 8080
EXPOSE 11211/tcp 11211/udp
🔰注:"EXPOSE" 并不会让容器的端口访问到主机。要使其可访问,需要在 " docker run" 运行容器时通过 "-p" 来发布这些端口,或通过 "-P" 参数来发布 " EXPOSE" 导出的所有端口
```
### 5.**WORKDIR**
* 指定创建容器后, 终端默认处在的工作目录, 也就是落脚点
```plain
🍉格式:WORKDIR /path/to/workdir
🔰示例:WORKDIR /a (这时工作目录为 /a)
WORKDIR b (这时工作目录为 /a/b)
WORKDIR c (这时工作目录为 /a/b/c)
🔰注:通过 "WORKDIR" 设置工作目录后,"Dockerfile" 中其后的命令 RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都会在该目录下执行。在使用 "docker run" 运行容器时,可以通过 "-w" 参数覆盖构建时所设置的工作目录
```
### 6.**ENV**
* 用来在构建镜像过程中设置环境变量
```plain
🍉格式:ENV
# 之后的所有内容均会被视为其 的组成部分,因此,一次只能设置一个变量
ENV = ...
#可以设置多个变量,每个变量为一个 "=" 的键值对,如果 中包含空格,可以使用 \ 来进行转义,也可以通过 "" 来进行标示;另外,反斜线也可以用于续行
🔰示例:ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat=fluffy
```
### 7.**ADD**
* 将宿主机目录下的文件拷贝到镜像里面 (会自动解压 tar 压缩包)
```plain
🍉格式:
ADD ...
ADD ["",... ""] # 用于支持包含空格的路径
🔰示例:ADD hom* /mydir/ # 添加所有以 "hom" 开头的文件
ADD hom?.txt /mydir/ # ? 替代一个单字符, 例如:"home.txt"
ADD test relativeDir/ # 添加 "test" 到 `WORKDIR`/relativeDir/
ADD test /absoluteDir/ # 添加 "test" 到 /absoluteDir/
```
### 8.**COPY**
* 类似 ADD, 拷贝本地文件到镜像中 (不会自动解压)
```plain
🍉语法:COPY < src>… < dest>
COPY [“< src>”,…“< dest>”]
🔰注:指令逻辑和 "ADD" 十分相似,同样 " Docker Daemon" 会从编译目录寻找文件或目录,"dest" 为镜像中的绝对路径或者相对于 "WORKDIR" 的路径
```
### 9.**VOLUME**
* 用于目录挂载
```plain
🍉格式:VOLUME ["/path/to/dir"]
🔰示例:VOLUME ["/data"]
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
🔰注:一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:1. 卷可以容器间共享和重用
2. 容器并不一定要和其它容器共享卷
3. 修改卷后会立即生效
4. 对卷的修改不会对镜像产生影响
5. 卷会一直存在,直到没有任何容器在使用它
```
### 10.**CMD** (这个指令需放在最后)
* 指定容器启动时要运行的命令
```plain
🍉格式:CMD ["executable","param1","param2"] (执行可执行文件,优先)
CMD ["param1","param2"] (设置了 ENTRYPOINT,则直接调用 ENTRYPOINT 添加参数)
CMD command param1 param2 (执行 shell 内部命令)
🔰示例:CMD echo "This is a test." | wc -w
CMD ["/usr/bin/wc","--help"]
🔰注:"CMD" 不同于 "RUN","CMD" 用于指定在容器启动时所要执行的命令,而 "RUN" 用于指定镜像构建时所要执行的命令
```
### 11.LABEL
* 为镜像添加元数据
```plain
🍉格式:LABEL = = = ...
🔰示例:LABEL version="1.0" description=" 这是一个 Web 服务器 " by="IT 笔录 "
🔰注:使用 "LABEL" 指定元数据时,一条 "LABEL" 指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条 "LABEL" 指令指定,以免生成过多的中间镜像
```
### 12.ONBUILD
* 用于设置镜像触发器
```plain
🍉格式:ONBUILD [INSTRUCTION]
🔰示例:ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
🔰注:当所构建的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被触发
```
## 五. 构建镜像案例
### 1. 使用 centos7 作为基础镜像部署 nginx 服务
* 先创建一个 **nginx.repo** 文件
```plain
# vim nginx.repo
''' 文件内容
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
'''
```
* 编写 **Dockerfile** 文件
```plain
[root@shawn ~]# vim Dockerfile
# 指定基础镜像(依赖镜像)FROM centos:7
# 执行一个命令
RUN yum install -y yum-utils
# 将本地文件添加到容器中
ADD nginx.repo /etc/yum.repos.d/nginx.repo
# 更新 YUM 缓存
RUN yum makecache
# 安装 nginx
RUN yum install -y nginx
# 制定容器启动默认执行的命令
CMD nginx -g 'daemon off;'
```
* 构建镜像
```plain
[root@shawn ~]# docker build -t install/nginx:v1 .
```
* 查看刚刚构建的镜像, 然后实例容器
```plain
[root@shawn ~]# docker images
[root@shawn ~]# docker run -dit install/nginx:v1 sh
```
* 查看刚刚实例出的容器, 并进入到容器中
```plain
[root@shawn ~]# docker exec -it 94f8e35f3357 bash
```
* 检测 **nginx** 是否部署成功
```plain
[root@shawn ~]# crul 127.0.0.1 # 出现 html 代码说明部署成功
```
### 2. 在容器中编译安装 nginx 服务
* 编辑 **Dockerfile** 文件
```plain
[root@shawn ~]# vim Dockerfile
''' 文件内容
# 指定基础镜像(依赖镜像)
FROM centos:7
# 执行命令
RUN yum install yum-utils wget zlib zlib-devel pcre pcre-devel make gcc gcc-c++
RUN cd /opt && wget http://nginx.org/download/nginx-1.18.0.tar.gz && tar -xvf nginx.1.18.0/ && cd nginx-1.18.0/ && ./configure && make && make install
# 指定进入容器的默认工作目录
WORKDIR /usr/local/nginx/sbin
# 指定容器启动默认执行的命令
CMD ./nginx -g 'daemon off;'
'''
```
* 构建镜像
```plain
[root@shawn ~]# docker build -t yuan/install/nginx:v2 .
```
* 查看是否构建成功, 并实例出容器
```plain
[root@shawn ~]# docker images
[root@shawn ~]# docker run -dit --name yuan_nginx yuan/install/nginx:v2 sh
```
* 查看容器是否启动成功, 并测试 **nginx**
```plain
[root@shawn ~]# docker exec yuan_nginx crul 127.0.0.1 # 出现 html 代码说明部署成功
```
### 3. 构建以 Centos 为依赖镜像并安装 Django 的服务
* 首先构建一个 Dockerfile 文件
```plain
[root@shawn ~]#vim Dockerfile
# 指定基础镜像
FROM centos:7
# 运行命令
RUN yum makecache && yum update -y && yum install -y python3 && pip3 install django
# 拷贝本地文件到容器
COPY shawn /root/
# 指定进入到容器的工作目录
WORKDIR /root/
# 指定向外暴露的端口
EXPOSE 8080
# 运行命令
CMD cd ./shawn && python3 manage.py runserver 0.0.0.0:8080
```
* 文件 shawn 的构建
```plain
在宿主机上安装 Django
django-admin startproject shawn #创建一个 "Shawn" 项目
cd ./shawn #进入目录
django-admin startapp application #开始项目
cd ./shawn
vim setting.cong #修改配置文件 "*" 代理
cd .. #退出
```
* 构建镜像
```plain
[root@shawn ~]#docker build -t test333:v1 .
```
* 查看并使用镜像实例化出容器
```plain
[root@shawn ~]#docker images
[root@shawn ~]#docker run -dit --name test001 -p 9999:8080 test333:v1 sh
```
* 查看刚开启的容器, 并进入容器启动 Django 服务
```plain
[root@shawn ~]#docker exec -it test001 bash
[root@80f1315c030c ~]# python3 manage.py runserver 0.0.0.0:8080
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
December 04, 2020 - 06:50:19
Django version 3.1.4, using settings 'lingxiu.settings'
Starting development server at http://0.0.0.0:8080/
Quit the server with CONTROL-C.
```
* 使用浏览器验证一下

### 3. 构建以 python 为依赖镜像并安装 Django 服务
### 编辑 Dockerfile 文件
```plain
[root@shawn ~]# vim Dockerfile
''' 文件内容
# 指定依赖镜像
FROM python:3.6
# 设置作者
MAINTAINER Shawn
# 执行命令
RUN /usr/local/bin/python -m pip install --upgrade pip
RUN pip3 install django==2.2.2
# 拷贝文件
COPY app /root/
# 设置工作目录
WORKDIR /root/
# 执行命令
CMD cd ./app && python3 manage.py runserver 0.0.0.0:7777
```
* 文件 app 的构建
```plain
在宿主机上安装 Django
django-admin startproject app #创建一个 "app" 项目
cd ./app #进入目录
django-admin startapp application #开始项目
cd ./app
vim setting.cong #修改配置文件 "*" 代理
cd .. #退出
```
* 构建镜像
```plain
[root@shawn ~]#docker build -t jjjj .
```
* 查看并使用镜像实例化出容器
```plain
[root@shawn ~]#docker images
[root@shawn ~]#docker run -dit --name jjjjtest -p 4444:7777 jjjj:latest sh
```
* 查看刚开启的容器, 并进入容器启动 Django 服务
```plain
[root@shawn ~]#docker exec -it jjjtest bash
root@b85f93fcc114:~# python3 manage.py runserver 0.0.0.0:7777
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
December 04, 2020 - 10:17:51
Django version 2.2.2, using settings 'app.settings'
Starting development server at http://0.0.0.0:7777/
Quit the server with CONTROL-C.
```
* 使用浏览器检验一下

### 4. 使用 NGINX 代理 Django
* 先构建一个 Django 服务, 步骤与上一个例子相同
* 改变了一下向外暴露的端口
```plain
🎅编写 "Dockerfile" 文件
[root@shawn DjangoDocker]#vim Dockerfile
''' 文件内容
# 指定依赖镜像
FROM pyhton:3.6
# 安装 Django
RUN /usr/local/bin/python -m pip install --upgrade pip
RUN pip3 install django==2.2.2
# COPY 文件
COPY app /root/
# 指定工作目录
WORKDIR /root/
# 运行命令
CMD cd ./app && python3 manage.py runserver 0.0.0.0:8080
'''
[root@shawn DjangoDocker]#ls
app Dockerfile # 这两个文件, "app" 在上一个例子中有构建
🎅构建镜像, 并查看
[root@shawn DjangoDocker]#docker build -t python_django:v6 .
[root@shawn DjangoDocker]#docker images
🎅实例出容器, 并查看
[root@shawn DjangoDocker]#docker run -dit --name p_d_test1 -p 8888:8080 python_django:v6 sh
6906ff9e3ec0f9d583eb27890d82c79deff4358a43e5f1ec768a702547d020bf
[root@shawn DjangoDocker]#docker ps
🎅进到容器里面, 开启服务, 再测试
[root@shawn DjangoDocker]#docker exec -it p_d_test1 bash
root@6906ff9e3ec0:~# python3 manage.py runserver 0.0.0.0:8080
[root@shawn DjangoDocker]#curl 127.0.0.1:8888
```
* 然后来编写 nginx 服务以及代理配置
```plain
🎅编写 "nginx.repo" 文件
[root@shawn NginxDocker]#vim nginx.repo
''' 文件内容(官网可复制)
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
'''
🎅编写 "default.conf" 文件(代理 "Django" 配置)
[root@shawn NginxDocker]#vim default.conf
''' 文件内容
server {
listen 80;
server_name www.py16zxl.com;
location / {# 这里填的是 Django 服务的访问地址与端口(映射端口)
proxy_pass http://192.168.13.234:8888/;
index index.html index.htm index.jsp;
}
}
'''
🎅编写 "Dockerfile" 文件
[root@shawn NginxDocker]#vim Dockerfile
''' 文件内容
# 指定依赖进行
FROM centos:7
# 指定作者
MAINTAINER shawn
# 安装依赖
RUN yum install -y yum-utils gcc gcc-c++ pcre pcre-devel zlib zlib-devel make wget
## 源码安装 nginx1.18.0
# RUN wget http://nginx.org/download/nginx-1.18.0.tar.gz && tar -xvf nginx-1.18.0.tar.gz && cd nginx.1.18.0 && ./configure --prefix="/usr/local/nginx-1.18.0" && make && make install
# 拷贝 NGINX 配置文件
COPY nginx.repo /etc/yum.repos.d/
# 更新 yum 软件包索引
RUN yum makecache fast
# yum 安装 nginx
RUN yum install -y nginx
# 指定向外暴露的端口
EXPOSE 8000
# 拷贝 nginx 默认配置文件
COPY default.conf /etc/nginx/conf.d/
# 容器起来运行的命令
CMD /usr/local/nginx-1.18.0/sbin/nginx -g 'daemon off;'
'''
🎅当前需要的文件
[root@shawn NginxDocker]#ls
default.conf Dockerfile nginx.repo
🎅开始构建镜像, 并查看
[root@shawn NginxDocker]#docker build -t nginx_d:v7 .
[root@shawn NginxDocker]#docker images
🎅实例化出容器, 并查看
[root@shawn NginxDocker]#docker run -dit --name nginx_d -p 80:80 nginx_d:v7 sh
[root@shawn NginxDocker]#docker ps
🎅进入容器, 开启 "nginx" 服务, 并验证
[root@shawn NginxDocker]#docker exec -it nginx_d bash
[root@51f54c1d5abb /]#nginx
[root@shawn NginxDocker]#curl 127.0.0.1:80
# 发现通过访问 nginx 也可以进入 Django 页面
```

---
---
url: /笔记/01.运维/03.Docker/02.Docker安装.md
---
# CentOS 7 安装 Docker
[官方中文安装参考手册:https://docs.docker-cn.com/engine/installation/linux/docker-ce/centos/#prerequisites](https://docs.docker-cn.com/engine/installation/linux/docker-ce/centos/#prerequisites)
## 一. 先确定是否 Centos 7 及以上的版本
cat /etc/redhat-release

## 二. 查看是否 ping 通外网

## 三.yum 安装 gcc 相关

## 四. 有旧版本可先卸载旧版本
yum -y remove docker\*
## 五. 刷新 yum 源缓存
yum makecache

## 六. 为了方便我们可以更新一下系统 (除了内核)
yum update -y --exclud=kernel\*
* \--exclud : 排除相关软件包
## 七. 安装需要的软件包
yum install -y yum-utils device-mapper-persistent-data lvm2


## 八. 安装 yum 源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

## 九. 更新 yum 软件包索引
yum makecache fast

## 十. 安装 Docker-ce
yum -y install docker-ce

## 十一. 安装完毕后启动
systemctl start docker
开机自启 : systemctl enable docker

## 十二. 测试
* docker images
* docker info
* docker version

* docker run hello-world

## 十三. 停止加卸载
* systemcel stop docker
* yum -y remove docker-ce
* rm -rf /var/lib/docker
---
---
url: /笔记/01.运维/03.Docker/03 Docker常用命令.md
---
# 一. 帮助命令
* `docker version`:查看 Docker 版本信息
* `docker info`:查看 Docker 信息
* `docker --help`:查看帮助信息
# 二. 登入命令
* `docker login [参数] [镜像仓库 URL]`

* 参数 `--username=xxx`:指定阿里云用户名(我用的是阿里云)
* `passwd`:是创建仓库时的密码
* `url`:仓库链接

# 三. 获取镜像
* 格式:`docker pull [镜像仓库 URL]/[命名空间名称]/[仓库名称]:[镜像版本号]`
* 示例:`docker pull docker.io/library/busybox:latest`
| URL | docker.io |
| ------------ | ----------- |
| **命名空间** | **library** |
| **仓库名称** | **busybox** |
| **版本号** | **latest** |
* 简化:`docker pull busybox:latest (.....)`

# 四. 查看镜像信息
* `docker images [参数]` / `docker images ls [参数]` 列出本地主机上的镜像

* 选项说明
| REPOSITORY | 仓库名字 |
| ------------ | -------------------------------- |
| **TAG** | **版本号(latest: 表示最新版本)** |
| **IMAGE ID** | **镜像 ID** |
| **CREATED** | **镜像创建时间到现在** |
| **SIZE** | **镜像文件的体积** |
```plain
🔰同一仓库源可以有多个 "TAG",代表这个仓库源的不同个版本,我们使用 " REPOSITORY:TAG" 来定义不同的镜像
🔰如果你不指定一个镜像的版本标签,例如你只使用 "ubuntu","docker" 将默认使用 "ubuntu:latest" 镜像
```
* 参数说明
* `-a`:显示所有镜像, 包括临时镜像
* `-q`:只显示镜像 ID

```
- `--digests`:显示镜像摘要信息
- `--no-trunc`:显示完整的镜像信息
```
# 五. 为镜像打 TAG
* 格式:`docker tag [原镜像仓库 url]/[原镜像命名空间]/[原镜像仓库名称]:[版本号] [新镜像仓库 url]/[新镜像命名空间]/[新镜像仓库名称]:[版本号]`
* 示例:`docker tag busybox:latest registry.cn-shanghai.aliyuncs.com/python16-shawn/busybox:v1`

# 六. 推送镜像
* 格式:`docker push [镜像仓库 URL]/[命名空间名称]/[仓库名称]:[版本号]`
* 示例:`docker push registry.cn-shanghai.aliyuncs.com/python16-shawn/busybox:v1`

再看看我的阿里云镜像仓库

# 七. 获取镜像详细信息
* 格式:`docker inspect [镜像 ID]` / `[镜像名称:版本号]`
* 示例:`docker inspect busybox:latest`
* `-f`:用 "golang" 语言模板语法获取镜像相关信息

# 八. 查看历史镜像
* 格式:`docker history [镜像名字: 镜像版本号]` / `[镜像 ID]`
* 示例:`docker history dc3bacd8b5ea`

# 九. 搜索镜像
* 格式:`docker search [所搜索的镜像名称] [参数]`
* 示例:`docker search python`

* 列表参数
| NAME | 仓库名称 |
| --------------- | ---------------------- |
| **DESCRIPTION** | **描述** |
| **STARS** | **收藏个数** |
| **OFFICIAL** | **是否是官方镜像** |
| **AUTOMATED** | **是否是自构建的镜像** |
* 命令行参数
* `-f`:过滤 示例: `docker search python -f stars=300` (收藏 300 的)

```
- `-s`:列出收藏不小于指定值的镜像 : 示例 : `docker search python -s 300`
```

```
- `--automated`:只列出 automated build 类型的镜像
```

```
- `--limit`:显示查询条数
```
# 十. 删除和清理镜像
* 格式:`docker rmi [镜像名称:版本号]` / `[镜像 ID]`
* `-f`:强制删除
## 1. 删除单个示例
* `docker rmi -f bf756fb1ae65`

## 2. 删除多个
* `docker rmi -f [镜像名 1:TAG] [镜像名 2:TAG]...`
## 3. 删除全部
* `docker rmi -f $(docker images -qa)`

## 4. 清理镜像
* 格式:`docker image prune [参数]`
* 参数:`-a (all)` 清理所有没有被使用的镜像
* `-f`:强制
# 十一. 保存镜像
* 格式:`docker commit [参数] [容器 ID]` / `[容器名称: 版本号]`
* 参数:
| `-a` | 指定作者 |
| ------ | ---------------------------- |
| **`-m`** | **简介** |
| **`-p`** | **保存镜像时, 镜像暂停运行** |
# 十二. 运行一个容器
前提:有镜像才能创建容器, 可以先下载一个: `docker pull nginx`
* 格式:`docker run [参数] [镜像名称 / 镜像 ID] [执行的命令(默认执行指定的命令)]`
* 参数:
| `-d` | 以守护进程的方式运行 |
| ---------- | ---------------------------------------------------------- |
| **`-p`** | **指定端口映射(格式:宿主主机端口: 容器向外暴露的端口)** |
| **`-P`** | **随机端口映射** |
| **`--name`** | **指定容器的名称(同一台宿主主机上的 docker 名称不能重复)** |
| **`--rm`** | **当一个容器结束了它的生命周期,就立即删除** |
| **`-v`** | **映射存储卷(可以映射文件及文件夹)** |
| **`-i`** | **打开标准输出(通常与 t 连用)** |
| **`-t`** | **创建一个伪终端** |
| **`-e`** | **在容器内设置一个环境变量** |
* 示例
```plain
"-d","-p"
docker run -d -p 8899:80 nginx:1.19.2
"-P"
docker run -d -P nginx:1.19.2
"--name"
docker run -d --name nginx_name -P nginx:1.19.2
"--rm"
docker run -d --rm --name nginx_rm nginx:1.19.2
"-v"
docker run -d -v /root/test:/usr/share/nginx/html nginx:1.19.2
"-e"
docker run -d -e NGINX_NAME=nginx nginx:1.19.2
```
* 本地查看

* 浏览器查看

ps : docker 当中至少有一个应用程序运行在前台
# 十三. 容器相关命令
## 1. 列出当前所有正在运行的容器
* `docker ps`
* 参数:
| `-a` | 列出当前所有正在运行的容器 + 历史上运行过的 |
| ------ | ------------------------------------------- |
| **`-l`** | **显示最近创建的容器** |
| **`-n`** | **显示最近 n 个创建的容器** |
| **`-q`** | **静默模式,只显示容器 ID** |
## 2. 退出容器
* `exit`:容器停止退出
* `Ctrl+P+Q`:容器不停止退出
## 3. 启动容器
* `docker start [容器 ID / 容器名]`
## 4. 重启容器
* `docker restart [容器 ID / 容器名]`
## 5. 停止容器
* `docker stop [容器 ID / 容器名]`
* `docker stop -f $(docker ps -qa)` (所有)
## 6. 强制停止容器
* `docker kill [容器 ID / 容器名]`
## 7. 刪除已停止的容器
* `docker rm [容器 ID]`
* `docker rm -f $(docker ps -qa)`
# 十四. 导入、导出成镜像文件
## 1. 针对容器进行导出导入
* `export`:将容器保存为镜像
* `import`:将 `export` 导出的包导入为镜像 -(可自定义镜像名字)
* 格式:`docker export [容器名或 ID] > [压缩包名称]`

* 格式:`docker import [压缩包名称] [自定义镜像名称]:[版本号]`

ps : 通过 `docker run -d -P nginx_song:v22 nginx -g 'daemon off;'` 运行导入的 (由容器导出的镜像)
## 2. 针对镜像进行导出导入
* `save`:保存镜像, (当使用镜像 ID 保存镜像,导入时没有镜像名称)
* `load`:导入镜像, 不能自定义名称, `save` 保存的更完整
* 格式:`docker save [镜像名或 ID] > [压缩包名称]`
* 注意:如果使用 ID 导出, 导入时没有镜像名和标签, 只有 ``

* 打包多个格式:`docker save -o [压缩包名称] [镜像名称或 ID ...]`
!\[img]\(https://cdn.nlark.com/yuque/0/2024/png/35775747/1717145464641-019ab
---
---
url: /笔记/01.运维/03.Docker/05.Docker网络.md
---
## 一. 网络基础
Docker 使用到的与 Linux 网络有关的技术分别有:网络名称空间、Veth、Iptables、网桥、路由
### 1.什么是网络名称空间
* 为了支持网络协议栈的多个实例,Linux 在网络协议栈中引入了网络名称空间(Network Namespace)
* 这些独立的协议栈被隔离到不同的命名空间中
* 处于不同的命名空间的网络协议栈是完全隔离的,彼此之间无法进行网络通信,就好像两个“平行宇宙”
* 通过这种对网络资源的隔离,就能在一个宿主机上虚拟多个不同的网络环境

ps : 如果不同网络名称空间之间要进行通信该怎么办呢?
### 2.Veth 设备对
* 引入 **Veth 设备对\*\*\*\* **就是为了在不同的网络名称空间之间进行通信
* 利用它可以直接将两个网络名称空间链接起来, Veth 设备是成对出现
* 像一对网卡, 我们将其中一端称为另一端的 peer

### 2.1. 底层原理实验示例
* 需求 : 建立两个名称空间, 并实现相互 **ping** 通
* 查看, 添加, 删除 **namespace** 命令
```plain
ip netns list # 查看
ip netns add [namespace] # 添加
ip netns delete [namespace] # 删除
```
* 建立两个名称空间
```plain
[root@shawn ~]#ip netns add test1
[root@shawn ~]#ip netns add test2
[root@shawn ~]#ip netns list
test2
test1
```
* 建立一对 **veth**
```plain
[root@shawn ~]#ip link add veth1 type veth peer name veth2
[root@shawn ~]#ip link
```

* 创建 **veth** 后, 我们将 **veth** 分别添加到名称空间 **test1** 及 **test2** 中
```plain
[root@shawn ~]#ip link set veth1 netns test1
[root@shawn ~]#ip link set veth2 netns test2
```
* 查看一下是否已经添加成功了
```plain
[root@shawn ~]#ip netns exec test1 ip link
[root@shawn ~]#ip netns exec test2 ip link
```


* 添加完成后我们就需要为其设置 **IP** 了, 并将其状态 **up**
```plain
[root@shawn ~]#ip netns exec test1 ip addr add 172.17.0.111/20 dev veth1
[root@shawn ~]#ip netns exec test2 ip addr add 172.17.0.112/20 dev veth2
[root@shawn ~]#ip netns exec test1 ip link set dev veth1 up
[root@shawn ~]#ip netns exec test2 ip link set dev veth2 up
```
* 查看 **IP** 是否设置成功
```plain
[root@shawn ~]#ip netns exec test1 ip a
[root@shawn ~]#ip netns exec test2 ip a
```


* 最后, 检测一下是否能相互 **ping** 通
```plain
[root@shawn ~]#ip netns exec test1 ping 172.17.0.112
[root@shawn ~]#ip netns exec test2 ping 172.17.0.111
```


* **成功**✅
### 3. 网桥(Bridge)
* Linux 可以支持多个不同的网络,它们之间能够相互通信,就需要一个网桥, 网桥就是二层的虚拟网络设备
* 它是把若干个网络接口“连接”起来,从而报文能够互相转发

* 网桥命令格式 : docker network \[命令参数]
```plain
⚽查看当前系统有哪些网桥 "ls"
[root@shawn ~]#docker network ls
'''
NETWORK ID NAME DRIVER SCOPE
befd59194a71 bridge bridge local
94f8e35f3357 host host local
79fb28a9a12e none null local
'''
⚽创建网桥 "create"
[root@shawn ~]#docker network create shawn
ffac93578a0ce40395936d226bd097fd049ad077022419a9b6b074b6fe2f892b
[root@shawn ~]#docker network ls
'''
NETWORK ID NAME DRIVER SCOPE
befd59194a71 bridge bridge local
94f8e35f3357 host host local
79fb28a9a12e none null local
ffac93578a0c shawn bridge local #新建的网桥
'''
⚽查看网桥信息, 格式 : "docker network inspect [网桥的名称 | 网桥 ID]"
[root@shawn ~]#docker network inspect shawn
'''
[
{
"Name": "shawn",
"Id": "ffac93578a0ce40395936d226bd097fd049ad077022419a9b6b074b6fe2f892b",
"Created": "2020-12-03T11:56:35.554136022+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {"Network": ""},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}}
]
'''
⚽删除网桥 "rm"
[root@shawn ~]#docker network rm shawn
shawn
[root@shawn ~]#docker network ls
'''
NETWORK ID NAME DRIVER SCOPE
befd59194a71 bridge bridge local
94f8e35f3357 host host local
79fb28a9a12e none null local
''' # "Shawn" 被删除了
⚽清理网桥 "prune", 我们先创建多个网桥, 然后一次性清除
[root@shawn ~]#docker network create shawn1
f4d2f2b57b48cd35b3cc9eddad0377cae95d213032ed0d6a92e9de9571adeb4e #创建成功
[root@shawn ~]#docker network create shawn2
ea66ed0d06adbe7f5211f3ae38b6edeb47dffac0f53240e9204fd3dcaf4670d5 #创建成功
[root@shawn ~]#docker network create shawn3
222d6e298d6091cea0a6229e19c84825a41ea92b7c0b512db47c858dde7259f0 #创建成功
[root@shawn ~]#docker network prune
WARNING! This will remove all custom networks not used by at least one container.
Are you sure you want to continue? [y/N] y # 问你是否要这样做 "yes"
Deleted Networks: # 将要删除以下网桥
shawn1
shawn2
shawn3
[root@shawn ~]#docker network ls
'''
NETWORK ID NAME DRIVER SCOPE
befd59194a71 bridge bridge local
94f8e35f3357 host host local
79fb28a9a12e none null local
''' # 发现并没有 "shawn" 系列的网桥
```
### 4.Iptables
* iptables 是 linux 系统自带的优秀且完全免费的基于包过滤的防火墙工具、它的功能十分强大、使用非常灵活、可以对流入、流出及流经服务器的数据包进行精细的控制
### 5. 总结
| network namespace | 主要提供了关于网络资源的隔离,包括网络设备、IPv4 和 IPv6 协议栈、IP 路由 表、防火墙、/proc/net 目录、/sys/class/net 目录、端口(socket)等 |
| :---: | :---: |
| **linux Bridge** | **功能相当于物理交换机,为连在其上的设备(容器)转发数据帧。如 docker0 网桥** |
| **iptables** | **主要为容器提供 NAT 以及容器网络安全** |
| **veth pair** | **两个虚拟网卡组成的数据通道。在 Docker 中,用于连接 Docker 容器和 Linux Bridge。一端在容器中作为 eth0 网卡,另一端在 Linux Bridge 中作为网桥的一 个端口** |
## 二.Docker 网络模式
安装 Docker 时,它会自动创建三个网络,bridge(创建容器默认连接到此网络)、none、host
* docker network ls : 查看当前系统有哪些网络(网桥)
```plain
[root@shawn ~]#docker network ls
NETWORK ID NAME DRIVER SCOPE
befd59194a71 bridge bridge local
94f8e35f3357 host host local
79fb28a9a12e none null local
```
### 1. 原理
* Docker 使用 Linux 桥接,在宿主机虚拟一个 Docker 容器网桥**(docker0)**
* Docker 启动一个容器时会根据 Docker 网桥的网段分配给容器一个 IP 地址,称为 Container-IP, 同时 Docker 网桥是每个容器的默认网关
* 因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的 Container-IP 直接通信
* Docker 网桥是宿主机 **虚拟\*\*\*\* **出来的,并不是真实存在的网络设备,外部网络是 **无法寻址\*\*\*\* **到的,这也意味着外部网络无法通过直接 Container-IP 访问到容器
* 如果容器希望外部访问能够访问到,可以通过映射容器端口到宿主主机(端口映射),即 docker run 创建容器时候通过 **-p** 或 **-P** 参数来启用,访问容器的时候就通过 **\[宿主机 IP]:\[容器向外暴露的端口]** 访问容器
### 2. 四类网络模式
| 网络模式 | 设置方法 | 简介 |
| :---: | :---: | :---: |
| Host | --network host | 容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口 |
| Bridge | --network bridge(默认此模式) | 此模式会为每一个容器分配、设置 IP 等,并将容器连接到一个 docker0 虚拟网桥,通过 docker0 网桥以及 Iptables nat 表配置与宿主机通信 |
| None | -- network none | 该模式关闭了容器的网络功能(只提供回环) |
| Container | --network "container:\[共享容器名称]" | 创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围 |
### 2.1、Host 模式
* 相当于 Vmware 中的桥接模式,与宿主机在同一个网络中,但没有独立 IP 地址, 容易造成端口冲突
* 但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的
* 格式 : docker run --network host \[镜像名称或 ID]
```plain
[root@shawn ~]#docker run -d --network host nginx:latest
```

### 2.2、Container 模式
* 这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享
* 新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等
* 同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信
* 格式 : docker run --network "containe:\[共享容器名称]" \[镜像名称或 ID]
* 实验:
```plain
⚽创建共享容器 "cont01"
[root@shawn ~]#docker run -dit --name cont01 busybox:latest sh
⚽创建链接容器 "cont02", 并指定网络模式 "container"
[root@shawn ~]#docker run -dit --name cont02 --network "container:cont01" busybox:latest sh
⚽查看两个容器的 "ip", 发现一样
[root@shawn ~]#docker exec cont01 ip a
[root@shawn ~]#docker exec cont02 ip a
```



### 2.3、None 模式
* 该模式将容器放置在它自己的网络栈中,但是并不进行任何配置, 只提供回环
* 该模式关闭了容器的网络功能 , 一般是因为容器并不需要网络(例如只需要写磁盘卷的批处理任务)
* 格式 : docker run --network none \[镜像名称或 ID]
```plain
[root@shawn ~]#docker run -dit --network none --name none_test busybox:latest sh
```

### 2.4、Bridge 网桥模式
* 相当于 Vmware 中的 Nat 模式,容器使用独立 network Namespace,并连接到 docker0 虚拟网卡(默认模式)
* 虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中
* bridge 模式是 docker 的 **默认网络模式\*\*\*\* **,不写 **--network** 参数,就是**bridge** 模式
* 使用 **docker run -p** 时,docker 实际是在 iptables 做了 DNAT 规则,实现 **端口转发功能**

* 相关操作
相关操作在文章第一段第三小章 **""网桥(Bridge)""**
---
---
url: /笔记/01.运维/02.Linux/01.linux常用命令.md
---
# Linux 文件和目录操作命令手册
## 📂 目录操作命令
### `cd` - 切换目录
| 命令 | 说明 |
|------|------|
| `cd /path` | 进入指定路径 |
| `cd ..` | 返回上一级目录 |
| `cd ../..` | 返回上两级目录 |
| `cd` 或 `cd ~` | 返回用户主目录 |
| `cd -` | 返回上次所在目录 |
### `ls` - 列出目录内容
| 选项 | 说明 |
|------|------|
| `ls` | 简单列出当前目录内容 |
| `ls -l` | 详细列表显示 |
| `ls -a` | 显示所有文件(包括隐藏文件) |
| `ls -R` | 递归列出子目录内容 |
| `ls [0-9]` | 显示含数字的文件名 |
### 其他目录命令
| 命令 | 说明 |
|------|------|
| `pwd` | 显示当前工作路径 |
| `mkdir -p path` | 创建多级目录 |
| `rmdir -p path` | 删除空目录 |
## 📄 文件操作命令
### 文件管理
| 命令 | 选项 | 说明 |
|------|------|------|
| `cp` | `-a` | 保留所有属性复制 |
| | `-r` | 递归复制目录 |
| | `-i` | 覆盖前提示 |
| `mv` | `-i` | 移动/重命名文件 |
| `rm` | `-rf` | 强制递归删除 |
### 文件查看
| 命令 | 示例 | 说明 |
|------|------|------|
| `cat` | `cat file` | 查看完整文件 |
| `head` | `head -n 5 file` | 查看前5行 |
| `tail` | `tail -n 5 file` | 查看后5行 |
| `less` | `less file` | 分页查看文件 |
## 🔍 文件查找
### `find` 命令
```bash
find /path -name "*.txt" # 按名称查找
find / -type f -size +1M # 查找大于1MB的文件
find ~ -mtime -7 # 查找7天内修改的文件
```
### 其他查找工具
| 命令 | 说明 |
|------|------|
| `whereis` | 查找二进制/手册文件 |
| `which` | 查找可执行文件路径 |
| `locate` | 快速文件查找(需updatedb) |
## 🔒 权限管理
### `chmod` 权限设置
```bash
chmod 755 file # 所有者rwx,组和其他rx
chmod u+x script.sh # 给所有者添加执行权限
```
### 所有权命令
| 命令 | 说明 |
|------|------|
| `chown user:group file` | 修改所有者和组 |
| `chgrp group file` | 修改文件组 |
## 📦 压缩解压
### `tar` 常用操作
```bash
# 压缩
tar -czvf archive.tar.gz /path
# 解压
tar -xzvf archive.tar.gz -C /target
# 查看
tar -tzvf archive.tar.gz
```
### 其他压缩工具
| 格式 | 压缩 | 解压 |
|------|------|------|
| .zip | `zip -r file.zip dir` | `unzip file.zip` |
| .gz | `gzip file` | `gunzip file.gz` |
| .bz2 | `bzip2 file` | `bunzip2 file.bz2` |
| .rar | `rar a file.rar dir` | `rar x file.rar` |
## 🖥️ 进程管理
### 进程查看
| 命令 | 选项 | 说明 |
|------|------|------|
| `ps` | `aux` | 查看所有进程 |
| `top` | | 动态进程监控 |
| `htop` | | 增强版top |
### 进程控制
```bash
kill -9 PID # 强制终止进程
killall -i nginx # 按名称终止进程
nice -n 10 command # 设置优先级
```
## 🎨 使用技巧
### 常用组合
```bash
# 查找并删除旧文件
find /tmp -type f -mtime +30 -exec rm {} \;
# 统计文件行数
find . -name "*.js" | xargs wc -l
# 实时查看日志
tail -f /var/log/syslog
```
### 快捷键
| 快捷键 | 功能 |
|------|------|
| Ctrl+C | 终止当前命令 |
| Ctrl+Z | 暂停进程 |
| Ctrl+D | 退出终端 |
| !! | 重复上条命令 |
| !$ | 上条命令的最后一个参数 |
---
---
url: /笔记/04.数据库/02.Redis.md
---
---
---
url: /笔记/map.md
---
---
---
url: /娱乐/01.书籍.md
---
## 看书籍
---
---
url: /关于/01.关于我.md
---
## 信息
* 一名程序员。
* 希望我永远对这个世界充满好奇,保持活力,不断学习,不断进步。
* 热爱生活,热爱学习,热爱编程,热爱开源我想在互联网上留下些足迹。
### 技术栈

### 笔记平台


### 宝剑锋从磨砺出,梅花香自苦寒来

### 其他平台
| 分类 | 链接 |
|------------|----------------------------------------------------------------------|
| 技术社区 | [CSDN](https://blog.csdn.net/kjl536566?spm=1000.2115.3001.5343) |
| 技术社区 | [稀土掘金](https://juejin.cn/ "稀土掘金") |
| 知识库 | [语雀](https://www.yuque.com "知识库") |
| 工具库 | [doc.yan](https://doc.yanmingke.top "第一个网站") |
| 开源托管 | [GitHub](https://github.com/Misyan-03 "GitHub") |
| Figma 设计 | [Figma](https://www.figma.com/ "Figma") |
### 邮箱
* [微软邮箱](mailto:some8090@outlook.com "OUTLOOK邮箱")
* [谷歌邮箱](mailto:yanmk32@gmail.com "谷歌邮箱")
---
---
url: /关于/02.关于本站.md
---

## 技术实现
本站使用的是`VitePress` SSG框架
* 主题:`vitepress-theme-teek`
* 评论插件:[Twikoo 网站评论系统](https://twikoo.js.org/)
* 评论数据存储:[MongoDB Atlas 云数据库](https://www.mongodb.com/cloud/atlas)
* 图床
[PicGo](https://picgo.github.io/PicGo-Doc/) + [Cloudflare](https://www.cloudflare-cn.com/enterprise/)
* 图标库:[阿里图标](https://www.iconfont.cn/)
* 网页音乐播放器:[明月皓空网](https://myhkw.cn/)
* 域名:[阿里云](https://www.aliyun.com/)
* 网站统计:[Vercel Analytics](https://vercel.com/analytics)
* 网站部署:[Vercel](https://vercel.com/)
## 为什么要创建个人博客
1. 拥有一个可以在浏览器输入网址,就可以打开的网站,真的超炫酷😜。
2. 本站作为自己的个人知识库/博客,主要记录下自己的技术文档。
3. 记录一些生活感悟等,也算是通过文字的方式来记录生活,热爱生活。
## 域名&网名&网站说明
个人域名是:`yanmingke.top`
个人网名是:`向画`
::: tip
> 网站路径为什么是?
>
> 这是我的姓名的拼音字母(●'◡'●)。
>
> 网站内容问题?
>
> 本站分享计算机前沿技术,博主与生活对线,闲暇时会进行网站维护更新,希望你能喜欢本网站,有问题请联系我,详情见公告。
:::
---
---
url: /@pages/categoriesPage.md
---
---
---
url: /索引/categoriesPage.md
---
---
---
url: /精神小屋/01.动态.md
---
## 🎯 理想时刻 - 2025年5月20日
> 一切皆有可能
## ✨ 第一条动态
---
---
url: /关于/03.友链.md
---
## 我的博客
::: shareCard 3
```yaml
- name: Yan # 昵称
desc: 一步步走 # 介绍
avatar: https://free.picui.cn/free/2025/04/04/67ef52b143bbb.png # 头像
link: http://localhost:5173/youlian # 链接
bgColor: "#FFE5B4" #--金黄色
textColor: "#A05F2C"
```
:::
## vdoing网站
::: shareCard 3
:::
## vitepress网站
## 其它风格
::: shareCard 3
```yaml
- name: POETIZE
desc: 这是一个 Vue2 Vue3 与 SpringBoot 结合的产物~(最美博客原作者)
avatar: https://yanminfke.top/images/POETIZE-logo.jpg
link: https://poetize.cn/
bgColor: '#FCDBA0'
textColor: '#621529'
- name: 宇阳
desc: 记录所学知识,缩短和大神的差距!
avatar: https://yanminfke.top/images/image-20250220073534772.png
link: https://liuyuyang.net
bgColor: '#CBEAFA' # 可选,默认var(–bodyBg)。颜色值有#号时请添加单引号
textColor: '#6854A1' # 可选,默认var(–textColor)
- name: Travele_
desc: 让我给你讲讲他的传奇故事吧(最美博客风格)
avatar: https://yanminfke.top/images/Travele11720161341654235.gif
link: https://blog.icc.cool/
bgColor: '#FFB6C1'
textColor: '#621529'
```
:::
---
---
url: /笔记/03.后端/01.图谱.md
---

* Java】Java基础知识体系
* 【版本特性】Java各个历史版本知识体系
* 【JVM】JVM核心知识点知识体系
* 【集合】Java集合框架知识体系
* 【IO】Java I/O框架知识体系
* 【并发】Java并发知识体系
* 【数据结构】数据结构基础知识体系
* 【算法】算法知识体系
* 【spring】Spring框架和核心技术深入浅出详解
* 【springboot】SpringBoot开发和常见中间件集成案例
* 【安全】开发安全相关知识体系
* 【代码质量】代码质量保障知识体系
* 【开发库】常用开发库知识体系
* 【网络】网络协议和常用工具知识体系
* 【正则】正则表达式知识体系
---
---
url: /工具/01.工具.md
---

---
---
url: /@pages/archivesPage.md
---
---
---
url: /索引/archivesPage.md
---
---
---
url: /娱乐/02..电影.md
---

> 每次看都有不同的感受,每次看都能发现新的细节,每次看都能引起新的思考。
## 科幻
::: imgCard
```yaml
- img: https://pub-9b9c7441fc9b4abfae66a9589eecd68f.r2.dev/technics/R-C.jpg
link: https://movie.douban.com/subject/1889243/
name: 星际穿越
desc: 爱是我们唯一能感知的、超越时空维度的东西;
```
:::
###
## 哲理
::: imgCard
```yaml
- img: https://pub-9b9c7441fc9b4abfae66a9589eecd68f.r2.dev/technics/t0113896f115ea3e432.jpg
link: https://movie.douban.com/subject/1292052/
name: 肖申克的救赎
desc: 希望是美好的,也许是人间至善,而美好的事物永不消逝;
- img: https://pub-9b9c7441fc9b4abfae66a9589eecd68f.r2.dev/technics/agan.jpg
link: https://movie.douban.com/subject/1292720/
name: 阿甘正传
desc: 生活就像一盒巧克力,你永远不知道下一块是什么味道;你必须放下过去,才能继续前进;
- img: https://pub-9b9c7441fc9b4abfae66a9589eecd68f.r2.dev/technics/chumen.jpg
link: https://movie.douban.com/subject/1292064/
name: 楚门的世界
desc: 祝你早安,午安,晚安;
- img: https://pub-9b9c7441fc9b4abfae66a9589eecd68f.r2.dev/technics/Snipaste_2025-05-04_00-15-53.png
link: https://movie.douban.com/subject/1292064/
name: 了不起的盖茨比
desc: 我们努力向前,逆水行舟,被浪潮不断地向后推,推回往昔从前;
```
:::
###
## 动漫
::: imgCard
```yaml
- img: https://pub-9b9c7441fc9b4abfae66a9589eecd68f.r2.dev/technics/fanrrenC.jpg
link: https://movie.douban.com/subject/34925294/
name: 凡人修仙传
desc: 人生如棋,落子无悔;
- img: https://pub-9b9c7441fc9b4abfae66a9589eecd68f.r2.dev/technics/240220030331723.jpg
link: https://movie.douban.com/subject/36779574/
name: 仙逆
desc: 聒噪;
```
:::
###
---
---
url: /关于/07.思考.md
---
运气很重要,但要有接住运气的实力
---
---
url: /笔记/04.数据库/01.数据库.md
---
mysql、oracle、sqlserver
先掌握基础语法(SELECT/INSERT/UPDATE/DELETE)
再学习高级特性(事务、索引、视图、存储过程)
最后深入原理(锁机制、执行计划、性能调优)
---
---
url: /笔记/00.技术/01.方向.md
---
* 数据库管理
* 前端开发
* 后端开发
* 嵌入式开发
---
---
url: /关于/08.更新日志.md
---
::: timeline 2025-04-0
:::
::: timeline 2025-04-04
* 新增 [网站评论系统](https://twikoo.js.org/),评论数据存到MongoDB Atlas,部署到vercel;
* 新增 [网站统计系统vercel](https://vercel.com/yans/vitepress-theme-teek/speed-insights);
* 网站运行时间使用js代码计算;
:::
::: timeline 2025-04-03
* 新增 时间线 功能;
* 新增 五彩纸屑 功能;
:::
::: timeline 2025-04-2
* `Blog`首次提交
:::
---
---
url: /@pages/tagsPage.md
---
---
---
url: /索引/tagsPage.md
---
---
---
url: /娱乐/03..游戏.md
---
## 玩游戏
* 生死狙击
联通六区老兵
* 英雄联盟

---
---
url: /关于/04.留言区.md
---
::: center
如果遇到了什么问题,欢迎大家留言讨论。
如果 10s 后评论区还在转圈,可以尝试刷新页面。
:::
---
---
url: /笔记/01.运维/03.Docker/01.Docker简介.md
---
## 一. 为什么要有 Docker
1. 一个软件从开发到部署的问题 : **运行环境和配置**
2. 软件是否可以带环境安装 : 安装时将 **开发的环境** 一模一样的 **复制过来**
```plain
一款产品从开发到上线,从操作系统,到运行环境,再到应用配置
作为开发 + 运维之间的协作我们需要关心很多东西,这也是很多互联网公司都不得不面对的问题
特别是各种版本的迭代之后,不同版本环境的兼容,对运维人员都是考验
"Docker" 之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案
```
## 二. 什么是 Docker
* 基于 Go 语言实现的云开源项目
* docker 是一个开源的软件部署解决方案
```plain
🎅示例 1
你在金鱼店的鱼缸里买一条小金鱼回去, 养在家里的盆子里, 那么小金鱼有可能水土不服 XX, 如果你把连鱼带缸的买回去就不会出现这种情况了, 因为生存环境没有改变
🎅夸张示例 2
如果我们搬家, 一般是把家具之类的一件一件的搬到新房子里去, 然后到新房子里有重新布置,"Docker" 的理念就是直接把房子铲起来放置到新的住址, 那么里面的配置都不用动了, 直接可以入住
```
* 解决了 **运行环境** 和**配置问题** 软件容器, 方便 **持续集成** 并有助于整体发布的容器虚拟化技术
ps : **docker 理念: 一次构建处处运行**
## 三.Docker 能做什么
### 1. 运行环境的配置
* 只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作
### 2. 虚拟机技术
* 在一个操作系统之上运行 / 安装另一种操作系统
* 应用程序, 操作系统和硬件三者之间的关系不变
```plain
🎅缺点
1. 资源占用多
2. 冗余步骤多
3. 启动慢(分钟级)
```
### 3. 容器虚拟化技术
* 不是模拟一个完整的操作系统, 他是将软件运行所需要的所有资源打包到一个 **隔离的容器**
* **只需要** 软件工作 **所需要的** 库资源和设置
* 因此系统变得 **高效轻量** , 且能保证 **任何环境** 中软件都能始终 **如一的运行**

### 4. 虚拟机技术与容器虚拟化技术
* 不同之处
```plain
🎅传统虚拟技术:
🔰虚拟出一堆硬件, 运行一个完整的操作系统
🔰然后在该系统上再运行一系列的应用程序
🎅容器虚拟化技术:
🔰容器内的应用进程直接运行在宿主的内核, 容器内没有机子的内核
🔰并且没有进行硬件的虚拟, 因此更轻便 (只包含业务所需要的环境:"docker" 基础镜像就 "170M")
🔰每个容器之间相互隔离, 每个容器都有自己的文件系统, 容器之间不会相互影响, 能区分计算资源
```
### 5. 底层原理
* Docker 是怎么工作的
```plain
Docker 是一个 Client-Server 结构的系统
Docker 守护进程运行在主机上,然后通过 Socket 连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器
容器,就是一个运行时的环境
```
* 为什么 Docker 比 VM 快
```plain
🔰docker 有着比虚拟机更少的抽象层
由于 docker 不需要 Hypervisor 实现硬件资源虚拟化, 运行在 docker 容器上的程序直接使用的都是实际物理机的硬件资源
因此在 CPU、内存利用率上 docker 将会在效率上有明显优势
🔰docker 利用的是宿主机的内核, 而不像虚拟机在宿主机上再运行一个内核
因此当新建一个容器时,docker 不需要和虚拟机一样重新加载这个操作系统内核
进而避免寻找、加载操作系统内核比较费时费资源的过程
🔰当新建个虚拟机时,虚拟机软件需要加载 Centos, 这个新建过程是 " 分钟级别的 "
docker 由于直接利用宿主机的操作系统,则省略了这个个过程因此新建一个 docker 容器只需要 " 几秒钟 "
```
### 6. 开发和运维问题解决
**Docker 理念** : 一次构建处处运行
* 更快的应用交付和部署
```plain
🎅传统的应用开发完成后,需要提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行
🎅Docker 化之后只需要交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间
```
* 更便捷的升级和扩缩容
```plain
🎅随着微服务架构和 Docker 的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个 "Docker 容 " 器将变成一块 " 积木 "," 应用的升级将变得非常容易 "
🎅当现有的容器不足以支撑业务处理时,可通过镜像运行 " 新的容器进行快速扩容 ",使应用系统的扩容从原先的天级变成分钟级甚至秒级
```
* 更简单的系统运维
```plain
🎅应用容器化运行后,生产环境运行的应用可与开发、测试环境的应用 " 高度一致 ",容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的 BUG
🎅当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复
```
* 更高效的计算资源利用
```plain
🎅Docker 是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的 Hypervisor 支持, 所以在一台物理机上 " 可以运行很多个容器实例 ",可大大 " 提升物理服务器的 CPU 和内存的利用率 "
```
## 四. 容器的基本组成
### 1. 结构图

### 3. 镜像 : image
* 一个只读模板, 一个镜像可以创建出来很多个容器
* 相当于 Python 当中类与对象的概念, 一个类可以实例化出来许多的 obj
### 4. 容器 : container
* 独立运行的一个或一组应用, 是由镜像实例化出来的
* 可以把它看成是一个迷你版的 Linux 环境, 基础镜像 170M (我们装的一般 4 个多 G)
* 它可以被启动、开始、停止、删除。每个容器都是相互隔离的
### 5. 仓库 : repository
* 集中存放镜像文件的场所
* 仓库有两种区分
```plain
🎅仓库注册服务器:
🔰存放着多个仓库
🎅仓库:
🔰 每个仓库包含多个镜像, 每个镜像有不同的标签('tag': 类似于版本号)
🔰仓库分为公开仓库(Public)和私有仓库(Private)两种形式
🔰最大的公开仓库是 "Docker Hub:(https://hub.docker.com/)"
```
### 6. 总结
* Docker 本身是一个容器运行载体或称之为 **管理引擎**
* image 文件生成的容器实例,本身也是一个文件,称为 **镜像文件**
* 同一个 image 文件,可以 **生成多个** 同时运行的容器实例
* **一个容器运行一种服务**,当我们需要的时候,就可以通过 docker 客户端创建一个对应的运行实例,也就是我们的容器
* 至于仓库,就是 **放了一堆镜像的地方**,我们可以把镜像发布到仓库中,需要的时候从仓库中拉下来就可以了
---
---
url: /nav.md
---
::: info 教程
如果你也想搭建此导航 [点我查看教程](/pages/e01e54)
:::
---
---
url: /关于/05.网站导航.md
---
::: navCard 3
```yaml
- name: 百度
desc: 百度——全球最大的中文搜索引擎及最大的中文网站,全球领先的人工智能公司
link: http://www.baidu.com/
img: https://www.baidu.com/favicon.ico
badge: 搜索引擎
- name: Google
desc: 全球最大的搜索引擎公司
link: http://www.google.com/
img: https://www.google.com/favicon.ico
badge: 搜索引擎
- name: Bing
desc: 微软推出的搜索引擎
link: http://www.bing.com/
img: https://www.bing.com/favicon.ico
badge: 搜索引擎
badgeType: tip
```
:::
## Docusaurus
::: navCard 3
```yaml
- name: Docusaurus官网
desc: 快速构建以内容为核心的最佳网站
img: https://docusaurus.io/img/docusaurus.svg
link: https://docusaurus.io/
- name: 愧怍
desc: 编程爱好者,编写所感兴趣的项目,现主攻js/ts全栈,希望能为开源社区做出一份贡献
img: https://kuizuo.cn/img/logo.webp
link: https://kuizuo.cn/
- name: Shake
desc: 世界继续热闹,愿你不变模样,勇敢且自由😃
img: https://www.shaking.site/img/head.jpg
link: https://www.shaking.site/
- name: 太空编程
desc: Docusaurus 大佬
img: https://spacexcode.com/img/favicon.ico
link: https://spacexcode.com/
```
:::
## 编程导航
::: navCard 3
```yaml
- name: 爱达网址聚合页
desc: 爱达杂货铺,收集互联网上用的东西
img: https://linkqiu.com/images/icons/logo.svg
link: https://linkqiu.com/
- name: 发现导航
desc: 精选实用导航网站
img: https://gcore.jsdelivr.net/gh/xjh22222228/nav-image@image/logo.svg
link: https://nav3.cn/
- name: 编程导航
desc: 程序员必备的导航网站
img: https://www.code-nav.cn/favicon.ico
link: https://www.code-nav.cn/
```
:::
## 站点
::: navCard 3
```yaml
- name: Developer Roadmap
desc: Roadmap to becoming a web developer.
img: https://roadmap.sh/manifest/apple-touch-icon.png
link: https://roadmap.sh/
- name: Shields.io
desc: 为你的开源项目生成高质量小徽章图标
img: https://shields.io/img/logo.png
link: https://shields.io/
- name: NGINX 配置
desc: 配置高性能、安全、稳定的 NGINX 服务器的最简单方法
img: https://www.digitalocean.com/_next/static/media/apple-touch-icon.d7edaa01.png
link: https://www.digitalocean.com/community/tools/nginx
- name: BootCDN
desc: 稳定、快速、免费的前端开源项目 CDN 加速服务
img: https://www.bootcdn.cn/assets/ico/favicon.ico
link: https://www.bootcdn.cn/
- name: 那些免费的砖
desc: 发现免费可商用的资源
img: https://img.thosefree.com/static/logo.png
link: https://www.thosefree.com/
```
:::
## 文档
::: navCard 3
```yaml
- name: MDN
desc: 从 2005 年开始记录网络技术,包括 CSS、 HTML 和 JavaScript。
img: https://developer.mozilla.org/favicon-48x48.bc390275e955dacb2e65.png
link: https://developer.mozilla.org/zh-CN/
- name: ES6 入门教程
desc: 《ECMAScript 6 入门教程》是一本开源的 JavaScript 语言教程,全面介绍 ECMAScript 6 新引入的语法特性
img: https://es6.ruanyifeng.com/favicon.ico
link: https://es6.ruanyifeng.com/
- name: 深入理解 TypeScript
desc: 《TypeScript Deep Dive》 是一本很好的开源书,从基础到深入,很全面的阐述了 TypeScript 的各种魔法,不管你是新手,还是老鸟,它都将适应你
img: https://jkchao.github.io//typescript-book-chinese/logo.png
link: https://jkchao.github.io/typescript-book-chinese/
- name: Rust 语言圣经
desc: 一份高质量 Rust 教程
img: https://course.rs/favicon.png
link: https://course.rs
- name: 在线工具
desc: 在线工具,开发人员工具,代码格式化、压缩、加密、解密,下载链接转换,ico 图标制作,字帖生成
img: https://tool.lu/favicon.ico
link: https://tool.lu/
```
:::
## 工具
::: navCard 3
```yaml
- name: 菜鸟工具
desc: 菜鸟工具,为开发设计人员提供在线工具,提供在线 PHP、Python、 CSS、JS 调试,中文简繁体转换,进制转换等工具
img: https://static.jyshare.com/images/c-runoob-logo.ico
link: https://c.runoob.com/
- name: ProcessOn
desc: 免费在线流程图思维导图
img: https://processon.com/favicon.ico
link: https://processon.com/
- name: Terminal Gif Maker
desc: 在线生成 Terminal GIF
img: https://www.terminalgif.com/favicon.ico
link: https://www.terminalgif.com
- name: AST Explorer
desc: 一个 Web 工具,用于探索由各种解析器生成的 AST 语法树
img: https://astexplorer.net/favicon.png
link: https://astexplorer.net/
- name: transform
desc: 各类数据格式与对象转换
img: https://transform.tools/static/favicon.png
link: https://transform.tools
- name: Hoppscotch
desc: 开源 API 开发生态系统
img: https://hoppscotch.io/favicon.ico
link: https://hoppscotch.io/
- name: JsonT.run
desc: 一个简洁的在线 JSON 解析器
img: https://www.jsont.run/favicon.ico
link: https://www.jsont.run/
- name: Apifox
desc: API 文档、API 调试、API Mock、API 自动化测试
img: https://cdn.apifox.cn/logo/apifox-logo-512.png
link: https://www.apifox.cn/
- name: 迅捷 PDF 转换器
desc: 在线电子书格式转换 PDF
img: https://app.xunjiepdf.com/favicon.ico
link: https://app.xunjiepdf.com/eb2pdf/?zhljj01/
- name: DeepL 翻译
desc: 号称全世界最准的翻译
img: https://static.deepl.com/img/logo/DeepL_Logo_darkBlue_v2.svg
link: https://www.deepl.com/translator/
- name: GeoGebra
desc: 数学教学软件,一个计算器套件
img: https://www.geogebra.org/favicon.ico
link: https://www.geogebra.org/
- name: removebg
desc: 在线抠图软件,可以去除图片背景
img: https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://www.remove.bg/zh&size=50
link: https://www.remove.bg/zh/
- name: Unscreen
desc: 移除任何视频的背景 — — 100% 自动,在线且免费
img: https://www.unscreen.com/favicon.ico
link: https://www.unscreen.com/
- name: 音乐解锁
desc: 移除已购音乐的加密保护
img: https://demo.unlock-music.dev/favicon.ico
link: https://demo.unlock-music.dev/
- name: freeconvert 文件转换器
desc: 轻松在线将文件从一种格式转换为另一种格式。
img: https://www.freeconvert.com/favicon.ico
link: https://www.freeconvert.com/zh
- name: convertio 文件转换器
desc: 将您的文件转换成任意格式。
img: https://static.convertio.co/favicon.ico
link: https://convertio.co/zh/
- name: EditIamge
desc: 压缩到指定 kb/MB。
img: http://editimage.cn/assets/qn-0Z4EW21D.png
link: http://editimage.cn/
- name: PathFinding.js
desc: 一个的路径搜索的动态在线演示
img: https://img1.github.io/tmp/1.jpg
link: http://qiao.github.io/PathFinding.js/visual/
- name: CMake 中文文档
desc: 热心网友根据官方教程翻译的文档
img: https://img1.github.io/tmp/4.jpg
link: https://sfumecjf.github.io/cmake-examples-Chinese/
- name: iconify
desc: 数千个图标,一个统一的框架
img: https://icon-sets.iconify.design/favicon.ico
link: https://icon-sets.iconify.design/
- name: icones
desc: Icon Explorer with Instant searching, powered by Iconify
img: https://icones.js.org/favicon.svg
link: https://icones.js.org/
- name: iconfont
desc: iconfont - 国内功能很强大且图标内容很丰富的矢量图标库,提供矢量图标下载、在线存储、格式转换等功能
img: https://www.iconfont.cn/favicon.ico
link: https://www.iconfont.cn/
- name: feathericons
desc: 简单美丽的开源图标
img: https://feathericons.com/favicon.ico
link: https://feathericons.com/
- name: undraw
desc: 一个不断更新的设计项目与美丽的 SVG 图像,使用完全免费
img: https://undraw.co/apple-touch-icon.png
link: https://undraw.co/
- name: igoutu
desc: 图标、插图、照片、音乐和设计工具
img: https://igoutu.cn/favicon.ico
link: https://igoutu.cn/
- name: Emojiall
desc: Emoji 表情大全
img: https://www.emojiall.com/apple-touch-icon.png
link: https://www.emojiall.com/zh-hans
- name: 渐变色网站
desc: 数百万个自动生成的渐变的网站
img: https://gradihunt.com/icon-192.png
link: https://gradihunt.com/
- name: 谷歌字体
desc: 一个生成渐变色背景的网站
img: https://googlefonts.cn/favicon.ico
link: https://googlefonts.cn/
- name: Typing SVG
desc: 一个动态生成的可自定义 SVG 打字效果
img: https://readme-typing-svg.herokuapp.com/favicon.ico
link: https://readme-typing-svg.herokuapp.com/demo/
- name: Hitokoto - 一言
desc: 随机分享一句话
img: https://hitokoto.cn/favicon.ico
link: https://hitokoto.cn/
- name: 一句
desc: 汇聚有料文字
img: http://yijuzhan.com/favicon.ico
link: http://yijuzhan.com/
- name: 今日诗句
desc: 每次返回一句诗词
img: https://www.jinrishici.com/img/icon-small.png
link: https://www.jinrishici.com/
- name: Tabler Icons
desc: 免费和开源的图标,设计时注重细节,使您的设计脱颖而出
img: https://tablericons.com/favicon.ico
link: https://tablericons.com/
- name: Open Doodles
desc: 一个免费供个人和商业使用的人物素描图库
img: https://assets.website-files.com/5d5d5904f8a21bfe5ff69367/5da4e9a054896ed6d830e869_icon-32w.png
link: https://www.opendoodles.com/
- name: DrawKit
desc: 免费的矢量 SVG 插图,供您在下一个项目中使用,不需要署名
img: https://cdn.prod.website-files.com/6364b6fd26e298b0b8b938f5/6364b6fd26e2989baeb93925_drawkit-nav-logo.svg
link: https://www.drawkit.io/
- name: GSAP
desc: 一个高性能的 JavaScript 动画库
img: https://gsap.com/favicon.ico
link: https://gsap.com/
- name: Bootstrap
desc: Bootstrap 是全球最受欢迎的前端开源工具库,它支持 Sass 变量和 mixin、响应式栅格系统、自带大量组件和众多强大的 JavaScript 插件。基于 Bootstrap 提供的强大功能,能够让你快速设计并定制你的网站
img: https://getbootstrap.com/docs/5.3/assets/img/favicons/favicon.ico
link: https://v5.bootcss.com/
- name: w3schools Css 教程
desc: w3schools 从基础到高级的 CSS 教程
img: https://www.w3schools.com/favicon.ico
link: https://www.w3schools.com/css
- name: CSS - Inspiration
desc: CSS 灵感
img: https://csscoco.com/inspiration/logo.png
link: https://csscoco.com/inspiration
- name: CSS 常用样式
desc: CSS 常用样式
img: https://tse1-mm.cn.bing.net/th?id=OIP-C.EgSPriuEnAtlIWJV8R_E1QHaGs&w=107&h=100&c=8&rs=1&qlt=90&o=6&pid=3.1&rm=2
link: https://github.com/QiShaoXuan/css_tricks
- name: CSSFX
desc: 一个精心制作的集合设计的重点是流动性,简单性和易用性。使用最小标记的 CSS 支持
img: https://cssfx.netlify.app/favicon.ico
link: https://cssfx.netlify.app/
- name: TailwindCSS
desc: Tailwind CSS 是一个功能类优先的 CSS 框架,它集成了诸如 flex, pt - 4, text - center 和 rotate - 90 这样的的类,它们能直接在脚本标记语言中组合起来,构建出任何设计
img: https://www.tailwindcss.cn/favicons/apple-touch-icon.png
link: https://www.tailwindcss.cn
- name: WindiCSS
desc: Windi CSS 是下一代工具优先的 CSS 框架
img: https://windicss.org/assets/logo.svg
link: https://windicss.org
- name: Twind
desc: 现存最小、最快、功能最齐全的完整 Tailwind - in - JS 解决方案
img: https://twind.dev/favicon.ico
link: https://github.com/tw-in-js/twind
- name: UnoCSS
desc: 即时按需原子 CSS 引擎
img: https://unocss.dev/favicon.svg
link: https://uno.antfu.me/
- name: NES.css
desc: 一个像素风格的 CSS 框架
img: https://nostalgic-css.github.io/NES.css/favicon.png
link: https://nostalgic-css.github.io/NES.css/
- name: clay.css
desc: claymorphism 泥陶态风格 CSS
img: https://codeadrian.github.io/clay.css/apple-touch-icon.png
link: https://codeadrian.github.io/clay.css/
- name: loading.io
desc: Animation Made Easy
img: https://loading.io/favicon.ico
link: https://loading.io/
- name: 神奇 UI 样式
desc: 我们赋予任何人创建、分享和使用用 CSS 和 HTML 制作的漂亮自定义元素的权力。
img: https://uiverse.io/favicon.ico
link: https://uiverse.io
- name: CodePen
desc: 一款前端所见即所得的工具,可以在线编辑制作前端页面。同时网站提供所有常见的 js、css 库,并且支持流行技术(如 SASS)
img: https://cpwebassets.codepen.io/assets/favicon/favicon-touch-de50acbf5d634ec6791894eba4ba9cf490f709b3d742597c6fc4b734e6492a5a.png
link: https://codepen.io/
- name: HYPE4
desc: 透明玻璃态生成器
img: https://hype4.academy/favicon/apple-icon-57x57.png
link: https://hype4.academy/tools/glassmorphism-generator
- name: wweb.dev
desc: CSS 动画背景生成器
img: https://ik.imagekit.io/wwebdev/tr:w-180/logo-text_51kQarOOiD-.png
link: https://wweb.dev/resources/animated-css-background-generator/
- name: Favicon.io
desc: 可以生成带有调整的网站图标 SVG 代码、生成基于文本的网站图标或生成基于表情符号的网站图标
img: https://favicon.io/favicon.ico
link: https://favicon.io/
- name: Omatsuri
desc: 收集不同的发电机,让您的生活更轻松。
img: https://omatsuri.app/assets/favicon.ico
link: https://omatsuri.app
- name: smooth shadow
desc: 平滑阴影生成器
img: https://shadows.brumm.af/favicon.ico
link: https://shadows.brumm.af/
- name: CSS Grid Generator
desc: CSS Grid 布局生成器
img: https://cssgrid-generator.netlify.app/favicon.ico
link: https://cssgrid-generator.netlify.app/
- name: Can I use
desc: 浏览器兼容性查询
img: https://caniuse.com/img/favicon-128.png
link: https://caniuse.com/
```
:::
---
---
url: /笔记/01.运维/01.图谱/01.图谱.md
---
