1.4-原型

参考书籍: Javascript设计模式与开发实践(曾探)

原型模式不单是一种设计模式,也被称为一种编程泛型。

原型模式 找到一个对象,然后通过 克隆 来创建一个一模一样的对象。使用原型模式,我们只需要调用负责克隆的方法,便能完成同样的功能。

ECMAScript 5 提供了Object.create方法,可以用来克隆对象。

但原型模式的真正目的并非在于需要得到一个一模一样的对象,而是 提供了一种便捷的方式去创建某个类型的对象,克隆只是创建这个对象的过程和手段

Object 是Animal 的原型,而Animal 是Dog 的原型,它们之间形成了一条原型链。这个原型链是很有用处的,当我们尝试调用Dog 对象的某个方法时,而它本身却没有这个方法,那么Dog 对象会把这个请求委托给它的原型Animal 对象,如果Animal 对象也没有这个属性,那么请求会顺着原型链继续被委托给Animal 对象的原型Object 对象,这样一来便能得到继承的效果,看起来就像Animal 是Dog 的“父类”,Object 是Animal 的“父类”。

原型编程范型至少包括以下基本规则:

  1. 所有的数据都是对象。
  2. 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
  3. 对象会记住它的原型。
  4. 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。

1、为什么所有数据都是对象?

Javascript的数据分为基本类型和对象类型,基本类型包括:undefined、number、boolean、string、function、object。按照JavaScript设计者的本意,除了undefined 之外,一切都应是对象。为了实现这一目标,number、boolean、string 这几种基本类型数据也可以通过“包装类”的方式变成对象类型数据来处理。

事实上,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( obj1 ) === Object.prototype ); // 输出:true
console.log( Object.getPrototypeOf( obj2 ) === Object.prototype ); // 输出:true

2. 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它,怎样克隆?

在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 ); // 输出:sven
console.log( a.getName() ); // 输出:sven
console.log( Object.getPrototypeOf( a ) === Person.prototype ); // 输出:true

在JavaScript 中 没有类的概念,这句话我们已经重复过很多次了。但刚才不是明明调用了newPerson()吗?

在这里Person 并不是类,而是 函数构造器,JavaScript 的函数既可以作为普通函数被调用,也可以作为构造器被调用。

当使用new 运算符来调用函数时,此时的函数就是一个构造器。 用new 运算符来创建对象的过程,实际上也只是先克隆Object.prototype 对象,再进行一些其他额外操作的过程。

3. 对象会记住它的原型

目前我们一直在讨论“对象的原型”,就JavaScript 的真正实现来说,其实 并不能说对象有原型,而只能说对象的构造器有原型。 对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型。

JavaScript 给对象提供了一个名为 __proto__的隐藏属性,某个对象的__proto__属性默认会指向它的构造器的原型对象,即{Constructor}.prototype。在一些浏览器中,__proto__被公开出来,我们可以在Chrome 或者Firefox 上用这段代码来验证:

var a = new Object();
console.log ( a.__proto__=== Object.prototype ); // 输出:true

实际上,__proto__就是对象跟“对象构造器的原型”联系起来的纽带。

4. 如果对象无法响应某个请求,它会把这个请求委托给它的构造器的原型

这条规则即是原型继承的精髓所在。

实际上,虽然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

我们来看看执行这段代码的时候,引擎做了哪些事情。

  1. 首先,尝试遍历对象a 中的所有属性,但没有找到name 这个属性。
  2. 查找name 属性的这个请求被委托给对象a 的构造器的原型,它被a.__proto__ 记录着并且指A.prototype,而A.prototype 被设置为对象obj。
  3. 在对象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

再看这段代码执行的时候,引擎做了什么事情。

  1. 首先,尝试遍历对象b 中的所有属性,但没有找到name 这个属性。
  2. 查找name 属性的请求被委托给对象b 的构造器的原型,它被b.__proto__ 记录着并且指向B.prototype,而B.prototype 被设置为一个通过new A()创建出来的对象。
  3. 在该对象中依然没有找到name 属性,于是请求被继续委托给这个对象构造器的原型A.prototype
  4. A.prototype 中找到了name 属性,并返回它的值。

和把B.prototype 直接指向一个字面量对象相比,通过B.prototype = new A()形成的原型链比之前多了一层。但二者之间没有本质上的区别,都是 将对象构造器的原型指向另外一个对象,继承总是发生在对象和对象之间。

最后还要留意一点,原型链并不是无限长的。现在我们尝试访问对象a 的address 属性。而对象b 和它构造器的原型上都没有address 属性,那么这个请求会被最终传递到哪里呢?

实际上,当请求达到A.prototype,并且在A.prototype 中也没有找到address 属性的时候,请求会被传递给A.prototype 的构造器原型Object.prototype,显然Object.prototype 中也没有address 属性,但Object.prototype 的原型是null,说明这时候原型链的后面已经没有别的节点了。所以该次请求就到此打住,a.address 返回undefined。

a.address // 输出:undefined
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值