【JavaScript】 JS面向对象的模式与实践

 

参考书籍编程

《JavaScript高级语言程序设计》—— Nicholas C.Zakas
《你不知道的JavaScript》  —— KYLE SIMPSON
 
在JS的面向对象编程中,咱们最为关注的是两种行为,一是建立对象二是类继承
 

JS建立对象

一.构造函数模式建立对象

第一种建立对象的方式是构造函数模式
 
以下所示, 将构造函数中的属性和方法赋给一个新对象
/**
 * description: 构造函数模式建立对象
 */
function Type (p) {
  this.param = p; // 定义属性
  this.method = function () { // 定义方法
    return this.param;
  }
}
          
var obj1 = new Type(1); // {param:1, method: function(){...}}
var obj2 = new Type(2); // {param:2, method: function(){...}}
console.log(obj1.method()) // 输出1
console.log(obj2.method()) // 输出2

 

 
当一个函数被加以new操做符前缀,它就会被当成一个构造函数调用并返回一个对象。因此构造函数和普通函数在形式上没有差异,区别只是在于有没有和new操做符搭配调用而已。
 
通常来讲,咱们知道,一个方法没有明确的调用对象时候,this的指向将是window, 但在构造函数建立对象时候, new操做符改变了this的指向, 使其和新建立的对象绑定。 因此构造函数体内执行的代码至关于:
var obj =  new Object;
obj.param = p;
obj.method = function () { ... };

 

二.原型模式建立对象

构造函数的缺陷与加入原型的缘由

 
咱们知道, 原型(prototype)已经不知不觉地加入到JS面向对象的你们庭里面来了, 但是他当初是如何被邀请进这个家庭里面的呢? 实际上,这个“邀请人”正是构造函数。
 
利用构造函数建立对象看起来并没有不妥,但它有个关键的问题: 冗余的函数建立。
例如上文中咱们建立obj1和obj2的过程当中,method这个方法被重复建立了两次
var obj1 = new Type(1); // {param:1, method: function(){...}}
var obj2 = new Type(2); // {param:2, method: function(){...}}

 

 
咱们发现, 和param属性相反, method方法对不一样的对象来讲函数体是相同的,重复建立函数是一种对内存的浪费
this.method = function () { // 定义方法
  return this.param;
} 

 

因此你们就想: 将其变成一个能够共享的方法或许更合适一些, 因而prototype就出如今JS面向对象的舞台上了。加入原型的缘由,一大部分缘由就是为了实现函数复用, 弥补构造函数的缺陷
 

构造函数和原型的关系

在JS的OO中,咱们能够把对象属性分为两部分:
 
  • 一部分是不一样对象各自独有的属性, 例如上文中的param. 在需求上,咱们但愿不一样的对象拥有不一样的param
  • 一部分是不一样对象间共享的属性, 例如上文中的method方法,在需求上,咱们但愿不一样的对象共同使用同一个method。
 
原型就是承载这一部分共享属性的载体,它是一个对象,叫作prototype
 
同时这个prototype对象仍是在每一个函数(或构造函数)在一开始就带有的属性。
 
因此总体思路是: 对于共享的那部分属性,从构造函数中“搬”到prototype对象中来:
 
因而咱们的代码从:
function Type (p) {
  this.param = p; // 定义属性
  this.method = function () { // 定义方法
    return this.param;
  }
}

 

变成了:
function Type (p) {
  this.param = p; // 不一样对象各自独有的属性
}
Type.prototype.method = function () { // 不一样对象共享的属性
  return this.param;
}

 

这样的话,咱们在利用构造函数建立对象的时候, 就不会作重复建立method方法这个多余的行为了,而是直接根据对象和构造函数的关系,直接从构造函数(Type.prototype)中的原型对象中取得method。
 
那么问题来了, 怎么在某个对象中取得method方法?
 

原型能是个普通对象吗?(反向探究原型的工做机制)

