面向对象一(工厂模式、new运算符、构造函数、构造函数原型)

84 篇文章 3 订阅

目录

1.面向对象编程,类和对象:

2.面向过程编程

2.1需求一:实现多个选项卡的

2.2需求二:需求变更:其中某一个实现选项卡点击切换下一页功能

2.3需求三:需求变更:另一个选项卡实现轮播图功能

2.4需求四:需求变更:多个选项卡分别更改数量

3.工厂模式

4.new运算符

5.构造函数

6.构造函数性能对比工厂模式

7.构造函数原型——prototype原型

8.总结


主要内容和目标

主要内容

  1. 工厂模式
  2. ​new运算符
  3. ​构造函数
  4. ​原型prototype
  5. ​面相对象和面相过程编程
  6. ​类和对象

主要目标:

  1. - 理解面相对象思想
  2. - 会使用工厂模式
  3. - 会使用new运算符
  4. - 会使用构造函数
  5. - 理解原型
  6. - 理解类和对象

1.面向对象编程,类和对象:

一、面相过程:注重解决问题的步骤,分析问题需要的每一步,实现函数依次调用;

二、面相对象:面向对象编程是用抽象方式创建基于现实世界模型的一种编程模式;

三、面相对象特性: 抽象、 继承、封装、多态

对象和类:

​   一、对象:具体的某个事物;(如:小明、叮当猫)

​   二、类:一类事物的抽象;(如:人类、猫类)

2.面向过程编程

2.1需求一:实现多个选项卡的

- 问题一:如何写?按照以前方式写

- 问题二:如何提高复用性?(函数封装)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .cont div {
            display: none;
        }
        .active {
            background: red;
        }
        .cont .show {
            display: block;
        }
    </style>
</head>
<body>
<div id="wrap1">
    <div class="btns">
        <button class="active">选卡一</button>
        <button>选卡二</button>
        <button>选卡三</button>
    </div>
    <div class="cont">
        <div class="show">内容一</div>
        <div>内容二</div>
        <div>内容三</div>
    </div>
</div>
<div id="wrap2">
        <div class="btns">
            <button class="active">选卡一</button>
            <button>选卡二</button>
            <button>选卡三</button>
        </div>
        <div class="cont">
            <div class="show">内容一</div>
            <div>内容二</div>
            <div>内容三</div>
        </div>
    </div>
<script>
{
    //需求:实现多个选项卡
    let wrap1 = document.querySelector("#wrap1");

    function tab(el){
        let btns = el.querySelectorAll(".btns button");
        let cont = el.querySelectorAll(".cont div");
        btns.forEach((item,index)=>{
            item.onclick = function(){
                btns.forEach((item,index)=>{
                    item.classList.remove("active");
                    cont[index].classList.remove("show");
                });
                item.classList.add("active");
                cont[index].classList.add("show");
            };
        });
    }
    tab(wrap1);
    
    let wrap2 = document.querySelector("#wrap2");
    tab(wrap2);
}
</script>
</body>  
</html>

2.2需求二:需求变更:其中某一个实现选项卡点击切换下一页功能

- 通过传统的传参数来解决 ;逻辑和判断越来越复杂;

- 实现:返还函数解决问题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .cont div {
            display: none;
        }
        .active {
            background: red;
        }
        .cont .show {
            display: block;
        }
        .next {
            margin: 20px 0 50px 0;
        }
    </style>
</head>
<body>
<div id="wrap1">
    <div class="btns">
        <button class="active">选卡一</button>
        <button>选卡二</button>
        <button>选卡三</button>
    </div>
    <div class="cont">
        <div class="show">内容一</div>
        <div>内容二</div>
        <div>内容三</div>
    </div>
    <button class="next">第一个选项卡实现下一页</button>
</div>
<div id="wrap2">
    <div class="btns">
        <button class="active">选卡一</button>
        <button>选卡二</button>
        <button>选卡三</button>
    </div>
    <div class="cont">
        <div class="show">内容一</div>
        <div>内容二</div>
        <div>内容三</div>
    </div>
    <button class="play">开始轮播</button>
