前端面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

前端阅读 02月7日 11:04

Golang 如何创建自定义类型?

在Go语言中,创建自定义类型通常通过使用type关键字来定义。这里有几种方式来创建自定义类型:基于已有的类型定义新类型:你可以基于一个已有的类型定义一个新的类型。这样做可以增加代码的可读性和可维护性。 type MyInt int结构体(Structures):结构体是一种聚合数据类型,它是字段的集合,每个字段都有自己的类型和名称。 type Person struct { Name string Age int }接口(Interfaces):接口是一种类型,它规定了变量有哪些方法。它是一种抽象类型,可以用来定义不同类型的共有的行为。 type Reader interface { Read(p []byte) (n int, err error) }类型别名:类型别名是Go 1.9引入的功能,主要用于代码重构,它提供了一种方式来给类型一个新的名字。 type Bytes = []byte利用这些方式,你可以根据具体需求创建自定义类型,以增强代码的结构和清晰度。
前端阅读 02月7日 11:03

Golang 中的包和模块有什么区别?

在Go语言中,包(Package)和模块(Module)是两个不同的概念,它们在Go项目结构中扮演着不同的角色:包(Package):包是Go语言的基本组织单元,每个Go文件都属于一个包,同一个目录下的所有Go文件属于同一个包。包用于组织相似的代码,可以通过包来封装数据和函数等实现模块化。包通过import语句被其他包引用,使用包内定义的公共接口(如函数、类型、变量等)。模块(Module):模块是Go 1.11及之后版本中引入的,用于支持版本控制和依赖管理。一个模块是一个或多个包的集合,它在文件go.mod中定义,该文件列出了模块的名字和其依赖的其他模块的版本。模块使得Go项目可以更好地管理外部依赖,确保项目依赖的一致性和可复现性。模块还允许开发者将自己的项目发布为可被其他项目依赖的库。总的来说,包是代码的物理组织形式,而模块则是项目级别的逻辑和版本控制概念。使用模块可以有效管理大型项目的依赖问题。
前端阅读 02月7日 11:02

Golang 如何嵌入结构体?

