面向对象编程

174 阅读10分钟
  1. 在面向对象的程序开发思想中,每一个对象都是功能中心,具有明确分工
  2. 面向对象编程具有灵活、代码可复用、容易维护和开发的优点

特点:

  • 封装性
  • 继承性
  • 多态性

面向对象的优点:

易复用、易维护、易扩展

ES6中的对象和类

  1. 抽取对象共有的属性和方法(封装)组成一个类(模板)
  2. 对类进行实例化,获取类的对象

对象

  • 万物皆对象
  • 对象特指某一个
  • 在 javascript 中,对象是一组无序的相关属性和方法的集合,所有事物都是对象
  • 对象是由属性和方法组成
    • 属性:事务的特征,在对象中用==属性==来表示(常用名词)
    • 方法:事务的行为,在对象中用==方法==来表示(常用动词)

  • 在ES6中类没有变量提升,所以必须先定义类,才能实例化对象
  • 类里面共有的属性和方法一定要加this使用
  • constructor里面的this指向实例对象,方法里面的this指向这个方法的调用者

类的定义

  • ES6之前通过 构造函数 实现面向对象编程
    • 构造函数有prototype属性指向原型对象

    • 构造函数的prototype里面有 constructor 属性指向构造函数本身

    • 构造函数可以通过原型对象添加方法

    • 构造函数的实例对象有_____proto_____ 指向构造函数的原型对象

        class Star{
            constructor(){
      
            }
            say(){
                console.log('say');
                
            }
        }
        let curry = new Star();
        // 1. 类的本质还是一个函数 ,我们可以认为类就是构造函数的另一种写法
        console.log(typeof Star);   //function
        // 2. 类也有 prototype 指向原型对象
        console.log(Star.prototype);
        // 3. 类的 prototype 里面也有 constructor 属性 指向 (构造函数本身)类
        console.log(Star.prototype.constructor);
        // 4. 类 也可以通过原型对象添加方法
        Star.prototype.sing = function () {
            console.log(123);
        }
        console.log(Star.prototype);
        // 5. 类的实例对象也会有__proto__ 指向类的原型对象
        console.log(curry.__proto__);
      

创建类

    // 创建一个明星类 class
    class Star{
        // 类里面有构造函数constructor 实例调用时执行
        constructor(name){
            // this 指的是创建的对象
            this.name = name
        }
        sing(song){
            console.log(this.name +'---'+ song);
        }
    }
    // 利用类创建一个对象 new 
    // 对象传入'kebo’ 被 constructor接收 -> 去执行constructor
    // kebo 就有了name属性 
    let kobe = new Star('kebo');
    console.log(kobe);
    kobe.sing('baskball');
  1. 通过 class 关键字创建类,类名首字母大写
  2. 类里面有 constructor 函数,可以接受传过来的参数,同时返回给实例对象
  3. constructor 函数 只要new实例生成时,就会自动调用这个函数,如果我们不写,如果我们不写,类也会生成这个函数
  4. 生成对象要用 new
  5. 语法规范 :
    1. 生成类后面直接大括号
    2. 生成实例对象后面小括号
    3. 构造函数不用加 function
    4. 类里面创建方法都不不要 function 直接方法名+小括号+大括号
    5. 方法与方法之间不需要 逗号

类的继承

    // 类的继承
    class Grandfather{
        constructor(){
        }
        money(){
            console.log(100);
        }
    }
    // father 就 继承了Grandfather
    class Father extends Grandfather{

    }
    // curry的实例就继承了爷爷 并且继承了爷爷的钱
    let curry = new Father();
    curry.money(); //100
  •    继承中,如果调用一个方法,先查找子类自己有没有这个方法,如果有就执行子类的(就近原则),如果没有就一层一层向上找
    

#####super关键字

  • super关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数。

  • super 还起初始化的作用

      class Person{
          constructor(a,b){
              this.a = a;
              this.b = b;
          }
          sum(a,b){
              return a+b
          }
          add(who){
              return who
          }
      }
      class Justin extends Person{
          constructor(a,b){
              super(a,b);//调用父类的构造函数
          }
          say(name){
              // 调用父类的普通方法
              return super.add('我是') +'-----' + name
          }
      }
      let justen = new Justin();
      console.log(justen.sum(10,20));
      console.log(justen.say('justin'));
      console.log(justen.add('aaa'));
    
  • 想使用自己的方法 并扩展使用父类的方法 super关键字在写在前面

  • 子类在构造函数中使用super,必须放在this前面(先调用父类的构造方法,在子类)

              // super 关键字
             // 定义一个计算的大类
         class Count{
             constructor(x,y){
                 this.x = x;
                 this.y = y;
             }
             sum(x,y){
                 return x+y
             }
         }
         // 子类有一个减法方法 ,我想同时使用我的减法并且使用父类的加法
         class SmallCount extends Count{
             constructor(x,y){
                 // 把参数给到父类的构造函数 (存在super和自己的属性时 super要写在上面)
                 // 这里注意 :并不是写了super才可以调用父类的方法,既然写了 extends 关键字就可以使用父类的方法 
                 // 只是把参数给到父类的构造函数
                 super(x,y);
                 this.x = x;
                 this.y = y;
             }
             sub(x,y){
                 return x-y
             }
         }
         let numA = new SmallCount();
         console.log(numA.sub(20,10)); //10
         console.log(numA.sum(20,10));//30
    

