saber酱

saber酱 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

saber酱 赞了文章 · 2020-12-17

原生js实现基于base64数据复制图片到剪切板,可以粘贴出图片,模拟浏览器复制功能。

原生js实现基于base64数据复制图片到剪切板,可以粘贴出图片,模拟浏览器复制功能。


前言:
初次发帖排版略丑,见谅。
之前我们这边有个需求就是说,前端通过canvas截图然后, 点击一个按钮实现复制截取的图片到剪切板上,然后我查了好多文档,还去了github上看了一圈也没看见比较好的解决方案。

不说废话了直接上代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>js复制图片-文字</title>
</head>
<body>

<div>
    <img id="hidImag" data-original="" />
</div>
<input id="copy" type="button" style="cursor: pointer;" value="复制图片到剪切板">
<script type="text/javascript">
    copy.onclick = function () {
        try {
           const base64Data = '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'
            if (document.body.createControlRange) { //IE 11 需要有个img标签
                var controlRange;
                var imgs = document.getElementById('hidImag');
                imgs.onload = function () {
                    controlRange = document.body.createControlRange();
                    imgs.contentEditable = 'true';
                    controlRange.addElement(imgs);
                    try {
                        var successful = controlRange.execCommand('copy');
                        var msg = successful ? 'successful' : 'unsuccessful';
                        console.log('Copying text command was ' + msg);
                    } catch (err) {
                        console.log(err);
                    }
                }
                imgs.src = 'data:image/png;base64,' + base64Data;
            }
            else { //chrome
                const blobInput = convertBase64ToBlob(base64Data, 'image/png');
                const clipboardItemInput = new ClipboardItem({ 'image/png': blobInput });
                navigator.clipboard.write([clipboardItemInput]);
                console.log('success');
            }
        } catch (e) {
            console.log(e);
        }
    }

    function convertBase64ToBlob(base64, type) {
        var bytes = window.atob(base64);
        var ab = new ArrayBuffer(bytes.length);
        var ia = new Uint8Array(ab);
        for (var i = 0; i < bytes.length; i++) {
            ia[i] = bytes.charCodeAt(i);
        }
        return new Blob([ab], { type: type });
    }
</script>
</body>
</html>
查看原文

赞 24 收藏 15 评论 4

saber酱 赞了文章 · 2020-12-07

Js事件循环(Event Loop)机制

前言

Event Loop是计算机系统的一种运行机制,是个很重要的概念。而Javascript用这种机制来解决单线程运行带来的问题。理解很熟悉将会有利于我们更容易理解Vue的异步事件。

JavaScript是单线程的

1、什么是单线程?

单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。简单来说,即同一时间只能做一件事件。

2、Js为什么是单线程?

Js是一种运行在网页的简单的脚本语言,由于设计的初衷是作为浏览器脚本语言,用于与用户互动,以及操作DOM。这决定它是单线程的。

3、单线程带来的问题?

单线程就意味着,所有任务都需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就需要一直等着。这就会导致IO操作(耗时但cpu闲置)时造成性能浪费的问题。

4、如何解决单线程的性能问题?

采用异步可以解决。主线程完全可以不管IO操作,暂时挂起处于等待中的任务,先运行排在后面的任务。等到IO操作返回了结果,再回过头,把挂起的任务继续执行下去。于是,所有任务可以分成两种,一种是同步任务,另一种是异步任务。

执行栈

Javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆(heap)和栈(stack)中来加以区分。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。但是我们这里说的执行栈和上面这个栈的意义却有些不同。

js 在执行可执行的脚本时,会经过以下步骤:

  1. 首先会创建一个全局可执行上下文globalContext,每当执行到一个函数调用时都会创建一个可执行上下文(execution context)EC
  2. 可执行程序可能会存在很多函数调用,那么就会创建很多EC,所以 JavaScript 引擎创建了执行上下文栈(Execution context stack,ECS)来管理执行上下文。
  3. 当函数调用完成,Js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。 这个过程反复进行,直到执行栈中的代码全部执行完毕。


