一,首先,为什么要使用封装?
这是从信息的角度出发的,信息的隐藏是最终的目的,而封装只不过是实现隐藏的一种方法。
这里我们需要明白一点就是:类的定义有如下的三种方式:
(第一种)门户大开型方式 (第二种)用命令规范区别私有和公有的方式 (第三种)闭包
现在详细描述一下每一种类的定义方式:
针对第一种,门户大开类型
首先,我们来看一种情况
(1)声明一个简单的类,代码如下
function Person(age,name) { this.name=name; this.age=age; }
(2)实例化类+调用
var p=new Person(-10,"小明"); alert(p.age)//结果出现年龄出现负数
从上述的运行结果中,我们可以看出程序可以正常执行,但这并符合实际,因为年龄出现 了负数,这不是我们想要的,我们需要一个能正确处理并产生与实际情况相符的解决方案。
为了能解决上述年龄出现的问题,我们可以这样做:扩展类的原型链
Person.prototype={ checkAge:function (age) { if(0<age&&age<150){ return true; }else { return false; } } }
加上解决方案后,代码如下
(1)基本类
function Person(age,name) { this.name=name; //调用方法判断验证 if(!this.checkAge(age)){ throw new Error("年龄必须在0-150之间"); } this.age=age; }
(2)年龄判断验证
Person.prototype={ checkAge:function (age) { if(0<age&&age<150){ return true; }else { return false; } } }
(3)调用
var p2=new Person(10,"小明"); alert(p2.age)
我们还可以给name添加一个读取验证,name为空时使用默认值 同样是扩展类的原型链
代码如下,
Person.prototype["getName"]=function () { return this.name||"我是默认的"; }
//调用
var p3=new Person(10,"");
alert(p3.getName())//结果为“我是默认的”
总结一下:当类被定义为门户大开类型时会出现与实际不符合的情况 ,虽然我们可以加在类上扩展原型类加验证方法解决,但是这样会使类变得臃肿。
针对第二种,用命名规范区别私有和公有
步骤如下,
(1)定义类 在类中定义变量(私有和公有变量)+验证方法的调用
//用命名规范来区别私有和公有 function Person(name,age,email) { //定义私有变量 this._name;//私有 this.setName(name);//只是方法的调用,方法中有验证,而不是在类中验证 this._age;//私有 this.setAge(age); this.email=email;//公有的 }
(2)在类的原型上面 扩展赋值方法
Person.prototype={//直接扩展至原型上,可以在本类的内部使用this调用 setName:function (name) { this._name=name; }, setAge:function (age) { //需要做判断符号实际情况 if(age>0&&age<150){//验证不在类中,类不会变的臃肿 this._age=age; }else { throw new Error("年龄必须是在0到150范围内") } } }
(3)应用
var text1=new Person("测试",-10,"qq.com"); alert(text1._age)//-10 程序会报错 这是我们想要的(说明验证是对的)
var p2=new Person("测试2",10,"qq.cpm"); alert(p2._age)//程序正常运行 达到我们的目标
总结一下:在类的定义是使用命名规范来定义私有变量和公有变量,并将验证方法和赋值方法扩展到本类的原型链上,在类中调用方法即可(会有返回值),这样不会导致类的臃肿。
针对第三种,闭包实现封装
这种方式有点像高级语言,在定义类是使用get,set方法实现数据的操作
(1)定义一个基本类(变量+操作变量的方法)
function Person(name,age,email) { //(1)声明变量和对变量进行操作的get和set方法 this.email=email;//公有变量
//get方法 this.getName=function (name){ return this.name;//为什么是this调用呢?请看set方法 } this.getAge=function (age){ return this.age; } //set方法 这里相当于在类上的扩展 this.setName=function (name) { this.name=name;//Person.prototype.name 这里写明了get中this的写法的来源 } this.setAge=function (age) { if(age>0&&age<150){ this.age=age;//Person.prototype.name 这里写明了get中this的写法的来源 }else { throw new Error("年龄必须是在0到150范围内"); } } //(2)写一个构造函数 做初始化 实现闭包 确保set是在get之前的,不然get时会出现错误 this.init=function () { this.setName(name); this.setAge(age); } this.init();//显示调用 }
(2)应用
var p=new Person("text",-10,"qq.com"); alert(p.age)//程序由于不符合实际而被阻断,符合要求
注:额外的闭包写法 var 方式
var _sex="M"; this.getSex=function () { return _sex; } this.setSex=function () { _sex=sex; }
总结一下:
(1)这里只是函数和属性的简单封装,还有更为复杂是业务需要封装,使用get和set方法时,需要一个构造函数用于两者先后顺序的初始化实现闭包,之后显示调用,确保set是在get之前的。
(2)闭包的实现,是通过get和set实现的,this.方式赋值时没有暴露在外面而是通过get,set方法实现闭包。
二,静态化
普通属性和函数是作用在对象上到,而静态函数是定义到类上的。
第一种静态函数的写法 :写在类上
(1)首先,定义一个简单的类,例如
function Person(name,age) { this.name=name; this.age;age; this.showName=function () { alert(this.name); } }
(2)定义一个写在类上的方法,Person.add --》(类.函数)或者(类.属性),例如
Person.add=function (x,y) { return x+y; }
(3)应用
alert(Person.add(10,20));//结果为30
总结一下,该种定义方式有点类似于高级语言的静态类,使用与高级语言的相同通过类直接调用。
第二种静态函数的写法 :使用类中类的方式完成每一个对象全拥有当前类中相同的属性和函数 。注意: 类中类的方式是一次性赋值的
(1)类的定义格式如下
var cat=(function () { //私有静态属性 var AGE=1; function add(x,y) { return x+y; }
return function () {//类中类 return返回的类中持有与上面类中相同的属性与函数 则共同的AGE和add称为静态属性和静态函数 this.AGE=AGE; this.add=function (x,y) { return add(x,y) } } })()//实例化cat,实质是通过return实例化的
(2)应用
alert(new cat().add(1,3))//4 alert(new cat().AGE)//1
总结一下:从上面的代码格式中我们不难看出在一个类中定义有私有的属性和方法,与一个返回可以初始化本类私有静态属性和方法的类,该类我们称为类中类。当我们实例化外层类时实质上是通过该类内部的类return实例化的。
封装的优点:
(1)保护内部数据完整性是封装一大用处
(2)对象的重构变得轻松,(如果没有封装你敢动正在运用的代码吗?) 答案肯定是不敢的。
(3)化模块间的耦合
弊端:
(1)私有的方法会变得难以进行单元测试
(2)使用封装意味着与复杂的代码打交道
(3)最大问题封装在JavaScript中很难实现 除非运用自如,否则到处封装,使测试变得困难。
以上只是学习的初步理解,不好还希望多多理解。