首先回顾一下ES5时想要创建实例化对象的写法:

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. // 在 Person 的原型上新增方法
  6. Person.prototype.say = function () {
  7. console.log("My name is " + this.name);
  8. };
  9. let person = new Person("zhangsan", "20");
  10. person.say();
  11. console.log(person);
  12. console.log(Object.getPrototypeOf(person));

image.png

class 的基本用法

ES6规定了class关键字进行构造函数的定义,主要目的是模仿其他类语言的「类」,其本质上就是ES5构造函数的语法糖。

以下是一个「类」的基本写法:

  1. class Person {
  2. // 给构造函数传递参数
  3. constructor(name, age) {
  4. // 实例化的私有属性
  5. this.name = name;
  6. this.age = age;
  7. }
  8. }
  9. let person = new Person("张三", 20);
  10. console.log(person);

image.png
**constructor**函数用于接受实例化时传递的参数,如果你没有写**constructor**函数系统也不会报错,而是默认给你创建一个

除了基本写法不同之外,给「类」新增原型方法(也称为公共方法)也不同:

  1. class Person {
  2. // 给构造函数传递参数
  3. constructor(name, age) {
  4. // 实例化的私有属性
  5. this.name = name;
  6. this.age = age;
  7. }
  8. // 不用写逗号
  9. // 公共的 say 方法
  10. say() {
  11. console.log("My name is " + this.name);
  12. }
  13. }
  14. let person = new Person("张三", 20);
  15. person.say(); // My name is 张三

:::danger ⚠️ 注意
calss不是对象,所以constructor方法和say方法之间不用写逗号。 :::

class没有公共的属性,如果直接赋值一个变量该变量会变成实例化对象的私有属性:

  1. class Person{
  2. name = "zhangsan";
  3. say(){}
  4. }
  5. console.log(new Person());

image.png

虽然class对构造函数进行了语法糖的封装,但是其本质上还是一个函数:

  1. class Person{}
  2. console.log(typeof Person); // function

那么如何将公有方法进行私有化?
第一种就是命名的时候加以区别:

  1. class Person{
  2. say(){}
  3. _eat(){} // _eat 就是一个私有的方法
  4. }

但是这样的方法只是形式上私有,实际仍然可以被操作,所以第二种办法就是使用Symbol将方法名唯一化。

  1. const eat = Symbol("eat");
  2. class Person{
  3. say(){}
  4. // 公有的方法私有化
  5. [eat]() {
  6. console.log("eat");
  7. }
  8. }

另外一个点和ES5不同的是class的原型方法不会被遍历:

  1. // ES5
  2. function Person(){}
  3. Person.prototype.say = function(){}
  4. Person.prototype.eat = function(){}
  5. console.log(Object.keys(Person.prototype)); // ['say', 'eat']
  6. // ES6
  7. class Person{
  8. say(){}
  9. eat(){}
  10. }
  11. console.log(Object.keys(Person.prototype)); // []

class也可以像函数表达式的方式进行声明:

  1. var A = function(){}
  2. var B = class{};
  3. let b = new B();

class也存在暂时性死区:

  1. new A();
  2. function A(){}
  3. new B(); // B is not defined
  4. class B{};

class还可以设置存值函数和取值函数:

  1. class Person {
  2. "use strict" // class 默认就是严格模式,不需要手动 "use strict"
  3. get name() {
  4. return "123";
  5. }
  6. set name(newVal) {
  7. console.log("789");
  8. }
  9. }
  10. let person = new Person();
  11. console.log(person.name); // 123
  12. person.name = "456"; // 789

如果想要给class设置静态的属性和方法需要使用static关键字:

  1. // ES5
  2. function A(){}
  3. A.name = "zhangsan"
  4. A.say= function(){}
  5. // ES6
  6. class B{
  7. static name = "zhangsan";
  8. static say() {}
  9. }

class 继承

ES5的时候如果想要实现继承是比较麻烦的,需要指来指去。
继承深入
到了ES6使用extends+super就可以实现继承(不过仅限于class内)

  1. class A{
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. // 静态方法不会被继承
  6. static a() {
  7. console.log("静态方法a");
  8. }
  9. say() {
  10. console.log("say");
  11. }
  12. }
  13. class B extends A{
  14. constructor(age) {
  15. // super 必须是基于 constructor 内部
  16. // 继承父类的实例后才能使用 this
  17. super("zhangsan");
  18. this.age = age;
  19. }
  20. }
  21. let b = new B(20);
  22. console.log(b);
  23. b.say();
  24. b.a(); // b.a is not a function

