zoukankan      html  css  js  c++  java
  • JavaScript面向对象轻松入门之封装(demo by ES5、ES6、TypeScript)

      本章默认大家已经看过作者的前一篇文章 JavaScript面向对象轻松入门之抽象》

    为什么要封装?

      封装(Encapsulation)就是把对象的内部属性和方法隐藏起来,外部代码访问该对象只能通过特定的接口访问,这也是面向接口编程思想的一部分。

      封装是面向对象编程里非常重要的一部分,让我们来看看没有封装的代码是什么样的:

    1     function Dog(){
    2         this.hairColor = '白色';//string
    3         this.breed = '贵宾';//string
    4         this.age = 2;//number
    5     }
    6     var dog = new Dog();
    7     console.log(dog.breed);//log: '贵宾'

      看似没有什么问题,但如果breed属性名修改了怎么办?比如换成this.type = ‘贵宾’,那所有使用Dog类的代码都要改变。

      如果类的代码和使用类的代码都是你写的,并且使用这个类的地方不多,你这么写无所谓。

      但如果使用这个类的地方比较多,或者协同开发时其它人还要使用你的类,那这样做就会让代码很难维护,正确的做法是:

     1     function Dog(){
     2         this.hairColor = '白色';//string
     3         this.age = 2;//number
     4         this._breed = '贵宾';//string
     5     }
     6     Dog.prototype.getBreed = function(){
     7         return this._breed;
     8     }
     9     Dog.prototype.setBreed = function(val){
    10         this._breed = val;
    11     }
    12     var dog = new Dog();
    13     console.log(dog.getBreed());//log: '贵宾'
    14     dog.setBreed('土狗');

      getBreed()就是接口,如果内部的属性变化了,比如breed换成了type ,那只需要改变getBreed()里的代码就可以了,并且你可以监听到所有获取这个属性的操作。

      所以封装有很多好处:

      1、只要接口不改变,内部的实现可以任意改变;

      2、使用者使用起来很方便,不用关系内部是如何实现;

      3、降低代码之间的耦合;

      4、满足大型应用程序和多人协同开发;

    getter/setter来封装私有属性

      其实还有另一种封装属性的方法,那就是用getter/setter,如下demo,本章不讲原理,只讲使用,原理可自行查资料:

     1     function Dog(){
     2         this.hairColor = '白色';//string
     3         this.age = 2;//number
     4         this._breed = '贵宾';//string
     5         Object.defineProperty(this, 'breed', {//传入this和属性名
     6             get : function () {
     7                 console.log('监听到了有人调用这个get breed')
     8                 return this._breed;
     9             },
    10             set : function (val) {
    11                 this._breed = val;
    12                 /*
    13                 如果不设置setter的话默认这个属性是不可设置的
    14                 但有点让人诟病的是,浏览器并不会报错
    15                 所以即使你想让breed是只读的,你也应该设置一个setter让其抛出错误:
    16                 throw 'attribute "breed"  is read only!';
    17                 */
    18             }
    19         });
    20     }
    21     var dog = new Dog();
    22     console.log(dog.breed);
    23     /*log:
    24         '监听到了有人调用这个get breed接口'
    25         '贵宾'
    26     */
    27     dog.breed = '土狗';
    28     console.log(dog.breed);
    29     /*log:
    30         '监听到了有人调用这个get breed接口'
    31         '土狗'
    32     */

      但这种方法写起来比较繁琐,作者一般是用getBreed()这种方法,getter/setter一般用在readonly的属性和一些比较重要的接口,以及重构没有封装接口的属性操作。

      还可以用闭包封装私有属性,是最安全的,但会产生额外的内存开销,所以作者不是很喜欢用,大家可自行了解。

    公有/私有概念

      前两小节我们简单的了解了下封装,但这些肯定是不够用的,下面的我们先来了解下几个概念:

      私有属性:即只能在类的内部调获取、修改的属性,不允许外部访问。

      私有方法:仅供类内部调用的方法,禁止外部调用。

      公有属性:可供类外部获取、修改的属性。理论上讲类的所有属性都应该是私有属性,只能通过封装的接口访问,但一些比较小的类,或者使用次数比较少的类,你觉得比较方便的话也可以不封装接口。

      公有方法:可供外部调用的方法,实现接口的方法如getBreed()就是公有方法,以及对外暴露的行为方法。

      静态属性、静态方法:类本身的属性和方法。这个就没必要区分公有私有了,所有的静态属性、静态方法都必须是私有的,一定要通过封装接口访问,这也是上一章中作者为什么要用getInstanceNumber()来访问Dog.instanceNumber属性。

        ES5 demo如下

     1     function Dog(){
     2         /*公有属性*/
     3         this.hairColor = null;//string
     4         this.age = null;//number
     5         /*私有属性,人们共同约定私有属性、私有方法前面加上_以便区分*/
     6         this._breed = null;//string
     7         this._init();
     8         /*属性的初始化最好放一个私有方法里,构造函数最好只用来声明类的属性和调用方法*/
     9         Dog.instanceNumber++;
    10     }
    11     /*静态属性*/
    12     Dog.instanceNumber = 0;
    13     /*私有方法,只能类的内部调用*/
    14     Dog.prototype._init = function(){
    15         this.hairColor = '白色';
    16         this.age = 2;
    17         this._breed = '贵宾';
    18     }
    19     /*公有方法:获取属性的接口方法*/
    20     Dog.prototype.getBreed = function(){
    21         console.log('监听到了有人调用这个getBreed()接口')
    22         return this._breed;
    23     }
    24     /*公有方法:设置属性的接口方法*/
    25     Dog.prototype.setBreed = function(breed){
    26         this._breed = breed;
    27         return this;
    28         /*这是一个小技巧,可以链式调用方法,只要公有方法没有返回值都建议返回this*/
    29     }
    30     /*公有方法:对外暴露的行为方法*/
    31     Dog.prototype.gnawBone = function() {
    32         console.log('这是本狗最幸福的时候');
    33         return this;
    34     }
    35     /*公有方法:对外暴露的静态属性获取方法*/
    36     Dog.prototype.getInstanceNumber = function() {
    37         return Dog.instanceNumber;//也可以this.constructor.instanceNumber
    38     }
    39     var dog = new Dog();
    40     console.log(dog.getBreed());
    41     /*log:
    42         '监听到了有人调用这个getBreed()接口'
    43         '贵宾'
    44     */
    45     /*链式调用,由于getBreed()不是返回this,所以getBreed()后面就不可以链式调用了*/
    46     var dogBreed = dog.setBreed('土狗').gnawBone().getBreed();
    47     /*log:
    48         '这是本狗最幸福的时候'
    49         '监听到了有人调用这个getBreed()接口'
    50     */
    51     console.log(dogBreed);//log: '土狗'
    52     console.log(dog);

      ES6 demo(新手可不看ES6和TypeScrpt实现部分):

     1     class Dog{
     2         constructor(){
     3             this.hairColor = null;//string
     4             this.age = null;//number
     5             this._breed = null;//string
     6             this._init();
     7             Dog.instanceNumber++;
     8         }
     9         _init(){
    10             this.hairColor = '白色';
    11             this.age = 2;
    12             this._breed = '贵宾';
    13         }
    14         get breed(){
    15             /*其实就是通过getter实现的,只是ES6写起来更简洁*/
    16             console.log('监听到了有人调用这个get breed接口');
    17             return this._breed;
    18         }
    19         set breed(breed){
    20             /*跟ES5一样,如果不设置的话默认breed无法被修改,而且不会报错*/
    21             console.log('监听到了有人调用这个set breed接口');
    22             this._breed = breed;
    23             return this;
    24         }
    25         gnawBone() {
    26             console.log('这是本狗最幸福的时候');
    27             return this;
    28         }
    29         getInstanceNumber() {
    30             return Dog.instanceNumber;
    31         }
    32     }
    33     Dog.instanceNumber = 0;
    34     var dog = new Dog();
    35     console.log(dog.breed);
    36     /*log:
    37         '监听到了有人调用这个get breed接口'
    38         '贵宾'
    39     */
    40     dog.breed = '土狗';//log:'监听到了有人调用这个set breed接口'
    41     console.log(dog.breed);
    42     /*log:
    43         '监听到了有人调用这个get breed接口'
    44         '土狗'
    45     */

      ES5ES6中虽然我们把私有属性和方法用“_”放在名字前面以区分,但外部还是可以访问到属性和方法的。

      TypeScrpt中就比较规范了,可以声明私有属性,私有方法,并且外部是无法访问私有属性、私有方法的:

     1     class Dog{
     2         public hairColor: string;
     3         readonly age: number;//可声明只读属性
     4         private _breed: string;//虽然声明了private,但还是建议属性名加_以区分
     5         static instanceNumber: number = 0;//静态属性
     6         constructor(){
     7             this._init();
     8             Dog.instanceNumber++;
     9         }
    10         private _init(){
    11             this.hairColor = '白色';
    12             this.age = 2;
    13             this._breed = '贵宾';
    14         }
    15         get breed(){
    16             console.log('监听到了有人调用这个get breed接口');
    17             return this._breed;
    18         }
    19         set breed(breed){
    20             console.log('监听到了有人调用这个set breed接口');
    21             this._breed = breed;
    22         }
    23         public gnawBone() {
    24             console.log('这是本狗最幸福的时候');
    25             return this;
    26         }
    27         public getInstanceNumber() {
    28             return Dog.instanceNumber;
    29         }
    30     }
    31     let dog = new Dog();
    32     console.log(dog.breed);
    33     /*log:
    34         '监听到了有人调用这个get breed接口'
    35         '贵宾'
    36     */
    37     dog.breed = '土狗';//log:'监听到了有人调用这个set breed接口'
    38     console.log(dog.breed);
    39     /*log:
    40         '监听到了有人调用这个get breed接口'
    41         '土狗'
    42     */
    43     console.log(dog._breed);//报错,无法通过编译
    44     dog._init();//报错,无法通过编译

    注意事项:

      1、暴露给别人的类,多个类组合成一个类时,所有属性一定都要封装起来;

      2、如果你来不及封装属性,可以后期用getter/setter弥补;

      3、每个公有方法,最好注释一下含义;

      4、在重要的类前面最好用注释描述所有的公有方法;

    后话

      如果你喜欢作者的文章,记得收藏,你的点赞是对作者最大的鼓励;

      作者会尽量每周更新一章,下一章是讲继承;

      大家有什么疑问可以留言或私信作者,作者尽量第一时间回复大家;

      如果老司机们觉得那里可以有不恰当的,或可以表达的更好的,欢迎指出来,我会尽快修正、完善。

     

  • 相关阅读:
    生手和FinallyJane一起学习ASP.NET
    使用eclipse 初学java
    关于C#中用access做数据库,使用like语句的问题(转)
    VSS (Visual Source Safe 2005) 用法详解(转)
    mysql 字符集问题之我见
    mysqli的预处理功能使用
    兼容IE和Firefox
    linux下netsnmp 已经安装,为什么没有 snmpwalk和snmpget
    ? PHP WBEM
    安装cacti时提示错误
  • 原文地址:https://www.cnblogs.com/elonhu/p/7044975.html
Copyright © 2011-2022 走看看