若是你了解过JS,你固然知道原型有一系列的机制,但我如今先不讲这些,而是从“prototype若是是个普通的对象会怎样”, 反向探究原型建立对象须要哪些必要的工做机制
 
若是原型只是一个普通地不能再普通的对象的话——
 
1. 若是原型只是个普通对象,在被建立的对象中取得在原型中的属性将会很麻烦
 
须要经过Object.getPrototypeOf取得原型对象的引用,而后才能从原型对象中取出method方法 。。。。 并且由于this绑定丢失的问题,方法还得用call调用!
 
function Type (p) {
  this.param = p; // 不一样对象各自独有的属性
}
 
Type.prototype.method = function () { // 不一样对象共享的属性
  return this.param;
}
          
// 卧槽,卧槽! 要是这么麻烦我干脆别用原型了
var obj = new Type(1);
// 超级麻烦!
console.log(Object.getPrototypeOf(obj).method.call(obj)) // 输出1
// 也很麻烦!同时这种简化破环了咱们面向对象的初衷
console.log(Type.prototype.method.call(obj)) //输出1    

 

 
反向思考: 原型的机制应该使得对象中能用很简单的方式使用原型中的属性, 最好是:,对象obj能直接经过obj.method访问其构造函数的原型对象中的属性, 这样的话,不管属性在构造函数的this中,仍是在prototype对象,建立的对象使用该属性的方式都是相同的! 这样,具体的实现就变成了和使用无关的“黑盒” 。
 
 
2. 若是原型只是个普通对象, 你将不得不考虑prototype对象中数据的维护问题 由于在这种假设下,prototype对象是全部对象的单一数据源, 因此对象A若是重写了该方法,对象B使用的就不是一开始prototype对象中定义的方法, 而是对象A修改事后的方法,而全部其余对象也和对象B同样。 这样的话,咱们就不能在某个对象中重写原型中的方法了。
 
反向思考: 原型的机制应当确保prototype对象中属性的稳定性, 换句话说,就是在某个对象重写原型中具备的属性时候, 仅仅起到覆盖原型中的同名属性做用就能够了, 而不要不要修改原型中的属性!
 
因此,JavaScript中的“原型”固然不是一个普通的对象,它是prototype对象以及背后的一系列机制造成的一个“总体”!
 
其实在上面“反向思考”的描述里,我已经说明了原型的工做机制了,不过下面仍是更具体地说明一下:
 

原型的工做机制

1. 在用new 操做符建立对象实例的时候, 该对象将得到一个指向其构造函数的原型对象的内部指针,所以,咱们能够经过“.”运算符直接访问原型中的属性, 就和访问实例属性同样(前提是实例对象中没有同名属性)
 
function Type (p) {
  this.param = p; // 不一样对象各自独有的属性
}
Type.prototype.method = function () { // 不一样对象共享的属性
  return this.param;
}         
var obj = new Type(1);
console.log(obj); 
obj.method();

 

 
 
2. 当访问对象中的某个属性时,先在对象实例中查找该属性, 若是在实例中找到了该属性名,则返回该属性的值,若是没有找到,则继续在对应构造函数的原型对象中寻找, 若是找到则返回原型中的属性值
function Type () {
  this.a = '实例对象中的a';
}
Type.prototype.a = 'prototype中的a';
Type.prototype.b = 'prototype中的b';
var obj = new Type();
console.log(obj);   //输出 : {a: "实例对象中的a"}
console.log(obj.a); //输出:实例对象中的a
console.log(obj.b); //输出: prototype中的b

 

 
如代码所示, 由于对于属性a, 由于在实例对象中查找到了, 因此就直接返回实例中的属性值,而对于属性b,z在实例对象中没有查找到,因此返回原型中的属性值
 
 

 

 
上面咱们说的是在实例中查找属性时,原型的做用机制,那么在添加属性的过程当中会发生什么事情呢?
 
