求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
 
我要提问 
Javascript实现图片导航
 

发布于2012-10-8

 

需要实现类似下图的一个效果,达到导航图片的目的。

可以使用jquery去实现,但容易陷入怎么做的思维中,如果自己手写的话,就可以先思考做什么,然后再思考怎么做。(当然这里做什么指原型已确定的)

第一步:做什么[分析]?

我们要做一个图片导航,一屏显示5张图片,用户能点“上一张”/“下一张”按钮,实现移动切换图片,如果往前没有图片了则“上一张”是无效状态,如果往后没有图片了则“下一张”是无效状态。

对应到我们的问题域则需要几个对象来完成相应的职责:

1、应该有一个movePanel类,状态:它应该知道总共有多少张图片,一屏显示多少张,当前显示的是哪些,还能不能向前或向后移动了;行为:它有last()和next()方法,分别表示向前或向后移动图片的功能。

function movePanel(totalCount,showNum){
    var curStep=0;    
    this.getCanLast=function(){};    
    this.getCanNext=function(){};    
    this.last=function(){};    
    this.next=function(){};
}

但是有一个问题,没有通知“上一张”/“下一张”按钮的状态切换,所以movePanel还一个通知外界自己状态改变的方法

function notify(){}

还有原型未列出,但出于性能考虑,需要图片预加载

function loadImg(){}

2、原型要求点击按钮后,需要移动的切换图片,即是一个动画效果,我们需要一个做移动类mover,状态:它知道自己所处的坐标;行为:以某种速度移动到另一个坐标;回调函数:当它移动时每一帧的坐标通知给外界,以及到达指定坐标后通知外界。我们通过回调方式,让外界知道当前的状态,而不是直接修改页面元素的坐标,达到解耦目的。

function mover(posX, posY) {
    this.moveTo = function (posX, posY, speed, frequencyfn, endfn) { };
}

明确了问题域和相应的职责,即确定了要做什么?接下来是怎么做了。

第二步:怎么做[设计]?

1、实现movePanel类

先实现它的getCanLast和getCanNext两个状态,根据当前位置curStep和总长度totalCount去计算得到相应的状态

    this.getCanLast=function(){
        if(curStep==0) return false;
        return true;
    };    
    this.getCanNext=function(){
        if(curStep+showNum>=totalCount) return false;
        return true;
    };

显然movePanel类中的last()/next()方法是依赖于mover类的,而mover类目前还有没实现,所以我们先用todo占个位。

    this.last=function(){
        if(!this.getCanLast()) return;        
        //…todo向前移动的代码
        curStep--;
    };    
    this.next=function(){
        if(!this.getCanNext()) return;
        //…todo向后移动的代码
        curStep++;        
    }

关于通知外界的notify(),我们很自然的想到observer模式,需要一个注册方法和一个观察者队列

    var subscriberArray=[];    
    var me=this;    
    this.register=function(fn){
        subscriberArray.push(fn);
    }
    function _notify(){
        for(var i in subscriberArray){
            subscriberArray[i](me);
        }
    }

由于notify并不会被外界调用,把它设计成私有的,还需要将notify放在last()/next()方法体的最后。

由于register()必须在构建movePanel的实例后才得调用,所以必须增加一个类似初始化显示图片的方法show(),在register()调用后再执行,而不能直接放在movePanel构造函数内。

    this.show = function () {
        for (var i = 0; i < showNum; i++) {  
            _loadImg();
        }
        _notify();
    }

假设我们一个加载图片的私有方法loadImg(),关于预加载图片,开始时预加载一张,并在执行next()后,根据情况再进行预加载。所以需要修改show()方法和next()/last()方法,并用一个变量bufferImgCount来跟踪缓存中的图片数量

    var bufferImgCount = 0;
    this.last = function(){
        if (!this.getCanLast()) return;
        //…todo向前移动的代码
        bufferImgCount++;
        curStep--;  
    };
    this.next = function(){
        if(!this.getCanNext()) return;
        //…todo向后移动的代码
        bufferImgCount--;
        if (bufferImgCount == 0 && _loadImg())
            bufferImgCount++;
        curStep++;
    }
    this.show = function () {
        for (var i = 0; i <= showNum; i++) { //include one buffer img            
            _loadImg();
        }
        bufferImgCount = 1;
        _notify();
    }

