TypeScript扩展了JavaScript类,以在构造函数中包括类型参数,实现子句,可访问性修饰符,成员变量声明和参数属性声明。
TODO:抽象类。
类声明声明了类类型和构造函数。
ClassDeclaration: ( Modified )
class BindingIdentifieropt TypeParametersopt ClassHeritage { ClassBody }
ClassDeclaration在包含的声明空间中引入了命名类型(类类型)和命名值(构造函数)。类类型由在类主体中声明的实例成员和从基类继承的实例成员形成。构造函数被赋予由构造函数声明,类主体中的静态成员声明以及从基类继承的静态成员形成的匿名类型。构造函数初始化并返回类类型的实例。
类声明的BindingIdentifier可能不是预定义的类型名称之一(第3.8.1节)。仅当类声明出现在导出默认声明中时,BindingIdentifier才是可选的(11.3.4.2节)。
类可以选择具有类型参数(第3.6.1节),这些类型参数用作在类型引用中引用该类时要提供的实际类型的占位符。具有类型参数的类称为泛型类。通用类声明的类型参数在整个声明的范围内,并且可以在ClassHeritage和ClassBody中引用。
下面的示例在包含的声明空间中引入了一个名为’Point’的命名类型(类类型)和一个名为’Point’的命名值(构造函数)。
class Point {
constructor(public x: number, public y: number) { }
public length() { return Math.sqrt(this.x * this.x + this.y * this.y); }
static origin = new Point(0, 0);
}
命名类型’Point’完全等同于
interface Point {
x: number;
y: number;
length(): number;
}
命名值“ Point”是一个构造函数,其类型与声明对应
var Point: {
new(x: number, y: number): Point;
origin: Point;
};
引用类的上下文区分了类类型和构造函数。 例如,在赋值语句中
var p: Point = new Point(10, 20);
类型注释中的标识符“ Point”是指类类型,而新表达式中的标识符“ Point”是指构造函数对象。
TODO:更新本节,以反映类扩展子句中的表达式。
类的继承规范由可选的extends和implements子句组成。 extend子句指定该类的基类,而Implements子句指定用于验证该类提供了实现的一组接口。
ClassHeritage: ( Modified )
ClassExtendsClauseopt ImplementsClauseopt
ClassExtendsClause:
extends ClassType
ClassType:
TypeReference
ImplementsClause:
implements ClassOrInterfaceTypeList
包含extends子句的类称为派生类,而extends子句中指定的类称为派生类的基类。当类继承规范忽略extends子句时,该类没有基类。但是,就像每种对象类型一样,对该类的类型引用(第3.3.1节)似乎具有名为“ Object”的全局接口类型的成员,除非这些成员被名称相同的成员隐藏。类。
类继承规范必须满足以下约束,否则会发生编译时错误:
以下示例说明了违反上述第一条规则的情况:
class A { a: number; }
namespace Foo {
var A = 1;
class B extends A { b: string; }
}
当作为表达式求值时,extends子句中的类型引用“ A”不引用“ A”的类构造函数(而是引用局部变量“ A”)。
唯一违反上述最后两个约束的情况是,当一个类使用不兼容的新成员覆盖一个或多个基类成员时。
请注意,由于TypeScript具有结构化的类型系统,因此类无需明确声明其已实现接口-它足以使该类仅包含适当的实例成员集。 类的Implements子句提供一种机制来断言和验证该类包含适当的实例成员集,但否则对类类型没有影响。
类主体由零个或多个构造函数或成员声明组成。 不允许在类的主体中使用语句-语句必须放在构造函数或成员中。
ClassElement: ( Modified )
ConstructorDeclaration
PropertyMemberDeclaration
IndexMemberDeclaration
类的主体可以选择包含一个构造函数声明。 构造器声明在第8.3节中描述。
成员声明用于声明类的实例和静态成员。 属性成员声明在8.4节中描述,而索引成员声明在8.5节中描述。
类的成员包括通过类主体中的成员声明引入的成员以及从基类继承的成员。
成员是实例成员或静态成员。
实例成员是类类型(第8.2.4节)及其关联的this类型的成员。 在构造函数,实例成员函数和实例成员访问器中,此类型是类的this-type(第3.6.3节)。
静态成员使用static修饰符声明,并且是构造函数类型的成员(第8.2.5节)。 在静态成员函数和静态成员访问器中,此类型是构造函数的类型。
类类型参数不能在静态成员声明中引用。
物业成员具有公共,私人或受保护的访问权限。缺省值为公共可访问性,但是属性成员声明可以包括public,private或protected修饰符,以明确指定所需的可访问性。
公共财产成员可以不受限制地随处访问。
只能在其声明类中访问私有财产成员。具体来说,只能在C的类主体中访问在C类中声明的私有成员M。
受保护的属性成员只能在其声明类和从其声明类派生的类中访问,并且受保护的实例属性成员必须通过封闭类或其子类的实例进行访问。具体来说,只能在C的类主体或从C派生的类的类主体中访问在类C中声明的受保护成员M。此外,当在主体中的属性访问EM中访问受保护实例成员M时对于类D,无论类型参数如何,都要求E的类型为D或直接或间接以D为基本类型的类型。
私有和受保护的可访问性仅在编译时强制执行,仅表示意图。由于JavaScript没有提供在对象上创建私有和受保护的属性的机制,因此无法在运行时在动态代码中强制私有和受保护的修饰符。例如,可以通过将对象的静态类型更改为Any并动态访问成员来破坏私有和受保护的可访问性。
下面的示例演示了私有和受保护的可访问性:
class A {
private x: number;
protected y: number;
static f(a: A, b: B) {
a.x = 1; // Ok
b.x = 1; // Ok
a.y = 1; // Ok
b.y = 1; // Ok
}
}
class B extends A {
static f(a: A, b: B) {
a.x = 1; // Error, x only accessible within A
b.x = 1; // Error, x only accessible within A
a.y = 1; // Error, y must be accessed through instance of B
b.y = 1; // Ok
}
}
在类“ A”中,因为在“ A”中声明了“ x”,所以允许对“ x”的访问,并且由于对二者都通过“ A”的实例或从其派生的类型进行,因此允许对“ y”的访问 ‘一个’。 在类“ B”中,不允许访问“ x”,并且第一次访问“ y”是错误的,因为它是通过实例“ A”发生的,该实例不是从封闭类“ B”派生的。
派生类从其未重写的基类继承所有成员。继承意味着派生类隐式包含基类的所有未重写成员。仅公共和受保护的财产成员可以被覆盖。
当派生类的属性成员与基类属性成员具有相同的名称和种类(实例或静态)时,可以说派生类中的属性成员将覆盖基类中的属性成员。覆盖属性成员的类型必须可分配给覆盖属性成员的类型(第3.11.4节),否则会发生编译时错误。
派生类实例成员函数可以覆盖基类实例成员函数,而其他种类的成员则不能。
派生类实例成员变量和访问器可以覆盖基类实例成员变量和访问器,而其他种类的成员则不能。
如上所述,只要类型兼容,基类静态属性成员就可以被任何类型的派生类静态属性成员覆盖。
当派生类索引成员与基类索引成员具有相同的索引类型(字符串或数字)时,可以说派生类中的索引成员将覆盖基类中的索引成员。覆盖索引成员的类型必须可分配给覆盖索引成员的类型(第3.11.4节),否则会发生编译时错误。
类声明声明了一个名为类类型的新命名类型(第3.7节)。 在类的构造函数和实例成员函数中,此类型是该类类型的this-type(第3.6.3节)。 类类型具有以下成员:
一个类的所有实例属性成员(包括私有或受保护的那些实例属性成员)必须满足3.9.4节中指定的该类的索引成员所隐含的约束。
在这个例子中
class A {
public x: number;
public f() { }
public g(a: any) { return undefined; }
static s: string;
}
class B extends A {
public y: number;
public g(b: boolean) { return false; }
}
“ A”的类类型等效于
interface A {
x: number;
f: () => void;
g: (a: any) => any;
}
并且“ B”的类类型等效于
interface B {
x: number;
y: number;
f: () => void;
g: (b: boolean) => boolean;
}
请注意,类中的静态声明不会影响类的类型,相反,静态声明会在构造函数对象上引入属性。 还要注意,在“ B”中声明“ g”会覆盖从“ A”继承的成员。
由类声明引入的构造函数的类型称为构造函数类型。构造函数的类型具有以下成员:
每个类自动包含一个名为“ prototype”的静态属性成员,该成员的类型为包含类型的Any类,该类替换每个type参数。
这个例子
class Pair<T1, T2> {
constructor(public item1: T1, public item2: T2) { }
}
class TwoArrays<T> extends Pair<T[], T[]> { }
引入两个对应于
interface Pair<T1, T2> {
item1: T1;
item2: T2;
}
interface TwoArrays<T> {
item1: T[];
item2: T[];
}
和两个对应的构造函数
var Pair: {
new <T1, T2>(item1: T1, item2: T2): Pair<T1, T2>;
}
var TwoArrays: {
new <T>(item1: T[], item2: T[]): TwoArrays<T>;
}
请注意,构造函数类型中的每个构造签名都具有与其类相同的类型参数,并使用作为类型实参传递的那些类型参数返回其类的实例化。 还要注意,当派生类未声明构造函数时,在将构造签名从基本构造函数类型传播到派生构造函数类型之前,将替换基类引用中的类型参数。
构造函数声明声明一个类的构造函数。
ConstructorDeclaration:
AccessibilityModifieropt constructor ( ParameterListopt ) { FunctionBody }
AccessibilityModifieropt constructor ( ParameterListopt ) ;
指定主体的构造函数声明称为构造函数实现,而没有主体的构造函数声明称为构造函数重载。可以在一个类中指定多个构造函数重载,但是一个类最多可以具有一个构造函数实现。类中的所有构造函数声明必须指定相同的修饰符集。仅支持公共构造函数,而私有或受保护的构造函数会导致错误。
在没有构造函数声明的类中,提供了一个自动构造函数,如第8.3.3节所述。
当类具有构造函数重载时,重载确定提供给构造函数对象的类型的构造签名,并且构造函数实现签名(如果有)必须可分配给该类型。否则,构造函数实现本身将确定构造签名。这与函数声明中处理重载的方式完全相同(第6.2节)。
当一个类同时具有构造函数重载和构造函数实现时,重载必须在实现之前,并且所有声明都必须是连续的,并且中间没有语法元素。
允许构造函数的函数体包含return语句。如果return语句指定表达式,则这些表达式必须具有可分配给该类的this-type(第3.6.3节)的类型。
泛型类的类型参数在范围内,可以在构造函数声明中访问。
与函数类似,只有构造函数实现(而非构造函数重载)才能为可选参数指定默认值表达式。 此类默认值表达式引用它是编译时错误。 当输出目标为ECMAScript 3或5时,对于具有默认值的每个参数,为构造函数生成的JavaScript中将包含一条语句,该语句将默认值替换为省略的参数。
ConstructorImplementation的参数可以带有public,private或protected修饰符作为前缀。 这称为参数属性声明,是用于声明与参数名称相同的属性并使用参数的值对其进行初始化的简写形式。 例如,声明
class Point {
constructor(public x: number, public y: number) {
// Constructor body
}
}
等价于
class Point {
public x: number;
public y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
// Constructor body
}
}
参数属性声明可以声明一个可选参数(通过添加问号或默认值),但是此类声明引入的属性始终被视为必需属性(第3.3.6节)。
超级调用(第4.9.1节)用于调用基类的构造函数。 超级调用由关键字super组成,后跟括号中的参数列表。 例如:
class ColoredPoint extends Point {
constructor(x: number, y: number, public color: string) {
super(x, y);
}
}
没有extends子句的类的构造函数不能包含超级调用,而派生类的构造函数必须在其函数体内的某个位置至少包含一个超级调用。 不允许在构造函数外部或构造函数内部的局部函数中进行超级调用。
如果满足以下两个条件,则构造函数主体中的第一条语句必须是超级调用:
在这种必需的超级调用中,参数表达式引用此是编译时错误。
如果该类没有基类,则使用初始化程序对参数属性和实例成员变量进行初始化,如果该类没有基类,则立即进行初始化;如果该类是派生类,则在超级调用之后立即进行初始化。
如果类省略了构造函数声明,则会提供一个自动构造函数。
在没有extends子句的类中,自动构造函数没有参数,除了执行实例成员变量初始化程序(第8.4.1节)(如果有)外,不执行任何操作。
在派生类中,自动构造函数具有与基类构造函数相同的参数列表(并可能有重载)。 自动提供的构造函数首先使用等效于的调用将调用转发给基类构造函数
BaseClass.apply(this, arguments);
然后执行实例成员变量初始化器(如果有)。
属性成员声明可以是成员变量声明,成员函数声明或成员访问器声明。
PropertyMemberDeclaration:
MemberVariableDeclaration
MemberFunctionDeclaration
MemberAccessorDeclaration
没有静态修饰符的成员声明称为实例成员声明。实例属性成员声明以类类型(第8.2.4节)声明属性,并且必须指定包含类中所有实例属性成员和参数属性声明中唯一的名称,但实例get和set访问器声明可以成对出现指定相同的名称。
具有静态修饰符的成员声明称为静态成员声明。静态属性成员声明以构造函数类型(第8.2.5节)声明属性,并且必须指定包含类中所有静态属性成员声明中唯一的名称,但static get和set访问器声明可以成对指定。一样的名字。
请注意,实例和静态属性成员的声明空间是分开的。因此,可以使实例成员和静态属性成员具有相同的名称。
除了重写(如第8.2.3节所述)外,派生类声明与基类成员具有相同名称和种类(实例或静态)的属性成员是错误的。
每个类都会自动包含一个名为“ prototype”的静态属性成员,该成员的类型是类类型的实例化,类型Any用作每个类型参数的类型实参。明确声明名称为“ prototype”的静态属性成员是错误的。
下面是同时包含实例和静态属性成员声明的类的示例:
class Point {
constructor(public x: number, public y: number) { }
public distance(p: Point) {
var dx = this.x - p.x;
var dy = this.y - p.y;
return Math.sqrt(dx * dx + dy * dy);
}
static origin = new Point(0, 0);
static distance(p1: Point, p2: Point) { return p1.distance(p2); }
}
类类型“ Point”具有以下成员:
interface Point {
x: number;
y: number;
distance(p: Point);
}
构造函数“ Point”具有与声明相对应的类型:
var Point: {
new(x: number, y: number): Point;
origin: Point;
distance(p1: Point, p2: Point): number;
}
成员变量声明声明实例成员变量或静态成员变量。
MemberVariableDeclaration:
AccessibilityModifieropt staticopt PropertyName TypeAnnotationopt Initializeropt ;
与成员变量声明关联的类型的确定方式与普通变量声明相同(请参见5.2节)。
实例成员变量声明在类类型中引入成员,并可以选择在类的实例上初始化属性。实例成员变量声明中的初始化程序将为该类的每个新实例执行一次,并且等效于在构造函数中为其分配属性。在实例成员变量的初始化表达式中,它是类的this类型(第3.6.3节)。
静态成员变量声明在构造函数类型中引入属性,并可以选择在构造函数对象上初始化属性。加载包含的脚本或模块时,静态成员变量声明中的初始化程序将执行一次。
实例成员变量的初始化器表达式在类构造函数主体的范围内求值,但不允许引用构造函数的参数或局部变量。这实际上意味着来自外部作用域的与构造函数参数或局部变量同名的实体在实例成员变量的初始化表达式中不可访问。
由于实例成员变量初始化器等效于构造函数中对此属性的分配,因此该示例
class Employee {
public name: string;
public address: string;
public retired = false;
public manager: Employee = null;
public reports: Employee[] = [];
}
等价于
class Employee {
public name: string;
public address: string;
public retired: boolean;
public manager: Employee;
public reports: Employee[];
constructor() {
this.retired = false;
this.manager = null;
this.reports = [];
}
}
成员函数声明声明实例成员函数或静态成员函数。
MemberFunctionDeclaration:
AccessibilityModifieropt staticopt PropertyName CallSignature { FunctionBody }
AccessibilityModifieropt staticopt PropertyName CallSignature ;
成员函数声明的处理方式与普通函数声明(第6节)相同,除了在成员函数中具有已知类型之外。
同一成员函数的所有声明必须指定相同的可访问性(公共,私有或受保护)和种类(实例或静态)。
实例成员函数声明以类类型声明一个属性,并将函数对象分配给该类的原型对象上的属性。 在实例成员函数声明的主体中,这是类的this-type(第3.6.3节)的类型。
静态成员函数声明以构造函数类型声明属性,并将函数对象分配给构造函数对象的属性。 在静态成员函数声明的主体中,此类型为构造函数类型。
成员函数可以使用超级属性访问来访问重写的基类成员(第4.9.2节)。 例如
class Point {
constructor(public x: number, public y: number) { }
public toString() {
return "x=" + this.x + " y=" + this.y;
}
}
class ColoredPoint extends Point {
constructor(x: number, y: number, public color: string) {
super(x, y);
}
public toString() {
return super.toString() + " color=" + this.color;
}
}
在静态成员函数中,这表示在其上调用了静态成员函数的构造函数对象。 因此,对“ new this()”的调用实际上可能会调用派生的类构造函数:
class A {
a = 1;
static create() {
return new this();
}
}
class B extends A {
b = 2;
}
var x = A.create(); // new A()
var y = B.create(); // new B()
请注意,TypeScript不需要或验证派生的构造函数是基本构造函数的子类型。 换句话说,将“ B”的声明更改为
class B extends A {
constructor(public b: number) {
super();
}
}
即使从’create’函数调用构造函数时未指定参数(因此将值’undefined’赋予’b’)也不会在示例中引起错误。
成员访问器声明声明实例成员访问器或静态成员访问器。
MemberAccessorDeclaration:
AccessibilityModifieropt staticopt GetAccessor
AccessibilityModifieropt staticopt SetAccessor
获取和设置访问器的处理方式与对象文字(第4.5节)中的处理方式相同,只是上下文类型在成员访问器声明中永远不可用。
具有相同成员名称的访问者必须指定相同的可访问性。
实例成员访问器声明以类类型声明一个属性,并使用get或set访问器在该类的原型对象上定义一个属性。 在实例成员访问器声明的主体中,这是类的this类型(第3.6.3节)。
静态成员访问器声明在构造函数类型中声明一个属性,并在具有get或set访问器的类的构造函数对象上定义一个属性。 在静态成员访问器声明的主体中,此类型为构造函数类型。
如第8.7.1节所述,在生成的JavaScript中,获取和设置访问器作为对“ Object.defineProperty”的调用而生成。
如果属性成员声明的PropertyName是不表示众所周知的符号(2.2.3)的计算的属性名称,则该构造被视为动态属性声明。 以下规则适用于动态属性声明:
索引成员声明在类类型中引入了索引签名(第3.9.4节)。
IndexMemberDeclaration:
IndexSignature ;
索引成员声明没有主体,并且不能指定可访问性修饰符。
一个类声明最多可以包含一个字符串索引成员声明和一个数字索引成员声明。 类的所有实例属性成员必须满足3.9.4节中指定的类的索引成员所隐含的约束。
不能为类的静态端声明索引成员。
请注意,在类中包括字符串索引签名几乎没有意义,因为它会约束该类的所有实例属性。 但是,当以类似数组的方式使用类时,数字索引签名可用于控制元素类型。
TODO:文档装饰者。
当输出目标为ECMAScript 2015或更高版本时,在生成的代码中删除类型参数,implementes子句,可访问性修饰符和成员变量声明,但否则以书面形式生成类声明。 如本节所述,当输出目标是ECMAScript 3或5时,将执行更全面的重写。
没有extends子句的类将生成等效于以下内容的JavaScript:
var <ClassName> = (function () {
function <ClassName>(<ConstructorParameters>) {
<DefaultValueAssignments>
<ParameterPropertyAssignments>
<MemberVariableAssignments>
<ConstructorStatements>
}
<MemberFunctionStatements>
<StaticVariableAssignments>
return <ClassName>;
})();
ClassName是类的名称。
ConstructorParameters是用逗号分隔的构造函数参数名称列表。
DefaultValueAssignments是默认属性值分配的序列,对应于为常规函数声明生成的默认值,如6.6节所述。
ParameterPropertyAssignments是一系列分配,对于构造函数中的每个参数属性声明,按声明的顺序依次分配一个
this.<ParameterName> = <ParameterName>;
其中ParameterName是参数属性的名称。
MemberVariableAssignments是一系列分配,对于每个实例成员变量声明,每个初始化声明器均按其声明的顺序进行分配,形式为
this.<MemberName> = <InitializerExpression>;
其中MemberName是成员变量的名称,InitializerExpression是为初始化程序表达式生成的代码。
ConstructorStatements是为构造函数主体中指定的语句生成的代码。
MemberFunctionStatements是一组语句序列,每个成员函数声明或成员访问器声明均按声明顺序进行。
实例成员函数声明生成以下形式的语句
<ClassName>.prototype.<MemberName> = function (<FunctionParameters>) {
<DefaultValueAssignments>
<FunctionStatements>
}
和静态成员函数声明生成以下形式的语句
<ClassName>.<MemberName> = function (<FunctionParameters>) {
<DefaultValueAssignments>
<FunctionStatements>
}
其中MemberName是成员函数的名称,而FunctionParameters,DefaultValueAssignments和FunctionStatements对应于为常规函数声明生成的那些函数,如6.6节所述。
一个get或set实例成员访问器声明,或一对具有相同名称的get和set实例成员访问器声明,将生成以下形式的语句
Object.defineProperty(<ClassName>.prototype, "" , {
get: function () {
<GetAccessorStatements>
},
set: function (<ParameterName>) {
<SetAccessorStatements>
},
enumerable: true,
configurable: true
};
一个get或set静态成员访问器声明,或者一对具有相同名称的get和set静态成员访问器声明,生成以下形式的语句
Object.defineProperty(<ClassName>, "" , {
get: function () {
<GetAccessorStatements>
},
set: function (<ParameterName>) {
<SetAccessorStatements>
},
enumerable: true,
configurable: true
};
其中MemberName是成员访问器的名称,GetAccessorStatements是为get acessor的函数主体中的语句生成的代码,ParameterName是set访问器参数的名称,SetAccessorStatements是为set访问器的函数主体中的语句生成的代码 。 仅当声明了get访问器时才包括’get’属性,而仅当声明了set访问器时才包括’set’属性。
StaticVariableAssignments是一系列语句,对于每个带有初始化程序的静态成员变量声明,每个声明均按声明的顺序进行,形式为
<ClassName>.<MemberName> = <InitializerExpression>;
其中Member Name是静态变量的名称,Initializer Expression是为初始值设定项表达式生成的代码。
具有extends子句的类生成的JavaScript等效于以下内容:
var <ClassName> = (function (_super) {
__extends(<ClassName>, _super);
function <ClassName>(<ConstructorParameters>) {
<DefaultValueAssignments>
<SuperCallStatement>
<ParameterPropertyAssignments>
<MemberVariableAssignments>
<ConstructorStatements>
}
<MemberFunctionStatements>
<StaticVariableAssignments>
return <ClassName>;
})(<BaseClassName>);
另外,下面的’__extends’函数在JavaScript源文件的开头生成。 它将所有属性从基本构造函数对象复制到派生构造函数对象(以继承静态成员),并适当地建立派生构造函数对象的’prototype’属性。
var __extends = this.__extends || function(d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function f() { this.constructor = d; }
f.prototype = b.prototype;
d.prototype = new f();
}
BaseClassName是在extends子句中指定的类名。
如果该类没有显式声明的构造函数,则SuperCallStatement采用以下形式:
_super.apply(this, arguments);
否则,如果要求构造函数以超级调用开头(如第8.3.2节所述),则存在SuperCallStatement并采用以下形式:
_super.call(this, <SuperCallArguments>)
其中,SuperCallArguments是超级调用中指定的参数列表。 请注意,此调用在使用初始化程序为参数属性和成员变量生成的代码之前。 构造函数中其他地方的超级调用会生成类似的代码,但为此类调用生成的代码将成为ConstructorStatements部分的一部分。
构造函数中的超级属性访问,实例成员函数或实例成员访问器生成的JavaScript等效于
_super.prototype.<PropertyName>
其中PropertyName是引用的基类属性的名称。 当超级属性访问出现在函数调用中时,生成的JavaScript等效于
_super.prototype.<PropertyName>.call(this, <Arguments>)
其中Arguments是为函数调用中指定的参数列表生成的代码。
静态成员函数或静态成员访问器中的超级属性访问生成的JavaScript等效于
_super.<PropertyName>
其中PropertyName是引用的基类属性的名称。 当超级属性访问出现在函数调用中时,生成的JavaScript等效于
_super.<PropertyName>.call(this, <Arguments>)
其中Arguments是为函数调用中指定的参数列表生成的代码。