class的基本用法《第三章》

01.静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class Foo{

static classMethod() {

return 'hello';

  }

}

Foo.classMethod()// 'hello'

varfoo=new Foo();

foo.classMethod()

// TypeError: foo.classMethod is not a function

上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

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

class Foo{

static bar() {

this.baz();

  }

static baz() {

console.log('hello');

  }

baz() {

console.log('world');

  }

}

Foo.bar()// hello

上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。

父类的静态方法,可以被子类继承。

class Foo{

static classMethod() {

return'hello';

  }

}

class Barextends Foo{

}

Bar.classMethod()// 'hello'

上面代码中,父类Foo有一个静态方法,子类Bar可以调用这个方法。

静态方法也是可以从super对象上调用的。

class Foo{

static classMethod() {

return'hello';

  }

}

class Bar extends Foo{

static classMethod() {

returnsuper.classMethod() + ', too';

  }

}

Bar.classMethod()// "hello, too"

2.属性的新写法

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

class IncreasingCounter{

constructor() {

this._count = 0;

  }

get value()  {

console.log('Getting the current value!');

returnthis._count;

  }

increment() {

this._count++;

  }

}

上面代码中,实例属性this._count定义在constructor()方法里面。另一种写法是,这个属性也可以定义在类的最顶层,其他都不变。

class IncreasingCounter{

_count=0;

get value() {

console.log('Getting the current value!');

return this._count;

  }

increment() {

this._count++;

  }

}

上面代码中,实例属性_count与取值函数value()和increment()方法,处于同一个层级。这时,不需要在实例属性前面加上this。

这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。

class foo{

bar = 'hello';

baz = 'world';

constructor() {

// ...

  }

}

上面的代码,一眼就能看出,foo类有两个实例属性,一目了然。另外,写起来也比较简洁。

3.静态属性

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

class Foo{

}

Foo.prop=1;

Foo.prop// 1

上面的写法为Foo类定义了一个静态属性prop。

目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性的前面,加上static关键字。

class MyClass{

static myStaticProp = 42;

constructor() {

console.log(MyClass.myStaticProp);// 42

  }

}

这个新写法大大方便了静态属性的表达。

// 老写法

class Foo{

// ...

}

Foo.prop = 1;

// 新写法

class Foo{

staticprop = 1;

}

上面代码中,老写法的静态属性定义在类的外部。整个类生成以后,再生成静态属性。这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一起的代码组织原则。另外,新写法是显式声明(declarative),而不是赋值处理,语义更好。

4.私有方法和私有属性

现有的解决方案

私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。

一种做法是在命名上加以区别。

class Widget{

// 公有方法

foo(baz) {

this._bar(baz);

  }

// 私有方法

_bar(baz) {

returnthis.snaf=baz;

  }

// ...

}

上面代码中,_bar()方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。

另一种方法就是索性将私有方法移出类,因为类内部的所有方法都是对外可见的。

class Widget{

foo(baz) {

bar.call(this,baz);

  }

// ...

}

function bar(baz) {

return this.snaf=baz;

}

上面代码中,foo是公开方法,内部调用了bar.call(this, baz)。这使得bar()实际上成为了当前类的私有方法。

还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。

const bar = Symbol('bar');

const snaf = Symbol('snaf');

export default class myClass{

// 公有方法

foo(baz) {

this[bar](baz);

  }

// 私有方法

[bar](baz) {

returnthis[snaf] = baz;

  }

// ...

};

上面代码中,bar和snaf都是Symbol值,一般情况下无法获取到它们,因此达到了私有方法和私有属性的效果。但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们。

const inst = newmyClass();

Reflect.ownKeys(myClass.prototype)

// [ 'constructor', 'foo', Symbol(bar) ]

上面代码中,Symbol 值的属性名依然可以从类的外部拿到。

私有属性的提案

目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用#表示。

class IncreasingCounter{

#count = 0;

get value() {

console.log('Getting the current value!');

return this.#count;

  }

increment() {

this.#count++;

  }

}

上面代码中,#count就是私有属性,只能在类的内部使用(this.#count)。如果在类的外部使用,就会报错。

const counter = new IncreasingCounter();

counter.#count // 报错

counter.#count = 42 // 报错

上面代码在类的外部,读取私有属性,就会报错。

下面是另一个例子。

class Point{

#x;

constructor(x=0) {

this.#x = +x;

  }

get x() {

returnthis.#x;

  }

set x(value) {

this.#x = +value;

  }

}

上面代码中,#x就是私有属性,在Point类之外是读取不到这个属性的。由于井号#是属性名的一部分,使用时必须带有#一起使用,所以#x和x是两个不同的属性。

之所以要引入一个新的前缀#表示私有属性,而没有采用private关键字,是因为 JavaScript 是一门动态语言,没有类型声明,使用独立的符号似乎是唯一的比较方便可靠的方法,能够准确地区分一种属性是否为私有属性。另外,Ruby 语言使用@表示私有属性,ES6 没有用这个符号而使用#,是因为@已经被留给了 Decorator。

这种写法不仅可以写私有属性,还可以用来写私有方法。

class Foo{

#a;

#b;

const ructor(a,b) {

this.#a = a;

this.#b = b;

  }

#sum() {

return this.#a + this.#b;

  }

