面向对象的JavaScript

68 阅读4分钟

JavaScript是动态类型语言

鸭子类型

正因为JavaScript是动态语言,我们使用时不能知道类型,也就不知道有没有想要的行为和属性。 比如想要一只鸭子,没有类型识别获取出对象或者非对象的其他类型,那我们有个很好的办法就是通过它有没有鸭子的行为或者形状来辨别是否是鸭子。 鸭子类型指导我们只关注对象的行为,而不太关注对象本身。

面向对象的概念

多态封装继承

多态

一段多态的JavaScript代码

在JavaScript中,多态性通常通过方法的重写(子类覆盖父类的方法)来实现。这里,我将给出一个简单的例子来展示如何在JavaScript中实现多态性。

首先,我们定义一个基类(父类),然后创建几个继承自这个基类的子类,每个子类都重写了一个基类中的方法,以展示不同的行为。

javascript // 定义一个基类(Animal) class Animal { constructor(name) { this.name = name; }

// 基类中的方法,子类可以覆盖这个方法
makeSound() {
    console.log(`${this.name} makes a generic sound.`);
}

}

// 创建一个Dog类,继承自Animal类 class Dog extends Animal { // Dog类重写makeSound方法 makeSound() { console.log(${this.name} says Woof!); } }

// 创建一个Cat类,也继承自Animal类 class Cat extends Animal { // Cat类也重写makeSound方法 makeSound() { console.log(${this.name} says Meow!); } }

// 接下来,我们创建几个Animal类型的对象,但实际上它们是Dog和Cat的实例 let myDog = new Dog("Rex"); let myCat = new Cat("Whiskers");

// 使用一个接受Animal类型参数的函数来展示多态性 function animalSounds(animal) { animal.makeSound(); }

// 调用函数,传入不同类型的Animal对象 animalSounds(myDog); // 输出: Rex says Woof! animalSounds(myCat); // 输出: Whiskers says Meow!

// 这里展示了多态性:同一个函数(animalSounds)能够处理不同类型的对象(Dog和Cat), // 并根据对象的实际类型(即它们重写了哪个版本的makeSound方法)来执行不同的操作。

在这个例子中,animalSounds函数接受一个Animal类型的参数,但实际上它可以接受任何继承自Animal的对象(在这个例子中是Dog和Cat的实例)。根据传入对象的实际类型(即它是Dog还是Cat),makeSound方法会以不同的方式执行,这展示了多态性的本质。

多态性使得我们的代码更加灵活和可扩展,因为它允许我们在不修改现有代码的情况下,为程序添加新的功能(例如,通过添加新的子类并重写方法)。

通常通过继承实现多态

封装

封装数据

function Person(firstName, lastName) {  
let _firstName = firstName; // 私有变量  
let _lastName = lastName;  
  
return {  
getFullName: function() {  
return `${_firstName} ${_lastName}`;  
},  
  
setFirstName: function(name) {  
if (typeof name === 'string') {  
_firstName = name;  
} else {  
console.error('Invalid name type');  
}  
}  
};  
}  
  
const person = Person('John', 'Doe');  
console.log(person.getFullName()); // John Doe  
person.setFirstName('Jane');  
console.log(person.getFullName()); // Jane Doe

使用ES6类