</div>
<script>
{
    /*
        方法参数封装:tab(el,pager=false,player=false)的方式,发现会有很多私有代码比如第一个选卡的下一页功能,
            和第二个选卡的自动播放功能,都分别属于第一个和第二个选卡的私有功能,不适合放在整个选项卡切换的tab方法里.
            因此需要将tab方法中的私有功能提取出来
    */
    //需求:改造选项卡
    let wrap1 = document.querySelector("#wrap1");

    //第二个参数用于实现第一个选项卡的下一页功能,pager=false不传第二个参数时第二个参数默认为false
    function Tab(el){
        let btns = el.querySelectorAll(".btns button");
        let cont = el.querySelectorAll(".cont div");
        btns.forEach((item,index)=>{
            item.onclick = function(){
                changeTab(index);
            };
        });
        function changeTab(num){
            btns.forEach((item,index)=>{
                item.classList.remove("active");
                cont[index].classList.remove("show");
            });
            btns[num].classList.add("active");
            cont[num].classList.add("show");
        }
        return changeTab;

    }
    // Tab(wrap1);
    //第一个选项卡私有方法:下一页
    let next = wrap1.querySelector(".next");
    let btns = wrap1.querySelectorAll(".btns button");
    let tab1 = Tab(wrap1);//这里的tab返回的是整个changeTab函数
    let num1 = 0;
    next.onclick = function(){
        num1++;
        if(num1>btns.length-1){
            num1=0;
        }
        tab1(num1);
    };
    
    let wrap2 = document.querySelector("#wrap2");
    let btns2 = wrap2.querySelectorAll(".btns button");
    let tab2 = Tab(wrap2);
    let num2 = 0;
    let playBtn = wrap2.querySelector(".play");
    playBtn.onclick = function(){
        let timer = 0;
        timer = setInterval(()=>{
            num2++;
            if(num2>btns2.length-1){
                num2=0;
            }
            tab2(num2);
        },1000);
    };
}
</script>
</body>  
</html>

2.3需求三:需求变更:另一个选项卡实现轮播图功能

- 如何灵活的自动播放?—>需要返还函数还需要返还属性:可以通过返还对象来解决;

见需求四示例

2.4需求四:需求变更:多个选项卡分别更改数量

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .cont div {
            display: none;
        }
        .active {
            background: red;
        }
        .cont .show {
            display: block;
        }
        .next {
            margin: 20px 0 50px 0;
        }
    </style>
</head>
<body>
<div id="wrap1">
    <div class="btns">
        <button class="active">选卡一</button>
        <button>选卡二</button>
        <button>选卡三</button>
    </div>
    <div class="cont">
        <div class="show">内容一</div>
        <div>内容二</div>
        <div>内容三</div>
    </div>
    <button class="next">第一个选项卡实现下一页</button>
</div>
<div id="wrap2">
    <div class="btns">
        <button class="active">选卡一</button>
        <button>选卡二</button>
        <button>选卡三</button>
        <button>选卡四</button>
        <button>选卡五</button>
    </div>
    <div class="cont">
        <div class="show">内容一</div>
        <div>内容二</div>
        <div>内容三</div>
        <div>内容四</div>
        <div>内容五</div>
    </div>
    <button class="play">开始轮播</button>
</div>
<script>
{
    /*
        工厂模式:生成一个对象;返回对象
    */
    //需求:选项卡数量不定——使用工厂模式实现
    let wrap1 = document.querySelector("#wrap1");

    //将函数和选项卡的个数通过工厂模式返回
    function Tab(el){
        let obj = {};
        let btns = el.querySelectorAll(".btns button");
        let cont = el.querySelectorAll(".cont div");
        btns.forEach((item,index)=>{
            item.onclick = function(){
                obj.changeTab(index);
            };
        });
        obj.changeTab = function(num){
            btns.forEach((item,index)=>{
                item.classList.remove("active");
                cont[index].classList.remove("show");
            });
            btns[num].classList.add("active");
            cont[num].classList.add("show");
        }
        obj.tabNum = btns.length;
        return obj;

    }
    // Tab(wrap1);
    //第一个选项卡私有方法:下一页
    let next = wrap1.querySelector(".next");
    let btns = wrap1.querySelectorAll(".btns button");
    let tabObj1 = Tab(wrap1);
    let num1 = 0;
    next.onclick = function(){
        num1++;
        if(num1>tabObj1.tabNum-1){
            num1=0;
        }
        tabObj1.changeTab(num1);
    };
    
    let wrap2 = document.querySelector("#wrap2");
    let btns2 = wrap2.querySelectorAll(".btns button");
    let tabObj2 = Tab(wrap2);
    let num2 = 0;
    let playBtn = wrap2.querySelector(".play");
    let timer = 0;
    playBtn.onclick = function(){
        clearInterval(timer);
        timer = setInterval(()=>{
            num2++;
            if(num2>tabObj2.tabNum-1){
                num2=0;
            }
            tabObj2.changeTab(num2);
        },1000);
    };
}
</script>
</body>  
</html>