3.在为实例添加属性时, 只会覆盖其构造函数的原型对象中的同名属性,这种状况叫作“属性屏蔽”,也就是之后经过obj.属性名,就会读取到实例中的属性了,而不是原型中的属性, 可是! 原型对象中的属性是不会被修改的!不管这个属性是基本类型仍是引用类型。这就是原型对象属性的“稳定性”
 
例如:
function Type () {}
Type.prototype.str = '字符串'
Type.prototype.num = 1;
Type.prototype.arr = [1,2,3];
var obj = new Type();
console.log(obj); // {}
obj.str = '覆盖后字符串';
obj.num = 2;
obj.arr = [3,4,5];
console.log(obj.str); // 覆盖后字符串
console.log(obj.num); // 2
console.log(obj.arr); // [3.4.5]
          
console.log(Type.prototype.str); // 字符串
console.log(Type.prototype.num); // 1
console.log(Type.prototype.arr); // [1, 2, 3]

 

 
直接添加基本类型的值(str, num),以及引用类型的值到实例中,是不会修改原型中的同名属性的,只会屏蔽
 
 

 

 
可是你们还须要考虑另外一种状况:若是咱们不添加实例属性,而是直接修改属性,这时会发生什么呢?例如在obj仍是空对象,没有任何属性的时候,咱们就尝试经过obj.num去增长num的值(此时只有原型对象中有num),原型对象中的num还会被修改吗?而在这个空的实例对象中又会发生什么事情呢?
让咱们来看看——
 
4. 在实例对象中没有该属性时,直接修改其在原型中的属性值, 若是这个属性是基本类型,那么会在原型的属性值的基础上为实例对象添加修改后的属性值, 而原型对象中的属性值是不变的
 
function Type () {}
Type.prototype.str = '字符串'
Type.prototype.num = 1;
var obj = new Type();
console.log(obj); // 空实例对象 {}
obj.str +=',加点东西'; // 尝试直接修改属性
obj.num += 1;       // 尝试直接修改属性
          
console.log(obj.str); // 字符串,加点东西
console.log(obj.num); // 2
          
console.log(Type.prototype.str); // 字符串 
console.log(Type.prototype.num); // 1  
 
console.log(obj); // {str: "字符串,加点东西", num: 2}

 

 
在这里咱们作了一个看起来“荒诞”的操做: 在实例对象中尚未添加属性值的时候,就尝试修改属性值(基本类型)。并且修改完后,咱们发现obj已经从控对象{ }变成了 {str: "字符串,加点东西", num: 2}, 修改属性变成了一种“变相”的添加属性
 
  1. 在实例中没有该属性时,直接修改基本类型的实例属性等同于为其添加属性,并且添加的属性值是在原型对象属性值的基础上进行的
  2. 在直接修改基本类型的实例属性时, 原型对象中的属性仍然没有变化! 这进一步证实了原型对象中的数据具备必定的“稳定性”

 

 

 
也许你能猜到接下来的内容是什么了。。。由于我在<点4>中强调了两点:“直接修改的值是基本类型”和原型对象具备'必定'的稳定性。
没错, 若是被直接修改的属性是属性是对象或者数组这类引用类型的数据的话, 原型对象中的数据也变得“不稳定”了
 
 
5.在实例对象中没有该属性时,直接修改其在原型中的属性值, 若是这个属性是引用类型的,那么! 原型中的属性值会被修改!也就是说,在这种状况下。 原型中的属性值是“不稳定”的! 可能会被某个对象直接篡改。
 
function Type () {}
Type.prototype.objProperty =  {a: 1};
Type.prototype.arrProperty = [1,2];
var obj = new Type();
console.log(obj.objProperty) // {a: 1}
console.log(obj.arrProperty) // [1, 2]
          
obj.objProperty.a = 111;  // 直接修改引用类型的属性值
obj.arrProperty.push(3);  // 直接修改引用类型的属性值
          
