js之面向对象编程,封装 继承 多态(详细篇)

本节知识点

  什么是面向对象编程
   面向对象和面向过程的区别
   new的实质
   javascript 模仿 private public protected,即作用域
   封装
   继承
   多态
   本节思维导图

一,什么是面向对象编程

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

在这里插入图片描述

二,面向对象和面向过程的区别

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

举一个小栗子,问把大象放进冰箱分为几部~

面向过程
  • 开门(冰箱);
  • 装进(冰箱,大象);
  • 关门(冰箱)。
面向对象的解决方法
  • 冰箱.开门()
  • 冰箱.装进(大象)
  • 冰箱.关门()

可以上面的这个例子,看出,思想是不一样的,冰箱开门、关门,相当于属性和一些方法,但这个例子还不能表现出具体的差别

五子棋例子

面向过程

步骤 
  1,开始游戏
  2,黑子先走
  3,绘制画面
  4,判断输赢
  5,轮到白子
  6,绘制画面
  7,判断输赢
  8,返回步骤2

面向对象的解决方法

步骤
  1,黑白双方,这两方的行为是一模一样的
  2,棋盘系统,负责绘制画面
  
第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的i变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。

三,new的实质

  • 1,创建对象,并给予属性名为__proto__,值为构造函数原型(prototype)的属性。
  • 2,将构造函数的this指向为刚创建的对象。
  • 3,执行构造函数的语句。
  • 4,将创建的对象进行返回。
    但是这里需要看构造函数有没有返回值,如果构造函数的返回值为基本数据类型string,boolean,number,null,undefined,那么就返回新对象,如果构造函数的返回值为对象类型,那么就返回这个对象类型

     function fn() {  }
     var obj = new fn();
     console.log(obj);
     console.log(fn.prototype);

相当于

	var obj = new fn();
	obj.__proto__ = fn.prototype;
	fn.call(obj);//call 为了动态改变this而出现的

分别打印出

四,javascript 模仿 private public protected,即作用域

在js中,并没有类似于private public protected这样的关键字,但是我们又希望我们定义的属性和方法有一定的访问限制,于是我们就可以模拟private public protected这些访问权限

定义:
  public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
 
  private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用,私有财产神圣不可侵犯嘛,即便是子女,朋友,都不可以使用。
 
  protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。

作用域类内部本包子孙包其他
public
private×××
protected×

举一个小栗子

var Book = function (id, name, price) {
        //private(在函数内部定义,函数外部访问不到,实例化之后实例化的对象访问不到)
        var num = 1;
        var id = id;
        function checkId() {
            console.log('private')
        }
        
        //protected(可以访问到函数内部的私有属性和私有方法,在实例化之后就可以对实例化的类进行初始化拿到函数的私有属性)
        this.getName = function () {
            console.log(id)
        }
        this.getPrice = function () {
            console.log(price)
        }

        //public(实例化的之后,实例化的对象就可以访问到了~)
        this.name = name;
        this.copy = function () {
            console.log('this is public')
        }
    }

    //在Book的原型上添加的方法实例化之后可以被实例化对象继承
    Book.prototype.proFunction = function () {
        console.log('this is proFunction')
    }

    //在函数外部通过.语法创建的属性和方法,只能通过该类访问,实例化对象访问不到
    Book.setTime = function () {
        console.log('this is new time')
    }
    var book1 = new Book('111','悲惨世界','$99')
    book1.getName();        // 111 getName是protected,可以访问到类的私有属性,所以实例化之后也可以访问到函数的私有属性
    book1.checkId();        //报错book1.checkId is not a function
    console.log(book1.id)   // undefined id是在函数内部通过定义的,是私有属性,所以实例化对象访问不到
    console.log(book1.name) //name 是通过this创建的,所以在实例化的时候会在book1中复制一遍name属性,所以可以访问到
    book1.copy()            //this is public
    book1.proFunction();    //this is proFunction
    Book.setTime();         //this is new time
    book1.setTime();        //报错book1.setTime is not a function

五,面向对象的三大特性:

1,封装

面向对象有三大特性,封装、继承和多态。对于ES5来说,没有class的概念,并且由于js的函数级作用域(在函数内部的变量在函数外访问不到),所以我们就可以模拟 class的概念,在es5中,类其实就是保存了一个函数的变量,这个函数有自己的属性和方法。将属性和方法组成一个类的过程就是封装。

封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。

关于类的书写详细和定义https://blog.csdn.net/weixin_44797182/article/details/103410301

1.1,通过构造函数添加
function Cat(name,color){
        this.name = name;
        this.color = color;
        this.eat = function () {
            alert('吃老鼠')
        }
    }