3.工厂模式

工厂模式解决了代码复用的问题,

1.但是却没有解决对象识别的问题。即创建的所有实例都是对象即Object类型。(不清楚是哪个对象的实例)

2.没有原型,占用内存。

3.工厂类型必须要有return返回

工厂模式:需要初始化obj对象;给对象中赋值;返回对象;使用时调用工厂模式中的属性或方法

    //工厂模式:需要初始化obj对象;给对象中赋值;返回对象;调用工厂模式中的属性或方法
    function Factory(){
        //初始化原料
        let obj = {};
        //加工原料
        obj.num = 1000;
        obj.product = function(){
            console.log("生产产品");
        }
        //出厂
        return obj;
    }

    let obj1 = Factory();
    console.log(obj1.num);//1000
    obj1.product();//生产产品

- 有没有更好的方式?见下一节构造函数的使用

4.new运算符

new的特点及功能

  1. new可以执行函数
  2. 自动创建空对象;
  3. 把空对象指向另外一个对象
  4. this绑定到空对象
  5. 隐式返还this;

new执行函数:可以写括号,也可以不写,建议写括号

    function Tab(){
        console.log("Tab...");
    }
    //new运算符可以执行函数
    // Tab();//Tab...
    new Tab();//Tab...
    // new Tab;//Tab...

如下:

  1. 把空对象指向另外一个对象:即this可以指向实例化后返回的tab1和tab2。
  2. new的过程,即new实例化后,this指的是实例化后的对象tab1和tab2;
  3. 自动创建空对象,把this绑定到创建好的空对象上,隐式返还this:
  4. 隐式返还this:如果手动return,就会返回手动return的内容,如果没有就会默认返回this

示例:通过new来改造工厂模式

    //new运算符可以自动创建空对象,将创建好的空对象和this进行绑定,并隐式返还this
    function Tab(){
        //这里创建的空对象就是this绑定的对象
        // let obj = {};//已经不需要手动创建了
        this.num = 1000;
        this.product = function(){
            console.log("生产产品");
        }
        // return this;//因为new运算符会隐式返还this,所以不需要手动返回
    }
    let tab1 = new Tab();
    console.log(tab1);//把空对象指向另外一个对象,即new实例化后,this可以指向实例化后的对象tab1。Tab {num: 1000, product: ƒ}
    
    tab1.product();//生产产品
    let tab2 = new Tab();
    tab2.product();//生产产品

new的原理分析:自定义一个new实例化的过程

  1. 创建一个空对象;
  2. 将创建的空对象和构造函数通过原型链进行链接;
  3. 将构造函数的this绑定到新的空对象上;
  4. 根据构造函数返回的类型判断:如果是值类型则返回对象,如果是引用类型则返回这个引用类型
    //自定义一个new的实例化过程
    function MyNew(constructor,...args){
        //1.定义一个空对象
        let obj = {};
        //2.将创建好的空对象和实例化的对象关联起来
        const result = constructor.call(obj,...args); //const result = constructor.apply(obj, args)
        //3.将构造函数的this指向新的对象的原型链上
        obj.__proto__ = constructor.prototype;
        //4.根据构造函数返回的类型判断,如果是值类型就返回对象,如果是引用类型,就返回这个引用类型
        return result instanceof Object? result : obj;
    }

    function Tab(){
        this.tabNum = 4;
        this.product = function(){
            console.log("product...")
        }
    }

    //此处实例化MyNew后,this就执行了mynew1。
    //tab传入MyNew后,相当于将Tab的this传给了obj,当调用obj时,就得到了tab的this
    let mynew1 = new MyNew(Tab);
    mynew1.product();//product...
    let mynew2 = new MyNew(Tab);
    mynew2.product();//product...

5.构造函数

- 构造函数要通过new来调用 this指向

- 约定俗成构造函数首字母大写

6.构造函数性能对比工厂模式

工厂模式:

    //工厂模式
    function Tab(){
        let obj = {};
        obj.name = "张三";
        obj.hobby = function(){
            console.log("打篮球");
        }
        return obj;
    }

    let tab1 = Tab();
    console.log(tab1);//{name: "张三", hobby: ƒ}

 构造函数:

    //构造函数
    function Tab(){
        this.name = "张三";
        this.hobby = function(){
            console.log("打篮球");
        }
    }

    let tab2 = new Tab();
    console.log(tab2);//Tab {name: "张三", hobby: ƒ}

