美文网首页
编程大白话之-面向对象

编程大白话之-面向对象

作者: Han涛_ | 来源:发表于2019-07-22 22:31 被阅读0次

    JS为什么要用面向对象的写法,下面引用一些理论性的知识来介绍一下面向对象。

    面向对象的几大特点:

    1封装好处:
    复用,降低冗余度,更有利于模块化编程,能够为变量函数提供更多的保护。使用对象来封装变量和函数。

    2继承代码中继承:
    子类获得父类的属性和方法(成员)的一种方式。
    js的继承实现方法:
    1属性拷贝(浅拷贝)
    2属性拷贝(深拷贝)
    3原型式继承
    4原型链继承
    5借用构造函数
    6组合继承

    3多态多种形态特点:
    对于同一个操作(指令),不同的对象表现出不同的反应 隐藏不同好处:灵活

    面向对象具有4个主要特性:

    唯一 :
    每个对象都有自身唯一的标识,通过这种标识,可找到相应的对象。在对象的整个生命期中,它的标识都不改变。

    抽象:
    将具有一致的属性和行为的对象抽象成类。反映了与应用有关的重要性质,而忽略其他一些无关内容。

    继承:
    继承子类自动共享父类数据结构和方法的机制,这是类之间的一种关系,继承性是面向对象思想最重要的一点。

    多态:
    多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。

    面向对象与面向过程到底有什么区别?

    可维护性强
    写代码简单,维护起来却不容易。
    经过深思熟虑的抽象,松散的代码可以被重构为“有胳膊有腿”的个体,
    属于它的变量被定义成属性,相关的函数片段被定义成它的行为(方法),看到这样清晰的结构,对这些函数片段和散落的变量就会一目了然其作用。

    可复用性强
    如果做一个轮播图,可能100行之内就可以搞定。但有很多页面都需要你这个轮播图,但是有些许不同,比如轮播时间间隔,轮播方向,切换图片后的行为(轮播动画结束时需要执行的链接地址更新,统计等等等回调函数)。
    或者,一个页面有两处轮播,而且两处的class,行为不同,你会将那段代码改吧改吧复制成两个吗?
    如果是面向对象,就可以将方法,属性抽象,初始化不同的实例,实例间求同存异,各自都有自己的状态,互不影响。

    看了上面的介绍不知你是否对面向对象有了更深一步的了解,纯粹的看文字都是很枯燥乏味的。下面我们就通过实例来简单介绍一下面向对象的应用。


    Hantao.gif

    (简单的结构图,请不要计较页面的美观)
    如图所示,在一个页面中我同时要用到两个或多个tab切换,如果分开写会造成大量的代码,增加dom的操作,同样会影响性能,所以这个时候我们就可以用面向对象的方法,来避免和减少此类缺点。

    面向对象的写法只要分为三部分就可以将其看明白
    1.将普通方法中的 公共变量 作为对象中的 this 下面的属性
    2.将具体的功能函数 以 对象.protype.方法 的形式展示
    3.修改方法中this的指向

      function Tab(container, className, data) {
                this.container = document.querySelector(container);                         // 获取到最大的盒子
                this.titles = this.container.querySelector('.tab-title');                   // 通过最大的盒子获取标题盒子
                this.contents = this.container.querySelector('.contnet');                   // 通过最大的盒子获取内容盒子
                this.cur = className;                                                       // 将接收到的颜色类,命名为一个新的变量
                this.data = data;                                                           
                this.init();                           // 在自身的方法上,让它自动执行init()函数
            }
            Tab.prototype = {
                constructor: Tab,                   // 手动的将constructor,指向了Tab构造函数
                init: function() {
                    this.render();                      // 把渲染页面和事件都绑在了原型上,可是原型不会自动调用,需要绑定一个事件来自动执行原型上的方法
                    this.addEvent()
                },                                
                addEvent: function() {                          // 绑定事件            // 事件放进对象中,所以要将其改成(key:值)对的形式
                    var that = this;                                                // 为了避免和事件内的this发生冲突,所以提前将外面的this指向重新命名
                    that.titles.addEventListener('click', function (e) {                // 此时的titles是指调用它的,所以要添加this
                        var event = e || window.e;
                        var tar = event.target || event.srcElement;
                        if (tar.nodeName === 'LI') {
                            var idx = tar.dataset.id;
                            // console.log(tar.dataset.id)                              // 获取下标的方法,提前在渲染页面时通过data-设置下标,用目标元素
                            // console.log(tar.getAttribute('data-id'))                     // 通过getAttribute获取设置的data-id的下标
    
                            for (var i = 0; i < this.children.length; i++) {
                                this.children[i].className = '';
                                that.contents.children[i].className = '';
                            }
                            tar.className = that.cur;
                            that.contents.children[idx].className = that.cur;
                        }
                    })
                },
    
                render: function() {                             // 渲染页面
                    var data = this.data;                               // 这个函数里面的data都是外面函数里面的获取到的,所以将this.data命名了一个新的变量,方便里面调用不用更改
                    var tHtml = '';
                    var cHtml = '';
                    for (var i = 0; i < data.length; i++) {
                        tHtml += `<li data-id="${i}">${data[i].title}</li>`;
                        cHtml += `<div >${data[i].content.text}</div>`;
                    }
                    this.titles.innerHTML = tHtml;                              // 这里面的标题和内容都是当前,所以前面都要加this
                    this.contents.innerHTML = cHtml;
                    this.titles.children[0].className = this.cur;
                    this.contents.children[0].className = this.cur;
                }
            }
    
            new Tab('#container', 'active', data);                  // 创建构造函数传参(第1.页面内容最大盒子,第2.新添加颜色的类,第3.渲染页面的数据)
            new Tab('#container2', 'active', data2);                    // 封装好方法后,如果需要重复多个,直接新建构造函数、重复盒子框架、渲染页面内容就可以了
    

    以上代码并没有考虑兼容优化等问题,纯粹只是介绍面向对象的方法,如有强迫症的大神也可以将其进行优化,很希望看到大神提点。

    一个太少,那就再来一个,轮播图算的上是应用极为广泛的了。我们常用一些框架来完成实现,但很多面试官会出题让面试者用原生JS来写轮播图,有的甚至会要求用面向对象的方式,那么就来个面向对象的原生JS无缝轮播图希望能对要面试的有所帮助。

    Hantao.gif
    function Boon(containet, option) {
        this.container = document.querySelector(containet);         // 获取到最大的盒子,通过大盒子再去获取盒子内的内容,这样如果页面有多个轮播图或内容时,不会导致混乱
        this.boonWidth = this.container.clientWidth;                // 大盒子的宽度=容器可视区域的宽度
        this.wrap = this.container.querySelector('.wrap');          // 图片的盒子
        this.lBtn = this.container.querySelector('.lBtn');
        this.rBtn = this.container.querySelector('.rBtn');          // 获取左右按钮
        this.moving = false;                     // 在图片移动未结束时,避免事件重复,增加一个判断,初始为false
        this.moveTime = (option && option.moveTime) || 0.8;           // 接收判断是否有其他对象传入,如有接收moveTime值(图片运行时间),如果没有设置成默认的值
        this.autoPlays = (option && option.autoPlays) || false;         // 设置是否自动播放,设置的值为true,否则为false
        this.autoTime = (option && option.autoTime) || 1000;          // 设置自动播放的时间
        this.pageContainer = option && option.pagei ? this.container.querySelector(option.pagei) : null;        // 获取页码的容器
        this.init();                    // 将原型上所有要执行的函数都封装进一个函数内,统一在构造函数中进行调用,版面整齐
    }
    Boon.prototype = {
        constructor: Boon,          // 手动的将原型指向构造函数
        init() {
            this.initWrap();                                // 初始化轮播图
            this.pageContainer && this.pages();             // 初始化页码
            this.pageContainer && this.addPage();           // 初始化页码点击跳转事件
            this.addEventBtn();                             // 添加左右按钮事件
            this.addEventWrap();                            // 监听图片盒子添加移动事件
            this.autoPlays && this.autoPlay();              // autoPlays为true时执行autoPlay函数
            this.containers();
                    
        },
        // 初始化轮播图
        initWrap() {
            var startImg = this.wrap.children[0].cloneNode(true);                                   // 克隆第一个和最后一个子节点
            var endImg = this.wrap.children[this.wrap.children.length - 1].cloneNode(true);
            this.wrap.appendChild(startImg);                                                    // 在最后把第一张插入
            this.wrap.insertBefore(endImg, this.wrap.children[0]);                              // 在第一张前面插入最后一张
            // 重置图片盒子的宽度
            for(var i = 0; i < this.wrap.children.length; i++) {                                // 大盒子的宽度要由内容撑起
                this.wrap.children[i].style.width = this.boonWidth + 'px';
            }
            this.wrap.style.width = this.wrap.children.length * this.boonWidth + 'px';      // 设置大盒子的宽度
            this.wrap.style.transform = `translate3d(-${this.boonWidth}px, 0, 0)`;          // 初始向左移动一个图片的位置,因为在前面插入了一张图片,移动一个图片的位置,刚好显示的是正确的第一张图片
            this.index = 1;                                                     // 设置初始下标从1开始
            
        },
        // 添加左右按钮事件
        addEventBtn() {
            var self = this;        
            this.rBtn.addEventListener('click', function() {
                if(self.moving) return;                     // 如果初始值为true时,说明有图片正在移动,所以直接返回,不要执行下面的代码 
                self.index++;
                self.move();
            })
            this.lBtn.addEventListener('click', function() {
                if(self.moving) return;
                self.index--;
                self.move();
            })
        },
        // 移动的事件,每次在点击按钮后进行调用
        move() {
            this.moving = true;                 // 在图片移动时将初始值设置为true
            this.wrap.style.transition = `transform ${this.moveTime}s`;
            this.wrap.style.transform = `translate3d(-${this.boonWidth * this.index}px, 0, 0)`;
            this.pageContainer && this.changPage();         // 让图片在移动时,执行页码的函数进行关联
        },
        // 监听图片盒子添加移动事件
        addEventWrap() {
            var self = this;                         // 将实例的this指向存为一个变量,方便在事件函数内使用,方便区别this的指向
            this.wrap.addEventListener('transitionend', function() {            // transitionend:监听动画结束的事件
                if(self.index === self.wrap.children.length - 1) {          // 判断右边按钮的边界值
                    self.index = 1;                                                 // 将index的值设为1(真正第一张图片的下标)
                    self.wrap.style.transition = 'none';                        // 移动图片的执行的时间设为0
                    self.wrap.style.transform = `translate3d(-${self.boonWidth * self.index}px, 0, 0)`;          // 移动到指定的位置
                    
                }
                if(self.index <= 0) {                                       // 判断左边按钮的边界值
                    self.index = self.wrap.children.length - 2;                 // 下标设为图片的总数-2,(为倒数第2张图片的位置)                                
                    self.wrap.style.transition = 'none';                        // 移动图片的执行的时间设为0
                    self.wrap.style.transform = `translate3d(-${self.boonWidth * self.index}px, 0, 0)`;          // 移动到指定的位置
                    
                }
                self.moving = false;                            // 当图片移动结束之后,将初始值设为false
            })
        },
        // 添加自动轮播事件
        autoPlay() {
            var self = this;
            this.time = setInterval(function() {
                self.rBtn.click();      
            }, self.autoTime);                          //  执行定时器时,判断设置的间隔时间为多久
        },
        // 给container添加滑入滑出事件
        containers() {
            var self = this;
            this.container.addEventListener('mouseenter', function() {
                clearInterval(self.time);
                self.time = null;
            });
            this.container.addEventListener('mouseleave', function() {
                self.autoPlay();
            })
        },
        // 渲染页码
        pages() {
            var len = this.wrap.children.length -2;
            var html = '';
            for(var i = 0; i < len; i++) {
                html += `<span data-id="${i + 1}"></span>`;
            }
            this.pageContainer.innerHTML = html;                        // 渲染添加页码
            this.pageContainer.children[0].classList.add('color');      // 给初始页码添加一个颜色类
        },
        // 添加页码关联事件
        changPage() {
            for(var i = 0; i < this.pageContainer.children.length; i++) {
                this.pageContainer.children[i].classList.remove('color');
            }                                                                       // 初始化轮播图时,设置了index的初始值
            var index = (this.index <= 0 ? index = this.pageContainer.children.length - 1 : this.index - 1) % this.pageContainer.children.length;          // 因为实际图片的起始下标要比页码下标多1位,所以将实例中的index的下标-1,最后一个页码结束后得到一个下标为5的数,如果不取余,没有第5个页码,所以无法调回第1个,所以5%4得1,所以页码会回到第一个页码上
            // 设置新的下标= (如果图片的下标<=0,新下标=图片子元素的长度-1,否则就图片下标-1) 括号中得到的数 % 页码所有子元素的长度 后得到的返回值赋给新的index 
            this.pageContainer.children[index].classList.add('color');
        },
        // 添加页码点击跳转事件(事件委托)
        addPage() {
            var self = this;                                            // 区分实例的this指向和事件内部的this指向
            this.pageContainer.addEventListener('click', function(e) {          // 事件委托,2级事件
                var event = e || window.event;                              // 兼容取到evenet(事件对象)
                var tar = event.target || event.srcElement;                 // 兼容取到事件目标的节点
                if(tar.nodeName === 'SPAN') {                               // 判断目标是否为span标签
                    var index = tar.dataset.id;                             // 渲染页面时给每一个标签元素都设置一个下标
                    self.index = index;                                     // 把实例的下标与页码的下标同步
                    self.move();                                            // 同时调用移动事件
                }
            });
        }
    }
    new Boon('#sw', {                   // 实例化传入大盒子
        // moveTime: 0.1,               // 将图片运行的时间设置成可更改的
        autoPlays: true,                // 是否开启自动播放
        autoTime: 2000,                 // 如果设置间隔时间就按间隔计算,否则按照默认运行时间
        pagei: '#page',                 // 页码的大盒子
    });        
    
    

    对于面向对象更多的还是要在工作中去运用,希望今天的内容对需要想了解的朋友有所帮助,由于案例简陋,难免会有问题,所以希望大家多多指点~!

    相关文章

      网友评论

          本文标题:编程大白话之-面向对象

          本文链接:https://www.haomeiwen.com/subject/fheglctx.html