发布于 2026-01-05 2 阅读
0

面向 JavaScript 开发者的 Golang - 第二部分:差异之处 结论 参考文献:

面向 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'
Enter fullscreen mode Exit fullscreen mode


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"

Enter fullscreen mode Exit fullscreen mode

相似之处

  • var除了关键字名称之外,两者之间并没有太多相似之处const。Go中的关键字在行为方面var更接近JS 中的关键字。let
  • var可以像 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();
Enter fullscreen mode Exit fullscreen mode


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

Enter fullscreen mode Exit fullscreen mode
  • 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",
};
Enter fullscreen mode Exit fullscreen mode


var dict = map[string]interface{}{
    "key1": 10,
    "key2": "hello",
}

var stringMap = map[string]string{
    "key1": "hello",
    "key2": "world",
}
Enter fullscreen mode Exit fullscreen mode

可变性

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'
Enter fullscreen mode Exit fullscreen mode


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'

Enter fullscreen mode Exit fullscreen mode

错误处理

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
Enter fullscreen mode Exit fullscreen mode


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(":)")
}
Enter fullscreen mode Exit fullscreen mode

组合而非继承

在 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'
Enter fullscreen mode Exit fullscreen mode


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'
}
Enter fullscreen mode Exit fullscreen mode

并发性

并发是 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();
  });
}
Enter fullscreen mode Exit fullscreen mode

另一方面,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)
}
Enter fullscreen mode Exit fullscreen mode

汇编

JavaScript是解释执行的,而不是编译执行的。虽然有些 JS 引擎使用 JIT 编译,但对开发者来说这并不重要,因为我们不需要编译 JavaScript 就能运行它。使用 TypeScript 或 Babel 进行转译也不算数 😉

Go是编译型语言,因此提供编译时类型安全,并在一定程度上提供内存安全。

范例

JavaScript主要是一种面向对象的编程语言,但由于其语言的灵活性,你可以轻松编写命令式或函数式风格的代码。这门语言相当自由,几乎没有任何强制要求。它没有预设的规范,也没有提供任何现成的工具。开发者需要自行配置工具。

Go语言主要是一种命令式语言,虽然也可以进行一些面向对象和函数式编程,但不如 JavaScript 那么容易。这门语言相当严格,并且有很强的规范性,例如代码风格和格式。它还提供了用于测试、格式化、构建等的内置功能。

结论

有人在上一篇文章的评论区问我,为什么JS开发者应该在众多语言中选择Go。我认为,JS并非完美无缺,因此学习一些其他语言对JS开发者来说大有裨益,不仅能让他们更务实地运用JS,还能帮助他们更好地巩固编程基础概念。当然,还有很多其他选择,比如Rust、Go、Haskel、Kotlin等等,但我认为Go是一个很好的起点,因为它是所有语言中最简单的之一,而且应用广泛。我的第二选择是Kotlin或Rust

参考:


如果您喜欢这篇文章,请点赞或留言。

你可以在TwitterLinkedIn上关注我。


封面图片由norfolkjs (由Lookmai Rattana设计)和juststickers 的图片制作而成。

文章来源:https://dev.to/deepu105/golang-for-javascript-developers-part-2-p3p