【青训营】-JS学习笔记

写好JS的原则

各司其职

  • html负责结构
  • css负责表现
  • JavaScript负责行为

举个例子:

我们想要实现通过点击太阳或者月亮来更改页面的模式

image-20210819082342827

方案一:

我们可以捕获这个图标的点击事件,点击后修改字体颜色与背景颜色。

image-20210819082539578

但这样就将js与css耦合了

方案二:

我们将js直接修改样式,改成了js修改元素的类,样式的转变就交给了css来进行。

image-20210819082856772

方案三:

不使用js的纯css方案

这里使用input复选框加label的形式,完成状态的捕获

当我们点击label时,对应的复选框也会被点击

再通过复选框的选中状态伪类就能实现模式的切换

image-20210819083120859

无论是第二种或第三种方案都比第一种方案好。

结论:

  • HTML/CSS/JS 各司其责
  • 应当尽量避免不必要的由JS直接操作样式
  • 可以用class来表示状态
  • 纯展示类交互寻求零js方案

组件封装

组件是指web页面上抽出来一个个包含模板(HTML),功能(JS),***样式(CSS)***的单元。

组件设计原则:

  • 封装性
  • 正确性
  • 扩展性
  • 复用性。

封装组件的时候不要一下就想着把组件做好。

我们可以从简单的功能入手,然后一步一步迭代,优化

组件封装可以从一下几个步骤着手:

组件设计步骤

  • 结构设计

  • 展现效果

  • 行为设计

    • API(功能)
    • Event(控制流)

下面以创建轮播图组件示例讲解组件封装过程。

结构设计

结构设计是第一步,我们要将需要展示的所有元素再html文档种体现出来

对于轮播图组件来说,就是一个容器,包含一组列表,列表中放入图片

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="slider.css">
</head>

<body>
    <div id="my-slider" class="slider-list">
        <ul>
            <li class="slider-list__item--selected">
                <img src="https://img12.360buyimg.com/pop/s590x470_jfs/t1/200666/3/2407/80779/611cbdbfE67561765/802cf07557ad00c6.jpg.webp" alt="">
            </li>
            <li class="slider-list__item">
                <img src="https://imgcps.jd.com/ling4/100009077475/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-5f3a47329785549f6bc7a6e0/0f1863cd/cr/s/q.jpg" alt="">
            </li>
            <li class="slider-list__item">
                <img src="https://img13.360buyimg.com/pop/s590x470_jfs/t1/204538/27/418/100695/6110eb81E40c33891/98a22a2cf9021e4e.jpg.webp" alt="">
            </li>
            <li class="slider-list__item">
                <img src="https://img10.360buyimg.com/pop/s590x470_jfs/t1/184419/8/18902/98852/6114bd1bEa22d6dbb/1cfa09c57dbf3817.jpg.webp" alt="">
            </li>
        </ul>
    </div>
</body>
<script src="slider.js"></script>

</html>
复制代码

展示效果

将结构设计好之后,我们再浏览器看到的就是一堆元素的排列。image-20210819103050265

需要我们再根据设计需求,对不同元素进行样式调整。包括位置,尺寸,颜色等等

对于轮播图来说,我们要把图片设置为绝对定位,不显示的透明度为0,显示的透明度为1

#my-slider {
    position: relative;
    width: 590px;
}

.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;
}
复制代码

image-20210819103424916

加入CSS后,所有的图片都集中起来了,已经有轮播图的感觉了,下面就是要控制显示哪张图片

行为设计

设计组件的行为,我们需要根据组件的功能来定。

封装时提供各种功能的api接口。

API(功能)

api的设计应保证原子操作,职责单一,满足灵活性。

对于轮播图来说,我们需要能够控制跳转到指定图片,跳转到前一个,跳转到后一个

就做了一下的封装设计

image-20210819104030726

class Slider {
    constructor(id) {
        this.container = document.getElementById(id);
        this.items = this.container.querySelectorAll(".slider-list__item,.slider-list__item--selected");
    }
    getSelectedItem() {
        const select = this.container.querySelector(".slider-list__item--selected");
        return select;
    }
    getSelectedItemIndex() {
        return Array.from(this.items).indexOf(this.getSelectedItem());
    }
    sliderTo(index) {
        const select = this.getSelectedItem();
        if (select) {
            select.className = "slider-list__item";
        }
        const item = this.items[index];
        if (item) {
            this.items[index].className = "slider-list__item--selected";
        }
    }
    sliderNext() {
        const index = this.getSelectedItemIndex();
        const nextIndex = (index + 1) % this.items.length;
        this.sliderTo(nextIndex);
    }
    sliderPrevious() {
        const index = this.getSelectedItemIndex();
        const previousIndex = (index + this.items.length - 1) % this.items.length;
        this.sliderTo(previousIndex);
    }
}
复制代码

