JavaScript原型与构造函数入门指南

89 阅读4分钟

在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"(来自原型)

三、为什么需要原型

  1. 减少内存使用:将公共属性和方法放在原型上,避免每个实例都创建副本
  2. 代码重复:多个实例共享相同功能
  3. 动态扩展:可随时向原型添加新功能,所有实例都能立即使用
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引擎会:

  1. 先在对象自身查找
  2. 如果找不到,则去对象的__proto__(即其构造函数的prototype)上查找
  3. 继续沿着原型链向上查找,直到找到或到达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操作符的执行顺序

  1. 创建空对象:创建一个新的空对象
  2. 设置原型链:将新对象的 __proto__ 指向构造函数的 prototype
  3. 执行构造函数:将新对象作为 this 执行构造函数
  4. 返回对象:返回创建的对象(除非构造函数返回另一个对象)
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()

六、原型的重要特性

  1. 实例不能直接修改原型属性(除非直接操作构造函数.prototype)
  2. 删除操作只影响实例自身属性,不影响原型
//修改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"

总结

  1. 构造函数:用于创建和初始化对象的函数(通常首字母大写)
  2. prototype 属性:每个函数都有的对象,包含共享的属性和方法
  3. proto 属性:每个对象都有的属性,指向其构造函数的 prototype
  4. 原型链:通过 proto 形成的链条,实现属性和方法的查找
  5. new 操作符:创建新对象 → 设置原型链 → 执行构造函数 → 返回对象