首先我们看看原型编程范型至少包括以下基本规则。
所有的数据都是对象。
要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
对象会记住它的原型。
如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。
下面我们来谈论下javascript的原型继承:
1. 所有的数据都是对象 :
js有两套机制类型:基本类型和对象类型,基本类型包括 undefined、number、boolean、string、function、object ;对象类型包括String、Number、Array、Date、function以及自己建立的对象等等。实际上,js除了underfined 不是对象,其他的都是对象,那么在 js中也一定会有一个根对象,这些对象追根溯源都来源于这个根对象 。
事实上,JavaScript 中的根对象是 Object.prototype 对象。Object.prototype 对象是一个空的 对象。我们在 JavaScript 遇到的每个对象,实际上都是从 Object.prototype 对象克隆而来的, Object.prototype 对象就是它们的原型。比如下面的 obj1 对象和 obj2 对象:
var obj1 = new Object();
var obj2 = {};
可以利用 ECMAScript 5 提供的 Object.getPrototypeOf 来查看这两个对象的原型:
console.log( Object.getPrototypeOf( obj2 ) === Object.prototype ); // 输出:true
console.log( Object.getPrototypeOf( obj1 ) === Object.prototype ); // 输出:true
2. 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
var obj1 = new Object()或者 var obj2 = {}。此时,引擎内部会从 Object.prototype 上面克隆一个对象出来,我们最终得到的就是这个对象。 再来看看如何用 new 运算符从构造器中得到一个对象
function Person(name) {
this.name = name;
};
Person.prototype.getName = function () {
return this.name;
};
var a = new Person('sven')
console.log(a.name); // 输出:sven
console.log(a.getName()); // 输出:sven
console.log(Object.getPrototypeOf(a) === Person.prototype);// 输出:true
当使用 new 运算符来调用函数时,此时的函数就是一个构造器。 用new 运算符来创建对象的过程,实际上也只是先克隆 Object.prototype 对象(JavaScript 是通过克隆 Object.prototype 来得到新的对象,但实际上并不是每次都真正地克隆了一个新的对象。从 内存方面的考虑出发,JavaScript 还做了一些额外的处理 ),再进行一些其他额 外操作的过程。
3. 对象会记住它的原型
如果请求可以在一个链条中依次往后传递,那么每个节点都必须知道它的下一个节点。同理,JavaScript 语言中的原型链查找机制,每个对象至少应该先记住它自己的原型。目前我们一直在讨论“对象的原型”,就 JavaScript 的真正实现来说,其实并不能说对象有 原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好 的说法是对象把请求委托给它的构造器的原型。那么对象如何把请求顺利地转交给它的构造器 的原型呢? 前我们一直在讨论“对象的原型”,就 JavaScript 的真正实现来说,其实并不能说对象有 原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好 的说法是对象把请求委托给它的构造器的原型。那么对象如何把请求顺利地转交给它的构造器 的原型呢?
JavaScript 给对象提供了一个名为__proto__的隐藏属性,某个对象的__proto__属性默认会指 向它的构造器的原型对象,即{Constructor}.prototype。 实际上,__proto__就是对象跟“对象构造器的原型”联系起来的纽带。正因为对象要通过 __proto__属性来记住它的构造器的原型 。
var a = new Object();
console.log(a.__proto__ === Object.prototype); // 输出:true
4. 如果对象无法响应某个请求,它会把这个请求委托给它的构造器的原型
这条规则即是原型继承的精髓所在。在 JavaScript 中,每个对象都是从 Object.prototype 对象克隆而来的,如果是这样的话, 我们只能得到单一的继承关系,即每个对象都继承自 Object.prototype 对象,这样的对象系统显 4 然是非常受限的。 但对象构造 器的原型并不仅限于 Object.prototype 上,而是可以动态指向其他对象。 这样一来,当对象 a 需 要借用对象 b 的能力时,可以有选择性地把对象 a 的构造器的原型指向对象 b,从而达到继承的 效果。下面的代码是我们最常用的原型继承方式:
var obj = {name: 'sven'};
var A = function () {};
A.prototype = obj;// 输出:sven
我们来看看执行这段代码的时候,引擎做了哪些事情。
首先,尝试遍历对象 a 中的所有属性,但没有找到 name 这个属性。
查找 name 属性的这个请求被委托给对象 a 的构造器的原型,它被 a.__proto__ 记录着并且指向 A.prototype,而 A.prototype 被设置为对象 obj。
在对象 obj 中找到了 name 属性,并返回它的值。
当我们期望得到一个“类”继承自另外一个“类”的效果时,往往会用下面的代码来模拟实现:
var A = function () {};
A.prototype = {name: 'sven'};
var B = function () {};
B.prototype = new A();
var b = new B();
console.log(b.name); // 输出:sven
在来看看执行这段代码的时候,引擎做了哪些事情。
首先,尝试遍历对象 b 中的所有属性,但没有找到 name 这个属性。
查找 name 属性的请求被委托给对象 b 的构造器的原型,它被 b.__proto__ 记录着并且指向 B.prototype,而 B.prototype 被设置为一个通过 new A()创建出来的对象。
在该对象中依然没有找到 name 属性,于是请求被继续委托给这个对象构造器的原型 A.prototype。
在 A.prototype 中找到了 name 属性,并返回它的值。