类的公共方法

    类里面的公有属性和方法一定要加this
    class Person{
    <!--constructor里面的this指向的是创建的实例对象-->
        constructor(name,age){
            this.name = name,
            this.age = age,
                //记得一定要加this
            this.sing()
        }
        <!--谁调用这个方法 this指向谁-->
        sing(){
            console.log('我会唱歌');
        }
    }
    let curry = new Person('curry',18);
    console.log(curry); 

会默认调用sing() 这里就会直接打印 ‘我会唱歌’ 一定要加this

构造函数和原型

构造函数

  • 在ES6之前 javascript 没有类的概念 所以用构造函数来定义类的概念

  • 构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,他总是与new 一起使用,我们可以把对象中的一些公共属性和方法抽取出来,封装到这个幻函数中

      function Person(a,b) {
          this.a = a;
          this.b = b;
          Person.prototype.add=function(x,y){
              return x+y
          }
          this.sub = function(x,y){
              return x-y
          }
      }
      new Person()
    

new在执行中会做的四件事

  1. 再内存中创建一个新的空对象
  2. this指向这个新对象
  3. 执行构造函数里的代码,给这个新对象添加属性和方法
  4. 返回新对象

####构造函数中的属性和方法我们称为成员 成员分为:静态成员和实例成员 实例成员:构造函数内部通过 ==this== 添加的成员 ——> 实例成员只能通过实例化的对象来访问 静态成员:在构造函数本身上添加的成员 ——>静态成员只能通过构造函数来访问

  function Person(a, b) {
    this.a = a;
    this.b = b;
    this.sub = function (x, y) {
      return x - y;
    };
  }
  let curry = new Person('z','y');
  console.log(curry.a); // 'z'  实例成员
  Person.sex = '男'
  console.log(Person.sex) //男  静态成员

构造函数的问题

  • 构造函数存在内存浪费的问题,每次创建一个新对象,像属性之类的可以直接赋值给新对象,但是像函数这种复杂数据类型又会开辟一份新的内存空间,100个实例对象 就会开辟100份内存

        function Person(a, b) {
          this.a = a;
          this.b = b;
          this.sub = function (x, y) {
            return x - y;
          };
        }
        let curry = new Person(123,456);
        let kobe = new Person(111,222);
        console.log(curry.sub===kobe.sub); //false
        console.log(curry.a===kobe.a); //false
    

    解决方案:

    • 我们希望所有的实例对象使用同一个函数,已达到节省内存的目的,所以我们使用构造函数原型: ==prototype== 构造函数里通过原型分配的函数是所有对象 ==共享的==  
      
    • javascript 规定 ,==每一个构造函数都有一个 prototype 属性== ,指向另一个对象,注意这个prototype 就是就个对象   prototype 翻译过来就是原型 所以说 ,==每一个构造函数都有一个原型对象==,这个对象中的属性和方法,都会被构造函数所拥有
      
    • 一般情况下,我们把简单数据类型直接放到构造函数上就可以,一些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法 
      
    function Person(a, b) {
            this.a = a;
            this.b = b;
            Person.prototype.say = function () {
              console.log('我们是原型上的方法');
            };
    }
    let curry = new Person(123, 456);
    let kobe = new Person(111, 222);
    curry.say(); //我们是原型上的方法
    kobe.say(); //我们是原型上的方法
    console.log(curry.say===kobe.say);  //true

对象原型 proto

  • 疑问:为什么我们实例对象可以访问到构造函数的原型对象上的方法?
  • 回答:就是因为我们实例对象都会有一个属性____proto_____ ,而这个属性就指向构造函数的原型对象 _____proto_____ === 构造函数的原型对象

补充:方法的查找规则:先看实例对象自己身上是否有那个方法,有的话执行自己的方法,没有的话,因为有 _____proto_____的存在,就去构造函数的原型对象上找

