Effective JavaScript Item 33 让构造函数不再依赖new关键字

本系列作为EffectiveJavaScript的读书笔记。

在将function当做构造函数使用时,需要确保该函数是通过new关键字进行调用的。

function User(name, passwordHash) {
	this.name = name;
	this.passwordHash = passwordHash;
}

如果在调用上述构造函数时,忘记了使用new关键字,那么:

var u = User("baravelli", "d8b74df393528d51cd19980ae0aa028e");
u; // undefined
this.name; // "baravelli"
this.passwordHash; // "d8b74df393528d51cd19980ae0aa028e"

可以发现得到的u是undefined,而this.name以及this.passwordHash则被赋了值。但是这里的this指向的则是全局对象。

如果将构造函数声明为依赖于strict模式:

function User(name, passwordHash) {
	"use strict";
	this.name = name;
	this.passwordHash = passwordHash;
}
var u = User("baravelli", "d8b74df393528d51cd19980ae0aa028e");
// error: this is undefined

那么在忘记使用new关键字的时候,在调用this.name= name的时候会抛出TypeError错误。这是因为在strict模式下,this的默认指向会被设置为undefined而不是全局对象。

那么,是否有种方法能够保证在调用一个函数时,无论使用了new关键字与否,该函数都能够被当做构造函数呢?下面的代码是一种实现方式,使用了instanceof操作:

function User(name, passwordHash) {
	if (!(this instanceof User)) {
		return new User(name, passwordHash);
	}
	this.name = name;
	this.passwordHash = passwordHash;
}

var x = User("baravelli", "d8b74df393528d51cd19980ae0aa028e");
var y = new User("baravelli", "d8b74df393528d51cd19980ae0aa028e");
x instanceof User; // true
y instanceof User; // true

以上的if代码块就是用来处理没有使用new进行调用的情况的。当没有使用new时,this的指向并不是一个User的实例,而在使用了new关键字时,this的指向是一个User类型的实例。

另一个更加适合在ES5环境中使用的实现方式如下:

function User(name, passwordHash) {
	var self = this instanceof User ? this : Object.create(User.prototype);
	self.name = name;
	self.passwordHash = passwordHash;
	return self;
}

Object.create方法是ES5提供的方法,它能够接受一个对象作为新创建对象的prototype。那么在非ES5环境中,就需要首先实现一个Object.create方法:

if (typeof Object.create === "undefined") {
	Object.create = function(prototype) {
		function C() { }
		C.prototype = prototype;
		return new C();
	};
}

实际上,Object.create方法还有接受第二个参数的版本,第二个参数表示的是在新创建对象上赋予的一系列属性。

当上述的函数确实使用了new进行调用时,也能够正确地得到返回的新建对象。这得益于构造器覆盖模式(Constructor Override Pattern)。该模式的含义是:使用了new关键字的表达式的返回值能够被一个显式的return覆盖。正如以上代码中使用了returnself来显式定义了返回值。

当然,以上的工作在某些情况下也不是必要的。但是,当一个函数是需要被当做构造函数进行调用时,必须对它进行说明,使用文档是一种方式,将函数的命名使用首字母大写的方式也是一种方式(基于JavaScript语言的一些约定俗成)。

总结:

  1. 使用Object.create来保证一个函数确实是被当做一个构造函数进行调用的,无论new关键字是否被使用了。
  2. 对于作为构造函数的函数,在文档中指明这一点来保证其他用户能够正确的使用它。

时间: 2024-08-06 18:40:25

Effective JavaScript Item 33 让构造函数不再依赖new关键字的相关文章

Effective C++ Item 33 避免遮掩继承过来的名称

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie ? 不懂 c++为什么derived classes 内的名称要遮掩 base classes 内的名称. 经验:derived classes 内的名称会遮掩 base classes 内的名称.在 public 继承下从来没有人希望如此. C++ 的名称遮掩规则所做的唯一事情就是: 遮掩名称 derived class 作用域被嵌套在 base class 作用域里 class Bas

