Ryuu 的个人博客

一个计算机初学者

C# 有两种常量,一种是**编译期 (compile-time)的,另一种是运行期 (runtime)**的。[1]

1
2
3
4
5
// Compile-time constant:
public const int Millennium = 2000;

// Runtime constant:
public static readonly int ThisYear = 2004;

以上代码展示了如何在 class 或 struct 的范围内声明这两种常量 此外编译期常量还能在方法中声明,而 readyonly 常量则不行。[2]

编译器的常量取值嵌入目标代码中。例如[3]

1
if(myDateTime.Year == Millennium)

编译成 IL 之后,与直接使用字面量2000是一样的

1
if(myDateTime.Year == 2000)

运行期常量与之不同,如果代码中使用到了运行期常量,那么其生成的 IL 的、也会同样引用该变量,而不会直接使用字面量。[4]

这两种变量支持的值也不一样。编译期的常量只能用来表示内置的 int, float, enum, string。非基本变量不能使用编译期常量声明,需要使用 readonly。在生成 IL 的过程中,只有用来表示这些原始类型的编译期常量才会替换成字面量。[5]

无法编译,试图使用 new 操作符进行初始化: (Ryuu: 即使是参数是值类型也不行,其对象在编译期不存在)

1
2
3
// DON'T DO THIS!
// Does not compile ,use readonly instead:
private const DateTime classCreation = new DateTime(2000, 1, 1, 0, 0, 0);

**编译期常量只能用数字,字符串或 null 初始化。**readonly 常量在执行完构造函数 (constructor) 之后不可以再修改。(和编译期常量不同,他的值是在执行完构造函数后才初始化的)[6]

在生成 IL 的时候,代码中的编译期常量会直接以其常量值写入,如果在制作另外的程序集 (assembly) 的时候用到了该程序集中的编译期常量,那么这个常量将会以字面值写入另外的程序集。[7]

有的时候开发者确实想把某个值固定在编译期,比如程序版本记录,如果更新整个项目,那么里面每个版本号都会变为最新,如果仅更新其中某些程序集,那么只有更新的程序集的版本号会变为最新值。

const 的性能比 readonly 的要好。由于程序集可以直接访问值,而不用查询变量,因此性能稍高。但是,开发者需要考虑是否值得为了这点性能而使得代码变得僵硬。在决定这么做之前,您应该先通过 profile 工具做性能测试。(可以试试 BenchmarkDotNet)[8]

