js 中的class(构造函数的语法糖)
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
等价与
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
class 的数据类型
typeof class = 'function'
手动修改class 的原型
Object.assign(Point.prototype, { toString() { console.log('string') }, toValue() { console.log('value') } })
覆盖原型上方法;
Object.keys(Point.prototype) == ['toValue'];
在类上定义的方法是不可枚举
var Point = function (x, y) {
// ...
};
Point.prototype.toString = function() {
// ...
};
Object.keys(Point.prototype) 这种直接在构造函数的原型上加方法是可以枚举的(这是和class 不一样的地方)
Object.keys(Point.prototype) == ['toString']
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
class 必须通过new 调用
实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
class Point {
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.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
不建议使用__proto__ ,因为这是浏览器厂商自己定义的, 所有的实例共享一个原型对象
Object.getPrototypeOf(point) == point.__proto__
类的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
静态方法中的this 指向问题
从这个例子还可以看出,静态方法可以与非静态方法重名。
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar() // hello
静态方法的继承
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
可以在子类的super对象上面上调用
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
实例属性的旧的写法
class Foo {
constructor() {
this._count = 0
}
getValue() {
return this._count
}
}
实例属性的新的写法
class Foo {
_count = 0;
getValue() {
return this._count
}
}
类的静态属性
class Foo {
}
Foo.prop = 1;
Foo.prop
Es6规定class 内部只用静态方法, 没有静态属性 提案中的静态属性的写法
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
类的私有方法和私有属性的实现 一种做法是命名上加以区别, 这种方法是靠不住的, 实例上面依然是可以调用这个方法的
class Widget {
// 公有方法
foo (baz) {
this._bar(baz);
}
// 私有方法
_bar(baz) {
return this.snaf = baz;
}
// ...
}
另一种做法,将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
class Widget {
foo (baz) {
bar.call(this, baz);
}
// ...
}
function bar(baz) {
return this.snaf = baz;
}
第三种方法,利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值,一般情况下无法获取到它们,因此达到了私有方法和私有属性的效果。但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们。
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
// ...
}
const inst = new myClass();
Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]
new Target 属性
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
this.length = length;
this.width = width;
}
}
var obj = new Rectangle(3, 4); // 输出 true
需要注意的是,子类继承父类时,new.target会返回子类。
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
// ...
}
}
class Square extends Rectangle {
constructor(length) {
super(length, width);
}
}
var obj = new Square(3); // 输出 false
利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape {
constructor(length, width) {
super();
// ...
}
}
var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确