重学 ES6 之 Class 基础

1. 基本概念

  • 传统方法是通过构造函数,生成实例对象
  • ES6 的 Class 只是一个语法糖,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法。
  • 用法跟构造函数的用法完全一致
  • 写法:内部定义方法不需要 function 关键字;方法间不需要分号;必须使用 new 调用
  • 类的所有方法都定义在类的 prototype 属性上面
  • this 关键字指向类的实例对象,this 上显示定义的属性即定义在实例对象上
  • name 属性总是返回紧跟在 class 关键字后面的类名
  • 类和模块的内部,默认就是严格模式,所以不需要使用 use strict 指定运行模式
// 1. 传统写法
function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function () {
  return "(" + this.x + ", " + this.y + ")";
};
let p = new Point(1, 2); // {x:1,y:2}

// 2. class 改写 =>
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    console.log("this->", this); // Point {x:1,y:2}
  }
  toString() {
    return "(" + this.x + ", " + this.y + ")";
  }
}

let p = new Point(1, 2); // {x:1,y:2}
// 等同于
Point.prototype = {
  constructor() {},
  toString() {},
};

// 3. class的数据类型就是函数,class本身就指向构造函数(与 ES5 行为是一致!)
typeof Point; // 'function'
// constructor 存放在 prototype 中,指向自己的构造函数
Point === Point.prototype.constructor; // true
复制代码

2. 构造方法

  • 通过 new 命令生成实例对象时,自动调用 constructor()
  • 会默认添加,默认返回实例对象 this

3. 实例属性、实例对象、this 指向

  • 类相当于实例的原型,所有在类中定义的方法,都会被实例继承
  • 实例属性: 定义在 this 对象上的属性,即定义在了实例本身上
  • 其他定义的方法默认都定义在实例的原型上(类的 prototype 属性上)
  • 类的所有实例共享一个原型对象
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return "(" + this.x + ", " + this.y + ")";
  }
}

let point = new Point(2, 3);
// x定义在this上,即实例本身上;方法定义在实例的原型上
point.hasOwnProperty("x"); // T
point.hasOwnProperty("toString"); // F
point.__proto__.hasOwnProperty("toString"); // T
复制代码
  • this 丢失问题
// 继续还是上面那个例子
const { toString } = point;
toString(); // 报错 x 找不到
复制代码

解决方式: 1)在构造函数中绑定 this 2)使用箭头函数 3) 使用 Proxy,获取方法的时候,自动绑定 this

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    // this.toString = this.toString.bind(this);
  }

  toString = () => {
    return "(" + this.x + ", " + this.y + ")";
  };
}
let point = new Point(2, 3);
const { toString } = point;
toString(); // ? √
复制代码

实例属性除了一般定义在 constructor()方法里面的 this 上面以外,也可以定义在类的最顶层,不需要加 this
好处是看上去比较整齐,一眼就能看出这个类有哪些实例属性

class Foo {
  bar = "hello";
  baz = "world";
  constructor() {}
}

let f = new Foo(); // Foo {bar,baz}
复制代码

4. 存值/取值函数 setter/getter

  • 使用 get/set 关键字对某个属性设置 取值/存值函数,对该属性的操作进行拦截
  • 存值函数和取值函数是设置在属性的 Descriptor 对象上的
class MyClass {
  constructor() {}
  get prop() {
    return "getter";
  }
  set prop(value) {
    console.log("setter: " + value);
  }
}
let inst = new MyClass();
inst.prop = 123; // setter: 123
inst.prop; // 'getter'
复制代码

5. 静态属性、静态方法

  1. 静态方法
  • 静态方法,在方法前加上 static 关键字,不会被实例继承,直接通过类来调用
  • 静态方法中的 this 指向类,而不是实例!(因为是通过类调用的,this 指向调用方)
  • 静态方法可以与非静态方法重名
  • 父类的静态方法,可以被子类继承
  • 静态方法也是可以从 super 对象上调用的
class Foo {
  static classMethod() {
    return "hello";
  }
  static f1() {
    this.f2(); // this指向class,所以还是调用静态方法
    // Foo.prototype.f2(); // 如果要调用非静态方法的话
  }
  static f2() {
    console.log("static f2");
  }
  f2() {
    console.log("f2");
  }
}

Foo.classMethod(); // hello
Foo.f1(); // static f2
复制代码
  1. 静态属性,指 class 本身的属性,不是定义在实例对象(this)上的属性
// 老写法
class MyClass {}
MyClass.myStaticProp = 42;

// 新写法
class MyClass {
  static myStaticProp = 42;
  constructor() {
    console.log(MyClass.myStaticProp); // 42
  }
}

let m = new MyClass(); // MyClass{} : 实例对象上是没有东西的
复制代码

6. 私有方法、私有属性

  1. 可以将私有方法移出类,因为类内部的所有方法都是对外可见的
class Widget {
  foo(baz) {
    bar.call(this, baz);
  }
}

function bar(baz) {
  return (this.snaf = baz);
}

let w = new Widget();
w.foo("123");
w; // Widget {snaf:123}
复制代码
  1. 私有属性的提案,在属性名之前使用 # 表示

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

class IncreasingCounter {
  #count = 0;
  get value() {
    console.log("Getting the current value!");
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}
复制代码

其他

class 表达式

// 使用表达式定义类,Me也可省略
const MyClass = class Me {};
let inst = new MyClass();

// 立即执行的class
let person = new (class {
  constructor(name) {
    this.name = name;
  }
})("张三");
复制代码

向类添加方法

  1. 使用 Object.assign() 方法
Object.assign(Point.prototype,{
  toString()
})
复制代码
  1. 通过实例的 __proto__ 属性改写原型。但必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例
p1.__proto__.printName = function () {
  return "Oops";
};
复制代码

与 ES5 区别

  1. 类不存在变量提升(为继承考虑)

  2. 类的内部所有定义的方法,都是不可枚举的;而 ES5 中是可枚举的!

class Point {
  constructor(x, y) {}
  toString() {}
}

Object.keys(Point.prototype); // []
Object.getOwnPropertyNames(Point.prototype); // ["constructor","toString"]

// ES5
let Point = function (x, y) {};
Point.prototype.toString = function () {};
Object.keys(Point.prototype); // ["toString"]
Object.getOwnPropertyNames(Point.prototype); // ["constructor","toString"]
复制代码

new.target 属性

  • ES6 为 new 命令引入了一个 target 属性,一般用在构造函数之中,返回 new 命令作用于的那个构造函数
  • 这个属性可以用来确定构造函数是怎么调用的
  • 子类继承父类时,new.target 会返回子类

参考:es6.ruanyifeng.com/#docs/class

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享