Effective JavaScript Item 38 调用父类的构造函数在子类的构造函数

作为这一系列Effective JavaScript的读书笔记. 在一个游戏或者图形模拟的应用中.都会有场景(Scene)这一概念.在一个场景中会包括一个对象集合,这些对象被称为角色(Actor). 而每一个角色依据其类型会有一个图像用来表示,同一时候场景也须要保存一个底层图形展示对象的引用,被称为上下文(Context): function Scene(context, width, height, images) { this.context = context; this.width =

Effective JavaScript Item 38 在子类构造函数中调用父类构造函数

本系列作为Effective JavaScript的读书笔记. 在一个游戏或者图形模拟的应用中,都会有场景(Scene)这一概念.在一个场景中会包含一个对象集合,这些对象被称为角色(Actor).而每个角色根据其类型会有一个图像用来表示,同时场景也需要保存一个底层图形展示对象的引用,被称为上下文(Context): function Scene(context, width, height, images) { this.context = context; this.width = width

Effective JavaScript Item 28 不要依赖函数的toString方法

本系列作为Effective JavaScript的读书笔记. 在JavaScript中,函数对象上存在一个toString方法,它能够方便地将函数的源代码转换返回成一个字符串对象. (function(x) { return x + 1; }).toString(); // "function (x) {\n return x + 1;\n}" toString方法不仅仅会让一些黑客找到攻击的方法,而且该方法也存在严重的限制. 首先,toString方法的实现方式并没有被ECMASc

Effective JavaScript Item 32 绝不要修改__proto__

本系列作为Effective JavaScript的读书笔记. 和Object.getPrototypeOf相比,__proto__的特殊之处还体现在它能够修改一个对象的原型继承链.因为它是一个属性,除了执行获取它的操作外,还能够对它进行设置. 但是,绝不要修改__proto__.原因如下: 首先,最显而易见的原因就是便携性.因为不是所有的JavaScript执行环境都支持这一属性,所以使用了__proto__之后,代码就不能在那些不支持__proto__的环境中运行了. 其次,是性能上的考虑.

Effective JavaScript Item 40 避免继承标准类型

本系列作为Effective JavaScript的读书笔记. ECMAScript标准库不大,但是提供了一些重要的类型如Array,Function和Date.在一些场合下,你也许会考虑继承其中的某个类型来实现特定的功能,但是这种做法并不被鼓励. 比如为了操作一个目录,可以让目录类型继承Array类型如下: function Dir(path, entries) { this.path = path; for (var i = 0, n = entries.length; i < n; i++

Effective JavaScript Item 39 绝不要重用父类型中的属性名

本系列作为Effective JavaScript的读书笔记. 如果需要向Item 38中的Actor对象添加一个ID信息: function Actor(scene, x, y) { this.scene = scene; this.x = x; this.y = y; this.id = ++Actor.nextID; scene.register(this); } Actor.nextID = 0; 同时,也需要向Actor的子类型Alien中添加ID信息: function Alien(

Effective JavaScript Item 27 使用闭包而不是字符串来封装代码

本系列作为Effective JavaScript的读书笔记. 对于代码封装,在JavaScript中有两种方式可以办到.第一种就是使用function,第二种则是利用eval()函数,传入到该函数的字符串参数可以是一段代码. 当对使用哪种方式犹豫不决时,使用function.因为使用字符串的一个重要缺点是,传入的字符串并不是一个闭包,而function则可以代表一个闭包.关于闭包的特点,在Item 11中进行了描述. 下面是一段使用字符串来封装代码的例子: function repeat(n,

Effective JavaScript Item 37 认识this的隐式指向

本系列作为Effective JavaScript的读书笔记. CSV数据通常都会被某种分隔符进行分隔,所以在实现CSV Reader时,需要支持不同的分隔符.那么,很自然的一种实现就是将分隔符作为构造函数的参数. function CSVReader(separators) { this.separators = separators || [","]; this.regexp = new RegExp(this.separators.map(function(sep) { retu