JS 面向对象

1.面向对象基本介绍

  1. 什么是面向对象
    所谓的面向对象,是一种编程思想,编程思路,代码的书写格式
    之前为了简化代码,我们是将程序封装为函数的形式来调用
    函数会有一些小问题:
    函数内部定义的局部作用域变量,函数外部无法直接调用
    函数调用使用时,会占用大量的系统内存,容易造成数据溢出数据泄露,容易被黑客攻击
    函数一般只是封装一些模块化的功能
    复杂程序的封装会,封装成其他形式,使用面向对象的方式来封装程序
  2. 简单介绍面向对象的编程思想
    我们之前的编程方式,称为面向过程的编程方式
    面向过程和面向对象编程思想的区别
    举例生活中的实例 : 中午要吃饺子
    面向过程:
    1,和面
    2,和饺子馅
    3,包饺子–擀皮,放馅,包饺子
    4,烧水
    5,水开了煮饺子
    6,捞饺子
    7,吃饺子
    面向对象:
    1,找一个饺子馆 — 封装的面向对象的程序,完成制作饺子的过程,我们只要点就可以了
    2,点饺子 — 调用封装的程序
    3,做好了吃饺子
    4,结账

总结
面向过程 : 自己独立的一步一步的完成程序的定义和执行
面向对象 : 有封装好的面向对象的的程序
直接调用执行就可以了
功能和作用类似于 封装好的函数
但是 封装的语法和思想与函数不同

2. 对象的再介绍

为什么要面向对象,不是面向字符串等
对象的优点

  1. 对象中,可以定义并且存储多个数据单元以及所有JavaScript支持的数据类型

     //   const obj = {age:18,name:'张三',fun:()=>{},arr:[1,2,3,]}
    
  2. 对象中,调用具体数据很方便

     //   调用数据时,不用考虑数据的顺序
     //     const obj1 =  {age:18,name:'张三'}
     //     const obj2 =  {name:'张三',age:18}
     //     调用 name 和 age 都是不用考虑 数据单元的顺序
     //     只要键名/属性 输入正确就可以了
    
  3. 对象中,可以定义函数,还可以通过函数的this,方便的调用对象本身的数据

     //     const obj1 =  {age:18,name:'张三',fun:function(){ console.log(this.name) }}
     //     const obj2 =  {age:18,name:'张三',fun:function(){ console.log(this.name) }}
     //   调用对象的数据,不用管对象名称 是什么,只要通过this,就可以指向这个对象, 
     //   obj1 中的 this,指向的就是obj1   obj2 中的 this,指向的就是obj2
     //   this.name  分别就是 obj1.name  obj2.name  
    

4. 面向对象编程的基本思想

面向对象的基本思想
基本思路就是,创建一个对象,给这个对象,添加上属性和属性值,还有函数等方法
之后通过操作这个对象,来完成需要的效果

先通过一个函数的方法,来创建对象

    function createObj(){
        // 创建对象
        const obj = {};

        // 给对象添加属性和属性值
        obj.name = '张三';
        obj.age = 18;
        obj.addr = '北京';
        obj.sex = '男';

        // 给对象添加方法
        obj.funNameAge = function(){
            console.log(this.name , this.age);
        }
        obj.funNameAddr = function(){
            console.log(this.name , this.addr);
        }
        obj.funSexAge = function(){
            console.log(this.sex , this.age);
        }
        obj.funAll = function(){
            console.log(this.name, this.sex , this.age, this.addr);
        }

        // 返回这个创建好的对象
        return obj;
    }

    // 调用函数,函数创建对象,并且作为返回值
    // 变量中存储的就是函数创建的对象
    const obj = createObj();

    // 可以通过调用obj中存储的对方的方法,来实现功能
    obj.funAll();

5. 工厂模式

工厂模式
所谓的工厂模式,是一种书写函数的语法形式,语法规范
就向工厂中的流水线一样,按照步骤来执行需要的操作
步骤1,创建对象
步骤2,给对象定义属性和属性值
步骤3,给对象添加方法
步骤4,定义对象为返回值

