如何写好JavaScript 学习笔记 | 青训营

75 阅读2分钟

原始代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #my-slider{
            position: relative;
            width: 790px;
            height: 400px;
        }
        .slider-list ul{
            list-style-type: none;
            position: relative;
            padding: 0;
            margin: 0;
        }
        .slider-list_item,
        .slider-list_item--selected{
            position: absolute;
            transition: opacity 1s;
            opacity: 0;
            text-align: center;
        }
        .slider-list_item--selected{
            transition: opacity 1s;
            opacity: 1;
        }
    </style>
</head>
<body>
    <div id="my-slider" class="slider-list">
        <ul>
            <li class="slider-list_item--selected">
                <img src="../素材库/ab97fd983f935901892202a7c8358345.jpeg" alt="">
            </li>
            <li class="slider-list_item">
                <img src="../素材库/b2d3e58811b2b0054d440c87e60b3a45.jpeg" alt="">
            </li>
            <li class="slider-list_item">
                <img src="../素材库/cdc625752f4fc2a84c9a94abf9a4685b.jpeg" alt="">
            </li>
            <li class="slider-list_item">
                <img src="../素材库/ab97fd983f935901892202a7c8358345.jpeg" alt="">
            </li>
        </ul>
        <a class="slider-list_next"></a>
        <a class="slider-list_previous"></a>
        <div class="slide-list_control">
            <span class="slide-list_control-buttons--selected"></span>
            <span class="slide-list_control-buttons"></span>
            <span class="slide-list_control-buttons"></span>
            <span class="slide-list_control-buttons"></span>
        </div>
    </div>
    <script>
        class Slider{
            constructor(id,cycle = 3000){
                // 获取容器
                this.container = document.getElementById(id); 
                // 获取所有图片
                this.items = this.container.querySelectorAll('.slider-list_item,.slider-list_item--selected') 
                this.cycle = cycle
                // 获取底部控制器
                const controller = this.container.querySelector('.slide-list_control') 
                if(controller){
                    // 获取所有圆形按钮
                    const buttons = controller.querySelectorAll('.slide-list_control-buttons,.slide-list_control-buttons--selected') 
                    controller.addEventListener('mouseover',e=>{
                        // 获取被选中的按钮索引
                        const idx = Array.from(buttons).indexOf(e.target)
                        if(idx >= 0){
                            this.slideTo(idx)
                            this.stop()
                        }
                    })
                    controller.addEventListener('mouseout',e=>{
                        this.start()
                    })
                    // 绑定自定义slide事件
                    this.container.addEventListener('slide',e=>{
                        const idx = e.detail.index
                        const selected = controller.querySelector('.slide-list_control-buttons--selected')
                        if(selected) selected.className = 'slide-list_control-buttons'
                        buttons[idx].className = 'slide-list_control-buttons--selected'
                    })
                }
                const previous = this.container.querySelector('.slide-list_previous')
                if(previous){
                    previous.addEventListener('click',e=>{
                        this.stop()
                        this.slidePrevious()
                        this.start()
                        e.preventDefault()
                    })
                }
                const next = this.container.querySelector('.slide-list_next')
                if(next){
                    next.addEventListener('click',e=>{
                        this.stop()
                        this.slideNext()
                        this.start()
                        e.preventDefault()
                    })
                }
            }
            getSelectedItem(){
                const selected = this.container.querySelector('.slider-list_item--selected')
                return selected
            }
            getSelectedItemIndex(){
                return Array.from(this.items).indexOf(this.getSelectedItem())
            }
            slideTo(idx){
                const selected = this.getSelectedItem()
                if(selected){
                    selected.className = 'slider-list_item'
                }
                const item = this.items[idx]
                if(item){
                    item.className = 'slider-list_item--selected'
                }
                const detail = {index:idx}
                const event = new CustomEvent('slide',{bubbles:true,detail})
                this.container.dispatchEvent(event)
            }
            slideNext(){
                const currentIdx = this.getSelectedItemIndex()
                const nextIdx = (currentIdx+1)% this.items.length
                this.slideTo(nextIdx)
            }
            slidePrevious(){
                const currentIdx = this.getSelectedItemIndex()
                const previousIdx = (this.items.length + currentIdx - 1)%this.items.length
                this.slideTo(previousIdx)
            }
            start(){
                this.stop()
                this.timer = setInterval(()=>{
                    this.slideNext()
                },this.cycle)
            }
            stop(){
                clearInterval(this.timer)
            }
        }

        const slider = new Slider('my-slider')
        slider.start()
        
    </script>
