Categories
程式開發

重学JavaScript01:就从面向对象说起吧


你真的理解Object吗?

Macmillan“的解释是:

a thing that you can see and touch” that is not alive” and is usuallysolid“一种你能够触摸到的实物,并非活物。

新华字典中,对象的基本含义是:

行动或思考时作为目标的人或事物特指恋爱的对方

“你是我的研究对象,你是我的学习对象,这是今天的讨论对象”但如果单独用,就成了恋爱对方。“这是我对象”

因此对象在中文世界中并非像英文所指的那么普世,于是对象成了计算机领域中的专有名词,代表一切事物的总称。

而从英文解释中,可以体会到,object是人类认知世界,而产生的一种思维抽象。什么意思呢?也就是人类成长过程中,对眼前事物的感知。

我是两个孩子的爸爸,我认真观察过他们,他俩小时候都是先用眼睛看,进入口欲期后用嘴巴感知,这个东西是红色,那个东西冰冰凉,再然后大人们教他们数数,OK,这是两个苹果。于是对象有了属性和值。

《面向对象分析与设计中》,Grady Booch 说,从人类认知角度来说,对象是:

一个可以触摸或者可以看见的东西;人的智力可以理解的东西;可以指导思考或行动(进行想象或施加动作)的东西。

对象的特征是:

对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象。对象有状态:对象具有状态,同一对象可能处于不同状态之下。对象具有行为:即对象的状态,可能因为它的行为产生变迁。

计算机中的Object

在计算机语言的设计中,不同的设计者利用着他们对object理解,来对object进行了描述,也就是我们今天耳熟能详的,比如Java、C++ 中基于“类”的面相对象编程的概念。

但Javascript有点特立独行,使用了“原型(Prototype“)”。

the first form of something new, made before it is produced in large quantities

这里先不展开。

JavaScript 的Object 之路

由于产生之时的政治原因,JS受管理层要求模仿Java,于是你看到了创始人 Brendan Eich 在“原型运行时”的基础上引入了 new、this 等语言特性,使之“看起来更像 Java”。ES6规范之前,甚至有大量的框架将JS改造成基于类来编程,而这样做的弊端明显要大于收益。

JavaScript 的对象特征

幸运的是,任何语言的运行时,类的概念都会被弱化。

对照上面说的,面向对象的三个特征:唯一、状态、行为

唯一标识性

一般,对所有的语言来说,对象的唯一标识性都是通过内存地址来实现的,对象具有唯一标识的内存地址,也就具有唯一标识了。

也就是为什么,下面的返回为false:

var o1 = { a: 1 };
var o2 = { a: 1 };
console.log(o1 === o2);

状态与行为

对不同的语言来说,状态和行为会使用不同的描述:

C++ 中的“成员变量”和“成员函数”Java中的“属性”和“方法”

而在JS中,状态和行为统一抽象成了属性(函数也是一种特殊的对象)。

如下例中,对对象o来说,d 和 f 就是两个普通的属性

var o = {
d: 1,
f() {
console.log(this.d);
}
};

也就是说,JS能很好地体现对象的基本特征。

JS对象的独有特色

但为什么很多人说“JS不是面向对象”的呢?因为JS的对象设计有其独有的特点,那就是:

对象具有高度的动态性,因为JS能在运行时被修改状态和行为。

你试过Java在运行时添加向对象添加属性吗?JS 能做到!

var o = { a: 1 };
o.b = 2;
console.log(o.a, o.b); //1 2

JS的属性

JS为了提高抽象能力,将属性设计成了(比别的语言)更加复杂的形势,提供了数据属性和*访问器属性*两类。也就是说,JS的属性并非简单的名称(键)和值,而是用了一组attribute(特征)来描述property(属性)。

数据属性

数据属性类似其他语言的“属性”,具有四个特征。

属性的值:value能否赋值:writable能否被(如for in)枚举:enumerable能否被更新(改变或删除):configurable

虽然,我们通常只关系值。

访问器属性

也就是getter/setter属性,它也有四个特征。

取值时调用:getter设置时调用:setter能否被(如for in)枚举:enumerable能否被更新(改变或删除):configurable

也就是说,读和写的时候,访问器属性可以执行代码,让读和写得到完全不同的值,可以看作一种函数的语法糖。

实践一下

这位客官问了,我怎么没设置过什么writable、enumerable、configurable啊?因为通常我们定义属性时,其默认值都是true,那我们怎么看到呢?getOwnPropertyDescripter

var o = { a: 1 };
o.b = 2;
//a和b皆为数据属性
Object.getOwnPropertyDescriptor(o,"a") // {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o,"b") // {value: 2, writable: true, enumerable: true, configurable: true}

那如何改变呢?defineProperty

var o = { a: 1 };
Object.defineProperty(o, "b", {value: 2, writable: false, enumerable: false, configurable: true});
//a和b都是数据属性,但特征值变化了
Object.getOwnPropertyDescriptor(o,"a"); // {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o,"b"); // {value: 2, writable: false, enumerable: false, configurable: true}
o.b = 3;
console.log(o.b); // 2

那访问器属性呢?

var o = { get a() { return 1 } };
o.a = 2
console.log(o.a); // 1

每次访问属性都会实行getter 或者setter 函数,因此o.a 每次都得到1。

总结一下, JS中的对象,实际上是一个“属性的集合”:

属性的key 为字符串或Symbol,属性的value 为数据属性特征值或者访问器属性特征值

上面例子中的的a就是key,而它的value,是{writable:true,value:1,configurable:true,enumerable:true}

因此,JS的对象与其他语言相比,有些另类。

特殊却普通

正如我们前面说到的:

任何语言的运行时,类的概念都会被弱化。

因此JS可以模仿多数面向对象编程范式,比如基于原型,甚至基于类。也正因此,JS是正如其语言标准所说的:JS是一门面向对象的语言。

既然这样,我们就不要机械地用JS来模仿其他语言,充分利用它的高度动态性的属性集合这一对象系统,挖掘它的能力吧。