接下来就要实现 loadImg()方法,这个方法要根据当前已加载的情况,去加载下一个的图片,所以我们需要在movePanel类中增加一个alreadyCreateCount的变量,等等,这个变量其他方法会用到吗,好像不会,那么干嘛不设计成一个闭包,让变量存在闭包中,不去污染movePanel的空间

    var _loadImg = (function () {
        var alreadyCreateCount = 0;
        return function () {// closure function
            if (alreadyCreateCount == totalCount) return false;
            _createImgByIndexfn(alreadyCreateCount);
            alreadyCreateCount++;
            return true;
        }
    })(); 

其中createImgByIndexfn是回调函数,用于调用具体创建图片的方法,这也跟页面相关,所以不放在这个问题域中。

至此,我们的代码成这样

function movePanel(totalCount, showNum) {
    var subscriberArray=[];    
    var me=this;   
    var bufferImgCount = 0; 
    this.getCanLast=function(){
        if(curStep==0) return false;
        return true;
    };    
    this.getCanNext=function(){
        if(curStep+showNum>=totalCount) return false;
        return true;
    };
    this.register=function(fn){
        subscriberArray.push(fn);
    }
    this.last = function(){
        if (!this.getCanLast()) return;
        //…todo向前移动的代码
        bufferImgCount++;
        curStep--;
        _notify();
    };
    this.next = function(){
        if(!this.getCanNext()) return;
        //…todo向后移动的代码
        bufferImgCount--;
        if (bufferImgCount == 0 && _loadImg())
            bufferImgCount++;
        curStep++;
        _notify();
    }
    this.show = function (createImgByIndexfn) {
        _createImgByIndexfn = createImgByIndexfn;
        for (var i = 0; i <= showNum; i++) { //include one buffer img            
            _loadImg();
        }
        bufferImgCount = 1;
        _notify();
    }
    var _createImgByIndexfn = function (index) { };  //callback
    var _loadImg = (function () {
        var alreadyCreateCount = 0;
        return function () {// closure function
            if (alreadyCreateCount == totalCount) return false;
            _createImgByIndexfn(alreadyCreateCount);
            alreadyCreateCount++;
            return true;
        }
    })();
    function _notify(){
        for(var i in subscriberArray){
            subscriberArray[i](me);

        }
    }
}

2、实现mover类

mover类就一个moveTo()方法,要实现它有动画效果,在一定时间内持续移动一定的距离,我们需要一点动画的知识,当连续变化的影像为每秒25次的速度就能给人流畅的感觉,我们就以25/1000为基准,去调节动画移动的快慢,需要借助setTimeout()方法,在适当的时候去执行坐标的变化。如果考虑可用性,我们可以给移动速度一些默认选项(快/中/慢)。

function mover(posX, posY) {
    this.posX = posX;
    this.posY = posY;
    this.speedCfg = { 'slow': 2000, 'normal': 1000, 'fast': 500 };
    this.frequency = 25.0 / 1000; //25 frames per second
    var me = this;
    this.moveTo = function (posX, posY, speed, frequencyfn, endfn) {
        if (typeof (speed) == 'string') {
            if (!speed in this.speedCfg) { speed = 'normal'; }
            speed = this.speedCfg[speed];
        }
        var steps = speed * this.frequency;
        var distanceX = (posX - this.posX) / steps;
        var distanceY = (posY - this.posY) / steps;
        for (var i = 0; i < steps - 1; i++) {
            setTimeout(function () {
                me.posX += distanceX;
                me.posY += distanceY;
                frequencyfn(me.posX, me.posY);
            }, (1 / this.frequency) * (i + 1));
        }
        setTimeout(function () {
            this.posX = posX;
            this.posY = posY;
            endfn(this.posX, this.posY);
        }, (1 / this.frequency) * steps);
    };
}

我们可以看到,代码中只是纯计算,并不依赖其他页面元素或其他第三方框架,这样是方便测试的,我们只需填上合适的值,就可以看到是否是期望的结果。

<html>
<body>
    <div id='begin'>
        begin:
    </div>
    <div id='frame'>
        frame:
    </div>
    <div id='end'>
        end:
    </div>
