最近在看《javascript高级程序设计》(第二版)
javascript中对象的创建
•工厂模式
•构造函数模式
•原型模式
•结合构造函数和原型模式
•原型动态模式
面向对象的语言大都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象。虽然从技术上讲,javascript是一门面向对象的语言,但是javascript没有类的概念,一切都是对象。任意一个对象都是某种引用类型的实例,都是通过已有的引用类型创建;引用类型可以是原生的,也可以是自定义的。原生的引用类型有:Object、Array、Data、RegExp、Function。 !引用类型就是一种数据结构,将数据和功能组织在一起,通常被称为类。 缺乏类概念的javascript中,需要解决的问题就是如何高效的创建对象。
1.1.0.创建对象的一般方法
var person = {}; //对象字面量表示,等同于var person = new Objcect(); person.name = 'evansdiy'; person.age = '22'; person.friends = ['ajiao','tiantian','pangzi']; person.logName = function() { console.log(this.name); }
基于Object引用类型,创建了一个对象,该对象包含四个属性,其中一个为方法。如果需要很多类似person的实例,那就会有许多重复的代码。
1.1.1.工厂模式 [top]
通过一个可以包含了对象细节的函数来创建对象,然后返回这个对象。
function person(name,age,friends) { var o = { name: name, age: age, friends: friends, logName: function() { console.log(this.name); } }; return o; } var person1 = person('Evansdiy','22',['ajiao','tiantian','pangzi']);
每次调用person函数,都会通过该函数内部的对象o创建新的对象,然后返回,除此之外,这个为了创建新对象而存在的内部对象o没有其他的用途。另外,无法判断工厂模式创建的对象的类型。
1.1.2.构造函数模式 [top]
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.logName = function() { console.log(this.name); } } //通过new操作符创建Person的实例 var person1 = new Person('boy-a','22','worker'); var person2 = new Person('girl-b','23','teacher'); person1.logName(); //boy-a person2.logName(); //girl-a
对比工厂模式,可以发现,这里并不需要创建中间对象,没有return。另外,可以将构造函数的实例标识为一种特定的类型,这就解决了对象识别的问题(通过检查实例的constructor属性,或利用instanceof操作符检查该实例是否通过某个构造函数创建)。
console.log(person1.constructor == Person);//constructor位于构造函数原型中,并指向构造函数,结果为true
console.log(person1 instanceof Person);//通过instanceof操作符,判断person1是否为构造函数Person的实例但构造函数模式也有自己的问题,实际上,logName方法在每个实例上都会被重新创建一次,需要注意的是,通过实例化创建的方法且并不相等,以下代码将会得到false:
console.log(person1.logName == person2.logName);//false我们可以将方法移到构造器外部(变为全局函数)来解决这个问题:
function logName() { console.log(this.name); } function logAge() { console.log(this.age); }
但是,在全局下创建的全局函数实际上只能被经由Person创建的实例调用,这就有点名不副实了;如果方法很多,还需要逐一定义,缺少封装性。
1.1.3.原型模式 [top]
javascript中的每一个函数都包含一个指向prototype属性的指针(大部分浏览器可以通过内部属性__proto__访问),prototype属性是一个对象,其中包含了由某种引用类型创建的所有实例共享的属性和方法。
function Person() {} Person.name = 'evansdiy'; Person.prototype.friends = ['ajiao','jianjian','pangzi']; Person.prototype.logName = function() { console.log(this.name); } var person1 = new Person(); person1.logName();//'evansdiy'
以上代码做了这几件事情:
1.定义了一个构造函数Person,Person函数自动获得一个prototype属性,该属性默认只包含一个指向Person的constructor属性;
2.通过Person.prototype添加三个属性,其中一个作为方法;
3.创建一个Person的实例,随后在实例上调用了logName()方法。
这里需要注意的是logName()方法的调用过程:
1.在person1实例上查找logName()方法,发现没有这个方法,于是追溯到person1的原型
2.在person1的原型上查找logame()方法,有这个方法,于是调用该方法 基于这样一个查找过程,我们可以通过在实例上定义原型中的同名属性,来阻止该实例访问原型上的同名属性,需要注意的是,这样做并不会删除原型上的同名属性,仅仅是阻止实例访问。
var person2 = new Person();
person2.name = 'laocai';如果我们不再需要实例上的属性时,可以通过delete操作符删除。
delete person2.name;利用for-in循环枚举出实例可以访问到的所有属性(不论该属性存在于实例或是原型中):
for(i in person1) { console.log(i); }
同时,也可以利用hasOwnProperty()方法判断某个属性到底存在于实例上,还是存在于原型中,只有当属性存在于实例中,才会返回true: