ES5中prototype和prototype.constructor详解

一、基本概念

1. prototype是什么?

  • 每个函数 都有一个prototype属性(只有函数有)

  • 它是一个对象,称为原型对象

  • 用于实现基于原型的继承和共享属性

javascript 复制代码
function Person(name) {
    this.name = name;
}

// Person.prototype 是原型对象
console.log(typeof Person.prototype); // "object"
console.log(Person.prototype); // Person {}

2. constructor是什么?

  • 每个原型对象 都有一个constructor属性

  • 它指向创建该原型的构造函数

  • 是一个循环引用

javascript 复制代码
function Person(name) {
    this.name = name;
}

console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype.constructor.name); // "Person"

二、原型链的基本结构

javascript 复制代码
function Person(name) {
    this.name = name;
}

// 添加方法到原型
Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

const alice = new Person('Alice');

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

// 完整的原型链
// alice --> Person.prototype --> Object.prototype --> null

三、构造函数、实例、原型的关系图

javascript 复制代码
构造函数 Person
    │
    │ new Person()
    ▼
实例 alice        Person.prototype (原型对象)
    │                  │
    │.__proto__--------┘
    ▼                  constructor --> Person
alice.__proto__          │
    │                  │.__proto__
    ▼                  ▼
Person.prototype      Object.prototype
    │                  │
    │.constructor      │.constructor
    └──────────────────┘

四、prototype的详细作用

1. 共享方法和属性

javascript 复制代码
function Person(name) {
    this.name = name;
    this.id = Math.random(); // 每个实例都有自己的id
}

// 共享方法 - 所有实例共享同一个函数
Person.prototype.sayHello = function() {
    console.log(`Hello from ${this.name}`);
};

// 共享属性 - 所有实例共享同一个引用
Person.prototype.species = 'Homo sapiens';

const p1 = new Person('Alice');
const p2 = new Person('Bob');

console.log(p1.sayHello === p2.sayHello); // true - 共享同一个函数
console.log(p1.id === p2.id); // false - 每个实例有自己的id
console.log(p1.species === p2.species); // true - 共享属性

// 修改共享属性会影响所有实例
Person.prototype.species = 'Human';
console.log(p1.species); // "Human"
console.log(p2.species); // "Human"

2. 原型链查找机制

javascript 复制代码
function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

const person = new Person('Alice');

// 访问属性和方法时的查找顺序
console.log(person.hasOwnProperty('name')); // true - 自身属性
console.log(person.hasOwnProperty('sayHello')); // false - 来自原型

// 原型链查找过程
person.sayHello(); // JavaScript会:
// 1. 检查person对象自身是否有sayHello属性 - 没有
// 2. 检查person.__proto__(Person.prototype)是否有 - 有,执行
// 3. 如果还没有,继续向上查找person.__proto__.__proto__(Object.prototype)
// 4. 直到找到或到达null

五、prototype.constructor的详细作用

1. 识别对象类型

javascript 复制代码
function Person(name) {
    this.name = name;
}

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

const p = new Person('Alice');
const a = new Animal('Dog');

console.log(p.constructor === Person); // true
console.log(a.constructor === Animal); // true
console.log(p.constructor === a.constructor); // false

// 判断对象类型
function getType(obj) {
    return obj.constructor.name;
}

console.log(getType(p)); // "Person"
console.log(getType(a)); // "Animal"
console.log(getType([])); // "Array"
console.log(getType({})); // "Object"

2. 创建新实例

javascript 复制代码
function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

const p1 = new Person('Alice');
const p2 = new p1.constructor('Bob'); // 使用constructor创建新实例

p2.sayHello(); // Hello, I'm Bob

// 动态创建实例(不知道构造函数时)
function createInstance(obj, ...args) {
    return new obj.constructor(...args);
}

const p3 = createInstance(p1, 'Charlie');
p3.sayHello(); // Hello, I'm Charlie

3. 修复constructor(重要!)

javascript 复制代码
// 问题:重写prototype会丢失constructor
function Person(name) {
    this.name = name;
}

// 完全重写prototype
Person.prototype = {
    sayHello: function() {
        console.log(`Hello, I'm ${this.name}`);
    }
    // 注意:这里没有constructor属性!
};

const p = new Person('Alice');
console.log(p.constructor === Person); // false!
console.log(p.constructor === Object); // true! (来自Object.prototype)

