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

相关文章

检查 Nginx 是否启动的几种方法

《检查Nginx是否启动的几种方法》本文主要介绍了检查Nginx是否启动的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1. 使用 systemctl 命令(推荐)2. 使用 service 命令3. 检查进程是否存在4

Java方法重载与重写之同名方法的双面魔法(最新整理)

《Java方法重载与重写之同名方法的双面魔法(最新整理)》文章介绍了Java中的方法重载Overloading和方法重写Overriding的区别联系,方法重载是指在同一个类中,允许存在多个方法名相同... 目录Java方法重载与重写:同名方法的双面魔法方法重载(Overloading):同门师兄弟的不同绝

MySQL字符串转数值的方法全解析

《MySQL字符串转数值的方法全解析》在MySQL开发中,字符串与数值的转换是高频操作,本文从隐式转换原理、显式转换方法、典型场景案例、风险防控四个维度系统梳理,助您精准掌握这一核心技能,需要的朋友可... 目录一、隐式转换:自动但需警惕的&ld编程quo;双刃剑”二、显式转换:三大核心方法详解三、典型场景

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

MySQL快速复制一张表的四种核心方法(包括表结构和数据)

《MySQL快速复制一张表的四种核心方法(包括表结构和数据)》本文详细介绍了四种复制MySQL表(结构+数据)的方法,并对每种方法进行了对比分析,适用于不同场景和数据量的复制需求,特别是针对超大表(1... 目录一、mysql 复制表(结构+数据)的 4 种核心方法(面试结构化回答)方法 1:CREATE

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

springboot中配置logback-spring.xml的方法

《springboot中配置logback-spring.xml的方法》文章介绍了如何在SpringBoot项目中配置logback-spring.xml文件来进行日志管理,包括如何定义日志输出方式、... 目录一、在src/main/resources目录下,也就是在classpath路径下创建logba

解决idea启动项目报错java: OutOfMemoryError: insufficient memory

《解决idea启动项目报错java:OutOfMemoryError:insufficientmemory》:本文主要介绍解决idea启动项目报错java:OutOfMemoryError... 目录原因:解决:总结 原因:在Java中遇到OutOfMemoryError: insufficient me

SQL Server中行转列方法详细讲解

《SQLServer中行转列方法详细讲解》SQL行转列、列转行可以帮助我们更方便地处理数据,生成需要的报表和结果集,:本文主要介绍SQLServer中行转列方法的相关资料,需要的朋友可以参考下... 目录前言一、为什么需要行转列二、行转列的基本概念三、使用PIVOT运算符进行行转列1.创建示例数据表并插入数

C++打印 vector的几种方法小结

《C++打印vector的几种方法小结》本文介绍了C++中遍历vector的几种方法,包括使用迭代器、auto关键字、typedef、计数器以及C++11引入的范围基础循环,具有一定的参考价值,感兴... 目录1. 使用迭代器2. 使用 auto (C++11) / typedef / type alias