原 荐 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章节,里面有详细的说明。


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

查看所有标签

猜你喜欢:

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

复杂网络理论及其应用

复杂网络理论及其应用

汪小帆、李翔、陈关荣 / 清华大学出版社 / 2006 / 45.00元

国内首部复杂网络专著 【图书目录】 第1章 引论 1.1 引言 1.2 复杂网络研究简史 1.3 基本概念 1.4 本书内容简介 参考文献 第2章 网络拓扑基本模型及其性质 2.1 引言 2.2 规则网络 2.3 随机图 2.4 小世界网络模型 2.5 无标度网络模型 ......一起来看看 《复杂网络理论及其应用》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具