学习JavaScript设计模式(封装)

在JavaScript 中,并没有对抽象类和接口的支持。JavaScript 本身也是一门弱类型语言。在封装类型方面,JavaScript 没有能力,也没有必要做得更多。对于JavaScript 的设计模式实现来说,不区分类型是一种失色,也可以说是一种解脱。

从设计模式的角度出发,封装在更重要的层面体现为封装变化。

通过封装变化的方式,把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易。这可以最大程度地保证程序的稳定性和可扩展性。

javascript封装的的基本模式有3种:

1、使用约定优先的原则,将所有的私有变量以_开头

 <script type="text/javascript">
  /**
   * 使用约定优先的原则,把所有的私有变量都使用_开头
   */
  var Person = function (no, name, age)
  {
   this.setNo(no);
   this.setName(name);
   this.setAge(age);
  }
  Person.prototype = {
   constructor: Person,
   checkNo: function (no)
   {
    if (!no.constructor == "string" || no.length != 4)
     throw new Error("学号必须为4位");
   },
   setNo: function (no)
   {
    this.checkNo(no);
    this._no = no;
   },
   getNo: function ()
   {
    return this._no;
   setName: function (name)
   {
    this._name = name;
   },
   getName: function ()
   {
    return this._name;
   },
   setAge: function (age)
   {
    this._age = age;
   },
   getAge: function ()
   {
    return this._age;
   },
   toString: function ()
   {
    return "no = " + this._no + " , name = " + this._name + " , age = " + this._age;
   }
  };
  var p1 = new Person("0001", "小平果", "22");
  console.log(p1.toString());  //no = 0001 , name = 小平果 , age = 22
  p1.setNo("0003");
  console.log(p1.toString());  //no = 0003 , name = 小平果 , age = 22
  p1.no = "0004";
  p1._no = "0004";
  console.log(p1.toString()); //no = 0004 , name =小平果 , age = 22

 </script>

看完代码,是不是有种被坑的感觉,仅仅把所有的变量以_开头,其实还是可以直接访问的,这能叫封装么,当然了,说了是约定优先嘛。

下划线的这种用法这一个众所周知的命名规范,它表明一个属性仅供对象内部使用,直接访问它或设置它可能会导致意想不到的后果。这有助于防止程序员对它的无意使用,却不能防止对它的有意使用。

这种方式还是不错的,最起码成员变量的getter,setter方法都是prototype中,并非存在对象中,总体来说还是个不错的选择。如果你觉得,这不行,必须严格实现封装,那么看第二种方式。

2、严格实现封装

<script type="text/javascript">
  /**
   * 使用这种方式虽然可以严格实现封装,但是带来的问题是get和set方法都不能存储在prototype中,都是存储在对象中的
   * 这样无形中就增加了开销
   */
  var Person = function (no, name, age)
  {
   var _no , _name, _age ;
   var checkNo = function (no)
   {
    if (!no.constructor == "string" || no.length != 4)
     throw new Error("学号必须为4位");
   };
   this.setNo = function (no)
   {
    checkNo(no);
    _no = no;
   };
   this.getNo = function ()
   {
    return _no;
   }
   this.setName = function (name)
   {
    _name = name;
   }

   this.getName = function ()
   {
    return _name;
   }

   this.setAge = function (age)
   {
    _age = age;
   }
   this.
     getAge = function ()
   {
    return _age;
   }

   this.setNo(no);
   this.setName(name);
   this.setAge(age);
  }
  Person.prototype = {
   constructor: Person,
   toString: function ()
   {
    return "no = " + this.getNo() + " , name = " + this.getName() + " , age = " + this.getAge();
   }
  }
  ;
  var p1 = new Person("0001", "小平果", "22");
  console.log(p1.toString());  //no = 0001 , name =小平果 , age = 22
  p1.setNo("0003");
  console.log(p1.toString());  //no = 0003 , name = 小平果 , age = 22
  p1.no = "0004";
  console.log(p1.toString()); //no = 0003 , name = 小平果 , age = 22

 </script>

那么这与我们先前讲过的其他创建对象的模式有什么不同呢,在上面的例子中,我们在创建和引用对象的属性时总要使用this关键字。而在本例中,我们用var声明这些变量。这意味着它们只存在于Person构造器中。checkno函数也是用同样的方式声明的,因此成了一个私用方法。

需要访问这些变量和函数的方法只需要声明在Person中即可。这些方法被称为特权方法,因为它们是公用方法,但却能够访问私用属性和方法。为了在对象外部能访问这些特权函数,它们的前面被加上了关键字this。因为这些方法定义于Person构造器的作用域,所以它们能访问到私用属性。引用这些属性时并没有使用this关键字,因为它们不是公开的。所有取值器和赋值器方法都被改为不加this地直接引用这些属性。

任何不需要直接访问的私用属性的方法都可以像原来那样在Person.prototype中声明。像toString()方法。只有那些需要直接访问私用成员的方法才应该被设计为特权方法。但特权方法太多又会占用过多的内存,因为每个对象实例都包含所有特权方法的新副本。

看上面的代码,去掉了this.属性名,严格的实现了封装,只能通过getter,setter访问成员变量了,但是存在一个问题,所有的方法都存在对象中,增加了内存的开销。

3、以闭包的方式封装

<script type="text/javascript">

  var Person = (function ()
  {
   //静态方法(共享方法)
   var checkNo = function (no)
   {
    if (!no.constructor == "string" || no.length != 4)
     throw new Error("学号必须为4位");
   };
   //静态变量(共享变量)
   var times = 0;

    //return the constructor.
   return function (no, name, age)
   {
    console.log(times++); // 0 ,1 , 2
    var no , name , age; //私有变量
    this.setNo = function (no) //私有方法
    {
     checkNo(no);
     this._no = no;
    };
    this.getNo = function ()
    {
     return this._no;
    }
    this.setName = function (name)
    {
     this._name = name;
    }

    this.getName = function ()
    {
     return this._name;
    }

    this.setAge = function (age)
    {
     this._age = age;
    }
    this.getAge = function ()
    {
     return this._age;
    }

    this.setNo(no);
    this.setName(name);
    this.setAge(age);
   }
  })();

  Person.prototype = {
   constructor: Person,
   toString: function ()
   {
    return "no = " + this._no + " , name = " + this._name + " , age = " + this._age;
   }
  };

  var p1 = new Person("0001", "小平果", "22");
  var p2 = new Person("0002", "abc", "23");
  var p3 = new Person("0003", "aobama", "24");

  console.log(p1.toString());  //no = 0001 , name = 小平果 , age = 22
  console.log(p2.toString());  //no = 0002 , name = abc , age = 23
  console.log(p3.toString()); //no = 0003 , name = aobama , age = 24

 </script>

上述代码,js引擎加载完后,会直接执行Person = 立即执行函数,然后此函数返回了一个子函数,这个子函数才是new Person所调用的构造函数,又因为子函数中保持了对立即执行函数中checkNo(no) ,times的引用,(很明显的闭包)所以对于checkNo和times,是所有Person对象所共有的,创建3个对象后,times分别为0,1,2 。这种方式的好处是,可以使Person中需要复用的方法和属性做到私有且对象间共享。

这里的私用成员特权成员仍然被声明在构造器。但那个构造器却从原来的普通函数变成了一个内嵌函数,并且被作为包含它的函数的返回值给变量Person。这就创建了一个闭包,你可以把静态的私用成员声明在里面。位于外层函数声明之后的一对空括号很重要,其作用是代码一载入就立即执行这个函数。这个函数的返回值是另一个函数,它被赋给Person变量,Person因此成了一个构造函数。在实例华Person时,所调用的这个内层函数。外层那个函数只是用于创建一个可以用来存储静态成员的闭包。

在本例中,checkno被设计成为静态方法,原因是为Person的每个实例都生成这个方法的一个新副本毫无道理。此外还有一个静态属性times,其作用在于跟踪Person构造器的总调用次数

以上就是本文的全部内容,希望对大家的学习有所帮助,大家可以更深入的学习了解封装的意义。

(0)

相关推荐

  • javascript prototype的深度探索不是原型继承那么简单第1/3页

    1 什么是prototype JavaScript中对象的prototype属性,可以返回对象类型原型的引用.这是一个相当拗口的解释,要理解它,先要正确理解对象类型(Type)以及原型(prototype)的概念.         前面我们说,对象的类(Class)和对象实例(Instance)之间是一种"创建"关系,因此我们把"类"看作是对象特征的模型化,而对象看作是类特征的具体化,或者说,类(Class)是对象的一个类型(Type).例如,在前面的例子中,p1和

  • javascript设计模式 接口介绍

    这本书中第一个重要的内容就是接口. 大家对接口应该都不陌生,简单的说接口就是一个契约或者规范.在强类型的面相对象语言中,接口可以很容易的实现.但是在javascript中并没有原生的创建或者实现接口的方式,或者判定一个类型是否实现了某个接口,我们只能利用js的灵活性的特点,模拟接口. 在javascript中实现接口有三种方式:注释描述.属性验证.鸭子模型. note:因为我看的是英文书,翻译水平有限,不知道有些词汇如何翻译,大家只能领会精神了. 1. 注释描述 (Describing Inte

  • JavaScript类和继承 this属性使用说明

    this属性表示当前对象,如果在全局作用范围内使用this,则指代当前页面对象window: 如果在函数中使用this,则this指代什么是根据运行时此函数在什么对象上被调用. 我们还可以使用apply和call两个全局方法来改变函数中this的具体指向. 先看一个在全局作用范围内使用this的例子: 复制代码 代码如下: <script type="text/javascript"> console.log(this === window); // true consol

  • JavaScript设计模式学习之“类式继承”

    在做一件事情之前,首先要清楚做这件事情的好处有什么,相信不会有哪个人愿意无缘无故的去做事情.一般说来,我们在设计类的时候,实际上就是希望能减少重复性的代码,使用继承可以完美的做到这一点,借助继承机制,你可以在现有类的基础上再次进行设计并且充分利用它们已经具备的各种方法,而对设计的修改也更为轻松.废话不多说了,举例说明: 复制代码 代码如下: function Person(name){     this.name = name; } Person.prototype.getname = func

  • 小议javascript 设计模式 推荐

    记得早前就说过要和大家分享"javascript设计模式",迟迟没写不是因为我懒,最近确实太忙,忙工作,忙旅游(啊哦?),好不容易这几天空闲了,接下来是兑现之前空口白话的时间了. 在讨论设计模式之前,请确认您已经有一定的脚本编程基础,如果不甚了解,建议可以先查阅本人很久之前写的这篇<浅谈javascript面向对象编程>请看下一篇文章. 讲到设计模式,不得不先重点着墨于"接口设计",因为接口设计在设计模式中的意义太大了,大于模式本身.直观起见,先介绍一下

  • 深入了解javascript中的prototype与继承

    通常来说,javascript中的对象就是一个指向prototype的指针和一个自身的属性列表.javascript创建对象时采用了写时复制的理念.只有构造器才具有prototype属性,原型链继承就是创建一个新的指针,指向构造器的prototype属性.prototype属性之所以特别,是因为javascript时读取属性时的遍历机制决定的.本质上它就是一个普通的指针. 构造器包括: 1.Object2.Function3.Array4.Date5.String 下面我们来举一些例子吧 复制代

  • 学习JavaScript设计模式(多态)

    多态的实际含义是:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果.换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈. 从字面上来理解多态不太容易,下面我们来举例说明一下. 主人家里养了两只动物,分别是一只鸭和一只鸡,当主人向它们发出"叫"的命令时,鸭会"嘎嘎嘎"地叫,而鸡会"咯咯咯"地叫.这两只动物都会以自己的方式来发出叫声.它们同样"都是动物,并且可以发出叫声",但根据主

  • 不错的一篇关于javascript-prototype继承

    1.最基本的用法 把ClassA的一个实例赋值给ClassB, ClassB就继承了ClassA的所有属性. 代码入下: function ClassA() { this.a='a'; } function ClassB() { this.b='b'; } ClassB.prototype=new ClassA(); var objB=new ClassB(); for(var p in objB)document.write(p+" "); [Ctrl+A 全选 注:如需引入外部Js

  • 实现JavaScript中继承的三种方式

    一.原型链继承 在原型链继承方面,JavaScript与java.c#等语言类似,仅允许单父类继承.prototype继承的基本方式如下: 复制代码 代码如下: function Parent(){} function Child(){} Child.prototype = new Parent(); 通过对象Child的prototype属性指向父对象Parent的实例,使Child对象实例能通过原型链访问到父对象构造所定义的属性.方法等. 构造通过原型链链接了父级对象,是否就意味着完成了对象

  • 学习JavaScript设计模式(接口)

    1.接口概述 1).什么是接口? 接口是提供了一种用以说明一个对象应该具有哪些方法的手段.尽管它可以表明这些方法的语义,但它并不规定这些方法应该如何实现. 2). 接口之利 促进代码的重用. 接口可以告诉程序员一个类实现了哪些方法,从而帮助其使用这个类. 有助于稳定不同类之前的通信方式. 测试和调式因此也能变得更轻松. 在javascript这种弱类型语言中,类型不匹配错误很难跟踪.使用接口可以让这种错误的查找变午更容易一点,因为此时如果一个对象不像所要求的类型,或者没有实现必要的方法,那么你会

  • JavaScript继承基础讲解(原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承)

    说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象JS基础讲解,工厂模式.构造函数模式.原型模式.混合模式.动态原型模式>,接下来讲一般通过那些方法完成JavaScript的继承. 原型链 JavaScript中实现继承最简单的方式就是使用原型链,将子类型的原型指向父类型的实例即可,即"子类型.prototype = new 父类型();&qu

  • 深入理解JavaScript是如何实现继承的

    最近最网上看了一个人面试淘宝时的经历,然后发现了自己有好多好多不太清楚的地方,所以特此写点文章来加深自己对一些问题的理解. 文章中提到了一个问题是:JavaScript是如何实现继承的? 下面我便阐述一些在网上找到的方法和实例来解释下,借以加深自己的印象. 我们知道JavaScript中的function是万能的,除了用于的函数定义,也可以用于类的定义. JavaScript的继承,说起来也是有点怪,不像C++和一些面向对象的语言,他没有public,private等访问控制修饰,也没有impl

  • JavaScript原型继承之基础机制分析

    这一语言功能的本质依赖于 JavaScript 特有的原型链(prototype chain)模式. 所以严格意义上说,JavaScript 是基于原型的面向对象语言.也就是说,每个实例对象都具有一个原型.对象从该原型中继承属性和方法. 1.构造函数 利用构造函数,可以简单地创建对象.构造函数内的 this 关键字指向实例对象本身: 复制代码 代码如下: function People(name){ this.name = name; } 使用 new 运算符和构造函数创建实例对象: 复制代码

  • javascript 设计模式之单体模式 面向对象学习基础

    单体模式(singleton) 单体是在脚本加载时创建的,能将一系列有关联的变量和方法组织为一个逻辑单元,逻辑单元里面的内容通过单一的变量进行访问: 一个单体主要分为三部分 用于访问内部信息的入口变量(如:Sky) 属性(如:nickName/age/timeInfo) 方法(如:sayHello) 基本结构 复制代码 代码如下: var Sky = { /* * 作用一,变量管理 */ nickName: "sky", age: "26", /* * 作用二,加载

  • 学习JavaScript设计模式(继承)

    1.继承 在javascript中继承是一个非常复杂的话题,比其他任何面向对象语言的中的继承都复杂得多.在大多数其他面向对象语言中,继承一个类只需要使用一个关键字即可.与它们不同,在javascript中要想达到传承公用成员的目的,需要采取一系列措施.更有甚者,javascript属于使用原型式继承的少数语言之一.利益于这种语言的灵活性,你既可使用标准的基于类的继承,也可使用更微妙一些的原型式继承. 2.为什么需要继承? 一般来说,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化对象间的

随机推荐