1、简介
类的由来
javascript语言中,生成实例对象的传统方法是通过构造函数
es5的写法function Point(x,y){this.x=x;this.y=y;}Point.prototype.toString=function(){console.log('toString');}var p=new Ponit(1,2);es6的class写法class Point{constructor(x,y){this.x=x;this.y=y;}toString(){console.log('toString');}}类的所有方法都定义在类的prototype属性上面class Point {constructor(){// ...}}Object.assign(Point.prototype, {toString(){},toValue(){}});类的内部所有的方法,都是不可枚举的,这一点和es5不一致(可枚举可以用for in,或者Object.keys()遍历出来)class Point {constructor(x, y) {// ...}toString() {// ...}}Object.keys(Point.prototype)// []Object.getOwnPropertyNames(Point.prototype) //["constructor","toString"]Point === Point.prototype.constructorconstructor()方法默认返回实例对象(即this),完全可以指定返回另一个对象与Es5一样,类的所有实例共享一个原型对象var p1=new Ponit();var p2=new Ponit();p1.__proto__===p2.__proto__p1和p2都是Point的实例,它们的原型都是Point.prototypeObject.getPrototypeOf 方法来获取实例对象的原型point.__proto__===Point.prototype
constructor方法
constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法
constructor()方法默认返回实例对象(即this),完全可以返回另外一个对象
class Foo{constructor(){return Object.create(null)}}new Foo() instanceof Foo //false
类的实例
与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则是定义在原型上(即定义在class上),类的所有实例共享一个原型对象
class Point{}var point=Ponit(2,3) //报错
取值函数(getter)和存值函数(setter)
与es5一样,在‘类’的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为
存值和取值函数是设置在属性的Descriptor对象上的
var descriptor=Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html")'get' in descriptor;'set' in descriptor;
class表达式
let person=new class {constructor(name) {this.name=name;}sayName(){}}('章三')
注意点⚠️
(1)、严格模式
(2)、不存在变量提升
(3)、name属性
class Point {}Point.name // "Point"
(4)、this的指向
class Logger {printName(name='there') {this.print(`Hello`)}print(text){console.log(text)}}const logger=new Logger();const {printName}=logger;printName();
上面方法printName方法中的this,默认指向Logger类的实例话,但是这个方法单独取出来使用,this会指向该方法
运行时所在的环境,由于class是严格模式,所以this实际指向的是undefined。
一个比较简单的解决方法是在构造方法中绑定this.
class Logger {constructor(){this.printName=this.printName.bind(this)}}
另一种解决方案是使用箭头函数
class Obj {constructor(){this.getThis=()=>this;}}const myObj = new Obj();myObj.getThis() === myObj // true
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承,如果在一个方法前加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为’静态方法’;
⚠️如果静态方法包含this关键字,这个this指向的是类,而不是实例
静态方法可以被继承
静态方法也可以从super对象上调用的
class Foo {static classMethod(){return 'hello'}}class Bar extends Foo {static classMethod(){return super.classMethod()+',too';}}Bar.classMethod() //'hello,too'
静态属性
静态属性指的是Class本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性
因为Es6明确规定,Class内部只有静态方法,没有静态属性。现在有一个提案,在实例属性的前面,加上static关键字。
实例属性
实例属性除了定义在constructor方法里面的this上面,也可以定义在类的最顶层。
class Point {constructor(){this._count=0;}}class Point {_count=0;}
new.target属性
new.target属性可以用来确定构造函数是怎么调用的
方法一function Person(name) {if(new.target!==undefined) {this.name=name;}else{throw new Error('必须使用new命令生成实例');}}方法二function Person(name) {if(new.target===Person) {this.name=name;}else{throw new Error('必须使用new命令生成实例');}}
1、class内部调用new.target,返回当前Class
2、子类继承父类时,new.target会返回子类 (利用这一点可以写出不能独立使用,必须继承后才能使用的类)
super关键字
super关键字,即可以当作函数使用,也可以当作对象使用
- 第一种:super作为函数调用时,代表父类的构造函数,返回的是子类的实例,即super内部的this指的是B的实例,ES6要求,子类的构造函数必须执行一 次super函数
第二种:super作为对象时,在普通方法中,指向父类的原型对象;
在静态方法中,指向父类;在ES6规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向 当前的子类实例 ```javascript class A{ constructor(){ this.x=1; } print(){ console.log(this.x) } }
class B extend A{ constructor(){ super(); this.x=2; } m(){ super.print(); } } let b=new B(); b.m() //2
由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。```javascriptclass A {constructor() {this.x = 1;}}class B extends A {constructor() {super();this.x = 2;super.x = 3;console.log(super.x); // undefined,这里太绝了吧,表示没看懂💔console.log(this.x); // 3}}let b = new B();
上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined。
注意⚠️使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
