构造函数
在ECMAScript中的构造函数可用来创建特定类型的对象。像Object Array这样的原生的构造函数 在运行时会自动出现在运行环境中。 当然 我们也可以创建自定义的构造函数, 从而定义自定义对象类型的属性和方法。
例如:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name)
};
}
var person1 = new Person("张三", 18,"工人");
var person2 = new Person("李四", 25, "包工头")
在这个例子中, 我们可以看到与正常函数的一些不同之处:
1.没有显式地创建对象;
2.直接将属性和方法赋给了this对象;
3. 没有return 语句。
此外 我们还注意到函数名Person的首字母是大写的,按照惯例 ,构造函数的首字母是大写的 而非构造函数的首字母是不大写的。
要创建Person函数的新实例, 必须使用new 操作符。 以这种方式调用构造函数会经历以下四个步骤:
1.创建一个新对象;
2.将构造函数的作用域赋给新对象(因此this就指向了这个新对象;
3. 执行构造函数中的代码(为这个新对象添加新属性);
4.返回新对象。
在前面例子的最后,person1和person2分别保存着Person的一个不同的实例。 这两个对象都有一个constructor(构造函数属性),都指向了Person,如下所示
console.log(person1.constructor === Person);//返回true
console.log(person2.constructor === Person);//返回true
对象的constructor属性最初是用来标识对象类型的。 但是提到检测对象类型,还是使用 instanceof操作符更加可靠一些。我们在这个例子中创建的所有对象既是Object的实例 也是Person的实例,这一点通过instanceof操作符可以得到验证。
console.log(person1 instanceof Object)//返回true
console.log(person1 instanceof Person)//返回true
console.log(person2 instanceof Object)//返回true
console.log(person1 instanceof Person)//返回true
创建自定义的构造函数意味着将来可以将它的实例标识作为一种特定的类型,而这正是构造函数模式胜过工厂模式的地方。
在这个例子中, 之所以person1和person2都是Object的实例,是因为所有对象均继承自Object。
构造函数和其他函数的唯一区别, 就是在于调用他们的方式不同。但是构造函数也是函数, 不存在定义构造函数的特殊语法。 任何函数,只要通过new操作符来调用,那它就可以作为构造函数, 而且任何函数,不通过new 操作符来调用, 它就是普通函数。例如 前面的例子就可以通过以下任何一种方式来调用:
//当作普通函数来调用:
Person("柳白猿", 25,"箭士");//添加到了window上面;
window.sayName(); // 返回柳白猿;
//当作构造函数来调用:
var person = new Person("孙悟空", 500, "行者");
person.sayName();// 孙悟空;
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"八戒", 1000, "使者");
o.sayName ()//八戒
构造函数的问题
构造函数虽然好用,但也并非没有缺点。使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在前面的例子中 person1和person2都有一个名为名为sayName的方法,但那个方法不是同一个Function的实例。函数也是对象, 因此每定义一个函数,就是实例化了一个对象。所以不同实例上的同名函数是不相等的, 以下代码可以证明这一点。
console.log(person1.sayName ===person2.sayName)//返回false
然而 创建两个完成同样任务的Function的实例的确没必要。况且有this对象在, 根本不用在执行代码前就把函数绑定到特定对象的上面。因此,大可以像下面这样,通过函数定义转移到构造函数外边来解决这个问题。
function Person(name, age,job) {
this.name = name;
this,age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
var person1 = new Person('心猿意马', 1500, '不明');
var person2 = new Person('覆水难收', 2000, '所以');
在这个例子中, 我们把sayName()函数的定义转移到了构造函数外部。 而在构造函数内部,我们把sayName的属性设置成等于全局的sayName函数。这样一来,由于sayName包含的是一个指向函数的指针, 因此person1和person2对象就共享了在全局作用域中定义的同一个sayName()函数。这样做确实解决了两个函数做同一件事的问题,可是新的问题又来了,在全局作用域中,定义的函数是实际上只能被某个函数调用,这让全局作用域有些名不副实。而更让人无法接受的是,如果对象需要定义很多的方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就毫无意义可言了。 好在, 我们可以通过使用原型模式很好地解决这个问题。