标准的工厂模式,会有对应的参数

       function createDumpling(pi,mian,xian,tioliao){
           // 创建包饺子对象
           const dumpling = {};

           // 给包饺子对象,添加属性
           dumpling.pi = pi;
           dumpling.xian = xian;
           dumpling.mian = mian;
           dumpling.tiaoliao = tiaoliao;

           // 给包饺子对象,添加方法

           // 和面
           dumpling.huomian = function(){
               console.log(this.mian);
           }

           // 和饺子馅
           dumpling.huoxian = function(){
               console.log(this.xian);
           }

           // 包饺子
           dumpling.bao = function(){
               console.log(this.xian,this.pi,this,mian);
           }

           // 煮饺子
           dumpling.zhu = function(){
               console.log('煮饺子了,等着吃吧');
           }

           // 返回包饺子对象
           return dumpling;
       }

       // 要开始包饺子
       
       // 创建一个执行包饺子功能的对象,并且输入参数

       const baojiaozi1 = createDumpling('薄皮' , '白面粉' , '猪肉大葱');

       const baojiaozi2 = createDumpling('厚皮' , '玉米面' , '鱼香肉丝');

       console.log(baojiaozi1);
       // 可以调用任意的封装的方法
       baojiaozi1.huoxian();   // 调用和饺子馅方法
       baojiaozi1.huomian();     // 调用和面方法

6. 面向对象编程的优点

面向对象编程的优点
优点与函数的优点类似
高内聚 低耦合
高内聚 : 将所有需要的程序,都定义封装在对象内 ; 对象中存储所有需要的属性,所有需要的方法
低耦合 : 尽量减少特殊程序的执行

面向对象编程的特点
抽象 — 通过描述 对象 共有的特点(属性和属性值) , 来形容一个对象
这个对象不是一个非常具体事例的内容,是一个抽象化的实例
制作饺子,制作包子,制作馅饼,制作馅窝头…
有皮,有汁,味甜 — 西瓜 , 橘子 , 桃 , 爆浆蛋糕 , 夹心糖果 …
四条腿的,有一个面的 — 桌子 , 凳子 , 床 …

封装 — 将所有的程序,都定义在一个对象中

7. 自定义构造函数

创建对象的方法,有两种
字面量 const obj = {}
构造函数 const obj = new Object()
这个构造函数,就是JavaScript程序定义好的构造函数,我们直接使用就可以了
所谓的构造函数,实际也是一种函数

构造函数专门用于生成,定义对象的
通过构造函数,生成的对象,称为实例化对象
强调: 构造函数,就是一种函数,是专门用于生成对象的函数
实例化对象,就是通过构造函数,生成的对象,称为实例化对象

构造函数分为两种,一种是JavaScript程序定义好的构造函数,称为 内置构造函数
一种是程序员自己定义的构造函数,称为 自定义构造函数

构造函数和普通函数的区别
  1. 构造函数一定要和 关键词 new 一起使用
    new 关键词具有特殊的功能,
    会自动的给 构造函数中 定义一个对象,并且返回这个对象
    我们只要对这个对象设定属性,设定方法就可以了
  2. 构造函数为了和其他函数区别
    语法规范规定,构造函数,函数名称,第一个字母必须大写,使用大驼峰命名法
  3. 构造函数,给对象定义属性和方法的语法,与一般函数不同

实例化对象和普通的对象,没有任何的区别,只是建立方式不同而已

自定义构造函数
注意:
(1) 函数名称要使用大驼峰命名法
(2) 自定义构造函数中,不要定义对象,和 定义return
new 关键词会执行,如果定义了,会产生冲突

        function CrtObj(name,age,sex,addr){
            // 在构造函数中,使用this,来指代对象
            // 这个对象,就是我们使用构造函数,生成的实例化对象
            
            // 定义属性
            // 给实例化对象,添加name属性,属性值是输入的name参数
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.addr = addr;

            // 定义方法
            this.funAll = function(){
                console.log(this.name,this.age,this.sex,this.addr )
            }
            this.funNameAge = function(){
                console.log(this.name,this.age)
            }
            this.funSexAddr = function(){
                console.log(this.sex,this.addr )
            }

        }

        // 通过自定义构造函数来生对象,实例化对象
        // 调用执行构造函数时,都必须要和new 关键词一起使用
        const obj1 = new CrtObj('张三',18,'男','北京');
        console.log(obj1);
        // 调用 对象/实例化对象 中的方法
        obj1.funAll();
        obj1.funNameAge();
        obj1.funSexAddr();