const 关键字用来声明那些必须在编译期得以确定的值,例如 attribute 参数、switch case 语句的标签、enum 的定义等,偶尔用于声明不会随版本而变化的值。除此之外的值考虑用 readonly 常量声明。[9]


  1. Bill Wagner 的 Effective C# (Covers C# 6.0), 3rd Edition Item 2 将 constreadonly 的选择放在“编译期常量 vs. 运行期字段”的语义边界下讨论;Microsoft 的 constreadonly 文档也分别把二者定义为编译期常量和只读字段约束:https://www.informit.com/store/effective-c-sharp-covers-c-sharp-6.0-50-specific-ways-9780134579283https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/consthttps://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly↩︎

  2. Microsoft const 文档说明常量可声明为局部常量或字段;readonly 文档则把 readonly 描述为字段修饰符,赋值位置受字段初始化器和构造函数约束:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/consthttps://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly↩︎

  3. C# 规范 §15.5.3.3 在讨论 constants 与 static readonly fields 的版本差异时说明,常量的值会在编译期取得;这就是跨程序集使用 const 时会出现字面值写入调用方编译产物的根源:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#15533-versioning-of-constants-and-static-readonly-fields↩︎

  4. 同一段 C# 规范把 static readonly 字段与 const 对比:字段值在运行期获得,而不是像常量那样在编译期写入使用方:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#15533-versioning-of-constants-and-static-readonly-fields↩︎

  5. Microsoft const 文档列出的可用类型比正文示例更完整:内置数值类型、boolcharstring、枚举类型,以及引用类型的 null。这里的关键边界不是这几个例子本身,而是初始化表达式必须能在编译期成为常量表达式:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/consthttps://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#1226-constant-expressions↩︎

  6. C# 规范 §15.5.3 规定 readonly 字段只能在声明的变量初始化器、实例构造函数、静态构造函数或静态字段初始化器中赋值;但 readonly 不是深度不可变,引用对象内部是否可变仍取决于对象自身设计:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#1553-readonly-fieldshttps://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly↩︎

  7. C# 规范 §15.5.3.3 专门用 constants 与 static readonly fields 对比说明 binary versioning:常量在编译期取值,静态只读字段在运行期取值;因此公开 const 的变更通常要求依赖方重新编译才能看到新值:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#15533-versioning-of-constants-and-static-readonly-fields↩︎

  8. Microsoft CA1802 “Use literals where appropriate” 将 static readonly 改为 const 视为可能的性能优化,但规则说明也把适用范围限定在值可在编译期确定且语义上适合常量的场景;公开 API 还要先考虑版本语义:https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1802https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#15533-versioning-of-constants-and-static-readonly-fields↩︎

  9. attribute 参数类型受 C# 规范 §23.2.4 限制,常见 attribute 参数、case 标签和枚举值都属于需要编译期常量表达式的语境;这些场景才是 const 的天然职责:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/attributes#2324-attribute-parameter-typeshttps://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#1226-constant-expressions↩︎

静态工厂和构造器有个共同的局限性:都不能很好地扩展大量的可选参数[1]

例:

考虑用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份含量,每罐含量,每份卡路里,还有超过20个可选域:总脂肪量,饱和脂肪量,转化脂肪量,胆固醇,钠等等。大多数的产品在某几个可选域中都会有非0的值。

对于这样的类,应该用哪种构造器或者静态方法来编写呢?

1. 重叠构造器 (telescoping constructor)

提供第一个只有必要参数的构造器

提供第二个包含必要参数且包含一个可选参数的构造器

提供第三个包含必要参数且包含两个可选参数的构造器

以此类推,最后一个构造器包含所有参数

如下是个简单的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class NutritionFacts {
private final int servingSize; //(ml) required
private final int servings; //(per container) required
private final int calories; // optional
private final int fat; //(g) optional
private final int sodium; //(mg) optional
private final int carbohydrate; //(g) optional

//必须的选项
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}

public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0);
}

public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}

public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}

//包含所有的选项
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}

仅仅想创建一个该类对象,使用最短的构造器即可

但如果想要设置参数表中靠后的参数问题就来了

1
NutritionFacts cocaCola = new NutritionFacts(240,8,100,0,35,27);

如上初始化中 fat 的值为0,这个参数本是不用初始化的,就6个参数的情况下,还说的过去,随着参数增加,这样就不行了。

重叠构造器是可行的,但当参数增多时,客户端的代码将变得很难编写,阅读性也较差,并且如果初始化时容易填错顺序,导致运行时的错误。[2]

2. JavaBeans模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
private int servingSize = -1; //(ml) required
private int servings = -1; //(per container) required
private int calories = 0; // optional
private int fat = 0; //(g) optional
private int sodium = 0; //(mg) optional
private int carbohydrate = 0; //(g) optional

public NutritionFacts() {
}

public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}

public void setServings(int servings) {
this.servings = servings;
}

public void setCalories(int calories) {
this.calories = calories;
}

public void setFat(int fat) {
this.fat = fat;
}

public void setSodium(int sodium) {
this.sodium = sodium;
}

public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}

// 实例化
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

JavaBeans模式创建实例很容易,代码可读性也很强,但其有很严重的缺点:[3]

  1. 构造的过程包含多个调用,在构造过程中JavaBeans可能处于不一致状态

    例如一个线程正在使用setter初始化值,而另一个线程正用getter取得该对象的字段

  2. 使用JavaBeans模式则该类不可成为不可变类 (因为有setter访问器)[4]