Event(控制流)

做好前面的部分我们已经把轮播图的控制对象做好了

那要想真的实现轮播图的效果,我们还需要设计控制流

对于轮播图,我们主要设计三个控制功能

  1. 定时翻页
  2. 左右点击翻页
  3. 下方控制点翻页

image-20210819125622921

有了这个控制思路,先把控制用到的元素写入html文档中

<a class="slider-list__previous">&lt;</a>
<a class="slider-list__next">&gt;</a>
<div class="slider-list__control">
    <span class="slider-list__control-buttons--selected"></span>
    <span class="slider-list__control-buttons"></span>
    <span class="slider-list__control-buttons"></span>
    <span class="slider-list__control-buttons"></span>
</div>
复制代码

在给元素设置对应的样式

.slider-list__next,
.slider-list__previous {
    position: absolute;
    top: 200px;
    line-height: 40px;
    padding: 0 5px 5px 5px;
    background-color: black;
    opacity: 0;
    font-size: 30px;
    color: white;
}

.slider-list__next:hover,
.slider-list__previous:hover {
    opacity: 0.7;
    transition: opacity 0.5s;
}

.slider-list__next {
    right: 0;
}

.slider-list__control {
    position: absolute;
    display: flex;
    flex-direction: row;
    justify-content: space-evenly;
    align-items: center;
    bottom: 40px;
    left: 260px;
    height: 20px;
    width: 70px;
    background-color: rgba(185, 183, 183, 0.911);
    border-radius: 10px;
}

.slider-list__control-buttons,
.slider-list__control-buttons--selected {
    height: 10px;
    width: 10px;
    background-color: white;
    border-radius: 5px;
}

.slider-list__control-buttons--selected {
    background-color: red;
}
复制代码

有了这些呢,下面就是利用js驱动元素类的转变

对于定时翻页,其实就是设置一个定时器,定时执行翻页的方法。但是由于在控制过程中可能会使用左右翻页或者控制点翻页,所以我们不能让他们互相冲突。需要给定时翻页设置启停,在左右翻或者控制点翻的时候我们要先停,翻完之后再启动。

我们给Slider类添加了两个方法

start() {
    this.stop();
    this._timer = setInterval(() => {
        this.sliderNext();
    }, this.cycle)
}
stop() {
    clearInterval(this._timer);
}
复制代码

对于对于左右翻页比较好说,我们直接在Slider类初始化时,添加点击的监听事件,点击后先关闭定时器,翻页结束再开启定时器。

// 向前翻
const previous = this.container.querySelector(".slider-list__previous");
if (previous) {
    previous.addEventListener('click', evt => {
        this.stop();
        this.sliderPrevious();
        this.start();
        evt.preventDefault();
    })
}
// 向后翻
const next = this.container.querySelector(".slider-list__next");
if (next) {
    next.addEventListener('click', evt => {
        this.stop();
        this.sliderNext();
        this.start();
        evt.preventDefault();
    })
}
复制代码

对于控制点翻页,我们的控制点需要时刻与显示的当前图片进行对应,就涉及到当前控制点与当前图片的耦合。为了解耦,我们自己设计一个自定义事件。

当执行翻页方法后,会触发事件。

const detail = { index: index }
const event = new CustomEvent("slide", { bubbles: true, detail })
this.container.dispatchEvent(event);
复制代码

再设置事件的监听,当监听到翻页事件后,我们修改当前控制点。

this.container.addEventListener('slide', evt => {
    const idx = evt.detail.index;
    const selected = controller.querySelector('.slider-list__control-buttons--selected');
    if (selected) {
        selected.className = "slider-list__control-buttons";
    }
    buttons[idx].className = "slider-list__control-buttons--selected";
})
复制代码

做完这些,就基本完成了整个组件的封装

这里贴出完整代码

html部分

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="slider.css">
</head>

