内容要点:

例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 类和构造函数》的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. .net在网页中生成二维码和条形码

    二维码: 1.下载ThoughtWorks.QRCode.dll文件 2.创建Web项目,添加引用刚才下载的文件 3.在项目中添加aspx窗体,编写代码如下 <%@ Page Language= ...

  2. 瑶瑶GBK好的,UTF-8卡死

    请求地址: 开发环境核心 esb : http://10.15.22.120:8866/0203000007/EmpAndDptRelateInfoSync/V1 用gbk可以马上返回. 用utf-8 ...

  3. GridView”的控件 必须放在具有 runat=server 的窗体标记内 “错误提示”

    在做导出数据到EXCEL程序中,出现了错误提示:类型“GridView”的控件“GridView1”必须放在具有 runat=server 的窗体标记 解决办法  重写 VerifyRendering ...

  4. 第9章 创建Web数据库

    1.登录MySQL: mysql -h hostname -u username -p password *-h 用于指定所希望连接的主机,即运行MySQL服务器的机器: -u 用于指定连接数据库时使 ...

  5. dirty cow exp

    公司搞底层的改了一下,说做到了几个不死机 /* * (un)comment correct payload first (x86 or x64)! * * $ gcc cowroot.c -o cow ...

  6. [Android]Android SDk Manager中创建模拟器无法选择CPU问题解析

    方法一.正常下载所需sdk包 启动 Android SDK Manager ,打开主界面,依次选择「Tools」.「Options...」,弹出『Android SDK Manager - Setti ...

  7. .net core 11

  8. 加速Android Studio的Gradle构建速度

    在利用Android Studio做项目时,发现随着项目内资源的逐渐增多(或者项目创建时间太过久远,而又未经常打开),Android Studio的build速度也越来越慢.(P.S.在做我的CSGO ...

  9. 微信小程序 textarea 简易解决方案

    微信小程序中textarea没有bindchange事件,所以无法在输入时给变量赋值. 虽然可以使用bindblur事件,但是绑定bindblur事件,如果再点击按钮,则先执行完按钮事件后,再去执行b ...

  10. LeetCode-448. Find All Numbers Disappeared in an Array C#

    Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and ot ...