:::danger ES6要求,子类的构造函数必须执行一次super函数,否则引擎就会报错。 ::: image.png

super使用的更多细节:

实现 class 的过程

结合上面的案例我们知道了class有以下这些特点:

  1. 暂时性死区
  2. 严格模式
  3. 原型方法不能枚举
  4. 必须使用new进行实例化
  5. 默认的constructor

根据这些特点我们可以模拟一个class的过程:

  1. class Person {
  2. constructor(name, age) {
  3. this.name = name;
  4. this.age = age;
  5. }
  6. static eat() {
  7. console.log("eat function");
  8. }
  9. say() {
  10. console.log("say function");
  11. }
  12. drink() {
  13. console.log("drink function");
  14. }
  15. }
  1. // 判断必须使用new进行实例化
  2. function _classCallCheck(instance, Constructor) {
  3. if (!instance instanceof Constructor) {
  4. throw new Error("Cannot call a class as a function");
  5. }
  6. }
  7. // 定义构造函数的属性
  8. var _createClass = (function () {
  9. function defineProperties(target, props) {
  10. for (let index = 0; index < props.length; index++) {
  11. var descriptor = props[index];
  12. descriptor.enumerable = descriptor.enumerable || false;
  13. descriptor.configurable = true;
  14. if ("value" in descriptor) {
  15. descriptor.writable = true;
  16. }
  17. Object.defineProperty(target, descriptor.key, descriptor);
  18. }
  19. }
  20. return function (Constructor, protoProps, staticProps) {
  21. if (protoProps) {
  22. defineProperties(Constructor.prototype, protoProps);
  23. }
  24. if (staticProps) {
  25. defineProperties(Constructor, protoProps);
  26. }
  27. return Constructor;
  28. };
  29. })();
  30. // 立即执行函数
  31. var Person = (function () {
  32. function Person(name, age) {
  33. _classCallCheck(this, Person);
  34. this.name = name;
  35. this.age = age;
  36. }
  37. // 赋值原型属性和静态属性
  38. _createClass(
  39. Person,
  40. [
  41. {
  42. key: "say",
  43. value: function () {
  44. console.log("say");
  45. },
  46. },
  47. {
  48. key: "eat",
  49. value: function () {
  50. console.log("eat");
  51. },
  52. },
  53. ],
  54. [
  55. {
  56. key: "drink",
  57. value: function () {
  58. console.log("drink");
  59. },
  60. },
  61. ]
  62. );
  63. return Person;
  64. })();

装饰器

什么是装饰器?
把对象进行修饰,修饰的同时并不影响实际的使用,只是新增一些功能。

例如给class类增加一个装饰器:

  1. function testable(target, name, descriptor) {
  2. // descriptor 和 Object.defineProperty 中的 descriptor 是一样的
  3. // 都是对属性的特性进行定义
  4. }
  5. @testable
  6. class Person {}

class的方法新增一个装饰器:

  1. function readonly(target, name, descriptor) {
  2. descriptor.writable = false;
  3. }
  4. class Person {
  5. @readonly
  6. say() {
  7. console.log("say function");
  8. }
  9. }

如果装饰器后面需要跟参数,装饰器函数则需要使用闭包的形式:

  1. function readonly(str) {
  2. console.log(str);
  3. return function(target, name, descriptor){
  4. descriptor.writable = false;
  5. }
  6. }
  7. class Person {
  8. @readonly("123")
  9. say() {
  10. console.log("say function");
  11. }
  12. }

使用装饰器给class方法新增日志:

  1. let log = (type) => {
  2. return function show(target, name, descriptor) {
  3. // descriptor.value 就表示属性的值,也就是函数
  4. let src_method = descriptor.value;
  5. // 重写了 descriptor.value
  6. descriptor.value = (...arg) => {
  7. src_method.apply(target, arg);
  8. console.log(type);
  9. };
  10. };
  11. };
  12. class AD {
  13. @log("show")
  14. show() {
  15. console.log("ad is show");
  16. }
  17. @log("click")
  18. click() {
  19. console.log("ad is click");
  20. }
  21. }
  22. new AD().show();
  23. // ad is show
  24. // show
  25. new AD().click();
  26. // ad is click
  27. // click