7.4 识别new运算进行的构造器调用
说明:
在一个函数中识别当前调用是使用new来进行的构造器调用,还是普通的函数或方法调用。
示例背景:
如果一个函数被设计为既可以用new运算来产生对象实例,又可以作为普通函数调用,或者可以作为对象方法调用,那么如何识别当前究竟在哪种环境下被调用呢?
示例代码:
175 /**
176 * 1. 识别当前函数是否使用new运算来构建实例
177 */
178 function MyObject() {
179 if (this instanceof arguments.callee) {
180 // 是使用new运算构建实例
181 }
182 else {
183 // 是普通函数调用
184 }
185 }
186 var obj = new MyObject();
187
188 /**
189 * 2. 识别当前函数是否使用作为方法调用
190 */
191 function foo() {
192 if (this === window) {
193 // 是普通函数调用
194 }
195 else {
196 // 是方法调用
197 }
198 }
199 obj.aMethod = foo;
200 obj.aMethod();
示例说明:
当使用new运算时,构建过程运行在函数自身的闭包中,而且新构建的对象总是构造器的一个实例。因此,示例1使用arguments.callee来找到这个构造器函数自身,并检测this对象是否为它的实例,从而可以检测是否运行在new运算的实例构建过程中。
但这种方法存在一个(唯一的)问题。如果用户代码试图将构造器函数作为它的实例的一个方法,那么示例1就会误判。例如:
obj.aMethod = MyObject;
obj.aMethod(); // <-- 这个方法调用会被识别为new()构建过程
这时,除非用户代码愿意使用更为复杂的构造过程,在MyObject()中加入更多的识别代码并与构造器原型等采用某种特别的约定,否则上述的误判问题是不可能解决的。
除了示例1所示的方法之外,用户代码也可以用一种简洁的方法来识别new运算中的构造器调用:
5 if (this.constructor === arguments.callee) {
6 ...
这种方法依赖于构建器原型的constructor属性,因此要求MyObject的原型或该原型的constructor成员未被重写过。这种方法虽然有些限制,但仍然是常常用到的。
示例2利用了一条简单的规则:在普通函数调用中,this引用指向宿主对象——在浏览器环境中是window,如果是在其他环境中,请参考相关文档。
当用户代码使用aFunction.apply或aFunction.call来调用函数自身时,可能会显式地指定this引用为null、undefined等无意义的值。这种情况下,JavaScript引擎内部会忽略该值,并以宿主对象作为this引用传入函数。因此我们不必额外地检测这些值。





