JavaScript---原型链(ES5)

prototype的设计思想

1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。这是历史上第一个比较成熟的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。比如,如果网页上有一栏"用户名"要求填写,浏览器就无法判断访问者是否真的填写了,只有让服务器端判断。如果没有填写,服务器端就返回错误,要求用户重新填写,这太浪费时间和服务器资源了。因此,网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。

工程师Brendan Eich负责开发这种新语言。他觉得,没必要设计得很复杂,这种语言只要能够完成一些简单操作就够了,比如判断用户有没有填写表单。1994年正是面向对象编程(object-oriented programming)最兴盛的时期,C++是当时最流行的语言,而Java语言的1.0版即将于第二年推出,Sun公司正在大肆造势。

Brendan Eich无疑受到了影响,Javascript里面所有的数据类型都是对象(object),这一点与Java非常相似。但是,他随即就遇到了一个难题,到底要不要设计"继承"机制呢?如果真的是一种简易的脚本语言,其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。但是,他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。

因此,他就把new命令引入了Javascript,用来从原型对象生成一个实例对象。但是,Javascript没有"类",怎么来表示原型对象呢?这时,他想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。

但是,用构造函数生成实例对象,有一个致命的缺点,那就是无法共享属性和方法。每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。

考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地(呈现出来的独有的一面)的,另一种是引用(隐藏起来共享的一面)的。


由于该文本编辑器符号限制,"__proto__" 属性,下文简称 "proto"属性

一、"proto"属性

所有的对象都含有属性:“proto”,该属性指向构造函数的 “prototype” 属性

特殊:class关键字的定义的类(有父类)的 “proto” 属性直接指向父类,class是ES6新特性提出来的声明关键字。

示例1
	function testFun(x,y){
	this.a = x;
		this.b = y;
	}
	
	var testObj = new testFun("aa","2");
	
	/* testobj的"__proto__"属性指向构造函数的"prototype"属性 */
	testObj.__proto__ === testFun.prototype //true
	
	/* testFun的"__proto__"属性指向Function的"prototype"属性 */
	testFun.__proto__ === Function.prototype //true

所有的内置构造函数,包括Function的"proto"属性,都指向Function函数的"prototype"属性

	var m = [];
	m.__proto__ === Array.prototype //true
	Array.__proto__ === Function.prototype //true
	
	var n = {};
	n.__proto__ === Object.prototype //true
	Object.__proto__ === Function.prototype //true
	
	Function.__proto__ === Function.prototype //ture

意味着所有的内置构造函数包括其本身Function构造函数:实际上都是由Function该构造函数构造出来的。

二、"prototype"属性

所有的函数都含有属性:“prototype”,对象由函数生成,生成对象时,对象的"proto"属性指向函数的"prototype"属性。

函数的 "prototype" 属性是一个对象(只要是一个对象,就具有"__proto__"属性,指向该对象的构造函数的"prototype"属性),对象两个属性:		
					constructor			指向本身
					__proto__			指向该对象的构造函数的 "prototype" 属性
示例1的函数(testFun):
	testFun.prototype.constructor === testFun			//true
	testFun.prototype.__proto__ === Object.prototype	//true,意味着,该属性是一个由Object构造的对象

对象无法再构造新的实例,没有什么需要别人共享的,所以也就不需要,也不存在 “prototype” 属性,因为该属性是用来存放(隐藏起来共享的一面)的属性。

三、原型链

函数可以实例化对象,而这些实例化的对象需要有共享的一面,而这一面隐藏在"prototype"属性上,函数本身也是一个对象,所以同样拥有"proto"属性

对象.proto === 构造函数.prototype

	function Fun(){}
	var f = new Fun();
	
	/* 函数的prototype属性是一个对象,该对象拥有两个属性:constructor 和 __proto__ */
	Fun.prototype
	/*输出:	{constructor: ƒ}
			↲constructor: ƒ Fun()					>>.constructor指向 本身
			↲__proto__: Object(类型)				>>.	__proto__指向 Object.prototype,说明该对象由Object构造函数构造	*/
			
	typeof Fun.prototype // "Object"
	Fun.prototype.__proto__ === Object.prototype // true
	
	/* 对象的 __proto__ 属性指向其构造函数的 prototype 属性 */

两层链:

	f.__proto__ === Fun.prototype
	Fun.__proto__ === Function.prototype
	Function.__proto__  === Function.prototype

注释:因为对象(f)上层即函数(Fun),再上层即顶层函数(Function),再上层即最顶层的(依旧是Function,意味着所有的内置构造函数包括其本身Function构造函数:实际上都是由Function该构造函数构造出来的)

	Fun.prototype.__proto__ === Object.prototype

通过原型链可以使用链上级的定义在"prototype"属性中的方法,注意该方式可以动态添加。从而动态地扩展基类的功能特性。这在静态对象语言中是很不可思议的。

	Object.prototype.OOO = "OOOOO"
	Fun.prototype.QQQ = "QQQQQ"
	
	f.OOO // "OOOOO"
	f.QQQ // "QQQQQ"
	Fun.OOO // "OOOOO"

源头

一切的对象都是Object构造函数的子孙对象,Object 是一切(呈现出来的独有的一面)的源头,而Object.prototype 则是一切(隐藏起来共享的一面)的源头

	Function.prototype.__proto__ === Object.prototype //true
	Object.prototype.__proto__ === null  //true,JavaScript原型链的终点

猜你喜欢

转载自blog.csdn.net/weixin_41087220/article/details/90082770