ES6 class

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。相对于传统的面向对象语言,语义化等方面差强人意。ES6引入了 Class概念,作为对象的模板,一定程度上可以将class视为构造函数的语法糖。

构造函数与类

传统构造函数

function Person(name,age){
	this.name=name;
	this.age=age;
}
Person.prototype.toString=function(){
	return `My name is ${this.name} , i am ${this.age} years old.`
}
var Joe=new Person('Joe',24);

Class

class Person{
	constructor(name,age) {
	     	this.name=name;
	     	this.age=age;
	}
	toString(){
		return `My name is ${this.name} , i am ${this.age} years old.`
	}
}
const Joe=new Person('Joe',24)

ES6 的类,完全可以看作构造函数的另一种写法。

class Person{}
console.log(typeof Person);//function
console.log(Person===Person.prototype.constructor);//true
特性

1.class 内定义的成员方法都是在类的prototype上的 ,如果是箭头函数则是定义在类实例上。

2.class 内声明的方法直接写方法名,使用function声明报错 。也无法在class内显示声明变量。

3.class 内部所有定义的方法,都是不可枚举的(non-enumerable)。

class Teacher{
	say(){}
	do(){}
	run=()=>{}
}
Object.keys(Teacher.prototype);//[]
Object.getOwnPropertyNames(Teacher.prototype);//["constructor", "say","do"]

4.通过Object.assign()方法可以向class批量添加方法。

Object.assign(Person.prototype, {
  toString(){},
  toValue(){}
});

5.类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。

constructor()

1.constructor()类的默认方法,通过new命令生成对象实例时,自动调用该方法。如果没有显式定义,一个空的constructor()会被默认添加。

2.constructor()默认返回实例对象(this),因此完全可以显示return obj; 以此改变默认返回的对象。

class Foo {
  constructor() {
    return Object.create(null);
  }
}

console.log(new Foo() );//{}
console.log(new Foo() instanceof Foo);//false  null没有原型对象

3.创建实例的时候constructor()等同于构造函数,代码块内this指向的属性都会作为实例自身的属性。(没有修改默返回对象情况下)

4.实例属性除了定义在constructor()里面的this上面,也可以定义在类的最顶层。

class Foo{
	bar = 1;
	baz = 2;
	fun(){}
}
new Foo();// Foo {bar: 1, baz: 2}

可见定义在顶层的属性实则还是放进了constructor()中执行的。

取值函数 getter 和 设值函数 setter

取值函数和设值函数,就是get set属性描述符。因为是方法的形式,所以也是定义在prototype上的。

class Teacher{
	constructor(name) {
	    this._name=name;
	}
	get privateName(){
		return this._name;
	}
	
	set privateName(name){
		this.privateName=_name;
	}
}
const Joe=new Teacher('Joe');
console.log(Joe.privateName);//Joe
console.log(Object.getOwnPropertyDescriptor(Teacher.prototype,"privateName"));
//{enumerable: false, configurable: true, get: ƒ, set: ƒ}

class表达式

声明
const MyClass = class { /* ... */ };
立即执行
const MyClass = new class { /* ... */ }();
特殊
const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};
//let in = new Me();//报错
let inst = new MyClass();
console.log(inst.getClassName());

这个类的名字是Me,但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用MyClass引用。

class需要注意的地方

严格模式

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。

class不能提升

class 声明的类名不存在变量提升。

new Foo(); // ReferenceError
class Foo {}
this 的指向

类的方法内部如果含有this,它默认指向类的实例。但是必须非常小心,一旦单独使用该方法,很可能因为this指向丢失报错。

class Logger {
  printName(name = 'there') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}
const logger = new Logger();
//logger.printName(); //Hello there
const { printName } = logger;//传递了引用

printName();//报错 Uncaught TypeError: Cannot read property 'print' of undefined

logger.printName()是正常打印的,因为printName()使用的this指向的就是logger实例对象。
const printName =logger.printName 建立一个指向logger.printName方法体的引用。printName()执行的时候,依赖的上下文对象是全局对象。相当于要把全局对象传递进去当成this,然而,严格模式下全局环境禁止 this 指向全局对象,而是指向undefined的。


📌构造函数模拟上述class

function Logger(){
}
Logger.prototype.printName=function(name = 'there'){
	console.log(this);//window
	this.print(`Hello ${name}`);
}
Logger.prototype.print=function(text){
	print(text);
}
const logger = new Logger();
const { printName } = logger;

printName();//执行了window.print() 触发页面打印功能

📌解决class 方法调用失去上下文(this)问题

一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。

class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}

创建实例的时候,调用构造方法时候已经绑定了printName 方法的this就是实例对象了。

另一种解决方法是使用箭头函数。

静态方法(类方法)

类的普通的方法都是定义在类的原型上的,方法会被继承,需要通过类的实例对象来调用。

特征

1.类的静态方法则是定义在类上的,方法不会被继承,直接通过类调用。

class Foo{
	static fun(){console.log('static');}
	bar(){}
}
console.dir(Foo)//class Foo
Foo.fun();//static

在这里插入图片描述
2.该方法没被继承,隶属于类对象的,无法被实例调用。

new Foo().fun();//Uncaught TypeError: (intermediate value).fun is not a function

3.如果静态方法包含this关键字,这个this指的是类,而不是实例。

4.静态方法可以与非静态方法重名。

