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. 静态属性、静态方法
- 静态方法
- 静态方法,在方法前加上 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
复制代码
- 静态属性,指 class 本身的属性,不是定义在实例对象(this)上的属性
// 老写法
class MyClass {}
MyClass.myStaticProp = 42;
// 新写法
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
let m = new MyClass(); // MyClass{} : 实例对象上是没有东西的
复制代码
6. 私有方法、私有属性
- 可以将私有方法移出类,因为类内部的所有方法都是对外可见的
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}
复制代码
- 私有属性的提案,在属性名之前使用
#
表示
#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;
}
})("张三");
复制代码
向类添加方法
- 使用 Object.assign() 方法
Object.assign(Point.prototype,{
toString()
})
复制代码
- 通过实例的
__proto__
属性改写原型。但必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例
p1.__proto__.printName = function () {
return "Oops";
};
复制代码
与 ES5 区别
-
类不存在变量提升(为继承考虑)
-
类的内部所有定义的方法,都是不可枚举的;而 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 会返回子类
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END