在Go语言中,嵌入结构体是通过将一个结构体作为另一个结构体的字段但不指定字段名来实现的。这种嵌入的结构体字段会使其内部的字段和方法对外层结构体直接可见。这是一种实现组合的方式,Go语言通过这种方式可以达到类似继承的效果。例如,如果你有一个Base结构体,你可以在另一个Derived结构体中嵌入Base结构体:type Base struct { Name string}type Derived struct { Base // 嵌入Base结构体 Age int}在这种情况下,Derived结构体会自动继承Base结构体的所有字段和方法。因此,你可以直接通过Derived的实例访问Name字段,就像下面这样:d := Derived{ Base: Base{Name: "John"}, Age: 30,}fmt.Println(d.Name) // 输出 "John"这样,Derived结构体实例d可以直接访问Name,尽管这个字段是在Base结构体中定义的。这种方式简化了结构体之间的关系,并可以实现代码的重用和扩展。
前端阅读 02月7日 11:00

Golang 中数组和切片有什么区别?

在Go语言中,数组和切片是两种不同的数据结构,主要有以下几点区别:大小固定性:数组:其长度在声明时就必须指定,而且一旦定义,数组的大小就不能改变。切片:是基于数组的一种更灵活的数据结构,其长度是动态的,可以根据需要增长或缩减。声明方式:数组:声明时需要指定元素的数量,例如 var a [5]int 表示一个包含5个整数的数组。切片:不需要在声明时指定数量,例如 var s []int 是一个整数切片,初始时是空的。内存分配:数组:作为值类型,数组在内存中的分配是连续的,且其大小在编译时就确定了。切片:虽然基于数组,但它包括一个指向数组的指针、切片的长度和容量。这使得切片可以根据需要动态地扩展或缩减其容量。性能影响:数组:因为是值类型,所以在作为参数传递给函数时,会进行整个数组的复制,可能会影响性能,尤其是对于大数组。切片:作为引用类型,传递时只会复制切片的描述符(指针、长度、容量),而不是底层数组的数据,所以性能更优。用途:数组:适用于存储固定数量的同类型元素。切片:更加灵活,适用于不确定数量的情况,是Go中最常用的数据结构之一,尤其是在需要动态增减元素的场景。总结来说,数组是一种基本但固定长度的数据结构,而切片提供了更多灵活性和高性能的操作,适用于更广泛的场景。
前端阅读 02月7日 11:00

Golang 如何创建和使用函数闭包?

在Go语言中,闭包是一种特殊类型的匿名函数,它可以捕获其定义作用域中的变量。这意味着函数可以访问并操作其外部函数中定义的变量,即使外部函数已经执行完毕。创建和使用闭包的基本步骤如下:1. 定义闭包闭包通常在一个函数内部定义,并返回。这个内部函数会访问并操作外部函数的变量。package mainimport "fmt"func outerFunction() func() int { var x int = 0 return func() int { x += 1 return x }}在这个例子中,outerFunction 返回了一个匿名函数,这个匿名函数每次被调用时会增加变量 x 的值并返回。x 是定义在 outerFunction 内部的,所以这个匿名函数就形成了一个闭包,因为它“捕获”了 x 的当前状态和后续状态。2. 使用闭包一旦定义了闭包,你可以像使用普通函数那样使用闭包,但它会记住和操作其闭包变量的状态。package mainimport "fmt"func main() { increment := outerFunction() // 创建闭包 fmt.Println(increment()) // 输出: 1 fmt.Println(increment()) // 输出: 2 fmt.Println(increment()) // 输出: 3}每次调用 increment 时,它都会增加在 outerFunction 中定义的 x 的值。尽管每次调用 outerFunction 都会创建新的 x,这里 increment 使用的是同一个 x。总结闭包允许你将状态与功能绑定在一起,非常适合创建私有变量和构造函数工厂等场景。在Go中,由于闭包的性质,它们常被用于创建生成器、迭代器等结构。
前端阅读 02月7日 10:59

GraphQL 中如何使用变量?

在GraphQL中,变量用于在查询或者突变(Mutation)中动态地传递参数。这样做的好处是可以重用相同的查询或突变定义,但是使用不同的数据值。变量使得查询结构更加清晰,并且有助于防止注入攻击。如何使用变量定义变量: 在查询或突变中,首先要在操作类型后声明变量及其类型。例如,如果你想通过ID获取用户信息,你可以这样写: query GetUser($id: ID!) { user(id: $id) { name email } }这里,$id 是变量,ID! 表示它是一个非空的ID类型。传递变量: 当发送查询时,你需要在请求的variables部分提供具体的变量值。例如,在上面的查询中,你可以传递如下JSON对象: { "id": "123" }这个JSON对象说明变量$id的具体值是"123"。通过使用变量,GraphQL查询可以更加灵活和安全地处理不同的数据需求。
前端阅读 02月7日 00:15

Jenkins中的代理指令是什么?

在Jenkins中,agent 指令用于指定 Jenkins 应该在哪个环境中执行整个流水线或特定阶段。这可以是任何可用的执行器,比如一个特定的服务器、一组服务器,或者是在Docker容器中运行。例如:pipeline { agent none // 不在任何agent上执行 stages { stage('Build') { agent { docker 'maven:3-alpine' } steps { sh 'mvn clean install' } } stage('Test') { agent { label 'my-defined-label' } steps { sh 'mvn test' } } }}在这个例子中,Build 阶段会在一个指定的Docker容器中执行,而 Test 阶段则会在配置了相应标签 my-defined-label 的节点上执行。这种方式提供了高度的灵活性和控制,允许针对不同阶段选择最适合的运行环境。
前端阅读 12月7日 00:13

Java中如何定义析构函数?

在Java中,不存在传统意义上的析构函数。Java使用垃圾回收机制来管理内存,因此不需要像在C++中那样显式定义析构函数来释放对象所占用的资源。但是,Java提供了一个方法叫做finalize(),它可以被视为Java中析构函数的一个类似物。finalize()方法在垃圾回收器决定回收对象的内存之前被调用,用于执行清理活动,比如关闭文件流或网络连接等。protected void finalize() throws Throwable { try { // 清理资源的代码,例如关闭文件 } finally { super.finalize(); }}然而,依赖finalize()进行资源清理并不推荐,因为它的执行时机是不确定的。推荐的做法是使用try-with-resources语句或者显式地调用清理方法,例如使用close()方法。
前端阅读 02月7日 00:13

Java中的对象是如何序列化的?

在Java中,对象序列化是指将对象的状态转换为字节序列的过程,这使得对象可以被存储或者通过网络传输。对象序列化主要通过实现 java.io.Serializable 接口来完成。这是一个标记接口,它不包含任何方法,仅用于标识类的对象可以被序列化。具体的序列化过程通常如下:实现Serializable接口: 要使Java类可序列化,类必须实现 java.io.Serializable 接口。ObjectOutputStream: 使用 ObjectOutputStream 类将对象写入流中。这个类有一个 writeObject() 方法,用于序列化指定的对象并将其输出到输出流中。序列化过程: 当通过 writeObject() 方法写入对象时,Java虚拟机(JVM)首先检查该对象是否已经被序列化过。如果没有,JVM将记录该对象的类型和状态(即其成员变量的值),然后递归地对该对象的所有引用进行相同处理。transient关键字: 如果不希望某个字段被序列化,可以使用 transient 关键字来修饰该字段。被 transient 修饰的字段在对象序列化时会被忽略。UID: 在类中声明一个名为 serialVersionUID 的静态常量可以用来显式定义序列化版本UID。这有助于确保序列化的兼容性,即在类定义变化时仍然能够对老版本的序列化对象进行反序列化。反序列化是上述过程的逆过程,主要通过使用 ObjectInputStream 类和其 readObject() 方法来实现,将字节序列恢复为Java对象。
前端阅读 12月7日 00:13

Java中的对象是如何创建的?

在Java中,对象是通过使用关键字 new 来创建的。首先,我们需要定义一个类,类是创建对象的模板。当我们使用 new 关键字创建类的实例时,Java虚拟机(JVM)会在堆内存中为该对象分配空间,并调用构造函数来初始化对象。例如,如果我们有一个名为 Person 的类:public class Person { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; }}我们可以通过以下方式创建 Person 类的对象:Person person = new Person("John Doe", 30);这里,new Person("John Doe", 30) 部分首先为 Person 对象分配堆内存,然后调用 Person 类的构造函数,并传入 "John Doe" 和 30 作为参数,完成对象的初始化。
前端阅读 02月7日 00:13

如何在Java中创建线程?

在Java中创建线程主要有两种方式:继承Thread类:首先,定义一个继承自Thread的类。然后,在该类中覆写run()方法,将你想要在该线程中执行的代码放入run()方法中。创建这个继承了Thread类的实例,并调用实例的start()方法来启动线程。示例代码: class MyThread extends Thread { public void run(){ System.out.println("我的线程正在运行"); } } public class TestThread { public static void main(String args[]) { MyThread t = new MyThread(); t.start(); // 启动线程 } }实现Runnable接口:定义一个实现了Runnable接口的类。实现该接口的run()方法,在这个方法里放入你想在线程中运行的代码。创建该类的实例,并将它作为参数传递给Thread的构造函数来创建一个线程对象。调用线程对象的start()方法来启动线程。示例代码: class MyRunnable implements Runnable { public void run(){ System.out.println("通过Runnable接口创建线程"); } } public class TestRunnable { public static void main(String args[]) { MyRunnable myRunnable = new MyRunnable(); Thread t = new Thread(myRunnable); t.start(); // 启动线程 } }这两种方法本质上都是构建一个可以独立执行的代码块,并通过start()方法来让Java虚拟机调用这个代码块的run()方法。使用Runnable接口的方式更加灵活,因为Java不支持多重继承,所以当你的类已经继承了其他类时,只能选用实现Runnable接口的方式来创建线程。
前端阅读 12月7日 00:10

MySQL中NULL值和零值有什么区别?

在MySQL中,NULL值代表字段中没有数据,即该字段是空的。它用于表示未知或不适用的值。而零值是一个实际的数据值,表示数值0。在逻辑上,NULL与任何其他值(包括零)都不相等,甚至NULL与NULL之间也不相等。这意味着在进行比较时,任何包含NULL的计算或比较的结果也是NULL。而零值在比较时表现为具体的数值0,可以直接用于计算和比较。
前端阅读 02月7日 00:10

MySQL中有哪些不同的数据类型?

在MySQL中,数据类型主要可以分为以下几类:数值类型:整型:TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT浮点数和双精度:FLOAT, DOUBLE, DECIMAL日期和时间类型:DATE:仅日期TIME:仅时间DATETIME:日期和时间TIMESTAMP:时间戳YEAR:年份字符串类型:字符串:CHAR, VARCHAR文本:TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT二进制:BINARY, VARBINARY二进制文本:TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB枚举类型:ENUM集合类型:SET空间数据类型:GEOMETRY, POINT, LINESTRING, POLYGON, 等等。JSON数据类型:JSON每种数据类型都有其特定用途和存储需求,选择合适的数据类型可以优化数据库性能和存储效率。
前端阅读 02月7日 00:09

Python 对象名称前的单下划线和双下划线是什么意思?

在Python中,对象名称前加单下划线(_)和双下划线(__)有不同的含义:单下划线(_):作用:它通常用来指示变量或函数是“内部使用”的,或者说是“私有”的,虽然这只是一种约定,并不会真正阻止外部访问。约定:这是一个程序员之间的约定,意味着这样的属性或方法主要供类内部使用,不应该在类的外部被使用。Python并没有强制这样的属性或方法不能在类的外面访问。双下划线(__):作用:在Python中,以双下划线开头的属性或方法表示名称改写(name mangling)以避免在子类中被覆盖。名称改写:如果你在一个类中定义了一个以双下划线开头的属性,Python解释器会把这个属性的名称改写为_ClassName__AttributeName,这使得它在子类中更难被意外访问和修改。目的:主要用于在类中封装私有变量,防止在继承中由于变量名相同而造成覆盖。
前端阅读 02月7日 00:05

Solidity中assert和require有什么区别?

在Solidity中,assert 和 require 用于处理错误和异常条件,但它们的用途和行为有明显差异:require: 通常用于输入验证或满足合约执行前的条件。如果 require 的条件失败,交易将被撤销,所有状态修改将被回滚,并退还剩余的gas。require 很适合用来检查外部条件(如函数参数值、合约状态等)。assert: 用于检查代码逻辑不应该发生的内部错误。通常,assert 用于检测合约内部状态的错误或不一致。如果 assert 的条件失败,同样会导致交易被撤销,所有状态修改被回滚。但与 require 不同的是,assert 失败将消耗所有提供的gas。简而言之,require 用于输入或条件检查,而 assert 用于确保代码逻辑在执行过程中的正确性。
前端阅读 02月7日 00:04

Solidity中的回退功能是什么?

在Solidity中,回退函数(Fallback Function)是一种特殊的函数,它没有名称、不接受任何参数也不返回任何值。这个函数会在合约接收到以太币(Ether)但没有匹配到其他任何函数时被调用,或者当调用的函数签名与合约中的任何已定义函数都不匹配时被触发。它通常用于直接接收以太币的转账或作为一个通用的异常处理器。在Solidity 0.6.x之后的版本,为了使合约代码更清晰和更安全,分成了两种类型的回退函数:接收函数(Receive function) - 专门用来处理纯ETH发送(不带任何数据的ETH转账)。这个函数必须用receive() external payable来声明。回退函数(Fallback function) - 如果没有匹配到接收函数,或者调用了不存在的函数,或者发送了ETH但调用包含数据,那么回退函数会被触发。这个函数是用fallback() external payable来声明的。这两个函数的存在提供了灵活性和安全性,使智能合约能够根据发送的是纯ETH还是带数据的ETH调用来适当地响应。
前端阅读 02月7日 00:03

Solidity中有多少种类型的库?

在Solidity中,库(Libraries)主要分为两类:函数库(Functional Libraries):这种类型的库包含了一系列的静态函数,可以在智能合约中被调用,但不能存储状态变量。函数库的目的是为了代码复用,例如常见的数学运算或数据结构操作。数据类型库(Data Type Libraries):这种类型的库对特定的数据类型提供扩展的功能,通常通过使用using for语法。这允许库中的函数作为目标类型的方法被调用,可以看作是向现有数据类型添加新的方法或属性。
前端阅读 12月7日 00:01

TypeScript中的接口是什么?

在TypeScript中,接口(Interface)是一个重要的结构,用于定义对象的形状,也就是用来描述对象中应该包含哪些属性和方法以及它们的类型。接口主要用于类型检查,让开发者在编写代码时能确保满足特定的结构和类型约束。接口可以包括属性和方法的声明,但所有这些都是抽象的,没有具体的实现。使用接口后,任何实现了该接口的类都必须遵循接口中定义的结构。例子:interface Person { name: string; age: number; greet(phrase: string): void;}class User implements Person { name: string; age: number; constructor(n: string, a: number) { this.name = n; this.age = a; } greet(phrase: string) { console.log(phrase + ' ' + this.name); }}在上述代码中,Person 接口规定了一个类必须有 name 和 age 两个属性,并且有一个 greet 方法。User 类实现了这个接口,因此必须提供这些属性和方法的具体实现。这样的机制有助于保证TypeScript中的数据结构和行为的一致性。
前端阅读 02月6日 23:59

在TypeScript中使用泛型有什么好处?

在TypeScript中使用泛型主要有以下几个好处:类型安全:泛型可以帮助保持代码的类型安全性。通过使用泛型,可以在编译时期检查类型是否正确,从而减少运行时发生错误的可能性。代码复用:泛型允许我们编写可重用的代码组件。一个泛型类或函数可以用不同的类型参数来使用,这样就可以用同一套代码来处理不同类型的数据,增加了代码的复用性。灵活性和可扩展性:使用泛型可以使代码更加灵活和可扩展。你可以定义一个泛型接口或类,用户在使用时可以根据自己的需要来指定具体的类型,这样一来,代码库就可以更容易地适应未来的需求变化。更好的维护性:泛型让类型的使用更加明确,降低了因类型错误或不当使用而引起的问题,从而减少维护成本。集成开发环境(IDE)的支持:使用泛型还可以提高开发效率,因为大多数现代IDE都能够利用泛型提供更准确的代码自动完成、类型检查和文档提示。