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:本文内容来源于网络,最后是自己整合的,仅作为交流学习 如有侵权 请联系删除】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值