<body>
    <div id="my-slider" class="slider-list">
        <ul>
            <li class="slider-list__item--selected">
                <img src="https://img12.360buyimg.com/pop/s590x470_jfs/t1/200666/3/2407/80779/611cbdbfE67561765/802cf07557ad00c6.jpg.webp" alt="">
            </li>
            <li class="slider-list__item">
                <img src="https://imgcps.jd.com/ling4/100009077475/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-5f3a47329785549f6bc7a6e0/0f1863cd/cr/s/q.jpg" alt="">
            </li>
            <li class="slider-list__item">
                <img src="https://img13.360buyimg.com/pop/s590x470_jfs/t1/204538/27/418/100695/6110eb81E40c33891/98a22a2cf9021e4e.jpg.webp" alt="">
            </li>
            <li class="slider-list__item">
                <img src="https://img10.360buyimg.com/pop/s590x470_jfs/t1/184419/8/18902/98852/6114bd1bEa22d6dbb/1cfa09c57dbf3817.jpg.webp" alt="">
            </li>
        </ul>
        <a class="slider-list__previous">&lt;</a>
        <a class="slider-list__next">&gt;</a>
        <div class="slider-list__control">
            <span class="slider-list__control-buttons--selected"></span>
            <span class="slider-list__control-buttons"></span>
            <span class="slider-list__control-buttons"></span>
            <span class="slider-list__control-buttons"></span>
        </div>
    </div>
</body>
<script src="slider.js"></script>

</html>
复制代码

css部分

#my-slider {
    position: relative;
    width: 590px;
    height: 474px;
}

.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;
}

.slider-list__next,
.slider-list__previous {
    position: absolute;
    top: 200px;
    line-height: 40px;
    padding: 0 5px 5px 5px;
    background-color: black;
    opacity: 0;
    font-size: 30px;
    color: white;
}

.slider-list__next:hover,
.slider-list__previous:hover {
    opacity: 0.7;
    transition: opacity 0.5s;
}

.slider-list__next {
    right: 0;
}

.slider-list__control {
    position: absolute;
    display: flex;
    flex-direction: row;
    justify-content: space-evenly;
    align-items: center;
    bottom: 40px;
    left: 260px;
    height: 20px;
    width: 70px;
    background-color: rgba(185, 183, 183, 0.911);
    border-radius: 10px;
}

.slider-list__control-buttons,
.slider-list__control-buttons--selected {
    height: 10px;
    width: 10px;
    background-color: white;
    border-radius: 5px;
}

.slider-list__control-buttons--selected {
    background-color: red;
}
复制代码

js部分

class Slider {
    constructor(id, cycle = 3000) {
        this.cycle = cycle;
        this.container = document.getElementById(id);
        this.items = this.container.querySelectorAll(".slider-list__item,.slider-list__item--selected");

        // 向前翻
        const previous = this.container.querySelector(".slider-list__previous");
        if (previous) {
            previous.addEventListener('click', evt => {
                this.stop();
                this.sliderPrevious();
                this.start();
                evt.preventDefault();
            })
        }
        // 向后翻
        const next = this.container.querySelector(".slider-list__next");
        if (next) {
            next.addEventListener('click', evt => {
                this.stop();
                this.sliderNext();
                this.start();
                evt.preventDefault();
            })
        }
        // 控制点
        const controller = this.container.querySelector(".slider-list__control");
        if (controller) {
            const buttons = controller.querySelectorAll('.slider-list__control-buttons,.slider-list__control-buttons--selected');
            controller.addEventListener("mouseover", evt => {
                const idx = Array.from(buttons).indexOf(evt.target);
                if (idx >= 0) {
                    this.sliderTo(idx);
                    this.stop();
                }
            })
            controller.addEventListener('mouseout', evt => {
                this.start();
            })
            this.container.addEventListener('slide', evt => {
                const idx = evt.detail.index;
                const selected = controller.querySelector('.slider-list__control-buttons--selected');
                if (selected) {
                    selected.className = "slider-list__control-buttons";
                }
                buttons[idx].className = "slider-list__control-buttons--selected";
            })
        }
    }
    getSelectedItem() {
        const select = this.container.querySelector(".slider-list__item--selected");
        return select;
    }
    getSelectedItemIndex() {
        return Array.from(this.items).indexOf(this.getSelectedItem());
    }
    sliderTo(index) {
        const select = this.getSelectedItem();
        if (select) {
            select.className = "slider-list__item";
        }
        const item = this.items[index];
        if (item) {
            this.items[index].className = "slider-list__item--selected";
        }
        const detail = { index: index }
        const event = new CustomEvent("slide", { bubbles: true, detail })
        this.container.dispatchEvent(event);
    }
    sliderNext() {
        const index = this.getSelectedItemIndex();
        const nextIndex = (index + 1) % this.items.length;
        this.sliderTo(nextIndex);
    }
    sliderPrevious() {
        const index = this.getSelectedItemIndex();
        const previousIndex = (index + this.items.length - 1) % this.items.length;
        this.sliderTo(previousIndex);
    }
    start() {
        this.stop();
        this._timer = setInterval(() => {
            this.sliderNext();
        }, this.cycle)
    }
    stop() {
        clearInterval(this._timer);
    }
}
const slider = new Slider("my-slider", 2000);
slider.start();
复制代码

