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

Javascript基于对象的三大特征和C++,Java面向对象的三大特征同样,都是封装(encapsulation)、继承(inheritance )和多态(polymorphism )。只不过实现的方式不一样,其基本概念是差很少的。其实除三大特征以外,还有一个常见的特征叫作抽象(abstract),这也就是咱们在一些书上有时候会看到面向对象四大特征的缘由了。javascript


1、封装性html


    封装就是把抽象出来的数据和对数据的操做封装在一块儿,数据被保护在内部,程序的其它部分只有经过被受权的操做(成员方法),才能对数据进行操做。java

  JS封装只有两种状态,一种是公开的,一种是私有的。
    案例:编程

function Person(name,sal){
    this.name=name;         //公开
    var sal=sal;                 //私有
    this.showInfo=function(){ //公开
        window.alert(this.name+" "+sal);
    }
    function showInfo2(){      //把函数私有化
        window.alert("你好"+this.name+" "+sal);
    }
}
var p1 = new Person('Cece', 20, 10000); 
window.alert(p1.name + " is " +p1.age); //Cece is undefined
p1.showInfo();//Cece 20
p1.showInfo2();//VM302:1 Uncaught TypeError: p1.showInfo2 is not a function(…)

构造函数方式与原型方式给对象添加方法的区别:数组

//1.经过构造函数方式给对象添加方法
function Dog(name){
    this.name=name;
    this.shout=function(){
        window.alert("小狗尖叫"+this.name);
    }
}
var dog1=new Dog("aa");
var dog2=new Dog("bb");
if(dog1.shout==dog2.shout){
    window.alert("相等");
}else{
    window.alert("不相等");
}

//会输出“不相等”
//2.经过原型方式给对象添加方法
function Dog(name){
    this.name=name;    
}
Dog.prototype.shout=function(){
    window.alert("小狗尖叫"+this.name);
}
var dog1=new Dog("aa");
var dog2=new Dog("bb");
if(dog1.shout==dog2.shout){
    window.alert("相等");
}else{
    window.alert("不相等");
}

//会输出“相等”

说明经过构造函数来分配成员方法,给每一个对象分配一份独立的代码。这样的弊端就是若是对象实例有不少,那函数的资源占用就会很大,并且有可能形成内存泄漏。安全

而原型法是你们共享同一份代码,就不会有那种弊端。app

所以,经过构造函数添加成员方法和经过原型法添加成员方法的区别:函数

1.经过原型法分配的函数是全部对象共享的;学习

2.经过原型法分配的属性是独立的;(若是你不修改属性,他们是共享)测试

3.若是但愿全部的对象使用同一个函数,最好使用原型法添加方法,这样比较节省内存。

 

特别强调:咱们前面学习的经过prototype给全部的对象添加方法,可是这种方式不能去访问类的私有变量和方法。案例:

function Person(){
    this.name="Cece";
    var age=18;
    this.abc=function(){    //公开
        window.alert("abc");
    }
    function abc2(){        //私有
        window.alert("abc");
    }
}
Person.prototype.fun1=function(){
    window.alert(this.name);//Cece
    //window.alert(age);//Uncaught ReferenceError: age is not defined(…)
    //abc2();           //Uncaught ReferenceError: abc2 is not defined(…)
    this.abc();         //abc
}
var p1=new Person();
p1.fun1();

 

2、继承性


继承能够解决代码复用,让编程更加靠近人类思惟。当多个类存在相同的属性(变量)和方法时,能够从这些类中抽象出父类,在父类中定义这些相同的属性和方法,全部的子类不须要从新定义这些属性和方法,只须要经过继承父类中的属性和方法。
JS中实现继承的方式:

1.类继承:


(1)对象冒充
案例:

//1.把子类中共有的属性和方法抽取出,定义一个父类Stu 
function Stu(name, age){ 
    this.name = name; 
    this.age = age; 
    this.show = function(){ 
        window.alert(this.name + " " + this.age); 
    } 
} 
function MidStu(name, age) { 
    this.stu = Stu; 
    // 经过对象冒充来实现继承的 
    // 对象冒充的意思就是获取那个类的全部成员,由于js是谁调用那个成员就是谁的,这样MidStu就有了Stu的成员了 
    this.stu(name, age); 
    this.payFee = function(){ 
        window.alert("缴费" + money * 0.8); 
    } 
} 
function Pupil(name, age) { 
    this.stu = Stu; 
    // 经过对象冒充来实现继承的 
    this.stu(name, age); 
    this.payFee = function(){ 
        window.alert("缴费" + money * 0.5); 
    } 
} 

