原 荐 JavaScript创建对象(三)——原型模式

栏目: JavaScript · 发布时间: 6年前

内容简介:在《JavaScript创建对象(二)——构造函数模式》中提到,构造函数模式存在相同功能的函数定义多次的问题。本篇文章就来讨论一下该问题的解决方案——原型模式。首先我们来看下什么是原型。我们在创建一个函数时,这个函数会包含一个属性

在《JavaScript创建对象(二)——构造函数模式》中提到,构造函数模式存在相同功能的函数定义多次的问题。本篇文章就来讨论一下该问题的解决方案——原型模式。

首先我们来看下什么是原型。我们在创建一个函数时,这个函数会包含一个属性 prototype ,这个属性是一个指针,它指向一个对象——该函数的原型对象,这就是原型,它包含了该函数类型的所有实例可共享的属性和方法,见下面示意图:

原 荐 JavaScript创建对象(三)——原型模式

如图所示,声明了一个函数 Person 。在JavaScript中,一个函数被声明的同时就具有了一些属性,其中有一个叫做 prototype ,它指向了该函数的原型对象,即上述示例中的 Person Prototype 。同时,这个原型对象有一个叫做 constructor 的属性反过来又指向了该函数对象。

当我们创建一个函数的实例时,例如上面的 var personObj = new Person('张三', 12); ,这个实例也会有一个属性指向该函数的原型对象,在Chrome的开发 工具 中显示为 __proto__

上面我们说原型的属性可以被该函数类型的所有实例所共享,那具体是怎么实现呢?看下面的示例:

function Person(){
}
//给原型添加自定义属性和方法
Person.prototype.name = '张三'; 
Person.prototype.sayName = function(){
    console.log(this.name);
}
var p1 = new Person();
//给p1添加age属性
p1.age = 18;
console.log(p1.name);//张三
console.log(p1.age);//18
p1.sayName();//张三
var p2 = new Person();
console.log(p2.name);//张三
console.log(p2.age);//undefined
p2.sayName();//张三

在上面的代码中,我们并没有给实例添加 name 属性和 sayName 方法,但是依然可以通过实例调用,貌似实例天生就具有了原型的属性和方法,其实不是的,下面是在Chrome的开发工具中看到的内容:

原 荐 JavaScript创建对象(三)——原型模式

我们看到, p1Person 类型的,我们给 p1 设置了 age 属性,这里也能看到 age18 。另外我们看到 p1 有个 __proto__ 属性,这个就是我们在原型示意图中说的指向原型对象的属性。

代码读取对象实例某个属性的时候会执行一次搜索,首先搜索对象实例,如果搜索到了就返回,如果没有则会继续搜索 __proto__ 指向的原型对象,搜索到了就返回。所以上面例子中p1.age是搜索到了p1的age属性返回了, p1.name 是搜索到 Person Prototypename 返回了, p2.agePerson Prototype 中也没搜到,于是返回了 undefined 。这就是实例对象共享原型属性的原理。

除了上面的写法,原型还有一种更简单的定义方式,就是用一个包含所有属性和方法的对象字面量来重写整个原型对象,这样避免了每当给原型添加一个属性就要书写一遍 Person.prototype 的繁琐,同时从视觉上看也更好地封装了原型的功能,如下代码所示:

function Person{
}
Person.prototype = {
    constructor: Person,
    name: '张三',
    age: 18,
    job: 'JavaScript',
    sayName: function(){
        console.log(this.name);
    }
}

现在回到文章一开始提出的相同功能的函数定义多次的问题,因为函数原型的属性和方法可以由所有实例所共享,所以只要在原型中定义一次,所有实例就都可以使用,这样就完美解决了构造函数模式的问题。

总结一下,与构造函数模式相比:

  1. 原型模式不必在构造函数中定义属性和方法,而是直接定义在原型中。
  2. 这些属性和方法被所有实例共享。

原型模式虽然好用,但也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。其次,最大的问题是由原型的共享本性带来的,下面来分析一下原型的共享问题。

通过共享,我们解决了构造函数模式相同功能的函数定义多次的问题,所以共享对于函数是有好处的。对于基本类型的属性,如上面的 nameage ,因为属性的搜索机制是从实例到原型,所以可以通过给实例添加一个同名的属性,屏蔽掉原型中相应的属性,问题也不大。然而,对于引用类型的数据来说,问题就比较严重了。来看下面的示例:

function Person(){
}
Person.prototype = {
    constructor: Person,
    name: '张三',
    age: 18,
    job: 'JavaScript',
    friends: ['小明', '小刚'],
    sayName: function(){
        console.log(this.name);
    }
}
var p1 = new Person();
var p2 = new Person();
p2.name = '李四';
p1.friends.push('小红');//张三交了个女朋友小红
console.log(p1.friends);//["小明", "小刚", "小红"]
console.log(p2.friends);//["小明", "小刚", "小红"],我擦,小红怎么也成了李四的女朋友
console.log(p1.friends == p2.friends);//true

如上所示, Person.prototype 包含了一个引用类型,数组 friends ,其中 friends 只是一个指针, ['小明', '小刚'] 才是真正的对象。通过 p1.friends 修改了这个数组,因为共享的问题, p2.friends 访问的也是同一个数组。假如我们的初衷就是共享一个数组,那么也没问题。但是多数情况下应该是不想共享的场景。比如这里,张三新交了一个女朋友小红,结果小红同时也是李四的女朋友,是张三有这癖好?是小红劈腿?还是人家张三只是单纯地交了个女朋友,被你搞得复杂了?这个说不清,既然说不清,那么程序就有问题。所以很少有人单独使用原型模式,那么这个问题怎么解决呢?办法还是有的,那就是组合使用构造函数模式和原型模式,这个实现也很简单,但为了区分原型模式,后面将会单独列一篇文章。

本文参考《JavaScript高级程序设计(第3版)》,关于原型模式的其他特点读者可以查阅第6.2.3章节,里面有详细的说明。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

图形程序开发人员指南

图形程序开发人员指南

Michael Abrash / 前导工作室 / 机械工业出版社 / 1998 / 128

Michael Abrash's classic Graphics Programming Black Book is a compilation of Michael's previous writings on assembly language and graphics programming (including from his "Graphics Programming" column......一起来看看 《图形程序开发人员指南》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具