class Foo {
  static bar() {
    this.baz();
  }
  static baz() {
    console.log('hello');
  }
  baz() {
    console.log('world');
  }
}

Foo.bar() // hello

静态属性

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
两种方式:1.class 内static声明 2.class 外像普通对象一样加属性

class Foo{
	bar = 1;
	static baz=2
}
Foo.bac=3
console.log(new Foo())// Foo {bar: 1}
console.log(Foo.bac,Foo.baz);//3 2

私有方法和私有属性

ES6 不提供私有方法和私有属性,只能通过变通方法模拟实现。

私有方法和私有属性,指的是只能在类的内部访问的方法和属性,外部不能访问。

声明式的私有

变量名方法名前面加下划线,告诉别人只是私有的,一种约定俗成的做法!明显,这样做并不真正的私有,掩人耳目罢了!

class Foo{
	_bar = 1;
	_baz(){}
}

使用Symbol类型的属性名

这个只要编码者不透露信息给使用者捕捉并解读到,就相当于私有了

const bar=Symbol()
const baz=Symbol()
class Foo{
	[baz] = 1;
	[bar](){}
}
let foo=new Foo()
console.log(foo);//Foo {Symbol(): 1}
console.log(Reflect.ownKeys(foo))//[Symbol()]

在这里插入图片描述

类的继承

Class 可以通过extends关键字实现继承, ES5 则通过修改原型链实现继承。

继承机制

ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面Parent.apply(this)

ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到子类的this上面(所以必须先调用super方法),然后再用子类的构造函数修改this

1.子类必须在constructor()中调用super(),否则新建实例时会报错。

因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super(),子类就得不到this对象。

class Parent{
	constructor(name){
		this.name=name
	}
}
class Son extends Parent{
	constructor(name,age) {
	    super(name);
		this.age=age
	}
}
let s=new Son('Joe',25);

2.子类没有显示定义constructor方法,会在默认构造方法调用super方法。

class Son extends Parent{
}

// 等同于
class Son extends Parent{
  constructor(...args) {
    super(...args);
  }
}

3.在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。

class Son extends Parent{
	constructor(name,age) {
		console.log(this.name);//Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
	    super(name);
		this.age=age
	}
}

4.Object.getPrototypeOf()判断一个类是否继承了另一个类。

console.log(Object.getPrototypeOf(Son)===Parent);//true

super

super这个关键字,既可以当作函数使用,也可以当作对象使用。

super作为函数

代表父类的构造函数,只能用在子类的构造方法中。

class A {}

class B extends A {
  constructor() {
    super();
  }
}

super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。

super作为对象

在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

class Father{
	constructor(){
		this.other=1;
	}
	
}
class Son extends Father{
	constructor() {
	    super();
		console.log(super.other);//undefined
	}
}
console.log(new Son());

other属性是定义在父类的实例对象上的(constructor方法是用来创建实例的,this指向实例对象),而在普通方法中super是指向原型对象的。super指向Father.prototype。


在子类普通方法中通过super调用父类的方法时,父类方法内部的this也指向当前的子类实例。`super.say.call(this)`
class Father{
	x=1;
	say(){
		console.log(this.x);
	}
}
class Son extends Father{
	x=2;
	speak(){
		super.say()
	}
}
let boy=new Son();
boy.speak();//2

子类对super的属性的读取操作:读取得是父类原型属性,写入的是子类实例
class Father{
	x=1;
	say(){
		console.log(this.x);
	}
}
class Son extends Father{
	constructor(arg) {
	    super();
		this.x=2;
		super.x=3;
		console.log(this.x);//3
		console.log(super.x);//undefined
	}
}
let boy=new Son();

super在静态方法之中指向父类自身,在普通方法之中指向父类的原型对象。
class Father{
	x=1;
	static x=2;
	static say(){
		console.log('c',this.x);
	}
}
class Son extends Father{
	 x=3
	static speak(){
		console.log('a',super.x);//读取父类自身属性x
		console.log('b',this.x);//读取子类自身属性x
		super.say()//父类静态方法中的this是指向子类自身 
	}
}
Son.speak();
console.dir(Son)

在这里插入图片描述
b 2 原因是Son类自身没有x属性,会往原型链查找该变量,找到父类身上的x,就是2

改一下:

class Son extends Father{
	 static x=3
	static speak(){
		console.log('a',super.x);//读取父类自身属性x
		console.log('b',this.x);//读取子类自身属性x
		super.say()//父类静态方法中的this是指向子类自身 
	}
}
Son.speak();
console.dir(Son)

在这里插入图片描述

原生构造函数的继承

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()
class myArray extends Array{
	constructor(...args) {
	    super(...args)
	}
	//自定义方法  获取对象数组特定key对应的值数组
	 getMappingValueArrayOfKey(keyName){
		if(Object.prototype.toString.call(this)=='[object Array]'){
			return this.map((item,index)=>{
				return item[keyName]
			})
		}
		return 'null(参数一应为对象数组)';//不是数组
	}
}
const arr=new myArray({name:'dog',sound:'汪汪'},{name:'cat',sound:'喵喵'})
arr.push({name:'pig',sound:'哼哼'});
console.log(arr);//[{…}, {…}, {…}]
console.log(arr.getMappingValueArrayOfKey('name'));//["dog", "cat", "pig"]

参考文档:
Class 的基本语法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值