3. Builder模式

此模式有重叠构造器的安全性,也有JavaBeans模式的高可读性[5]

不直接生成需要的对象,而是得到一个builder对象,调用所有的必要的构造器或静态工厂,设置每一个需要设置的参数,最后调用一个无参数的build方法来生成一个不可变对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}

public static class Builder {
// Required parameters
private int servingSize;
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;

public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

// builder的 setter 返回自身实现链接调用
public Builder calories(int calories) {
this.calories = calories;
return this;
}

public Builder fat(int fat) {
this.fat = fat;
return this;
}

public Builder sodium(int sodium) {
this.sodium = sodium;
return this;
}

public Builder carbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
return this;
}

public NutritionFacts build() {
return new NutritionFacts(this);
}
}
}

// 实例化
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();

Builder 模式的可读性提高了很多,代码简易。Builder模式模拟了了具名可选参数

如果该类包含多个字段,Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。与重叠构造器与JavaBeans相比要更加安全与易读。[6]


  1. Joshua Bloch, Effective Java, 3rd Edition, Item 2, frames this problem as considering a builder when a class faces many constructor parameters. ↩︎

  2. Joshua Bloch, Effective Java, 3rd Edition, Item 2, uses the NutritionFacts example to compare telescoping constructors, JavaBeans, and builders for many optional parameters. ↩︎

  3. Oracle, Writing JavaBeans Components and JavaBeans Properties, documents JavaBeans as coding conventions around properties, methods, events, getters, and setters; Effective Java Item 2 discusses the object-consistency tradeoff of using that style for value construction. ↩︎

  4. Oracle, Java Language Specification §8.3.1.2 final Fields, defines that a blank final instance variable must be definitely assigned by the end of every constructor, which is the language-level basis for constructor-bounded immutable fields. ↩︎

  5. Joshua Bloch, Effective Java, 3rd Edition, Item 2, presents the builder idiom as combining the safety of telescoping constructors with the readability of JavaBeans. ↩︎

  6. Joshua Bloch, Effective Java, 3rd Edition, Item 2, supports using builders when optional parameters are numerous; Erich Gamma et al., Design Patterns: Elements of Reusable Object-Oriented Software, provides the classic Builder pattern background, whose focus is broader than the Effective Java builder idiom. ↩︎

静态工厂方法与构造器不同的优点[1]

  1. 它们有名称 (静态方法)

    例:构造器 BigInteger(int, int, Random) 返回的 BigInteger 可能为素数,如果用名为 BigInteger.probablePrime 的静态工厂方法来表示,显然更为清楚 (1.4的发行版本中最终增加了这个方法)[2]

  2. 不必每次调用时都创建一个新对象 (可以将构建好的实例进行缓存并重复利用)

    Boolean.valueOf(boolean) 从来不创建对象。这种方法类似于 Flyweight模式(享元模式)。如果程序经常创建相同的对象,并且创建对象的代价高,这种技术将会极大的提升性能。[3]

  3. 可以返回原返回类型的任何子类型对象 (可以返回隐藏类的实例,将实现类隐藏将使得API十分简洁)[4]

  4. 创建参数化类型(泛型)实例的时候,使代码变得更加简洁 (类型推导 type inference)[5]

    1
    2
    3
    4
    5
    6
    7
    8
    // 调用参数化构造器时,指明类型
    Map<String, List<String>> map = new HashMap<String, List<String>>();
    // 假如在 HashMap 中提供静态工厂
    public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
    }
    // 创建实例将会变简洁
    Map<String, List<String>> map = HashMap.newInstance();

静态工厂方法的缺点

  1. 类如果不含公有的或者受保护的构造器,就不能被子类化

  2. 与其他的静态方法实际上没有任何区别

    在API文档中,静态工厂方法不会像构造器一样被单独列出并标识,对于仅提供静态工厂方法的类而言,想查到如何实例化该类是比较麻烦的。

    静态工厂方法的一些惯用名称如下:

    1. valueOf
    2. of
    3. getInstance
    4. newInstance
    5. getType
    6. newType

