前面一章节简单介绍了js 里面创建对象的几种方式,其中每种都是有利有弊;但是最为安全有效的还是最后一个混合构造函数方式;

回顾一下这种方式的简单套路
1,利用构造函数加属性;2,利用原型加方法(事件函数);

现在我们来把一个普通的面向过程的函数改成面向对象的形式;用一个最简单的tab来说明

<style media="screen">
    button.act{
        background:red;
    }
    .box{
        width:400px;
        height:300px;
        color:white;
    }
    .box div{
        display:none;
        height:100%;

    }
    .box div.act{
        display:block;
    }
</style>
<div id="wrap">
    <div class="nav">
        <button type="button" class="act" name="button">选项一</button>
        <button type="button" name="button">选项二</button>
        <button type="button" name="button">选项三</button>
    </div>
    <div class="box">
        <div class="box-i act" style="background:black">选项一div</div>
        <div class="box-i" style="background:teal">选项二div</div>
        <div class="box-i" style="background:blue">选项三div</div>
    </div>
</div>

<script>
window.onload=function () {
    var op=document.getElementById('wrap');
    var aBtn=op.getElementsByTagName('button');
    var aBox=op.getElementsByClassName('box-i');

    for (var i = 0; i < aBtn.length; i++) {
        aBtn[i].index=i;
        aBox[i].index=i;
        aBtn[i].onclick=function(){
            for (var j = 0; j < aBtn.length; j++) {
                aBtn[j].classList.remove('act');
                aBox[j].classList.remove('act');
            }
            this.classList.add('act');
            aBox[this.index].classList.add('act');
        }
    }
}
</script>

没啥样式,结构也没啥,js部分也很简单(贼恶心);

现在我们来把这东西改成对象形式的,在改成面向对象的时候我们需要明确几点;

1,不能有函数嵌套,但是可以有全局变量;

2,把普通的函数改成构造函数

3,函数里面的变量变成属性

4,函数里面的方法改为对象的方法

明确了上面的几点来动手吧,先从简单的动手,把原来函数的变量改成属性和改成构造函数吧,从window.onload里面把这东西拿出去
为此我们需要一个构造函数来做

function swtab(id){
    this.oparent=document.getElementById(id);
    this.aBtn=this.oparent.getElementsByTagName('button');
    this.aBox=this.oparent.getElementsByClassName('box-i');
    for (var i = 0; i < this.aBtn.length; i++) {
        this.aBox[i].index=i;
        this.aBtn[i].index=i;
        this.aBtn[i].onclick=function(){
            for (var j = 0; j < this.aBtn.length; j++) {
                this.aBtn[j].classList.remove('act');
                this.aBox[j].classList.remove('act');
            }
            this.classList.add('act');
            this.aBox[this.index].classList.add('act');
        }
    }
}

上面做了2件事,第一把原本代码块用一个函数抽了出去,并且 把原来的变量改成了这个构造函数的属性;(变成了this.),也就是以前所有的变量都要使用this. 的形式了;

现在我们要用第4点把构造函数里面的嵌套函数也抽出去,别忘了构造函数加属性,原型加方法原则

function swtab(id){
    this.oparent=document.getElementById(id);
    this.aBtn=this.oparent.getElementsByTagName('button');
    this.aBox=this.oparent.getElementsByClassName('box-i');
    for (var i = 0; i < this.aBtn.length; i++) {
        this.aBox[i].index=i;
        this.aBtn[i].index=i;
        this.aBtn[i].onclick=this.fnClick;
    }
}
swtab.prototype.fnClick= function(){
    for(var j=0;j<this.aBtn.length;j++){
        this.aBtn[j].classList.remove('act');
        this.aBox[j].classList.remove('act');
    }
    this.classList.add('act');
    this.aBox[this.index].classList.add('act');
}

现在没了函数嵌套,而是把click 函数挂在swtab的原型上;现在我们运行

在swtab.prototype.fnClick函数里面的for循环会得到 Uncaught TypeError: Cannot read property 'length' of undefined

这一句this.aBtn.length 引起了报错,为什么呢?在面向对象的时候,很多时候都是this引起的,要时刻警惕this到底指向哪儿去了,我们去原型方法(swtab.prototype.fnClick)里面console.log(this);

当我们点击了按钮之后 控制台输出了 <button type="button" name="button">选项二</button>,也就说现在this 指向了点击的那个按钮,仔细想一想并没有错,虽然这个fnClick 是属于swtab 实例对象的,但是这个时候调用他的并不是swtab 实例对象,而是被点击的那个按钮,一个按钮自然是没有length 属性了;

现在问题来了,我要怎么保持这个this不变,哪怕是按钮点击调用也保持不变;没错,这时候你应该想起了 var _this=this 这种奇怪的写法;对,我们使用闭包保持对this 的引用;使用闭包关键就是函数套函数,内层使用外层的变量

现在我们对调用函数在改变一下

var _this=this;
this.aBtn[i].onclick=function(){
    _this.fnClick();
};

现在我们在看一下 fnClick 函数里面的this指去哪儿

swtab {oparent: div#wrap, aBtn: HTMLCollection(3), aBox: HTMLCollection(3)}

可以看到,现在这个this 被保持引用了swtab 实例对象了,也没有报length的错误了;但是我们却得到了一个新的错误 Uncaught TypeError: Cannot read property 'add' of undefined
看来是fnClick 函数最后两句报错了; 很显而易见的错,我们一开始就是把this 定为当前点击的按钮,但是现在这个函数里面的this 被闭包锁定了swtab 实例对象,当然classList 属性不存在了;

这就难受了,这个时候fnClick函数里面this同时存在2个状态,一个要指向实例对象,另一个是要指向当前点击按钮...如果我们能够从外部获取到当前点击的按钮那就好办了,所以我们可以在外部传入当前点击的按钮...

现在在改造一下

var _this=this;
this.aBtn[i].onclick=function(){
    _this.fnClick(this);
};


swtab.prototype.fnClick= function(cur){
    var _btn=cur;
    for(var j=0;j<this.aBtn.length;j++){
        this.aBtn[j].classList.remove('act');
        this.aBox[j].classList.remove('act');
    }
    _btn.classList.add('act');
    this.aBox[_btn.index].classList.add('act');
}

现在在试一下,函数正常运行;
最后完成的代码

window.onload=function (argument) {
    new swtab('wrap');
}

function swtab(id){
    this.oparent=document.getElementById(id);
    this.aBtn=this.oparent.getElementsByTagName('button');
    this.aBox=this.oparent.getElementsByClassName('box-i');
    var _this=this;
    for (var i = 0; i < this.aBtn.length; i++) {
        this.aBox[i].index=i;
        this.aBtn[i].index=i;
        this.aBtn[i].onclick=function(){
            _this.fnClick(this);
        }
    }
}
swtab.prototype.fnClick= function(cur){
    var _btn=cur;
    for(var j=0;j<this.aBtn.length;j++){
        this.aBtn[j].classList.remove('act');
        this.aBox[j].classList.remove('act');
    }
    _btn.classList.add('act');
    this.aBox[_btn.index].classList.add('act');
}

现在总结一下,上面的东西看起来简单,但是里面的东西却是不少,既有对象的混合创建,又有闭包保证this 引用,还有this传递,从中也可看到,出的问题基本都是在这个this 的指向上,所以这个this 确实及其重要


墨韵
109 声望0 粉丝