2025-01-21 日报 Day73

2025-01-21 日报 Day73

Yuyang 前端小白🥬

今日的鸡汤

在任何一个你没有察觉的时刻,包括现在,通过行动去改变命运的机会,一直都存在。

今日学习内容

1、JS 红皮书 P249-253 第八章:对象、类与面向对象编程

今日笔记

1、前几节深入讲解了如何只使用 ECMAScript 5 的特性来模拟类似于类(class-like)的行为。不难看出,各种策略都有自己的问题,也有相应的妥协。正因为如此,实现继承的代码也显得非常冗长和混乱。
为解决这些问题,ECMAScript 6 新引入的 class 关键字具有正式定义类的能力。类(class)是ECMAScript 中新的基础性语法糖结构,因此刚开始接触时可能会不太习惯。虽然 ECMAScript 6 类表面上看起来可以支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念。
2、类定义: 定义类也有两种主要方式:类声明和类表达式。这两种方式都使用 class 关键字加大括号:

1
2
3
4
// 类声明
class Person {}
// 类表达式
const Animal = class {};

与函数表达式类似,类表达式在它们被求值前也不能引用。不过,与函数定义不同的是,虽然函数声明可以提升,但类定义不能:

1
2
3
4
5
6
7
8
9
10
11
12
console.log(FunctionExpression); // undefined 
var FunctionExpression = function() {};
console.log(FunctionExpression); // function() {}
console.log(FunctionDeclaration); // FunctionDeclaration() {}
function FunctionDeclaration() {}
console.log(FunctionDeclaration); // FunctionDeclaration() {}
console.log(ClassExpression); // undefined
var ClassExpression = class {};
console.log(ClassExpression); // class {}
console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined
class ClassDeclaration {}
console.log(ClassDeclaration); // class ClassDeclaration {}

另一个跟函数声明不同的地方是,函数受函数作用域限制,而类受块作用域限制:

1
2
3
4
5
6
{ 
function FunctionDeclaration() {}
class ClassDeclaration {}
}
console.log(FunctionDeclaration); // FunctionDeclaration() {}
console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined

类构成
类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。
与函数构造函数一样,多数编程风格都建议类名的首字母要大写,以区别于通过它创建的实例(比如,通过 class Foo {}创建实例 foo):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 空类定义,有效 
class Foo {}
// 有构造函数的类,有效
class Bar {
constructor() {}
}
// 有获取函数的类,有效
class Baz {
get myBaz() {}
}
// 有静态方法的类,有效
class Qux {
static myQux() {}
}

类表达式的名称是可选的。在把类表达式赋值给变量后,可以通过 name 属性取得类表达式的名称字符串。但不能在类表达式作用域外部访问这个标识符。

1
2
3
4
5
6
7
8
9
let Person = class PersonName { 
identify() {
console.log(Person.name, PersonName.name);
}
}
let p = new Person();
p.identify(); // PersonName PersonName
console.log(Person.name); // PersonName
console.log(PersonName); // ReferenceError: PersonName is not defined

