在以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象 总是从类中创建而来。而在原型编程的思想中,类并不是必需的,对象未必需要从类中创建而来, 一个对象是通过克隆另外一个对象所得到的。
1. 使用克隆的原型模式
从设计模式的角度讲,原型模式是用于创建对象的一种模式,如果我们想要创建一个对象, 一种方法是先指定它的类型,然后通过类来创建这个对象。原型模式选择了另外一种方式,我们 不再关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象
既然原型模式是通过克隆来创建对象的,那么很自然地会想到,如果需要一个跟某个对象一 模一样的对象,就可以使用原型模式
原型模式的实现关键,是语言本身是否提供了 clone 方法。ECMAScript 5提供了 Object.create 方法
var Plane = function () { this.blood = 100; this.attackLevel = 1; this.defenceLevel = 1;}var myPlane = new Plane();myPlane.blood = 500;myPlane.attackLevel = 10;myPlane.defenceLevel = 7;// 在不支持 Object.create 方法的浏览器中,则可以使用以下代码Object.create = Object.create || function (obj) { var F = function () { }; F.prototype = obj; return new F();}var clonePlane = Object.create(myPlane);console.log(clonePlane);
2. 原型编程范型的一些规则
- 所有的数据都是对象
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
- 对象会记住它的原型。
- 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。
3. JavaScript中的原型继承(构建对象系统)
同理,js也同样遵循以上的编程规则
1. 所有的数据都是对象
JavaScript在设计的时候,模仿 Java 引入了两套类型机制:基本类型和对象类型。基本类型包括 undefined 、 number 、 boolean 、 string 、 function 、 object
除了 undefined 之外,一切都应是对象。为了实现这一目标,number 、 boolean 、 string 这几种基本类型数据也可以通过“包装类”的方式变成对象类型数据来处理
不能说在 JavaScript中所有的数据都是对象,但可以说绝大部分数据都是对象。那么相信在 JavaScript中也一定会有一个根对象存在,这些对象追根溯源都来源于这个根对象。
JavaScript 中的根对象是 Object.prototype 对象。 Object.prototype 对象是一个空的对象。我们在JavaScript 遇到的每个对象,实际上都是从 Object.prototype 对象克隆而来的,Object.prototype 对象就是它们的原型。
可以利用 ECMAScript 5提供的 Object.getPrototypeOf 来查看这两个对象的原型:
var obj1 = new Object();var obj2 = {};console.log( Object.getPrototypeOf( obj1 ) === Object.prototype ); // 输出:trueconsole.log( Object.getPrototypeOf( obj2 ) === Object.prototype ); // 输出:true2. 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
在 JavaScript语言里,我们并不需要关心克隆的细节,因为这是引擎内部负责实现的。我
们所需要做的只是显式地调用 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 ); // 输出:svenconsole.log( a.getName() ); // 输出:svenconsole.log( Object.getPrototypeOf( a ) === Person.prototype ); // 输出:true这里 Person 并不是类,而是函数构造器,JavaScript 的函数既可以作为普通函数被调用,也可以作为构造器被调用,当使用 new 运算符来调用函数时,此时的函数就是一个构造器。 用new 运算符来创建对象的过程,实际上也只是先克隆 Object.prototype 对象,再进行一些其他额外操作的过程。
可以模拟new的过程:
// 模拟new的实现方式// 先来一个Person构造器
function Person(name) { this.name = name;}
Person.prototype.getName = function () { return this.name;}
var objectFactory = function () { var obj = new Object(), //从Object.prototype上克隆一个空的对象{} Constructor = [].shift.call(arguments); // 取得外部传入的构造器,此例是 Person obj.__proto__ = Constructor.prototype; // 指向正确的原型 var ret = Constructor.apply(obj, arguments); // 借用外部传入的构造器给 obj 设置属性 return typeof ret === 'object' ? ret : obj; // 确保构造器总是会返回一个对象}
var a = objectFactory(Person, 'weng');console.log(a.getName());3. 对象会记住它的原型
对象的原型”,就 JavaScript 的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型
JavaScript 给对象提供了一个名为 __proto__ 的隐藏属性,某个对象的 __proto__ 属性默认会指向它的构造器的原型对象,即 {Constructor}.prototype 。在一些浏览器中, __proto__ 被公开出来
var a = new Object();console.log ( a.__proto__=== Object.prototype ); // 输出:true__proto__ 就是对象跟“对象构造器的原型”联系起来的纽带。正因为对象要通过_proto__ 属性来记住它的构造器的原型,所以我们的 objectFactory 函数来模拟用 new创建对象时, 需要手动给 obj 对象设置正确的 __proto__ 指向
obj.__proto__ = Constructor.prototype;
让 obj.proto 指向 Person.prototype ,而不是原来的 Object.prototype
4. 如果对象无法响应某个请求,它会把这个请求委托给它的构造器的原型
在 JavaScript 中,每个对象都是从 Object.prototype 对象克隆而来的,如果是这样的话, 我们只能得到单一的继承关系,即每个对象都继承自 Object.prototype 对象,这样的对象系统显 然是非常受限的。
实际上,虽然 JavaScript 的对象最初都是由 Object.prototype 对象克隆而来的,但对象构造 器的原型并不仅限于 Object.prototype 上,而是可以动态指向其他对象。这样一来,当对象 a 需 要借用对象 b 的能力时,可以有选择性地把对象 a 的构造器的原型指向对象 b ,从而达到继承的 效果
常用原型继承代码解析
var obj = { name: 'sven' };var A = function(){};A.prototype = obj;var a = new A();console.log( a.name ); // 输出:sven- 首先,尝试遍历对象 a 中的所有属性,但没有找到 name 这个属性
- 查找 name 属性的这个请求被委托给对象 a 的构造器的原型,它被
a. __proto__记录着并且 指向.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 属性,并返回它的值