本章默认大家已经看过作者的前一篇文章 《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、在重要的类前面最好用注释描述所有的公有方法;

后话

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

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

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

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

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

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

    写在前面的话 这是一个JavaScript面向对象系列的文章,本篇文章主要讲概述,介绍面向对象,后面计划还会有5篇文章,讲抽象.封装.继承.多态,最后再来一个综合. 说实话,写JavaScript面向 ...

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

    继承是面向对象很重要的一个概念,分为接口继承和实现继承,接口继承即为继承某个对象的方法,实现继承即为继承某个对象的属性.JavvaScript通过原型链来实现接口继承.call()或apply()来实 ...

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

    多态(Polymorphism)按字面的意思就是"多种状态",同样的行为(方法)在不同对象上有不同的状态. 在OOP中很多地方都要用到多态的特性,比如同样是点击鼠标右键,点击快捷方 ...

  4. JavaScript面向对象轻松入门之抽象(demo by ES5、ES6、TypeScript)

    抽象的概念 狭义的抽象,也就是代码里的抽象,就是把一些相关联的业务逻辑分离成属性和方法(行为),这些属性和方法就可以构成一个对象. 这种抽象是为了把难以理解的代码归纳成与现实世界关联的概念,比如小狗这 ...

  5. JavaScript面向对象轻松入门之综合

    javascrpit面向对象之综合   这一章是对前几章的一个总结,通过一个案例来综合认识javascript面向对象的基本用法   需求: 几乎所有的web应用都需要保存数据一些到本地,那么我们就来 ...

  6. Javascript 面向对象编程—继承和封装

      前  言 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类) ...

  7. JavaScript面向对象编程入门

    来源极客网 function Person() { var _this = {} //创建一个空的对象,接着我们利用这个"空的对象"承载Person的属性和方法 _this.say ...

  8. Javascript面向对象三大特性(封装性、继承性、多态性)详解及创建对象的各种方法

    Javascript基于对象的三大特征和C++,Java面向对象的三大特征一样,都是封装(encapsulation).继承(inheritance )和多态(polymorphism ).只不过实现 ...

  9. Javascript 面向对象编程1:封装

    Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,他又不是一种真正的面向对象编程语言,因为它的语法中没有class(类). 那么,如果我们要把& ...

随机推荐

  1. 基于JS的问卷调查

    主要工作 因为代码不好展示,也不好截长图,可以去看我的GitHub地址:https://github.com/14glwu/MyBlog/blob/master/questionnaire.html ...

  2. python中str的find()

    今天学习语法的时候发现字符串自带函数find和操作符in功能十分近似,几乎一模一样 if 'a' in name:    print 'Yes, it contains the string &quo ...

  3. EmpyoyeeManger_1.0

    整体结构 首先创建一个名为employee的数据库 create database employee; 然后在该数据库下建一张表 CREATE TABLE t_emp( id BIGINT PRIMA ...

  4. ElasticSearch5在Ubuntu系统下的安装和Java调用

    ElasticSearch是开源搜索平台的新成员,实时数据分析的神器.可以理解为作为搜索的数据库,可以提供搜索功能.对比关系型数据库,具有以下的相似关系: 关系型数据库 数据库 表 行 列 Elast ...

  5. 谈谈一些有趣的CSS题目(十七)-- 不可思议的颜色混合模式 mix-blend-mode

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

  6. 设计模式的征途—6.建造者(Builder)模式

    建造者模式又称为生成器模式,它是一种较为复杂.使用频率也相对较低的创建型模式.建造者模式为客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品.因为,没有人买车会只买一个方向盘或者轮胎,大 ...

  7. js获取一组不重复的随机数的方法

    一.基本思路: 建立一个数组存放所有可以取到的值,每次从该数组中随机取走一个,放到新的数组中,直到完成. 二.实现方法 1.方法一: (1)创建一个数组arr,数组元素为所有可能出现元素的集合: (2 ...

  8. Java之路 ——初识Eclipse

    零.大纲 一.前言 二.获取Eclipse 三.运行Eclipse 四.创建及运行第一个Java Project 五.界面介绍 六.如何调试 七.获取插件 八.Eclipse 快捷键 九.总结 一.前 ...

  9. iis7 安装laravel5.4环境

    laravel版本: Laravel5.4IIS版本:IIS7站点配置就不详细说啦,大家网上可以搜一坨很多的配置方法啦哈直接上图: 由于IIS没有像Apache.htaccess文件,创建一个Web. ...

  10. 反序列py脚本分享(原创)

    代码如下: #!/usr/bin/env python # coding=utf-8 import socket import sys import requests import base64 im ...