构造函数其实就是一个使 用 new
操作符调用的函数,
当使用 new 调用时,构造函数内用到的 this 对象会指向新创建的对象实例
。如下面的例子所示:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person("Nicholas", 29, "Software Engineer");
上面这个例子中,
Person
构造函数使用
this
对象给三个属性赋值:
name
、
age
和
job
。当和
new
操作符连用时,则会创建一个新的
Person
对象,同时会给它分配这些属性。问题出在当没有使用
new 操作符来调用该构造函数的情况上。由于该 this
对象是在运行时绑定的,所以直接
Person()
,
this
会映射到全局对象
window
上,导致错误对象属性的意外增加。例如:
var person = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //"Nicholas"
alert(window.age); //29
alert(window.job); //"Software Engineer"
这里,原本针对
Person
实例的三个属性被加到
window
对象上,因为构造函数是作为普通函数调
用的,忽略了
new
操作符。这个问题是由
this 对象的晚绑定
造成的,在这里
this
被解析成
window
对象。由于
window
的
name
属性是用于识别链接目标和
frame
的,所以这里对该属性的偶然覆盖可能 会导致该页面上出现其他错误。这个问题的解决方法就是创建一个作用域安全的构造函数
。
作用域安全的构造函数在进行任何更改前,
首先确认 this 对象是正确类型的实例
。如果不是,那
么会创建新的实例并返回。请看以下例子:
function Person(name, age, job){
if (this instanceof Person){
this.name = name;
this.age = age;
this.job = job;
} else {
return new Person(name, age, job);
}
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //""
alert(person1.name); //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name); //"Shelby"
这段代码中的
Person
构造函数添加了一个检查并确保
this
对象是
Person
实例的
if
语句,它
表示要么使用
new
操作符,要么在现有的
Person
实例环境中调用构造函数。任何一种情况下,对象初 始化都能正常进行。如果 this
并非
Person
的实例,那么会再次使用
new
操作符调用构造函数并返回 结果。最后的结果是,调用 Person
构造函数时无论是否使用
new
操作符,都会返回一个
Person
的新 实例,这就避免了在全局对象上意外设置属性。
关于作用域安全的构造函数的贴心提示。实现这个模式后,你就锁定了可以调用构造函数的环境。
如果你使用构造函数窃取模式的继承且不使用原型链,那么这个继承很可能被破坏。这里有个例子:
function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0;
};
} else {
return new Polygon(sides);
}
}
function Rectangle(width, height){
Polygon.call(this, 2);
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}
var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined
在这段代码中,
Polygon
构造函数是作用域安全的,然而
Rectangle
构造函数则不是。新创建一
个
Rectangle
实例之后,这个实例应该通过
Polygon.call()
来继承
Polygon
的
sides
属性。但是,
由于
Polygon 构造函数是作用域安全的
,
this
对象并非
Polygon
的实例,所以会创建并返回一个新 的 Polygon
对象。
Rectangle
构造函数中的
this
对象并没有得到增长,同时
Polygon.call()
返回
的值也没有用到,所以
Rectangle
实例中就不会有
sides
属性。
如果
构造函数窃取结合使用原型链或者寄生组合
则可以解决这个问题。考虑以下例子:
function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0;
};
} else {
return new Polygon(sides);
}
}
function Rectangle(width, height){
Polygon.call(this, 2);
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides); //2
上面这段重写的代码中,一个
Rectangle
实例也同时是一个
Polygon
实例,所以
Polygon.call()
会照原意执行,最终为
Rectangle
实例添加了
sides
属性。
多个程序员在同一个页面上写
JavaScript
代码的环境中,作用域安全构造函数就很有用了。届时, 对全局对象意外的更改可能会导致一些常常难以追踪的错误。除非你单纯基于构造函数窃取来实现继 承,推荐
作用域安全的构造函数作为最佳实践
。