vue-clipboard2在vue的created生命周期中直接调用copyText方法报错的原因分析

本文主要是介绍vue-clipboard2在vue的created生命周期中直接调用copyText方法报错的原因分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

vue-clipboard2在vue的created生命周期中直接调用copyText方法报错

先说现象:在created生命周期中会进入reject状态(被catch到),不在生命周期的方法中调用而通过click事件来调用会正常进入resolved状态(成功进入then阶段)。

下面进行相关源码分析:

出错代码:

created() {this.$copyText('asdasdasdas').then(() => {console.log('复制成功');}).catch(err => {console.error('复制出错', err); // 执行到这里了})
},

打开vue-clipboard2的源码,可以发现底层使用了clipboard这个库:

Vue.prototype.$copyText = function (text, container) {return new Promise(function (resolve, reject) {var fakeElement = document.createElement('button')// 注意这里,使用了Clipboard的构造方法var clipboard = new Clipboard(fakeElement, {text: function () { return text },action: function () { return 'copy' },container: typeof container === 'object' ? container : document.body})clipboard.on('success', function (e) {clipboard.destroy()resolve(e)})// 注意这里clipboard.on('error', function (e) {clipboard.destroy()reject(e)})if (VueClipboardConfig.appendToBody) document.body.appendChild(fakeElement)fakeElement.click()if (VueClipboardConfig.appendToBody) document.body.removeChild(fakeElement)})
}

在它的package.json中找到clipboard的依赖,确保别找错了:

"dependencies": {"clipboard": "^2.0.0"
},

再看一下我们安装的clipboard的版本:

"version": "2.0.4"

github上搜一下这个库的源码:https://github.com/zenorocha/clipboard.js

方便查找代码的引用关系,我们去这个网址:https://sourcegraph.com/github.com/zenorocha/clipboard.js@master/-/blob/src/clipboard.js

之前的代码调用了clipboard的构造方法:

 constructor(trigger, options) {super();this.resolveOptions(options);this.listenClick(trigger);}/*** Defines if attributes would be resolved using internal setter functions* or custom functions that were passed in the constructor.* @param {Object} options*/resolveOptions(options = {}) {this.action    = (typeof options.action    === 'function') ? options.action    : this.defaultAction;this.target    = (typeof options.target    === 'function') ? options.target    : this.defaultTarget;this.text      = (typeof options.text      === 'function') ? options.text      : this.defaultText;this.container = (typeof options.container === 'object')   ? options.container : document.body;}/*** 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));}

triggervue-clipboard2传进来的button实例,listenClick做的就是给这个button加上click事件,再看一下onClick方法:

    onClick(e) {// button实例 delegateTarget是事件委托dom,这里我们走的是currentTargetconst trigger = e.delegateTarget || e.currentTarget;if (this.clipboardAction) {this.clipboardAction = null;}this.clipboardAction = new ClipboardAction({action    : this.action(trigger), // 'copy'target    : this.target(trigger), // undefinedtext      : this.text(trigger), // 'text' => 传入的text参数container : this.container, // 默认为bodytrigger   : trigger,emitter   : this});}

this.target方法再初始化时被定义成下面这个函数,因为vue-clipboard2没有传这个参数

    defaultTarget(trigger) {const selector = getAttributeValue('target', trigger); // 返回undefined,因为button没有target这个属性 if (selector) {return document.querySelector(selector);}}

下面再看看ClipboardAction的构造方法做了什么:

 constructor(options) {this.resolveOptions(options);this.initSelection();}/*** Defines base properties passed from constructor.* @param {Object} options*/resolveOptions(options = {}) {this.action    = options.action;this.container = options.container;this.emitter   = options.emitter;this.target    = options.target;this.text      = options.text;this.trigger   = options.trigger;this.selectedText = '';}/*** Decides which selection strategy is going to be applied based* on the existence of `text` and `target` properties.*/initSelection() {if (this.text) {this.selectFake();}else if (this.target) {this.selectTarget();}}

主要是对传进来的参数进行本地赋值,看到initSelection方法,进入了第一个分支:

    selectFake() {const isRTL = document.documentElement.getAttribute('dir') == 'rtl';// 这个方法做的事情是删除textarea节点,清除container上的click事件// 将fakeHandler,fakeHandlerCallback,fakeElem置为nullthis.removeFake();this.fakeHandlerCallback = () => this.removeFake();this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;this.fakeElem = document.createElement('textarea');// Prevent zooming on iOSthis.fakeElem.style.fontSize = '12pt';// Reset box modelthis.fakeElem.style.border = '0';this.fakeElem.style.padding = '0';this.fakeElem.style.margin = '0';// Move element out of screen horizontallysthis.fakeElem.style.position = 'absolute';this.fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px';// Move element to the same position verticallylet yPosition = window.pageYOffset || document.documentElement.scrollTop;this.fakeElem.style.top = `${yPosition}px`;this.fakeElem.setAttribute('readonly', '');this.fakeElem.value = this.text;this.container.appendChild(this.fakeElem);this.selectedText = select(this.fakeElem);this.copyText();}

创建了一个textareadom节点,value为我们传进去的text

select方法为外部依赖,做的事情是帮我们选中textarea中的文字。接下来调用了copyText方法,

    /*** Executes the copy operation based on the current selection.*/copyText() {let succeeded;try {succeeded = document.execCommand(this.action); // this.action === 'copy'}catch (err) {succeeded = false;}this.handleResult(succeeded);}

可以看到调用了execCommand方法来执行操作系统的copy方法,而我们报的错是在handleResultemit出来的,所以我们的$copyText方法进入了catch分支。

    // vue-clipboard2的监听事件clipboard.on('error', function (e) {clipboard.destroy()reject(e)})  handleResult(succeeded) {this.emitter.emit(succeeded ? 'success' : 'error', {action: this.action,text: this.selectedText,trigger: this.trigger,clearSelection: this.clearSelection.bind(this)});}

也就是说succeeded变量值为false,这一点在我们断点调试一下可以发现确实返回了fasle。

在这里插入图片描述

为什么呢?先看一下MDN文档对于execCommand方法的说明:

bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument);
// 一个 Boolean ,如果是 false 则表示操作不被支持或未被启用。
// 注意:在调用一个命令前,不要尝试使用返回值去校验浏览器的兼容性

可是我的浏览器是chrome 78,按理说支持这个方法啊,可是为什么会返回false呢?

返回false的原因其实也是浏览器对安全性的考虑,因为copy这个操作不是由用户操作产生的,而是由代码自执行的,所以默认执行失败。

document.execCommand的特殊性

浏览器处于安全考虑,document.execCommand这个api只能在真正的用户操作之后才能被触发。

以下引用自W3C草案:

If an implementation supports ways to execute clipboard commands through scripting, for example by calling the document.execCommand() method with the commands “cut”, “copy” and “paste”, the implementation must trigger the corresponding action, which again will dispatch the associated clipboard event.

copy事件的执行过程:

  1. If the script-triggered flag is set, then
    1. If the script-may-access-clipboard flag is unset, then
      1. Return false from the copy action, terminate this algorithm
  2. Fire a clipboard event named copy
  3. If the event was not canceled, then
    1. Copy the selected contents, if any, to the clipboard. Implementations should create alternate text/html and text/plain clipboard formats when content in a web page is selected.
    2. Fire a clipboard event named clipboardchange
  4. Else, if the event was canceled, then
    1. Call the write content to the clipboard algorithm, passing on the DataTransferItemList list items, a clear-was-called flag and a types-to-clear list.
  5. Return true from the copy action

参考链接

Cannot use document.execCommand('copy'); from developer console

execCommand(‘copy’) does not work in Ajax / XHR callback?

W3C:Clipboard API and events

这篇关于vue-clipboard2在vue的created生命周期中直接调用copyText方法报错的原因分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/246924

相关文章

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

Vue和React受控组件的区别小结

《Vue和React受控组件的区别小结》本文主要介绍了Vue和React受控组件的区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录背景React 的实现vue3 的实现写法一:直接修改事件参数写法二:通过ref引用 DOMVu

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

504 Gateway Timeout网关超时的根源及完美解决方法

《504GatewayTimeout网关超时的根源及完美解决方法》在日常开发和运维过程中,504GatewayTimeout错误是常见的网络问题之一,尤其是在使用反向代理(如Nginx)或... 目录引言为什么会出现 504 错误?1. 探索 504 Gateway Timeout 错误的根源 1.1 后端

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja