文章目录
简介
类的由来
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。
function Ponit(x, y){
this.x = x;
this.y = y;
}
Ponit.prototype.toString = function (){
return '(' + this.x + ',' +this.y + ')';
}
var p = new Ponit(1,2);
在ES6中,提供了更接近传统语言的写法,引入了calss(类)这个概念,作为对象的模版。通过class
关键字,可以定义类。
新的class
写法只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。
使用class
改写上面的代码
class Ponit {
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return '(' + this.x + ',' +this.y + ')';
}
}
上面代码中;
定义了一个class
(类),其中有:
- 构造方法
constructor
- 关键字
this
代表实例对象
注意:定义class
的方法的时候,前面不需要加上function
关键字,直接吧函数定义放进去就可以了,并且方法之间不需要都好分割,加了会报错。
ES6的类,完全可以看做构造函数的另一种写法
class Point {
// ...
}
console.log(typeof Point); // 'function'
console.log(Point === Point.prototype.constructor) // true
类的数据类型就是函数,类的本身就指向构造函数,使用的时候,也直接使用new
命令,跟构造函数的用法完全一致。
-
类的所有方法都定义在类的
prototype
属性上面 -
在类的实例上面调用方法,其实就是调用原型上的方法
-
类的内部所有定义的方法,都是不可枚举的(这里和ES5的行为不一致)
class Point{
constructor(x,y){
// ...
}
toString(){
// ...
}
}
Object.keys(Point.prototype); // {}
Object.getOwnPropertyNames(Point.prototype); // {"constructor","toString"}
constructor方法
方法constructor
是类的默认方法,通过new
命令生成对象实例时,会自动调用这个方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法被默认添加
class Point {
// ...
}
// 等同于
class Point {
constructor(){
}
// ...
}
方法 constructor
默认返回实例对象(this
),完全可以指定返回另一个对象。
class Foo{
constructor{
return Object.create(null);
}
}
console.log(new Foo() instanceof Foo); // false
类必须使用new
调用,否则会报错。这是他跟普通构造函数的主要区别,后者不用new
也可以执行
类的实例
通过new
命令来生成类的实例,如果没有就会报错。
实例的属性除非显式定义在其本身(this
对象上),否则都是定义在原型上(class
上)。
class Ponit {
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return '(' + this.x + ',' +this.y + ')';
}
}
var point = new Point(2, 3);
point.toString(); // (2,3)
point.hasOwnPrototype('x'); // true
point.hasOwnPrototype('y'); // true
point.hasOwnPrototype('toString'); // false
point._proto_.hasOwnProperty('toString') // true
上面的代码中,x
、y
都是实例对象point
自身的属性(因为定义在this
变量上),所以hasOwnProperty
方法返回true
,而toString
是原型对象的属性(因为定义在Point
类上),所以hasOwnProperty
方法返回false
.
类的所有实例共享一个原型对象
取值函数(getter)和存值函数(setter)
在类的内部使用get
和set
关键字,对某个属性设置取值函数和存值函数,拦截这个属性的存取行为。
存值函数、取值函数是设置在对应属性的 Descriptor 对象上。
属性表达式
类的属性名,可以采用表达式。
const methodName = 'getArea';
class Square {
constructor(length){
// ...
}
[methodName] (){
// ...
}
}
这里Square
类的方法名getArea
就是从表达式methodName
中得到
Class 表达式
与函数一样,类也可以使用表达式的形式定义
const MyClass = class Me {
getClassName(){
return Me.class;
}
}
const inst = new MyClass();
inst.getClassName(); // Me
Me.name // ReferenceError: Me is not defined
上面代码中,就是用表达式定义了一个类。需要注意的是,这个类的name
是Me
,但是Me
只在Class 的内部可用,指代当前类。在Class 外部,这个类只能用 MyClass
。
需要注意
- 严格模式
类 和 模块内部默认就是严格模式,所以不需要使用use strict
指定运行模式。
- 不存在提升
类不存在变量提升(hoist),这点与ES5 完全不同
new Foo(); // ReferenceError
class Foo {}
- name属性
本质上,类知识构造函数的一层包装,所以函数的许多特性都被Class继承,包括name
属性
class Point {}
console.log(Point.name); // 'Point'
- Generator 方法
如果某个方法之前加上(*
),就表示这个方法是一个Generator 函数
class Foo {
constructor(...args){
this.args = args;
}
* [Symbol.iterator] () {
for (let arg of this.args){
yield arg;
}
}
}
for (let x of new Foo('hello','world')){
console.log(x);
}
// 'hello'
// 'world'
- this 的指向
类的方法内部如果有this
,默认指向类的实例。
静态方法
在类中的某一个方法前加上static
关键字,就表示这个方法不会被实例继承,而是直接通过类来调用。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod(); // 'hello'
var foo = new Foo();
foo.classMethod(); // // TypeError
注意:如果静态方法包含this
关键字,这个this
指的是类,而不是实例
-
父类的静态方法是可以被子类继承的
-
静态方法也是可以从
super
对象上调用的
class Foo {
static classMethod(){
return 'hello';
}
}
class Bar extends Foo {
static classMethod(){
return super.classMethod() + ' world';
}
}
Bar.classMethod(); // 'hello world'
实例属性的新写法
实例属性除了定义在constructor()
方法里的this
上面,也可以在类的最顶层
class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
实例属性 _count
定义在constructor
中。另一种写法:这个属性也可以定义在类的最顶层,其他的都不变。
class IncreasingCounter {
_count = 0;
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
相比较而言,第二种写法,实例对象自身的属性都定义在类的头部,看上去整齐,一目了然,简洁。
静态属性
静态属性指的是Class本身的属性,即:Class.propName
,而不是定义在实例对象(this
)上的属性
class Foo {
}
Foo.prop = 1;
console.log(Foo.prop); // 1
目前,只有这一种写法。(在ES6中明确规定有静态方法没有静态属性)。现在有一个提案提供了类的静态属性,写法是在实例属性的前面,加上static
关键字。
私有方法和私有属性
现有解决方案
- 在命名上加以区别
class Widget{
// public
foo (baz){
this._bar(baz)
}
// private
_bar(baz){
return this.snaf = baz;
}
// ...
}
在方法前加_
表示只限于内部使用的私有方法,但是这种方式是存在风险的,在类的外部,还是可以调用到这个方法。
- 将私有方法放到模块外,因为模块内部的所有方法都是对外可见的。
class Widget{
foo (baz){
bar.call(this, baz);
}
// ...
}
function bar(baz){
return this.snaf = baz;
}
- 利用
Symbol
值的唯一性,将私有方法的名字命名为一个Symbol
值。
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// public
foo(baz){
this.[bar](baz);
}
// private
[bar](baz){
return this[snaf] = baz;
}
// ...
}
在上述代码中,虽然一般情况下,无法获取到,因此达到了私有方法和私有属性的效果,但绝不是绝对拿不到,Reflect.ownKeys()
依然可以拿到它们。
私有属性的提案
现在有一个提案,为calss
添加私有属性、私有方法,是在属性名、方法名前加#
表示
new.target属性
命令new
可以让构造函数生成实例。ES6为new
命令引入一个new.target
属性,这个属性一半在构造函数之中,返回new
命令作用的那个构造函数。
如果构造函数不是通过new
命令或Reflect.constuct()
调用的,new.target
会返回undefined
,因此这个属性可以用来确定构造函数是如何调用的。
- Class 内部调用
new.target
,返回当前class
. - 子类继承父类,
new.target
会返回子类
就可以有不能独立使用,必须继承后才能使用的类
class Shape {
constructor() {
if(new.target === Shape){
throw new Error('Shape 不能单独实例化');
}
}
}
class Rectangle extends Shape {
constructor(length,width){
super();
// ...
}
}
var x = new Shape(); // 错误
var y = new Rectangle(3,4); // 正确
注意:在函数外使用new.target
会报错
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。