1、class的基本语法
· constructor() 是构造方法
· this 代表实例对象
· 定义 “类” 的方法时,前面不需要加上 function 这个保留字,直接定义函数即可。
· 方法之间不需要用逗号分隔, 加了会报错
class Point {
constructor( x, y ){
this.x = x;
this.y = y;
}
toString(){
return `${ this.x }, ${ this.y }`;
}
};
2)类的数据类型就是函数, 类本身就指向构造函数
class Point {
// ...
};
typeof Point; // function
Point = Point.prototype.constructor; // true
3)构造函数的 prototype 属性在 “类” 上继续存在。类的所有方法都定义在类的 prototype 属性上
class Point {
constructor(){}
toString(){}
toValue(){}
};
// 等同于
Point.prototype = {
constructor(){}
toString(){}
toValue(){}
};
4)在类的实例上调用方法, 其实就是调用原型上的方法
class Point {};
let point = new Point();
console.log( point.constructor === Point.prototype.constructor ); // true
5)类的方法(除 constructor 以外)都定义在 prototype 对象上( constructor 是包含在prorotype 中的不可被枚举的属性 ),所以类的新方法都可以添加在prototype 对象上。Object.assign()可以向类中添加多个方法。
class Point {
constructor(){}
};
Object.assign( Point.prorotype, {
toString(){},
toValue(){}
});
Point.prototype.constructor === Point; // true
prototype 对象上的 constructor 属性直接指向 "类"(本例是 Point )本身
6)ES6规定类的内部定义的所有方法都是不可被枚举的( non-enumerable ),ES5 是可枚举的
class Point {
constructor(){}
toString(){}
};
Object.keys( Point ); // []
Object.getOwnPropertyNames( Point.prototype ); // [ "constructor", "toString"]
7)类的属性名可以采用表达式
let methodName = 'getArea';
class Square {
constructor(){}
[methodName](){}
}
2、constructor 方法
1)constructor() 是类的默认方法, 通过 new 命令生成对象实例时自动调用该方法。一个类必须有 constructor 方法, 如果没有定义, 一个空的 constructor() 会被默认添加。
class Point {};
// 等同于
class Point{
constructor(){}
};
2)constructor() 默认返回实例对象( 即 this ),不过完全可以指定返回另外一个对象
class Foo {
constructor(){
return Object.create( null ); // 默认 return this;
}
};
new Foo instanceof Foo; // fasle
3)类必须使用 new 来调用, 否则会报错。
class Point {
constructor(){}
};
let point = new Point();
3、类的实例对象
1)生成实例对象的写法与ES5完全一样,也是使用 new 命令。如果忘记加上 new ,像函数
那样调用 Class 将会报错。
class Point {
constrcutor(){}
};
let point = new Point();
2)constructor中定义的属性可以称为实例属性(即定义在this对象上),constructor外声明的属性都是定义在原型上的,可以称为原型属性(即定义在class上)。hasOwnProperty()函数用于判断属性是否是实例属性。其结果是一个布尔值,true说明是实例属性,false说明不是实例属性。in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
// 定义类
class Point {
constructor( x, y ){ // 显式定义在 this 对象上
this.x = x;
this.y = y;
},
toString(){} // 定义在原型上 Class Point{}
};
3)类的所有实例共享一个原型对象,它们的原型都是Person.prototype,所以proto属性是相等的
let p1 = new Point(2,3);
let p2 = new Point(4,5);
p1.__proto__ === p2.__proto__; // true
4、Class 表达式
1)与函数一样, Class 也可以使用表达式的形式定义
const MyClass = class Me {
getClassName(){
return Me.name
}
};
let inst = new MyClass();
inst.getClassName(); // Me
这个类的名字是 MyClass 而不是 Me, Me 只在 Class 内部可用,指代当前类
2)采用 Class 表达式,可以写出立即执行的 class
let person = new class {
costructor( name ){
this.name = name;
}
sayName(){
console.log( this.name );
}
}('hello');
perosn.sayName(); // hello
5、不存在变量提升
1)类不存在变量提升, 这一点与 ES5 完全不同。因为 ES6 不会把变量提升到代码头部。这种规定与继承有关,必须保证子类在父类之后定义。
// 不允许变量
new Foo(); // ReferenceError
class Foo{};
// 继承
{
let Foo = class {};
class Bar extends Foo{}
}
6、this 指向
类的方法内部如果含有 this , 它将默认指向类的实例。一旦单独使用该方法,可能报错。
有三种解决方法:
· constructor(){ this.method = this.method.bind( this ) }
· constructor(){ this.method = () => { } }
· Proxy, 获取方法的时候自动绑定 this
class Logger {
constrcutor(){
this.printName = this.printName.bind( this );
this.print = this.print.bind( this );
}
printName( name ){
this.print(`Hello ${ name }`);
}
print( text ){
console.log( text );
}
};
let logger = new Logger();
const { printName } = logger;
7、Class 的取值函数( getter )和存值函数( setter )
1)在 “类” 的内部可以使用 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
prop 属性有对应的存值函数和取值函数, 因此赋值和读取行为都被自定义了
2)存值函数和取值函数是设置在属性的 Descriptor 对象上的
class Custom {
constructor( el ){
this.el = el;
}
get html(){
return this.el.innerHTML'
}
set html( value ){
this.el.innerHTML = value;
}
};
let descriptor = Object.gerOwnPropertyDescriptor( Custom.prototype, 'html' );
"get" in descriptor; // true
"set" in descriptor; // true
8、Class 的 Generator 方法
1)如果某个方法之前加上星号( * ), 就表示该方法是一个 Generator 函数
class Foo {
constructor( ...args ){
this.age = args;
}
* [Symbol.iterator](){
for( let arg of this.args ){
yield arg;
}
}
};
for( let x of new Foo( 'hello', 'world' ) ){
console.log( x );
};
9、Class 的静态方法
1)类相当于实例的原型, 所有在类中定义的方法都会被实例继承。如果在一个方法前加上 static 关键字, 就表示该方法不会被实例继承, 而是直接通过类调用,称为静态方法
class Foo {
static classMethod(){
return 'hello';
}
};
Foo.classMethod(); // hello
2)父类的静态方法可以被子类继承
class Foo {
static classMethod(){
return 'hello';
}
};
class barChild extends Foo {};
barChild.classMethod(); // hello
3)静态方法也可以从 super 对象上调用
class Foo {
static classMethod(){
return 'hello';
}
};
class barChild extends Foo {
static classMethodChild(){
return super.classMethod() + 'world';
}
};
10、Class 静态属性和实例属性
1)Class 的实例属性可以用等式写入类的定义之中
class MyClass {
myprop = 1;
constructor(){
console.log( this.myprop );
}
}
2)静态属性指的是 Class 本身的属性, Class.propname, 而不是定义在实例 对象( this )的属性, 只要在实例属性写法前面加上 static 关键字就可以了。
· 静态属性也是可以被子类继承的
· 也可以被 super 对象上调用
添加静态属性
class Foo {
static prop = 1;
};
静态属性被继承
class Foo {
static prop = 1;
};
class Bar extends Foo {};
Bar.prop;
super对象调用继承的静态属性
class Foo {
static prop = 1;
};
class Bar extends Foo {
barChild(){
return super.prop;
}
};
Bar.barChild(); // 1
11、new.target 属性
1)返回 new 命令所作用的构造函数。如果构造函数不是通过 new 命令调用的, 那么 new.target 会返回 undefined , 因此这个属性可以确定函数是怎么调用的。
function Person( name ){
if( new.target !== undefined ){
this.name = name;
}else if( new.target === Person ){
this.name = name;
}else{
throw new Error( '必须使用 new 生成实例' );
}
};
let person = new Person( 'hello' ); // 正确
let notPerson = Person.call( person, 'hello' ); // 报错
2)子类继承父类是 new.targe 会返回子类
class Rectang {
consiructor(){
console.log( new.target === Rectang );
}
};
class FooChild extends Rectang {
constructor(){
super();
}
};
let obj = new FooChild();
1、Class 的继承
1)Class 可以通过 extends 关键字实现继承。
class Point {};
class ColorPoint extends Point{};
2)Object.getPrototypeOf()—从子类获取父类
class Parent {};
class SumChild extends Parent{};
Object.getPrototypeOf( SumChild ); // class Parent {}
- super() 关键字: 可以当做函数使用,也可以当做对象使用。这两种情况下, 它的作用完全不同。
1)第一种情况, super() 作为函数调用时代表父类的构造函数。子类的构造函数必须执行 一次 super() 函数,否则会报错。super() 虽然代表了父类的构造函数,但是返回的是子类的实例, 即 super 内部的 this 是子类,因此 super() 等于就是Point.prototype.constructor.call( this );作为函数时, super() 只能在子类的构造函数之中, 用在其他地方就会报错
class Point {};
class FooChild extends Point {
constructor(){
super();
}
};
- super() 关键字
· super()作为函数时, super() 只能在子类的构造函数之中, 用在其他地方就会报错
· super() 作为函数调用时代表父类的构造函数。子类的构造函数必须执行一次 super() 函数,否则会报错。
· super() 虽然代表了父类的构造函数,但是返回的是子类的实例, 即 super 内部的 this 是子类,因此
super() 等于就是 父类.prototype.constructor.call( this );
class Point {};
class FooChild extends Point {
constructor(){
super();
}
};
· super 作为对象时在普通方法中指向父类的原型对象。但ES6规定, 通过 super 调用父类的方法,super 会绑定子类的 this, 由于绑定子类的 this , 因此如果通过 super 对某个属性赋值,这时 super 就是 this , 赋值的属性会变成子类的实例属性
例1 指向父类的原型对象
class Parent {
init(){
return 2;
}
};
class Child extends Parent {
constructor(){
super();
console.log( super.init() );
}
};
子类中的 super.init() 将 super 当做一个对象使用。这时 super 在普通方法之中指向
Parent.prototype, 所以 super.init() 就相当于 Parent.prototype.init();
由于prototype.init() 绑定了 Child 子类的 this, 实际上执行的是 super.init.call( this );
例2
class P {
constrcutor(){
this.index = 0;
}
};
class C extends P {
constrcutor(){
super();
this.index = 1;
super.index = 2;
console.log( super.index ); // undefined
console.log( this.index ); // 2
}
};
super.x 被赋值为 2, 等同于对 this.x 赋值为 2。当读取 super.x 时,相当于读取P.prototype.x,
所以返回 undefined
· 由于 super 指向父类的原型对象, 所以定义在父类实例上的方法或属性是无法通过super 调用,如果属性定义在父类的原型(prototype)对象上, super 就可以取到
例1 父类实例方法或属性不能通过 super 调用
class A {
constrcutor(){
this.y = 2;
}
};
class B extends A {
constrcutor(){
get toValue(){
return super.y;
}
}
};
let b = new B();
b.toValue(); // undefined
因为 y 是父类 P 实例的属性, 因此 super.y 就引用不到它。
例2 属性定义在父类的原型( prototype )对象上, super 可以调用
class A {};
A.prototype.y = 2;
class B extends A {
constructor(){
super();
console.log( super.y ); // 2
}
};
· 如果 super 作为对象用在静态方法之中, 这时 super 将指向父类, 而不是父类的原型对象
class Parent {
static parentStaticMethod( msg ){
console.log( `static, ${ msg }` );
}
ParentMethod( msg ){
console.log( `instance, ${ msg }`);
}
};
class Child extends Parent {
static chdilStaticMethod( msg ){
super.ParentMethod( msg );
}
childMethod( msg ){
console.log( msg );
}
}
· 使用 super 的时候, 必须显式指定是作为函数还是作为对象使用, 否则会报错
class A {};
class B extends A {
constructoe(){
super();
console.log( sunper ); // 报错
}
};
1、类的 prototype 属性和 proto 属性
1)每一个对象都有 proto 属性, 指向对应的构造函数的 prototype 属性。 Class 作为构造函数的语法糖, 同时拥有 prototype 属性和 proto 属性, 因此同时存在两条继承链。
· 子类的 proto 属性表示构造函数的继承, 总是指向父类的继承链
· 子类的 prototype 属性的 proto 属性表示方法的继承, 总是指向父类的prototype 属性
class A {};
class B extends A {};
console.log( B.__proto__ === A.prototype ); // true
console.log( B.prototype.__proto__ === A.protorype ); // true
2)类的继承的模式实现
class A {};
class B {};
// B实例继承A的实例
Object.setPrototypeOf( B.prototype, A.prototype );
// 等同于
B.prototype.__proto__ = A.prototype;
// B实例继承A的静态属性
Object.setPrototypeOf( B, A );
// 等同于
B.__proto__ = A;
const b = new B();
这两天继承链可以理解为: 作为一个对象, 子类( B )的原型( __proto__ 属性 )是父类( A );
作为一个构造函数, 子类( B )的原型( prototype )是父类的实例
1、extends 的继承目标
- extends 关键字后面可以跟多种类型的值, 但是还有3特殊情况
class B extends A {};
只要 A 有 prototype 属性的函数, 就能被 B 继承。由于函数都有 prototype 属性
(除了Function.prototype函数), 因此 A 可以是任意函数
· 子类继承 Object 类
class A extends Object(){};
console.log( A.__proto__ === Object ); // true
console.log(A.prototype.__proto__ === Object.prototype); // true
A 其实就是构造函数 Object 的复制, A 的实例就是 Object 的实例。
· 子类继承 null
class A extends null {};
console.log(A.__proto__ === Function.prototype); // true
console.log( A.prototype.__proto__ === undefined ); // true
· 不存在任何继承
class A {};
console.log(A.__proto__ === Function.prototype); // true
console.log( A.prototype.__proto__ === Object.prototype ); // true
1、实例的 proto 属性
· 子类实例的 proto 属性的 proto 属性指向父类实例的 proto 属性。 也就是说, 子类的原型的原型是父类的原型。
class Point {};
class ColorPoint extends Point {};
let point = new Point( 2, 3 );
let colorPoint = new ColorPoint( 2, 3, 'red' );
colorPoint.__proto__ === point.__proto__; // false
colorPoint.__proto__.__proto__ === point.__proto__; // true
ColorPoint 继承了 Point, 导致前者原型的原型是后者的原型
· 子类实例可以通过 proto.proto 属性来修改父类实例的行为
class Point {
constrcutor(){
this.name = 'hello';
}
printName(){
alert( this.name );
}
};
class ColorPoint extends Point {
constrcutor(){
super();
}
};
colorPoint.__proto__.__proto__.printName = function(){
this.name = 'world';
};