# JS 面向对象编程

ECMAScript是基于原型实现的面向对象编程语言。

基于原型的OOP和基于静态类的方式直接有很多差异。 让我们一起来看看他们直接详细的差异。

# 基于类特性和基于原型

# 基于静态类

在基于类的模型中,有个关于类和实例的概念。 类的实例也常常被命名为对象或范例

类与对象

类代表了一个实例(也就是对象)的抽象。

例如(这里和下面的例子都是伪代码):

C = Class {a, b, c} // 类C, 包括特性a, b, c

实例的特点是:属性(对象描述 )和方法(对象活动)。属性又包含一些特性比如是否可写的,可配置,可设置的( getter/setter )等。

对象存储了状态 (即在一个类中描述的所有属性的具体值),类为他们的实例定义了严格不变的结构(属性)和严格不变的行为(方法)

C = Class {a, b, c, method1, method2}
 
c1 = {a: 10, b: 20, c: 30} // 类C是实例:对象с1
c2 = {a: 50, b: 60, c: 70} // 类C是实例:对象с2,拥有自己的状态(也就是属性值)
``

**层次继承**

为了提高代码重用,类可以从一个扩展为另一个,在加上额外的信息。 这种机制被称为(分层)继承 

```js
D = Class extends C = {d, e} // {a, b, c, d, e}
d1 = {a: 10, b: 20, c: 30, d: 40, e: 50}

在类的实例上调用方的时候,通常会先在原生类中查找该方法,如果没找到就去父类去查找,如果还没找到,就到父类的父类去查找(类似原型链),如果查到继承的顶部还没查到,那结果就是:该对象没有类似的行为,也没办法获取结果

d1.method1() // D.method1 (no) -> C.method1 (yes)
d1.method5() // D.method5 (no) -> C.method5 (no) -> no result

基于类的关键概念

我们有如下关键概念:

  • 创建一个对象之前,必须声明类

  • 因此,该对象将由抽象成自身“结构和行为”的类里创建

  • 方法是通过了严格的,直接的,一成不变的继承链来处理

  • 子类包含了继承链中所有的属性

  • 创建类实例,类不能(因为静态模型)来改变其实例的特征(属性或方法)

  • 实例(因为严格的静态模型)除了有该实例所对应类里声明的行为和属性以外,是不能额外的行为或属性的

让我们看看在JavaScript里如何替代OOP模型,也就是我们所建议的基于原型的OOP

# 基于原型

这里的基本概念是动态可变对象。下面这样的对象可以独立存储他们所有的特性(属性,方法)

object = {a: 10, b: 20, c: 30, method: fn};
object.a; // 10
object.c; // 30
object.method();

此外,由于动态的,他们可以很容易地改变(添加,删除,修改)自己的特性:

object.method5 = function () {...}; // 添加新方法
object.d = 40; // 添加新属性 "d"
delete object.c; // 删除属性 "с"
object.a = 100; // 修改属性 "а"
 
// 结果是: object: {a: 100, b: 20, d: 40, method: fn, method5: fn};

也就是说,在赋值的时候,如果某些特性不存在,则创建它,如果它存在,就只是更新

原型链实现继承

原型链实现继承不是通过扩展类来实现的,而是通过原型来实现的,任何对象都可以被成为另一个对象的原型对象

下面是关于原型链继承的伪代码:

x = {a: 10, b: 20};
y = {a: 40, c: 50};
y.[[Prototype]] = x; // x是y的原型
 
y.a; // 40, 自身特性
y.c; // 50, 也是自身特性
y.b; // 20 – 从原型中获取: y.b (no) -> y.[[Prototype]].b (yes): 20
 
delete y.a; // 删除自身的"а"
y.a; // 10 – 从原型中获取
 
z = {a: 100, e: 50}
y.[[Prototype]] = z; // 将y的原型修改为z
y.a; // 100 – 从原型z中获取
y.e // 50, 也是从从原型z中获取
 
z.q = 200 // 添加新属性到原型上
y.q // 修改也适用于y

这个例子展示了原型作为辅助对象属性的重要功能和机制,和自身属性相比,这些原型上的属性是委托属性。这个机制被称为委托,并且基于它的原型模型是一个委托的原型(或基于委托的原型 )。引用的机制在这里称为发送信息到对象上,如果这个对象得不到响应就会委托给原型来查找(要求它尝试响应消息)

在这种情况下的代码重用被称为基于委托的继承或基于原型的继承。由于任何对象可以当成原型,也就是说原型也可以有自己的原型。 这些原型连接在一起形成一个所谓的原型链。 链也像静态类中分层次的,但是它可以很容易地重新排列,改变层次和结构

x = {a: 10}
 
y = {b: 20}
y.[[Prototype]] = x
 
z = {c: 30}
z.[[Prototype]] = y
 
z.a // 10
 
// z.a 在原型链里查到:
// z.a (no) ->
// z.[[Prototype]].a (no) ->
// z.[[Prototype]].[[Prototype]].a (yes): 10

基于原型的概念总结

  • 基本概念是对象

  • 对象是完全动态可变的(理论上完全可以从一个类型转化到另一个类型)

  • 对象没有描述自己的结构和行为的严格类,对象不需要类

  • 对象如果不能响应消息的话可以委托给原型

  • 在运行时随时可以改变对象的原型

  • 在基于委托的模型中,改变原型的特点,将影响到与该原型相关的所有对象

# 各种OOP实现的其它特性

多态

在ECMAScript中对象有几种含义的多态性。

例如,一个函数可以应用于不同的对象,就像原生对象的特性(因为这个值在进入执行上下文时确定的):

function test() {
  alert([this.a, this.b]);
}
 
test.call({a: 10, b: 20}); // 10, 20
test.call({a: 100, b: 200}); // 100, 200
 
var a = 1;
var b = 2;
 
test(); // 1, 2

不过,也有例外:Date.prototype.getTime() 方法,根据标准这个值总是应该有一个日期对象,否则就会抛出异常

lert(Date.prototype.getTime.call(new Date())); // time
alert(Date.prototype.getTime.call(new String(''))); // TypeError

上面的例子也可以被视为是一种参数多态性

封装

封装的基本目的是一个从辅助数据的用户中抽象出来,而不是一个防止黑客而隐藏数据。 更严重的,封装不是用 private 修饰数据而达到软件安全的目的。

ECMAScript规范的当前版本,没有定义 private, protectedpublic 修饰符

经常见到 JS版封装 方法

function A() {
 
  var _a; // "private" a
 
  this.getA = function _getA() {
    return _a;
  };
 
  this.setA = function _setA(a) {
    _a = a;
  };
 
}
 
var a = new A();
 
a.setA(10);
alert(a._a); // undefined, "private"
alert(a.getA()); // 10

要注意 ECMA-262-3 标准里没有定义任何关于“私有方法”的概念,上面这种只是模拟实现封装,而且 getA/setA 方法的创建,也导致内存的增加