</body>
</html>
<script>
    function mover(posX, posY) {
        this.posX = posX;
        this.posY = posY;
        this.speedCfg = { 'slow': 2000, 'normal': 1000, 'fast': 500 };
        this.frequency = 25.0 / 1000; 
        var me = this;
        this.moveTo = function (posX, posY, speed, frequencyfn, endfn) {
            if (typeof (speed) == 'string') {
                if (!speed in this.speedCfg) { speed = 'normal'; }
                speed = this.speedCfg[speed];
            }
            var steps = speed * this.frequency;
            var distanceX = (posX - this.posX) / steps;
            var distanceY = (posY - this.posY) / steps;
            for (var i = 0; i < steps - 1; i++) {
                setTimeout(function () {
                    me.posX += distanceX;
                    me.posY += distanceY;
                    frequencyfn(me.posX, me.posY);
                }, (1 / this.frequency) * (i + 1));
            }
            setTimeout(function () {
                this.posX = posX;
                this.posY = posY;
                endfn(this.posX, this.posY);
            }, (1 / this.frequency) * steps);
        };
    }

    var dt=new Date();
    document.getElementById('begin').innerHTML += '<br/>(X:' + 10 + ' Y:' + 20 + ') @ '+ dt.getSeconds()+"."+
dt.getMilliseconds();

    new mover(10,20).moveTo(
    60, 80, 'normal',
    function (x, y) {
        var dt=new Date();
        document.getElementById('frame').innerHTML += '<br/>(X:' + x + ' Y:' + y + ') @ '+ dt.getSeconds()+"."+
dt.getMilliseconds();
    },
    function (x, y) {
        var dt=new Date();
        document.getElementById('end').innerHTML += '<br/>(X:' + x + ' Y:' + y + ') @ '+ dt.getSeconds()+"."+
dt.getMilliseconds();
    }
);
</script>

3、使用mover去实现movePanel中的相关方法

我们使用mover实例来具体实现movePanel中的last()/next()方法,由于动画是异步的,所以原先在last()/next()中的todo后面代码需要放在动画结束的回调函数中。

而mover.moveTo所需的相关回调函数,movePanel拦截后,并作为last()/next()方法的参数,供调用方定制。

mover需要当前坐标才能初始化,所以movePanel需要维持当前坐标的变量curPos,curPos的初始化由movePanel参数提供。

具体实现如下

    var curPos = { 'x': curLeft, 'y': curTop };
    this.last = function (speed, frequencyfn, endfn) {
        if (!this.getCanLast()) return;        
        new mover(curPos.x, curPos.y).moveTo(
                _getLastPos().x, _getLastPos().y, speed, frequencyfn,
                function (x, y) {
                    endfn(x, y);
                    curPos.x = x;
                    curPos.y = y;
                    bufferImgCount++;
                    curStep--;
                    _notify();
                }
            );
    };
    this.next = function (speed, frequencyfn, endfn) {
        if (!this.getCanNext()) return;
        new mover(curPos.x, curPos.y).moveTo(
                _getNextPos().x, _getNextPos().y, speed, frequencyfn,
                function (x, y) {
                    endfn(x, y);
                    curPos.x = x;
                    curPos.y = y;
                    bufferImgCount--;
                    if (bufferImgCount == 0 && _loadImg())
                        bufferImgCount++;
                    curStep++;
                    _notify();
                }
            );
    };

这里_getLastPos()/_getNextPos()方法是取得上一坐标/下一坐标的私有方法,mover是支持任意坐标间的移动的,但在我们movePanle只需支持横向和纵向就可以,我们需要确定 方向 与 步长 即可计算出下一坐标的值了。这里采用了表驱动的做法,利于减少不必要的if/else,另外如果需要增加新的方向,只需在directCfg增加一个配置即可。

    var directCfg = { 'horizontal': [1, 0, -1, 0], 'vertical': [0, 1, 0, -1] }; //table-driven
    function _getLastPos() {
        return { 'x': curPos.x + directCfg[direct][0] * stepLen,
            'y': curPos.y + directCfg[direct][1] * stepLen
        };
    }
    function _getNextPos() {
        return { 'x': curPos.x + directCfg[direct][2] * stepLen,
            'y': curPos.y + directCfg[direct][3] * stepLen
        };
    }  

第三步:怎么用[调用]?