重构一:插件化

前面虽然已经实现了我们想要的所有功能,但是我们可以看出再构造函数里有很多代码,这些代码除了一些Slider的初始化之外还有一些控制元素的初始化。

这样控制元素与我们整个封装的耦合度太高,不利于后期与其他控制按钮的组合使用了。

所以我们要进行解耦

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

我们首先在Slider类中增加依赖注入的方法

registerPlugins(...plugins) {
    plugins.forEach(plugin => plugin(this));
}
复制代码

然后在类外三个插件

function pluginPrevious(slider) {
    // 向前翻
    const previous = slider.container.querySelector(".slider-list__previous");
    if (previous) {
        previous.addEventListener('click', evt => {
            slider.stop();
            slider.sliderPrevious();
            slider.start();
            evt.preventDefault();
        })
    }
}

function pluginNext(slider) {
    // 向后翻
    const next = slider.container.querySelector(".slider-list__next");
    if (next) {
        next.addEventListener('click', evt => {
            slider.stop();
            slider.sliderNext();
            slider.start();
            evt.preventDefault();
        })
    }
}

function pluginController(slider) {
    // 控制点
    const controller = slider.container.querySelector(".slider-list__control");
    if (controller) {
        const buttons = controller.querySelectorAll('.slider-list__control-buttons,.slider-list__control-buttons--selected');
        controller.addEventListener("mouseover", evt => {
            const idx = Array.from(buttons).indexOf(evt.target);
            if (idx >= 0) {
                slider.sliderTo(idx);
                slider.stop();
            }
        })
        controller.addEventListener('mouseout', evt => {
            slider.start();
        })
        slider.container.addEventListener('slide', evt => {
            const idx = evt.detail.index;
            const selected = controller.querySelector('.slider-list__control-buttons--selected');
            if (selected) {
                selected.className = "slider-list__control-buttons";
            }
            buttons[idx].className = "slider-list__control-buttons--selected";
        })
    }
}
复制代码

其实也很好理解,只是原本这三个函数是在Slider类的构造函数中执行的,依赖于Sliderthis。只不过我们将这三个函数单独抽离出来,然后利用Slider的注入插件的方法registerPluginsSliderthis通过参数的方式传给他们。

const slider = new Slider("my-slider", 2000);
slider.registerPlugins(/*pluginController,*/ pluginPrevious, pluginNext)
slider.start();
复制代码

这样我们如果有哪个功能不想用了,只需要将它注释掉,如果想要新加一个按钮来控制,也只用写成插件的方式,然后将插件注册进slider

重构二:模板化

经过刚刚的插件化之后,我们其实将控制程序与Slider类进行了解耦。

假如我们想要不使用控制点进行控制的话,我们仅需要在注册插件的地方注释掉注册

const slider = new Slider("my-slider", 2000);
slider.registerPlugins(/*pluginController,*/ pluginPrevious, pluginNext);
slider.start();
复制代码

这样我们控制点的方法就失效了,但是我们会发现,在页面上还是会存在控制点。而且控制点不会随着图片的更换而改变。

所以我们要将html的代码放到js中,让js来渲染结构。

image-20210819163349466

这样我们的html就只剩下一行代码

<div id="my-slider" class="slider-list"></div>
复制代码

我们的css不用变动

js

