原型
原型引入
我们看以下代码
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
//向对象中添加一个方法
this.sayName = function () {
console.log("我是" + this.name);
}
}
//创建一个Person的实例
var per = new Person("zdz", 18, "nv");
var per2 = new Person("xyl", 28, "nan");
per.sayName();
per2.sayName();
console.log(per.sayName == per2.sayName); //打印结果为false
他们的sayName方法是写在构造函数 Person 内部的,然后在两个实例中进行了调用。造成的结果是,构造函数每执行一次,就会给每个实例创建一个新的 sayName 方法。也就是说,每个实例的sayName都是唯一的。因此,最后一行代码的打印结果为false。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。这完全没有必要。因为所有方法都是同样的行为,完全应该共享。这个问题的解决方法,就是 JavaScript 的原型对象(prototype)
prototype的作用
我们所创建的每一个函数,解析器都会向函数中添加一个属性 prototype。这个属性对应着一个对象,这个对象就是我们所谓的原型对象。如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的实例对象中都会有一个隐含的属性,指向该构造函数的原型,我们可以通过proto来访问该属性。
// 定义构造函数
function Person() {}
var per1 = new Person();
var per2 = new Person();
console.log(Person.prototype); // 打印结果:[object object]
console.log(per1.__proto__ == Person.prototype); // 打印结果:true
上方代码的最后一行:打印结果表明,实例.proto 和 构造函数.prototype都指的是原型对象。
原型对象可以帮助我们实现原型对象上的资源共享,但原型上的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
其实,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。如果实例对象自身就有某个属性或方法,原型上的属性值并没有修改,只是它不会再去原型对象寻找这个属性或方法。
原型链
所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型。 如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。Object.prototype的原型是null。因此,原型链的尽头就是null
constructor
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
function P() {}
P.prototype.constructor === P // true
由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。
function P() {}
var p = new P();
p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false
通过,constructor。可以得知某个实例对象,到底是哪一个构造函数产生的。
另外,有了constructor属性,可以从一个实例对象新建另一个实例。
function Constr() {}
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
上面代码中,x是构造函数Constr的实例,可以从x.constructor间接调用构造函数。这使得在实例方法中,调用自身的构造函数成为可能。
我们有更简单的原型语法,但因为constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错。
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
如果不能确定constructor属性是什么函数,还有一个办法:通过name属性,从实例得到构造函数的名称。
function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"
总结原型链
- 原型对象也是对象,所以它也有原型,当我们使用或访问一个对象的属性或方法时:
- 它会先在对象自身中寻找,如果有则直接使用;
- 如果没有则会去原型对象中寻找,如果找到则直接使用;
- 如果没有则去原型的原型中寻找,直到找到Object对象的原型。
- Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回 null