可以看出movePanel/mover没有依赖页面元素,但最终页面图片的创建是需要依赖这些的。所以需要在调用时将这些依赖通过回调的方式传进去。以下是调用一个示例

    var imgList = [//初始化图片列表
    { title: 'A',
        src: 'http://img3.douban.com/icon/u1386103-5.jpg'
    },
    { title: 'B',
        src: 'http://img3.douban.com/icon/u4134736-7.jpg'
    },
    { title: 'C',
        src: 'http://img3.douban.com/icon/u1364691-1.jpg'
    },
    { title: 'D',
        src: 'http://img3.douban.com/icon/u4616093-4.jpg'
    },
    { title: 'E',
        src: 'http://img3.douban.com/icon/u43504680-2.jpg'
    },
    { title: 'F',
        src: 'http://img3.douban.com/icon/u4523892-23.jpg'
    },
    { title: 'G',
        src: 'http://img3.douban.com/icon/u1901783-16.jpg'
    },
    { title: 'H',
        src: 'http://img3.douban.com/icon/u1332495-14.jpg'
    },
    { title: 'I',
        src: 'http://img3.douban.com/icon/u3919874-2.jpg'
    },
    { title: 'J',
        src: 'http://img3.douban.com/icon/u31834465-48.jpg'
    }
    ];
    //初始化panel,确定起始坐标,方向,步长,图片数量,显示数量
    var panel = new movePanel(0, 5, 'horizontal', 39, imgList.length, 4);
    panel.register(function (p) {//侦听并更新按钮状态
        if (p.getCanLast())
            document.getElementById('btnLast').disabled = false;
        else
            document.getElementById('btnLast').disabled = true;
    });
    panel.register(function (p) {
        if (p.getCanNext())
            document.getElementById('btnNext').disabled = false;
        else
            document.getElementById('btnNext').disabled = true;
    });
    panel.show(function (index) {//显示,并传入创建图片的回调函数
        var img = document.createElement('IMG');
        img.src = imgList[index].src;
        img.title = imgList[index].title;
        document.getElementById('imgs').appendChild(img);
    });
    document.getElementById('btnLast').onclick = function () {
        panel.last('fast', function (x, y) {//响应每一帧的变化
            document.getElementById('imgs').style.left = x + 'px';
        }, function (x, y) {
            document.getElementById('imgs').style.left = x + 'px';
        });
    };
    document.getElementById('btnNext').onclick = function () {
        panel.next('fast', function (x, y) {
            document.getElementById('imgs').style.left = x + 'px';
        }, function (x, y) {
            document.getElementById('imgs').style.left = x + 'px';
        });
    };  

这一部份是比较容易变化的区域,比如想用A标签来代替现在的button,创建图片的过程中会加入其他事件等等,如果在这里觉得改变按纽状态,或创建图片比较复杂时,可以考虑使用jquery或者第三方的库(但不能一开始分析问题域时就使用第三方的库)。

完整的代码与示例:

<html>
<head>
<style>
div#content1 {position:relative; width:158px; height:50px; overflow:hidden; padding:0; }
div#imgs1 {position:absolute; top:5px; width:1000px; height:40px; padding:0; }
div#imgs1 img {margin:2px; width:35px; height:35px; display:block; float:left; }

div#content2 {position:relative; width:50px; height:182px; overflow:hidden; padding:0; }
div#imgs2 {position:absolute; left:5px; width:40px; height:1000px; padding:0; }
div#imgs2 img {margin:2px; width:35px; height:35px; display:block; }

</style>   
</head>
<body>
    <div id='content1'>
        <div id='imgs1'></div>
    </div>
    <input type='button' id='btnLast1' value="last" />
    <input type='button' id='btnNext1' value="next" />
<br/>
<br/>
    <div id='content2'>
        <div id='imgs2'></div>
    </div>
<br/>
    <input type='button' id='btnLast2' value="last" />
    <input type='button' id='btnNext2' value="next" />
</body>
</html>
<script>

/*API Begin*/

function mover(posX, posY) {
    this.posX = posX;
    this.posY = posY;
    this.speedCfg = { 'slow': 1500, 'normal': 1000, 'fast': 500 };
    this.frequency = 25.0 / 1000;
    var me = this;
    this.moveTo = function (posX, posY, speed, frequencyfn, endfn) {
        if (typeof (speed) == 'string') {
            if (!speed in this.speedCfg) { speed = 'normal'; }
            speed = this.speedCfg[speed];
        }
        var steps = speed * this.frequency;
        var distanceX = (posX - this.posX) / steps;
        var distanceY = (posY - this.posY) / steps;
        for (var i = 0; i < steps - 1; i++) {
            setTimeout(function () {
                me.posX += distanceX;
                me.posY += distanceY;
                frequencyfn(me.posX, me.posY);
            }, (1 / this.frequency) * (i + 1));
        }
        setTimeout(function () {
            this.posX = posX;
            this.posY = posY;
            endfn(this.posX, this.posY);
        }, (1 / this.frequency) * steps);
    };
}