总结
new 的作用

  1. 在构造函数中,自行创建一个对象,并且返回这个对象
  2. 因为new 关键词,创建了对象,此时,构造函数中的this,才会指向这个对象
    也就是将来生成的实例化对象
  3. 所有的构造函数中,this的指向,都是将来通过这个构造函数生成的实例化对象

7. 解决构造函数中的小问题

<script>
        // 自定义构造函数
        function CrtObj(name, age, sex, addr) {
            // 定义属性
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.addr = addr;
            // 定义方法
            this.funAll = function () {
                console.log(this.name, this.age, this.sex, this.addr);
            }
        }

        // 定义了构造函数,可以生成实例化对象
        const obj1 = new CrtObj('张三',18,'男','北京');
        const obj2 = new CrtObj('李四',19,'女','上海');
        const obj3 = new CrtObj('王五',20,'不详','火星');

        // 每个实例化对象中,都有属性和方法

        // console.log(obj1);
        // console.log(obj2);
        // console.log(obj3);

        // 通过同一个构造函数,生成的实例化对象
        // 属性相同,属性值可能不同
        // 定义的方法的程序,是相同的
        // 但是,如果做一个比较判断,结果是 false 

        // 表示,不同的实例化对象中,定义的是不同的方法/函数
        // 不同的方法和函数,会占用过多的内存空间
        // 要想办法,让使用同一个构造函数,生成的实例化对象,都是相同的方法

        // 原因:每次创建对象,都会在对象上定义一个新的方法,也就是新的函数
        //      函数存储时会生成一个独立的存储空间,不同函数有不同的存储空间
        // console.log( obj1.funAll == obj2.funAll );

        // 解决的方式
        // 将构造函数需要定义给实例化对象的方法,定义在函数的 prototype 属性中

        function CrtObj2(name, age, sex, addr) {
            // 定义属性
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.addr = addr;
        }
        // 在构造函数的 prototype 属性中,来定义实例化对象的方法
        CrtObj2.prototype.fun = function(){
            console.log(this.name, this.age, this.sex, this.addr);
        }

        const obj4 = new CrtObj2('张三',18,'男','北京');
        const obj5 = new CrtObj2('李四',19,'女','上海');

        // console.log(obj4);
        // console.log(obj5);

        // obj4.fun();

        // console.log( obj4.fun === obj5.fun );

        // 详细解释一下 prototype
        // 到底 prototype 是什么?
        // prototype 是每个函数本身就具有的一个特殊的属性
        // 可以在这个特殊的属性中,存储 数据和函数

        function fun(){}

        // 向 fun 函数 中的 prototype 中,定义属性 name 属性值 张三
        fun.prototype.name = '张三';

        fun.prototype.f = function(){console.log(123)};

        console.dir( fun );

        console.log( fun.prototype.name );
        fun.prototype.f();

        // 在构造函数中, prototype 的所用和使用方法

        // 在构造函数内部,定义实例化对象的属性
        // 通过 this关键词,指向实例化对象
        function CreFun(name,age){
            this.name = name;
            this.age = age;
        }
        // 如果是构造函数,创建实例化对象
        // 给实例化对象,添加属性,是通过this方法来添加定义的
        // 定义在 prototype 中的属性,就是写在 prototype 中的,不会写在实例化对象中
        CreFun.prototype.name2 = '李四';
        CreFun.prototype.age2 = 180;

        // 在 prototype 中定义的方法/函数,也不会定义在实例化对象上
        // 只会写在 prototype 中
        CreFun.prototype.ff = function(){
            console.log('1111');
        }


        // JavaScript中,每一个对象,都有一个 __proto__
        // 实例化对象的 __proto__  就是指向的 构造函数中, prototype 这个属性
        // 通过构造函数,生成的实例化对象,这歌实例化对象, __proto__ 存储的地址
        // 就是生成这个实例化对象的构造函数的 prototype 的地址
        // 实例化对象的 __proto__ 实际上 就是指向 构造函数的 prototype

        // 在对象中,调用数据,先在对象本身上找,有没有这个属性
        // obj6.name 企图调用 obj6中,name属性
        // 先在 obj6 对象本身的属性上找 , 如果有就调用本身属性上,对应的数据

        // 如果调用的属性,对象本身没有这个属性,会自动去 __proto__ 中寻找
        // 如果有,就使用 __proto__ 当中的数据
        // obj6.name2 企图调用 obj6中,name2属性
        // 实际上,obj6中,本身没有 name2属性
        // 去 __proto__ 中 寻找,没有 name2 
        // 如果有就会使用 name2 的数据

        const obj6 = new CreFun('张三',18);
        console.dir( CreFun )
        console.dir(obj6);
        console.log(obj6.name);
        console.log(obj6.name2);