class Slider {
    constructor(id, opts = { images: [], cycle: 3000 }) {
        this.options = opts;
        this.container = document.getElementById(id);
        this.container.innerHTML = this.render();
        this.items = this.container.querySelectorAll(".slider-list__item,.slider-list__item--selected");
        this.cycle = opts.cycle || 3000;
        this.sliderTo(0);
    }
    render() {
        const images = this.options.images;
        const content = images.map(image => `
            <li class="slider-list__item">
                <img src="https://juejin.cn/post/${image}">
            </li>
        `.trim())
        return `
        <ul>${content.join("")}</ul>
        `
    }
    registerPlugins(...plugins) {
        plugins.forEach(plugin => {
            const pluginContainer = document.createElement('div');
            pluginContainer.className = "slider-list__plugin";
            pluginContainer.innerHTML = plugin.render(this);
            this.container.appendChild(pluginContainer);
            plugin.action(this)
        });
    }
    getSelectedItem() {
        const select = this.container.querySelector(".slider-list__item--selected");
        return select;
    }
    getSelectedItemIndex() {
        return Array.from(this.items).indexOf(this.getSelectedItem());
    }
    sliderTo(index) {
        const select = this.getSelectedItem();
        if (select) {
            select.className = "slider-list__item";
        }
        const item = this.items[index];
        if (item) {
            this.items[index].className = "slider-list__item--selected";
        }
        const detail = { index: index }
        const event = new CustomEvent("slide", { bubbles: true, detail })
        this.container.dispatchEvent(event);
    }
    sliderNext() {
        const index = this.getSelectedItemIndex();
        const nextIndex = (index + 1) % this.items.length;
        this.sliderTo(nextIndex);
    }
    sliderPrevious() {
        const index = this.getSelectedItemIndex();
        const previousIndex = (index + this.items.length - 1) % this.items.length;
        this.sliderTo(previousIndex);
    }
    start() {
        this.stop();
        this._timer = setInterval(() => {
            this.sliderNext();
        }, this.cycle)
    }
    stop() {
        clearInterval(this._timer);
    }
}

const pluginPrevious = {
    render() {
        return `<a class="slider-list__previous">&lt;</a>`;
    },
    action(slider) {
        // 向前翻
        const previous = slider.container.querySelector(".slider-list__previous");
        if (previous) {
            previous.addEventListener('click', evt => {
                slider.stop();
                slider.sliderPrevious();
                slider.start();
                evt.preventDefault();
            })
        }
    }
}

const pluginNext = {
    render() {
        return `<a class="slider-list__next">&gt;</a>`;
    },
    action(slider) {
        // 向后翻
        const next = slider.container.querySelector(".slider-list__next");
        if (next) {
            next.addEventListener('click', evt => {
                slider.stop();
                slider.sliderNext();
                slider.start();
                evt.preventDefault();
            })
        }
    }
}

const pluginController = {
    render(slider) {
        const controller = slider.options.images.map((image, index) => {
            if (slider.getSelectedItemIndex() == index) {
                return `
                        <span class="slider-list__control-buttons--selected"></span>
                    `.trim();
            } else {
                return `
                        <span class="slider-list__control-buttons"></span>
                    `.trim();
            }
        })
        return `<div class="slider-list__control">${controller.join("")}</div>`;
    },
    action(slider) {
        // 控制点
        const controller = slider.container.querySelector(".slider-list__control");
        if (controller) {
            const buttons = controller.querySelectorAll('.slider-list__control-buttons,.slider-list__control-buttons--selected');
            controller.addEventListener("mouseover", evt => {
                const idx = Array.from(buttons).indexOf(evt.target);
                if (idx >= 0) {
                    slider.sliderTo(idx);
                    slider.stop();
                }
            })
            controller.addEventListener('mouseout', evt => {
                slider.start();
            })
            slider.container.addEventListener('slide', evt => {
                const idx = evt.detail.index;
                const selected = controller.querySelector('.slider-list__control-buttons--selected');
                if (selected) {
                    selected.className = "slider-list__control-buttons";
                }
                buttons[idx].className = "slider-list__control-buttons--selected";
            })
        }
    }
}
const images = [
    "https://img12.360buyimg.com/pop/s590x470_jfs/t1/200666/3/2407/80779/611cbdbfE67561765/802cf07557ad00c6.jpg.webp",
    "https://imgcps.jd.com/ling4/100009077475/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-5f3a47329785549f6bc7a6e0/0f1863cd/cr/s/q.jpg",
    "https://img13.360buyimg.com/pop/s590x470_jfs/t1/204538/27/418/100695/6110eb81E40c33891/98a22a2cf9021e4e.jpg.webp",
    "https://img10.360buyimg.com/pop/s590x470_jfs/t1/184419/8/18902/98852/6114bd1bEa22d6dbb/1cfa09c57dbf3817.jpg.webp"
]
const slider = new Slider("my-slider", { images, cycle: 2000 });
slider.registerPlugins(pluginController, pluginPrevious, pluginNext)
slider.start();
复制代码

