首页
统计
赞助
留言
关于
更多
友链
壁纸
直播
Search
1
Joe — 一款个人类型Typecho主题
33,051 阅读
2
Joe开源目录程序系统
12,105 阅读
3
Typecho自定义后台编辑器功能
8,474 阅读
4
Joe主题实现自动更新
7,655 阅读
5
Joe主题自定义搭配色教程
4,538 阅读
WEB前端
CSS
React
Vue 2.0
Vue 3.0
JavaScript
TypeScript
Typecho
小程序
苹果CMS
其他
生活
登录
Search
https://78.al
累计撰写
76
篇文章
累计收到
3,776
条评论
首页
栏目
WEB前端
CSS
React
Vue 2.0
Vue 3.0
JavaScript
TypeScript
Typecho
小程序
苹果CMS
其他
生活
页面
统计
赞助
留言
关于
友链
壁纸
直播
搜索到
76
篇与
HaoOuBa
的结果
2022-07-13
JS压缩图片并保留图片元信息
JS实现图片压缩比较简单,但是图片经过压缩后,压缩后的图片的元信息(拍摄时间、设备、地点)等会丢失掉,如果在特殊场景中需要使用这些元信息的话,就会出现问题了,因此需要将未压缩前的图片元信息填充至压缩后的图片中,以下是实现代码// 封装一个获取变量的数据类型函数 const getType = (data: unknown): string => { const toStingResult = Object.prototype.toString.call(data); const type = toStingResult.replace(/^\[object (\w+)\]$/, "$1"); return type.toLowerCase(); }; // 封装一个将 Base64 的字符串转换成 Blob 流的函数 const dataURLtoBlob = (dataURL: string): Blob | null => { const dataType = getType(dataURL); if (dataType !== "string") return null; const arr = dataURL.split(","); if (!arr[0] || !arr[1]) return null; const code = window.atob(arr[1]); const mimeExpRes = arr[0].match(/:(.*?);/); if (!mimeExpRes) return null; let len = code.length; const mime = mimeExpRes[1]; if (!mime) return null; const ia = new Uint8Array(len); while (len--) ia[len] = code.charCodeAt(len); return new Blob([ia], { type: mime }); }; // 利用规律编码格式把里面的标记以及值等分割开来,传原图片的 ArrayBuffer 进来 const getSegments = (arrayBuffer: ArrayBuffer): number[][] => { if (!arrayBuffer.byteLength) return []; let head = 0; let length, endPoint, seg; const segments = []; const arr = [].slice.call(new Uint8Array(arrayBuffer), 0); while (1) { if (arr[head] === 0xff && arr[head + 1] === 0xda) break; if (arr[head] === 0xff && arr[head + 1] === 0xd8) { head += 2; } else { length = arr[head + 2] * 256 + arr[head + 3]; endPoint = head + length + 2; seg = arr.slice(head, endPoint); head = endPoint; segments.push(seg); } if (head > arr.length) break; } return segments; }; // 传入上面 getSegments 的返回值,取出EXIF图片元信息 const getEXIF = (segments: number[][]): Array<number> => { if (!segments.length) return []; let seg: Array<number> = []; for (let i = 0; i < segments.length; i++) { const item = segments[i]; if (item[0] === 0xff && item[1] === 0xe1) { seg = seg.concat(item); } } return seg; }; // 将 getEXIF 获取的元信息,插入到压缩后的图片的 Blob 中,传 压缩图片后的 Blob 流 const insertEXIF = (blob: Blob, exif: number[]): Promise<Blob> => { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = () => { const arr = [].slice.call(new Uint8Array(fileReader.result as ArrayBuffer), 0); if (arr[2] !== 0xff || arr[3] !== 0xe0) { return reject(new Error("Couldn't find APP0 marker from blob data")); } const length = arr[4] * 256 + arr[5]; const newImage = [0xff, 0xd8].concat(exif, arr.slice(4 + length)); const uint8Array = new Uint8Array(newImage); const newBlob = new Blob([uint8Array], { type: "image/jpeg" }); resolve(newBlob); }; fileReader.readAsArrayBuffer(blob); }); }; // 压缩图片逻辑 const compressImage = (file: File, quality: number): Promise<Blob | null> => { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = () => { const img = new Image(); img.src = fileReader.result as string; img.onload = () => { const { width, height } = img; const canvas = window.document.createElement("canvas"); const ctx = <CanvasRenderingContext2D>canvas.getContext("2d"); canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0, width, height); const fileData = canvas.toDataURL("image/jpeg", quality); const fileBlob = dataURLtoBlob(fileData); resolve(fileBlob); }; img.onerror = (err) => reject(err); }; fileReader.onerror = (err) => reject(err); fileReader.readAsDataURL(file); }); }; /** * @description: 完整的压缩图片,最终对外暴露的函数 * @param {File} file * @param {number} quality 0 - 1 * @return {Promise<File>} */ export default (file: File, quality = 0.5): Promise<File> => { return new Promise((resolve, reject) => { const dataType = getType(file); if (dataType !== "file") return reject(new Error(`Expected parameter type is file, You passed in ${dataType}`)); if (file.type.indexOf("image") === -1) return resolve(file); // 压缩图片 compressImage(file, quality) .then((compressdBlob) => { if (!compressdBlob) return resolve(file); const fileReader = new FileReader(); fileReader.onload = () => { // 获取图片元信息 const segments = getSegments(fileReader.result as ArrayBuffer); const exif = getEXIF(segments); // 没有元数据的时候, 直接抛出压缩后的图片 if (!exif.length) return resolve(new File([compressdBlob], file.name, { type: file.type, lastModified: file.lastModified })); // 有元数据的时候, 将元信息合并到压缩图片里 insertEXIF(compressdBlob, exif) .then((newBlob) => resolve(new File([newBlob], file.name, { type: file.type, lastModified: file.lastModified }))) .catch(() => resolve(file)); }; fileReader.onerror = () => resolve(file); fileReader.readAsArrayBuffer(file); }) .catch(() => resolve(file)); }); };
2022年07月13日
2,050 阅读
13 评论
19 点赞
2022-07-13
记录部分安卓手机 input type="file"的onchange不能触发问题
今天写了一个文件上传,发现在部分安卓手机上触发不了onchange事件,代码如下:<input type="file" id="input" /> <script> // 在部分安卓手机不触发 input.onchange = () => { alert(123) } </script>经过不断测试后发现,在标签上补上accept="image/*"后可解决该问题,修复后的代码如下:<input type="file" accept="image/*" id="input" /> <script> input.onchange = () => { alert(123) } </script>
2022年07月13日
982 阅读
4 评论
12 点赞
2021-10-15
一些实用的函数
/** * @description: 校验身份证 * @param {*} * @return {*} */ export const validateIDCard = value => /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(value); /** * @description: 校验支付宝账号 * @param {*} * @return {*} */ export const validateAlipay = value => /^1\d{10}$|^[a-zA-Z\d._-]*\@[a-zA-Z\d.-]{1,10}\.[a-zA-Z\d]{1,20}$/.test(value); /** * @description: 校验银行卡 * @param {*} * @return {*} */ export const validateBankCode = value => /^\d{13,19}$/.test(value); /** * @description: 校验手机号 * @param {*} * @return {*} */ export const validatePhone = value => /^1\d{10}$/.test(value); /** * @description: 函数节流 * @param {*} * @return {*} */ export const throttle = function (fn, delay = 1000) { let prev = 0; return function () { const now = Date.now(); if (now - prev > delay) { fn.apply(this, arguments); prev = Date.now(); } } } /** * @description: 获取随机字符串 * @param {*} * @return {*} */ export const randomString = () => Math.random().toString(36).substr(2); /** * @description: 将 BASE64 转换文件 * @param {*} * @return {*} */ export const dataURLtoFile = (dataurl, filename) => { const arr = dataurl.split(','); const mime = arr[0].match(/:(.*?);/)[1]; if (!filename) filename = `${Date.parse(new Date())}.jpg`; const bstr = window.atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], filename, { type: mime }); } /** * @description: 压缩图片 * @param {*} * @return {*} */ export const compressImg = file => { const fileSize = parseFloat(Number.parseInt(file.size, 10) / 1024 / 1024).toFixed(2); const reader = new FileReader(); reader.readAsDataURL(file); return new Promise((resolve) => { reader.onload = e => { const img = new Image(); img.src = e.target.result; img.onload = () => { const w = img.width; const h = img.height; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); let base64; canvas.setAttribute('width', w); canvas.setAttribute('height', h); ctx.drawImage(img, 0, 0, w, h); if (fileSize <= 1) { base64 = canvas.toDataURL(file.type, 1); } else if (fileSize <= 3) { base64 = canvas.toDataURL(file.type, 0.8); } else if (fileSize <= 5) { base64 = canvas.toDataURL(file.type, 0.5); } else { base64 = canvas.toDataURL(file.type, 0.1); } let fileName = file.name; fileName = fileName.replace(/^(.+)\.(.+)$/, (fullName, name, suffix) => name + Math.floor(Math.random() * (9999 - 1000) + 1000) + '.' + suffix); resolve(dataURLtoFile(base64, fileName)); }; }; }); }
2021年10月15日
1,853 阅读
16 评论
21 点赞
2021-08-28
Linux系统下使用iftop结合iptables服务解决带宽被恶意请求的问题
起因最近博客总时不时的打不开,或者是打开巨慢。打开宝塔面板,发现能登录,但是登录非常缓慢,最后挤进来后,看到流量可视化图,上行一直流量一直居高不下。服务器带宽是2兆的,上行200kb,说明直接将整个服务器带宽占满,导致服务器无法访问排查Linux下使用iftop工具结合iptables服务来解决带宽资源被恶意请求满的问题,主要通过2个步骤来实现使用iftop 工具查出来是哪些个IP地址在请求主机的带宽资源,找出耗带宽的元凶找出耗带宽的IP地址或者段,分析是out方向还是in方向,使用iptables规则来进行控制首先安装 iftop 工具yum install flex byacc libpcap ncurses ncurses-devel libpcap-devel接着输入 iftop -n 运行,运行后会出现以下界面左侧表示自己的服务器的内网ip中间的<= =>这两个左右箭头,表示的是流量的方向右侧表示请求或发送的ip最右侧表示流量信息从上面图中可以看到,排行第一的流量消耗最大,并且是右箭头。反映出本地内网ip正朝着62.210.177.44这个ip疯狂发送数据,导致服务器带宽占满,无法访问,知道这个ip后,就可以利用 iptables 屏蔽这个ip禁止某个IP访问服务器iptables -I INPUT -s 地址 -j DROP禁止服务器访问此IPiptables -A OUTPUT -d 地址 -j DROP上面图中主要流量是从out方向出去的,那就直接在OUT方向设置策略iptables -A OUTPUT -d 62.210.177.44 -j DROP屏蔽掉后,在看流量统计已经恢复到了正常状态相关参数说明iptables相关参数:禁止此IP访问服务器:iptables -I INPUT -s 1.2.3.4 -j DROPiptables -A INPUT -s 1.2.3.4 -j DROP禁止服务器访问此IP:iptables -A OUTPUT -d 1.2.3.4 -j DROP如果要封某个网段:iptables -I INPUT -s 1.2.3.0/24 -j DROP清空屏蔽IP:iptables -t filter -D INPUT -s 1.2.3.4 -j DROPiptables -t filter -D OUTPUT -d 1.2.3.4 -j DROP一键清空所有规则:iptables -F查看:iptables -L INPUTiptables -L处理IP碎片数量,防止攻击,允许每秒100:iptables -A FORWARD -f -m limit --limit 100/s --limit-burst 100 -j ACCEPT设置ICMP包过滤,允许每秒1个包,限制触发条件是10个包:iptables -A FORWARD -p icmp -m limit --limit 1/s --limit-burst 10 -j ACCEPT
2021年08月28日
1,946 阅读
14 评论
15 点赞
2021-08-04
Vue 2与Vue 3在自定义组件v-model上的区别
在vue开发中,通常会对一个自定义的组件进行封装,并实现v-model双向绑定功能在 Vue 2 中,通常这样实现父组件<template> <Child v-model="number"></Child> </template> <script> export default { data() { return { number: 0 } }, components: { Child: () => import("./Child.vue") } } </script>子组件<template> <button @click="handleClick">{{ value }}</button> </template> <script> export default { props: { value: Number }, methods: { handleClick() { // 通过emit一个input事件出去,实现 v-model this.$emit('input', this.value + 1) } } } </script>在 vue 3 中,通过这样实现父组件<template> <Child v-model="number"></Child> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; export default defineComponent({ setup() { const number = ref(0); return { number }; }, }); </script>子组件<template> <button @click="handleClick">{{ value }}</button> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ props: { // 更换成了 modelValue modelValue: Number }, setup(props, { emit }) { // 关闭弹出层 const handleClick = () => emit('update:modelValue', props.modelValue + 1); return { handleClick }; }, }); </script>
2021年08月04日
1,225 阅读
15 评论
20 点赞
2021-07-28
Vue.set与this.$set源码
Vue.set()和this.$set()应用的场景在 Vue 2.X 项目开发中,有时候需要对数组进行修改,或是对对象新增一个属性,但是发现页面并不会同步更新。例如:const vm = new Vue({ data: { arr: [1, 2], obj: { a: 3 } } }); vm.$data.arr[0] = 3; // 页面不会发生改变 vm.$data.obj.b = 3; // 页面不会发生改变此时就需要使用到 Vue.set() 或 this.$set()。这个2个的使用方法一样,不过一个是挂载在Vue身上,一个挂载在Vue.prototype上// Vue.set import { set } from '../observer/index' Vue.set = set // this.$set import { set } from '../observer/index' Vue.prototype.$set = se使用Vue.set或this.$set来实现页面不更新的问题:const vm = new Vue({ data: { arr: [1, 2], obj: { a: 3 } } }); // 修改数组 Vue.set(vm.$data.arr, 0, 3); vm.$set(vm.$data.arr, 0, 3); // 对象新增属性 Vue.set(vm.$data.obj, 'b', 3); vm.$set(vm.$data.obj, 'b', 3);set函数的源代码../observer/indexfunction set (target: Array<any> | Object, key: any, val: any): any { // isUndef(target):用于判断传入的target是否是undefined或null // isPrimitive(target):用于判断传入的target是否是原始数据类型 // 如果是 null 或者是 undefined 时,抛出异常 if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 如果传入了一个数组,并且传入的key是数组的有效值 if (Array.isArray(target) && isValidArrayIndex(key)) { // 用于设置数组的最大length // Math.max(target.length, key):取数组的length和key二者中的最大值 target.length = Math.max(target.length, key) // 直接调用数组身上的splice方法,因为Vue里变异了数组方法,调用会触发dep.notify() target.splice(key, 1, val) return val } // 接下来就是对象的判断了 // 如果传入的 key 在原对象中,说明已经是响应式了,直接修改即可 if (key in target && !(key in Object.prototype)) { target[key] = val return val } // 这里到最后的代码,都是对象不在原对象上,是新增的属性值 const ob = (target: any).__ob__ // 如果是Vue实例对象,或根数据对象,则抛出警告 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // 如果没有 __ob__ 表示当前 target 不是响应式的,那么直接赋值 if (!ob) { target[key] = val return val } // 剩下就是给响应式对象增加一个 key 新属性,并通知更新 defineReactive(ob.value, key, val) ob.dep.notify() return val }
2021年07月28日
650 阅读
2 评论
12 点赞
2021-07-06
Vue 二次封装组件的方法
介绍有的时候我们全局引用 UI 框架(类似于 ElementUI)的时候,有些简单的组件我们想进行二次封装,并确保能传递所需的 props 和事件,这时候就需要用到 vue 实例上的两个属性:$attrs 和$listeners。代码二次封装的组件<template> <!-- v-bind="$attrs":会将传递过来的属性属性值排放在这里,可以看成react里的{...props}展开运算符类似 v-on="$listeners":父组件绑定的@事件会传递到这里 --> <el-input v-bind="$attrs" v-on="$listeners"></el-input> </template>父组件使用这个组件<template> <HelloWorld placeholder="请输入内容111" @input="handleInput"/> </template> <script> export default { methods: { handleInput() { console.log(1); } }, components: { HelloWorld: () => import('@/components/HelloWorld.vue') } }; </script>
2021年07月06日
853 阅读
5 评论
8 点赞
2021-07-02
HTTP缓存
前端缓存前端缓存可分为两大类:http缓存和浏览器缓存。我们今天重点讲的是http缓存,所以关于浏览器缓存大家自行去查阅。下面这张图是前端缓存的一个大致知识点:HTTP 缓存策略分为两种: 强缓存 和 协商缓存 ,这两种缓存策略都是服务端设置 HTTP Header 来实现的(一)强缓存强缓存的意思很简单,直接从浏览器缓存过的本地进行读取,不会去请求服务器。例如请求一个图片,当缓存后,第二次访问,直接从本地去拿,不会再去请求这个资源,可以节省服务器资源。可以通过三种方式来设置强缓存Expires:服务端在响应头中设置一个 GMT 格式的到期时间。客户端的本地时间小于响应头的 Expires 时间,那么会从本地进行读取,不会去请求服务器。如果超过了,那么就去请求服务器去获取最新资源。但是就是因为根据本地时间进行判断,本地时间可以随便修改,所以这种缓存机制有漏洞,会与服务端时间有偏差,为了解决这个问题,就出现了下面的 Cache-contorlCache-control:他和Expires不一样,Expires是直接设置一个时间戳就行了,而Cache-control可以设置下面这几种属性:max-age:这个用于设置一个滑动时间,例如设置 max-age=30 表示客户端时间向后滑动30秒,在这30秒内都是强缓存,不会去请求服务器s-maxage:这个和上面的一样,只不过这个设置的是代理服务器的缓存时间privte:这个表示缓存只能被客户端的浏览器缓存,不能被代理服务器缓存public:这个表示缓存既可以被浏览器缓存,也可以被代理服务器缓存no-store:这个属性表示不缓存,在任何情况下,都是与服务器进行最新的交互no-cache:这个并非不缓存的意思,这个表示强制进行协商缓存,会在下面描述(二)协商缓存协商缓存表示在使用本地的缓存之前,会先向服务器发一个请求,与服务器协商当前浏览器的缓存是否已经过期了,如果没过期,那么就使用本地的资源,如果过期了就去请求最新资源。协商缓存主要是解决强缓存资源不能及时更新的问题,协商缓存服务端可以通过2种设置来实现:第一种:last-modified 配合 If-Modified-Since例如,客户端请求一个 03.jpg,服务端接收到这个请求后,会读取这个文件的最后修改时间,然后设置到响应头中,设置的参数就是 last-modified,参数值是文件最后修改的时间戳。客户端第二次请求 03.jpg 这个文件的时候,会带上一个 If-Modified-Since 参数,服务端能拿到这个参数与last-modified进行比对,如果一致,那么就返回304状态,否则就去请求最新的文件,使用nodejs实现这个代码:(注意协商缓存需要设置Cache-Control为no-cache,表示设置成协商缓存)... // 判断客户端请求的是03这个图片 if(pathname === '/img/03.jpg') { // 读取 03 图片的最后修改时间 const { mtime } = fs.statSync("./img/03.jpg") // 判断客户端发送过来的if-modified-since是否与mtime一致,如果一致就直接返回304 if(req.headers['if-modified-since'] === mtime.toUTCString()) { res.statusCode = 304 res.end() } else { // 如果不一致,那么就请求最新的资源返回给客户端 const data = fs.readFileSync("./img/03.jpg") // 这2句代码是设置协商缓存 res.setHeader("last-modified", mtime.toUTCString()) res.setHeader("Cache-Control", "no-cache") res.end(data) } } ...上面的 last-modified 配合 If-Modified-Since在使用时有些弊端,例如将03.jpg修改成04.jpg,再改回03.jpg。此时这个文件其实是没有变化的,但是最后修改时间更改了,因此客户端就需要重新请求,因此就出现了下面的第二种使用Etag的方式第二种:Etag 配合 If-None-MatchEtag实现的方式服务端是为文件生成一个指纹,类似于MD5字符串。接着响应头中塞进 Etag 参数,参数的值就是计算出的字符串,客户端接收到后,第二次请求会带上一个 If-None-Match 的参数,接着服务端和上面第一种方式一样进行比对,nodejs的实现代码如下:// 引入 etag 模块 const etag = reqiure("etag") ... if(pathname === '/img/03.jpg') { const data = fs.readFileSync("./img/03.jpg") // 获取生成的etag字符串 const etag = etag(data) // 判断客户端发送的 If-None-Match 与服务端是否一致 if(req.headers['if-none-match'] === etag) { res.statusCode = 304 res.end() } else { // 如果不一致,那么就请求最新的资源返回给客户端 const data = fs.readFileSync("./img/03.jpg") // 这2句代码是设置协商缓存 res.setHeader("etag", etag) res.setHeader("Cache-Control", "no-cache") res.end(data) } } ...
2021年07月02日
1,027 阅读
5 评论
15 点赞
2021-06-28
Vue底层判断标签的性能优化方法
在vue中,如果写div、span等正常的html标签,vue会解析成传统的html标签,但当写不是这些标签的时候,vue会认为他是一个组件,例如:<Custom></Custom>。是如何做到这种判断的呢,首先自己来实现一个这样的判断const tags = 'div,span,img,a'.split(",") function checkTag(tag) { return tags.some(item => item === tag) } console.log(checkTag('Custom')); // false console.log(checkTag('div')); // true console.log(checkTag('a')); // true这里的实现方案有很多,可以用for、some、forEach等,但是都是离不开循环,思考这样的一个问题,传入一个a,a在字符串最后一个位置,所以会循环4次来判断是否包含a,如果页面上的标签极多,甚至会有上万次的循环再来查看Vue实现这个的方式,大致源码如下const tags = 'div,span,img,a' function makeMap(str) { var map = Object.create(null); var list = str.split(','); for (var i = 0; i < list.length; i++) { map[list[i]] = true; } return function (val) { return map[val]; } } const isHtmlTag = makeMap(tags) isHtmlTag('div') isHtmlTag('Custom') isHtmlTag('a')这里可以看出,实现出一个柯里化函数,并且也是一个闭包状态。此时,在第一次调用时,会循环一次,后续不论在去判断什么标签,都不会再去循环,因为第一次的循环结果利用闭包已经存在了内存里,这就是闭包能带来的性能优化
2021年06月28日
647 阅读
4 评论
10 点赞
2021-06-28
原生JS模拟虚拟dom,虚拟dom转真实dom
<div id="root" class="aaa"> <div class="title">11</div> <div class="title">222</div> <ul> <li>111</li> <li>222</li> <li>333</li> </ul> </div> <script type="text/javascript"> class VNode { constructor(name, attrs, value, type) { this.name = name this.attrs = attrs this.value = value this.type = type this.children = [] } addChildren(node) { this.children.push(node) } } function create(node) { let _vnode = null if (node.nodeType === 1) { const attributes = [...node.attributes] const attrs = attributes.reduce((prev, curr) => { prev[curr.name] = curr.value return prev }, {}); _vnode = new VNode(node.nodeName.toLowerCase(), attrs, null, node.nodeType) for (let item of node.childNodes) { _vnode.addChildren(create(item)) } } else if (node.nodeType === 3) { _vnode = new VNode(node.nodeName.toLowerCase(), null, node.nodeValue, node.nodeType) } return _vnode } let vnode = create(document.querySelector("#root")) console.log(vnode); function parse(node) { let nodeEl = null if (node.type === 1) { nodeEl = document.createElement(node.name) for (let key in node.attrs) nodeEl.setAttribute(key, node.attrs[key]) node.children.forEach(item => { nodeEl.appendChild(parse(item)) }); } else { nodeEl = document.createTextNode(node.value) } return nodeEl } let dom = parse(vnode) console.log(dom); </script>
2021年06月28日
759 阅读
4 评论
6 点赞
2021-06-28
Vue 2.X 拓展图片预览插件
Preview.jsimport ToastComponent from './Toast.vue' const Toast = {} Toast.install = Vue => { const ToastConstructor = Vue.extend(ToastComponent) const instance = new ToastConstructor() Vue.prototype.$Toast = function (options) { if (!options) { return console.error('Please Pass In The Data / 请传入内容') } if (options.constructor !== Object) { return console.error('Please Pass In The Object Format / 请传入对象格式') } options.msg = options.msg || '默认消息' options.duration = options.duration || 3000 if (document.getElementsByClassName('toast').length === 0) { instance.$mount(document.createElement('div')) document.body.appendChild(instance.$el) instance.msg = options.msg instance.visible = true setTimeout(() => { instance.visible = false document.body.removeChild(instance.$el) }, options.duration) } } } export default ToastPreview.vue<template> <transition name="bounce"> <div class="toast" v-show="visible"> <span>{{ msg }}</span> </div> </transition> </template> <script> export default { data() { return { msg: '', visible: false } } } </script> <style lang="css" scoped> .bounce-enter-active { animation: bounce-in 0.5s linear; } .bounce-leave-active { animation: bounce-in 0.5s linear reverse; } @keyframes bounce-in { 0% { transform: translate3d(-50%, 0, 0) scale(0); } 25% { transform: translate3d(-50%, 0, 0) scale(0.55); } 50% { transform: translate3d(-50%, 0, 0) scale(1); } 75% { transform: translate3d(-50%, 0, 0) scale(0.85); } 100% { transform: translate3d(-50%, 0, 0) scale(1); } } .toast { position: fixed; z-index: 5201314; top: 50%; left: 50%; transform: translate3d(-50%, 0, 0); background: rgba(0, 0, 0, 0.75); padding: 0 32px; height: 50px; line-height: 50px; color: #fff; user-select: none; border-radius: 4px; box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2); font-size: 14px; letter-spacing: 1px; } </style>
2021年06月28日
966 阅读
3 评论
16 点赞
2021-06-28
chrome闭包断点
首先呢,必须搞清楚闭包这个概念:闭包其实是一个特殊的对象,他由两部分组成,一个是执行上下文(代号A),以及在该执行上下文中创建的函数(代号B),当B执行时,如果访问了A中变量对象的变量,那么闭包就产生了。下面这个例子会产生闭包function add(x) { return function _add(y){ return x + y } } var sum = add(2)(3)上面这个例子毫无疑问肯定是 5那么使用chrome浏览器单步调试一下将断点打到 var sum = add(2)(3) 这里Brealpoints 表示当前打的断点是什么CallStack 表示当前函数调用栈Scope 表示当前作用域Scope 里面的 Local 表示当前活动对象Scope 里面的 Closure 表示闭包图上一直点击红色箭头所指按钮表示单点调试,代码一步一步执行,等执行到return那里的时候,右侧 Scope 里会多出一个 Closure 属性,括号内的add表示当前闭包函数是add,闭包产生的值是 x = 2有时候调试会发现没有这个 Closure 属性,例如下面这个例子const obj = { name: '我是需要被绑定改变this指向的对象' } function fn() { } Function.prototype.bind = function (sbbbbb) { const bind = Symbol(); sbbbbb[bind] = this return function () { sbbbbb[bind](); } } fn.bind(obj);这个例子是 bind 改变 this 指向的实现方式,将断点打向 fn.bind(obj) 接着看动态图在动画的过程中,当走到 sbbbbb[bind]() 的时候,Local 里面会多出一个 Return value: ƒ () 的属性,展开这个属性,会发现有个 [[Scopes]] 的属性,接着展开 [[Scopes]],里面有个 Closure (Function.bind) {sbbbbb: {…}, bind: Symbol()} 就表示当前产生的闭包
2021年06月28日
910 阅读
7 评论
7 点赞
1
2
...
7