面向 JavaScript 开发者的 Golang 教程 - 第二部分
更不同的事物
结论
参考:
原文发表于deepu.tech。
如果你是一名 JavaScript 开发者,并且正在考虑学习另一种编程语言,那么 Golang 是一个绝佳的选择。它简单易学,发展势头强劲,性能卓越,并且与 JavaScript 有一些相似之处。
本文并非对这两种语言进行比较,也并非声称它们非常相似。它是一份面向 JavaScript 开发者的 Go 语言快速入门指南。Go 语言的许多方面与 JavaScript 完全不同,我们也会对此进行探讨。
在本系列的前一部分中,我们学习了 JavaScript 和 Go 语言之间的一些相似之处。我们主要谈到了:
- 函数
- 范围
- 流量控制
- 内存管理
在本系列文章的这一部分,我们将探讨JS和Go之间更显著的区别。如果您还没有阅读上一部分,请先阅读上一部分。
更不同的事物
如您所见,这一部分的内容比上一部分要多,但请注意,有些差异非常细微,因此 JavaScript 开发人员很容易理解。
类型和变量
这是主要区别之一。JavaScript 是动态的、弱类型的,而 Go 是静态的、强类型的。
JavaScript
var foo = {
message: "hello",
};
var bar = foo;
// mutate
bar.message = "world";
console.log(foo.message === bar.message); // prints 'true'
// reassign
bar = {
message: "mars",
};
console.log(foo.message === bar.message); // prints 'false'
去
var foo = struct {
message string
}{"hello"}
var bar = foo // will create a copy of foo and assign to bar
// mutates only bar
// note bar.message is short for (*bar).message
bar.message = "world"
fmt.Println(foo.message == bar.message) // prints "false"
// reassign bar
bar = struct {
message string
}{"mars"}
fmt.Println(foo.message == bar.message) // prints "false"
var barPointer = &foo // assigns pointer to foo
// mutates foo
barPointer.message = "world"
fmt.Println(foo.message == barPointer.message) // prints "true"
// reassigns foo
*barPointer = struct {
message string
}{"mars"}
fmt.Println(foo.message == bar.message) // prints "true"
相似之处
var除了关键字名称之外,两者之间并没有太多相似之处const。Go中的关键字在行为方面var更接近JS 中的关键字。letvar可以像 JavaScript 那样同时声明多个实例var a, foo, bar int;。但在 Go 中,你还可以更进一步,像这样初始化它们var a, foo, bar = true, 10, "hello"。在 JavaScript 中,你可以使用解构赋值来实现类似的效果。var [a, foo, bar] = [true, 10, "hello"]
差异
- Go 需要在编译时获取类型信息,可以通过指定类型或类型推断来实现。
- Go语言有值类型(基本类型、数组和结构体)、引用类型(切片、映射和通道)以及指针。JS语言有值类型(基本类型)和引用类型(对象、数组和函数)。
- 在 Go 语言中,变量的类型在声明之后不能更改。
- 在 Go 语言中,变量赋值不能使用短路表达式。
var:=Go 函数内部有简写语法。- Go 语言严格不允许存在未使用的变量,任何未使用的变量都必须命名为 `\n`
_,这是一个保留字符。 - JavaScript 目前没有
private/public访问修饰符(目前已有添加此功能的提案),但在 Go 语言中,你可以通过命名约定来修改访问属性。字段或变量名以大写字母开头会将其设为公共变量,以小写字母开头则会设为私有变量。 constGo 语言中的常量赋值方式与 JavaScript 不同。在 Go 语言中,只有字符、字符串、布尔值或数值等基本类型才能赋值给常量。- Go 语言中的数组与 JavaScript 中的数组不同,它们的数组长度是固定的。JavaScript 数组是动态的,因此更类似于 Go 语言中的切片,切片是对数组进行动态长度的切割。
JavaScript
const foo = ["Rick", "Morty"];
// Adds to the end of the array.
foo.push("Beth");
// Removes from the end of the array.
element = foo.pop();
去
foo := []string{"Rick", "Morty"} // creates a slice
// Adds to the end of the array.
foo = append(foo, "Beth")
// Removes from the end of the array.
n := len(foo) - 1 // index of last element
element := foo[n] // optionally also grab the last elemen
foo = foo[:n] // remove the last element
- JavaScript 有 Object、Map/Set 和 WeakMap/WeakSet,它们可以像字典和集合一样使用。Go 只有一个简单的 Map,它更类似于 JavaScript 的 Object,因此可以满足需求。另外需要注意的是,Go 中的 Map 是无序的。
JavaScript
const dict = {
key1: 10,
key2: "hello",
};
const stringMap = {
key1: "hello",
key2: "world",
};
去
var dict = map[string]interface{}{
"key1": 10,
"key2": "hello",
}
var stringMap = map[string]string{
"key1": "hello",
"key2": "world",
}
可变性
JavaScript 和 Go 的另一个主要区别在于变量变更的处理方式。在 JavaScript 中,所有非原始类型变量都是按引用传递的,而且无法改变这种行为;而在 Go 中,除了 slice、map 和 channels 之外的所有变量都是按值传递的,我们可以通过显式地传递指向变量的指针来改变这种行为。
正因如此,Go 语言对可变性的控制力比 JS 更强。
另一个显著的区别是,在 Javascript 中,我们可以使用const关键字阻止变量的重新赋值,而 Go 中则不可能做到这一点。
我们在上一节中已经看到了可变性的一些实际应用,让我们再深入探讨一下。
JavaScript
let foo = {
msg: "hello",
};
function mutate(arg) {
arg.msg = "world";
}
mutate(foo);
console.log(foo.msg); // prints 'world'
去
type Foo struct {
msg string
}
var foo = Foo{"hello"}
var tryMutate = func(arg Foo) {
arg.msg = "world"
}
tryMutate(foo)
fmt.Println(foo.msg) // prints 'hello'
var mutate = func(arg *Foo) {
arg.msg = "world"
}
mutate(&foo)
fmt.Println(foo.msg) // prints 'world'
错误处理
Go 和 JS 在错误处理方面唯一的相似之处在于,它们都将错误视为值类型。在这两种语言中,你都可以将错误作为值传递。
除了上述错误处理方式之外,两者在 JavaScript 中也存在很大差异。
在 JavaScript 中,我们可以:
- 使用一种
try/catch机制来捕获同步函数和异步函数中的错误。async/await - 通过将错误传递给回调函数或使用 Promise 来处理异步函数中的错误。
在 Go 语言中,没有直接的try/catch错误处理机制,处理错误的唯一方法是从函数中返回错误值,或者使用函数暂停执行,或者在代码块中panic使用函数来挽救执行。这使得 Go 语言的错误处理非常冗长,你经常会看到著名的`if` 语句。recoverdeferif err != nil
JavaScript
function errorCausingFunction() {
throw Error("Oops");
}
try {
errorCausingFunction();
} catch (err) {
console.error(`Error: ${err}`);
} finally {
console.log(`Done`);
}
// prints
// Error: Error: Oops
// Done
// or the async way
function asyncFn() {
try {
errorCausingFunction();
return Promise.resolve();
} catch (err) {
return Promise.reject(err);
}
}
asyncFn()
.then((res) => console.log(`:)`))
.catch((err) => console.error(`Error: ${err}`))
.finally((res) => console.log(`Done`));
// prints
// Error: Error: Oops
// Done
去
var errorCausingFunction = func() error {
return fmt.Errorf("Oops")
}
err := errorCausingFunction()
defer fmt.Println("Done") // Closest to finally, but executes only at end of the enclosing function
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
} else {
fmt.Println(":)")
}
// prints
// Error: Oops
// Done
// or
err := errorCausingFunction()
defer func() { // Closest thing to finally behaviour, but executes only at end of the enclosing function
if err := recover(); err != nil {
fmt.Println("Recovered from err", err) // closest thing to catch behaviour
}
fmt.Println("Done")
}()
if err != nil {
panic(err)
} else {
fmt.Println(":)")
}
组合而非继承
在 JavaScript 中,我们可以使用继承来扩展或共享行为,而 Go 则选择组合。JavaScript 中也存在原型级别的继承,并且由于其语言的灵活性,也支持组合。
JavaScript
class Animal {
species;
constructor(species) {
this.species = species;
}
species() {
return this.species;
}
}
class Person extends Animal {
name;
constructor(name) {
super("human");
this.name = name;
}
name() {
return this.name;
}
}
var tom = new Person("Tom");
console.log(`${tom.name} is a ${tom.species}`); // prints 'Tom is a human'
去
type IAnimal interface {
Species() string
}
type IPerson interface {
IAnimal // composition of IAnimal interface
Name() string
}
type Animal struct {
species string
}
type Person struct {
Animal // composition of Animal struct
name string
}
func (p *Person) Name() string {
return p.name
}
func (p *Animal) Species() string {
return p.species
}
func NewPerson(name string) IPerson {
return &Person{Animal{"human"}, name}
}
func main() {
var tom IPerson = NewPerson("Tom")
fmt.Printf("%s is a %s\n", tom.Name(), tom.Species()) // prints 'Tom is a human'
}
并发性
并发是 Golang 最重要的特性之一,也是它真正大放异彩的地方。
从技术上讲, JavaScript是单线程的,因此它本身并不真正支持并发。虽然 Service Worker 的加入带来了一些并行支持,但仍然无法与 JavaScript 的强大功能和简洁性相媲美goroutines。并发与异步或响应式编程不同,而 JavaScript 对异步和响应式编程的支持非常出色。
// Sequential
async function fetchSequential() {
const a = await fetch("http://google.com/");
console.log(a.status);
await a.text();
const b = await fetch("http://twitter.com/");
console.log(b.status);
await b.text();
}
// Concurrent but not multi threaded
async function fetchConcurrent() {
const values = await Promise.all([fetch("http://google.com/"), fetch("http://twitter.com/")]);
values.forEach(async (resp) => {
console.log(resp.status);
await resp.text();
});
}
另一方面,Go 语言goroutines完全面向并发和并行。这些概念通过通道(channel)等机制内置于语言中。Go 也支持异步编程,但其代码比 JavaScript 的异步编程更为冗长。这意味着你可以编写同步 API,然后使用 goroutine 以异步方式调用它。然而,Go 社区通常不提倡编写异步 API。
// Sequential
func fetchSequential() {
respA, _ := http.Get("http://google.com/")
defer respA.Body.Close()
fmt.Println(respA.Status)
respB, _ := http.Get("http://twitter.com/")
defer respB.Body.Close()
fmt.Println(respB.Status)
}
// Concurrent and multithreaded
func fetchConcurrent() {
resChanA := make(chan *http.Response, 0)
go func(c chan *http.Response) {
res, _ := http.Get("http://google.com/")
c <- res
}(resChanA)
respA := <-resChanA
defer respA.Body.Close()
fmt.Println(respA.Status)
resChanB := make(chan *http.Response, 0)
go func(c chan *http.Response) {
res, _ := http.Get("http://twitter.com/")
c <- res
}(resChanB)
respB := <-resChanB
defer respB.Body.Close()
fmt.Println(respB.Status)
}
汇编
JavaScript是解释执行的,而不是编译执行的。虽然有些 JS 引擎使用 JIT 编译,但对开发者来说这并不重要,因为我们不需要编译 JavaScript 就能运行它。使用 TypeScript 或 Babel 进行转译也不算数 😉
Go是编译型语言,因此提供编译时类型安全,并在一定程度上提供内存安全。
范例
JavaScript主要是一种面向对象的编程语言,但由于其语言的灵活性,你可以轻松编写命令式或函数式风格的代码。这门语言相当自由,几乎没有任何强制要求。它没有预设的规范,也没有提供任何现成的工具。开发者需要自行配置工具。
Go语言主要是一种命令式语言,虽然也可以进行一些面向对象和函数式编程,但不如 JavaScript 那么容易。这门语言相当严格,并且有很强的规范性,例如代码风格和格式。它还提供了用于测试、格式化、构建等的内置功能。
结论
有人在上一篇文章的评论区问我,为什么JS开发者应该在众多语言中选择Go。我认为,JS并非完美无缺,因此学习一些其他语言对JS开发者来说大有裨益,不仅能让他们更务实地运用JS,还能帮助他们更好地巩固编程基础概念。当然,还有很多其他选择,比如Rust、Go、Haskel、Kotlin等等,但我认为Go是一个很好的起点,因为它是所有语言中最简单的之一,而且应用广泛。我的第二选择是Kotlin或Rust。
参考:
- http://www.pazams.com/Go-for-Javascript-Developers/
- https://github.com/miguelmota/golang-for-nodejs-developers
如果您喜欢这篇文章,请点赞或留言。
封面图片由norfolkjs (由Lookmai Rattana设计)和juststickers 的图片制作而成。
文章来源:https://dev.to/deepu105/golang-for-javascript-developers-part-2-p3p