JavaScript对象创建模式

页面导航:首页 > 网络编程 > JavaScript > JavaScript对象创建模式

JavaScript对象创建模式

来源: 作者: 时间:2016-02-02 09:59 【

任何程序中都不可避免地要创建对象,JavaScript也不例外。但JavaScript并没有类 20284;其他OO语言的类,可以通过类来创建实例对象。JavaScript中所谓的类,其实是一种设计模式:一个构造函

任何程序中都不可避免地要创建对象,Script也不例外。但并没有类似其他OO语言的类,可以通过类来创建实例对象。

JavaScript中所谓的类,其实是一种设计模式:一个构造函数(consturctor)和一个用于在该类实例间共享属性和方法的原型对象(Objcet.prototype)的结合。

 

为了达到属性继承,代码复用等目的,通过函数来模拟类来创建对象。

(或许你会疑惑,通过Object就可以创建对象了呀?确实如此,new一个Object对象后,给Object对象增加属性和方法,确实可以生成一个对象。但这种做法实在太麻烦,且不易封装复用。下面介绍的方法一就是这种方法的高级版)

方法一:用一个普通的函数来封装(俗称工厂模式)

 

function createPerson(name, age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function(){
        alert(this.name);
    };    
    return o;
}

var p1 = createPerson("Jack", 32);
var p2 = createPerson("Zhang", 30);
p1.sayName();   //Jack
p2.sayName();   //Zhang
这个例子平淡无奇,createPerson函数内创建了一个Object,并赋予它两个属性(name,age)和一个方法(sayName)。这样部分达到了类的作用。为何说是部分呢?因为它有个明显的缺陷,即无法解决对象识别问题,创建出来的对象(p1,p2)都是Object类型的:

 

 

alert(p1 instanceof Object);  //true
alert(p2 instanceof Object);  //true

 

如果你有一堆对象,有Person有Dog,你无法区分这些对象中哪些是Person哪些是Dog。为了解决上述缺陷,引入了构造函数的概念


 

方法二:通过构造函数来封装:(俗称构造函数模式)

 

function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        alert(this.name);
    };    
}

var p1 = new Person("Jack", 32);    //用new操作符来调用
var p2 = new Person("Zhang", 30);
p1.sayName();   //Jack
p2.sayName();   //Zhang

alert(p1 instanceof Object);  //true,显然创建的对象都既是Object,也是Person
alert(p1 instanceof Person);  //true
alert(p2 instanceof Object);  //true
alert(p2 instanceof Person);  //true

alert(p1.constructor == Person);  //true
alert(p1.constructor == Dog);     //false,这样就能区分对象究竟是Person还是Dog了

 

Person函数与createPerson函数相比,有以下不同:

1.没有var o = new Object();创建对象,自然最后也没有return o;返回对象

2.没有将属性和方法赋给Object对象,而是赋给this对象

因为Person函数内部使用了this对象,因此你必须用new操作符来创建对象:

 

var p = new Person("Jack", 32);
p.sayName();		//Jack

 

如果你忘记用new操作符来创建对象的话:(解决方式见※1)

var p = Person("Jack", 32);
p;				//undefined
this.name;			//Jack
this.age;			//32
灾难的事情发生了,因为没有用new来调用,因此Person函数内的this将指向window对象(即BOM),因此等于给window对象添加了两个全局变量。

像Person这样的函数被称为【构造函数】。你可能疑惑,既然JavaScript并没有类的概念,那怎么会有构造函数呢?其实构造函数与普通函数唯一的区别,就在于它们的调用方式不同。只要通过new操作符来调用,那它就可以作为构造函数。但构造函数毕竟也是函数,并不存在任何特殊的语法。

但通过构造函数的方式来模拟类的话,也有个缺陷:(一种不好的解决方式见※2)

 

alert(p1.sayName == p2.sayName);  //false
sayName只是个普通的方法,如果你创建多少个Person对象,显然你不希望多个对象中都有一个sayName方法的副本。这将造成无谓的浪费。

 

 

方法三:通过原型来封装:(俗称原型模式,但其实这里的例子是复合模式,即构造函数模式+原型模式)

 

每个函数都有个prototype属性,该属性其实是一个指针,指向一个对象,称为原型对象。原型对象中包含着可供所有实例共享的属性和方法。

可以将希望所有对象共享的属性或方法(如上述sayName方法)添加到原型对象中,达到继承复用的目的。

function Person(name, age) {
	this.name = name;  //name和age没有放到原型对象中,而是仍旧留在构造函数内部
	this.age = age;    //表示不希望每个实例都共享这两个数
}
Person.prototype.sayName = function(){    //将希望被所有对象共享的sayName方法放入原型对象中
	alert(this.name);
}

var p1 = new Person("Jack", 32);    //用new操作符来调用
var p2 = new Person("Zhang", 30);
p1.sayName();   //Jack
p2.sayName();   //Zhang
alert(p1.sayName == p2.sayName); //true,sayName方法确实被共享了,而不是每个对象中都有一个独立的副本


 

示意图:

\

Person构造函数(再次声明,无论什么函数内部都有原型指针,指向一个原型对象)内部有个原型指针,指向Person的原型对象。图中也可看出Person的原型对象内部有个constructor属性,指向对应的函数。这样函数和原型对象就双向绑定起来了。

 

※1:

已经展示了忘记用new操作符来调用构造函数的可怕后果。靠程序员自觉显然不是个好主意。有3种方式可以避免这种错误:

1.在函数内部第一行加上use strict;启用严格模式,这样var p = Person("Jack", 32);创建将失败。缺点是你必须保证环境支持ES5,否则无法保证严格模式能起作用。

2.改进构造函数:

 

function Person(name, age) { 
	if(!(this instanceof Person)){
		return new Person(name, age);
	}
	this.name = name;
	this.age= age; 
}
上述代码是自解释代码(self-explanatory),一行注释都不用就能看明白。缺点是需要额外的开销(即额外的函数调用),也无法适用于可变参数函数。

 

3.利用ES5的Object.create函数改进:

 

function Person(name, age) {
        var self = this instanceof Person? this : Object.create(Person.prototype);
        self.name = name;
        self.age= age;
        return self;
}

 

Object.create需要一个原型对象作为参数,并返回一个继承自该原型对象的新对象。同样你必须保证环境支持ES5


※2:

已经展示了通过构造函数来模拟类的缺陷,即无法实现共享。你可能会疑惑,通过共通函数不就能实现共享了吗?

 

function Person(name, age) {
        this.name = name;
        this.age= age;
        this.sayName = sayName;
}

function sayName() {
        alert(this.name);
}

var p1 = new Person("Jack", 32);
var p2 = new Person("Zhang", 30);

alert(p1.sayName == p2.sayName);  //true  
在构造函数中定义个函数指针,指向全局函数sayName,这样确实能达到共享的目的。但你真希望看到全局函数吗?如果Person有5,6个方法,那你就需要定义5,6个全局函数,同样是个灾难
Tags:

文章评论

最 近 更 新
热 点 排 行
Js与CSS工具
代码转换工具

<