红宝书4-第八章对象、类与面向对象编程(6)


虽然 ECMAScript 6 类表面上看起来能够支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念。 header

1.类定义

与函数类型类似,定义类也有两种主要方式:类声明类表达式。这两种方式都使用 class 关键字加大括号:javascript

// 类声明
class Person {} 
// 类表达式
const Animal = class {};

与函数表达式相似,类表达式在它们被求值前也不能引用。不过,与函数定义不一样的是,虽然函数声明能够提高,但类定义不能。java

另外一个跟函数声明不一样的地方是,函数受函数做用域限制,而类受块做用域限制编程

类的构成

类能够包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。空的类定义照样有效。默认状况下,类定义中的代码都在严格模式下执行。数组

与函数构造函数同样,多数编程风格都建议类名的首字母要大写,以区别于经过它建立的实例(好比,经过 class Foo {}建立实例 foo):ide

// 空类定义,有效
class Foo {} 
// 有构造函数的类,有效
class Bar { 
 constructor() {} 
} 
// 有获取函数的类,有效
class Baz { 
 get myBaz() {} 
} 
// 有静态方法的类,有效
class Qux { 
 static myQux() {} 
}

类表达式的名称是可选的。在把类表达式赋值给变量后,能够经过 name 属性取得类表达式的名称字符串。但不能在类表达式做用域外部访问这个标识符函数

let Person = class PersonName { 
 identify() { 
 console.log(Person.name, PersonName.name); 
 } 
} 
let p = new Person(); 
p.identify(); // PersonName PersonName 
console.log(Person.name); // PersonName 
console.log(PersonName); // ReferenceError: PersonName is not defined

2.类构造函数

  1. constructor 关键字用于在类定义块内部建立类的构造函数。
  2. 方法名 constructor 会告诉解释器在使用 new 操做符建立类的新实例时,应该调用这个函数
  3. 构造函数的定义不是必需的不定义构造函数至关于将构造函数定义为空函数

1. 实例化

使用 new 操做符实例化 Person 的操做等于使用 new 调用其构造函数。惟一可感知的不一样之处就是,JavaScript 解释器知道使用 new 和类意味着应该使用 constructor 函数进行实例化。测试

若是构造函数返回非空对象,则返回该对象;不然,返回刚建立的新对象this

class Person { 
 constructor(name) { 
 console.log(arguments.length); 
 this.name = name || null; 
 } 
} 
let p1 = new Person; // 0 
console.log(p1.name); // null 
let p2 = new Person(); // 0 
console.log(p2.name); // null 
let p3 = new Person('Jake'); // 1 
console.log(p3.name); // Jake

默认状况下,类构造函数会在执行以后返回 this 对象。构造函数返回的对象会被用做实例化的对象,若是没有什么引用新建立的 this 对象,那么这个对象会被销毁。不过,若是返回的不是 this 对象,而是其余对象,那么这个对象不会经过 instanceof 操做符检测出跟类有关联,由于这个对象的原型指针并无被修改。prototype

class Person { 
 constructor(override) { 
 this.foo = 'foo'; 
 if (override) { 
 return { 
 bar: 'bar' 
 }; 
 } 
 } 
} 
let p1 = new Person(), 
 p2 = new Person(true); 
console.log(p1); // Person{ foo: 'foo' } 
console.log(p1 instanceof Person); // true 
console.log(p2); // { bar: 'bar' } 
console.log(p2 instanceof Person); // false

类构造函数与构造函数的主要区别是,调用类构造函数必须使用 new 操做符。而普通构造函数若是不使用 new 调用,那么就会以全局的 this(一般是 window)做为内部对象。指针

调用类构造函数时若是忘了使用 new 则会抛出错误

function Person() {} 
class Animal {} 
// 把 window 做为 this 来构建实例
let p = Person(); 
let a = Animal(); 
// TypeError: class constructor Animal cannot be invoked without 'new'

类构造函数没有什么特殊之处,实例化以后,它会成为普通的实例方法(但做为类构造函数,仍然要使用 new 调用)。所以,实例化以后能够在实例上引用它。

2. 把类当成特殊函数

ECMAScript 中没有正式的类这个类型。从各方面来看,ECMAScript 类就是一种特殊函数。声明一个类以后,经过 typeof 操做符检测类标识符,代表它是一个函数

类标识符prototype 属性,而这个原型也有一个 constructor 属性指向类自身

与普通构造函数同样,可使用 instanceof 操做符检查构造函数原型是否存在于实例的原型链中

类是 JavaScript 的一等公民,所以能够像其余对象或函数引用同样把类做为参数传递:

// 类能够像函数同样在任何地方定义,好比在数组中
let classList = [ 
 class { 
 constructor(id) { 
 this.id_ = id; 
 console.log(`instance ${this.id_}`); 
 } 
 } 
]; 
function createInstance(classDefinition, id) { 
 return new classDefinition(id); 
} 
let foo = createInstance(classList[0], 3141); // instance 3141

与当即调用函数表达式类似,类也能够当即实例化

// 由于是一个类表达式,因此类名是可选的
let p = new class Foo {
constructor(x) { 
 console.log(x); 
 } 
}('bar'); // bar 
console.log(p); // Foo {}

3.实例、原型和类成员

类的语法能够很是方便地定义应该存在于实例上的成员、应该存在于原型上的成员,以及应该存在于类自己的成员。

1. 实例成员

每次经过new调用类标识符时,都会执行类构造函数。在这个函数内部,能够为新建立的实例(this)添加“自有”属性。至于添加什么样的属性,则没有限制。另外,在构造函数执行完毕后,仍然能够给实例继续添加新成员。