function movePanel(curLeft, curTop, direct, stepLen, totalCount, showNum) {
    var me = this;
    var curPos = { 'x': curLeft, 'y': curTop };
    var directCfg = { 'horizontal': [1, 0, -1, 0], 'vertical': [0, 1, 0, -1] }; //table-driven
    var subsciberArray = [];
    var curStep = 0;
    var bufferImgCount = 0;
    var isMoving = false;

    this.register = function (fn) {
        subsciberArray.push(fn);
    };

    this.getCanLast = function () {
        if (curStep == 0) return false;
        return true;
    };

    this.getCanNext = function () {
        if (curStep + showNum >= totalCount) return false;
        return true;
    };

    this.last = function (speed, frequencyfn, endfn) {
        if (!this.getCanLast()) return;
        if (isMoving) return;
        isMoving = true;
        new mover(curPos.x, curPos.y).moveTo(
                _getLastPos().x, _getLastPos().y, speed, frequencyfn,
                function (x, y) {
                    endfn(x, y);
                    curPos.x = x;
                    curPos.y = y;
                    bufferImgCount++;
                    curStep--;
                    _notify();
                    isMoving = false;
                }
            );
    };

    this.next = function (speed, frequencyfn, endfn) {
        if (!this.getCanNext()) return;
        if (isMoving) return;
        isMoving = true;
        new mover(curPos.x, curPos.y).moveTo(
                _getNextPos().x, _getNextPos().y, speed, frequencyfn,
                function (x, y) {
                    endfn(x, y);
                    curPos.x = x;
                    curPos.y = y;
                    bufferImgCount--;
                    if (bufferImgCount == 0 && _loadImg())
                        bufferImgCount++;
                    curStep++;
                    _notify();
                    isMoving = false;
                }
            );
    };

    this.show = function (createImgByIndexfn) {
        _createImgByIndexfn = createImgByIndexfn;
        for (var i = 0; i <= showNum; i++) {       
            _loadImg();
        }
        bufferImgCount = 1;
        _notify();
    }

    var _createImgByIndexfn = function (index) { };

    var _loadImg = (function () {
        var alreadyCreateCount = 0;
        return function () {
            if (alreadyCreateCount == totalCount) return false;
            _createImgByIndexfn(alreadyCreateCount);
            alreadyCreateCount++;
            return true;
        }
    })();

    function _notify() {
        for (var i in subsciberArray)
            subsciberArray[i](me);
    }

    function _getLastPos() {
        return { 'x': curPos.x + directCfg[direct][0] * stepLen,
            'y': curPos.y + directCfg[direct][1] * stepLen
        };
    }

    function _getNextPos() {
        return { 'x': curPos.x + directCfg[direct][2] * stepLen,
            'y': curPos.y + directCfg[direct][3] * stepLen
        };
    }
}

/*API end*/



    var imgList = [
    { title: 'A',
        src: 'http://img3.douban.com/icon/u1386103-5.jpg'
    },
    { title: 'B',
        src: 'http://img3.douban.com/icon/u4134736-7.jpg'
    },
    { title: 'C',
        src: 'http://img3.douban.com/icon/u1364691-1.jpg'
    },
    { title: 'D',
        src: 'http://img3.douban.com/icon/u4616093-4.jpg'
    },
    { title: 'E',
        src: 'http://img3.douban.com/icon/u43504680-2.jpg'
    },
    { title: 'F',
        src: 'http://img3.douban.com/icon/u4523892-23.jpg'
    },
    { title: 'G',
        src: 'http://img3.douban.com/icon/u1901783-16.jpg'
    },
    { title: 'H',
        src: 'http://img3.douban.com/icon/u1332495-14.jpg'
    },
    { title: 'I',
        src: 'http://img3.douban.com/icon/u3919874-2.jpg'
    },
    { title: 'J',
        src: 'http://img3.douban.com/icon/u31834465-48.jpg'
    }
    ];