对比发现构造函数:

构造函数写法简单;性能更好(公共空间(原型prototype)存放公共方法)

为什么构造函数的性能会更好?见下一节原型prototype

7.构造函数原型——prototype原型

  1. - 通过new实例化出来的对象其属性和行为来自两个部分,一部分来自构造函数,另一部分来自原型。
  2. - 当声明一个函数的时候,同时也申明了一个原型 。
  3. - 原型本身是一个对象。
  4. - 对象属性方法查找规则;
  5. -本身的构造函数由构造函数本身和prototype构成,通过new实例化后,由实例化后的对象和__proto__构成。prototype和__proto__名字不一样但是地址是一样的,即Tab.prototype === tab1.__proto__返回true。
  6. 原型上有一个预定义属性即constructor指向构造函数本身
  7. 创建对象,可使用new Object() ,也可以通过Object.create({key:value});进行创建
  8. 函数也是对象,也会有自己的__proto__。而函数的原型也是一个对象,也会有自己对应的__proto__。即__proto__会不断往下查找__proto__对象,即所谓的原型链。如下图:

为什么构造函数的性能会更好?

  • 工厂模式:发现工厂模式下,得到的两个对象tab1和tab2的内存地址是不一样的,所以tab1.hobby === tab2.hobby结果为false
    function Tab(){
        let obj = {};
        obj.name = "张三";
        obj.hobby = function(){
            console.log("打篮球");
        }
        return obj;
    }

    let tab1 = Tab();
    let tab2 = Tab();
    console.log(tab1.hobby === tab2.hobby);//false
  •  构造函数:发现构造函数实例化后得到的两个对象tab3和tab4也是存储在不同的内存空间,内存地址不一样。但是可以通过原型prototype将公共的内容存在公共空间下,这样就能节省内存空间,从而提升性能。
    function Tab(){
        this.name = "张三";
        this.hobby = function(){
            console.log("打篮球");
        }
    }

    let tab3 = new Tab();
    let tab4 = new Tab();
    console.log(tab3.hobby === tab4.hobby);//false
  • 使用原型后的tab5和tab6的hobby是存在同一内存空间下的,所以内存地址一致 
    function Tab(){
        this.name = "张三";
    }

    // 将公共内容存储到共用空间即prototype原型下
    Tab.prototype.hobby = function(){
        console.log("打篮球");
    };
    let tab5 = new Tab();
    let tab6 = new Tab();
    console.log(tab5.hobby === tab6.hobby);//true
    console.log(tab5.name === tab6.name);//true因为比较的本身就是基本数据类型,所以也返回true
  • 原型上有一个预定义属性即constructor指向构造函数本身。所以,使用公共空间时,只能通过追加方式添加prototype原型,而不能直接覆盖。如果是直接添加 Tab.prototype = {}会将构造函数原有的其他属性或方法覆盖

如下,直接使用Tab.prototype = {}时,已经覆盖了原有的构造函数中的name属性,所以实例化后的tab7再去获取name时,发现已经不存在了。所以使用公共空间prototype原型时必须使用追加的方式

     function Tab(){
        this.name = "张三";
    }
    Tab.prototype = {
        hobby(){
            console.log("打篮球");
        }
    };
    let tab7 = new Tab();
    console.log(tab5.name);//发现会报错:Uncaught ReferenceError: tab5 is not defined

 怎么理解原型上有一个预定义属性即constructor指向构造函数本身?

    let prototype = {
        //原型中的预定义属性constructor,执行构造函数本身
        constructor:"构造函数"
    };
    //使用追加
    prototype.hobby = function(){
        console.log("打篮球");
    }
    //通过追加方式,会保留原有的constructor 构造函数
    console.log(prototype);//{constructor: "构造函数", hobby: ƒ}

    //直接覆盖
    prototype = function(){
        hobby = function(){
            console.log("打篮球");
        }
    }
    //会覆盖构造函数的原有属性或方法
    console.log(prototype);//ƒ (){hobby = function(){console.log("打篮球");}}

 Object.assign()如果是新的内容会进行追加,如果内容重复不会进行追加。

Object.create({key:value});进行创建对象:

    //create创建对象
    let obj = Object.create({
        name:'lmf',
        age:18
    });
    console.log(obj);

8.总结

案例tab选项卡切换:

- 面相过程写法

- 面相对象写法

工厂模式:共性内容方函数里;少数的通过传参(可以传配置也可以传对象)实现;个别的通过返还函数单独处理

