9、JavaScript中的类

第九章、JavaScript中的类


1、类的声明
    1、基本的类声明语法
        不需要在类的个元素之间使用逗号隔开。类除了constructor外没有其他保留的方法名。

class PersonClass {
	constructor(name){
		this.name = name;
	}
	sayName(){
		console.log(this.name);
	}
}
let person = new PersonClass("zhangsan");
person.sayName();

        自有属性是实例中的属性,不会出现在原型上,且只能在类的构造函数或方法中创建。建议在构造函数中创建自有属性,可以方便的控制。
        类属性不可被赋予新值。
        类声明仅仅是基于已有自定义类型声明的语法糖。

    2、为何使用类语法
        类声明与自定义类型的区别:
            1、函数声明可以被提升,类声明与let声明不可以;真正执行声明语句之后,它们会一直存在于临界死区。
            2、类声明中的所有代码自动运行在严格模式下。
            3、类中所有方法都是不可枚举的;自定义类型中,需要通过Object.defineProperty()手动指定某个方法不可枚举。
            4、每个类都有一个[[constructor]]的内部方法,通过关键字new调用那些不含[[constructor]]的方法会报错。
            5、使用除new关键字以外的方式调用类的构造函数会报错。
            6、在类中修改类名会报错。
        类的名称在类中你能修改是因为,类的名称在类中相当于常量const声明的;在类外可以修改是因为,类的名称在类外相当于变量let生命的。

2、类表达式
    类和函数类似,也存在表达式形式
    1、基本的类表达式语法

let PersonClass = class {
	constructor(name){
		this.name = name;
	}
	sayName(){
		console.log(this.name);
	}
}
let person = new PersonClass("zhangsan");
person.sayName();

        类表达式不需要标识符在类后,类表达式在功能上等价于类声明。
        类表达式也不存在变量提升。

    2、命名类表达式
        声明时,在关键字class后添加一个标识符即可定义为命名类表达式。

3、作为一等公民的类
    一等公民是指一个可以传入函数,可以从函数返回,可以赋值给变量的值。
    JavaScript中函数和类都是一等公民。    
    将类作为参数传入函数中:

function createObj(classDef){
	return new classDef();
}
let obj = createObj(class {
	sayHi(){
		console.log("Hi");
	}
});
obj.sayHi();		//"Hi"

    类表达式的另一种使用方式:通过立即调用类构造函数创建单例。

let person = new class {
	constructor(name){
		this.name = name;
	}
	sayHi(){
		console.log(this.name);
	}
}("lisi");
person.sayHi();		//"Hi"

4、访问器属性
    类支持在原型上定义访问器属性
    访问器属性getter和setter

class CustomHTMLElement {
	constructor(element){
		this.element = element;
	}
	get html(){
		return this.element.innerHTML;
	}
	set html(value){
		this.element.innerHTML = value;
	}
}
let descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype,"html");
console.log("get" in descriptor);		// true
console.log("set" in descriptor);		// true
console.log(descriptor.enumerable);		// false

5、可计算成员名称
    类方法支持使用可计算名称。

let methodName = "sayName";
class PersonClass {
	constructor(name){
		this.name = name;
	}
	[methodName](){
		console.log(this.name);
	}
}
let me = new PersonClass("wangwu");
me.sayName();		// "wangwu"

    访问器属性也支持使用可计算名称。

let propertyName = "html";
class CustomHTMLElement {
	constructor(element){
		this.element = element;
	}
	get [propertyName](){
		return this.element.innerHTML;
	}
	set [propertyName](value){
		this.element.innerHTML = value;
	}
}

6、生成器方法
    可以将任何方法定义成生成器,包括类中的方法。
    如果一个类是用来表示值得集合的,就应该为它定义一个默认迭代器。
    使用Symbol.iterator定义生成器方法即可为类定义默认迭代器。

class Collection {
	constructor(){
		this.items = [];
	}
	*[Symbol.iterator](){
		yield this.items.values();
	}
}
let collection = new Collection();
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let v of collection){
	console.log(v);
}

7、静态成员
    创建静态成员:在方法或访问器属性名前使用正式的静态关键字static

class PersonClass {
	constructor(name){
		this.name = name;
	}
	static create(name){
		return new PersonClass(name);
	}
}
let person = PersonClass.create("shuhe");

    类中的所有方法和访问器属性都可以用static关键字定义,除了构造函数constructor
    备注:不可在类实例中访问类的静态成员,只能通过类直接访问。

8、继承与派生类
    使用extends关键字实现指定类的继承,原型会自动调整,通过调用super()方法即可访问基类的构造函数。

