1.类的由来
javascript 语言中,生成实例对象的传统方法是构造函数。
function P(x, y) {
this.x = x;
this.y = y;
}
P.prototype.toString = function() {
return `($(this.x),$(this.y))`;
};
var p = new P(1,2)
上面这种写法跟传统的面向对象语言(比如C++和java)差异很大,很容易让我学习这门语言感到困惑
ES6的创建类的方式:
class P {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `($(this.x)+$(this.y))`
}
}
// 原型链添加方法
P.prototype.toValue = function() {console.log(this.x)}
// 使用的时候
const p = new P(1,3);
p.toString()
P.prototype.constructor === P // true
2. constructor()方法
construction是类的默认方法,当类被实例化时自动执行 类的属性
class Inc {
_conut = 0;
get value() {
console.log('Getting the current value');
return this._count;
}
increment() {
this._count++;
// 还可以,不知道继承后能不能
super._count++
}
}
// 实例属性_count 与取值函数 value() 和increments()方法,
//处于同一个层级,这时,不需要在实例属性前面加上this
3.取与存值的函数
class MyClass {
consotructor() {}
get prop() { return 'getter'}
set prop(value){ console.log('setter ' + value);}
}
let inst = new MyClass();
inst.prop = 123
//VM3253:4 setter 123
123
inst.prop
//'getter'
descrtiptor = Object.getOwnPropertyDescriptor(MyClass.prototype, 'prop')
//{enumerable: false, configurable: true, get: ƒ, set: ƒ}
"get" in descrtiptor //true
"set" in descrtiptor // true
// 存值函数和取值函数都是定义在prop属性和描述对象上面,这与ES5完全一致
这样实例化后的prop既能存又能付值 是不是后期可以用来判断这个值复值的时候进行判断,或者其他操作,取的时候也可以额外添加一些功能和判断
4.Class表达式
与函数一样,类也能使用表达式来定义。
const MyClass = class Me{
getClassName() { reutrn Me.name};
}
let inst = new MyClass();
inst.getClassName();
Me.name
5. 静态方法
class Foo {
static count = 1;
static classMethod() {
console.log('I im startic')
}
sum() {Foo.count++; console.log(Foo.count)};
static sum() {console.log(this.count)};
// Foo.sum() 输出的是1每次都是1,然的说this在静态方法中指向的是类吗?而不是实例。
// 静态属性每次输出都是有记忆功能的。如何的实例
}
Foo.sum()
这个例子可以看出,静态方法的this是类的,而不是实例化的,而且静态方法和类方法是可以同名的。 静态方法可以调用静态方法。
class Foo {
static classMethod() {
console.log('hello')
return 'hello'
}
}
class Bar extends Foo{
// 继承Foo父类
static classMethod() {
// 重写静态方法
// 也可以先执行父类的静态方法
super.classMethod()
return 'world'
}
}
Bar.classMethod()
// 这个例子告诉了我们,继承父类的子类,也继承了父类的静态方法
// 也可以重写之前,执行父类方法super
6.私有方法和属性
私有方法和属性,是只能在类的内部访问的方法和属性,外部不能访问。这时常见的需求,有利于代码的封装,但很早的ES6 不提供,只能通过变通方法模拟实现。
class IncreasingConter {
#x;
constructor(x=0) {
this.#x = +x;
// 这里+x可以把x自动转成数值
}
get x() {
return this.#x;
}
set x(value) {
this.#x = +value
}
}
class IncreasingConter {
#x;
constructor(x=0) {
this.#x = +x;
}
get x() {
return this.#x;
}
set x(value) {
this.#x = +value
}
#sum () {
return this.#x + 3;
}
printSum() {
console.log(this.#sum())
}
static getCountValue(instance){
return instance.#x
}
// 静态方法取私有属性,必须传一个实例
static #compute() {
// 这是一个私有的静态方法
return IncreasingConter.#x;
}
}
私有属性不存在时,在哪里访问都会报错,带有#的私有属性或方法,在外部访问不了只能在类里访问,私有属性也可以用getter和seter方法,私有属性不限于this引用,只要是在类的内部,或者实例。
7.in运算
判断私有属性时,in 只能用在类的内部
8. 静态块
静态块的一个问题是,如果它有初始化逻辑,这个逻辑要么写在内的外部,要么写在constructor()方法里面。 使用静态块,在类生成时候且只运行一次,主要作用是对静态属性进行初始值化,以后,新建类的实例时,这个块就不在运行了。
class C {
static x =223;
static y;
static z;
constructor() {
// 这样初始化值会导致每次创建实例的时候都会调用一次
const obj = doSomethingWith(C.x)
C.y = obj.y;
C.z = obj.z;
}
static sum() {
// 这样你要手动来调用一次。
const obj = doSomethingWith(this.x)
this.y = obj.y;
this.x = obj.x;
}
// 使用静态块的方法
static {
try{
const obj = doSomethingWith(this.x)
this.y = obj.y;
this.x = obj.x;
}
catch{
this.y = ...;
this.x = ...;
}
}
}
try {
// 这样到外部创建也不方便。
const obj = doSomethingWith(C.x)
C.y = obj.y;
C.z = obj.z;
}catch {
C.y = ...;
C.z = ...;
}
//除了静态块属性的初始化,还可以将私有属性与类的外部代码分享。
class A {
#x = 1;
static {
getC = obj => obj.#x;
setC = obj => obj.#x = 23;
}
}
console.log(getC(new A())) // 1
9 类的注意点
1.严格模式
类和模块的内部,默认就是严格模式,所以不需要使用 use strict
2.不存在提升
new Foo() //ReferenceError
class Foo {}
使用在前,定义在后是会报错的
3.name属性
由于本质上s5和s6的构造函数的一层保障,所以函数的许多特性都被class继承,包括name
4.如果某个方法之前加上了星号(*)就表示该方法是一个Generator函数
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明
10 类的继承
Class类是可以通过 extends 关键字实现继承,让子类继承父类的属性,方法。
class A {
}
class B extends A {}
// A是父类,B是子类,B继承了A父类的方法和属性,等同于复制了父类
子类继承父类必须要在构造方法constructor()方法内调用super()方法,如果不调用子类将无法找到this 为啥要调用super,因为es6不同es5的继承机制,es5是先创建独立的子类实例对象,再将父类的方法和属性添加到这个对象上,既“实例在前,继承在后”。而es6的继承机制是,先将父类的属性和方法,加到一个空对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后” 如果不调用super方法,这一步就会生成一个继承父类的this对象。另外要注意的是,只有在先调用了super()方法之后才能使用this关键字,否则会报错,这时因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。
class P { }
class C extends P{
constructor(x, y, color) {
this.color = color; /// 错误的方式❌这里会报错。ReferenceError
super(x, y)
this.color = color; // 正确✅
}
toString() {
return this.color + ' ' + super.toString();
}
}
如果子类没有定义constructor()方法,这个方法会默认添加,并且还会调用super()方法,不管有没有显式定义,任何一个子类都有constructor()方法。
11. 私有属性和私有方法的继承。
子类无法访问父类的私有属性和方法,私有的属性和方法都只能再定义他的class类中使用。
class A{
#a = 1; // 私有属性
#m() {
console.log('hello')
};
getA() {
return this.#a;
}
}
class B extends A {
consrtuctor() {
super()
// 访问父类私有属性
console.log(this.#a) // 报错
this.#m() // 报错
//正确的访问方式
console.log(this.getm()) // 1
}
}
}
12. 静态属性和静态方法的继承
class A {
static foo = 100;
static obj = {n: 200} // 静态对象
}
class B extends A {
constructor() {
super()
B.foo--; // A.foo=100,B.foo=99
B.obj.n++; // a.obj.n =201,b.obj.n=201
}
}
// 静态属性和方法都是会继承给子类的,但是静态属性由于是浅拷贝,拷贝父类静态属性的值,当属性不是一个对象时候,那么继承的这个静态属性是两个彼此独立的。
// 如果是一个对象,由于浅拷贝是复制对象的内存地址,所以一个静态对象改变则都会变
13. Object.getPrototypeOf()方法可以用来从子类上获取父类,可以用来判断一个类是否继承了另一个类
14. super 关键字
super 这个关键字,即可当作函数使用,又可以当对象使用。这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super()函数。
class A {
constructor(){
console.log(new.target.name);
}
}
class B extends A {
constructor(){super()}
}
new A(); // A
new B(); // B
// 虽然super是代表构造父类的方法,但是它内部的this内部指向的是B
class A {
name = 'a';
constructor(){
console.log('My name is ' + this.name);
}
}
class B extends A {
name = 'b'
constructor(){
super()
}
}
const b = new B(); // 输出 My name is a
// 为啥是a呢?不是说super执行的this指向的是实例类吗?
// 这是因为在子类构造方法执行时,子类的属性和方法还没绑定到this,所以如果存在同名属性,此时拿到的事父类的属性
如果super作为函数使用,它只能在子类构造函数中使用,在其他地方使用就会报错 第二种情况:super作为对象时,在普通方法中,指向父类的原型对象,在静态方法中指向父类
class A {
constructor() {
}
s() {
console.log('a 的实例方法')
}
static s() {
console.log('a 的静态方法')
}
}
class B extends A {
constructor() {
super();
}
p() {
super.s()
}
static p() {
super.s();
}
}
new B().p() // a 的实例方法
B.p() //a的静态方法
注意:由于super 指向父类的原型对象,所以定义在父类的实例方法或属性,时无法通过super 调用的。
class A {
constructor(){
this.a = 2;
}
}
class B extends A {
get m() {
return super.a; // a is not defind
}
}
let b = new B();
b.m() // 报错
// 我们可以使用原型连来定义属性,这样super就能使用了
class A {
}
A.protostyle.x = 2;
class B extends A {
get m() {super.x} // 正常输出2
}
es6规定,在子类普通方法中通过 super 调用父类的方法时候,方法内部的this指向的是当前子类的实例
class A {
constructor() {
this.p = 2
}
c() {
console.log('super c this'+ this.p)
}
}
class B extends A {
constructor() {
super()
this.p = 3
}
a = 'b'
get m() {
return super.c();
}
}
const b = new B()
b.m // 输出 super c this 3 这里是实例子类的p
class A {
constructor() {
this.p = 2
}
c() {
console.log('super c this'+ this.p)
}
}
class B extends A {
constructor() {
super()
// 由于super指向的是子类实例所以通过super赋值的时候是修改子类的属性,
// super,同等于this 赋值时同等于this
this.p = 1;
super.p = 4;
console.log(super.x) // undefined 因为相当于 A.prototype.p 父类中没有原型属性p所以返回undefind
}
a = 'b'
get m() {
return super.c();
}
}
super对象 在子类静态方法中指向的是父类
在子类的静态方法中指向的是父类,在子类实例方法中指向的是父类的原型,prototype
另外在子类静态方法中调用父类的方法时,this指向的是子类,而不是子类的实例。
注意,使用super时,必须显示指定作为函数还是对象,如果不指定则报错
console.log(super) // 报错
15 Mixin模式
Minxin 指的是多个对象合成一个新的对象,新的对象具有各个组成成员的接口,它的最简单实现如下
const a = {
a: 'a'
};
const b = {b:'b'}
const c = {...a, ...b}