内容要点:

例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. python调用ansible api 2.0 运行playbook带callback返回

    # -*- coding:utf8 -*- ''' Created on 2017年1月13日 @author: qiancheng ''' import os import json from co ...

  2. trove显示更多flavor信息

    https://review.openstack.org/#/c/352786/12 这是我目前提交的commit,如果想添加新的flavor信息可以参考这个,有几个需要注意的点是在跑py27的时候, ...

  3. GTK+2.0学习——第一个GTK程序

    #include <gtk/gtk.h> #include <stdio.h> #include <stdlib.h> /* *点击了关闭按钮之后的回调函数 *gt ...

  4. 浅谈javascript中stopImmediatePropagation函数和stopPropagation函数的区别

    在事件处理程序中,每个事件处理程序中间都会有一个event对象,而这个event对象有两个方法,一个是stopPropagation方法,一个是stopImmediatePropagation方法,两 ...

  5. 运行ORB-SLAM笔记_编译篇(一)

    1.下载代码   https://github.com/raulmur/ORB_SLAM/    (同时也可以看看作者的牛叉论文,我是打算先用代码,再回头看论文) 2.打开后如下 就好像是用一件新产品 ...

  6. python字符串及正则表达式[转]

    原文链接:http://www.cnblogs.com/guojidong/archive/2012/12/20/2826388.html 字符串: 正则表达式 正则表达式元字符与语法图: 注意事项: ...

  7. [Q]自定义保存位置及文件名

    以“DWG To PDF.pc3”打印为例: 说明:<DrawingDirectory> 当前图纸所在目录<DrawingFolderName> 当前图纸文件所在文件夹名称&l ...

  8. Chapter 17_4 终结器

    Lua中的垃圾回收主要是针对Lua对象,但是也可以做一些额外的资源管理工作. 可以为表设定垃圾收集的元方法(对于完全用户数据,则需要使用C API),该元方法称为 终结器. Lua用"__g ...

  9. Chapter 17_3 table的默认值

    对象属性 有很多情况需要把有些属性绑定到某个对象,例如:函数与其名称.table的默认值.数组大小等... 当对象是一个table时,可以通过适当的key将属性存储在这个table中. 如果对象不是一 ...

  10. ECOS-LNMP ZendGuard

    因为ECOS产品加密后依赖PHP Zendguard运行(基于PHP5.3加密)  备注: Shopex485商城基于php5.2进行加密 URL:http://www.zend.com/en/pro ...