// 原型对象中的属性值被修改了
console.log(Type.prototype.objProperty) // {a: 111}
console.log(Type.prototype.arrProperty) // [1, 2, 3]
 
console.log(obj); // 输出 {} obj仍是空的!!

 

 
能够看到, 在实例中尚无该属性,就直接修改属性值时, 对于基本类型和引用类型的属性, 状况是截然相反的。不只没有在实例对象obj中生成属性, 还直接地把原型对象中的属性篡改了。 这种“不稳定”并非咱们想要的。
 
因此,通常来讲,咱们不能把数组或纯对象的数据放到原型中
 
(不能这样作:)

 

 
总结下上面说的1~5点:
 
在原型机制下:——
 
1. 在获取原型对象的属性值时,其方式能够和获取实例属性的方式相同。(嗯嗯,挺好的~~)
2. 在查找属性值时,实例对象中没有该属性则能够由原型对象提供属性默认值,若是实例对象有该属性,则屏蔽原型中的属性(嗯嗯,挺好的~)
3. 为实例属性添加属性值时, 不管该值是基本类型仍是引用类型, 都不会修改原型对象中的同名属性,而是仅起到屏蔽做用(嗯嗯,挺好的~)
4. 直接修改某属性值(在实例对象中没有,而在原型中有),若是这个属性是基本类型(字符串,数字,布尔型),那么也不会修改原型中的属性,同时在实例对象中会生成修改后的属性值(嗯嗯,挺好的~)
5. 直接修改某属性值(在实例对象中没有,而在原型中有),若是这个属性是引用类型(对象,数组),那么原型中属性会被实例对象直接篡改!同时实例对象并无添加该属性!(这是什么鬼!!!)
 
 

原型在OO体系中暴露的缺陷

 
大凡了解JS的同窗,大概也都知道原型是一个矛盾综合体: 强大却也脆弱, 让人高兴却也让人烦恼而第5点揭示的就是这种矛盾的集中爆发点
 
1到4点都告诉咱们:"哎呀protoType在JS面向对象里是多好的一个东西啊! 你看建立实例对象后, 它会提供默认的属性值,并且还赋予实例重写属性这种充分的自由,并且最重要的是——原型看起来很稳定嘛!"     嗯,看起来
 
但是在第5点中,咱们看到,在这种状况下,protoType中的数据一点也不稳定, 这种不稳定性甚至直接让人质疑它是否有理由存在在面向对象的世界中
 
例如紧接上面第5点中的代码
以下:
 
function Type () {}
Type.prototype.arrProperty = [1,2];
var obj1 = new Type();
var obj2 = new Type();
          
console.log(obj2.arrProperty) // [1, 2]
          
obj1.arrProperty.push(3); 
          
console.log(obj2.arrProperty); // [1, 2, 3] 我怎么被修改了???

 

 
能够看到, 因为引用了prototype对象中的arrProperty, obj2.arrProperty一开始是[1,2],但因为另外一个对象obj1直接修改了obj1.arrProperty,而很不巧的是, arrProperty是引用类型, 因此像上面第5点描述的那样,原型对象中的arrProperty属性被篡改了, 因此obj2.arrProperty也被改为了[1, 2, 3]
 
此时的obj2可谓是一脸"黑人问号脸":为何我会无缘无故地被修改了?? 并且修改个人竟然仍是和我分庭抗礼的obj1,这是什么道理呀?

 

 
 
 
在类和对象的概念中, 咱们通常会把类想象成一个静态的模板,并且类才是拥有"控制权"的
 
在原型模式下, prototype对象就是这个模板, 但咱们发现,在上述的某个实例对象直接修改原型的属性值时, prototype这个"类"模板竟然会被动态地修改, 并且修改它的是某个对象, 这让它彻底丧失了"控制权",而落到了下面的全部对象当中。这是在面向对象中没法让人接受的
 
因此,通常来讲,咱们不能把数组或纯对象的数据放到原型中
 

对原型模式的评价