/*sample 1 begin*/

   
    var panel1 = new movePanel(0, 5, 'horizontal', 39, imgList.length, 4);
    panel1.register(function (p) {
        if (p.getCanLast())
            document.getElementById('btnLast1').disabled = false;
        else
            document.getElementById('btnLast1').disabled = true;
    });
    panel1.register(function (p) {
        if (p.getCanNext())
            document.getElementById('btnNext1').disabled = false;
        else
            document.getElementById('btnNext1').disabled = true;
    });
    panel1.show(function (index) {
        var img = document.createElement('IMG');
        img.src = imgList[index].src;
        img.title = imgList[index].title;
        document.getElementById('imgs1').appendChild(img);
    });
    document.getElementById('btnLast1').onclick = function () {
        panel1.last(200, function (x, y) {
            document.getElementById('imgs1').style.left = x + 'px';
            document.getElementById('btnNext1').disabled = true;
            document.getElementById('btnLast1').disabled = true;
        }, function (x, y) {
            document.getElementById('imgs1').style.left = x + 'px';
            document.getElementById('btnNext1').disabled = false;
            document.getElementById('btnLast1').disabled = false;
        });
    };
    document.getElementById('btnNext1').onclick = function () {
        panel1.next(200, function (x, y) {
            document.getElementById('imgs1').style.left = x + 'px';
            document.getElementById('btnNext1').disabled = true;
            document.getElementById('btnLast1').disabled = true;
        }, function (x, y) {
            document.getElementById('imgs1').style.left = x + 'px';
            document.getElementById('btnNext1').disabled = false;
            document.getElementById('btnLast1').disabled = false;
        });
    }; 

/*sample 1 end*/


/*sample 2 begin*/

    var panel2 = new movePanel(5, 0, 'vertical', 37, imgList.length, 5);
    panel2.register(function (p) {
        if (p.getCanLast())
            document.getElementById('btnLast2').disabled = false;
        else
            document.getElementById('btnLast2').disabled = true;
    });
    panel2.register(function (p) {
        if (p.getCanNext())
            document.getElementById('btnNext2').disabled = false;
        else
            document.getElementById('btnNext2').disabled = true;
    });
    panel2.show(function (index) {
        var img = document.createElement('IMG');
        img.src = imgList[index].src;
        img.title = imgList[index].title;
        document.getElementById('imgs2').appendChild(img);
    });

    document.getElementById('btnLast2').onclick = function () {
        panel2.last(200, function (x, y) {
            document.getElementById('imgs2').style.top = y + 'px';
            document.getElementById('btnNext2').disabled = true;
            document.getElementById('btnLast2').disabled = true;
        }, function (x, y) {
            document.getElementById('imgs2').style.top = y + 'px';
            document.getElementById('btnNext2').disabled = false;
            document.getElementById('btnLast2').disabled = false;
        });
    };

    document.getElementById('btnNext2').onclick = function () {
        panel2.next(200, function (x, y) {
            document.getElementById('imgs2').style.top = y + 'px';
            document.getElementById('btnNext2').disabled = true;
            document.getElementById('btnLast2').disabled = true;
        }, function (x, y) {
            document.getElementById('imgs2').style.top = y + 'px';
            document.getElementById('btnNext2').disabled = false;
            document.getElementById('btnLast2').disabled = false;
        });
    }; 

/*sample 2 end*/

</script>

结论:

“做什么”,根据需求,建立问题域,分析相应的职责与协作,这其实是面向对象分析的范畴(问题域其实与具体实现语言的关联性并不大)。

“怎么做”,根据建立的问题域,利用一些设计手法,尽可能无依赖(第三方库/框架/浏览器特性/页面元素等)的实现相应的类或函数,将易变的部分解耦(javascript的编程既可使用面向对象的技术来解依赖,如例子中的observer,也有它自己的一些方式,如闭包,回调,表驱动等),并适当考虑更好的复用。这部分属于API设计与实现范畴。

“怎么用”,根据已实现API,进行调用,既依赖于API,也可能依赖于第三方库,包括结合CSS和HTML达到想要的效果,处于易变区域。属于实现与配置范畴。


相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程



Android手机开发(一)
理解Javascript
非典型ajax实践
彻底的Ajax
javascript 使用Cookies
使用 jQuery 简化 Ajax 开发
更多...   


Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术


某航空公司IT部 JavaScript实践
某电视软件 HTML5和JavaScript
中航信 JavaScript高级应用开发
大庆油田 web界面Ajax开发技术
和利时 使用AJAX进行WEB应用开发
更多...