# 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
, protected
和 public
修饰符
经常见到 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
方法的创建,也导致内存的增加