当使用函数作为一个构造函数时,程序依赖于调用者是否记得使用new操作符来调用该构造函数。注意:该函数假设接收者是一个全新的对象。

一个例子

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

当调用者,忘记使用new关键字时,那么这个函数的接收者是全局对象。

var u=User('wengxuesong','asdfasdfadf');
u;//undefinedthis.name;//'wengxuesong'this.pwd;//'asdfasdfadf'

该函数返回了无意义的undefined,还会修改全局对象。
如果将User函数应用ES5的严格模式,那么它的接收者为undefined

function User(name,pwd){
'use strict';
this.name=name;
this.pwd=pwd;
}
var u=User('wengxuesong','asdfasdfadf');//error:this is undefined

上面代码会报错,因为在严格模式下直接运行的函数中的this是undefined,无法给undefined定义属性。
如何才能使User函数在不使用new操作符的情况下,还是按预期工作呢?提供一个不管怎样调用都工作如构造函数的函数。实现该函数的一个简单方法是检查函数的接收者是否是正确的User实例

function User(name,pwd){
if(!(this instanceof User)){
return new User(name,pwd);//额外的函数调用
}
this.name=name;
this.pwd=pwd;
}

使用这种方式无论如何调用User函数,都会返回一个继承自User.prototype的对象

var x=User('wengxuesong','12123123');
var y=new User('songqiang','sfasdfasdf');
x instanceof User;//true
y instanceof User;//true

这个模式的一个缺点是它需要额外的函数调用,因此代价有点高。而且,很难适用于可变参数函数,因为没有一种直接模拟apply方法将可变参数函数作为构造函数调用的方式。

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

Object.create需要一个原型对象作为参数,并返回一个继承自该原型对象的新对象。因此,当以函数的方式调用该版本的User函数时,结果将返回一个继承自User.prototype对象的新对象,并且该对象具有已经初始化的name和pwd属性。
Object.create只有在ES5环境中才是有效的,可以通过代码进行兼容。上节已经写过一次了,再写一遍加深印象吧。

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

注意:这里只实现了单参数的Object.create函数。真实版本的Object.create函数还接受一个可选的参数,该参数描述了一组定义在新对象上的属性描述符。

如果使用new操作符调用新版本的User参数会发生什么?

构造函数覆盖模式,使用new操作符调用该函数的行为就如以函数调用它的行为一样。这能工作完全利益于js允许new表达式的结果可以被构造函数中的显式return语句所覆盖。当User函数返回self对象时,new表达式的结果就变为self对象。该self对象可能是另一个绑定到this的对象。
防范误用构造函数可能并不是太值得去做,尤其是当仅仅是局部使用构造函数时。但是理解如果以错误的方式调用构造函数会造成严重后果很重要。至少文档化构造函数期望使用new操作符调用是很重要的,尤其是在跨大型代码库中其享构造函数或该构造函数来自一个共享库时。

提示

  • 通过使用new操作符或Object.create方法在构造函数定义中调用自身使得该构造函数与调用语法无关

  • 当一个函数期望使用new操作符调用时,清晰地文档化该函数

附录一:Object.create方法

以下内容来自Mozilla 开发者社区
Object.create()方法创建一个拥有指定原型和若干指定属性的对象。

语法

Object.create(原型对象,[属性对象集])

异常

如果原型对象不是null或一个对象值,则抛出一个TypeError异常

实现类式继承

单继承
function Shape(){
this.x=0;
this.y=0;
}
Shape.prototype.move=function(x,y){
this.x+=x;
this.y+=y;
console.info('Shape moved.');
}; function Rectangle(){
Shape.call(this);//构造函数借用
}
Rectangle.prototype=Object.create(Shape.prototype); var rect=new Rectangle();
rect instanceof Rectangle;//true
rect instanceof Shape;//true
rect.move(1,1);//"Shape moved"

画张图表示一下上面的关系

多继承
function MyClass(){
SuperClass.call(this);
OtherSuperClass.call(this);
}
MyClass.prototype=Object.create(SuperClass.prototype);
mixin(MyClass.prototype,OtherSuperClass.prototype);//mixin
MyClass.prototype.myMethod=function(){ };
propertyObject参数
var o;
//创建原型为null的空对象
o=Object.create(null); o={};
//以字面量方式创建空对象相当于下面这句代码
o=Object.create(Object.prototype) o=Object.create(Object.prototype,{
//创建对象的数据属性
foo:{writable:true,configurable:true,value:'hello'},
//创建对象的访问器属性
bar:{
configurable:false,
get:function(){return 10;},
set:function(val){console.log('setting "o.bar" to',val);}
}
}); function Constructor(){}
o=new Constructor();
//相当于
o=Object.create(Constructor.prototype);
//如果Constructor里有一些初始化代码,Object.create不能执行那些代码 //应该相当于
o={};
o=Object.create(Constructor.prototype);
Constructor.call(o,args); //创建一个以另一个空对象为原型,且拥有一个属性p的对象
o=Object.create({},{p:{value:42}}); //省略了的属性默认为false,所以属性p是不可写,不可枚举,不可配置的
o.p=24;
o.p;//42
o.q=12;
for(var prop in o){
console.log(prop);
}
//'q'delete o.p;//false //创建一个可写的,可枚举的,可配置的属性p
o2=Object.create({},{p:{value:42,writable:true,enumerable:true,configurable:true}});