评价:原型模式是不完善的OO模式, 因此总体上看,它没法独立地完成面向对象设计的各类工做,而须要和构造函数模式配合使用
 
从做用上看: 原型是对构造函数模式的一种辅助和补充, 或者说某些不足的优化(解决冗余的函数建立的问题)
从地位上看: 构造函数仍然是OO的核心,而原型则在其次
 
 
正因如此, 咱们大多数时候将原型模式和构造函数模式配合使用来建立对象,这一模式被称为——
 

三.组合模式建立对象

若是咱们将方法和共享的属性放入原型中,而将须要实例化的属性放入构造函数的函数体中,咱们就能够既能实现函数复用,同时又能经过构造函数向实例属性传递参数,能够说是集二者之长,而避二者之短, 这就是组合模式
 
以下所示
function Person (name, age) {
  this.name = name;
  this.age = age;
  this.friends = ['Wang','Li'];
}
       
Person.prototype.sayName = function () {
  return this.name;
}
       
var person1 = new Person('Zhang',13);
var person2 = new Person('Huang',15);
       
person1.friends.push('Peng')
console.log(person1.friends); // ["Wang", "Li", "Peng"]
console.log(person2.friends); // ["Wang", "Li"]
       
console.log(person1.sayName()) // Zhang
console.log(person2.sayName()) // Huang
       
console.log(person1.sayName == person2.sayName) // true

 

上面的代码中,由于name, age,friends是实例属性,不一样的对象是各不相同的,因此咱们把它们放在构造函数中,而sayName方法应该是由不一样的对象共享的,因此咱们把它放在prototype对象中。
 
从person1.friends和person2.friends能够看出,建立对象时,实例属性被拷贝了多份不一样的副本,它们互不影响,而由person1.sayName == person2.sayName为true可知, 方法调用的是同一个, 这种效果正是咱们想的。
 
哪些属性应该(或能够)被放入原型中?
 
1. 函数: 应该尽可能被放入原型中
虽然是它引用类型,但在JS里咱们通常是没有办法在原来的基础上修改函数的(像array.push())那样,而只能为函数从新赋值,因此不用担忧会修改原型中的函数属性
 
2. 不变的共享属性: 能够被放入原型中
若是一个属性是共享的, 并且肯定它在未来也必定不会被修改,那它也能够放入原型中
 
3. 可能被修改的基本类型的属性:放入原型中也不会有问题,但最好不要这样作
没有必要,而且这种写法难以让人理解
 

JS中的继承

继承是JS面向对象设计的另外一大重点, 你会发现,下面我关于继承的分析,和上文建立对象的分析思路是彻底相同的
 

一.借用构造函数实现继承

 
在JS中,要让一个子类继承父类, 须要在子类的构造函数中经过call方法调用父类的构造函数,同时传入this做为第一个参数,表示将
函数执行的做用域绑定到当前做用域, 以下:
 
/**
 * description: 借用构造函数实现继承
 */
function superType () { // "父类"构造函数
  this.name = "aaa";
  this.sayName = function () {
    return this.name
  }
}
       
function subType () { // "子类"构造函数
  superType.call(this); // 调用“父类“的构造函数
}
       
var obj = new subType();
console.log(obj.name);  // 输出 aaa
console.log(obj.sayName());  // 输出 aaa

 

 
 
向父类的构造函数中写入参数的继承
 
若是咱们想要向父类的构造函数写入参数的话,可使用call方法的第二个参数:
 
function superType (name) { // "父类"构造函数
  this.name = name;
  this.sayName = function () {
    return this.name
  }
}
       
function subType (name) { // "子类"构造函数
  superType.call(this,name); // 调用“父类“的构造函数,并传递参数
}
       
var obj = new subType("XXX")
console.log(obj.name);   // 输出XXX
console.log(obj.sayName()); // 输出XXX

 

 
 
可是,一样的问题又出现了, 若是多个子类继承同一个父类, 可能会出现父类构造函数的方法被重复拷贝屡次的状况。
 
