这是下篇,讲述了类的一些常见用法和基本概念。对比了 ES5 与 ES6 的差异。
类
实际上是特殊的函数,就像你能够定义的函数表达式和函数声明,类语法有两个组成部分: 类表达式和类声明。 构造函数 、静态方法、原型方法、getter、setter 一个构造函数方法是一个特殊方法, 其用于创建和初始化使用一个类创建的一个对象。 一个构造函数可以使用 super 关键字来调用一个父类的构造函数。 静态方法: static 关键字来定义。调用静态类方法不需要实例话该类,但不能通过一个类实例调用静态方法。常用语为一个应用程序回创建工具函数。 ⚠️: 只有静态方法 没有静态属性
表达式和声明
类声明
定义一个类的一种方法是使用一个类声明。 要声明一个类, 你可以使用带有 class 关键字的类名
复制 class Retctange {
constructor(hight, width) {
this.height = height;
this.width = width;
}
}
函数声明和类声明之间的一个重要区别就是函数声明会提升, 类声明不会。例如下面例子
复制 let p = new Retctange();
class Retctange {}
//Uncaught ReferenceError: Cannot access 'Retctange' before initialization
类表达式
一个类表达式是定义一个类的另一种方式。类表达式可以是被命名的或者匿名的。 赋予一个命名类表达式的名称是类的主体的本地名称。
复制 let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName();
ES6的继承
class 内定义的所有函数都会置于该类的原型当中。
复制 class Animal {
constructor(name) {
this.name = name
}
}
Animal.prototype.species = 'animal'
class Leo extends Animal {
constructor(name) {
super(name)
}
}
constructor(){} 充当了之前的构造函数, super() 作为函数调用扮演着 Animal.call(this, name) 的角色(还可以表示父类). 最重要的是 Leo 的 proto 也指向了 Animal.
复制 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')
x 和 y 都是实例对象point 自身的属性(因为定义在 this 变量上), 所以 hasOwnPropery 返回 true, 而toString 是原型对象的属性(定义在 Point类上), 所以 hasOwnProperty 方法返回 false。
class 实现原理
ES5的继承,实质是先创造子类的实例对象 this, 然后再将父类的方法添加到 this 上面 Parent.apply(this), es6 的继承则是,将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法), 然后在用子类的构造函数修改this. super在调用之后, 内部的 this 指向的是 child,
复制 class parent {}
class Child extends Parent {}
Child.__proto__ === Parent // 继承属性
Child.prototype.__proto__ === Parent.prototype // 继承方法
复制 class Point {}
var point = new Point()
console.log(point.constructor === Point.prototype.constructor) // true
复制 class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
b 是 B 类的实例。它的constructor方法就 是B 类原型的 constructor 方法。
复制 class Foo {
constructor() {
return Object.create(null);
}
}
// console.log('new Foo() instanceof Foo', new Foo() instanceof Foo);
//实例不再指向 Foo
类必须使用 new 调用,否则会报错。 这是它跟普通构造函数的一个主要区别,后面不用 new 也可以执行。
复制 class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
复制 class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
子类的 super.p(),就是将 super 当作是一个对象使用,super在普通方法中, 指向 A.prototype, 所以super.p()就相当于A.prototype.p(); super 指向父类的原型对象, 所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
复制 var o1 = {
foo() {
console.log('o1:foo')
}
}
var o2 = {
foo() {
super.foo();
console.log("o2: foo")
}
}
// Object.setPrototypeOf(o2, o1);
o2.foo();
o2.foo()方法中的 super 引用静态锁定到 o2, 具体说是锁定到 o2 的[[Prototype]]。 基本上这里的 super 就是 Object.getPrototypeOf(o2) 实例不再指向 Foo
new target
这个属性能够用来确认构造函数是怎么调用
复制 function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
ES5 和 ES6
共同点
1.name
复制 class Point {}
Point.name // "Point"
复制 const obj = function Person() {
console.log('obj')
}
console.log('obj', obj.name) //Person
ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。 2. 继承
复制 class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于下边的代码
Point.prototype.constructor = function() {}
Point.prototype.toString = function() {}
Point.prototype.toValue = function() {}
在类的实例上面调用方法,其实就是调用原型上的方法。
复制 class Point {}
var point = new Point()
console.log(point.constructor === Point.prototype.constructor) // true
不同点
上面例子有讲,不在重复叙述。
this 的指向 函数的this 是指向区分多种,在这里不做展开,我们只看最简单的例子。 类的方法内部如果含有 this, 它默认指向类的实例。
复制
function A(){
this.sayThis = function(){
console.log('sayThis', this)
}
}
const a = new A();
console.log('a', a.sayThis() )
console.log('a', sayThis() )
sayThis // A sayThis() // window
复制 class Logger {
printName(name = 'there') {
this.print( `Hello ${name}` );
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
// const {
// printName
// } = logger;
console.log('printName', logger.printName())
console.log('printName', printName())
printName //Hello there printName(); // TypeError: Cannot read property 'print' of undefined
我们可以用 bind 或者箭头函数解决 this 指向问题
复制 class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
//或者
class Obj {
constructor() {
this.getThis = () => this;
}
}
我们可以将私有方法移出模块,模块内部的所有方法都是对外可见的。
复制 class Widget {
constructor(snaf) {
this.snaf = snaf;
}
foo(baz) {
console.log(this.snaf)
bar.call(this, baz);
}
// ...
}
function bar(baz) {
console.log('baz', baz)
return this.snaf = baz;
}
const widget = new Widget('obj');
const obj = widget.foo('1');
console.log('widget', widget.snaf)
console.log('obj', obj.snaf)
私有方法带#的提案
复制 class Foo {
#
privateValue = 42;
static getPrivateValue(foo) {
return foo.#privateValue;
}
}
console.log('Foo.getPrivateValue(new Foo());', Foo.getPrivateValue(new Foo())) //42