JavaScript设计模式:一、面向对象编程(第三节)

一、继承

js并没有继承这一个现有的机制,所以我们所说的继承是通过JS本身的机制去实现的。

1、类式继承

 1 // 类式继承
 2
 3 // 父类
 4 function SuperClass () {
 5   this.superValue = true
 6 }
 7
 8 // 为父类原型添加方法
 9 SuperClass.prototype.getSuperValue = function (arguments) {
10   return this.superValue
11 }
12
13 // 声明子类
14 function SubClass () {
15   this.subValue = false
16 }
17
18 // 继承父类
19 SubClass.prototype = new SuperClass()
20
21 // 为子类原型添加方法
22 SubClass.prototype.getSubValue = function (arguments) {
23   return this.subValue
24 }

这里之所以要将SubClass的原型赋值为SuperClass的实例,是因为如果我们将父类的实例赋值给子类的原型,那么子类的原型就可以访问到父类的原型上的属性和方法与从父类的构造函数中赋值的属性和方法,这正是类式继承的原理。

另外,我们可以通过instanceof操作符来检测某个对象是否是某个类的实例,或者说某个对象是否继承了某个类。instanceof通过判断对象的prototype链来确定这个对象是否是某个类的实例,而不关心对象与类的自身结构。

1 var instance = new SubClass()
2 console.log(instance instanceof SuperClass)  // true
3 console.log(instance instanceof SubClass)    // true
4 console.log(instance instanceof SuperClass)  // false

最后一个为什么是false?明明SubClass继承SuperClass,可是为什么SubClass instanceof SuperClass却是false?

之前说过,instanceof是判断前面的对象是否是后面类的对象的实例,并不表示两者之间的继承关系,我们在实现SubClass继承SuperClass时,是通过将SuperClass的实例赋值给SubClass的原型prototype,所以说SubClass.prototypr继承了SuperClass。

1 console.log(instance.prototype instanceof SuperClass)  // true

而上面的类式继承有两个严重的缺陷:

1、由于子类通过起原型prototype对父类实例化,继承了父类。所以说父类中的共有属性如果是引用类型,那么就会导致子类中的所有实例共享这同一个引用类型的属性,后果可想而知,A实例一旦修改了某个引用类型的属性,其他实例都会受到影响。

 1 function SuperClass () {
 2   this.color = [‘red‘, ‘green‘, ‘black‘]
 3 }
 4
 5 function SubClass () {
 6   // ...
 7 }
 8
 9 SubClass.prototype = new SuperClass()
10 var instance1 = new SubClass()
11 var instance2 = new SubClass()
12 console.log(instance2.color) // [‘red‘, ‘green‘, ‘black‘]
13 instance2.color.push(‘gray‘)
14 console.log(instance1.color); // [‘red‘, ‘green‘, ‘black‘, ‘gray‘]

从上面代码可以看出,instance2修改了color属性,导致instance1的color属性也被修改了。这正是问题所在

2、由于子类实现的继承是靠其原型prototype被赋值为父类的实例实现的,因此在创建父类的时候,是无法向父类传递参数的,这里所说的不能传递参数,并不是说真的传递参数了就会出错,而是这样做不符合面向对象编程的规则:对象(实例)才是属性的拥有者。如果在子类定义时就将属性赋了值,对象实例就不能再更改自己的属性了。这样就变成了类拥有属性,而不是对象拥有属性了。 举个例子,子类 Children 继承父类 Parents,Parents 构造函数:function Parents(name){ this.name=name; }使用原型链并给父类构造函数传参数:Children.prototype=new Parents("Hello");那么此时,Children 类就拥有了 name=“Hello” 属性,而 Children 类的实例对象 c1、c2、c3 等等只能被迫接受这个 name 属性。Children 是 "Hello" 的拥有者而 c1、 c2、c3不是。所以说,在创建父类是,是无法向父类传递参数的。

2、构造函数继承

正因为类式继承本身存在着一定的缺陷,因此有了构造函数继承

 1 // 构造函数式继承
 2 // 声明父类
 3 function SuperClass (value) {
 4   // 引用类型属性
 5   this.color = [‘red‘, ‘green‘, ‘black‘]
 6   this.value = value
 7 }
 8
 9 // 父类声明原型方法
10 SuperClass.prototype.showColor = function () {
11   console.log(this.color)
12 }
13
14 // 声明子类
15 function SubClass (value) {
16   // 继承父类
17   SuperClass.call(this, value)
18 }
19
20 // 创建第一个子类实例
21 var instance1 = new SubClass(1)
22 // 创建第二个子类实例
23 var instance2 = new SubClass(2)
24
25 instance1.books.push(‘gray‘)
26 console.log(instance1.color) // [‘red‘, ‘green‘, ‘black‘, ‘gray‘]
27 console.log(instance2.color) // [‘red‘, ‘green‘, ‘black‘]
28 instance1.showColor() // TypeError