兼容版

if(typeof Object.create !== 'function'){
Object.create=(function(){
function NOP(){}
var hasOwn=Object.prototype.hasOwnProperty;
return function(o){
//1、如果o不是Object或null,抛出一个类型错误异常
if(typeof o!=='object'){
throw TypeError('Object prototype may only be an Object or null.');
}
//2、使创建的一个新的对象为obj
//3、设置obj的内部属性[[Prototype]]为o
NOP.prototype=o;
var obj=new NOP();
NOP.prototype=null;//解除NOP函数的prototype的引用
//4、如果参数有Properties,那么检测并添加属性到obj上。
if(arguments.length>1){
var Properties=Object(arguments[1]);
for(var prop in Properties){
if(hasOwn.call(Properties,prop)){
obj[prop]=Properties[prop];
}
}
}
return obj;
}
})();
}

[Effective JavaScript 笔记]第33条:使构造函数与new操作符无关的更多相关文章

  1. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  2. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  3. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  4. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  5. [Effective JavaScript 笔记]第38条:在子类的构造函数中调用父类的构造函数

    示例 场景类 场景图(scene)是在可视化的过程中(如游戏或图形仿真场景)描述一个场景的对象集合.一个简单的场景包含了在该场景中的所有对象(称角色),以及所有角色的预加载图像数据集,还包含一个底层图 ...

  6. [Effective JavaScript 笔记]第52条:数组字面量优于数组构造函数

    js的优雅很大程序要归功于程序中常见的构造块(Object,Function及Array)的简明的字面量语法.字面量是一种表示数组的优雅方法. var a=[1,2,3,5,7,8]; 也可以使用构造 ...

  7. [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

    构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...

  8. [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列

    第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...

  9. [Effective JavaScript 笔记]第54条:将undefined看做“没有值”

    undefined值很特殊,每当js无法提供具体的值时,就会产生undefined. undefined值场景 未赋值的变量的初始值即为undefined. var x; x;//undefined ...

随机推荐

  1. 记<<ssh穿透防火墙连接内网的机器(不用路由端口映射)>>

    场景: 在家连接公司的内网服务器. 需求: 不用设置端口映射在家用putty登录公司内网服务器. 条件: 有一台公网服务器做转发,有开放端口的控制权.(公网服务器可以是阿里云ECS, 腾讯云主机这样的 ...

  2. [转载]使用HttpWebRequest进行请求时发生错误:基础连接已关闭,发送时发生错误处理

    转载,原文来自 http://blog.csdn.net/hawksoft/article/details/21776009 最近调试原来的微信模拟登陆时发生了“基础连接已关闭,发送时发生错误”的错误 ...

  3. Spring security 和 AOP 学习

    1.Spring security 登录验证拦截器 资源管理拦截器 认证和授权:      认证:登录时候确实存在此用户. 登录要认证!      授权:登录后判断权限级别,然后赋予相应的操作权限. ...

  4. java并发库--锁

    synchronized的缺陷: 被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,获取线程被阻塞时,没有释放锁会导致等待线 ...

  5. 【BZOJ 3282】Tree Link Cut Tree模板题

    知道了为什么要换根(changeroot),access后为什么有时要splay,以及LCT的其他操作,算是比较全面的啦吧,,, 现在才知道这些,,,真心弱,,, #include<cstdio ...

  6. jQuery插件开发详细教程

    这篇文章主要介绍了jQuery插件开发详细教程,将概述jQuery插件开发的基本知识,最佳做法和常见的陷阱,需要的朋友可以参考下 扩展jQuery插件和方法的作用是非常强大的,它可以节省大量开发时间. ...

  7. C 文件读写1

    打开文件 fopen( ) 函数来创建或者打开文件,这个调用会初始化一个FILE 类型的对象. 原型 FILE *fopen( const char * filename, const char * ...

  8. pycharm 常用设置

    PyCharm3.0默认快捷键( 1.编辑(Editing) Ctrl + Space 基本的代码完成(类.方法.属性) Ctrl + Alt + Space 快速导入任意类 Ctrl + Shift ...

  9. ORACLE在存储过程中记录日志的处理包

    Java开发过程中一般使用LOG4J来将程序的运行日志记录到文件中,在ORACLE存储过程中也需要记录日志,我将工作中自己整理的一个记录日志的包分享出来,其实很简单,希望大家多提意见. 一.表结构 为 ...

  10. 12.Android之Tabhost组件学习

    TabHost是整个Tab的容器,TabHost的实现有两种方式: 第一种继承TabActivity,从TabActivity中用getTabHost()方法获取TabHost.各个Tab中的内容在布 ...