静态工厂方法和共有构造器各有用处,一般情况下静态工厂更加合适,优先提供静态工厂,而不是共有构造器[6]

个人总结: 静态工厂方法是对获取对象的一种封装(封装为静态方法),具体实现可隐藏于其中,具有更高的灵活性[7]


  1. Joshua Bloch, Effective Java, 3rd Edition, Item 1, frames static factory methods as an alternative to constructors and organizes their advantages and disadvantages. ↩︎

  2. Oracle Java SE 21 API, BigInteger.probablePrime(int, Random), documents a named static factory for returning a positive probable prime with the requested bit length. ↩︎

  3. Oracle Java SE 21 API, Boolean.valueOf(boolean), documents returning Boolean.TRUE or Boolean.FALSE; Erich Gamma et al., Design Patterns: Elements of Reusable Object-Oriented Software, describes the Flyweight pattern background for shared objects. ↩︎

  4. Oracle Java SE 21 API, EnumSet, is an abstract class whose instances are created through static factory methods, illustrating how callers can depend on an abstract API while implementations remain hidden. ↩︎

  5. Oracle, Java Language Specification §15.9 Class Instance Creation Expressions, defines class instance creation and modern diamond inference; this supports the historical type-inference motivation while also bounding it for newer Java versions. ↩︎

  6. Joshua Bloch, Effective Java, 3rd Edition, Item 1, recommends considering static factory methods instead of constructors while preserving the tradeoff that both creation forms have uses. ↩︎

  7. Oracle Java SE 21 API, ServiceLoader, documents service-provider loading through an abstraction, which is one official example of object acquisition being mediated by an API rather than direct construction. ↩︎

类型参数的约束指出了能完成该泛型类工作的类必须具有的行为。若是某一类型无法满足约束,那么自然无法用于该泛型类型中。[1]不过这也意味着,每次在泛型类型中引入新的的束,都会给该类型的使用者增加更多的工作。实际情况各不相同,因此并没有万能的解决方案,不过太过极端总归是不好的。若是不给出任何约束,那么则必须在运行时进行过多检查,比如使用强制转换。反射并抛出运行时异常等来保证程序的正确性。[2]而若是约束过多,那么也会让类的使用者觉得麻烦。因此你要找到那个恰到好处的中间点,精确地时类型参数给出约束,不多也不少。
约束能让编译器了解某个类型参数更具体的信息,而不仅限于极为笼统的System.Object。[1:1]在创建泛型类型时,C# 编译器将要为泛型类型的定义生成合法的IL[3]而在进行编译时,虽然编译器对今后可能用来替换类型参数的具体类型了解甚少,但你需要生成合法的程序集。若是不添加任何约束,那么编译器只能假设这些类型仅具有最基本的特性,即System.Object中定义的方法。[1:2]
​ 编译器无法猜测出你对类型的假设,唯一能够确认的就是该类型继承于System.Object(因此我们无法创建不安全的泛型,也无法将指针作为类型参数。)我们知道。System.Object的功能非常有限,因此若是使用到了任何非System.Object的功能,编译器均会抛出异常。你甚至都无法使用最基础的构造函数new T(),因为若某个类型仅提供了有参数的构造函数,那么该构造函数将会被隐藏。[4]

最小化约束方法有很多种.其中最常见的一种是,确保泛型类型不要求其不需要的功能

以IEquatable为例,这个是个很常用的接口,且创建新类型时也经常用到.[5]

我们可以重写AreEqual方法,让其调用Equals方法.

1
2
3
4
public static AreEqual<T>(T left, T right)
{
return left.Equals(right);
}