因而,为了减小这种冗余的函数拷贝的问题,以节约内存, 原型又特么登场了,此次它带上了它的大舅妈——“原型链”
 

二.利用原型和原型链实现继承

 
以下所示:
/**
 * description: 利用原型和原型链实现继承
 */
function superType () { // "父类"构造函数
  this.name = 'XXX'
}
superType.prototype.sayName = function () {
  return this.name;
}
       
function subType () {  } // "子类"构造函数
       
// 建立父类构造函数的实例,并赋给子类的原型
subType.prototype = new superType();
       
var obj = new subType();
console.log(obj.sayName()); // 输出 XXX

 

                                                                   
最关键的一句代码是:
 
subType.prototype = new superType(); // {name: "XXX"}

 

它具备两个方面的做用:
1. 建立superType的实例{name: "XXX"}, 并做为subType构造函数的原型对象
2. 建立原型链
 
回顾以前说的:“被建立的实例都有一个指向其构造函数的原型对象的内部指针”,因此结合
subType.prototype = new superType();// #1
var obj = new subType(); // #2

 

可知, superType实例有一个指向superType.prototype的内部指针,而且经过#1将其赋给了subType.prototype,因此subType.prototype就有了一个指向superType.prototype指针了
 
而经过#2处的代码使得obj也有了一个指向subType.prototype的内部指针
 
综上: 如今内部指针的指向是: obj -> subType.prototype -> superType.prototype
 
这一条内部指针指向造成的连接, 就叫作原型链
 
 

原型链下属性的搜索机制

 
在原型的工做机制下,搜索属性的步骤是:
 
1> 在实例对象中搜索
2> 在实例对象的构造函数的原型中搜索
 
而在原型的基础上构造了原型链后, 这个步骤变成了:
 
1> 在实例对象中搜索 (obj)
2> 在当前的构造函数的原型中搜索 (subType.prototype)
3> 在当前的构造函数的"父类"构造函数的原型中搜索(superType.prototype)
 
在上述代码中obj.sayName()的调用过程是这样的:
1. 在obj中搜索有无sayName方法——无,向下搜索
2. 在subType.prototype中搜索有无sayName方法—— 无,向下搜索
3. 在superType.prototype中搜索有无sayName方法—— 找到了!,调用该方法返回name属性
(继续。。)
4. 在obj中搜索有无name属性—— 无,向下搜索
5. 在subType.prototype中搜索有无name属性—— 找到了! 返回name的值“XXX”
 

仅仅使用原型链实现继承的缺点

 
仅使用原型链实现继承的缺点,和原型模式建立对象的缺点同样:
 
1. 你没法向父类构造函数中传递参数
2. 依然存在对象篡改原型对象属性,进而影响全部其余对象的问题
 
function superType () { // "父类"构造函数
  this.arr = [1,2]
}
       
function subType () {  } // "子类"构造函数
subType.prototype = new superType();
       
var obj1 = new subType();
var obj2 = new subType();
console.log(obj2.arr); // 输出 [1, 2]
       
obj1.arr.push(3);
       
console.log(obj2.arr); // 输出 [1, 2, 3] 卧槽,我又被乱改了!

 

三.组合继承

 
故名思义, 组合继承就是指将原型链继承和借用构造函数继承组合在一块儿,从而发挥二者之长的一种继承模式
 
具体的思路是: 用原型链实现对方法和共享属性的继承; 而经过借用构造函数实现对实例属性的继承。
 
这样,既能实现函数复用, 同时又能保证每一个属性有它本身的属性
 
/**
 * description: 组合继承的例子
 */
function SuperType (name) {
  this.name = name;
  this.colors = ['red','blue','green'];
}
       
SuperType.prototype.sayName = function () {
  console.log(this.name)
}
       
function SubType(name, age) {
  SuperType.call(this,name); // 继承实例属性
  this.age = age;
}
       
