Javascript 高级面向对象

在前面我们学习了 javascript 得到对象的几种方式,下面我们首先来回顾下我们学习的几种得到 javascript 对象的方式有哪几种: 

一、通过new object得到

<span style="font-size:14px;">var pre = new object();</span>

使用这种定义的方式,虽然可以定义一个对象,但是因为没有类的约束,所以无法实现

对象的重复使用,如存在 10 个人,就要定义 10 个 person,抓狂抓狂太过于麻烦了,在操作过程中存
在问题。

二、通过使用json得到

<span style="font-size:14px;">var pre = {/*代码块*/}</span>

但是这个方法得到的东西和用new object()一样,当有大量重复的东西时,对象不能进行重复利用,导致内存的占有大大增加。因此,为了避免这种情况的出现,大家研究出来一个一种工厂方式来定义一个对象,下面我们来使用工厂方式来实现一个对象的定义。

三、使用工厂模式得到 

<span style="font-size:14px;">//基于工厂模式的定义方式定义对象,在一个方法中定义一个对象,将传递进来的属性赋给了这个对象 </span>
<span style="font-size:14px;">function createOb(name,age) { </span>
<span style="font-size:14px;">  var pre = new Object();  </span>
<span style="font-size:14px;">   <span style="font-family: Arial, Helvetica, sans-serif;">pre</span><span style="font-family: Arial, Helvetica, sans-serif;">.name = name;  </span></span>
<span style="font-size:14px;">   pre.age = age;</span>
<span style="font-size:14px;">   pre.say = function() {   </span>
<span style="font-size:14px;">alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");  }  return o; }  
//使用工厂模式的定义方法,有效的解决了对象无法重用的问题</span>
<span style="font-size:14px;"> var p1 = createOb("小明",18); </span>
<span style="font-size:14px;">p1.say();</span>
<span style="font-size:14px;"> var p2 = createOb("小红",20); 
p2.say();</span>

我们使用了工厂模式定义了对象,这样就很好的解决了对象无法重用的问题,但是此时又存在了另一个问题,就是我们无法判断得到的对象的类型了,如 typeof 或者instanceof 来判断类型,仅仅得到一个 Object 类型,所以就推出了基于构造函数的方式。
四、使用构造函数来创建一个对象
这种基于构造函数的创建方式,是 javascript 模拟其他面向对象语言的方式来实现对象的创建的
例:
//基于构造函数的创建对象的方式和基于工厂的方式类似 ,最大的区别就是函数的名称就是类的名称,按照面向对象语句的
 //潜规则(通用的规则),首字母大写,表示这是一个构造函数
 function Per(name,age) { 
 this.name = name; 
 this.age = age;  
this.say = function() {   
alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");  } }  
var p1 = new Per("小明",15); 
p1.say(); //使用构造函数的好处就是可以使用instanceof来判断这个对象的类型了 
//alert(p1 instanceof Person) 
基于构造函数的定义的方式最大的好处除了对象重复使用外,就是让我们还可以判断它的类型。 此时我们发现基于构造函数的定义对象的方式看似已经很完美了,我们需要的问题它都可以解决了,但是如果我们仔细的分析这段代码的话,就会发现这样的代码是存在问题的,为什么呢? 我们通过代码分析得知:say 方法在每个对象创建后都存在了一个方法拷贝(但是我们发现代码在只有调用时,say 方法才会在堆中创建),这样就增加了内存的消耗了,如果在对象中有大量的方法时,内存的消耗就会高,这样不可以了。 解决这个问题的就是我们可以把这个方法放到全局函数,这样就所有的对象指向了一个方法。 因此,
//解决方案就是将方法全部放在外面,成为全局函数
 function Per(name,age) { 
 this.name = name;  
this.age = age;  //可以将方法成为全局函数 
 this.say = say; 
function say() {  alert("我的名字是:"+this.name+",我今年"+this.age+"岁了"); }
 var p1 = new Per("小明",15); 
p1.say(); 
var p2 = new Person("小红",16); 
p2.say(); 
 
 但是这样写的话,会带来另一个问题,就是方法一点定义为全局函数,那么 window 对象就可以调用,这样就破坏了对象的封装性。而且如果有大量的方法,这样写导致整体代码充斥着大量的全局函数,这样将不利于开发。所以我们急需一种可以完美的解决上述问题的方案,javascript 给我们提供了一种解决这些问题的方案,就是基于原型的对象创建方案。 

因此,这里就出现了封装:

封装--Javascript 的原型(prototype) 

Prototype,原型的初览 

function Person() { } //使用原型来给对象赋值 ,这样就讲一个对象的属性和方法放在了该对象的原型中 /,外界是无法访问到这样数据的 

Person.prototype.name = "小明"; 

Person.prototype.age = 18; 

Person.prototype.say = function() {  alert("我的名字是:"+this.name+",我今年"+this.age+"岁了"); } 

var p1 = new Person();

 p1.say();//正常访问了 

say();//报错了 

 这样我们发现 window 就无法访问到 say 方法了,此时 say 方法只属于 Person 对象独有的方法。很好的解决了封装破坏的情况。 

原型

原型是 js 中非常特殊一个对象,当一个函数创建之后,会随之就产生一个原型对象,当通过这个函数的构造函数创建了一个具体的对象之后,在这个具体的对象中就会有一个属性指向原型。

function Person() { }  

Person.prototype.name = "小明";

 Person.prototype.age = 18; 

Person.prototype.say = function() {  

alert("我的名字是:"+this.name+",我今年"+this.age+"岁了"); }  

var p1 = new Person();

 p1.say();

 var p2 = new Person(); 

p2.name = "张三"; 

p2.age = 20; 

p2.say();

常见的原型检测方式 

alert(Person.prototype.isPrototypeOf(p1))

//检测p1的构造器是否指向Person对

alert(p1.hasOwnProperty("name"));

//检测某个属性是不是自己内存中的

同样我们可以使用 delete 语句来删除我们赋予对象的自己属性(注意:原型中的是无法删除的),如 

//可以使用delete语句删除对象中自己的属性,那么就会找到原型中的值

 delete p2.name; p2.say(); 
alert(p2.hasOwnProperty("name")); 

 检测在某个对象自己或者对应的原型中是否存在某个属性。 
alert("name" in p1);//true delete p2.name;//虽然删除了自己的name属性,但是原型中有 

alert("name" in p2);//true //原型和自己中都没有sex属性 
alert("sex" in p1);//false 

 那么问题来了?如果检测只在原型中,不在自己中的属性呢?

 //我们可以自己写代码来测试属性不在自己,在原型中 

function hasPrototypeProperty(obj,prop) { 

 if (!obj.hasOwnProperty(prop)) {

   if (prop in obj) {    

return true;   }   } 

 return false; 

alert(hasPrototypeProperty(p1,"name")); 
alert(hasPrototypeProperty(p2,"name")); 

原型重写

在上面的写法中,我们已经解决了大量的问题,使用原型。但是如果我们的对象中存在大量的属性或者方法的时候,使用上面的方式,感觉要写大量的【对象.prototype.属性名 】这样的代码,感觉不是很好,那么我们可以使用 json 的方式来写: 

function Person() { }  
Person.prototype = {  name : "小明", 

age : 18, 

 say : function() {   

alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");  } }

但是这种写法,我们是将该对象的原型覆盖(注意:这两种写法不一样的,第一种是扩充,第二种是覆盖。

封装--原型创建对象
因为原型存在,我们实现了对象的封装,但是这种封装也同样可能存在问题的。
1、 我们无法像使用构造函数的那样将属性传递用于设置值
2、 当属性中有引用类型, 可能存在变量值的重复
终极方案基于组合的对象定义
为了解决原型所带来的问题,需要通过组合构造函数和原型来实现对象的创建将: 属性在构造函数中定义,将方法在原型中定义。这种有效集合了两者的优点,是目前最为常用的一种方式写了那么多的代码,目的就是为了这种定义 javascript对象的方式,所以我们最终的定义 javascript对象的方案就是基于组合的方式定义,将属性的定义放在构造函数中,将方法的定义放在原型中

继承--原型创建对象

在面向对象的语言中,存在了三大特性 封装、继承、多态。我们前面一直说 javascript 是面向对象的语言,那么它应该也有面向对象语言这些特性,上面我们看来封装,那么下面我们来研究继承。继承,望名而知意,就是我们现实社会中的子孙后代继承了父辈的财富,我们一直在说,面向对象的语言就是在模拟现实世界,通过模拟现实世界来编程,那么在 javascript 中, 如何理解继承,如何实现继承呢?

原型链实现继承

function Parent() {  this.pv = "parent"; }  
Parent.prototype.showParent = function() {  alert(this.pv); }  
//定义一个子类 function Son() {  this.sv = "Son"; }  
//使用原型链来实现继承 Son.prototype = new Parent();  
Son.prototype.showSon = function() {  alert(this.sv); }  
var s1 = new Son(); s1.showParent(); 
s1.showSon(); 

当子类的原型指向父类的对象后,子类就继承了父类,实现了继承,这就是基于原型链的继承。

 但是使用原型链实现继承要注意以下一些问题: 
1、 不要在设定了原型链之后,再原型重写 
2、 一定要在原型链赋值之后才能添加或者覆盖方法 
父类方法的覆盖(重写) 
 当子类继承父类后,子类如果认为父类的方法不能满足自己或者不太满意父类的方法,
可以使用与父类同名的方法来覆盖(也叫重写)父类方法。 
作者:刘建宏 javascript 高级部分 QQ:591270587 
 注意:javascript 中存在重写,但是没有重载。 

原型链继承的缺陷 

 原型链继承存在的缺陷就是: 
1、 无法从子类中调用父类的构造函数,这样就没有办法把子类中属性赋值给父类。 
2、 父类中属性是在子类的原型中的,这违背了我们前面所讲的封装的理念(属性在对象中,方法在原型中) ,会出现前面值的混淆问题。 所以我们一般都不会使用单纯的原型链来实现继承

基于伪装实现继承 

 在前面我们学习了 call 和 apply 方法,这两个方法我们知道可以使用:函数名.call(上下文,参数列表),或:者函数名.apply(上下文,参数数组)的方式来调用函数,这样我们可以通过第一个参数上下文来改变调用函数的对象,那么基于这两个方法,我们可以实现一个基于伪装的继承。

function Parent() {  this.pv = "parent"; }  
//定义一个子类 function Son() {  this.sv = "Son";  Parent.call(this);//注意:此时的this指的是Son的对象        //那么就是Son对象调用Parent函数 }  
var s1 = new Son(); 
alert(s1.pv); 

在子类中的 this 指的就是子类实例化后的对象本身,当我们在子类中使用 call 方法调用父类后,就相当于将父类的构造方法绑定到了子类的对象身上,这样就伪装了子类可以使用父类的构造方法,完成了继承。 

子类初始化父类属性 

 基于原型链的继承我们说了缺陷就是无法实现子类去调用父类的构造函数,这样就无法修改父类的属性,但是基于伪装的完美的解决了这个问题

 function Parent(name) {  this.name = name; }  

 function Son(name,age) {  this.age = age;  Parent.call(this,name); }  
var s1 = new Son("小明",18); var s2 = new Son("小红",28); alert(s1.name+","+s1.age); 
alert(s2.name+","+s2.age); 
 这样我们可以通过子类来设置父类的属性了。 

伪装的缺陷 

 基于伪装的继承解决了基于原型链的问题,但不是说它就十分完美,它也存在了问题,
     由于使用伪造的方式继承,子类的原型不会指向父类,所以父类中写在原型中的方法不会被子类继承,所以子类调用不到父类的方法。  解决的办法就是将父类的方法放到子类中来,但是这样的又违背了封装的理念。 

终极方案—基于组合实现继承 

基于组合的继承就是:属性的继承基于伪装的方式实现,而方法的继承基于原型链的方式继承。 
function Parent(name) {  this.name = name;  this.friends = ["小红","小明"]; 
}  
Parent.prototype.parentSay = function() {  alert(this.name+"---->"+this.friends); }  
 function Son(name,age) {  this.age = age;  Parent.apply(this,[name]); }  
 Son.prototype = new Parent();  
Son.prototype.sonSay = function() {  alert(this.name+"*******==="+this.age); }  
var s1 = new Son("小花",18); s1.friends.push("小红"); s1.parentSay(); s1.sonSay(); var s2 = new Son("小明",28); s2.parentSay(); 
s2.sonSay(); 
 这是我们实现继承的终极方案





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值