Javascript 原型与原型链
这个问题在此之前 我都不能很好的回答出来 什么是原型 原型链 构造函数 实例 他们之间的关系 近期查阅大量资料文档 对比有一定的理解 并在此记录下
1.prototype
在传统的面向对象编程里面,首先会定义一个类,之后创建对象实例的时候,类中定义的属性和方法都会被复制到实例中,在Javascript中并不这样复制,而是在对象和它的构造器之间建立一个链接(__proto__属性,是从构造函数的prototype属性派生而来),之后通过上溯原型链,在构造器中找到这些属性和方法
function Persion(){}
console.log(Person.prototype);
每个函数上面都有一个属性(prototype) 这个属性指向函数的原型对象(Person.prototype)
即使只是定义了一个空的函数 也存在prototype属性,打印结果如下
上述事例表明,即使我们什么也不做,但是在浏览器的内存中已经存在两个对象:Person(函数)和Person.prototype,其中,我们称Person为构造函数,后面会用到这个函数来new对象,Person.prototype称为Person的原型对象,简称 原型
他们的关系如图:
原型的概念:每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。
2.__ proto__
__proto__属性 是每个对象(除null外)都会有的属性,这个属性会指向该对象的原型。
直接上代码
function Person() {
}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
// 学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
他们的关系如下:
补充说明:
绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。
3.constructor
** 每个原型都有一个constructor属性,指向该关联的构造函数。**
上代码
function Person() {
}
var person = new Person();
console.log(Person.prototype.constructor == Person) // true
所以上面的关系图就变成了
补充说明
function Person() {}
var person = new Person();
console.log(Person.prototype.constructor == Person) // true
console.log(person.constructor === Person); // true
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以
person.constructor === Person.prototype.constructor //true
4.实例与原型
如果我们给Person构造函数添加属性并new一个Person对象,看下会发生什么?
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.showName = function(){
return this.name;
}
var p1 = new Person("Tom",16);
console.log(p1.showName());//"Tom"
总结:每个实例上面都有一个隐式原型(proto) 指向了函数的对象,比如上面showName方法并不是在p1对象上声明的,是在Person这个构造函数的原型上的 也就是p1对象有一个隐式原型指向了Person.prototype
5.原型的原型
上代码
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.showName = function(){
return "访问的是原型上的方法";
}
var p1 = new Person("Tom",16);
p1.showName = function (){
return "访问的是p1对象上的方法";
}
var p2 = new Person();
console.log(p1.showName());//访问的是p1对象上的方法
console.log(p2.showName());//访问的是原型上的方法
上述例子中 原型上有showName方法,p1对象上也有showName方法,这个时候p1.showName()调用的是自身对象上的showName方法,但是p2对象上没有showName方法,原型上有,这个时候就会顺着p2对象的__proto__属性指向的原型(也就是Person)找找看有没有,然后找到了,输出的是原型上的方法。
如果原型上也没有对应的方法呢,这个时候他会顺着原型的原型去找对应的方法,最终找到最顶层也就是Object对象的原型,如果还是没有找到,就会返回undefined,可以验证一下,如下:
//--p1的showName是p1对象上的方法,p2的showName是原型上的方法,所以不等
console.log(p1.showName === p2.showName); //false
//--因为p2对象没有sex属性 然后到原型上找,直到找到最顶层Object 都没有sex这个属性,所以最后输出undefined
console.log(p2.sex); //undefined
结合之前的理解 关系图就变成了
总结:
实例访问属性和方法的时候,遵循以下两个原则:
1.如果实例上存在,则直接调用实例本身的属性和方法
2.如果实例上不存在,就会顺着__proto__找到实例的原型,在原型上找,找不到依旧顺着__proto__的指向一直向上找,直到Object层 找不到就会返回undefined
——这个其实就是原型链的原理
6.原型链
上面原型中有提到,对象在寻找某一属性的时候,如果自身属性没找到就去他的原型对象去找,若在原型对象上找到对应的属性则停止,若找不到,就继续去原型的原型上去找对应的属性,这样就构成了一条原型链
比如上面例子中Person的原型上的__proto__指向的就是Object的原型(Object.prototype)
验证如下:
console.log(Person.prototype.__proto__ === Object.prototype); //true
Object对象是javascript的顶层对象,他也有自己的原型Object.prototype,这个值默认为null
console.log(Object.prototype.__proto__ === null) // true
但是Object.prototype 是没有原型的(因为Object.prototype.proto 的值为 null),所以查找属性的时候 查找到Object.prototype还没找到的话就会停止查找
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
7.修改原型
如果手动修改原型呢?会有什么影响?看下面的例子
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.showName = function() {
return "Person原型上的showName方法";
}
var p1 = new Person("Tom",16);
var p2 = new Person("Lily",18);
p2.__proto__ = null;
console.log(p1.showName());//Person原型上的showName方法
console.log(p2.showName());//TypeError: p2.showName is not a function
上面的例子里面 我们手动修改了p2的__proto__,他原本指向的是Person.prototype,我们手动修改让他指向null,结果就会报错
再看一个下面的示例,新建一个构造函数Animal,强制修改p2的原型链,让他指向Animal的原型
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.showName = function (){
return "Person原型上的showName方法";
}
//然后定义另一个构造函数
function Animal(){
}
Animal.prototype.showName = function (){
return "Animal原型上的showName方法";
}
var p1 = new Person("Tom",16);
var p2 = new Person("Lily",18);
p2.__proto__ = Animal.prototype;//将p2的__proto__指向Animal的原型
console.log(p1.showName());//Person原型上的showName方法
console.log(p2.showName());//Animal原型上的showName方法
上述代码可以看到p2的showName调用的是Animal的原型上面的showName方法,不再是Person原型上的方法。
console.log(p2.showName() === Aminal.prototype.showName()); //true
这个也能说明 p2.showName就是调用的Animal原型上的showName方法
一般来说,我们不建议手动修改某个对象的原型,这样会破坏掉原来的原型链
但是我们可以利用原型更好的封装一个类,原型是javascript面向对象编程中非常重要的一点
8.类的封装使用
直接上代码
function Person(name,age){
this.name = name;
this.age = age;
this.showName = function (){
console.log("showName--");
}
}
var p1 = new Person("Tom",16);
var p2 = new Person("Lily",18);
p1.showName();
p2.showName();
console.log(p1.showName===p2.showName);//false
一般来说,抽象的方法是完成某一个事情的,但是上述两个对象,各自创建了一个方法,浪费内存,其实这些方法可以通过同一个方法来完成,这个方法不必定义在构造函数上,可以直接定义在构造函数的原型上,而属性则定义在构造函数上,如下:
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.showName = function (){
console.log("showName--");
}
var p1 = new Person("Tom",16);
var p2 = new Person("Lily",18);
p1.showName();
p2.showName();
console.log(p1.showName===p2.showName);//true
构造函数定义属性,原型上定义方法,这种方式可以更好的封装一个类。比如数组Array对象的sort方法 push方法等 都是用的这种方式来定义的
【ps:本文内容来源于网络,最后是自己整合的,仅作为交流学习 如有侵权 请联系删除】