这里在子类中使用了call,并将子类的this传递了进去,call会导致SuperClass再执行一次,而this确实指向了子类,因此子类自然也就继承了父类的共有属性,但由于这种类型的继承没有涉及到原型prototype,所以父类的原型方法自然不会被子类所继承,而如果想要被子类继承,那么方法或者属性就必须放在构造函数中,但是这样创建出来的每个实例都会单独拥有一份副本而不会共用,这有违代码复用的原则。

3、组合继承

组合继承,顾名思义,综合了以上两种继承方式的思想,所以名为组合

 1 // 组合式继承
 2 // 声明父类
 3 function SuperClass (name) {
 4   // 值类型共有属性
 5   this.name = name
 6   // 引用类型共有属性
 7   this.color = [‘red‘, ‘green‘, ‘black‘]
 8 }
 9
10 // 父类原型
11 SuperClass.prototype.getName = function () {
12   console.log(this.name)
13 }
14
15 // 声明子类
16 function SubClass (name, time) {
17   // 构造函数式继承父类name属性
18   SuperClass.call(this, name)
19   this.time = time
20 }
21
22 // 类式继承 子类原型继承父类
23 SubClass.prototype = new SuperClass ()
24 // 子类原型方法
25 SubClass.prototype.getTime = function () {
26   console.log(this.time)
27 }

组合继承,子类的实例中更改父类继承下来的引用类型属性如color,不会影响到其他实例,并且子类实例化过程中又能将参数传递到父类的构造函数中。

至此,组合继承应该算是比较完美的解决方案了,不过,仔细研究代码还是可以发现,我们在使用构造函数继承时,执行了一次父类的构造函数,而在实现子类原型的类式继承时,再次调用了一次父类的构造函数。因此,父类的构造函数执行了两边,所以,也不算完美。

4、原型式继承

2006年道格拉斯-克罗克福发表的《JavaScript中原型式继承》中提到了他的观点:借助原型prototype可以根据已有的对象创建一个新的对象,同时不必创建新的自定义对象。这段话可能不好理解,我们直接看代码:

1 // 原型式继承
2 function inheritobject(o) {
3   // 声明一个过渡函数对象
4   function F () {}
5   // 过渡对象的原型继承父对象
6   F.prototype = o
7   // 返回过渡对象的一个实例,该实例的原型继承了父对象
8   return new F()
9 }

这个方法其实与类式继承相似,它是对类式继承的一个封装。所以类式继承中出现的问题,这里也会出现,但是由于过渡类F的构造函数中并无其他的内容,所以使用起来较为方便。在原型式继承的基础之上,该大牛推出了寄生式继承。

5、寄生式继承

 1 // 寄生式继承
 2 // 声明基对象
 3 var book = {
 4   name: ‘book‘,
 5   color: [‘red‘, ‘green‘]
 6 }
 7 function createBook(obj) {
 8   // 通过原型继承方式创建新对象
 9   var o = new inheritObject(obj)
10   // 扩展新对象
11   o.getName = function () {
12     console.log(this.name)
13   }
14   // 返回拓展后的新对象
15   return o
16 }

其实寄生式继承是对原型继承的二次封装,并且在二次封装的过程中对继承的对象进行了拓展,这样新创建的对象不仅仅有父类中的属性和方法,也添加了新的属性和方法.其缺点也与原型式继承相同。

6、寄生组合式继承

 1 function inheritPrototype (SubClass, SuperClass) {
 2   // 复制一份父类的原型副本保存在变量中
 3   var temp = inheritObject(SuperClass.prototype)
 4   // 修正因为重写子类原型导致自留constructor指向改变
 5   temp.constructor = SubClass
 6   // 设置子类的原型
 7   SubClass.prototype = temp
 8 }
 9
10 // 定义父类
11 function SuperClass (name) {
12   this.name = name
13   this.color = [‘red‘, ‘green‘, ‘black‘]
14 }
15 //定义父类原型方法
16 SuperClass.prototype.getName = function () {
17   console.log(this.name)
18 }
19
20 // 定义子类
21 function SubClass (name, time) {
22   // 构造函数式继承
23   SuperClass.call(this, name)
24   // 子类属性
25   this.time = time
26 }
27
28 // 寄生式继承父类原型
29 inheritPrototype(SubClass, SuperClass)
30 // 子类新增原型方法
31 SubClass.prototype.getTime = function () {
32   console.log(this.time)
33 }
34
35 // 测试
36 var instance1 = new SubClass(‘a‘, 1)
37 var instance2 = new SubClass(‘b‘, 2)
时间: 2024-10-16 16:41:43

JavaScript设计模式:一、面向对象编程(第三节)的相关文章

javaScript设计模式之面向对象编程(object-oriented programming,OOP)(一)

