原生JS面向对象初探

104 阅读8分钟

以前是一直没用过这种思想进行过开发工作的,都是面向过程,把某一个部分完成了就行,但是越写到后边接触的越多我就会发现,有很多过程是重复的,这就是解决这个问题的共性,如果我把这些共性抽象出来,那想必就省事多了,就比如你用原生js做一个多层级的tab切换栏,你真不会一层一层地去写吧,事件也是一层一层的加,当然咯,一层层这样写下去也不是不可以,只要你时间够,但是万一以后要加功能呢?你还来得及吗,或者说你对你写的这个过程你还了解吗?

       <div class="digital-program">
      <div class="digital-program-container">
        <h2>中小企业数据电子化解决方案</h2>
        <div class="tab tab1">
          <ul class="tab-list">
            <li class="tab-item">
              <div class="tab-tilte">
                <h3>Epson Smart Panel</h3>
                <h4>(无线应用解决方案)</h4>
              </div>
              <div class="tab-content">11111</div>
            </li>
            <li class="tab-item">
              <div class="tab-tilte">
                <h3>Epson Smart Panel</h3>
                <h4>(无线应用解决方案)</h4>
              </div>
              <div class="tab-content">
                <div class="menu">
                  <ul class="menu-list">
                    <li class="menu-item">
                      <div class="menu-item-title" collapsed="false">
                        <i class="iconfont download"></i>
                        <span>软件下载</span>
                      </div>
                      <div class="menu-item-content">
                        <div class="download-info">
                          <h5>适用手机系统</h5>
                          <ul class="download-info-class-list">
                            <li class="download-info-class-item">
                              <span
                                >iOS系统和设备 : iOS 9.0或以上版本的iPhone/iPod
                                touch/iPad</span
                              >
                            </li>
                            <li class="download-info-class-item">
                              <span
                                >Android系统和设备 : Android
                                4.4或以上版本的设备</span
                              >
                            </li>
                            <li class="download-info-class-item">
                              <span>适用机型:</span>
                              <div class="tab">
                                <ul class="tab-list">
                                  <li class="tab-item">
                                    <div class="tab-tilte">
                                      <span>DS-360W</span>
                                    </div>
                                    <div class="tab-content">
                                      <div class="qrcode-box">
                                        <div class="qrcode">
                                          <div class="qrcode-img-box">
                                            <img
                                              src="./img/bg-img/qrcode.png"
                                            />
                                          </div>
                                          <div class="qrcode-intro">
                                            <i class="iconfont iphone"></i>
                                            <span>扫描二维码</span>
                                            <span>下载IOS客户端</span>
                                          </div>
                                        </div>
                                        <div class="qrcode">
                                          <div class="qrcode-img-box">
                                            <img
                                              src="./img/bg-img/qrcode.png"
                                            />
                                          </div>
                                          <div class="qrcode-intro">
                                            <i class="iconfont android"></i>
                                            <span>扫描二维码</span>
                                            <span>下载IOS客户端</span>
                                          </div>
                                        </div>
                                      </div>
                                    </div>
                                  </li>
                                </ul>
                              </div>
                            </li>
                          </ul>
                        </div>
                      </div>
                    </li>
                    <li class="menu-item">
                      <div class="menu-item-title" collapsed="false">
                        <i class="iconfont install"></i>
                        <span>安装指南</span>
                      </div>
                      <div class="menu-item-content">33333</div>
                    </li>
                    <li class="menu-item">
                      <div class="menu-item-title" collapsed="false">
                        <i class="iconfont use-guide"></i>
                        <span>使用指南</span>
                      </div>
                      <div class="menu-item-content">22222</div>
                    </li>
                    <li class="menu-item">
                      <div class="menu-item-title" collapsed="true">
                        <i class="iconfont question"></i>
                        <span>常见问题</span>
                      </div>
                      <div class="collapse-list" style="visibility: hidden">
                        <div class="collapse-item">
                          <span>IOS版FAQ</span>
                        </div>
                        <div class="collapse-item">
                          <span>Android版FAQ</span>
                        </div>
                      </div>
                      <div class="menu-item-content">33333</div>
                    </li>
                  </ul>
                  <div class="menu-content-container"></div>
                </div>
              </div>
            </li>
            <li class="tab-item">
              <div class="tab-tilte">
                <h3>Epson DS-730N</h3>
                <h4>(网络LAN应用解决方案)</h4>
              </div>
              <div class="tab-content">33333</div>
            </li>
          </ul>
          <div class="tab-content-container"></div>
        </div>
      </div>
    </div>

上面是html架构, 总的说一下,我要完成下面的效果:

image.png