constructor 构造函数

  • 主要作用是实例对象是通过那个构造函数创建的
  • 实例对象原型(proto) 和 构造函数的原型对象(prototype) 里面都有一个constructor 属性,因为他指向构造函数本身,所以我们称为构造函数
  • constructor 主要记录该实例对象主要引用哪个构造函数
      function Person(a, b) {
        this.a = a;
        this.b = b;
        Person.prototype.say = function () {
          console.log('我们是原型上的方法');
        };
        Person.prototype.sum = function(){
          console.log('sum');
        };
      }
      let curry = new Person(123, 456);
      let kobe = new Person(111, 222);
      curry.__proto === Person.prototype
      console.log(curry.__proto__);
      console.log(Person.prototype);

    //   输出:
        // {say: ƒ, sum: ƒ, constructor: ƒ}
                构造函数为Person
        //constructor: ƒ Person(a, b)
  • 很多时候我们需要手动的利用 constructor 这个属性指向原来的构造函数

        function Person(a, b) {
          this.a = a;
          this.b = b;
          Person.prototype = {
              constructor:Person, <!--会强制指向-->
              say(){
                  console.log('say');
              },
              sum(){
                  console.log('sum');
              }
          }
        }
        let curry = new Person(123, 456);
        let kobe = new Person(111, 222);
        console.log(curry.__proto__);
        console.log(Person.prototype);
    <!--    -------------------------------------------------------->
    
        function Person(a, b) {
          this.a = a;
          this.b = b;
          Person.prototype = {
              constructor:Person,
              say(){
                  console.log('say');
              },
              sum(){
                  console.log('sum');
              }
          }
        }
        let curry = new Person(123, 456);
        let kobe = new Person(111, 222);
        console.log(curry.__proto__.constructor);
        console.log(Person.prototype.constructor);
        console.log(curry.__proto__.constructor===Person.prototype.constructor);  //ture
    

原型链

对象成员查找机制

  • 当访问一个对象的属性或方法时,首先查找这个对象自身有没有该属性
  • 如果没有就查找它的原型(也就是_____proto_____指向的prototype原型对象)
  • 如果还是没有就查找原型对象的原型
  • 一直找到Object为止(null)
  • _____proto_____的意义是为对象成员提供一个查找线路

原型对象上的this

  • 在构造函数中,里面的this指向的是实例对象
  • 原型对象函数里面的this 也指向实例对象

利用原型对象(prototype)添加新内置对象方法

<!--查看数组内置方法-->
  console.log(Array.prototype);
  <!--利用原型对象添加-->
  Array.prototype.sum=function(){
    let sum = 0
    for (let i = 0; i < this.length; i++) {
      sum+=this[i]
    }
    return sum
  }
  
  var arr = [3,2,4];// 9
  console.log(arr.sum());
  var arr2 = new Array(3,2,4);
  console.log(arr2.sum()); //9
  
  
  <!--错误示范,不可以使用这种方式,因为这样是赋值 会覆盖原先的方法 这是不被允许的-->
Array.prototype={
    // constructor:Array,
    sum:function(){
      let sum = 0
      for (let i = 0; i < this.length; i++) {
        sum+=this[i]
      }
      return sum
    }
  }

改变this指向

  • call 方法

      //  call 方法  1、调用这个函数 ,2、并且修改函数运行时this的指向
      // fun.call(thisObj,arg1,arg2)
      // thisObj ->当前调用函数this的指向对象
      // arg1,arg2 ->传递的其他参数
    
      function fn(){
          console.log('fn');
          console.log(this);
      }
      let Obj = {
          name:'Obj'
      }
      // 1、调用函数
      fn.call();  //fn   && window
      // 2、改变this指向
      fn.call(Obj) // fn && Obj
      // 3、还可以传参数
    

继承

  1. 使用call 方法改变子类的this 实现属性或属性方法的继承

  2. 利用子类的原型对象 = 父类的实例 实现父类原型对象上的方法的继承

  3. 特别注意:原型对象的constructor是指向构造函数的,第二点虽然实现了继承 但是也会改变子类constructor的指向 导致自己子类的原型方法不能使用,所以我们必须手动让子类的原型对象指向子类的构造函数(Father.prototype.constructor = Father

     function Person(name,age) {
         this.name = name;
         this.age = age
         Person.prototype.money=function(){
             console.log('我是人类的钱');
         }
     }
    
     let per = new Person('李四',18);
     console.log(per);
     
     function Father(identity) {
         this.identity = true
         Person.call(this,'张三',13);
    
         Father.prototype.say = function(){
             console.log('say');
         }
         Father.prototype.constructor = Father
    
     }
    
     Father.prototype = new Person();
     // console.log(Person.prototype.constructor);
     // console.log(Father.prototype.constructor);
     
     let fa = new Father();
     
     console.log(fa);
     
     fa.money(); // '我是人类的钱'
     fa.say();   //'say'``