对象创建与继承

工厂模式

工厂模式是一种创建对象的设计模式。

基本实现

function createPerson(name, age) {
    return {
        name: name,
        age: age,
        greet() {
            console.log(`你好,我是${this.name}`);
        }
    };
}

const person1 = createPerson("东巴文", 1);
const person2 = createPerson("张三", 20);

person1.greet();  // 你好,我是东巴文
person2.greet();  // 你好,我是张三

优点与缺点

// 优点:
// 1. 简单易用
// 2. 避免重复代码

// 缺点:
// 1. 无法识别对象类型
// 2. 每个对象都创建新的方法副本

console.log(person1 instanceof Object);  // true
console.log(person1 instanceof Person);  // Person未定义
console.log(person1.greet === person2.greet);  // false

构造函数模式

构造函数用于创建特定类型的对象。

基本实现

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.greet = function() {
        console.log(`你好,我是${this.name}`);
    };
}

const person1 = new Person("东巴文", 1);
const person2 = new Person("张三", 20);

person1.greet();  // 你好,我是东巴文

new操作符做了什么

// new Person("东巴文", 1) 做了四件事:
// 1. 创建一个新对象
// 2. 将this绑定到新对象
// 3. 执行构造函数代码
// 4. 返回新对象

// 手动模拟new
function myNew(Constructor, ...args) {
    const obj = Object.create(Constructor.prototype);
    const result = Constructor.apply(obj, args);
    return result instanceof Object ? result : obj;
}

构造函数的问题

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.greet = function() {
        console.log(`你好,我是${this.name}`);
    };
}

// 每个实例都创建新的方法
const person1 = new Person("东巴文", 1);
const person2 = new Person("张三", 20);

console.log(person1.greet === person2.greet);  // false

// 解决:将方法定义在外部
function greet() {
    console.log(`你好,我是${this.name}`);
}

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.greet = greet;
}

console.log(person1.greet === person2.greet);  // true

原型模式

原型模式使用原型对象共享属性和方法。

prototype属性

function Person() {}

// 原型对象
Person.prototype.name = "默认名称";
Person.prototype.age = 0;
Person.prototype.greet = function() {
    console.log(`你好,我是${this.name}`);
};

const person1 = new Person();
const person2 = new Person();

person1.name = "东巴文";  // 实例属性遮蔽原型属性

console.log(person1.name);  // "东巴文"(实例属性)
console.log(person2.name);  // "默认名称"(原型属性)

console.log(person1.greet === person2.greet);  // true(共享方法)

原型对象语法

function Person() {}

// 对象字面量重写原型
Person.prototype = {
    constructor: Person,  // 需要手动设置
    name: "默认名称",
    age: 0,
    greet() {
        console.log(`你好,我是${this.name}`);
    }
};

原型对象的关系

function Person(name) {
    this.name = name;
}

const person = new Person("东巴文");

// 实例与原型的关系
console.log(person.__proto__ === Person.prototype);  // true
console.log(Person.prototype.constructor === Person);  // true
console.log(person instanceof Person);  // true

// 原型链
console.log(Person.prototype.__proto__ === Object.prototype);  // true
console.log(Object.prototype.__proto__);  // null

原型链

原型链是实现继承的主要方法。

原型链结构

实例 → 构造函数.prototype → Object.prototype → null

person → Person.prototype → Object.prototype → null

属性查找

function Person(name) {
    this.name = name;
}

Person.prototype.age = 0;

const person = new Person("东巴文");

// 查找name:在实例上找到
console.log(person.name);  // "东巴文"

// 查找age:在原型上找到
console.log(person.age);   // 0

// 查找不存在属性:返回undefined
console.log(person.gender);  // undefined

属性遮蔽

function Person(name) {
    this.name = name;
}

Person.prototype.name = "原型名称";

const person = new Person("东巴文");

console.log(person.name);  // "东巴文"(实例属性遮蔽原型)

delete person.name;
console.log(person.name);  // "原型名称"(删除后访问原型)

检查属性位置

function Person(name) {
    this.name = name;
}

Person.prototype.age = 0;

const person = new Person("东巴文");

// hasOwnProperty:检查自身属性
console.log(person.hasOwnProperty("name"));  // true
console.log(person.hasOwnProperty("age"));   // false

// in操作符:检查可访问属性
console.log("name" in person);  // true
console.log("age" in person);   // true

原型继承

原型继承通过原型链实现继承。

基本原型继承

function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(`${this.name}在吃东西`);
};

function Dog(name, breed) {
    Animal.call(this, name);  // 继承属性
    this.breed = breed;
}

// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
    console.log(`${this.name}在叫`);
};

const dog = new Dog("小黑", "拉布拉多");
dog.eat();   // 小黑在吃东西
dog.bark();  // 小黑在叫

原型链图示

dog → Dog.prototype → Animal.prototype → Object.prototype → null

Object.create

Object.create创建一个新对象,使用现有对象作为原型。

基本用法

const proto = {
    greet() {
        console.log(`你好,我是${this.name}`);
    }
};

const obj = Object.create(proto);
obj.name = "东巴文";
obj.greet();  // 你好,我是东巴文

创建纯净对象

// 没有原型的对象
const pureObj = Object.create(null);
console.log(pureObj.toString);  // undefined

// 用作字典
const dict = Object.create(null);
dict.key = "value";

第二个参数

const obj = Object.create(
    { proto: "原型属性" },
    {
        name: {
            value: "东巴文",
            writable: true,
            enumerable: true,
            configurable: true
        }
    }
);

console.log(obj.proto);  // "原型属性"
console.log(obj.name);   // "东巴文"

实现继承

function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(`${this.name}在吃东西`);
};

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype, {
    constructor: {
        value: Dog,
        writable: true,
        configurable: true
    }
});

Dog.prototype.bark = function() {
    console.log(`${this.name}在叫`);
};

寄生继承

寄生继承创建一个封装继承过程的函数。

基本实现

function createAnother(original) {
    const clone = Object.create(original);
    clone.sayHi = function() {
        console.log("你好!");
    };
    return clone;
}

const person = {
    name: "东巴文",
    greet() {
        console.log(`我是${this.name}`);
    }
};

const another = createAnother(person);
another.greet();  // 我是东巴文
another.sayHi();  // 你好!

寄生组合继承

function inheritPrototype(SubType, SuperType) {
    const prototype = Object.create(SuperType.prototype);
    prototype.constructor = SubType;
    SubType.prototype = prototype;
}

function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(`${this.name}在吃东西`);
};

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

inheritPrototype(Dog, Animal);

Dog.prototype.bark = function() {
    console.log(`${this.name}在叫`);
};

组合继承

组合继承结合了原型链和构造函数继承。

基本实现

function Animal(name) {
    this.name = name;
    this.colors = [];
}

Animal.prototype.eat = function() {
    console.log(`${this.name}在吃东西`);
};

function Dog(name, breed) {
    Animal.call(this, name);  // 第二次调用Animal
    this.breed = breed;
}

Dog.prototype = new Animal();  // 第一次调用Animal
Dog.prototype.constructor = Dog;

const dog1 = new Dog("小黑", "拉布拉多");
const dog2 = new Dog("小白", "金毛");

dog1.colors.push("黑色");
console.log(dog2.colors);  // [](独立的数组)

组合继承的问题

// 调用了两次父类构造函数
// 1. Dog.prototype = new Animal()
// 2. Animal.call(this, name)

// 导致原型上有重复属性
console.log(Dog.prototype.name);  // undefined

寄生组合继承

寄生组合继承是最理想的继承方式。

完整实现

function inheritPrototype(SubType, SuperType) {
    const prototype = Object.create(SuperType.prototype);
    prototype.constructor = SubType;
    SubType.prototype = prototype;
}

function Animal(name) {
    this.name = name;
    this.colors = [];
}

Animal.prototype.eat = function() {
    console.log(`${this.name}在吃东西`);
};

function Dog(name, breed) {
    Animal.call(this, name);  // 只调用一次
    this.breed = breed;
}

inheritPrototype(Dog, Animal);

Dog.prototype.bark = function() {
    console.log(`${this.name}在叫`);
};

const dog = new Dog("小黑", "拉布拉多");
dog.eat();   // 小黑在吃东西
dog.bark();  // 小黑在叫

继承方式对比

继承方式 调用父类次数 共享引用 东巴文建议
原型继承 1 不推荐
构造函数继承 N 不推荐
组合继承 2 可用
寄生组合继承 1 推荐

下一步

掌握了对象创建与继承后,让我们继续学习:

  1. ES6类 - 学习ES6类语法
  2. 对象解构与扩展 - 学习对象操作
  3. 字符串方法 - 学习字符串操作

东巴文(db-w.cn) - 让编程学习更简单

🧬 东巴文寄语:理解原型链和继承是JavaScript进阶的关键,掌握各种继承方式的优缺点,选择最合适的方案。在ES6中,class语法让继承更加简洁。在 db-w.cn,我们帮你掌握面向对象编程!