学习JavaScript设计模式(封装)


Posted in Javascript onNovember 26, 2015

在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构造器的总调用次数

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

Javascript 相关文章推荐
漂亮的widgets,支持换肤和后期开发新皮肤
Apr 23 Javascript
jQuery Validation实例代码 让验证变得如此容易
Oct 18 Javascript
jquery写个checkbox——类似邮箱全选功能
Mar 19 Javascript
一个简单的瀑布流效果(主体形式自写)
May 27 Javascript
js利用事件的阻止冒泡实现点击空白模态框的隐藏
Jan 24 Javascript
Three.js学习之文字形状及自定义形状
Aug 01 Javascript
Bootstrap免费字体和图标网站(值得收藏)
Mar 16 Javascript
vue init webpack 建vue项目报错的解决方法
Sep 29 Javascript
node.js中Buffer缓冲器的原理与使用方法分析
Nov 23 Javascript
JavaScript Blob对象原理及用法详解
Oct 14 Javascript
ES6字符串的扩展实例
Dec 21 Javascript
JavaScript异步操作中串行和并行
Nov 20 Javascript
JS实现密码框根据焦点的获取与失去控制文字的消失与显示效果
Nov 26 #Javascript
学习JavaScript设计模式(接口)
Nov 26 #Javascript
Jquery中request和request.form和request.querystring的区别
Nov 26 #Javascript
Jquery检验手机号是否符合规则并根据手机号检测结果将提交按钮设为不同状态
Nov 26 #Javascript
JS延时提示框实现方法详解
Nov 26 #Javascript
js使用cookie记录用户名的方法
Nov 26 #Javascript
Bootstrap每天必学之导航
Nov 26 #Javascript
You might like
php实现单笔转账到支付宝功能
2018/10/09 PHP
基于jquery的cookie的用法
2011/01/10 Javascript
按钮JS复制文本框和表格的代码
2011/04/01 Javascript
js之onload事件的一点使用心得
2013/08/14 Javascript
jquery选择器之内容过滤选择器详解
2014/01/27 Javascript
jQuery异步上传文件插件ajaxFileUpload详细介绍
2015/05/19 Javascript
JS实现网页上随滚动条滚动的层效果代码
2015/11/04 Javascript
jqGrid用法汇总(全经典)
2016/06/28 Javascript
Web打印解决方案之证件套打的实现思路
2016/08/29 Javascript
分分钟玩转Vue.js组件
2016/10/25 Javascript
浅谈jquery之on()绑定事件和off()解除绑定事件
2016/10/26 Javascript
jquery中绑定事件的异同
2017/02/28 Javascript
Extjs表单输入框异步校验的插件实现方法
2017/03/20 Javascript
Vue2.0 vue-source jsonp 跨域请求
2017/08/04 Javascript
纯js实现页面返回顶部的动画(超简单)
2017/08/10 Javascript
Javascript快速实现浏览器系统通知
2017/08/26 Javascript
Nodejs Express 通过log4js写日志到Logstash(ELK)
2018/08/30 NodeJs
ES6 Class中实现私有属性的一些方法总结
2019/07/08 Javascript
vue实现滑动超出指定距离回顶部功能
2019/07/31 Javascript
layui自己添加图片按钮并点击跳转页面的例子
2019/09/14 Javascript
js实现数字跳动到指定数字
2020/08/25 Javascript
Python判断操作系统类型代码分享
2014/11/22 Python
python文件操作之目录遍历实例分析
2015/05/20 Python
简单了解OpenCV是个什么东西
2017/11/10 Python
利用python库在局域网内传输文件的方法
2018/06/04 Python
Python读取系统文件夹内所有文件并统计数量的方法
2018/10/23 Python
解决Pycharm调用Turtle时 窗口一闪而过的问题
2019/02/16 Python
python实现图像全景拼接
2020/03/27 Python
中学教师管理制度
2014/01/14 职场文书
企业后勤岗位职责
2014/02/28 职场文书
平面设计专业求职信
2014/08/09 职场文书
团组织推优材料
2014/12/29 职场文书
拾金不昧感谢信
2015/01/21 职场文书
审查起诉阶段律师意见书
2015/05/19 职场文书
基于Android10渲染Surface的创建过程
2022/08/14 Java/Android
Java获取字符串编码格式实现思路
2022/09/23 Java/Android