我们将讨论一个老问题:在JavaScript中创建对象的构造函数。

存在的问题

假设我们想要创建最典型的面向对象设计的示例:Circle类。假设我们正在为一个简单的Canvas库编写一个Circle。除此之外,我们可能想知道如何做到以下几点:

  • 在给定的画布Canvas上画一个给定的圆Circle。
  • 记录所做的圆圈的总数。
  • 跟踪给定圆的半径,以及如何对其值施加不变量。
  • 计算给定圆的面积。

当前的JS习惯说法是,我们应该首先创建一个函数,作为构造函数;然后将我们可能想要的任何属性添加到函数本身,然后用一个对象Object替换构造函数的prototype属性。这个prototype对象将包含构造函数创建的实例对象开始时应该包含的所有属性。即使是一个简单的例子,当你把它全部写出来的时候,这最终会变成一大堆样板文件:

function Circle(radius) {
this.radius = radius;
Circle.circlesMade++;//circlesMade是与实例无关的属性
} Circle.draw = function draw(circle, canvas) { /* Canvas drawing code */ } Object.defineProperty(Circle, "circlesMade", {
get: function() {
return !this._count ? 0 : this._count;
}, set: function(val) {
this._count = val;
}
}); Circle.prototype = {
area: function area() {
return Math.pow(this.radius, 2) * Math.PI;
}
}; Object.defineProperty(Circle.prototype, "radius", {
get: function() {
return this._radius;
}, set: function(radius) {
if (!Number.isInteger(radius))
throw new Error("Circle radius must be an integer.");
this._radius = radius;
}
});

代码不仅很麻烦,而且也很不直观。它需要对函数的工作方式以及已安装的各种属性是如何进入创建的实例对象的有相当深入的了解。如果这个方法看起来很复杂,不要担心。这篇文章的重点是展示一种更简单的代码编写方法。

方法定义的语法

ES6提供了一种向对象添加特殊属性的新语法。虽然很容易将area方法添加到Circle.prototype中,为radius添加getter/setter对感觉要就复杂多了。随着JS转向更面向对象的方法,人们开始对设计更简洁的方法来添加对象访问器感兴趣。我们需要一种新的方式来添加方法到一个对象,就像方法已经使用obj.prop = method被添加到obj中一样。不需要使用Object.defineProperty这样复杂的API。人们希望能够轻松地做以下事情:

  1. 向对象添加普通函数属性。
  2. 向对象添加生成器函数属性。
  3. 向对象添加普通访问器函数属性。
  4. 添加上面的任何一个,就像在最终的对象上用[]语法做过一样。我们称这些为Computed属性名(Computed property names)

有些事情以前是做不到的。例如,没有办法定义一个getter或setter来给obj.prop赋值。因此,必须添加新的语法。你现在可以写这样的代码:

var obj = {
// 方法被添加而不带function关键字,使用属性名作为函数名。
method(args) { ... }, // 让方法称为一个生成器,在方法名前加一个*号
*genMethod(args) { ... }, // 在|get|和|set|的帮助下,访问器(accessors)现在可以用内联写法。 // 注意,以这种方式注册的getter不能有参数
get propName() { ... }, // 注意,以这种方式注册的setter只能有一个参数
set propName(arg) { ... }, // 为了解决上面的第四个问题,[] 语法可以出现在任何方法名出现的地方。
// 这里可以使用Symbols,调用函数,连接字符串,或者其他任何可以产生一个属性id的表达式。
// 这个语法对访问器(accessors)和生成器(generators)也起作用。
[functionThatReturnsPropertyName()] (args) { ... }
};

使用这个新的语法,我们现在可以重写上面的代码片段了:

function Circle(radius) {
this.radius = radius;
Circle.circlesMade++;
} Circle.draw = function draw(circle, canvas) { /* Canvas drawing code */ } Object.defineProperty(Circle, "circlesMade", {
get: function() {
return !this._count ? 0 : this._count;
}, set: function(val) {
this._count = val;
}
}); Circle.prototype = {
area() {
return Math.pow(this.radius, 2) * Math.PI;
}, get radius() {
return this._radius;
},
set radius(radius) {
if (!Number.isInteger(radius))
throw new Error("Circle radius must be an integer.");
this._radius = radius;
}
};