// 解决方案1:手动添加constructor
Person.prototype = {
    constructor: Person, // 显式设置constructor
    sayHello: function() {
        console.log(`Hello, I'm ${this.name}`);
    }
};

// 解决方案2:使用Object.defineProperty
Person.prototype = {
    sayHello: function() {
        console.log(`Hello, I'm ${this.name}`);
    }
};
Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false, // 不可枚举
    writable: true,
    value: Person
});

// 解决方案3:不重写,只扩展(推荐)
Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};
Person.prototype.sayGoodbye = function() {
    console.log(`Goodbye from ${this.name}`);
};

六、继承中的prototype和constructor

1. 经典继承模式

javascript 复制代码
// 父类
function Animal(name) {
    this.name = name;
}

Animal.prototype.speak = function() {
    console.log(`${this.name} makes a sound`);
};

// 子类
function Dog(name, breed) {
    Animal.call(this, name); // 1. 调用父类构造函数
    this.breed = breed;
}

// 2. 设置原型链
Dog.prototype = Object.create(Animal.prototype);

// 3. 修复constructor
Dog.prototype.constructor = Dog;

// 4. 添加子类方法
Dog.prototype.bark = function() {
    console.log(`${this.name} barks!`);
};

const dog = new Dog('Rex', 'German Shepherd');
dog.speak(); // Rex makes a sound
dog.bark(); // Rex barks!

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog.constructor === Dog); // true

2. constructor在继承中的重要性

javascript 复制代码
function Animal(name) {
    this.name = name;
}

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

// 错误的继承方式
Dog.prototype = Animal.prototype; // 共享原型,污染父类
Dog.prototype.constructor = Dog; // 这会修改Animal.prototype.constructor!

console.log(Animal.prototype.constructor === Dog); // true!Animal的constructor被修改了

// 正确的继承方式
Dog.prototype = Object.create(Animal.prototype); // 创建新对象,不共享
Dog.prototype.constructor = Dog; // 只修改Dog.prototype.constructor

console.log(Animal.prototype.constructor === Animal); // true,保持正确

七、内置构造函数的prototype和constructor

javascript 复制代码
// 数组
const arr = [1, 2, 3];
console.log(arr.constructor === Array); // true
console.log(Array.prototype.constructor === Array); // true

// 对象
const obj = {};
console.log(obj.constructor === Object); // true
console.log(Object.prototype.constructor === Object); // true

// 函数
const func = function() {};
console.log(func.constructor === Function); // true
console.log(Function.prototype.constructor === Function); // true

// 原型链的顶端
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.constructor === Object); // true
console.log(Object.prototype.__proto__ === null); // true

八、实际应用场景

1. 检查对象类型

javascript 复制代码
function isArray(obj) {
    return obj.constructor === Array;
    // 或者更好的方式:Array.isArray(obj)
}

function isDate(obj) {
    return obj.constructor === Date;
}

function isRegExp(obj) {
    return obj.constructor === RegExp;
}

2. 深拷贝

javascript 复制代码
function deepClone(obj) {
    // 根据constructor创建新实例
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    
    // 处理数组
    if (obj.constructor === Array) {
        return obj.map(item => deepClone(item));
    }
    
    // 处理日期
    if (obj.constructor === Date) {
        return new Date(obj.getTime());
    }
    
    // 处理正则表达式
    if (obj.constructor === RegExp) {
        return new RegExp(obj);
    }
    
    // 处理普通对象
    const cloned = new obj.constructor();
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloned[key] = deepClone(obj[key]);
        }
    }
    
    return cloned;
}

3. 动态创建组件

javascript 复制代码
// UI框架中常见模式
function Component(config) {
    this.id = config.id;
    this.type = config.type;
}

function Button(config) {
    Component.call(this, config);
    this.text = config.text;
}

Button.prototype = Object.create(Component.prototype);
Button.prototype.constructor = Button;

function Input(config) {
    Component.call(this, config);
    this.value = config.value;
}

Input.prototype = Object.create(Component.prototype);
Input.prototype.constructor = Input;

// 工厂函数,根据类型创建组件
function createComponent(config) {
    const componentTypes = {
        button: Button,
        input: Input,
        // 可以动态注册新类型
    };
    
    const ComponentClass = componentTypes[config.type];
    if (!ComponentClass) {
        throw new Error(`Unknown component type: ${config.type}`);
    }
    
    return new ComponentClass(config);
}