var midStu = new MidStu("zs", 13); 
midStu.show(); 
var pupil = new Pupil("ls", 10); 
pupil.show(); 

(2)经过call或者apply实现
案例:

//1.把子类中共有的属性和方法抽取出,定义一个父类Stu 
function Stu(name,age){ 
    //window.alert("确实被调用."); 
    this.name=name; 
    this.age=age; 
    this.show=function(){ 
        window.alert(this.name+"年龄是="+this.age); 
    } 
} 
//2.经过call或者apply来继承父类的属性的方法 
function MidStu(name,age){ 
    //这里这样理解: 经过call修改了Stu构造函数的this指向, 
    //让它指向了调用者自己. 
    Stu.call(this,name,age); 
    //若是用apply实现,则能够 
    //Stu.apply(this,[name,age]); //说明传入的参数是 数组方式 
    //能够写MidStu本身的方法. 
    this.pay=function(fee){ 
        window.alert("你的学费是"+fee*0.8); 
    } 
} 
function Pupil(name,age){ 
    Stu.call(this,name,age);//当咱们建立Pupil对象实例,Stu的构造函数会被执行,当执行后,咱们Pupil对象就获取从 Stu封装的属性和方法 
    //能够写Pupil本身的方法. 
    this.pay=function(fee){ 
        window.alert("你的学费是"+fee*0.5); 
    } 
} 
//测试 
var midstu=new MidStu("zs",15); 
var pupil=new Pupil("ls",12); 
midstu.show(); 
midstu.pay(100); 
pupil.show(); 
pupil.pay(100); 

2.原型继承

原型继承是js中最通用的继承方式,不用实例化对象,经过直接定义对象,并被其余对象引用,这样造成的一种继承关系,其中引用对象被称为原型对象。

function A(){  
    this.color = 'red';  
}  
function B(){}  
function C(){}  
B.prototype = new A();  
C.prototype = new B();  
// 测试原型继承  
var c = new C();  
console.log(c.color); // red  

原型继承显得很简单,不须要每次构造都调用父类的构造函数,也不须要经过复制属性的方式就能快速实现继承。但它也存在一些缺点:

① 每一个类型只有一个原型,因此不支持多重继承(即一个子类继承自多个父类)。
② 不能很好的支持多参数或动态参数的父类,显得不够灵活。

③ 占用内存多,每次继承都须要实例化一个父类,这样会存在内存占用过多的问题。

3.复制继承(知道就好)

复制继承就是利用for in 遍历对象成员,逐一复制给另外一个对象。经过这种方式来实现继承。
function A(){  
    this.color = 'red';  
}  
A.prototype.say = function() {  
    console.log(this.color);  
}  
var a = new A();  
var b = {};  
// 开始拷贝  
for(var item in a) {  
    b[item] = a[item];  
}  
// 开始测试  
console.log(b.color); // red  
b.say(); // red  

封装后:

Function.prototype.extend = function(obj){  
    for(item in obj){  
        this.constructor.prototype[item] = obj[item];  
    }  
}  
function A(){  
    this.color = 'green';  
}  
A.prototype.say = function(){  
    console.log(this.color);  
}  
// 测试  
var b = function(){};  
b.extend(new A());  
b.say(); // green  

复制继承其实是经过反射机制复制类对象中的可枚举属性和方法来模拟继承。这种能够实现多继承。但也有缺点:

① 因为是反射机制,不能继承非枚举类型的属性和方法。对于系统核心对象的只读方法和属性也没法继承。
② 执行效率差,这样的结构越庞大,低效就越明显。
③ 若是当前类型包含同名成员,这些成员会被父类的动态复制给覆盖。
④ 多重继承中,复制继承不能清晰描述父类和子类的相关性。
⑤ 在实例化后才能遍历成员,不够灵活,也不支持动态参数

⑥ 复制继承仅仅是简单的引用赋值,若是父类成员包含引用类型,那么也会带来不少反作用,如不安全,容易遭受污染等。

4.混合继承(构造+原型)

混合继承是把多种继承方式一块儿使用,发挥各个优点,来实现各类复杂的应用。
最多见的就是把类继承和原型继承一块儿使用。作法是将须要独立的属性方法放入构造函数中,而能够共享的部分则放入原型中,这样作能够最大限度节省内存而又保留对象实例的独立性。注意:
一、把方法写在原型中比写在构造函数中消耗的内存更小,由于在内存中一个类的原型只有一个,写在原型中的行为能够被全部实例共享,实例化的时候并不会在实例的内存中再复制一份
而写在类中的方法,实例化的时候会在每一个实例中再复制一份,因此消耗的内存更高。
因此没有特殊缘由,咱们通常把属性写到类中,而行为写到原型中。
二、构造函数中定义的属性和方法要比原型中定义的属性和方法的优先级高,若是定义了同名称的属性和方法,构造函数中的将会覆盖原型中的。
function A(x,y){  
    this.x = x;  
    this.y = y;  
}  
A.prototype.add = function(){  
    return (this.x-0) + (this.y-0);  
}  
function B(x,y){  
    A.call(this,x,y);  
}  
B.prototype = new A();  

