封装一个组件 + 函数惰性思想(重写应用)

453 阅读6分钟

这里我们以css的封装为例

封装一个utils:

~function(){ function utils(Elem,attr){

}
return utils{
    
}

}()

惰性思想重写:

var utils = (function(){
    var flag = 'getComputedStyle' in window;
    //如果存在说明当前浏览器是标准浏览器,反之说明是IE6-IE8
    function getCss(curEle,attr){
        
    }
    return {
        getCss:getCss
    }
})();

上面我们说是用了js惰性思想,能一次解决的,绝对不会每一次都重新处理,属于js优化的一种,那么我们还有一种惰性思想,叫做函数重载,或者叫函数重复盖,或者函数重写,还是用当前列子举例

我们现在不再封装utils

var _flag = window.getComputedStyle
function getCSS(cueEle,attr){
    if(_flag){
        return window.getComputedStyle(cueEle,null)[attr]
    }
    return cueEle.currentStyle[attr]
}

但是这样我们每次还是需要判断,我们对上面代码在进行优化

function getCSS(cueEle,attr){
    if(window.getComputedStyle){
        getCss = function(cueEle,attr){
            return window.getComputedStyle(cueEle,null)[attr]
        }
    }else{
        getCss = function(cueEle,attr){
            return cueEle.currentStyle[attr]
        }
    }
    return getCSS(cueEle,attr)//此处的getCss是其中的某个小方法 了
}
最开始
  • 第一次执行getCss,如果是标准浏览器让

getCSS = window.getComputedStyle(cueEle,null)[attr]

,如果是IE6-8getCSS = cueEle.currentStyle[attr]
  • 第二次执行getCss,直接纸执行某一个小方法,不需要在进行某种验证

--等我去玩局王者再回来写,暂且省略

重写选项卡

  • 先获取tabbox,获取tabbox下所有的li,和div
  • 循环所有li给li 绑定onclick事件
var tabBox = document.getElementById('tabBox');
    tabList = tabBox.getElementByTagName('li');
    conList = tabBox.getElementByTagName('div');
for(var i=0;i<tabList.length;i++){
    var curTab = tabList[i];
    curTab.myIndex = i;
    curTab.onclick = function(){
        //清除所有li选中样式
        for(var k=0;k<tabList.length;k++){
            tabList[k].className = null;
            tabList[k].className = 'con';
        }
        //让当前操作的li有选中样式
        this.className = 'select';
        conList[this.myIndex].className = 'con select';
    }
}
  • 这么做可以实现,每次进入页面之后我们会把所有的li都进行清除样式,这样会浪费性能,我们必须要清除上一次操作的li就可以,所以优化过后代码如下
var tabBox = document.getElementById('tabBox');
    tabList = tabBox.getElementByTagName('li');
    conList = tabBox.getElementByTagName('div');
var _prev = 0;//记录上一次选中的结果,默认选中第一个
for(var i=0;i<tabList.length;i++){
    var curTab = tabList[i];
    curTab.myIndex = i;
    curTab.onclick = function(){
        //清除上一个li选中样式
        tabList[_prev].className = null;
        tabList[_prev].className = 'con';
        //让当前操作的li有选中样式
        this.className = 'select';
        conList[this.myIndex].className = 'con select';
        _prev = this.myIndex;
    }
}
  • 那么问题又来了,getElementByTagName会获取当前元素下的所有后代元素,而不是子元素,那我们如何获取呢,当然我们也可以采取class获取。但是原生js并没有getElementsClassName的方法,这时候考虑我们的utils;哈哈~

使用Dom库完成选项卡的封装

代码如下

var tabBox = document.getElementById('tabBox');
    tab = utils.getElementsClassName('tab',tabBox)[0];
    tabList = utils.children(tab,'li');
    conList = utils.children(tabBox,'div');
var _prev = 0;//记录上一次选中的结果,默认选中第一个
for(var i=0;i<tabList.length;i++){
    var curTab = tabList[i];
    curTab.myIndex = i;
    curTab.onclick = function(){
        //清除上一个li选中样式
        tabList[_prev].className = null;
        tabList[_prev].className = 'con';
        //让当前操作的li有选中样式
        this.className = 'select';
        conList[this.myIndex].className = 'con select';
        _prev = this.myIndex;
    }
}

为了提高代码的复用性,我们可以封装以上代码,可以用高级单利模式 html

<div class = 'tabBox'>
    <ul class = 'tab clearfix'><!--页卡-->
        <li></li>
    </ul>
    <div class='con select'><!--内容-->
    </div>
    <div class='con select'><!--内容-->
    </div>
    <div class='con select'><!--内容-->
    </div>

js

~(function(){
    var tabBoxList = utils.getElementsClassName('tabBox');
    for(var i=0;i<tabBoxList.length;i++){
        change(tabBoxList[i])
    }
    function change(tabBox){
        var tab = utils.getElementsClassName('tab',tabBox)[0];
            tabList = utils.children(tab,'li');
            conList = utils.children(tabBox,'div');
        var _prev = 0;//记录上一次选中的结果,默认选中第一个
        for(var i=0;i<tabList.length;i++){
            var curTab = tabList[i];
            curTab.myIndex = i;
            curTab.onclick = function(){
                //清除上一个li选中样式
                tabList[_prev].className = null;
                tabList[_prev].className = 'con';
                //让当前操作的li有选中样式
                this.className = 'select';
                conList[this.myIndex].className = 'con select';
                _prev = this.myIndex;
            }
        }    
    }
})()

