JS 原型与原型链的设计哲学及应用
文章目录
1.原型和原型链的详细设计背景和原因1. 面向对象编程的影响2. 简易脚本语言的需求3. 对象的联系机制4. new 命令的引入5. `prototype` 属性的引入6. 实例对象的属性和方法
2.原型3.原型链原型链调用顺序原型链图解多层原型链的应用
1.原型和原型链的详细设计背景和原因
1. 面向对象编程的影响
在 JS 设计之初,正是面向对象编程(OOP)最兴盛的时期。当时,C++ 是最流行的语言,而 Java 语言的 1.0 版本即将于第二年推出,Sun 公司正在大力推广。JavaScript 的设计者 Brendan Eich 无疑受到了这些语言的影响,因为 JS 中所有的数据类型都是对象,这一点与 Java 非常相似。
2. 简易脚本语言的需求
尽管受到了面向对象编程的影响,Brendan Eich(JS的创造者) 仍然希望 JS 能够保持简单和易用,作为一种简易的脚本语言。如果完全引入“继承”机制,尤其是“类”(class)的概念,JS 就会变得过于正式和复杂,这与 Brendan Eich 的设计初衷不符。因此,他需要找到一种既简单又有效的机制来实现对象之间的属性和方法共享。
3. 对象的联系机制
由于 JavaScript 中所有数据类型都是对象,必须有一种机制将所有对象联系起来。因此,Brendan Eich 决定引入“继承”机制,但不使用“类”的概念。他选择了一种更轻量级、更灵活的方法——原型和原型链。
4. new 命令的引入
考虑到 C++ 和 Java 语言都使用 new 命令生成实例对象,Brendan Eich 决定将 new 命令引入 JavaScript。这样,开发者可以使用熟悉的语法来创建对象实例。然而,JavaScript 没有“类”,所以 new命令后面跟的是构造函数,而不是类。
C++ 的写法:
ClassName *object = new ClassName(param);
Java 的写法:
Foo foo = new Foo();
JavaScript 的写法:
const instance = new ConstructorFunction();
5. prototype 属性的引入
new 命令的一个缺点是无法共享属性和方法。为了解决这个问题,Brendan Eich 为构造函数设置了一个 prototype 属性。这个属性包含一个对象,所有实例对象需要共享的属性和方法都放在这个对象里面,而不需要共享的属性和方法则放在构造函数里面。
构造函数:
function Person(name) {
this.name = name; // 实例对象独有的属性
}
Person.prototype.sayName = function() {
console.log(this.name); // 共享的方法
};
创建实例:
const person1 = new Person('Alice');
const person2 = new Person('Bob');
person1.sayName(); // 输出: Alice
person2.sayName(); // 输出: Bob
6. 实例对象的属性和方法
实例对象的属性和方法分为两种:
本地属性和方法:这些属性和方法直接定义在实例对象上,每个实例对象都有自己的副本。引用的属性和方法:这些属性和方法定义在构造函数的 prototype 对象上,所有实例对象共享这些属性和方法。示例:
function Person(name) {
this.name = name; // 本地属性
}
Person.prototype.age = 25; // 共享属性
Person.prototype.sayName = function() {
console.log(this.name); // 共享方法
};
const person1 = new Person('Alice');
const person2 = new Person('Bob');
console.log(person1.age); // 输出: 25
console.log(person2.age); // 输出: 25
person1.age = 30; // 修改 person1 的 age 属性
console.log(person1.age); // 输出: 30
console.log(person2.age); // 输出: 25
delete person1.age; // 删除 person1 的 age 属性
console.log(person1.age); // 输出: 25
2.原型
在 JavaScript 中,每个函数都有一个 prototype属性,该属性指向一个对象,称为原型对象。原型对象可以包含属性和方法,这些属性和方法可以被该函数创建的所有实例对象共享。
__proto__:向下查找;可以理解为指针,实例对象中的属性,指向了构造函数的原型(prototype)prototype:向上查找,使用 prototype 可以把方法挂在原型上,内存中保存一份,其他的实例就可以共享
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
const person1 = new Person('Alice');
const person2 = new Person('Bob');
person1.sayName(); // 输出: Alice
person2.sayName(); // 输出: Bob
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
Person 函数的 prototype 属性指向一个原型对象,该对象包含一个 sayName 方法。person1 和 person2 是 Person 的实例,它们的 __proto__ 属性指向 Person.prototype。当调用 person1.sayName() 时,JavaScript 会在 person1 对象中查找 sayName 方法,如果没有找到,就会沿着 __proto__ 链向上查找,最终在 Person.prototype 中找到 sayName 方法。
3.原型链
原型链是一种机制,通过这种机制,JavaScript 实现了继承。每个对象都有一个内部属性 Prototype(在大多数现代浏览器中可以通过 __proto__ 访问),该属性指向其构造函数的 prototype 对象。如果在对象中找不到某个属性或方法,JavaScript 会沿着 Prototype 链向上查找,直到找到该属性或方法,或者到达链的顶端(即 null)。
原型链调用顺序
先在对象/实例本身查找 ->构造函数中查找 ->对象原型中查找 -> 构造函数的原型中 -> 当前原型的原型中查找 -> 终点为null
原型链图解
多层原型链的应用
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类的构造函数
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 设置 Dog 的原型为 Animal 的原型
Dog.prototype.constructor = Dog; // 修复 constructor 指针
Dog.prototype.bark = function() {
console.log(`${this.name} is barking.`);
};
const dog1 = new Dog('Rex', 'German Shepherd');
dog1.eat(); // 输出: Rex is eating.
dog1.bark(); // 输出: Rex is barking.
console.log(dog1.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
Animal 是一个基类,包含一个 eat 方法。Dog 继承自 Animal,通过 Object.create(Animal.prototype)设置 Dog 的原型为 Animal 的原型。Dog 的实例 dog1 可以访问 Animal 原型上的 eat 方法和 Dog 原型上的 bark 方法。