没错,就是一个三级联动,因为现在活还不算太多,所以我想好好趁着这段时间学些底层的东西稳固下基础,所有的任务我都用原生js进行开发,顺便了解下各个常用组件的运行机制。 废话不多说,先分析下这个组件

function totransFormArr(fakeArr) {
  let newArr = [];
  fakeArr.forEach((element, index) => {
    newArr[index] = fakeArr[index];
  });
  return newArr;
}
// tab栏模块
var tabDomList = totransFormArr(document.querySelectorAll(" .tab1 .tab-item"));
var tabContentDomList = totransFormArr(
  document.querySelectorAll(".tab1 .tab-content")
);
// 当前指示tab的index的值
var tabIndex = 1;
function initTable1() {
  let tabContainer = document.querySelector(".tab1 .tab-content-container");
  tabContainer.innerHTML = tabContentDomList[tabIndex].innerHTML;
  tabDomList[tabIndex].querySelector(".tab-tilte").style.color = "#0c89ef";
  tabDomList.forEach((e, index) => {
    e.removeChild(tabContentDomList[index]);
    e.querySelector(".tab-tilte").addEventListener("click", () => {
      let tabContainer = document.querySelector(".tab1 .tab-content-container");
      let titleDom = e.querySelector(".tab-tilte");
      titleDom.style.color = "#0c89ef";
      tabContainer.innerHTML = tabContentDomList[index].innerHTML;
      tabDomList[tabIndex].querySelector(".tab-tilte").style.color = "#000";
      tabIndex = index;
    });
  });
}
function initTable2() {
    let tabContainer = document.querySelector(".tab1 .tab-content-container");
    tabContainer.innerHTML = tabContentDomList[tabIndex].innerHTML;
    tabDomList[tabIndex].querySelector(".tab-tilte").style.color = "#0c89ef";
    tabDomList.forEach((e, index) => {
      e.removeChild(tabContentDomList[index]);
      e.querySelector(".tab-tilte").addEventListener("click", () => {
        let tabContainer = document.querySelector(".tab1 .tab-content-container");
        let titleDom = e.querySelector(".tab-tilte");
        titleDom.style.color = "#0c89ef";
        tabContainer.innerHTML = tabContentDomList[index].innerHTML;
        tabDomList[tabIndex].querySelector(".tab-tilte").style.color = "#000";
        tabIndex = index;
      });
    });
  }
initTable1();
// 获取tab类下的ul li的dom
// 得到一个li的dom的数组之后 可以设定一个indexLi  表示当前指示的li的下标
// 然后在依次得到与li并列的 tab-content 依次放置在一个数组当中 并从原dom结构中移除

// 再根据指示的indexLi 在ul标签下返回一个container tab-content-container 这个容器当中包含了之前获取到的tab-content的内容

上边是俩个tab的初始化函数; 我一开始的想法就是先把各个标题对应的内容都写在一块,然后在初始化的时候把内容都抽离出来,对就是removeChild(...) 把要切换的内容都抽离出来存在一个全局变量当中,切换到它的时候再取处理来。这种办法是我首先想到的,其实只是做单个tab切换栏是没有问题的,但当我们在里边嵌套其他的切换栏的时候就会出现样式变了,js做的一些处理也没了。我想这的原因可能是我把原状态的dom存起来了,但是后边我又对这些dom进行了一些操作,然后状态就没更新上,所以样式和js效果都不对劲,后来我想固定住这个状态,下一次dom刷新的时候我就再存一次,结果还是不可以,这里我倒是想了很久,或许是更换位置后dom之前的操作没保存下来吧。

image.png

image.png

如图所示,一开始是好的,但是当你切换的时候就变成乱码了。

// 菜单部分
// 默认选中菜单index
var menuSelectedIndex = 0;
// 获取所有的menu-item
var menuItemList = document.querySelectorAll(".menu-list .menu-item");
// 菜单内容列表
var menuContentList = [];
// 初始化menu
function initMenu() {
  let contentBox = document.querySelector(".menu-content-container");
  // 首先是初始化默认选项
  // 得到默认选项的content
  var menuDeafult = menuItemList[0].querySelector(".menu-item-content");
  menuItemList[0].querySelector(
    ".menu-item-title"
  ).parentNode.style.backgroundColor = "#0c89ef";
  menuItemList[0].querySelector(".menu-item-title").parentNode.style.color =
    "#fff";
  contentBox.innerHTML = menuDeafult.innerHTML;
  //   contentBox.innerHTML = menuDeafult.innerHTML;
  // 给menuitem初始化 该隐藏的都隐藏掉
  menuItemList.forEach((e, index) => {
    let menuTitle = e.querySelector(".menu-item-title");
    let menuContent = e.removeChild(e.querySelector(".menu-item-content"));
    menuContentList.push(menuContent);
    console.log(menuContent);
    menuTitle.addEventListener("click", () => {
      menuTitle.parentNode.style.backgroundColor = "#0c89ef";
      menuTitle.parentNode.style.color = "#fff";
      let lastSelected = menuItemList[menuSelectedIndex];
      lastSelected.style.backgroundColor = "#fff";
      lastSelected.style.color = "#000";
      // 在相应位置显示
      contentBox.innerHTML = menuContentList[index].innerHTML;
      menuSelectedIndex = index;
    });
  });
}
initMenu();

