js 基础 - class
2023-07-25 18:15:47 # fontend

1. new 操作符干了什么?

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

  1. 创建一个新对象
  2. 新对象的原型对象指向当前构造函数的原型对象
  3. 当前 this 指针指向新对象
  4. 如果没有返回对象,则返回 this
1
2
3
4
5
function _new(obj, ...rest) {
let newObj = Object.create(obj.prototype);
let result = obj.apply(newObj, rest);
return typeof result === 'object' ? result : newObj;
}

2. new.target 是什么属性

new.target 属性允许你检测函数或构造方法是否通过 new 运算符调用的,在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是undefined。

3. 为什么一定要调用 super 才能实现继承?

因为 es6 的继承机制是继承在前,实例在后,先将父类的属性和方法加到一个空对象,然后再把空对象作为子类的实例; es5 是实例在前,继承在后

4. 说说你理解的 class

es5 之前生成实例对象是通过 new 构造函数来实现的,这种写法不够直观简洁,所以 es6 中引入 class 这个概念,通过 class 关键字可以定义类,它其实是一个语法糖,大部分功能 es5 也能做到,新的写法只是更清晰,更像面向对象编程的语法。

其基本语法是:

1
2
3
4
5
6
7
8
9
10
11
12
class A {
constructor(name, age) {
this.name = name;
this.age = age;
}

hello() {
return `我的名字是 ${this.name}`;
}
}

let a = new A();

this

class 只能通过调用 new 实例化对象,其他调用方式都会报错,通过 new 操作符,将 this 指向当前实例化的对象。new.target 的属性指向创建实例对象的当前类。

super

  1. 子类继承父类的时候,必须在 constructor 中执行一次 super(),其作用是形成子类的 this 对象,将父类的实例属性和方法放到这个 this 对象。
  2. super 既可以作为对象使用,也可以当作函数调用。作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类。

实例属性和原型属性

  1. constructor 中定义的属性属于实例对象上的,并非原型上的,但是类中定义的方法默认是原型方法,除非添加特殊关键字 static 或者 #
  2. 如何声明原型上的属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class A {
constructor(name, age) {
this.name = name;
this.age = age;
}

get defaultName() {
return this.name;
}

}

let a = new A('test', 18);
a.hasOwnProperty('defaultName'); // false defaultName 则是定义在原型对象上的属性
a.hasOwnProperty('age'); // true

静态属性或方法 & 私有属性或方法

  1. static 关键字表示静态,不会被实例化,只能直接通过类来调用;但是可以被继承,可通过子类访问
  2. # 关键字表示私有,早期解决方案为 _属性名称, 私有属性会被实例化,但是实例对象只能读取属性或方法。
  3. 访问不存在的私有属性或方法会报错,普通属性只会返回 undefined,此方法可以用来判断某个对象是否存在私有属性,但是需要增加 try catch,有个更好的判断私有属性是否存在的方法是 in 操作符, 他不会报错,返回一个布尔值。注意 in 只能在类的内部使用,另外对于 Object.create() Object.getPrototypeOf() 形成的继承是无效的,因为这种继承不会传递私有属性
1
2
3
4
5
6
7
8
9
10
11
12
13
class C {
#brand;

static isC(obj) {
if (#brand in obj) {
// 私有属性 #brand 存在
return true;
} else {
// 私有属性 #foo 不存在
return false;
}
}
}

继承

  1. extends 关键字可以实现类之间的继承。
1
2
3
4
5
6
7
8
9
10
class A {}

class B extends A{}

B.__proto__ === A; // 子类的 __proto__ 指向父类, 表示构造函数的继承
B.prototype.__proto__ === A.prototype; // 子类 prototype 属性的 __proto__ 属性,表示方法的继承,总是指向父类的 prototype

// 类继承的原理如下:
Object.setPrototypeOf(B.prototype, A.prototype); // B 继承 A 的原型方法
Object.setPrototypeOf(B,A); // B 继承 A 的非私有属性

与 es5 构造函数的区别

  1. class 不存在变量提升
  2. class 就是构造函数的一层包装,函数的许多特性都被 Class 继承, name 属性如果没有特别声明的话,总是返回 class 关键字后面的类名,
  3. 某个方法前加上 * 表示是一个 Generator 函数
  4. 类方法中的 this 默认指向实例化的对象,但是一旦单独使用,可能会报错
  5. class 只能通过 new 操作符调用,不能直接调用
  6. class 的方法是不可枚举的,通过 for in 不能访问
  7. 通过 class 创建的函数具有特殊的内部属性标记 [[IsClassConstructor]]: true
  8. new 是构造函数生成实例化对象的命令, ex6 为 new 引入了 new.target 属性,此属性返回 new 作用于的那个构造函数或者类,因此这个属性可以用来确定构造函数是否通过 new 调用, class 中返回 class 名称,如果是子类调用,则返回子类名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 命令生成实例');
}
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错