JavaScript面向对象的三大特性,封装、继承和多态

面向对象编程?

我在我java面向对象文章中说过,万事万物皆对象。面向对象的思想主要是以对象为主,将一个问题抽象出具体的对象,并且将抽象出来的对象和对象的属性和方法封装成一个类。

面向对象编程思想(直接将生活逻辑映射到我们程序中的思想叫做面向对象编程思想)
1.找出实体
2.分析出实体属性和功能
3.让实体之间相互作用
举个例子来阐述一哈:

例子:
有一辆车 60km\h 跑在一条1000km的路上 多久可以跑完?

var cars ={//车-》实体1
           speed:60,//车速度 属性1
           run:function(road){//车会在路上跑 属性2
            return road.length /this.speed; 
           }
       };
       var kuakai ={//跨海大桥,路。。实体2
           length:1000//路的属性
       };
       var hours=cars.run(kuakai);//让车在路上跑,让实体之间相互作用
       alert(hours);

首先:面向对象是一个编程思想,支撑面向对象编程思想的语法是类(ECMA6之前没有类这个概念)和对象。所以JavaScript使用构造函数(强制改变this指向函数)充当类的角色

**

1. 封装函数

**
封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。
javascript提供了一个构造函数工厂模式,用来在创建对象时初始化对象。
ps:工厂模式:
1.原料
2.加工
3.出厂
注:凡是满足上述三个步骤创建对象的函数,我们都把它叫做工厂方法。
举个栗子
//创建一个有名字有性别的人

function Person(name,sex){
    // 1.原料(声明一个空对象)
var obj = new Object();
    // 2.加工(添加属性和方法)
    obj.name=name;
    obj.sex =sex;
    obj.showName=function(){
    alert("我的名字是"+p1.name);
    }
    obj.showSex = function(){
        alert("我是"+p1.sex+"的");
    }
    //3.出厂(输出)
    return obj
}

注意:构造函数时需要注意三点代码规范问题

  1. 首字母大写(建议构造函数首字母大写,即使用大驼峰命名,非构造函数首字母小写)
  2. 内部使用this
  3. 使用 new生成实例

通过构造函数添加属性和方法实际上也就是通过this添加的属性和方法。因为this总是指向当前对象的,所以通过this添加的属性和方法只在当前对象上添加,是该对象自身拥有的。所以我们实例化一个新对象的时候,this指向的属性和方法都会得到相应的创建,也就是会在内存中复制一份,这样就造成了内存的浪费。

function Cat(name,color){
 this.name = name;
 this.color = color;
 this.eat = function () {
 alert('吃老鼠')
 }
 }
var cat1 = new Cat('tom','red')//生成实例 通过this定义的属性和方法,我们实例化对象的时候都会重新复制一份

通过prototype 原型对象
概念:每一个函数上,都有一个原型对象prototype
在类上通过 this的方式添加属性和对象会导致内存浪费的问题,我们就考虑,有什么方法可以让实例化的类所使用的方法直接使用指针指向同一个方法。于是,就想到了原型的方式

也就是说,对于那些不变的属性和方法,我们可以直接将其添加在类的prototype 对象上。便于后续使用同一套代码

function Cat(name,color){
    this.name = name;
    this.color = color;
  }
  Cat.prototype.type = "猫科动物";
  Cat.prototype.eat = function(){alert("吃老鼠")};
 var cat1 = new Cat("大毛","黄色");
  var cat2 = new Cat("二毛","黑色");
  alert(cat1.type); // 猫科动物
  cat1.eat(); // 吃老鼠
// 这时所有实例的type属性和eat()方法,是猫这个一个类共有的属性,所以添加protopyte方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。

继承

继承:1.继承,分类更加细分的构造函数
按照我的理解,继承分为继承对象的属性和继承对象的共有的函数:继承对象的属性直接使用call强制当前对象this转成继承对象this
继承对象的属性是:使用call(this,{
});强制将子类的this指向父类

