当使用函数作为一个构造函数时,程序依赖于调用者是否记得使用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. BASE64Decoder 编码(sun.jar)

    Base64 是网络上最常见的用于传输8Bit 字节代码的编码方式之一,大家可以查看RFC2045 -RFC2049 ,上面有MIME 的详细规范.  Base64 要求把每三个8Bit 的字节转换为

  2. cryptDB安装分析

    cryptDB的安装脚步是用ruby语言写的,由于这里对ruby语言不熟悉,只能做简答的分析.我们先看看cryptDB的目录结构. 主要的目录有bins.doc.main.udf目录,下面我们通过分析 ...

  3. WRONGTYPE Operation against a key holding the wrong kind of value

    今天改动代码,一运行就跑错了,错误原因: 因为redis中已经存在了相同的key, 而且key对应的值类型并不是Set,而是SortSet(改动前):再调用smembers时,抛出此错误. 解决方法: ...

  4. 【Moqui业务逻辑翻译系列】Story of Online Retail Company 在线零售公司的故事

    h1. Story of Online Retail Company 在线零售公司的故事 Someone decides to sell a product. [Product Marketer Ma ...

  5. Excel解析与导入导出

    第三次结对编程作业 结对成员: 031302610黄志鹏 031302603 陈波 功能分析 1.将初始排课表excel导入系统数据库 2.将系统数据库的排课数据显示在web界面 实现思路 一.实现将 ...

  6. HTML5开发注意事项及BUG解决

    1.点透Q:元素A上定位另外一个元素B,点击元素B,如果元素A有事件或链接,会触发元素A上的事件或链接,即点透A:在元素B的touchend中增加ev.preventDefault();阻止默认事件即 ...

  7. A星寻路算法

    A星寻路算法 1.准备一个close关闭列表(存放已被检索的点),一个open开启列表(存放未被检索的点),一个当前点的对象cur 2.将cur设成开始点 3.从cur起,将cur点放入close表中 ...

  8. Java基础-CGLIB动态代理

    JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继 ...

  9. 【Gym 100610A】Alien Communication Masterclass

    题 Andrea is a famous science fiction writer, who runs masterclasses for her beloved readers. The mos ...

  10. JAVA的整型与字符串相互转换

    1如何将字串 String 转换成整数 int? A. 有两个方法: 1). int i = Integer.parseInt([String]); 或 i = Integer.parseInt([S ...