printSum() {

console.log(this.#sum());

  }

}

上面代码中,#sum()就是一个私有方法。

另外,私有属性也可以设置 getter 和 setter 方法。

class Counter{

#xValue = 0;

const ructor() {

super();

// ...

  }

get #x() { return #xValue; }

set #x(value) {

this.#xValue = value;

  }

}

上面代码中,#x是一个私有属性,它的读写都通过get #x()和set #x()来完成。

私有属性不限于从this引用,只要是在类的内部,实例也可以引用私有属性。

class Foo{

#privateValue = 42;

staticgetPrivateValue(foo) {

return foo.#privateValue;

  }

}

Foo.getPrivateValue(newFoo());// 42

上面代码允许从实例foo上面引用私有属性。

私有属性和私有方法前面,也可以加上static关键字,表示这是一个静态的私有属性或私有方法。

class FakeMath{

staticPI = 22/7;

static #totallyRandomNumber = 4;

static #computeRandomNumber() {

return FakeMath.#totallyRandomNumber;

  }

staticrandom() {

console.log('I heard you like random numbers…')

returnFakeMath.#computeRandomNumber();

  }

}

FakeMath.PI// 3.142857142857143

FakeMath.random()

// I heard you like random numbers…

// 4

FakeMath.#totallyRandomNumber // 报错

FakeMath.#computeRandomNumber() // 报错

上面代码中,#totallyRandomNumber是私有属性,#computeRandomNumber()是私有方法,只能在FakeMath这个类的内部调用,外部调用就会报错。

in 运算符

try...catch结构可以用来判断是否存在某个私有属性。

class A{

use(obj) {

try{

obj.#foo;

}catch{

// 私有属性 #foo 不存在

   }

  }

}

consta=newA();

a.use(a);// 报错

上面示例中,类A并不存在私有属性#foo,所以try...catch报错了。

这样的写法很麻烦,可读性很差,V8 引擎改进了in运算符,使它也可以用来判断私有属性。

class A{

use(obj) {

if(#foo in obj) {

// 私有属性 #foo 存在

}else{

// 私有属性 #foo 不存在

   }

  }

}

上面示例中,in运算符判断当前类A的实例,是否有私有属性#foo,如果有返回true,否则返回false。

in也可以跟this一起配合使用。

class A{

#foo = 0;

m() {

console.log(#foo in this); // true

console.log(#bar in this); // false

  }

}

注意,判断私有属性时,in只能用在定义该私有属性的类的内部。

class A{

#foo = 0;

statictest(obj) {

console.log(#foo in obj);

  }

}

A.test(newA())// true

A.test({})// false

class B{

#foo = 0;

}

A.test(newB())// false

上面示例中,类A的私有属性#foo,只能在类A内部使用in运算符判断,而且只对A的实例返回true,对于其他对象都返回false。

子类从父类继承的私有属性,也可以使用in运算符来判断。

class A{

#foo = 0;

statictest(obj) {

console.log(#foo in obj);

  }

}

classSubAextendA{};

A.test(newSubA())// true

上面示例中,SubA从父类继承了私有属性#foo,in运算符也有效。

注意,in运算符对于Object.create()、Object.setPrototypeOf形成的继承,是无效的,因为这种继承不会传递私有属性。

class A{

#foo = 0;

statictest(obj) {

console.log(#foo in obj);

  }

}

consta= new A();

consto1=Object.create(a);

A.test(o1)// false

A.test(o1.__proto__)// true

const o2={};

Object.setPrototypeOf(o2,A);

A.test(o2)// false

A.test(o2.__proto__)// true

上面示例中,对于修改原型链形成的继承,子类都取不到父类的私有属性,所以in运算符无效。

5.new.target 属性

new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

function Person(name) {

if(new.target!==undefined) {

this.name=name;

}else{

thrownewError('必须使用 new 命令生成实例');

  }

}

// 另一种写法

function Person(name) {

if(new.target===Person) {

this.name=name;

}else{

thrownewError('必须使用 new 命令生成实例');

  }

}

varperson=newPerson('张三');// 正确

varnotAPerson=Person.call(person,'张三');// 报错

上面代码确保构造函数只能通过new命令调用。

Class 内部调用new.target,返回当前 Class。

class Rectangle{

const ructor(length,width) {

console.log(new.target===Rectangle);

this.length=length;

this.width=width;

  }

}

varobj=newRectangle(3,4);// 输出 true

需要注意的是,子类继承父类时,new.target会返回子类。

class Rectangle{

const ructor(length,width) {

console.log(new.target===Rectangle);

// ...

  }

}

class SquareextendsRectangle{

const ructor(length,width) {

super(length,width);

  }

}

varobj=newSquare(3);// 输出 false

上面代码中,new.target会返回子类。

利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。

class Shape{

const ructor() {

if(new.target===Shape) {

thrownewError('本类不能实例化');

   }

  }

}

class RectangleextendsShape{

const ructor(length,width) {

super();

// ...

  }

}

varx=newShape();// 报错

vary=newRectangle(3,4);// 正确

上面代码中,Shape类不能被实例化,只能用于继承。

注意,在函数外部,使用new.target会报错。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容