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原型链的终点