继承函数时:
for(var funcName in Dog.prototype){
Teddy.prototype[funcName] = Dog.prototype[funcName]
}

 function Dog({name,type,age}){//父类函数的属性
           this.name = name;
           this.type= type;
           this.age = age;
       }
       /* 
            通过构造函数的原型添加方法
       */
       Dog.prototype ={//共有的函数
           run:function(){
               alert(this.name+'会飞快的奔跑');
           },
           showSelf:function(){
               alert(`这是一个${this.type}的,${this.age}岁的,叫${this.name}的小狗`);
           }
       }


       /* 
            分类更加细分的构造函数。继承
       */
      function Teddy({name,type,age,color}){
          //1.继承父一级构造函数所有的属性
        Dog.call(this,{
            name:name,
            type:type,
            age:age
        })

        //添加自己的函数
        this.color = color;
      }
      //继承共有的函数
	for(var funcName in Dog.prototype){
		Teddy.prototype[funcName] = Dog.prototype[funcName]
                    }
      	var xiaohong = new Teddy({
          name:'小花',
          type:'taidi',
          age:'5',
          color:'red'
      })

类的原型对象prototype对象的作用就是为类的原型添加共有方法的,但是类不能直接访问这些方法,只有将类实例化之后,新创建的对象复制了父类构造函数中的属性和方法,并将原型__proto__ 指向了父类的原型对象。这样子类就可以访问父类的public 和protected 的属性和方法,同时,父类中的private 的属性和方法不会被子类继承。

构造函数继承:构造函数继承的核心思想就是SuperClass.call(this,id),直接改变this的指向,使通过this创建的属性和方法在子类中复制一份,因为是单独复制的,所以各个实例化的子类互不影响。但是会造成内存浪费的问题

//构造函数继承
 //声明父类
 function SuperClass(id) {
 var name = 'javascript'
 this.books=['javascript','html','css'];
 this.id = id
 }
 //声明父类原型方法
 SuperClass.prototype.showBooks = function () {
 console.log(this.books)
 }
 //声明子类
 function SubClass(id) {
 SuperClass.call(this,id)
 }
 //创建第一个子类实例
 var subclass1 = new SubClass(10);
 var subclass2 = new SubClass(11);
 console.log(subclass1.books);
 console.log(subclass2.id);
 console.log(subclass1.name); //undefined
 subclass2.showBooks();

组合式继承:

我们先来总结一下类继承和构造函数继承的优缺点:

类继承 / 构造函数继承

核心思想 :子类的原型是父类实例化的对象 / SuperClass.call(this,id)

优点 :子类实例化对象的属性和方法都指向父类的原型 / 每个实例化的子类互不影响

缺点 :子类之间可能会互相影响 / 内存浪费

所以组合式继承就是汲取两者的优点,即避免了内存浪费,又使得每个实例化的子类互不影响。

//组合式继承
 //声明父类
 var SuperClass = function (name) {
 this.name = name;
 this.books=['javascript','html','css']
 };
 //声明父类原型上的方法
 SuperClass.prototype.showBooks = function () {
 console.log(this.books)
 };
 //声明子类
 var SubClass = function (name) {
 SuperClass.call(this, name)
 };
 //子类继承父类(链式继承)
 SubClass.prototype = new SuperClass();
 //实例化子类
 var subclass1 = new SubClass('java');
 var subclass2 = new SubClass('php');
 subclass2.showBooks();
 subclass1.books.push('ios'); //["javascript", "html", "css"]
 console.log(subclass1.books); //["javascript", "html", "css", "ios"]
 console.log(subclass2.books); //["javascript", "html", "css"]

寄生组合继承:
那么问题又来了~组合式继承的方法固然好,但是会导致一个问题,父类的构造函数会被创建两次(call()的时候一遍,new的时候又一遍),所以为了解决这个问题,又出现了寄生组合继承。