使用构造方法改造多个选项卡且选项卡数量不一致的需求:

注意点:

  • 构造方法声明时一般只写共用属性,共用方法一般写在原型里;
  • 如果是每个实例化对象都需要调用的方法,可以再构造函数声明时就执行;
  • forEach方法的返回值为undefined,如果需要循环获取返回值,不能使用forEach循环;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .cont div {
            display: none;
        }
        .active {
            background: red;
        }
        .cont .show {
            display: block;
        }
        .next {
            margin: 20px 0 50px 0;
        }
    </style>
</head>
<body>
<div id="wrap1">
    <div class="btns">
        <button class="active">选卡一</button>
        <button>选卡二</button>
        <button>选卡三</button>
    </div>
    <div class="cont">
        <div class="show">内容一</div>
        <div>内容二</div>
        <div>内容三</div>
    </div>
    <button class="next">第一个选项卡实现下一页</button>
</div>
<div id="wrap2">
    <div class="btns">
        <button class="active">选卡一</button>
        <button>选卡二</button>
        <button>选卡三</button>
        <button>选卡四</button>
        <button>选卡五</button>
    </div>
    <div class="cont">
        <div class="show">内容一</div>
        <div>内容二</div>
        <div>内容三</div>
        <div>内容四</div>
        <div>内容五</div>
    </div>
    <button class="play">开始轮播</button>
</div>
<script>
{
    /*
        构造方法实现多个不同数量的选项卡
            思路:btns保留为Tab的属性
                psFor保留为Tab原型上的方法
                可以封装一个添加事件监听的方法去为元素添加各种事件
        面向对象的基本思想:尽量少用函数嵌套,降低耦合;构造函数中一般只写属性或者初始化方法,其他方法一般都不写在构造函数里,以节省内存空间
    */
    //需求:选项卡数量使用构造方法实现
    //构造函数和原型里的this都指的是实例化后的tab1或者tab2对象
    function Tab(btns,cont){
        this.btns = btns;
        this.cont = cont;
        this.tabNum = btns.length;
        //因为每个选项卡都需要循环,所以写在构造函数中
        this.tabFor();
    }
    //循环选项卡
    Tab.prototype.tabFor = function(){
        this.btns.forEach((item,index)=>{
            item.onclick = ()=>{
                //因为都使用箭头函数,所以最后的this,指向最外层作用域的Tab
                this.changeTab(index);
            };
        });
    }
    //切换选项卡时更改样式
    Tab.prototype.changeTab = function(num){
        this.btns.forEach((item,index)=>{
            item.classList.remove("active");
            this.cont[index].classList.remove("show");
        });
        this.btns[num].classList.add("active");
        this.cont[num].classList.add("show");
    }
    //轮播或下一页时,获取点击切换的index
    Tab.prototype.getIndex = function(){
        //可以通过this获取到所有实例化后的对象,即可以得到所有选项卡,就能获取到被选中的index
        //注意forEach循环的返回值是undefined,不能使用forEach循环返回
        for (let i = 0; i < this.btns.length; i++) {
            if(this.btns[i].classList.value === "active"){
                return i;
            }
        }
    }

    //第一个选项卡私有方法:下一页
    let wrap1 = document.querySelector("#wrap1");
    let btns = wrap1.querySelectorAll(".btns button");
    let cont = wrap1.querySelectorAll(".cont div");
    let next = wrap1.querySelector(".next");
    let tab1 = new Tab(btns,cont);
    // console.log(tab1.getIndex());
    let num1 = 0;
    next.onclick = function(){
        num1 = tab1.getIndex();
        //这有选项卡1和2点击下一页或轮播私有,其他时候没有,所以不能写在原型上
        num1++;
        num1 = num1>tab1.tabNum-1?0:num1;
        tab1.changeTab(num1);
    };
    
    //第一个选项卡私有方法:自动播放
    let wrap2 = document.querySelector("#wrap2");
    let btns2 = wrap2.querySelectorAll(".btns button");
    let cont2 = wrap2.querySelectorAll(".cont div");
    let tab2 = new Tab(btns2,cont2);
    let playBtn = wrap2.querySelector(".play");
    let timer = 0;
    playBtn.onclick = function(){
        let num2 = 0;
        clearInterval(timer);
        timer = setInterval(()=>{
            num2 = tab2.getIndex();
            num2++;
            num2 = num2>tab2.tabNum-1?0:num2;
            tab2.changeTab(num2);
        },1000);
    };
}
</script>
</body>  
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值