《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 安装并启动 ...
随机推荐
- 【完全背包】HDU 1284 钱币兑换问题
Problem Description 在一个国家仅有1分,2分,3分硬币,将钱N兑换成硬币有很多种兑法.请你编程序计算出共有多少种兑法. Input 每行只有一个正整数N,N小于32768. Out ...
- 使用node.js编写脚本将JSON数据转换为SQL语句
安装依赖模块 当node.js脚本在运行的时候,需要很多支持模块,这些模块存储在node_modules文件夹中.该脚本在执行过程中需要使用到fs.string-format两个支持模块,作用分别是: ...
- socket select()模型
转载:http://www.cnblogs.com/xiangshancuizhu/archive/2012/10/05/2711882.html 由于socket recv()方法是阻塞式的,当有多 ...
- Python基础知识学习_Day6
一.time&datetime模块 常用选项如下: import time print(time.asctime()) #返回时间格式 print(time.localtime())#返回本地 ...
- C#如何实现url短地址?C#短网址压缩算法与短网址原理入门
c# url短地址压缩算法与短网址原理的例子,详细介绍了短网址的映射算法,将长网址md5生成32位签名串,分为4段,每段8个字节,然后生成短网址,具体见文本实例. 短网址映射算法: 将长网址md5生成 ...
- String与StringBuild、StringBuffer的区别
String与StringBuild.StringBuffer的区别相信困扰了好多新入门的JAVA程序员,而这也是笔试和面试的一道常见题型,如何全面的回答该问题,变得尤为重要. 首先我们需要清楚一点, ...
- CodeForces 687C The Values You Can Make
$dp$,背包. $f[i][j][s]$表示前$i$个物品,凑出$j$价格的情况下,能否凑出$s$价格,$f[i][j][s]=1$表示能,否则不能. 转移很简单:如果$f[i][j][s]=1$, ...
- json的遍历
第一种json结构: var jsongood = {"goods":[{"parentId":"null","productId ...
- JavaScript基础(更新第二波)
下面接着说JavaScript打开新的窗口. open()方法可以查找一个已经存在或者新建的浏览器窗口. 语法: window.open([URL]),[窗口名称],[参数字符串] 参数说明: URL ...
- 2014年蓝桥杯预选赛 C/C++ 本科A组试题--切面条
//主要是要找到f(n)=2*f(n-1)-1的规律. #include <stdio.h> #include <math.h> int f(int n) { if(n==0) ...