</body>
</html>

该代码定义一个Slider类,实现轮播图的功能

  1. 在构造函数中给容器,控制器与其按钮,左右箭头绑定事件监听器
  2. 在类中创建各方法
  3. new创建实例对象
  • 代码的缺点:
    1. 可维护性差,如果要修改某个功能,需要同时对html,css,js做相应的修改
    2. 构造函数过于臃肿

第一次改进,插件化

将控制元素抽取成插件,插件与组件之间通过依赖注入的方式建立联系

constructor(id,cycle = 3000){
    this.container = document.getElementById(id);
    this.items = this.container.querySelectorAll('.slider-list_item,.slider-list_item--selected')
    this.cycle = cycle
}
registerPlugins(...plugins){
    // 将this注入
    plugins.forEach(plugin => plugin(this))
}
function pluginController(slider){
            const controller = slider.container.querySelector('.slide-list_control')
            if(controller){
                const buttons = controller.querySelectorAll('.slide-list_control-buttons,.slide-list_control-buttons--selected')
                controller.addEventListener('mouseover',e=>{
                    const idx = Array.from(buttons).indexOf(e.target)
                    if(idx >= 0){
                        slider.slideTo(idx)
                        slider.stop()
                    }
                })
                controller.addEventListener('mouseout',e=>{
                    slider.start()
                })

                slider.container.addEventListener('slide',e=>{
                    const idx = e.detail.index
                    const selected = controller.querySelector('.slide-list_control-buttons--selected')
                    if(selected) selected.className = 'slide-list_control-buttons'
                    buttons[idx].className = 'slide-list_control-buttons--selected'
                })
            }
        }

        function pluginPrevious(slider){
            const previous = slider.container.querySelector('.slide-list_previous')
            if(previous){
                previous.addEventListener('click',e=>{
                    slider.stop()
                    slider.slidePrevious()
                    slider.start()
                    e.preventDefault()
                })
            }
        }

        function pluginNext(slider){
            const next = slider.container.querySelector('.slide-list_next')
            if(next){
                next.addEventListener('click',e=>{
                    slider.stop()
                    slider.slideNext()
                    slider.start()
                    e.preventDefault()
                })
            }
        }          

将轮播器和底部控制器,左右箭头,抽取成插件,使用注册插件依赖注入的形式控制

slider.registerPlugins(pluginController,pluginPrevious,pluginNext)
  • 优点: 构造函数变得简洁,不需要某个功能时,只需要把那个插件取消注册即可

  • 仍有缺点: 对于html部分仍需要去页面中删除

第二次改进,模板化

html

<div id="my-slider" class="slider-list"></div>

