class
基本用法
// es5造类
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function() {
return this.name;
}
let p = new Person('AIpoem',19);
// es6造类
class Person(){
// 构造方法,实例化new的时候立即被调用
constructor(name,age) {
this.name = name;
this.age = age;
}
sayName() {
return this.name;
}
sayAge() {
return this.age;
}
}
let p = new Person('AIpoem',19);
prototype
属性,在 ES6 的“类”上面也存在。事实上,类的所有方法都定义在类的prototype
属性上
class Person {
constructor() {
}
sayName() {
}
sayAge() {
}
}
// 等同于
Person.prototype = {
constructor() {}
sayName() {}
sayAge() {}
};
所以,在类的实例
上调用方法,实际上就是调用原型
上的方法
class Person {}
const p = new Person();
p.constructor === Person.prototype.constructor // true
p
是Person
类的实例,它的constructor()
方法就是Person
类原型上的constructor()
方法
所以类的新方法可以添加在prototype
对象上,使用Object.assign()
方法可以很方便地一次向类添加多个方法
class Person() {
}
Object.assign(Person.prototype, {
sayName() {},
sayAge() {}
})
prototype
对象的constructor()
属性,直接指向"类"
的本身
Person.prototype.constructor === Person // true
constructor 方法
constructor
方法是类的默认方法,通过new
命令生成对象实例的时候,自动调用constructor
方法- 一个类必须有
constructor
方法,如果没有显式定义,会默认添加一个空
的constructor()
方法
class Person {
}
// 等同于
class Person {
constructor() {}
}
类的实例
实例的属性:
除非显式定义在其本身上(定义在this
对象上),否则都是定义在原型上(定义在class
上)
//定义类
class Person{
constructor(name, age) {
this.name = name;
this.age = age;
}
toString() {
return '(' + this.name + ', ' + this.age + ')';
}
}
const p = new Person('AIpoem',19);
console.log(p.toString()); // (AIpoem, 19)
console.log(p.hasOwnProperty("name")); // true
console.log(p.hasOwnProperty("age")); // true
console.log(p.hasOwnProperty("toString")); // false
console.log(p.__proto__.hasOwnProperty("toString")) // true
上面代码中,name
和age
都是实例对象p
自身的属性(因为定义在了this
对象上),而toString()
是原型对象的属性(因为定义在了Person
类上)
类的所有实例共享一个原型对象:
const p1 = new Person('AIpoem1',19);
const p2 = new Person('AIpoem2',20);
p1.__proto__ === p2.__proto__
//true
实例p1
和p2
的原型都是Person.prototype
,所以__proto__
属性是相等的
这意味着,可以通过实例的__proto__
属性为类
添加方法:
const p1 = new Person('AIpoem1',19);
p1.__proto__.sayName = function() {
return this.name;
}
const p2 = new Person('AIpoem2',19);
console.log(p1.sayName()); // AIpoem1
console.log(p2.sayName()); // AIpoem2
在实例p1
的原型上添加了一个sayName()
方法,由于p1
和p2
共享一个原型,所以p2
也能调用sayName()
方法
⚠️:这意味着使用实例的__proto__
属性改写原型,必须相当谨慎,因为会影响到所有的实例
getter和setter
在类的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数
class MyClass{
constructor() {
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter:' + value);
}
}
const c = new MyClass();
c.prop = 123;
// setter: 123
c.prop
// 'getter'
存值函数
和取值函数
是设置在属性的Descriptor
对象上的
Class表达式
和函数一样,类也可以使用表达式定义
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
这个类的名字是Me
,但是Me
只在Class
的内部可用,指代当前类
在 Class
外部,这个类只能用MyClass
引用
如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式:
const MyClass = class { /* ... */ };
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承
在一个方法前加上static
关键字,该方法不会被实例继承,而是通过类
来调用,这就是静态方法
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
上面代码中,classMethod
是静态方法,可以直接在类
上调用(Foo.classMethod()
),而不是在实例上调用
⚠️:静态方法中的this
关键字,指的是类
,而不是实例
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar() // hello
上面代码中,静态方法bar()
调用了this.baz()
,等同于调用Foo.baz()
。而且还能看出:静态方法
可以与非静态方法
重名
实例属性的新写法
实例属性可以定义在:
constructor()
里的this
上类
的最顶层
class Foo {
constructor() {
this.count = 0;
}
}
class Foo {
count = 0;
constructor() {
// ...
}
}
静态属性
类本身
的属性,是静态属性,不是定义在实例(this
)上的属性
class Foo {
static prop = 1;
}
上面的代码为Foo
类定义了一个静态属性prop
私有方法和私有属性
私有方法
和私有属性
,是只能在类
的内部
访问的方法
和属性
,外部
不能访问
私有方法的定义:
一种方法是将私有方法移除类,因为类内部的所有方法
都是对外可见
的
class Student {
setName(name) {
person.call(this,name);
}
}
function person(name) {
return this.name = name;
}
let s = new Student();
s.setName("AIpoem");
console.log(s);
// Student{name: 'AIpoem'}
上面代码中,sayName()
是公开方法,内部调用了person.call(this,name)
,这使得person()
实际上成为了Student
类的私有方法
还有一种方法是将私有方法
的名字
命名为一个Symbol
值
const bar = Symbol('bar');
const snaf = Symbol('snaf');
class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
}
let s = new myClass();
s.foo(111);
console.log(s);
// myClass {Symbol(snaf): 111}
上面代码中,bar
和snaf
都是Symbol
值,一般情况下无法获取到它们,因此达到了私有方法
和私有属性
的效果
有一个提案,为类加了私有属性和私有方法,方法是在属性名或方法名之前使用#
表示
class IncreasingCounter {
#count = 0;
increment() {
this.#count++;
}
}
上面代码中,#count
就是私有属性,只能在类的内部使用(this.#count
)
class Foo {
#a;
#b;
constructor(a,b) {
this.#a = a;
this.#b = b;
}
#sum() {
return this.#a + this.#b;
}
}
上面代码中,#sum()
就是一个私有方法
in运算符-判断是否存在某个私有属性
in
运算符,用来判断私有属性
class A {
use(obj) {
if (#foo in obj) {
// 私有属性 #foo 存在
}else {
// 私有属性 #foo 不存在
}
}
}
子类从父类继承的私有属性,也可以使用in
运算符来判断
class A {
#foo = 0;
static test(obj) {
console.log(#foo in obj);
}
}
class SubA extends A {};
A.test(new SubA()) // true
静态块
允许在类
的内部
设置一个代码块
,在类生成
时运行一次
,主要作用是对静态属性
进行初始化
class C {
static x = 1;
static y;
static z;
static {
try {
const obj = doSomethingWith(this.x);
this.y = obj.y;
this.z = obj.z;
} catch {
this.y = ...;
this.z = ...;
}
}
}
上面代码中的static
代码块就是静态块,它的好处是将静态属性y
和z
的初始化写进了类的内部,而且只运行一次
⚠️:每个类只能有一个静态块,在静态属性声明之后运行,静态块的内部不能有return
语句
静态块
还可以将私有属性
和类的外部代码
分享:
let getX;
class C {
#x = 1;
static {
getX = obj => obj.#x;
}
}
console.log(getX(new C())); // 1
上面代码中,如果类外部的getX()
希望获取到#x
属性,以前要写在类的constructor()
里,这样每次新建实例都会定义一次getX()
现在写在静态块里,只在类生成
的时候定义一次
new.target 属性
new.target
属性可以用来确定构造函数是怎么调用的
该属性一般用在构造函数
之中,返回**new
命令作用于的那个构造函数**
如果构造函数不是通过new
命令或者Reflect.construct()
调用的,new.target
会返回undefined
function Person(name) {
if(new.target !== undefined) {
this.name = name;
} else {
throw new Error('必需使用new命令生成实例')
}
}
上面代码确保构造函数只能通过new
命令调用