ES6 |Class
如果对构造函数、原型对象、实例对象还不清楚,建议先看这里的原型基础部分
Class基本概念
传统方法是通过构造函数生成新对象
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
class
是构造器的另一种写法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
-
类的所有方法都定义在类的
prototype
属性上,即原型对象上(静态方法除外)class Point { constructor(){...} toString(){...} toValue(){...} }
等同于
class Point { constructor(){...} } Point.prototype = { toString(){}, toValue(){} };
-
通过类创建实例
var b = new Point(); b.toString() //在实例上调用的方法其实就是原型上的方法
-
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用
use strict
指定运行模式
其他知识点
-
Object.assign
方法可以很方便地一次向类添加多个原型方法。class Point { constructor(){...} } Object.assign(Point.prototype, { toString(){}, toValue(){} });
-
类的内部所有定义的方法,都是不可枚举的(non-enumerable)
-
类的属性名,可以采用表达式
let methodName = "getArea"; class Square{ constructor(length) {...} [methodName]() {...} }
-
不存在变量提升
Class的实例对象
1.如何创建
使用new
命令生成类的实例对象
var point = new Point(2, 3);
2.constructor方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加
-
constructor
方法默认返回实例对象(即this
) -
constructor
方法也可以指定返回另外一个对象class Foo { constructor() {return Object.create(null);} //返回一个全新的对象 } new Foo() instanceof Foo // false //导致实例对象不是Foo类的实例
3.实例上的方法
-
实例的方法都是定义在原型上(通过在
class
上添加) -
实例的属性和方法可以定义在实例对象本身(通过在
this
上添加)class Point { //通过this将实例的属性和方法定义在实例对象本身 constructor(x, y) { this.x = x; this.y = y; this.fun=function (){return 1} } //通过在class将实例的属性和方法定义在原型上 toString() {return '(' + this.x + ', ' + this.y + ')';} }
验证:
var point = new Point(2, 3) //由于x和y定义在实例对象本身,所以实例有这个属性 point.hasOwnProperty('x') // true point.hasOwnProperty('y') // true point.hasOwnProperty('fun') // true //由于toString定义在原型上,所以实例对象本身没有这个方法,而实例对象的原型有这个方法 point.hasOwnProperty('toString') // false point.__proto__.hasOwnProperty('toString') // true
-
类的所有实例共享一个原型对象
var p1 = new Point(2,3); var p2 = new Point(3,2); p1.__proto__ === p2.__proto__ //true
-
可以通过实例的
__proto__
属性为类的原型对象添加方法var p1 = new Point(2,3); var p2 = new Point(3,2); p1.__proto__.printName = function () { return 'Oops' }; //添加 p1.printName() // "Oops" p2.printName() // "Oops" //共享类的原型对象的方法
4.实例上的属性
以前:在类的constructor方法上的定义实例的属性
class Point {
constructor(myProp) {
this.myProp= myProp;
}
}
现在:可以在类中用等式定义实例的属性
class MyClass {
myProp = 42;
}
var myclass=new MyClass()
myclass.myProp//42
5.this的指向
类的方法内部的this
默认指向类的实例
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`); //this指向Logger类的实例
}
print(text) {
console.log(text);
}
}
//创建实例
const logger = new Logger();
//单独从实例中取出方法
const { printName } = logger;
//单独使用该方法
printName(); // TypeError: Cannot read property 'print' of undefined
//解析:该函数内部有个this是指向实例对象的。单独取出来后this会指向该方法运行时所在的环境,然后因为找不到print方法而导致报错
-
解决1:在构造方法中加上绑定
this
的语句class Logger { constructor() { this.printName = this.printName.bind(this); } // ... }
-
解决2:在构造方法中使用箭头函数
class Logger { constructor() { this.printName = (name = 'there') => { this.print(`Hello ${name}`); }; } // ... }
-
解决3:使用
Proxy
Class的继承
1. Extends
-
类相当于实例的原型,所有在类中定义的方法,都会被实例继承
-
类之间可以通过
extends
关键字实现继承class ColorPoint extends Point {} //ColorPoint类继承了Point类的所有属性和方法
class B extends A {} //只要是一个有prototype属性的函数,就能被B继承
三种特殊情况:
-
子类继承Object类
class A extends Object {} A.__proto__ === Object // true A.prototype.__proto__ === Object.prototype // true //A其实就是构造函数Object的复制,A的实例就是Object的实例。
-
不存在任何继承
class A {} A.__proto__ === Function.prototype // true A.prototype.__proto__ === Object.prototype // true //A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Funciton.prototype。但是,A调用后返回一个空对象(即Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性。
-
子类继承null
class A extends null {} A.__proto__ === Function.prototype // true A.prototype.__proto__ === undefined // true //A也是一个普通函数,所以直接继承Funciton.prototype。但是,A调用后返回的对象不继承任何方法,所以它的__proto__指向Function.prototype
-
2.class的静态方法
静态方法不会被实例继承,而是直接通过类来调用,所谓静态方法,就是方法前面加上了static
关键字
class Foo {
static classMethod() {
return 'hello';
}
}
//直接在Foo类上调用
Foo.classMethod() // 'hello'
//不可在Foo类的实例中调用
var foo = new Foo();
foo.classMethod()// TypeError: foo.classMethod is not a function
3.class的静态属性
静态属性指的是Class本身的属性,即Class.propname
,而不是定义在实例对象(this
)上的属性。同理,静态属性不会被继承
以前的写法
class Foo {}
Foo.prop = 1; //定义一个静态属性
Foo.prop // 1
现在的写法
class MyClass {
static myStaticProp = 1; //在类中定义一个静态属性
}
MyClass.myStaticProp//1
4.super 关键字
-
super作为函数调用时,代表父类的构造函数。且
super()
只能用在子类的构造函数之中class ColorPoint extends Point { constructor(x, y, color) { super(x, y); //调用父类的constructor(x, y) this.color = color; } }
子类构造函数的
this
-
ES6 要求:子类的构造函数必须执行一次super函数。因为子类的
this
对象(实例对象)是继承父类的,所以子类必须在constructor
方法中调用super方法class Point { /* ... */ } class ColorPoint extends Point { //子类本身是没有this的,只能通过super继承父类的this constructor() {} } let cp = new ColorPoint(); // ReferenceError//没有this无法创建实例
-
子类实例是基于父类实例加工的
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) {//基于父类实例加上了color this.color = color; // 没有继承父类this:ReferenceError super(x, y); //继承父类this后返回父类实例: this.color = color; // 正确 } }
其他
-
实例对象既是子类的实例对象又是父类的实例对象
let cp = new ColorPoint(25, 8, 'green'); cp instanceof ColorPoint // true cp instanceof Point // true
-
Object.getPrototypeOf
方法可以用来从子类上获取父类Object.getPrototypeOf(ColorPoint) === Point // true
-
super虽然代表了父类的构造函数,但是返回的是子类的实例
class Parent { constructor() { console.log(new.target.name); } } class Children extends Parent { constructor() { super(); } } new A() // A new B() // B //注意,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)
-
-
super在普通方法中作为对象时,指向父类的原型对象
class A { p() { return 2; } } class B extends A { constructor() { super(); //这里super被当做一个对象使用了,指向A.prototype(父类原型),相当于A.prototype.p() console.log(super.p()); // 2 } }
由于
super
指向父类的原型对象,所以定义在父类实例上(构造函数上)的方法或属性,是无法通过super
调用的class A { constructor() { this.p = 2; //1. 构造方法内定义的p(即实例上的p) } } class B extends A { get m() { return super.p;//2. 这里返回父类的p,相当于A.prototype.p } } let b = new B(); b.m // undefined //3. 由于A.prototype找不到p,报错(因为p是在实例上定义的)
//解决:这时如果把父类的p定义在父类的原型对象上,子类super.p就能访问到。比如: A.prototype.p = 2 b.m //2
ES6 规定,通过
super
调用父类的方法时,super
会绑定子类的this
class A { constructor() { this.x = 1; } print() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 2; } m() { super.print();//super被当做对象,指向的是父类的原型对象,所以可以找到print } } let b = new B(); b.m() // 2 //super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()会绑定子类B的this,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)
如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性
class A { constructor() { this.x = 1; } } class B extends A { constructor() { super(); this.x = 2; super.x = 3; //由于会绑定子类的this,这里相当于this.x=3 console.log(super.x); // undefined //当读取super.x的时候,读的是A.prototype.x,所以返回undefined console.log(this.x); // 3 } }
-
super
在静态方法中作为对象时,指向父类class Parent { //父类 static myMethod(msg) { console.log('static', msg); } //父类的原型对象 myMethod(msg) { console.log('instance', msg); } } class Child extends Parent { static myMethod(msg) { super.myMethod(msg); //super在静态方法之中指向父类 } myMethod(msg) { super.myMethod(msg); //在普通方法之中指向父类的原型对象 } } Child.myMethod(1); // static 1 //这里调用的是父类的静态方法 var child = new Child(); //实例化 child.myMethod(2); // instance 2 //这里调用的是父类的原型对象
-
由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字
var obj = { toString() { return "MyObject: " + super.toString(); } }; obj.toString(); // MyObject: [object Object]
Class表达式
类可以使用表达式的形式定义
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
//上面代码表示,Me只在Class内部有定义
//如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式
const MyClass = class { /* ... */ };
采用Class表达式,可以写出立即执行的Class
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {console.log(this.name);}
}('张三');
person.sayName(); // "张三"
其他
私有方法
-
利用call方法
class Widget { //公有方法 foo (baz) { //私有方法:利用call使bar成为了当前模块的私有方法 bar.call(this, baz); } } function bar(baz) { return this.snaf = baz; }
-
利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值
const bar = Symbol('bar'); const snaf = Symbol('snaf'); class myClass{ // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } };
class的私有属性
私有属性即在类之外是读取不到的属性,通过在属性名前使用#
来添加
class的prototype属性和__proto__属性
-
子类的
__proto__
属性,表示构造函数的继承,总是指向父类。 -
子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。class A {} class B extends A {} B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
实例的_proto_
属性
子类实例的_proto_
属性的_proto_
属性,指向父类实例的_proto_
属性。也就是说,子类的原型的原型,是父类的原型
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red'); //继承Point
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
因此,通过子类实例的__proto__.__proto__
属性,可以修改父类实例的行为
p2.__proto__.__proto__.printName = function () {
console.log('Ha');
};
p1.printName() // "Ha"
原生构造函数的继承
原生构造函数是指语言内置的构造函数,通常用来生成数据结构。以前,这些原生构造函数是无法继承的
ES6允许继承原生构造函数定义子类,因为ES6是先新建父类的实例对象this
,然后再用子类的构造函数修饰this
,使得父类的所有行为都可以继承。
Class的取值函数(getter)和存值函数(setter)
与ES5一样,在Class内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'