然后我还重新定义了一个menu初始化事件,加上去之后整个三级联动就特别混乱了。再加之我要做的是响应式,在手机上也要适配,也要用,所以,最后我放弃了这种面向过程的写法。

面向对象来袭

这时我就想换个思路了,既然之前是状态没保存上,那我怎么样能随时利用这个状态呢。我想了一个晚上也没有啥思路,第二天一到公司,突然就灵光一现,对,面向对象,就是面向对象,一个tab是一个对象,tab栏里有标题,内容,当前切换的index等等,把所有属性都融入到这个tab栏里,用什么就从自身对象拿什么,不用担心状态乱套。 说干就干,我写了俩个对象,一个是tab对象,另一个是Collapse对象。

// 定义一个切换工具的类
function SwitchTabTool(
  initClass,
  direction,
  tabItemTitleNomalStyle,
  tabItemTitleSelectedStyle,
  isShowSelectedBar,
  defaultIndex,
  beforeIcon,
  afterIcon
) {
  this.initClass = initClass;
  this.direction = direction; // 方向
  this.tabItemTitleNomalStyle = tabItemTitleNomalStyle; // 正常状态下的tabItem样式
  this.tabItemTitleSelectedStyle = tabItemTitleSelectedStyle; // 选中状态下的tabItem样式
  this.isShowSelectedBar = isShowSelectedBar; // 是否显示选中指示条 如果显示的话就在顶层元素添加一个tab-item-stop-sign类
  this.selectedIndex = defaultIndex; // 默认状态下选中的item项
  this.beforeIcon = beforeIcon || ""; // 标题前图标
  this.afterIcon = afterIcon || ""; // 标题后图标
  this.Switchdom; // 存放switch的dom
  this.initSwitchTab();
}
// 这是折叠面板的对象
function Collapse() {
  let arrtributes = { ...arguments[0] };
  this.initClass = arrtributes.initClass; // 初始化类名
  this.beforeIconList;
  this.afterIconList;
  this.initCollapse();
}
// tab初始化方法
SwitchTabTool.prototype.initSwitchTab = function () {
  // 1 获取switchTool
  let s = document.querySelector("." + this.initClass);
  this.Switchdom = s;
  // 2 确定方向
  if (this.direction === "column") {
    document
      .querySelector("." + this.initClass + " .switchTabContanier")
      .classList.add("columnRange");
  } else {
    document
      .querySelector("." + this.initClass + " .switchTabContanier")
      .classList.add("rowRange");
  }
  // 3 确定是否显示指示条
  if (this.isShowSelectedBar) {
    s.classList.add("ShowSelectedBar");
  }
  // 4 获取完成之后是对title部分的处理 首先获取switchTabItem 注意是直接子元素  以免后面有嵌套的同类工具
  let switchTabItemList = document.querySelectorAll(
    "." + this.initClass + ">" + ".switchTabContanier>.switchTab-item"
  );
  // 5 然后是循环 根据对应的item下面的title以及参数对各项功能进行配置
  switchTabItemList.forEach((e, index) => {
    let titleDom = e.querySelector(".switchTab-item-title");
    // 1、前后加上对应icon
    if (this.beforeIcon.length) {
      let appendEle = document.createElement("i");
      appendEle.setAttribute("class", "iconfont " + this.beforeIcon);
      titleDom.insertBefore(appendEle, titleDom.querySelector(".title"));
    }
    if (this.afterIcon.length) {
      let appendEle = document.createElement("i");
      appendEle.setAttribute("class", "iconfont " + this.afterIcon);
      titleDom.appendChild(appendEle);
    }
    // 2、添加默认状态样式
    titleDom.classList.add(this.tabItemTitleNomalStyle);
    // 3、添加默认选中状态的样式
    if (index === this.selectedIndex) {
      titleDom.classList.add(this.tabItemTitleSelectedStyle);
      e.querySelector(".content").style.display = "flex";
    }
    // 4、给每个title加上点击事件
    titleDom.addEventListener("click", (event) => {
      if (index !== this.selectedIndex) {
        titleDom.classList.add(this.tabItemTitleSelectedStyle);
        titleDom.parentNode.querySelector(".content").style.display =
          "flex";
        switchTabItemList[this.selectedIndex]
          .querySelector(".switchTab-item-title")
          .classList.remove(this.tabItemTitleSelectedStyle);
        switchTabItemList[this.selectedIndex].querySelector(
          ".content"
        ).style.display = "none";
        this.selectedIndex = index;
      }
      event.stopPropagation();
    });
  });
};
// 折叠面板初始化方法
Collapse.prototype.initCollapse = function () {
  // 获取Collapse的dom
  let collapseDom = document.querySelector("." + this.initClass);
  // 获取此Collapse下面所有的直接collapse-item子元素
  let collapseItemList = document.querySelectorAll(
    "." + this.initClass + ">" + ".collapse-item"
  );
  // 上次展开的index
  let spanIndex = -1;
  // 循环collapseItemList 获取title 获取并存放beforeIcon 和 AfterIcon
  collapseItemList.forEach((e, index) => {
    e.querySelector("div").classList.add("collapse-item-content");
    // 获取title beforeIcon AfterIcon isCollapsed(默认为关闭状态)
    let title = e.getAttribute("title") || "";
    let beforeIcon = e.getAttribute("beforeIcon") || "verticalLine";
    let afterIcon = e.getAttribute("afterIcon") || "down";
    let isCollapsed = e.getAttribute("isCollapsed") || true;
    let appendHtml = document.createElement("div");
    appendHtml.innerHTML = `
          <div class='collapse-item-title-front'>
            <i class='iconfont ${beforeIcon}'></i>
            <span class='collapse-item-title'>${title}</span>
          </div>
          <i class='iconfont ${afterIcon}'></i>
        `;
    appendHtml.classList.add("collapse-item-tag");
    e.insertBefore(appendHtml, e.querySelector(".collapse-item-content"));
    // 添加点击事件
    e.querySelector(".collapse-item-tag").addEventListener("click", () => {
      // 判断是否是折叠的
      if (isCollapsed) {
        // 内容出现
        e.querySelector(".collapse-item-content").style.display = "flex";
        // 更换图标
        e.querySelector(".collapse-item-tag>.iconfont").classList.replace(
          `${afterIcon}`,
          afterIcon === "add" ? "reduce" : "up"
        );
        // 添加展开时候的样式 span-style
        e.querySelector(
          ".collapse-item-title-front .collapse-item-title"
        ).classList.add("span-style");
        // 更换折叠状态
        isCollapsed = false;
        spanIndex = index;
      } else {
        // 折叠起来 内容收起
        e.querySelector(".collapse-item-content").style.display = "none";
        // 更换图标
        e.querySelector(".collapse-item-tag>.iconfont").classList.replace(
          afterIcon === "add" ? "reduce" : "up",
          `${afterIcon}`
        );
        // 给现在这个项移除展开样式
        e.querySelector(
          ".collapse-item-title-front .collapse-item-title"
        ).classList.remove("span-style");
        isCollapsed = true;
      }
      event.stopPropagation();
    });
  });
};

