构造函数其实就是一个使用new操作符调用的函数。当使用new调用时,构造函数内用到的this对象会指向新创建的对象实例,如下面的例子所示:

1
2
3
4
5
6
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
var person = new Person("aqingya", 18, "吴志广");

上面这个例子中,Person构造函数使用this对象给三个属性赋值: name、age和 job。当和new操作符连用时,则会创建一个新的 Person对象,同时会给它 分配这些属性。问题出在当没有使用new操作符来调用该构造函数的情况上。由于该this对象是在运行时绑定的,所以直接调用Person(),this会映射到全局对象window.上,导致错误对象属性的意外增加。例如:

1
2
3
4
var person = Person("aqingya", 18, "吴志广");
alert(window.name); //"aqingya"
alert(window.age); //18
alert(window.job); //吴志广

原本针对Person实例的三个属性被加到window对象上,因为构造函数是作为普通函数调用的,忽略了new操作符。这个问题是由this 对象的晚绑定造成的,在这里this被解析成了window对象。

解决方法:就是创建一个作用域安全的构造函数在进行更改之前,首先确认this对象的正确类型的实例,如果不是就会创建新的实例并返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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操作符调用构造函数并返回结果。


但是这也会出现一个问题:实现这个模式后,你就锁定了可以调用构造函数的环境。如果你使用构造函数窃取模式的继承且不使用原型链,那么这个继承很可能被破坏。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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属性。

解决方法:构造函数窃取结合使用原型链的方法可以解决该问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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();
//让Rectangle的原型成为 Polygon的实例。Rectangle的实例同时也是Polygon的实例。
//所以 Polygon.call()会按照原意执行。为Rectangle添加sides属性

var rect = new Rectangle(5, 10);
alert(rect.sides); //2


愿你的坚持终有收获。