class Animal {}
let a1 = new Animal()
let a2 = new Animal.constructor()
console.log(a1 instanceof Animal) // true
console.log(a2 instanceof Animal.constructor) // true
静态属性和静态方法
在一个类的声明中,以 static 关键字开头把一个方法作为一个整体赋值给类,这样的方法被称为静态的(static)。
注意:它不是某个实例对象的方法,而是整个 class 的方法。
class User {
static staticMethod() {
console.log(this === User);
}
}
// 上述写法跟下述直接将方法作为属性赋值的写法作用相同
class User { }
User.staticMethod = function() {
console.log(this === User);
};
class MyArray extends Array {
// 覆盖 species 到父级的 Array 构造函数上
static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
instanceof
instanceof 操作符用于检查一个对象是否属于某个特定的 class。
obj instanceof Class
如果 obj 隶属于 Class 类(或 Class 类的衍生类),则返回 true。构造函数也可用。
class Rabbit {}
let rabbit = new Rabbit()
function Rabbit2() {}
let rabbit2 = new Rabbit2()
rabbit instanceof Rabbit // true
rabbit2 instanceof Rabbit2 // true
构造函数
构造函数的主要目的是实现可重用的对象创建代码,能更方便地创建类似的对象。 构造函数在技术上是常规函数,从技术上讲,除了箭头函数(它没有 this)之外,任何函数都可以用作构造器。 所以我们用两个约定来规范构造函数的使用:
举例:
当用 new 执行一个函数时,函数发生以下步骤:
返回 this 举例,也就是当 new User() 时:
Class 基本语法
ES6引入新的关键字class,它提供了一种更为优雅的创建对象和实现继承的方式,底层仍然是基于原型的实现。
class 语法
除了构造函数(构造器)之外,还有一个更高级的“类”构建方式。 在 JavaScript 中,类是一种函数。 类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。 基本语法是:
定义上述的类之后,使用 new MyClass() 来创建具有上述列出的所有方法的新对象。 new 会自动调用 constructor() 方法,因此我们可以在 constructor() 中初始化对象。 举例:
定义 class User {...} 时实际做了以下事情:
class 是语法糖吗?
从上述定义 class 和 new class 的过程可见,可以将 class 视为一种定义构造器及其原型方法的语法糖。 但其实它们之间存在重大差异,比如:
类继承
类继承是一个类扩展另一个类的一种方式。可以在现有功能之上创建新功能。 扩展另一个类的语法是:class Child extends Parent。 举例:
在内部,关键字 extends 使用了很好的旧的原型机制进行工作。它将 Rabbit.prototype.[[ Prototype]] 设置为 Animal.prototype。所以,如果在 Rabbit.prototype 中找不到一个方法,JavaScript 就会从 Animal.prototype 中获取该方法。
另外,类语法不仅允许指定一个类,在 extends 后可以指定任意表达式。 例如,一个生成父类的函数调用:
这里 class User 继承自 f("Hello") 的结果。
重写父类方法
有时,我们不希望完全替换父类的方法,而是想在沿用父类方法的基础上,拓展其他功能。 Class 为此提供了 "super" 关键字。
class Rabbit extends Animal { hide() { alert(
${this.name} hides!
); } stop() { super.stop(); // 调用父类的 stop this.hide(); // 然后 hide } }假如给 Rabbit 添加一个自定义的 constructor。除了 name 之外,它还会指定 earLength。 继承类的 constructor 必须调用 super(...),并且一定要在使用 this 之前调用。
这是因为,在 JavaScript 中,继承类(所谓的“派生构造器”,英文为 “derived constructor”)的构造函数与其他函数之间是有区别的。派生构造器具有特殊的内部属性 [[ ConstructorKind]]:"derived"。这是一个特殊的内部标签。
该标签会影响它的 new 行为:
与普通构造函数一样,可以使用 instanceof 操作符检查构造函数原型是否存在于实例的原型链中:
由此可知,可以使用instanceof 操作符检查一个对象与类构造函数,以确定这个对象是不是类的实例。
类本身具有与普通构造函数一样的行为。在类的上下文中,类本身在使用new 调用时就会被当成构造函数。重点在于,类中定义的 constructor 方法不会被当成构造函数。
静态属性和静态方法
在一个类的声明中,以 static 关键字开头把一个方法作为一个整体赋值给类,这样的方法被称为静态的(static)。 注意:它不是某个实例对象的方法,而是整个 class 的方法。
私有的和受保护的属性和方法
受保护的属性 在类中,受保护的属性通常以下划线 _ 作为前缀。这是程序员之间有一个众所周知的约定,即不应该从外部访问此类型的属性和方法。
只读的属性 有时候一个属性必须只能被在创建时进行设置,之后不能再被修改。在类中只设置该属性的 getter,不设置 setter 即可。
扩展内建类
内建的类,例如 Array,Map 等也都是可以扩展的。 比如,从原生 Array 类继承一个类 PowerArray():
使用 filter 等内建方法,返回的是子类 PowerArray 的新对象。 当 b.filter() 被调用时,它的内部使用的是 b.constructor 来创建新的结果数组,而不是使用原生的 Array。
如果希望使用原生的 Array,则可以使用 Symbol.species 返回 Array。species 访问器属性允许子类覆盖对象的默认构造函数。 以下是 MDN[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species] 中关于 Symbol.species 的举例:
instanceof
instanceof 操作符用于检查一个对象是否属于某个特定的 class。
如果 obj 隶属于 Class 类(或 Class 类的衍生类),则返回 true。构造函数也可用。
通常,instanceof 在检查中会将原型链考虑在内。
以上笔记参考《现代 JavaScript 教程》,《JavaScript高级程序设计(第4版)》及 MDN 文档