Go
go
方法,函数,接口
闭包是一个非常有用的特性,它允许你在一个函数中定义另一个函数,这个内部函数会访问并操作外部函数中定义的变量。闭包的一个关键特性是即使外部函数已经返回,内部函数(闭包)依然可以访问和修改这些外部变量。
context
context
在 Go 中用于管理和传递请求的元数据、控制请求的生命周期(如取消和超时处理)
http server
1 | package main |
通过这种设计,Go 的 HTTP 服务器能够有效地处理并发连接和请求,每个连接都在自己的 goroutine 中处理,从而利用 Go 的并发模型。
并发
Goroutines 是 Go 中实现并发的基础。一个 Goroutine 可以看作是一个轻量级的线程,由 Go 运行时管理。Goroutines 比系统线程更加轻量,启动速度更快,占用的内存更少,切换开销也小得多。
1 | go f(x, y, z) |
这行代码会启动一个新的 Goroutine 去执行 f(x, y, z)
函数。这意味着 f
会异步执行,调用者可以继续执行下一行代码而不用等待 f
完成。
Channels 是用来在 Goroutines 之间进行通信的管道。你可以把它们想象成在不同的 Goroutines 之间传递消息的方式。Channels 是类型安全的,一个 int 类型的 channel 只能传递 int 类型的数据。
创建 Channels:
1
ch := make(chan int)
使用 Channels:
1
2ch <- v // 发送 v 到 channel ch。
v := <-ch // 从 ch 接收数据,并赋值给 v。
如果发送或接收操作未准备好,则操作会阻塞,直到另一端准备好为止。
带缓冲的 Channels:
1
ch := make(chan int, 100) // 创建一个容量为 100 的缓冲 channel。
带缓冲的 channel 在缓冲满之前可以持续发送,而不会阻塞。当缓冲空时,接收操作会阻塞。
Range and Close
你可以关闭一个 channel 来通知接收方不会再发送更多的数据。关闭后,接收操作仍然可以从 channel 接收数据,直到 channel 中的数据被接收完。
1 | close(ch) |
Select
Select 语句让你可以等待多个 channel 操作,并继续执行一旦其中一个操作准备好。
1 | select { |
如果所有 channel 操作都没有准备好,select
会阻塞。如果有 default
分支,则会执行 default
。
Mutex
互斥锁(Mutex)用于确保多个 Goroutine 在访问共享资源时不会发生冲突。sync.Mutex
提供了基本的锁定功能,帮助保护数据结构免受并发访问问题。
1 | type SafeCounter struct { |
在这个例子中,每次修改 v
的时候,我们都会使用 mu.Lock()
和 mu.Unlock()
来确保只有一个 Goroutine 可以访问 v
。
异步
JavaScript 的执行环境确实是单线程的,这意味着在任何给定时刻,只能执行一个任务。然而,JavaScript 通过事件循环和回调机制支持异步操作,允许非阻塞的行为,例如处理 I/O、定时事件、执行网络请求等。这里是一些关键的概念和机制,解释 JavaScript 如何在单线程中实现异步操作:
1. 事件循环(Event Loop)
JavaScript 的事件循环是其异步行为的核心。事件循环使得 JavaScript 代码,即使是在单线程中,也能进行异步执行。事件循环工作原理如下:
- JavaScript 运行时环境中有一个消息队列,其中包含了所有待处理的事件和执行回调的计划。
- 当 JavaScript 引擎执行完同步代码后(比如你的脚本的初始加载),它将查看队列中是否有任何待处理的任务。
- 如果队列中有待执行的异步任务,事件循环将一个接一个地取出来执行这些任务的回调
2.Web API 和回调队列
尽管 JavaScript 核心是单线程的,浏览器提供了多种 Web API,如 setTimeout
, XMLHttpRequest
, 和 DOM 事件等,这些都可以处理异步操作。这些操作在背后实际上可能由浏览器的多线程环境处理:
- 当这些 API 完成异步操作时(例如,一个时间延迟、一个HTTP请求返回结果、或一个用户交互事件发生),相应的回调函数就会被放入任务队列中。
- 一旦调用栈清空,事件循环将开始从队列中提取任务并执行它们。
3. Promise 和 Async/Await
JavaScript 的 Promise
是一个代表最终完成或失败的异步操作的对象。它允许你对异步操作的成功或失败进行链式调用处理。
Promise
提供.then()
和.catch()
方法来处理成功或失败的结果。async/await
是建立在 Promises 之上的语法糖,使得异步代码的写法更加接近同步代码的风格。Promise
对象是通过new Promise()
构造函数创建的,它接受一个称为执行器(executor)函数的参数。这个执行器函数自动启动执行,并接受两个函数作为参数:resolve
和reject
。resolve(value)
:当异步操作成功时调用,并将异步操作的结果作为参数传递。reject(error)
:当异步操作失败时调用,并将错误作为参数传递。
1
2
3
4
5
6
7
8
9
10const myPromise = new Promise((resolve, reject) => {
const condition = true; // 假设这是异步操作的结果
if (condition) {
resolve('Promise is resolved successfully.');
//resolve解析成果, JavaScript 的 Promise 中,一旦状态被解析(resolve)或拒绝(reject),它就被锁定,后续的任何解析或拒绝调用都将被忽略。
} else {
reject('Promise is rejected.');
}
});使用 Promise
创建了
Promise
之后,你可以使用.then()
,.catch()
, 和.finally()
方法来处理成功或失败的结果。.then()
.then()
方法接受两个函数作为参数(第二个参数是可选的):- 第一个函数是当
Promise
成功解决时执行的。 - 第二个函数是当
Promise
被拒绝时执行的。
1
2
3
4myPromise.then(
(value//resolve解析结果) => { console.log(value); }, // 成功处理函数
(error) => { console.log(error); } // 失败处理函数
);.catch()
.catch()
方法用于指定当Promise
被拒绝时执行的函数,是.then(null, rejection)
的语法糖。1
2
3myPromise.catch(
(error) => { console.log(error); }
);.finally()
.finally()
方法用于指定不管Promise
最后的状态如何,都会执行的操作。这对于清理资源或执行一些完成操作很有用,无论操作成功还是失败。1
2
3myPromise.finally(() => {
console.log('Completed'); // 总是会执行
});链式调用
Promise
允许链式调用,这意味着你可以在一个Promise
解决后立即开始另一个异步操作,并根据前一个操作的结果继续处理。1
2
3
4
5
6
7
8
9
10
11
12
13
14myPromise
.then((value) => {
console.log(value);
return 'Doing another async operation';
})
.then((secondValue) => {
console.log(secondValue);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
console.log('Chain completed');
});!异步/等待(
async
/await
)async
/await
是基于Promise
的一个语法糖,可以使异步代码看起来更像同步代码。1
2
3
4
5
6
7
8
9
10
11
12async function asyncFunction() {
try {
const result = await myPromise;
console.log(result);
} catch (error) {
console.log(error);
} finally {
console.log('Async function completed');
}
}
asyncFunction();async
函数隐式返回一个Promise
,这使得异步函数可以使用await
关键字来暂停执行,直到Promise
解决或被拒绝。这种方式使得异步代码的编写更直观和易于管理。
配置文件
不使用配置文件(硬编码)
在没有使用配置文件的情况下,所有配置(如数据库连接信息、端口号等)都会直接写在代码中。这种方式简单直接,但不易于维护,且存在安全风险。
1 | package main |
在这个示例中,HTTP 服务器的端口号 8080
被直接硬编码在代码中。如果你需要更改端口或者这个端口在部署环境中已被占用,你必须修改代码并重新部署应用。
示例:使用配置文件
使用配置文件可以将配置从代码中分离出来,增加应用的灵活性并降低维护难度。
1 | package main |
配置文件 config.yaml
可能看起来像这样:
1 | server: |
router
后端对接前端,httpserver对接fetchAPI
json,在 Go 语言中,处理 JSON 数据主要依赖于 encoding/json
标准库。这个库通过反射(reflection)来匹配结构体的字段与 JSON 对象中的键。当涉及到使用 tags 和处理嵌套结构时,处理方式稍微复杂,但原则是相同的。
1 | URL:{host}/comment/add |
1 | type Comment struct { |
1 | document.getElementById('commentForm').addEventListener('submit', function(event) { |
1 | page, err := strconv.Atoi(r.URL.Query().Get("page")) |
1 | fetch('/comment/get?page='+currentPage+'&size=10') |
1 | type Response struct { |
js对接html
1 | <form id="commentForm"> |