这段代码与上面的代码片段并不完全相同。对象字面量中的方法定义注册为可配置和可枚举的,而注册在第一个代码段中的访问器(get,set)将是不可配置和不可枚举的。在实践中,这一点很少被注意到,为了简洁起见,我决定省略上面的可列举性和可配置性。

不过,情况正在好转,对吧?不幸的是,即使有了这种新的方法定义语法,对于Circle的定义我们也无能为力,因为我们还没有定义函数。在定义函数的时候,没有办法给它赋予属性。

类定义语法

虽然这更好了,但仍然不能满足那些想要用JavaScript实现面向对象设计的更清晰解决方案的人。他们认为,其他语言有一个用于处理面向对象设计的构造,这个构造称为

很好。那么,让我们给JS添加

我们想要一个允许我们向有命名构造函数添加方法,并向它的.prototype添加方法的系统,这样它们就会出现在类的构造实例中。既然我们有了新奇的新方法定义语法,我们肯定应该使用它。然后,我们只需要一种方法来区分哪些是在类的所有实例上通用的,哪些函数是特定于给定实例的。在c++或Java中,关键字是static。看起来和其他的一样好。让我们使用它。

现在,如果有一种方法可以指定其中一个方法作为构造函数被调用,那将是很有用的。在c++或Java中,它将被命名为与类相同的名称,没有返回类型。因为JS没有返回类型,我们需要一个.constructor属性,为了向后兼容,我们把这个方法称为构造函数constructor

把它们组合在一起,我们就可以重写Circle类:

class Circle {
constructor(radius) {
this.radius = radius;
Circle.circlesMade++;
}; static draw(circle, canvas) {
// Canvas drawing code
}; static get circlesMade() {
return !this._count ? 0 : this._count;
};
static set circlesMade(val) {
this._count = val;
}; area() {
return Math.pow(this.radius, 2) * Math.PI;
}; get radius() {
return this._radius;
};
set radius(radius) {
if (!Number.isInteger(radius))
throw new Error("Circle radius must be an integer.");
this._radius = radius;
};
}

我们不仅可以把所有与圆相关的东西放在一起,而且所有东西看起来都很干净。这肯定比我们开始的时候好。即便如此,一些人可能还会有疑问。我将尝试预测并解决以下问题:

  • 分号是怎么回事?

    为了“让东西看起来更像传统的类”,我们决定使用更传统的分隔符。不喜欢它吗?它是可选的。分隔符不是必须的。

  • 如果我不想要构造函数,但仍然想在创建的对象上放置方法,该怎么办?

    这没问题。构造函数方法是完全可选的。如果你没有提供一个,默认情况下会补充一个空的构造函数constructor() {}

  • 构造函数可以是生成器吗?

    不可以。添加一个非普通方法的构造函数将导致TypeError。这包括生成器和访问器。

  • 我可以用计算属性名([computed property name])定义构造函数吗?

    不可以。。这很难检测,所以我们就不试了。如果你用一个计算属性名定义了一个方法,这个方法最终返回constructor,你仍然会得到一个命名为constructor的方法,它最终不是类的构造函数。

  • 如果我改变Circle的值呢?这会导致新的Circle行为错误吗?

    不会的!与函数表达式非常相似,类获得其指定名称的内部绑定。外部力量无法更改此绑定,因此无论您在外围作用域Circle中将Circle变量设置为什么。构造函数中的circlesMade++将按预期运行。