重构三:组件框架

到目前为止,如果说从轮播器的角度来说已经实现的很好了

但是如果我们在设计网站的时候肯定不止用到一个组件,所以我们可以抽象出一个更加通用的组件模型。用这个组件模型去创建更多的组件。

image-20210819193443054

这样我们就可以写出一个抽象的组件类了

class Component {
    constructor(id, opts = { name, data: [] }) {
        this.container = document.getElementById(id);
        this.options = opts;
        this.container.innerHTML = this.render(opts.data);
    }
    registerPlugins(...plugins) {
        plugins.forEach(plugin => {
            const pluginContainer = document.createElement('div');
            pluginContainer.className = `${this.name}__plugin`;
            pluginContainer.innerHTML = plugin.render(this);
            this.container.appendChild(pluginContainer);
            plugin.action(this)
        });
    }
    render(component) {
        // 抽象
        return "";
    }
}
复制代码

利用这个抽象的组件类来设计Slider

class Slider extends Component {
    constructor(id, opts = { name: "slider-list", data: [], cycle: 3000 }) {
        super(id, opts);
        this.items = this.container.querySelectorAll(".slider-list__item,.slider-list__item--selected");
        this.cycle = opts.cycle || 3000;
        this.sliderTo(0);
    }
    render(data) {
        const content = data.map(image => `
            <li class="slider-list__item">
                <img src="https://juejin.cn/post/${image}">
            </li>
        `.trim())
        return `
        <ul>${content.join("")}</ul>
        `
    }
    getSelectedItem() {
        const select = this.container.querySelector(".slider-list__item--selected");
        return select;
    }
    getSelectedItemIndex() {
        return Array.from(this.items).indexOf(this.getSelectedItem());
    }
    sliderTo(index) {
        const select = this.getSelectedItem();
        if (select) {
            select.className = "slider-list__item";
        }
        const item = this.items[index];
        if (item) {
            this.items[index].className = "slider-list__item--selected";
        }
        const detail = { index: index }
        const event = new CustomEvent("slide", { bubbles: true, detail })
        this.container.dispatchEvent(event);
    }
    sliderNext() {
        const index = this.getSelectedItemIndex();
        const nextIndex = (index + 1) % this.items.length;
        this.sliderTo(nextIndex);
    }
    sliderPrevious() {
        const index = this.getSelectedItemIndex();
        const previousIndex = (index + this.items.length - 1) % this.items.length;
        this.sliderTo(previousIndex);
    }
    start() {
        this.stop();
        this._timer = setInterval(() => {
            this.sliderNext();
        }, this.cycle)
    }
    stop() {
        clearInterval(this._timer);
    }
}

const pluginPrevious = {
    render() {
        return `<a class="slider-list__previous">&lt;</a>`;
    },
    action(slider) {
        // 向前翻
        const previous = slider.container.querySelector(".slider-list__previous");
        if (previous) {
            previous.addEventListener('click', evt => {
                slider.stop();
                slider.sliderPrevious();
                slider.start();
                evt.preventDefault();
            })
        }
    }
}

const pluginNext = {
    render() {
        return `<a class="slider-list__next">&gt;</a>`;
    },
    action(slider) {
        // 向后翻
        const next = slider.container.querySelector(".slider-list__next");
        if (next) {
            next.addEventListener('click', evt => {
                slider.stop();
                slider.sliderNext();
                slider.start();
                evt.preventDefault();
            })
        }
    }
}

