上次更新时间:9/29/2020, 5:41:05 PM 0

# 介绍

  • 类是ES6 中新增的概念,关键字是 class
  • 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类的数据类型就是函数,类本身就指向构造函数。对类使用new命令,跟构造函数的用法完全一致。class写法只是让对象原型的写法更加清晰、更像面向对象编程
  • 类的所有方法都定义在类的 prototype 属性上面
  • 类的内部所有定义的方法,都是不可枚举的(non-enumerable)
  • 类的所有实例对象都从同一个原型对象上继承属性,原型对象是类的唯一标识:当切仅当两个对象继承自同一个原型对象时,它们才属于同一个类的实例

# 创建类

  • 通过 class 关键字,可以定义类
  • 类所有的方法都是定义在类的 prototype 属性上
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

const point = new Point()
point.hasOwnProperty('x'); // true
point.__proto__.hasOwnProperty('x'); // false
point.__proto__.hasOwnProperty('toString'); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意:在 class 定义体内部不用逗号分隔成员

# 属性的getter和setter

在 class 内部,可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。( getter 和 setter 是设置在属性的描述对象上的)

class MyClass {
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: ' + value);
  }
}

var instance = new MyClass();

instance.prop = 123; // setter: 123
instance.prop; // getter

var propDesc = Object.getOwnPropertyDescriptor(
    instance.prototype,
    'prop'
);
console.log(propDesc); // 可看到 get 和 set 方法设置在属性的描述对象上
// {
//     configurable: true
//     enumerable: false
//     get: ƒ prop()
//     set: ƒ prop(value)
// }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 创建类的实例

  • 通过 new 命令生成类的实例
  • 类的所有实例共享一个原型对象
  • 实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)
class Point {
    w = 100
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return '(' + this.x + ', ' + this.y + ')';
    }
}

var p1 = new Point(2, 3);
var p2 = new Point(3, 4);
p1.__proto__ === p2.__proto__; // true

p1.x; // 2
p1.z; // undefined
p1.w; // 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# constructor

  • constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法
  • 一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加
  • constructor 方法默认返回实例对象(即this),但也可以指定返回另外一个对象
  • constructor 内的属性和方法绑定在实例上

实例属性也可以定义在类的最顶层

class Point {
    instanceValue = 100 // 实例属性
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.print = function() {
            console.log(this.x, this.y)
        }
    }
}

var point1 = new Point(1, 2)

point1.instanceValue; // 100
point1.hasOwnProperty('instanceValue') // true

// constructor 内定义的方法
point1.print() // 1, 2
point1.hasOwnProperty('print') // true

// 实例方法
point1.sum = function() {
    console.log(this.x + this.y)
}
point1.sum(); // 3
point1.hasOwnProperty('sum') // true

console.log(point1.constructor) // 输出 Class Point{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# static

使用 static 关键字可以声明静态属性和静态方法,它们都有以下特点:

  • 静态属性/静态方法是添加到类的函数对象上,而不是该函数对象的 prototype 对象上
  • 静态属性/静态方法不会被实例继承,而是直接通过类来调用
  • 静态属性/静态方法中的 this 指的是类,而不是实例
  • 父类的静态属性/静态方法,可以被子类继承
  • 静态属性/静态方法也可以从 super 对象上调用
  • 静态属性/静态方法可以与非静态属性/静态方法重名,实例上的属性/方法会覆盖类的属性/方法
class Foo {
    sameName = 'tony'
    sex = 'female'
    static sameName = 'jack';
    static age = 18

    constructor() {
        console.log(this.sameName); // tony
        console.log(Foo.sameName); // jack
    }

    static bar() {
      this.print();
    }
    static print() { // 静态方法
      console.log('hello');
    }
    print() { // 原型方法
      console.log('world');
    }
}

Foo.prototype.hasOwnProperty('someName'); // false -> 静态属性不绑定在 prototype 对象上
Foo.prototype.hasOwnProperty('bar'); // false -> 静态方法不绑定在 prototype 对象上

Foo.sameName; // jack -> 通过类调用静态属性
Foo.bar() // hello -> 通过类调用静态方法

var f = new Foo();

