为什么使用工厂模式?
使用Objec构造函数(var person = new Object();)或对象字面量方式(var person= {};)都可以创建单个对象,但是这些方式都有明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。为了解决这个问题,我们可以使用工厂模式的一种变体。
工厂模式
工厂模式就是创建一个函数,用函数封装以特定接口创建对象的细节。
function createPerson(name,age){
var person=new Object();
person.name=name;
person.age=age;
person.info=function(){
alert('姓名是:'+this.name);
}
return person;
}
var per=createPerson('zs',27);
per.info();
函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象。
可以无数次地调用这个函数,而且每次都返回一个包含两个属性一个方法的对象。
工厂模式虽然解决了多个相抵对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
构造函数模式
构造函数用来创建特定类型的对象,像Object和Array的原生构造函数,在运行时会自动出现在执行环境中。
此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
例如,可以使用构造函数模式将前面的栗子重写如下:
function Person(name,age){
this.name=name;
this.age=age;
this.info=function(){
alert('姓名是:'+this.name);
}
return person;
}
var person1=new Person('zs',27);
var person2=new Person('ls',30);
person.info();
上述栗子中,Person()取代了createPerson()函数。二者有很大的相似之处,也有不同之处。
主要有:1.没有显示地创建对象
2.直接将属性和方法赋值给了this
3.没有return语句
此外,还应当注意到 Person的名字使用的是大写的P。这是惯例,构造函数始终都应该以一个大写字母开头,以区分非构造函数。
类似的有java种的class类,也要以大写字母开头。
我的理解,构造函数就相当于是创建了一个class类,创建实例的话,就是用new 创建,这种创建实例的方式,更像是es6的class。
总之,构造函数本身也是函数,只不过可以用来创建对象而已。
要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数,实际上会有以下4个步骤。
1.创建一个新对象
2.将构造函数的作用域赋值给新对象(因此this就指向了这个新对象)
3.执行构造函数中的代码(为这个新对象添加属性)
4.返回新对象
在上个栗子中,person1和person2分别属于不同的对象,保存着不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person
person1.constructor===Person;//true
person2.constructor===Person;//true
person1 instanceof Object ;//true
person1 instanceof Person ;//true
person2同理;
创建自定义的构造函数意味着将来可以将他的实例表视为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。
在这个栗子中,person1和person2之所以都是Object的实例,是因为所有对象均继承自Object。
构造函数模式的特点
构造函数与其他函数的唯一区别,在于调用的方式不同。任何函数,只要通过new操作符来调用,它就可以作为构造函数;
任何函数如果不通过new操作符来调用,那么他跟普通函数也不会有什么两样
比如,我们可以将上栗中的Person构造函数使用任何一种方式调用。
//当做构造函数
var per1=new Person('ww',23)
console.log(per1.name);//'ww'
per1.info();//姓名是:ww
//当做普通函数执行,属性和方法都被添加给了window/global
Person('ww',23);
window.info();//姓名是:ww
//在另一个对象的作用域中执行
var user=new Object();
Person.call(user,'ww',23);
user.info();//姓名是:ww
构造函数的问题
使用构造函数的主要问题就在于每个方法都要在每次创建实例的时候重新创建一遍。
在上面栗子中person1和person2都有一个info的方法,当是这两个方法不是同一个Function实例。
因为在ECMAScript中的函数也是对象,因此每定义一个函数,也就是实例化了一个对象。其实就相当于这样定义:
function Person(name,age){
this.name=name;
this.age=age;
this.info=new Function(){console.log('姓名是'+this.name)}
}
以这种方式创建的函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制仍然是相同的。
因此,不同实例上的同名函数是不相等的
person1.info===person2.info;//false
此时,我们可以思考,是否可以将实现相同功能的info方法提取出来,在构造函数外封装呢?我们可以这样
function Person(name,age){
this.name=name;
this.age=age;
this.info=info;
}
function info(){
console.log(this.name);
}
此时,我们把函数info定义在了构造函数外部,在构造函数内部,我们将info属性等于全局下的info。
由于info包含的是一个指向函数的指针,因此perso1和person2就共享了全局作用下的同一个info()函数。
但是问题是:全局作用域下的方法,不能只是让某一个对象去调用,否则就有点名不副实了,
除此之外,如果对象需要定义很多的方法,那么按照这种思想,我们需要定义很多很多的全局作用下的方法,这样太可怕了!
怎么解决呢?期待后续:原型模式
=============================================
构造函数案例
var F = function(){}
Object.prototype.a = function(){
console.log('a()')
}
Function.prototype.b = function(){
console.log('b()')
}
var f = new F();
F.a();
F.b();
f.a();
f.b();
分析:函数F使用new操作符创建实例,说明F是构造函数
所以F的最终指向为Function.prototype ,而Function最终指向Object,所以F.a()的执行结果就是:a() F.b()的执行结果是:b(),
f是F的一个实例对象,最终指向Object,但是没有指向Function,所以最后一行f.b()会报错 显示b未定义
所以f的最终指向为Object