class Rectangle {
	constructor(length,width){
		this.length = length;
		this.width = width;
	}
	getArea(){
		return this.length * this.width;
	}
}
class Square extends Rectangle {
	constructor(length){
		super(length,length);
	}
}
let square = new Square(3);
console.log(square.getArea());					// 9
console.log(square instanceof Square);			// true
console.log(square instanceof Rectangle);		// true

    继承自其他类的类被称为派生类,如果在派生类中指定了构造函数则必须调用super(),否则程序会报错;
    如果在派生类中不使用构造函数,则当创建派生类的实例时,系统会自动调用super()并传入参数。
    备注:
        ①、只可在派生类的构造函数中使用super(),如果尝试在非派生类中或函数中使用会报错。
        ②、在派生类的构造函数中访问this之前一定要调用super(),它负责初始化this,如果在调用super()之前访问this会报错。
        ③、如果不想调用super(),则唯一的方法是让类的构造函数返回一个对象。

    1、类方法遮蔽
        派生类中的方法总会覆盖基类中的同名方法。
        在派生类中的方法中可以通过super调用基类中的同名方法。

class Square extends Rectangle {
	constructor(length){
		super(length,length);
	}
	getArea(){
		return super.getArea();
	}
}

    2、静态成员继承
        如果基类中有静态成员,那么这些静态成员在派生类中也可用。

    3、派生自表达式的类
        只要表达式可以被解析为一个函数并且具有[[constructor]]属性和原型,那么就可以用extends进行派生。
        extends强大的功能使得类可以继承自任意类型的表达式。

function Rectangle(length,width){
	this.length = length;
	this.width = width;
}
Rectangle.prototype.getArea = function(){
	return this.length * this.width;
};
function getBase(){
	return Rectangle;
}
class Square extends getBase() {
	constructor(length){
		super(length,length);
	}
}
let x = new Square(3);
console.log(x.getArea());		// 9
console.log(x instanceof Rectangle);	// true

        由于可以动态的确定使用哪个基类,因此可以创建不同的继承方法。

let SerializableMixin = {
	serialize(){
	return JSON.stringify(this);				}
};
let AreaMixin = {
	getArea(){
		return this.length * this.length;
	}
};
function mixin(...mixins){
	let base = function(){};
	Object.assign(base.prototype,...mixins);
	return base;
}
class Square extends mixin(AreaMixin,SerializableMixin){
	constructor(length){
		super();
		this.length = length;
		this.width = length;
	}
}
let x = new Square(3);
console.log(x.getArea());		// 9
console.log(x.serialize());		// "{"length":3, "width":3}"

        备注:由于使用了extends关键字,派生类构造函数必须调用super();
            如果多个mixin对象具有相同属性,那么是有最后一个被添加的属性被保留。

    4、内建对象的继承
        在ES5的传统继承中,先又派生类型创建this的值,然后调用基类型的构造函数。即this的值开始指向的是派生类的实例。
        ES6中的继承恰好相反,先由基类创建this的值,然后派生类的构造函数再来修改这个值。
        基于类生成特殊数组的例子:

clas MyArray extends Array {
	// ...
}
let colors = new MyArray();
colors[0] = "red";
console.log(colors.length);		// 1
colors.length = 0;
console.log(colors[0]);			// undefined

    5、Symbol.species属性
        内建对象的一个实用之处:原本在内建对象中返回实例自身的方法将自动返回派生类的实例。

clas MyArray extends Array {
	// ...
}
let items = new MyArray(1,2,3,4);
let subItems = items.slice(1,3);
console.log(items instanceof Array);		// true
console.log(subItems instanceof Array);		// true

        这种情况是通过Symbol.species属性实现的。
        Symbol.species被用于定义返回函数的静态访问属性。
        被返回的函数是一个构造函数(即this),每当要在实例的方法中创建类的实例时必须使用这个构造函数。
        已经定义Symbol.species属性的内建类型有:
            Array、ArrayBuffer、Map、Promise、RegExp、Set、Typed arrays
        通过Symbol.species可以定义当派生类的方法返回实例时,应该返回的值的类型。

clas MyArray extends Array {
	static get [Symbol.species](){
		return Array;
	}
}
let items = new MyArray(1,2,3,4);
let subItems = items.slice(1,3);
console.log(items instanceof Array);		// true
console.log(subItems instanceof Array);		// true
console.log(subItems instanceof MyArray);		// false

        一般来说,只要想在类方法中调用this.constructor,就应该使用Symbol.species属性,从而让派生类重写返回类型。

9、在类的构造函数中使用new.target
    new.target的值是根据函数被调用的方式改变的。
    在类的构造函数中也可以通过new.target来确定类是如何被调用的。
    简单情况下,new.target等于类的构造函数。但继承存在特例:

class Rectangle {
	constructor(length,width){
		console.log(new.target === Rectangle);
		this.length = length;
		this.width = width;
	}
}
class Square extends Rectangle {
	constructor(length){
		super(length,length);
	}
}
// new.target的值是Square
let obj = new Square(3);		// false

    可以用new.target创建一个抽象基类。

class Shape {
	constructor(){
		if(new.target === Shape){
			throw new Error("这个类不能直接被实例化");
		}
	}
}
class Rectangle extends Shape {
	constructor(length,width){
		super();
		this.length = length;
		this.width = width;
	}
}
let x = new Shape();		// 抛出错误
let y = new Rectangle(3,4);	// 没有抛出错误
console.log(y instanceof Shape);	// true

  备注:类必须通过关键字才能调用,所以在类的构造函数中,new.target值永远不会是undefined。    

发布了247 篇原创文章 · 获赞 23 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/LiyangBai/article/details/103223575