原型与原型链详解
构造函数
与new运算符一起使用在创建对象的语句中的函数。
在ES6之前,我们面向对象都是通过构造函数实现的。我们把对象的公共属性和方法放在构造函数中。
// 创建一个构造函数
function Foo(name, age) {
this.name = name;
this.age = age;
this.say = function() {
console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
}
}
实例对象
使用new运算符创建的对象。
构造函数虽然好用,但是这样做存在浪费内存的问题。如下例,构造函数中有say方法,函数属于复杂数据类型,它会单独在内存中开辟空间。下例创建了两个实例对象,就开辟了两个内存空间来存放同一个函数,造成了内存浪费的问题。
// 创建一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function() {
console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
}
}
// 创建实例对象
var person1 = new Person('cxk', '22');
var person2 = new Person('lyf', '23');
person1.say(); // 我的名字叫cxk,今年22岁
person2.say(); // 我的名字叫lyf,今年23岁
原型
将构造函数中的函数存放在原型对象(prototype)中,就可以解决内存浪费的问题。
- prototype(显式原型):每个对象(除了null)都具有的属性,该属性指向该对象的原型
- _proto_(隐式原型):只有函数(实例)对象才有的属性,该属性指向该函数的原型对象
// 创建一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
}
// 创建实例对象
var person= new Person('cxk', '22');
// 构造函数的显式原型(prototype) 和 实例对象的隐式原型(__proto__) 指向的是同一个原型对象
console.log(Person.prototype); // {say: ƒ, constructor: ƒ}
console.log(person.__proto__); // {say: ƒ, constructor: ƒ}
console.log(Person.prototype === person.__proto__); // true
constructor属性
原型对象上都存在一个constructor属性,它指向构造函数本身。
// 创建一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
console.log(Person.prototype); // {constructor: ƒ}
console.log(Person.prototype.constructor);
/*
运行结果:
ƒ Person(name, age) {
this.name = name;
this.age = age;
}
*/
console.log(Person.prototype.constructor === Person); // true
原型链
个人理解,原型链就是实例对象查找属性和方法的规则:
可以看出,person实例对象上并没有say方法,say方法是设置在原型对象(prototype)上的,但是使用person.say()却可以成功调用。
这是因为,person在自身对象上没有找到say方法,就会顺着__proto__属性去查找原型上是否存在say方法,如果有则调用,如果没有则继续顺着原型对象的__proto__属性去原型的原型上查找,一直找到最顶层为止。
这样查找变量的过程,就形成了一条原型链。
也因为原型链的存在,所有的实例对象都可以共享使用say方法。
// 创建一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
}
// 创建实例对象
var person= new Person('cxk', '22');
person.say(); // 我的名字叫cxk,今年22岁
原型的原型
上述原型链的查找规则描述:person在自身对象上没有找到say方法,就会顺着__proto__属性去查找原型上是否存在say方法,如果有则调用,如果没有则继续顺着原型对象的__proto__属性去原型的原型上查找,一直找到最顶层为止。
那么原型的原型是什么呢?原型链的尽头又是哪里?
// 创建一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
console.log(Person.prototype); // {constructor: ƒ}
console.log(Person.prototype.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(Person.prototype.__proto__.__proto__); // null
console.log(Person.prototype.__proto__.constructor); // ƒ Object() { [native code] }
// 证明原型链的查找规则是否成立:
// 将say方法放在原型的原型上,看看实例对象person是否可以查找到
Person.prototype.__proto__.say = function() {
console.log('我的名字叫' + this.name + ',今年' + this.age + '岁');
}
var person = new Person('cxk', '22');
person.say(); // 我的名字叫cxk,今年22岁