在JavaScript中,原型和构造函数是理解对象如何创建的继承的关键概念。本文将通过简单易懂的方式,结合代码示例,为你揭开这些概念的神秘面纱。
一、构造函数:创建对象的模板
构造函数是创建对象的特殊函数,用于初始化新创建的对象。JavaScript提供了许多内置构造函数:
// 使用字面量创建(更常用)
let str = "World"; // 字符串字面量
let num = 3.14; // 数字字面量
let bool = false; // 布尔字面量
let objLiteral = {}; // 对象字面量
let arrLiteral = [4, 5, 6]; // 数组字面量
let funcLiteral = function() {}; // 函数字面量
// 使用构造函数创建对象
let strObj = new String("Hello"); // 字符串对象
let numObj = new Number(42); // 数字对象
let boolObj = new Boolean(true); // 布尔对象
let obj = new Object(); // 普通对象
let arrObj = new Array(1, 2, 3); // 数组对象
let fnObj = new Function('a', 'b', 'return a + b'); // 函数对象
二、原型(prototype): 共享属性和方法
每个函数都有一个prototype属性,它是一个对象,包含可以由该构造函数创建的所有实例共享的属性和方法。
//Person.prototype --- 原型 也是一个对象{};
// 在Person的原型上添加say属性
Person.prototype.say = 'Hello';
function Person() {
this.name = 'John';
this.age = 30;
}
let p = new Person();
console.log(p.name); // "John"(来自构造函数)
console.log(p.say); // "Hello"(来自原型)
三、为什么需要原型
- 减少内存使用:将公共属性和方法放在原型上,避免每个实例都创建副本
- 代码重复:多个实例共享相同功能
- 动态扩展:可随时向原型添加新功能,所有实例都能立即使用
function Car(color, owner) {
this.name = 'su7'
this.height = 1400
this.width = 2000
this.color = color
this.owner = owner
}
let car1 = new Car('red', 'Zhangsan')
let car2 = new Car('blue', 'Lisi')
console.log(car1.name); // "su7"(来自各自实例对象自身的属性)
console.log(car2.name); // "su7"(来自各自实例对象自身的属性)
这样是不是显得有些冗杂,接下来把公共属性挂在原型上就会使得代码更加简洁。
Car.prototype.name = 'su7';
Car.prototype.height = 1400;
Car.prototype.width = 2000;
function Car(color, owner) {
// 每个实例特有的属性
this.color = color;
this.owner = owner;
}
let car1 = new Car('red', 'Zhangsan');
let car2 = new Car('blue', 'Lisi');
console.log(car1.name); // "su7"(来自原型)
console.log(car2.name); // "su7"(来自原型)
四、原型链:JavaScript的继承机制
当访问对象的属性时,JavaScript引擎会:
- 先在对象自身查找
- 如果找不到,则去对象的
__proto__(即其构造函数的prototype)上查找 - 继续沿着原型链向上查找,直到找到或到达null
let arr = [];
//arr.__proto__ = Array.prototype
//Array.prototype.__proto__ = Object.prototype
// arr -> Array.prototype -> Object.prototype -> null
let num = 1;
//num.__proto__ = Number.prototype
//Number.prototype.__proto__ = Object.prototype
// num -> Number.prototype -> Object.prototype -> null
// 创建没有原型的对象
let c = Object.create(null); // c.__proto__ = null
五、new操作符的执行顺序
- 创建空对象:创建一个新的空对象
- 设置原型链:将新对象的
__proto__指向构造函数的prototype - 执行构造函数:将新对象作为
this执行构造函数 - 返回对象:返回创建的对象(除非构造函数返回另一个对象)
function Car() {
this.name = 'su7',
this.height = 1400
}
// 使用 new 操作符
let car = new Car()
在代码中,注释也清晰地展示了这个过程:
function Car() {
// 1. 创建空对象
// let obj = {}
// 2.设置原型链
// obj.__proto__ = Car.prototype
this.name = 'su7',
this.height = 1400
//3.将构造函数里的 this 指向 obj
//obj = {
// name = 'su7',
// height = 1400
}
// 4.return obj
}
let car = new Car()
六、原型的重要特性
- 实例不能直接修改原型属性(除非直接操作构造函数.prototype)
- 删除操作只影响实例自身属性,不影响原型
//修改str长度不成立
let str = ' hello '
str.length = 10
console.log(str.length) //打印结果还是 7
// 在Car的原型上添加run方法
Car.prototype.run = function() {
console.log('Running');
};
function Car() {
this.name = 'su7';
}
let car = new Car();
// 尝试删除原型上的方法(不会成功)
delete car.run;
car.run(); // 仍然输出"Running"
总结
- 构造函数:用于创建和初始化对象的函数(通常首字母大写)
- prototype 属性:每个函数都有的对象,包含共享的属性和方法
- proto 属性:每个对象都有的属性,指向其构造函数的 prototype
- 原型链:通过 proto 形成的链条,实现属性和方法的查找
- new 操作符:创建新对象 → 设置原型链 → 执行构造函数 → 返回对象