Skip to content
On this page

原型和原型链

之所以存在原型,是因为JS语言要实现面向对象,而原型是面向对象的实现手段之一。能否判断一个实例的类型成为了一个语言能否支持面向对象的标准。JS通过原型的方式避免了类型的丢失,通过原型可以知道某个对象属于哪个类型。

在 JavaScript 中,每一个引用数据类型都有一个内部属性 _proto_,该属性指向另外一个对象,该对象被称之为原型(prototype)。

js
let arr = [];
let obj = {};
let func = function() {};

arr.__proto__ === Array.prototype; // true
obj.__proto__ === Object.prototype; // true
func.__proto__ === Function.prototype; // true

在这些引用数据类型的原型上,定义了一些公用的变量和方法。当我们定义对应类型的变量时,就可以通过原型链的方式继承到这些公用的属性和方法。

构造函数

原型(prototype)通常在构造函数中定义:

js
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.say = function() {
  console.log('hi!, i am ' + this.name);
}

let ming = new Person('小明', 18)
ming.say(); // hi!, i am 小明

如上所示,定义了一个名为Person的构造函数,在构造函数的原型上定义了say方法。通过Person构造函数创建出来的实例对象中,其原型上都会存在say方法。当执行ming.say()时,会现在ming实例对象中查找是否存在say方法,如果没有找到,则通过原型链的方式找到这个对象的原型,从而执行原型上的say方法。

js
ming.valueOf(); // Person {name: '小明', age: 18}

如上所示,在ming这个实例对象和它的原型上,都没有定义valueOf方法,为什么调用了valueOf方法没有报错呢?继续往下探究。

前面说到,每一个引用数据类型都有一个内部属性 _proto_,该属性指向另外一个对象,该对象被称之为原型。而ming这个实例对象的原型,本身也是一个对象,它也有__proto__属性,所以它也会有自己的原型,也就是Object这个构造函数的原型。而Object构造函数的原型上定义了我们常见的对象处理方法,例如hasOwnProperty/isPrototypeOf/toString等等,当然也包括例子中的valueOf

实际上,还是通过原型链的方式,找到了Object的原型,从而调用了对应的方法。

需要注意的是,Object构造函数的原型也是对象,也存在__proto__属性,但是它为null,原型链的顶端就到null为止。

总结

综上所述,我认为原型就是一个用来存放公用的变量和方法的对象。通过原型链的方式可以继承一些公用的变量和方法,从而让代码更加的清晰和简洁,每一个对象或方法可以更专注于本身所需要处理的业务逻辑。