class 基本语法

1.简介

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。


1.jpg

上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。

2.jpg

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而 this关键字则代表实例对象也就是说,ES5 的构造函数Person,对应 ES6 的Person类的构造方法。

Person类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

在类的实例上面调用方法,其实就是调用原型上的方法。

prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为是一致的。

1.jpg

另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

1.jpg

上面代码中,toString方法是Person类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。

1.jpg

上面代码采用 ES5 的写法,toString方法就是可枚举的。




2.严格模式

类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。

考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。



3.constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。

// 定义类

class Person{
    constructor(name,age){
        this.name=name;
        this.age=age;
        console.log(2213)   // 2213
    }
}

let p1=new Person('Andy',31)

可以看到控制台打印了2213, 证明通过new命令生成实例对象时,自动调用constructor方法

另外,类里面的this指代的都是实例对象,请看下面这个例子

// 定义类

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

    toString(){
        console.log(this) 
    }
}

let p1=new Person('Andy',31)   //  Person {name: "Andy", age: 31}


p1.toString()


一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

1.jpg

上面代码中,定义了一个空的类Person,JavaScript 引擎会自动为它添加一个空的constructor方法。

constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

1.jpg

上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。

类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。



4.类的实例对象

生成类的实例对象的写法,与 ES5 完全一样,也是使用new命令。

需要注意的是,需要加上new,如果忘记加上new,像函数那样调用Class,将会报错。

1.jpg

与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

toString

上面代码中,定义了一个 Person类,constructor中的sexagenew出来的实例对象的属性,打印 Person.hasOwnProperty('sex')结果是false, 打印 p1.hasOwnProperty('sex')结果是true 。 这也证明了 constructor中的this指代的是实例对象。




与 ES5 一样,类的所有实例共享一个原型对象。

1.jpg

上面代码中,p1和p2都是Person的实例,它们的原型都是Person.prototype,所以 _proto_属性是相等的。

这也意味着,可以通过实例的_proto_属性为“类”添加方法。

1.jpg

上面代码在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,
因此p2也可以调用这个方法。而且,此后新建的实例p3也可以调用这个方法。
这意味着,使用实例的_proto
属性改写原型,必须相当谨慎,
不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。





5.class表达式

与函数一样,类也可以使用表达式的形式定义。

const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};

let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined

上面代码使用表达式定义了一个类。

需要注意的是,这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类。




6.不存在变量提升

类不存在变量提升(hoist),这一点与 ES5 完全不同

new Foo(); // ReferenceError
class Foo {}

上面代码中,Foo类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。

{
  let Foo = class {};
  class Bar extends Foo {
  }
}

上面的代码不会报错,因为Bar继承Foo的时候,Foo已经有定义了。但是,如果存在class的提升,上面代码就会报错,因为class会被提升到代码头部,而let命令是不提升的,所以导致Bar继承Foo的时候,Foo还没有定义。


7.私有方法和私有属性

私有方法是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。





8.this的指向

类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

1.jpg

上面的例子说明,在单独执行printName这个函数的时候,由于this的指向问题,函数中的代码 this.print()当前上下文环境中找不到print这个函数,所以报错。

一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。

请看下面这个更为复杂的情况。

2.jpg

这个例子中,在class Logger的constructor中我们使用了bind方法,把printName这个方法的执行上下文环境绑定到了class Logger的实例上。

bind方法是新创建一个函数,然后把它的上下文绑定到bind()括号中的参数上,然后将它返回。

所以,bind后函数不会执行,而只是返回一个改变了上下文的函数副本,而call和apply是直接执行函数。

后面的代码中,我们new了一个 Logger的实例,打印出来。
发现实例中有userName passwords 和 printName三个属性,前面两个是key value属性,最后的一个 printName是Logger实例的一个方法。

通过var {printName}=logger把printName单独拿出来,打印printName发现它是一个函数,它是实例logger的一个OwenProperty,其原型proto指向Function的prototype属性。

注意到printName._proto与实例 logger._proto显然是不等的。

它与Function的原型才相等!

前面提到,我们使用bind方法,将printName的上下文环境this改变成了class Logger的实例对象上,所以直接调用 printName方法可行了,它会打印出hello lv的信息。


关于bind()函数

1.jpg

这个例子中,我们在constructor中使用了bind方法,将实例对象logger的上下文环境this绑定到了一个新的对象上面,新的对象上面也有print方法

我们把实例对象logger的print方法赋值给printName方法,单独调用printName方法时,发现打印的都是 敌法师,新的对象里面的name,sex属性值。




9.name 属性

由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。

class Point {}
Point.name // "Point"

前面有提到过。

Person.hasOwnProperty('name');打印的是true,是因为巧合就是这个原因。实际上打印 Person.hasOwnProperty('age');打印的就是false





10.Class 的取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

1.jpg

上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。

存值函数和取值函数是设置在属性的 Descriptor 对象上的。

12.class的静态方法

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

1.jpg

上面代码中,Person类有个静态方法hello(),可以在Person类上调用,但是不能在Person的实例上调用。

尝试 p.hello()是报错的。

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

1.jpg

上面代码中,静态方法bar调用了this.baz,这里的this指的是Person类,而不是Person的实例,等同于调用Person.baz。

另外,从这个例子还可以看出,静态方法可以与非静态方法重名。



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


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

1.jpg





13.class的静态属性和实例属性

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

// 定义类

class Person{
    
}

Person.prop=1;

console.log(Person.prop);  //  1

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

目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。

// 以下两种写法都无效
class Person {
  // 写法一
  prop: 2

  // 写法二
  static prop: 2
}

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

推荐阅读更多精彩内容

  • class的基本用法 概述 JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子: ...
    呼呼哥阅读 4,089评论 3 11
  • 1、引言 JavaScript是一门基于原型继承的语法,ES5中我们实现面向对象构造“父类”的写法一般通过构造函数...
    七_五阅读 157评论 0 0
  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,098评论 0 6
  • 讲解员的讲解中,我们了解到东区自来水厂的水源是黄河的地表水,流经两条主干道来到自来水厂,首先经过曝气池除去水中高含...
    emmmmmm哦阅读 131评论 0 0
  • 亲爱的孩子! 此时此刻,我们每个人都面对选择!都在检视着自己!我们如何去选择?我们为什么做决定?我们怎样的选择造就...
    论剑阅读 246评论 0 0