解析JavaScript原型(一)

229 阅读5分钟

前言

我在之前的文章中曾介绍过一道网易的面试题:所有对象最终都继承自Object.prototype?那篇文章也详细的介绍了其中的原因,今天我们补充一点知识,介绍一下原型的一些基本知识。

初始原型:

我们都知道,我们可以在对象上添加属性或者方法,以达到实现相应功能的目的。而原型[[Prototype]],是JavaScript中对象的一个特殊的内置属性,其本质就是对其他对象的引用,下面举个简单的例子。

function Person(){
  this.name = '老六'
  this.age = 21
}

上述是一个构造函数,想要让老六(我现实中的一个朋友0.0)say hi我们可以怎么做呢?相信大家能想到的肯定是直接在构造函数内部定义sayHi(),如下:

function Person(){
  this.name = '老六'
  this.age = 21
  this.sayHi = function(){
    return 'Hi,我是 ' + this.name
  }
}
let p = new Person();
console.log(p.sayHi());
// 输出结果 Hi,我是老六

之前讲到过,原型是JavaScript中对象的一个特殊的内置属性,既然是对象的属性,那么我们理所当然得可以通过原型来实现上述功能,下面我们尝试一下。

Person.prototype.sayHi = function () {
  return 'Hi,我是'+this.name 
}
let p = new Person();
console.log(p.sayHi());

上述代码我们最终也能输出结果Hi,我是老六。

既然同样能达到实现对应功能,那么原型的这种实现,会不会有点多余呢,它本身相比在构造函数内部直接附加属性和方法有什么优势呢?下面我们一起来探究一下。

你是否曾想过一个这样的问题,当我们需要给对象增添新的方法和属性时,比如一些共有的方法,如果我们在构造函数中添加,那么我们就必须得给每个实例对象都添加该属性,这将会大大增大后期维护的难度,但是如果我们给直接在对象的原型上添加这些属性的话,所以实例对象都会拥有该属性,不需要修改每个实例对象的构造函数,从而减少代码的维护成本。这样说你可能还不能理解通透,下面我会通过实际的例子让大家认识的更充分。

// 构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 在原型上添加共有的方法
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

Person.prototype.increaseAge = function() {
  this.age++;
};

// 创建实例
const person1 = new Person('Alice', 25);
const person2 = new Person('Bob', 30);

// 调用共有方法
person1.sayHello(); // 输出: Hello, my name is Alice and I am 25 years old.
person2.sayHello(); // 输出: Hello, my name is Bob and I am 30 years old.

// 调用共有方法来增加年龄
person1.increaseAge();
console.log(person1.age); // 输出: 26

person2.increaseAge();
console.log(person2.age); // 输出: 31

上述例子我们可以清晰的看到,由于每个实例对象的年龄可能不相同,但每过一年,所有人的年龄都增长一岁,如何我们对实例对象进行修改,或者调用实例对象上的方法增加年龄,当然是可以的。但是一旦实例对象十分多的话,上述的操作就不太现实了,这会大大增加我们后期的维护成本,但我们在原型上添加共有方法,调用共有方法从而实现每过一年所有实例对象的年龄增加1,这将会大大降低后期代码维护的成本。

除了上述优势,原型还有下面的独特之处。

我们引出如下例子:

// 定义一个构造函数Car
// 用户可以自定义颜色
function Car(owner,color) {
  this.name = '法拉利'
  this.lang = 4900
  this.height = 1400
  this.owner = owner
  this.color = color
}

上述代码中,用户可以自定义法拉利的颜色,但是车长和车宽以及名字这些属性不能修改,(你总不可能把人家的车标给换了吧 ,哈哈哈)当我们用户修改这些属性时,程序并不会执行(法拉利公司不能让你把人家名字给换了吧,如果你能让法拉利老板修改他们品牌的名字,那就厉害了。), 如下:

function Car(owner,color) {
  this.name = '法拉利'
  this.lang = 4900
  this.height = 1400
  this.owner = owner
  this.color = color
}

var car1 = new Car('老六', 'red');

// 这样可以修改,用户自己想把自己的车叫什么名字,这随意
// 但汽车的本质还是法拉利
car1.name = '劳斯莱斯幻影'

// 但如果我们直接对Car这个构造函数进行修改,并不能直接对name进行修改
Car.name = '布加迪'

//输出的是用户自己给自己车的称呼:劳斯莱斯幻影
console.log(car1.name)

那么,如果法拉利的老板想换名字了,怎么办呢?有没有办法实现?这时候我们就可以使用原型实现对整个法拉利品牌名字的大改造。如下

Car.prototype.name = '法拉利'

function Car(owner,color) {
  this.lang = 4900
  this.height = 1400
  this.owner = owner
  this.color = color
}

var car1 = new Car('老六', 'red');
var car2 = new Car('老五', 'green');

car1.name = '劳斯莱斯幻影'

Car.prototype.name = '布加迪'

console.log("老六的车:"+car1.name)
// 输出:老六的车:劳斯拉斯幻影
console.log("老五的车:"+car1.name)
// 输出:老五的车:布加迪

上述例子可以看到

  1. 实例对象只能修改自己的属性,其他实例对象不会受影响 ,car1.name = '劳斯莱斯幻影'
  2. 要改只能通过原型来改 Car.prototype.name = '布加迪',原本用户给自己车取的名字并不会被修改
  3. 如果要删除实例对象上的属性,实例对象自己是做不到的,delete car1.name,比如上述代码不会执行
  4. 要删除实例对象上的属性,也只能通过原型来删除。delete Car.prototype.name;

原型就这些东西吗,当然不止,原型的深奥远比我们想得复杂,看下图:

_-1237392885__5cf4d2cc90022d57236031d8a4c8e311_436127147_mmexport1699599212356_0_xg_0.jpg

后续我会继续创作解析JavaScript原型(二),将会和大家一起解开上图的奥秘,和大家一起更加深刻的理解JavaScript中十分重要的概念--原型。

敬请期待......