js

 constructor(id,opts= {images:[],cycle :3000}){
                this.container = document.getElementById(id);
                this.options = opts
                this.container.innerHTML = this.render()
                this.items = this.container.querySelectorAll('.slider-list_item,.slider-list_item--selected')
                this.cycle = cycle
                this.slideTo(0)
            }
            render(){
                const images = this.options.images
                const content = images.map(image =>
                ` <li class="slider-list_item--selected">
                    <img width="790" height="400" src="${image}" alt="">
                </li>`.trim()
                )
                return `<ul>${content.join('')}</ul>`
            }
            registerPlugins(...plugins){
                // 将this注入
                plugins.forEach(plugin => {
                    const pluginContainer = document.createElement('div')
                    pluginContainer.className = '.slider-list_plugin'
                    pluginContainer.innerHTML = plugin.render(this.options.images)
                    this.container.appendChild(pluginContainer)
                    plugin.action(this)
                } )
            }
            const pluginController={
            render(images){
                return
                `
                <div class="slide-list_control">
                    ${images.map((image,i)=>
                        `
                        <span class="slide-list_control-buttons${i===0?'--selected':''}"></span>
                        `
                        ).join('')}
                </div>
                `.trim()
            },
            action(Slider){
                const controller = slider.container.querySelector('.slide-list_control')
                if(controller){
                    const buttons = controller.querySelectorAll('.slide-list_control-buttons,.slide-list_control-buttons--selected')
                    controller.addEventListener('mouseover',e=>{
                        const idx = Array.from(buttons).indexOf(e.target)
                        if(idx >= 0){
                            slider.slideTo(idx)
                            slider.stop()
                        }
                    })
                    controller.addEventListener('mouseout',e=>{
                        slider.start()
                    })

                    slider.container.addEventListener('slide',e=>{
                        const idx = e.detail.index
                        const selected = controller.querySelector('.slide-list_control-buttons--selected')
                        if(selected) selected.className = 'slide-list_control-buttons'
                        buttons[idx].className = 'slide-list_control-buttons--selected'
                    })
                }
            }
           
        }

        const pluginPrevious={
            render(){
                return `<a class="slider-list_previous"></a>`
            },
            action(slider){
                const previous = slider.container.querySelector('.slide-list_previous')
                if(previous){
                    previous.addEventListener('click',e=>{
                        slider.stop()
                        slider.slidePrevious()
                        slider.start()
                        e.preventDefault()
                    })
                }
            }
        }

        const pluginNext={
            render(){
                return `<a class="slider-list_next"></a>`
            },
            action(slider){
                const next = slider.container.querySelector('.slide-list_next')
                if(next){
                    next.addEventListener('click',e=>{
                        slider.stop()
                        slider.slideNext()
                        slider.start()
                        e.preventDefault()
                    })
                }
            }
        }
        const slider = new Slider('my-slider',{
            // 图片库由此导入
            images:[]
            ,cycle:3000
        }

通过将html模板化,使得HTML代码优化仅剩一行,而剩余的html代码由各插件模板创建

  • 优点: 将html模板化,更易于扩展

最终封装,组件框架

class Component{
            constructor(id,opts = {className,images:[]}){
                this.container = document.getElementById(id)
                this.options = opts
                this.container.innerHTML = this.render(opts.images)
            }
            registerPlugins(...plugins){
                // 将this注入
                plugins.forEach(plugin => {
                    const pluginContainer = document.createElement('div')
                    pluginContainer.className = className
                    pluginContainer.innerHTML = plugin.render(this.options.images)
                    this.container.appendChild(pluginContainer)
                    plugin.action(this)
                } )
            }
            // 类似虚函数
            render(images){
                return ''
            }
        }
class Slider extends Component{
            constructor(id,opts= {className:'slider-list_plugin',images:[],cycle :3000}){
                // super 调用父类的构造方法
                super(id,opts)
                this.items = this.container.querySelectorAll('.slider-list_item,.slider-list_item--selected')
                this.cycle = cycle
                this.slideTo(0)
            }
            // 重写render方法
            render(images){
                const content = images.map(image =>
                ` <li class="slider-list_item--selected">
                    <img width="790" height="400" src="${image}" alt="">
                </li>`.trim()
                )
                return `<ul>${content.join('')}</ul>`
            }
      }
  1. 创建模板类,包含构造函数,注册插件方法,渲染方法

  2. 创建slider类继承模板类,在构造函数中调用父类的构造方法,初始化自己的变量,并重写父类的render方法