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

时间:2022-09-27 12:41:52

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

一、封装性


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

  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("不相等");
} //会输出“相等”

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

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

因此,通过构造函数添加成员方法和通过原型法添加成员方法的区别:

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();

二、继承性


继承可以解决代码复用,让编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过继承父类中的属性和方法。
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.混合继承(构造+原型)

混合继承是把多种继承方式一起使用,发挥各个优势,来实现各种复杂的应用。
最常见的就是把类继承和原型继承一起使用。做法是将需要独立的属性方法放入构造函数中,而可以共享的部分则放入原型中,这样做可以最大限度节省内存而又保留对象实例的独立性。注意:
1、把方法写在原型中比写在构造函数中消耗的内存更小,因为在内存中一个类的原型只有一个,写在原型中的行为可以被所有实例共享,实例化的时候并不会在实例的内存中再复制一份
而写在类中的方法,实例化的时候会在每个实例中再复制一份,所以消耗的内存更高。
所以没有特殊原因,我们一般把属性写到类中,而行为写到原型中。
2、构造函数中定义的属性和方法要比原型中定义的属性和方法的优先级高,如果定义了同名称的属性和方法,构造函数中的将会覆盖原型中的。
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中的三大特征之继承

三、多态性


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");

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

// 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);
};

显然,当我们要创建批量的person1、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面向对象编程

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

  1. [.net 面向对象编程基础] (11) 面向对象三大特性——封装

    [.net 面向对象编程基础] (11) 面向对象三大特性——封装 我们的课题是面向对象编程,前面主要介绍了面向对象的基础知识,而从这里开始才是面向对象的核心部分,即 面向对象的三大特性:封装.继承. ...

  2. python 面向对象三大特性(封装 多态 继承)

    今天我们来学习一种新的编程方式:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)注:Java和C#来说只支持面向对象编程,而python比较灵活即支持面 ...

  3. php部分--面向对象三大特性-封装(另加连续调用的一个例子)、继承(重写、重载的例子)、多态;

    一.封装性: 目的:为了使类更加安全. 做法:1设置私有成员 2在类中建方法,访问私有成员 3在方法里边加控制(if) 私有成员访问的两种方法: 方法一:set(可写) get(可读)做方法(可读可写 ...

  4. 深入理解Java面向对象三大特性 封装 继承 多态

    1.封装 封装的定义: 首先是抽象,把事物抽象成一个类,其次才是封装,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系 为什么需要封装: 封装符合面向对象设计原则的第一条:单一性原则,一个类 ...

  5. 面向对象三大特性——封装(含property)

    一.封装概念 封装是面向对象的特征之一,是对象和类概念的主要特性. 封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏. 二.隐藏属性 在p ...

  6. Python()- 面向对象三大特性----封装

    封装: [封装]         隐藏对象的属性和实现细节,仅对外提供公共访问方式.[好处] 1. 将变化隔离: 2. 便于使用:3. 提高复用性: 4. 提高安全性:[封装原则]      1. 将 ...

  7. Python面向对象三大特性(封装、继承、多态)

    封装 类中把某些属性和方法隐藏起来,或者定义为私有,只在类的内部使用,在类的外部无法访问,或者留下少量的接口(函数)供外部访问:从上一篇文章中的私有属性与私有方法中的代码体现了该特性. class m ...

  8. JAVA基础——面向对象三大特性:封装、继承、多态

    JAVA面向对象三大特性详解 一.封装 1.概念: 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问. 2.好处: 只能通过规定的方法访问数据. ...

  9. 夯实Java基础系列1:Java面向对象三大特性(基础篇)

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...

随机推荐

  1. asp&period;net 使用DroDownList来实现二级联动

    今天做新闻发布系统的时候,用到了二级联动,我把使用方法记录下来,以便日后查阅以及帮助新手朋友们.下面是效果图: 下面来讲解一下实现的方法: 1.在.aspx页面中,拖入两个DroDownList控件. ...

  2. ABP理论学习之发布说明

    返回总目录 查看更详细信息以及下载源代码请查看原文档 ABP v0.9.2.0 | [更新日期:2016/6/6 11:21:28 ] 解决方案转换成xproj/project.json格式. 添加了 ...

  3. Visual Studio Code asp&period;net 5环境搭建技能Get

    启动准备阶段 预热 1 VS Code 官方地址:https://www.visualstudio.com/en-us/products/code-vs.aspx 2 安装Node.js :https ...

  4. Javascript Basic Operation Extraction

    1.  logic operation : '&&' and '||'  .For this two logic operations,its' results are inconcl ...

  5. 招商银行支付dll在64位windows系统下的注册使用问题

    按照文档中的说明,注册完dll后,依然报找不到COM组件的错误.尝试过以下方法: 1.在VS中将项目编译目标改为x86,只能解决VS可以启动程序的问题,一部署到IIS中就出错. 2.估计是因为权限问题 ...

  6. Oracle - index (索引)

        索引: 一种独立于表的模式对象, 可以存储在与表不同的磁盘或表空间中 @ 索引被删除或损坏, 不会对表产生影响, 其影响的只是查询的速度 @ 索引一旦建立, Oracle 管理系统会对其进行自 ...

  7. TypeError&colon; unsupported operand type&lpar;s&rpar; for &plus;&colon; &&num;39&semi;float&&num;39&semi; and &&num;39&semi;decimal&period;Decimal&&num;39&semi;

    TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal' 浮点型和双精度类型 相加报错 from deci ...

  8. Scala-Unit-1-概述及安装

    一.Scala简介 官网:www.scala-lang.org Scala语言很强大,它集成了面对对象和函数式编程的特点,并且运行在JVM(Java Virtual Machine)上,即必须安装jd ...

  9. stl源码剖析 详细学习笔记 RB&lowbar;tree &lpar;2&rpar;

    //---------------------------15/03/22---------------------------- //一直好奇KeyOfValue是什么,查了下就是一个和仿函数差不多 ...

  10. nodejs的交叉&lpar;跨平台&rpar;编译&lpar;to android&rpar;

    nodejs的二进制包有两种安装方式node-gyp以及node-pre-gyp 这两条命令会写入该包的安装脚本. node-gyp是使用gyp工具编译源码,因此必须指定交叉编译器(参见http:// ...