从ES6开始,JavaScript引入了class关键字,它提供了一种更接近传统面向对象编程语言的语法。虽然ES6类中的属性默认是公开的,但你可以使用私有字段(使用#前缀)来封装数据。

javascriptclass Person {
    #firstName; // 私有属性
    #lastName;

    constructor(firstName, lastName) {
        this.#firstName = firstName;
        this.#lastName = lastName;
    }

    getFullName() {
        return `${this.#firstName} ${this.#lastName}`;
    }

    setFirstName(name) {
        if (typeof name === 'string') {
            this.#firstName = name;
        } else {
            console.error('Invalid name type');
        }
    }
}

const person = new Person('John', 'Doe');
console.log(person.getFullName()); // John Doe
person.setFirstName('Jane');
console.log(person.getFullName()); // Jane Doe
// console.log(person.#firstName); // SyntaxError: Private field '#firstName' must be declared in an enclosing class

在这个例子中,#firstName#lastName是私有属性,它们只能在Person类的内部被访问和修改。尝试从类的外部访问这些属性将导致语法错误。

封装是面向对象编程中的一个核心概念,它有助于创建更加模块化和可维护的代码。在JavaScript中,你可以使用对象字面量、构造函数和闭包、以及ES6类(包括私有字段)来实现数据的封装。

封装实现

继承

JavaScript中的继承有哪些实现方式

在JavaScript中,继承的实现方式主要有以下几种:

  1. 原型链继承(Prototype Chain Inheritance)

这是JavaScript中最基本的继承方式。每个构造函数都有一个原型对象(prototype),而原型对象又包含一个指向构造函数的指针(constructor)。实例对象内部包含一个指向原型对象的指针(proto,但在严格模式下不推荐使用,应使用Object.getPrototypeOf())。如果原型对象本身也是另一个原型的实例,那么原型对象将含有一个指向另一个原型的指针。这样,就形成了一个原型链。

javascript function Parent() { this.name = 'Parent'; }

Parent.prototype.sayHello = function() { console.log('Hello from Parent!'); };

function Child() { this.age = 10; }

// Child继承自Parent Child.prototype = new Parent(); Child.prototype.constructor = Child; // 修正constructor指向

var child = new Child(); console.log(child.name); // Parent child.sayHello(); // Hello from Parent! 2. 借用构造函数继承(Constructor Stealing / Pseudoclassical Inheritance)

也称为伪造对象或经典继承。这种方法通过在子类的构造函数中调用父类的构造函数,从而借用父类构造函数来增强子类实例。这种继承方式通过使用call()或apply()方法来实现。

javascript function Parent(name) { this.name = name; }

function Child(name, age) { Parent.call(this, name); // 借用Parent构造函数 this.age = age; }

var child = new Child('Child', 5); console.log(child.name); // Child 3. 组合继承(Combination Inheritance)

组合继承是原型链继承和借用构造函数继承的组合。它使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既可以在子类实例上设置属性,也可以实现方法的复用。

javascript function Parent(name) { this.name = name; }

Parent.prototype.sayHello = function() { console.log('Hello from Parent!'); };

function Child(name, age) { Parent.call(this, name); // 继承属性 this.age = age; }

Child.prototype = new Parent(); // 继承方法 Child.prototype.constructor = Child; // 修正constructor指向 Child.prototype.sayGoodbye = function() { console.log('Goodbye from Child!'); };

var child = new Child('Child', 5); console.log(child.name); // Child child.sayHello(); // Hello from Parent! child.sayGoodbye(); // Goodbye from Child! 4. 原型式继承(Prototypal Inheritance)

原型式继承是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义的类型。这种继承方式通过复制现有的对象来创建新对象,新对象将继承现有对象的属性和方法。

javascript function object(o) { function F() {} F.prototype = o; return new F(); }

var person = { name: 'Nicholas', friends: ['Shelby', 'Court', 'Van'] };

var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person); console.log(yetAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob'] 5. 寄生式继承(Parasitic Inheritance)

寄生式继承与原型式继承紧密相关,但它不是通过复制现有对象的属性来创建新对象,而是创建一个新对象,然后扩展这个对象,最后返回这个新对象。这个新对象可以使用任何现有的对象来提供它所需要的方法,但不需要原型链。

javascript function createAnother(original) { var clone = Object.create(original); // 创建一个新对象,使用现有的对象来提供原型 clone.sayHi = function() { // 以某种方式增强这个对象 console.log('hi'); }; return clone; // 返回这个对象 }

var person = { name: 'Nicholas' };

var anotherPerson = createAnother(person); anotherPerson.sayHi(); // "hi" 6. 寄生组合式继承(Parasitic Combination Inheritance)

寄生组合式继承