每一个实例都对应一个惟一的成员对象,这意味着全部成员都不会在原型上共享

class Person { 
 constructor() { 
 // 这个例子先使用对象包装类型定义一个字符串
 // 为的是在下面测试两个对象的相等性
 this.name = new String('Jack'); 
 this.sayName = () => console.log(this.name); 
 this.nicknames = ['Jake', 'J-Dog'] 
 } 
} 
let p1 = new Person(), 
 p2 = new Person(); 
p1.sayName(); // Jack 
p2.sayName(); // Jack 
console.log(p1.name === p2.name); // false 
console.log(p1.sayName === p2.sayName); // false 
console.log(p1.nicknames === p2.nicknames); // false 
p1.name = p1.nicknames[0]; 
p2.name = p2.nicknames[1]; 
p1.sayName(); // Jake 
p2.sayName(); // J-Dog

2. 原型方法与访问器

为了在实例间共享方法,类定义语法把在类块中定义的方法做为原型方法。

class Person { 
 constructor() { 
 // 添加到 this 的全部内容都会存在于不一样的实例上
 this.locate = () => console.log('instance'); 
 }
 // 在类块中定义的全部内容都会定义在类的原型上
locate() { 
console.log('prototype'); 
} 
} 
let p = new Person(); 
p.locate(); // instance 
Person.prototype.locate(); // prototype

能够把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象做为成员数据:

class Person { 
 name: 'Jake' 
} 
// Uncaught SyntaxError: Unexpected token

类方法等同于对象属性,所以可使用字符串、符号或计算的值做为键:

const symbolKey = Symbol('symbolKey'); 
class Person { 
 stringKey() { 
 console.log('invoked stringKey'); 
 } 
 [symbolKey]() { 
 console.log('invoked symbolKey'); 
 } 
 ['computed' + 'Key']() { 
 console.log('invoked computedKey'); 
 } 
} 
let p = new Person(); 
p.stringKey(); // invoked stringKey 
p[symbolKey](); // invoked symbolKey 
p.computedKey(); // invoked computedKey

类定义也支持获取和设置访问器。语法与行为跟普通对象同样:

class Person { 
set name(newName) { 
this.name_ = newName; 
} 
get name() { 
return this.name_; 
} 
} 
let p = new Person(); 
p.name = 'Jake'; 
console.log(p.name); // Jake

3. 静态类方法

能够在类上定义静态方法。这些方法一般用于执行不特定于实例的操做,也不要求存在类的实例。与原型成员相似,静态成员每一个类上只能有一个

静态类成员在类定义中使用 static 关键字做为前缀。在静态成员中,this 引用类自身。其余全部约定跟原型成员同样:

class Person { 
 constructor() { 
 // 添加到 this 的全部内容都会存在于不一样的实例上
 this.locate = () => console.log('instance', this); 
 } 
 // 定义在类的原型对象上
 locate() { 
 console.log('prototype', this); 
 } 
 // 定义在类自己上
 static locate() { 
 console.log('class', this); 
 } 
} 
let p = new Person(); 
p.locate(); // instance, Person {} 
Person.prototype.locate(); // prototype, {constructor: ... } 
Person.locate(); // class, class Person {}

4. 非函数原型和类成员

虽然类定义并不显式支持在原型或类上添加成员数据,但在类定义外部,能够手动添加:

class Person { 
 sayName() { 
 console.log(`${Person.greeting} ${this.name}`); 
 } 
} 
// 在类上定义数据成员
Person.greeting = 'My name is';
// 在原型上定义数据成员
Person.prototype.name = 'Jake'; 
let p = new Person(); 
p.sayName(); // My name is Jake

在这里插入图片描述

5. 迭代器与生成器方法

类定义语法支持在原型和类自己上定义生成器方法:

class Person { 
 // 在原型上定义生成器方法
 *createNicknameIterator() { 
 yield 'Jack'; 
 yield 'Jake'; 
 yield 'J-Dog'; 
 } 
 // 在类上定义生成器方法
 static *createJobIterator() { 
 yield 'Butcher'; 
 yield 'Baker'; 
 yield 'Candlestick maker'; 
 } 
} 
let jobIter = Person.createJobIterator(); 
console.log(jobIter.next().value); // Butcher 
console.log(jobIter.next().value); // Baker 
console.log(jobIter.next().value); // Candlestick maker 
let p = new Person(); 
let nicknameIter = p.createNicknameIterator(); 
console.log(nicknameIter.next().value); // Jack 
console.log(nicknameIter.next().value); // Jake 
console.log(nicknameIter.next().value); // J-Dog

由于支持生成器方法,因此能够经过添加一个默认的迭代器,把类实例变成可迭代对象:

class Person { 
 constructor() { 
 this.nicknames = ['Jack', 'Jake', 'J-Dog']; 
 } 
*[Symbol.iterator]() { 
yield *this.nicknames.entries(); 
} 
} 
let p = new Person(); 
for (let [idx, nickname] of p) { 
 console.log(nickname); 
}
// Jack 
// Jake 
// J-Dog

也能够只返回迭代器实例:

class Person { 
 constructor() { 
 this.nicknames = ['Jack', 'Jake', 'J-Dog']; 
 } 
 [Symbol.iterator]() { 
return this.nicknames.entries(); 
} 
} 
let p = new Person(); 
for (let [idx, nickname] of p) { 
 console.log(nickname); 
} 
// Jack 
// Jake 
// J-Dog
相关文章
相关标签/搜索