总结
函数有一个 属性 叫 prototype
其中可以定义,存储, 属性属性值 函数名称函数等等数据
定义在 prototype 中的内容 就是 函数自己本身的数据

对象有一个 属性 叫 proto
构造函数,在生成实例化对象时,会将自己 prototype 这个空间的地址
赋值给 实例化对象 的 proto 来存储
实际上 构造函数的 prototype 和 生成的实例化对象的 proto 指向的是同一个 存储空间
可以相互调用数据

        构造函数 
        function Fun(){}
        只定义在函数 Fun 中的数据
        Fun.prototype.name = 'abc';
        Fun.prototype.f = function(){};

        // 通过 构造函数,生成实例化对象
        const obj = new Fun();
        构造函数 Fun 的 prototype 的 空间地址 就赋值给了
        实例化对象 obj 的 __proto__ 
        obj 的 __proto__  和 Fun 的 prototype 指向的是同一个空间地址

        
        通过构造函数,定义给实例化对象的属性,必须使用this,在构造函数中操作
        function Fun(){
            通过this方法才是定义给实例化对象的属性和方法
            this.name = '张三'
            this.age = 18
        }
        只是定义在 构造函数 Fun 中的数据,跟实例化对象没有半毛钱关系
        Fun.prototype.name = 'abc';
        Fun.prototype.f = function(){};

        当调用 实例化对象 中 f函数时 obj.f()
        实例化对象本身中,是没有这个函数的
        会继续在 __protop__ 中寻找,是否有这个方法函数 f
        __protop__ 实际上指向的 就是 构造函数的 prototype
        构造函数的 prototype 中,是有这个方法的,那么就可以正常调用

        所有通过这个构造函数生成的实例化对象,实际上,本身都没有定义方法和函数
        使用时,都是调用生成实例化对象的构造函数,本身prototype中定义的方法
        就不会重复生成函数,占用内存空间

