作用域安全构造函数 – 学习画板

首页 » JavaScript » 作用域安全构造函数

作用域安全构造函数

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

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
}
var 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属性是用于识别链接目标和框架的,所以这里对该属性的偶然覆盖可能会导致该页面上出现其它错误。这个问题的解决方法就是创建一个作用域安全的构造函数。
作用域安全的构造函数在进行任何更改前,首先确认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实例环境中调用构造函数。任何一种情况下,对象初始化都能正常进行。如果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.with * 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()返回的值也没用用到,所以Rectangel实例中就不会有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.with * this.height;
    };
}

Rectangle.prototype = new Polygon();

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

上面这段重写的代码中,一个Rectangle实例也同时是一个Polygon实例,所以Polygon.call()会照意愿执行,最终会为Rectangle实例添加了sides属性。

此文章发表在 JavaScript. 将 固定链接 加入收藏.