[ES6 ] Class 类
Classes
并不是说 JavaScript 从此变得像其它基于类的面向对象语言一样,有了一种全新的继承模型。
JavaScript 中的类只是 JavaScript 现有的、基于原型的继承模型的一种语法包装(语法糖),
它能让我们用更简洁明了的语法实现继承。
ES6 中的类实际上就是个函数,
而且正如函数的定义方式有函数声明和函数表达式两种一样。
类体中的代码都强制在 严格模式 中执行,即便你没有写 "use strict"。
#简单比较
//传统写法: function Rectangle(height, width) { this.height = height; this.width = width; } Rectangle.prototype.toString = function() { return '(' + this.height + ', ' + this.width + ')'; } //新的写法更加接近我们来自其他面向对象语言的经验: class Rectangle { constructor(height, width) { this.height = height; this.width = width; } toString() { return '(' + this.height + ', ' + this.width + ')'; } }
#类声明
class Polygon { constructor(height, width) { this.height = height; this.width = width; } }
类声明不会变量提升
var p = new Polygon(); // ReferenceError class Polygon {}
#类表达式
// 匿名类表达式 var Polygon = class { constructor(height, width) { this.height = height; this.width = width; } }; // 命名类表达式 var Polygon = class Polygon { constructor(height, width) { this.height = height; this.width = width; } };
#Prototype methods
class Polygon { constructor(height, width) { this.height = height; this.width = width; } get area() { return this.calcArea(); } calcArea() { return this.height * this.width; } } const square = new Polygon(10, 10); console.log(square.area);//100
#静态方法
static 关键字用来定义类的静态方法。 静态方法是指那些不需要对类进行实例化,使用类名就可以直接访问的方法。静态方法经常用来作为工具函数。
class Point { constructor(x, y) { this.x = x; this.y = y; } static distance(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; return Math.sqrt(dx * dx + dy * dy); } } const p1 = new Point(5, 5); const p2 = new Point(10, 10); console.log(Point.distance(p1, p2));//7.0710678118654755
#使用 extends 关键字创建子类
extends 关键字可以用来创建继承于某个类的子类。
//ES6之前类的写法 function Animal(name) { this.name = name; } Animal.prototype.speak = function() { console.log(this.name + ' makes a noise.'); } //extends class Dog extends Animal { //不覆盖同名方法 speak() { console.log(this.name + ' barks.'); } } const d = new Dog('Mitzie'); d.speak(); //Mitzie barks. const a = new Animal('husky'); a.speak(); //husky makes a noise.
#ES6之前的写法
//ES6之前类的写法 function Animal(name) { this.name = name; } Animal.prototype.speak = function() { console.log(this.name + ' makes a noise.'); } function Dog(x) { //不覆盖同名方法 Animal.call(this,x); // this.speak = function() { // console.log(this.name + ' barks.'); // } } Dog.prototype.speak = function() { console.log(this.name + ' barks.'); } var d = new Dog('Mitzie'); d.speak(); //Mitzie barks. var a = new Animal('husky'); a.speak(); //husky makes a noise.
#species
The species pattern lets you override default constructors.
class MyArray extends Array { // Overwrite species to the parent Array constructor static get[Symbol.species]() { return Array; } } var a = new MyArray(1, 2, 3); var mapped = a.map(x => x * x); console.log(mapped);//[1, 4, 9] console.log(mapped instanceof MyArray); // false console.log(mapped instanceof Array); // true
#使用 super 关键字引用父类
super 关键字可以用来调用其父类的构造器或者类方法
class Cat { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Lion extends Cat { speak() { super.speak(); console.log(this.name + ' roars.'); } } const l = new Lion('mimi'); l.speak();//mimi makes a noise. //mimi roars.
#Mix-ins
Abstract subclasses or mix-ins are templates for classes. An ECMAScript class can only have a single superclass, so multiple inheritance from tooling classes
var CalculatorMixin = Base => class extends Base { calc() {} }; var RandomizerMixin = Base => class extends Base { randomize() {} }; //这样用 class Foo {} class Bar extends CalculatorMixin(RandomizerMixin(Foo)) {}
constructor
class 根据 constructor 方法来创建和初始化对象。
仅在实例化一个类的时候被调用。
一个类只能拥有一个名为 constructor 的方法,没有指定时,默认空实现。
如果 class 定义的时候包含多个构造方法,程序将会抛出 SyntaxError 错误。
#
class Polygon { // ..and an (optional) custom class constructor. If one is // not supplied, a default constructor is used instead: // constructor() { } constructor(height, width) { this.name = 'Polygon'; this.height = height; this.width = width; } // Simple class instance methods using short-hand method // declaration sayName() { console.log('Hi, I am a ', this.name + '.'); } sayHistory() { console.log('"Polygon" is derived from the Greek polus (many) ' + 'and gonia (angle).'); } // We will look at static and subclassed methods shortly } class Square extends Polygon { constructor(length) { // Here, it calls the parent class' constructor with lengths // provided for the Polygon's width and height super(length, length); // Note: In derived classes, super() must be called before you // can use 'this'. Leaving this out will cause a reference error. this.name = 'Square'; } get area() { return this.height * this.width; } set area(value) { this.area = value; } } let s = new Square(5); s.sayName();//Hi, I am a Square. console.log('The area of this square is ' + s.area);//The area of this square is 25
Method definitions(方法的定义)
ECMAScript从ECMAScript6开始,引入了一种更简短的在对象初始器中定义方法的语法,这是一种把方法名直接赋给函数的简写方式。
#语法
var obj = { property( parameters… ) {}, *generator( parameters… ) {}, // also with computed keys: [property]( parameters… ) {}, *[generator]( parameters… ) {}, // compare ES5 getter/setter syntax: get property() {}, set property(value) {} };
#比较
//过去的写法 var obj = { foo: function() {}, bar: function() {} }; //现可被简写为: var obj = { foo() {}, bar() {} };
#生成器方法的简写语法
// 用有属性名的语法定义方法: var obj2 = { g: function*() { var index = 0; while (true) yield index++; } }; // 同一个方法,简写语法: var obj2 = { * g() { var index = 0; while (true) yield index++; } }; var it = obj2.g(); console.log(it.next().value); // 0 console.log(it.next().value); // 1
#Method definitions are not constructable??
All method definitions are not constructors and will throw a TypeError if you try to instantiate them.
var obj = {
method() {},
};
new obj.method; // TypeError: obj.method is not a constructor
var obj = {
* g() {}
};
new obj.g; // TypeError: obj.g is not a constructor (changed in ES2016)
#混合
var obj = { a: "foo", b() { return this.a; } }; console.log(obj.b()); // "foo"
#计算属性名
该简写方法也支持计算属性名
var bar = { foo0 : function (){return 0;}, foo1(){return 1;}, ["foo" + 2](){return 2;}, }; console.log(bar.foo0()); // 0 console.log(bar.foo1()); // 1 console.log(bar.foo2()); // 2
static
static methodName() { ... }
static 关键字用来定义类的静态方法。
静态方法是指那些不需要对类进行实例化,使用类名就可以直接访问的方法。静态方法经常用来作为工具函数。
#static 定义的是类的方法只有类能调用,而普通方法是实例的方法只有类实例能调用。
class A { static fn() { console.log('sss'); } fn2() { console.log('www'); } } let a = new A(); //a.fn(); // 报错 A.fn();//sss a.fn2();//www //A.fn2(); // 报错
#
class Tripple { static tripple(n) { n = n || 1; return n * 3; } } class BiggerTripple extends Tripple { static tripple(n) { return super.tripple(n) * super.tripple(n); } } console.log(Tripple.tripple()); // 3 console.log(Tripple.tripple(6)); // 18 console.log(BiggerTripple.tripple(3)); // 81 9*9 var tp = new Tripple(); console.log(BiggerTripple.tripple(3)); // 81(不会受父类被实例化的影响) //不可以在类实例化后调用 console.log(tp.tripple()); // Uncaught TypeError:'tp.tripple is not a function'.
super
super([arguments]); // 访问父对象上的构造函数
super.functionOnParent([arguments]);
super 关键字可以用来调用其父类的构造器或者类方法,与其他语言相似。
super 关键字在构造函数中通常被单独使用,且必须用于 this 关键字之前。它也被用来访问父对象上的函数。
#
class Polygon { // ..and an (optional) custom class constructor. If one is // not supplied, a default constructor is used instead: // constructor() { } constructor(height, width) { this.name = 'Polygon'; this.height = height; this.width = width; } // Simple class instance methods using short-hand method // declaration sayName() { console.log('Hi, I am a ', this.name + '.'); } sayHistory() { console.log('"Polygon" is derived from the Greek polus (many) ' + 'and gonia (angle).'); } // We will look at static and subclassed methods shortly } class Square extends Polygon { constructor(length) { // Here, it calls the parent class' constructor with lengths // provided for the Polygon's width and height // 此处 super 访问了父类的构造函数, // 传入 Polygon 的长宽参数。 super(length, length); // Note: In derived classes, super() must be called before you // can use 'this'. Leaving this out will cause a reference error. // 注意: 在派生类中,super() 必须在你使用 this 之前调用, // 否则会导致引用错误。 this.name = 'Square'; } get area() { return this.height * this.width; } set area(value) { this.area = value; } } let s = new Square(5); s.sayName(); //Hi, I am a Square. console.log('The area of this square is ' + s.area); //The area of this square is 25
#Super-calling static methods
You are also able to call super on static methods.
class Human { constructor() {} static ping() { return 'ping'; } } class Computer extends Human { constructor() {} static pingpong() { return super.ping() + ' pong'; } } console.log(Computer.pingpong()); // 'ping pong'
#Deleting super properties will throw
You can not use the delete operator and super.prop or super[expr] to delete a parent class' property,
it will throw a ReferenceError.
class Base { constructor() {} foo() {} } class Derived extends Base { constructor() {} delete() { delete super.foo; } } new Derived().delete(); // ReferenceError: invalid delete involving 'super'.
#Super.prop can not overwrite non-writable properties
When defining non-writable properties with
e.g. Object.defineProperty, super can not overwrite the value of the property.
class X { constructor() { Object.defineProperty(this, "prop", { configurable: true, writable: false, value: 1 }); } f() { super.prop = 2; } } var x = new X(); x.f(); console.log(x.prop); // 1
#Using super.prop in object literals
Super can also be used in the object initializer / literal notation. In this example, two objects define a method.In the second object, super calls the first object's method. This works with the help of Object.setPrototypeOf() with which we are able to set the prototype of obj2 to obj1, so that super is able to find method1 on obj1.
var obj1 = { method1() { console.log("method 1"); } } var obj2 = { method2() { super.method1(); } } Object.setPrototypeOf(obj2, obj1); obj2.method2(); // logs "method 1"
extends
class ChildClass extends ParentClass { ... }
extends用来创建继承于父类的子类
被用在类声明或者类表达式以创建一个其他类的子类。
extends关键词用来集成一个普通类以及内建对象。
扩展的.prototype必须是一个Object 或者 null。
#
class ColorRectangle extends Rectangle{ constructor(height, width, color) { super(height, width); this.color = color; } toString(){ return this.color + ' ' + super.toString(); } }
#使用extends扩展内建对象
class MyDate extends Date { constructor() { super(); } getFormattedDate() { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; return this.getDate() + '-' + months[this.getMonth()] + '-' + this.getFullYear(); } } var aDate = new MyDate(); console.log(aDate.getTime());//1463041231167 console.log(aDate.getFormattedDate());//12-May-2016
#继承null
可以像扩展普通类一样扩展null,但是新对象的原型将不会继承 Object.prototype.
class nullExtends extends null { constructor() {} } console.log(Object.getPrototypeOf(nullExtends)); // Function.prototype console.log(Object.getPrototypeOf(nullExtends.prototype)); // null
setter
set 语法可以将一个函数绑定在当前对象的指定属性上,
当那个属性被赋值时,你所绑定的函数就会被调用。
在 javascript 中,如果试着改变一个属性的值,那么对应的 setter 将被执行。
setter 经常和 getter 连用以创建一个伪属性。
一个拥有真实值的属性就不能再有 setter 了。
使用 set 语法时请注意:
它的标识符可以是 number 与 string 二者之一。
它必须有一个明确的参数
在同一个对象中,不能为一个已有真实值的变量使用 set ,也不能为一个属性设置多个 set。
( { set x(v) { }, set x(v) { } } 和 { x: ..., set x(v) { } } 是不允许的 )
setter 可以用 delete 操作来移除。
#在对象初始化时定义 setter
var o = { set current(str) { return this.log[this.log.length] = str; }, log: [] } console.log(o.current);//undefined console.log(o.log);//[] o.current = 'xx'; console.log(o.current);//undefined console.log(o.log);//["xx"]
#可以随时使用 Object.defineProperty() 给一个已经存在的对象添加一个 setter。
var o = { a: 0 }; Object.defineProperty(o, "b", { set: function(x) { this.a = x / 2; } }); o.b = 10; // Runs the setter, which assigns 10 / 2 (5) to the 'a' property console.log(o.a) // 5
#使用 computed 属性名 (计算属性名)
var expr = "foo"; var obj = { baz: "bar", set [expr](v) { this.baz = v; } }; console.log(obj.baz); // "bar" obj.foo = "baz"; // run the setter console.log(obj.baz); // "baz"
getter
{get prop() { ... } }
{get [expression]() { ... } }
get 语句作为函数绑定在对象的属性上,当访问该属性时调用该函数.
有时候希望访问属性时能返回一个动态计算后的值, 或希望不通过使用明确的方法调用而显示内部变量的状态.在JavaScript中, 能通过使用 getter 实现.
尽管可能结合使用getter和setter创建一个伪属性,但不能既使用getter绑定到一个属性上,同时又用该属性真实的存储一个值.
使用get语法时应注意以下问题:
可以使用数值或字符串作为标识;
必须不带参数
在对象字面量中,同一个属性不能有两个get,也不能既有get又有属性键值对(不允许使用 { get x() { }, get x() { } } 和 { x: ..., get x() { } } ).
可通过 delete 操作符删除getter.
#在新对象初始化时定义一个getter
试图赋给latest新值的话不会改变该值.
var log = ['test']; var obj = { get latest() { if (log.length == 0) return undefined; return log[log.length - 1] } } console.log(obj.latest); // "test"
#使用defineProperty在存在的对象上定义 getter
在任意时间添加getter到一个存在的对象,使用 Object.defineProperty().
var o = { a: 0 } Object.defineProperty(o, "b", { get: function() { return this.a + 1; } }); console.log(o.b) // 1 Runs the getter, which yields a + 1 (which is 1)
#使用computed property name(计算后的属性名)
var expr = "foo"; var obj = { get [expr]() { return "bar"; } }; console.log(obj.foo); // "bar"