8. 原型对象原型属性原型链

  1. 原型对象
    每一个函数,天生都有一个 prototype 属性,称为原型对象
    是一个专门用来存储数据,函数等内容的空间

  2. 原型属性
    每一个对象,天生都有一个 proto 属性,称为原型属性
    实例化对象的原型属性,指向的创建实例化对象的构造函数的 prototype

             函数,数组,等,JavaScript中的数据类型
             实际存储的形式都是一个对象,都会有 __proto__ 属性
             在JavaScript中,可以用对象的形式存储任何数据类型
    
  3. 原型链
    所谓的原型链,就是所有相互关联的变量,使用 proto 属性串联起来
    调用数据数据时,会通过 proto 将所有相互关联的 变量 串联
    只要有一个变量中 有相应的属性,就会调用成功

        // 字面量方式,创建字符串
        // let str1 = 'beijing';
        // console.dir(str1);
        // // 构造函数方式,创建字符串
        // let str2 = new String();
        // console.dir(str2);

        function fun(){}
        console.dir(fun);
 
        // 实例化对象1 ---> 通过构造函数1创建的
        // 构造函数1是 实例化对象2 的方法 
        // 实例化对象2 ---> 通过构造函数2创建的
        // 构造函数2是 实例化对象3 的方法 
        // 实例化对象3 ---> 通过构造函数3创建的
        
        // 构造函数3 ---> 生成 实例化对象3 ---> 构造函数2 ---> 生成 实例化对象2 ---> 构造函数1 ---> 生成 实例化对象1
        // 构造函数本身有 prototype  也有 __proto__
        // 实例化对象 有 __proto__

        /*
            实例化对象1 __proto__ 指向 构造函数1的 prototype
        
            构造函数1本身也有 __proto__ 指向的是 实例化对象2

            实例化对象2 __proto__ 指向 构造函数2的 prototype

            构造函数2本身也有 __proto__ 指向的是 实例化对象3

            实例化对象3 __proto__ 指向 构造函数3的 prototype

            最终,所有的构造函数,对象,数组等都会指向 JavaScript中的顶级对象 Object 

            会在 单例模式中 给大家演示 
        
            为什么 实例化对象 本身没有的函数方法, 构造函数上有,为什么可以调用使用
            原因就是因为原型链的存在
            实例化对象的 __proto__ 指向构造函数的 prototype , 就可以调用使用 构造函数 prototype中定义的方法
        */

9. 选项卡之面向对象

<style>
    *{
        margin: 0;
        padding:0;
    }

    ul,ol,li{
        list-style: none;
    }

    .cont{
        width: 800px;
        height: 600px;
        border: 5px solid #333;
        margin: 0 auto;
        display: flex;
        flex-direction: column;
    }

    .cont ul{
        width: 100%;
        height: 60px;
        display: flex;
    }

    .cont ul li{
        flex:1;
        font-size: 35px;
        color: #fff;
        border-left: 2px solid blue;
        border-right: 2px solid blue;
        background: hotpink;
        display: flex;
        justify-content: center;
        align-items: center;

    }

    .cont ol{
        flex:1;
        position: relative;
    }

    .cont ol li{
        width: 100%;
        height: 100%;
        font-size: 150px;
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        top:0;
        left:0;
        background: burlywood;
        display: none;
    }

    /* 按钮标签 哪个标签有这个属性,哪个就显示特殊背景颜色 */
    .cont ul li.active{
        background: skyblue;
        color: black;
    }

    /* 内容标签 哪个标签有这个属性,哪个就显示 */
    .cont ol li.active{
        display: flex;
    }

</style>
  • 按钮1
  • 按钮2
  • 按钮3
  1. 内容1
  2. 内容2
  3. 内容3