实例

function fun3() {
    console.log('fun3')
}
function fun2() {
    fun3();
}
function fun1() {
    fun2();
}
fun1();

当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。知道了这样的工作原理,让我们来看看如何处理上面这段代码:

1.执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈

ECStack = [
    globalContext
];
  1. 全局上下文初始化
   globalContext = {
        VO: [global],
        Scope: [globalContext.VO],
        this: globalContext.VO
    }
  1. 初始化的同时,fun1函数被创建,保存作用域链到函数的内部属性[[scope]]
 fun1.[[scope]] = [
      globalContext.VO
    ];
  1. 执行 fun1 函数,创建fun1函数执行上下文,fun1函数执行上下文被压入执行上下文栈
 ECStack = [
        fun1,
        globalContext
    ];
  1. fun1函数执行上下文初始化:

    1.复制函数 [[scope]] 属性创建作用域链。

    2.用 arguments 创建活动对象。

    3.初始化活动对象,即加入形参、函数声明、变量声明。

    4.将活动对象压入fun1 作用域链顶端。

同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]

  checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined,
            f: reference to function f(){}
        },
        Scope: [AO, globalContext.VO],
        this: undefined
    }
  1. 执行 fun2() 函数,重复步骤2。
  2. 最终形成这样的执行栈:
   ECStack = [
        fun3
        fun2,
        fun1,
        globalContext
    ];
  1. fun3执行完毕,从执行栈中弹出...一直到fun1

事件循环(Event Loop)

JavaScript内存模型

在了解事件循环之前,先要弄明白Js的内存模型,这有助于更好的理解事件循环。

  • 调用栈(Call Stack):用于主线程任务的执行。
  • 堆(Heap):用于存放非结构数据,如程序分配的变量和对象。
  • 任务队列(Queue): 用于存放异步任务。

Js异步执行的运行机制

  1. 所有同步任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

任务

异步任务存放在任务队列里,异步任务分为 宏任务(macrotask)与微任务(microtask),不同的API注册的任务会依次进入自身对应的队列中,然后等待Event Loop将它们依次压入执行栈中执行。

宏任务主要包含:

  • script(整体代码)
  • setTimeout
  • setInterval
  • I/OUI交互事件
  • setImmediate(Node.js 环境)

微任务主要包含:

  • Promise
  • MutaionObserver
  • process.nextTick(Node.js 环境)

我们的JavaScript的执行过程是单线程的,所有的任务可以看做存放在两个队列中——执行队列和事件队列。

执行队列里面是所有同步代码的任务,事件队列里面是所有异步代码的宏任务,而我们的微任务,是处在两个队列之间。

JavaScript执行时,优先执行完所有同步代码,遇到对应的异步代码,就会根据其任务类型存到对应队列(宏任务放入事件队列,微任务放入执行队列之后,事件队列之前);当执行完同步代码之后,就会执行位于执行队列和事件队列之间的微任务,然后再执行事件队列中的宏任务。

实例

new Promise(resolve => {
    resolve(1);
    
    Promise.resolve().then(() => {
        // t2
        console.log(2)
    });
    console.log(4)
}).then(t => {
    // t1
    console.log(t)
});
console.log(3);

这段代码的流程大致如下:

  1. script 任务先运行。首先遇到Promise实例,构造函数首先执行,所以首先输出了 4。此时 microtask 的任务有 t2t1
  2. script 任务继续运行,输出 3。至此,第一个宏任务执行完成。
  3. 执行所有的微任务,先后取出 t2t1,分别输出 21
  4. 代码执行完毕

综上,上述代码的输出是:4321

事件循环

主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

从上图我们可以看出:

  • 主线程运行的时候,产生堆(heap)和栈(stack)。
  • 栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。
  • 栈中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数。

小结

事件循环其实并不难,多查阅资料,多看看相关例子就ok。希望一知半解的童鞋抓紧学习。

