WEB前端第三十八课——js类和对象,继承,设计模式

时间:2020-09-09 01:00:00 来源:互联网 热度: 作者: 佚名 字体:

1.基础

  类,是一种抽象的,并不实际存在的,表示一种事物共有特征的描述

  对象,是具体的,实际存在的,类中的某一个体

  JavaScript是一个典型的“面向过程思想”的语言,语言中不存在类和对象的概念,

  但是,由于JavaScript经常要解决“面向对象思想”的问题,所以JavaScript使用一些方法模拟面向对象思想的特征。

 2.类的创建

  构造函数,通过 new命令生成对象的函数称为构造函数,构造函数一般首字母大写

  new命令的作用是先创建一个对象,然后让对象调用构造函数,因此,构造函数中的 this指向的是 new创建的对象

  语法示例:

    function Student(name, age, sex){

      this .name=name;

      this .age=age;

      this .sex=sex;

    };

    var Lili = new Student('Lili', 20, 'female');

  上述代码中,函数 Student代表类名,通过传递实参向类的属性赋值,new命令将这些键值对构造为对象,然后赋值给变量

  注意,这种方法只是 js中众多创建类的方式之一,且并不是最优的创建方式

3.面向对象思想

  面向对象(oop),创建一个对象,让对象拥有做某件事的能力(给对象属性和方法),然后命令对象做某件事(封装、继承、多态)

  面向过程(pop),分析出解决问题的所有步骤,将其构建为一个一个函数,然后将这些步骤按照一定顺序实现(顺序、选择、循环)

  oop的核心就是如何构建一个对象,也就是“对象的封装”!

4.封装

  是指构造具有某种特征的类,以通过对其进行实例化,来获得满足需求的对象的过程

  封装的特征:

    公有,对象中的属性和方法,在对象外部能够直接访问的,称为公有属性和方法

    私有,对象中的属性和方法,仅在对象内部才可以使用的,称为私有属性和方法

  在构造函数中,通过“this .属性”的方式为类添加公有的属性和方法

    this.property所添加的内容在对象外部能够直接被访问

  在构造函数中,添加局部变量和闭包的方式为类添加私有的属性和方法

    局部变量保证了对象外部无法直接获取,闭包保证了对象外部可以间接获取

  对于所有对象都具有的共同属性,为了减少在构造对象时重复传递相同属性值,通过 prototype属性进行统一定义

    语法:类名 .prototype .相同属性值的属性 = 属性值;

5.原型

  js中给函数提供了一个对象类型的属性,叫作 prototype(原型)

  原型归函数所有,不用创建,是默认就存在的

    语法:Class .prototype .property = ' value ';

  注意,js中提供了一种机制,如果是通过类创建的对象,当访问的属性在对象中没有找到时,

     会去找创建这个对象的类的原型属性,如果能找到,则视为当前对象拥有这个属性。

  本质上,原型的存在就是为了给类的对象添加共同的属性

  作用,使用原型能够有效地节约内存空间,通过原型创建的属性和方法,能够被所有这个类创建的对象访问

6.构造函数语法小结

  function  ClassName(para1,para2,...) {    //类名首字母须大写
   var privateProperty = 'value';      //定义私有属性
   var privateFunc = function () { }      //定义私有方法
   this.publicProperty1 = para1;      //定义公有属性
   this.publicProperty2 = para2;
    
this.publicFunc = function(){ };      //定义公有方法(特权函数,可以读写privateProperty)
      ... ...
  }
  ClassName.prototype.publicProperty3 = 'value3';  //定义原型(共有)属性及属性值
  ClassName.prototype.publicFunc=function () { };   //定义原型(共有)方法

   var  obj = new  ClassName(实参1,实参2,…);    //通过类创建对象

7.原型属性

  结构:原型是一个对象,在原型中通常有两个属性

    ① 构造器Constructor,该属性指向了这个类本身(表明当前原型归属于哪个类所有)

    ② 原型指向_proto_,该属性指向原型本身,提供给通过类创建的对象使用

  作用:创建类的公有属性和公有方法,为创建对象服务

     节约内存空间,不必为每一个对象都分配公有属性和公有方法的内存

  缺点:原型中不能保存数组这类引用类型的数据

     因为地址传递的问题会导致出现修改的连锁变化

     比如,通过 obj .publicProperty .pop(); 删除共有属性数组中的元素,

        会导致其他对象访问 prototype .publicProperty 数组时也缺少了这个元素