经过上述我的咔咔一顿操作,总算是可以了。下图是效果:

image.png

上边是web端的

image.png

这个是移动端的

看着就舒服哈哈哈。 但是还是得总结下我这开发过程存在的不足之处:

1、对象属性抽象的不够精确,就像第二个collapse对象,完全就弄错了,collapse能提供什么呢,emm,提供初始化的顶层dom元素变量?添加宽高属性,依照我使用Elementui与swiper的经验,我猜想应该是提供一个整体的环境,虽说看起来是没多大用,但是你想要进行什么操作都必须带上这个最顶级的dom元素变量进行操作。然后就是第二层我应该用collapse-item继承collapse的。上边我逻辑错了,一股脑全加载collapse上边了。
2、就是样式的隔离,这个是个特别大的问题,在开发的过程中我感觉我重复写了特别多的样式,不是这个样式被那个样式覆盖了,就是那个被这个替代了,css样式写得毫无结构化可以言,只能说是缝缝补补的那种。对此我也想到了一个方法,就是抽象出公用属性,自定义属性先不写,用到的时候再写,不能使用后代选择器,否则一定会被覆盖,最好再加上自定义的类名。
3、对于整个组件的架构,需要什么属性,怎么实现,这样设计会出现什么问题等等,就完全是摸着石头过河,一步步陷入深渊都不知道,这个三级联动我用了3天的时间才做完,还是开发的太少,相关方面的基础也不足,比如说js的作用域和闭包等。可以参考一些开源的 已经现有的组件库的源码加以巩固基础。

最后再附上项目网址 哈哈哈 轻喷! www.zphsow.ltd/aps/