面试的时候,总会被问到,你对javascript面向对象的理解? 面向对象编程(object-oriented programming,OOP)是一种程序设计范型.它讲对象作为程序的设计基本单元,讲程序和数据封装其中,以提高程序的重用性.灵活性和扩展性. 一.举个例子 有这么一个需求:做一个验证表单功能,仅需要验证用户名,邮箱,密码等 觉得在项目产品开发中,自己是这么写的 function checkName(){ //验证姓名 } function checkEmail(){ //验证邮箱 }

javaScript设计模式之面向对象编程(object-oriented programming,OOP)--寄生组合式继承

组合式继承:将类式继承同构造函数继承组合使用,但是存在一个问题,子类不是父类的实例,而子类的原型式父类的实例,所以才有了寄生组合式继承. 意思就是说,寄生就是寄生式继承,寄生式继承就是依托于原型继承,原型继承又与类式继承差不多,所以另外一种继承模式应该是构造函数继承.当然子类不是父类的实例的问题是由于类式继承引起的. 说道这,我不得不说一下,道格拉斯大哥对寄生式继承的改造 function inheritPrototype(subClass,superClass){ //复制一份父类的原型副本保

深入理解javascript中实现面向对象编程方法

介绍Javascript中面向对象编程思想之前,需要对以下几个概念有了解: 1. 浅拷贝和深拷贝:程序在运行过程中使用的变量有在栈上的变量和在堆上的变量,在对象或者变量的赋值操作过程中,大多数情况先是复制栈上的信息,这样就会出现以下情况,如果变量是对象,那么这一操作,复制的只是真正对象所在 的堆内存空间的起始地址,这就是所谓的浅拷贝,如果是深拷贝,则是在内存堆空间中重新分配一个内存,并把分配的内存的起始地址复制过去. 2. 引用类型数据和值类型数据:谈到引用类型数据和值类型数据,自然而然的联想到

JavaScript中的面向对象编程,详解原型对象及prototype,constructor,proto,内含面向对象编程详细案例(烟花案例)

面向对象编程: 面向:以什么为主,基于什么模式 对象:由键值对组成,可以用来描述事物,存储数据的一种数据格式 编程:使用代码解决需求 面向过程编程: 按照我们分析好的步骤,按步骤解决问题 优点:性能比面向对象高,适合跟硬件联系很紧密的东西 缺点:没有面向对象那么容易维护,复用,扩展 面向对象编程: 把事务分解成一个个对象,然后由对象之间分工与合作,分工明确,每一个对象都是功能中心 面向对象特性:封装性.继承性 .多态性 封装性:将一个功能封装起来,小封装 将多个函数封装起来,整体封装起来形成一个

js 设计模式 oop 面向对象编程

最初我们写js代码的时候是这么写 function checkName(){ //验证姓名 } function checkEmail(){ //验证邮箱 } function checkPassword(){ //验证密码 } 这种方式会造成全局变量的严重污染,再过渡到 var checkObject = { checkName : function(){}; checkEmail: function(){}; checkPassword: funcion(){}; } //也可如此写 var

JavaScript 中的面向对象编程

使用JSON 来定义一个对象: <script type="text/javascript">var xiaoming = { name : 'xiaoming', age : 18, say : function(){ console.log('my name is '+this.name); }}xiaoming.say();xiaoming.name = 'li xiao ming';xiaoming.say();</script>  

JavaScript高级程序设计: 面向对象编程

工厂模式 工厂模式虽然解决了创建多个相似对象的问题,但没有解决对象识别问题. function createPerson( name , age , job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ console.log( this.name ); } return o; } var person1 = createPerson('Alice',23,'PHP

如何理解并学习javascript中的面向对象(OOP) [转]

如果你想让你的javascript代码变得更加优美,性能更加卓越.或者,你想像jQuery的作者一样,写出属于自己优秀的类库(哪怕是基于 jquery的插件).那么,你请务必要学习javascript面向对象,否则你无法更灵活的使用javascript这门语言. 什么事闭包?到底什么是原型?(知道闭包和原型的,就算得上是javascript的高手了.但真正能够理解,并且灵活运用的人并不多)到底该如何学习javascript中的面向对象呢?在javascript这么语言正如日中天,相信不少人正在为

JavaScript设计模式:一、面向对象编程

JavaScript面向对象编程 众所周知,JS作为一门脚本语言,由于其设计者在设计JS的时候,也仅仅用了很少的时间就完成了JS这门语言的创建,JS虽然拥有着脚本语言的优势,但是JS也存在着天生的缺陷.其中之一就是:"没有完整的面向对象和自定义类型支持",这是因为JS本身没有很好的模块化.但事实上是,很多JS学习者或者使用者的共同点是:他们接触的第一门编程语言大都是C++或者Java这种老牌OOP语言,写代码时都是秉持着面向对象的思想,这在学习JS或者使用JS不免会感到有一些难受. 那