原创 mike Web全栈开发者驿站
原型和原型链在 js中起着至关重要的作用。它们使得 js 能够以一种独特的方式实现面向对象编程,虽然与传统的面向对象编程语言有所不同,但却提供了很大的灵活性和强大的功能。然而,原型链也有一些缺点,如属性查找的性能问题、继承的脆弱性以及隐私和封装问题等,在使用时需要注意这些问题,以确保代码的正确性和可维护性。
一、什么是原型
定义
:每个函数都有一个 prototype 属性,它指向一个对象,这个对象被称为该函数的原型。当函数被用作构造函数创建对象时,新创建的对象会自动拥有一个指向构造函数原型的内部指针,这个指针被称为对象的原型。
function Person() {}console.log(Person.prototype);
原型的作用:
实现属性和方法的共享
:在原型上定义的属性和方法可以被由该构造函数创建的所有对象共享。这可以节省内存空间,因为不需要为每个对象都单独存储相同的属性和方法。
function Person() {}Person.prototype.name = '无名氏';Person.prototype.sayHello = function() {console.log('Hello!');};const person1 = new Person();const person2 = new Person();console.log(person1.name); // 无名氏console.log(person2.name); // 无名氏person1.sayHello(); // Hello!person2.sayHello(); // Hello!
二、什么是原型链
定义
:当访问一个对象的属性或方法时,如果在对象本身找不到,JavaScript 引擎会沿着对象的原型链向上查找,直到找到该属性或方法或者到达原型链的顶端(Object.prototype)。对象的原型链是由对象的原型以及原型的原型等组成的链条。
function Person() {}Person.prototype.name = '无名氏';const person1 = new Person();console.log(person1.name); // 无名氏
在这个例子中,当访问 person1.name 时,首先在 person1 对象本身查找 name 属性,找不到后沿着原型链在 Person 的原型上找到了 name 属性。
原型链的作用:
实现继承
:JavaScript 中没有传统的类继承概念,而是通过原型链实现继承。一个对象可以通过原型链继承另一个对象的属性和方法。
function Person() {}Person.prototype.sayHello = function() {console.log('Hello from Person!');};function Student() {}Student.prototype = Object.create(Person.prototype);const student1 = new Student();student1.sayHello(); // Hello from Person!
在这个例子中,Student 的原型被设置为一个新的对象,这个新对象的原型是 Person.prototype,从而实现了 Student 对象继承 Person 对象的方法。
原型决定实例的初始属性和方法
当使用构造函数创建一个实例时,该实例会自动拥有一个指向构造函数原型的内部指针。构造函数的原型上定义的属性和方法会被实例所继承,成为实例的初始属性和方法。
function Person() {}
Person.prototype.name = '无名氏';
Person.prototype.sayHello = function() {console.log('Hello!');
};
const person1 = new Person();
console.log(person1.name); // 无名氏
person1.sayHello(); // Hello!
在这个例子中,构造函数 Person 的原型上定义了属性 name 和方法 sayHello。当创建实例 person1 时,person1 自动继承了这些属性和方法。
实例可以修改从原型继承的属性
实例可以修改从原型继承的属性值,但这不会影响原型上的属性值。如果实例添加了一个与原型上同名的属性,那么实例会优先使用自己的属性,而不是原型上的属性。
function Person() {}
Person.prototype.name = '无名氏';
const person1 = new Person();
const person2 = new Person();
console.log(person1.name); // 无名氏
console.log(person2.name); // 无名氏
person1.name = '张三';
console.log(person1.name); // 张三
console.log(person2.name); // 无名氏
在这个例子中,修改 person1 的 name 属性不会影响 person2 的 name 属性,因为 person1 只是在自己的实例上添加了一个新的属性,而不是修改了原型上的属性。
实例可以添加自己独有的属性和方法
除了继承原型上的属性和方法,实例还可以添加自己独有的属性和方法。这些属性和方法不会影响其他实例或原型
function Person() {}
const person1 = new Person();
person1.age = 20;
person1.sayAge = function() {console.log(`I am ${this.age} years old.`);
};
console.log(person1.age); // 20
person1.sayAge(); // I am 20 years old.
const person2 = new Person();
console.log(person2.age); // undefined
person2.sayAge(); // TypeError: person2.sayAge is not a function
在这个例子中,person1 添加了自己独有的属性 age 和方法 sayAge,而 person2 没有这些属性和方法。
三、如何确定一个对象的原型?
可以通过以下几种方式确定一个对象的原型:
使用Object.getPrototypeOf()方法
这是最直接的方法,它返回指定对象的原型(即内部的 [[Prototype]])。
function Person() {}
const person = new Person();
const prototype = Object.getPrototypeOf(person);
console.log(prototype === Person.prototype); // true
使用__proto__
属性(不推荐在生产环境中使用)
在一些浏览器环境中,对象有一个 __proto__
属性,可以用来访问对象的原型。但这个属性是非标准的,不建议在生产环境中使用。
function Person() {}
const person = new Person();
console.log(person.__proto__ === Person.prototype); // true
通过构造函数的prototype属性推断
如果知道一个对象是由哪个构造函数创建的,可以通过该构造函数的 prototype 属性来推断对象的原型。
function Person() {}
const person = new Person();
console.log(person instanceof Person && Person.prototype.isPrototypeOf(perso
n)); // true
这个方法通过判断对象是否是构造函数的实例以及构造函数的原型是否在对象的原型链上来间接确定对象的原型。