通常状况下,JS中建立对象的方式能够用构造函数Object或者对象字面量的方式,但须要建立几个具备相同属性或方法的对象时,就得写大量的冗余代码。故而出现了下述几种建立对象的方法。设计模式
工厂模式是一种常见的设计模式。这种模式把对象的建立过程抽象出来并封装成一个函数。须要使用同类型的对象时,能够不断地调用此函数。例子以下:浏览器
function createPerson (name, age) {
var o = new Object();
o.name = name;
o.sayName = function(){
console.log(this.name)
}
return o;
}
复制代码
工厂模式解决了建立多个相同类型对象的问题,即减小了代码的冗余,但没有解决对象识别的问题。bash
JavaScript中的构造函数其实就是普通的函数,但构造函数的目的通常用来建立对象。按照约定俗成的惯例,构造函数应该以一个大写字母开头。例子以下:函数
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小红', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小红
复制代码
此例子与工厂模式的区别有:学习
此外,构造函数模式还解决了对象识别的问题:ui
xiaoming instanceof Person // true
xiaohong instanceof Person // true
复制代码
特别地,由于构造函数也是函数,在不用new操做符调用时,函数内部的this对象实际上是指向了全局对象(浏览器环境是window对象),具体缘由请学习this的原理。this
构造函数并非没有缺点,如上面例子的sayName方法,在每一个实例上都从新建立了一遍:spa
xiaoming.sayName === xiaohong.sayBane // false
复制代码
像sayName这种函数都是为了完成相同的任务(打印实例的名字),大可抽象出来做为一个共享的函数prototype
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
console.log(this.name);
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小红', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小红
复制代码
但这种方式使得sayName函数变为了全局函数,而且只合适被Person new出来的实例调用。故而带来下面几个问题:设计
利用JS的原型对象和原型链,能够解决构造函数模式中带来的那几个问题:
function Person(){};
Person.prototype = {
constructor: Person,
name: '小明',
age: 18,
sayName: function () {
console.log(this.name)
}
}
var xiaoming = new Person();
var xiaohong = new Person();
xiaoming.sayName(); //小明
xiaohong.sayName(); //小明
复制代码
上面的例子能够看到,虽然解决了构造函数模式存在的问题,但缺点也是大大的:
Person.prototype.friends = ['张三', '李四'];
xiaoming.friends === xiaohong.friends; //true
xiaoming.frinds.push('王五');
xiaohong.friends; // ['张三', '李四', '王五'];
复制代码
组合使用构造函数模式和原型模式是ES5中建立对象最经常使用的模式,此方式用构造函数定义实例的公共属性,使用原型模式定义实例的公共方法,最大限度下降了内存:
function Person(name, age){
this.name = name;
this.age = age;
this.friends = ['张三', '李四'];
};
Person.prototype.sayName = function () {
console.log(this.name);
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小红', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小红
复制代码
另外,对于引用类型的共享的属性值,实例之间不会互相影响:
xiaoming.friends ==== xiaohong.friends; //false
xiaoming.frinds.push('王五');
xiaohong.friends; // ['张三', '李四'];
复制代码
动态原型模式就是把全部信息封装在构造函数里:
function Person(name, age){
this.name = name;
this.age = age;
if(typeof this.sayName !== 'function'){
Person.prototype.sayName = function () {
console.log(this.name);
}
}
};
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小红', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小红
复制代码
动态原型模式同事保持了构造函数模式和原型模式的优势,且封装性比构造函数和原型组合使用的方式更好。但要注意在构造函数执行后不能重写原型对象,不然会切断现有实例和新原型的关系。由于该模式中原型对象上的自定义方法是在构造函数中挂载的。
红皮书还介绍了寄生构造函数模式和稳妥构造函数模式,但实践中很是少用。有兴趣的同窗能够查阅。