接上篇介绍的构造函数模式,这篇就来了解一下什么是原型链Portotype模式是怎么创建函数的,使用这个创建相比上面的几个有什么优势和不足。
我们上面介绍了构造函数模式在创建很多个实例的时候也是会占用很多的内存,每创建一个系统就会自动的为它分配一个空间,这就表示如果项目很大有很多的实例那样分配的空间会成百上千,会造成内存的浪费,更重要的是运行速度的缓慢,所以为了解决这个问题我们可以使用原型链Prototype模式来创建。
说明一下,JavaScript中规定,每个构造函数中都有一个prototype属性,指向的另一个对象。这个对象的所有属性和方法,都会被构造函数的实例所继承。所以可以把那些不变的属性和方法,直接定义到prototype对象。
上面的图,我们很明显的看出使用构造函数实例出来的都需要系统分配一个内存,而使用prototype创建的函数只要一个内存即可。大大的减少了内存的消耗和可用性。
具体看下面代码:
<script>
function peopleObj(){}
peopleObj.prototype.name = '小明';
peopleObj.prototype.age = '21';
peopleObj.prototype.run = function(){
return this.name + '的年龄是' + this.age + "岁了";
};
var xiaoming = new peopleObj(); // 创建小明的实例
var xiaohong = new peopleObj(); // 创建小红的实例
alert(xiaoming.run == xiaohong.run) // 弹出的是 true(前几次我们测试过没有使用原型链prototype时,两个函数是不相等的,原因是因为两个实例的在系统中分配的地址是不一样的,是独立的,看上面的图理解)
alert(xiaoming.constructor); // 看下段文字解释;弹出的是 function peopleObj(){}
alert(xiaoming.run());
</script>
这时所有的实例的方法,都是指向的同一个内存地址,指向的是prototype对象,因此提高了运行效率。这里关于说到的内存中的不同地址,还有函数到底是引用类型和基本类型,下篇再解释。
上例中的原型链prototype解决了之前所出现的多重实例化、浪费内存的问题,但是虽然prototype有这么多方便,但是也是还有一些不足的地方。
我们接着往下看。
<script>
function Monster(){}
Monster.prototype = {
constructor : Monster, //此处强制的将对象指回构造函数 Monster
name : '小明', //原型字面量方式会将对象的constructor变为Object
age : '21',
job : ['看电视','听音乐'],
run : function(){
return this.name + '的年龄是' + this.age
}
};
var monsterA = new Monster();
var monsterB = new Monster();
monsterA.job.push('打篮球');
alert(monsterA.constructor); // 弹出的是 function Object(){ [native code] }
alert(monsterA.job); // 弹出的是 看电视,听音乐,打篮球
alert(monsterB.job); // 弹出的是 看电视,听音乐,打篮球
alert(monsterB.run());
</script>
把上面的代码换了一个舒适的写法,也好方便我们以往的观看。在测试的时候我们发现,弹出来的方法和上面第一个代码的方法是一样的,虽然两个方法是一样实现的目的完成的也是一样的,但是它们两个还是有一小点区别的,为了验证区别,我在两个代码的下面分别弹出了alert(xiaoming.constructor),alert(monsterA.constructor);显然两个表示的是一样的,所以我在下段代码的第四行加了一句代码 “constructor : Monster” ,这时候 alert(monsterA.constructor);弹出来的就是 function Monster(){}。
我们可以把代码换了一个写法之后,里面也要相应的加上一句代码,当然不加也不会影响太大。
可能有的朋友已经看出来了,在我们使用原型链的时候发现它创建的是一个空的对象,没有属性和方法,而是在后面的prototype的代码中才加入的属性和方法。这样就出现一个问题,它不能通过给构造函数传递参数来初始化属性的值。当我创建一个两个实例的时候他们的姓名和年龄都是“小明”“21”,都是不会改变。第二,如果我们只能共享这个方法不能共享属性,如果我想换一个 name = “小红”,那就没办法实现,方法共享是没有问题,但是属性我们需要它有一定的独立性多样化。接上面代码的例子,如果重新添加一个属性到 job 这个数组里面,我们在下面分别弹出 alert(monsterA.job); alert(monsterB.job);,发现除了 monsterA 去打篮球了,monsterB 也去了打篮球,但问题是我并没有写 monsterB.job.push(‘打篮球’);,所以这里就出现了属性的不能变的问题。
所以原型链Prototype有两个明显的缺陷:
① 构造函数没有参数:使用原形方式,不能通过给构造函数传递参数来初始化属性的值
② 属性指向的是对象:而不是函数时,函数共享不会造成问题,但对象却很少被多个实例共享,如果共享是对象就会造成问题
综上述,大部分情况我们还是使用的是原型链和构造函数一起的使用,下篇介绍。