在上述代码中,若AreEqual()定义在一个带有IEquatable约束的泛型类中,那么AreEqual将调用IEquatable.Equals.否则, C#编译器则不会假设具体类型一定会实现IEquatable,因此唯一可用的Equals()就是System.Object.Equals().[5:1]

上述示例可以看到C#泛型和C++模板之间的主要区别.

在C#中,编译器仅能使用约束给出的信息来生成IL. 即使为某个特定的实例指定的类型拥有更好的方法,也不会在运行时使用,除非在该泛型类型编译时就给出限定.[3:1]


  1. Microsoft Learn, Constraints on type parameters。用于支撑“约束告诉编译器类型参数必须具备哪些能力”、无约束时只能依赖 System.Object 成员,以及约束过少会把检查推迟到运行时的判断。 ↩︎ ↩︎ ↩︎

  2. Microsoft Learn, Generic types and methods。用于支撑泛型通过类型参数和约束把类型安全前移到编译期,减少运行时强制转换和类型错误。 ↩︎

  3. Microsoft Learn, C# language specification - Type parameter constraints。用于支撑类型实参约束在编译期检查、受约束类型参数可访问约束暗示成员,以及泛型定义依赖约束信息生成合法程序的语言规则。 ↩︎ ↩︎

  4. Microsoft Learn, new constraint。用于支撑泛型代码只有在声明 new() 约束时才能实例化类型参数 T,且类型实参必须具有 public parameterless constructor。 ↩︎

  5. Microsoft Learn, IEquatable Interface。用于支撑 IEquatable<T> 表达同类型相等性比较能力,以及泛型代码可通过该接口约束调用类型特定的 Equals↩︎ ↩︎

.NET 平台的头两个版本 (1.1 及 1.2) 不支持泛型

System.Object 是所有类型的最终基类[1]

为何要使用泛型代码?

  1. 健壮性

将 Object 作为 参数或返回类型 难免会出现意外的类型 导致运行时的错误[2]

  1. 性能

    1.1 版本的弱类型系统需要在代码中添加检验代码以保证参数或返回类型的正确[3]

    ,当检验失败时还会执行更多的其他代码,这不可避免的导致了更大的性能开销[4]

“总体说来,弱类型系统将带来各种各样的麻烦,从性能低下直至程序异常终止等。”

自 .NET 2.0 引入了泛型[2:1]