九、常见陷阱和注意事项

1. constructor的可写性

javascript 复制代码
function Person(name) {
    this.name = name;
}

const p = new Person('Alice');

// constructor属性可以被修改
p.constructor = Array;
console.log(p.constructor === Array); // true(但p不是数组)

// 更安全地获取constructor
console.log(Object.getPrototypeOf(p).constructor === Person); // true

2. 性能考虑

javascript 复制代码
// 方法定义在原型上 vs 定义在构造函数中

// 方式1:定义在原型上(推荐)
function Person1(name) {
    this.name = name;
}
Person1.prototype.sayHello = function() {
    console.log(`Hello, ${this.name}`);
};
// 优点:所有实例共享同一个函数,节省内存

// 方式2:定义在构造函数中
function Person2(name) {
    this.name = name;
    this.sayHello = function() {
        console.log(`Hello, ${this.name}`);
    };
}
// 缺点:每个实例都有独立的函数副本,浪费内存

const people1 = [];
const people2 = [];

// 创建1000个实例测试
for (let i = 0; i < 1000; i++) {
    people1.push(new Person1(`Person1-${i}`));
    people2.push(new Person2(`Person2-${i}`));
}

console.log(people1[0].sayHello === people1[1].sayHello); // true
console.log(people2[0].sayHello === people2[1].sayHello); // false

十、面试常见问题

问题1:解释new操作符做了什么?

javascript 复制代码
function newOperator(constructor, ...args) {
    // 1. 创建一个空对象,继承构造函数的原型
    const obj = Object.create(constructor.prototype);
    
    // 2. 将构造函数的this绑定到这个新对象
    const result = constructor.apply(obj, args);
    
    // 3. 如果构造函数返回一个对象,则返回该对象
    //    否则返回新创建的对象
    return result instanceof Object ? result : obj;
}

// 使用自定义的new
function Person(name) {
    this.name = name;
}

const p = newOperator(Person, 'Alice');
console.log(p.name); // Alice
console.log(p instanceof Person); // true

问题2:如何判断一个属性是自身的还是继承的?

javascript 复制代码
function Person(name) {
    this.name = name;
}
Person.prototype.species = 'Human';

const p = new Person('Alice');

// 方法1:hasOwnProperty
console.log(p.hasOwnProperty('name')); // true
console.log(p.hasOwnProperty('species')); // false

// 方法2:in操作符 vs hasOwnProperty
console.log('name' in p); // true
console.log('species' in p); // true
console.log('toString' in p); // true(继承自Object.prototype)

// 方法3:Object.getOwnPropertyNames
console.log(Object.getOwnPropertyNames(p)); // ['name']

总结要点

  1. prototype是函数的属性,用于实现基于原型的继承

  2. constructor是原型对象的属性,指向创建该原型的构造函数

  3. 实例通过__proto__访问原型,原型通过constructor访问构造函数

  4. 方法应该定义在原型上,属性应该定义在构造函数中

  5. 继承时需要修复constructor,防止污染父类

  6. new操作符会创建新对象,设置原型链,绑定this

  7. constructor可用于类型识别和动态创建实例

  8. ES5的原型系统是理解JavaScript继承的基础,虽然ES6引入了class语法,但底层仍然是原型机制

理解prototype和constructor是掌握JavaScript面向对象编程的关键,也是理解现代框架(如React、Vue)内部实现的基础。

相关推荐
Van_captain18 小时前
rn_for_openharmony常用组件_Tabs选项卡
javascript·开源·harmonyos
赵民勇18 小时前
ES6中的const用法详解
javascript·es6
Van_captain19 小时前
React Native for OpenHarmony Toast 轻提示组件:自动消失的操作反馈
javascript·开源·harmonyos
Van_captain19 小时前
React Native for OpenHarmony Modal 模态框组件:阻断式交互的设计与实现
javascript·开源·harmonyos
xkxnq19 小时前
第一阶段:Vue 基础入门(第 14天)
前端·javascript·vue.js
前端小臻19 小时前
列举react中类组件和函数组件常用到的方法
前端·javascript·react.js
研☆香19 小时前
html css js文件开发规范
javascript·css·html
赵民勇19 小时前
JavaScript中的this详解(ES5/ES6)
前端·javascript·es6
wayne21419 小时前
React Native 状态管理方案全梳理:Redux、Zustand、React Query 如何选
javascript·react native·react.js