SubType.prototype = new SuperType();  // 继承方法
SubType.prototype.sayAge = function () { // 写入新的方法
  console.log(this.age)
}
       
var obj1 = new SubType('Wang', 20);
obj1.colors.push('black');
console.log(obj1.colors); // ["red", "blue", "green", "black"]
obj1.sayName(); // Wang
obj1.sayAge();  // 20
        
var obj2 = new SubType('Zhang', 23);
console.log(obj2.colors); // ["red", "blue", "green"]
obj2.sayName(); // Zhang
obj2.sayAge(); // 23

 

 
要注意的是, 经过SuperType.call(this,name); 拷贝到SubType中的属性,是会覆盖SubType.prototype中的属性的(属性屏蔽) 因此这个时候SubType.prototype = new SuperType();的主要做用是取得对原型链中SuperType.prototype的引用
 

面向对象中的原型——OO体系和OLOO体系的碰撞和融合

 
饶了一圈咱们仍是讲一讲原型吧, 从JS建立对象和继承这两大任务上看, 你已经大致了解到了原型在OO中的重要性, 但却总觉原型的各类原理和表现难以让人理解, 就好像一个外来人进入了一个群体里, 尽管他尽可能中规中矩,但看起来却总和周围人事格格不入。
 
这很正常,由于这个外来人原本就不在这个群体(OO)里面, 而是来自另一个群体(OLOO)。
 
实际上,咱们能够把面向对象看做一种设计模式(OO), 而把原型所体现的设计模式归结为另一种设计模式(OLOO)。
 

OO设计模式

 
OO,也即面向对象, 在它的世界里, 有一个父类和一堆子类, 父类是定义通用的行为, 而子类在父类的基础上定义更为细化的行为。
通常来讲,但愿子类的结构和父类大致相同,并鼓励子类经过重写实现多态
 
举个例子:
咱们最多见的交通工具和车子的比喻, 交通工具类Vehicle是一个通用的父类,而car,bus等子类是在父类的基础上细化出来的子类
 
如下为伪代码
 
class Vehicle {
  setColor (color) { this.color = color }
  setWheels (num) { this.wheels = num }
  setEngine (num) { this.engine = num }
}
 
class Car extends Vehicle { // 继承
  setWheels () { this.wheels = 4 } // 方法重写
  setEngine (1) { this.engine = 1 } // 方法重写
}

 如图设计模式

 

 
 

OLOO设计模式

 
全称是Object linked to Other Object,即对象关联设计模式, 在它的设计里面, 由一个主对象责提供其余衍生对象所须要调用的方法或属性,其余衍生对象须要某些基础的方法,就 到这个主对象中来“拿”,这个拿的过程,就是方法的委托
 
例如:
 
如下为伪代码
// 工具对象
VehicleParts = {
  setWheels: function (num) { ... } // 安装车轮
  setEngine:  function (num) { ... } // 安装引擎
}
 
// 衍生对象
Car.protoType = VehicleParts; // 委托
Car.build = function () {
  setWheels(4); // 4轮子
  setEngine(1); // 1引擎
}
 
Bike.protoType = VehicleParts; // 委托
Bike.build = function () {
  setWheels(2); // 2轮子
  setEngine(0); // 0引擎
}

 如图数组

 

 

 
而原型,就是一个来自于OLOO的世界而进入OO的世界的人物
 

对原型恰当的认知方式

原型一直以来难以让人理解的缘由是, 咱们不遗余力想要把它归入JS面向对象设计的一部分,可是又不得不面对它在使用中诸多不知足面向对象要求的表现, 例如我上面提到的建立对象和继承中它存在的问题。
 
也许最恰当的认知方式就是: 不要把它看做面向对象体系的一部分。
 
它是OO体系和OLOO体系的碰撞,也是OLOO体系对于OO的补充,但并它不是OO体系的延伸,历来也不是 
 
 
相关文章
相关标签/搜索