以上我们批量实现某一个相同结构布局的操作,可以理解为一个简单的封装,那么如何将这个疯装成插件

使用构造函数初步封装选项卡的插件

任何类库的封装,我们必用构造函数模式 js

function ChangeTab(tabBox){
    //在类的任何方法都可以调用,,我们一般吧方法存在当前实例上,保证每个this都是当前类的实例
    this.tabBox = tabBox;
    //开始实现选项卡的功能
    //ChangeTab.prototype.init.call(this) == this.init();
    this.init();
}
ChangeTab.prototype = {
    constructor:ChangeTab, //保证原型链的完整性
    init:function(){ 
        this.tab = utils.children( this.tabBox,'ul')[0];
        this.tabList = utils.children( this.tab,'li');
        this.conList = utils.children( this.tabBox,'div');
        this._prev = 0;
        this.change();
    }
    this.change(tabBox){
        var tab = this.tab,
            tabList =this.tabList,
            conList =  this.conList,
            _prev = this._prev,
            _this = this;
        for(var i=0;i<tabList.length;i++){
        //this是当前的li, _this是实例
            tabList[i].myIndex = i;
            tabList[i].onclick = function(){
                tabList[_prev].className = null;
                tabList[_prev].className = 'con';
                this.className = 'select';
                _this.conList[this.myIndex].className = 'con select';
                _this._prev = this.myIndex;
            }
        } 
    }
}

关于this

  • 非严格模式下自执行函数中this是 window,undefined
  • 给元素绑定方法一般都是触发类的元素
  • 方法执行看前面有没有点,点前面是谁,this就是谁,没有点this就是window
  • 构造函数中的this是当前类的实例
  • 使用call方法和applay可以改变this指向
  • ES6中箭头函数没有this,他的this是当前他的宿主环境中的this 如上,手机核心点都在于this,我们很容易被this影响,我们必须new一个函数才可以,直接执行ChangeTab,this会指向window,为了防止冲突,我们把函数放到闭包里,且进行响应的优化
function(){
    function ChangeTab(tabBox){
        this.tabBox = tabBox;
        this.init();
    }
    ChangeTab.prototype = {
        constructor:ChangeTab, //保证原型链的完整性
        init:function(){ }
        this.change(tabBox){}
    }
    window.CT = ChangeTab;
}();
var tabBoxList = utils.getElementByClassName('tabBox');
for(var i=0;i<tabBoxList.length;i++){
    new CT(tabBoxList[i])
}

我们也可以增加额外的配置项

function(){
    function ChangeTab(tabBox,options){
        var _default = {
            initIndex : 0,//默认选中第几个
            eventType : 'click'// 事件类型
        }
        for(var key in options){
            if(options.hasOwnProperty(key)){
                _defalt[key] = options[key];
            }
        }
        this.initIndex = _default.initIndex;
        this.eventType = _default.eventType;
        this.tabBox = tabBox;
        this.init();
    }
    ChangeTab.prototype = {
        constructor:ChangeTab, //保证原型链的完整性
        init:function(){ 
            this.tab = utils.children( this.tabBox,'ul')[0];
            this.tabList = utils.children( this.tab,'li');
            this.conList = utils.children( this.tabBox,'div');
            this._prev = 0;
            this.clear();
            this.change();
        }
        clear:function(){
            //清空所有的样式类
            for(var i=0;i<this.tabList.length;i++){
                this.tabList[i].className = '';
                this.conList[i].className = 'con';
            }
            //初始化默认选中页卡
            this.tabList[this.initindex].className = 'select';
            this.conList[this.initindex].className = 'con select';
        }
        change(tabBox){
            var tab = this.tab,
            tabList =this.tabList,
            conList =  this.conList,
            _prev = this._prev,
            _this = this;
            for(var i=0;i<tabList.length;i++){
            //this是当前的li, _this是实例
                tabList[i].myIndex = i;
                tabList[i]['on' + this.eventType} = function(){
                    tabList[_prev].className = null;
                    tabList[_prev].className = 'con';
                    this.className = 'select';
                    _this.conList[this.myIndex].className = 'con select';
                    _this._prev = this.myIndex;
                }
            } 
        }
    }
    window.CT = ChangeTab;
}();
var tabBoxList = utils.getElementByClassName('tabBox');
for(var i=0;i<tabBoxList.length;i++){
    new CT(tabBoxList[i],{
            initIndex : 1,//默认选中第几个
            eventType : 'mouseover'// 事件类型
        })
}
  • 我们会发现构造函数
  • 所有信息之间的传递都是基于实例this
  • 所有的方法都在构造函数上
  • 所有的值都在实例私有属性上
  • 插件封装不仅仅是实现功能,而是提供更多的可变性,可变性通过传参方式搞定
  • 保持函数独立性,可以接收代码实例
  • 如果有额外需求,单独调用,可以在原型扩展方法

组件和插件

  • 插件 只是有js功能,基本上只要导入js就可以了,具备具体的业务逻辑iscroll swiper
  • ui组件 比插件大,把样式,js,结构全部规定好,可以理解成模版,相对于插件用起来简单,封装麻烦,但是可扩展性不足 bootstrop
  • 类库 Dom库 jq,提供常用方法,相当于工具包 jquery zepto
  • 框架 vue-mvvm react-mvm 基本具备自己独有的设计程序思想 不仅仅提供方法和相关结构 虚拟DOM渲染 效率高,方便后期维护,后期扩展,组件化模块化实现,易操作