8.原型链

  原型链构成,由对象的“_proto_”属性和对象的构造函数的原型的“_proto_”属性构成的链式结构称为原型链

        原型链的顶端是 Object对象,Object对象没有“_proto_”属性,或者说它的“_proto_”属性指向了自身

  原型链作用,访问对象的属性或方法时,首先在对象本身中查找是否拥有这个属性或方法,

        如果没有找到,那么就沿着原型链逐级向上查找,直到 Object为止

        在任何一级寻找到这个属性或方法都视为对象拥有这个属性或方法(继承)

  原型链创建,函数的原型(prototype) 设置为 另一个函数的对象(实例)

        语法示例:ClassName101 .prototype = new  ClassName1 ;

9.继承

  在面向对象的语言中,子类能够在不声明的情况下,使用父类的属性和方法的特性叫作继承

  而JavaScript语言本质上并不是一门面向对象的语言,所以,需要通过某种手段来模拟继承,这个方法就是“原型链”

  链式继承示例:

<script>
    function RichMan() {}
    RichMan.prototype.money='billions of pounds';
    var father=new RichMan();   //创建父类的实例化对象

    function Son() {}
    Son.prototype=father;       //创建子类继承关系
    
    var boy=new Son();          //创建子类实例化对象
    console.log(boy.money);     //billions of pounds
</script>

  存在的问题:

    ① 原型链继承,子类实例化不能向父类构造函数传参,但是可以直接访问父类的原型属性

     构造继承,只能访问到父类实例属性,实例化时可以向父类构造函数传参,但是不能访问父类的原型属性和方法

    ② 原型链继承,子类 prototype的 constructor属性,实际上就是父类 prototype的 constructor属性,这样并不合理

     构造函数,子类 prototype的 constructor属性是子类本身,父类也是同样,但构造继承的子类无法享有父类的prototype属性和方法

  构造继承示例:

    function RichMan(fcash,fhouse,fcar) {
        this.cash=fcash;
        this.house=fhouse;
        this.car=fcar;
    }
/*    RichMan.prototype.money='billions of pounds';
    var father=new RichMan();   //创建父类的实例化对象*/

    function Son(scash,shouse,scar) {
        RichMan.call(this,scash,shouse,scar);      //创建子类构造继承关系
    }
    var john=new Son(1,2,3);        //可以访问父类实例属性,but not 原型属性
    var dancy=new Son(4,5,6);

10.组合继承

  为了解决“原型链继承”和“构造继承”的各自缺点,在创建子类继承时同时使用两种继承方式,即组合继承

 1 <script>
 2     function RichMan(fcash,fhouse,fcar) {
 3         this.cash=fcash;
 4         this.house=fhouse;
 5         this.car=fcar;
 6     }
 7     RichMan.prototype.money='billions of pounds';
 8     // var father=new RichMan();   //创建父类的实例化对象
 9 
10     function Son(scash,shouse,scar) {
11         RichMan.call(this,scash,shouse,scar);   //创建“构造继承”关系
12     }
13     Son.prototype=new RichMan();        //创建“链式继承”关系
14     var john=new Son(1,2,3);
15     var dancy=new Son(4,5,6);
16     console.log(john);                 //Son {cash: 1, house: 2, car: 3}
17     console.log(dancy.money);          //billions of pounds
18 
19 /*    Son.prototype=father;       //创建子类继承关系
20     var boy=new Son();          //创建子类实例化对象
21     console.log(boy.money);     //billions of pounds*/
22 </script>

  组合继承的弊端:

    在子类中调用了两次父类构造函数,一次用于构造继承,一次用于链式继承,也就是对实例属性初始化了两次,

    但这一弊端不太致命,子类在实例化时,构造继承的实例属性覆盖了链式继承的实例属性,只是多消耗了一些内存

  寄生组合继承

    核心思想:通过寄生方式,砍掉父类的实例属性,这样就能在调用两次父类的构造的时候,不会再次实例属性/方法。 

 1 <html lang="en">
 2 <head>
 3     <meta charset="UTF-8">
 4     <title>继承</title>
 5 </head>
 6 <body>
 7 
 8 <script>
 9     function RichMan(fcash,fhouse,fcar) {
10         this.cash=fcash;
11         this.house=fhouse;
12         this.car=fcar;
13     }
14     RichMan.prototype.money='billions of pounds';
15     // var father=new RichMan();   //创建父类的实例化对象
16 
17     function Son(scash,shouse,scar) {
18         RichMan.call(this,scash,shouse,scar);   //创建子类“构造继承”关系
19     }
20     // Son.prototype=new RichMan();       //创建“链式继承”关系
21     Son.prototype.constructor=Son;        //将子类原型属性‘constructor’指向子类本身!
22 
23     (function () {                     //创建自执行函数,并嵌套一个空的构造函数 Medi,
24         function Medi() { }             //将空构造函数插入到原型链作为中间节点,即原子类变为孙类
25         Medi.prototype=new RichMan();   //这样就避免在每一次子(孙)类实例化时进行两次父类实例化属性和方法
26         Son.prototype=new Medi();       //也就是用这种寄生方式替代原来的直接“链式继承”关系
27     }());
28 
29     var john=new Son(1,2,3);
30     var dancy=new Son(4,5,6);
31     console.log(john);                 //Son {cash: 1, house: 2, car: 3}
32     console.log(dancy.money);          //billions of pounds
33 
34 /*    Son.prototype=father;       //创建子类继承关系
35     var boy=new Son();          //创建子类实例化对象
36     console.log(boy.money);     //billions of pounds*/
37 </script>
38 </body>
39 </html>

 