以 System.IComparable 为例比较泛型版本的优势[5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IComparable<T>
{
int CompareTo(T other);
}

// 1.1版本
public interface IComparable
{
int CompareTo(object obj);
}

// 实现
public int CompareTo(Customer right)
{
return Name.CompareTo(right.Name);
}

public int CompareTo(object right)
{
if (!(right is Customer))
throw new ArgumentException("Argument not a customer","right");
Customer rightCustomer = (Customer)right;
return Name.CompareTo(rightCustomer.Name);
}

使用泛型接口的四个优势

  1. 简洁
  2. 高效
  3. 避免了所有的装箱/拆箱以及类型转换操作 (个人认为这点其实包含在1与2中)[4:1]
  4. 不会抛出异常 (非泛型版本中运行时可能出现的异常变为可由编辑器捕获的异常[2:2]

个人总结: 用基类传来传去固然是不怎么安全,并且还会增加代码量与更多性能开销,一般情况下用泛型约束确实是最好的选择 (参数不继承自同一基类另说)[6]


  1. Microsoft Learn, C# language specification - Types。用于支撑 C# 统一类型系统中所有类型最终可视为 object 的语言层事实。 ↩︎

  2. Microsoft Learn, Generic types and methods。用于支撑泛型把类型事实参数化、由编译器维护类型安全,并能让类型错误更早暴露的判断。 ↩︎ ↩︎ ↩︎

  3. Microsoft Learn, IComparable Interface。用于支撑非泛型 IComparable.CompareTo 接收 object,因此实现中常需要运行时类型检查的边界。 ↩︎

  4. Microsoft Learn, Boxing and UnboxingGenerics in the runtime。用于支撑值类型进入 object API 时会装箱、泛型运行时可避免常见装箱路径,从而减少额外分配和转换成本。 ↩︎ ↩︎

  5. Microsoft Learn, IComparable InterfaceIComparable.CompareTo(T) Method。用于支撑泛型比较接口把比较对象类型写进签名,减少非泛型 object 入口的类型转换和检查。 ↩︎

  6. Microsoft Learn, Constraints on type parameters。用于支撑泛型约束可以声明类型参数必须具备的能力,让编译器允许调用相应成员并检查调用方传入类型。 ↩︎

在当前网页按下 F12,转至 Console 输入并回车:[1]

1
2
3
4
// 设置文档body元素内容可编辑性为 true
javascript:document.body.contentEditable='true'
// 打开文档设计模式
document.designMode='on'

便可操作网页中的文本 (如删除,剪切,粘贴,输入操作)[2]


  1. MDN 的 HTMLElement.contentEditableDocument.designMode 文档分别说明:元素可通过 contentEditable 进入可编辑状态,整个 document 可通过 designMode 控制是否可编辑;这支撑正文中“在 Console 临时开启页面可编辑状态”的技巧:https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/contentEditablehttps://developer.mozilla.org/en-US/docs/Web/API/Document/designMode↩︎

  2. contentEditable / designMode 改变的是页面内容的编辑状态,适合临时操作和手动选择文本;它不等同于产品级剪贴板 API。真正写入系统剪贴板应优先评估 Clipboard API,选区辅助则属于 Selection API:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_APIhttps://developer.mozilla.org/en-US/docs/Web/API/Selection↩︎

Hexo 小姿势

在本地启动 hexo server[1]

(ctrl + c 停止)

1
hexo s

创建新的博客文章 new blog[2]

1
hexo n "文章名称"

生成文件并部署至仓库 deploy[3]

1
hexo d

部署的信息在 _config.yml 文件最下的 deploy[4]

// _config.yml 示例 (记得冒号后要有空格)

1
2
3
4
5
6
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git
repo: https://github.com/Ryuu-64/Ryuu-64.github.io
branch: master

hexo 小问题

当node 版本过高时会产生以下 Warning[5]

1
2
3
4
5
6
7
8
9
10
11
12
13
(node:13888) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:13888) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:13888) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:13888) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(node:13888) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:13888) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency

官方的 issues[6] https://github.com/stylus/stylus/issues/2534

解决方案

不用改变node的版本[7]

找到项目中的此文件: \node_modules\stylus\lib\nodes\index.js 在最前处添加如下代码即可

1
2
3
exports.lineno = null;
exports.column = null;
exports.filename = null;

后续问题

结果还是有问题

博客部署出错了 详情如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FATAL Something's wrong. Maybe you can find the solution here: https://hexo.io/docs/troubleshooting.html
TypeError [ERR_INVALID_ARG_TYPE]: The "mode" argument must be integer. Received an instance of Object
at copyFile (fs.js:1972:10)
at tryCatcher (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\util.js:16:23)
at ret (eval at makeNodePromisifiedEval (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promisify.js:184:12), <anonymous>:13:39)
at F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\hexo-fs\lib\fs.js:144:39
at tryCatcher (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\util.js:16:23)
at Promise._settlePromiseFromHandler (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:547:31)
at Promise._settlePromise (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:604:18)
at Promise._settlePromise0 (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:649:10)
at Promise._settlePromises (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:729:18)
at Promise._fulfill (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:673:18)
at Promise._resolveCallback (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:466:57)
at Promise._settlePromiseFromHandler (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:559:17)
at Promise._settlePromise (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:604:18)
at Promise._settlePromise0 (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:649:10)
at Promise._settlePromises (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:729:18)
at Promise._fulfill (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:673:18)

最终方案

下载一个 nodejs 的 12.x 的版本[8]

再部署就没有任何问题了

