原型模式
我们创建的每一个函数,都有一个
prototype
属性,指向一个对象,而这个对象的用途就是包涵所有可以被实例继承的方法和属性,也就是说,不必在构造函数中定义对象实例的信息,我们可以把那些不变的属性和方法,直接添加到原型对象中,如下面这个例子:function Person(name, age, job) { this.name = name; this.age = age; this.job = job; } Person.prototype.sayName = function() { alert(this.name); } var person1 = new Person("nicolas", 29, "software engineer"); var person2 = new Person("greg", 27, "doctor"); person1.sayName() ; // "nicolas" person2.sayName(); // "greg" alert(person1.sayName == person2.sayName); // true
这里我们可以看到两个实例访问的都是同一个sayName函数,他们共享了原型对象上的这一方法。要完全理解原型模式的工作原理,我们就需要先理解ECMAScript中原型对象的性质。下面总结一些关键点:
任何时候创建一个函数,这个函数都会自动获得一个
prototype
属性,这个属性指向了了函数的原型对象,同时所有的原型对象都会包含一个constructor
属性,指向了这个prototype
属性所在的函数。根据前面的例子来说,也就是Person.prototype.constructor
指向了Person
。这个原型对象默认是只获得
constructor
属性的,而其他方法,则是从Object继承而来。在浏览器脚本中,并没有标准的方式来访问prototype
,但是Firefix、Safari、Chrome在每个对象上都支持一个属性_proto_
来访问。在我们调用
sayName
方法时,虽然person1
实例上并没有这个方法,但是会从实例开始按照原型链往上搜索,因此我们如果在实例中添加一个属性,这个属性与原型中的一个属性同名,那么这个属性就会屏蔽原型中那个属性,因为代码执行时,发现实例上已经有了这个属性,就会停止往上搜索。因此只要创建了这个属性,哪怕再次把该属性设置为null
,也依然无法恢复,不过可以使用delete
操作符来删除该属性,从而重新访问原型中的同名属性。关于实例中的属性还是原型中的属性的检测。
in
操作符是只要通过对象能访问到就返回true
,而hasOwnProperty()
只有属性存在于实例上时才返回true
,在for-in
循环中,返回的是所有能够通过对象访问,而且可枚举(enumerable)的属性。要获得所有可枚举的实例属性,可以使用ECMAScript5中的Object.keys()
方法。var keys = Object.keys(Person); alert(keys); //"name,age,job"
注意到我们每次为原型对象添加一个属性或方法就要敲一遍
Person.prototype
,因此为了减少不必要的输入,更常见的是直接用对象字面量来重写整个原型对象:function Person() {}; Person.prototype = { name:"nicolas", age: 29, job: "software engineer", sayName: function(){ alert(this.name); } }
但是有一点要注意,
constructor
属性不再指向Person了,因为我们重新创建了一个新的原型对象,因此需要手动指定:function Person() {}; Person.prototype = { constructor: Person, name:"nicolas", age: 29, job: "software engineer", sayName: function(){ alert(this.name); } }
当然这样直接设置会导致该属性是可以枚举的,因为默认参数enumerable会设置为true,你也可以使用Object.defineProperty()来设置。
创建对象的几种模式(二)