生成实例

var cat1 = new Cat('tom','red')

通过this定义的属性和方法,我们实例化对象的时候都会重新复制一份

1.2,通过原型prototype

Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
也就是说,对于那些不变的属性和方法,我们可以直接将其添加在类的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(); // 吃老鼠
1.3,在类的外部通过.语法添加

我们还可以在类的外部通过. 语法进行添加,因为在实例化对象的时候,并不会执行到在类外部通过.语法添加的属性,所以实例化之后的对象是不能访问到. 语法所添加的对象和属性的,只能通过该类访问。

即通过.添加的属性,只能在实例化的呢个函数中显示,与此相关的是,prototype则是直接修改原来的

1.4三者的区别

通过构造函数、原型和. 语法三者都可以在类上添加属性和方法。但是三者是有一定的区别的。

构造函数:通过this添加的属性和方法总是指向当前对象的,所以在实例化的时候,通过this添加的属性和方法都会在内存中复制一份,这样就会造成内存的浪费。但是这样创建的好处是即使改变了某一个对象的属性或方法,不会影响其他的对象(因为每一个对象都是复制的一份)。

原型:通过原型继承的方法并不是自身的,我们要在原型链上一层一层的查找,这样创建的好处是只在内存中创建一次,实例化的对象都会指向这个prototype 对象,但是这样做也有弊端,因为实例化的对象的原型都是指向同一内存地址,改动其中的一个对象的属性可能会影响到其他的对象

. 语法:在类的外部通过. 语法创建的属性和方法只会创建一次,但是这样创建的实例化的对象是访问不到的,只能通过类的自身访问

2,继承

继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。

2.1,类式继承

所谓的类式继承就是使用的原型的方式,将方法添加在父类的原型上,然后子类的原型是父类的一个实例化对象。

    var SuperClass = function () {
        var id = 1;
        this.name = ['javascript'];
        this.superValue = function () {
            console.log('这是父类输出的内容');
        }
    };

    //为父类添加共有方法
    SuperClass.prototype.getSuperValue = function () {
        return this.superValue();
    };

    //声明子类
    var SubClass = function () {
        this.subValue = function () {
            console.log('这是子类输出的内容')
        }
    };

    //继承父类
    SubClass.prototype = new SuperClass();

    //为子类添加共有方法
    SubClass.prototype.getSubValue = function () {
        return this.subValue()
    };

    var sub = new SubClass();
    var sub2 = new SubClass();

    sub.getSuperValue();         //这是父类输出的内容
    sub.getSubValue();           //这是子类输出的内容

    console.log(sub.name);      //javascript
    sub.name.push('java');      //["javascript"]
    console.log(sub2.name)      //["javascript", "java"]

其中最核心的一句代码是SubClass.prototype = new SuperClass() ;

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

缺点:如上述代码的最后一段,使用类继承的方法,如果父类的构造函数中有引用类型,就会在子类中被所有实例共用,因此一个子类的实例如果更改了这个引用类型,就会影响到其他子类的实例。

2.2,构造函数继承

正式因为有了上述的缺点,才有了构造函数继承,构造函数继承的核心思想就是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);   //    (3) ["javascript", "html", "css"]
    console.log(subclass1.name);   //undefined
    console.log(subclass2.id);      //11

2.3,组合式继承

类继承和 构造函数继承的优缺点:

类继承构造函数继承
核心思想子类的原型是父类实例化的对象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');
    subclass1.books.push('ios');    

    subclass2.showBooks();
    console.log(subclass1.books);  
    console.log(subclass2.books);  

分别打印出

3,js多态

多态其实是不同对象作用于同一操作产生的不同效果。多态的思想实际上是把“想做什么”和“谁去做”分开。

多态的好处在于:你不必再向对象询问“你是什么类型”后得到答案,在调用对象的某个行为。你尽管调用这个行为就是了,剩下的都交给多态来负责。

规范来说:多态最根本的作用就是通过把过程化的条件语句转化为对象的多态性,从而消除这些条件分支语句。


        var cat = {
            eat: function () {
                console.log('猫咪喜欢吃鱼!');
            }
        }
        var dog = {
            eat: function () {
                console.log('狗喜欢吃肉');
            }
        }

        function hobby(animal) {
            if (animal.eat instanceof Function) {
                animal.eat()
            }
        }
        hobby(cat); //猫咪喜欢吃鱼!
        hobby(dog); //狗喜欢吃肉

本节知识点思维导图

在这里插入图片描述

posted @ 2022-04-02 09:47  coderwcb  阅读(164)  评论(0编辑  收藏  举报