[ES6深度解析]12:Classes的更多相关文章

  1. [ES6深度解析]15:模块 Module

    JavaScript项目已经发展到令人瞠目结舌的规模,社区已经开发了用于大规模工作的工具.你需要的最基本的东西之一是一个模块系统,这是一种将你的工作分散到多个文件和目录的方法--但仍然要确保你的所有代 ...

  2. ES6深度解析3:Generators

    介绍ES6 Generators 什么是Generators(生成器函数)?让我们先来看看一个例子. function* quips(name) { yield "hello " ...

  3. [ES6深度解析]13:let const

    当Brendan Eich在1995年设计了JavaScript的第一个版本时,他犯了很多错误,包括从那时起就成为该语言一部分的一些错误,比如Date对象和当你不小心将它们相乘时对象会自动转换为NaN ...

  4. [ES6深度解析]14:子类 Subclassing

    我们描述了ES6中添加的新类系统,用于处理创建对象构造函数的琐碎情况.我们展示了如何使用它来编写如下代码: class Circle { constructor(radius) { this.radi ...

  5. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  6. spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理

    @Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...

  7. Spring源码深度解析之事务

    Spring源码深度解析之事务 目录 一.JDBC方式下的事务使用示例 (1)创建数据表结构 (2)创建对应数据表的PO (3)创建表和实体之间的映射 (4)创建数据操作接口 (5)创建数据操作接口实 ...

  8. [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析

    [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...

  9. Deep Learning模型之:CNN卷积神经网络(一)深度解析CNN

    http://m.blog.csdn.net/blog/wu010555688/24487301 本文整理了网上几位大牛的博客,详细地讲解了CNN的基础结构与核心思想,欢迎交流. [1]Deep le ...

随机推荐

  1. 简单学习java内存马

    看了雷石的内存马深入浅出,就心血来潮看了看,由于本人java贼菜就不介绍原理了,本文有关知识都贴链接吧 前置知识 本次主要看的是tomcat的内存马,所以前置知识有下列 1.tomcat结构,tomc ...

  2. 题解 guP4552 IncDec Sequence

    这道题是一道差分的题目 差分数组p即p[i]=a[i]-a[i-1] 如果我们把一个区间[l,r]里的数+1,那么我们不难发现p[l]'=a[l]+1-a[l-1]=p[l]+1,p[r+1]'=a[ ...

  3. 团队开发day03

    完成安卓的登录和注册界面的设计,进行服务器端的开发,设计javabean实体 映射,零售商 ,商品,品牌商,订单类的构建 遇到问题:安卓发起网络请求,客户端回应请求,数据处理设置. 使用传统的方法 / ...

  4. 写了这么多年 CSS,initial 和 inherit 以及 unset 和 revert 还傻傻分不清楚?

    经常会碰到,问一个 CSS 属性,例如 position 有多少取值. 通常的回答是 static.relative.absolute 和 fixed .当然,还有一个稍微生僻的 sticky .其实 ...

  5. linux U盘安装系统工具usb-creator-gtk

    linux平台下U盘安装linux系统的工具.它是ubuntu自带的工具,将建时将覆盖U盘中的所有内容.

  6. P2015

    二叉苹果树 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include< ...

  7. [考试总结]noip18

    发现之前咕掉了这个考试的总结. 今天就把它给补上. 这也是一个炸裂的一场 开局以为 \(T1\) 可做,然而事实证明我又错了... 莽了一个随机化上去,轻松过了所有样例... 以为稳了 然而挂掉了.. ...

  8. 纯C语言(C89)实现简单链表

    起因 工作很少接触纯C项目,业余写着玩玩,不断雕琢 目标 纯C实现简单链表,提供方便易用泛型接口,避免依赖 实现 完全封装,隐藏结构体细节,不支持栈创建 拷贝存储,轻微性能代价换来易用性 list.h ...

  9. 大数据学习(17)—— HBase表设计

    为啥要把表设计拿出来独立成章?因为我觉得像我这样搞了很多年Java后端开发的技术人员,在学习HBase的时候,会受到关系型数据库3NF.BCNF的影响.事实上,数据库范式在HBase里完全没用,必须转 ...

  10. 移动APP我们需要关注什么

    移动APP关注的点比web或者PC上的程序更多 1.测试用例的设计 移动互联网的快节奏,要放弃传统的测试用例编写方式,不需要写详细的测试用例,采用罗列测试点的方式如思维导图,这样既节省时间又能够直观清 ...