3

title: JS对象(1)重新认识面向对象
date: 2016-10-05
tags: JavaScript


0x00 面向对象

从设计模式上看,对象是计算机抽象现实世界的一种方式。

面向对象编程(object-oriented programming)的最主要目的是提高程序的重复使用性。

我们说,对象具有属性(property)和方法(function),(其实本质上方法也是一种属性)在面向对象的设计模式中,属性是一种描述,描述对象的状态,比如一个人一天 24 小时的情绪,每个时刻他的情绪都具有不同的状态,喜怒哀乐,抑郁癫狂,贪嗔痴念,当然情绪只是生而为人的众多状态中的一种而已。而对象中的方法是一种功能,它的功能就是操作对象的属性。比如,吃饭可以改变一个人的状态,对吃货而已,吃饱以后会觉得特别幸福,吃饭是一种功能,它改变了一个人的情绪,让人觉得幸福。

那么,反过来细想,人是一个对象,它具有情绪,他还要吃饭。所以可以说,对象是对属性和方法的一种封装。


0x01 南拳北腿

对象中的几个概念:

  • 一切事物皆对象

  • 对象具有封装和继承特性

  • 对象与对象之间使用消息通信,各自存在消息隐藏

然而,面向对象仅仅是一个概念或者编程思想而已,它不应该依赖于某个语言存在。比如 Java 采用面向对象思想构造其语言,它实现了类、继承、派生、多态、接口等机制。但是这些机制,只是实现面向对象编程的一种手段,而非必须。换言之,一门语言可以根据其自身特性选择合适的方式来实现面向对象。

JavaScript 语言是通过一种叫做原型(prototype) 的方式来实现面向对象编程的。

所以无论是基于类的(class-based)面向对象,还是 基于原型的 (prototype-based) 面向对象 也都只是为实现面向对象这一理念在构造客观世界的两种不同方式而已。


0x02 类 or 原型

在基于类的面向对象方式中,对象(object) 依靠 类(class) 来产生。而在基于原型的面向对象方式中,对象(object) 则是依靠 构造器(constructor) 利用 原型(prototype) 构造出来的。

而关于这两种方式谁更为彻底地表达了面向对象的思想,目前尚有争论。

类是一个抽象概念而并非实体,而对象的产生是一个实体的产生;
原型方式中的构造器 (constructor) 和原型 (prototype) 本身是其他对象通过原型方式构造出来的对象。

在类式面向对象语言中,对象的状态 (state) 由对象实例 (instance) 所持有,对象的行为方法 (method) 则由声明该对象的类所持有,并且只有对象的结构和方法能够被继承;而在原型式面向对象语言中,对象的行为、状态都属于对象本身,并且能够一起被继承。

JavaScript 是一种基于原型的编程语言,并没有 class 语句,而是把函数用作类。当然,ES6 提供了 class 的语法糖


0x03 JavaScript 中的面向对象

ECMAScript 是一门彻底的面向对象的编程语言,JavaScript 是其中的一个变种 (variant)。它提供了 6 种基本数据类型,即 Boolean、Number、String、Null、Undefined、Object。为了实现面向对象,ECMAScript设计出了一种非常成功的数据结构 - JSON(JavaScript Object Notation), 这一经典结构已经可以脱离语言而成为一种广泛应用的数据交互格式。

ECMAScript除了字面式声明(literal notation) 方式之外, 允许通过构造器(constructor) 创建对象。每个构造器实际上是一个函数(function) 对象, 该函数对象含有一个prototype属性用于实现基于原型的继承(prototype-based inheritance)共享属性(shared properties)。对象可以由 “new 关键字 + 构造器调用” 方式来创建。

但需要指出的是,这与java语言的new含义毫无关系,因为其对象构造的机理完全不同。ECMAScript 完全可以用其它非new表达式来用调用构造器创建对象。


0x04 原型链

涉及到继承这一块,Javascript 只有一种结构,那就是:对象。在 javaScript 中,每个对象都有一个指向它的原型对象(prototype)的内部属性_proto_属性。而这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)

一张经典的 JavaScript 原型链图可以揭示一切:

prototype chain

要理解上图,只需要搞明白两个属性(指针)的区别,即是_proto_ 属性和 prototype 属性。

  1. 每个构造函数都有一个 prototype 属性,该属性是一个指针,指向该构造函数的原型

  2. 而实例中包含一个属性 _proto_ 属性,该属性是一个指针,指向该实例所属的构造函数创建的原型

不同的浏览器对实例中的 _proto_ 属性表示不同。

每创建一个构造函数便会相应的创建一个与其相对应的原型对象(由构造函数创建出来的原型对象,JS 引擎自动创建)。

在默认情况下,构造函数创建的原型对象都会有一个 constructor 属性,而这个属性指向 prototype 属性所在的那个函数。

所以,我们可以得出一个结论,构造函数的 prototype 和由该构造函数创建的实例的 _proto_ 指向的是内存中的的同一块地址,也正是如此,才使得在原型中定义的方法和属性具有共享的基础。

有了 原型链,便可以定义一种所谓的 属性隐藏机制,并通过这种机制实现继承。ECMAScript 规定,当要给某个对象的属性赋值时,解释器会查找该对象原型链中第一个含有该属性的对象(注:原型本身就是一个对象,那么原型链即为一组对象的链。对象的原型链中的第一个对象是该对象本身)进行赋值。

所以,当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。


0x05 JavaScript 的类式继承实现

基于原型的继承方式,虽然实现了代码复用,但其行文松散且不够流畅,可阅读性差,不利于实现扩展和对源代码进行有效地组织管理。不得不承认,类式继承方式在语言实现上更具健壮性,且在构建可复用代码和组织架构程序方面具有明显的优势。


0x06 JavaScript 私有成员实现

JavaScript 没有实现面向对象中的信息隐藏,即私有和公有。与其他类式面向对象那样显式地声明私有公有成员的方式不同,JavaScript 的信息隐藏就是靠闭包实现的。


曼路
108 声望4 粉丝

宇宙一隅,活捉一只程序猿