11.设计模式

  设计模式(Design Pattern)是一套被反复使用的、多数人知晓的、经过分类的代码设计经验的总结(模板)

  作用:提高代码可重用性、让代码更容易被他人理解、提高代码的可靠性,

     设计模式使代码编写真正工程化,是软件工程的基石脉络,如同大厦的结构一样

  常见种类:

    ① 工厂模式

    ② 构造函数模式

    ③ 原型模式

    ④ 混合模式

    ⑤ 动态原型模式

12.工厂模式

    <script>
        function RichMan(fcash,fhouse,fcar) {
            var richMan={};             //定义局部变量,类型为Object对象
            richMan.cash=fcash;         //直接定义内部对象的属性
            richMan.house=fhouse;
            richMan.car=fcar;
            return richMan;             //将内部对象作为调用函数的返回值
        }
        var man=RichMan('muchCash','bigHouse','luxuryCar');
        console.log(man);
        console.log(man instanceof RichMan);        //返回值为 false
    </script>

  工厂模式是软件开发中经常被使用的一种设计模式

  instanceof 方法一个实例是否归属于一个类。

  通过工厂模式创建的对象,最大的问题是无法确定其属于哪一个类!

13.构造函数模式

    <script>
        function RichMan(fcash,fhouse,fcar) {
            this.cash=fcash;         //定义this属性
            this.house=fhouse;
            this.car=fcar;
        }
        //通过 new命令创建对象
        var man=new RichMan('muchCash','bigHouse','luxuryCar');
        console.log(man);
        console.log(man instanceof RichMan);        //返回值为 true
    </script>

  构造函数和工厂模式最大的区别是:

    没有显示创建一个对象,而是通过 new命令隐式创建一个对象

    然后让隐式对象来实际执行构造函数,因此构造函数中的 this指向的是这个隐式对象

  通过构造函数创建的对象可以明确判断其归属于哪一个类

  构造函数创建对象必须使用 new命令,函数名的首字母通常大写

  构造函数模式最大的问题是面对子类对象共有的属性值,不能有效地节约内存占用!

14.原型模式

    <script>
        function RichMan(fcash,fhouse,fcar) {}
        //使用 prototype方法定义类的共有属性
        RichMan.prototype.cash='muchCash';
        RichMan.prototype.house='bigHouse';
        RichMan.prototype.car='luxuryCar';
        //通过 new命令创建对象
        var man=new RichMan();
        man.house='manyHouse';  //对于相同属性不同属性值时单独赋值
        console.log(man);
        console.log(man instanceof RichMan);        //返回值为 true
    </script>

  弊端:在处理不同属性值的公有属性时,增加了内存的空间占用!

15.混合模式

    <script>
        function RichMan(fcash,fhouse,fcar) {
            this.cash=fcash; 
            this.house=fhouse;
            this.car=fcar;
        }
        RichMan.prototype.advantage=function () {
            console.log('数钱数到手抽筋')
        }
        //通过 new命令创建对象
        var man=new RichMan('muchCash','bigHouse','luxuryCar');
        console.log(man);
        man.advantage();
    </script>

16.动态原型模式

    <script>
        function RichMan(fcash,fhouse,fcar) {
            this.cash=fcash;
            this.house=fhouse;
            this.car=fcar;
            //使用“懒加载”的方式,定义共有属性的原型
            if (typeof RichMan._initialized=='undefined'){
                RichMan.prototype.advantage=function () {
                    console.log('数钱数到手抽筋')
                }
                RichMan._initialized=true;
            }
        }
        //通过 new命令创建对象
        var man=new RichMan('muchCash','bigHouse','luxuryCar');
        console.log(man);
        man.advantage();
    </script>

  懒加载:使用时才加载和占用内存空间,在没有使用之前相当于不存在

  动态原型模式和混合模式很相似,二者都是为了解决原型模式中所有内容都公有的问题

  动态原型模式的特点在于,

    通过判断一个类的 “._initialized” 属性的类型(typeof),进而判断这个类有没有被实例化过,

    如果没有被实例化过,在第一次调用(初始化)时就会将其释放,并将属性值写为 true。

  “._initialized”属性是每一个类都拥有的私有属性,它仅用来表示类是否被实例化过,是Boolean类型的可读写属性。