《JS权威指南学习总结--9.2 类和构造函数》
内容要点:
例9-1展示了在JS中定义类的其中一种方法。但这种方法并不常用,毕竟它没有定义构造函数,构造函数是用来初始化新创建的对象的。
使用关键字new来调用构造函数会自动创建一个新对象,因此构造函数本身只需初始化这个新对象的状态即可。
调用构造函数的一个重要特征是,构造函数的prototype属性被用做新对象的原型。这意味着通过同一个构造函数创建的所有对象都继承自一个相同的对象,因此它们都是同一类的成员。
下例9-2对例9-1中的"范围类"做了修改,使用构造函数代替工厂函数:
一.例9-2 使用构造函数来定义"范围类"
//range2.js:表示值得范围的类的另一种实现
//这是一个构造函数,用以 初始化 创建的"范围对象"。注意,这里并没有创建并返回一个对象,仅仅是初始化。
function Range(from,to){
//存储"范围"对象的起始位置和结束位置(状态)
//这两个属性是不可继承的,每个对象都拥有唯一的属性
this.from = from;
this.to = to;
}
//所有的"范围对象"都继承自这个对象。注意,属性的名字必须是 "prototype"
Range.prototype = {
//如果x在这个范围内,则返回true,否则返回false
//这个方法可以比较数字范围,也可以比较字符串和日期范围
includes :function(x){ return this.from <=x && x<=this.to; },
//对于范围内的每个整数都调用一个f,这个方法只可用做数字范围
foreach : function(f){ for(var x = Math.ceil(this.from); x <=this.to;x++) f(x);},
//返回表示这个范围的字符串
toString : function(){ return "(" + this.from + "..." +this.to + ")"; }
};
//这里是使用"范围对象"的一些例子
var r = new Range(1,3); //创建一个范围对象
r.includes(2); //=>true:2 在这个范围内
r.foreach(console.log); //输出1 2 3
console.log(r.toString()); //输出(1...3)
console.log(r.constructor); //Object()
例9-1和例9-2代码比较:
首先,工厂函数range()转化为构造函数时被重命名为Range()。这里遵循了一个常见的编程约定:
从某种意义上讲,定义构造函数既是定义类,并且类名首字母要大写,而普通的函数和方法都是首字母小写。
再者,注意Range()构造函数是通过new关键字调用的,而range()工厂函数则不必使用new。
例9-1通过调用普通函数来创建新对象,例9-2则使用构造函数调用来创建新对象。
由于Range()构造函数是通过new关键字调用的,因此不必调用inherit()或其他什么逻辑来创建新对象。在调用构造函数之前就已经创建了新对象,通过this关键字可以获取这个新对象,Range()构造函数只不过时初始化this而已。构造函数甚至不必返回这个新创建的对象,构造函数会自动创建对象,然后将构造函数作为这个对象的方法来调用一次,最后返回这个对象。
事实上,构造函数的命名规则(首字母大写)和普通函数是如此不同还有另外一个原因,构造函数调用和普通函数调用时不尽相同的。构造函数就是用来"构造新对象"的,它必须通过关键字new调用,如果将构造函数用做普通函数的话,往往不会正常工作。
开发者可以通过命名约定来(构造函数首字母大写,普通函数首字母小写)判断是否应当在函数之前冠以关键字new。
例9-1和例9-2之间还有一个非常重要的区别,就是原型对象的命名。在第一段示例代码中的原型是range.methods。这种命名方式很方便同时具有很好的语义,但又过于随意。
在第二段示例代码中的原型是Range.prototype,这是一个强制的命名。对Range()构造函数的调用会自动使用Range.prototype作为Range对象的原型。
二.构造函数和类的标识
上文提到,原型对象是类的唯一标识:当且仅当两个对象继承自同一个原型对象时,它们才是属于同一个类的实例。而 初始化对象的状态 的 构造函数 则不能作为类的标识,两个构造函数的prototype属性可能指向同一个原型对象。那么这两个构造函数创建的实例是属于同一个类的。
尽管构造函数不像原型那么基础,但构造函数是类的"外在表现"。很明显的,构造函数的名字通常用做 类名。
比如,我们说Range()构造函数创建Range对象。然而,更根本地讲,当使用instanceof运算符来检测对象是否属于某个类时会用到构造函数。
假设这里有一个对象r,我们想知道r是否是Range对象,我们这样写:
r instanceof Range //如果r继承自Range.prototype,则返回true
实际上instanceof运算符并不会检查r是否由Range()构造函数初始化而来,而会检查r是否继承自Range.prototype。不过,instanceof的语法则强化了 "构造函数是类的公有标识"的概念。
三.constuctor属性
在例9-2中,将Range.prototype定义为一个新对象,这个对象包含类所需要的方法。其实没有必要新创建一个对象,用单个对象直接量的属性就可以方便地定义原型上的方法。
任何JS函数都可以用做构造函数,并且调用构造函数是需要用到一个prototype属性的。因此,每个JS函数(ES5中的Function.bind()方法返回的函数除外)都自动拥有一个prototype属性。
这个属性的值是一个对象,这个对象包含唯一一个不可枚举属性constructor。constructor属性的值是一个函数对象:
var F = function(){}; //这是一个函数对象
var p = F.prototype; //这是F相关联的原型对象
var c = p.constructor; //这是与原型相关联的函数
c === F //=>true:对于任意函数F.prototype.constructor==F
可以看到 构造函数的原型 中存在预先定义好的constructor属性,这意味着 对象 通常继承的constructor均指代它们的构造函数。由于构造函数是类的"公共标识",因此这个constructor属性为对象提供了类。
var o = new F(); //创建类F的一个对象
o.constructor ===F //=>true,constructor属性指代这个类
需要注意的是,实际上,例9-2中定义的Range类使用它自身的一个新对象重写预定义的Range.prototype对象。这个新定义的原型对象不含有constructor属性。因此Range类的实例也不含有constructor属性。我们可以通过补救措施来修正这个问题,显式给原型添加一个构造函数:
Range.prototype = {
constructor: Range, //显式设置构造函数反向引用
includes :function(x){ return this.from <=x && x<=this.to; },
foreach : function(f){ for(var x = Math.ceil(this.from); x <=this.to;x++) f(x);},
toString : function(){ return "(" + this.from + "..." +this.to + ")"; }
};
另外一种常见的解决办法是使用预定义的原型对象,预定义的原型对象包含constructor属性,然后依次给原型对象添加方法:
//扩展预定义的Range.prototype对象,而不重写之,这样就自动创建Range.prototype.constructor属性
Range.prototype.includes = function(x){ return this.from <=x && x<=this.to; };
Range.prototype.foreach=function(f){ for(var x = Math.ceil(this.from); x <=this.to;x++) f(x);};
Range.prototype.toString=function(){ return "(" + this.from + "..." +this.to + ")"; };
console.log(r.constructor); //Range(from,to){...}
《JS权威指南学习总结--9.2 类和构造函数》的更多相关文章
- 简单物联网:外网访问内网路由器下树莓派Flask服务器
最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...
- 利用ssh反向代理以及autossh实现从外网连接内网服务器
前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...
- 外网访问内网Docker容器
外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...
- 外网访问内网SpringBoot
外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...
- 外网访问内网Elasticsearch WEB
外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...
- 怎样从外网访问内网Rails
外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...
- 怎样从外网访问内网Memcached数据库
外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...
- 怎样从外网访问内网CouchDB数据库
外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...
- 怎样从外网访问内网DB2数据库
外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...
- 怎样从外网访问内网OpenLDAP数据库
外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...
随机推荐
- Mysql连表查询
http://blog.csdn.net/qmhball/article/details/8000003 可以参考这篇文章
- 巧用ajax请求服务器加载数据列表时提示loading
我们利用weui.js中的weui.loading为效果,ajax的beforeSend与complete方法,做一个加载数据时会有几秒的 loading... 要在页面需要加载的JS文件: < ...
- 在转换为 UTC 时大于 DateTime.MaxValue 或小于 DateTime.MinValue 的 DateTime 值无法系列化为 JSON
看到这个错往往会感到莫名其妙! 其原因其实是数据库的DateTime最小值和C#的DateTime不一样 Sql Server DateTime 类型必须介于 1/1/1753 12:00:00 AM ...
- mongodb tip-1
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px "Helvetica Neue"; color: #454545 } p. ...
- [MFC美化] SkinMagic使用详解1- SkinMagic使用流程
[SkinMagic使用流程] 1.工程配置SkinMagic相关文件 2.初始化SkinMagic皮肤文件,窗体加载皮肤 3.释放皮肤资源 特别声明,SkinMagic要是破解版的,如果不是,可能需 ...
- Beego学习笔记——Config
配置文件解析 这是一个用来解析文件的库,它的设计思路来自于database/sql,目前支持解析的文件格式有ini.json.xml.yaml,可以通过如下方式进行安装: go get github. ...
- 获取XMLHttpRequest对象
//创建一个XMLHttpRequest对象 ,利用此对象与服务器进行通信 是AJAX技术的核心 /////////////////////////////////////////////////// ...
- CodeForces 709C Letters Cyclic Shift
贪心. 肯定是两个$a$之间的那些字符都$-1$,没有$a$就全部$-1$.如果输入的串全是$a$,那么把最后一个$a$改成$z$. #pragma comment(linker, "/ST ...
- [Q]安装问题(找不到InstallUtilLib.dll)
安装时提示 使用下面的方法解决(参考) 一.如果您的系统提示“没有找到Installutillib.Dll”或者“缺少Installutillib.Dll”等类似错误信息,请把Installutill ...
- [Q]系统环境改变导致“未注册”的解决方法
据用户反映设置账户开机密码后显示未注册, 具体表现: 1. 重装试用版,重新获取注册申请码,发现注册申请码跟原来没有发生变化. 2. 重新使用原来的授权文件注册,但打开后显示未注册. 3. 发现“** ...