作用域安全的构造函数

构造函数其实就是一个使 用 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 代码的环境中,作用域安全构造函数就很有用了。届时, 对全局对象意外的更改可能会导致一些常常难以追踪的错误。除非你单纯基于构造函数窃取来实现继 承,推荐 作用域安全的构造函数作为最佳实践

你可能感兴趣的:(周记生活,javaScript,javascript,开发语言)