九: ES6 Class 类
前言
该部分为书籍 深入了解ES6 第九章(JS的类)笔记
ES5 中的仿类结构
在 ES5 中与类最接近的是: 创立一个构造器, 而后将方法指派到该构造器的原型上, 这种方式通常被称为创立一个自己设置类型
function PersonType(name) { this.name = name;}PersonType.prototype.sayName = function() { console.log(this.name);};let person = new PersonType("Nicholas");person.sayName(); // 输出 "Nicholas"console.log(person instanceof PersonType); // trueconsole.log(person instanceof Object); // true类的公告
类在 ES6 中最简单的形式就是类公告, 看起来”很像”其余语言中的类
1. 基本的类公告
以 class 关键字开始, 其后是类的名称; 剩余部分的语法看起来就像对象字面量中的方法简写, 在方法之间不需要使用逗号.
class PersonClass { // 等价于 PersonType 构造器 constructor(name) { this.name = name; } // 等价于 PersonType.prototype.sayName sayName() { console.log(this.name) }}let person = new PersonClass('Nicholas');person.sayName(); // 输出 "Nicholas"console.log(person instanceof PersonClass); // trueconsole.log(person instanceof Object); // true// class 类也属于函数console.log(typeof PersonClass); // "function"console.log(typeof PersonClass.prototype.sayName); // "function"2. 与自己设置类型的区别
虽然类与自己设置类型之间有类似性, 但依然有少量重要的区别:
- 类公告不会被提升, 这与函数定义不同. 类公告的行为与
let类似, 因而在程序的执行到达公告处之前, 类会存在与暂时性死区内. - 类公告中的所有代码会自动运行在严格模式下, 并且也无法退出严格模式
- 类的所有方法都是不可枚举的, 这是对与自己设置类型的明显变化, 后者必需用
Object.defineProperty()才能将方法改变为不可枚举的. - 类的所有方法内部都没有
[[Construct]], 因而使用new来调用它们会抛出错误. - 调用类构造器时不使用
new, 会抛出错误 - 试图在类的方法内部重写类名, 会抛出错误
上例中 PersonClass 类公告实际上就直接的等价于以下未使用类语法的代码:
// 直接等价与 PersonClasslet PersonType2 = (function() { "use strict"; // 这里确保了第 6 点: 不能在类的内部重写类名 const PersonType2 = function(name) { // 确认函数被调用时使用了 new if (typeof new.target === 'undefind') { throw new Error("Constructor must be called with new."); } this.name = name; } Object.defineProperty(PersonType2.prototype, "sayName" { value: function() { // 确认函数被调用时没有使用 new if (typeof new.target === 'undefind') { throw new Error("Method cannot be called with new.") } console.log(this.name); }, enumerable: false, writable: true, configurable: true }); return PersonType2;}())只有在类的内部, 类名才被视为是使用
const公告的. 也就意味着可以在外部重写类名, 但不能在类的方法内部这么做class Foo { constructor() { Foo = "bar"; // 执行时抛出错误 }}// 但在类公告之后没问题Foo = "baz";
类表达式
类与函数有类似之处, 即它们都有两种形式: 公告与表达式. 类表达式被设计用于变量公告, 或者可作为参数传递给函数
1. 基本的类表达式
let PersonClass = class { // 等价于 PersonType 构造器 constructor(name) { this.name = name; } // 等价于 PersonType.prototype.sayName sayName() { console.log(this.name); }};let person = new PersonClass("Nicholas");person.sayName(); // 输出 "Nicholas"console.log(person instanceof PersonClass); // trueconsole.log(person instanceof Object); // trueconsole.log(typeof PersonClass); // "function"console.log(typeof PersonClass.prototype.sayName); // "function"除了语法差异, 类表达式的功能等价于类公告. 相对于函数公告与函数表达式之间的区别, 类公告与类表达式都不会被提升, 因而对代码运行时的行为影响甚微
2. 具名类表达式
上面的示例中使用的是一个匿名的类表达式, 不过就像函数表达式那样, 也可以为类表达式命名. 为此需要在 class 关键字后增加标识符
let PersonClass = class PersonClass2 { // 等价于 PersonType 构造器 constructor(name) { this.name = name; } // 等价于 PersonType.prototype.sayName sayName() { console.log(this.name); }}console.log(typeof PersonClass); // "function"console.log(typeof PersonClass2); // "undefined"类标识符 PersonClass2 只在类定义内部中存在, 只能用在类方法内部
作为一级公民的类
在编程中, 能被作为值值来使用的就成为一级公民(first-class citizen), 意味着它能作为参数传给函数、能作为函数返回值、能用来给变量赋值。 JS的函数就是一级公民
类同样也是一级公民
作为参数传入函数:
function createObject(classDef) { sreturn new classDef();}let obj = createObject(class { sayHi() { console.log("Hi!"); }});obj.sayHi(); // "Hi!"类表达式的另一个有趣用途是立即调用类构造器, 以创立单例
let person = new class { construstor(name) { this.name = name; } sayName() { console.log(this.name); }}("Nicholas");person.sayName(); // "Nicholas"
访问器属性
创立一个 getter, 使用 get 关键字, 并要与后方标识符之间留出空格;
创立一个 setter, 使用 set 关键字, 并要与后方标识符之间留出空格;
class CustomHTMLElement { constructor(element) { this.element = element; } get html() { return this.element.innerHTML; } set html(value) { this.element.innerHTML = value; }}var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html");console.log("get" in descriptor); // trueconsole.log("set" in descriptor); // trueconsole.log(descriptor.enumerable); // false需计算的成员名
对象字面量与类之间的类似点还不仅前面那些. 类方法与类访问器属性也都能使用需计算的名称. 语法相同于对象字面量中的需计算名称: 毋庸使用标识符, 而是用方括号来包裹一个表达式
let methodName = "sayName", propertyName = "html";class PersonClass { constructor(name) { this.name = name; } [methodNmae]() { console.log(this.name); } set [propertyName](value) { this.name = value; } get [propertyName]() { return this.name; }}let me = new PersonClass("Nicholas");me.sayName(); // "Nicholas"生成器方法
在类上同样可以定义一个生成器
class MyClass { *createIterator() { yield 1; yield 2; yield 3; }}let instance = new MyClass();let iterator = instance.createIterator();**也可以自己设置一个默认迭代器, 让实例成为可迭代对象 **
class Collection { constructor() { this.items = []; } *[Symbol.iterator]() { yield *this.items.values(); }}var collection = new Collection();collection.items.push(1);collection.items.push(2);collection.items.push(3);for (let x of collection) { console.log(x);}// 输出:// 1// 2// 3静态成员
静态成员表示为定义在类上面的方法(类 相当于 函数, 也可以当作对象一样增加属性和方法) ==> 静态成员不能用实例来访问, 始终需要直接用类自身来访问它们
类定义静态成员, 只需在方法与访问器属性的名称前增加正式的 static 标注
注意: 可以在类中的任何方法与访问器属性上使用 static 关键字, 唯一限制就是不能将它用于 constructor 方法的定义
class PersonClass { // 等价于 PersonType 构造器 constructor(name) { this.name = name; } // 等价于 PersonType.prototype.sayName sayName() { console.log(this.name); } // 等价于 PersonType.create static create(name) { return new PersonClass(name); }}let person = PersonClass.create("Nicholas");使用派生类进行继承
类继承使用 extends 关键字来指定当前类所需要继承的函数, 生成类的原型会被自动调整, 还能调用 super() 方法来访问基类的构造器
// ES5的继承function Rectangle(length, width) { this.length = length; this.width = width;}Rectangle.prototype.getArea = function() { return this.length * this.width;}function Square(length) { Rectangle.call(this, length, length);}Square.prototype = Object.create(Rectangle.prototype, { constructor: { value: Square, enumerable: true, writable: true, configurable: true }})var square = new Square(3);console.log(square.getArea()); // 9console.log(square instanceof Square); // trueconsole.log(square instanceof Rectangle); // true// ES6的继承class Rectangle { constructor(length, width) { this.length = length; this.width = width; } getArea() { return this.length * this.width; }}class Square extends Rectangle { construstor(length) { // 与 Rectangle.call(this, length, length)相同 super(length, length); }}var square = new Square(3);console.log(square.getArea()); // 9console.log(square instanceof Square); // trueconsole.log(square instanceof Rectangle); // true继承了其余类的类被称为派生类. 假如派生类指定了构造器, 就需要使用 super(), 否则会造成错误. 若选择不使用构造器, super() 方法会被自动调用, 并会使用创立新实例时提供的所有参数
class Square extends Rectangle { // 没有构造器}// 等价于:class Square extends Rectangle { constructor(...args) { super(...args); }}使用
super()时注意点:
- 只能在派生类中使用
super(). 若尝试在非派生的类( 即: 没有使用extends关键字的类)或者函数中使用它, 就会抛出错误.- 在构造器中, 你必需在访问
this之前调用super(). 因为super()负责初始化this, 因而试图先访问this自然就会造成错误.- 唯一能避免调用
super()的方法, 是从类构造器中返回一个对象.
1. 屏蔽类方法
派生类中的方法总是会屏蔽基类的同名方法
class Square extends Rectangle { constructor(length) { super(length, length); } // 重写病屏蔽 Rectangle.prototype.getArea() getArea() { return this.length * this.length; }}当然, 也可以使用 super.getArea() 方法来调用基类中的同名方法
class Square extends Rectangle { constructor(length) { super(length, length); } // 重写、屏蔽并调用了 Rectangle.prototype.getArea() getArea() { // 效果等同于对象中 super 引用(即引用的是当前原型对象), 并且 this 值会被自动设置为当前实例 return super.getArea(); }}2. 继承静态成员
假如基类包好静态成员, 那么这些静态成员在派生类中会自动继承
class Rectangle { constructor(length, width) { this.length = length; this.width = width; } getArea() { return this.length * this.width; } static create(length, width) { return new Rectangle(length, width); }}class Square extends Rectangle { constructor(length) { // 与 Rectangle.call(this, length, length) 相同 super(length, length); }}// 派生类 Square 直接继承了基类的静态成员 createvar rect = Square.create(3, 4);console.log(rect instanceof Rectangle); // trueconsole.log(rect.getArea()); // 12console.log(rect instanceof Square); // false3. 从表达式中派生类
在 ES6 中派生类的最强大能力: 或者许就是能够从表达式中派生类. 只需一个表达式能够返回一个具备 [[Construct]] 属性以及原型的函数, 即可以对其使用 extends
// 从 ES5 风格的构造器中继承function Rectangle(length, width) { this.length = length; this.width = width;}Rectangle.prototype.getArea = function() { return this.length * this.width;};class Square extends Rectangle { constructor(length) { super(length, length); }}extends 后面能接受任意类型的表达式, 例如动态地决定所要继承的类:
function Rectangle(length, width) { this.length = length; this.width = width;}Rectangle.prototype.getArea = function() { return this.length * this.width;};function getBase() { return Rectangle;}class Square extends getBase() { constructor(length) { super(length, length); }}// 也可以有效地创立混入let SerializableMixin = { serialize() { return JSON.stringify(this); }}let AreaMixin = { getArea() { return this.length * this.width; }}function mixin(...mixins) { var base = fucntion() {}; Object.assigin(base.prototyp, ...mixins); return base;}class Square extends mixin(AreaMixin, SerializableMixin) { constructor(length) { super(); this.length = length; this.width = length; }}var x = new Square(3);console.log(x.getArea()); // 9console.log(x.serialize()); // "{"length":3,"width":3}"任意表达式都能在
extends关键字后使用, 但并非所有表达式的结果都是一个有效的类. 特别的, 下列表达式类型会导致错误:
- null;
- 生成器函数;
试图使用结果为上述值得表达式来创立一个新的类实例, 都会抛出错误, 由于不存在
[[Construct]]可供调用
4. 继承内置对象
在 ES5 中, 通过继承机制来创立自己设置的特殊数组类型, 这个不可能做到的
在 ES6 中的类, **其设计目的之一就是允许从内置对象进行继承. 为了达成这个目的, 类的继承模型与 ES5 或者更早版本的传统继承模型有轻微差异: **
在 ES5 的传统继承中,
this的值会先被派生类( 例如MyArray) 创立, 随后基类构造器(例如Array.apply()方法)才被调用. 这意味着this一开始就是MyArray的实例, 之后才使用了Array的附加属性对其进行了装饰在 ES6 基于类的继承中,
this的值会先被基类(Array)创立, 随后才被派生类的构造器(MyArray)所修改. 结果是this初始就拥有作为基类的内置对象的所有功能, 并能正确接受与之关联的所有功能
// 在 ES5 中尝试继承数组function MyArray() { Array.apply(this, arguments);}MyArray.prototype = Object.create(Array.prototype, { constructor: { value: MyArray, writable: true, configurable: true, enumerable: true }});// MyArray 实例上的 lengt 属性以及数值属性, 其行为与内置数组并不一致, 由于这些功能并未被涵盖在 Array.apply() 或者 数组原型中var colors = new MyArray();colors[0] = "red";console.log(colors.length); // 0colors.length = 0;console.log(colors[0]); // "red"// ES6的继承内置对象class MyArray extends Array { // 空代码块}// 行为与正规数组一直var colors = new MyArray();colors[0] = "red";console.log(colors.length); // 1colors.length = 0;console.log(colors[0]); // undefined5. Symbol.species 属性
继承内置对象一个有趣的方面是: 任意能返回内置对象实例的方法, 在派生类上却会自动返回派生类的实例
class MyArray extends Array { // 空代码块}let items = new MyArray(1, 2, 3, 4); subitems = items.slice(1, 3);console.log(items instanceof MyArray); // true// 本意是返回一个 Array, 但是却返回了 MyArrayconsole.log(subitems instanceof MyArray); // true**Symbol.species 属性在后端造成了这种变化 **
Symbol.species 知名符号被用于定义一个能返回函数的静态访问器属性, 每当类实例的方法(构造器除外) 必需创立一个实例时, 前面返回的函数就被用为新实例的构造器
下列内置类型都定义了 Symbol.species:
- Array (所以自己设置类型 MyArray 也继承了 Array的
Symbol.species) - ArrayBuffer
- Map
- Promise
- RegExp
- Set
- 类型化数组
以上每个类型都拥有默认的 Symbol.species 属性, 其返回值为 `this, 意味着该属性总是会返回自身的构造器函数.
// 几个内置类型使用 species 的方式相似于此class MyClass { // 此处只有 getter 而没有 setter, 这是由于修改类的 species 是不允许的 static get [Symbol.species]() { return this; } constructor(value) { this.value = value; } clone() { return new this.constructor[Symbol.species](this.value); }}// 在 Array 派生出的类中, 可以重写 Symbol.species 来决定继承的方法应返回何种 类型的对象class MyArray extends Array { static get [Symbol.species]() { return Array; }}let items = new MyArray(1, 2, 3, 4), subitems = items.slice(1, 3);console.log(items instanceof MyArray); // true// 这样, 使用 Array 继承方法就会返回 Array 实例console.log(subitems instanceof Array); // trueconsole.log(subitems instanceof MyArray); // false在类构造器中使用 new.target
在第三章中 new.target : 保存着使用 new 调用时的 this引用
同样可以在类构造器中使用 new.targer, 来判断类是被如何调用的
注意: 类构造器被调用时不能缺少 new, 因而 new.target 属性就始终会在类构造器内被定义, 于是 new.target 属性在类构造器内部就绝不会是 undefined
// 不发生继承时, new.target 就等于当前类class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); // true this.length = length; this.width = width; }}// new.target 就是 Rectanglelet obj = new Rectangle(3,4); // 输出 true// 发生继承关系时, new.target 等于派生类class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); this.length = length; this.width = width; }}class Square extends Rectangle { constructor(length) { super(length, length) }}// new.target 就是 Squarevar obj = new Square(3); // 输出 false根据这个特性, 构造器能根据如何被调用而有不同行为, 并且这给了更改这种行为的能力
// 创立一个笼统基类(一种不能被实例化的类)// 静态的基类class Shape { constructor() { if (new.target === Shape) { throw new Error("This class cannot be instantiated directly.") } }}class Rectangle extends Shape { constructor(length, width) { super(); this.length = length; this.width = width; }}var x = new Shape(); // 抛出错误var y = new Rectangle(3, 4); // 没有错误console.log(y instanceof Shape); // true1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 九: ES6 Class 类