刚刚问题的关键是父类的构造函数在类继承和构造函数继承的组合形式中被创建了两遍,但是在类继承中我们并不需要创建父类的构造函数,我们只是要子类继承父类的原型即可。所以说我们先给父类的原型创建一个副本,然后修改子类constructor属性,最后在设置子类的原型就可以了~

//原型式继承

//原型式继承其实就是类式继承的封装,实现的功能是返回一个实例,改实例的原型继承了传入的o对象

function inheritObject(o) {
 //声明一个过渡函数对象
 function F() {}
 //过渡对象的原型继承父对象
 F.prototype = o;
 //返回一个过渡对象的实例,该实例的原型继承了父对象
 return new F();
 }
 //寄生式继承
 //寄生式继承就是对原型继承的第二次封装,使得子类的原型等于父类的原型。并且在第二次封装的过程中对继承的对象进行了扩展
 function inheritPrototype(subClass, superClass){
 //复制一份父类的原型保存在变量中,使得p的原型等于父类的原型
 var p = inheritObject(superClass.prototype);
 //修正因为重写子类原型导致子类constructor属性被修改
 p.constructor = subClass;
 //设置子类的原型
 subClass.prototype = p;
 }
 //定义父类
 var SuperClass = function (name) {
 this.name = name;
 this.books = ['javascript','html','css']
 };
 //定义父类原型方法
 SuperClass.prototype.getBooks = function () {
 console.log(this.books)
 };
 //定义子类
 var SubClass = function (name) {
 SuperClass.call(this,name)
 }
 inheritPrototype(SubClass,SuperClass);
 var subclass1 = new SubClass('php')

多态

在子一级构造函数重写共有对象的f方法

注意:只会在子一级生效,并不会形象父一级构造函数的方法

继承和多态同一件事情的两种不同的侧重

继承:侧重是从父一级构造函数,继承到的属性和方法
多态:侧重是,子一级,自己重写和新增的属性和方法

ps:哈哈哈,看到这里是不是还是有点蒙,那我只好告诉你最简单的方法了。

其实ECMA6中已经添加了class语法了。直接使用extends继承属性和方法即可
使用class类的方法如下:

/*
        ECMA6 class语法
      */
     class Person{
         //class 属性添加
         constructor(name,sex,age){
             this.name = name;
             this.sex = sex;
             this.age = age
         }
         showSelf(){
             alert(`我是一个叫${this.name},今年${this.age}岁,性别${this.sex}`);
         }
     }
    //  var p1 = new Person('bule','男',10);
    //  p1.showSelf();
     //extend  继承
     class Worker extends Person{
         constructor(name,sex,age,job){
            //1.继承到父一级的属性
             super(name,sex,age);
             this.job = job
         }
    showjob(){
        alert("我的工作是"+this.job);
     }
     }
     var  w1 = new Worker('小米','男',20,'程序员');
     w1.showSelf();
     w1.showjob();

对比一下ECMA6以前的方法:

        /* 
            传统构造函数的方法
         */
       function Person(name,age,sex){
             this.name=name;
             this.age =age;
             this.sex =sex;
         }
         Person.prototype.showSelf =function(){
             alert(`我是一个叫${this.name},今年${this.age}岁,性别${this.sex}`);
         }
         //白领
         //继承属性
         function Worker(name,sex,age,job){
            Person.call(this,name,sex,age);
            this.job = job;
         }
        //继承方法
        //1.原型链  继承父一级的方法
         for(var i in Person.prototype){
        Worker.prototype[i] = Person.prototype[i];
         }
         //2.方法二  Object.create();
        // Worker.prototype = Object.create(Person.prototype);
        // 3.调用构造函数继承
        //  Worker.prototype  = new Person();
        Worker.prototype.showjob = function(){
             alert("我的工作是"+this.job);
         }
         var w1 = new Worker('小米','男',18,'白领');
         w1.showSelf();
         w1.showjob(); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值