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     */

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

  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、在重要的类前面最好用注释描述所有的公有方法;

后话

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

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

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

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

时间: 2024-08-06 11:58:07

JavaScript面向对象轻松入门之封装(demo by ES5、ES6、TypeScript)的相关文章

JavaScript面向对象轻松入门之概述(demo by ES5、ES6、TypeScript)

写在前面的话 这是一个JavaScript面向对象系列的文章,本篇文章主要讲概述,介绍面向对象,后面计划还会有5篇文章,讲抽象.封装.继承.多态,最后再来一个综合. 说实话,写JavaScript面向对象的文章实在是太多了,网上一搜一大堆,很多书里面也介绍的很详细.但作者当初在学习面向对象的时候还是非常困难,特别是在习惯了面向过程编程的情况下,不知道大家有没有这个感受. 作者分析了一下其中的原因,恐怕是因为里面涉及的概念太多:原型.原型链.继承.this.constructor.call等等,这

JavaScript面向对象轻松入门之多态(demo by ES5、ES6、TypeScript)

多态(Polymorphism)按字面的意思就是"多种状态",同样的行为(方法)在不同对象上有不同的状态. 在OOP中很多地方都要用到多态的特性,比如同样是点击鼠标右键,点击快捷方式.点击桌面空白处.点击任务栏等弹出的菜单都是不同的. 方法重写(override): 即子类定义一个与父类名字相同的方法,以此覆盖父类方法,以此来实现不同的功能. 1 function Animal(){} 2 var AnimalP = Animal.prototype; 3 AnimalP.eat =

JavaScript面向对象轻松入门之继承(demo by ES5、ES6)

继承是面向对象很重要的一个概念,分为接口继承和实现继承,接口继承即为继承某个对象的方法,实现继承即为继承某个对象的属性.JavvaScript通过原型链来实现接口继承.call()或apply()来实现实现继承. 接口继承的实现在ES5中是比较麻烦,在其它OOP语言中一个extends关键字就可以实现,但在ES5中要通过原型链去模拟,非常难理解,对初学者很不友好,并且有好几种接口继承的方式.本文为了对初学者更友好,并不打算让读者了解接口继承的原理,而是直接把接口继承实现方法封装成一个函数,大家只

Javascript 面向对象编程1:封装

Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,他又不是一种真正的面向对象编程语言,因为它的语法中没有class(类). 那么,如果我们要把"属性(property)"和"方法(method)",封装成一个对象,甚至要从原型对象生成一个实例对象,那么怎么解决呢? 一.生成实力对象的原始模式 假定我们把猫看成一个对象,它有"名字"和"颜色"两个属性. var Cat =

JavaScript面向对象编程入门

来源极客网 1 function Person() { 2 var _this = {} //创建一个空的对象,接着我们利用这个"空的对象"承载Person的属性和方法 3 _this.sayHello = function(){ 4 alert("PHello"); 5 } 6 return _this; 7 } 8 9 function Teacher_1() { 10 var _this = Person();//注意这里的语法小括号 11 return _t

Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇

Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript时,可以选择面向过程的方式编程,显得高效:但在实际工作中,遇到的项目需求和框架较大的情况下,选择面向对象的方式编程显得尤其重要,Javascript原生语法中没有提供表述面向对象语言特性的关键字和语法(如extends.implement).为了实现这些面向对象的特性,需要额外编写一些代码,如下.

【转】「译」ES5, ES6, ES2016, ES.Next: JavaScript 的版本是怎么回事?

ES5, ES6, ES2016, ES.Next: What's going on with JavaScript versioning? Posted by Hux on September 22, 2015 JavaScript 有着很奇怪的命名史. 1995 年,它作为网景浏览器(Netscape Navigator)的一部分首次发布,网景给这个新语言命名为 LiveScript.一年后,为了搭上当时媒体热炒 Java 的顺风车,临时改名为了 JavaScript (当然,Java 和

Javascript 面向对象编程(一):封装

学习Javascript,最难的地方是什么? 我觉得,Object(对象)最难.因为Javascript的Object模型很独特,和其他语言都不一样,初学者不容易掌握. 下面就是我的学习笔记,希望对大家学习这个部分有所帮助.我主要参考了以下两本书籍: <面向对象的Javascript>(Object-Oriented JavaScript) <Javascript高级程序设计(第二版)>(Professional JavaScript for Web Developers, 2nd

Swift轻松入门——基本语法介绍和详细地Demo讲解(利用WebView打开百度、新浪等网页)

本文主要分为两个部分,第一部分介绍Swift的基本语法,第二部分讲解一个利用WebView来打开百度.sina等网页的小demo,如果对swift的语法不感兴趣的同学可以直接跳到第二部分来感受下Swift的魅力-(本文的demo源码已上传至github:https://github.com/iOSGeek0829/XSurfing) 一.Swift常用语法 Swift是Apple去年推出的一门新的语言,基于C和Objective-C,而没有C的一些兼容约束,它采用了安全的编程模式和添加现代的功能