在JavaScript中函数作为对象的属性使用时,我们称其为方法调用;如果函数使用new
操作符来调用时,我们称其为构造函数。
在涉及到构造函数时,通常绕不开关于this
的讨论。因为构造函数调用会将一个全新的对象作为this
变量的值,并隐式返回这个新对象作为调用的结果。
在使用构造函数时,如果调用者忘记使用new
关健字,那么函数的接收者将是全局对象。
function Person(name, age) {
this.name = name;
this.age = age;
}
// 使用`new`调用
var jenemy = new Person('jenemy', 18);
console.log(window.age); // undefined
// 不使用
var jenemy = Person('jenemy', 18);
console.log(window.age); // 18
不使用new
调用构造函数的结果是我们无意间创建了全局变量name
和age
,如果这些全局变量已经存在则会被修改。
一个开发者熟知的解决方案是,在调用函数前先判断函数的接收者是否为当前函数的实例。
function Person(name, age) {
if (!(this instanceof Person)) {
return new Person(name, age);
}
this.name = name;
this.age = age;
}
这种模式的一个缺点是需要额外的函数调用,在性能上代价有点高。一种更为有效的方式是使用ES5的Object.create()
函数。
function Person(name, age) {
var self = this instanceof Person ? this : Object.create(Person.prototype);
self.name = name;
self.age = age;
return self;
}
注意,上面二种解决方案都使用了instance
操作符来判断对象的实例。如果看过我写的《javascript判断一个对象是否为数组》文章,会发现instanceof
操作符并不可靠。上面的示例,稍作修改:
function Person(name, age) {
this.name = name;
this.age = age;
}
Object.defineProperty(Person, Symbol.hasInstance, {
value(v) {
return false;
}
})
var wu = new Person('jenemy', 18);
console.log(wu instanceof Person); // false
如果没有修改Person
对象内建的Symbol.hasInstance
方法,上面的结果很显然应该在控制台输出true
。为了解决函数调用这种模棱两可的问题,ES6提供了元属性new.target
,当调用函数的[[Construct]]方法时,new.target
被赋值为new
操作的目标,通常为新创建对象的实例。
function Person(name, age) {
if (!new.target) {
throw 'Peron must called with new';
}
this.name = name;
this.age = age;
}
var wu = Person('jenemy', 18); // Uncaught Peron must called with new
需要注意的是在函数外使用new.target
会报语法错误。同时,它不受对象的Symbol.hasInstance
方法被修改的影响。所以如果是在ES6环境,使用new.target
是最可靠的解决方案。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。