const pluginController = {
    render(slider) {
        const controller = slider.options.data.map((image, index) => {
            if (slider.getSelectedItemIndex() == index) {
                return `
                        <span class="slider-list__control-buttons--selected"></span>
                    `.trim();
            } else {
                return `
                        <span class="slider-list__control-buttons"></span>
                    `.trim();
            }
        })
        return `<div class="slider-list__control">${controller.join("")}</div>`;
    },
    action(slider) {

        // 控制点
        const controller = slider.container.querySelector(".slider-list__control");
        if (controller) {
            const buttons = controller.querySelectorAll('.slider-list__control-buttons,.slider-list__control-buttons--selected');
            controller.addEventListener("mouseover", evt => {
                const idx = Array.from(buttons).indexOf(evt.target);
                if (idx >= 0) {
                    slider.sliderTo(idx);
                    slider.stop();
                }
            })
            controller.addEventListener('mouseout', evt => {
                slider.start();
            })
            slider.container.addEventListener('slide', evt => {
                const idx = evt.detail.index;
                const selected = controller.querySelector('.slider-list__control-buttons--selected');
                if (selected) {
                    selected.className = "slider-list__control-buttons";
                }
                buttons[idx].className = "slider-list__control-buttons--selected";
            })
        }
    }
}
const images = [
    "https://img12.360buyimg.com/pop/s590x470_jfs/t1/200666/3/2407/80779/611cbdbfE67561765/802cf07557ad00c6.jpg.webp",
    "https://imgcps.jd.com/ling4/100009077475/5Lqs6YCJ5aW96LSn/5L2g5YC85b6X5oul5pyJ/p-5f3a47329785549f6bc7a6e0/0f1863cd/cr/s/q.jpg",
    "https://img13.360buyimg.com/pop/s590x470_jfs/t1/204538/27/418/100695/6110eb81E40c33891/98a22a2cf9021e4e.jpg.webp",
    "https://img10.360buyimg.com/pop/s590x470_jfs/t1/184419/8/18902/98852/6114bd1bEa22d6dbb/1cfa09c57dbf3817.jpg.webp"
]
const slider = new Slider("my-slider", { data: images, cycle: 2000 });
slider.registerPlugins(pluginController, pluginPrevious, pluginNext)
slider.start();
复制代码

涉及的代码可在03.js学习 · Craipy/青训营笔记代码 – 码…中查看

过程抽象

  • 用来处理局部细节控制的方法
  • 函数式编程思想的基础应用

我们把函数本身看成是控制器,我们关心控制器的输入和输出

image-20210819092459115

通过例子来理解一下:

操作次数限制

假如我们做这样一个例子。一个任务列表,我们点击后代表任务完成,然后任务会渐隐的消失,等消失之后,在将这个元素从list列表中删除,我们很轻易就写出了右端的这个代码。

image-20210820063637560image-20210820063615707

这个代码一般运行时没有问题的,但是如果连续点击两次同一个任务按钮,就会报错。

image-20210820064220194

因为创建了两个异步任务,第一个异步任务把元素删除了,第二个异步任务就会报错了。

为了能够让只执行一次的需求覆盖到不同事件处理,我们可以将这个需求剥离出来,这个过程我们称为过程抽象

我们刚刚所讲的 各司其职组件封装 其实大多都是数据抽象,将数据抽象出来,抽象出一个对象,然后把对象传给我们的插件。

实际上 过程抽象 我们可以把一个动作函数 抽象出来然后给更多的任务去使用。

举个例子:

我们有一个人,有一个门,人要开门进房间。

如果我们把人和们抽象出来,这就是数据抽象

其实我们也可以把开门这个动作抽象出来,这就是过程抽象

再看这个,只执行一次的需求,我们 可以写一个高阶函数。来实现就是说,每个函数执行一次之后就被清空了。那这样就解决了执行一次的问题,而且也具有通用性。

image-20210820065915579

高阶函数

  • 以函数为参数
  • 以函数作为返回值
  • 常用于作为函数装饰器

高阶函数的基础范式 –等价范式

image-20210820070413700

常用的高阶函数

一般情况,我们碰到这种问题,没有想到要使用高阶函数。而是想到的是当前这个函数中怎么解决这些问题。

只考虑当前函数的话,这样我们就可能会定义一下全局的标志位。这样虽然能够实现,但是可能以后,另一个函数也要有这样的功能的时候,我们还要用同样的方法,同样的思想在写一遍。

image-20210820080825207

但是当我们将函数看成一个整体,这个整体不变动的情况下,来解决这个问题,这时候我们可以写另外一个函数来调用原函数

image-20210820080913222

像这样,如果只是普通的调用,那么扩展性并不强。如果将函数作为形参进行调用,那么我们可以将这种操作扩展到所有函数。

image-20210820081431652

编程范式

总结上面的结论,我们可以把编程总结为两个范式

  • 命令式
  • 声明式

image-20210820082956227

命令式更偏向于怎么做

声明式更偏向于做什么

用一个例子来看

toggle–命令式

toggle–声明式

toggle–三态

写代码应该关注什么

  • 风格
  • 效率
  • 约定
  • 使用场景
  • 设计
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享