3、类的构造函数: constructor 关键字用于在类定义块内部创建类的构造函数。方法名 constructor 会告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数。构造函数的定义不是必需的,不定义构造函数相当于将构造函数定义为空函数。

  • 使用 new 操作符实例化 Person 的操作等于使用 new 调用其构造函数。唯一可感知的不同之处就是,JavaScript 解释器知道使用 new 和类意味着应该使用 constructor 函数进行实例化。
    使用 new 调用类的构造函数会执行如下操作。
    (1) 在内存中创建一个新对象。
    (2) 这个新对象内部的[[Prototype]]指针被赋值为构造函数的 prototype 属性。
    (3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
    (4) 执行构造函数内部的代码(给新对象添加属性)。
    (5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Animal {} 
    class Person {
    constructor() {
    console.log('person ctor');
    }
    }
    class Vegetable {
    constructor() {
    this.color = 'orange';
    }
    }
    let a = new Animal();
    let p = new Person(); // person ctor
    let v = new Vegetable();
    console.log(v.color); // orange
    类实例化时传入的参数会用作构造函数的参数。如果不需要参数,则类名后面的括号也是可选的:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Person {
    constructor(name) {
    console.log(arguments.length);
    this.name = name || null;
    }
    }
    let p1 = new Person; // 0
    console.log(p1.name); // null
    let p2 = new Person(); // 0
    console.log(p2.name); // null
    let p3 = new Person('Jake'); // 1
    console.log(p3.name); // Jake
    默认情况下,类构造函数会在执行之后返回 this 对象。构造函数返回的对象会被用作实例化的对象,如果没有什么引用新创建的 this 对象,那么这个对象会被销毁。不过,如果返回的不是 this 对象,而是其他对象,那么这个对象不会通过 instanceof 操作符检测出跟类有关联,因为这个对象的原型指针并没有被修改。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Person { 
    constructor(override) {
    this.foo = 'foo';
    if (override) {
    return {
    bar: 'bar'
    };
    }
    }
    }
    let p1 = new Person(),
    p2 = new Person(true);
    console.log(p1); // Person{ foo: 'foo' }
    console.log(p1 instanceof Person); // true
    console.log(p2); // { bar: 'bar' }
    console.log(p2 instanceof Person); // false
    类构造函数与构造函数的主要区别是,调用类构造函数必须使用 new 操作符。而普通构造函数如果不使用 new 调用,那么就会以全局的 this(通常是 window)作为内部对象。调用类构造函数时如果忘了使用 new 则会抛出错误:
    1
    2
    3
    4
    5
    6
    function Person() {} 
    class Animal {}
    // 把 window 作为 this 来构建实例
    let p = Person();
    let a = Animal();
    // TypeError: class constructor Animal cannot be invoked without 'new'
    类构造函数没有什么特殊之处,实例化之后,它会成为普通的实例方法(但作为类构造函数,仍然要使用 new 调用)。因此,实例化之后可以在实例上引用它:
    1
    2
    3
    4
    5
    6
    7
    class Person {} 
    // 使用类创建一个新实例
    let p1 = new Person();
    p1.constructor();
    // TypeError: Class constructor Person cannot be invoked without 'new'
    // 使用对类构造函数的引用创建一个新实例
    let p2 = new p1.constructor();
    4、把类当成特殊函数: ECMAScript 中没有正式的类这个类型。从各方面来看,ECMAScript 类就是一种特殊函数。声明一个类之后,通过 typeof 操作符检测类标识符,表明它是一个函数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    class Person {} 
    console.log(Person); // class Person {}
    console.log(typeof Person); // function
    // 类标识符有 prototype 属性,而这个原型也有一个 constructor 属性指向类自身:

    class Person{}
    console.log(Person.prototype); // { constructor: f() }
    console.log(Person === Person.prototype.constructor); // true

    class Person {}
    let p = new Person();
    console.log(p instanceof Person); // true

    class Person {}
    let p1 = new Person();
    console.log(p1.constructor === Person); // true
    console.log(p1 instanceof Person); // true
    console.log(p1 instanceof Person.constructor); // false
    let p2 = new Person.constructor();
    console.log(p2.constructor === Person); // false
    console.log(p2 instanceof Person); // false
    console.log(p2 instanceof Person.constructor); // true

    // 类可以像函数一样在任何地方定义,比如在数组中
    let classList = [
    class {
    constructor(id) {
    this.id_ = id;
    console.log(`instance ${this.id_}`);
    }
    }
    ];
    function createInstance(classDefinition, id) {
    return new classDefinition(id);
    }
    let foo = createInstance(classList[0], 3141); // instance 3141

    // 因为是一个类表达式,所以类名是可选的
    let p = new class Foo {
    constructor(x) {
    console.log(x);
    }
    }('bar'); // bar
    console.log(p); // Foo {}
此页目录
2025-01-21 日报 Day73