关于JS原型链通俗讲解
写在开头的话
本文章将从感性的角度理解JS原型链,文章内容将以通俗的语言讲解,力求每个人都能明白JS中原型的含义,但也因为这个原因,本文内容并不严谨,本文重在理解原型概念,而不是明白底层的实际操作!!!
什么是原型链
在理解原型之前,我们先来“跳跃式”地来看一下原型链:原型链实际上就是继承,即子类继承父类的属性和方法
一个proto的例子
假设有两个变量,它们均为对象:
在JS中变量名和函数名可以用汉字,但实际项目中不建议这样做,以下仅作演示
var 中国={};
中国.位置="亚洲";
中国.展示位置=function(){console.log(this.位置)};
var 陕西={};
我现在想让对象陕西继承中国的属性和方法,这时该怎们做呢?
先别急,我们先来看这样一个示例:
var 示例={名称="普通对象"};
这时,如果我们调用示例.名称
,我们得到的就是"普通对象"
,接下来我给你变个魔术:
var 示例={};
示例.__proto__={名称="普通对象"};
注:__porto__
属性左右各有两个下划线,因为CSDN文章编写问题,以下均写为proto
相比于上一段代码,我们这次给示例添加了一个名为proto的属性,并且proto属性是一个对象,最后,我们把名称放到了proto对象下
这时我们再调用示例.名称
,我们得到还是"普通对象"
!!!
这时候你可能会问:
"名称"属性应该在示例.proto下才对呀?示例.__proto__.名称
这样的写法才正确啊!
*确实,名称属性就是在示例.proto这个对象中的,但proto属性是JS中一个特殊
的属性!!!*实际上当我们调用示例.名称
时,会发生以下操作:
- 在示例中寻找属性名称
- 示例.名称不存在
- 试图在示例.proto中寻找名称
- 示例.proto存在属性名称
- 返回示例.proto.名称
让我们来看一看到底发生了什么,首先,当我们调用示例.名称时,底层为我们在示例对象中寻找名称属性,可是并未找到,接下来就是最关键的一步!!这时,底层会试图在示例.__proto__
中寻找名称属性,最后,它找到了,所以我们调用示例.名称
,却返回了示例.__proto__.名称
综上所述,我们可以概括,当我们调用一个对象的属性或方法时,如果这个对象没有该属性或方法,底层就会自动到__proto__
属性中去寻找
var 中国={};
中国.位置="亚洲";
中国.展示位置=function(){console.log(this.位置)};
var 陕西={};
回到开头的问题:
让对象陕西继承中国的属性和方法其实很简单,只需要将陕西的proto属相指向中国就行了:
var 中国={};
中国.位置="亚洲";
中国.展示位置=function(){console.log(this.位置)};
var 陕西={__proto__:中国};
这时,如果我们调用陕西.展示位置(),将输出亚洲,这时过程图:
- 在陕西中寻找方法展示位置
- 陕西.展示位置不存在
- 试图在陕西.proto中寻找展示位置
- 陕西.proto也就是中国存在方法展示位置
- 返回中国.proto.展示位置
- 执行展示位置,打印this.位置
- 因为在陕西下调用展示位置所以this.位置替换为陕西.位置,可它
不存在
- 试图在陕西.proto中寻找位置
- 找到陕西.proto.位置
- 返回它,也就是中国.位置
proto属性也可以套娃:
var 中国={};
中国.位置="亚洲";
中国.展示位置=function(){console.log(this.位置)};
var 陕西={__proto__:中国};
陕西.位置="黄河流域";
var 西安={__proto__:陕西};
仍可以正常调用西安.展示位置()输出"黄河流域",过程与前文一致,只不过是先到陕西中寻找位置,输出黄河流域
以上就是proto属性的用法,proto属性是一个对象(Object类型)你可以把它理解为一个跳板
,当调用对象中没有的方法时,底层就回到proto属性中寻找
一个prototype的例子
请看下面的例子:
var 中国={};
中国.位置="亚洲";
中国.展示位置=function(){console.log(this.位置)};
中国.全名="中华人民共和国";
var 陕西={__proto__:中国};
如果我们调用陕西.全名将返回中华人民共和国,因为陕西的proto属性指向中国,所以我们可以在陕西下调用中国的任何
属性和方法,调用陕西.全名显然是很荒谬的!!!为了解决这一问题,我们应将陕西的proto属性指向中国可以被继承的属性和方法
:
var 中国={};
中国.可被继承={};
中国.可被继承.位置="亚洲";
中国.可被继承.展示位置=function(){console.log(this.位置)};
中国.全名="中华人民共和国";
var 陕西={__proto__:中国.可被继承};
这样,我们在调用陕西的属性和方法时,只会在陕西本身和中国.可被继承中寻找,这时陕西.全名就是undefined
当然,在实际项目中,这个可被继承对象,被命名为prototype
即,规范书写为:
var 中国={};
中国.全名="中华人民共和国";
中国.prototype={};
中国.prototype.位置="亚洲";
中国.prototype.展示位置=function(){console.log(this.位置)};
var 陕西={__proto__=中国.prototype};
陕西.简称="秦,陕";
陕西.prototype={__proto__=中国.prototype};
陕西.prototype.位置="黄河流域";
var 西安={__proto__:陕西.prototype};
注:
陕西={__proto__=中国.prototype}
是为了实现陕西.展示位置()
陕西.prototype={__proto__=中国.prototype}
是为了实现西安.展示位置()
如果你没看懂上面的例子,没关系,我将会在下一节深入解释它
以上就是prototype属性的用法,prototype属性是一个对象(Object类型)你可以把它理解为一个可被继承列表
,里面存放着期望
被继承的属性和方法
注:prototype仅仅是期望被继承的!!!毕竟你仍可以var 陕西={__proto__=中国}
原型的概念
在实际项目中,对象的proto属性总是指向另一个
对象的prototype属性,如:var 陕西={__proto__=中国.prototype}
即在当前对象中找不到指定属性方法时,去另一个对象的可被继承对象中寻找
如上,此时我们称:中国是陕西的原型
!!!
以下是一些关于原型链的规则:
在实际项目中,
任何对象(Object类型)都有proto属性,
只有函数默认拥有prototype属性,
任何函数.prototype.__proto__指向Object.prototype
,Object.prototype.__proto__
为null
,
所有数据类型的构造函数的__proto__属性一致
小结
以上就是本节的内容,当然像是原型链的规则或是JS的new关键字以及项目中的实际继承等详细信息我会下节讲(constructor也推到之后吧…),希望通过本节的介绍你能对JS原型链有初步的概念。