<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Vue</title>
	<atom:link href="https://www.hu365.dev/blog/tag/vue/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.hu365.dev</link>
	<description>Justin Woo（吴晓虎）的个人博客，生命不息，学习不止</description>
	<lastBuildDate>Sun, 18 Feb 2024 01:34:56 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.4.3</generator>

<image>
	<url>https://www.hu365.dev/wp-content/uploads/2022/10/cropped-logo-正矩形.blue_-32x32.png</url>
	<title>Vue</title>
	<link>https://www.hu365.dev</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>clipboard.js 与 Vue</title>
		<link>https://www.hu365.dev/blog/2022/08/134/</link>
		
		<dc:creator><![CDATA[Justin]]></dc:creator>
		<pubDate>Sun, 21 Aug 2022 12:27:00 +0000</pubDate>
				<category><![CDATA[前端笔记]]></category>
		<category><![CDATA[Clipboard]]></category>
		<category><![CDATA[Vue]]></category>
		<guid isPermaLink="false">https://www.hu365.dev/?p=134</guid>

					<description><![CDATA[相关参考资料： zenorocha/clipboard.js: Modern copy to clipboard. No Flash. Just 3kb gzipped (github.com) Clipboard.js 一个专职于“复制文本”操作的 js 库 <a href="https://www.hu365.dev/blog/2022/08/134/" class="cosmoswp-btn">阅读更多</a>]]></description>
										<content:encoded><![CDATA[
<p>相关参考资料：</p>



<ol><li><a href="https://github.com/zenorocha/clipboard.js">zenorocha/clipboard.js: Modern copy to clipboard. No Flash. Just 3kb gzipped (github.com)</a></li></ol>



<h2 class="wp-block-heading">Clipboard.js</h2>



<p>一个专职于“复制文本”操作的 <code>js</code> 库，以其短小精悍、简单实用的功能而被广泛应用</p>



<p>写这边文章的目的是为了说明：<code>Clipboard.js</code>的事件绑定存在直接绑定和事件委托两种方式</p>



<p>其实官网有说明使用方法和注意事项，不过鉴于以前没用过这个库，我并没有第一时间去看文档，反而是看过源码之后才发现文档里面居然已经写明了：</p>



<figure class="wp-block-image size-full"><a href="https://www.hu365.dev/wp-content/uploads/2022/10/image-2.webp"><img data-dominant-color="2c2f37" data-has-transparency="false" style="--dominant-color: #2c2f37;" fetchpriority="high" decoding="async" width="868" height="280" src="https://www.hu365.dev/wp-content/uploads/2022/10/image-2.webp" alt="" class="not-transparent wp-image-152" srcset="https://www.hu365.dev/wp-content/uploads/2022/10/image-2.webp 868w, https://www.hu365.dev/wp-content/uploads/2022/10/image-2-300x97.webp 300w, https://www.hu365.dev/wp-content/uploads/2022/10/image-2-768x248.webp 768w" sizes="(max-width: 868px) 100vw, 868px" /></a></figure>



<p>所以，看文档的好习惯，还是不能丢了···，那下面纯粹记录一下<code>bug</code>解决过程</p>



<h2 class="wp-block-heading">一：Bug 描述</h2>



<p>维护一个<code>Vue2</code>+<code>Element</code>的项目，调用<code>Clipboard.js</code>初始化的复制按钮；页面第一次进入后点击正常，后续退出、再进入，每次点击会累计多触发一次</p>



<figure class="wp-block-image size-full"><a href="https://www.hu365.dev/wp-content/uploads/2022/10/image.webp"><img data-dominant-color="d8ddd7" data-has-transparency="false" style="--dominant-color: #d8ddd7;" decoding="async" width="448" height="209" src="https://www.hu365.dev/wp-content/uploads/2022/10/image.webp" alt="" class="not-transparent wp-image-154" srcset="https://www.hu365.dev/wp-content/uploads/2022/10/image.webp 448w, https://www.hu365.dev/wp-content/uploads/2022/10/image-300x140.webp 300w" sizes="(max-width: 448px) 100vw, 448px" /></a></figure>



<figure class="wp-block-image size-full"><a href="https://www.hu365.dev/wp-content/uploads/2022/10/image-1.webp"><img data-dominant-color="dfe4dd" data-has-transparency="false" style="--dominant-color: #dfe4dd;" decoding="async" width="459" height="281" src="https://www.hu365.dev/wp-content/uploads/2022/10/image-1.webp" alt="" class="not-transparent wp-image-153" srcset="https://www.hu365.dev/wp-content/uploads/2022/10/image-1.webp 459w, https://www.hu365.dev/wp-content/uploads/2022/10/image-1-300x184.webp 300w" sizes="(max-width: 459px) 100vw, 459px" /></a></figure>



<p>看看初始化是怎么写的：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">if (!this.jsonClipboard) {
    this.jsonClipboard = new Clipboard('.json-btn');
    this.jsonClipboard.on('success', (e) => {
        this.$message.success('复制成功');
    })
}</pre>



<p>这么写好像没问题；保证每次进入页面只初始化一次<br>而且每次页面进入，<code>dom</code>都是重新生成，不存在重复绑定同一个<code>dom</code>的情况</p>



<p>难道，他绑定的事件不是绑定在按钮<code>dom</code>？</p>



<h2 class="wp-block-heading">二：源码查阅</h2>



<p>GitHub：<a href="https://github.com/zenorocha/clipboard.js">zenorocha/clipboard.js: Modern copy to clipboard. No Flash. Just 3kb gzipped (github.com)</a><br>文件：<code>/src/clipboard.js</code></p>



<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// https://github.com/zenorocha/clipboard.js/blob/master/src/clipboard.js#L22
/**
 * Base class which takes one or more elements, adds event listeners to them,
 * and instantiates a new `ClipboardAction` on each click.
 */
class Clipboard extends Emitter {
    /**
     * @param {String|HTMLElement|HTMLCollection|NodeList} trigger
     * @param {Object} options
     */
    constructor(trigger, options) {
        super();

        this.resolveOptions(options);
        this.listenClick(trigger); // 事件监听
    }

    // ...
    // 此处省略 1w 字
    // ...
    
    // https://github.com/zenorocha/clipboard.js/blob/master/src/clipboard.js#L58
    /**
     * Adds a click event listener to the passed trigger.
     * @param {String|HTMLElement|HTMLCollection|NodeList} trigger
     */
    listenClick(trigger) {
        this.listener = listen(trigger, 'click', (e) => this.onClick(e));
    }

    // ...
    // 此处省略 1w 字
    // ...
} </pre>



<p><code>listen</code>方法来自于组件<code>good-listener</code>，出自他自己之手<br>GitHub：<a href="https://github.com/zenorocha/good-listener">zenorocha/good-listener: A more versatile way of adding &amp; removing event listeners (github.com)</a><br>文件：<code>/<a href="https://github.com/zenorocha/good-listener/tree/master/src">src</a>/listen.js</code></p>



<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// https://github.com/zenorocha/good-listener/blob/master/src/listen.js#L4
/**
 * Validates all params and calls the right
 * listener function based on its target type.
 *
 * @param {String|HTMLElement|HTMLCollection|NodeList} target
 * @param {String} type
 * @param {Function} callback
 * @return {Object}
 */
function listen(target, type, callback) {
    if (!target &amp;&amp; !type &amp;&amp; !callback) {
        throw new Error("Missing required arguments");
    }

    if (!is.string(type)) {
        throw new TypeError("Second argument must be a String");
    }

    if (!is.fn(callback)) {
        throw new TypeError("Third argument must be a Function");
    }

    if (is.node(target)) {
        return listenNode(target, type, callback);
    } else if (is.nodeList(target)) {
        return listenNodeList(target, type, callback);
    } else if (is.string(target)) {
        return listenSelector(target, type, callback);
    } else {
        throw new TypeError(
            "First argument must be a String, HTMLElement, HTMLCollection, or NodeList"
        );
    }
}

/**
 * Adds an event listener to a HTML element
 * and returns a remove listener function.
 *
 * @param {HTMLElement} node
 * @param {String} type
 * @param {Function} callback
 * @return {Object}
 */
function listenNode(node, type, callback) {
    node.addEventListener(type, callback);

    return {
        destroy: function() {
            node.removeEventListener(type, callback);
        }
    }
}

/**
 * Add an event listener to a list of HTML elements
 * and returns a remove listener function.
 *
 * @param {NodeList|HTMLCollection} nodeList
 * @param {String} type
 * @param {Function} callback
 * @return {Object}
 */
function listenNodeList(nodeList, type, callback) {
    Array.prototype.forEach.call(nodeList, function(node) {
        node.addEventListener(type, callback);
    });

    return {
        destroy: function() {
            Array.prototype.forEach.call(nodeList, function(node) {
                node.removeEventListener(type, callback);
            });
        }
    }
}

/**
 * Add an event listener to a selector
 * and returns a remove listener function.
 *
 * @param {String} selector
 * @param {String} type
 * @param {Function} callback
 * @return {Object}
 */
function listenSelector(selector, type, callback) {
    return delegate(document.body, selector, type, callback);
}
</pre>



<p>这里这哥们又调用了一个自己的库：<code>delegate</code><br>Github：<a href="https://github.com/zenorocha/delegate">zenorocha/delegate: Lightweight event delegation (github.com)</a></p>



<h2 class="wp-block-heading">三：解决方案</h2>



<p>从上面的源码已经很清楚：<br>参数为<code>node</code>、<code>nodeList</code>时，直接使用<code>addEventListener</code>进行事件绑定；<br>参数为字符串时，会用事件委托实现选择器可能的所有<code>dom</code>的操作事件</p>



<p>而出现<code>bug</code>的原因自然是：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 此处使用类选择器 .json-btn
this.jsonClipboard = new Clipboard('.json-btn');</pre>



<p>所以在使用字符串/选择器做参数时，一定要在页面销毁/退出时，主动销毁绑定的委托事件：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">beforeDestroy() {
    if (this.jsonClipboard) this.jsonClipboard.destroy();
},</pre>



<h2 class="wp-block-heading">四：总结</h2>



<p>总的来说，依托外部组件进行的事件绑定，一定要清楚它是如何绑定、如何解绑<br>在<code>Vue</code>中使用<code>Clipboard.js</code>，不管用何种方式进行初始化，它绑定的事件会一直存在</p>



<p>所以任何时候都应在组件销毁（前）进行手动销毁，即使指定<code>Dom</code>的事件绑定不会出现类似<code>bug</code>，但绑定的事件会一直存在于内存中，对性能也是一种负担</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