相关文章

查看原文

赞 4 收藏 2 评论 0

saber酱 赞了回答 · 2020-11-20

解决vue 中 vue-router、transition、keep-alive 怎么结合使用?

使用 transition-group 可以解决这个问题
1. 你原来的写法
<transition-group :name="name">
      <keep-alive key="keep-alive">
        <router-view class="router-view" v-if="$route.meta.keepAlive"></router-view>
      </keep-alive>
      <router-view class="router-view" v-if="!$route.meta.keepAlive" key="not-keep-alive"></router-view>
    </transition-group>
2. 我修改之后的写法

就是多加了一层div,v-if挪到div上,切换路由的时候就有transition-group默认的过渡效果了

<transition-group :name="name">
    <div key="keep-alive"  v-if="$route.meta.keepAlive">
      <keep-alive key="keep-alive">
        <router-view class="router-view"></router-view>
      </keep-alive>
    </div>  
      <router-view class="router-view" v-if="!$route.meta.keepAlive" key="not-keep-alive"></router-view>
    </transition-group>

关注 11 回答 8

saber酱 回答了问题 · 2020-09-02

解决Chrome 扩展在后台发出的请求如何携带前台 cookie?

后台可以修改网络请求,使用 chrome.webRequest.onHeadersReceived API,给每个请求的 response headers 都添加 Access-Control-Allow-Origin : *,那么前台发出的所有请求都可以跨域。(如果此请求本来就有这个标头,就先删掉然后添加)

先在 manifest.json 里申请权限:

"permissions": ["webRequest", "webRequestBlocking", "*://*/*" ]

然后在后台代码(background.js)里修改网络请求:

function removeMatchingHeaders(headers: any, regex: any) {
  for (var i = 0, header; (header = headers[i]); i++) {
    if (header.name.match(regex)) {
      headers.splice(i, 1);
      console.log('Removing header "' + header.name + '":"' + header.value + '"');
      return;
    }
  }
}

function responseListener(details: any) {

  removeMatchingHeaders(details.responseHeaders, /access-control-allow-origin/i);
  details.responseHeaders.push({ name: 'Access-Control-Allow-Origin', value: '*' });

  return { responseHeaders: details.responseHeaders };
}

chrome.webRequest.onHeadersReceived.addListener(responseListener, {
  urls: ['*://*/*']
}, [
  'blocking',
  'responseHeaders',
  'extraHeaders'
]);

可以根据自己需要把网址范围 *://*/* 修改一下,避免影响所有网络请求。

关注 3 回答 3

saber酱 提出了问题 · 2020-09-01

解决Chrome 扩展在后台发出的请求如何携带前台 cookie?

我用 Chrome 扩展程序从 https://www.pixiv.net/artworks/84085174 下载一张图片,图片是跨域的。https://i.pximg.net/img-original/img/2020/09/01/16/22/11/84085174_p0.png

前台直接请求会被跨域策略阻止,我在后台脚本 background.js 里可以请求,但是 403,因为需要携带 cookie。不知道怎么解决。

后台网址是 chrome 扩展的网址:
chrome-extension://penkncbihcdfkhngfonjppnnkhodlali/_generated_background_page.html
fetch 或 xhr,虽然它们可以发送 cookie,但因为是在后台页面执行的,无法携带前台页面的 cookie。而且它们都不支持在请求头里设置自定义 cookie,我就算获取了前台的 cookie 也发送不出去,一筹莫展,不知道该怎么办。

关注 3 回答 3

saber酱 赞了回答 · 2020-08-24

解决这样修改算是解耦吗,是否应该这样?

下面是个人的理解:
这种监听捕获处理流程没有太大的问题。
你所谓的增加事件event2,应该有对应的监听处理,而不应该简单的和init关联啊,要么整合进init的处理过程,要么完全独立,这样来其实要么只修改子模块,要么因为是新事件,要同时修改子模块和主控(不过这样你前面的模式一样需要两个都修改)。

关于你提到的2种模式对工作流程的关联特性其实是针对不同应用的,不能一概而论,有时第一种模式更适合,有时第二种模式适合,比如第二种模式主要是面对异步处理的,和第一种逻辑线路就不同的。

关注 2 回答 1

saber酱 提出了问题 · 2020-08-21

解决这样修改算是解耦吗,是否应该这样?

在前端项目里,设想有一个流程控制模块和一些负责单个组件、功能的子模块。流程控制引入子模块的实例,单方面调用:

流程控制:

import base

init(){
  // 指示子模块初始化
  base.init()
}

如果改为流程控制模块触发 init 事件,子模块里响应 init 事件:

流程控制:

// 只触发事件
init(){
  fire('init')
}

子模块:

listen('init){
  this.init()
}

修改之后看起来很好看,因为流程控制里不需要引用子模块,减少了一些 import。表面上看似乎是解耦了,但我觉得也有不妥的地方,似乎更难维护了:

  1. 这让子模块绑定了流程控制的逻辑。如果以后业务变更,增加了一个事件 event2 需要子模块再执行 init,需要同时修改这两个模块:流程控制触发事件,然后子模块里添加对这个事件的监听。但在一开始的形式里,只需要修改流程控制即可,后面的形式反而需要修改两个模块。
  2. 一开始的形式里,查看流程控制的代码时,可以清晰的看到工作流程,它指示各个子模块初始化。后面的形式里,看不到工作流程,只能去搜索有哪些子模块监听了它所触发的事件,相当麻烦。

到底该不该这样修改呢,或者是有更好的方式?但我在这方面造诣不深,想问下前辈们的意见,谢谢!

关注 2 回答 1

saber酱 提出了问题 · 2020-06-10

解决vue-cli 提示 'get' and 'set' accessor must have the same type.

vue-cli 3 项目,使用了 class 风格的组件(vue-property-decorator)
2.png

有个 getter 和 setter,是 vue 实例的计算属性,因为用了 class 风格所以是用 get 和 set 写的。
编辑器提示 get 和 set 寄存器必须有相同的类型,这个地方我不理解,因为 get 和 set 的参数类型不一样,返回值也不一样,这个要求“相同的类型” 不懂什么意思。
不过运行时没有提示这个错误,可以正常使用。请大佬解惑~

关注 1 回答 1

saber酱 赞了回答 · 2020-06-09

flex布局子元素高度如何自适应?

1 假如如下结构

clipboard.png

父级元素设置高度,并在父级元素上设置flex布局,

.parent {
  width: 400px;
  height: 500px;
  display: flex;
  flex-direction: column;
}

在子元素上,设置子元素的flex属性值为1,以及overflow属性为 scroll;

.child {
  width: 100%;
  flex: 1;
  overflow: scroll;
}

关注 5 回答 4

saber酱 赞了回答 · 2020-05-26

vue-cli3中process.env.BASE_URL在哪里设置?

在根目录的 vue.config.js 中配置 baseUrl

如果你要看原理的化可以看这部分源码,我之前写了 vue-cli 3 相关的一些分析文章

源码部分:@vue/cli-service/lib/util/resolveClientEnv.js

module.exports = function resolveClientEnv (options, raw) {
  const env = {}
  Object.keys(process.env).forEach(key => {
    if (prefixRE.test(key) || key === 'NODE_ENV') {
      env[key] = process.env[key]
    }
  })
  env.BASE_URL = options.baseUrl

  if (raw) {
    return env
  }

  for (const key in env) {
    env[key] = JSON.stringify(env[key])
  }
  return {
    'process.env': env
  }
}

核心就是:

env.BASE_URL = options.baseUrl

关注 3 回答 2

认证与成就

  • 获得 22 次点赞
  • 获得 62 枚徽章 获得 2 枚金徽章, 获得 18 枚银徽章, 获得 42 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-01-04
个人主页被 986 人浏览