1. 总是记得用new关键字来执行构造函数。
前面提到,可以用构造函数创建JavaScript的对象,这个构造函数在使用的时候需要使用new关键字,但如果忘记写入new关键字,会怎么样?事实上这个函数还是会被执行的,没有语法错误,但有逻辑错误。因此如果没有用new关键字,函数里的this指针将不会被存放新的对象的引用,实际存入是的全局对象。如果你在网页里执行代码,这个this就是window。比如,这个时候,构造函数里的this.member指向的就是window.member。如果在全局对象window里没有定义这个member属性,这个属性将会被隐式的声明!这有可能会捅出个大篓子:
// constructor function Waffle() { this.tastes = "yummy"; } // a new object var good_morning = new Waffle(); console.log(typeof good_morning); // "object" console.log(good_morning.tastes); // "yummy" // antipattern: // forgotten `new` var good_morning = Waffle(); console.log(typeof good_morning); // "undefined" console.log(window.tastes); // "yummy"
这就是为什么我们应该让构造函数的首字母大写,让人一看就知道它是个构造函数。因为它如果不用new关键字执行,是会出问题的。
2. 在构造函数中别用this,用that
既然在构造函数中用了this会有隐患,那么我们可以别用this,改用that。当然,that不是个关键字。所以构造函数会变成这个样子:
function Waffle() { var that = {}; that.tastes = "yummy"; return that; }
这看起来有点奇怪,不过的确能提高代码的安全性。如果需要构造的对象足够简单,也可以直接return一个Literal的对象:
function Waffle() { return { tastes: "yummy" }; }
使用这种方法写出的构造函数,总是能返回一个我们需要的对象,不管它是如果被调用的:
var first = new Waffle(), second = Waffle(); console.log(first.tastes); // "yummy" console.log(second.tastes); // "yummy"
这种模式的最大问题是,新建的对象与Waffle对象的原型之间的关系丢失了,而通过Waffle的原型加入通用方法将不能奏效。如果要保证新对象和Waffle原型之间的关系,可以使用instanceof做个判断,然后再决定如何创建对象:
function Waffle() { if (!(this instanceof Waffle)) { return new Waffle(); } this.tastes = "yummy"; } Waffle.prototype.wantAnother = true; // testing invocations var first = new Waffle(), second = Waffle(); console.log(first.tastes); // "yummy" console.log(second.tastes); // "yummy" console.log(first.wantAnother); // true console.log(second.wantAnother); // true
还有一种通用的方法可以检查调用构造函数的方式 ,就是通过arguments.callee来判断:
if (!(this instanceof arguments.callee)) { return new arguments.callee(); }
这种方法的原理是,所有的函数对象中,都有一个arguments的成员对象,arguments的其中一个属性叫callee,它指向这个函数的调用者。但这种方法并不适用过严格的JavaScript标准,所以不建议使用。