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.复制继承(知道就好)
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.多重继承
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点:
关于继承更多知识参考:面向对象在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
作法是将须要独立的属性方法放入构造函数中,而能够共享的部分则放入原型中,这样作能够最大限度节省内存而又保留对象实例的独立性。