f.sameName; // tony -> 实例上有同名属性,取实例的属性值

f.bar() // f.bar is not a function -> 静态方法不被实例继承
f.print() // world -> 实例调用的是原型上的方法

class ChildFoo extends Foo {
}

ChildFoo.sameName; // jack -> 子类可继承父类的静态属性
ChildFoo.bar() // 'hello' -> 子类可继承父类的静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 私有方法和私有属性

使用符号 # 就可以声明私有属性和私有方法,它们都有以下特点:

  • 只能在类的内部访问的方法和属性,外部不能访问
  • 私有属性也可以设置 getter 和 setter方法
  • 私有属性不限于从this引用,只要是在类的内部,实例也可以引用私有属性
  • 私有属性/私有方法前面,也可以加上static关键字,表示这是一个静态的私有属性或私有方法。
class Rectangle {
  #height = 0;
  #width;
  constructor(height, width) {    
    this.#height = height;
    this.#width = width;
  }

  #area() {
    console.log(this.#height * this.#width)
  }

  print() {
    this.#area()
  }
}

var r = new Rectangle(10, 20)
r.#area(); // Uncaught SyntaxError: Private field '#area' must be declared in an enclosing class
r.#height; // Uncaught SyntaxError: Private field '#height' must be declared in an enclosing class
r.print(); // 200
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# new.target属性

new.target 属性一般用在构造函数之中,返回 new 命令作用于的那个构造函数。如果构造函数不是通过 new 命令或 Reflect.construct() 调用的,new.target 会返回 undefined,因此这个属性可以用来确定构造函数是怎么调用的

  • class 内部调用 new.target,返回当前 class
  • 子类继承父类时,new.target会返回子类(利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。)
  • 在函数外部,使用new.target会报错
class Rectangle {
  constructor(length, width) {
    console.log('new.target>>>', new.target)
    console.log(new.target === Rectangle);
    this.length = length;
    this.width = width;
  }
}

var obj = new Rectangle(3, 4); // true -> class 内部调用 new.target,返回当前 class

class Square extends Rectangle {
  constructor(length, width) {
    super(length, width);
  }
}

var obj = new Square(3); // false -> 子类继承父类时,new.target会返回子类,此时new targer = Class Square

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# extend和super

extend

  • class 可以通过 extends 关键字实现继承
  • 子类可以重写父类的方法

super

  • super 关键字可以调用父类。super 既可以当作函数使用,也可以当作对象使用。
  1. 作为函数调用时:

super.method():调用父方法 super():调用父构造函数(仅在constructor中)

  • 作为函数调用时,代表父类的构造函数
  • super() 返回的是子类的实例
  • 子类的构造函数必须执行一次super()
  • super() 必须在使用 this 之前调用,否则报错
  • super() 只能用在子类的构造函数中,
  1. 作为对象调用时
  • 普通方法中,指向父类的原型对象
  • 静态方法中,指向父类。方法内部的 this 指向当前子类,而不是子类实例

Object.getPrototypeOf()

  • Object.getPrototypeOf方法可以用来从子类上获取父类
class Point {
  constructor(x, y) {
    console.log('new.target.name>>', new.target.name);
    this.x = x;
    this.y = y;
  }

  sum() {
    return 3;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 在使用 this 之前必须调用一次
    this.color = color;
  }

  normalMethod() {
    console.log(super.sum())
  }
}


var parent = new Point(8, 9) // new.target.name>> Point
var child = new ColorPoint(1, 2) // new.target.name>> ColorPoint
child.normalMethod(); // 3

// 从子类获取父类
Object.getPrototypeOf(ColorPoint) === Point // true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。 子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // static 1

var child = new Child();
child.myMethod(2); // instance 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 使用注意

  • 类和模块的内部,默认就是严格模式
  • 类不存在变量提升
  • ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。
  • 如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。
  • 类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

# es5 实现class

es6 class

class Person{
	constructor(name, age) {
        this.name = name;
        this.age = age;
    }
  
    eat() {
		return 'eat';
	}
    
    static say() {
    	return 'say';
    }
}

class China extends Person {
	constructor(color) {
        super()
        this.color = color;
    }
  
