JS面向对象篇三、创建对象的几种方法
先来说下最简单的两种创建对象的方式:通过Object构造函数创建以及对象字面量方式。
Object函数创建对象
var person = new Object()
person.name = 'youyang'
person.age = 18
person.sayName = function () {
console.log(this.name)
}
对象字面量
var person = {
name : 'youyang',
age : 18,
sayName : function () {
console.log(this.name)
}
}
以上两种方式在创建单个对象时没有问题,但如果创建多个类似对象的话,就会产生大量重复代码,为了解决这一问题,出现了工厂模式创建对象的方法。
工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,用函数来封装创建一个具体对象的过程,然后就可以多次调用这个函数以创建相似的对象。
function createPerson (name, age) {
var obj = new Object()
obj.name = name
obj.age = age
obj.sayName = function () {
console.log(this.name)
}
return obj
}
var p1 = createPerson('youyang', 18)
var p2 = createPerson('xiaoqu', 28)
这种方式虽然解决了创建多个相似对象的问题,但是这种方式创建的对象无法判断对象的类型。
构造函数模式
ECMAScript中的构造函数可用来创建特定类型的对象。因此自定义类型对象可以像下面这样创建:
function Person (name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
var p1 = new Person('youyang', 18)
var p2 = new Person('youyang', 20)
为了区分构造函数和普通函数,构造函数的首字母要大写,不过构造函数本身也是一个函数,只不过是用来创建对象而已。同时使用构造函数创建实例对象,必须使用new操作符,以这种方式调用构造函数实际上会经历下面4个步骤:
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
- 执行构造函数的代码(为这个新对象添加属性);
- 将这个新对象返回(其实构造函数的最后有一条隐式代码return this);
运用构造函数创建的对象,就可以用instance来检测对象类型了:
console.log(p1 instanceof Person) // true
console.log(p2 instanceof Person) // true
另外再次说明,构造函数也是函数,它和其他函数的不同仅仅在于可以用new来调用创建一个实例对象,当构造函数不用new来调用时那它就和普通函数就没有区别。
构造函数解决了对象的类型检测问题,但是它依然还是存在缺陷的,观察构造函数创建对象的代码,每创建一个对象,就会执行一次Person构造函数,而在函数内部有一个sayName方法,那么每执行一次Person,就会创建一个sayName方法,也就是p1实例的sayName方法和p2实例的sayName方法不是一个方法。
console.log(p1.sayName == p2.sayName) // false
那么当创建多个对象时就会相应的创建多个函数,而同样功能的方法,创建多个是没有必要的。
原型模式
每个函数都有一个prototype属性,同样每个构造函数都有一个prototype,这个属性是一个指针,它指向一个对象,这个对象就是原型对象。而原型对象上的属性是可以被所有由该构造函数创建的实例共享的。
理解原型模式的工作原理必须先理解原型对象,建议先看下上篇文章:理解原型对象
下面是原型模式创建对象的例子:
function Person () {}
Person.prototype = {
constructor: Person,
name : 'youyang',
age : 18,
colors : ['red', 'yellow', 'blue']
sayName : function () {
console.log(this.name)
}
}
var p1 = new Person()
var p2 = new Person()
而原型模式的缺点一方面,它没办法像构造函数那样传参的方式动态获取不同的属性值,所有的对象实例共享了原型上的属性,它们默认取得的是相同的属性值,另一方面而=原型模式的共享本质导致了更加严重的问题,就是所有实例共享的引用类型属性会相互影响。
p1.colors.push('green')
console.log(p2.colors) // ['red', 'yellow', 'blue', 'green']
可见改变了p1实例的colors属性,然而p2实例受到了影响,这是我们不希望看到的。
组合使用构造函数模式和原型模式
创建自定义类型最常见的方式就是组合使用构造函数模式与原型模式,构造函数模式用于定义实例属性,而原型模式用于定义共享的方法和属性。
function Person (name, age) {
this.name = name
this.age = age
this.colors = ['red', 'yellow', 'blue']
}
Person.prototype = {
constructor: Person,
sayName : function () {
console.log(this.name)
}
}
var p1 = new Person('youyang', 18)
var p2 = new Person('xiaoqu', 20)
p1.colors.push('green')
console.log(p1.colors) // ['red', 'yellow', 'blue', 'green']
console.log(p2.colors) // ['red', 'yellow', 'blue']
console.log(p1,sayName === p2.sayName) // true
由此可见,原型模式与构造函数模式组合使用,既能动态的定义每个实例对象自己的属性,而且引用类型属性互不影响,同时还共享了原型上的方法。
这种构造函数与原型混合的模式是javascript中使用最广泛认同度最高的一种创建自定义类型的方法。
除了上面介绍的集中创建对象的方式,还有动态原型模式,寄生构造函数模式和稳妥构造函数模式,其中动态原型模式是在组合模式(构造函数模式和原型模式组合使用)的基础上再进一步修改得来,将原型的初始化操作也一同封装进构造函数中。寄生构造函数模式和稳妥构造函数模式也都有相应的应用场景,但是很少使用到,就不一一介绍了。