还有问题就把高版本的 nodejs 删除[8:1]


  1. Hexo 官方 commands 与 server 文档把 hexo server 列为启动本地服务器的命令,并说明它会在本地启动服务、监听文件变化,适合写作时预览:https://hexo.io/docs/commandshttps://hexo.io/docs/server↩︎

  2. Hexo 官方 commands 文档说明,hexo new [layout] <title> 用于创建新文章或新页面;因此这里的 hexo n "文章名称"hexo new 的简写用法:https://hexo.io/docs/commands↩︎

  3. Hexo 官方 commands 文档说明,hexo generate 用于生成静态文件,hexo deploy 用于部署网站;one-command deployment 文档说明本地一键部署通常依赖已配置的 deploy 插件,例如 hexo-deployer-githttps://hexo.io/docs/commandshttps://hexo.io/docs/one-command-deployment↩︎

  4. Hexo 官方 one-command deployment 文档说明,部署配置写在 _config.ymldeploy 字段中,并可配置 typerepobranch 等值;GitHub Pages 文档也提醒分支和 Pages 设置需要与实际仓库匹配:https://hexo.io/docs/one-command-deploymenthttps://hexo.io/docs/github-pages↩︎

  5. Stylus GitHub issue #2534 记录了 NodeJS 14 环境下启动时出现 Accessing non-existent property 'lineno'columnfilename 这组 warning 的问题;这能支撑原文中的 warning 现象,但它是特定依赖和 Node 版本组合下的排障线索:https://github.com/stylus/stylus/issues/2534↩︎

  6. 该链接指向 Stylus 仓库的 issue #2534,标题为 NodeJS 14 warnings,创建于 2020-05-04,issue 正文包含与原文相同的 circular dependency warning 文本:https://github.com/stylus/stylus/issues/2534↩︎

  7. 这类直接修改 node_modules/stylus/lib/nodes/index.js 的做法更接近本机临时实验;npm 的 package-lock.json / npm ci 文档强调依赖树和干净安装的可复现性,Hexo troubleshooting 也更适合从版本、配置、插件、主题和错误输出排查,因此长期方案应沉淀为依赖版本、锁文件、升级或可复现补丁,而不是只保留本机手改:https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-jsonhttps://docs.npmjs.com/cli/v11/commands/npm-cihttps://hexo.io/docs/troubleshooting↩︎

  8. 这段是 2021 年上下文中的本机排障经验。Node.js 官方 release 页面按 Current、Active LTS、Maintenance LTS、End-of-life 区分版本状态,并建议生产应用使用 Active LTS 或 Maintenance LTS;因此现代维护时不应默认退回 Node 12.x,而应先核对当前 Hexo / 主题 / 插件支持范围并选择仍受维护的 LTS:https://nodejs.org/en/about/previous-releaseshttps://hexo.io/docs/troubleshooting↩︎ ↩︎

我的博客搭建详细步骤

事前准备

1.安装 Node.js

Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境[1]
建议下载 LTS 版本 (Long Term Support | 长期支持)[2]
官网上下载安装包,然后跟着提示进行操作即可
下载完毕后可以进入控制台
输入 node -v 查看 node 版本[3]
输入 npm -v 查看 npm 版本 (node package manager | node包管理器)[4]

2.安装 git

Git 是一个免费的开源分布式版本控制系统[5]
官网上下载安装包,然后跟着提示进行操作即可,步骤较多,可参考各种安装教程
下载完毕后可以进入控制台,输入 git --version 查看 git 版本,确认安装成功

git config --global user.name “输入你的名称”
git config --global user.email “输入你的邮箱地址”
设置自己的名称和电子邮箱[6]

3.安装淘宝镜像 (可选步骤)

如果使用 npm 安装依赖过慢
进入控制台输入如下命令安装淘宝镜像
npm install -g cnpm --registry=https://registry.npm.taobao.org
使用 cnpm 代替 npm 以提升速度

开始搭建

阅读全文 »
0%