[Effective JavaScript 笔记]第33条:使构造函数与new操作符无关
当使用函数作为一个构造函数时,程序依赖于调用者是否记得使用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操作符无关的更多相关文章
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第38条:在子类的构造函数中调用父类的构造函数
示例 场景类 场景图(scene)是在可视化的过程中(如游戏或图形仿真场景)描述一个场景的对象集合.一个简单的场景包含了在该场景中的所有对象(称角色),以及所有角色的预加载图像数据集,还包含一个底层图 ...
- [Effective JavaScript 笔记]第52条:数组字面量优于数组构造函数
js的优雅很大程序要归功于程序中常见的构造块(Object,Function及Array)的简明的字面量语法.字面量是一种表示数组的优雅方法. var a=[1,2,3,5,7,8]; 也可以使用构造 ...
- [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑
构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...
- [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列
第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...
- [Effective JavaScript 笔记]第54条:将undefined看做“没有值”
undefined值很特殊,每当js无法提供具体的值时,就会产生undefined. undefined值场景 未赋值的变量的初始值即为undefined. var x; x;//undefined ...
随机推荐
- CSS3小分队——进击的border-radius
上一篇:<CSS float属性小解——”浮“生若水> 写在前面: ~~强势插入~~如果有想进一步了解float属性的小伙伴,可以猛戳上面的链接,<CSS float属性小解——”浮 ...
- KMP和扩展KMP
文章网上太多这里提一下代码细节: KMP: scanf("%s\n",s); scanf("%s\n",t); int ls=strlen(s),lt=strl ...
- AngularJs——grunt神器的使用
前面我们已经知道了如何安装grunt,本章节给各位道友介绍如何使用 grunt 的插件,grunt是重点在于如何配置使用 Gruntfile.js,官网上也有很多范例. 1,包装函数 module.e ...
- Grovvy之解析XML文件
假设现有customer.xml 文件内容如下: <?xml version="1.0" ?> <customers> <corporate> ...
- OVER(PARTITION BY)函数介绍
问题场景 最近在项目中遇到了对每一个类型进行求和并且求该类型所占的比例,当时考虑求出每种类型的和,并在java中分别对每一种类型的和与总和相除求出所占比例.后来,想到这样有点麻烦,并且项目中持久层使用 ...
- Easyui使用记录
一天就这搞了这几行. 1. if else 可以嵌套: 2. 子页面调用父页面js,需要使用top.父页面js的方法. <script type="text/javascript&qu ...
- FusionCharts的使用方法
来源于:http://www.cnblogs.com/xuhongfei/archive/2013/04/12/3016882.html 今天统计价格变化规律的时候找到的一个很好的文档,很详细 一.简 ...
- Excel如何查找名字重复的数据
来源于:http://jingyan.baidu.com/article/414eccf6091ff86b431f0aec.html Cokery今天在帮助同事整理数据的时候遇到了一个难题,就是在Ex ...
- Hibernate-缓存
Hibernate是一个持久层框架,经常访问物理数据库.为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能.缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在 ...
- Extjs Window用法详解
今天我们来介绍一下Extjs中一个常用的控件Window.Window的作用是在页面中创建一个窗口,这个窗口作为容器,可以在它里面加入grid.form等控件,从而来实现更加复杂的界面逻辑. 本文的示 ...