<script>
    // 面向过程值事件委托形式
    // 获取父级div标签对象
    // var oDiv = document.querySelector('div');

    // // 获取标签对象
    // var ullis = document.querySelectorAll('ul li');
    // var ollis = document.querySelectorAll('ol li');
    // oDiv.onclick = function(e){
    //     if(e.target.getAttribute('name') === 'ulli'){
    //         // 1,给所有的li标签,清除样式
    //         ullis.forEach(function(item,key){
    //             item.className = '';
    //             ollis[key].className = '';
    //             // 给item,也就是ul中的li标签,定义属性
    //             item.setAttribute('index',key);
    //         })
    //         e.target.className = 'active';
    //         ollis[e.target.getAttribute('index')].className = 'active';
    //     }         
    // }
    

    // 面向对象的方法
    // 创建一个对象,这个对象中有属性有方法
    // 属性是需要操作的标签等
    // 方法就是实现选项卡效果的程序
    

    // 定义构造函数
    // 参数:需要执行的tab切换的标签对象,是所有需要执行选项卡效果标签的父级
    
    // 面向对象方法1,建立变量,存储this
    function SetTab1(ele){
        // 先要单独,接收存储参数
        this.ele = ele;
        // 通过参数,来获取需要的标签对象
        this.ullis = ele.querySelectorAll('ul li');
        this.ollis = ele.querySelectorAll('ol li');
        // 在构造函数中,定义的this指向的是实例化对象
    }
    // 定义构造函数的方法
    SetTab1.prototype.fun = function(){
        // 选项卡思路
        // 给所有的ul中的li,添加点击事件,点击时,清除所有的ul,ol中li的样式
        // 给当前点击的li,添加样式,再给对应的ol中的li,添加样式
        
        // 定义一个变量,专门存储this指向
        // 此时的this指向,还是 实例化对象
        // oldThis 变量中 存储的就是 实例化对象
        let oldThis = this;

        // 循环遍历 ul中所有的li
        // 这里的 this.ullis 指向的是 实例化对向中的属性 this.ullis 中存储的数据
        // this.ullis   this.ollis   this.ele 其中的this,应该都是指向实例化对象
        this.ullis.forEach(function(item,key){
            // 给 li标签添加点击事件
            item.addEventListener('click' , function(){
                // 在点击事件中,此时this指向的是绑定点击事件的标签 item
                // 就不是 实例化对象了 
                // 此时,要想正确调用 实例化对象属性的属性值
                // 必须 使用 一个 指向是 实例化对象的内容

                // 先循环遍历所有的ul和ol中的li,清除css样式
                oldThis.ullis.forEach(function(item2 , k){
                    item2.className = '';
                    oldThis.ollis[k].className = '';
                })
                // 给点击的ul,li添加样式
                item.className = 'active';
                // 给对应的ol,li添加样式
                oldThis.ollis[key].className = 'active';
            })
        })
    }

    // 面向对象方法2,箭头函数
    function SetTab2(ele){
        // 先要单独,接收存储参数
        this.ele = ele;
        // 通过参数,来获取需要的标签对象
        this.ullis = ele.querySelectorAll('ul li');
        this.ollis = ele.querySelectorAll('ol li');
        // 在构造函数中,定义的this指向的是实例化对象
    }
    // 定义构造函数的方法 
    // 这里的function不能改为箭头函数,里面的都改
    SetTab2.prototype.fun = function(){
        // 将构造函数中,所有的函数,都写成箭头函数
        // this 都会指向父级,最终都指向 实例化对象
        this.ullis.forEach( (item,key)=>{
    
            // 把普通的函数,改为箭头函数,this指向就是父级程序的this指向
            // 也就是 实例化对象了
            // 如果不改,this指向的是绑定事件的标签,也就是item
            item.addEventListener('click' , ()=>{
                // 这里的forEach也要改为箭头函数,不然this指向的是window
                this.ullis.forEach((item2 , k)=>{
                    item2.className = '';
                    this.ollis[k].className = '';
                })
                // 给点击的ul,li添加样式
                item.className = 'active';
                // 给对应的ol,li添加样式
                this.ollis[key].className = 'active';
            })
        })
    }

    // 获取标签对象
    const oDiv = document.querySelector('div');

    // 通过构造函数,生成实例化对象
    // const tabs1 = new SetTab1(oDiv);
    // console.log(tabs1)
    // 调用实例化对象中的方法,来执行程序,实现选项卡效果
    // tabs1.fun();

    const tabs2 = new SetTab2(oDiv);
    tabs2.fun() 
    // 特别注意:
    // 调用构造函数,尤其是封装了方法的构造函数,必须要在定义构造函数之后,来调用
    // 如果先调用,可以调用构造函数,也可以生成实例化对象
    // 实例化对象中,也有属性
    // 但是,对 构造函数 prototype 赋值定义方法,不会提前执行
    // 生成的实例化对象,就没有绑定方法

    // 面向对象语法,最关键的就是this的使用
    // 一定要分清楚,每一个函数中,this的指向
    // 常用  方法1: 找变量,存储this指向
    //      方法2: 将function,写成 箭头函数,this为父级程序this
</script>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值