    color() {
    	return 'color'
    }
}

var fllower = new China();

console.log(fllower);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

通过 babel 转换可以看到使用 es5 是如何实现 class 的

"use strict"; // 严格模式

function _typeof(obj) { 
  "@babel/helpers - typeof"; 
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 
    _typeof = function _typeof(obj) { 
      return typeof obj; 
    }; 
  } else { 
    _typeof = function _typeof(obj) {
      return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 
      }; 
  }
  return _typeof(obj); 
}

function _inherits(subClass, superClass) { 
  if (typeof superClass !== "function" && superClass !== null) { 
    throw new TypeError("Super expression must either be null or a function"); 
  } 
  subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); 
  if (superClass) _setPrototypeOf(subClass, superClass); 
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
    o.__proto__ = p;
    return o;
  }; 
  return _setPrototypeOf(o, p);
}

function _createSuper(Derived) { 
  var hasNativeReflectConstruct = _isNativeReflectConstruct(); 
  return function _createSuperInternal() { 
    var Super = _getPrototypeOf(Derived), result; 
    if (hasNativeReflectConstruct) { 
      var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); 
    } else { 
      result = Super.apply(this, arguments); 
    } 
    return _possibleConstructorReturn(this, result); 
  }; 
}

function _possibleConstructorReturn(self, call) { 
  if (call && (_typeof(call) === "object" || typeof call === "function")) { 
    return call; 
  } 
  return _assertThisInitialized(self); 
}

function _assertThisInitialized(self) { 
  if (self === void 0) { 
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 
  } 
  return self; 
}

function _isNativeReflectConstruct() { 
  if (typeof Reflect === "undefined" || !Reflect.construct) return false; 
  if (Reflect.construct.sham) return false; 
  if (typeof Proxy === "function") return true; 
  try { 
    Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 
    return true; 
  } catch (e) { 
    return false; 
  } 
}

function _getPrototypeOf(o) {
   _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 
     return o.__proto__ || Object.getPrototypeOf(o); 
    }; 
  return _getPrototypeOf(o); 
}

function _instanceof(left, right) { 
  if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { 
    return !!right[Symbol.hasInstance](left); 
  } else { 
    return left instanceof right; 
  } 
}
// 检查声明的class类是否通过new的方式调用
function _classCallCheck(instance, Constructor) { 
  if (!_instanceof(instance, Constructor)) { 
    throw new TypeError("Cannot call a class as a function"); 
  } 
}
// 历函数数组,分别声明其描述符 并添加到对应的对象上
function _defineProperties(target, props) { 
  for (var i = 0; i < props.length; i++) { 
    var descriptor = props[i]; 
    descriptor.enumerable = descriptor.enumerable || false; 
    descriptor.configurable = true; 
    if ("value" in descriptor) descriptor.writable = true; 
    Object.defineProperty(target, descriptor.key, descriptor); 
  } 
}
// 收集公有函数和静态方法,将方法添加到构造函数或构造函数的原型中,并返回构造函数。
function _createClass(Constructor, protoProps, staticProps) { 
  if (protoProps) _defineProperties(Constructor.prototype, protoProps); 
  if (staticProps) _defineProperties(Constructor, staticProps); 
  return Constructor; 
}

var Person = /*#__PURE__*/function () {
  function Person(name, age) {
    _classCallCheck(this, Person);

    this.name = name;
    this.age = age;
  }

  _createClass(Person, [{
    key: "eat",
    value: function eat() {
      return 'eat';
    }
  }], [{
    key: "say",
    value: function say() {
      return 'say';
    }
  }]);

  return Person;
}();

var China = /*#__PURE__*/function (_Person) {
  _inherits(China, _Person);

  var _super = _createSuper(China);

  function China(color) {
    var _this;

    _classCallCheck(this, China);

    _this = _super.call(this);
    _this.color = color;
    return _this;
  }

  _createClass(China, [{
    key: "color",
    value: function color() {
      return 'color';
    }
  }]);

  return China;
}(Person);

var fllower = new China();
console.log(fllower);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
上次更新时间: 9/29/2020, 5:41:05 PM