首先一看到创建对象,马上想到的就是对象字面量创建对象 var obj = {}; 或者使用Object构造函数创建对象,var obj = new Object(); 没错。但是为什么还要讲创建对象呢?因为这个两种方式虽然能够创建对象,但是,当我们创建对象很多地方相同怎么办,这会产生很多重复代码,一想到重复代码,脑子就应该浮现的解决办法:函数!
这里一共有7中创建对象的模式:
- 工程模式
- 构造函数模式
- 原型模式
- 组合使用构造函数模式和原型模式
- 动态原型模式
- 寄生构造函数模式
- 稳妥构造函数模式
这么多,到底怎么个表现形式,下面来一一举例说明:
工厂模式:
其实工厂模式,就是利用函数来进行代码的封装
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log(this.name);
};
return o;
}
var person1 = createPerson('Jack', 27, 'doctor');
很明显,这样也就创建了一个对象,而且我们可以多次创建,使用不同的参数。这样也就解决了我们想多次相似对象的问题。但是问题来了,这样的方式,创建的对象多了,我们怎么知道哪个对象是由哪个构造函数创建的呢?也就是我们说的怎么知道一个对象的类型。构造函数模式来了~
构造函数模式:
这个我们都好理解,也见过,构造函数,原生的构造函数就有Object,对吧就像祖宗,还有Array等。那创建自定义构造函数呢
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
var person2 = new Person('Pibi', 22, 'student');
这里与上面不同的是:1、没有显示的创建对象,2、直接将属性和方法赋值给this对象,3、没有return语句。而且我们最后创建对象使用的是new关键字,这个过程发生了什么呢?按照惯例也为了区分,构造函数首字母都用大写。
通过new关键字调用Person函数,实际上经历了以下四个步骤:
- 创建新对象; var obj = {}; 形象一点
- 将构造函数的作用域赋给新对象(也就是this指向obj)
- 执行构造函数代码,也就是obj添加了name,age,job属性和sayName方法
- 最后返回新对象,也就是说,上面person2保存了这个返回对象的引用
我们检测对象的类型,person2是Person的实例,当然也是Object的实例,毕竟祖宗嘛。
当然我们的构造函数也可以当做普通函数调用,也就是不用new关键字,不过这个时候this对象,在浏览器中的话就是window对象了。
var person = Person('Jack', 22, 'student'); //普通函数调用
Person函数没有返回值,当然person也就是undefined,this对象指向window,所以window对象可以调用
关于不使用new关键字调用函数,this指向window问题。我们来想想解决办法,创建作用域安全的构造函数:
function Person(name, age, job) {
if (this instanceof Person) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
} else {
return new Person(name, age, job);
}
}
通过一个判断,来判断this是否是Person的实例来实现。无论是否使用new关键字,这样我们都能够得到一个Person新实例这样也就避免了在全局对象上意外设置属性。
有利有弊,构造函数的缺点就是,对于像上面同样的方法sayName,多个对象我们得创建多次,每次还不是一样。怎么办呢?可以单独抽出来。
function sayName() {
console.log(this.name);
}
最后上面的this.sayName = sayName; 这样就可以了。多个对象就是同一个函数了。新问题出现,这样得定义多少个全局函数呀~,而且这个全局函数仅仅是被指定的对象调用。
原型模式:
function PersonThird() {
}
PersonThird.prototype.name = 'Jony';
PersonThird.prototype.age = 23;
PersonThird.prototype.job = 'doctor';
PersonThird.prototype.sayName = function () {
console.log(this.name);
};
var person3 = new PersonThird();
console.log(person3.sayName());
这样就完美了,直接在原型对象上面创建对象实例姚公用的方法属性。原型不理解的可以看看在下这篇文章。
但是虽然对象实例可以访问原型的东西,但是却不能重写,毕竟作用域链的访问就是,如果对象实例定义了name。那么访问的就是自己的name。其它对象没定义,仍然是原型对象的name。hasOwnProperty()判断是否是自己的属性,而不是原型的。for in 单独使用就是 'name' in person2 返回true 意思就是只要能够访问到,就为true。
这个原型对象的创建方式就很神奇了,不仅我们可以自定义这样创建,其实就连所有原生的引用类型,都是采用这种模式创建的。比如Object、Array、String 等 。
虽然可以修改原生对象的原型,但是这样做不好,导致冲突等等。
这个方式的缺点:好的一面可能带来坏的一面。正因为共享,对于包含引用类型的属性,就会发生问题。实例对象都会受到音音响,因为都是一样的。
组合使用构造函数模式和原型模式:
function ComplexPerson(name, age, job) {
this.name = name;
this.age = name;
this.job = job;
}
ComplexPerson.prototype = {
constructor: ComplexPerson,
sayName: function () {
console.log(this.name);
}
}
var person4 = new ComplexPerson('Cassie', 12, 'student');
console.log(person4);
集构造函数模式和原型模式为一体,公认比较好的模式。
动态原型模式:
function DynamicPerson(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if (typeof this.sayName !== 'function') {
DynamicPerson.prototype.sayName = function () {
console.log(this.name);
};
}
}
var person5 = new DynamicPerson('personFive', 33, 'free');
也就是判断一下。
寄生构造函数模式
返回的对象和构造函数或者构造函数的原型属性没关系,不能依赖instanceof来检测,不建议使用
稳妥构造函数模式
不用new,不用this保证封闭。
终于写完了,也方便日后自己的复习和理解,希望能有一点点的帮助吧。