TypeScript 概览

TypeScript

TypeScript 是 JavaScript 类型的超集,它可以编译成纯 JavaScript,可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。

基础类型

TypeScript 支持与 JavaScript 几乎相同的数据类型,此外还提供了方便使用的枚举类型。

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
// boolean
let isDone: boolean = false;
// number
// JavaScript 和 TypeScript 中的数字不是浮点类型 floating point 就是大整数类型 BigInteger
let decLiteral: number = 6;
let bigLiteral: number = 100n;
// string
let name: string = 'bob';
// Array
let list: number[] = [1,2,3];
let list: Array<number> = [1,2,3];
// Tuple
// 元组类型要求值类型、顺序和个数一一对应
let tuple: [string, string] = ['string', 'string'];
// enum
enum Color {Red = 0, Green, Blue}
let c: Color = Color.Green;
// Any
let source: any = 4
// Void
let unusable: void = undefined;
// Null & Undefined
let u: undefined = undefined;
let n: null = null;
// Never
function error(message: string): never {
throw new Error(message);
}
// 类型断言
// 明确知道值的类型时可使用类型断言
let strValue: any = 'str';
let length: number = (<string>strValue).length;
let length: number = (strValue as string).length

变量声明

  • var

    可以通过 var 关键字定义变量,但是 var 声明可以在包含它的函数、模块、命名空间或全局作用域内部任何位置被访问,包含它的代码块对此没有影响,这可能会引发一些错误,比如多次声明同一个变量并不会报错。

  • let

    当用 let 声明一个变量,它使用的是词法作用域或块作用域。不同于使用 var 声明的变量那样可以在包含它们的函数外访问,块作用域变量在包含它们的块之外是不能访问的。

  • const

    const 拥有与 let 相同的作用域规则,但是声明的变量被赋值后不能再改变。

每次进入一个作用域时,它创建了一个变量的环境,就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。当 let 声明出现在循环体里时拥有完全不同的行为,不仅是在循环里引入了一个新的变量环境,而是针对每次迭代都会创建这样一个新作用域。

接口

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做 “鸭式辨型法”或”结构性子类型化”。 在 TypeScript 里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

JavaScript 为开发者提供了非常大的灵活性:比如你可以为一个初始化为整数的变量赋值一个函数。但同时这种不确定性也会带来很多的麻烦,TypeScript 内置的接口就可以用来解决这个问题。

接口帮助我们在赋值和传递参数时进行类型检查,确保我们给变量的赋值符合变量的类型或接收参数正确(接口在某种程度上表示了变量或函数对于赋值给它的值或传递给它的参数的一种期望和要求)。

可选属性

接口里的属性不全都是必需的,有些是只在某些条件下存在,或者根本不存在,可以为接口定义可选属性来实现这些需求。

1
2
3
4
interface OptionAttribute {
optionNum?: number;
optionStr?: string;
}

可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。

只读属性

如果希望对象的某些属性只有在刚创建的时候修改其值,可以通过在属性名前用 readonly 来指定属性为只读属性。

1
2
3
4
interface ReadonlyAttribute {
readonly num: number;
readonly str: string;
}

TypeScript 具有 ReadonlyArray<T> 类型,它与 Array<T> 类似,只是把所有可变方法去掉了,因此可以保证数组创建后再也不能被修改。

readonly vs const

使用 const 来修饰变量,使用 readonly 来修饰属性

额外的类型检查

TypeScript 中,对象字面量赋值给变量或作为参数进行传递的时候,会经过 “额外属性检查”,如果一个对象字面量存在任何 “目标类型” 不包含的属性时,会发生错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface SquareConfig { 
width: number;
color?: string;
}

function createSquare(config: SquareConfig): { color: string, area: number } {
let newSquare = { color: 'blue', area: 0 };
if (config.width) newSquare.area = config.width * config.width;
if (config.color) newSquare.color = config.color;
return newSquare;
}

// error: height not expected in type SquareConfig
let mySquare = createSquare({ color: 'green', width: 10, height: 10 });

使用 ‘类型断言’ 可以绕开额外属性检查

1
let mySquare = createSquare({ color: 'green', width: 10, height: 10 } as SquareConfig);

但是更好的做法是在接口中提供一个字符串索引签名

1
2
3
4
5
interface SquareConfig { 
width: number;
color?: string;
[propName: string]: any;
}

函数类型

接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。

参数列表里的每个参数都需要名字和类型。

1
2
3
interface TypeFunc {
(source: string, subString: string): boolean;
}

这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。

1
2
3
4
5
let mySearchFunc: TypeFunc;
mySearchFunc = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}

对于函数类型的类型检查来说,函数的参数名不需要与接口定义里的名字相匹配。

函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。如果没有指定类型,TypeScript 的类型系统会推断出参数类型,而函数的返回值类型是通过其返回值推断出来的。

可索引的类型

与使用接口描述函数类型差不多,我们也可以描述那些能够通过索引得到的类型。可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。

1
2
3
4
5
6
7
8
// 表明了当用 number 去索引 StringArr 时会得到 string 类型的返回值
interface StringArr {
[index: number]: string;
}

let myArr: StringArr;
myArr = ["A","B"];
let myStr: string = myArr[0];

索引签名共有两种类型:字符串和数字。可以同时使用这两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。这是因为当使用 number 来索引时,TypeScript 会将它转换为 string 然后再去索引对象。

1
2
3
4
interface StringArray { 
[index: string]: string;
[index: number]: string;
}

一般将索引签名设置为只读,这样可以防止给索引赋值。

类类型

TypeScript 中的接口也能够用来明确的强制一个类去符合某种协议/契约。

1
2
3
4
5
6
7
8
9
10
11
12
interface ClockInterface {
currentTime: Date;
setTime:(d: Date);
}

class Clock implements ClockInterface {
currentTime: Date;
setTime:(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {}
}

接口描述了类的公共部分,而不是公共和私有两部分,它不会帮你检查类是否具有某些私有成员。

类静态部分与实例部分的区别

类具有两个类型 :静态部分的类型和实例的类型。

当一个类实现了一个接口时,只对其实例部分进行类型检查。constructor 存在于类的静态部分,不在检查范围内。因此我们应该直接操作类的静态部分。

可以认为类的静态部分指的是类本身,实例部分指的是类实例化出来的对象。

继承接口

和类一样,接口也可以相互继承。这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

1
2
3
4
5
6
7
8
9
10
11
interface Father {
familyName: string;
}

interface Son extends Father {
givenName: string;
}

let aPerson = <Son>{};
aPerson.familyName = 'S';
aPerson.givenName = 'T';

一个接口可以继承多个接口,创建出多个接口的合成接口。

1
2
3
4
5
6
7
8
9
10
11
interface Shape {
color: string;
}

interface Stroke {
penWidth: number;
}

interface Square extends Shape, Stroke {
width: number;
}

混合类型

接口能够描述 JavaScript 中丰富的类型,可以使用 混合类型 来使某个对象具有多个类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Counter {
(start: number): number;
interval: number;
reset(): void;
}

function getCounter(): Counter {
let counter = <Counter>function (start: number) {};
counter.interval = 1;
counter.reset = function () {};
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 2;

接口继承类

当接口继承了一个类类型时,它会继承类的成员但不继承其实现。就好像接口声明了所有类中存在的成员,但没有提供具体实现一样。接口同样会继承到类的 privateprotected 成员。这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现。

当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定的属性时起作用。

ECMAScript 2015 , 也就是 ECMAScript 6 开始, JavaScript 中也能使用基于类的面向对象的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义一个类
class Person {
familyName: string;
constructor(familyName: string, givenName: string, age: number) {
this.familyName = familyName;
}
say() {
return 'Hello, my name is ' + this.familyName + ' ' + this.givenName;
}
move(distanceInMeters = 0) {
console.log('${this.name} moved ${distanceInMeters}m.');
}
}
// 创建类实例
lei aPerson = new Person('Wang', 'XiaoHong', 22);

在引用任何一个类的成员时都需要使用 this ,它表示我们访问的是类的成员。

继承

TypeScript 中允许使用继承来扩展现有的类。

1
2
3
4
5
6
7
8
class Student extends Person {
constructor(familyName: string, givenName: string, age: number) {
super(familyName, givenName, age);
}
move(distanceInMeters = 5) {
super.move(distanceInMeters);
}
}

如果子类包含了构造函数,它必须调用 super() ,它会执行基类的构造函数。在构造函数中访问 this 的属性之前,一定要先调用 super()

子类从父类中继承属性和方法

修饰符

  • public

    publicTypeScript 中类成员的默认属性

  • private

    类成员声明为 private 时,不能在生声明它的类的外部访问

  • protected

    类成员声明为 protected 时,在派生类中仍可访问

    构造函数也能被标记为 protected ,这意味着它不能在包含它的类外被实例化,但是能被继承

  • readonly

    通过 readonly 属性将属性设置为只读,只读属性必须在声明时或构造函数里被初始化

参数属性

参数属性通过给构造函数参数前添加一个访问限定符来声明。

1
2
3
4
class Student {
constructor(public familyName: string, public givenName: string, public age: number){
}
}

通过这个特性我们可以方便的在一个地方定义并初始化一个成员。

存取器

TypeScript 支持通过 getters/setters 来截取对对象成员的访问,它们可以有效的控制对对象成员的访问。

1
2
3
4
5
6
7
8
9
10
11
class student {
private _familyName: string;

get familyName(): string {
return this._familyNmae;
}

set familyName(fName: string) {
this._familyName = fName;
}
}

需要注意的是,存取器只支持 ECMAScript 5 或更高,其次,只带有 getter 存取器被推断为 readonly

静态属性

类的实例成员是那些仅当类被实例化时才会被初始化的属性,而类的静态成员存在于类本身上面而不是类的实例上。实例想要访问静态属性时,需要在其加上类名。

1
2
3
4
5
6
7
class student {
static school = 'ts';
...
getStudentSchool() {
return student.school;
}
}

抽象类

抽象类一般作为其它派生类的基类使用,一般不会被实例化。不同于接口,抽象类可以包含成员的实现细节。

使用 abstract 关键字定义抽象类和在抽象类中定义抽象方法。

1
2
3
abstract class Human {
abstract function_name: return_type;
}

抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。

抽象方法的语法与接口语法相似,两者都是定义方法签名但不包含方法体。然而,抽象方法必须包含 abstract 关键字并且可以包含访问修饰符。

函数

函数是 JavaScript 应用程序的基础。 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在 TypeScript 里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。 TypeScriptJavaScript 函数添加了额外的功能,让我们可以更容易地使用。

函数类型

1
2
3
function fun(arg_1: arg_type, arg_2: arg_type, ...): return_type {
...
}

TypeScript 能够通过返回语句自动推断出返回值类型,因此函数返回类型一般是省略的。

1
let fun_var_name: (arg_1: arg_type, arg_2: arg_type, ...) => return_type;

函数类型包括两部分,参数类型和返回值类型。而且只要参数类型是匹配的,就认为它是有效的函数类型,而不在乎参数名是否准确。

可选参数和默认参数

JavaScript 中函数的每个参数都是可选的,可传可不传,没有传参的时候它的值就是 undefined。而 TypeScript 中函数的每个参数默认情况下都是必须的,编译器检查用户是否为每个参数都传入了值,还会假设只有这些参数被传递进函数-即传递给一个函数的参数个数必须与函数期望的参数个数一致。

1
2
3
4
5
6
let myAdd: (x: number, y: number) => number = function (x: number, y: number) { 
return x + y;
}

const sum = myAdd(1); // error: Expected 2 arugements, but got 1
const sum1 = myAdd(1, 2, 3); // error: Expected 2 arugements, but got 3

TypeScript 中,可以在参数名称旁使用 ? 实现可选参数的功能。

1
2
3
4
5
6
7
// 可选参数
function buildStudent(name: string, grade: number, gender?: string) {
return name + ' ' + grade + ' ' + (gender ? gender : '');
}

const student_1 = buildStudent('Tom', 2);
const student_2 = buildStudent('Jerry', 2, 'male');

需要注意的是,可选参数必须跟在可选参数后面。

TypeScript 中,当用户没有传递这个参数或传递的值是 undefined 时,我们可以为参数提供一个默认值。在所有必须参数后面的带默认初始化的参数都是可选的,与可选参数一样,在调用函数的时候可以省略-即可选参数与末尾的默认参数共享参数类型。

需要注意的是,如果带默认值的参数出现在必须参数之前,用户必须明确的传入 undefined 值来获得默认值。

剩余参数

如果你想同时操作多个参数或不知道会有多少个参数传递进来,在 JavaScript 中,你可以通过 arguments 来访问所有传入的参数,在 TypeScript 中,你则可以将所有参数收集到一个变量里。

1
2
3
4
5
6
// 剩余参数
function buildChildren(name: string, age: number, ...restOfProperty: string[]) {
return `${name} ${age} ${restOfProperty.join(' ')}`;
}

const children = buildChildren('Mike', 12, 'male', 'grade 3');

泛型

泛型是程序设计语言的一种特性,是程序员在编写代码时定义的一些可变部分,这些部分在使用前必须做出指明。

1
2
3
4
5
6
7
// 类型变量 T 会捕获用户传入的类型,之后我们就可以使用者个类型
function identity<T>(arg: T): T {
return arg;
}

const strId = identity('JavaScript');
const numId = identity(12);

泛型变量

使用泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型-即你必须把这些参数当做是任意或所有类型。

泛型函数

1
2
3
4
5
6
7
function combineFunc<T,K>(x: T, y: K): [T, K] {
return [T, K];
}

let combine: <T,K>(x: T, y: K) => [T,K] = combineFunc;
// or
let combine: { <T,K>(x: T, y: K): [T, K] } = combineFunc;

泛型接口

1
2
3
4
5
interface CombineFunc {
<T,K>(x: T, y: K): [T, K];
}

let combine: CombineFunc = (x, y) => [x, y];

泛型类

1
2
3
4
5
6
class CombineFunC {
combine: <T,K>(x: T, y: K) => [T, K];
}

let combineC: CombineFunC = new CombineFunC<number, number>();
combineC.combine = (x, y) => [x, y];

枚举

使用枚举可以定义一些带名字的常量,清晰的表达意图或创建一组有区别的用例。TypeScript 支持数字的和基于字符串的枚举。

数字枚举

数字枚举默认的枚举值是从 0 开始自增长的,如果你为第一个枚举名字指定了一个数字枚举值,则会从这个数字值开始自增长。

1
2
3
4
5
6
enum Status {
DELETED = -1,
VALID,
UNPAID,
PAID
}

字符串枚举

字符串枚举中,每个成员都需要使用字符串字面量或另外一个字符串枚举成员进行初始化。

需要注意的是,字符串枚举没有自增长行为。

1
2
3
4
5
6
enum StatusTip {
DELETED = 'Order has been deleted.',
VALID = 'Order is valid.',
UNPAID = 'Order is unpaid.',
PAID = 'Order is paid'
}

异构枚举

枚举可以混合字符串和数字成员。

1
2
3
4
enum HeterogeneousEnum {
NO = 0,
YES = 'Yes'
}

计算的和常量成员

每个枚举成员都带有一个值,它可以是常量或计算出来的。当满足如下条件时,枚举成员被当做常量:

  • 它是枚举的第一个成员,且没有初始化器,此时它的值为 0

  • 它不带有初始化器且它之前的枚举成员是一个数字常量,此时它的值为上一个枚举成员的值 + 1

  • 枚举成员使用常量枚举表达式初始化。常量枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足以下条件时,它就是一个常量枚举表达式:

    • 一个枚举表达式字面量

    • 一个对之前定义的常量枚举成员的引用

    • 带括号的常量枚举表达式

    • 一元运算符 + - ~ 其中之一应用在了常量枚举表达式

    • 常量枚举表达式作为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^ 的操作对象(若常量枚举表达式求值后为 NaNInfinity,则会在编译时报错)

      1
      2
      3
      4
      5
      6
      7
      enum FileAccess {
      None,
      Read = 1 << 1,
      Write = 1 << 2,
      ReadWrite = Read | Write,
      G = '1024'.length
      }

联合枚举与枚举成员的类型

存在一种特殊的非计算的常量枚举成员的子集:字面量枚举成员。字面量枚举成员是指不带有初始值的常量枚举成员或是被初始化为

  • 任何字符串字面量
  • 任何数字字面量
  • 应用了一元 - 符号的数字字面量

当所有枚举成员都拥有字面量枚举值时,它就带有了一种特殊的语义。

运行时枚举

枚举是在运行时真正存在的对象

反向映射

数字枚举成员具有从枚举值到枚举名字的反向映射。

1
2
3
4
5
6
7
8
enum Status {
DELETED = -1,
VALID,
UNPAID,
PAID
}

console.log(Status[0]); // VALID

const 枚举

常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除-常量枚举成员会在使用的地方被内联进来,这是因为常量枚举不允许包含计算成员。

1
2
3
4
5
6
7
8
const enum Directions {
Up,
Down,
Left,
Right
}

let directions = [ Directions.Up, Directions.Down, Directions.Left, Directions.Right ];

类型推论

类型推论是指能够在编译期间自动推导出值的类型的能力。一般发生在初始化变量和成员,设置默认参数值和决定函数返回值时。

最佳通用类型

当需要从几个表达式中推断类型时,会使用这几个表达式的类型来推断出一个最合适的通用类型。

上下文类型

当表达式的类型与所处的位置相关时,类型推论按照上下文归类来推论类型。当然,如果表达式包含了明确的类型信息,上下文的类型会被忽略。

类型兼容性

TypeScript 中的类型兼容性是基于结构子类型的-结构子类型是一种只使用其成员来描述类型的方式。

原始类型和对象类型的兼容性

如果 x 要兼容 y, 那么 y 至少需要具有与 x 相同的属性。

1
2
3
4
5
// 如果 x 要兼容 y, y 至少要与 x 拥有相同的属性
let x = { name: 'x' };
let y = { name: 'y', alias: 'Y' };
x = y;
y = x; // Property 'alias' is missing in type '{ name: string; }' but required in type '{ name: string; alias: string; }

函数的类型兼容性

如果函数 x 要兼容函数 y,那么 x 至少要与 y 具有相同的参数并且返回值类型相同。

1
2
3
4
5
6
7
8
let funCom = (x: number, y: number) => x + y;
let funAbc = (x: number, y: number) => [x, y];
let funSub = (x: number) => x;
let funDef = (x: number, y: number) => x * y;
funCom = funSub;
funSub = funCom; // Type '(x: number, y: number) => number' is not assignable to type '(x: number) => number'
funCom = funAbc; // Type '(x: number, y: number) => number[]' is not assignable to type '(x: number, y: number) => number'.Type 'number[]' is not assignable to type 'number'
funCom = funDef;

类的兼容性

比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class W {
static sign: string = 'AAAAA';
pro: string = 'W';
constructor(name: string){}
}

class Y {
pro: string = 'W';
constructor(length: number) {}
}

let wC = new W('W');
let yC = new Y(2);

wC = yC; // OK
yC = wC; // OK

类的私有成员和受保护成员会影响兼容性。 当检查类实例的兼容时,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。

泛型的兼容性

1
2
3
4
5
6
interface NotEmpty<T> {
}

let gA: NotEmpty<number>;
let gB: NotEmpty<string>;
gA = gB; // ok
1
2
3
4
5
6
7
interface NotEmpty<T> {
data: T;
}

let gA: NotEmpty<number>;
let gB: NotEmpty<string>;
gA = gB; // Error

高级类型

交叉类型

交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。

联合类型

联合类型表示一个值可以是几种类型之一。

1
2
3
4
5
6
7
8
/ 联合类型
// 联合类型表示一个值可以是几个类型之一
function pad(padding: number | string) {
return padding;
}

console.log(pad(1));
console.log(pad('A'));

如果一个值是联合类型,我们只能访问联合类型所有类型里共有的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Elephant {
name: string;
walk();
}

interface Shark {
name: string;
swim();
}

function getAnimal(): Elephant | Shark {
return { name: 'Elephant', walk() { console.log('walk') } } as Elephant;
}

let aAnimal = getAnimal();
console.log(aAnimal.name);
aAnimal.walk();
aAnimal.swim();

Reflow and Redraw

回流和重绘以及性能的优化

在解释回流和重绘的概念之前,我们首先需要知道两个概念:DOM 和 CSSOM。

  • DOM (Document Object Model) 文档对象模型

    DOM 是 HTML 和 XML 文档的编程接口,它提供了对文档的结构化表述,并使得开发人员能够从程序中访问它,改变文档的结构、样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。

  • CSSOM (CSS Object Model) CSS对象模型

    CSSOM 是一组允许 JavaScript 操作 CSS 的 API,非常类似于 DOM,它允许用户动态的修改 CSS 样式。

DOM 和 CSSOM 构建

浏览器渲染页面时会构建 DOM 和 CSSOM 树,这个过程可简化如下:

  1. 转换 - 浏览器从磁盘或网络读取 HTML 的原始字节,并按照其指定的编码方式将其解析为字符
  2. 符号化(令牌化) - 浏览器将字符串转换为 HTML 标准规定的各种具有特殊含义和规则的令牌
  3. 词法分析- 将令牌转换为定义了其规则和属性的对象
  4. DOM构建 - HTML 标记定义了不同的标记之间的关系,根据标签创建的对象在结构树中根据由原始标记定义的父子关系相互连接。

整个过程最后形成一个文档对象结构模型,即 DOM。CSSOM 的构建与 DOM 的构建非常类似,不再重复。

渲染树的构建

最后 CSSOM 和 DOM 会结合形成渲染树。渲染树用来计算每个可见元素的布局,并在绘制到屏幕的过程中作为输出,简化后的过程如下:

  1. 从 DOM 树的根节点开始,遍历每个可见的节点
    • 有些节点是不可见的(比如 <script> 标签、<meta> 标签 等),这些节点会被忽略, 因为它们并不影响渲染的输出
    • 一些节点通过 CSS 被隐藏了,它们也会被忽略
  2. 为每一个可见的节点找到匹配的 CSSOM 规则并在节点上应用这些规则
  3. 将节点连同内容和计算好的样式发送出去

渲染树构建完成后,即进入 “布局” 阶段。

布局

在渲染树中我们已经计算好了哪个节点应该被显示和它们的计算样式,但是对于它们在设备的视口中的精确位置和大小还没有进行计算,对其进行计算就是 ”布局“,也被称为 回流

为了计算页面中每个对象的精确位置和大小,浏览器从渲染树的根节点开始遍历。

布局过程的输出是一个“盒子模型”,它精确地捕获视口中每个元素的确切位置和大小:所有相对测量值都转换为屏幕上的绝对像素。

绘制

布局完成后,我们已经知道了节点的可见性、计算样式和几何结构,浏览器将会把渲染树中的每个节点转换为屏幕上的实际像素。这个步骤通常被称为 ”绘制“ 或 ”栅格化“;

由于浏览器需要进行大量的工作进行绘制,这个阶段可能会花费一些时间。

回流

当渲染树中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。

可能会引起回流的操作如下

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容发生改变
  • 元素字体大小发生变化
  • 添加或删除可见的 DOM 元素
  • 激活 CSS 伪类
  • 查询某些属性或调用某些方法

常用且可能导致回流的属性和方法

  • clientWidth , clientHeight, clientTop, clientLeft
  • offsetWidth, offsetHeight, offsetTop, offsetLeft
  • scrollWidth, scrollHeight, scrollTop, scrollLeft
  • scrollIntoView(), scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientReact()
  • scrollTo()

重绘

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:colorbackground-colorvisibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

性能影响

回流比重绘的代价要高的多。有时候仅仅回流一个单一的元素,它的父元素及任何与它相关的元素都会产生回流。

浏览器会对频繁的回流或重绘操作进行优化:

浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的时候,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

当你访问下列属性是,浏览器会立刻清空队列:

  • clientWidth , clientHeight, clientTop, clientLeft
  • offsetWidth, offsetHeight, offsetTop, offsetLeft
  • scrollWidth, scrollHeight, scrollTop, scrollLeft
  • width, height
  • getComputedStyle()
  • getBoundingClientRect()

因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。

如何避免回流

CSS

  • 避免使用table布局。
  • 尽可能在DOM树的最末端改变class
  • 避免设置多层内联样式。
  • 将动画效果应用到position属性为absolutefixed的元素上。
  • 避免使用CSS表达式(例如:calc())。

JavaScript

  • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
  • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
  • 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

NPM 命令

NPM

npm 安装与升级

1
2
3
4
// 获取当前使用的 npm 的版本
npm -v
// 升级 npm
npm install npm -g

package 安装与卸载

本地安装将会把安装包安装在运行 npm 命令时所在目录的 ./node_modules 目录下;
全局安装则会将安装包安装在你的 node 所在目录;

1
2
3
4
npm install <package name>		// local
npm install <package name> -g // global
npm install <package name> --save-dev | -D// development
npm install <package name> --save | -S // production

通过命令 npm install <package name> —save 安装的 package 会被加到 package.json 文件中的 dependencies 部分,而通过命令 npm install <package name> —save-dev 安装的 package 会被加到 devDependencies 部分。
命令如果不加 —save 或 —save-dev 则 package 不会被添加到 package.json 文件中

卸载某个 package

1
npm uninstall <package name>

更新某个 package

1
npm update <package name>

安装最新版本的 package

1
npm install <package name>@latest

安装指定版本号的 package

1
npm install <package name>@[verion number]

查看信息

可以通过命令来查看已经安装的 packeage

1
2
3
4
npm list
npm list -g
# --depth 表示深度,深度为 0 时,不显示依赖模块
npm list --depth=0 -g

查看某个 package 是否安装或查看其版本号(若已存在)

1
npm list <package name>

查看某个 package 是否存在

1
npm ls

搜索某个模块

1
npm search <package name>

查看某个 package 的路径

1
npm root <package name>

切换安装源

1
2
3
4
# 淘宝镜像
npm config set registry https://registry.npm.taobao.org
# 官方源
npm config set registry https://registry.npmjs.org/

HTML5 概览进阶篇

构建响应式网站

响应式网站设计(Responsive Web design)的理念是:页面的设计与开发应当根据用户行为以及设备环境(系统平台、屏幕尺寸、屏幕定向等)进行相应的响应和调整。具体的实践方式由多方面组成,包括弹性网格和布局、图片、CSS media query 的使用等。无论用户正在使用笔记本还是iPad,我们的页面都应该能够自动切换分辨率、图片尺寸及相关脚本功能等,以适应不同设备;换句话说,页面应该有能力去自动响应用户的设备环境。响应式网页设计就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本。这样,我们就可以不必为不断到来的新设备做专门的版本设计和开发了。

可伸缩的图像

默认情况下, 图像显示的尺寸是 HTML 中指定的 width 和 height 属性值。如果不指定这些属性值,图像就会自动按照其原始尺寸显示。此外,你还可以通过 CSS 以像素为单位设置 width 和 height。

显然,当屏幕宽度有限的时候,按原始尺寸显示图像就不一定合适了。使用可伸缩图像技术就可以让图像在可用空间内缩放,但不会超过其本来的宽度。可用空间是由包含图像的元素决定的,它们根据包含它们的元素的尺寸按比例缩放。

1
2
3
.photo-full {
max-width: 100%;
}

图像缩放的可用空间是由其父元素建立的内容区域。如果父元素有水平方向上的内边距,可用空间就会相应减小。

可以使用 background-size 属性对背景图像进行缩放(对 IE8 无效)。

弹性布局

拥有定宽容器的网页对响应式页面来说并不合适,我们希望页面能进行缩放,并正好适应浏览器视觉区域大小,流式布局就可以做到这一点。

创建弹性布局需要使用百分数宽度,并将它们应用于页面里的主要区域。

元素的百分数宽度基于其父元素提供的可用空间。

1
2
3
.div-flex {
width: 60%;
}

还可以对元素设置基于百分数的 margin 和 padding 值。

媒体查询

媒体查询增强了媒体类型方法,允许根据特定的设备特性定位样式。 要调整网站的呈现样式,让其适应不同的屏幕尺寸,采用媒体查询特别方便。下面列出了可以包含在媒体查询里的媒体特性 :

  • width/height/device-width/device-height
  • orientation/aspect-ratio/device-aspect-ratio
  • color/color-index/monochrome
  • resolution/scan/grid
  • -wbkit-device-pixel-ration/-moz-device-pixel-ration

除了 orientation/scan/grid 以外,上述属性均可添加 min- 和 max- 前缀。

  • 指向外部样式表的链接

    1
    <link rel="stylesheet" media="logic type and (feature: value)" href="stylesheet.css" />
  • 关于样式表中的媒体查询

    1
    2
    3
    4
    5
    6
    // logic 取值为 only 或 not 或 and
    // type 媒体类型
    // feature 预定义的媒体特性
    @media logic type (feature: value) {
    /* 目标 CSS 样式规则写在这里 */
    }

下面是一个例子

1
2
// 仅当媒体类型为 screen 且视觉区域最小宽度为 480px 时加载并使用
<link rel="stylesheet" media="only screen and (min-width: 480px)" href="style-480.css" />

可以使用 and 将多个特性和值的组合串接起来,还可以创建一系列媒体查询(使用逗号分隔每个媒体查询)。在用逗号分隔的媒体查询列表中,如果有一个媒体查询为真,则整个媒体查询列表为真。

1
2
3
<link rel="stylesheet" media="only screen and (min-width: 480px) and (max-width: 767px)" href="style.css" />
//
<link rel="stylesheet" media="only print and (color)" href="color-page.css" />

通过媒体查询,可以根据设备的媒体属性应用不同的样式。尽管媒体查询包含了很多功能,但其中 min-width 和 max-width 是创建响应式网页时用的最多的。

有时你可能希望为高像素密度设备设定样式。一种常见的用例是为这样的显示屏准备双倍尺寸(2x)的 sprite,从而让图像显得更锋利。

假设你的 sprite 的原始尺寸是 200 像素 ×150 像素,其中每个图像都用 1 像素分隔。创建一个双倍大小的版本(400 像素 ×300 像素),每个图像之间就有 2 像素的间隔。同时, sprite 中的每个图像都是原始尺寸的 2 倍。针对高像素密度设备,可以使用下面的媒体查询:

1
2
3
4
5
6
7
@media (-o-min-device-pixel-ratio: 5/4),
(-webkit-min-device-pixel-ratio:1.25),
(min-resolution: 120dpi) {
.class {
···
}
}

需要注意, 就是 Internet Explorer 8 及以下的版本不支持媒体查询。这意味着这些浏览器只会呈现媒体查询以外的样式,即基准样式。

使用 CSS3 进行增强

网站制作者多年来面临的挑战之一就是,使用 CSS 建立丰富布局的选择是有限的。在大多数情况下,要建立丰富的布局,就需要使用额外的 HTML、CSS 及大量图像。这样做的结果就是,页面变得更为复杂,可访问性降低,浏览器需要花费更长的时间下载和显示页面,同时,页面变得更为脆弱,更难维护。

近年来,浏览器快速吸纳了很多新的 CSS3 属性,让上述情况有了改观。如今,仅使用 CSS 创建圆角、渐变和阴影以及调整透 明度等已经变成现实。结果是网页可以使用更少的标记和图像,这样加载的速度也会变快。

渐进增强和 polyfill

渐进增强强调创建所有用户都能访问的基本层面内容和功能,同时为更强大的浏览器提供增强的体验。总结来说,就是网站在不同 Web 浏览器中的外观和行为不一样是完全可以接受的,只要内容是可访问的。

如果你想弥合较弱的浏览器和较强的浏览器之间的差距,可以使用 polyfill 。polyfill 通常使用 JavaScript 来实现,它可以为较弱的浏览器提供一定程度的对 HTML5 和 CSS3 的 API 和属性的支持。同时,当浏览器本身就具有相应的能力时,会不动声色地退而使用官方的支持。需要注意的是这样所通常会对性能产生一定的影响,因为较弱的浏览器运行 JavaScript 的速度要慢很多。

理解厂商前缀

CSS3 规范要达到 W3C 的推荐标准(即定稿)状态要经过数年。浏览器则通常在 W3C 开发标准的过程中就会提前实现这些特性。这样,标准在最终敲定之前就能知道哪些地方还能进一步改进。

在包含某个特性的初始阶段,浏览器通常会使用厂商前缀实现这类特性。这样,每个浏览器都可以引入自己的 CSS 属性支持方式,从而可以获取反馈, 而且一旦标准发生改变也不会造成影响。

每个出浏览器都有自己的前缀:-webkit- (WebKit/Safari/及版本的 Chrome)、-moz- (Firefox)、-ms- (Internet Explorer)、-o-(Opera)。应该将前缀放在 CSS 属性名的前面。

如今大多数情况下,一般只需要 -webkit 前缀,而且并非所有的 CSS3 属性都需要使用为浏览器准备的前缀。

为元素创建圆角

使用 CSS3,可以在不引入额外的标记或图像的情况下,为大多数元素(包括表单元素、 图像,甚至段落文本)创建圆角 。

1
2
3
4
5
6
7
8
9
.all-corners {
-webkit-border-radius: 20px;
border-radius: 20px;
}

.left-corner {
-webkit-border-top-left-radius: 40px;
border-top-left-radius: 40px;
}

同 border、margin 和 padding 属性一样,border-radius 属性也有简写方式 :

1
2
3
4
.corner {
border-radius: 10px 20px; // 左上方、右下方设置为 10px,右上方、左下方设置为 20px
border-radius: 20px 0 0 // 左上方设置为 20px ,其它为 0
}

创建四个相同的圆角

1
2
3
4
5
// r 为圆角半径
.corner {
-webkit-border-radius: r;
border-radius: r;
}

创建一个圆角

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// r 为圆角半径
.corner-top-left {
border-top-left-radius: r;
}

.corner-top-right {
border-top-right-radius: r;
}

.corner-bottom-left {
border-bottom-left-radius: r;
}

.corner-bottom-right {
border-bottom-right-radius: r;
}

创建椭圆圆角

1
2
3
4
// x 为水平方向上的半径大小,y 为垂直方向上的半径大小
.corner-ellipse {
border-radius: x/y;
}

不支持 border-radius 的旧的浏览器仅会以方角呈现元素。

添加阴影

使用 text-shadow 为段落、标题等元素中的文本添加动态的阴影效果。

1
text-shadow: x-offset y-offset blur-radius color;   

将 text-shadow 改回默认值

1
text-shadow: none;

使用 box-shadow 属性则可以为元素本身添加阴影 ,同时还允许使用两个可选的属性 inset 和 spread。

1
box-shadow: x-offset y-offset blur-radius inset spread color;

多重背景

为单个 HTML 元素指定多个背景是 CSS3 引入的一个特性。 通过减少对某些元素的需求并制定多重背景便可简化 HTML 代码,并让它容易理解和维护。而且多重背景几乎可以用于任何元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.weather {
background-color: #FFF;
background-image:
url(fine.png),url(rain.png),
url(cloud.png),url(fog.png);
background-positin:
50% 102%,100% -150px,
0 -150px,50% 100%;
background-repeat:
no-repeat,no-repeat,
no-repeat,repeat-x;
}
// 或
.weather {
background-color: #FFF;
background:
url(fine.png) no-repeat 50% 102%,
url(rain.png) no-repeat 100% -150px,
url(cloud.png) no-repeat 0 -150px,
url(fog.png) repeat-x 50% 100%;
}

渐变背景

渐变背景也是 CSS3 中的新特性,通过它可以在不使用图像的情况下创建从一种颜色到另一种颜色的过渡。

CSS 渐变是一个背景图像,因此,属性既可以是 background 简记法,也可以是 background-image。应该始终包含一个为旧浏览器准备的基准 background 定义,并放置在渐变背景的定义之前。默认情况下,线性渐变是从上往下渐变的,因此在属性值中不需要指定to bottom。如果要使用相反的方向,则使用to top。

使用CSS创建渐变有两种主要的方式:线性渐变和径向渐变,每种方式都有不同的必选参数和可选参数。除非指定一种颜色向另一种颜色过渡的位置,否则浏览器会自行决定不同颜色之间的过渡。

使用关键字创建对角线方向的渐变:

1
2
3
4
background: linear-gradient(to bottom right,color,color);
background: linear-gradient(to bottom left,color,color);
background: linear-gradient(to top right,color,color);
background: linear-gradient(to top left,color,color);

指定渐变角度数

1
background: linear-gradient(90deg,color,color);

数值代表的是圆周上的点的位置:0 代表最顶端的点,90 代表最左边的点,180 代表最底端的点,270 代表最右边的点。

根据渐进增强的原则,最好为不支持背景渐变属性的浏览器提供一个备用选项。在 CSS 中,它可以位于背景渐变规则的前面。

设置不透明度

使用 opacity 属性可以修改元素(包括图像)的透明度。其默认值为 1,可以使用 0.00 至 1.00 之间的两位小数。

1
opacity: o;

HTML5 概览

学习HTML 5 与 CSS 过程中的的基础知识的概要总结。

网页的构造块

尽管网页变得越来越复杂,但是构成它的低层结构依旧十分简单,归纳起来,一个网页主要包括三个部分:

  • 文本内容 - 页面内容纯文字
  • 对其它文件的引用 - 如对文件、音视频文件、样式表和 JavaScript 文件的应用
  • 标记 - 描述文本并确保引用正确工作

以上内容都仅由文本构成,因此网页可以保存为纯文本格式。以下是一个基本的 HTML 页面 :

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title> This is a simle html page </title>
</head>
<body>
</body>
</html>

标签 :元素、属性、值及其它

  • 元素 :大多数元素既包含文本,也包含其它元素

    1
    2
    3
    4
    // 习惯上,标签采用小写字母
    <em>content</em>
    // 空元素既不包含文本也不包含其它元素,比如 img
    <img src="img.jpg" width="300" height="300" alt="img" />
  • 属性和值 :属性包含了元素的额外信息(尽量使用小写字母来编写属性的名称),有的属性可以接受任何值,但是最常见的还是仅接受预定义值的属性

    1
    <em property-name=property-value ··· ></em>

    元素可以有多个属性,每个属性都有自己的值,属性的排列顺序并不重要,它们之间用空格隔开。

    布尔属性的值时可选的,因为只要这种属性只要出现就表示其值为真

    1
    <input type="email" name="emailaddr" required />
  • 父元素和子元素 :如果一个元素包含另一个元素,它就是被包含元素的父元素。

网页的文本内容

元素中包含的文本可能是网页上最基本的成分,不过,当浏览器呈现HTML时,会把文本中的多个空格或者制表符压缩成单个空格,把回车符和换行符转换成单个空格或者直接忽略。

链接、图像和其它非文本内容

网页上的图像、视频、音乐等外部文件实际上并没有放在HTML文件中,而是单独保存的,页面只是简单地引用了这些文件。

文件名和文件夹名

在我们开始写一个html文件之前,需要知道如何命名文件和文件夹才能更加有助于组织文件、使访问者更容易找到并访问你的页面,确保它们的浏览器能够正确地处理页面,以及增强搜索引擎优化。

  1. 文件名采用小写字母
  2. 使用正确的扩展名
  3. 用段横线分割单词

URL

URL即 Uniform Resource Locator,统一资源定位符,它包含了关于文件存储位置和浏览器应该如何处理它的信息。

互联网上的每个文件都有唯一的URL。

1
2
3
4
5
6
// 第一部分称为模式,浏览器如何处理需要打开的文件,最常见的模式是HTTP、ftp、mailto
// 第二部分是文件所在的主机名称
// 第三部分是路径,路径包含了到达这个文件的文件夹以及文件自身的名称
"http://www.site.com/home/index.html"
"ftp://ftp.site.com/pub/proposal.pdf"
"mailto:somename@somedomain.com"

有时,URL路径不以文件名结尾,而以一个目录结尾,在这种情况下,URL值得是路径中最后一个目录中的默认文件,通常为index.html。

URL可以使绝对的,也可以是相对的。绝对URL包含了指向目录或文件的完整信息,包括模式、主机名和路径。而相对URL则以自身的位置为参考点,描述文件的位置。

相对URL可以

  • 引用同一目录下的文件

    如果目标文件与当前页面在同一个目录中,那么这个文件就只有文件名和扩展名

  • 引用子目录下的文件

    如果目标文件在当前目录的子目录中,则相对 URL 为 “子目录名/文件名+扩展名”

  • 引用上层目录的文件

    目标文件在当前目录的父目录中,则 “../文件名+扩展名”

基本HTML结构

每个HTML文档都应该包含以下基本成分

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>		// 声明页面为HTML5文档
<html lang="en"> // lang="en" 指定页面内容的默认语言
<head>
<meta charset="utf-8" /> // charset="utf-8" 声明文档的字符编码
<title></title> // 页面的标题
</head>
<body>

</body>
</html>

在文档的head部分,通常要致命页面标题,提供为搜索引擎准备的关于页面本身的信息,加载样式表,以及加载 JavaScript 文件(通常处于性能考虑,大多数时候在页面底部 </body> 结束后才加载 JavaScript)。

页面标题

每一个HTML页面都必须有一个title 元素,标题应该是简短的、描述性的,而且是唯一的。页面标题通常是搜索引擎的搜索结果中链接的文字,它是判断搜索结果这种页面相关度的重要因素。

值得注意的是,标题中不能包含任何格式、HTML、图像或指向其它页面的链接。

分级标题

HTML中有六级标题用于创建页面信息的层级关系,使用好h1、h2 ··· h6 来表示。

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>		
<html lang="en">
<head>
<meta charset="utf-8" />
<title>分级标题</title>
</head>
<body>
<h1> 第一级标题 </h1>
<h2 lang="es"> 第二级标题 </h2>
</body>
</html>

对于任何页面来说,分级标题都可以说是最重要的HTML元素,由于标题通常传达的是页面的主题,如果这些标题与搜索词匹配,就会被搜索引擎赋予很高的权重。

创建分级标题时,要避免跳过某些级别,尽管这样做是可以的。

创建页眉

一个页面可以有任意数量的header元素,它们的含义可以根据其上下文而有所不同,处于页面顶端的或接近这个位置的header是整个页面的页眉。通常,页眉包裹网站标志、主导航和其它全站链接。

1
2
3
4
5
6
7
8
9
10
···
<body>
<header role="banner"> // 可选的 role=“banner” 显式的指出该页眉为页面级别的页眉
<nav>
<ul>
<li></li>
</ul>
</nav>
</header>
</body>

header 也很适合对页面深处的一组介绍性或导航型内容进行标记,但是,你需要只在必要的时候才使用header,大多数情况下使用 h1 ~ h6 就能满足要求。而且需要注意的是,header 与 h1 ~ h6 元素中的标题是不能互换的,它们都有各自的语意目的。

不能在 header 里嵌套 footer 元素或另一个 header,也不能在 footer 或 address 元素里嵌套 header。

标记导航

HTML的早期版本中没有元素明确表示主导航链接的区域,在HTML5中引入了 nav 元素来表示。nav 中的链接可以指向页面中的内容。但应仅对文档中重要的链接群使用 nav。

1
2
3
4
5
6
7
8
9
10
···
<body>
<header role="banner">
<nav role="navigation">
<ul>
<li></li>
</ul>
</nav>
</header>
</body>

需要注意的是,nav 元素不会对其内容添加任何默认样式。

HTML5 规范不推荐对辅助性的页脚链接使用 nav,而且也不允许 nav 嵌套在 address 元素中。

标记页面的主要区域

大多数网页都有一些不同的区域,如页眉、页脚、包含额外信息的附注栏、指向其它网站的链接等等,不过一个页面只有一个部分代表其主要内容,可以将这样的内容放在 main 元素中,该元素在一个页面中仅使用一次。main 元素是HTML5中新添加的元素。

1
2
3
4
5
<body>
<main role="main">
// 页面的主要内容
</main>
</body>

与 p、header、footer 等元素一样,main 元素的内容显示在新的一行,除此之外不会影响页面的任何样式。

如果创建的是 Web 应用,则应使用 main 包围其主要的功能。

不能将 main 放置在 article、aside、footer、header 或 nav 元素中。

创建文章

HTML5 的另一个新元素 article :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
···
<body>
<header>
<nav role="banner">
// 包含多个链接的导航
</nav>
</header>
<main role="main">
<article>
<h1> ··· </h1>
<p> ··· </p>
<p> ··· </p
···
</article>
</main>
</body>

在HTML5 中,article 并不仅仅用于包含像报纸文章一样的内容,钙元素的严格定义如下

articile 元素表示文档、页面、应用或网站中一个独立容器,原则上是可独立分配或可再用的,就像聚合内容中的各部分。

定义区块

HTML5 中的新元素 section ,用来代表文档或应用中的一个一般区块。在这里,section 是具有相似主题的一组内容,通常包含一个标题。

尽管将 section 定义为 ‘通常的’ 区块,但是不要将它与 div 元素混淆。从语义上讲,section 标记的是页面中的特定区域,而 div 则不传达任何语义。

如果只是出于添加样式的原因要对内容添加一个容器,应使用 div 而不是 section。

可以将 section 嵌套在 article 里,从而显式地标出报告、故事、手册等文章中的不同部分或不同章节。

指定附注栏

有时候,页面中有一部分内容与主体内容相关性没有那么强,可以独立存在,则可以使用 aside 元素来标记它们。

使用 aside 的例子还包括重要引述、侧栏、指向相关文章的一组链接、广告、nav 元素组等。

在HTML中,应该将附注栏内容放在 main 的内容之后,处于 SEO 和可访问性的目的,最好将重要的内容放在前面,你可以通过 CSS 来改变它们在浏览器中的显示顺序。

对于与内容有关的图像,使用 figure 而非 aside。

HTML5 不允许将 aside 元素嵌套在 address 元素中。

创建页脚

元素 footer 同 header 一样,不仅仅只能用在页面底部标记页脚,还可以用在其它地方。footer 元素代表嵌套它的最近的 article、aside、blockquote、body、details、fieldset、figure、nav、section 或 td 元素的页脚。

如果一个 footer 抱着它所在区块的所有内容,它代表的是像附录、索引、版权页、许可协议这样的内容。

不能在 footer 里嵌套 header 或另一个 footer,同时,也不能将 footer 嵌套在 header 或 address 元素里。

创建通用容器

有时候需要在一段内容外围包一个容器,从而可以为其应用 CSS 样式或 JavaScript 效果,div 是一个完全没有任何语义含义的容器,是一个通用容器。

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
... 
<body>
<div>
<header role="banner">
<nav role="navigation">
... [包含多个链接的无序列表] ...
</nav>
</header>

<main role="main"> <article>
<h1 id="gaudi">Barcelona's ➝ Architect</h1>
... [文章的其余内容] ...
<h2 id="sagrada-familia" lang="es"> ➝La Sagrada Família</h2>
... [图像和段落] ...
<h2 id="park-guell">Park Guell</h2>
... [另一个图像和段落] ... </article>
</main>
<aside role="complementary">
<h1>Architectural Wonders of ➝ Barcelona</h1>
... [aside的其余内容] ...
</aside>

<footer role="contentinfo">
<p><small>&copy; Copyright All About ➝ Gaudí</small></p>
</footer>
</div>
</body>
</html>

值得注意的是,div 元素自身没有任何默认样式,知识器其包含的内容从新的一行开始,不过我们可以对 div 添加样式实现自己的设计。div 对使用 JavaScript 实现一些特定的交互行为或效果也是有帮助的。

尽管始终强调 HTML 用于对内容的含义进行描述,但 div 并不是唯一没有语义价值的元素。span 是与 div 对应的一个元素: div 是块级内容的无语义容器,而 span (写作 这里是内容 )则是短语内容的无语义容器,例如它可以放在段落元素 p 之内。

使用ARIA改善可访问性

WAI-ARIA(Web Accessibility Initiative’s Accessible Rich Internet Applications),无障碍网页倡议,无障碍的富互联网应用。ARIA 是一种技术规范,在HTML 提供相应的语义功能之前,它会使用属性来填补一些语义上的空白。

无障碍访问的意义是让所有的访问者都能获取网站的内容。在大多数情况下,让页面具有无障碍访问的功能并不难。只需对内容使用恰当的 HTML 进行标记,就能改进网站的可访问性。

地标角色

ARIA 中的地标角色可以帮用户识别页面区域,对这些区域指定 role 属性就可以了。

地标角色 如何使用及何时使用
role=”main” :文档的主要内容 与 main 元素是对应关系。最好将其添加到 main 元素,也可以添加到其它表示主体内容的元素,在买个页面只使用一次。
role=”complementary” : 补充性内容 与 aside 元素是对应关系。应将其添加到 aside 或 仅含补充性内容的 div 元素。与 nav 元素是对应关系。
role=”contentinfo” : 内容信息 将其添加至整个页面的页脚,通常为 footer 元素,每个页面仅使用一次。
role=”navigation” : 导航 应将其添加到每个 nav 元素,或其它包含导航型链接的容器。
role=”banner” : 横幅 将其添加到页面级的 header 元素,每个页面只使用一次

即便不适用 ARIA 地标角色,页面看起来也没有任何差别,但是使用它们可以提升使用辅助设备的用户的体验。

为元素指定类别或 ID 名称

可以给 HTML 元素分配唯一的标识符,或指定其属于某个类别,或同时使用标识符和类别。添加 ID 和类别后,就可以对具有给定 ID 或 class 名称的元素添加样式、创建指向具有特定 id 的元素的链接或使用 JavaScript 获取 id 和 class 属性从而对元素添加特定的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
···
<body>
<div class="container"> // 如果要制定多个类别,用空格将不通过的分类名称分开即可
<header role="banner">
<nav role="navigation">
</nav>
</header>

<main role="main">
<article class="architect gaudi">
<h1 id="gaudi"></h1> // id 几乎可以使任何字符,只要不以数字开头且不包含空格。
<h2 id="sagrada-familia"></h2>
</article>
</main>
</div>
</body>

为一个或多个元素添加 class 属性,就可以对所有这类元素统一进行格式化。

HTML 文档中的每个 id 都必须是唯一的,并且每个元素都只能有一个 id。与此相反的是,一个 class 名称可以分配给页面中任意数量的元素,并且一个元素可以有一个以上的 class。

建议你无论打算如何使用 id 和 class,都应该为它们选择有意义的名称。

为元素添加 title 属性

可以使用 title 属性为网站上任何部分加上提示标签,不过用的最多的是链接。

1
2
3
4
···
<ul title="table of contents">

</ul>

添加了 title 属性的元素,在用户将访问者将鼠标移到该元素上时,会提示该 title。

添加注释

可以在 HTML 文档中添加注释,表明区块开始和结束的位置,提示某段代码的意图,或者阻止内容显示等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
<!-- ==== 开始主体内容 ==== -->
<main role="main">
<article class="architect">
<h1 id="gaudi">Barcelona's Architect</h1>
<!-- 这一段不会显示出来,因为它被注释掉了
<p>Antoni Gaudí's incredible buildings bring millions of tourists to Barcelona each year. </p>
-->
<p>Gaudí's non-conformity, already visible in his teenage years
...
</p>
...
</article>
</main>
<!-- 结束主体内容 -->
<!-- ==== 开始附注栏 ==== -->
... [附注栏内容] ...
<!-- 结束附注栏 -->
...

注释是不可见的,如果将一些内容注释掉,它们将不会显示出来。

在主要区块的开头和结尾添加注释是一种常见的做法,可以让你或和你一起合作开发的人员将来修改代码变的更加容易。

注释不能嵌套在其它注释里。

文本

除非网站添加了太多视频和图片,否则网页的大部分内容还是文本,针对不同的文本类型,选择不同的 HTML 语义化元素是非常重要的一项能力。

内容显示的样子与其使用的标记是没有关系的,为内容添加样式可以用 CSS 来做。

添加段落

HTML 会忽略你在文本编辑器中输入的回车符和其它额外的空格,所以要在网页中开始一个新的段落,应使用 p 元素。

1
2
3
4
5
6
7
···
<body>
<h1></h1>
<p>
content
</p>
</body>

默认情况下,浏览器会在标题和段落之间,以及不同的段落之间添加垂直间距。使用 CSS 可以控制所有内容元素的格式

指定细则

HTML5 中,small 表示细则一类的旁注,通常包括免责声明、注意事项、法律限制、版权信息等。small 通常是行内文本中的一小块,而不是包含多个段落或其它元素的大块文本。

1
2
3
4
5
6
7
···
<body>
<h1></h1>
<p>
<small> some restrications </small>
</p>
</body>

用 small 标记页面的版权信息时一种常见的做法。不过,small 只适用于短语,不宜用于标记长的的法律声明。

标记主要和强调的文本

我们一般用 strong 元素表示内容的重要性,而 em 则表示内容的着重点。根据内容需要,这两个元素既可以单独使用,也可以一起使用。

1
2
3
4
5
6
7
···
<body>
<h1></h1>
<p>
<strong> <em></em> </strong>
</p>
</body>

浏览器通常将 strong 文本用粗体显示,而将 em 文本以斜体显示。如果 em 是 strong 的子元素,将同时以斜体和粗体显示文本。

不要用 b 元素代替 strong,也不要用 i 元素代替 em。尽管它们在浏览器中显示的样式是一样的,但是它们的含义却很不一样。

可以在标记为 strong 的短语中再嵌套 strong 文本。如果这样做,作为另一个 strong 元素的子元素的 strong 文本的重要程度会递增。这种规则对嵌套在另一个 em 里的 em 文本也适用。

HTML 5 强调元素的语义,而非表现。b 和 i 元素是早期 HTML 遗留下的产物,它们分别用于将文本变为粗体和斜体。

HTML 5 将 b 重新定义为出于实用目的提醒读者注意的一块文字,不传达任何额外的重要性,也不表示其它的语态和语气,用于文档摘要中的关键词、评论中的产品名等。

HTML 5 将 i 重新定义为表示一块不同于其它文字的文字,具有不同的语态和语气,或其它不同于常规之处,用于如分类名称、技术术语、外语里的惯用语等。

创建图

在 HTML 5 之前,并没有专门实现图的元素,开发人员通常会使用不那么理想的 div 元素来实现。通过引入 figure 和 figcaption,HTML 5 改变了这种情况。图可以是图表、照片、图形、插图、代码片段以及其它类似的独立内容。

1
2
3
4
5
6
7
8
9
···
<body>
<h1> ···</h1>
<p> ··· </p>
<figure>
<figcaption></figcaption> // figcaption 是 figure 的标题,可选,出现在 figure 内容的开头或结尾
<img src="chart-revenue.png" width="180" height="180" alt="Revenue chart: Clothing 42%, Toys 36%, Food 22%" />
</figure>
</body>

需要注意的是,figcaption 元素并不是必需的,但如果包含它,它就必须是 figure 元素内嵌的第一个或最后一个元素。figure 通常情况下包含在 article 中。

指明引用或参考

使用 cite 元素可以指明对某内容源的引用或参考。

1
2
3
4
5
6
···
<body>
<p>
<city lang="it"> ··· </city>
</p>
</body>

对于要从引用来源中引述内容的情况,使用 blockquote 或 q 元素标记因数的文本。cite 只是用于参考源本身,而不是从中引述的内容。

HTML 5 中,不应使用 cite 作为对人名的引用,但在 HTML 以前的版本中允许这样做。

引述文本

有两个特殊的元素用以标记引述的文本。blockquote 元素表示单独存在的引述,通常更长,但也可能不是,它默认显示在新的一行。而 q 则用于短的引述,如句子里面的引述。

1
2
3
4
5
<p>
<blockquote cite="引用源">
content
</blockquote>
</p>

浏览器默认对 blockquote 文本进行缩进,cite 属性值则并不会显示出来。

如果 blockquote 中仅包含一个单独的段落或短语,可以不必将其包在 p 中再放入 blockquote。

1
2
3
4
<p>
<q> ··· </q>
<q lang="fr"> ··· </q>
</p>

如果引述文本的语言与页面默认语言不同,就在 q 元素中添加 lang 属性。

浏览器应对 q 元素中的文本自动加上特定语言的引号,不同的浏览器效果不同。

注意,q 元素引用的内容不能跨越不同的段落,在这种情况下应该使用 blockquote。

指定时间

HTML 5 中使用 time 元素标记时间、日期或时间段,time 是HTML 5 新增的元素。

1
2
3
4
<time>08:45</time>
<time datetime="2018-05-24T10:00:00"></time>
<time datetime="03-29"></time>
<time datetime="2h 30m"></time>

元素 time 中包含的文本内容会出现在屏幕上,对用户可见,而可选的 datetime 属性则是为机器准备的。该属性需要遵循特定的格式。

如果忽略了 datetime 属性,文本内容就必须是合法的日期或时间格式。datetime 属性不会单独产生任何效果,但它可以用于 Web 应用之间同步日期和时间。

需要注意的是,不能在 time 元素中嵌套另一个 time 元素,也不能在没有 datetime 属性的 time 元素中包含其它元素。

理解有效的时间格式

属性 datetime 或没有 datetime 属性的 time 元素,必须提供特定的机器可读格式的日期和时间。

常用的如 YYYY-MM-DDThh:mm:ss ,这种格式如果包含时间,秒是可选的。

如果需要提供毫秒数,则格式为 hh:mm:sss

如果要表示时间段,最简单的语法为:nh nm ns

全球日期和时间及时间差

在格式 YYYY-MM-DDThh:mm:ss 末尾加上字母 Z,就成了 UTC。

UTC 是主要的全球时间标准,可以通过相对 UTC 时差的方式表示时间。这时不写字母 Z,写上 ‘-’ 或 ‘+’及时差即可,比如:

1985-11-02T17:19:00-02:30

解释缩写词

使用 abbr 元素标记缩写词并解释其含义,但是不必对每个缩写词都使用 addr,只在需要帮助访问者了解改词含义的时候使用。

1
2
<abbr title="National Football League">NFL</abbr>
<abbr title="light amplicfication by stimulated emission of radiation">laser</abbr>

使用可选的 title 属性提供缩写词的全程。灵位,也可以将全称放在缩写词后面的括号里。

通常,仅在缩写词第一次出现在屏幕上时给出其全称。

定义术语

在 HTML 中定义术语时,可以使用 dfn 元素对其做语义上的区分。仅用 dfn 包围要定义的术语,而不是包围定义。

1
2
3
<p>
<dfn> ··· </dfn>
</p>

通常,dfn 元素默认以斜体显示。在是当的情况下可以包住其它短语元素。

如果在 dfn 中添加了可选的 title 属性,其值应与 dfn 术语一致。

创建上标和下标

比主体文本稍高或稍低的字母或数字分别称为上标和下标。HTML 中包含用来定义这两种文本的元素。

1
2
<sub> ··· </sub>	//下标
<sup> ··· </sup> //上标

上标和下标字符会轻微地扰乱行与行之间均匀的间距。不过我们可以使用 CSS 解决这个问题。

添加作者联系信息

实际上,address 元素是用以定义与 HTML 页面或页面的一部分有关的作者、相关人士或组织的联系信息,通常位于页面底部或相关部分内。至于 address 具体表示的是哪一种信息,取决于该元素出现的位置。

如果要为一个 article 提供作者联系信息,就将光标放在该元素内。如果要提供整个页面的作者联系信息,就将光标放在 body 中(更常 见的做法是放在页面级的 footer 里) 。

1
2
3
4
5
6
7
8
<footer role="contentinfo">
<p>
<small>&copy;2014 The MrSilent Inc</small>
</p>
<address>
Have a question or comment about the site? <a href="feedback.html">Contact us</a>
</address>
</footer>

标注编辑和不再准确的文本

有时可能需要将在前一个版本之后对页面内容的编辑标出来,或者对不再准确、不再相关的文本进行标记。有两种用于标注编辑的元素:代表添加内容的 ins 元素和标 记已删除内容的 del 元素。这两个元素既可以单独使用,也 可以一起使用。

del 和 ins 都支持两个属性 : cite 和 datetime。

cite 属性(区别于 cite 元素)用 于提供一个 URL,指向说明编辑原因的页面。

datetime 属性提供编辑的时间。

1
2
3
4
5
6
<p>
<ul>
<li><del></del></li>
<li><ins></ins></li>
</ul>
</p>

同时,s 元素用以标注不再准确或不再相关的内容。

标记代码

如果你的内容包含代码示例或文件名,就可以使 用 code 元素 。

1
2
3
<p>
<code></code>
</p>

code 元素表示其中的文本是代码或文件名,如果你的代码需要显示 < 或 >,应分别使用 &lt;和 &gt;

而如果显示单独的一块代码,则用 pre 元素保住 code 元素以维持其格式。

1
2
3
4
5
6
<p>
<pre>
<code>
</code>
</pre>
</p>

使用预格式化的文本

通常,浏览器会将所有额外的回车和空格压缩,并根据窗口的大小自动换行。预格式化的文本可以保持文本固有的换行和空格。 它是计算机代码示例的理想元素,不过你也可以将它用于文本 。

1
2
3
4
<pre>
<code>
</code>
</pre>

对包含重要的空格和换行的文本,pre 元素是非常合适的,因为 pre 内容里的缩进和换行都被保留了。

预格式化的文本通常以等宽字体显示。

突出显示文本

HTML 5 使用 mark 元素实现对特定词语的突出显示。

可以用 CSS 对 mark 元素里的文字应用样式。

1
2
3
4
5
6
7
<pre>
<code>
<mark>.greenText</mark> {
color:green;
}
</code>
</pre>

创建换行

浏览器会根据包含内容的块或窗口的宽 度让文本自动换行。大多数情况下,让内容像这样充满整行是很合适的,但有时你希望 手动地强制文字进行换行。可以使用 br 元素 实现这一要求。

要确保使用 br 是最后的选择,因为该元素将表现样式带入了 HTML,而不是让所有呈现样式都交由 CSS 控制。

1
2
       
<br />

创建 span

同 div 一样,span 元素是没有任何语义的。 不同的是,span 只适合包围字词或短语内容, 而 div 适合包含块级内容。

由于 span 没有任何语义,因此应将它作为最后的选择,仅在没有其他合适的元素时才使用它。

图像

当前 Web 上使用最广泛的三种图片格式是 GIF、PNG 和 JPEG。

JPEG 格式适用于彩色照片,因为它包含了大量颜色并进行了合理的压缩,采用这种格式保存的文件相对较小。不过,JPEG 是一种有损格式,在将图像保存为 JPEG 时会丢失一部分原始信息。

PNG 和 GIF 是无损的格式,采用这两种格式对图像进行压缩不会造成品质的损失。相对于 JPEG ,PNG 和 GIF 保存的图像的文件尺寸会大很多。因此,只有在压缩造成的质量损失不可忽略的情况下才使用 PNG 保存照片。

一般来说,如果要选择使用无损格式的图片,那么 PNG 应是优先的选择,因为它对透明度的支持更好,压缩算法也更好,产生的文件更小。

JPEG/PNG-24/PNG-32 均支持超过1600万众的颜色,而 GIF 和 PNG-8 只支持 256 中颜色,所以标识和其它颜色较少的图像通常保存为 PNG-8 格式,而照片和颜色复杂的插图等则应选择支持颜色较多的格式。

在页面中插入图像

可以在网页中放置各种各样的图像,从标志到照片都可以。当访问者浏览网页时,浏览器会自动加载像在 HTML 文档中描述的图像。不过, 图像加载时间跟访问者的网络连接强度、图像尺寸,以及页面中包含的图像个数相关。

1
<img src="corner-market.jpg" />

在实践中,为了保持良好的文件组织结构,通常将图像保存在单独的文件夹中。

提供替代文本

使用 alt 属性,可以为图像添加一段描述性文本,当图像因为某种原因不显示的时候,就将这段文字显示出来。HTML5 规范推荐将 alt 文本理解为图像的替代性描述 : “一般来说,替代文本是考虑图像未能正常加载的情况下需要呈现 文字。”通常,这意味着 alt 文本可以插入到图像两侧的文本流中,在大多数情况下, 它不应是对图像的描述。

1
<img src="market.jpg" alt="somen description text" />

如果图像对内容的价值较小,则可以提供空的替代文 本,即 alt=””。

如果图像与邻近的文本表达的信息相似,也可以将 alt 属性留空。

如果图像是页面设计的一部分,而不是内容的一部分,则应使用 CSS background-image 属性引入该图像,而不是使用 img 标记。

指定图像尺寸

通常我们会在 HTML 中明确指定图像的高度和宽度,这样浏览器就不必花时间来判断图像的尺寸,从而更快地将图像显示出来。 而且指定图像的尺寸,浏览器就可以预留空间,在图像加载的同时让文本显示在周围,保持布局的稳定。

可以通过浏览器或图像编辑软件获取图像的精确尺寸。

1
<img src="market.jpg" width="300" height="300" />

链接

链接有两个主要的部分:目标和标签。

使用目标(destination)可以指定访问者点击链接时会发生什么。可以创建链接进入另一个页面、在页面内跳转、显示图像、下载文件、 呼叫电话等等。不过,最常见的是连接到其他网页的链接,其次是连接到其他网页特定位置(称为锚,anchor)的链接。目标是通过编写 URL 定义的,通常只能在(桌面)浏览器的状态栏中看到。

链接的第二个部分是标签(label),即访问者在浏览器中看到或在屏幕阅读器中听到的部分。激活标签就可以到达链接的目标。 例如,航空公司网站上可能有这样的链接标 签:预订航班。标签可以是文本、图像或二者兼有。浏览器通常会将标签文本默认显示为带下划线的蓝色文字。

指向网页的链接

1
<a href="https://www.google.com" rel="external">Search with Google</a>

当要连接的页面与当前页面在同一目录下时,只需文件名即可进行访问。

rel 属性是可选的,它用来描述包含链接的页面和链接指向的页面之间的关系。

HTML 块级链接

HTML 5 几乎允许在链接内包含任何类型的元素或元素组。

1
2
3
4
5
6
<a href="giraffe-escapes.html">
<p>
···
</p>
<p>Read more</p>
</a>

创建锚并链接到锚

通常,激活一个链接会将用户带到对应网页的顶端。如果要想用户跳至网页的特定区域,可以创建一个锚,并在链接中引用该锚 。

首先你需要在目标网页中创建一个锚

1
2
3
4
<!-- https://www.site.com/home.html -->
<p id="anchro-name">
···
</p>

创建链接到特定锚的链接

1
<a href="https://www.site.com/home.html#anchor-name"></a>

如果锚位于页面的底部,且它下面的内容的高度小于浏览器的可视区域的高度, 那么它可能不会显示在窗口的顶部,而是显 示在中间。

其它类型的链接

并非只能创建指向其他网页的链接,其实可以创建指向任何 URL 的链接——RSS 源、 图像、希望访问者可以下载的文件、电子邮 件地址、电话号码等 。

1
2
3
4
5
<a href="img/market.jpg"> 链接到图片 </a>
<a href="media/song.mp3"> 链接到音频 </a>
<a href="media/movie.mp4"> 链接到视频 </a>
<a href="mailto:someone@somedomain.com"> 链接到邮箱地址 </a>
<a href="tel:+18002134567"> 链接到电话 </a>

对于指向万维网上任何文件(包括图像、 ZIP 文件、程序、PDF 及其他等)的链接,输 入 http://www.site.com/dir/file.ext, 其 中 www.site.com 是主机名称,dir/file.ext 是目标文件的路径。后者包括了文件目录和文件名(以及扩展名)。

对于电子邮件地址,输入 mailto:name@domain.com(不以 http:// 开头),其中 name@domain.com 是电子邮件地址。

对于电话号码,输入 tel:+(不以 http:// 开头)并紧跟着国家代码和电话号码(所有的号码中都不必包含短横线)。

如果链接指向的文件是浏览器不知道如何处理的类型(例如 Excel 文件),浏览器将试着打开一个辅助程序来查看这个文件,或试着将它下载到访问者的磁盘上。