// 测试  
var b = new B(2,1);  
console.log(b.x); // 2  
console.log(b.add()); // 3 

5.多重继承

继承通常包括单向继承和多向继承,单向继承模式较为简单,每一个子类有且仅有一个超类,多重继承是一个比较复杂的继承模式。一个子类可拥有多个超类。JavaScript原型继承不支持多重继承,但可经过混合模式来实现多重继承。下面让类C来继承类A和类B:
function A(x){  
    this.x = x;  
}  
A.prototype.hi = function(){  
    console.log('hi');  
}  
function B(y){  
    this.y = y;  
}  
B.prototype.hello = function(){  
    console.log('hello');  
}  
// 给Function增长extend方法  
Function.prototype.extend = function(obj) {  
    for(var item in obj) {  
        this.constructor.prototype[item] = obj[item];  
    }  
}  
// 在类C内部实现继承  
function C(x,y){  
    A.call(this,x);  
    B.call(this,y);  
};  
C.extend(new A(1));  
C.extend(new B(2));  

// 经过复制继承后,C变成了一个对象,再也不是构造函数了,能够直接调用  
C.hi(); // hi  
C.hello(); // hello  
console.log(C.x); // 1  
console.log(C.y); // 2  

在js中实现类继承,须要设置3点:

① 在子类构造函数结构体内,使用函数call()调用父类构造函数,把子类的参数传递给调用函数如上面的例子:A.call(this,x) 这样子类能够继承父类的全部属性和方法。
② 在子类和父类之间创建原型链,如上例:B.prototype = new A() 为了实现类的继承必须保证他们原型链上的上下级关系。即设置子类的prototype 属性指向父类的一个实例便可。
③ 恢复子类原型对象的构造函数, 如上例:B.prototype.constructor = B
在类继承中,call() 和 apply() 方法被频繁使用,它们之间的功能和用法都是相同的,惟一区别就是第2个参数类型不一样。若是深刻,参考:http://blog.csdn.net/tyro_java/article/details/51020720
类的构造函数中的成员,通常称之为本地成员,本地成员继承能够用call 和 apply。而类的原型成员就是类的原型中的成员。

 

关于继承更多知识参考:面向对象在javascript中的三大特征之继承

 

3、多态性


JS的函数重载
这个是多态的基础,在以前的Javascript入门已经说过了,JS函数不支持多态,可是事实上JS函数是无态的,支持任意长度,类型的参数列表。若是同时定义了多个同名函数,则以最后一个函数为准。
案例1:js不支持重载

/*****************说明js不支持重载*****/
function Person(){ 
    this.test1=function (a,b){ 
        window.alert('function (a,b)');  
    } 
    this.test1=function (a){ 
        window.alert('function (a)'); 
    } 
} 
var p1=new Person(); 
//js中不支持重载. 
//可是这不会报错,js会默认是最后同名一个函数,能够看作是后面的把前面的覆盖了。 
p1.test1("a","b"); 
p1.test1("a");

案例2:js如何实现重载

//js怎么实现重载.经过判断参数的个数来实现重载 
function Person(){ 
    this.test1=function (){ 
        if(arguments.length==1){ 
            this.show1(arguments[0]); 
        }else if(arguments.length==2){ 
            this.show2(arguments[0],arguments[1]); 
        }else if(arguments.length==3){ 
            this.show3(arguments[0],arguments[1],arguments[2]); 
        } 
    } 
    this.show1=function(a){ 
        window.alert("show1()被调用"+a); 
    } 
    this.show2=function(a,b){ 
        window.alert("show2()被调用"+"--"+a+"--"+b); 
    } 
    function show3(a,b,c){ 
        window.alert("show3()被调用"); 
    } 
} 
var p1=new Person(); 
//js中不支持重载. 
p1.test1("a","b"); 
p1.test1("a"); 

一、多态基本概念
多态是指一个引用(类型)在不一样状况下的多种状态。也能够理解成:多态是指经过指向父类的引用,来调用在不一样子类中实现的方法。
案例:

// Master类 
function Master(name){ 
    this.nam=name; 
    //方法[给动物喂食物] 
} 
//原型法添加成员函数 
Master.prototype.feed=function (animal,food){ 
    window.alert("给"+animal.name+" 喂"+ food.name); 
} 
function Food(name){ 
    this.name=name; 
} 
//鱼类 
function Fish(name){ 
    this.food=Food; 
    this.food(name); 
} 
//骨头 
function Bone(name){ 
    this.food=Food; 
    this.food(name); 
} 
function Peach(name){ 
    this.food=Food; 
    this.food(name); 
} 
//动物类 
function Animal(name){ 
    this.name=name; 
} 
//猫猫 
function Cat(name){ 
    this.animal=Animal; 
    this.animal(name); 
} 
//狗狗 
function Dog(name){ 
    this.animal=Animal; 
    this.animal(name); 
} 
//猴子 
function Monkey(name){ 
    this.animal=Animal; 
    this.animal(name); 
} 
var cat=new Cat("猫"); 
var fish=new Fish("鱼"); 

var dog=new Dog("狗"); 
var bone=new Bone("骨头"); 

var monkey=new Monkey("猴"); 
var peach=new Peach("桃"); 

//建立一个主人 
var master=new Master("zs"); 
master.feed(dog,bone); 
master.feed(cat,fish); 
master.feed(monkey,peach); 

多态利于代码的维护和扩展,当咱们须要使用同一类树上的对象时,只须要传入不一样的参数就好了,而不须要再new 一个对象。

以上就是Javascript基于对象三大特性。

 

附录:js中建立对象的各类方法(如今最经常使用的方法是组合模式)。

1)原始模式

//1.原始模式,对象字面量方式
var person = {
    name: 'Jack',
    age: 18,
    sayName: function () { alert(this.name); }
};

//1.原始模式,Object构造函数方式
var person = new Object();
person.name = 'Jack';
person.age = 18;
person.sayName = function () {
    alert(this.name);
};

显然,当咱们要建立批量的person一、person2……时,每次都要敲不少代码,资深copypaster都吃不消!而后就有了批量生产的工厂模式。

2)工厂模式

//2.工厂模式,定义一个函数建立对象
function creatPerson (name, age) {
    var temp = new Object();
    person.name = name;
    person.age = age;
    person.sayName = function () {
        alert(this.name);
    };
    return temp;
}

工厂模式就是批量化生产,简单调用就能够进入造人模式。指定姓名年龄就能够造一堆小宝宝啦,解放双手。可是因为是工厂暗箱操做的,因此你不能识别这个对象究竟是什么类型(instanceof 测试为 Object),另外每次造人时都要建立一个独立的temp对象,代码臃肿。

3)构造函数

//3.构造函数模式,为对象定义一个构造函数
function Person (name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        alert(this.name);
    };   
}
var p1 = new Person('Jack', 18); //建立一个p1对象
Person('Jack', 18);    //属性方法都给window对象,window.name='Jack',window.sayName()会输出Jack

构造函数与C++、JAVA中类的构造函数相似,易于理解,另外Person能够做为类型识别(instanceof 测试为 Person 、Object)。可是全部实例依然是独立的,不一样实例的方法实际上是不一样的函数。这里把函数两个字忘了吧,把sayName当作一个对象就好理解了,就是说张三的 sayName 和李四的 sayName是不一样的存在,但显然咱们指望的是共用一个 sayName 以节省内存。

4)原型模式

//4.原型模式,直接定义prototype属性
function Person () {}
Person.prototype.name = 'Jack';
Person.prototype.age = 18;
Person.prototype.sayName = function () { alert(this.name); };

//4.原型模式,字面量定义方式
function Person () {}
Person.prototype = {
    name: 'Jack',
    age: 18,
    sayName: function () { alert(this.name); }
};
var p1 = new Person(); //name='Jack' var p2 = new Person(); //name='Jack'

这里须要注意的是原型属性和方法的共享,即全部实例中都只是引用原型中的属性方法,任何一个地方产生的改动会引发其余实例的变化。

5)混合模式(构造+原型)

//5. 原型构造组合模式,
function Person (name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype = {
    hobby: ['running','football'];
    sayName: function () { alert(this.name); },
    sayAge: function () { alert(this.age); }
};

var p1 = new Person('Jack', 20);
//p1:'Jack',20; __proto__: ['running','football'],sayName,sayAge

var p2 = new Person('Mark', 18);
//p1:'Mark',18;__proto__: ['running','football'],sayName,sayAge

作法是将须要独立的属性方法放入构造函数中,而能够共享的部分则放入原型中,这样作能够最大限度节省内存而又保留对象实例的独立性。

 

参考:Javascript基于对象三大特性(封装性、继承性、多态性)

   简单粗暴地理解js原型链--js面向对象编程

相关文章
相关标签/搜索