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

相关文章

CentOS7增加Swap空间的两种方法

《CentOS7增加Swap空间的两种方法》当服务器物理内存不足时,增加Swap空间可以作为虚拟内存使用,帮助系统处理内存压力,本文给大家介绍了CentOS7增加Swap空间的两种方法:创建新的Swa... 目录在Centos 7上增加Swap空间的方法方法一:创建新的Swap文件(推荐)方法二:调整Sww

QT6中绘制UI的两种方法详解与示例代码

《QT6中绘制UI的两种方法详解与示例代码》Qt6提供了两种主要的UI绘制技术:​​QML(QtMeta-ObjectLanguage)​​和​​C++Widgets​​,这两种技术各有优势,适用于不... 目录一、QML 技术详解1.1 QML 简介1.2 QML 的核心概念1.3 QML 示例:简单按钮

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

Oracle 通过 ROWID 批量更新表的方法

《Oracle通过ROWID批量更新表的方法》在Oracle数据库中,使用ROWID进行批量更新是一种高效的更新方法,因为它直接定位到物理行位置,避免了通过索引查找的开销,下面给大家介绍Orac... 目录oracle 通过 ROWID 批量更新表ROWID 基本概念性能优化建议性能UoTrFPH优化建议注

PostgreSQL 序列(Sequence) 与 Oracle 序列对比差异分析

《PostgreSQL序列(Sequence)与Oracle序列对比差异分析》PostgreSQL和Oracle都提供了序列(Sequence)功能,但在实现细节和使用方式上存在一些重要差异,... 目录PostgreSQL 序列(Sequence) 与 oracle 序列对比一 基本语法对比1.1 创建序

Pandas进行周期与时间戳转换的方法

《Pandas进行周期与时间戳转换的方法》本教程将深入讲解如何在pandas中使用to_period()和to_timestamp()方法,完成时间戳与周期之间的转换,并结合实际应用场景展示这些方法的... 目录to_period() 时间戳转周期基本操作应用示例to_timestamp() 周期转时间戳基

在 PyQt 加载 UI 三种常见方法

《在PyQt加载UI三种常见方法》在PyQt中,加载UI文件通常指的是使用QtDesigner设计的.ui文件,并将其转换为Python代码,以便在PyQt应用程序中使用,这篇文章给大家介绍在... 目录方法一:使用 uic 模块动态加载 (不推荐用于大型项目)方法二:将 UI 文件编译为 python 模

Python将字库文件打包成可执行文件的常见方法

《Python将字库文件打包成可执行文件的常见方法》在Python打包时,如果你想将字库文件一起打包成一个可执行文件,有几种常见的方法,具体取决于你使用的打包工具,下面就跟随小编一起了解下具体的实现方... 目录使用 PyInstaller基本方法 - 使用 --add-data 参数使用 spec 文件(

Python的pip在命令行无法使用问题的解决方法

《Python的pip在命令行无法使用问题的解决方法》PIP是通用的Python包管理工具,提供了对Python包的查找、下载、安装、卸载、更新等功能,安装诸如Pygame、Pymysql等Pyt... 目录前言一. pip是什么?二. 为什么无法使用?1. 当我们在命令行输入指令并回车时,一般主要是出现以

通过C#获取Excel单元格的数据类型的方法详解

《通过C#获取Excel单元格的数据类型的方法详解》在处理Excel文件时,了解单元格的数据类型有助于我们正确地解析和处理数据,本文将详细介绍如何使用FreeSpire.XLS来获取Excel单元格的... 目录引言环境配置6种常见数据类型C# 读取单元格数据类型引言在处理 Excel 文件时,了解单元格