-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 833 KB
/
content.json
1
{"meta":{"title":"over58","subtitle":"A fast, simple & powerful blog framework powered by Node.js.","description":"技术博客","author":"over58","url":"https://over58.github.io","root":"/"},"pages":[],"posts":[{"title":"微信小程序笔记","slug":"微信小程序笔记","date":"2021-02-26T06:07:21.810Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2021/02/26/微信小程序笔记/","link":"","permalink":"https://over58.github.io/2021/02/26/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%AC%94%E8%AE%B0/","excerpt":"","text":"模板 使用模板的时候除了wxml需要include过去,wxss需要在使用这个模板的wxss文件中@import ··· 如何使用模板 1234567<import src="../../components/list-item/list-item.wxml"/><view wx:for="{{list}}" wx:key="index"> <template is="listItem" data="{{...item}}"></template></view> //需要单独讲 wxss 文件引入 1@import '/components/list-item/list-item.wxss'; 组件使用12345{ "usingComponents": { "new-list-item": "/components/new-list-item/new-list-item" }} 传参123456properties: { item: { type: Object, value: {} } },","categories":[],"tags":[]},{"title":"批量执行异步代码","slug":"批量执行异步代码","date":"2021-02-26T06:07:21.810Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2021/02/26/批量执行异步代码/","link":"","permalink":"https://over58.github.io/2021/02/26/%E6%89%B9%E9%87%8F%E6%89%A7%E8%A1%8C%E5%BC%82%E6%AD%A5%E4%BB%A3%E7%A0%81/","excerpt":"","text":"123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263/** * @description 批量执行异步代码,同一时间只能执行指定最大数量的异步操作 */class BatchRequest { constructor(max = 3) { this.queue = []; this.max = max } addRequest(fn) { this.queue.push(fn) } run() { let count = 0 let timer = setInterval(() => { if (this.queue.length) { Array.from({ length: Math.max(0, this.max - count) }).forEach(() => { let fn = this.queue.shift() if (fn) { count++ fn().then(() => { count-- }) } }) } else { clearInterval(timer) } }, 10) }}function createPromise(time, text) { return function () { console.log('执行中~') return new Promise(function (resolve) { setTimeout(function () { console.log(text) resolve(text) }, time) }) }}var client = new BatchRequest(5);client.addRequest(createPromise(5000, '1000'))client.addRequest(createPromise(5000, '2000'))client.addRequest(createPromise(3000, '3000'))client.addRequest(createPromise(3000, '3000'))client.addRequest(createPromise(3000, '3000'))client.addRequest(createPromise(3000, '3000'))client.addRequest(createPromise(3000, '3000'))client.addRequest(createPromise(3000, '3000'))client.addRequest(createPromise(4000, '2000-2'))client.addRequest(createPromise(2000, '1000-2'))client.addRequest(createPromise(3000, '3000-2'))client.run()","categories":[],"tags":[]},{"title":"引导弹窗","slug":"引导弹窗","date":"2021-02-26T06:07:21.810Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2021/02/26/引导弹窗/","link":"","permalink":"https://over58.github.io/2021/02/26/%E5%BC%95%E5%AF%BC%E5%BC%B9%E7%AA%97/","excerpt":"","text":"Js项目中有时候需要为新用户进行引导操作是=时,需要弹窗来告诉新用户如何进行一步步操作。之前的时候用过introjs,现在看了下实现原理,主要是去更改z-index来实现重点元素“凸出”到蒙层上面。最近看了一篇文章,学到了一个感觉非常新颖的思路,利用globalCompositeOperation的思路将蒙层中需要凸显的那块儿给“抠掉”。 使用方法在需要重点凸显的元素上直接加id: “intro-item”, 所以目前同一个页面只支持一个元素凸显出来。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899<template> <div v-show="show" class="intro-mask"> <canvas ref="canvas" /> <div class="content"> <slot /> </div> </div></template><script>/** * 目的:用于引导弹窗中需要凸显某个元素 * 用法:需要凸显的元素添加id 为intro-item,然后引入intro-mask就行,可以自己在slot中添加额外的元素 * 注意:需要在intro-item渲染出来之后,在启用这个组件 */import { onMounted, ref, toRefs, nextTick } from 'vue'export default { props: { backgroundColor: { type: String, default: 'rgba(0, 0, 0, 0.7)', }, expandX: { type: Number, default: 5, }, expandY: { type: Number, default: 5, }, }, setup(props, { emit }) { let show = ref(true) const canvas = ref(null) const draw = function() { const introItem = document.getElementById('intro-item') if (introItem) { const clientWidth = document.documentElement.clientWidth const clientHeight = document.documentElement.clientHeight canvas.value.width = clientWidth canvas.value.height = clientHeight let { height = 0, width = 0, top = 0, left = 0 } = introItem.getBoundingClientRect() // 向外拓展凸显元素的返回 width = Math.min(clientWidth, width + props.expandX * 2) height = Math.min(clientHeight, height + props.expandY * 2) left = Math.max(0, Math.min(left - props.expandX, left)) top = Math.max(0, Math.min(top - props.expandY, top)) const ctx = canvas.value.getContext('2d') ctx.fillStyle = props.backgroundColor ctx.fillRect(0, 0, canvas.value.width, canvas.value.height) ctx.globalCompositeOperation = 'xor' ctx.fillStyle = '#fff' ctx.fillRect(left, top, width, height) emit('transfer-data', { height, width, top, left }) } else { show = false console.warn('未找到#intro-item元素') } } onMounted(function() { draw() }) return { canvas, show, } },}</script><style lang="less" scoped>.intro-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; canvas { width: 100%; height: 100%; } .content { color: #000; position: absolute; top: 0; left: 0; right: 0; bottom: 0; }}</style> 运行截图","categories":[],"tags":[]},{"title":"vue中使用highcharts和echarts的实践","slug":"vue中使用highcharts和echarts的实践","date":"2021-02-26T06:07:21.786Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2021/02/26/vue中使用highcharts和echarts的实践/","link":"","permalink":"https://over58.github.io/2021/02/26/vue%E4%B8%AD%E4%BD%BF%E7%94%A8highcharts%E5%92%8Cecharts%E7%9A%84%E5%AE%9E%E8%B7%B5/","excerpt":"","text":"我从事云平台前端的开发,由于项目的需要,写了一个云监控Monitor项目。起初,我选择了 Echarts, 原因很简单,中文文档,定制化能力也比较好。事实上,使用了一年多来,用的也是蛮顺手的。但是,这是一个监控项目,里面存在大量的图,每个图有着巨量的数据,而且每条line的名字也超长。带来的问题就是,页面及其卡顿,即使是我限制了每个图中的数据量,仍然无法降低,页面无操作的情况下,CPU仍然占据20%以上(八个图,每个图平均10条line)。网上找了一通,都指向z_render()函数,echarts会不停的执行这个函数。实在是不堪忍受这样的卡顿,在进行了充分调研的情况下,果断换了highcharts。 经过实际项目测试:同等数量的情况下,highcharts和echarts 所占的内存相近,但CPU占比很低。举一个栗子,一个页面上有八个图,每个图平均大概10条线,当页面渲染完成,无任何鼠标点击的时候,echarts的CPU占比稳定在20%左右,highcharts始终在1%以下。","categories":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/categories/graph/"}],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"},{"name":"highcharts","slug":"highcharts","permalink":"https://over58.github.io/tags/highcharts/"},{"name":"echarts","slug":"echarts","permalink":"https://over58.github.io/tags/echarts/"}],"author":"徐勇超"},{"title":"js的事件模型","slug":"js的事件模型","date":"2021-02-26T06:07:21.774Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2021/02/26/js的事件模型/","link":"","permalink":"https://over58.github.io/2021/02/26/js%E7%9A%84%E4%BA%8B%E4%BB%B6%E6%A8%A1%E5%9E%8B/","excerpt":"引言其实,说起js的事件模型,有点基础的都能说几句, 分为三个阶段, 捕获-事件处理-冒泡,巴拉巴拉…。但是这并没有考虑到我们 “伟大” 的IE,有点考虑的不够全面。接下来系统性的详细的讲一波儿 事件模型的前世今生故事源于传说中的浏览器大战,微软的 IE 和网景的Netspace Navigator。IE的事件流是冒泡 从里面往上面冒, netscape是从外部元素往内部元素捕获;后来出来了个W3C委员会,想要统一,为了兼容,宣布了后来的W3C事件模型(捕获-事件处理-冒泡),从此天下一统。","text":"引言其实,说起js的事件模型,有点基础的都能说几句, 分为三个阶段, 捕获-事件处理-冒泡,巴拉巴拉…。但是这并没有考虑到我们 “伟大” 的IE,有点考虑的不够全面。接下来系统性的详细的讲一波儿 事件模型的前世今生故事源于传说中的浏览器大战,微软的 IE 和网景的Netspace Navigator。IE的事件流是冒泡 从里面往上面冒, netscape是从外部元素往内部元素捕获;后来出来了个W3C委员会,想要统一,为了兼容,宣布了后来的W3C事件模型(捕获-事件处理-冒泡),从此天下一统。 事件的三种模型DOM0(原始事件模型)12345<input id="myButton" type="button" value="Press Me" onclick="alert('thanks');" >ordocument.getElementById("myButton").onclick = function () {alert('thanks');} 通常情况下事件监听函数如果返回一个值并且是false,则会阻止浏览器执行默认的动作 优点: 所有浏览器都兼容 缺点: 代码耦合严重 事件监听器只能有一个,重复赋值,后面会覆盖前面的 没有事件的冒泡、委托等机制完成更为负载的情况 IEIE将event作为window的一个属性。IE的事件模型只有两步,执行处理函数,然后冒泡。添加和移除事件监听的方式 1234attachEvent( "eventType","handler")//其中evetType为事件的类型,如onclick,注意要加’on’。detachEvent("eventType","handler" ) DOM2事件分为三个阶段: 捕获-处理目标-冒泡(IE8以及更早版本不支持DOM事件流) 捕获阶段: 事件被从document一直向下传播到目标元素,在这过程中依次检查经过的节点是否注册了该事件的监听函数,若有则执行。 事件处理: 事件到达目标元素,执行目标元素的事件处理函数 冒泡: 事件从目标元素上升一直到达document,同样依次检查经过的节点是否注册了该事件的监听函数,有则执行。 事件对象常用对象和属性 DOM事件模型中的事件对象常用属性: type用于获取事件类型 currentTarget 当前正在处理的事件的节点,在事件捕获或冒泡阶段 target获取事件目标 stopPropagation()阻止事件冒泡 preventDefault()阻止事件默认行为 keyCode:按下的键的值; stopImmediatePropagation() (DOM3)阻止任何事件的运行;详情看http://39.105.159.58:8080/2019/09/16/stopImmediatePropagation%E5%92%8CstopPropagation%E7%9A%84%E5%8C%BA%E5%88%AB/ IE事件模型中的事件对象常用属性: type用于获取事件类型 srcElement获取事件目标 cancelBubble阻止事件冒泡 returnValue阻止事件默认行为 事件对象中和定位相关的属性 x/y与clientX/clientY值一样,表示距浏览器可视区域(工具栏除外区域)左/上的距离; pageX/pageY,距页面左/上的距离,它与clientX/clientY的区别是不随滚动条的位置变化; screenX/screenY,距计算机显示器左/上的距离,拖动你的浏览器窗口位置可以看到变化; layerX/layerY与offsetX/offsetY值一样,表示距有定位属性的父元素左/上的距离。 事件委托/代理1234567891011121314151617181920<ul id="parent"> <li class="child">1</li> <li class="child">2</li> <li class="child">3</li></ul><script type="text/javascript"> //父元素 var dom= document.getElementById('parent'); //父元素绑定事件,代理子元素的点击事件 dom.onclick= function(event) { var event= event || window.event; var curTarget= event.target || event.srcElement; if (curTarget.tagName.toLowerCase() == 'li') { //找到被代理的节点 //事件处理 } }</script> 优点: 节省内存占用,减少事件注册 新增子对象时无需再次对其绑定事件,适合动态添加元素 eventBus的事件模型123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566/** * 完整的思路就是,使用一个map存储 type:处理函数 * 有两种存储形式 * type: function * type: [function, funciton, ...] * 删除和添加的时候考虑到这两种情况就行了 */class EventEmitter{ constructor () { this._events = this._events || new Map() this._maxListeners = this._maxListeners || 10 }}EventEmitter.prototype.emit = function(type, ...args){ let handler = this._events.get(type) if (Array.isArray(handler)) { for(let i=0;i<handler.length;i++) { if(args.length > 0) { handler[i].apply(this, args) }else { handler[i].call(this) } } }else { if(args.length >0) { handler.apply(this, args) } else { handler.call(this) } } return true}EventEmitter.prototype.addListener = function(type, fn) { const handler = this._events.get(type) if(!handler) { this._events.set(type, handler) }else if(handler && typeof handler === 'function') { this._events.set(type, [handler, fn]) }else { handler.push(fn) }}EventEmitter.prototype.removeListener = function(type, fn) { const handler = this._events.get(type) if(handler && typeof handler === 'function') { this._events.delete(type) } else { let position = -1 for (let i=0;i<handler.length;i++) { if (handler[i] === fn) { position = i } } if (position !== -1) { handler.splice(position, 1) if (handler.length === 1) { this._events.set(type, handler[0]) } } else { return this } }}","categories":[],"tags":[{"name":"事件模型","slug":"事件模型","permalink":"https://over58.github.io/tags/%E4%BA%8B%E4%BB%B6%E6%A8%A1%E5%9E%8B/"}],"author":["徐勇超"]},{"title":"golang学习笔记","slug":"golang学习笔记","date":"2021-02-26T06:07:21.770Z","updated":"2021-02-26T06:07:21.770Z","comments":true,"path":"2021/02/26/golang学习笔记/","link":"","permalink":"https://over58.github.io/2021/02/26/golang%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","excerpt":"数组 数组是值类型 数组的三种定义方式, var, : , […]int 函数的参数传递只有一种传递,就是值传递 slice slice是一个array的view,本身不存储数据,slice可以向后扩展(cap中进行拓展),不可以向前拓展 Slice: ptr, len, cap 如果添加元素时大于cap, 系统会分配一个新的array. 由于值传递的关系,必须要接受append的返回值, s = append(s, val) 创建 S1 = []int{1,2,3,4,5} Append(s1, val) make([]int, 16 , 32) type, len, cap 删除 1s2 = append(s2[:3], s2[4:]... )","text":"数组 数组是值类型 数组的三种定义方式, var, : , […]int 函数的参数传递只有一种传递,就是值传递 slice slice是一个array的view,本身不存储数据,slice可以向后扩展(cap中进行拓展),不可以向前拓展 Slice: ptr, len, cap 如果添加元素时大于cap, 系统会分配一个新的array. 由于值传递的关系,必须要接受append的返回值, s = append(s, val) 创建 S1 = []int{1,2,3,4,5} Append(s1, val) make([]int, 16 , 32) type, len, cap 删除 1s2 = append(s2[:3], s2[4:]... ) Map定义12345678910111213var s = map[string]string ={ "name": "tom", "age": 20, "sex": "man"}//简单mapmap[K]V //复合mapmap[K]map[K2]V//空mapmake(map[string]string) 取值123courseNamem, ok : = m['cause']如果没有取到对应的map的值,会自动的返回空, 为了防止这种情况,添加了ok来判断map是不是真的有这个key 遍历range map的key 使用哈希表,必须可以比较相等 除了slice,map,funcion 的内建类型都可以作为key 面向对象结构体 只支持封装,不支持继承和多态 没有class,只有struct 1234567891011121314type treeNode struct{ value int left, right *treeNode}func (node treeNode) print() { fmt.Println(node.value)}//可以更改原值, 通过传个地址进去func (node *treeNode) setValue (val int) { node.vlaue = val} nil指针也可以调用方法","categories":[],"tags":[]},{"title":"移动端适配方案","slug":"移动端适配方案","date":"2021-02-25T16:38:16.000Z","updated":"2021-02-25T16:38:16.000Z","comments":true,"path":"2021/02/25/移动端适配方案/","link":"","permalink":"https://over58.github.io/2021/02/25/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E9%80%82%E9%85%8D%E6%96%B9%E6%A1%88/","excerpt":"","text":"DPR = 设备像素(物理像素、不变的) / 设备独立像素(相对像素) screen.width指的是显示器水平方向的像素,不随着我们浏览器的窗口变化而变化 chrome上面进行移动端开发的时候, screen.width表示的就是选择的响应式窗口的宽度 window.innerWidthwindow.innerWidth指的是浏览器窗口的宽度,是可以变化的,所以使用的是CSS像素。 可以发现在100%缩放情况下,window.innerWidth的值为1192,window.innerHeight的值为455,接着我们尝试将放大到200%,发现window.innerWidth的值变成了原来的1/2 解释如下先看看下面的这个公式, 我们一直拿的都是相对像素。 DPR = 设备像素(物理像素、不变的) / 设备独立像素(相对像素) 当我们放大的时候,实际上将window.devicePixelRatio 放大为原来的2倍。设备像素不变, 那么我们拿到的相对像素的值就变得顺理成章了 几个常见单位的对比 window.innerWidth指的是浏览器窗口的宽度(包含滚动条),用CSS像素(相对像素)衡量,会变 document.documentElement.clientWidth指的是viewport的宽度,等于浏览器窗口的宽度(不包含滚动条), 用CSS像素(相对像素)衡量 会变 document.documentElement.offsetWidth指的是html的宽度,默认为浏览器窗口的宽度 会变 document.documentElement.offsetHeight指的是html的高度,没有显示给html指定高度的话,为0 不会变 ”会变“ 指的是随着window.devicePixelRatio变化而变化","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"css中的各种单位","slug":"css中的各种单位","date":"2021-02-25T15:25:42.000Z","updated":"2021-02-25T15:25:42.000Z","comments":true,"path":"2021/02/25/css中的各种单位/","link":"","permalink":"https://over58.github.io/2021/02/25/css%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E5%8D%95%E4%BD%8D/","excerpt":"物理像素(设备像素)表示物理设备中最小的点, 是绝对单位 px 相对单位 px / 设备像素 = dpr remhtml中font-size的值 %相对于父元素 em当前元素中font-size的值。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。 EM特点 em的值并不是固定的; em会继承父级元素的字体大小。","text":"物理像素(设备像素)表示物理设备中最小的点, 是绝对单位 px 相对单位 px / 设备像素 = dpr remhtml中font-size的值 %相对于父元素 em当前元素中font-size的值。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。 EM特点 em的值并不是固定的; em会继承父级元素的字体大小。 注意:任意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合: 1em=16px。那么12px=0.75em,10px=0.625em。为了简化font-size的换算,需要在css中的body选择器中声明Font-size=62.5%,这就使em值变为 16px*62.5%=10px, 这样12px=1.2em, 10px=1em, 也就是说只需要将你的原来的px数值除以10,然后换上em作为单位就行了。 所以我们在写CSS的时候,需要注意两点: body选择器中声明Font-size=62.5%; 将你的原来的px数值除以10,然后换上em作为单位; 重新计算那些被放大的字体的em数值。避免字体大小的重复声明。 也就是避免1.2 * 1.2= 1.44的现象。比如说你在#content中声明了字体大小为1.2em,那么在声明p的字体大小时就只能是1em,而不是1.2em, 因为此em非彼em,它因继承#content的字体高而变为了1em=12px。 vh/vw分别是视窗的高和宽, 相对于视窗,将视窗分成了100份 vmin/vmax vmin 代表vh、vw中的最小值 vmax 代表vh、vw中的最大值 frfr是一个自适应单位,被用于在一系列长度值中分配剩余空间 123456<div class="grid"> <div class="column"></div> <div class="column"></div> <div class="column"></div> <div class="column"></div></div> 12345.grid { display: grid; grid-template-columns: repeat(4, 25%); grid-column-gap: 10px;} 常常和repeat结合起来用 1repeat(number of columns/rows, the column width we want); 数字在写line-height的时候直接写数字,代表是几倍的font-size","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"webpack的hash、chunkhash、contenthash","slug":"webpack的hash、chunkhash、contenthash","date":"2021-02-25T15:22:13.000Z","updated":"2021-02-25T15:22:13.000Z","comments":true,"path":"2021/02/25/webpack的hash、chunkhash、contenthash/","link":"","permalink":"https://over58.github.io/2021/02/25/webpack%E7%9A%84hash%E3%80%81chunkhash%E3%80%81contenthash/","excerpt":"","text":"对于webpack的hash,常用于cdn缓存。我理解的是文件不变的情况下,最后打包出来的hash串也不会变。最近被问到了这是三个hash的区别,就查了一下,发现还很有讲究。 先看一下三个hash的解释: [hash] is a “unique hash generated for every build” 每次build都会变 [chunkhash] is “based on each chunks’ content” 基于每个chunk内容生产的hash值, 每次chunk内容变了,chunk生成的文件的哈希值就都变了 [contenthash] is “generated for extracted content” 基于具体的文件内容生成文件自己的hash","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"批量发送请求batchRequest","slug":"批量发送请求batchRequest","date":"2021-02-24T15:58:32.000Z","updated":"2021-02-24T15:58:32.000Z","comments":true,"path":"2021/02/24/批量发送请求batchRequest/","link":"","permalink":"https://over58.github.io/2021/02/24/%E6%89%B9%E9%87%8F%E5%8F%91%E9%80%81%E8%AF%B7%E6%B1%82batchRequest/","excerpt":"","text":"1234567891011121314151617181920212223242526272829303132333435363738394041class BatchRequest { constructor(max = 3) { this.queue = []; this.max = max } addRequest(fn) { this.queue.push(fn) } run() { let count = 0 let timer = null if (this.queue.length) { timer = setInterval(() => { Array.from({ length: Math.max(0, this.max - count) }).forEach(() => { let fn = this.queue.shift() if (fn) { count++ console.log('执行') fn().then(() => { console.log('over') count-- }) } }) }) }else{ clearInterval(timer) } }}var a = new BatchRequest()a.addRequest(fn)a.addRequest(fn)a.addRequest(fn)a.addRequest(fn)a.addRequest(fn)a.run()","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"能不能实现图片懒加载?","slug":"能不能实现图片懒加载?","date":"2021-02-24T12:28:52.000Z","updated":"2021-02-24T12:28:52.000Z","comments":true,"path":"2021/02/24/能不能实现图片懒加载?/","link":"","permalink":"https://over58.github.io/2021/02/24/%E8%83%BD%E4%B8%8D%E8%83%BD%E5%AE%9E%E7%8E%B0%E5%9B%BE%E7%89%87%E6%87%92%E5%8A%A0%E8%BD%BD%EF%BC%9F/","excerpt":"","text":"方案一:clientHeight、scrollTop 和 offsetTop首先给图片一个占位资源: 12345678910111213141516171819202122<img src="default.jpg" data-src="http://www.xxx.com/target.jpg" />接着,通过监听 scroll 事件来判断图片是否到达视口:let img = document.getElementsByTagName("img");let num = img.length;let count = 0;//计数器,从第一张图片开始计lazyload();//首次加载别忘了显示图片window.addEventListener('scroll', lazyload);function lazyload() { let viewHeight = document.documentElement.clientHeight;//视口高度 let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;//滚动条卷去的高度 for(let i = count; i <num; i++) { // 元素现在已经出现在视口中 if(img[i].offsetTop < scrollHeight + viewHeight) { if(img[i].getAttribute("src") !== "default.jpg") continue; img[i].src = img[i].getAttribute("data-src"); count ++; } }} 当然,最好对 scroll 事件做节流处理,以免频繁触发: 12// throttle函数我们上节已经实现window.addEventListener('scroll', throttle(lazyload, 200)); 方案二:getBoundingClientRect现在我们用另外一种方式来判断图片是否出现在了当前视口, 即 DOM 元素的 getBoundingClientRect API。上述的 lazyload 函数改成下面这样: 12345678910function lazyload() { for(let i = count; i <num; i++) { // 元素现在已经出现在视口中 if(img[i].getBoundingClientRect().top < document.documentElement.clientHeight) { if(img[i].getAttribute("src") !== "default.jpg") continue; img[i].src = img[i].getAttribute("data-src"); count ++; } }} 方案三: IntersectionObserver这是浏览器内置的一个API,实现了监听window的scroll事件、判断是否在视口中以及节流三大功能。我们来具体试一把: 123456789101112131415let img = document.getElementsByTagName("img");const observer = new IntersectionObserver(changes => { //changes 是被观察的元素集合 for(let i = 0, len = changes.length; i < len; i++) { let change = changes[i]; // 通过这个属性判断是否在视口中 if(change.isIntersecting) { const imgElement = change.target; imgElement.src = imgElement.getAttribute("data-src"); observer.unobserve(imgElement); } }})observer.observe(img); 这样就很方便地实现了图片懒加载,当然这个IntersectionObserver也可以用作其他资源的预加载,功能非常强大。","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"HTTPS为什么让数据传输更安全","slug":"HTTPS为什么让数据传输更安全","date":"2021-02-24T12:27:20.000Z","updated":"2021-02-24T12:27:20.000Z","comments":true,"path":"2021/02/24/HTTPS为什么让数据传输更安全/","link":"","permalink":"https://over58.github.io/2021/02/24/HTTPS%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AE%A9%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E6%9B%B4%E5%AE%89%E5%85%A8/","excerpt":"","text":"谈到HTTPS, 就不得不谈到与之相对的HTTP。HTTP的特性是明文传输,因此在传输的每一个环节,数据都有可能被第三方窃取或者篡改,具体来说,HTTP 数据经过 TCP 层,然后经过WIFI路由器、运营商和目标服务器,这些环节中都可能被中间人拿到数据并进行篡改,也就是我们常说的中间人攻击。 为了防范这样一类攻击,我们不得已要引入新的加密方案,即 HTTPS。 HTTPS并不是一个新的协议, 而是一个加强版的HTTP。其原理是在HTTP和TCP之间建立了一个中间层,当HTTP和TCP通信时并不是像以前那样直接通信,直接经过了一个中间层进行加密,将加密后的数据包传给TCP, 响应的,TCP必须将数据包解密,才能传给上面的HTTP。这个中间层也叫安全层。安全层的核心就是对数据加解密。 接下来我们就来剖析一下HTTPS的加解密是如何实现的。 对称加密和非对称加密概念首先需要理解对称加密和非对称加密的概念,然后讨论两者应用后的效果如何。 对称加密是最简单的方式,指的是加密和解密用的是同样的密钥。 而对于非对称加密,如果有 A、 B 两把密钥,如果用 A 加密过的数据包只能用 B 解密,反之,如果用 B 加密过的数据包只能用 A 解密。 加解密过程接着我们来谈谈浏览器和服务器进行协商加解密的过程。 首先,浏览器会给服务器发送一个随机数client_random和一个加密的方法列表。 服务器接收后给浏览器返回另一个随机数server_random和加密方法。 现在,两者拥有三样相同的凭证: client_random、server_random和加密方法。 接着用这个加密方法将两个随机数混合起来生成密钥,这个密钥就是浏览器和服务端通信的暗号。 各自应用的效果如果用对称加密的方式,那么第三方可以在中间获取到client_random、server_random和加密方法,由于这个加密方法同时可以解密,所以中间人可以成功对暗号进行解密,拿到数据,很容易就将这种加密方式破解了。 既然对称加密这么不堪一击,我们就来试一试非对称加密。在这种加密方式中,服务器手里有两把钥匙,一把是公钥,也就是说每个人都能拿到,是公开的,另一把是私钥,这把私钥只有服务器自己知道。 好,现在开始传输。 浏览器把client_random和加密方法列表传过来,服务器接收到,把server_random、加密方法和公钥传给浏览器。 现在两者拥有相同的client_random、server_random和加密方法。然后浏览器用公钥将client_random和server_random加密,生成与服务器通信的暗号。 这时候由于是非对称加密,公钥加密过的数据只能用私钥解密,因此中间人就算拿到浏览器传来的数据,由于他没有私钥,照样无法解密,保证了数据的安全性。 这难道一定就安全吗?聪明的小伙伴早就发现了端倪。回到非对称加密的定义,公钥加密的数据可以用私钥解密,那私钥加密的数据也可以用公钥解密呀! 服务器的数据只能用私钥进行加密(因为如果它用公钥那么浏览器也没法解密啦),中间人一旦拿到公钥,那么就可以对服务端传来的数据进行解密了,就这样又被破解了。而且,只是采用非对称加密,对于服务器性能的消耗也是相当巨大的,因此我们暂且不采用这种方案。 对称加密和非对称加密的结合可以发现,对称加密和非对称加密,单独应用任何一个,都会存在安全隐患。那我们能不能把两者结合,进一步保证安全呢? 其实是可以的,演示一下整个流程: 浏览器向服务器发送client_random和加密方法列表。 服务器接收到,返回server_random、加密方法以及公钥。 浏览器接收,接着生成另一个随机数pre_random, 并且用公钥加密,传给服务器。(敲黑板!重点操作!) 服务器用私钥解密这个被加密后的pre_random。 现在浏览器和服务器有三样相同的凭证:client_random、server_random和pre_random。然后两者用相同的加密方法混合这三个随机数,生成最终的密钥。 然后浏览器和服务器尽管用一样的密钥进行通信,即使用对称加密。 这个最终的密钥是很难被中间人拿到的,为什么呢? 因为中间人没有私钥,从而拿不到pre_random,也就无法生成最终的密钥了。 回头比较一下和单纯的使用非对称加密, 这种方式做了什么改进呢?本质上是防止了私钥加密的数据外传。单独使用非对称加密,最大的漏洞在于服务器传数据给浏览器只能用私钥加密,这是危险产生的根源。利用对称和非对称加密结合的方式,就防止了这一点,从而保证了安全。 添加数字证书尽管通过两者加密方式的结合,能够很好地实现加密传输,但实际上还是存在一些问题。黑客如果采用 DNS 劫持,将目标地址替换成黑客服务器的地址,然后黑客自己造一份公钥和私钥,照样能进行数据传输。而对于浏览器用户而言,他是不知道自己正在访问一个危险的服务器的。 事实上HTTPS在上述结合对称和非对称加密的基础上,又添加了数字证书认证的步骤。其目的就是让服务器证明自己的身份。 传输过程为了获取这个证书,服务器运营者需要向第三方认证机构获取授权,这个第三方机构也叫CA(Certificate Authority), 认证通过后 CA 会给服务器颁发数字证书。 这个数字证书有两个作用: 服务器向浏览器证明自己的身份。 把公钥传给浏览器。 这个验证的过程发生在什么时候呢? 当服务器传送server_random、加密方法的时候,顺便会带上数字证书(包含了公钥), 接着浏览器接收之后就会开始验证数字证书。如果验证通过,那么后面的过程照常进行,否则拒绝执行。 现在我们来梳理一下HTTPS最终的加解密过程: ![image-20210224135155149](/Users/xuyongchao/Library/Application Support/typora-user-images/image-20210224135155149.png) 认证过程浏览器拿到数字证书后,如何来对证书进行认证呢? 首先,会读取证书中的明文内容。CA 进行数字证书的签名时会保存一个 Hash 函数,来这个函数来计算明文内容得到信息A,然后用公钥解密明文内容得到信息B,两份信息做比对,一致则表示认证合法。 当然有时候对于浏览器而言,它不知道哪些 CA 是值得信任的,因此会继续查找 CA 的上级 CA,以同样的信息比对方式验证上级 CA 的合法性。一般根级的 CA 会内置在操作系统当中,当然如果向上找没有找到根级的 CA,那么将被视为不合法。 总结HTTPS并不是一个新的协议, 它在HTTP和TCP的传输中建立了一个安全层,利用对称加密和非对称机密结合数字证书认证的方式,让传输过程的安全性大大提高。","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"说一说CSRF攻击","slug":"说一说CSRF攻击","date":"2021-02-24T12:26:28.000Z","updated":"2021-02-24T12:26:28.000Z","comments":true,"path":"2021/02/24/说一说CSRF攻击/","link":"","permalink":"https://over58.github.io/2021/02/24/%E8%AF%B4%E4%B8%80%E8%AF%B4CSRF%E6%94%BB%E5%87%BB/","excerpt":"","text":"什么是CSRF攻击?CSRF(Cross-site request forgery), 即跨站请求伪造,指的是黑客诱导用户点击链接,打开黑客的网站,然后黑客利用用户目前的登录状态发起跨站请求。 举个例子, 你在某个论坛点击了黑客精心挑选的小姐姐图片,你点击后,进入了一个新的页面。 那么恭喜你,被攻击了:) 你可能会比较好奇,怎么突然就被攻击了呢?接下来我们就来拆解一下当你点击了链接之后,黑客在背后做了哪些事情。 可能会做三样事情。列举如下: 1. 自动发 GET 请求黑客网页里面可能有一段这样的代码: 1<img src="https://xxx.com/info?user=hhh&count=100"> 进入页面后自动发送 get 请求,值得注意的是,这个请求会自动带上关于 xxx.com 的 cookie 信息(这里是假定你已经在 xxx.com 中登录过)。 假如服务器端没有相应的验证机制,它可能认为发请求的是一个正常的用户,因为携带了相应的 cookie,然后进行相应的各种操作,可以是转账汇款以及其他的恶意操作。 2. 自动发 POST 请求黑客可能自己填了一个表单,写了一段自动提交的脚本。 1234567<form id='hacker-form' action="https://xxx.com/info" method="POST"> <input type="hidden" name="user" value="hhh" /> <input type="hidden" name="count" value="100" /></form><script> document.getElementById('hacker-form').submit();</script> 同样也会携带相应的用户 cookie 信息,让服务器误以为是一个正常的用户在操作,让各种恶意的操作变为可能。 3. 诱导点击发送 GET 请求在黑客的网站上,可能会放上一个链接,驱使你来点击: 1<a href="https://xxx/info?user=hhh&count=100" taget="_blank">点击进入修仙世界</a> 点击后,自动发送 get 请求,接下来和自动发 GET 请求部分同理。 这就是CSRF攻击的原理。和XSS攻击对比,CSRF 攻击并不需要将恶意代码注入用户当前页面的html文档中,而是跳转到新的页面,利用服务器的验证漏洞和用户之前的登录状态来模拟用户进行操作。 防范措施1. 利用Cookie的SameSite属性CSRF攻击中重要的一环就是自动发送目标站点下的 Cookie,然后就是这一份 Cookie 模拟了用户的身份。因此在Cookie上面下文章是防范的不二之选。 恰好,在 Cookie 当中有一个关键的字段,可以对请求中 Cookie 的携带作一些限制,这个字段就是SameSite。 SameSite可以设置为三个值,Strict、Lax和None。 a. 在Strict模式下,浏览器完全禁止第三方请求携带Cookie。比如请求sanyuan.com网站只能在sanyuan.com域名当中请求才能携带 Cookie,在其他网站请求都不能。 b. 在Lax模式,就宽松一点了,但是只能在 get 方法提交表单况或者a 标签发送 get 请求的情况下可以携带 Cookie,其他情况均不能。 c. 在None模式下,也就是默认模式,请求会自动携带上 Cookie。 2. 验证来源站点这就需要要用到请求头中的两个字段: Origin和Referer。 其中,Origin只包含域名信息,而Referer包含了具体的 URL 路径。 当然,这两者都是可以伪造的,通过 Ajax 中自定义请求头即可,安全性略差。 3. CSRF TokenDjango作为 Python 的一门后端框架,如果是用它开发过的同学就知道,在它的模板(template)中, 开发表单时,经常会附上这样一行代码: 1{% csrf_token %} 这就是CSRF Token的典型应用。那它的原理是怎样的呢? 首先,浏览器向服务器发送请求时,服务器生成一个字符串,将其植入到返回的页面中。 然后浏览器如果要发送请求,就必须带上这个字符串,然后服务器来验证是否合法,如果不合法则不予响应。这个字符串也就是CSRF Token,通常第三方站点无法拿到这个 token, 因此也就是被服务器给拒绝。 总结CSRF(Cross-site request forgery), 即跨站请求伪造,指的是黑客诱导用户点击链接,打开黑客的网站,然后黑客利用用户目前的登录状态发起跨站请求。 CSRF攻击一般会有三种方式: 自动 GET 请求 自动 POST 请求 诱导点击发送 GET 请求。 防范措施: 利用 Cookie 的 SameSite 属性、验证来源站点和CSRF Token。","categories":[{"name":"网络","slug":"网络","permalink":"https://over58.github.io/categories/%E7%BD%91%E7%BB%9C/"}],"tags":[],"author":["徐勇超"]},{"title":"说一说XSS攻击","slug":"说一说XSS攻击","date":"2021-02-24T12:25:58.000Z","updated":"2021-02-24T12:25:58.000Z","comments":true,"path":"2021/02/24/说一说XSS攻击/","link":"","permalink":"https://over58.github.io/2021/02/24/%E8%AF%B4%E4%B8%80%E8%AF%B4XSS%E6%94%BB%E5%87%BB/","excerpt":"","text":"什么是 XSS 攻击?XSS 全称是 Cross Site Scripting(即跨站脚本),为了和 CSS 区分,故叫它XSS。XSS 攻击是指浏览器中执行恶意脚本(无论是跨域还是同域),从而拿到用户的信息并进行操作。 这些操作一般可以完成下面这些事情: 窃取Cookie。 监听用户行为,比如输入账号密码后直接发送到黑客服务器。 修改 DOM 伪造登录表单。 在页面中生成浮窗广告。 通常情况,XSS 攻击的实现有三种方式——存储型、反射型和文档型。原理都比较简单,先来一一介绍一下。 存储型存储型,顾名思义就是将恶意脚本存储了起来,确实,存储型的 XSS 将脚本存储到了服务端的数据库,然后在客户端执行这些脚本,从而达到攻击的效果。 常见的场景是留言评论区提交一段脚本代码,如果前后端没有做好转义的工作,那评论内容存到了数据库,在页面渲染过程中直接执行, 相当于执行一段未知逻辑的 JS 代码,是非常恐怖的。这就是存储型的 XSS 攻击。 反射型反射型XSS指的是恶意脚本作为网络请求的一部分。 比如我输入: 1http://sanyuan.com?q=<script>alert("你完蛋了")</script> 这杨,在服务器端会拿到q参数,然后将内容返回给浏览器端,浏览器将这些内容作为HTML的一部分解析,发现是一个脚本,直接执行,这样就被攻击了。 之所以叫它反射型, 是因为恶意脚本是通过作为网络请求的参数,经过服务器,然后再反射到HTML文档中,执行解析。和存储型不一样的是,服务器并不会存储这些恶意脚本。 文档型文档型的 XSS 攻击并不会经过服务端,而是作为中间人的角色,在数据传输过程劫持到网络数据包,然后修改里面的 html 文档! 这样的劫持方式包括WIFI路由器劫持或者本地恶意软件等。 防范措施明白了三种XSS攻击的原理,我们能发现一个共同点: 都是让恶意脚本直接能在浏览器中执行。 那么要防范它,就是要避免这些脚本代码的执行。 为了完成这一点,必须做到一个信念,两个利用。 一个信念千万不要相信任何用户的输入! 无论是在前端和服务端,都要对用户的输入进行转码或者过滤。 如: 1<script>alert('你完蛋了')</script> 转码后变为: 1<script>alert('你完蛋了')</script> 这样的代码在 html 解析的过程中是无法执行的。 当然也可以利用关键词过滤的方式,将 script 标签给删除。那么现在的内容只剩下: 12 什么也没有了:) 利用 CSPCSP,即浏览器中的内容安全策略,它的核心思想就是服务器决定浏览器加载哪些资源,具体来说可以完成以下功能: 限制其他域下的资源加载。 禁止向其它域提交数据。 提供上报机制,能帮助我们及时发现 XSS 攻击。 利用 HttpOnly很多 XSS 攻击脚本都是用来窃取Cookie, 而设置 Cookie 的 HttpOnly 属性后,JavaScript 便无法读取 Cookie 的值。这样也能很好的防范 XSS 攻击。 总结XSS 攻击是指浏览器中执行恶意脚本, 然后拿到用户的信息进行操作。主要分为存储型、反射型和文档型。防范的措施包括: 一个信念: 不要相信用户的输入,对输入内容转码或者过滤,让其不可执行。 两个利用: 利用 CSP,利用 Cookie 的 HttpOnly 属性。","categories":[{"name":"网络","slug":"网络","permalink":"https://over58.github.io/categories/%E7%BD%91%E7%BB%9C/"}],"tags":[],"author":["徐勇超"]},{"title":"谈谈你对重绘和回流的理解","slug":"谈谈你对重绘和回流的理解","date":"2021-02-24T12:25:21.000Z","updated":"2021-02-24T12:25:21.000Z","comments":true,"path":"2021/02/24/谈谈你对重绘和回流的理解/","link":"","permalink":"https://over58.github.io/2021/02/24/%E8%B0%88%E8%B0%88%E4%BD%A0%E5%AF%B9%E9%87%8D%E7%BB%98%E5%92%8C%E5%9B%9E%E6%B5%81%E7%9A%84%E7%90%86%E8%A7%A3/","excerpt":"","text":"我们首先来回顾一下渲染流水线的流程: ![image-20210224135331182](/Users/xuyongchao/Library/Application Support/typora-user-images/image-20210224135331182.png) 接下来,我们将来以此为依据来介绍重绘和回流,以及让更新视图的另外一种方式——合成。 回流首先介绍回流。回流也叫重排。 触发条件简单来说,就是当我们对 DOM 结构的修改引发 DOM 几何尺寸变化的时候,会发生回流的过程。 具体一点,有以下的操作会触发回流: 一个 DOM 元素的几何属性变化,常见的几何属性有width、height、padding、margin、left、top、border 等等, 这个很好理解。 使 DOM 节点发生增减或者移动。 读写 offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作。 调用 window.getComputedStyle 方法。 回流过程依照上面的渲染流水线,触发回流的时候,如果 DOM 结构发生改变,则重新渲染 DOM 树,然后将后面的流程(包括主线程之外的任务)全部走一遍。 ![image-20210224135359846](/Users/xuyongchao/Library/Application Support/typora-user-images/image-20210224135359846.png) 相当于将解析和合成的过程重新又走了一篇,开销是非常大的。 重绘触发条件当 DOM 的修改导致了样式的变化,并且没有影响几何属性的时候,会导致重绘(repaint)。 重绘过程由于没有导致 DOM 几何属性的变化,因此元素的位置信息不需要更新,从而省去布局的过程。流程如下: ![image-20210224135420524](/Users/xuyongchao/Library/Application Support/typora-user-images/image-20210224135420524.png) 跳过了生成布局树和建图层树的阶段,直接生成绘制列表,然后继续进行分块、生成位图等后面一系列操作。 可以看到,重绘不一定导致回流,但回流一定发生了重绘。 合成还有一种情况,是直接合成。比如利用 CSS3 的transform、opacity、filter这些属性就可以实现合成的效果,也就是大家常说的GPU加速。 GPU加速的原因在合成的情况下,会直接跳过布局和绘制流程,直接进入非主线程处理的部分,即直接交给合成线程处理。交给它处理有两大好处: 能够充分发挥GPU的优势。合成线程生成位图的过程中会调用线程池,并在其中使用GPU进行加速生成,而GPU 是擅长处理位图数据的。 没有占用主线程的资源,即使主线程卡住了,效果依然能够流畅地展示。 实践意义知道上面的原理之后,对于开发过程有什么指导意义呢? 避免频繁使用 style,而是采用修改class的方式。 不要把 DOM 节点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。 可能的修改层级比较低的 DOM节点。当然,改变层级比较底的 DOM节点有可能会造成大面积的 reflow,但是也可能影响范围很小。 因为改变 DOM 树中的一级会导致所有层级的改变,上至根部,下至被改变节点的子节点。这导致大量时间耗费在执行 reflow 上面 避免使用CSS的JavaScript表达式,如果css里有expression,每次都会重新计算一遍。 使用createDocumentFragment进行批量的 DOM 操作。 对于 resize、scroll 等进行防抖/节流处理。 添加 will-change: tranform ,让渲染引擎为其单独实现一个图层,当这些变换发生时,仅仅只是利用合成线程去处理这些变换,而不牵扯到主线程,大大提高渲染效率。当然这个变化不限于tranform, 任何可以实现合成效果的 CSS 属性都能用will-change来声明。这里有一个实际的例子,一行will-change: tranform拯救一个项目,点击直达。","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"说一说浏览器的本地存储?各自优劣如何?","slug":"说一说浏览器的本地存储-各自优劣如何","date":"2021-02-24T12:16:37.000Z","updated":"2021-02-24T12:16:37.000Z","comments":true,"path":"2021/02/24/说一说浏览器的本地存储-各自优劣如何/","link":"","permalink":"https://over58.github.io/2021/02/24/%E8%AF%B4%E4%B8%80%E8%AF%B4%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8-%E5%90%84%E8%87%AA%E4%BC%98%E5%8A%A3%E5%A6%82%E4%BD%95/","excerpt":"","text":"浏览器的本地存储主要分为Cookie、WebStorage和IndexedDB, 其中WebStorage又可以分为localStorage和sessionStorage。接下来我们就来一一分析这些本地存储方案。 CookieCookie 最开始被设计出来其实并不是来做本地存储的,而是为了弥补HTTP在状态管理上的不足。 HTTP 协议是一个无状态协议,客户端向服务器发请求,服务器返回响应,故事就这样结束了,但是下次发请求如何让服务端知道客户端是谁呢? 这种背景下,就产生了 Cookie. Cookie 本质上就是浏览器里面存储的一个很小的文本文件,内部以键值对的方式来存储(在chrome开发者面板的Application这一栏可以看到)。向同一个域名下发送请求,都会携带相同的 Cookie,服务器拿到 Cookie 进行解析,便能拿到客户端的状态。 Cookie 的作用很好理解,就是用来做状态存储的,但它也是有诸多致命的缺陷的: 容量缺陷。Cookie 的体积上限只有4KB,只能用来存储少量的信息。 性能缺陷。Cookie 紧跟域名,不管域名下面的某一个地址需不需要这个 Cookie ,请求都会携带上完整的 Cookie,这样随着请求数的增多,其实会造成巨大的性能浪费的,因为请求携带了很多不必要的内容。 安全缺陷。由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改,在 Cookie 的有效期内重新发送给服务器,这是相当危险的。另外,在HttpOnly为 false 的情况下,Cookie 信息能直接通过 JS 脚本来读取。 localStorage和Cookie异同localStorage有一点跟Cookie一样,就是针对一个域名,即在同一个域名下,会存储相同的一段localStorage。 不过它相对Cookie还是有相当多的区别的: 容量。localStorage 的容量上限为5M,相比于Cookie的 4K 大大增加。当然这个 5M 是针对一个域名的,因此对于一个域名是持久存储的。 只存在客户端,默认不参与与服务端的通信。这样就很好地避免了 Cookie 带来的性能问题和安全问题。 接口封装。通过localStorage暴露在全局,并通过它的 setItem 和 getItem等方法进行操作,非常方便。 操作方式接下来我们来具体看看如何来操作localStorage。 1let obj = { name: "sanyuan", age: 18 };localStorage.setItem("name", "sanyuan"); localStorage.setItem("info", JSON.stringify(obj)); 接着进入相同的域名时就能拿到相应的值: 1let name = localStorage.getItem("name");let info = JSON.parse(localStorage.getItem("info")); 从这里可以看出,localStorage其实存储的都是字符串,如果是存储对象需要调用JSON的stringify方法,并且用JSON.parse来解析成对象。 应用场景利用localStorage的较大容量和持久特性,可以利用localStorage存储一些内容稳定的资源,比如官网的logo,存储Base64格式的图片资源,因此利用localStorage sessionStorage特点sessionStorage以下方面和localStorage一致: 容量。容量上限也为 5M。 只存在客户端,默认不参与与服务端的通信。 接口封装。除了sessionStorage名字有所变化,存储方式、操作方式均和localStorage一样。 但sessionStorage和localStorage有一个本质的区别,那就是前者只是会话级别的存储,并不是持久化存储。会话结束,也就是页面关闭,这部分sessionStorage就不复存在了。 应用场景 可以用它对表单信息进行维护,将表单信息存储在里面,可以保证页面即使刷新也不会让之前的表单信息丢失。 可以用它存储本次浏览记录。如果关闭页面后不需要这些记录,用sessionStorage就再合适不过了。事实上微博就采取了这样的存储方式。 IndexedDBIndexedDB是运行在浏览器中的非关系型数据库, 本质上是数据库,绝不是和刚才WebStorage的 5M 一个量级,理论上这个容量是没有上限的。 关于它的使用,本文侧重原理,而且 MDN 上的教程文档已经非常详尽,这里就不做赘述了,感兴趣可以看一下使用文档。 接着我们来分析一下IndexedDB的一些重要特性,除了拥有数据库本身的特性,比如支持事务,存储二进制数据,还有这样一些特性需要格外注意: 键值对存储。内部采用对象仓库存放数据,在这个对象仓库中数据采用键值对的方式来存储。 异步操作。数据库的读写属于 I/O 操作, 浏览器中对异步 I/O 提供了支持。 受同源策略限制,即无法访问跨域的数据库。 总结浏览器中各种本地存储和缓存技术的发展,给前端应用带来了大量的机会,PWA 也正是依托了这些优秀的存储方案才得以发展起来。重新梳理一下这些本地存储方案: cookie并不适合存储,而且存在非常多的缺陷。 Web Storage包括localStorage和sessionStorage, 默认不会参与和服务器的通信。 IndexedDB为运行在浏览器上的非关系型数据库,为大型数据的存储提供了接口。","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"vue2.x的合并策略","slug":"vue2.x的合并策略","date":"2020-12-27T15:53:55.000Z","updated":"2020-12-27T15:53:55.000Z","comments":true,"path":"2020/12/27/vue2.x的合并策略/","link":"","permalink":"https://over58.github.io/2020/12/27/vue2.x%E7%9A%84%E5%90%88%E5%B9%B6%E7%AD%96%E7%95%A5/","excerpt":"data 权重 自己的data> mixins的data > mixins的mixins的data … > 全局的mixins的data 相同的属性的话,如果双方都是对象的话,对象会被递归合并 hooks 合并为array 123456789101112131415function mergeHook ( parentVal, childVal) { var res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal; return res ? dedupeHooks(res) : res} watcher merged as array 【父元素的watcher,自己的watcher…】 props, computed, inject, methods正常对象合并,同属性名时,自己的优先级高, 直接替换掉父级的同名属性","text":"data 权重 自己的data> mixins的data > mixins的mixins的data … > 全局的mixins的data 相同的属性的话,如果双方都是对象的话,对象会被递归合并 hooks 合并为array 123456789101112131415function mergeHook ( parentVal, childVal) { var res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal; return res ? dedupeHooks(res) : res} watcher merged as array 【父元素的watcher,自己的watcher…】 props, computed, inject, methods正常对象合并,同属性名时,自己的优先级高, 直接替换掉父级的同名属性 以上所有结论的相关源码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418/** * 处理合并策略 * Option overwriting strategies are functions that handle * how to merge a parent option value and a child option * value into the final value. */var strats = config.optionMergeStrategies;/** * Options with restrictions */if (process.env.NODE_ENV !== 'production') { strats.el = strats.propsData = function (parent, child, vm, key) { if (!vm) { warn( "option \\"" + key + "\\" can only be used during instance " + 'creation with the `new` keyword.' ); } return defaultStrat(parent, child) };}/** * Helper that recursively merges two data objects together. * merge数据, 如果双方的某个属性也都是是对象,也进行merge */function mergeData (to, from) { if (!from) { return to } var key, toVal, fromVal; var keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from); for (var i = 0; i < keys.length; i++) { key = keys[i]; // in case the object is already observed... if (key === '__ob__') { continue } toVal = to[key]; fromVal = from[key]; if (!hasOwn(to, key)) { set(to, key, fromVal); } else if ( toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal) ) { // 递归的merge数据对象 mergeData(toVal, fromVal); } } return to}/** * Data */function mergeDataOrFn ( parentVal, childVal, vm) { if (!vm) { // in a Vue.extend merge, both should be functions if (!childVal) { return parentVal } if (!parentVal) { return childVal } // when parentVal & childVal are both present, // we need to return a function that returns the // merged result of both functions... no need to // check if parentVal is a function here because // it has to be a function to pass previous merges. return function mergedDataFn () { return mergeData( typeof childVal === 'function' ? childVal.call(this, this) : childVal, typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } else { return function mergedInstanceDataFn () { // instance merge, var instanceData = typeof childVal === 'function' ? childVal.call(vm, vm) : childVal; var defaultData = typeof parentVal === 'function' ? parentVal.call(vm, vm) : parentVal; if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } } }}// 合并datastrats.data = function ( parentVal, childVal, vm ) { if (!vm) { if (childVal && typeof childVal !== 'function') { process.env.NODE_ENV !== 'production' && warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ); return parentVal } return mergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) }; /** * Hooks and props are merged as arrays. */function mergeHook ( parentVal, childVal) { var res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal; return res ? dedupeHooks(res) : res}function dedupeHooks (hooks) { var res = []; for (var i = 0; i < hooks.length; i++) { if (res.indexOf(hooks[i]) === -1) { res.push(hooks[i]); } } return res}LIFECYCLE_HOOKS.forEach(function (hook) { strats[hook] = mergeHook;});/** * Assets * * When a vm is present (instance creation), we need to do * a three-way merge between constructor options, instance * options and parent options. */function mergeAssets ( parentVal, childVal, vm, key) { var res = Object.create(parentVal || null); if (childVal) { process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm); return extend(res, childVal) } else { return res }}ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets;});/** * Watchers. * * Watchers hashes should not overwrite one * another, so we merge them as arrays. */strats.watch = function ( parentVal, childVal, vm, key) { // work around Firefox's Object.prototype.watch... if (parentVal === nativeWatch) { parentVal = undefined; } if (childVal === nativeWatch) { childVal = undefined; } /* istanbul ignore if */ if (!childVal) { return Object.create(parentVal || null) } if (process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm); } if (!parentVal) { return childVal } var ret = {}; extend(ret, parentVal); for (var key$1 in childVal) { var parent = ret[key$1]; var child = childVal[key$1]; if (parent && !Array.isArray(parent)) { parent = [parent]; } ret[key$1] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child]; } return ret};/** * Other object hashes. */strats.props =strats.methods =strats.inject =strats.computed = function ( parentVal, childVal, vm, key) { if (childVal && process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm); } if (!parentVal) { return childVal } var ret = Object.create(null); extend(ret, parentVal); if (childVal) { extend(ret, childVal); } return ret};strats.provide = mergeDataOrFn;/** * Default strategy. */var defaultStrat = function (parentVal, childVal) { return childVal === undefined ? parentVal : childVal};/** * Validate component names */function checkComponents (options) { for (var key in options.components) { validateComponentName(key); }}function validateComponentName (name) { if (!new RegExp(("^[a-zA-Z][\\\\-\\\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { warn( 'Invalid component name: "' + name + '". Component names ' + 'should conform to valid custom element name in html5 specification.' ); } if (isBuiltInTag(name) || config.isReservedTag(name)) { warn( 'Do not use built-in or reserved HTML elements as component ' + 'id: ' + name ); }}/** * Ensure all props option syntax are normalized into the * Object-based format. */function normalizeProps (options, vm) { var props = options.props; if (!props) { return } var res = {}; var i, val, name; if (Array.isArray(props)) { i = props.length; while (i--) { val = props[i]; if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.'); } } } else if (isPlainObject(props)) { for (var key in props) { val = props[key]; name = camelize(key); res[name] = isPlainObject(val) ? val : { type: val }; } } else if (process.env.NODE_ENV !== 'production') { warn( "Invalid value for option \\"props\\": expected an Array or an Object, " + "but got " + (toRawType(props)) + ".", vm ); } options.props = res;}/** * Normalize all injections into Object-based format */function normalizeInject (options, vm) { var inject = options.inject; if (!inject) { return } var normalized = options.inject = {}; if (Array.isArray(inject)) { for (var i = 0; i < inject.length; i++) { normalized[inject[i]] = { from: inject[i] }; } } else if (isPlainObject(inject)) { for (var key in inject) { var val = inject[key]; normalized[key] = isPlainObject(val) ? extend({ from: key }, val) : { from: val }; } } else if (process.env.NODE_ENV !== 'production') { warn( "Invalid value for option \\"inject\\": expected an Array or an Object, " + "but got " + (toRawType(inject)) + ".", vm ); }}/** * Normalize raw function directives into object format. */function normalizeDirectives (options) { var dirs = options.directives; if (dirs) { for (var key in dirs) { var def$$1 = dirs[key]; if (typeof def$$1 === 'function') { dirs[key] = { bind: def$$1, update: def$$1 }; } } }}function assertObjectType (name, value, vm) { if (!isPlainObject(value)) { warn( "Invalid value for option \\"" + name + "\\": expected an Object, " + "but got " + (toRawType(value)) + ".", vm ); }}/** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */function mergeOptions ( parent, child, vm) { if (process.env.NODE_ENV !== 'production') { checkComponents(child); } if (typeof child === 'function') { child = child.options; } normalizeProps(child, vm); normalizeInject(child, vm); normalizeDirectives(child); // Apply extends and mixins on the child options, // but only if it is a raw options object that isn't // the result of another mergeOptions call. // Only merged options has the _base property. if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm); } if (child.mixins) { for (var i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm); } } } var options = {}; var key; for (key in parent) { mergeField(key); } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key); } } function mergeField (key) { var strat = strats[key] || defaultStrat; options[key] = strat(parent[key], child[key], vm, key); } return options}","categories":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/categories/vue/"}],"tags":[],"author":["徐勇超"]},{"title":"vue中自定义属性的d.ts书写问题","slug":"vue中自定义属性的d-ts书写问题","date":"2020-12-27T15:53:55.000Z","updated":"2020-12-27T15:53:55.000Z","comments":true,"path":"2020/12/27/vue中自定义属性的d-ts书写问题/","link":"","permalink":"https://over58.github.io/2020/12/27/vue%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89%E5%B1%9E%E6%80%A7%E7%9A%84d-ts%E4%B9%A6%E5%86%99%E9%97%AE%E9%A2%98/","excerpt":"","text":"在vue的instance上添加自定义的属性1234567// plugin.tsconst plugin:PluginInterface = { install (app: App) { app.config.global.globalProperties.http = http }} 12345//plugin.d.tsdeclare interface PluginInterface { install: PluginInstallFunction, initVue: (instance: App, layout: any) => App} 在window上挂载自定义属性1234567891011 global{ interface Window { webkit: any Vue: any, _hmt: any, msRequestAnimationFrame?:any mozRequestAnimationFrame?:any WeixinJSBridge: any wx: any }}","categories":[{"name":"typescript","slug":"typescript","permalink":"https://over58.github.io/categories/typescript/"}],"tags":[],"author":["徐勇超"]},{"title":"require和import比较","slug":"require和import比较","date":"2020-12-27T15:39:40.000Z","updated":"2020-12-27T15:39:40.000Z","comments":true,"path":"2020/12/27/require和import比较/","link":"","permalink":"https://over58.github.io/2020/12/27/require%E5%92%8Cimport%E6%AF%94%E8%BE%83/","excerpt":"","text":"A–require 和 import比较 出现时间 加载机制 输出 用法 特点 require 2009 运行时加载 浅拷贝 见下 社区方案,提供了服务器/浏览器的模块加载方案。非语言层面的标准。只能在运行时确定模块的依赖关系及输入/输出的变量,无法进行静态优化。 import 2015 静态编译 值引用 见下 语言规格层面支持模块功能。支持编译时静态分析,便于JS引入宏和类型检验。动态绑定 B–用法比较 require123const fs = require('fs')exports.fs = fsmodule.exports = fs import123456789101112import fs from 'fs'import {default as fs} from 'fs'import * as fs from 'fs'import {readFile} from 'fs'import {readFile as read} from 'fs'import fs, {readFile} from 'fs'export default fsexport const fsexport function readFileexport {readFile, read}export * from 'fs' B1–支持实现度 模块 浏览器 node require x 支持 es module look node > 13 C–解析结果 require是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量 import是解构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require D–引用实例123456789101112// ES2015 modules// ---------------------------------// one.jsconsole.log('running one.js');import { hello } from './two.js';console.log(hello);// ---------------------------------// two.jsconsole.log('running two.js');export const hello = 'Hello from two.js'; 123456789101112// CommonJS modules// ---------------------------------// one.jsconsole.log('running one.js');const hello = require('./two.js');console.log(hello);// ---------------------------------// two.jsconsole.log('running two.js');module.exports = 'Hello from two.js'; 123456789es6running two.jsrunning one.jshello from two.jscommonjsrunning one.jsrunning two.jshello from two.js E–前景展望,谁会一统模块江湖 node > 13 已经全面支持 link,目前是共存局面 deno 使用es模块deno 两种同事存在的可能性,不是很大,es6模块会越来越普及,建议以后都使用es6模块机制 F–资源分享 es module in browser","categories":[{"name":"工程化","slug":"工程化","permalink":"https://over58.github.io/categories/%E5%B7%A5%E7%A8%8B%E5%8C%96/"}],"tags":[],"author":["徐勇超"]},{"title":"深克隆和浅克隆","slug":"深克隆和浅克隆","date":"2020-12-27T15:32:40.000Z","updated":"2020-12-27T15:32:40.000Z","comments":true,"path":"2020/12/27/深克隆和浅克隆/","link":"","permalink":"https://over58.github.io/2020/12/27/%E6%B7%B1%E5%85%8B%E9%9A%86%E5%92%8C%E6%B5%85%E5%85%8B%E9%9A%86/","excerpt":"","text":"概念浅拷贝创建一个新的对象,这个对象有着对原始对象的属性值的一份拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果是引用对象,拷贝的是地址。所以如果其中一个对象改变了这个地址,就是影响另一个对象 深拷贝将一个对象从内存中完整的拷贝一份出来,从堆中开辟出一个新的区域来存放新对象,且修改新的对象不会影响到原对象 简易版(实际上也是项目中最常用的)1JSON.parse(JSON.stringify(原对象)) 基础版1234567891011function deep_clone1 (source) { if (typeof source === 'object') { let target = {} for(let key in source) { target[key] = clone(source[key]) } return target }else{ return source }} 但是很显然,还存在很多问题,比如并没有考虑数组、循环引用 考虑数组1234567891011function deep_clone2 (source) { if (typeof source === 'object') { let target = Array.isArray(source) ? [] : {} for(let key in source) { target[key] = clone(source[key]) } return target }else{ return source }} 数组和对象的遍历方式可以一致,就是存储的时候不同,[]和{} 处理循环引用1234567891011121314151617function deep_clone3 (source, map = new Map()) { if (typeof source === 'object') { let target = Array.isArray(source) ? [] : {} // 专门处理循环引用问题(object类型) if(map.get(source)) { return map.get(source) } map.set(source, target) for(let key in source) { target[key] = deep_clone3(source[key], map) } return target }else{ return source }} 使用WeakMap画龙点睛WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。 什么是弱引用呢? 在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。举个例子:如果我们使用Map的话,那么对象间是存在强引用关系的: 1234let obj = { name : 'ConardLi'}const target = new Map();target.set(obj,'code秘密花园');obj = null; 复制代码虽然我们手动将obj,进行释放,然是target依然对obj存在强引用关系,所以这部分内存依然无法被释放。再来看WeakMap: 1234let obj = { name : 'ConardLi'}const target = new WeakMap();target.set(obj,'code秘密花园');obj = null; 复制代码如果是WeakMap的话,target和obj存在的就是弱引用关系,当下一次垃圾回收机制执行时,这块内存就会被释放掉。设想一下,如果我们要拷贝的对象非常庞大时,使用Map会对内存造成非常大的额外消耗,而且我们需要手动清除Map的属性才能释放这块内存,而WeakMap会帮我们巧妙化解这个问题。 引用ConardLi:https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1 性能优化在上面的代码中,我们遍历数组和对象都使用了for in这种方式,实际上for in在遍历时效率是非常低的.经过比较执行效率 while > for > for in先实现一个通用的forEach 循环, iterate 是遍历的回调函数, 每次接收value和index两个参数: 12345678function forEach(arr, iterate) { let index = -1 const len = array.length while(++index < len>) { iterate(array[index], index) } return array} 然后对之前的代码进行调整 1234567891011121314151617181920212223function deep_clone3 (source, map = new Map()) { if (typeof source === 'object') { const isArray = Array.isArray(source) let target = isArray ? [] : {} // 专门处理循环引用问题(object类型) if(map.get(source)) { return map.get(source) } map.set(source, target) const keys = isArray ? undefined ? Object.keys(source) forEach(keys || source, (value, index)) { if (keys) { // 处理对象, value就是对象key key = value } target[key] = deep_clone3(source[key], map) } return target }else{ return source }} 其他数据类型可继续遍历的类型1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980const mapTag = '[object Map]';const setTag = '[object Set]';const arrayTag = '[object Array]';const objectTag = '[object Object]';const argsTag = '[object Arguments]';const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];function forEach(array, iteratee) { let index = -1; const length = array.length; while (++index < length) { iteratee(array[index], index); } return array;}function isObject(target) { const type = typeof target; return target !== null && (type === 'object' || type === 'function');}function getType(target) { return Object.prototype.toString.call(target);}function getInit(target) { const Ctor = target.constructor; return new Ctor();}function clone(target, map = new WeakMap()) { // 克隆原始类型 if (!isObject(target)) { return target; } // 初始化 const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target, type); } // 防止循环引用 if (map.get(target)) { return target; } map.set(target, cloneTarget); // 克隆set if (type === setTag) { target.forEach(value => { cloneTarget.add(clone(value)); }); return cloneTarget; } // 克隆map if (type === mapTag) { target.forEach((value, key) => { cloneTarget.set(key, clone(value)); }); return cloneTarget; } // 克隆对象和数组 const keys = type === arrayTag ? undefined : Object.keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget;} 不可继续遍历的类型Bool、Number、String、Symbol、Date、Error这几种类型我们都可以直接用构造函数和原始数据创建一个新对象: 1234567891011121314151617181920212223242526272829303132333435function cloneOtherType(targe, type) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); default: return null; }}function cloneSymbol(targe) { return Object(Symbol.prototype.valueOf.call(targe));}克隆正则:function cloneReg(targe) { const reFlags = /\\w*$/; const result = new targe.constructor(targe.source, reFlags.exec(targe)); result.lastIndex = targe.lastIndex; return result;}作者:ConardLi链接:https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 完整版123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147const mapTag = '[object Map]';const setTag = '[object Set]';const arrayTag = '[object Array]';const objectTag = '[object Object]';const argsTag = '[object Arguments]';const boolTag = '[object Boolean]';const dateTag = '[object Date]';const numberTag = '[object Number]';const stringTag = '[object String]';const symbolTag = '[object Symbol]';const errorTag = '[object Error]';const regexpTag = '[object RegExp]';const funcTag = '[object Function]';const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];function forEach(array, iteratee) { let index = -1; const length = array.length; while (++index < length) { iteratee(array[index], index); } return array;}function isObject(target) { const type = typeof target; return target !== null && (type === 'object' || type === 'function');}function getType(target) { return Object.prototype.toString.call(target);}function getInit(target) { const Ctor = target.constructor; return new Ctor();}function cloneSymbol(targe) { return Object(Symbol.prototype.valueOf.call(targe));}function cloneReg(targe) { const reFlags = /\\w*$/; const result = new targe.constructor(targe.source, reFlags.exec(targe)); result.lastIndex = targe.lastIndex; return result;}function cloneFunction(func) { const bodyReg = /(?<={)(.|\\n)+(?=})/m; const paramReg = /(?<=\\().+(?=\\)\\s+{)/; const funcString = func.toString(); if (func.prototype) { const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { if (param) { const paramArr = param[0].split(','); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); }}function cloneOtherType(targe, type) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); case funcTag: return cloneFunction(targe); default: return null; }}function clone(target, map = new WeakMap()) { // 克隆原始类型 if (!isObject(target)) { return target; } // 初始化 const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target, type); } else { return cloneOtherType(target, type); } // 防止循环引用 if (map.get(target)) { return map.get(target); } map.set(target, cloneTarget); // 克隆set if (type === setTag) { target.forEach(value => { cloneTarget.add(clone(value, map)); }); return cloneTarget; } // 克隆map if (type === mapTag) { target.forEach((value, key) => { cloneTarget.set(key, clone(value, map)); }); return cloneTarget; } // 克隆对象和数组 const keys = type === arrayTag ? undefined : Object.keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget;}module.exports = { clone};","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"经典的代码片段","slug":"经典的代码片段","date":"2020-12-27T15:32:40.000Z","updated":"2020-12-27T15:32:40.000Z","comments":true,"path":"2020/12/27/经典的代码片段/","link":"","permalink":"https://over58.github.io/2020/12/27/%E7%BB%8F%E5%85%B8%E7%9A%84%E4%BB%A3%E7%A0%81%E7%89%87%E6%AE%B5/","excerpt":"","text":"reduce用来删除对象的key-value12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364const clean = (obj) => { return Object.keys(obj).reduce((acc, key) => { const value = obj[key]; if (value === undefined || value === null) { return acc; } if (Array.isArray(value) && !value.length) { return acc; } if ( Object.prototype.toString.call(value) === '[object Object]' && !Object.keys(value).length ) { return acc; } acc[key] = value; return acc; }, {})}console.log( clean({ name: 'sex', age: 0, hubbies: [], friends: null, likes: {} }))const clean2 = (obj, keys= []) => { return Object.keys(obj).reduce((acc, key) => { const value = obj[key]; if(keys.includes(key)) { return acc } acc[key] = value; return acc; }, {})}console.log( clean2({ name: 'sex', age: 0, hubbies: [], friends: null, likes: {} }, ['name', 'sex', 'likes']) ) when操作12345678910111213when( condition, whenTruthy = Function.prototype, whenFalsy = Function.prototype, ) { if (condition) { whenTruthy(this); } else { whenFalsy(this); } return this; }","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[]},{"title":"自增version的脚本","slug":"自增version的脚本","date":"2020-08-26T15:37:48.000Z","updated":"2020-08-26T15:37:48.000Z","comments":true,"path":"2020/08/26/自增version的脚本/","link":"","permalink":"https://over58.github.io/2020/08/26/%E8%87%AA%E5%A2%9Eversion%E7%9A%84%E8%84%9A%E6%9C%AC/","excerpt":"","text":"开发一些个npm包的时候经常忘了更改version, 然后写了个打包时自增version的小脚本 12345678910111213141516171819202122/** * 自动的增加一个version patch * version: major.minor.patch */const fse = require('fs-extra')const semver = require('semver')const path = require('path')const fileName = path.join(__dirname,'../package.json')fse.readJSON(fileName,{throws:false}).then(packageJson => { packageJson.version = semver.inc(packageJson.version, 'patch') fse.writeJSONSync(fileName, packageJson, {spaces: ' ', EOL: '\\n', encoding:'utf-8'}, err =>{ if(err) { console.error(err) process.exit(-1) } })}).catch(err =>{ console.log(err); process.exit(-1)})","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"浏览器页面渲染过程","slug":"浏览器页面渲染过程","date":"2020-08-16T18:08:06.000Z","updated":"2020-08-16T18:08:06.000Z","comments":true,"path":"2020/08/16/浏览器页面渲染过程/","link":"","permalink":"https://over58.github.io/2020/08/16/%E6%B5%8F%E8%A7%88%E5%99%A8%E9%A1%B5%E9%9D%A2%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B/","excerpt":"","text":"浏览器是一个多进程架构 页面渲染分为以下的步骤 网络进程获取到html之后通过ipc传递到渲染器主进程 解析html 创建dom树 解析css进行样式计算 结合dom树和样式计算后的结果,确定节点的大小和位置,形成layout tree paint - 遍历layout tree 确定节点的绘制顺序层级表 laryer - 布局 然后根据主进程将layoutTree 和绘制信息表传给合成器线程 合成器线程 - 将得到的信息分图层分成更小的图块 栅格线程 - 将更小的图块进行栅格化raster,返还给合成器线程draw quads图块信息 存储在GPU中 frame 合成器将栅格线程返回的图块合成帧交给浏览器进程 浏览器进程 收到一帧的图像后传给GPU进行渲染 为什么要避免大量的重排和重绘,以及为啥使用tranform就好重排的话,会导致页面重新走一遍上面的所有流程 重绘的话(比如改颜色), 只走了layout , layer 和之后的流程 transform的话, 不占用主线程(不卡顿),直接就是合成器线程和栅格线程进行操作(少了大量计算) 为什么要使用requestAnimationFrame呢我们都知道,页面的刷新频率最起码达到60ms一帧的时候,我们才不会觉得页面卡顿。如果说我们做了一个极其耗费时间的js操作,导致下一帧应该渲染的时候,由于js计算占据这主线程,那么原本的那一帧并没有渲染,这称之为掉帧。使用requestAnimationFrame的好处就是呢, 会将主线程的任务分散到每一帧的间隔,到了该渲染下一帧的时候,先把工作暂停下来,等待渲染结束后计算计算,从而不影响动画的流程 参考自https://www.bilibili.com/video/BV1x54y1B7RE?from=search&seid=4107892898942828381","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"Vue是什么","slug":"Vue是什么","date":"2020-07-30T14:15:27.000Z","updated":"2020-07-30T14:15:27.000Z","comments":true,"path":"2020/07/30/Vue是什么/","link":"","permalink":"https://over58.github.io/2020/07/30/Vue%E6%98%AF%E4%BB%80%E4%B9%88/","excerpt":"代码12345678910111213141516function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue","text":"代码12345678910111213141516function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue 主要步骤1.initMixin(Vue)2.stateMixin(Vue)3.eventsMixin(Vue)4.lifecycleMixin(Vue)5.renderMixin(Vue) initMixin(Vue)添加了Vue.prototype._init()方法 stateMixin(Vue)添加了 Vue.prototype.$data Vue.prototype.$props Vue.prototype.$set(响应式更改,并通知) Vue.prototype.$delete(响应式删除,并通知) Vue.prototype.$watch 并已经为$data和$props添加了set,get方法,执行如下 1234567891011121314151617181920 Object.defineProperty(Vue.prototype, '$data', { get () { return this._data }, set () { warn( 'Avoid replacing instance root $data. ' + 'Use nested data properties instead.', this ) } }) Object.defineProperty(Vue.prototype, '$props', { get () { return this._props }, set () { warn(`$props is readonly.`, this) }}) eventsMixin(Vue)添加了 Vue.prototype.$on Vue.prototype.$once Vue.prototype.$off Vue.prototype.$emit $on使用了发布-订阅模式,类似于node中的events模块,所有的event存在于vm._events中,结构如下: 1234{ event1: [fn1, fn2...] event2: []} lifecycleMixin(Vue)添加了 Vue.prototype._update Vue.prototype.$forceUpdate Vue.prototype.$destroyrenderMixin(Vue)添加了 Vue.prototype.$nextTick Vue.prototype._render new Vue()的时候干了什么执行了initMixin(Vue)中添加的Vue.prototype._init方法 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid, 全局唯一 vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } } 如上所示,主要做了下面的这些事情 添加一些属性 _uid, _isVue merge options 初始化lifecycle 初始化events 初始化render 调用beforeCreate 初始化injections 初始化state 初始化provide 调用created 如果$el存在,则调用$mount($el)方法 初始化lifecycle123456789101112vm.$parent = parentvm.$root = parent ? parent.$root : vmvm.$children = []vm.$refs = {}vm._watcher = nullvm._inactive = nullvm._directInactive = falsevm._isMounted = falsevm._isDestroyed = falsevm._isBeingDestroyed = false 初始化events12345678vm._events = Object.create(null)vm._hasHookEvent = false// init parent attached eventsconst listeners = vm.$options._parentListenersif (listeners) { updateComponentListeners(vm, listeners)} 初始化render12345678910111213141516vm.$slots = resolveSlots(options._renderChildren, renderContext)vm.$scopedSlots = emptyObject// bind the createElement fn to this instance// so that we get proper render context inside it.// args order: tag, data, children, normalizationType, alwaysNormalize// internal version is used by render functions compiled from templatesvm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)// normalization is always applied for the public version, used in// user-written render functions.vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)<!-- 响应式的定义了$attrs和$listeners -->defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true) 调用beforeCreate 这里才调用beforeCreate钩子此时有了events, $attrs,$listeners 初始化injections resolve injections before data/props将inject全部执行响应式操作defineReactive(vm, key, result[key]) 初始化state12345678910111213141516export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) }} 初始化props (initProps) 初始化methods (initMethods) 初始化data (initData) 初始化计算属性 (initComputed) vm._computedWatchers 初始化watch (initWatch) vm.$watch(expOrFn, handler, options) 初始化provide12345678export function initProvide (vm: Component) { const provide = vm.$options.provide if (provide) { vm._provided = typeof provide === 'function' ? provide.call(vm) : provide }} 将provide中的对象存储到vm._provided中 调用created1vm.$emit('hook:' + hook) 如果$el存在,则调用$mount($el)方法 callHook(vm, ‘beforeMount’) vm._update() 1234567new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) callHook(vm, ‘mounted’)","categories":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/categories/vue/"}],"tags":[],"author":["徐勇超"]},{"title":"video基本知识点","slug":"video基本知识点","date":"2020-07-08T11:38:50.000Z","updated":"2020-07-08T11:38:50.000Z","comments":true,"path":"2020/07/08/video基本知识点/","link":"","permalink":"https://over58.github.io/2020/07/08/video%E5%9F%BA%E6%9C%AC%E7%9F%A5%E8%AF%86%E7%82%B9/","excerpt":"前言HTML5中 video 标签定义视频,比如电影片段或其他视频流。也就是说video是用来播放视频的,而且是HTML5中的新标签。所以对老浏览器是不支持的,来看看支持。 Video属性 属性 值 描述 autoplay autoplay 如果出现该属性,则视频在就绪后马上播放。 controls controls 如果出现该属性,则向用户显示控件,比如播放按钮。 height pixels 设置视频播放器的高度。 width pixels 设置视频播放器的宽度。 loop loop 如果出现该属性,则当媒介文件完成播放后再次开始播放。 muted muted 规定视频的音频输出应该被静音。 poster URL 规定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。 preload pixels 如果出现该属性,则视频在页面加载时进行加载,并预备播放。如果使用 “autoplay”,则忽略该属性。 src URL 要播放的视频的 URL。","text":"前言HTML5中 video 标签定义视频,比如电影片段或其他视频流。也就是说video是用来播放视频的,而且是HTML5中的新标签。所以对老浏览器是不支持的,来看看支持。 Video属性 属性 值 描述 autoplay autoplay 如果出现该属性,则视频在就绪后马上播放。 controls controls 如果出现该属性,则向用户显示控件,比如播放按钮。 height pixels 设置视频播放器的高度。 width pixels 设置视频播放器的宽度。 loop loop 如果出现该属性,则当媒介文件完成播放后再次开始播放。 muted muted 规定视频的音频输出应该被静音。 poster URL 规定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。 preload pixels 如果出现该属性,则视频在页面加载时进行加载,并预备播放。如果使用 “autoplay”,则忽略该属性。 src URL 要播放的视频的 URL。 Video事件 属性 值 描述 oncanplay script 当文件就绪可以开始播放时运行的脚本(缓冲已足够开始时)。 oncanplaythrough script 当媒介能够无需因缓冲而停止即可播放至结尾时运行的脚本。 onemptied script 当发生故障并且文件突然不可用时运行的脚本(比如连接意外断开时)。 onended script 当媒介已到达结尾时运行的脚本(可发送类似“感谢观看”之类的消息)。 onerror script 当在文件加载期间发生错误时运行的脚本。 onloadeddata script 当媒介数据已加载时运行的脚本。 onpause script 当媒介被用户或程序暂停时运行的脚本。 onplay script 当媒介已就绪可以开始播放时运行的脚本。 onplaying script 当媒介已开始播放时运行的脚本。 onprogress script 当浏览器正在获取媒介数据时运行的脚本。 ontimeupdate script 当播放位置改变时(比如当用户快进到媒介中一个不同的位置时)运行的脚本。 onvolumechange script 每当音量改变时(包括将音量设置为静音)时运行的脚本。 onwaiting script 当媒介已停止播放但打算继续播放时(比如当媒介暂停已缓冲更多数据) 运行脚本12345678910111213141516171819202122232425<video width="100%" height="100%" id="playVideo"> <source src="视频地址" type="video/mp4"></source></video><!--自定义进度条和音量控制--><div class="playControll"> <div class="playPause playIcon"></div> <div class="timebar"> <span class="currentTime">0:00:00</span> <div class="progress"> <div class="progress-bar progress-bar-danger progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width: 0%"></div> </div> <span class="duration">0:00:00</span> </div> <div class="otherControl"> <span class="volume glyphicon glyphicon-volume-down"></span> <span class="fullScreen glyphicon glyphicon-fullscreen"></span> <div class="volumeBar"> <div class="volumewrap"> <div class="progress"> <div class="progress-bar progress-bar-danger" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width: 8px;height: 40%;"></div> </div> </div> </div> </div></div> js 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879var myVid=document.getElementById("playVideo");//播放的速度myVid.playbackRate = 1myVid.onloadstart = function(){ console.log(`onloadstart 客户端开始请求数据 `);}myVid.ondurationchange=function(){ console.log(`durationchange 资源长度改变 `);}myVid.onratechange=function(){ console.log(`onratechange //播放速率改变 `);}myVid.onseeking=function(){ console.log(` //seeking 寻找中 点击一个为(缓存)下载的区域`);}myVid.onseeked=function(){ console.log(` //seeked 寻找完毕 `);}myVid.onplay=function(){ console.log(`开始播放时触发 `);}myVid.onwaiting=function(){ console.log(`播放由于下一帧数据未获取到导致播放停止,但是播放器没有主动预期其停止,仍然在努力的获取数据,简单的说就是在等待下一帧视频数据,暂时还无法播放。 `);}myVid.onplaying=function(){ console.log(`真正处于播放的状态,这个时候我们才是真正的在观看视频。 `);}myVid.oncanplay=function(){ console.log(`视频播放器已经可以开始播放视频了,但是只是预期可以正常播放,不保证之后的播放不会出现缓冲等待。 `);}myVid.onpause=function(){ console.log(`暂停播放时触发 `);}myVid.onended=function(){ alert(` //播放结束 loop 的情况下不会触发 `);}myVid.onvolumechange=function(){ console.log(`音量改变 `);}myVid.onloadedmetadata=function(){ console.log(`获取视频meta信息完毕,这个时候播放器已经获取到了视频时长和视频资源的文件大小。 `);}myVid.onloadeddata=function(){ console.log(`"视频播放器第一次完成了当前播放位置的视频渲染。"`);}myVid.onabort=function(){ console.log(`客户端主动终止下载(不是因为错误引起), `);}myVid.onerror=function(){ console.log(`请求数据时遇到错误`); //1.用户终止 2.网络错误 3.解码错误 4.URL无效 alert(myVid.error.code);}//客户端请求数据myVid.onprogress=function(){ console.log(`客户端正在请求数据 触发多次,是分段请求的`); console.log(myVid.buffered); //0.此元素未初始化 1.正常但没有使用网络 2.正在下载数据 3.没有找到资源 console.log(`networkState ${myVid.networkState}`); // //当前播放的位置,赋值可改变位置 myVid.currentTime = 11 从11秒位置开始播放 console.log(myVid.currentTime); // //返回当前资源的URL console.log(myVid.currentSrc); console.log(myVid.videoWidth); //播放结束 返回true 或 false console.log(myVid.ended); //音量大小 为0-1 之间的值 console.log(myVid.volume); //当前资源长度 console.log(myVid.duration); console.log(myVid.startDate) // myVid.currentTime = 11} 视频控制12345678910111213141516171819202122232425var myVid=document.getElementById("playVideo");myVid.play(); //播放视频myVid.pause(); //暂停视频myVid.width=560; //设置视频宽度myVid.height=560; //设置视频高度myVid.volume = 0.8; // 音量控制全屏和退出全屏// 全屏if (playVideo[0].requestFullscreen) { playVideo[0].requestFullscreen();} else if (playVideo[0].mozRequestFullScreen) { playVideo[0].mozRequestFullScreen();} else if (playVideo[0].webkitRequestFullscreen) { playVideo[0].webkitRequestFullscreen();} else if (playVideo[0].msRequestFullscreen) { playVideo[0].msRequestFullscreen();}// 退出全屏if (document.exitFullscreen) { document.exitFullscreen();} else if (document.mozExitFullScreen) { document.mozExitFullScreen();} else if (document.webkitExitFullscreen) { document.webkitExitFullscreen();} Media方法和属性 HTMLVideoElement和HTMLAudioElement 均继承自HTMLMediaElement Media.error; //null:正常 Media.error.code; //1.用户终止 2.网络错误 3.解码错误 4.URL无效 网络状态 Media.currentSrc; //返回当前资源的URL Media.src = value; //返回或设置当前资源的URL Media.canPlayType(type); //是否能播放某种格式的资源 Media.networkState; //0.此元素未初始化 1.正常但没有使用网络 2.正在下载数据 3.没有找到资源 Media.load(); //重新加载src指定的资源 Media.buffered; //返回已缓冲区域,TimeRanges Media.preload; //none:不预载 metadata:预载资源信息 auto: 3.2准备状态 Media.readyState; HAVE_NOTHING HAVE_METADATA HAVE_CURRENT_DATA HAVE_FUTURE_DATA HAVE_ENOUGH_DATA Media.seeking; //是否正在seeking回放状态 Media.currentTime = value; //当前播放的位置,赋值可改变位置 Media.startTime; //一般为0,如果为流媒体或者不从0开始的资源,则不为0 Media.duration; //当前资源长度 流返回无限 Media.paused; //是否暂停 Media.defaultPlaybackRate = value;//默认的回放速度,可以设置 Media.playbackRate = value;//当前播放速度,设置后马上改变 Media.played; //返回已经播放的区域,TimeRanges,关于此对象见下文 Media.seekable; //返回可以seek的区域 TimeRanges Media.ended; //是否结束 Media.autoPlay; //是否自动播放 Media.loop; //是否循环播放 Media.play(); //播放 Media.pause(); //暂停 视频控制 Media.controls;//是否有默认控制条 Media.volume = value; //音量 Media.muted = value; //静音 TimeRanges(区域)对象 TimeRanges.length; //区域段数 TimeRanges.start(index) //第index段区域的开始位置 TimeRanges.end(index) //第index段区域的结束位置 常见应用视频非全屏播放12345<video x5-video-player-type="h5" playsinline webkit-playsinline></video> 隐藏视频右下角的三个点123456 <video //关闭下载, 音轨 controlslist="nodownload noremoteplayback" //禁止画中画 disablePictureInPicture/> 摘自: https://cloud.tencent.com/developer/article/1588145 和https://cloud.tencent.com/developer/article/1462727?from=10680","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"emit中是修改文件的最后机会","slug":"emit中是修改文件的最后机会","date":"2020-06-19T15:47:09.000Z","updated":"2020-06-19T15:47:09.000Z","comments":true,"path":"2020/06/19/emit中是修改文件的最后机会/","link":"","permalink":"https://over58.github.io/2020/06/19/emit%E4%B8%AD%E6%98%AF%E4%BF%AE%E6%94%B9%E6%96%87%E4%BB%B6%E7%9A%84%E6%9C%80%E5%90%8E%E6%9C%BA%E4%BC%9A/","excerpt":"","text":"以一个 webpack-console-clear 插件为例,去掉每一个文件中 console 函数 12345678910111213141516171819202122232425262728compiler.hooks.emit.tapAsync('emit', (compilation, callback) => { compilation.chunks.forEach(chunk => { chunk.files.forEach(filename => { var source = compilation.assets[filename].source() var consoleName = ['console', 'window.console'] var consoleType = ['log', 'info', 'warn', 'error', 'assert', 'count', 'clear', 'group', 'groupEnd', 'groupCollapsed', 'trace', 'debug', 'dir', 'dirxml', 'profile', 'profileEnd', 'time', 'timeEnd', 'timeStamp', 'table', 'exception'] const rConsole = new RegExp('(' + consoleName.join('|') + ')' + '.(?:' + consoleType.join('|') + ')\\\\s{0,}\\\\([^;]*\\\\)(?!\\\\s*[;,]?\\\\s*\\\\/\\\\*\\\\s*NotClearConsole\\\\s*\\\\*\\\\/)\\\\s{0,};?', 'gi') source = source.replace(rConsole, function () { return source.replaceWith || '' }) compilation.assets[filename] = { source: function () { return source }, size: function () { return source.length } } }) }) callback() })","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"监听模式下获取到更改的文件","slug":"监听模式下获取到更改的文件","date":"2020-06-19T15:05:01.000Z","updated":"2020-06-19T15:05:01.000Z","comments":true,"path":"2020/06/19/监听模式下获取到更改的文件/","link":"","permalink":"https://over58.github.io/2020/06/19/%E7%9B%91%E5%90%AC%E6%A8%A1%E5%BC%8F%E4%B8%8B%E8%8E%B7%E5%8F%96%E5%88%B0%E6%9B%B4%E6%94%B9%E7%9A%84%E6%96%87%E4%BB%B6/","excerpt":"","text":"有时候我们需要直到触发此次 compilation 的文件是哪个,可以在watch 模式下,添加watchRun 钩子函数,获得修改的文件 1234567myWebpackPlugin.prototype.apply = function(compiler) { compiler.hooks.watchRun.tapAsync('watchRun', (compiler, callback) => { // 可以获取到更改的文件, 开发者可以做一些额外的事情 console.log(compiler.watchFileSystem.watcher.mtimes) callback() })}","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"iview组件探索","slug":"iview组件探索","date":"2020-05-25T19:30:37.000Z","updated":"2020-05-25T19:30:37.000Z","comments":true,"path":"2020/05/25/iview组件探索/","link":"","permalink":"https://over58.github.io/2020/05/25/iview%E7%BB%84%E4%BB%B6%E6%8E%A2%E7%B4%A2/","excerpt":"","text":"directives1234567891011121314151617181920Vue.directive('clickoutside', { bind (el, binding, vnode) { function documentHandler(e){ if(el.contains(e.target)) { return false } if(binding.value) { binding.value(e) } } el.__vueClickOutside__ = documentHandler document.addEventListener('click', documentHandler) }, unbind(el, binding){ document.removeEventListener('click', el.__vueClickOutside__) delete el.__vueClickOutside__ }}) utils12345678910111213141516171819202122232425262728293031323334353637383940export function camelcaseToHyphen (str) { return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();}function scrollTop(el, from = 0, to, duration = 500, endCallback) { let ratio = 50; // 随意设置的因子,根据具体情况设置的,无实际逻辑意义,无需纠结 if (!window.requestAnimationFrame) { window.requestAnimationFrame = ( window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { return window.setTimeout(callback, 1000 / 60); } ); } const difference = Math.abs(from - to); const step = Math.ceil(difference / duration * ratio); function scroll(start, end, step) { if (start === end) { endCallback && endCallback(); return; } let d = (start + step > end) ? end : start + step; if (start > end) { d = (start - step < end) ? end : start - step; } if (el === window) { window.scrollTo(d, d); } else { el.scrollTop = d; } window.requestAnimationFrame(() => scroll(d, end, step)); } scroll(from, to, step); } 查找组件的相关函数123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778function findComponentDownward(context, componentName) { const childrens = context.children let children = null if(childrens.length) { for(const child of childrens) { const {name} = child.$options if(name === componentName) { children = child break }else{ // 深度优先遍历 children = findComponentDownward(child, componentName) if(children) break } } } return children}// ignoreComponnetNames指的是不再往下找了function findComponentsDownward(context, componentName, ignoreComponnetNames = []){ if(!Array.isArray(ignoreComponnetNames)) { ignoreComponnetNames = [ignoreComponnetNames] } return context.$children.reduce((components, child) => { if(child.$options.name === componentName) { components.push(child) } if(ignoreComponnetNames.indexOf(child.$options.name) < 0) { const foundChilds = findComponentDownward(child, componentName) return components.concat(foundChilds) }else{ return components } }, [])}function findComponentUpward(context, componentName) { if(typeof componentName === 'string') { componentNames = [componentName] }else{ componentNames = componentName } let parent = context.$parent let name = parent.$options.name while(parent && (!name || componentNames.indexOf(componentName)<0)){ parent = parent.$parent if(parent) name = parent.$optins.name } return parent}// 递归向上查找function findComponentsUpward(context, componentName, ignoreComponnetNames) { let parents = [] const parent = context.$parent if(parent) { if(parent.$options.name === componentName) parents.push(parent) return parents.concat(findComponentsDownward(parent, componentName)) }else{ return [] }}function findBrothersComponents (context, componentName, exceptMe = ture) { let res = context.$parent.$children.filter(item => { return item.$options.name === componentName }) let index = res.findIndex(item => item._uid === context._uid) if(exceptMe) res.splice(index,1) return res} dom 上添加监听和移除监听的简单封装123456789101112131415161718192021222324252627const on = (function(){ if(document.addEventListener) { return function(element, event, handler, useCapture = true) { element.addEventListener(event, handler, useCapture) } }else{ return function(element, event, handler) { if(element && event && handler) { element.attchEvent('on'+ event, handler) } } }})()const off = (function(){ if(document.removeEventListener) { return function(element, event, handler, useCapture = true) { element.removeEventListener(event, handler, useCapture) } }else{ return function(element, event, handler) { if(element && event && handler) { element.detachEvent('on'+ event, handler) } } }})()","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"git-reset的基本操作","slug":"git-reset的基本操作","date":"2020-05-02T12:33:28.000Z","updated":"2020-05-02T12:33:28.000Z","comments":true,"path":"2020/05/02/git-reset的基本操作/","link":"","permalink":"https://over58.github.io/2020/05/02/git-reset%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/","excerpt":"","text":"有时候,我们用Git的时候有可能commit提交代码后,发现这一次commit的内容是有错误的,那么有两种处理方法: 1、修改错误内容,再次commit一次 2、使用git reset 命令撤销这一次错误的commit 第一种方法比较直接,但会多次一次commit记录。 而我个人更倾向第二种方法,错误的commit没必要保留下来。 那么今天来说一下git reset。它的一句话概括 1git-reset - Reset current HEAD to the specified state 意思就是可以让HEAD这个指针指向其他的地方。例如我们有一次commit不是不是很满意,需要回到上一次的Commit里面。那么这个时候就需要通过reset,把HEAD指针指向上一次的commit的点。 它有三种模式,soft,mixed,hard,具体的使用方法下面这张图,展示的很全面了。 reset 不加参数(mixed):保留工作目录,并清空暂存区reset 如果不加参数,那么默认使用 --mixed 参数。它的行为是:保留工作目录,并且清空暂存区。也就是说,工作目录的修改、暂存区的内容以及由 reset 所导致的新的文件差异,都会被放进工作目录。简而言之,就是「把所有差异都混合(mixed)放在工作目录中」。reset –soft保留工作目录,并把重置 HEAD 所带来的新的差异放进暂存区(staged)reset –hard 重置stage区和工作目录reset --hard 会在重置 HEAD 和branch的同时,重置stage区和工作目录里的内容。当你在 reset 后面加了 --hard 参数时,你的stage区和工作目录里的内容会被完全重置为和HEAD的新位置相同的内容。换句话说,就是你的没有commit的修改会被全部擦掉。","categories":[{"name":"git","slug":"git","permalink":"https://over58.github.io/categories/git/"}],"tags":[],"author":["徐勇超"]},{"title":"git基本操作","slug":"git基本操作","date":"2020-05-02T12:05:23.000Z","updated":"2020-05-02T12:05:23.000Z","comments":true,"path":"2020/05/02/git基本操作/","link":"","permalink":"https://over58.github.io/2020/05/02/git%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/","excerpt":"最近一次 Commit 的代码有问题12git add 我是修改内容.txtgit commit --amend 【amend】修正,会对最新一条 commit 进行修正,会把当前 commit 里的内容和暂存区(stageing area)里的内容合并起来后创建一个新的 commit,用这个新的 commit 把当前 commit 替换掉。 输入上面的命令后,Git 会进入提交信息编辑界面,然后你可以删除之前的 changeId,并且修改或者保留之前的提交信息,:wq 保存按下回车后,你的 commit 就被更新了。对于 amend 还可能出现几种小问题,下面列举下: 刚刚写的提交信息有问题,想修改怎么办?1git commit --amend -m "新的提交信息" 刚刚提交完代码发现,我有个文件没保存,漏了提交上去怎么办?最简单的方式,再次 commit: 1git commit -m "提交信息" 另一中方式,使用–no-edit,它表示提交信息不会更改,在 git 上仅为一次提交。 12git add changgeFile // changeFile 刚刚漏了提交的文件git commit --amend --no-edit","text":"最近一次 Commit 的代码有问题12git add 我是修改内容.txtgit commit --amend 【amend】修正,会对最新一条 commit 进行修正,会把当前 commit 里的内容和暂存区(stageing area)里的内容合并起来后创建一个新的 commit,用这个新的 commit 把当前 commit 替换掉。 输入上面的命令后,Git 会进入提交信息编辑界面,然后你可以删除之前的 changeId,并且修改或者保留之前的提交信息,:wq 保存按下回车后,你的 commit 就被更新了。对于 amend 还可能出现几种小问题,下面列举下: 刚刚写的提交信息有问题,想修改怎么办?1git commit --amend -m "新的提交信息" 刚刚提交完代码发现,我有个文件没保存,漏了提交上去怎么办?最简单的方式,再次 commit: 1git commit -m "提交信息" 另一中方式,使用–no-edit,它表示提交信息不会更改,在 git 上仅为一次提交。 12git add changgeFile // changeFile 刚刚漏了提交的文件git commit --amend --no-edit 最新提交的代码没问题,它上一次提交的有问题怎么办?上面说的是最新一次的提交出了问题,接下来说之前提交的代码发现有问题了想修改,应该怎么办?需要一个新的命令: 1git rebase -i rebase -i 是 rebase –interactive 的缩写形式,意为「交互式 rebase」。所谓「交互式 rebase」,就是在 rebase 的操作执行之前,你可以指定要 rebase 的 commit 链中的每一个 commit 是否需要进一步修改。 注意点:看 commit 历史的时候,最新的提交在最下面,刚开始使用时候总是搞错。 刚刚写完的提交太烂了,不想改了,想直接丢弃怎么办?你可以用 reset –hard 来撤销 commit 1git reset --hard HEAD^ HEAD 表示 HEAD^ 往回数一个位置的 commit ,HEAD^ 表示你要恢复到哪个 commit。因为你要撤销最新的一个 commit,所以你需要恢复到它的父 commit ,也就是 HEAD^。那么在这行之后,你的最新一条就被撤销了。 Git 代码已经 push 上去发现有问题情况一:如果出错内容还在私有分支这种情况你修改后,再次提交会报错,由于你在本地对已有的 commit 做了修改,这时你再 push 就会失败,因为中央仓库包含本地没有的 commits。这种情况只在你自己的分支 branch1 ,可以使用强制 push 的方式解决冲突。 1git push origin branch1 -f -f 是 –force 的缩写,意为「忽略冲突,强制 push」 情况2:如果出错内容已经 push 到了 master 分支这种情况可以使用 Git 的 revert 指令。 1git revert HEAD^ 上面这行代码就会增加一条新的 commit,它的内容和倒数第二个 commit 是相反的,从而和倒数第二个 commit 相互抵消,达到撤销的效果。在 revert 完成之后,把新的 commit 再 push 上去,这个 commit 的内容就被撤销了。revert 与前面说的 reset 最主要的区别是,这次改动只是被「反转」了,并没有在历史中消失掉,你的历史中会存在两条 commit :一个原始 commit ,一个对它的反转 commit。 Git 关于暂存的问题假如正在开发手中需求的时候,突然来了个紧急 bug 要修复,这时候需要先 stash 已经写的部分代码,使自己返回到上一个 commit 改完 bug 之后从缓存栈中推出之前的代码,继续工作。 添加缓存栈: git stash 查看缓存栈: git stash list 推出缓存栈: git stash pop 取出特定缓存内容:git stash apply stash@{1} 注意:没有被 track 的文件(即从来没有被 add 过的文件不会被 stash 起来,因为 Git 会忽略它们。如果想把这些文件也一起 stash,可以加上 -u 参数,它是 –include-untracked 的简写。就像这样:git stash -u","categories":[{"name":"git","slug":"git","permalink":"https://over58.github.io/categories/git/"}],"tags":[],"author":["徐勇超"]},{"title":"如何为网站接入百度统计","slug":"如何为网站接入百度统计","date":"2020-04-29T19:49:29.000Z","updated":"2020-04-29T19:49:29.000Z","comments":true,"path":"2020/04/29/如何为网站接入百度统计/","link":"","permalink":"https://over58.github.io/2020/04/29/%E5%A6%82%E4%BD%95%E4%B8%BA%E7%BD%91%E7%AB%99%E6%8E%A5%E5%85%A5%E7%99%BE%E5%BA%A6%E7%BB%9F%E8%AE%A1/","excerpt":"","text":"step 1添加网站,并点击“获取代码”按钮,然后到了下图 复制代码并添加到网站的页面中 step 2在需要埋点的地方调用 1234567/**category: 自定义action: click|showlabel: 自定义value: 置为''就行, 目前没发现有啥用处**/window._hmt.push(['_trackEvent', category, action, label, value]) step 3讲代码提交发布后,等上一段时间,并不会立刻实时的显示出来,不要以为自己写错了,一通排查… 成功后可以查看到如图所示:","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"electron间的通信","slug":"electron间的通信","date":"2020-04-29T10:24:08.000Z","updated":"2020-04-29T10:24:08.000Z","comments":true,"path":"2020/04/29/electron间的通信/","link":"","permalink":"https://over58.github.io/2020/04/29/electron%E9%97%B4%E7%9A%84%E9%80%9A%E4%BF%A1/","excerpt":"示意图主进程和子进程间的通信渲染进程—->主进程(利用 ipcMain, ipcRenderer和 webContents.send)渲染进程:12const {ipcRenderer} = require('electron')ipcRenderer.send('custom-event', 'I am renderer process') 主进程1234const {ipcMain} = require('electron')ipcMain.on('custom-event', ({sender}, args)=>{ sender.send('reply', '我收到了')})","text":"示意图主进程和子进程间的通信渲染进程—->主进程(利用 ipcMain, ipcRenderer和 webContents.send)渲染进程:12const {ipcRenderer} = require('electron')ipcRenderer.send('custom-event', 'I am renderer process') 主进程1234const {ipcMain} = require('electron')ipcMain.on('custom-event', ({sender}, args)=>{ sender.send('reply', '我收到了')}) 主进程—->渲染进程主进程123456789// 主进程.main.jsvar mainWindow = null;app.on('ready', function() { mainWindow = new BrowserWindow({width: 800, height: 600}); mainWindow.loadURL('file://' + __dirname + '/index.html'); mainWindow.webContents.on('did-finish-load', function() { mainWindow.webContents.send('ping', 'whoooooooh!'); });}); 渲染进程1234const {ipcRenderer} = require('electron') ipcRenderer.on('ping', ({sender, args}) =>{ console.log(args) }) 注意,webContents.on 监听的是已经定义好的事件,如上面的 did-finish-load。要监听自定义的事件还是通过 ipcMain 和 ipcRenderer。 渲染进程 A 和 渲染进程 B之间的通信通过主进程 mainProcess作为中转站,转发事件 利用 electron.remote 模块进行通信在渲染进程中,可以通过 1const { remote } = require('electron'); 获取到 remote 对象,通过 remote 对象可以让渲染进程访问/使用主进程的模块。例如,通过 remote 在渲染进程中新建一个窗口: 123const { BrowserWindow } = require('electron').remote let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('https://github.com') 同样的,我们也可以通过 remote 对象访问到 app 对象。这样我们就可以访问到我们在主进程中挂载到 electron.app 对象上的方法。 如: main.js 文件: 12345// In main processconst { app } = require('electron');const utils = require('./utils');app.utils = utils; // 将在 Electron 层实现的接口绑定到 app 上 index.js 文件(被网页引用的脚本文件): 123456const { remote } = require('electron');// In renderer processfunction() { // remote.app.utils 对象与上述文件中的 utils 对象是一样的。 remote.app.utils.test();} Electron 的两种进程通信方法是如何实现的?知道怎么用还不够,还需要了解 Electron 是如何实现这两种通信方法的,以及 Electron 为什么要实现两种通信方法,这两种通信方法的有什么不同的地方。弄清楚这些开发起来才会对程序的数据流比较清晰。 ipcMain 和 ipcRenderer The ipcMain module is an instance of the EventEmitter class. When used in the main process, it handles asynchronous and synchronous messages sent from a renderer process (web page). Messages sent from a renderer will be emitted to this module. ipcMain 和 ipcRenderer 都是 EventEmitter 类的一个实例。而 EventEmitter 类由 NodeJS 中的 events 模块导出。 events.EventEmitterEventEmitter 类是 NodeJS 事件的基础,实现了事件模型需要的接口, 包括 addListener,removeListener, emit 及其它工具方法. 同原生 JavaScript 事件类似, 采用了发布/订阅(观察者)的方式, 使用内部 _events 列表来记录注册的事件处理器。 我们通过 ipcMain和ipcRenderer 的 on、send 进行监听和发送消息都是 EventEmitter 定义的相关接口。 那么 ipcMain 和 ipcRenderer 是如何实现这些接口的呢? ipc-renderer.jsipc-renderer.js: 1234567891011121314const binding = process.atomBinding('ipc')...// Created by init.js.const ipcRenderer = v8Util.getHiddenValue(global, 'ipc')ipcRenderer.send = function (...args) { return binding.send('ipc-message', args)}....module.exports = ipcRenderer 调用了 atomBinding('ipc') 得到的 binding 对象的 send 方法。后面 binding.send 应该就是 IPC 相关的实现了:对传送的数据进行序列化和反序列化。 12345678910111213141516// 主进程ipcMain.on('test1', (e) => { const obj = {}; obj.toJSON = () => 'call toJSON'; e.returnValue = obj;})ipcMain.on('test2', (e) => { const obj = { name: '123' }; e.returnValue = obj;})// 渲染进程let returnValue = ipcRenderer.sendSync('test1');console.log(typeof returnValue, returnValue); // 'string call toJSON'returnValue = ipcRenderer.sendSync('test2');console.log(typeof returnValue, returnValue); // 'object Object name: "123"__proto__: Object' 从渲染进程输出的消息可以看到,主进程将返回值调用 toJSON 后传递给渲染进程。渲染进程再对传输过来的内容进行反序列化。 remote 远程对象通过 remote 对象,我们可以不必发送进程间消息来进行通信。但实际上,我们在调用远程对象的方法、函数或者通过远程构造函数创建一个新的对象,实际上都是在发送一个同步的进程间消息(官方文档 上说这类似于 JAVA 中的 RMI)。 也就是说,remote 方法只是不用让我们显式的写发送进程间的消息的方法而已。在上面通过 remote 模块创建 BrowserWindow 的例子里。我们在渲染进程中创建的 BrowserWindow 对象其实并不在我们的渲染进程中,它只是让主进程创建了一个 BrowserWindow 对象,并返回了这个相对应的远程对象给了渲染进程。 摘自 https://imweb.io/topic/5b13a663d4c96b9b1b4c4e9c","categories":[{"name":"electron","slug":"electron","permalink":"https://over58.github.io/categories/electron/"}],"tags":[],"author":["徐勇超"]},{"title":"webpack之tapable","slug":"webpack之tapable","date":"2020-04-11T16:33:04.000Z","updated":"2020-04-11T16:33:04.000Z","comments":true,"path":"2020/04/11/webpack之tapable/","link":"","permalink":"https://over58.github.io/2020/04/11/webpack%E4%B9%8Btapable/","excerpt":"示意图 SyncSyncHook使用123456789101112131415161718192021222324252627const { SyncHook } = require('tapable')class Lesson { constructor() { this.hooks = { arch: new SyncHook(['arch']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tap('a', function(name) { console.log('node', name) }) this.hooks.arch.tap('b', function(name) { console.log('react', name) }) } // 触发事件 start() { this.hooks.arch.call('world') }}var a = new Lesson()a.tap()a.start()","text":"示意图 SyncSyncHook使用123456789101112131415161718192021222324252627const { SyncHook } = require('tapable')class Lesson { constructor() { this.hooks = { arch: new SyncHook(['arch']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tap('a', function(name) { console.log('node', name) }) this.hooks.arch.tap('b', function(name) { console.log('react', name) }) } // 触发事件 start() { this.hooks.arch.call('world') }}var a = new Lesson()a.tap()a.start() 原理123456789101112131415class SyncHook { constructor() { this.tasks = [] } tap(name, task) { this.tasks.push(task) } call(...args) { this.tasks.forEach(item => { item.task.call(null, ...args) }) }} SyncBailHook使用12345678910111213141516171819202122232425262728293031const { SyncBailHook } = require('tapable')// 指的是保险hook,可以随时停止后续事件的执行class Lesson { constructor() { this.hooks = { arch: new SyncBailHook(['arch', 'a']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tap('a', function(name) { console.log('node', name) // return '返回一个不为 === undefined的值,就会阻断后续的代码' return 0 }) this.hooks.arch.tap('b', function(name) { console.log('react', name) }) } // 触发事件 start() { this.hooks.arch.call('world') }}var a = new Lesson()a.tap()a.start() 原理123456789101112131415161718class SyncBailHook { constructor() { this.tasks = [] } tap(name, task) { this.tasks.push(task) } call(...args) { let res = true let i = 0 do { res = this.tasks[i++].call(null, ...args) } while (i < this.tasks.length && res === undefined) }} SyncWaterfallHook使用1234567891011121314151617181920212223242526272829const { SyncWaterfallHook } = require('tapable')class Lesson { constructor() { this.hooks = { arch: new SyncWaterfallHook(['arch', 'a']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tap('a', function(name) { console.log('node', name) return '返回数据作为下一个事件的参数(如果返回undefined的话,等同于没有返回,不做处理 )' }) this.hooks.arch.tap('b', function(data) { console.log('react', data) }) } // 触发事件 start() { this.hooks.arch.call('world') }}var a = new Lesson()a.tap()a.start() 原理123456789101112131415161718192021class SyncWaterfallHook { constructor() { this.tasks = [] } tap(name, task) { this.tasks.push(task) } call(...args) { const [first, ...others] = this.tasks const ret = first(...args) others.reduce((a, b) => { if (a === undefined) { return b(...args) } else { return b(a) } }, ret) }} SyncLoopHook使用123456789101112131415161718192021222324252627282930const { SyncLoopHook } = require('tapable')// 同步遇到某个不返回undefined的监听函数会执行多次class Lesson { constructor() { this.index = 0 this.hooks = { arch: new SyncLoopHook(['arch', 'a']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tap('a', (name) => { console.log('node', name) return ++this.index === 3 ? undefined : '继续学' }) this.hooks.arch.tap('b', function(name) { console.log('react', name) }) } // 触发事件 start() { this.hooks.arch.call('world') }}var a = new Lesson()a.tap()a.start() 原理1234567891011121314151617181920class SyncLoopHook { constructor() { this.tasks = [] } tap(name, task) { this.tasks.push(task) } call(...args) { let index = 0 let ret do { ret = this.tasks[index](...args) if (ret === undefined) { index++ } } while (index < this.tasks.length) }} AsyncAsyncParallelHook使用1234567891011121314151617181920212223242526272829303132333435const { AsyncParallelHook } = require('tapable')class Lesson { constructor() { this.hooks = { arch: new AsyncParallelHook(['arch']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tapAsync('a', (name, cb) => { setTimeout(() => { console.log('node', name) cb() }, 2000) }) this.hooks.arch.tapAsync('b', (name, cb) => { setTimeout(() => { console.log('react', name) cb() }, 1000) }) } // 触发事件 start() { this.hooks.arch.callAsync('world', function() { console.log('end') }) }}var a = new Lesson()a.tap()a.start() 原理-tapAsync1234567891011121314151617181920212223242526class AsyncParallelHook { constructor() { this.tasks = [] } tapAsync(name, task) { this.tasks.push({ name, task }) } callAsync(...args) { const cb = args.pop() let index = 0 const done = () => { index++ if (index === this.tasks.length) { cb() } } this.tasks.forEach(item => { item.task(...args, done) }) }} 原理-tapPromise1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556class AsyncParallelHook { constructor() { this.tasks = [] } tapPromise(name, task) { this.tasks.push({ name, task }) } promise(...args) { const tasks = this.tasks.map(task => task(...args)) return Promise.all(tasks) }}class Lesson { constructor() { this.hooks = { arch: new AsyncParallelHook(['arch', 'a']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tapPromise('a', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('node', name) resolve() }, 2000) }) }) this.hooks.arch.tapPromise('react', (name, cb) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('react', name) resolve() }, 2000) }) }) } // 触发事件 start() { this.hooks.arch.promise('world').then(() => { // 这个方法是所有的promise全部执行完之后才会调用这个的 console.log('end') }) }}var a = new Lesson()a.tap()a.start() AsyncParallelBailHook使用1234567891011121314151617181920212223242526272829303132333435363738394041const { AsyncParallelBailHook } = require('tapable')class Lesson { constructor() { this.hooks = { arch: new AsyncParallelBailHook(['arch']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tapPromise('a', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('node', name) reject('wrong') }, 1000) }) }) this.hooks.arch.tapPromise('b', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('react', name) resolve() }, 2000) }) }) } // 触发事件 start() { this.hooks.arch.promise('world').then(() => { console.log('success') }).catch(() => { console.log('fail') }) }}var a = new Lesson()a.tap()a.start() 原理12 AsyncSeriesHook使用12345678910111213141516171819202122232425262728293031323334353637const { AsyncSeriesHook } = require('tapable')// 异步串行class Lesson { constructor() { this.hooks = { arch: new AsyncSeriesHook(['arch']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tapAsync('a', (name, cb) => { setTimeout(() => { console.log('node', name) cb() }, 1000) }) this.hooks.arch.tapAsync('b', (name, cb) => { setTimeout(() => { console.log('react', name) cb() }, 1000) }) } // 触发事件 start() { this.hooks.arch.callAsync('world', function() { // 每一个结束后都回调这里,也可以自己计数实现类似于Promise.all的效果 console.log('end') }) }}var a = new Lesson()a.tap()a.start() 原理-tapAsync/callAsync1234567891011121314151617181920class AsyncSeriesHook { constructor() { this.tasks = [] } tapAsync(name, task) { this.tasks.push(task) } callAsync(...args) { const fn = args.pop() let index = 0 const next = () => { if (this.tasks.length === index) return fn() const task = this.tasks[index++] task(...args, next) } next() }} 原理tapPromise/promise12345678910111213141516class AsyncSeriesHook { constructor() { this.tasks = [] } tapPromise(name, task) { this.tasks.push(task) } promise(...args) { const [first, ...others] = this.tasks return others.reduce((p, n) => { // 跟redux的源码一致 return p.then(() => n(...args)) }, first(...args)) }} AsyncSeriesWaterfallHook使用12345678910111213141516171819202122232425262728293031323334353637const { AsyncSeriesWaterfallHook } = require('tapable')// 异步串行class Lesson { constructor() { this.hooks = { arch: new AsyncSeriesWaterfallHook(['arch']) } } // 往钩子上注册事件 tap() { this.hooks.arch.tapAsync('a', (name, cb) => { setTimeout(() => { console.log('node', name) cb() }, 2000) }) this.hooks.arch.tapAsync('b', (name, cb) => { setTimeout(() => { console.log('react', name) cb() }, 1000) }) } // 触发事件 start() { this.hooks.arch.callAsync('world', function() { // 每一个结束后都回调这里,也可以自己计数实现类似于Promise.all的效果 console.log('end') }) }}var a = new Lesson()a.tap()a.start() 原理1234567891011121314151617181920212223242526class AsyncSeriesWaterfallHook { constructor() { this.tasks = [] } tapAsync(name, task) { this.tasks.push(task) } callAsync(...args) { const fn = args.pop() let index = 0 const next = (err, data) => { const task = this.tasks[index] if (!task || err === 'error') return fn() if (index === 0) { task(data, next) } else { task(data, next) } index++ } next(null, ...args) }}","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"vue源码阅读","slug":"vue源码阅读","date":"2020-04-06T14:37:54.000Z","updated":"2020-04-06T14:37:54.000Z","comments":true,"path":"2020/04/06/vue源码阅读/","link":"","permalink":"https://over58.github.io/2020/04/06/vue%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/","excerpt":"","text":"new 的时候到底干了什么 入口文件时/core/index.js 1234567891011121314151617181920212223242526import Vue from './instance/index'import { initGlobalAPI } from './global-api/index'import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'initGlobalAPI(Vue)Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering})Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext }})// expose FunctionalRenderContext for ssr runtime helper installationObject.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext})Vue.version = '__VERSION__'export default Vue 定义Vue1234567891011121314151617//定义Vuefunction Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options)}initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue) initMixin1234567891011121314151617181920// 每次new vue()的时候都会调用这个方法Vue.prototype._init = function(){ //指定全局唯一的uid vm.uid = uid++ // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) //抛出beforeCreate Hooks, 也就是说在beforeCreate之前做的操作有 : // 1. 初始化生命周期,2 初始化事件, 3。 初始化render函数 callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')} stateMixin123456// 主要初始化了以下内容Vue.prototype.$dataVue.prototype.$propsVue.prototype.$watchVue.prototype.$setVue.prototype.$delele eventsMixin1234Vue.prototype.$onVue.prototype.$onceVue.prototype.$offVue.prototype.$emit lifecycleMixin123Vue.prototype._updateVue.prototype.$forceUpdateVue.prototype.$destroy renderMixin12Vue.prototype.$nextTick Vue.prototype._render global-api/index.js123456789101112131415161718192021222324252627282930Vue.util = { warn, extend, mergeOptions, defineReactive } Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 2.6 explicit observable API Vue.observable Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue// 继承keep-alive extend(Vue.options.components, builtInComponents) initUse(Vue) initMixin(Vue) initExtend(Vue) initAssetRegisters(Vue) global-api/use.js1Vue.use global-api/mixin.js1234Vue.mixin = function (mixin: Object) { this.options = mergeOptions(this.options, mixin) return this } global-api/extend.js1Vue.extend global-api/assets.js123456789101112export const ASSET_TYPES = [ 'component', 'directive', 'filter'] ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ) })","categories":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/categories/vue/"}],"tags":[],"author":["徐勇超"]},{"title":"前端性能监控","slug":"前端性能监控","date":"2020-04-05T20:49:02.000Z","updated":"2020-04-05T20:49:02.000Z","comments":true,"path":"2020/04/05/前端性能监控/","link":"","permalink":"https://over58.github.io/2020/04/05/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7/","excerpt":"performance时间顺序图 1.performance.timing对象 navigationStart:当前浏览器窗口的前一个网页关闭,发生unload事件时的Unix毫秒时间戳。如果没有前一个网页,则等于fetchStart属性。 unloadEventStart:如果前一个网页与当前网页属于同一个域名,则返回前一个网页的unload事件发生时的Unix毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0。 unloadEventEnd:如果前一个网页与当前网页属于同一个域名,则返回前一个网页unload事件的回调函数结束时的Unix毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0。 redirectStart:(重定向时间)返回第一个HTTP跳转开始时的Unix毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0。 redirectEnd:(重定向时间)返回最后一个HTTP跳转结束时(即跳转回应的最后一个字节接受完成时)的Unix毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0。 fetchStart:返回浏览器准备使用HTTP请求读取文档时的Unix毫秒时间戳。该事件在网页查询本地缓存之前发生。 domainLookupStart:返回域名查询开始时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值。 domainLookupEnd:返回域名查询结束时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值。 connectStart:返回HTTP请求开始向服务器发送时的Unix毫秒时间戳。如果使用持久连接(persistent connection),则返回值等同于fetchStart属性的值。 connectEnd:返回浏览器与服务器之间的连接建立时的Unix毫秒时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。 secureConnectionStart:返回浏览器与服务器开始安全链接的握手时的Unix毫秒时间戳。如果当前网页不要求安全连接,则返回0。 requestStart:返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的Unix毫秒时间戳。 responseStart:返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的Unix毫秒时间戳。 responseEnd:返回浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的Unix毫秒时间戳。 domLoading:返回当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的readystatechange事件触发时)的Unix毫秒时间戳。 domInteractive:返回当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的Unix毫秒时间戳。 domContentLoadedEventStart:返回当前网页DOMContentLoaded事件发生时(即DOM结构解析完毕、所有脚本开始运行时)的Unix毫秒时间戳。 domContentLoadedEventEnd:返回当前网页所有需要执行的脚本执行完成时的Unix毫秒时间戳。 domComplete:返回当前网页DOM结构生成时(即Document.readyState属性变为“complete”,以及相应的readystatechange事件发生时)的Unix毫秒时间戳。 loadEventStart:返回当前网页load事件的回调函数开始时的Unix毫秒时间戳。如果该事件还没有发生,返回0。 loadEventEnd:返回当前网页load事件的回调函数运行结束时的Unix毫秒时间戳。如果该事件还没有发生,返回0。 有了这些api,我们就可以计算一些需要的时间数据了","text":"performance时间顺序图 1.performance.timing对象 navigationStart:当前浏览器窗口的前一个网页关闭,发生unload事件时的Unix毫秒时间戳。如果没有前一个网页,则等于fetchStart属性。 unloadEventStart:如果前一个网页与当前网页属于同一个域名,则返回前一个网页的unload事件发生时的Unix毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0。 unloadEventEnd:如果前一个网页与当前网页属于同一个域名,则返回前一个网页unload事件的回调函数结束时的Unix毫秒时间戳。如果没有前一个网页,或者之前的网页跳转不是在同一个域名内,则返回值为0。 redirectStart:(重定向时间)返回第一个HTTP跳转开始时的Unix毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0。 redirectEnd:(重定向时间)返回最后一个HTTP跳转结束时(即跳转回应的最后一个字节接受完成时)的Unix毫秒时间戳。如果没有跳转,或者不是同一个域名内部的跳转,则返回值为0。 fetchStart:返回浏览器准备使用HTTP请求读取文档时的Unix毫秒时间戳。该事件在网页查询本地缓存之前发生。 domainLookupStart:返回域名查询开始时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值。 domainLookupEnd:返回域名查询结束时的Unix毫秒时间戳。如果使用持久连接,或者信息是从本地缓存获取的,则返回值等同于fetchStart属性的值。 connectStart:返回HTTP请求开始向服务器发送时的Unix毫秒时间戳。如果使用持久连接(persistent connection),则返回值等同于fetchStart属性的值。 connectEnd:返回浏览器与服务器之间的连接建立时的Unix毫秒时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。 secureConnectionStart:返回浏览器与服务器开始安全链接的握手时的Unix毫秒时间戳。如果当前网页不要求安全连接,则返回0。 requestStart:返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的Unix毫秒时间戳。 responseStart:返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的Unix毫秒时间戳。 responseEnd:返回浏览器从服务器收到(或从本地缓存读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的Unix毫秒时间戳。 domLoading:返回当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的readystatechange事件触发时)的Unix毫秒时间戳。 domInteractive:返回当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的Unix毫秒时间戳。 domContentLoadedEventStart:返回当前网页DOMContentLoaded事件发生时(即DOM结构解析完毕、所有脚本开始运行时)的Unix毫秒时间戳。 domContentLoadedEventEnd:返回当前网页所有需要执行的脚本执行完成时的Unix毫秒时间戳。 domComplete:返回当前网页DOM结构生成时(即Document.readyState属性变为“complete”,以及相应的readystatechange事件发生时)的Unix毫秒时间戳。 loadEventStart:返回当前网页load事件的回调函数开始时的Unix毫秒时间戳。如果该事件还没有发生,返回0。 loadEventEnd:返回当前网页load事件的回调函数运行结束时的Unix毫秒时间戳。如果该事件还没有发生,返回0。 有了这些api,我们就可以计算一些需要的时间数据了 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455// 计算加载时间function getPerformanceTiming () { var performance = window.performance; if (!performance) { // 当前浏览器不支持 console.log('你的浏览器不支持 performance 接口'); return; } var t = performance.timing; var times = {}; //【重要】页面加载完成的时间 //【原因】这几乎代表了用户等待页面可用的时间 times.loadPage = t.loadEventEnd - t.navigationStart; //【重要】解析 DOM 树结构的时间 //【原因】反省下你的 DOM 树嵌套是不是太多了! times.domReady = t.domComplete - t.responseEnd; //【重要】重定向的时间 //【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com times.redirect = t.redirectEnd - t.redirectStart; //【重要】DNS 查询时间 //【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长? // 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364) times.lookupDomain = t.domainLookupEnd - t.domainLookupStart; //【重要】读取页面第一个字节的时间 //【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么? // TTFB 即 Time To First Byte 的意思 // 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte times.ttfb = t.responseStart - t.navigationStart; //【重要】内容加载完成的时间 //【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么? times.request = t.responseEnd - t.requestStart; //【重要】执行 onload 回调函数的时间 //【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么? times.loadEvent = t.loadEventEnd - t.loadEventStart; // DNS 缓存时间 times.appcache = t.domainLookupStart - t.fetchStart; // 卸载页面的时间 times.unloadEvent = t.unloadEventEnd - t.unloadEventStart; // TCP 建立连接完成握手的时间 times.connect = t.connectEnd - t.connectStart; return times;} 2.performance.now()获取当前的微秒数,比Date.now()更精确,微秒是毫秒的1000倍。Date.now() 输出的是 UNIX 时间,即距离 1970 的时间,而 performance.now() 输出的是相对于 performance.timing.navigationStart(页面初始化) 的时间。 12345678910var start = performance.now();...var end = performance.now();console.log('耗时:' + (end - start) + '微秒。');Date.now()// 1544255347157performance.now()// 9580145.67 3.performance.mark()顾名思义,就是标记,在程序运行中对其进行时间标记。方便我们计算程序的运行耗时。 123456789101112131415161718192021// 执行前做个标记window.performance.mark('markStart');// do ...window.performance.mark('markEnd');// 测量两个标记之间的测量距离window.performance.measure('measure1', 'markStart', 'markEnd');// 看下保存起来的标记 markvar marks = window.performance.getEntriesByType('mark'); console.log(marks); // 看下保存起来的测量 measurevar measure = window.performance.getEntriesByType('measure'); console.log(measure); // 清除指定标记window.performance.clearMarks('markStart'); // 清除所有标记window.performance.clearMarks(); // 清除指定测量window.performance.clearMeasures('measure1'); // 清除所有测量window.performance.clearMeasures(); 4.performance.getEntries()浏览器获取网页时,会对网页中每一个对象(脚本文件、样式表、图片文件等等)发出一个HTTP请求。performance.getEntries方法以数组形式,返回这些请求的时间统计信息,有多少个请求,返回数组就会有多少个成员。以PerformanceNavigationTiming对象形式返回 5.performance.navigation对象提供一些用户行为信息 5.1 performance.navigation.type该属性返回一个整数值,表示网页的加载来源,可能有以下4种情况: 0:网页通过点击链接、地址栏输入、表单提交、脚本操作等方式加载,相当于常数performance.navigation.TYPE_NAVIGATENEXT。 1:网页通过“重新加载”按钮或者location.reload()方法加载,相当于常数performance.navigation.TYPE_RELOAD。 2:网页通过“前进”或“后退”按钮加载,相当于常数performance.navigation.TYPE_BACK_FORWARD。 255:任何其他来源的加载,相当于常数performance.navigation.TYPE_UNDEFINED。 5.2 performance.navigation.redirectCount该属性表示当前网页经过了多少次重定向跳转. 6.参考链接Performance API 初探 performance – 监控网页与程序性能 作者:Juliana_链接:https://www.jianshu.com/p/47a6b7866ba6来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"performance","slug":"performance","permalink":"https://over58.github.io/tags/performance/"}],"author":["徐勇超"]},{"title":"node.js压缩和解压缩","slug":"node-js压缩和解压缩","date":"2020-04-05T17:52:57.000Z","updated":"2020-04-05T17:52:57.000Z","comments":true,"path":"2020/04/05/node-js压缩和解压缩/","link":"","permalink":"https://over58.github.io/2020/04/05/node-js%E5%8E%8B%E7%BC%A9%E5%92%8C%E8%A7%A3%E5%8E%8B%E7%BC%A9/","excerpt":"Node基础:资源压缩之zlib概览做过web性能优化的同学,对性能优化大杀器gzip应该不陌生。浏览器向服务器发起资源请求,比如下载一个js文件,服务器先对资源进行压缩,再返回给浏览器,以此节省流量,加快访问速度。 浏览器通过HTTP请求头部里加上Accept-Encoding,告诉服务器,“你可以用gzip,或者defalte算法压缩资源”。 Accept-Encoding:gzip, deflate 那么,在nodejs里,是如何对资源进行压缩的呢?答案就是Zlib模块。 入门实例:简单的压缩/解压缩压缩的例子非常简单的几行代码,就完成了本地文件的gzip压缩。 123456789var fs = require('fs');var zlib = require('zlib');var gzip = zlib.createGzip();var inFile = fs.createReadStream('./extra/fileForCompress.txt');var out = fs.createWriteStream('./extra/fileForCompress.txt.gz');inFile.pipe(gzip).pipe(out);","text":"Node基础:资源压缩之zlib概览做过web性能优化的同学,对性能优化大杀器gzip应该不陌生。浏览器向服务器发起资源请求,比如下载一个js文件,服务器先对资源进行压缩,再返回给浏览器,以此节省流量,加快访问速度。 浏览器通过HTTP请求头部里加上Accept-Encoding,告诉服务器,“你可以用gzip,或者defalte算法压缩资源”。 Accept-Encoding:gzip, deflate 那么,在nodejs里,是如何对资源进行压缩的呢?答案就是Zlib模块。 入门实例:简单的压缩/解压缩压缩的例子非常简单的几行代码,就完成了本地文件的gzip压缩。 123456789var fs = require('fs');var zlib = require('zlib');var gzip = zlib.createGzip();var inFile = fs.createReadStream('./extra/fileForCompress.txt');var out = fs.createWriteStream('./extra/fileForCompress.txt.gz');inFile.pipe(gzip).pipe(out); 解压的例子同样非常简单,就是个反向操作。 123456789var fs = require('fs');var zlib = require('zlib');var gunzip = zlib.createGunzip();var inFile = fs.createReadStream('./extra/fileForCompress.txt.gz');var outFile = fs.createWriteStream('./extra/fileForCompress1.txt');inFile.pipe(gunzip).pipe(outFile); 服务端gzip压缩代码超级简单。首先判断 是否包含 accept-encoding 首部,且值为gzip。 否:返回未压缩的文件。 是:返回gzip压缩后的文件。 123456789101112131415161718192021222324252627var http = require('http');var zlib = require('zlib');var fs = require('fs');var filepath = './extra/fileForGzip.html';var server = http.createServer(function(req, res){ var acceptEncoding = req.headers['accept-encoding']; var gzip; if(acceptEncoding.indexOf('gzip')!=-1){ // 判断是否需要gzip压缩 gzip = zlib.createGzip(); // 记得响应 Content-Encoding,告诉浏览器:文件被 gzip 压缩过 res.writeHead(200, { 'Content-Encoding': 'gzip' }); fs.createReadStream(filepath).pipe(gzip).pipe(res); }else{ fs.createReadStream(filepath).pipe(res); }});server.listen('3000'); 服务端字符串gzip压缩代码跟前面例子大同小异。这里采用了zlib.gzipSync(str)对字符串进行gzip压缩。 12345678910111213141516171819var http = require('http');var zlib = require('zlib');var responseText = 'hello world';var server = http.createServer(function(req, res){ var acceptEncoding = req.headers['accept-encoding']; if(acceptEncoding.indexOf('gzip')!=-1){ res.writeHead(200, { 'content-encoding': 'gzip' }); res.end( zlib.gzipSync(responseText) ); }else{ res.end(responseText); }});server.listen('3000'); 压缩字符串并返回一个promise的完整例子123456789101112131415161718192021222324252627282930313233343536373839404142434445464748const path = require('path')const fs = require('fs')const zlib = require('zlib')/** * * @param {string} dest 写入文件 * @param {string, butter, unint8array} code 字符串 * @param {string} zip 是否需要压缩 */function write(dest, code, zip) { return new Promise((resolve, reject) => { function report (extra) { console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || '')) resolve() } if(zip) { zlib.gzip(code, (err, zipped)=>{ if(err) return reject(err) fs.writeFile(dest, zipped, (err) => { if (err) return reject(err); report(" (gzipped: " + getSize(zipped) + ")"); }); }) }else{ fs.writeFile(dest, code, err => { if (err) return reject(err); report() }) } })}function getSize(code) { return (code.length / 1024).toFixed(2) + "kb";}function blue(str) { return "\\x1b[1m\\x1b[34m" + str + "\\x1b[39m\\x1b[22m";}module.exports = write","categories":[{"name":"node","slug":"node","permalink":"https://over58.github.io/categories/node/"}],"tags":[],"author":["徐勇超"]},{"title":"electron学习笔记","slug":"electron学习笔记","date":"2020-04-05T11:39:56.000Z","updated":"2020-04-05T11:39:56.000Z","comments":true,"path":"2020/04/05/electron学习笔记/","link":"","permalink":"https://over58.github.io/2020/04/05/electron%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","excerpt":"","text":"开发第一个基础应用1234567const { app, BrowserWindow} = require('electron')function createWindow(){ var win = new BrowserWindow({height: 800, width:800}) win.loadFile('./index.html')}app.on('ready',createWindow) 常用点标题栏设置目录1mainWindow.setRepresentedFilename('/Users/xyc/Learn/projects/hexo-blog') 打开对话框12345678910111213141516171819202122let options = { title: '标题', message: 'mac中的标题', // createDirectory只针对 mac // multiSelections 用于多选 properties: ['openFile', 'createDirectory', 'multiSelections'], buttonLabel: '我的打开', default: '/Users/yongchao9/', filters: [ {name: '图片', extensions: ['jpg', 'png', 'gif', 'jpeg']}, {name: '视频', extensions: ['mp4', 'avi']}, // eslint-disable-next-line standard/object-curly-even-spacing {name: '全部', extensions: ['*'] } ] } if (process.platform === 'darwin') { options.properties.push('openDirectory') } dialog.showOpenDialog(options, (files) => { this.files = files }) 如果需要同时选择多个文件和目录的话,mac 和 windows的设置方法不同Mac: 需要同时指定 openFile和 openDirectoryWindows: 只需要指定 openFile, 就可以选择文件和目录,如果指定了openDirectory,就只能选择目录了","categories":[{"name":"electron","slug":"electron","permalink":"https://over58.github.io/categories/electron/"}],"tags":[],"author":["徐勇超"]},{"title":"svg和css的滤镜实现置灰效果","slug":"svg和css的滤镜实现置灰效果","date":"2020-02-27T15:42:24.000Z","updated":"2020-02-27T15:42:24.000Z","comments":true,"path":"2020/02/27/svg和css的滤镜实现置灰效果/","link":"","permalink":"https://over58.github.io/2020/02/27/svg%E5%92%8Ccss%E7%9A%84%E6%BB%A4%E9%95%9C%E5%AE%9E%E7%8E%B0%E7%BD%AE%E7%81%B0%E6%95%88%E6%9E%9C/","excerpt":"","text":"","categories":[{"name":"svg","slug":"svg","permalink":"https://over58.github.io/categories/svg/"}],"tags":[],"author":["徐勇超"]},{"title":"移动端iphone点击返回时强制页面刷新解决办法","slug":"移动端iphone点击返回时强制页面刷新解决办法","date":"2020-02-23T02:13:39.000Z","updated":"2020-02-23T02:13:39.000Z","comments":true,"path":"2020/02/23/移动端iphone点击返回时强制页面刷新解决办法/","link":"","permalink":"https://over58.github.io/2020/02/23/%E7%A7%BB%E5%8A%A8%E7%AB%AFiphone%E7%82%B9%E5%87%BB%E8%BF%94%E5%9B%9E%E6%97%B6%E5%BC%BA%E5%88%B6%E9%A1%B5%E9%9D%A2%E5%88%B7%E6%96%B0%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/","excerpt":"","text":"在做移动端项目的时候经常遇到这样一个功能比如: 返回后页面不刷新,一些失效的信息依然显示在页面上。这个问题在iphone手机上会出现。 onpageshow 事件在用户浏览网页时触发。 onpageshow 事件类似于 onload 事件,onload 事件在页面第一次加载时触发, onpageshow 事件在每次加载页面时触发,即 onload 事件在页面从浏览器缓存中读取时不触发,此外还有pagehide在不显示的时候触发。 为了查看页面是直接从服务器上载入还是从缓存中读取,可以使用 PageTransitionEvent 对象的 persisted 属性来判断。 1234window.addEventListener('pageshow', function(event) { console.log("PageShow Event " + event.persisted); console.log(event)}) 如果页面从浏览器的缓存中读取该属性返回 ture,否则返回 false。然后在根据true或false在执行相应的页面刷新动作或者直接ajax请求接口更新数据。这一点有个缺陷就是,无论是不是需要更新数据这个页面都会刷新,我们要做的只是数据变化了才需要更新。于是想到另一个办法在可能会出现数据变化的页面设置缓存,即为只要页面数据变化了就写缓存一条记录,在返回页面后检测到这条记录就说明需要页面刷新或调用接口刷新。 处理方法为: 12345678910// a.html 设置刷新 检测缓存是否有标志 要是有就说明数据有变化 a.html跳转到b.html页面window.addEventListener("pageshow", function(){ if(sessionStorage.getItem("need-refresh")){ location.reload(); sessionStorage.removeItem("need-refresh"); }});// b.html 如果是数据变化了就写一条缓存 b.html返回到a.html页面sessionStorage.setItem("need-refresh", true); 摘自https://www.cnblogs.com/wangmaoling/p/8022561.html","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"pageshow","slug":"pageshow","permalink":"https://over58.github.io/tags/pageshow/"}],"author":["徐勇超"]},{"title":"浏览器缓存","slug":"缓存","date":"2020-02-10T15:15:11.000Z","updated":"2020-02-10T15:15:11.000Z","comments":true,"path":"2020/02/10/缓存/","link":"","permalink":"https://over58.github.io/2020/02/10/%E7%BC%93%E5%AD%98/","excerpt":"浏览器缓存缓存的方案上面的内容让我们大概了解了缓存机制是怎样运行的,但是,服务器是如何判断缓存是否失效呢?我们知道浏览器和服务器进行交互的时候会发送一些请求数据和响应数据,我们称之为HTTP报文。报文中包含首部header和主体部分body。与缓存相关的规则信息就包含在header中。boby中的内容是HTTP请求真正要传输的部分。 缓存是性能优化中非常重要的一环,浏览器的缓存机制对开发也是非常重要的知识点。接下来以5个部分来把浏览器的缓存机制说清楚: 强缓存 协商缓存 缓存位置 缓存优点 不同刷新的请求执行过程","text":"浏览器缓存缓存的方案上面的内容让我们大概了解了缓存机制是怎样运行的,但是,服务器是如何判断缓存是否失效呢?我们知道浏览器和服务器进行交互的时候会发送一些请求数据和响应数据,我们称之为HTTP报文。报文中包含首部header和主体部分body。与缓存相关的规则信息就包含在header中。boby中的内容是HTTP请求真正要传输的部分。 缓存是性能优化中非常重要的一环,浏览器的缓存机制对开发也是非常重要的知识点。接下来以5个部分来把浏览器的缓存机制说清楚: 强缓存 协商缓存 缓存位置 缓存优点 不同刷新的请求执行过程 强缓存浏览器中的缓存作用分为两种情况,一种是需要发送HTTP请求,一种是不需要发送。 首先是检查强缓存,这个阶段不需要发送HTTP请求。 如何来检查呢?通过相应的字段来进行,但是说起这个字段就有点门道了。 在HTTP/1.0和HTTP/1.1当中,这个字段是不一样的。在早期,也就是HTTP/1.0时期,使用的是Expires,而HTTP/1.1使用的是Cache-Control。让我们首先来看看Expires。 ExpiresExpires即过期时间,存在于服务端返回的响应头中,告诉浏览器在这个过期时间之前可以直接从缓存里面获取数据,无需再次请求。比如下面这样: 1Expires: Wed, 22 Nov 2019 08:41:00 GMT 表示资源在2019年11月22号8点41分过期,过期了就得向服务端发请求。 这个方式看上去没什么问题,合情合理,但其实潜藏了一个坑,那就是服务器的时间和浏览器的时间可能并不一致,那服务器返回的这个过期时间可能就是不准确的。因此这种方式很快在后来的HTTP1.1版本中被抛弃了。 Cache-Control在HTTP1.1中,采用了一个非常关键的字段:Cache-Control。这个字段也是存在于 它和Expires本质的不同在于它并没有采用具体的过期时间点这个方式,而是采用过期时长来控制缓存,对应的字段是max-age。比如这个例子: 1Cache-Control:max-age=3600 代表这个响应返回后在 3600 秒,也就是一个小时之内可以直接使用缓存。 如果你觉得它只有max-age一个属性的话,那就大错特错了。 它其实可以组合非常多的指令,完成更多场景的缓存判断, 将一些关键的属性列举如下:public: 客户端和代理服务器都可以缓存。因为一个请求可能要经过不同的代理服务器最后才到达目标服务器,那么结果就是不仅仅浏览器可以缓存数据,中间的任何代理节点都可以进行缓存。 private:这种情况就是只有浏览器能缓存了,中间的代理服务器不能缓存。 no-cache: 跳过当前的强缓存,发送HTTP请求,即直接进入协商缓存阶段。 no-store:非常粗暴,不进行任何形式的缓存。 s-maxage:这和max-age长得比较像,但是区别在于s-maxage是针对代理服务器的缓存时间。 值得注意的是,当Expires和Cache-Control同时存在的时候,Cache-Control会优先考虑。 当然,还存在一种情况,当资源缓存时间超时了,也就是强缓存失效了,接下来怎么办?没错,这样就进入到第二级屏障——协商缓存了。 协商缓存强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存。 具体来说,这样的缓存tag分为两种: Last-Modified 和 ETag。这两者各有优劣,并不存在谁对谁有绝对的优势,跟上面强缓存的两个 tag 不一样。 Last-Modified即最后修改时间。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。 浏览器接收到后,如果再次请求,会在请求头中携带If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间。 服务器拿到请求头中的If-Modified-Since的字段后,其实会和这个服务器中该资源的最后修改时间对比: 如果请求头中的这个值小于最后修改时间,说明是时候更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。 否则返回304,告诉浏览器直接用缓存。 ETagETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过响应头把这个值给浏览器。 浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match这个字段的内容,并放到请求头中,然后发给服务器。 服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行比对: 如果两者不一样,说明要更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。 否则返回304,告诉浏览器直接用缓存。 两者对比 在精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况: 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。 Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。 在性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 Etag需要根据文件的具体内容生成哈希值。 另外,如果两种方式都支持的话,服务器会优先考虑ETag。 缓存位置前面我们已经提到,当强缓存命中或者协商缓存中服务器返回304的时候,我们直接从缓存中获取资源。那这些资源究竟缓存在什么位置呢? 浏览器中的缓存位置一共有四种,按优先级从高到低排列分别是: Service Worker Memory Cache Disk Cache Push Cache Service WorkerService Worker 借鉴了 Web Worker的 思路,即让 JS 运行在主线程之外,由于它脱离了浏览器的窗体,因此无法直接访问DOM。虽然如此,但它仍然能帮助我们完成很多有用的功能,比如离线缓存、消息推送和网络代理等功能。其中的离线缓存就是 Service Worker Cache。 Service Worker 同时也是 PWA 的重要实现机制,关于它的细节和特性,我们将会在后面的 PWA 的分享中详细介绍。 Memory Cache 和 Disk CacheMemory Cache指的是内存缓存,从效率上讲它是最快的。但是从存活时间来讲又是最短的,当渲染进程结束后,内存缓存也就不存在了。 Disk Cache就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是他的优势在于存储容量和存储时长。稍微有些计算机基础的应该很好理解,就不展开了。 好,现在问题来了,既然两者各有优劣,那浏览器如何决定将资源放进内存还是硬盘呢?主要策略如下: 比较大的JS、CSS文件会直接被丢进磁盘,反之丢进内存 内存使用率比较高的时候,文件优先进入磁盘 Push Cache即推送缓存,这是浏览器缓存的最后一道防线。它是 HTTP/2 中的内容,虽然现在应用的并不广泛,但随着 HTTP/2 的推广,它的应用越来越广泛。关于 Push Cache,有非常多的内容可以挖掘,不过这已经不是本文的重点,大家可以参考这篇扩展文章。 总结对浏览器的缓存机制来做个简要的总结: 首先通过 Cache-Control 验证强缓存是否可用 如果强缓存可用,直接使用 否则进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的Last-Modified或者ETag字段检查资源是否更新 若资源更新,返回资源和200状态码 否则,返回304,告诉浏览器直接从缓存获取资源 缓存的优点 减少了冗余的数据传递,节省宽带流量 减少了服务器的负担,大大提高了网站性能 加快了客户端加载网页的速度 这也正是HTTP缓存属于客户端缓存的原因。 不同刷新的请求执行过程 浏览器地址栏中写入URL,回车 浏览器发现缓存中有这个文件了,不用继续请求了,直接去缓存拿。(最快) F5 就是告诉浏览器,别偷懒,好歹去服务器看看这个文件是否有过期了。于是浏览器就战战兢兢的发送一个请求带上If-Modify-since。 Ctrl+F5 告诉浏览器,你先把你缓存中的这个文件给我删了,然后再去服务器请求个完整的资源文件下来。于是客户端就完成了强行更新的操作.","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"前端性能优化之重排、重绘与合成","slug":"前端性能优化之重排、重绘与合成","date":"2020-01-20T21:11:18.000Z","updated":"2020-01-20T21:11:18.000Z","comments":true,"path":"2020/01/20/前端性能优化之重排、重绘与合成/","link":"","permalink":"https://over58.github.io/2020/01/20/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B9%8B%E9%87%8D%E6%8E%92%E3%80%81%E9%87%8D%E7%BB%98%E4%B8%8E%E5%90%88%E6%88%90/","excerpt":"","text":"reflow重排(也称回流),指的是浏览器为了重新计算文档中元素的位置和几何结构而重新渲染部分或全部文档的过程。也就是说通过JavaScript或者CSS修改元素的几何位置属性,例如改变元素的宽度、高度等就会引发reflow。 以下行为会引发reflow: 页面渲染器初始化 添加或删除可见的DOM元素 盒模型相关的属性改变 定位属性及浮动相关的属性的改变 改变节点内部文字结构也会触发回流 浏览器窗口大小发生改变 repaint重绘,指的是当页面中元素样式的改变并不影响它在文档流中的位置时,例如通过JavaScript更改了字体颜色,浏览器会将新样式赋予给元素并重新绘制的过程。 以下行为会引发repaint: 页面中的元素更新样式风格相关的属性。 如想知道还有哪些属性会引发reflow或者repaint请查看 reflow和repaint的渲染过程先看看渲染流程线: 有了图中介绍的渲染流水线基础(看不懂的可以看我之前的文章),来看看reflow和repaint分别引发的渲染流程的哪些步骤。 1、 更新了元素的几何属性(reflow) 通过JavaScript或者CSS修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。如下图所示: 2、 更新元素的绘制属性(repaint) 通过JavaScript或者CSS修改元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。 3、直接合成阶段 可能都注意到了tiles后面的阶段不是在主线程上执行,也就是更改一个既不要布局也不要绘制的属性,这样的效率是最高的。比如使用CSS的transform来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。 总结在开发过程中要尽量减少重排,适当使用重绘,尽量使用合成。 作者:zhangwinwin链接:https://juejin.im/post/5e199d7a6fb9a02ffa6a83e1来源:掘金","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"进度条","slug":"进度条","date":"2020-01-13T15:07:33.000Z","updated":"2020-01-13T15:07:33.000Z","comments":true,"path":"2020/01/13/进度条/","link":"","permalink":"https://over58.github.io/2020/01/13/%E8%BF%9B%E5%BA%A6%E6%9D%A1/","excerpt":"","text":"","categories":[{"name":"style","slug":"style","permalink":"https://over58.github.io/categories/style/"}],"tags":[],"author":["徐勇超"]},{"title":"聊天框","slug":"聊天框","date":"2020-01-13T15:02:37.000Z","updated":"2020-01-13T15:02:37.000Z","comments":true,"path":"2020/01/13/聊天框/","link":"","permalink":"https://over58.github.io/2020/01/13/%E8%81%8A%E5%A4%A9%E6%A1%86/","excerpt":"","text":"","categories":[{"name":"style","slug":"style","permalink":"https://over58.github.io/categories/style/"}],"tags":[],"author":["徐勇超"]},{"title":"前端的常用转换操作","slug":"前端的常用转换操作","date":"2020-01-09T17:20:47.000Z","updated":"2021-02-26T06:07:21.802Z","comments":true,"path":"2020/01/09/前端的常用转换操作/","link":"","permalink":"https://over58.github.io/2020/01/09/%E5%89%8D%E7%AB%AF%E7%9A%84%E5%B8%B8%E7%94%A8%E8%BD%AC%E6%8D%A2%E6%93%8D%E4%BD%9C/","excerpt":"前端数据操作总结 src转img123456789101112function srctoimg(src){ return new Promise((reslove,reject)=>{ let img = new Image() img.onload = function(){ reslove(img) } img.onerror = function(err) { reject(err) } img.src = src })} img转canvas12345678function imgtocanvas(img){ let canvas = document.createElement("canvas"); let ctx = canvas.getContext('2d') canvas.width = img.width canvas.height = img.height ctx.drawImage(img, 0, 0, canvas.width, canvas.height); return canvas}","text":"前端数据操作总结 src转img123456789101112function srctoimg(src){ return new Promise((reslove,reject)=>{ let img = new Image() img.onload = function(){ reslove(img) } img.onerror = function(err) { reject(err) } img.src = src })} img转canvas12345678function imgtocanvas(img){ let canvas = document.createElement("canvas"); let ctx = canvas.getContext('2d') canvas.width = img.width canvas.height = img.height ctx.drawImage(img, 0, 0, canvas.width, canvas.height); return canvas} ImageData转canvas12345678function ImageDatetocanvas(imgData){ let canvas = document.createElement("canvas"); let ctx = canvas.getContext('2d') canvas.width = imgData.width canvas.height = imgData.height ctx.putImageData(imgData,canvas.width, canvas.height); return canvas} canvas转ImageData1234function canvastoImageDate(canvas){ let ctx = canvas.getContext('2d') return ctx.createImageData(canvas.width,canvas.height)} canvas像素操作123456789function canvaspixel(canvas,deal) { let ctx = canvas.getContext('2d') var imgData = ctx.createImageData(canvas.width, canvas.height); for (var i = 0; i < imgData.data.length; i += 4) { deal(r,g,b,a) } ctx.putImageData(imgData, canvas.width, canvas.height); return canvas} canava转DataURL(base64) 1canvas.toDataURL() DataURL(base64)转blob12345678function dataURLtoBlob(dataurl) { var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], { type: mime });} file转arrayBuffer123456789function filetoblob(file){ return new Promise((resolve, reject) => { var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function (e) { resolve(reader.result) } })} file转blob123456789function filetoblob(file){ return new Promise((resolve, reject) => { var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function (e) { resolve(new Blob([reader.result],{type:file.type})) } })} (blob,arraybuffer)转file123function blobtofile(blob,name){ return new File([blob], name ,{type:blob.type})} file(blob)转DataURL(base64)123456789101112function filetoblob(file) { return new Promise((resolve, reject) => { var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function (e) { resolve(reader.result) } reader.onerror = function (e) { resolve(reader.result) } })} dataURL转File12345678function dataURLtoFile(dataurl, filename) { var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while(n--){ u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], filename, {type:mime});} blob转objectURL1window.URL.createObjectURL(blob) objectURL转img1srctoimg(src) objectURL(url)转blob1234567891011121314151617function URLtoblob(){ return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open('GET', input) xhr.responseType = 'blob' xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response) } else { reject(xhr.statusText) } } xhr.onerror = () => reject(xhr.statusText) xhr.send() }) }} objectURL(url)转arraybuffer,当服务端传递二级制数据时使用1234567891011121314151617function URLtoblob(){ return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open('GET', input) xhr.responseType = 'arraybuffer' xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response) } else { reject(xhr.statusText) } } xhr.onerror = () => reject(xhr.statusText) xhr.send() }) }} FormData设置blob12345function appendBlob(blob){ var fd = new FormData(); fd.append("image", blob, "image.png"); return fd} Uint8ClampedArray Uint8Array 区别Uint8ClampedArray 1 ,它会将负数归入0,大于255的数归入255,所以取模就不用了。 2 ,小数取整 Uint8Array 1,Uint8Array([-23]) 等价于 new Uint8Array([ 233 ]) 2,四舍五入在处理0-255无区别 arraybuffer,视图(Uint8Array、Float64Array等)之slicebuf返回buf 视图返回视图 1,分配内存 2,拷贝数据数据1,new ArrayBuffer(40) 2,new Uint8Array( [ 1, 2, 3, 4 ] ) 3,Array.from(uint8Array);视图参数var v3 = new Int16Array(buf, 2, 2); 第一个参数:视图对应的底层ArrayBuffer对象,该参数是必需的。 第二个参数:视图开始的字节序号,默认从0开始。 第三个参数:视图包含的数据个数,默认直到本段内存区域结束。视图.buffer 获取缓冲区视图对象DataView 123456789101112131415161718192021222324var buffer = new ArrayBuffer(24);var dv = new DataView(buffer);// 从第1个字节读取一个8位无符号整数var v1 = dv.getUint8(0);// 从第2个字节读取一个16位无符号整数var v2 = dv.getUint16(1); // 从第4个字节读取一个16位无符号整数var v3 = dv.getUint16(3);setInt8:写入1个字节的8位整数。setUint8:写入1个字节的8位无符号整数。setInt16:写入2个字节的16位整数。setUint16:写入2个字节的16位无符号整数。setInt32:写入4个字节的32位整数。setUint32:写入4个字节的32位无符号整数。setFloat32:写入4个字节的32位浮点数。setFloat64:写入8个字节的64位浮点数。// 在第1个字节,以大端字节序写入值为25的32位整数dv.setInt32(0, 25, false); // 在第5个字节,以大端字节序写入值为25的32位整数dv.setInt32(4, 25); // 在第9个字节,以小端字节序写入值为2.5的32位浮点数dv.setFloat32(8, 2.5, true); 实现atob( base64 转 string) window.atob123456789101112131415161718192021222324252627282930function _atob(s) { var base64hash = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; s = s.replace(/\\s|=/g, ''); var cur, prev, mod, i = 0, result = []; while (i < s.length) { cur = base64hash.indexOf(s.charAt(i)); mod = i % 4; switch (mod) { case 0: //TODO break; case 1: result.push(String.fromCharCode(prev << 2 | cur >> 4)); break; case 2: result.push(String.fromCharCode((prev & 0x0f) << 4 | cur >> 2)); break; case 3: result.push(String.fromCharCode((prev & 3) << 6 | cur)); break; } prev = cur; i++; } return result.join('');} 实现btoa(string 转 base64) window.btoa12345678910111213141516171819202122232425262728293031323334353637383940414243function _btoa(s) { var base64hash = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; if (/([^\\u0000-\\u00ff])/.test(s)) { throw new Error('INVALID_CHARACTER_ERR'); } var i = 0, prev, ascii, mod, result = []; while (i < s.length) { ascii = s.charCodeAt(i); mod = i % 3; switch (mod) { // 第一个6位只需要让8位二进制右移两位 case 0: result.push(base64hash.charAt(ascii >> 2)); break; //第二个6位 = 第一个8位的后两位 + 第二个8位的前4位 case 1: result.push(base64hash.charAt((prev & 3) << 4 | (ascii >> 4))); break; //第三个6位 = 第二个8位的后4位 + 第三个8位的前2位 //第4个6位 = 第三个8位的后6位 case 2: result.push(base64hash.charAt((prev & 0x0f) << 2 | (ascii >> 6))); result.push(base64hash.charAt(ascii & 0x3f)); break; } prev = ascii; i++; } // 循环结束后看mod, 为0 证明需补3个6位,第一个为最后一个8位的最后两位后面补4个0。另外两个6位对应的是异常的“=”; // mod为1,证明还需补两个6位,一个是最后一个8位的后4位补两个0,另一个对应异常的“=” if (mod == 0) { result.push(base64hash.charAt((prev & 3) << 4)); result.push('=='); } else if (mod == 1) { result.push(base64hash.charAt((prev & 0x0f) << 2)); result.push('='); } return result.join('');} atob,btoa 不能编码解码中文12345var str = "China,中国";window.btoa(window.encodeURIComponent(str))//"Q2hpbmElRUYlQkMlOEMlRTQlQjglQUQlRTUlOUIlQkQ="window.decodeURIComponent(window.atob('Q2hpbmElRUYlQkMlOEMlRTQlQjglQUQlRTUlOUIlQkQ='))//"China,中国" 编码含义1,区分数据部分和参数部分2,解决中文乱码(服务端和客户端编码不一致) escape不编码字符有69个:,+,-,.,/,@,_,0-9,a-z,A-ZencodeURI不编码字符有82个:!,#,$,&,’,(,),,+,,,-,.,/,:,;,=,?,@,_,,0-9,a-z,A-ZencodeURIComponent不编码字符有71个:!, ‘,(,),*,-,.,_,,0-9,a-z,A-Z 摘自https://juejin.im/post/5c00e8a66fb9a049db72dbd0#heading-22","categories":[],"tags":[]},{"title":"绘制扇形","slug":"绘制扇形","date":"2020-01-08T21:00:56.000Z","updated":"2020-01-08T21:00:56.000Z","comments":true,"path":"2020/01/08/绘制扇形/","link":"","permalink":"https://over58.github.io/2020/01/08/%E7%BB%98%E5%88%B6%E6%89%87%E5%BD%A2/","excerpt":"","text":"","categories":[{"name":"style","slug":"style","permalink":"https://over58.github.io/categories/style/"}],"tags":[{"name":"css动画","slug":"css动画","permalink":"https://over58.github.io/tags/css%E5%8A%A8%E7%94%BB/"}],"author":["徐勇超"]},{"title":"typora编写markdown","slug":"typora编写markdown","date":"2020-01-08T15:39:23.000Z","updated":"2020-01-08T15:39:23.000Z","comments":true,"path":"2020/01/08/typora编写markdown/","link":"","permalink":"https://over58.github.io/2020/01/08/typora%E7%BC%96%E5%86%99markdown/","excerpt":"","text":"1.下载typora2.然后需要图片的时候就直接粘贴就行","categories":[{"name":"文档","slug":"文档","permalink":"https://over58.github.io/categories/%E6%96%87%E6%A1%A3/"}],"tags":[],"author":["徐勇超"]},{"title":"纯css绘制雷达扫描图","slug":"纯css绘制雷达扫描图","date":"2020-01-08T12:30:16.000Z","updated":"2020-01-08T12:30:16.000Z","comments":true,"path":"2020/01/08/纯css绘制雷达扫描图/","link":"","permalink":"https://over58.github.io/2020/01/08/%E7%BA%AFcss%E7%BB%98%E5%88%B6%E9%9B%B7%E8%BE%BE%E6%89%AB%E6%8F%8F%E5%9B%BE/","excerpt":"","text":"","categories":[{"name":"style","slug":"style","permalink":"https://over58.github.io/categories/style/"}],"tags":[{"name":"css动画","slug":"css动画","permalink":"https://over58.github.io/tags/css%E5%8A%A8%E7%94%BB/"}],"author":["徐勇超"]},{"title":"hexo中嵌入codepen","slug":"hexo中嵌入codepen","date":"2020-01-08T11:20:16.000Z","updated":"2020-01-08T11:20:16.000Z","comments":true,"path":"2020/01/08/hexo中嵌入codepen/","link":"","permalink":"https://over58.github.io/2020/01/08/hexo%E4%B8%AD%E5%B5%8C%E5%85%A5codepen/","excerpt":"","text":"安装使用npm安装插件 1$ npm install hexo-codepen --save 语法1{% codepen userId|anonymous|anon slugHash theme [defaultTab [height [width]]] %} 生成的html123<p data-height="265" data-theme-id="dark" data-slug-hash="bgjKKE" data-default-tab="css,result" data-user="CiTA" data-embed-version="2" data-pen-title="CSS sidebar toggle" class="codepen">See the Pen <a href="https://codepen.io/CiTA/pen/bgjKKE/">CSS sidebar toggle</a> by Silvestar Bistrović (<a href="https://codepen.io/CiTA">@CiTA</a>) on <a href="https://codepen.io">CodePen</a>.</p><script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>看源码实际上是插入了一个iframe 参数使用 参数 值 userId codepen的用户名 slugHash 当前pen url上的hash值 theme dark defaultTab css,result 默认展现的tab height 265 width 默认100%,这个值应该由主题调整 demo1{% codepen xuyongchaos rNaJGRW dark %} 效果","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"readyState和DomContentLoaded的先后顺序","slug":"readyState和DomContentLoaded的先后顺序","date":"2019-12-30T15:42:08.000Z","updated":"2019-12-30T15:42:08.000Z","comments":true,"path":"2019/12/30/readyState和DomContentLoaded的先后顺序/","link":"","permalink":"https://over58.github.io/2019/12/30/readyState%E5%92%8CDomContentLoaded%E7%9A%84%E5%85%88%E5%90%8E%E9%A1%BA%E5%BA%8F/","excerpt":"结论![image-20210225190144323](/Users/xuyongchao/Library/Application Support/typora-user-images/image-20210225190144323.png) 快速了解document.readyStatedocument.readyState是一个只读属性,可以返回当前文档的准备状态。 语法var state = document.readyState;其中state值包含下面三个值: loading表示文档正在加载中。 interactive表示文档已完成加载,文档已被解析,但图像、样式表和框架等子资源仍在加载。 complete表示文档和所有子资源已完成加载。如果状态变成这个,表明load事件即将触发。document.readyState是一个IE7,IE8浏览器也支持的很古老的属性,设计的初衷是好的,看起来也会是一个非常有用的属性,但是实际上我敢保证,对于绝大多数的前端开发人员,肯定没有在实际项目中用过这个属性。 包括我自己,在很长一段时间以内,都认为这个属性中看不中用,连个锤子用都没有。 为什么呢? 原因有两个:一是没必要;二是有更好的选择。","text":"结论![image-20210225190144323](/Users/xuyongchao/Library/Application Support/typora-user-images/image-20210225190144323.png) 快速了解document.readyStatedocument.readyState是一个只读属性,可以返回当前文档的准备状态。 语法var state = document.readyState;其中state值包含下面三个值: loading表示文档正在加载中。 interactive表示文档已完成加载,文档已被解析,但图像、样式表和框架等子资源仍在加载。 complete表示文档和所有子资源已完成加载。如果状态变成这个,表明load事件即将触发。document.readyState是一个IE7,IE8浏览器也支持的很古老的属性,设计的初衷是好的,看起来也会是一个非常有用的属性,但是实际上我敢保证,对于绝大多数的前端开发人员,肯定没有在实际项目中用过这个属性。 包括我自己,在很长一段时间以内,都认为这个属性中看不中用,连个锤子用都没有。 为什么呢? 原因有两个:一是没必要;二是有更好的选择。 DOM事件绑定没必要等domready对于web页面开发,JavaScript最主要的功能之一就是页面上DOM元素的交互实现,如果DOM元素还没有加载也没有被解析,自然这些DOM操作难以为继。于是,JS初始化的时机就变得很重要。 实际上,如果我们的JS文件以及script代码都放在页面的底部,我们不用考虑JS初始化时机,因为JS放在底部,等到要执行的时候,上面DOM都已经解析好了,百分之一亿可以放心运行。也就是什么readyState,什么DOMContentLoaded都不需要考虑。 我们经常会看到下面的代码: 123$(document).ready(function() { // 少年的你,代码写在这里...}); 我会立即马上把$(document).ready这一戳东西删掉,只要你的JS代码是在页面底部的,这些都完全不需要,放心删除,绝对不会有任何问题,直接下面这样就好了: 123(function() { // zhangxinxu: 这里代码...})(); 执行时机更快。 结合现状几乎所有的网页性能分析工具都会把JS文件放在底部作为考察项之一,JS放在底部已经算是网页开发常事之一了。很多萌新可能不知道,在10年前,绝大多数网页的JavaScript引用都是在head中的,所以那个时期,有大量的文章探讨文档的加载机制,JS的执行时间等等问题。 随着一年一年的进步,现在这样的现象已经不多了,大家习惯都比较好,遇不到问题,自然探讨的就少了,探讨的少了,很多开发人员就不知道这样的事情,也就自然不会用了,毕竟,还有大把的新技术新框架要去学习呢。 这就是document.readyState以及’readystatechange’事件现在出现比较少的原因。 附上实际开发’readystatechange’事件使用示例: 123document.onreadystatechange = function () { // document.readyState发生变化的时候执行} DOMContentLoaded事件document.readyState越来越少用到的另外一个原因,就是半路上杀出一个程咬金——‘DOMContentLoaded’事件。 DOMContentLoaded事件是DOM Level 3新增的一个事件类型,IE9+浏览器支持,表示DOM节点内容加载完毕。 1234document.addEventListener('DOMContentLoaded', function () {});或者:window.addEventListener('DOMContentLoaded', function () {}); DOMContentLoaded设计初衷就是为了方便JS代码的初始化,要比记得记不住的interactive状态容易理解,也容易使用的多。 因此,如果为了提防我们的JavaScript代码不小心跑到了页面的顶部,需要确保在DOM加载完毕之后执行,推荐使用下面代码: 123window.addEventListener('DOMContentLoaded', function () { // https://www.zhangxinxu.com/wordpress/?p=9032代码这里...}); 由于script元素本身也是DOM元素,因此,只要脚本不是异步加载的,无论放在页面什么位置,DOMContentLoaded事件一定会触发。 我们不妨来看一个例子,了解更多关于DOMContentLoaded事件触发更多细节。 假设有一个名为 insert.js 的文件,里面的JS代码如下: 1234console.log('zxx被加载了~' + document.readyState);window.addEventListener('DOMContentLoaded', function () { console.log('CSS选择器这本书很赞哦!' + document.readyState);}); 然后还有一个名为load-test.html的页面,里面代码这样: 123<body><script src="./insert.js"></script></body> 结果页面进入,控制台出现了下图所示的结果: 控制台输出结果截图 可以看到,”DOMContentLoaded”事件绑定时候文档状态是’loading’,执行的时候文档状态是可交互的’interactive’。 配合其他测试(这里不展示),我们可以得出一个网页文档几个加载状态变化和事件触发顺序是这样子的: 123456789document.readyState = 'loading'↓document.readyState变成'interactive'↓'DOMContentLoaded'事件执行↓document.readyState变成'complete'↓'load'事件执行 以及可以推测出一个结论: DOMContentLoaded事件必须在文档状态为loading时候绑定才有效。 正是由于上面推论,才使得document.readyState有了应用场景。 需要用到document.readyState的场景由于DOMContentLoaded事件绑定后可能并不会执行,于是出现了需要用到document.readyState的场景。 什么时候DOMContentLoaded事件不会执行呢? 那就是相关脚本是在页面DOM加载完毕后执行的场景下。 眼见为实,我们看例子: 假设有一个名为 insert.js 的文件,里面的JS代码如下: 1234console.log('zxx被加载了~' + document.readyState);window.addEventListener("DOMContentLoaded", function () { console.log('CSS选择器这本书很赞哦!');}); 然后还有一个名为load-test.html的页面,里面代码这样: 1234567<body> <script> var eleScript = document.createElement('script'); eleScript.src = './insert.js'; document.head.appendChild(eleScript); </script></body> 结果页面进入,控制台只有’zxx被加载了~’的文案,并没有DOMContentLoaded事件中的console.log输出,如下图所示: 因为DOMContentLoaded事件绑定的时候,页面的准备状态已经是’interactive’而不是’loading’了。 如果insert.js是一个公用组件,尤其以后会开源的那种,那势必要考虑到各种加载场景,页面的头部,页面底部,或者异步动态加载,此时,单纯靠DOMContentLoaded事件只能覆盖前两种情况,异步动态加载无能为力。 此时,一直认为没什么锤子用的document.readyState倒是排上了用场,假设组件的初始方法名为init,则好的实现方法是这样的: 1234567if (document.readyState != 'loading') { init();} else { window.addEventListener("DOMContentLoaded", function () { init(); });} 这是当下实际开发中,唯一需要使用document.readyState的场景,在开源或者大规模使用的框架或组件中精准初始化使用。 load/readState/DomContentLoaded/pageshow执行的先后顺序 摘自 https://www.zhangxinxu.com/wordpress/2019/10/document-readystate/","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"移动端判断横竖屏的兼容性写法","slug":"移动端判断横竖屏的兼容性写法","date":"2019-12-11T17:24:55.000Z","updated":"2019-12-11T17:24:55.000Z","comments":true,"path":"2019/12/11/移动端判断横竖屏的兼容性写法/","link":"","permalink":"https://over58.github.io/2019/12/11/%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%88%A4%E6%96%AD%E6%A8%AA%E7%AB%96%E5%B1%8F%E7%9A%84%E5%85%BC%E5%AE%B9%E6%80%A7%E5%86%99%E6%B3%95/","excerpt":"","text":"window.orientation12345var orient = Math.abs(window.orientation) === 90 ? 'landscape' : 'portrait' <!-- ps 英语渣渣必备-->// landscape 横屏// portrait 竖屏 widnow.orientation四种取值| 角度 | 说明 || — | — ||0 |竖屏||90 |横屏-顺时针旋转90度||180 |竖屏-顺时针旋转90度||-90 |横屏-逆时针旋转90度| orientationChange移动端的设备提供了一个事件:orientationChange事件这个事件是苹果公司为safari中添加的。以便开发人员能够确定用户何时将设备由横向查看切换为纵向查看模式。在设备旋转的时候,会触发这个事件, 1234window.addEventListener("orientationchange", function() { // Announce the new orientation number alert(window.orientation);}, false); 不支持orientation时1var orientation = (window.innerWidth > window.innerHeight) ? 'landscape' : 'portrait'; 什么时机进行判断window.resize时判断横竖屏,并且可以操作一些自定义的其他操作 兼容性写法1var orient = (Math.abs(window.orientation) === 90 || window.innerWidth > window.innerHeight) ? 'landscape' : 'portrait' 横竖屏时仅仅css不同的时候,不需要其他js操作借助 media queries 123456789@media all and (orientation: portrait) { body div {background: red;} } @media all and (orientation: landscape) { body div {background: blue; } } 这个orientation media query 在ios3.2+和安卓2.0+上还有其他浏览器上有效。 生产环境实际使用需要考虑键盘调起时引起的横竖屏判断错误(focusin, focusout), 结合matchMedia()和人为的指定竖屏取小,横屏取大来解决 123456789101112131415161718var keyUp = falsedocument.addEventListener('focusin', function () { keyUp = true})document.addEventListener('focusout', function () { keyUp = false}) var portrait = window.matchMedia("(orientation: portrait)"); if(keyUp) { var width = portrait ? screen.availWidth: screen.availHeight }else{ var width = (portrait.matches ? Math.min(screen.availWidth, screen.availHeight): Math.max(screen.availWidth, screen.availHeight) ) || docEl.clientWidth }var rem = width / 18.75docEl.style.fontSize = rem + 'px'","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[],"author":["徐勇超"]},{"title":"webpack总览","slug":"webpack总览","date":"2019-11-27T21:19:17.000Z","updated":"2019-11-27T21:19:17.000Z","comments":true,"path":"2019/11/27/webpack总览/","link":"","permalink":"https://over58.github.io/2019/11/27/webpack%E6%80%BB%E8%A7%88/","excerpt":"","text":"entrycontext解析weboack.config.js的目录,默认为执行启动 Webpack 时所在的当前工作目录。 Chunk 名称Webpack 会为每个生成的 Chunk 取一个名称,Chunk 的名称和 Entry 的配置有关: 如果 entry 是一个 string 或 array,就只会生成一个 Chunk,这时 Chunk 的名称是 main; 如果 entry 是一个 object,就可能会出现多个 Chunk,这时 Chunk 的名称是 object 键值对里键的名称 配置动态Entry假如项目里有多个页面需要为每个页面的入口配置一个entry,但这些页面数量可能会不断增长,这时entry的配置会受到其他因素的影响导致不能写成静态的值。解决办法就是把entry设置成一个函数去动态返回上面所说的配置: 123456789101112131415161718//同步函数entry: () => { return { a: './pages/a', b: './pages/b' }}//异步函数entry: () => { return new Promise((resolve) => { resolve({ a: './pages/a', b: './pages/b' }) })}当结合 output.library 选项时:如果传入数组,则只导出最后一项。 output filename path 配置输出后文件存在本地的目录 publicPath 发布到线上的url前缀library导出 libraryTaraget 指明库(library)被打包后,以什么形式导出,赋值到哪个位置 library 指明导出后库的名字,或者说key libraryExport 指明导出哪一个模块 undefined 导出整个模块 default var MyDefaultModule = entry_return.default; [‘MyModule’, ‘MySubModule’] var MySubModule = entry_return.MyModule.MySubModule;demo 1234当 libraryTarget: 'window' ; library: 'MyLibrary' 时window['MyLibrary'] = _entry_return_;window.MyLibrary.doSomething();","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"webpack如何查找模块","slug":"webpack如何查找模块","date":"2019-11-27T20:24:10.000Z","updated":"2019-11-27T20:24:10.000Z","comments":true,"path":"2019/11/27/webpack如何查找模块/","link":"","permalink":"https://over58.github.io/2019/11/27/webpack%E5%A6%82%E4%BD%95%E6%9F%A5%E6%89%BE%E6%A8%A1%E5%9D%97/","excerpt":"主要涉及的东西 resolve.modules resolve.mainFields resolve.mainFiles resolve.extensions resolve.alias resolve.modules指定寻找模块时的目录引入模块时分为绝对路径和相对路径但绝对路径和相对路径有些区别 绝对路径:直接在指定的目录中搜索 相对路径:通过查看当前目录以及祖先路径(即 ./node_modules, ../node_modules 等等),相对路径将类似于 Node 查找 ‘node_modules’ 的方式进行查找。 实行的是广度优先遍历 假设有如下配置: 1234567{ // some other configs resolveLoader: { modules: ['loaders1', 'loaders2'] } // some other configs} 如果当前进程目录是 /a/b/c ,现在要查找 babel-loader ,就会按照如下顺序查找: 1234567891011/a/b/c/loaders1/babel-loader/.../a/b/c/loaders2/babel-loader/.../a/b/loaders1/babel-loader/.../a/b/loaders2/babel-loader/.../a/loaders1/babel-loader/.../a/loaders2/babel-loader/.../loaders1/babel-loader/.../loaders2/babel-loader/...","text":"主要涉及的东西 resolve.modules resolve.mainFields resolve.mainFiles resolve.extensions resolve.alias resolve.modules指定寻找模块时的目录引入模块时分为绝对路径和相对路径但绝对路径和相对路径有些区别 绝对路径:直接在指定的目录中搜索 相对路径:通过查看当前目录以及祖先路径(即 ./node_modules, ../node_modules 等等),相对路径将类似于 Node 查找 ‘node_modules’ 的方式进行查找。 实行的是广度优先遍历 假设有如下配置: 1234567{ // some other configs resolveLoader: { modules: ['loaders1', 'loaders2'] } // some other configs} 如果当前进程目录是 /a/b/c ,现在要查找 babel-loader ,就会按照如下顺序查找: 1234567891011/a/b/c/loaders1/babel-loader/.../a/b/c/loaders2/babel-loader/.../a/b/loaders1/babel-loader/.../a/b/loaders2/babel-loader/.../a/loaders1/babel-loader/.../a/loaders2/babel-loader/.../loaders1/babel-loader/.../loaders2/babel-loader/... 具体如何解析寻找模块这个过程有一个很关键的模块 enhanced-resolve 就是处理依赖模块路径的解析的,这个模块可以说是 Node.js 那一套模块路径解析的增强版本,有很多可以自定义的解析配置。模块解析规则 解析绝对路径 由于我们已经取得文件的绝对路径,因此不需要进一步再做解析。 解析相对路径 相对路径+上下文路径(context path) => 绝对路径 解析模块名 模块将resolve.modules指定的所有目录内搜索,也可以通过添加resolve.alias 来创建一个别名。一旦根据上述规则解析路径后,解析器(resolver)将检查路径是否指向文件或目录 如果路径指向一个文件:1. 如果路径具有文件扩展名,则被直接将文件打包。 2. 否则,将使用 resolve.extensions 选项作为文件扩展名来解析,此选项告诉解析器在解析中能够接受哪些扩展名(例如 .js, .jsx)。指向一个目录1. 文件夹包含package.json, 则按照顺序查找 resolve.mainFields 配置选项中指定的字段。并且 package.json 中的第一个这样的字段确定文件路径。 2. 如果 package.json 文件不存在或者 package.json 文件中的 main 字段没有返回一个有效路径,则按照顺序查找 resolve.mainFiles 配置选项中指定的文件名,看是否能在 import/require 目录下匹配到一个存在的文件名 3. 文件扩展名通过 resolve.extensions 选项采用类似的方法进行解析demoresolve在 webpack 配置中,和模块路径解析相关的配置都在 resolve 字段下: 12345module.exports = { resolve: { // ... }} resolve.alias假设我们有个 utils 模块极其常用,经常编写相对路径很麻烦,希望可以直接 import ‘utils’ 来引用,那么我们可以配置某个模块的别名,如: 1234alias: { utils: path.resolve(__dirname, 'src/utils') // 这里使用 path.resolve 和 __dirname 来获取绝对路径 } 上述的配置是模糊匹配,意味着只要模块路径中携带了 utils 就可以被替换掉,如: 12345678import 'utils/query.js' // 等同于 import '[项目绝对路径]/src/utils/query.js' 如果需要进行精确匹配可以使用:alias: { utils$: path.resolve(__dirname, 'src/utils') // 只会匹配 import 'utils' } resolve.extensions12extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'],// 这里的顺序代表匹配后缀的优先级,例如对于 index.js 和 index.jsx,会优先选择 index.js 看到数组中配置的字符串大概就可以猜到,这个配置的作用是和文件后缀名有关的。是的,这个配置可以定义在进行模块路径解析时,webpack 会尝试帮你补全那些后缀名来进行查找,例如有了上述的配置,当你在 src/utils/ 目录下有一个 common.js 文件时,就可以这样来引用.import * as common from ‘./src/utils/common’webpack 会尝试给你依赖的路径添加上 extensions 字段所配置的后缀,然后进行依赖路径查找,所以可以命中 src/utils/common.js 文件。 resolve.mainFiles12默认:mainFiles: ['index']","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"F2绘制雷达图踩坑","slug":"F2绘制雷达图踩坑","date":"2019-11-26T17:49:51.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/11/26/F2绘制雷达图踩坑/","link":"","permalink":"https://over58.github.io/2019/11/26/F2%E7%BB%98%E5%88%B6%E9%9B%B7%E8%BE%BE%E5%9B%BE%E8%B8%A9%E5%9D%91/","excerpt":"描述有一个绘制雷达图的需求,想着使用比较校的库,决定放弃echarts、highcharts这样体积比较有点大的库,采用了G2,发现使用过程中还依赖DataSet包,还是有点大么,决定从G2迁到F2上面 坑一: 明明照着官网写的,图就是画不出来容器dom元素必须是canvas, 用id来指明dom 123const chart = new F2.Chart({ id: 'box', }) 图的区域的颜色总是比自己设置的浅了很多1F2.Global.shape.area.fillOpacity = 0.6 legend要自定义itemFormatter 函数返回空字符串,否则会有奇妙的事情发生","text":"描述有一个绘制雷达图的需求,想着使用比较校的库,决定放弃echarts、highcharts这样体积比较有点大的库,采用了G2,发现使用过程中还依赖DataSet包,还是有点大么,决定从G2迁到F2上面 坑一: 明明照着官网写的,图就是画不出来容器dom元素必须是canvas, 用id来指明dom 123const chart = new F2.Chart({ id: 'box', }) 图的区域的颜色总是比自己设置的浅了很多1F2.Global.shape.area.fillOpacity = 0.6 legend要自定义itemFormatter 函数返回空字符串,否则会有奇妙的事情发生 code123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191<template> <canvas id="box" ref="container" class="box" /></template><script>import improveImage from '../images/xxxx.png'let vm = nullexport default { props: { //原始数据 origin: { type: Array, required: true }, height: { type: Number, default: 350 }, width: { type: Number, default: 750 }, pointColor: { type: String, default: 'red' } }, data () { return { chart: null } }, watch: { origin () { this.refresh() } }, created () { vm = this }, mounted () { this.refresh() }, methods: { refresh () { if (this.chart) { this.chart.destroy() this.chart = null } this.draw() }, async draw () { let data = [] let origin = this.origin origin.forEach(x => { let standard = { item: x.item, user: 'standard', score: x.standard } if (x.extraHtml) { standard['extraHtml'] = x.extraHtml } let my = { item: x.item, user: 'my', score: x.my } data.push(standard, my) }) const F2 = await import(/* webpackChunkName: "g2" */'@antv/f2') F2.Global.shape.area.fillOpacity = 0.6 const chart = new F2.Chart({ id: 'box', forceFit: true, padding: [0, 0, 30, 0], pixelRatio: window.devicePixelRatio, height: vm.height, width: vm.width }) chart.source(data, { score: { min: 0, max: 100 } }) // 指定坐标系 chart.coord('polar', { radius: 0.6 }) chart.axis('item', { field: null, line: null, tickLine: null, grid: { lineStyle: { lineDash: null }, hideFirstLine: true }, label: (text, index) => { const score = data[index * 2 + 1].score return { text: `${text}\\n(${score}分)` } } }) chart.axis('score', { line: null, tickLine: null, grid: { type: 'polygon', lineStyle: { lineDash: null } }, label: null }) chart.legend('user', { clickable: false, hoverable: false, custom: true, joinString: '', items: [ { value: '我的分数', marker: { symbol: 'square', fill: '#FBDA5F', radius: 6 } }, { value: '用户平均水平', marker: { symbol: 'square', fill: '#BBF4dF', radius: 6 } } ], offsetY: -30, position: 'bottom', align: 'center', itemFormatter: function (val, a) { return '' } }) chart .area() .position('item*score') .color('user', function (val) { if (val === 'standard') { return '#BBF4FF' } else { return '#FBDA7F' } }) chart .line() .position('item*score') .color('user', function (val) { if (val === 'standard') { return '#BBF4FF' } else { return '#FBDA7F' } }) .size(1) // 禁止显示提示框 chart.tooltip(false) chart.render() this.chart = chart } }}</script><style lang="less" scoped>.box { font-size: 0.6rem;}</style> 12345678910111213141516171819202122chart.legend('user', { clickable: false, hoverable: false, custom: true, joinString: '', items: [ { value: '我的宝贝', marker: { symbol: 'square', fill: '#FBDA7F', radius: 6 } }, { value: '同龄VIP用户', marker: { symbol: 'square', fill: '#BBF4FF', radius: 6 } } ], offsetY: -30, position: 'bottom', align: 'center', itemFormatter: function (val, a) { return '' } })","categories":[{"name":"可视化","slug":"可视化","permalink":"https://over58.github.io/categories/%E5%8F%AF%E8%A7%86%E5%8C%96/"}],"tags":[{"name":"F2","slug":"F2","permalink":"https://over58.github.io/tags/F2/"}],"author":["徐勇超"]},{"title":"vue-awesome-swiper的坑","slug":"vue-awesome-swiper的坑","date":"2019-11-26T17:24:08.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/11/26/vue-awesome-swiper的坑/","link":"","permalink":"https://over58.github.io/2019/11/26/vue-awesome-swiper%E7%9A%84%E5%9D%91/","excerpt":"loop下点击事件无效问题描述: 当启用loop时,如果在swiper-slide中添加了点击事件,那么当开始下一个循环的时候,又一个短暂的时间, 点击是无效的。这样的原因大概是: 开启loop后,vue-awesome-swiper会复制几个swiper-slide,但是却没有复制相应的事件。 过程 找了很多,也发现了很多hack方法,一一试了下,都不太满足我的需求。最后找的这个https://blog.csdn.net/sxs1995/article/details/90648523 还不错。我试了之后,确实,能够解决点击失效问题。但是我的逻辑是要在点击的同时需要一个TouchEvent, 按照上面的例子,获得的是事件对象在我使用clipboard进行复制的时候,报错 “clipboard.js?f71e:32 Uncaught TypeError: Cannot read property ‘hasAttribute’ of null”。于是我查了文档。发现了这个。","text":"loop下点击事件无效问题描述: 当启用loop时,如果在swiper-slide中添加了点击事件,那么当开始下一个循环的时候,又一个短暂的时间, 点击是无效的。这样的原因大概是: 开启loop后,vue-awesome-swiper会复制几个swiper-slide,但是却没有复制相应的事件。 过程 找了很多,也发现了很多hack方法,一一试了下,都不太满足我的需求。最后找的这个https://blog.csdn.net/sxs1995/article/details/90648523 还不错。我试了之后,确实,能够解决点击失效问题。但是我的逻辑是要在点击的同时需要一个TouchEvent, 按照上面的例子,获得的是事件对象在我使用clipboard进行复制的时候,报错 “clipboard.js?f71e:32 Uncaught TypeError: Cannot read property ‘hasAttribute’ of null”。于是我查了文档。发现了这个。 最后核心代码12345678910111213141516171819202122232425262728data () { return { swiperOption: { loop: true, speed: 700, slidesPerView: 1, autoplay: { delay: 4000, disableOnInteraction: false }, on: { // 推荐使用这种方式解决loop下dom失效问题,注意事件类型为tap click: function (e) { vm.download(e, this.realIndex) } } } }},methods: { download (e, index) { console.log('操作的slide的索引',index) //handleClipboard 是封装clipboard的一个函数,不必追究 handleClipboard('复制文本', e, { showToast: false }) }}","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}],"author":["徐勇超"]},{"title":"px,em,rem的区别","slug":"px-em-rem的区别","date":"2019-10-25T16:18:18.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/10/25/px-em-rem的区别/","link":"","permalink":"https://over58.github.io/2019/10/25/px-em-rem%E7%9A%84%E5%8C%BA%E5%88%AB/","excerpt":"pxpx像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。 emem是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。 EM特点 em的值并不是固定的; em会继承父级元素的字体大小。","text":"pxpx像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。 emem是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。 EM特点 em的值并不是固定的; em会继承父级元素的字体大小。 注意:任意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合: 1em=16px。那么12px=0.75em,10px=0.625em。为了简化font-size的换算,需要在css中的body选择器中声明Font-size=62.5%,这就使em值变为 16px*62.5%=10px, 这样12px=1.2em, 10px=1em, 也就是说只需要将你的原来的px数值除以10,然后换上em作为单位就行了。 所以我们在写CSS的时候,需要注意两点: body选择器中声明Font-size=62.5%; 将你的原来的px数值除以10,然后换上em作为单位; 重新计算那些被放大的字体的em数值。避免字体大小的重复声明。 也就是避免1.2 * 1.2= 1.44的现象。比如说你在#content中声明了字体大小为1.2em,那么在声明p的字体大小时就只能是1em,而不是1.2em, 因为此em非彼em,它因继承#content的字体高而变为了1em=12px。 remrem是CSS3新增的一个相对单位(root em,根em),这个单位引起了广泛关注。这个单位与em有什么区别呢?区别在于使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。目前,除了IE8及更早版本外,所有浏览器均已支持rem。对于不支持它的浏览器,应对方法也很简单,就是多写一个绝对单位的声明。这些浏览器会忽略用rem设定的字体大小。下面就是一个例子: 1p {font-size:14px; font-size:.875rem;} 注意: 选择使用什么字体单位主要由你的项目来决定,如果你的用户群都使用最新版的浏览器,那推荐使用rem,如果要考虑兼容性,那就使用px,或者两者同时使用","categories":[{"name":"style","slug":"style","permalink":"https://over58.github.io/categories/style/"}],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}],"author":["徐勇超"]},{"title":"git合并多个commit","slug":"git合并多个commit","date":"2019-10-24T23:19:56.000Z","updated":"2021-02-26T06:07:21.770Z","comments":true,"path":"2019/10/24/git合并多个commit/","link":"","permalink":"https://over58.github.io/2019/10/24/git%E5%90%88%E5%B9%B6%E5%A4%9A%E4%B8%AAcommit/","excerpt":"","text":"一般的做法(直接merge)1234git chekcout anothermodify ...git chekcout mastergit merge another 改进版本:合并多个提交为一条(git merge –squash branchname)12git merge --squash anothergit commit -m "message here" –squash含义和原理如下: –squash选项的含义是:本地文件内容与不使用该选项的合并结果相同,但是不提交、不移动HEAD,因此需要一条额外的commit命令。其效果相当于将another分支上的多个commit合并成一个,放在当前分支上,原来的commit历史则没有拿过来。 判断是否使用–squash选项最根本的标准是,待合并分支上的历史是否有意义。 如果在开发分支上提交非常随意,甚至写成微博体,那么一定要使用–squash选项。版本历史记录的应该是代码的发展,而不是开发者在编码时的活动。 只有在开发分支上每个commit都有其独自存在的意义,并且能够编译通过的情况下(能够通过测试就更完美了),才应该选择缺省的合并方式来保留commit历史。","categories":[],"tags":[{"name":"git","slug":"git","permalink":"https://over58.github.io/tags/git/"}],"author":["徐勇超"]},{"title":"hexo博客图片问题解决方案","slug":"hexo博客图片问题解决方案","date":"2019-10-16T15:25:28.000Z","updated":"2021-02-26T06:07:21.770Z","comments":true,"path":"2019/10/16/hexo博客图片问题解决方案/","link":"","permalink":"https://over58.github.io/2019/10/16/hexo%E5%8D%9A%E5%AE%A2%E5%9B%BE%E7%89%87%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/","excerpt":"在网上查了有两种方式,哪种好用,则自己选择,个人是主推第二种方式 第一种方式,在第三方网站上上传图片,譬如七牛,然后在你博客中已下面格式即可插入图片, 1![图片简介](https://图片链接) 优点:不占git空间.缺点:图片管理的想自杀第二种方式,在本地插入,hexo官网也有相关说明,hexo图片上传说明 设置站点配置_config.yml:将post_asset_folder: false改为post_asset_folder: true 执行hexo new [xxxx],生成xxxx.md和xxxx文件夹 把要引用的图片拷贝到xxxx文件夹中","text":"在网上查了有两种方式,哪种好用,则自己选择,个人是主推第二种方式 第一种方式,在第三方网站上上传图片,譬如七牛,然后在你博客中已下面格式即可插入图片, 1![图片简介](https://图片链接) 优点:不占git空间.缺点:图片管理的想自杀第二种方式,在本地插入,hexo官网也有相关说明,hexo图片上传说明 设置站点配置_config.yml:将post_asset_folder: false改为post_asset_folder: true 执行hexo new [xxxx],生成xxxx.md和xxxx文件夹 把要引用的图片拷贝到xxxx文件夹中 1{% asset_img example.jpg This is an example image %}来引用本地图片 如果想使用markdown语法来保持文章编辑整洁,那么可以使用hexo-asset-image插件来实现 那么配置的顺序则为: 设置站点配置_config.yml:将post_asset_folder: false改为post_asset_folder: true 执行 npm install hexo-asset-image –save 装插件 执行hexo new [xxxx],生成xxxx.md和xxxx文件夹 把要引用的图片拷贝到xxxx文件夹中 使用 1![](xxxx/example.jpg) 来引用本地图片 然后么,你可能会遇到一个问题,发现图片没有上传,嘿嘿嘿,那走的坑就跟我的一样了. 后面就跟你们说下这个问题的解决方案是怎么发现的 解决方案: 将hexo-asset-image版本切到0.0.1版本试试","categories":[{"name":"其他","slug":"其他","permalink":"https://over58.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"https://over58.github.io/tags/hexo/"}],"author":["徐勇超"]},{"title":"组件生命周期","slug":"组件生命周期","date":"2019-10-16T10:36:39.000Z","updated":"2021-02-26T06:07:21.814Z","comments":true,"path":"2019/10/16/组件生命周期/","link":"","permalink":"https://over58.github.io/2019/10/16/%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/","excerpt":"单个组件的生命周期","text":"单个组件的生命周期 分为三个阶段: 初始化阶段: 只有默认的事件,没有data,methods beforeCreate 监听对象和初始化事件坚挺 created 此时已经可以访问data和methods 内存中编译好模版 beforeMount 此时页面仍然是旧的,没有挂载到上面 vm.$el 替换 el ,挂载页面 mounted 挂载完成,页面此时是最新的 运行阶段: 监听数据变化 beforeUpdate diff, 虚拟dom 重新绘制, patch updated 销毁阶段: beforeDestroy 此时仍然可以正常访问data, methods 关闭watchers和子组件的事件监听 destroyed Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:加载渲染过程父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted 子组件更新过程父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated 父组件更新过程父 beforeUpdate -> 父 updated 销毁过程父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed","categories":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/categories/vue/"}],"tags":[{"name":"lifecycle","slug":"lifecycle","permalink":"https://over58.github.io/tags/lifecycle/"}],"author":["徐勇超"]},{"title":"webpack配置","slug":"node脚手架搭建","date":"2019-10-11T00:26:56.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/10/11/node脚手架搭建/","link":"","permalink":"https://over58.github.io/2019/10/11/node%E8%84%9A%E6%89%8B%E6%9E%B6%E6%90%AD%E5%BB%BA/","excerpt":"常用的npm package commander.js,可以自动的解析命令和参数,用于处理用户输入的命令。 download-git-repo,下载并提取 git 仓库,用于下载项目模板。 Inquirer.js,通用的命令行用户界面集合,用于和用户进行交互。 handlebars.js,模板引擎,将用户提交的信息动态填充到文件中。 ora,下载过程久的话,可以用于显示下载中的动画效果。 chalk,可以给终端的字体加上颜色。 log-symbols,可以在终端上显示出 √ 或 × 等的图标 作者:Rick_Lee 链接:https://juejin.cn/post/6844903875808346120 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。","text":"常用的npm package commander.js,可以自动的解析命令和参数,用于处理用户输入的命令。 download-git-repo,下载并提取 git 仓库,用于下载项目模板。 Inquirer.js,通用的命令行用户界面集合,用于和用户进行交互。 handlebars.js,模板引擎,将用户提交的信息动态填充到文件中。 ora,下载过程久的话,可以用于显示下载中的动画效果。 chalk,可以给终端的字体加上颜色。 log-symbols,可以在终端上显示出 √ 或 × 等的图标 作者:Rick_Lee 链接:https://juejin.cn/post/6844903875808346120 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 一个例子12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455 #!/usr/bin/env nodeconst chalk = require('chalk')console.log('Hello, cli!')console.log(chalk.green('init创建'))const fs = require('fs')const program = require('commander')const download = require('download-git-repo')const inquirer = require('inquirer')const ora = require('ora')const symbols = require('log-symbols')const handlebars = require('handlebars')program .version(require('./package').version, '-v, --version') .command('init <name>') .action(name => { console.log(name) inquirer .prompt([ { type: 'input', name: 'author', message: '请输入你的名字' } ]) .then(answers => { console.log(answers.author) const lqProcess = ora('正在创建...') lqProcess.start() download( 'direct:https://github.com/Chant-Lee/rick-cli-templates1.git', name, { clone: true }, err => { if (err) { lqProcess.fail() console.log(symbols.error, chalk.red(err)) } else { lqProcess.succeed() const fileName = `${name}/package.json` const meta = { name, author: answers.author } if (fs.existsSync(fileName)) { const content = fs.readFileSync(fileName).toString() const result = handlebars.compile(content)(meta) fs.writeFileSync(fileName, result) } console.log(symbols.success, chalk.green('创建成功')) } } ) }) })program.parse(process.argv)","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"webpack配置","slug":"webpack配置","date":"2019-10-11T00:26:56.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/10/11/webpack配置/","link":"","permalink":"https://over58.github.io/2019/10/11/webpack%E9%85%8D%E7%BD%AE/","excerpt":"","text":"devtool如果是生产环境,压根不应该有devtool这个选项,这样build之后不会产生map文件,如果需要map文件用来方便查找问题,则设置devtool就行 performance123456789performance: { hints: 'warning', // false | warning | error maxEntrypointSize: 1048576, // 入口文件最大值为1M maxAssetSize: 3145728, // 资源文件最大值为3M assetFilter: function (assetFilename) { // 只给出js文件的性能提示 return assetFilename.endsWith('.js') } } resolve.alias用来设置快捷方式123456789101112resolve: { alias: { // 只能匹配到vue$结尾的字符串 // 比如:import Test1 from 'vue'; // 不能匹配 import Test1 from 'vue-router'; vue$: 'vue/dist/vue.esm.js' }, // 自动解析确定的扩展, ps: import File from '../path/to/file'; extensions: ['*', '.js', '.vue', '.json']}, externals防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。 例如,从 CDN 引入 jQuery,而不是把它打包 12345678externals: { highcharts: { root: 'Highcharts', commonjs: 'highcharts', commonjs2: 'highcharts', amd: 'highcharts' } }, library 和 libraryTarget123456789output: { filename: '[name].js', // 以何种形式暴露library, 指的是暴露出来的名字 library: 'HighchartsVueXyc', // 选项将导致 bundle 带有更完整的模块头部,以确保与各种模块系统的兼容性。 // 将你的 library 暴露为所有的模块定义下都可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量。 libraryTarget: 'umd', path: path.resolve(__dirname, './dist') },","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"webpack前端页面分析插件","slug":"webpack前端页面分析插件","date":"2019-09-17T19:23:07.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/09/17/webpack前端页面分析插件/","link":"","permalink":"https://over58.github.io/2019/09/17/webpack%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2%E5%88%86%E6%9E%90%E6%8F%92%E4%BB%B6/","excerpt":"","text":"1.添加插件 1cnpm install webpack-bundle-analyzer -save vue.config.js文件12345678chainWebpack: config => { // 运行npm run analyze 显示性能分析 if (process.env.analyze && process.NODE_ENV === production) { config .plugin("webpack-bundle-analyzer") .use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin); } }, 3.scripts中添加 1"analyz": "NODE_ENV=production analyze=true npm run build" 运行1npm run analyze","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[],"author":["徐勇超"]},{"title":"vue-cli3中iview按需引入","slug":"vue-cli3中iview按需引入","date":"2019-09-17T19:16:59.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/09/17/vue-cli3中iview按需引入/","link":"","permalink":"https://over58.github.io/2019/09/17/vue-cli3%E4%B8%ADiview%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5/","excerpt":"","text":"1.安装babel-plugin-import插件 1cnpm install babel-plugin-import --save-dev 在文件.babelrc中添加配置 12345678"plugins": [ "transform-vue-jsx", "transform-runtime", ["import", { "libraryName": "iview", "libraryDirectory": "src/components" }] ]] 在plugins文件夹中新建一个iview.js文件 Vue from 'vue'1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374import 'iview/dist/styles/iview.css'import { Row, Col, Switch, Form, FormItem, Select, Input, InputNumber, Button, Icon, Card, Modal, Menu, MenuItem, Submenu, Message, Notice, Layout, Sider, Content, Tag, Table, Poptip, Page, Timeline, TimelineItem, Tabs, TabPane, Tooltip, Alert, Checkbox, CheckboxGroup, RadioGroup, Radio} from 'iview'Vue.component('Row', Row)Vue.component('Col', Col)Vue.component('iSwitch', Switch)Vue.component('Form', Form)Vue.component('FormItem', FormItem)Vue.component('Select', Select)Vue.component('Input', Input)Vue.component('InputNumber', InputNumber)Vue.component('Button', Button)Vue.component('Icon', Icon)Vue.component('Card', Card)Vue.component('Modal', Modal)Vue.component('Card', Card)Vue.component('Menu', Menu)Vue.component('Submenu', Submenu)Vue.component('MenuItem', MenuItem)Vue.component('Layout', Layout)Vue.component('Sider', Sider)Vue.component('Content', Content)Vue.component('Tag', Tag)Vue.component('Table', Table)Vue.component('Poptip', Poptip)Vue.component('Page', Page)Vue.component('Timeline', Timeline)Vue.component('TimelineItem', TimelineItem)Vue.component('Tabs', Tabs)Vue.component('TabPane', TabPane)Vue.component('Tooltip', Tooltip)Vue.component('Alert', Alert)Vue.component('Checkbox', Checkbox)Vue.component('CheckboxGroup', CheckboxGroup)Vue.component('RadioGroup', RadioGroup)Vue.component('Radio', Radio)Vue.component('Message', Message)Vue.component('Notice', Notice) 在main.js文件中 123// import iView from 'iview'// Vue.use(iView)import '@/plugins/iview 到这里基本完成,但是还有一些iview的全局API是需要额外处理的,这里就不多说了","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"},{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/tags/webpack/"}],"author":["徐勇超"]},{"title":"proxy体验","slug":"proxy体验","date":"2019-09-16T19:22:32.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/09/16/proxy体验/","link":"","permalink":"https://over58.github.io/2019/09/16/proxy%E4%BD%93%E9%AA%8C/","excerpt":"","text":"todoList1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title></head><body> <div id="app"> <input type="text" id="input"> <div> TODO: <span id="text"></span> </div> <button id="btn">Add To Todo List</button> <ul id="list"></ul> </div> <script> const input = document.getElementById('input') const text = document.getElementById('text') const list = document.getElementById('list') const btn = document.getElementById('btn') let render const inputObj = new Proxy({}, { get (target, key, receiver) { return Reflect.get(target, key, receiver) }, set (target, key, value, receiver) { if (key === 'text') { input.value = value text.innerHTML = value } return Reflect.set(target, key, value, receiver) } }) class Render { constructor (arr) { this.arr = arr } init () { const fragment = document.createDocumentFragment() for (let i = 0; i < this.arr.length; i++) { const li = document.createElement('li') li.textContent = this.arr[i] fragment.appendChild(li) } list.appendChild(fragment) } addList (val) { const li = document.createElement('li') li.textContent = val list.appendChild(li) } } const todoList = new Proxy([], { get (target, key, receiver) { return Reflect.get(target, key, receiver) }, set (target, key, value, receiver) { console.log(target, key, value, receiver) if (key !== 'length') { render.addList(value) } return Reflect.set(target, key, value, receiver) } }) window.onload = () => { render = new Render([]) render.init() } input.addEventListener('keyup', e => { inputObj.text = e.target.value }) btn.addEventListener('click', () => { todoList.push(inputObj.text) // 每次 增加和删除 都会引起 length 的改变 inputObj.text = '' }) console.log(todoList) </script></body></html>","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"es6","slug":"es6","permalink":"https://over58.github.io/tags/es6/"}],"author":["徐勇超"]},{"title":"css特性","slug":"css特性","date":"2019-09-16T00:11:25.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/09/16/css特性/","link":"","permalink":"https://over58.github.io/2019/09/16/css%E7%89%B9%E6%80%A7/","excerpt":"权重 !important Infinity 行间样式 1000 id 100 class |属性|伪类 10 标签选择器|伪元素 1 通配符 0 继承 不存在进位的情况,只要高位大,就不用比了,权重一定高","text":"权重 !important Infinity 行间样式 1000 id 100 class |属性|伪类 10 标签选择器|伪元素 1 通配符 0 继承 不存在进位的情况,只要高位大,就不用比了,权重一定高 层叠性当同一个元素的有多个样式且权重相同时,后面覆盖前面的 标准盒模型 content padding border margin box-sizing属性可以设置盒模型","categories":[{"name":"style","slug":"style","permalink":"https://over58.github.io/categories/style/"}],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}],"author":["徐勇超"]},{"title":"模拟一些常见的js函数","slug":"模拟一些常见的js函数","date":"2019-09-15T23:29:42.000Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2019/09/15/模拟一些常见的js函数/","link":"","permalink":"https://over58.github.io/2019/09/15/%E6%A8%A1%E6%8B%9F%E4%B8%80%E4%BA%9B%E5%B8%B8%E8%A7%81%E7%9A%84js%E5%87%BD%E6%95%B0/","excerpt":"","text":"apply123456789101112131415161718Function.prototype.myapply = function(context, arr) { var context = Object(context) || window; context.fn = this; var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); } delete context.fn; return result;}; call1234567Function.prototype.myCall = function(context) { context.fn = this let args = Array.prototype.slice(arguments, 1) let result = context.fn(...args) delete context.fn return result} instanceof都知道instanceof实际上是用来判断对象的原型链上面能不能找到指定类型的原型。然后就按照这个原理来写歌demo 12345678910function instance_of(l, r){ let proto = r.__proto__ l = l.__proto__ while(l) { if (l === proto) return true if(l === null) return false l = l.__proto__ } return false}","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"模拟函数","slug":"模拟函数","permalink":"https://over58.github.io/tags/%E6%A8%A1%E6%8B%9F%E5%87%BD%E6%95%B0/"}],"author":["徐勇超"]},{"title":"stopImmediatePropagation和stopPropagation的区别","slug":"stopImmediatePropagation和stopPropagation的区别","date":"2019-09-15T18:22:11.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/09/15/stopImmediatePropagation和stopPropagation的区别/","link":"","permalink":"https://over58.github.io/2019/09/15/stopImmediatePropagation%E5%92%8CstopPropagation%E7%9A%84%E5%8C%BA%E5%88%AB/","excerpt":"在事件处理程序中,每个事件处理程序中间都会有一个event对象,而这个event对象有两个方法,一个是stopPropagation方法,一个是stopImmediatePropagation方法,两个方法只差一个Immediate,这里就说说这两个方法的区别","text":"在事件处理程序中,每个事件处理程序中间都会有一个event对象,而这个event对象有两个方法,一个是stopPropagation方法,一个是stopImmediatePropagation方法,两个方法只差一个Immediate,这里就说说这两个方法的区别 stopImmediatePropagation方法:stopImmediatePropagation方法作用在当前节点以及事件链上的所有后续节点上,目的是在执行完当前事件处理程序之后,停止当前节点以及所有后续节点的事件处理程序的运行 stopPropagation方法stopPropagation方法作用在后续节点上,目的在执行完绑定到当前元素上的所有事件处理程序之后,停止执行所有后续节点的事件处理程序 区别:从概念上讲,在调用完stopPropagation函数之后,就会立即停止对后续节点的访问,但是会执行完绑定到当前节点上的所有事件处理程序;而调用stopImmediatePropagation函数之后,除了所有后续节点,绑定到当前元素上的、当前事件处理程序之后的事件处理程序就不会再执行了 Demo12345678910111213141516// html<div id = "div1"> <button id = "button1"></button></div>// js var div = document.getElementById("div1"); var btn = document.getElementById("button1"); div.addEventListener("click" , function(e){ // e.stopImmediatePropagation() e.stopPropagation() alert("第一次执行"); } , true); //1 div.addEventListener("click" , function(){alert("第二次执行");} , true); //2 btn.addEventListener("click" , function(){alert("button 执行");}); 1.在这里,给 1 函数alert后加上stopImmediatePropagation, 那么之后弹出窗口“第一次执行”2.但是如果给 1 函数alert后加上stopPropagation , 那么之后会弹出窗口“第一次执行”,“第二次执行”两个窗口","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"装箱和拆箱","slug":"装箱和拆箱","date":"2019-09-12T11:07:11.000Z","updated":"2021-02-26T06:07:21.818Z","comments":true,"path":"2019/09/12/装箱和拆箱/","link":"","permalink":"https://over58.github.io/2019/09/12/%E8%A3%85%E7%AE%B1%E5%92%8C%E6%8B%86%E7%AE%B1/","excerpt":"装箱转换:把基本类型转换为对应的包装类型 拆箱操作:把引用类型转换为基本类型 既然原始类型不能扩展属性和方法,那么我们是如何使用原始类型调用方法的呢? 每当我们操作一个基础类型时,后台就会自动创建一个包装类型的对象,从而让我们能够调用一些方法和属性,例如下面的代码:","text":"装箱转换:把基本类型转换为对应的包装类型 拆箱操作:把引用类型转换为基本类型 既然原始类型不能扩展属性和方法,那么我们是如何使用原始类型调用方法的呢? 每当我们操作一个基础类型时,后台就会自动创建一个包装类型的对象,从而让我们能够调用一些方法和属性,例如下面的代码: 12var name = "ConardLi";var name2 = name.substring(2); 实际上发生了以下几个过程: 创建一个String的包装类型实例 在实例上调用substring方法 销毁实例 也就是说,我们使用基本类型调用方法,就会自动进行装箱和拆箱操作,相同的,我们使用Number和Boolean类型时,也会发生这个过程。 从引用类型到基本类型的转换,也就是拆箱的过程中,会遵循ECMAScript规范规定的toPrimitive原则,一般会调用引用类型的valueOf和toString方法,你也可以直接重写toPeimitive方法。一般转换成不同类型的值遵循的原则不同,例如: 引用类型转换为Number类型,先调用valueOf,再调用toString 引用类型转换String类型, 先调用toString,再调用valueOf 若valueOf和toString都不存在,或者没有返回基本类型,则抛出TypeError异常。 1234567891011121314151617181920const obj = { valueOf: () => { console.log('valueOf'); return 123; }, toString: () => { console.log('toString'); return 'ConardLi'; },};console.log(obj - 1); // valueOf 122console.log(`${obj}ConardLi`); // toString ConardLiConardLiconst obj2 = { [Symbol.toPrimitive]: () => { console.log('toPrimitive'); return 123; },};console.log(obj2 - 1); // valueOf 122const obj3 = { valueOf: () => { console.log('valueOf'); return {}; }, toString: () => { console.log('toString'); return {}; },};console.log(obj3 - 1); // valueOf // toString// TypeError 除了程序中的自动拆箱和自动装箱,我们还可以手动进行拆箱和装箱操作。我们可以直接调用包装类型的valueOf或toString,实现拆箱操作: 123var num =new Number("123"); console.log( typeof num.valueOf() ); //numberconsole.log( typeof num.toString() ); //string","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"类型转换","slug":"类型转换","permalink":"https://over58.github.io/tags/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/"}],"author":["徐勇超"]},{"title":"判断类型的方式","slug":"判断类型的方式","date":"2019-09-12T11:04:47.000Z","updated":"2021-02-26T06:07:21.790Z","comments":true,"path":"2019/09/12/判断类型的方式/","link":"","permalink":"https://over58.github.io/2019/09/12/%E5%88%A4%E6%96%AD%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%96%B9%E5%BC%8F/","excerpt":"typeof取值范围 number string object function boolean symbol boolean","text":"typeof取值范围 number string object function boolean symbol boolean 123456789typeof 12 // 'number'typeof 'aa' // 'string'typeof false // 'boolean'typeof {} // 'object'typeof function(){} // 'function'typeof [] // 'object'typeof new Date() // 'object'typeof Symbol() // 'symbol'typeof /\\w+/ig // 'object' instanceofinstanceof操作符可以帮助我们判断引用类型具体是什么类型的对象: 123[] instanceof Array // truenew Date() instanceof Date // truenew RegExp() instanceof RegExp // true 我们先来回顾下原型链的几条规则: 1.所有引用类型都具有对象特性,即可以自由扩展属性 2.所有引用类型都具有一个__proto__(隐式原型)属性,是一个普通对象 3.所有的函数都具有prototype(显式原型)属性,也是一个普通对象 4.所有引用类型__proto__值指向它构造函数的prototype 5.当试图得到一个对象的属性时,如果变量本身没有这个属性,则会去他的__proto__中去找[] instanceof Array实际上是判断Array.prototype是否在[]的原型链上。 所以,使用instanceof来检测数据类型,不会很准确,这不是它设计的初衷: 12[] instanceof Object // truefunction(){} instanceof Object // true 另外,使用instanceof也不能检测基本数据类型,所以instanceof并不是一个很好的选择。 toString上面我们在拆箱操作中提到了toString函数,我们可以调用它实现从引用类型的转换。 每一个引用类型都有toString方法,默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中type是对象的类型。 12const obj = {};obj.toString() // [object Object] 注意,上面提到了如果此方法在自定义对象中未被覆盖,toString才会达到预想的效果,事实上,大部分引用类型比如Array、Date、RegExp等都重写了toString方法。 我们可以直接调用Object原型上未被覆盖的toString()方法,使用call来改变this指向来达到我们想要的效果。 jquery中判断类型的方式123456789101112131415161718192021222324252627282930313233343536373839var types = [ 'Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp', 'Object', 'Error', 'Symbol']var class2type = {}types.forEach(name => { class2type[`[object ${name}]`] = name.toLocaleLowerCase()})function type(obj) { if (obj == null) { return obj+'' } return typeof obj === 'object' ? class2type[Object.prototype.toString.call(obj)] || 'object' : typeof obj}测试:console.log(type(12)) // numberconsole.log(type('12')) // string console.log(type(false)) // booleanconsole.log(type({})) // objectconsole.log(type([])) // objectconsole.log(type(Symbol())) //symbolconsole.log(type(function(){})) //functionconsole.log(type(undefined)) //undefinedconsole.log(type(null)) // nullconsole.log(type(Boolean(1)))","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"类型","slug":"类型","permalink":"https://over58.github.io/tags/%E7%B1%BB%E5%9E%8B/"}],"author":["徐勇超"]},{"title":"类型转换","slug":"类型转换","date":"2019-09-12T11:04:11.000Z","updated":"2021-02-26T06:07:21.814Z","comments":true,"path":"2019/09/12/类型转换/","link":"","permalink":"https://over58.github.io/2019/09/12/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/","excerpt":"1. 类型转换规则","text":"1. 类型转换规则 2. If语句和逻辑语句在if语句和逻辑语句中,如果只有单个变量,会先将变量转换为Boolean值,只有下面几种情况会转换成false,其余被转换成true: null undefined ‘’ NaN 0 false 3.各种运数学算符我们在对各种非Number类型运用数学运算符(- * /)时,会先将非Number类型转换为Number类型; 1 - true // 0 1 - null // 1 1 * undefined // NaN 2 * [‘5’] //10 注意+是个例外,执行+操作符时:1.当一侧为String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。2.当一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number类型。3.当一侧为Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。 1234123 + '123' // 123123 (规则1)123 + null // 123 (规则2)123 + true // 124 (规则2)123 + {} // 123[object Object] (规则3) 4. ==使用==时,若两侧类型相同,则比较结果和===相同,否则会发生隐式转换,使用==时发生的转换可以分为几种不同的情况(只考虑两侧类型不同) 4-1. NaNNaN和其他任何类型比较永远返回false(包括和他自己)。 1NaN == NaN // false 4-2. BooleanBoolean和其他任何类型比较,Boolean首先被转换为Number类型。 1234true == 1 // true true == '2' // falsetrue == ['1'] // truetrue == ['2'] // false 这里注意一个可能会弄混的点:undefined、null和Boolean比较,虽然undefined、null和false都很容易被想象成假值,但是他们比较结果是false,原因是false首先被转换成0 4-3.String和NumberString和Number比较,先将String转换为Number类型。 12123 == '123' // true'' == 0 // true 4-4 null和undefinednull == undefined比较结果是true,除此之外,null、undefined和其他任何结果的比较值都为false。 1234567null == undefined // truenull == '' // falsenull == 0 // falsenull == false // falseundefined == '' // falseundefined == 0 // falseundefined == false // false 4-5 原始类型和引用类型当原始类型和引用类型做比较时,对象类型会依照ToPrimitive规则转换为原始类型: 12'[object Object]' == {} // true'1,2,3' == [1, 2, 3] // true 来看看下面这个比较: 1[] == ![] // true !的优先级高于==,![]首先会被转换为false,然后根据上面第三点,false转换成Number类型0,左侧[]转换为0,两侧比较相等。 12[null] == false // true[undefined] == false // true 根据数组的ToPrimitive规则,数组元素为null或undefined时,该元素被当做空字符串处理,所以[null]、[undefined]都会被转换为0。 所以,说了这么多,推荐使用===来判断两个值是否相等…😭 有意思的面试题一道经典的面试题,如何让:a == 1 && a == 2 && a == 3。根据上面的拆箱转换,以及==的隐式转换,我们可以轻松写出答案: 1234567891011var a = { value:[3,2,1], valueOf: function () {return this.value.pop(); },}或者var a = { value:[1,2,3], valueOf: function () {return this.value.shift(); },} 对象和数字比较==,对象会隐式转换为number, 调用valueOf函数","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"类型","slug":"类型","permalink":"https://over58.github.io/tags/%E7%B1%BB%E5%9E%8B/"}],"author":["徐勇超"]},{"title":"dataTable中自定义列排序问题","slug":"dataTable中自定义列排序问题","date":"2019-09-10T22:49:54.000Z","updated":"2021-02-26T06:07:21.770Z","comments":true,"path":"2019/09/10/dataTable中自定义列排序问题/","link":"","permalink":"https://over58.github.io/2019/09/10/dataTable%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89%E5%88%97%E6%8E%92%E5%BA%8F%E9%97%AE%E9%A2%98/","excerpt":"在使用dataTables的时候,有些列中会是一些包含内容的按钮或链接,不是单纯的文字内容。那么dataTables会将这些内容视为字符串,进行排序,有时候这不符合我们的期望。那么就需要自己能够灵活的指定某些列按照我们的意愿进行排序,幸运的是dataTable支持这样的插件拓展。 首先创建一个文件叫dataTables.sort.plungin.js,加入以下代码。 12345678910111213141516jQuery.extend(jQuery.fn.dataTableExt.oSort, { "html-percent-pre": function (a) { var x = String(a).replace(/<[\\s\\S]*?>/g, ""); //去除html标记 x = x.replace(/&amp;nbsp;/ig, ""); //去除空格 x = x.replace(/%/, ""); //去除百分号 return parseFloat(x); }, "html-percent-asc": function (a, b) { //正序排序引用方法 return ((a < b) ? -1 : ((a > b) ? 1 : 0)); }, "html-percent-desc": function (a, b) { //倒序排序引用方法 return ((a < b) ? 1 : ((a > b) ? -1 : 0)); }});","text":"在使用dataTables的时候,有些列中会是一些包含内容的按钮或链接,不是单纯的文字内容。那么dataTables会将这些内容视为字符串,进行排序,有时候这不符合我们的期望。那么就需要自己能够灵活的指定某些列按照我们的意愿进行排序,幸运的是dataTable支持这样的插件拓展。 首先创建一个文件叫dataTables.sort.plungin.js,加入以下代码。 12345678910111213141516jQuery.extend(jQuery.fn.dataTableExt.oSort, { "html-percent-pre": function (a) { var x = String(a).replace(/<[\\s\\S]*?>/g, ""); //去除html标记 x = x.replace(/&amp;nbsp;/ig, ""); //去除空格 x = x.replace(/%/, ""); //去除百分号 return parseFloat(x); }, "html-percent-asc": function (a, b) { //正序排序引用方法 return ((a < b) ? -1 : ((a > b) ? 1 : 0)); }, "html-percent-desc": function (a, b) { //倒序排序引用方法 return ((a < b) ? 1 : ((a > b) ? -1 : 0)); }}); 2.在前台页面中加入以下的 js 引用。 12345678910111213141516<script type="text/javascript" src="jquery.dataTables.js"></script><script type="text/javascript" src="dataTables.numericComma.js"></script> <script type="text/javascript"> var oTable1 = $('#table_report').dataTable({ "aoColumnDefs": [ { "sType": "html-percent", "aTargets": [8] }, //指定列号使用自定义排序 ], "bLengthChange": true, //开关,是否显示每页大小的下拉框 "aLengthMenu": [[5, 10, 25, -1], [5, 10, 25, "所有"]], 'iDisplayLength': 25, //每页显示10条记录 'bFilter': true, //是否使用内置的过滤功能 "bInfo": true, //开关,是否显示表格的一些信息 "bPaginate": true //开关,是否显示分页器 }); });</script>","categories":[{"name":"插件","slug":"插件","permalink":"https://over58.github.io/categories/%E6%8F%92%E4%BB%B6/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"dataTable","slug":"dataTable","permalink":"https://over58.github.io/tags/dataTable/"}],"author":["徐勇超"]},{"title":"codemirror基本配置","slug":"codemirror基本配置","date":"2019-09-09T22:51:12.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/09/09/codemirror基本配置/","link":"","permalink":"https://over58.github.io/2019/09/09/codemirror%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE/","excerpt":"","text":"12345678910111213141516171819202122232425262728293031export const editorOption = { tabSize: 2, styleActiveLine: true, line: true, mode: 'text/x-nginx-conf', lineNumbers: true, // 显示行号 theme: 'solarized', // 设置主题 keyMap: 'sublime', // 绑定sublime fullScreen: false, // 全屏模式 matchBrackets: true, // 括号匹配 indentWithTabs: true, readOnly: false}import { codemirror } from 'vue-codemirror'import { editorOption } from '@/api/convention'require('codemirror/keymap/sublime.js')<div style="width: 80%;"> <codemirror v-model="templateForm.content" :options="editorOption"></codemirror></div><style>.CodeMirror { height: 360px;}.CodeMirror-fullscreen { z-index: 9999;}</style>","categories":[{"name":"插件","slug":"插件","permalink":"https://over58.github.io/categories/%E6%8F%92%E4%BB%B6/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"codemirror","slug":"codemirror","permalink":"https://over58.github.io/tags/codemirror/"}],"author":["徐勇超"]},{"title":"比较两个值相等","slug":"比较两个值相等","date":"2019-09-07T22:54:00.000Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2019/09/07/比较两个值相等/","link":"","permalink":"https://over58.github.io/2019/09/07/%E6%AF%94%E8%BE%83%E4%B8%A4%E4%B8%AA%E5%80%BC%E7%9B%B8%E7%AD%89/","excerpt":"首先回顾javascript的数据类型: string number boolean undefined null object(array 、function、普通object ) symbol(es6新加) 其中 string、number、boolean 、undefined、null 是直接比较值的,可以通过===判断object 、symbol是比较地址的,地址相同则视为相等 so,","text":"首先回顾javascript的数据类型: string number boolean undefined null object(array 、function、普通object ) symbol(es6新加) 其中 string、number、boolean 、undefined、null 是直接比较值的,可以通过===判断object 、symbol是比较地址的,地址相同则视为相等 so, 123456789101112function valueEqual (a, b) { //处理number string boolean undefined null function symbol if (a === b) return true //处理数组前的预判断 if (!(a instanceof Array) || !(b instanceof Array)) return false if (a.length !== b.length) return false //正式判断数组和对象 for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false } return true}","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"监听快捷键","slug":"监听快捷键","date":"2019-09-07T22:52:48.000Z","updated":"2021-02-26T06:07:21.814Z","comments":true,"path":"2019/09/07/监听快捷键/","link":"","permalink":"https://over58.github.io/2019/09/07/%E7%9B%91%E5%90%AC%E5%BF%AB%E6%8D%B7%E9%94%AE/","excerpt":"","text":"123456789101112131415161718192021222324252627<html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script type="text/javascript" language=JavaScript> document.onkeydown=function(event){ var e = event || window.event || arguments.callee.caller.arguments[0]; if(e && e.keyCode==27){ // 按 Esc //要做的事情 alert("按 esc"); } if(e && e.keyCode==113){ // 按 F2 //要做的事情 alert("按 f2"); } if(e && e.keyCode==13){ // enter 键 //要做的事情 alert("按 Enter"); } if (e.keyCode == 86 && e.ctrlKey) { alert("你按下了ctrl+V"); } }; </script> </head> <body></body> </html>","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"repaint重绘和reflow回流","slug":"repaint重绘和reflow回流","date":"2019-09-07T12:16:40.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/09/07/repaint重绘和reflow回流/","link":"","permalink":"https://over58.github.io/2019/09/07/repaint%E9%87%8D%E7%BB%98%E5%92%8Creflow%E5%9B%9E%E6%B5%81/","excerpt":"前言: 为什么不能用CSS通配符 *,CSS选择器层叠为什么不能超过三层,CSS为什么尽量使用类选择器,书写HTML为什么少使用table,为什么结构要尽量简单-DOM树要小…. 浏览器解析大概的工作流程大致可归纳为四个步骤: 解析HTML以构建DOM树:渲染引擎开始解析HTML文档,转换树中的html标签或js生成的标签到DOM节点,它被称为 – 内容树。 构建渲染树:解析CSS(包括外部CSS文件和样式元素以及js生成的样式)成样式结构体,根据CSS选择器计算出节点的样式,创建另一个树 —- 渲染树(render tree)。 注:在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而firefox会去掉_开头的样式。 布局渲染树: 从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标。 绘制渲染树: 遍历渲染树,每个节点将使用UI后端层来绘制。","text":"前言: 为什么不能用CSS通配符 *,CSS选择器层叠为什么不能超过三层,CSS为什么尽量使用类选择器,书写HTML为什么少使用table,为什么结构要尽量简单-DOM树要小…. 浏览器解析大概的工作流程大致可归纳为四个步骤: 解析HTML以构建DOM树:渲染引擎开始解析HTML文档,转换树中的html标签或js生成的标签到DOM节点,它被称为 – 内容树。 构建渲染树:解析CSS(包括外部CSS文件和样式元素以及js生成的样式)成样式结构体,根据CSS选择器计算出节点的样式,创建另一个树 —- 渲染树(render tree)。 注:在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而firefox会去掉_开头的样式。 布局渲染树: 从根节点递归调用,计算每一个元素的大小、位置等,给每个节点所应该出现在屏幕上的精确坐标。 绘制渲染树: 遍历渲染树,每个节点将使用UI后端层来绘制。 比较: render tree能识别样式,render tree中每个node都有自己的style,而且render tree不包含隐藏的节点(比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。注意 visibility:hidden隐藏的元素还是会包含到render tree中的,因为visibility:hidden 会影响布局(layout),会占有空间。定义 我们可以看到 Reflow 和Repaint 分别出现在了第三和第四步。因此我们给出下面的定义: 对于DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式(浏览器的、开发人员定义的等)来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow; 当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。回流与重绘总结: 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。 每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为repaint重绘。 注意:回流必将引起重绘,而重绘不一定会引起回流。 引起Repain和Reflow的一些操作 Reflow 的成本比 Repaint 的成本高得多的多。DOM Tree 里的每个结点都会有 reflow 方法,一个结点的 reflow 很有可能导致子结点,甚至父点以及同级结点的 reflow。 当你增加、删除、修改 DOM 结点时,会导致 Reflow 或 Repaint。 当你移动 DOM 的位置,或是搞个动画的时候。 当你修改 /删除CSS 样式的时候。 当你 Resize 窗口的时候(移动端没有这个问题),或是滚动的时候。 当你修改网页的默认字体时。 当你设置 style 属性的值 (Setting a property of the style attribute)。 注:display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。 如何减少Repain和Reflow? Reflow是不可避免的,只能将Reflow对性能的影响减到最小,给出下面几条建议: 不要一条一条地修改 DOM 的样式。与其这样,还不如预先定义好 css 的 class,然后修改 DOM 的 className,即将多次改变样式属性的操作合并成一次操作:// 不好的写法 var left = 10, top = 10; el.style.left = left + “px”; el.style.top = top + “px”; el.style.background = ‘#eee’; // 比较好的写法 el.className += “ theclassname”; 让要操作的元素进行”离线处理”,处理完后一起更新使用DocumentFragment进行缓存操作,引发一次回流和重绘;使用display:none技术,只引发两次回流和重绘;原理:由于isplay属性为none的元素不在渲染树中,对隐藏的元素操 作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘; 不要把 DOM 节点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。 尽可能的修改层级比较低的 DOM节点。当然,改变层级比较底的 DOM节点有可能会造成大面积的 reflow,但是也可能影响范围很小。 因为改变 DOM 树中的一级会导致所有层级的改变,上至根部,下至被改变节点的子节点。这导致大量时间耗费在执行 reflow 上面 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素为动画的 HTML 元素,例如动画,那么修改他们的 CSS 是会大大减小 reflow 。因为, 它们不影响其他元素的布局,所它他们只会导致重新绘制,而不是一个完整回流。这样消耗会更低。 不要用tables布局的一个原因就是tables中某个元素一旦触发reflow就会导致table里所有的其它元素reflow。在适合用table的场合,可以设置table-layout为auto或fixed,这样可以让table一行一行的 渲染,这种做法也是为了限制reflow的影响范围。 避免使用CSS的JavaScript表达式,如果css里有expression,每次都会重新计算一遍。 来源: https://blog.csdn.net/ClaireKe/article/details/51375622","categories":[{"name":"style","slug":"style","permalink":"https://over58.github.io/categories/style/"}],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"},{"name":"渲染过程","slug":"渲染过程","permalink":"https://over58.github.io/tags/%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B/"}],"author":["徐勇超"]},{"title":"从url到页面渲染的流程","slug":"从url到页面渲染的流程","date":"2019-09-07T11:45:28.000Z","updated":"2021-02-26T06:07:21.790Z","comments":true,"path":"2019/09/07/从url到页面渲染的流程/","link":"","permalink":"https://over58.github.io/2019/09/07/%E4%BB%8Eurl%E5%88%B0%E9%A1%B5%E9%9D%A2%E6%B8%B2%E6%9F%93%E7%9A%84%E6%B5%81%E7%A8%8B/","excerpt":"从用户在浏览器中输入一个URL,到整个页面渲染,这个过程中究竟发生了什么呢? 网络请求DNS解析获取IP地址 首先会在浏览器的缓存中查找,是否缓存了URL,如果有,就直接向该URL对应的服务器发送请求;如果没有则进行下一步; 在本地的hosts文件中是否保存了该URL和其对应的IP地址,如果保存了,就直接向该URL对应的服务器发送请求;如果没有则进行下一步; 向本地DNS服务器(一般由本地网络接入服务器提供商ISP提供,比如移动、联通)发送DNS请求,本地DNS服务器会首先查询它的缓存记录,如果有就将该域名对应的IP地址返回给用户,如果没有则进行下一步; 首先向根域名服务器发送DNS查询请求,根域名服务器返回给可能保存了该域名的一级域名服务器地址;本地主机再根据返回的地址,向一级域名服务器发送DNS查询请求;…一直迭代,直到找到对应的域名存放的服务器,向其发送DNS查询请求,该域名服务器返回该域名对应的IP地址;","text":"从用户在浏览器中输入一个URL,到整个页面渲染,这个过程中究竟发生了什么呢? 网络请求DNS解析获取IP地址 首先会在浏览器的缓存中查找,是否缓存了URL,如果有,就直接向该URL对应的服务器发送请求;如果没有则进行下一步; 在本地的hosts文件中是否保存了该URL和其对应的IP地址,如果保存了,就直接向该URL对应的服务器发送请求;如果没有则进行下一步; 向本地DNS服务器(一般由本地网络接入服务器提供商ISP提供,比如移动、联通)发送DNS请求,本地DNS服务器会首先查询它的缓存记录,如果有就将该域名对应的IP地址返回给用户,如果没有则进行下一步; 首先向根域名服务器发送DNS查询请求,根域名服务器返回给可能保存了该域名的一级域名服务器地址;本地主机再根据返回的地址,向一级域名服务器发送DNS查询请求;…一直迭代,直到找到对应的域名存放的服务器,向其发送DNS查询请求,该域名服务器返回该域名对应的IP地址; TCP/IP连接三次握手:为什么要进行三次握手?如果是两次握手,如下面的对话只有前两句,有可能出现的问题是:客户端之前发送了一个连接请求报文,由于网络原因滞留在网络中,后来到达服务器端,服务器接收到该请求,就会建立连接,等待客户端传送数据。而此时客户端压根就不知道发生了什么,白白造成了服务器资源浪费。 客户端:我要请求数据可以吗? 服务器:可以的 客户端:好的 浏览器向web服务器发送http请求 客户机与服务器建立连接后就可以通信了,这里就暂时先不详细展开说http请求了。讲下客户端请求静态资源和动态资源。 静态资源:如果客户端请求的是静态资源,则web服务器根据URL地址到服务器的对应路径下查找文件,然后给客户端返回一个HTTP响应,包括状态行、响应头和响应正文。 动态资源:如果客户端请求的是动态资源,则web服务器会调用CGI/VM执行程序完成相应的操作,如查询数据库,然后返回查询结果数据集,并将运行的结果–HTML文件返回给web服务器。Web服务器再将HTML文件返回给用户。 浏览器渲染 详情请看http://yongchao.online/2019/09/07/repaint重绘和reflow回流/ 浏览器拿到HTML文件后,根据渲染规则进行渲染: 解析HTML,构建DOM树 解析CSS,生成CSS规则树 合并DOM树和CSS规则树,生成render树 布局render树 绘制render数、树,即绘制页面像素信息 GPU将各层合成,结果呈现在浏览器窗口中。 四次挥手客户端没有数据发送时就需要断开连接,以释放服务器资源。 客户端:我没有数据要发送了,打算断开连接 服务器:你的请求我收到了,我这还有数据没有发送完成,你等下 服务器:我的数据发送完毕,可以断开连接了 客户端:ok,你断开连接吧(客户端独白:我将在2倍的最大报文段生存时间后关闭连接。如果我再次收到服务器的消息,我就知道服务器没有收到我的这句话,我就再发送一遍)。 最终服务器收到该客户端发送的消息断开连接,客户端也关闭连接。 注:本文摘自 https://segmentfault.com/a/1190000014311983 总结![image-20210224121938525](/Users/xuyongchao/Library/Application Support/typora-user-images/image-20210224121938525.png) 解析完成了网络请求和响应,如果响应头中Content-Type的值是text/html,那么接下来就是浏览器的解析和渲染工作了。 首先来介绍解析部分,主要分为以下几个步骤: 构建 DOM树 样式计算 生成布局树(Layout Tree) 样式计算关于CSS样式,它的来源一般是三种: link标签引用 style标签中的样式 元素的内嵌style属性 格式化样式表首先,浏览器是无法直接识别 CSS 样式文本的,因此渲染引擎接收到 CSS 文本之后第一件事情就是将其转化为一个结构化的对象,即styleSheets。 这个格式化的过程过于复杂,而且对于不同的浏览器会有不同的优化策略,这里就不展开了。 在浏览器控制台能够通过document.styleSheets来查看这个最终的结构。当然,这个结构包含了以上三种CSS来源,为后面的样式操作提供了基础。 标准化样式属性有一些 CSS 样式的数值并不容易被渲染引擎所理解,因此需要在计算样式之前将它们标准化,如em->px,red->#ff0000,bold->700等等。 计算每个节点的具体样式样式已经被格式化和标准化,接下来就可以计算每个节点的具体样式信息了。 其实计算的方式也并不复杂,主要就是两个规则: 继承和层叠。 每个子节点都会默认继承父节点的样式属性,如果父节点中没有找到,就会采用浏览器默认样式,也叫UserAgent样式。这就是继承规则,非常容易理解。 然后是层叠规则,CSS 最大的特点在于它的层叠性,也就是最终的样式取决于各个属性共同作用的效果,甚至有很多诡异的层叠现象,看过《CSS世界》的同学应该对此深有体会,具体的层叠规则属于深入 CSS 语言的范畴,这里就不过多介绍了。 不过值得注意的是,在计算完样式之后,所有的样式值会被挂在到window.computedStyle当中,也就是可以通过JS来获取计算后的样式,非常方便。 生成布局树现在已经生成了DOM树和DOM样式,接下来要做的就是通过浏览器的布局系统确定元素的位置,也就是要生成一棵布局树(Layout Tree)。 布局树生成的大致工作如下: 遍历生成的 DOM 树节点,并把他们添加到布局树中。 计算布局树节点的坐标位置。 值得注意的是,这棵布局树值包含可见元素,对于 head标签和设置了display: none的元素,将不会被放入其中。 有人说首先会生成Render Tree,也就是渲染树,其实这还是 16 年之前的事情,现在 Chrome 团队已经做了大量的重构,已经没有生成Render Tree的过程了。而布局树的信息已经非常完善,完全拥有Render Tree的功能。 之所以不讲布局的细节,是因为它过于复杂,一一介绍会显得文章过于臃肿,不过大部分情况下我们只需要知道它所做的工作是什么即可,如果想深入其中的原理,知道它是如何来做的,我强烈推荐你去读一读人人FED团队的文章从Chrome源码看浏览器如何layout布局。 总结梳理一下这一节的主要脉络: 渲染过程接下来就来拆解下一个过程——渲染。分为以下几个步骤: 建立图层树(Layer Tree) 生成绘制列表 生成图块并栅格化 显示器显示内容 一、建图层树如果你觉得现在DOM节点也有了,样式和位置信息也都有了,可以开始绘制页面了,那你就错了。 因为你考虑掉了另外一些复杂的场景,比如3D动画如何呈现出变换效果,当元素含有层叠上下文时如何控制显示和隐藏等等。 为了解决如上所述的问题,浏览器在构建完布局树之后,还会对特定的节点进行分层,构建一棵图层树(Layer Tree)。 那这棵图层树是根据什么来构建的呢? 一般情况下,节点的图层会默认属于父亲节点的图层(这些图层也称为合成层)。那什么时候会提升为一个单独的合成层呢? 有两种情况需要分别讨论,一种是显式合成,一种是隐式合成。 显式合成下面是显式合成的情况: 一、 拥有层叠上下文的节点。 层叠上下文也基本上是有一些特定的CSS属性创建的,一般有以下情况: HTML根元素本身就具有层叠上下文。 普通元素设置position不为static并且设置了z-index属性,会产生层叠上下文。 元素的 opacity 值不是 1 元素的 transform 值不是 none 元素的 filter 值不是 none 元素的 isolation 值是isolate will-change指定的属性值为上面任意一个。(will-change的作用后面会详细介绍) 二、需要剪裁的地方。 比如一个div,你只给他设置 100 * 100 像素的大小,而你在里面放了非常多的文字,那么超出的文字部分就需要被剪裁。当然如果出现了滚动条,那么滚动条会被单独提升为一个图层。 隐式合成接下来是隐式合成,简单来说就是层叠等级低的节点被提升为单独的图层之后,那么所有层叠等级比它高的节点都会成为一个单独的图层。 这个隐式合成其实隐藏着巨大的风险,如果在一个大型应用中,当一个z-index比较低的元素被提升为单独图层之后,层叠在它上面的的元素统统都会被提升为单独的图层,可能会增加上千个图层,大大增加内存的压力,甚至直接让页面崩溃。这就是层爆炸的原理。这里有一个具体的例子,点击打开。 值得注意的是,当需要repaint时,只需要repaint本身,而不会影响到其他的层。 二、生成绘制列表接下来渲染引擎会将图层的绘制拆分成一个个绘制指令,比如先画背景、再描绘边框……然后将这些指令按顺序组合成一个待绘制列表,相当于给后面的绘制操作做了一波计划。 这里我以百度首页为例,大家可以在 Chrome 开发者工具中在设置栏中展开 more tools, 然后选择Layers面板,就能看到下面的绘制列表: 三、生成图块和生成位图现在开始绘制操作,实际上在渲染进程中绘制操作是由专门的线程来完成的,这个线程叫合成线程。 绘制列表准备好了之后,渲染进程的主线程会给合成线程发送commit消息,把绘制列表提交给合成线程。接下来就是合成线程一展宏图的时候啦。 首先,考虑到视口就这么大,当页面非常大的时候,要滑很长时间才能滑到底,如果要一口气全部绘制出来是相当浪费性能的。因此,合成线程要做的第一件事情就是将图层分块。这些块的大小一般不会特别大,通常是 256 * 256 或者 512 * 512 这个规格。这样可以大大加速页面的首屏展示。 因为后面图块数据要进入 GPU 内存,考虑到浏览器内存上传到 GPU 内存的操作比较慢,即使是绘制一部分图块,也可能会耗费大量时间。针对这个问题,Chrome 采用了一个策略: 在首次合成图块时只采用一个低分辨率的图片,这样首屏展示的时候只是展示出低分辨率的图片,这个时候继续进行合成操作,当正常的图块内容绘制完毕后,会将当前低分辨率的图块内容替换。这也是 Chrome 底层优化首屏加载速度的一个手段。 顺便提醒一点,渲染进程中专门维护了一个栅格化线程池,专门负责把图块转换为位图数据。 然后合成线程会选择视口附近的图块,把它交给栅格化线程池生成位图。 生成位图的过程实际上都会使用 GPU 进行加速,生成的位图最后发送给合成线程。 四、显示器显示内容栅格化操作完成后,合成线程会生成一个绘制命令,即”DrawQuad”,并发送给浏览器进程。 浏览器进程中的viz组件接收到这个命令,根据这个命令,把页面内容绘制到内存,也就是生成了页面,然后把这部分内存发送给显卡。为什么发给显卡呢?我想有必要先聊一聊显示器显示图像的原理。 无论是 PC 显示器还是手机屏幕,都有一个固定的刷新频率,一般是 60 HZ,即 60 帧,也就是一秒更新 60 张图片,一张图片停留的时间约为 16.7 ms。而每次更新的图片都来自显卡的前缓冲区。而显卡接收到浏览器进程传来的页面后,会合成相应的图像,并将图像保存到后缓冲区,然后系统自动将前缓冲区和后缓冲区对换位置,如此循环更新。 看到这里你也就是明白,当某个动画大量占用内存的时候,浏览器生成图像的时候会变慢,图像传送给显卡就会不及时,而显示器还是以不变的频率刷新,因此会出现卡顿,也就是明显的掉帧现象。 总结到这里,我们算是把整个过程给走通了,现在重新来梳理一下页面渲染的流程。","categories":[],"tags":[{"name":"渲染过程","slug":"渲染过程","permalink":"https://over58.github.io/tags/%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B/"}]},{"title":"reduce函数的应用","slug":"reduce函数的应用","date":"2019-08-25T22:38:13.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/08/25/reduce函数的应用/","link":"","permalink":"https://over58.github.io/2019/08/25/reduce%E5%87%BD%E6%95%B0%E7%9A%84%E5%BA%94%E7%94%A8/","excerpt":"1var arr = [11,3,4,556,7,8,-4] 获得最大值/最小值123var a= arr.reduce((a, b) => { return a > b ? a: b})","text":"1var arr = [11,3,4,556,7,8,-4] 获得最大值/最小值123var a= arr.reduce((a, b) => { return a > b ? a: b}) 数组去重1234567var arr1 = [1, 2, 3, 1, 1, 2, 3, 3, 4, 3, 4, 5]let ret1 = arr1.reduce((a, b) => { if(!a.includes(b)) { a.push(b) } return a}, []) 实现map函数1234567891011Array.prototype._map = function(cb) { if(typeof cb === 'function') { let arr = this return arr.reduce((prev, next, index, array) => { prev.push(cb(next, index, array)) return prev }, []) }else{ throw new Error('cb' + ' is not function' ) }} 实现一个filter函数1234567891011Array.prototype._filter = function(cb) { if(typeof cb === 'function') { let arr = this return arr.reduce((prev, next, index, array) => { cb(next, index, array) && prev.push(next) return prev }, []) }else{ throw new Error('cb' + ' is not function' ) }} 数组扁平化1234567891011let arr2 = [1, 2, '3js', [4, 5, [6], [7, 8, [9, 10, 11], null, 'abc'], {age: 12}, [13, 14]], '[]'];function flatten(arr) { if(Array.isArray(arr)) { return arr.reduce((prev, cur) => { // 如果遍历的当前项是数组,再迭代展平 return Array.isArray(cur) ? prev.concat(flatten(cur)) : prev.concat(cur) }, []) } else { throw new Error(arr + ' is not array') }} 统计字符串中每个字符出现的次数123456const str = '9kFZTQLbUWOjurz9IKRdeg28rYxULHWDUrIHxCY6tnHleoJ'var ret3 = Array.from(str).reduce((accumulator, current) => { current in accumulator ? accumulator[current]++ : accumulator[current] = 1 return accumulator}, {}) 过滤满足多个条件的数组 将过滤函数作为数组进行调用, 初始值为原数据数组 123const filter1 = (arr) => arr.filter(item => item.name.length === 3)const filter2 = (arr) => arr.filter(item => item.age > 26)var ret5 = [filter1, filter2].reduce((accumulator, fn) => fn(accumulator), arr4)","categories":[],"tags":[],"author":["徐勇超"]},{"title":"css中的各种尺寸","slug":"css中的各种尺寸","date":"2019-08-20T22:11:46.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/08/20/css中的各种尺寸/","link":"","permalink":"https://over58.github.io/2019/08/20/css%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E5%B0%BA%E5%AF%B8/","excerpt":"","text":"client clientTop border-top-width clientLeft border-left-width clientWidth 水平方向 content + padding clientHeight 竖直方向 content + padding offset offsetTop offsetLeft offsetWidth 水平方向 content + padding + border offsetHeight 竖直方向 content + padding + border","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}],"author":["徐勇超"]},{"title":"repalce函数的应用","slug":"repalce函数的应用","date":"2019-08-20T21:51:37.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/08/20/repalce函数的应用/","link":"","permalink":"https://over58.github.io/2019/08/20/repalce%E5%87%BD%E6%95%B0%E7%9A%84%E5%BA%94%E7%94%A8/","excerpt":"介绍 字符 替换文本 $1、$2…$99 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。 $& 与 regexp 相匹配的子串。 $` 位于匹配子串左侧的文本。 $’ 位于匹配子串右侧的文本。 $$ 直接量符号。","text":"介绍 字符 替换文本 $1、$2…$99 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。 $& 与 regexp 相匹配的子串。 $` 位于匹配子串左侧的文本。 $’ 位于匹配子串右侧的文本。 $$ 直接量符号。 格式化日期1234567891011var date = new Date().toLocaleString()//method1var formatDate= date.replace(/(\\d+)\\/(\\d+)\\/(\\d+)\\s+[\\u4e00-\\u9fa5]+(\\d+):(\\d+):(\\d+)/g, '$1/$2/$3 $3:$4:$5')console.log(formatDate ) // 2019/8/20 20:11:43//method2date.replace(/[\\u4e00-\\u9fa5]+(\\d+):(\\d+):(\\d+)/g, function(all, p1,p2,p3){ return [p1, p2, p3].join(':')}) 转换为驼峰命名1var s1 = "get-element-by-id" // 转化为 getElementById 12345var f = function(s) { return s.replace(/-\\w/g, function(x) { return x.slice(1).toUpperCase(); })} 查找字符串中出现最多的字符和个数 例: abbcccddddd -> 字符最多的是d,出现了5次 1234567891011121314151617let str = "abcabcabcbbccccc";let num = 0;let char = ''; // 使其按照一定的次序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re = /(\\w)\\1+/g;str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; }});console.log(`字符最多的是${char},出现了${num}次`); 实现千位分隔符 // 保留三位小数parseToMoney(1234.56); // return ‘1,234.56’parseToMoney(123456789); // return ‘123,456,789’parseToMoney(1087654.321); // return ‘1,087,654.321’ 1234567891011function parseToMoney(num) { num = parseFloat(num.toFixed(3)); let [integer, decimal] = String.prototype.split.call(num, '.'); integer = integer.replace(/\\d(?=(\\d{3})+$)/g, '$&,'); return integer + '.' + (decimal ? decimal : '');}作者:寻找海蓝96链接:https://juejin.im/post/5d51e16d6fb9a06ae17d6bbc来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ?=正向肯定预查 详细信息看这里正则的基本知识","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"visibilty:hidden、display:none、opacity:0的区别","slug":"visibilty、display-none的区别","date":"2019-08-19T13:22:30.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/08/19/visibilty、display-none的区别/","link":"","permalink":"https://over58.github.io/2019/08/19/visibilty%E3%80%81display-none%E7%9A%84%E5%8C%BA%E5%88%AB/","excerpt":"","text":"分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景display: none;1.DOM 结构:浏览器不会渲染 display 属性为 none 的元素,不占据空间;2.事件监听:无法进行 DOM 事件监听;3.性能:动态改变此属性时会引起重排,性能较差;4.继承:不会被子元素继承,毕竟子类也不会被渲染;5.transition:transition 不支持 display。 visibility: hidden;1.DOM 结构:元素被隐藏,但是会被渲染不会消失,占据空间;2.事件监听:无法进行 DOM 事件监听;3.性 能:动态改变此属性时会引起重绘,性能较高;4.继 承:会被子元素继承,子元素可以通过设置 visibility: visible; 来取消隐藏;5.transition:transition 不支持 display。 opacity: 01.DOM 结构:透明度为 100%,元素隐藏,占据空间;2.事件监听:可以进行 DOM 事件监听;3.性 能:提升为合成层,不会触发重绘,性能较高;4.继 承:会被子元素继承,且,子元素并不能通过 opacity: 1 来取消隐藏;5.transition:transition 不支持 opacity。 联系:它们都能让元素不可见摘自 https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/100","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}],"author":"徐勇超"},{"title":"深度优先遍历和广度优先遍历","slug":"深度优先遍历和广度优先遍历","date":"2019-08-15T23:28:18.000Z","updated":"2021-02-26T06:07:21.814Z","comments":true,"path":"2019/08/15/深度优先遍历和广度优先遍历/","link":"","permalink":"https://over58.github.io/2019/08/15/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86%E5%92%8C%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E9%81%8D%E5%8E%86/","excerpt":"","text":"广度优先遍历12345678910111213141516function traverse(node) { let stack = [] let nodes = [] if(node) { stack.push(node) while(stack.length) { let item = stack.shift() let children = item.children nodes.push(item) for(let i=0;i<children.length;i++) { stack.push(children[i]) } } } return nodes} 深度优先遍历12345678910function deepTraverse(node, ret) { if(node !== null) { ret.push(node) let children = node.children for(let i=0;i<children.length;i++) { deepTraverse(children[i], ret) } } return ret}","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"算法","slug":"算法","permalink":"https://over58.github.io/tags/%E7%AE%97%E6%B3%95/"}],"author":["徐勇超"]},{"title":"list转成tree型数据结构","slug":"list转成tree型数据结构","date":"2019-08-09T14:53:18.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/08/09/list转成tree型数据结构/","link":"","permalink":"https://over58.github.io/2019/08/09/list%E8%BD%AC%E6%88%90tree%E5%9E%8B%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","excerpt":"12345678910111213141516function convert(list) { const res = [] const map = list.reduce((res, v) => (res[v.id] = v, res), {}) for (const item of list) { if (item.parentId === 0) { res.push(item) continue } if (item.parentId in map) { const parent = map[item.parentId] parent.children = parent.children || [] parent.children.push(item) } } return res} 原始 list 数据 如下 123456789101112let list =[ {id:1,name:'部门A',parentId:0}, {id:2,name:'部门B',parentId:0}, {id:3,name:'部门C',parentId:1}, {id:4,name:'部门D',parentId:1}, {id:5,name:'部门E',parentId:2}, {id:6,name:'部门F',parentId:3}, {id:7,name:'部门G',parentId:2}, {id:8,name:'部门H',parentId:4}, {id:9,name:'部门H',parentId:8}];const result = convert(list);","text":"12345678910111213141516function convert(list) { const res = [] const map = list.reduce((res, v) => (res[v.id] = v, res), {}) for (const item of list) { if (item.parentId === 0) { res.push(item) continue } if (item.parentId in map) { const parent = map[item.parentId] parent.children = parent.children || [] parent.children.push(item) } } return res} 原始 list 数据 如下 123456789101112let list =[ {id:1,name:'部门A',parentId:0}, {id:2,name:'部门B',parentId:0}, {id:3,name:'部门C',parentId:1}, {id:4,name:'部门D',parentId:1}, {id:5,name:'部门E',parentId:2}, {id:6,name:'部门F',parentId:3}, {id:7,name:'部门G',parentId:2}, {id:8,name:'部门H',parentId:4}, {id:9,name:'部门H',parentId:8}];const result = convert(list); 结果12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758[ { "id":1, "name":"部门A", "parentId":0, "children":[ { "id":3, "name":"部门C", "parentId":1, "children":[ { "id":6, "name":"部门F", "parentId":3 } ] }, { "id":4, "name":"部门D", "parentId":1, "children":[ { "id":8, "name":"部门H", "parentId":4, "children":[ { "id":9, "name":"部门H", "parentId":8 } ] } ] } ] }, { "id":2, "name":"部门B", "parentId":0, "children":[ { "id":5, "name":"部门E", "parentId":2 }, { "id":7, "name":"部门G", "parentId":2 } ] }]","categories":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/categories/graph/"}],"tags":[],"author":["徐勇超"]},{"title":"绘制网络拓扑图","slug":"绘制网络拓扑图","date":"2019-08-08T23:58:17.000Z","updated":"2021-02-26T06:07:21.818Z","comments":true,"path":"2019/08/08/绘制网络拓扑图/","link":"","permalink":"https://over58.github.io/2019/08/08/%E7%BB%98%E5%88%B6%E7%BD%91%E7%BB%9C%E6%8B%93%E6%89%91%E5%9B%BE/","excerpt":"效果图如下: 实现原理以及优缺点实现原理实际上是graph类型和lines类型结合画出来的,graph可以绘制的拓扑图,目前三种layout, 包括力导图(force)、环形布局(circular)、二维坐标系(none)。前两种基本上排版位置不怎么受自己控制,第三种的画,节点位置完全的受数据中的x/y位置控制,但是估计计算位置是个麻烦事儿,如果节点的数量很少或者说比较固定的话,采用这个非常不错。lines类型主要负责线条上添加动态的效果 live demohttps://gallery.echartsjs.com/editor.html?c=xbETBRwXrm或者http://39.105.159.58:3000/#/graph","text":"效果图如下: 实现原理以及优缺点实现原理实际上是graph类型和lines类型结合画出来的,graph可以绘制的拓扑图,目前三种layout, 包括力导图(force)、环形布局(circular)、二维坐标系(none)。前两种基本上排版位置不怎么受自己控制,第三种的画,节点位置完全的受数据中的x/y位置控制,但是估计计算位置是个麻烦事儿,如果节点的数量很少或者说比较固定的话,采用这个非常不错。lines类型主要负责线条上添加动态的效果 live demohttps://gallery.echartsjs.com/editor.html?c=xbETBRwXrm或者http://39.105.159.58:3000/#/graph 注意点1.nodes数据的x/y值一定要指定,否则画不出来2.graph类型必须设置coordinateSystem: ‘cartesian2d’,指定定位系统采用二维坐标轴 附上vue实现的代码graph.vue 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185<template> <div> <div ref="chart" style="height:800px;border:solid 1px red"></div> </div></template><script>import echarts from 'echarts'import graph from './data2'let vm = nullexport default { components: { }, data () { return { charts: { nodes: graph.data.elements.nodes, edges: graph.data.elements.edges, linesData: [] }, nodePositionMap: new Map(), chartInstance: null } }, computed: { }, methods: { initData () { this.charts.nodes = this.charts.nodes.map(node => { this.nodePositionMap.set(node.data.name, [node.position.x, node.position.y]) return { name: node.data.name, data: node.data, value: [node.position.x, node.position.y], // x: node.position.x, // y: node.position.y, symbolSize: 20 * node.data.size, // alarm: node.alarm, // symbol: 'image:///asset/get/s/' + node.img, itemStyle: { normal: { color: '#12b5d0' } } } }) this.charts.linesData = [] this.charts.edges = this.charts.edges.map(edge => { this.charts.linesData.push([ { coord: this.nodePositionMap.get(edge.data.source) }, { coord: this.nodePositionMap.get(edge.data.target) } ]) return { source: edge.data.source, target: edge.data.target, label: { normal: { show: true // formatter: edge.nam } }, lineStyle: { normal: { color: edge.data.colour } } } }) }, init () { this.initData() let option = { title: { text: '网络拓扑图' }, tooltip: { trigger: 'item', formatter: '{b}', alwaysShowContent: true }, backgroundColor: '#F5F5F5', xAxis: { min: 0, max: 800, show: false, type: 'value' }, yAxis: { min: 0, max: 300, type: 'value', show: false }, series: [ { type: 'graph', layout: 'none', id: 'a', coordinateSystem: 'cartesian2d', edgeSymbol: ['', 'arrow'], // symbolSize: 50, label: { normal: { show: true, position: 'bottom' // color: '#12b5d0' } }, lineStyle: { normal: { width: 2, shadowColor: 'none' } }, xAxis: { min: 0, max: 12, show: false, type: 'value' }, yAxis: { min: 0, max: 12, show: false, type: 'value' }, // edgeSymbolSize: 8, draggable: true, nodes: this.charts.nodes, edges: this.charts.edges, z: 4, itemStyle: { normal: { label: { show: true, formatter: function (item) { return item.data.name } } } } }, { name: 'A', type: 'lines', coordinateSystem: 'cartesian2d', z: 4, effect: { show: true, trailLength: 0, symbol: 'arrow', color: '#12b5d0', symbolSize: 8 }, lineStyle: { normal: { curveness: 0 } }, data: this.charts.linesData } ] } this.chartInstance = echarts.init(this.$refs.chart) this.chartInstance.setOption(option) } }, mounted () { vm = this this.$nextTick(() => { this.init() }) window.addEventListener('resize', () => { vm.chartInstance && this.chartInstance.resize() }) }}</script><style scoped></style> 想要看更详细的代码以及其他的栗子源码看这里https://github.com/xuyongchaos/vue-charts.git","categories":[{"name":"可视化","slug":"可视化","permalink":"https://over58.github.io/categories/%E5%8F%AF%E8%A7%86%E5%8C%96/"}],"tags":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/tags/graph/"}],"author":["徐勇超"]},{"title":"cytoscape绘制一个拓扑图","slug":"cytoscape绘制一个拓扑图","date":"2019-07-18T14:52:39.000Z","updated":"2021-02-26T06:07:21.770Z","comments":true,"path":"2019/07/18/cytoscape绘制一个拓扑图/","link":"","permalink":"https://over58.github.io/2019/07/18/cytoscape%E7%BB%98%E5%88%B6%E4%B8%80%E4%B8%AA%E6%8B%93%E6%89%91%E5%9B%BE/","excerpt":"","text":"只记录核心代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586import cytoscape from 'cytoscape'import edgehandles from 'cytoscape-edgehandles'cytoscape.use(edgehandles) let makeSvg = function (node) { let data = node.data() return { image: data.image || 'none', width: 50, height: 50 } } const cy = cytoscape({ container: this.$refs.topology, layout: { name: 'breadthfirst', directed: true }, userZoomingEnabled: false, style: [ { selector: 'node', style: { shape: 'ellipse', // rectangle vee pentagon content: function (ele) { const data = ele.data() if (data.title) { return `${data.title}-${data.name}` } else { return `${data.id}` } }, 'text-valign': 'center', 'text-halign': 'right', 'color': 'data(color)', 'background-color': 'data(color)', 'background-image': function (node) { return makeSvg(node).image }, 'background-width': '60%', 'background-height': '60%', 'width': function (node) { return makeSvg(node).width }, 'height': function (node) { return makeSvg(node).height }, 'text-rotation': function (node) { //根据条件旋转文字,避免节点太多文字相互覆盖 let data = node.data() if (data.level === 3 && level3Len > 10) { return 45 } else { return 0 } } } }, { 'selector': 'node:selected', 'style': { 'min-zoomed-font-size': 0, 'z-index': 9999, 'border-color': 'black', 'border-width': 2, 'color': 'black' } }, { selector: 'edge', style: { 'curve-style': 'straight', 'target-arrow-shape': 'triangle', 'line-color': 'gray', 'width': 0.8, 'line-style': 'data(lineStyle)' } } ], elements: { nodes: nodes, edges: edges } }) cy.on('tap', 'node', function (evt) { let data = evt.target.data() if (data.click) { vm.getIpStat(data.ip) } })","categories":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/categories/graph/"}],"tags":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/tags/graph/"},{"name":"cytoscape","slug":"cytoscape","permalink":"https://over58.github.io/tags/cytoscape/"}],"author":["徐勇超"]},{"title":"http访问控制","slug":"http访问控制","date":"2019-07-18T14:33:20.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/07/18/http访问控制/","link":"","permalink":"https://over58.github.io/2019/07/18/http%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6/","excerpt":"http访问控制(cors)概念跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 ⚠️不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了 简单请求: (满足下面所有条件) 方法:GET、HEAD、POST Content-Typ为下面三个值之一:text/plain 、mutipart/form-data 、application/x-www-data-urlencoded","text":"http访问控制(cors)概念跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 ⚠️不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了 简单请求: (满足下面所有条件) 方法:GET、HEAD、POST Content-Typ为下面三个值之一:text/plain 、mutipart/form-data 、application/x-www-data-urlencoded 复杂请求【预检请求】(满足下面任一条件) 与前述简单请求不同,“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。当请求满足下述任一条件时,即应首先发送预检请求: 使用了下面任一 HTTP 方法: PUT DELETE CONNECT OPTIONS TRACE PATCH 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为: Accept Accept-Language Content-Language Content-Type (需要注意额外的限制) Content-Type 的值不属于下列之一: application/x-www-form-urlencoded multipart/form-data text/plain 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。 请求中使用了ReadableStream对象。 HTTP响应首部字段:Access-Control-Allow-OriginAccess-Control-Expose-Headers 让服务器把允许浏览器访问的头放入白名单。在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。 Access-Control-Max-Age 指定了preflight请求的结果能够被缓存多久 Access-Control-Allow-Credentials 指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。当用在对preflight预检测请求的响应中时,它指定了实际的请求是否可以使用credentials。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页 Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法 Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。 HTTP请求首部字段:Origin 首部字段表明预检请求或实际请求的源站。 它不包含任何路径信息,只是服务器名称。Note: 有时候将该字段的值设置为空字符串是有用的,例如,当源站是一个 data URL 时。 Access-Control-Request-Method 首部字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。 Access-Control-Request-Headers 首部字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。 摘录自https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS 最后来一个小demo吧index1.html 12345678910111213141516171819<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title></head><body> port: 9000 index page <!-- <script src="http://127.0.0.1:9001/"></script> --> <script> var xhr = new XMLHttpRequest() xhr.open('put', 'http://127.0.0.1:9001/') // xhr.setRequestHeader('X-Custom-Field', 'custom-field') xhr.send() </script></body></html> server1.js 123456789101112const http = require('http');const fs = require('fs')http.createServer(function(req, res) { if(req.url === '/') { file = fs.readFileSync('./index1.html', 'utf-8') res.writeHead(200, { 'Content-Type': 'text/html' }) res.end(file) }}).listen(9000, '127.0.0.1') server2.js 12345678910111213141516171819202122232425262728293031323334353637383940414243const http = require('http');// http.createServer(function(req, res) {// if(req.url === '/') {// console.log('come in 127.0.0.1:9001 server')// res.writeHead(200,{// 'Content-Type': 'text/plain'// })// res.end('9001 index page')// }// }).listen(9001, '127.0.0.1')// http.createServer(function(req, res) {// if(req.url === '/') {// console.log('come in 127.0.0.1:9001 server')// res.writeHead(200,{// 'Access-Control-Allow-Origin': '*', // 告诉浏览器允许访问的origin// 'Content-Type': 'text/plain' // })// res.end('9001 index page')// }// }).listen(9001, '127.0.0.1')http.createServer(function(req, res) { if(req.url === '/') { console.log('come in 127.0.0.1:9001 server') res.writeHead(200,{ 'Access-Control-Allow-Origin': '*', // 告诉浏览器允许访问的origin 'Access-Control-Allow-Methods': 'PUT', 'Allow-Control-Request-Method': 'PUT', // 'Access-Control-Allow-Headers': 'X-Custom-Field', // 用来指定允许的自定义header // 'Access-Control-Max-Age': 0, //指定预请求(prefight)的有效期, 单位为秒 // 'Cache-Control': 'no-store', 'Content-Type': 'text/plain' }) res.end(JSON.stringify({ name: 'Tom' })) }}).listen(9001, '127.0.0.1')","categories":[{"name":"nginx","slug":"nginx","permalink":"https://over58.github.io/categories/nginx/"}],"tags":[{"name":"http","slug":"http","permalink":"https://over58.github.io/tags/http/"}],"author":["徐勇超"]},{"title":"code自定义iview语法提示","slug":"code自定义iview语法提示","date":"2019-07-15T23:34:30.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/07/15/code自定义iview语法提示/","link":"","permalink":"https://over58.github.io/2019/07/15/code%E8%87%AA%E5%AE%9A%E4%B9%89iview%E8%AF%AD%E6%B3%95%E6%8F%90%E7%A4%BA/","excerpt":"vscode 中支持自定义语法提示,于是我就将一些常见的代码模块记录一下,开发时只要输入少量代码就可以获得整个代码块,提高了开发效率。","text":"vscode 中支持自定义语法提示,于是我就将一些常见的代码模块记录一下,开发时只要输入少量代码就可以获得整个代码块,提高了开发效率。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644'.text.html.vue': 'button': 'prefix': 'btn' 'body':""" <Button type="$1"></Button> """ 'icon': 'prefix': 'icon' 'body':""" <Icon type="$1"/> """ 'row': 'prefix': 'row' 'body':""" <Row> <Col></Col> </Row> """ 'col': 'prefix': 'col' 'body':""" <Col></Col> """ 'card': 'prefix': 'card' 'body':""" <Card dis-hover> $1 </Card> """ 'collapse': 'prefix': 'collapse' 'body':""" <Collapse> <Panel name=""> </Panel> </Collapse> """ 'Split': 'prefix': 'split' 'body':""" <Split v-model="split1"> <div slot="left"> Left Pane </div> <div slot="right"> Right Pane </div> </Split> """ 'divider': 'prefix': 'divider' 'body':""" <Divider /> """ 'cell': 'prefix': 'cell' 'body':""" <Cell title="$1"></Cell> """ 'CellGroup': 'prefix': 'cellgroup' 'body':""" <CellGroup> <Cell></Cell> </CellGroup> """ 'Tabs': 'prefix': 'tabs' 'body':""" <Tabs> <Tabpane label=""></Tabpane> </Tabs> """ 'Dropdown': 'prefix': 'dropdown' 'body':""" <Dropdown> <Button type="primary"> 下拉菜单 </Button> <DropdownMenu slot="list"> <DropdownItem></DropdownItem> </DropdownMenu> </Dropdown> """ 'Steps': 'prefix': 'steps' 'body':""" <Steps :current="1"> <Step title="$1" content="$2"></Step> </Steps> """ 'Input -->vue': 'prefix': 'input' 'body':""" <Input v-model.trim="$1"/> """ "form -->vue": "prefix": "form" "body": """ <Form ref="Form" :model="Form" :rules="FormRules" label-position="right"> <Form-item label="" prop=""> </Form-item> <Form-item> <div style="text-align:center"> <Button type="primary" @click="">提交</Button> <Button type="warning" @click="">重置</Button> </div> </Form-item> </Form> """ "form validate-->vue": "prefix": "validate" "body": """ this.$refs['$1'].validate((valid) => { if (valid) { this.$Message.error('表单验证成功!') } else { this.$Message.error('表单验证失败!') } }) """ "form reset-->vue": "prefix": "reset" "body": """ this.$refs['$1'].resetFields() """ "formitem -->vue": "prefix": "formitem" "body": """ <Form-item label="$1" prop="$2"> </Form-item> """ "formitem-input -->vue": "prefix": "formitem.input" "body": """ <Form-item label="$1" prop="$2"> <Input v-model="$3"></Input> </Form-item> """ "formitem-select -->vue": "prefix": "formitem.select" "body": """ <Form-item label="$1" prop="$2"> select </Form-item> """ "formitem-switch -->vue": "prefix": "formitem.switch" "body": """ <Form-item label="$1" prop="$2"> switch </Form-item> """ "formitem-radiogroup -->vue": "prefix": "formitem.radiogroup" "body": """ radiogroup """ "formitem-checkgroup -->vue": "prefix": "formitem.checkgroup" "body": """ checkgroup """ "radio -->vue": "prefix": "radio" "body": """ <Radio label="$1">否</Radio> """ "radiogroup -->vue": "prefix": "radiogroup" "body": """ <RadioGroup v-model="a"> <Radio label="">是</Radio> <Radio label="">否</Radio> </RadioGroup> """ "checkbox -->vue": "prefix": "checkbox" "body": """ <Checkbox label="$1"></Checkbox> """ "checkboxgroup -->vue": "prefix": "checkboxgroup" "body": """ <CheckboxGroup v-model="$1"> <Checkbox label="$2"> </Checkbox> </CheckboxGroup> """ "switch -->vue": "prefix": "switch" "body": """ <i-switch v-model="" :true-value="" :false-value=""> <span slot="open">开</span> <span slot="close">关</span> </i-switch> """ "select -->vue": "prefix": "select" "body": """ <Select v-model="$1"> <Option v-for="(item, index) in List" :value="$2" :key="index">{{$3}}</Option> </Select> """ "datepicker -->vue": "prefix": "datepicker" "body": """ <DatePicker type="date"></DatePicker> """ "inputnumber -->vue": "prefix": "inputnumber" "body": """ <InputNumber v-model="$1"></InputNumber> """ "rate -->vue": "prefix": "rate" "body": """ <Rate v-model="$1"></Rate> """ "alert -->vue": "prefix": "alert" "body": """ <Alert type=""></Alert> """ "alert.success -->vue": "prefix": "alert" "body": """ <Alert type="success"></Alert> """ "alert.warning -->vue": "prefix": "alert.warning" "body": """ <Alert type="warning"></Alert> """ "alert.error -->vue": "prefix": "alert.error" "body": """ <Alert type="error"></Alert> """ "message -->vue": "prefix": "icon" "body": """ this.$Message.info('表单验证失败') """ "message.success -->vue": "prefix": "message.success" "body": """ this.$Message.success('表单验证成功!') """ "message.warning -->vue": "prefix": "message.warning" "body": """ this.$Message.warning(content: '表单验证失败!') """ "message.error -->vue": "prefix": "message.error" "body": """ this.$Message.error('表单验证失败!') """ "message.loading -->vue": "prefix": "message.loading" "body": """ this.$Message.loading('表单验证失败!') """ "notice -->vue": "prefix": "notice" "body": """ this.$Notice.open({ title: '', desc: '' }) """ "notice.info -->vue": "prefix": "notice.info" "body": """ this.$Notice.info({ title: '', desc: '' }) """ "notice.success -->vue": "prefix": "notice.succes" "body": """ this.$Notice.succes({ title: '', desc: '' }) """ "notice.warning -->vue": "prefix": "notice.warning" "body": """ this.$Notice.warning({ title: '', desc: '' }) """ "notice.error -->vue": "prefix": "notice.error" "body": """ this.$Notice.error({ title: '', desc: '' }) """ "Modal -->vue": "prefix": "modal" "body": """ <Modal v-model="$1ModalVis" title="" :mask-closable="false" @on-cancel=""> </Modal> """ "Modal.info -->vue": "prefix": "modal.info" "body": """ this.$Modal.info({ title: '', content: '', onOk () { } }) """ "Modal.success -->vue": "prefix": "modal.success" "body": """ this.$Modal.success({ title: '', content: '', onOk () { } }) """ "Modal.warning -->vue": "prefix": "modal.warning" "body": """ this.$Modal.warning({ title: '', content: '', onOk () { } }) """ "Modal.error -->vue": "prefix": "modal.error" "body": """ this.$Modal.error({ title: '', content: '', onOk () { } }) """ "Modal.confirm -->vue": "prefix": "modal.confirm" "body": """ this.$Modal.confirm({ title: '', content: '', onOk () { } }) """ "timeline -->vue": "prefix": "timeline" "body": """ <Timeline> <TimelineItem color=""></TimelineItem> </Timeline> """ "timelineitem -->vue": "prefix": "timelineitem" "body": """ <TimelineItem color=""></TimelineItem> """ "tag -->vue": "prefix": "tag" "body": """ <Tag>$1</Tag> """ "tag.for type='border,dot' -->vue": "prefix": "tag.for" "body": """ <Tag v-for="(item, index) in $1" :key="index">{{item.$2}}</Tag> """ "tooptip -->vue": "prefix": "tooptip" "body": """ <Tooltip content=""> </Tooltip> """ "poptip -->vue": "prefix": "poptip" "body": """ <Poptip trigger="hover" title="$1" content="$2"> $3 </Poptip> """ "carousel -->vue": "prefix": "carousel" "body": """ <Carousel v-model="$1" loop> <CarouselItem></CarouselItem> </Carousel> """ "carouselitem -->vue": "prefix": "carouselitem" "body": """ <CarouselItem></CarouselItem> """ "menu -->vue": "prefix": "menu" "body": """ <Menu theme="dark"> <Submenu name=""> <template slot="title"> </template> <MenuItem name=""></MenuItem> </Submenu> </Menu> """ "menuitem -->vue": "prefix": "menuitem" "body": """ <MenuItem name=""></MenuItem>s """ "drop -->vue": "prefix": "drop" "body": """ <Dropdown @on-click=""> <DropdownMenu slot="list"> <DropdownItem></DropdownItem> </DropdownMenu> </Dropdown> """ "breadcrumb -->vue": "prefix": "breadcrumb" "body": """ <Breadcrumb> <BreadcrumbItem v-for="(item, index) in $1" :key="index" to="$2">$3</BreadcrumbItem> </Breadcrumb> """ "circle -->vue": "prefix": "circle" "body": """ <Circle :percent=""> </Circle> """ "backtop -->vue": "prefix": "backtop" "body": """ <BackTop></BackTop> """ "spin -->vue": "prefix": "spin" "body": """ <Spin></Spin> """ "vue -->vue": "prefix": "vue" "body": """ <template> <div name="$1"> </div> </template> <script> let vm = null export default { components: { }, data () { return { } }, computed: { }, methods: { }, created () { vm = this console.log(vm) } } </script> <style scoped> </style> """ "api -->vue": "prefix": "api" "body": """ api.$1($2).then(res => { console.log(res) }).catch(err => { console.log('err', err) }) """ "columns -->vue": "prefix": "columns" "body": """ let $1Col = [ { title: '', key: '' }, { title: '', key: '' }, { title: '', key: '' } ] """ "column -->vue": "prefix": "column" "body": """ { title: '', key: '' } """ "column.selection -->vue": "prefix": "column.selection" "body": """ { type: 'selection', width: 60, align: 'center' } """ "column.index -->vue": "prefix": "column.index" "body": """ { type: 'index', width: 60, align: 'center' } """ "render -->vue": "prefix": "render" "body": """ render: (h, params) => { return h() } """ "render.button -->vue": "prefix": "render" "body": """ render: (h, params) => { return h('Button', { props: { type: 'primary', size: 'small' }, on: { click () { } } }, '查看') } """ "rules -->vue": "prefix": "rules" "body": """ let $1FormRules = { $2: { required: true, message: '$3不能为空', trigger: 'blur' }, $4: { required: true, message: '$5不能为空', trigger: 'blur' }, $6: { required: true, message: '$7不能为空', trigger: 'blur' } } """ "rule -->vue": "prefix": "rule" "body": """ $1: [ {required: true, message: '$2不能为空', trigger: 'blur'} ] """ "import -->vue": "prefix": "import" "body": """ import $1 from '@/' """ "func -->vue": "prefix": "func" "body": """ function (item) { $1 } """ "dispatch -->vue": "prefix": "dispatch" "body": """ this.$store.dispatch('$1') """ "router -->vue": "prefix": "router" "body": """ this.$router.push({ name: '$1' }) """ "routerview -->vue": "prefix": "routerview" "body": """ <router-view></router-view> """ "colorpicker -->vue": "prefix": "colorpicker" "body": """ <ColorPicker v-model="$1" /> """ "ntable (slot='batch,ouput')-->vue": "prefix": "ntable" "body": """ <NTable :nColumns="$1" :nData="$2"></NTable> """ "buttonGroup": "prefix": "buttongroup", "body": """ <ButtonGroup> <Button type="primary">L</Button> <Button type="dashed">R</Button> </ButtonGroup> """ "nextTick": "prefix": "nextTick", "body": """ this.$nextTick(() => { }) """","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"vscode","slug":"vscode","permalink":"https://over58.github.io/tags/vscode/"}],"author":["徐勇超"]},{"title":"amd/cmd/commonjs和es6的简单对比","slug":"amd-cmd-commonjs和es6的简单对比","date":"2019-07-08T23:11:12.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/07/08/amd-cmd-commonjs和es6的简单对比/","link":"","permalink":"https://over58.github.io/2019/07/08/amd-cmd-commonjs%E5%92%8Ces6%E7%9A%84%E7%AE%80%E5%8D%95%E5%AF%B9%E6%AF%94/","excerpt":"","text":"AMDAMD是RequireJS在推广过程中的对模块定义的规范化产出。异步加载模块,依赖前置 12345678define("package/lib", function(lib){ function () foo{ lib.log("hello world) } return { foo: foo }}) CMDCMD是SeaJS在推广过程中对模块定义的规范化产出。 异步加载, 依赖就近 12345define(function(require, exports, module){})//通过require引入依赖var $ = require("jquery");var Spinning = require("./Spinning") CommonJSCommonJS规范 - module.exports 适用于服务端 123exports.area = function() { return Math.PI * r * r} ES6 - import/export12345678910import $ from 'jquery'export default{ data () { return { } }, methods:{ }}","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"js中函数的参数传递误区","slug":"js中函数的参数传递误区","date":"2019-06-18T10:52:41.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/06/18/js中函数的参数传递误区/","link":"","permalink":"https://over58.github.io/2019/06/18/js%E4%B8%AD%E5%87%BD%E6%95%B0%E7%9A%84%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92%E8%AF%AF%E5%8C%BA/","excerpt":"","text":"误区一直以来都以为在函数参数传递的过程中遵循: 普通类型使用值传递,对象使用引用传递。很多文章也这么说,现在发现大错特错了。下面举个例子来说吧 demo12345678910111213<!-- 在函数中对参数进行了重新赋值 -->function setName(obj) { obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg";}// 定义一个全局变量var person = new Object();setName(person);// 如果对象是引用传递的话,name应该是"Greg"的,事实上结果是Nicholasconsole.log(person.name); 解析在js的参数传递中,有且只有值传递一种。但是变量的访问却有按值访问和按引用访问。在传递对象参数的过程中,实际上是将对象地址的一个拷贝传过去了,就是一个string类型的值。如果是修改对象的属性的话,使用按引用访问的方式,找到堆中的对象进行修改。但是当对参数重新赋值的时候,只不过是改变了obj的存储的string类型的地址值,不存在访问的操作,也就对原始的对象无影响了,obj指向了一个全新的对象,和原来的对象断了联系。","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"js中对象的delete","slug":"js中对象的delete","date":"2019-06-18T10:23:34.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/06/18/js中对象的delete/","link":"","permalink":"https://over58.github.io/2019/06/18/js%E4%B8%AD%E5%AF%B9%E8%B1%A1%E7%9A%84delete/","excerpt":"","text":"delete操作有以下几个特点: 删除自有属性,不影响原型上的,如果自己没有这个属性,仍然返回true 属性描述符configable: false的属性是不能被删除的, 返回false 删除是将key-value都删除了 demo123456789101112131415161718function Person(){}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function(){ console.log(this.name)}var person1 = new Person();console.log(person1)console.log('person1是否自己存在属性name: ', person1.hasOwnProperty("name")); //falseperson1.name = "Greg";console.log('person1的name进行重新赋值以后的person1(对象的属性赋值)', person1)delete person1.name;console.log('删除person1的name属性',person1)console.log('获取person1的name属性(prototype上的)', person1.name) result12345Person {}person1是否自己存在属性name: falseperson1的name进行重新赋值以后的person1(对象的属性赋值) Person { name: 'Greg' }删除person1的name属性 Person {}获取person1的name属性(prototype上的) Nicholas","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"macrotask和microtask以及eventloop的介绍","slug":"macrotask和microtask以及eventloop的介绍","date":"2019-06-17T14:44:56.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/06/17/macrotask和microtask以及eventloop的介绍/","link":"","permalink":"https://over58.github.io/2019/06/17/macrotask%E5%92%8Cmicrotask%E4%BB%A5%E5%8F%8Aeventloop%E7%9A%84%E4%BB%8B%E7%BB%8D/","excerpt":"","text":"前言提起js,有这么几个概念非了解不可。单线程 、回调函数、非阻塞、执行上下文、调用栈、上下文、事件循环、任务队列。 单线程one thread = one call stack == one thing at a time解释来讲就是单线程意味着只有一个调用栈,在同一时刻只能做一件事儿 执行上下文执行上下文就是当前JavaScript代码被解析和执行是所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文中运行。 执行上下文的类型,总共有三类 1.全局执行上下文:这是默认的,最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。共有两个过程: - .创建有全局对象,在浏览器中这个全局对象就是window对象。 - .将this指针指向这个全局对象。一个程序中只能存在一个执行上下文。 2.函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在多个函数执行上下文,这些函数执行上下文按照特定的顺序执行一系列步骤,后文具体讨论。 3.Eval函数执行上下文:运行在eval函数中的代码也获得了自己的执行上下文,但由于Eval较为少用到,也不建议使用,就不去详细讨论了。。。(eval方法是在运行时对脚本进行解释执行,而普通的javascript会有一个预处理的过程。所以会有一些性能上的损失;eval也存在一个安全问题,因为它可以执行传给它的任何字符串,所以永远不要传入字符串或者来历不明和不受信任源的参数。 (执行栈)调用栈执行栈,也叫调用栈,具有LIFO(Last in, First out 后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。当JavaScript引擎首次读取脚本时,会创建一个全局执行上下文并将其Push到当前执行栈中。每当发生函数调用时,引擎都会为该函数创建一个新的执行上下文并Push到当前执行栈的栈顶。引擎会运行执行上下文在执行栈栈顶的函数,根据LIFO规则,当此函数运行完成后,其对应的执行上下文将会从执行栈中Pop出,上下文控制权将转到当前执行栈的下一个执行上下文。 任务队列、macrotask、mircotask同步任务和异步任务因 js是单线程语言 , 存在大量的IO等耗时操作,所以有“同步任务”和“异步任务”的区分同步任务 在主线程上排队执行的任务,前一个任务执行完毕,才能执行后一个任务;异步任务不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。总之只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制,这个过程是循环往复的,所以也叫做Event Loop事件循环 macrotask、mircotask队列microtasks: process.nextTick promise Object.observe MutationObserver macrotasks: setTimeout setInterval setImmediate I/O UI渲染 一个事件循环有一个或多个任务队列,一个任务队列是任务的集合whatwg规范:https://html.spec.whatwg.org/multipage/webappapis.html#task-queue task queue 就是 macrotask queue 每一个 event loop 都有一个 microtask queue task queue == macrotask queue != microtask queue 一个任务 task 可以放入 macrotask queue 也可以放入 microtask queue 中 理解了这些定义之后,再看执行原理: 事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。更为形象的显示代码的执行过程,请看https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ 还要注意一点: 包裹在一个 script 标签中的js代码也是一个 task 确切说是 macrotask。 事件循环执行栈中执行完之后会从任务队列中读取一个task进行执行,这个过程是循环的,称之为 “事件循环” 非阻塞遇到异步操作,主线程会继续处理后面的代码,当异步操作完成以后在任务队列(task)中添加事件 作者:BenjaminShih来源:CSDN原文:https://blog.csdn.net/sjn0503/article/details/76087631版权声明:本文为博主原创文章,转载请附上博文链接!","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":"徐勇超"},{"title":"缓存memorize","slug":"缓存memorize","date":"2019-06-11T16:41:04.000Z","updated":"2021-02-26T06:07:21.818Z","comments":true,"path":"2019/06/11/缓存memorize/","link":"","permalink":"https://over58.github.io/2019/06/11/%E7%BC%93%E5%AD%98memorize/","excerpt":"","text":"code123456789101112function memoize(func, hashFunc) { var memoize = function(key) { var cache = memoize.cache var address = '' + (hashFunc ? hashFunc.apply(this, arguments) : key) if(Object.getOwnPropertyNames(cache).indexOf(address) === -1) { cache[address] = func.apply(this, arguments) } return cache[address] } memoize.cache = {} return memoize} test123456789let fibonacci = memoize(function(n){ return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);})console.log(fibonacci(1))console.log(fibonacci(2))console.log(fibonacci(3))console.log(fibonacci(4))console.log(fibonacci(10))console.log(fibonacci(100))","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":"徐勇超"},{"title":"节流和防抖","slug":"节流和防抖","date":"2019-06-11T15:10:55.000Z","updated":"2021-02-26T06:07:21.818Z","comments":true,"path":"2019/06/11/节流和防抖/","link":"","permalink":"https://over58.github.io/2019/06/11/%E8%8A%82%E6%B5%81%E5%92%8C%E9%98%B2%E6%8A%96/","excerpt":"前言很多常见中由于事件频繁的被触发,造成频繁的执行Dom操作,资源加载等行为,导致UI卡顿甚至崩溃。 window 的resize和scroll事件 mousedown keydown mousemove keydup事件实际上对于window的resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。 throttle介绍每间隔一定的时间执行一次函数,在此期间内得多次函数调用忽略不处理 demo1234567891011function throttle(fn, wait){ let pervious = 0 return function (...args) { let now = Date.now() if(now - pervious > wait) { pervious = now fn.apply(this, args) } }}","text":"前言很多常见中由于事件频繁的被触发,造成频繁的执行Dom操作,资源加载等行为,导致UI卡顿甚至崩溃。 window 的resize和scroll事件 mousedown keydown mousemove keydup事件实际上对于window的resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。 throttle介绍每间隔一定的时间执行一次函数,在此期间内得多次函数调用忽略不处理 demo1234567891011function throttle(fn, wait){ let pervious = 0 return function (...args) { let now = Date.now() if(now - pervious > wait) { pervious = now fn.apply(this, args) } }} underscore源码分析123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 /* options的默认值 * 表示首次调用返回值方法时,会马上调用func;否则仅会记录当前时刻,当第二次调用的时间间隔超过wait时,才调用func。 * options.leading = true; * 表示当调用方法时,未到达wait指定的时间间隔,则启动计时器延迟调用func函数,若后续在既未达到wait指定的时间间隔和func函数又未被调用的情况下调用返回值方法,则被调用请求将被丢弃。 * options.trailing = true; * 注意:当options.trailing = false时,效果与上面的简单实现效果相同 */_.throttle = function(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = _.now(); if (!previous && options.leading === false) previous = now; // 默认为时开头就调用一次的, Date.now() - previous (0) > wait var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { // 当间隔大于wait 或者 当前时间小于上一个时间(!!!客户端人为的更改系统时间后,马上执行了func函数) if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 无timeout和 允许结尾时调用一次函数 timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; }; 防抖介绍如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。 简单demo123456789101112131415161718function debouce (fn, wait, immediate) { let timer = null return function(...args){ <!-- 清除原来的延时器 --> if(tiemr) clearTimeout(timer) <!-- 第一次调用 --> if (immediate && !tiemr){ fn.apply(this, args) } timer = setTimeout(function() { fn.apply(this, args) }, wait) }} 加强版的throttle现在考虑一种情况,如果用户的操作非常频繁,不等设置的延迟时间结束就进行下次操作,会频繁的清除计时器并重新生成,所以函数 fn 一直都没办法执行,导致用户操作迟迟得不到响应。有一种思想是将「节流」和「防抖」合二为一,变成加强版的节流函数,关键点在于「 wait 时间内,可以重新生成定时器,但只要 wait 的时间到了,必须给用户一个响应。这种合体思路恰好可以解决上面提出的问题。 1234567891011121314151617181920212223function throttle(fn, wait , immediate) { let pervious = 0 let timer = null return function(...args) { let now = Date.now() if(now - pervious < wait) { if (timer) clearTimeout(timer) if (immediate && !timer) { fn.apply(this, args) } timer = setTimeout(function() { pervious = now fn.apply(this, args) }) }else{ pervious = now fn.apply(this, args) } }} underscore源码分析实现原理和demo可以说是一摸一样 1234567891011121314151617181920212223242526272829303132333435363738394041424344// 此处的三个参数上文都有解释_.debounce = function(func, wait, immediate) { var timer, result; // 定时器计时结束后 // 1、清空计时器,使之不影响下次连续事件的触发 // 2、触发执行 func var later = function(context, args) { timer = null; if (args) result = func.apply(context, args); }; // 将 debounce 处理结果当作函数返回 var debounced = restArguments(function(args) { if (timer) clearTimeout(timer); if (immediate) { // 第一次触发后会设置 timer, // 根据 timer 是否为空可以判断是否是首次触发 var callNow = !timer; timer = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { // 设置定时器 timer = _.delay(later, wait, this, args); } return result; }); // 新增 手动取消 debounced.cancel = function() { clearTimeout(timer); timer = null; }; return debounced;};// 根据给定的毫秒 wait 延迟执行函数 func_.delay = restArguments(function(func, wait, args) { return setTimeout(function() { return func.apply(null, args); }, wait);});","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":"徐勇超"},{"title":"highcharts中一些插件的使用方式demo","slug":"highcharts中一些插件的使用方式demo","date":"2019-06-03T19:54:27.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/06/03/highcharts中一些插件的使用方式demo/","link":"","permalink":"https://over58.github.io/2019/06/03/highcharts%E4%B8%AD%E4%B8%80%E4%BA%9B%E6%8F%92%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8Fdemo/","excerpt":"","text":"效果图demo123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251<template> <Card dis-hover class="chart-item" style="margin-bottom:20px"> <template v-if="!simple"> <div slot="title" class="chart-item-title"> {{ title }} <span style="float:right"> <slot name="extra"> <template v-if="unit"> <span class="desc-title">单位</span> {{unit}} <span class='vertital-divider'>|</span> </template> </slot> </span> </div> <div class="chart-item-header"> <slot name="operation"></slot> </div> </template> <div ref="chart" :class="['chart-item-content', {'simple' : simple}]"></div> </Card></template><script>import Highcharts from 'highcharts'import HighchartsNoData from 'highcharts/modules/no-data-to-display'import HighchartsExporting from 'highcharts/modules/exporting'import mockData from './mock.js'HighchartsNoData(Highcharts)HighchartsExporting(Highcharts)export default { name: 'ChartItem', props: { simple: { type: Boolean, default: false }, type: { type: String, default: 'line', validator (val) { if (['area', 'spline', 'line'].indexOf(val) > -1) { return true } else { throw new Error('chart type must be oneof [line, spline, area]') } } }, title: { type: String, default: '' }, // 图表名字 extra: Object, // 关于图表的一些其他信息 data: { type: Array, required: true }, unit: { type: String, default: '' }, plotLine: { type: Object, default: null }, notTimeStamp: { type: Boolean, default: false } }, data () { return { chartInstance: null, mockData: mockData } }, computed: { // resize () { // return this.$store.state.common.resize // }, option () { return { chart: { type: this.type }, title: { text: this.simple ? this.title : '', style: { fontWeight: 'bold' } }, credits: { text: 'http://nevis.sina.com.cn', href: 'http://nevis.sina.com.cn' }, legend: { enabled: true, lineHeight: 20, maxHeight: 40 }, tooltip: { xDateFormat: '%Y-%m-%d %H:%M', valueDecimals: 2, shared: true }, xAxis: { startOnTick: true, tickmarkPlacement: 'on', type: 'datetime', dateTimeLabelFormats: { day: '%Y/%b/%e' }, followPointer: true, crosshair: true }, yAxis: { startOnTick: true, lineWidth: 1, tickWidth: 1, title: { align: 'middle', text: `值(${this.unit})` }, plotBands: [], min: this.min || null }, plotOptions: { series: { lineWidth: 1, marker: { enabled: false } } }, exporting: { enabled: true, buttons: { contextButton: { menuItems: [ 'viewFullscreen', 'downloadPNG', 'downloadJPEG', 'downloadPDF', 'downloadSVG' ] } }, filename: this.title }, series: this.innerData } }, innerData () { if (this.notTimeStamp) { return this.data.map(item => { return [Date.parse(item[0]), item[1]] }) } else { return this.data } } }, watch: { // resize () { // this.chartInstance && this.chartInstance.reflow() // } }, methods: { init () { Highcharts.setOptions({ global: { useUTC: false }, lang: { loading: '加载中...', shortMonths: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], printChart: '打印图表', downloadJPEG: '导出jpeg', downloadPNG: '导出png', downloadSVG: '导出svg', downloadPDF: '导出pdf', noData: '暂无数据', contextButtonTitle: '导出', viewFullscreen: '全屏显示', viewData: '表格显示数据' } }) let option = this.option if (this.plotLine) { option = Object.assign(this.option, { yAxis: { plotLines: [ { value: this.plotLine.value, color: 'red', width: 2, dashStyle: 'solid', label: { text: this.plotLine.text || '', align: this.plotLine.align || 'left', style: { color: 'red', fontSize: '18px' } } } ] } }) } Highcharts.chart(this.$refs.chart, option) } }, mounted () { this.init() }, beforeDestroy () { this.chartInstance && this.chartInstance.destroy() this.chartInstance = null }}</script><style lang="less" scoped>.chart-item { margin-bottom: 20px; &-title { font-weight: bold; height: 18px; line-height: 18px; .vertital-divider{ margin: 0 5px; display: inline-block; width: 1px; vertical-align: middle; color: #e8eaec; box-sizing: border-box; background: #e8eaec; } .desc-title{ color: #888; } } &-header { text-align: right; } &-content { height: 300px; } .simple{ height: 346px; }}</style>","categories":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/categories/graph/"}],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"},{"name":"highcharts","slug":"highcharts","permalink":"https://over58.github.io/tags/highcharts/"}],"author":["徐勇超"]},{"title":"iphone中的一些兼容问题","slug":"iphone中的一些兼容问题","date":"2019-06-03T19:46:19.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/06/03/iphone中的一些兼容问题/","link":"","permalink":"https://over58.github.io/2019/06/03/iphone%E4%B8%AD%E7%9A%84%E4%B8%80%E4%BA%9B%E5%85%BC%E5%AE%B9%E9%97%AE%E9%A2%98/","excerpt":"","text":"滚动不流畅解决办法: 123 overflow-y: scroll;touch-action: pan-y;-webkit-overflow-scrolling: touch; input再次获得焦点时,需要多次点击12345678910111213<!-- 解决300ms延迟 -->import fastClick from 'fastclick'// 解决ios输入框bug:第一次点击输入框,正常反应;// 点击键盘完成后,再次点击输入框,很难再获得焦点的问题fastClick.prototype.onTouchEnd = function (event) { if (event.target.hasAttribute('type') && event.target.getAttribute('type') === 'text') { event.preventDefault() return false }}fastClick.attach(document.body)","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"},{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}],"author":"徐勇超"},{"title":"vue中组件间的六种通信方式","slug":"vue中组件通信的六种方式","date":"2019-06-03T19:00:42.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/06/03/vue中组件通信的六种方式/","link":"","permalink":"https://over58.github.io/2019/06/03/vue%E4%B8%AD%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E5%85%AD%E7%A7%8D%E6%96%B9%E5%BC%8F/","excerpt":"","text":"方法一 props、$emit父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。 1.父组件向子组件传值接下来我们通过一个例子,说明父组件如何向子组件传递值:在子组件Users.vue中如何获取父组件App.vue中的数据 users:[“Henry”,”Bucky”,”Emily” 12345678910111213141516171819//App.vue父组件<template> <div id="app"> <users v-bind:users="users"></users>//前者自定义名称便于子组件调用,后者要传递数据名 </div></template><script>import Users from "./components/Users"export default { name: 'App', data(){ return{ users:["Henry","Bucky","Emily"] } }, components:{ "users":Users }} 12345678910111213141516171819//users子组件<template> <div class="hello"> <ul> <li v-for="user in users">{{user}}</li>//遍历传递过来的值,然后呈现到页面 </ul> </div></template><script>export default { name: 'HelloWorld', props:{ users:{ //这个就是父组件中子标签自定义名字\\ type:Array, required:true } }}</script> 总结:父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed 2.子组件向父组件传值(通过事件形式)接下来我们通过一个例子,说明子组件如何向父组件传递值:当我们点击“Vue.js Demo”后,子组件向父组件传递值,文字由原来的“传递的是一个值”变成“子向父组件传值”,实现子组件向父组件值的传递。 123456789101112131415161718192021// 子组件<template> <header> <h1 @click="changeTitle">{{title}}</h1>//绑定一个点击事件 </header></template><script>export default { name: 'app-header', data() { return { title:"Vue.js Demo" } }, methods:{ changeTitle() { this.$emit("titleChanged","子向父组件传值");//自定义事件 传递值“子向父组件传值” } }}</script> 123456789101112131415161718192021222324252627// 父组件<template> <div id="app"> <app-header v-on:titleChanged="updateTitle" ></app-header>//与子组件titleChanged自定义事件保持一致 // updateTitle($event)接受传递过来的文字 <h2>{{title}}</h2> </div></template><script>import Header from "./components/Header"export default { name: 'App', data(){ return{ title:"传递的是一个值" } }, methods:{ updateTitle(e){ //声明这个函数 this.title = e; } }, components:{ "app-header":Header, }}</script> 总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。 方法二、 $emit / $on这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。 1.具体实现方式:123var Event=new Vue();Event.$emit(事件名,数据);Event.$on(事件名,data => {}); 2.举个例子假设兄弟组件有三个,分别是A、B、C组件,C组件如何获取A或者B组件的数据 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576<div id="itany"> <my-a></my-a> <my-b></my-b> <my-c></my-c></div><template id="a"> <div> <h3>A组件:{{name}}</h3> <button @click="send">将数据发送给C组件</button> </div></template><template id="b"> <div> <h3>B组件:{{age}}</h3> <button @click="send">将数组发送给C组件</button> </div></template><template id="c"> <div> <h3>C组件:{{name}},{{age}}</h3> </div></template><script>var Event = new Vue();//定义一个空的Vue实例var A = { template: '#a', data() { return { name: 'tom' } }, methods: { send() { Event.$emit('data-a', this.name); } }}var B = { template: '#b', data() { return { age: 20 } }, methods: { send() { Event.$emit('data-b', this.age); } }}var C = { template: '#c', data() { return { name: '', age: "" } }, mounted() {//在模板编译完成后执行 Event.$on('data-a',name => { this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event }) Event.$on('data-b',age => { this.age = age; }) }}var vm = new Vue({ el: '#itany', components: { 'my-a': A, 'my-b': B, 'my-c': C }}); </script> 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。 方法三、vuex 1.简要介绍Vuex原理Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。 2.简要介绍各模块在流程中的功能: Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。 dispatch:操作行为触发方法,是唯一能执行action的方法。 actions:操作行为处理模块,由组件中的 $store.dispatch(‘action 名称’,data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。 commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。 mutations:状态改变操作方法,由actions中的 commit(‘mutation 名称’)来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。 state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。 getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。3.Vuex与localStoragevuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。1234567891011121314151617181920let defaultCity = "上海"try { // 用户关闭了本地存储功能,此时在外层加个try...catch if (!defaultCity){ defaultCity = JSON.parse(window.localStorage.getItem('defaultCity')) }}catch(e){}export default new Vuex.Store({ state: { city: defaultCity }, mutations: { changeCity(state, city) { state.city = city try { window.localStorage.setItem('defaultCity', JSON.stringify(state.city)); // 数据改变的时候把数据拷贝一份保存到localStorage里面 } catch (e) {} } }}) 这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:12JSON.stringify(state.subscribeList); // array -> stringJSON.parse(window.localStorage.getItem("subscribeList")); // string -> array 方法四、 $attrs / $listeners1.简介多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法—- $attrs/ $listeners$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。通常配合 interitAttrs 选项一起使用。$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件接下来我们看个跨级通信的例子:123456789101112131415161718192021222324252627// index.vue<template> <div> <h2>浪里行舟</h2> <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠" ></child-com1> </div></template><script>const childCom1 = () => import("./childCom1.vue");export default { components: { childCom1 }, data() { return { foo: "Javascript", boo: "Html", coo: "CSS", doo: "Vue" }; }};</script> 1234567891011121314151617181920212223// childCom1.vue<template class="border"> <div> <p>foo: {{ foo }}</p> <p>childCom1的$attrs: {{ $attrs }}</p> <child-com2 v-bind="$attrs"></child-com2> </div></template><script>const childCom2 = () => import("./childCom2.vue");export default { components: { childCom2 }, inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性 props: { foo: String // foo作为props属性绑定 }, created() { console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" } }};</script> 1234567891011121314151617181920212223// childCom2.vue<template> <div class="border"> <p>boo: {{ boo }}</p> <p>childCom2: {{ $attrs }}</p> <child-com3 v-bind="$attrs"></child-com3> </div></template><script>const childCom3 = () => import("./childCom3.vue");export default { components: { childCom3 }, inheritAttrs: false, props: { boo: String }, created() { console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" } }};</script> 1234567891011121314// childCom3.vue<template> <div class="border"> <p>childCom3: {{ $attrs }}</p> </div></template><script>export default { props: { coo: String, title: String }};</script> $attrs表示没有继承数据的对象,格式为{属性名:属性值}。Vue2.4提供了 $attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。简单来说: $attrs与 $listeners 是两个对象, $attrs 里存放的是父组件中绑定的非 Props 属性, $listeners里存放的是父组件中绑定的非原生事件。方法五、provide/inject1.简介Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。2.举个例子假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件12345678910111213// A.vueexport default { provide: { name: '浪里行舟' }}// B.vueexport default { inject: ['name'], mounted () { console.log(this.name); // 浪里行舟 }} 可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 浪里行舟,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 浪里行舟。这就是 provide / inject API 最核心的用法。需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的—-vue官方文档所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的3.provide与inject 怎么实现数据响应式一般来说,有两种办法: provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods 使用2.6最新API Vue.observable 优化响应式 provide(推荐)我们来看个例子:孙组件D、E和F获取A组件传递过来的color值,并能实现数据响应式变化,即A组件的color变化后,组件D、E、F不会跟着变(核心代码如下:)1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768// A 组件 <div> <h1>A 组件</h1> <button @click="() => changeColor()">改变color</button> <ChildrenB /> <ChildrenC /></div>...... data() { return { color: "blue" }; }, // provide() { // return { // theme: { // color: this.color //这种方式绑定的数据并不是可响应的 // } // 即A组件的color变化后,组件D、E、F不会跟着变 // }; // }, provide() { return { theme: this//方法一:提供祖先组件的实例 }; }, methods: { changeColor(color) { if (color) { this.color = color; } else { this.color = this.color === "blue" ? "red" : "blue"; } } } // 方法二:使用vue2.6最新API Vue.observable 优化响应式 provide // provide() { // this.theme = Vue.observable({ // color: "blue" // }); // return { // theme: this.theme // }; // }, // methods: { // changeColor(color) { // if (color) { // this.theme.color = color; // } else { // this.theme.color = this.theme.color === "blue" ? "red" : "blue"; // } // } // }// F 组件 <template functional> <div class="border2"> <h3 :style="{ color: injections.theme.color }">F 组件</h3> </div></template><script>export default { inject: { theme: { //函数式组件取值不一样 default: () => ({}) } }};</script> 虽说provide 和 inject 主要为高阶插件/组件库提供用例,但如果你能在业务中熟练运用,可以达到事半功倍的效果!方法六、 $parent / $children & ref ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例 $parent / $children:访问父 / 子实例需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:1234567891011121314151617181920212223242526// component-a 子组件export default { data () { return { title: 'Vue.js' } }, methods: { sayHello () { window.alert('Hello'); } }}// 父组件<template> <component-a ref="comA"></component-a></template><script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // Vue.js comA.sayHello(); // 弹窗 } }</script> 不过,这两种方法的弊端是,无法在跨级或兄弟间通信。1234// parent.vue<component-a></component-a><component-b></component-b><component-b></component-b> 我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,就得配置额外的插件或工具了,比如 Vuex 和 Bus 的解决方案总结常见使用场景可以分为三类: 父子通信: 父向子传递数据是通过 props,子向父是通过 events( $emit);通过父链 / 子链也可以通信( $parent / $children);ref 也可以访问组件实例;provide / inject API; $attrs/$listeners 兄弟通信: Bus;Vuex 跨级通信: Bus;Vuex;provide / inject API、 $attrs/$listeners","categories":[],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}],"author":"徐勇超"},{"title":"深拷贝","slug":"深拷贝","date":"2019-05-04T15:29:06.000Z","updated":"2021-02-26T06:07:21.814Z","comments":true,"path":"2019/05/04/深拷贝/","link":"","permalink":"https://over58.github.io/2019/05/04/%E6%B7%B1%E6%8B%B7%E8%B4%9D/","excerpt":"概念浅拷贝创建一个新的对象,这个对象有着对原始对象的属性值的一份拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果是引用对象,拷贝的是地址。所以如果其中一个对象改变了这个地址,就是影响另一个对象 深拷贝将一个对象从内存中完整的拷贝一份出来,从堆中开辟出一个新的区域来存放新对象,且修改新的对象不会影响到原对象","text":"概念浅拷贝创建一个新的对象,这个对象有着对原始对象的属性值的一份拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果是引用对象,拷贝的是地址。所以如果其中一个对象改变了这个地址,就是影响另一个对象 深拷贝将一个对象从内存中完整的拷贝一份出来,从堆中开辟出一个新的区域来存放新对象,且修改新的对象不会影响到原对象 简易版(实际上也是项目中最常用的)1JSON.parse(JSON.stringify(原对象)) 基础版1234567891011function deep_clone1 (source) { if (typeof source === 'object') { let target = {} for(let key in source) { target[key] = clone(source[key]) } return target }else{ return source }} 但是很显然,还存在很多问题,比如并没有考虑数组、循环引用 考虑数组1234567891011function deep_clone2 (source) { if (typeof source === 'object') { let target = Array.isArray(source) ? [] : {} for(let key in source) { target[key] = clone(source[key]) } return target }else{ return source }} 数组和对象的遍历方式可以一致,就是存储的时候不同,[]和{} 处理循环引用1234567891011121314151617function deep_clone3 (source, map = new Map()) { if (typeof source === 'object') { let target = Array.isArray(source) ? [] : {} // 专门处理循环引用问题(object类型) if(map.get(source)) { return map.get(source) } map.set(source, target) for(let key in source) { target[key] = deep_clone3(source[key], map) } return target }else{ return source }} 使用WeakMap画龙点睛WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。 什么是弱引用呢? 在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。举个例子:如果我们使用Map的话,那么对象间是存在强引用关系的: 1234let obj = { name : 'ConardLi'}const target = new Map();target.set(obj,'code秘密花园');obj = null; 复制代码虽然我们手动将obj,进行释放,然是target依然对obj存在强引用关系,所以这部分内存依然无法被释放。再来看WeakMap: 1234let obj = { name : 'ConardLi'}const target = new WeakMap();target.set(obj,'code秘密花园');obj = null; 复制代码如果是WeakMap的话,target和obj存在的就是弱引用关系,当下一次垃圾回收机制执行时,这块内存就会被释放掉。设想一下,如果我们要拷贝的对象非常庞大时,使用Map会对内存造成非常大的额外消耗,而且我们需要手动清除Map的属性才能释放这块内存,而WeakMap会帮我们巧妙化解这个问题。 引用ConardLi:https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1 性能优化在上面的代码中,我们遍历数组和对象都使用了for in这种方式,实际上for in在遍历时效率是非常低的.经过比较执行效率 while > for > for in先实现一个通用的forEach 循环, iterate 是遍历的回调函数, 每次接收value和index两个参数: 12345678function forEach(arr, iterate) { let index = -1 const len = array.length while(++index < len>) { iterate(array[index], index) } return array} 然后对之前的代码进行调整 1234567891011121314151617181920212223function deep_clone3 (source, map = new Map()) { if (typeof source === 'object') { const isArray = Array.isArray(source) let target = isArray ? [] : {} // 专门处理循环引用问题(object类型) if(map.get(source)) { return map.get(source) } map.set(source, target) const keys = isArray ? undefined ? Object.keys(source) forEach(keys || source, (value, index)) { if (keys) { // 处理对象, value就是对象key key = value } target[key] = deep_clone3(source[key], map) } return target }else{ return source }} 其他数据类型可继续遍历的类型1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980const mapTag = '[object Map]';const setTag = '[object Set]';const arrayTag = '[object Array]';const objectTag = '[object Object]';const argsTag = '[object Arguments]';const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];function forEach(array, iteratee) { let index = -1; const length = array.length; while (++index < length) { iteratee(array[index], index); } return array;}function isObject(target) { const type = typeof target; return target !== null && (type === 'object' || type === 'function');}function getType(target) { return Object.prototype.toString.call(target);}function getInit(target) { const Ctor = target.constructor; return new Ctor();}function clone(target, map = new WeakMap()) { // 克隆原始类型 if (!isObject(target)) { return target; } // 初始化 const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target, type); } // 防止循环引用 if (map.get(target)) { return target; } map.set(target, cloneTarget); // 克隆set if (type === setTag) { target.forEach(value => { cloneTarget.add(clone(value)); }); return cloneTarget; } // 克隆map if (type === mapTag) { target.forEach((value, key) => { cloneTarget.set(key, clone(value)); }); return cloneTarget; } // 克隆对象和数组 const keys = type === arrayTag ? undefined : Object.keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget;} 不可继续遍历的类型Bool、Number、String、Symbol、Date、Error这几种类型我们都可以直接用构造函数和原始数据创建一个新对象: 123456789101112131415161718192021222324252627282930function cloneOtherType(targe, type) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); default: return null; }}function cloneSymbol(targe) { return Object(Symbol.prototype.valueOf.call(targe));}克隆正则:function cloneReg(targe) { const reFlags = /\\w*$/; const result = new targe.constructor(targe.source, reFlags.exec(targe)); result.lastIndex = targe.lastIndex; return result;} 克隆函数首先,我们可以通过prototype来区分下箭头函数和普通函数,箭头函数是没有prototype的。我们可以直接使用eval和函数字符串来重新生成一个箭头函数,注意这种方法是不适用于普通函数的。我们可以使用正则来处理普通函数:分别使用正则取出函数体和函数参数,然后使用new Function ([arg1[, arg2[, …argN]],] functionBody)构造函数重新构造一个新的函数: 1234567891011121314151617181920212223242526272829function cloneFunction(func) { const bodyReg = /(?<={)(.|\\n)+(?=})/m; const paramReg = /(?<=\\().+(?=\\)\\s+{)/; const funcString = func.toString(); if (func.prototype) { console.log('普通函数'); const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { console.log('匹配到函数体:', body[0]); if (param) { const paramArr = param[0].split(','); console.log('匹配到参数:', paramArr); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); }}作者:ConardLi链接:https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 原文https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"变量、作用域和内存问题","slug":"js基本类型和引用类型","date":"2019-04-30T15:24:17.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/04/30/js基本类型和引用类型/","link":"","permalink":"https://over58.github.io/2019/04/30/js%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B/","excerpt":"变量全局属性的两种创建方法的区别1. var a=’a’执行Object.getOwnPropertyDescriptor(window, ‘a’) 123456{ value: "a", writable: true, enumerable: true, configurable: false} 2. a=’a’123456{ value: "a", writable: true, enumerable: true, configurable: true} 总而言之,最明显的区别就是var a = ‘a’这种方式定义的变量不能删除","text":"变量全局属性的两种创建方法的区别1. var a=’a’执行Object.getOwnPropertyDescriptor(window, ‘a’) 123456{ value: "a", writable: true, enumerable: true, configurable: false} 2. a=’a’123456{ value: "a", writable: true, enumerable: true, configurable: true} 总而言之,最明显的区别就是var a = ‘a’这种方式定义的变量不能删除 复制变量的方式 复制基本类型时直接复制的是值 复制引用类型时复制的是一个指针 传递参数 传递参数的时候都是按值传递的。(在看了js高级程序设计之前,我是懵的。)访问变量有按值和按引用两种方式,而参数只能按值传递举个栗子就清楚了 123456function setName(obj) { obj.name = "Nicholas";}var person = new Object();setName(person);alert(person.name); //"Nicholas" 12345678function setName(obj) { obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg";}var person = new Object();setName(person);alert(person.name); //"Nicholas" 执行环境和作用域执行环境(execution context,为简单起见,有时也称为“环境”)是 JavaScript 中最为重要的一个概 念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个 与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然我们 编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是 保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所 在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对 6 象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。作用域链中 的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延 续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始, 然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。 基本类型 Undefined Unll Boolean Number String Symbol 小结1、基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;2、从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;3、引用类型的值是对象,保存在堆内存中;4、包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;5、从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"类型转换","slug":"类型转换","permalink":"https://over58.github.io/tags/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/"}],"author":["徐勇超"]},{"title":"count-to组件","slug":"count-to组件","date":"2019-04-29T19:37:54.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/04/29/count-to组件/","link":"","permalink":"https://over58.github.io/2019/04/29/count-to%E7%BB%84%E4%BB%B6/","excerpt":"","text":"123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120<template> <div> <slot name="prepend"></slot><span :class="classes" ref="number" :id="eleId"></span><slot name="append"></slot> </div></template><script>import CountUp from 'countup'export default { name: 'CountTo', props: { satrtVal: { type: Number, default: 0 }, endVal: { type: Number, required: true }, /** * * @description 小数点的精度 */ decimals: { type: Number, default: 0 }, /** * * @description 动画时间,单位: 秒 */ duration: { type: Number, default: 1 }, separator: { type: String, default: ',' }, /** * * @description 是否使用分组 */ useGrouping: { type: Boolean, default: true }, /** * * @description 动画开始延迟,单位:毫秒 */ delay: { type: Number, default: 0 }, className: { type: String, default: '' } }, data () { return { counter: {} } }, computed: { eleId () { return `count-to-${this._uid}` }, classes () { return [ 'count-to-number', this.className ] } }, watch: { endVal (newVal) { this.counter.update(newVal) this.emitEndEvent() } }, methods: { getCount () { return this.$refs.number.innerText }, /** * * @description 动画结束时发送事件 */ emitEndEvent () { setTimeout(() => { this.$nextTick(() => { this.$emit('on-animation-end') }) }, this.duration * 1000 + 5) } }, mounted () { this.$nextTick(() => { this.counter = new CountUp(this.eleId, this.satrtVal, this.endVal, this.decimals, this.duration, { useEasing: true, useGrouping: this.useGrouping, separator: this.separator, decimal: '.' }) setTimeout(() => { this.counter.start() this.emitEndEvent() }, this.delay) }) }}</script><style lang="less" scoped>.count-to-number{ color: #000;}</style>","categories":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/categories/vue/"}],"tags":[{"name":"组件","slug":"组件","permalink":"https://over58.github.io/tags/%E7%BB%84%E4%BB%B6/"}]},{"title":"button-popup","slug":"button-popup","date":"2019-04-29T17:58:49.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/04/29/button-popup/","link":"","permalink":"https://over58.github.io/2019/04/29/button-popup/","excerpt":"效果图","text":"效果图 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115<template> <Poptip :width="width" placement="bottom-start" trigger="click"> <Button>{{title}}{{checkAll ? '(全选)' : ''}}</Button> <div class="content" slot="content"> <Input v-model="search" class="item"/> <Checkbox v-model="checkAll" class="item" label="all" @on-change="handleChangeAll">全部</Checkbox> <CheckboxGroup v-model="select" @on-change="handleChange"> <Checkbox v-for="item in groups" v-show="item.value.indexOf(search) > -1" :key="item.value" :label="item.label" class="item"> {{item.label}} </Checkbox> </CheckboxGroup> </div> </Poptip></template><script>/** * 数据格式: * [{label: '', value: ''}] * * or * * ['XXX1', 'xxx2'] */export default { props: { title: { type: String, required: true }, data: { type: Array, required: true }, value: { type: Array, required: true }, width: { type: Number, default: 100 } }, data () { return { select: this.value, checkAll: false, search: '' } }, computed: { groups () { return this.data.map(item => { if (typeof item === 'string') { return { label: item, value: item } } else if (typeof item === 'object') { if (('value' in item) && ('label' in item)) { return item } } else { throw new Error('data is valid') } }) } }, watch: { value () { this.init() } }, methods: { handleChange (val) { if (val.length === this.data.length) { this.select = this.data this.checkAll = true } else { this.checkAll = false } this.$emit('on-change', this.select) }, handleChangeAll (checkAll) { if (checkAll) { this.select = this.data } else { this.select = [] } this.$emit('on-change', this.select) }, init () { this.select = this.value if (this.select.length === this.data.length) { this.checkAll = true } else { this.checkAll = false } } }, mounted () { this.init() }}</script><style lang="less" scoped>.content{ overflow: hidden;}.item{ display: block; text-overflow: ellipsis;}</style>","categories":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/categories/vue/"}],"tags":[{"name":"组件","slug":"组件","permalink":"https://over58.github.io/tags/%E7%BB%84%E4%BB%B6/"}]},{"title":"js的几种模块化方式","slug":"js的几种模块化方式","date":"2019-04-29T15:03:11.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/04/29/js的几种模块化方式/","link":"","permalink":"https://over58.github.io/2019/04/29/js%E7%9A%84%E5%87%A0%E7%A7%8D%E6%A8%A1%E5%9D%97%E5%8C%96%E6%96%B9%E5%BC%8F/","excerpt":"","text":"模块化的必要性:为了提高代码的开发效率,方便代码的维护,重构。 模块化与组件化的区别:模块可以理解为分解的页面逻辑,比如一个网站的登录,用户管理等;组件则是一个具体的功能。具体来说一个下拉框是一个组件,一个登录功能一个模块。 目前常见的模块化规范(排名不分先后): AMD CMD CommonJS ES6 1. AMDAMD与CMD类似,不同的是AMD推崇依赖前置,–requireJS 推广过程中出现的规范。 123456789/** main.js中引入1.js及2.js **/// 执行基本操作define(["1.js","2.js"],function($,_){ // some code here});/** 如果1.js中又引入了3.js,那就会先广度优先,然后深度遍历。 请求1.js和2.js然后在1.js中进行3.js的请求,3.js返回结果后查看2.js是否已经返回, 如果已经返回则合并结果后返回给main.js**/ 2. CMDCMD推崇就近依赖。 –sea.js推广过程中出现的规范。但是因为在AMD&CMD都是在浏览器端使用,采用的是异步加载,其实CMD还是需要在一开始就请求需要的,只是写法上更方便了。(采用的是正则匹配来获得js文件名,所以注释掉的仍然会请求,并且只可以书写字符串,不可以使用表达式) 1234567891011121314151617/** AMD写法 **/define(["1", "2"], function(1, 2) { // 依赖前置 function foo(){ lib.log('hello world!'); } return { foo: foo };});/** CMD写法 **/define(function(require, exports, module) { var test = require('./1'); //就近依赖 test.sayHi();}); 3. CommonJS大前端使用的Node即时CommonJS的实例。与AMD&CMD的不同之处在于CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。require参数规则: 3.1. 如果参数字符串以“/”开头,则表示加载的是一个位于绝对路径的模块文件。比如,require(‘/home/marco/foo.js’)将加载/home/marco/foo.js。3.2. 如果参数字符串以“./”开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件。比如,require(‘./circle’)将加载当前脚本同一目录的circle.js。3.3. 如果参数字符串不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块(位于Node的系统安装目录中),或者一个位于各级node_modules目录的已安装模块(全局安装或局部安装)。3.4. 如果参数字符串不以“./“或”/“开头,而且是一个路径,比如require(‘example-module/path/to/file’),则将先找到example-module的位置,然后再以它为参数,找到后续路径。3.5. 如果指定的模块文件没有发现,Node会尝试为文件名添加.js、.json、.node后,再去搜索。.js件会以文本格式的JavaScript脚本文件解析,.json文件会以JSON格式的文本文件解析,.node文件会以编译后的二进制文件解析。3.6. 如果想得到require命令加载的确切文件名,使用require.resolve()方法。 1234567const webpack = require('webpack');//引入const sayHi = function (){ console.log('haha');}module.exports = { sayHi:sayHi}//导出 4. ES6相对于以上的AMD&CMD是用于浏览器端,CommonJS用于服务器端。ES6的模块化非常可喜可贺的是浏览器和服务器通用的模块解决方案。那它是怎么做到的呢?区别与以上三者需要在 进行时加载,ES6尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。使用: 1234567ES6使用import进行引入,export default(比export更友好)进行导出。import { lastName as surname } from './profile.js';function foo() { console.log('foo');}export default foo; 作者:littleStar链接:https://juejin.im/post/5cc5909d518825253f4a5a68来源:掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}]},{"title":"js的有趣应用","slug":"js的有趣应用","date":"2019-04-29T14:14:53.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/04/29/js的有趣应用/","link":"","permalink":"https://over58.github.io/2019/04/29/js%E7%9A%84%E6%9C%89%E8%B6%A3%E5%BA%94%E7%94%A8/","excerpt":"","text":"打乱数组1let fn = (arr) => arr.slice().sort(() => Math.random() - 0.5) 生成随机的十六进制颜色11'#' + Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, '0') 212Array.from({length:6},()=>Math.floor(Math.random()*16).toString(16)).join("")// Array.from()的第一个参数指定了第二个参数的运行次数 数组去重1[new Set(arr)] 获取URL的查询参数1q={};location.search.replace(/([^?&=]+)=([^&]+)/g,(_,k,v)=>q[k]=v);q; 返回一个键盘12// 用字符串返回一个键盘图形(_=>[..."`1234567890-=~~QWERTYUIOP[]\\\\~ASDFGHJKL;'~~ZXCVBNM,./~"].map(x=>(o+=`/${b='_'.repeat(w=x<y?2:' 667699'[x=["BS","TAB","CAPS","ENTER"][p++]||'SHIFT',p])}\\\\|`,m+=y+(x+' ').slice(0,w)+y+y,n+=y+b+y+y,l+=' __'+b)[73]&&(k.push(l,m,n,o),l='',m=n=o=y),m=n=o=y='|',p=l=k=[])&&k.join``)() 来源 https://juejin.im/post/5cc55eb5e51d456e577f93f0","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}]},{"title":"全屏显示组件","slug":"全屏显示组件","date":"2019-04-29T10:29:38.000Z","updated":"2021-02-26T06:07:21.790Z","comments":true,"path":"2019/04/29/全屏显示组件/","link":"","permalink":"https://over58.github.io/2019/04/29/%E5%85%A8%E5%B1%8F%E6%98%BE%E7%A4%BA%E7%BB%84%E4%BB%B6/","excerpt":"","text":"1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556<template> <Button type="primary" @click="toggle">{{full ? '退出全屏' : '全屏'}}</Button></template><script>export default { name: 'FullScreen', props: { elem: { type: HTMLDivElement, required: true } }, data () { return { full: false } }, methods: { toggle () { this.full = !this.full if (this.full) { if (this.elem.requestFullscreen) { this.elem.requestFullscreen() } else if (this.elem.webkitRequestFullscreen) { this.elem.webkitRequestFullscreen() } else if (this.elem.mozRequestFullScreen) { this.elem.mozRequestFullScreen() } else if (this.elem.msRequestFullscreen) { this.elem.msRequestFullscreen() } } else { if (document.exitFullscreen) { document.exitFullscreen() } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen() } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen() } else if (document.msExitFullscreen) { document.msExitFullscreen() } } this.$emit('on-change', this.full) } }}</script><style scoped>.btn-con .ivu-tooltip-rel{ height: 64px; width: 64px; line-height: 56px;}.btn-con .ivu-tooltip-rel i { cursor: pointer;}</style>","categories":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/categories/vue/"}],"tags":[{"name":"组件","slug":"组件","permalink":"https://over58.github.io/tags/%E7%BB%84%E4%BB%B6/"}]},{"title":"封装line和pie简单组件","slug":"封装line和pie简单组件","date":"2019-04-29T10:13:37.000Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2019/04/29/封装line和pie简单组件/","link":"","permalink":"https://over58.github.io/2019/04/29/%E5%B0%81%E8%A3%85line%E5%92%8Cpie%E7%AE%80%E5%8D%95%E7%BB%84%E4%BB%B6/","excerpt":"效果图:","text":"效果图: ChartLine.vue123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250<template> <Card dis-hover class="chart-item" style="margin-bottom:20px"> <template v-if="!simple"> <div slot="title" class="chart-item-title"> {{ title }} <span style="float:right"> <slot name="extra"> <template v-if="unit"> <span class="desc-title">单位</span> {{unit}} <span class='vertital-divider'>|</span> </template> </slot> </span> </div> <div class="chart-item-header"> <slot name="operation"></slot> </div> </template> <div ref="chart" :class="['chart-item-content', {'simple' : simple}]"></div> </Card></template><script>import Highcharts from 'highcharts'import HighchartsNoData from 'highcharts/modules/no-data-to-display'import HighchartsExporting from 'highcharts/modules/exporting'import mockData from './mock.js'HighchartsNoData(Highcharts)HighchartsExporting(Highcharts)export default { name: 'ChartItem', props: { simple: { type: Boolean, default: false }, type: { type: String, default: 'line', validator (val) { if (['area', 'spline', 'line'].indexOf(val) > -1) { return true } else { throw new Error('chart type must be oneof [line, spline, area]') } } }, title: { type: String, default: '' }, // 图表名字 extra: Object, // 关于图表的一些其他信息 data: { type: Array, required: true }, unit: { type: String, default: '' }, plotLine: { type: Object, default: null }, notTimeStamp: { type: Boolean, default: false }, zoomType: { type: String, default: 'none' // 可用值 none x y xy } }, data () { return { chartInstance: null, mockData: mockData } }, computed: { option () { return { chart: { type: this.type, zoomType: zoomType }, title: { text: this.simple ? this.title : '', style: { fontWeight: 'bold' } }, credits: { text: 'http://xxx.com.cn', href: 'http://xxx.com.cn' }, legend: { enabled: true, lineHeight: 20, maxHeight: 40 }, tooltip: { xDateFormat: '%Y-%m-%d %H:%M', valueDecimals: 2, shared: true }, xAxis: { startOnTick: true, tickmarkPlacement: 'on', type: 'datetime', dateTimeLabelFormats: { day: '%Y/%b/%e' }, followPointer: true, crosshair: true }, yAxis: { startOnTick: true, lineWidth: 1, tickWidth: 1, title: { align: 'middle', text: `值(${this.unit})` }, plotBands: [], min: this.min || null }, plotOptions: { series: { lineWidth: 1, marker: { enabled: false } } }, exporting: { enabled: true, buttons: { contextButton: { menuItems: [ 'viewFullscreen', 'downloadPNG', 'downloadJPEG', 'downloadPDF', 'downloadSVG' ] } }, filename: this.title }, series: this.innerData } }, innerData () { if (this.notTimeStamp) { return this.data.map(item => { return [Date.parse(item[0]), item[1]] }) } else { return this.data } } }, watch: { }, methods: { init () { Highcharts.setOptions({ global: { useUTC: true }, lang: { loading: '加载中...', shortMonths: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], printChart: '打印图表', downloadJPEG: '导出jpeg', downloadPNG: '导出png', downloadSVG: '导出svg', downloadPDF: '导出pdf', noData: '暂无数据', contextButtonTitle: '导出', viewFullscreen: '全屏显示', viewData: '表格显示数据' } }) let option = this.option if (this.plotLine) { option = Object.assign(this.option, { yAxis: { plotLines: [ { value: this.plotLine.value, color: 'red', width: 2, dashStyle: 'solid', label: { text: this.plotLine.text || '', align: this.plotLine.align || 'left', style: { color: 'red', fontSize: '18px' } } } ] } }) } Highcharts.chart(this.$refs.chart, option) } }, mounted () { this.init() }, beforeDestroy () { this.chartInstance && this.chartInstance.destroy() this.chartInstance = null }}</script><style lang="less" scoped>.chart-item { margin-bottom: 20px; &-title { font-weight: bold; height: 18px; line-height: 18px; .vertital-divider{ margin: 0 5px; display: inline-block; width: 1px; vertical-align: middle; color: #e8eaec; box-sizing: border-box; background: #e8eaec; } .desc-title{ color: #888; } } &-header { text-align: right; } &-content { height: 400px; } .simple{ height: 446px; }}</style> ChartPie.vue123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184<template> <Card dis-hover class="chart-item" style="margin-bottom:20px"> <template v-if="!simple"> <div slot="title" class="chart-item-title"> {{ title }} <span style="float:right"> <slot name="extra"> <template v-if="unit"> <span class="desc-title">单位</span> {{unit}} <span class='vertital-divider'>|</span> </template> </slot> </span> </div> <div class="chart-item-header"> <slot name="operation"></slot> </div> </template> <div ref="chart" class="chart-item-content"></div> </Card></template><script>import Highcharts from 'highcharts'import HighchartsNoData from 'highcharts/modules/no-data-to-display'import HighchartsExporting from 'highcharts/modules/exporting'HighchartsNoData(Highcharts)HighchartsExporting(Highcharts)export default { name: 'ChartItem', props: { simple: { type: Boolean, default: false }, title: String, // 图表名字 extra: Object, // 关于图表的一些其他信息 data: { type: Array, required: true }, unit: { type: String, default: '' } }, data () { return { chartInstance: null } }, computed: { // resize () { // return this.$store.state.common.resize // }, option () { return { chart: { type: 'pie' }, title: { text: this.simple ? this.title : '', style: { fontWeight: 'bold' } }, credits: { text: 'http://nevis.sina.com.cn', href: 'http://nevis.sina.com.cn' }, legend: { enabled: true, lineHeight: 20, maxHeight: 40 }, // 提示框 tooltip: { pointFormat: '{series.name}: <b>{point.percentage:.2f}%</b>' }, // 数据点 plotOptions: { pie: { allowPointSelect: true, cursor: 'pointer', dataLabels: { enabled: true, format: '<b>{point.name}</b>: {point.percentage:.2f} %' }, showInLegend: true } }, exporting: { enabled: true, buttons: { contextButton: { menuItems: [ 'viewFullscreen', 'downloadPNG', 'downloadJPEG', 'downloadPDF', 'downloadSVG' ] } }, filename: this.title }, series: [ { data: this.innerData } ] } }, innerData () { return this.data } }, watch: { // resize () { // this.chartInstance && this.chartInstance.reflow() // } }, methods: { init () { Highcharts.setOptions({ global: { useUTC: true }, lang: { loading: '加载中...', shortMonths: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], printChart: '打印图表', downloadJPEG: '导出jpeg', downloadPNG: '导出png', downloadSVG: '导出svg', downloadPDF: '导出pdf', noData: '暂无数据', contextButtonTitle: '导出', viewFullscreen: '全屏显示', viewData: '表格显示数据' } }) Highcharts.chart(this.$refs.chart, this.option) } }, mounted () { this.init() }, beforeDestroy () { this.chartInstance && this.chartInstance.destroy() this.chartInstance = null }}</script><style lang="less" scoped>.chart-item { margin-bottom: 20px; &-title { font-weight: bold; height: 18px; line-height: 18px; .vertital-divider{ margin: 0 5px; display: inline-block; width: 1px; vertical-align: middle; color: #e8eaec; box-sizing: border-box; background: #e8eaec; } .desc-title{ color: #888; } } &-header { text-align: right; } &-content { height: 300px; } .simple{ height: 346px; }}</style> 使用12345678910111213141516171819202122232425262728293031323334353637383940414243<template> <div class="chart"> <Row> <Col span="12"> <chart-line :data="mockData.pie.count_tendency" title="响应时间分布(simple)" type="spline" simple></chart-line> </Col> <Col span="12"> <chart-pie :data="mockData.pie.count" title="响应时间分布" type="spline"></chart-pie> </Col> </Row> <chart-line type="spline" :data="mockData.pie.count_tendency" title="响应时间分布" :plot-line="{text: '5000', value: 5000, align: 'right'}" ></chart-line> </div></template><script>import mockData from '@/components/chart/mock.js'import ChartLine from '@/components/chart/ChartLine'import ChartPie from '@/components/chart/ChartPie'export default { name: 'chart', components: { ChartLine, ChartPie }, data () { return { mockData: mockData } }}</script><style lang="less" scoped>.chart{}</style>","categories":[{"name":"可视化","slug":"可视化","permalink":"https://over58.github.io/categories/%E5%8F%AF%E8%A7%86%E5%8C%96/"}],"tags":[{"name":"highcharts","slug":"highcharts","permalink":"https://over58.github.io/tags/highcharts/"}]},{"title":"vue中引入higcharts拓展模块","slug":"vue中引入higcharts拓展模块","date":"2019-04-12T19:01:37.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/04/12/vue中引入higcharts拓展模块/","link":"","permalink":"https://over58.github.io/2019/04/12/vue%E4%B8%AD%E5%BC%95%E5%85%A5higcharts%E6%8B%93%E5%B1%95%E6%A8%A1%E5%9D%97/","excerpt":"","text":"场景vue中使用highcharts绘制图表的时候,当数据位空的时候,图表显示空白,没有问题提示,不太友好。 解决方法highcharts中存在很多拓展模块,当你引入import Highcharts from ‘highcharts’ 的时候,并没有自动引入,需手动引用.那里面又一个模块就是专门解决上述问题的,no-data-to-display模块。 1234567891011121314import Highcharts from 'highcharts'import HighchartsNoData from 'highcharts/modules/no-data-to-display'HighchartsNoData(Highcharts)Highcharts.setOptions({ global: { useUTC: true }, lang: { loading: '加载中...', shortMonths: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], noData: '暂无数据' //指定无数据时的文字提示 }}) 结果如下","categories":[],"tags":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/tags/graph/"},{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}]},{"title":"js中的this机制","slug":"js中的this机制","date":"2019-04-08T20:01:38.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/04/08/js中的this机制/","link":"","permalink":"https://over58.github.io/2019/04/08/js%E4%B8%AD%E7%9A%84this%E6%9C%BA%E5%88%B6/","excerpt":"概念this是函数 ( 运行时,即指的是被调用时 )的上下文, 指向调用它的 (最近的上下文)。 绑定规则 函数绑定 new绑定 箭头函数绑定","text":"概念this是函数 ( 运行时,即指的是被调用时 )的上下文, 指向调用它的 (最近的上下文)。 绑定规则 函数绑定 new绑定 箭头函数绑定 1.函数调用JS(ES5)里面有三种函数调用形式: 123func(p1, p2) obj.child.method(p1, p2)func.call(context, p1, p2) // 先不讲 apply 一般,初学者都知道前两种形式,而且认为前两种形式「优于」第三种形式。从看到这篇文章起,你一定要记住,第三种调用形式,才是正常调用形式: 1func.call(context, p1, p2) 其他两种都是语法糖,可以等价地变为 call 形式: 12345func(p1, p2) 等价于func.call(undefined, p1, p2)obj.child.method(p1, p2) <=>obj.child.method.call(obj.child, p1, p2) 请记下来。(我们称此代码为「转换代码」,方便下文引用)至此我们的函数调用只有一种形式: 1func.call(context, p1, p2) 举些其他例子: 123456789101112131415var a= { name: 'XXX', xxx: function () { console.log(this) }}function xxx() { console.log(this)}(b = a.xxx)()<=> var b = a.xxx.bind(undefined); b()(a.xxx)()<=> a.xxx() Dom元素绑定事件时的thisMDN这样解释: 通常来说this的值是触发事件的元素的引用,这种特性在多个相似的元素使用同一个通用事件监听器时非常让人满意。当使用 addEventListener() 为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。这种this绑定时浏览器内置的,不方便看,但可以假想为: 123456var button = document.getElementById("btn)button.addEventListener('click', handlder)// 当事件被触发时handler.call(event.currentTarget, event) // 那么 this 是什么不言而喻 2.new绑定使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。1、创建(或者说构造)一个新的临时对象。2、指定原型。3、返回这个新对象(这个新对象会绑定到函数调用的this)。4、给原型指定名字为prototype 手写一个new实现 12345678910111213141516171819function Person() { // 1. 创建一个空的对象 var obj = new Object(), // 2. 链接到原型,obj 可以访问到构造函数原型中的属性 // 获得构造函数,arguments中去除第一个参数(构造函数) obj.__proto__ = Person.prototype; obj.__proto__.constructor = Person // 3. var ret = Person.apply(obj, arguments); return ret instanceof Object ? ret : obj;};//4.Person.prototype = { constructor: null} 3.箭头函数ES6新增一种特殊函数类型:箭头函数,根据外层(函数或者全局)作用域(词法作用域)来决定this。 foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改(new也不行)。 123456789101112131415function foo() { // 返回一个箭头函数 return (a) => { // this继承自foo() console.log( this.a ); };}var obj1 = { a: 2};var obj2 = { a: 3}var bar = foo.call( obj1 );bar.call( obj2 ); // 2,不是3! ES6之前和箭头函数类似的模式,采用的是词法作用域取代了传统的this机制。 12345678910function foo() { var self = this; // lexical capture of this setTimeout( function() { console.log( self.a ); // self只是继承了foo()函数的this绑定 }, 100 );}var obj = { a: 2};foo.call(obj); // 2 代码风格统一问题:如果既有this风格的代码,还会使用 seft = this 或者箭头函数来否定this机制。 只使用词法作用域并完全抛弃错误this风格的代码; 完全采用this风格,在必要时使用bind(..),尽量避免使用 self = this 和箭头函数。","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}],"author":["徐勇超"]},{"title":"webpack","slug":"webpack","date":"2019-04-04T19:04:19.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/04/04/webpack/","link":"","permalink":"https://over58.github.io/2019/04/04/webpack/","excerpt":"安装1npm install webpack webpack-cli webpack-dev-server -D 1.基本配置 webpack.config.js当在项目中直接运行webpack时,默认读取webpack.config.js中的配置,等同于运行 webpack webpack.config.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin') //自动生成htmlmodule.exports = { mode: 'development', // 可选development|production entry: path.join(__dirname, 'src', 'main.js'), watch: true, output: { path: path.resolve(__dirname, 'dist'), publicPath: '', // js 路径为public + path + filename filename: "main.js", chunkFilename: '[name].js' }, module: { rules: [ { test: /\\.css$/, // css-loader 导入css // style-loader 将css插入到style标签中 use: ['style-loader', 'css-loader'] }, { test: /\\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, { test: /\\.html$/, // 导入 html use: ['html-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html' }) ], devtool: 'source-map', devServer: { contentBase: path.join('/dist/'), inline: true, host: '127.0.0.1', port: 3000, }};","text":"安装1npm install webpack webpack-cli webpack-dev-server -D 1.基本配置 webpack.config.js当在项目中直接运行webpack时,默认读取webpack.config.js中的配置,等同于运行 webpack webpack.config.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin') //自动生成htmlmodule.exports = { mode: 'development', // 可选development|production entry: path.join(__dirname, 'src', 'main.js'), watch: true, output: { path: path.resolve(__dirname, 'dist'), publicPath: '', // js 路径为public + path + filename filename: "main.js", chunkFilename: '[name].js' }, module: { rules: [ { test: /\\.css$/, // css-loader 导入css // style-loader 将css插入到style标签中 use: ['style-loader', 'css-loader'] }, { test: /\\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, { test: /\\.html$/, // 导入 html use: ['html-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html' }) ], devtool: 'source-map', devServer: { contentBase: path.join('/dist/'), inline: true, host: '127.0.0.1', port: 3000, }}; 2.html处理html-webpack-plugin: 可以指定模版生成html,并可以进行去除双引号、折叠空白符号之类的操作 12345678910111213const HtmlWebpackPlugin = require('html-webpack-plugin')plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', minify: { removeAttributeQuotes: true, collapseInlineTagWhitespace: true }, hash: true })] 3.样式处理3.1基本设置123456789101112131415161718module: { rules: [ { test: /\\.css$/, use: [{ // 插入到style中 loader: 'style-loader', options: { insertAt: 'bottom' } }, 'css-loader'] }, { test: /\\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] } ]} 3.2 将所有的样式抽离到一个css文件12345678910111213141516const MiniCssExtractPlugin= require('mini-css-extract-plugin')module: { rules: [ { test: /\\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader'] }, { test: /\\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] } ]} 3.3 将样式自动添加前缀123456789101112131415161718192021222324cnpm install postcss-loader autoprefixer// webpack.config.jsmodule: { rules: [ { test: /\\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] }, { test: /\\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] } ]}// postcss.config.jsmodule.exports = { plugins: [ require('autoprefixer') ]} or 1234567891011121314var postcssLoader = { loader: 'postcss-loader', options: { plugins: (loader) => [ require('autoprefixer')({ browsers: [ // 加这个后可以出现额外的兼容性前缀 "> 0.01%" ] }) ], sourceMap: true }} 3.4 production 压缩css12345678910const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');module.exports = { optimization: { minimizer: [ new OptimizeCSSAssetsPlugin() ] }, mode: 'production', // production | development} 3.5 production 压缩js123456789101112131415cnpm install uglifyjs-webpack-plugin -D const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); //压缩jsmodule.exports = { optimization: { //优化项 minimizer: [ new UglifyWebpackplugin({ test: /\\.js(\\?.*)?$/i, cache: true, // 是否并行处理 parallel: true, sourceMap: true }) ] }} 4. 转换es6语法以及校验12345678910111213141516171819202122232425262728293031323334cnpm install babel-loader @babel/core @babel/preset-env -Dmodule.exports = { entry: ["@babel/polyfill", "./src/main.js"], module:[ { test: /\\.js$/, include: path.resolve(__dirname, 'src'), exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ '@babel/preset-env' ], plugins: [ "@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime" //根据自己的需求自行添加 ] } } }, { test: /\\.js$/, use: { loader: 'eslint-loader', options: { enfore: 'pre' } } }]} 5. 全局变量引入的问题(以jquery为例) 全局loader expose-loader 前置loder 普通loader 内联loader 后置loader postcss-loader 5.1 import $ from ‘jquery’ 时1234module = [ test: require.resove('jquery'), use:'expose-loader?$'] 5.2 将$注入到每一个模块中去,在模块中可以直接使用$123456const webpack = require('webpack')plugins: [ new webpack.ProvidePlugin({ $: 'jquery' })], 6.处理图片6.1 处理css,js中的图片引用123456module: [ { test: /.(jpg|jpeg|png|gif|svg)$/, use:'file-loader' }] 6.2 处理html中引入的图片123456module: [ { test: /.html$/, use:'html-withimg-loader' }] 6.3 将小的图片转成base64,减少http请求1234567891011module: [ { loader: 'url-loader', options: { // 小于50k的图片转成base64 limit: 50 * 1024, // 50k name: '[hash:8].[ext]', outputPath: './images' } }] 7.打包文件分类12345678910111213141516171819202122232425//imgmodule: [ { loader: 'url-loader', options: { // 小于50k的图片转成base64 limit: 50 * 1024, // 50k name: '[hash:8].[ext]', outputPath: '/images/', //图片打包到images下 //打包后为图片的引入路径前添加url前缀,可以在需要将图片使用cdn的时候用(和其他文件的publicPath不同) //publicPath: 'http://www.xxxcdn.com/' } }]//cssplugins: [ new MiniCss({ filename: 'css/main.css' // 会将css文件打包为 dist/css/main.css }),]// jsoutput: { filename: 'js/main.js'}","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[]},{"title":"webpack常见配置","slug":"webpack常见配置","date":"2019-04-04T19:04:19.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/04/04/webpack常见配置/","link":"","permalink":"https://over58.github.io/2019/04/04/webpack%E5%B8%B8%E8%A7%81%E9%85%8D%E7%BD%AE/","excerpt":"安装1npm install webpack webpack-cli webpack-dev-server -D 1.基本配置 webpack.config.js当在项目中直接运行webpack时,默认读取webpack.config.js中的配置,等同于运行 webpack webpack.config.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin') //自动生成htmlmodule.exports = { mode: 'development', // 可选development|production entry: path.join(__dirname, 'src', 'main.js'), watch: true, output: { path: path.resolve(__dirname, 'dist'), publicPath: '', // js 路径为public + path + filename filename: "main.js", chunkFilename: '[name].js' }, module: { rules: [ { test: /\\.css$/, // css-loader 导入css // style-loader 将css插入到style标签中 use: ['style-loader', 'css-loader'] }, { test: /\\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, { test: /\\.html$/, // 导入 html use: ['html-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html' }) ], devtool: 'source-map', devServer: { contentBase: path.join('/dist/'), inline: true, host: '127.0.0.1', port: 3000, }};","text":"安装1npm install webpack webpack-cli webpack-dev-server -D 1.基本配置 webpack.config.js当在项目中直接运行webpack时,默认读取webpack.config.js中的配置,等同于运行 webpack webpack.config.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin') //自动生成htmlmodule.exports = { mode: 'development', // 可选development|production entry: path.join(__dirname, 'src', 'main.js'), watch: true, output: { path: path.resolve(__dirname, 'dist'), publicPath: '', // js 路径为public + path + filename filename: "main.js", chunkFilename: '[name].js' }, module: { rules: [ { test: /\\.css$/, // css-loader 导入css // style-loader 将css插入到style标签中 use: ['style-loader', 'css-loader'] }, { test: /\\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, { test: /\\.html$/, // 导入 html use: ['html-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html' }) ], devtool: 'source-map', devServer: { contentBase: path.join('/dist/'), inline: true, host: '127.0.0.1', port: 3000, }}; 2.html处理html-webpack-plugin: 可以指定模版生成html,并可以进行去除双引号、折叠空白符号之类的操作 12345678910111213const HtmlWebpackPlugin = require('html-webpack-plugin')plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', minify: { removeAttributeQuotes: true, collapseInlineTagWhitespace: true }, hash: true })] 3.样式处理3.1基本设置123456789101112131415161718module: { rules: [ { test: /\\.css$/, use: [{ // 插入到style中 loader: 'style-loader', options: { insertAt: 'bottom' } }, 'css-loader'] }, { test: /\\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] } ]} 3.2 将所有的样式抽离到一个css文件12345678910111213141516const MiniCssExtractPlugin= require('mini-css-extract-plugin')module: { rules: [ { test: /\\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader'] }, { test: /\\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] } ]} 3.3 将样式自动添加前缀123456789101112131415161718192021222324cnpm install postcss-loader autoprefixer// webpack.config.jsmodule: { rules: [ { test: /\\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] }, { test: /\\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] } ]}// postcss.config.jsmodule.exports = { plugins: [ require('autoprefixer') ]} or 1234567891011121314var postcssLoader = { loader: 'postcss-loader', options: { plugins: (loader) => [ require('autoprefixer')({ browsers: [ // 加这个后可以出现额外的兼容性前缀 "> 0.01%" ] }) ], sourceMap: true }} 3.4 production 压缩css12345678910const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');module.exports = { optimization: { minimizer: [ new OptimizeCSSAssetsPlugin() ] }, mode: 'production', // production | development} 3.5 production 压缩js123456789101112131415cnpm install uglifyjs-webpack-plugin -D const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); //压缩jsmodule.exports = { optimization: { //优化项 minimizer: [ new UglifyWebpackplugin({ test: /\\.js(\\?.*)?$/i, cache: true, // 是否并行处理 parallel: true, sourceMap: true }) ] }} 4. 转换es6语法以及校验12345678910111213141516171819202122232425262728293031323334cnpm install babel-loader @babel/core @babel/preset-env -Dmodule.exports = { entry: ["@babel/polyfill", "./src/main.js"], module:[ { test: /\\.js$/, include: path.resolve(__dirname, 'src'), exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ '@babel/preset-env' ], plugins: [ "@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime" //根据自己的需求自行添加 ] } } }, { test: /\\.js$/, use: { loader: 'eslint-loader', options: { enfore: 'pre' } } }]} 5. 全局变量引入的问题(以jquery为例) 全局loader expose-loader 前置loder 普通loader 内联loader 后置loader postcss-loader 5.1 import $ from ‘jquery’ 时1234module = [ test: require.resove('jquery'), use:'expose-loader?$'] 5.2 将$注入到每一个模块中去,在模块中可以直接使用$123456const webpack = require('webpack')plugins: [ new webpack.ProvidePlugin({ $: 'jquery' })], 6.处理图片6.1 处理css,js中的图片引用123456module: [ { test: /.(jpg|jpeg|png|gif|svg)$/, use:'file-loader' }] 6.2 处理html中引入的图片123456module: [ { test: /.html$/, use:'html-withimg-loader' }] 6.3 将小的图片转成base64,减少http请求1234567891011module: [ { loader: 'url-loader', options: { // 小于50k的图片转成base64 limit: 50 * 1024, // 50k name: '[hash:8].[ext]', outputPath: './images' } }] 7.打包文件分类12345678910111213141516171819202122232425//imgmodule: [ { loader: 'url-loader', options: { // 小于50k的图片转成base64 limit: 50 * 1024, // 50k name: '[hash:8].[ext]', outputPath: '/images/', //图片打包到images下 //打包后为图片的引入路径前添加url前缀,可以在需要将图片使用cdn的时候用(和其他文件的publicPath不同) //publicPath: 'http://www.xxxcdn.com/' } }]//cssplugins: [ new MiniCss({ filename: 'css/main.css' // 会将css文件打包为 dist/css/main.css }),]// jsoutput: { filename: 'js/main.js'}","categories":[{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"}],"tags":[]},{"title":"伪类确定元素数量","slug":"伪类确定元素数量","date":"2019-03-30T11:48:10.000Z","updated":"2021-02-26T06:07:21.790Z","comments":true,"path":"2019/03/30/伪类确定元素数量/","link":"","permalink":"https://over58.github.io/2019/03/30/%E4%BC%AA%E7%B1%BB%E7%A1%AE%E5%AE%9A%E5%85%83%E7%B4%A0%E6%95%B0%E9%87%8F/","excerpt":"","text":"原理12li:first-child:nth-last-child(n){} 选中的li既是第一个元素也是倒数第n个元素,这就表明了一共有n个li元素代码链接https://codepen.io/xuyongchaos/pen/vMBLpY","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}]},{"title":"执行栈和执行上下文","slug":"执行栈和执行上下文","date":"2019-03-26T09:57:17.000Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2019/03/26/执行栈和执行上下文/","link":"","permalink":"https://over58.github.io/2019/03/26/%E6%89%A7%E8%A1%8C%E6%A0%88%E5%92%8C%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87/","excerpt":"执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。 执行上下文的类型执行上下文总共有三种类型 1.全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。 2.函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。 3.Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,不用很少用而且不建议使用。 执行栈执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。 首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。 根据执行栈LIFO规则,当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。 执行上下文的创建执行上下文分两个阶段创建:1)创建阶段; 2)执行阶段 创建阶段 1、确定 this 的值,也被称为 This Binding。 2、LexicalEnvironment(词法环境) 组件被创建。 3、VariableEnvironment(变量环境) 组件被创建。 直接看伪代码可能更加直观 12345ExecutionContext = { ThisBinding = <this value>, // 确定this LexicalEnvironment = { ... }, // 词法环境 VariableEnvironment = { ... }, // 变量环境}","text":"执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。 执行上下文的类型执行上下文总共有三种类型 1.全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。 2.函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。 3.Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,不用很少用而且不建议使用。 执行栈执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。 首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。 根据执行栈LIFO规则,当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。 执行上下文的创建执行上下文分两个阶段创建:1)创建阶段; 2)执行阶段 创建阶段 1、确定 this 的值,也被称为 This Binding。 2、LexicalEnvironment(词法环境) 组件被创建。 3、VariableEnvironment(变量环境) 组件被创建。 直接看伪代码可能更加直观 12345ExecutionContext = { ThisBinding = <this value>, // 确定this LexicalEnvironment = { ... }, // 词法环境 VariableEnvironment = { ... }, // 变量环境} This Binding全局执行上下文中,this 的值指向全局对象,在浏览器中this 的值指向 window对象,而在nodejs中指向这个文件的module对象。 函数执行上下文中,this 的值取决于函数的调用方式。具体有:默认绑定、隐式绑定、显式绑定(硬绑定)、new绑定、箭头函数,具体内容会在【this全面解析】部分详解。 词法环境(Lexical Environment) Global code:通俗点讲就是源文件代码,就是一个词法环境 函数代码 :一个函数块内自己是一个新的词法环境 eval:进入eval调用的代码有时会创建一个新的词法环境 with结构:一个with结构块内也是自己一个词法环境 catch结构:一个catch结构快内也是自己一个词环境 读到这里有些小伙伴急了,“不对,不对,我记得只有在全局代码、函数代码、和eval代码三种情况,才会创建运行上下文,你专门有5种”。对,你说的没错,只有在全局代码、函数代码、和eval代码三种情况,才会创建运行上下文,但我这里说的是词法环境,Lexical Environments。不是运行上下文。 作者:G哥讲码堂链接:https://juejin.im/post/5c05120be51d4513416d2111来源:掘金 词法环境有两个组成部分 1、环境记录:存储变量和函数声明的实际位置 环境记录分为两种 declarative environment records 主要用于函数 、catch词法环境 object environment records. 主要用于with 和global的词法环境 declarative environment records可以简单理解为字典类型的结构,key-value形式结论变量等对应的名字和值。 而object environment records会关联一个对象,用这个对象的属性-值来登记变量等对应的名字和值。 2、对外部环境的引用:可以访问其外部词法环境 词法环境有两种类型 1、全局环境:是一个没有外部环境的词法环境,其外部环境引用为 null。拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this 的值指向这个全局对象。 2、函数环境:用户在函数中定义的变量被存储在环境记录中,包含了arguments 对象。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。 直接看伪代码可能更加直观 1234567891011121314151617GlobalExectionContext = { // 全局执行上下文 LexicalEnvironment: { // 词法环境 EnvironmentRecord: { // 环境记录 Type: "Object", // 全局环境 // 标识符绑定在这里 outer: <null> // 对外部环境的引用 } }FunctionExectionContext = { // 函数执行上下文 LexicalEnvironment: { // 词法环境 EnvironmentRecord: { // 环境记录 Type: "Declarative", // 函数环境 // 标识符绑定在这里 // 对外部环境的引用 outer: <Global or outer function environment reference> } } 变量环境变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。 在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储函数声明和变量( let 和 const )绑定,而后者仅用于存储变量( var )绑定。 使用例子进行介绍 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859let a = 20; const b = 30; var c;function multiply(e, f) { var g = 20; return e * f * g; }c = multiply(20, 30);执行上下文如下所示GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // 标识符绑定在这里 a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // 标识符绑定在这里 c: undefined, } outer: <null> } }FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // 标识符绑定在这里 Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // 标识符绑定在这里 g: undefined }, outer: <GlobalLexicalEnvironment> } } 变量提升的原因:在创建阶段,函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 let 和 const 的情况下)。所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 let 和 const 定义的变量就会提示引用错误的原因。这就是所谓的变量提升。 执行阶段此阶段,完成对所有变量的分配,最后执行代码。 如果 Javascript 引擎在源代码中声明的实际位置找不到 let 变量的值,那么将为其分配 undefined 值。","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}]},{"title":"vue实现文件下载","slug":"vue实现文件下载","date":"2019-03-21T10:45:33.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/03/21/vue实现文件下载/","link":"","permalink":"https://over58.github.io/2019/03/21/vue%E5%AE%9E%E7%8E%B0%E6%96%87%E4%BB%B6%E4%B8%8B%E8%BD%BD/","excerpt":"","text":"原理1<a href="url" download="文件名.后缀名"> 实际使用场景上面的原理中适合开放的资源下载,http请求中无需验证时使用。在实际使用过程中,a标签中的url中直接设置header比较麻烦且不安全,而且从开发规范上api一般上要封装一下(header中需要一些特定设置),放到统一的文件。 解决办法:下载相关和api responseType设置为blob(!!!重要),在以blob形式获取到文件之后js创建一个a标签,设置url和downlaod后并触发,最后释放url资源并删除创建的a标签 123456789101112131415161718192021222324252627282930313233343536373839404142api:downloadFile (url) => { axios.get(url, { params: { }, headers: { }, // 重要 responseType: 'blob' })}html: <button @click="download">下载</button>js: function downlaod (param){ api.downloadFile(param).then(data => { if (!data) { this.$Message.error('下载内容为空') return } let url = window.URL.createObjectURL(new Blob([data])) let link = document.createElement('a') link.style.display = 'none' link.href = url link.setAttribute('download', etcdCluster + '.zip') document.body.appendChild(link) link.click() //释放URL对象所占资源 window.URL.revokeObjectURL(url) //用完即删 document.body.removeChild(link) }).catch(err => { console.log('err: ', err) }) } MDN相关链接https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURLhttps://developer.mozilla.org/zh-CN/docs/Web/API/URL/revokeObjectURL","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}]},{"title":"js自定义modal框","slug":"js自定义modal框","date":"2019-03-17T20:55:41.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/17/js自定义modal框/","link":"","permalink":"https://over58.github.io/2019/03/17/js%E8%87%AA%E5%AE%9A%E4%B9%89modal%E6%A1%86/","excerpt":"前言作为一个Web开发而言,modal模态框一定不会陌生。本文将简单的讲一下如何使用js自定义一个modal框,当然了,本文的重点并不是教你如何构建一个modal框,而是希望能够教你如何构建一个组件的思路。好了,废话不多说,正文开始。 modal是什么,有什么功能modal是位于用户主窗口之上的一个元素,当它打开的时候,主窗口无法操作,位于主窗口之上的modal框内可以操作。一个modal至少包含以下几个功能: 模态框的蒙版 .modal-overlay 头部 .modal-header 主体 .modal-body 脚步 .modal-footer 关闭按钮 .modal-close 关闭按钮包括多种方式:取消按钮(footer中)、关闭按钮(右上角)、ESC按键、点击模态框主体外的蒙版的区域 构建插件1.首先选择iife。这里使用了闭包的知识,因为闭包可以创建一个私有域。","text":"前言作为一个Web开发而言,modal模态框一定不会陌生。本文将简单的讲一下如何使用js自定义一个modal框,当然了,本文的重点并不是教你如何构建一个modal框,而是希望能够教你如何构建一个组件的思路。好了,废话不多说,正文开始。 modal是什么,有什么功能modal是位于用户主窗口之上的一个元素,当它打开的时候,主窗口无法操作,位于主窗口之上的modal框内可以操作。一个modal至少包含以下几个功能: 模态框的蒙版 .modal-overlay 头部 .modal-header 主体 .modal-body 脚步 .modal-footer 关闭按钮 .modal-close 关闭按钮包括多种方式:取消按钮(footer中)、关闭按钮(右上角)、ESC按键、点击模态框主体外的蒙版的区域 构建插件1.首先选择iife。这里使用了闭包的知识,因为闭包可以创建一个私有域。 123(function(){ var 私有变量 = 值})() 2.设置选项(options) 1234567891011121314151617181920212223242526272829<!-- 设置一个windo中可以访问的函数 -->this.Modal = function() { this.modal = null; // 模态弹出框 this.overlay = null; //蒙板 this.closeButton = null; // 右上角关闭按钮 this.footerCloseButton = null //footer关闭按钮 this.options = { className: 'fade-and-drop', content: '这是一个自定义的模态框', minHeight: '300px', maxHeight: '600px', closable: true, // 是否可关闭,决定着是否有关闭按钮 overlay: true }; // 合并默认设置和用户自定义设置,用户自定义配置>默认配置 if (arguments[0] && typeof arguments[0] === 'object') { this.options = mergeOptions(this.options, arguments[0]); }};//合并对象属性的工具方法function mergeOptions(target, source) { for (let property in source) { if (source.hasOwnProperty(property)) { target[property] = source[property]; } } return target;} 3. 核心功能 现在我们对模态框的插件架构有了一定的了解,它包括了:构造函数、选项和公共方法。但它还不能做什么?接下来我们就要给他们添加相应的核心功能。所以我们再来看看,一个模态框应该做什么: 构建一个模态元素并将其添加到页面中 将选项(options)中的className指定一个类名,并将其添加到模态元素中 如果选项中的closeButton为true,则添加关闭按钮 如果选项中的content是 HTML 字符串,则将其设置为模态元素中的内容 如果选项中的content是domNode,则将其内部内容设置为模态元素的内容 分别设置模态的maxWidth和minWidth 如果选项中的overlay为true,则给模态框添加一个蒙层 当模态框显示时,添加一个scotch-open类名,可以在 CSS 中使用它来定义一个open状态 当模态框关闭时,删除scotch-open类名 如果模态框的高度超过视窗的高度,还可以添加一个scotch-anchored类,这样就可以处理这个场景的样式展示 代码思路12345678910111213141516171819202122232425262728293031323334353637(function(){ //构造函数 this.Modal = function () { // 初始化默认option,  this.options = { } //合并用户自定义配置和默认配置,并赋值给options, 每个实例都可能不一样,所有不能放在prototype上,同时在其它函数中又要引用它,所有定义为公有属性 this.options = mergeOptions(this.options, arguments[0]) } // 打开Modal的方法,每个实例中的这个方法都是一样的,定义在prototype上 Modal.prototype.open = function(){ // 初始化Dom 1.overlay蒙版元素 2.modal元素{ close按钮 content footer } // 初始化Event 包括:{ close事件 transitionEnd事件(关闭按钮中Css使用了过渡动画,等过渡动画完成之后才能移除dom),需要注意的就是不同的的浏览器中这个时间的名字可能不一样 } } //关闭方法 Modal.prototype.close = function(){ //移除Dom }})() !!!!完整的代码链接 https://codepen.io/xuyongchaos/pen/aMGXLy?editors=1010使用到的东西 闭包 DocumentFragment 如何减少浏览器回流 DOM操作 DOM事件 this相关call, bind css样式 总结:编写一个简单的插件需要的知识还是挺多的,没事的话可以自己编写一下,不仅锻炼了模块化编程思维,还可以对以前的知识进行查缺补漏。fighting!!!","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}]},{"title":"从js的属性描述符描述vue.js的响应式视图","slug":"从js的属性描述符描述vue-js的响应式视图","date":"2019-03-15T19:57:05.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/03/15/从js的属性描述符描述vue-js的响应式视图/","link":"","permalink":"https://over58.github.io/2019/03/15/%E4%BB%8Ejs%E7%9A%84%E5%B1%9E%E6%80%A7%E6%8F%8F%E8%BF%B0%E7%AC%A6%E6%8F%8F%E8%BF%B0vue-js%E7%9A%84%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A7%86%E5%9B%BE/","excerpt":"前言JavaScript 的对象,拥有任意数量的唯一键,键可以是字符串(String)类型或标记(Symbol,ES6 新增的基本数据类型)类型,每个键对应一个值,值可以是任意类型的任意值。对于对象内的属性,JavaScript 提供了一个属性描述器接口 PropertyDescriptor 定义对象的属性123456789var obj = { name: 'Tom', sex: 'man}orvar obj = {}obj.name = 'Tom' Object.defineProperty()上面使用的方式不能对属性描述符的操作,需要使用 Object.ddefineProperty(obj, prop, descriptor) 当使用 defineProperty()方法操作属性的时候,描述符的默认值为: value: undefined set: undefined get: undefined writable: false enumerable: false configable: false 不使用该方法定义属性,默认值为: value: undefined set: undefined get: undefined writable: true enumerable: true configable: true 还支持批量修改对象属性以及描述对象123456789Object.defineProperties(obj, { name: { value: 'Tom', configable: true }, sex: { value: 'man' }})","text":"前言JavaScript 的对象,拥有任意数量的唯一键,键可以是字符串(String)类型或标记(Symbol,ES6 新增的基本数据类型)类型,每个键对应一个值,值可以是任意类型的任意值。对于对象内的属性,JavaScript 提供了一个属性描述器接口 PropertyDescriptor 定义对象的属性123456789var obj = { name: 'Tom', sex: 'man}orvar obj = {}obj.name = 'Tom' Object.defineProperty()上面使用的方式不能对属性描述符的操作,需要使用 Object.ddefineProperty(obj, prop, descriptor) 当使用 defineProperty()方法操作属性的时候,描述符的默认值为: value: undefined set: undefined get: undefined writable: false enumerable: false configable: false 不使用该方法定义属性,默认值为: value: undefined set: undefined get: undefined writable: true enumerable: true configable: true 还支持批量修改对象属性以及描述对象123456789Object.defineProperties(obj, { name: { value: 'Tom', configable: true }, sex: { value: 'man' }}) 读取属性描述符对象 Object.getOwnPropertyDescriptor(obj,prop)属性描述符对象value 属性的值存储器函数(setter/getter)1.get2.set 123456789101112var x = {}Object.defineProperty(x, 'count', { get: funciton () { return this.value }, set: function (val) { this.count = val } })console.log(x) x.count = 1 console.log(x.count) 执行上面的代码,会发现报错,执行栈溢出。 上述代码在执行set 函数中执行 count赋值操作的时候(this.count = val),循环调用自己,形成了死循环。更改为以下代码: 123456789101112var x = {}Object.defineProperty(x, 'count', { get: () { return this._count } set: function (val) { this._count = val }})console.log(x) x.count = 1 console.log(x.count) 实际上,在使用 defineProperty()方法设置对象的属性的时候,通常需要在对象内部维护一个新的内部变量(以下划线_开头,表示为内部变量) 注:当设置了存取器描述时,不能设置value 和writable, 可以当作没有这两个属性 writable 指定对象的value是否可以改变enumerable 指定对象中的某属性是否可以枚举,就是for in 操作是否可以遍历出来configable 指定对象属性的描述符是否可以改变","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}]},{"title":"stringify的使用","slug":"stringify的使用","date":"2019-03-13T14:34:07.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/13/stringify的使用/","link":"","permalink":"https://over58.github.io/2019/03/13/stringify%E7%9A%84%E4%BD%BF%E7%94%A8/","excerpt":"","text":"这篇文章的由来是这样的:前两天遇到这样一个场景,接口返回了一个json对象,要求我进行格式化显示(显示出json对象的结构)。那么我就想了,一个对象要想显示出来,得转成string 类型的,于是我就使用了JSON.stringify(),但是转成字符串之后并没有缩进,显示出来的是一个长长的字符串。难道我还要自己人为的遍历对象,自己拼接一个带缩进的字符串么?太TM扯了!于是查看了stringify的官方文档,发现了很多有意思的东西,这个函数的作用远远不止是将json对象转成string那么简单,下面是我列举的stringify的几个小功能: json格式化,带缩进 过滤掉无效的字段 对符合某种条件的字段做操作 原始数据 12345678person = { sex: 'man', name: 'Tom', telphones: [ "234123423", "2345234523" ]} 一、原始情况,直接显示json字符串 code: 1JOSN.stringify(person) 运行结果: 1{"sex":"man","name":"Tom","age":22,"telphones":["2341234123","3452345"]} 二、将一个json对象格式化显示出来 code: 1JSON.stringify(person, null, 2) 运行结果: 12345678{ sex: 'man', name: 'Tom', telphones: [ "234123423", "2345234523" ] } 三、不显示某些字段(哪些字段不需要显示就返回undefined) code 123456JOSN.stringify(person, function(k, v){ if (k === 'telphones'){ return undefined } return v}, 2) 运行结果: 1234{ sex: 'man', name: 'Tom'} 四、只显示某些字段 code 1JOSN.stringify(person, ['sex'], 2) 运行结果: 123{ sex: 'man'} 更多详情https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"JSON","slug":"JSON","permalink":"https://over58.github.io/tags/JSON/"}]},{"title":"将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组","slug":"将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组","date":"2019-03-09T18:48:18.000Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2019/03/09/将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组/","link":"","permalink":"https://over58.github.io/2019/03/09/%E5%B0%86%E6%95%B0%E7%BB%84%E6%89%81%E5%B9%B3%E5%8C%96%E5%B9%B6%E5%8E%BB%E9%99%A4%E5%85%B6%E4%B8%AD%E9%87%8D%E5%A4%8D%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%9C%80%E7%BB%88%E5%BE%97%E5%88%B0%E4%B8%80%E4%B8%AA%E5%8D%87%E5%BA%8F%E4%B8%94%E4%B8%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E7%BB%84/","excerpt":"1var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 解决方法11[...new Set(arr.flat(Infinity).sort((a,b) => a-b))] 解决方法2123[...new Set(arr.toString().split(',').map(Number).sort((a,b) => a-b))]orArrary.froms(new Set(arr.toString().split(',').map(Number).sort((a,b) => a-b)))","text":"1var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 解决方法11[...new Set(arr.flat(Infinity).sort((a,b) => a-b))] 解决方法2123[...new Set(arr.toString().split(',').map(Number).sort((a,b) => a-b))]orArrary.froms(new Set(arr.toString().split(',').map(Number).sort((a,b) => a-b))) 解决方法31234567function flatten(arr){ while(arr.some(item => Array.isArray(item))){ arr = [].concat(...arr) } return arr}Array.from(new Set(flatten(arr))).sort((a,b) => a-b) 解决方法4123456Arrary.prototype.flat = function(){ return [].concat[...this.map(item => (Array.isArray(item) ? item.flat() : [item]))]}Arrary.prototype.unique = function() { return [...new Set(this)]} 补充: 12345678910数组实例的flat()特点:1.falt()默认为“拉平”一层2.如果原数组有空位, flat()方法会跳过空位flatMap方法:介绍:对原方法中的每个成员执行一个函数,然后对返回值的数组执行flat()方法。该方法返回一个新数组,不改变原数组// 相当于 [[2, 4], [3, 6], [4, 8]].flat()[2, 3, 4].flatMap((x) => [x, x * 2])// [2, 4, 3, 6, 4, 8]","categories":[{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"}],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}]},{"title":"vue中使用cytoscape绘制拓扑图方案","slug":"vue中使用cytoscape绘制拓扑图方案","date":"2019-03-08T15:36:50.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/03/08/vue中使用cytoscape绘制拓扑图方案/","link":"","permalink":"https://over58.github.io/2019/03/08/vue%E4%B8%AD%E4%BD%BF%E7%94%A8cytoscape%E7%BB%98%E5%88%B6%E6%8B%93%E6%89%91%E5%9B%BE%E6%96%B9%E6%A1%88/","excerpt":"有一个需求需要绘制拓扑图,然后选图表库选择了cytoscape,看了官方文档http://js.cytoscape.org,感觉和network.js很像,包括两种element,一种是node(描述其相关信息),另一种是 edge(描述node之间的关系, 通过指明source, target确定指向),其余的都是一些样式配置和事件的监听,可以说是非常的清晰明了了。此外文档的Extensions部分提供了各种的UI插件和API插件,拓展性也不错。 数据结构1234567891011121314151617181920212223242526nodes: [ { "id": "a",//required "name": "demo",//optional // ... }, { "id": "b",//required "name": "demo",//optional }]edges: [ //描述了 a-->b { "id": "aadsfasdf", //optional "source": "a", //required, source-node-id "target": "b", //required target-node-id }, //描述了 a-->a,会出现一个指向自己的圆,不过想要实现这一点需要cytoscape-edgehandles插件 { "id": "aadsfasdf", //optional "source": "a", //required, source-node-id "target": "a", //required target-node-id }]","text":"有一个需求需要绘制拓扑图,然后选图表库选择了cytoscape,看了官方文档http://js.cytoscape.org,感觉和network.js很像,包括两种element,一种是node(描述其相关信息),另一种是 edge(描述node之间的关系, 通过指明source, target确定指向),其余的都是一些样式配置和事件的监听,可以说是非常的清晰明了了。此外文档的Extensions部分提供了各种的UI插件和API插件,拓展性也不错。 数据结构1234567891011121314151617181920212223242526nodes: [ { "id": "a",//required "name": "demo",//optional // ... }, { "id": "b",//required "name": "demo",//optional }]edges: [ //描述了 a-->b { "id": "aadsfasdf", //optional "source": "a", //required, source-node-id "target": "b", //required target-node-id }, //描述了 a-->a,会出现一个指向自己的圆,不过想要实现这一点需要cytoscape-edgehandles插件 { "id": "aadsfasdf", //optional "source": "a", //required, source-node-id "target": "a", //required target-node-id }] 实际栗子123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218<template> <div id="cy"></div></template><script>import cytoscape from 'cytoscape'<!--为edge添加事件和二维的布局 -->import edgehandles from 'cytoscape-edgehandles'<!-- 提供类似于tooltip的提示框 -->import popper from 'cytoscape-popper'import tippy from 'tippy.js'<!-- 引入data -->import data from './data.js'<!-- 使用插件 -->cytoscape.use(edgehandles)cytoscape.use(popper)<!-- 可以使用自己选的配色 -->let colors = ['#FFFFCC', '#CCFFFF', '#FFCCCC', '#FFFF99', '#CCCCFF', '#FF9966', '#FF6666', '#FFCC99', '#CCFF99', '#CCCCCC', '#CCFFCC', '#99CC99', '#99CCCC']let colors1 = ['#FF6666', '#006699', '#FF9966', '#0066CC', '#339933', '#FFCC33', '#FF9900', '#FFFFCC', '#CC6600', '#CCCC44', '#99CC33', '#0099CC', '#99CCCC', '#FF0033', '#333399', '#CCCC00', '#33CC99', '#FFFF00', '#336699']let colors2 = ['#CCFF99', '#99CCFF', '#99CCCC', '#CCFFCC', '#66CCCC', '#CCCCFF', '#FFFFCC', '#CCFFFF', '#66CCFF', '#6699CC']let vm = nullexport default { props: { }, components: { }, data () { return { tippyInstance: null } }, computed: { }, watch: { }, methods: { draw () { let nodes = data.nodes let edges = data.edges nodes.map((x, i) => { x.data.color = colors[i % 13] // x.data.color = colors1[i % 19] x.data.color = colors2[i % 10] return x }) let cy = cytoscape({ container: document.getElementById('cy'), layout: { name: 'grid', concentric: function (n) { return n.id() === 'j' ? 200 : 0 }, levelWidth: function (nodes) { return 100 }, minNodeSpacing: 100 }, style: [ { selector: 'node', style: { 'content': 'data(name)', 'width': 'mapData(size, 0, 1.0, 40, 60)', 'height': 'mapData(size, 0, 1.0, 40, 60)', 'background-color': 'data(color)' } }, { selector: 'edge', style: { 'curve-style': 'unbundled-bezier', 'target-arrow-shape': 'triangle', 'target-arrow-color': 'data(colour)', 'line-color': 'data(colour)', 'width': 'mapData(width, 0, 1.0, 1, 3)', // 'label': 'data(info)' 'control-point-distances': [40, -40], 'control-point-weights': [0.25, 0.75] } }, // some style for the extension { selector: '.eh-handle', style: { 'background-color': 'red', 'width': 12, 'height': 12, 'shape': 'ellipse', 'overlay-opacity': 0, 'border-width': 12, // makes the handle easier to hit 'border-opacity': 0 } }, { selector: '.eh-hover', style: { 'background-color': 'red' } }, { selector: '.eh-source', style: { 'border-width': 2, 'border-color': 'red' } }, { selector: '.eh-target', style: { 'border-width': 2, 'border-color': 'red' } }, { selector: '.eh-preview, .eh-ghost-edge', style: { 'background-color': 'red', 'line-color': 'red', 'target-arrow-color': 'red', 'source-arrow-color': 'red' } }, { selector: '.eh-ghost-edge.eh-preview-active', style: { 'opacity': 0 } }, { selector: '.edge-out-highlight', style: { 'line-color': 'black', 'target-arrow-color': 'black', width: 3 } }, { selector: '.edge-in-highlight', style: { 'line-color': 'purple', 'target-arrow-color': 'purple', width: 3 } } ], elements: { nodes: nodes, edges: edges } }) // edge添加事件 cy.on('tap', 'edge', function (evt) { var node = evt.target if (vm.tippyInstance) { vm.tippyInstance.hide() vm.tippyInstance.destroy() } vm.makeTippy(node) vm.tippyInstance.show() }) // node 添加事件 cy.on('tap', 'edge', function (evt) { var node = evt.target if (vm.tippyInstance) { vm.tippyInstance.hide() vm.tippyInstance.destroy() } vm.makeTippy(node) vm.tippyInstance.show() }) cy.on('tap', 'node', function (evt) { var node = evt.target node.incomers('edge').toggleClass('edge-in-highlight') node.edgesWith('*').toggleClass('edge-out-highlight') }) }, makeTippy (node) { <!-- 点击edge出现一个弹框,显示一些额外的信息 --> this.tippyInstance = tippy(node.popperRef(), { content: function () { var div = document.createElement('div') div.innerHTML = `<p style="text-align:left;padding-top:8px;">${node._private.data.info}</p>` return div }, trigger: 'manual', arrow: true, placement: 'bottom', hideOnClick: false, multiple: true, sticky: true }) } }, created () { vm = this }, mounted () { this.$nextTick(() => { this.draw() }) }}</script> 效果图","categories":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/categories/graph/"}],"tags":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/tags/graph/"},{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}]},{"title":"cytoscape的简单使用","slug":"cytoscape的简单使用","date":"2019-03-07T14:29:42.000Z","updated":"2021-02-26T06:07:21.770Z","comments":true,"path":"2019/03/07/cytoscape的简单使用/","link":"","permalink":"https://over58.github.io/2019/03/07/cytoscape%E7%9A%84%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/","excerpt":"","text":"cy实例对象常用操作添加cy.add(eleObj/eleObjs/eles)removecy.remove(elems/selector)获取cy.colleciton 返回一个new empty collection cy.getElementById() or cy.$id() return one element cy.$(selector)、cy.elements(selector) return elements cy.nodes(selector) cy.edges(selector) cy.filter(selector) return elements cy.filter(function(ele, i, eles)) - ele The current element under consideration for filtering. - i The counter used for iteration over the elements in the graph. - eles The collection of elements being filtered ps: cy.nodes(‘[weight > 50]’); 批量修改(能够有效的减少渲染成本)cy.batch() cy.satrtBatch() cy.endBatch() demo如下 12345678910cy.startBatch();<!-- 多次的样式修改操作 -->cy.$('#j') .data('weight', '70') .addClass('funny') .removeClass('serious')cy.endBatch();能够减少中间的redraw的成本,和jquery中将一系列修改样式的操作合并到一个类中,对这个类进行操作一个道理。 createcy.mount() cy.unmount()销毁cy.destroy() 有利于gc临时数据操作cy.scratch([namespace], [value]) cy.removeScratch()全局函数: cytoscape在调试过程中在console中打印一些错 cytoscape.warnings(false) 禁止报错 cytoscape.warnings(true) 开启报错 cytoscape.warnings() 得到当前状态 collection的一些操作","categories":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/categories/graph/"}],"tags":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/tags/graph/"},{"name":"cytoscape","slug":"cytoscape","permalink":"https://over58.github.io/tags/cytoscape/"}]},{"title":"post和get的区别","slug":"post和get的区别","date":"2019-03-05T17:02:27.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/05/post和get的区别/","link":"","permalink":"https://over58.github.io/2019/03/05/post%E5%92%8Cget%E7%9A%84%E5%8C%BA%E5%88%AB/","excerpt":"","text":"介绍首先, GET、POST 都是htt请求的的方法。它们本质上并无差别。HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的 。 问题那么, 问题来了。1、”GET请求在URL中传送的参数是有长度限制的,而POST没有”是什么鬼?。 答: url长度限制都是浏览器设置的;而GET也可以在request body中传递参数,只不过不同的服务器对这些数据的处理方式不同,有些接受,有些忽略。 2、GET和POST还有一个重大区别,简单的说:GET产生一个TCP数据包;POST产生两个TCP数据包。长的说:对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。 答: 1. GET与POST都有自己的语义,不能随便混用。 2. 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。 3. 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。","categories":[],"tags":[{"name":"http","slug":"http","permalink":"https://over58.github.io/tags/http/"}]},{"title":"webhook-demo","slug":"webhook-demo","date":"2019-03-05T09:46:15.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/03/05/webhook-demo/","link":"","permalink":"https://over58.github.io/2019/03/05/webhook-demo/","excerpt":"","text":"12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061var http = require('http') , exec = require('exec')const PORT = process.env.PORT || 9988let transporter = nodemailer.createTransport({ service: 'smpt.163.com', host: 'smtp.163.com', secureConnection: true, port: 465, auth: { user: '[email protected]', pass: 'XXXXXXX' }});let defaultOpions = { from: 'yongchao blog <[email protected]>', to: '[email protected]', subject: 'yongchao blog', html: '<b>blog deploy success !</b>'}var deployServer = http.createServer(function(request, response) { if (request.url.search(/deploy\\/?$/i) > 0) { var commands = [ 'make restart' //这是我自定义的重新部署的代码 ].join(' && ') exec(commands, function(err, out, code) { if (err instanceof Error) { response.writeHead(500) response.end('Server Internal Error.') throw err } process.stderr.write(err) process.stdout.write(out) response.writeHead(200) response.end('Deploy Done.') transporter.sendMail(defaultOpions, (err, info) => { if(err) { console.error(err) }else{ console.log(err, info) } }) }) } else { response.writeHead(404) response.end('Not Found.') }})deployServer.listen(PORT, () => { console.log('start service' + PORT)})","categories":[],"tags":[]},{"title":"vue-directive","slug":"vue-directive","date":"2019-03-04T19:34:37.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/03/04/vue-directive/","link":"","permalink":"https://over58.github.io/2019/03/04/vue-directive/","excerpt":"vue-directive 的几个钩子函数 bind 指令第一次被绑定到元素时调用,只调用一次 inserted 被绑定元素插入到父节点时调用 update 被绑定元素所在模版更新时调用,不论绑定值是否变化都调用 componentUpdated 被绑定元素所在模版在完成一次更新周期时调用 unbind 元素解绑时调用,只调用一次","text":"vue-directive 的几个钩子函数 bind 指令第一次被绑定到元素时调用,只调用一次 inserted 被绑定元素插入到父节点时调用 update 被绑定元素所在模版更新时调用,不论绑定值是否变化都调用 componentUpdated 被绑定元素所在模版在完成一次更新周期时调用 unbind 元素解绑时调用,只调用一次 每个钩子函数都有参数: el: 指令绑定的element,用来操作dom binging 一个对象,包含以下属性 name: 指令名,不包含v-前缀 value: 指令的绑定值,例如 v-my-directive=叮+ l”, value的值是,2 oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用.无论value 值是否改变都可用。 expression: 绑定值的字符串形式。例如v-my-directive=”1+ 1”,expression的值是”1+I”. arg: 指令的arguments, 例如 v-my-directive:foo, arg 的值是 foo modifiers: 一个包含修饰符的对象 。 例如 v-my-directive.foo.bar,修饰符对象 modifiers的值是{ foo: true, bar: true } vnode oldVnode: 上一个虚拟节点仅在 update 和 componentUpdated 钩子中可用 。 clickoutsize指令的实现12345678910111213141516171819202122232425262728Vue.directive('clickoutside', { bind: function (el, binding) { function documentHandler (e) { if (binding.arg === 'esc' && e.keyCode === 27) { <!-- v-clickoutside的value 是一个函数,这里相当于执行绑定的函数 --> binding.value(e) } <!-- 如果点击的元素是在绑定了v-clickoutside指令元素的内容,则忽略 --> if (el.contains(e.target)) { return false } if (binding.expression) { binding.value(e) } } <!-- 在元素添加一个元素用来存绑定的函数,是为了unbind的时候能够找到这个函数 --> el.__vueClickOutside__ = documentHandler <!-- 在全局添加一些事件 --> window.addEventListener('keydown', documentHandler) document.addEventListener('click', documentHandler) document.addEventListener('keydown', documentHandler) }, unbind: function (el) { document.removeEventListener('click', el.__vueClickOutside__) delete el.__vueClickOutside__ }})","categories":[],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}]},{"title":"对于echarts图表随着浏览器窗口resize的优化","slug":"对于echarts图表随着浏览器窗口resize的优化","date":"2019-03-04T19:07:32.000Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2019/03/04/对于echarts图表随着浏览器窗口resize的优化/","link":"","permalink":"https://over58.github.io/2019/03/04/%E5%AF%B9%E4%BA%8Eecharts%E5%9B%BE%E8%A1%A8%E9%9A%8F%E7%9D%80%E6%B5%8F%E8%A7%88%E5%99%A8%E7%AA%97%E5%8F%A3resize%E7%9A%84%E4%BC%98%E5%8C%96/","excerpt":"说明: 有时候项目中会显示一些图表,而且width可能并不是固定的(可能100%),那么当浏览器窗口变化的时候,图表的大小应该跟随着变大或变小,称之为resize","text":"说明: 有时候项目中会显示一些图表,而且width可能并不是固定的(可能100%),那么当浏览器窗口变化的时候,图表的大小应该跟随着变大或变小,称之为resize 1234567891011121314151617181920212223242526272829303132333435363738/** * 用来处理每一个有图表的页面添加resize , 离开时移除resize函数 */import echarts from 'echarts'import _ from 'lodash'export default { data () { return { doms: [] } }, computed: { chartResize () { return _.throttle(() => { return this.doms.forEach(dom => { dom && dom.resize() }) }, 400) } }, methods: { initChart () { this.doms.forEach(dom => { dom && echarts.init(dom) }) } }, mounted () { console.log('mixins mounted') this.doms = [this.$refs['charts']] this.initChart() window.addEventListener('resize', this.chartResize) }, destroyed () { console.log('mixins destroyed') window.removeEventListener('resize', this.chartResize) }} ps: 使用的时候在需要图表的页面引入这个mixins","categories":[{"name":"可视化","slug":"可视化","permalink":"https://over58.github.io/categories/%E5%8F%AF%E8%A7%86%E5%8C%96/"}],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"},{"name":"echarts","slug":"echarts","permalink":"https://over58.github.io/tags/echarts/"}]},{"title":"覆盖ui框架css原生样式","slug":"覆盖ui框架css原生样式","date":"2019-03-04T19:03:48.000Z","updated":"2021-02-26T06:07:21.818Z","comments":true,"path":"2019/03/04/覆盖ui框架css原生样式/","link":"","permalink":"https://over58.github.io/2019/03/04/%E8%A6%86%E7%9B%96ui%E6%A1%86%E6%9E%B6css%E5%8E%9F%E7%94%9F%E6%A0%B7%E5%BC%8F/","excerpt":"","text":"加scoped 1234567891011<style scoped>/deep/.rootName .className{ }or .rootName >>> .className{ }</style> 不加scoped原理:在组件中添加了父css类, 在修改的样式作用域限定为父css类,减小css的影响范围 1234<style>.rootName .className{}</style>","categories":[],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}]},{"title":"vue-intro使用方法及注意点","slug":"vue-intro使用方法及注意点","date":"2019-03-04T19:01:39.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/03/04/vue-intro使用方法及注意点/","link":"","permalink":"https://over58.github.io/2019/03/04/vue-intro%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95%E5%8F%8A%E6%B3%A8%E6%84%8F%E7%82%B9/","excerpt":"","text":"1、使用时必须引入intro.js2、let intro=Intro.intro()3、intro.setOptions({}).start().oncomplete().onskip(function)4、intro这个插件只能提示一些静态的页面上已经有的一些元素,异步的或者是后来动态显示的元素无法加上提示.","categories":[],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}]},{"title":"vue中定时器问题","slug":"vue中定时器问题","date":"2019-03-04T18:58:04.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/03/04/vue中定时器问题/","link":"","permalink":"https://over58.github.io/2019/03/04/vue%E4%B8%AD%E5%AE%9A%E6%97%B6%E5%99%A8%E9%97%AE%E9%A2%98/","excerpt":"Vue中使用了定时器后在关闭页面后必须手动清理","text":"Vue中使用了定时器后在关闭页面后必须手动清理 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758方案一data: { tiemr: null },method: { method1 () { this.timer = setInterval(()=> { // logic }, interval) } },beforeDestroy() { clearInterval(this.timer) this.timer = null}方案二:通过$once来监听定时器,在beforeDestroy中可以被清除优点:这两段代码写在一起,不用特意定义一个data.timer,减少了数据监听的成本消耗缺点:适用于只有离开页面关闭定时器的情况,const timer = setInterval(()=>{ // logic}, interval)this.$once('hook:beforeDestroy', ()=>{ clearInterval(timer)})ps: template> <div class="test"> <Button @click="addInterval()">add</Button> <h1>测试</h1> <router-view/> </div></template><script>export default { methods: { addInterval () { const timer = setInterval(() => { console.log('aaa') }, 100) this.$once('hook:beforeDestroy', () => { clearInterval(timer) }) } }}</script>","categories":[],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"}]},{"title":"js的灵活","slug":"js的灵活","date":"2019-03-04T14:53:55.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/04/js的灵活/","link":"","permalink":"https://over58.github.io/2019/03/04/js%E7%9A%84%E7%81%B5%E6%B4%BB/","excerpt":"js是超级灵活的语言step11234567891011121314var checkObj = function () {} checkObj.prototype.checkName = function(){ console.log('checkName')}checkObj.prototype.checkEmail = function(){ console.log('checkEmail')}checkObj.prototype.checkPassword = function(){ console.log('checkPassword')}var a = new checkObj()a.checkName()a.checkEmail()a.checkPassword()","text":"js是超级灵活的语言step11234567891011121314var checkObj = function () {} checkObj.prototype.checkName = function(){ console.log('checkName')}checkObj.prototype.checkEmail = function(){ console.log('checkEmail')}checkObj.prototype.checkPassword = function(){ console.log('checkPassword')}var a = new checkObj()a.checkName()a.checkEmail()a.checkPassword() step212345678910111213141516var checkObj = function () {}checkObj.prototype = { checkName:function(){ console.log('checkName') }, checkEmail: function(){ console.log('checkEmail') }, checkPassword: function(){ console.log('checkPassword') }}var a = new checkObj()a.checkName()a.checkEmail()a.checkPassword() step3 链式调用1234567891011121314151617var checkObj = function () {} checkObj.prototype = { checkName:function(){ console.log('checkName') return this }, checkEmail: function(){ console.log('checkEmail') return this }, checkPassword: function(){ console.log('checkPassword') return this }}var a = new checkObj()a.checkName().checkEmail().checkPassword()","categories":[],"tags":[]},{"title":"js的封装和继承","slug":"js原型","date":"2019-03-04T14:20:07.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/04/js原型/","link":"","permalink":"https://over58.github.io/2019/03/04/js%E5%8E%9F%E5%9E%8B/","excerpt":"### 简单介绍谈到JAVASCRIPT的继承,只有一种结构:对象。每个对象都有一个私有属性proto 指向它的原型对象(prototype) . 原型对象也有自己的proto ,层层向上直到一个对象的原型对象为NULL.根据定义,NULL是没有原型的,并作为这个原型链的最后一个环节。 !!! 实例对象的proto 等于该对象构造函数的prototypeps: 123var obj = {}obj.__proto__ === Object.prototype","text":"### 简单介绍谈到JAVASCRIPT的继承,只有一种结构:对象。每个对象都有一个私有属性proto 指向它的原型对象(prototype) . 原型对象也有自己的proto ,层层向上直到一个对象的原型对象为NULL.根据定义,NULL是没有原型的,并作为这个原型链的最后一个环节。 !!! 实例对象的proto 等于该对象构造函数的prototypeps: 123var obj = {}obj.__proto__ === Object.prototype 封装12345678910111213141516171819202122232425var Book = function (id,name, price) { // 私有属性 var num = 1 // 私有函数 function checkId(){ } // 公有属性 this.id = id this.name = name this.price = price // 公有函数 this.getName=function(){} this.getPrice = function(){} this.setName = function (name){this.name = name} this.setPrice = function(price){this.price = price} this.getNum = function(){return num}}//类静态公有属性(对象不能访问)Book.isChinese = true//类静态公有方法(对象不能访问)Book.resetTime = function(){}var book = new Book('adf2323','js设计模式', 232)console.log(book.name, book.price, book.id)console.log(book.getNum()) ### New的作用 1234567Function Book(id, name , price) { Var this = {} This.name = name This.id = id This.price = price Return this} 类的原型对象的作用继承通过将父类的实例赋值给子类的原型对象。 类的原型对象的作用就是为类的原型添加共有方法,但是类并不能直接访问这些函数和方法。当我实例化的时候,新创建的对象复制了父类的构造函数内的属性与方法并且将原型__proto__ 指向父类的原型对象,这样就拥有了父类的原型对象上的属性和方法,并且这个新创建的对象可以访问到父类原型对象上到的属性和方法常见的继承的几种方式类式继承12345678910111213141516171819//声明父类function SuperClass(){}// 为父类添加共有方法SuperClass.prototype.getSuperValue=function(){}//声明子类function SubClass(){ this.subValue = false}//继承SubClass.prototype = new SuperClass();// 为子类添加共有方法SubClass.prototype.getSubValue = function (){ return this.subValue}缺点:由于子类通过其原型prototype 对父类进行实例化,继承了父类。所以说父类中的共有属性如果是引用类型,就会被子类中的所有实例共用,存在被修改的可能。 构造函数式继承123456789101112131415function SuperClass(id){ this.books = ['js', 'html', 'css'] this.id = id}SuperClass.prototype.getSuperValue = function () { console.log('getSuperValue')}function SubClass(id){ // 继承父类 SuperClass.call(this, id)}缺点:只继承了父类构造函数中的公有属性。如果一个函数后者变量想要被继承么,就必须放在父类构造函数中,这样创建的每个实例都会单独拥有一份而不能共用,这样违背了代码复用的原则。为了综合这两种模式的有点,后来有了组合式继承。 组合式继承123456789101112131415161718192021function SuperClass(id){ // 将公有引用类型放在构造函数中 this.books = ['js', 'html', 'css'] this.id = id}SuperClass.prototype.getSuperValue = function () { console.log('getSuperValue')}function SubClass(id){ // 继承父类中构造函数的属性和方法 SuperClass.call(this, id)}SubClass.prototype = new SuperClass()SubClass.prototype.getTime = function(){ }缺点: SuperClass.call(this, id)执行了一次父类构造函数new SuperClass()又执行了一次父类构造函数 寄生组合式继承1234567891011121314151617181920function SuperClass(id){ // 将公有引用类型放在构造函数中 this.books = ['js', 'html', 'css'] this.id = id}SuperClass.prototype.getSuperValue = function () { console.log('getSuperValue')}function SubClass(id){ // 继承父类中构造函数的属性和方法 SuperClass.call(this, id)}function inherit(subClass, superClass){ //定义了一个没有自有属性的对象 var F = function(){} F.prototype = superClass.prototype subClass.prototype = new F() // 干净的继承了父类的公有属性} 补充:inherit函数1234567891011function inherit(p){ if(p === null) throw TypeError if(Object.create){ return Object.create(p) } let t = typeof p if(t !== "object" || t!== "function") throw TypeError function F(){} F.prototype = p return new F();}","categories":[],"tags":[{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"}]},{"title":"nodejs发送邮件","slug":"nodejs发送邮件","date":"2019-03-04T10:44:17.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/04/nodejs发送邮件/","link":"","permalink":"https://over58.github.io/2019/03/04/nodejs%E5%8F%91%E9%80%81%E9%82%AE%E4%BB%B6/","excerpt":"使用nodemailer包实现发送邮件","text":"使用nodemailer包实现发送邮件 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152var express = require('express')const nodemailer = require('nodemailer')const app = express()const PORT = process.env.PORT || 3000// 配置163邮箱let transporter = nodemailer.createTransport({ service: 'smpt.163.com', host: 'smtp.163.com', secureConnection: true, port: 465, auth: { user: '[email protected]', pass: 'XXXX' }});// 配置gmail邮箱// let transporter = nodeMailer.createTransport('SMTP',{// service: 'gmail',// host: 'smtp.gmail.com',// secure: true,// port:465,// auth: {// //邮箱// user: '[email protected]',// //登入密码// pass: 'XXXXXXX',// } // });let defaultOpions = { from: '[email protected]', to: '[email protected]', subject: '主题', text: '内容', html: '<b>内容</b>'}app.get('/', (req, res) => { transporter.sendMail(defaultOpions, (err, info) => { if(err) { console.error(err) }else{ console.log(err, info) } res.send() })})app.listen(PORT, () => { console.log('start service')})","categories":[],"tags":[{"name":"node","slug":"node","permalink":"https://over58.github.io/tags/node/"}]},{"title":"git常用知识点","slug":"git常用知识点","date":"2019-03-03T21:43:19.000Z","updated":"2021-02-26T06:07:21.770Z","comments":true,"path":"2019/03/03/git常用知识点/","link":"","permalink":"https://over58.github.io/2019/03/03/git%E5%B8%B8%E7%94%A8%E7%9F%A5%E8%AF%86%E7%82%B9/","excerpt":"常用的几个命令12345678910git addgit commitgit statusgit diff git branchgit remotegit pull git push git resetgit tag","text":"常用的几个命令12345678910git addgit commitgit statusgit diff git branchgit remotegit pull git push git resetgit tag 常见的场景版本需要回退到旧版本123456789101、git reset git reset --hard "目标版本commmit-id" git push origin master -f 暴力,不建议 适用场景: 如果想恢复到之前某个提交的版本,且那个版本之后提交的版本我们都不要了,就可以用这种方法。2、git revert git revert的作用通过反做创建一个新的版本,这个版本的内容与我们要回退到的目标版本一样,但是HEAD指针是指向这个新生成的版本,而不是目标版本。 git revert -n 版本号 git commit -m 版本名 适用场景: 如果我们想恢复之前的某一版本(该版本不是merge类型),但是又想保留该目标版本后面的版本,记录下这整个版本变动流程,就可以用这种方法。 拉取远程分支并创建本地分支1231、git checkout -b newBranch origin/remoteBranch2、git fetch origin remoteBranch:newBranch3、git checkout -b newBranch --trace origin/remoteBranch 修改上一次commit的信息,未push到远程分支1git commit -m 'message' --amend 忽略对某个文件或者文件夹的的修改将文件或者文件夹的名字添加到.gitignore文件","categories":[],"tags":[{"name":"git","slug":"git","permalink":"https://over58.github.io/tags/git/"}]},{"title":"npm常用知识点","slug":"npm常用知识点","date":"2019-03-03T14:26:47.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/03/npm常用知识点/","link":"","permalink":"https://over58.github.io/2019/03/03/npm%E5%B8%B8%E7%94%A8%E7%9F%A5%E8%AF%86%E7%82%B9/","excerpt":"更新到最新正式版本1npm install npm@latest -g 更新到未来将会释放的版本1npm install npm@next -g 列出某个包的所有历史banbe1npm view <package> versions 初始化1npm init or npm init --yes(直接使用默认的设置)","text":"更新到最新正式版本1npm install npm@latest -g 更新到未来将会释放的版本1npm install npm@next -g 列出某个包的所有历史banbe1npm view <package> versions 初始化1npm init or npm init --yes(直接使用默认的设置) 安装包1npm install package or npm i package 安装包之 –save1npm install package --save 安装包并将包的信息写入package.json中的dependencies 安装包之 –save-dev1npm install package --save-dev 安装包并将包的信息写入package.json中的devDependencies 安装包之 指定版本1npm install package@version 全局包12345安装:npm install package -g查看那些全局包过期: npm outdated -g --depth=0更新某个全局包:npm update package -g更新所有的全局包: npm update -g卸载全局包: npm uninstall package -g semver package的版本问题 semver的格式: 主版本号.次版本号.修订号 range12345< <= > >== Advanceed Range Syntax 1.2.3 - 2.3.4 := >=1.2.3 <=2.3.4 1.2.3 - 2.3 := >=1.2.3 <2.4.0 1.2.3 - 2 := >=1.2.3 <3.0.0 X-Ranges := >=0.0.0 (Any version satisfies) 1.x := >=1.0.0 <2.0.0 (Matching major version) 1.2.x := >=1.2.0 <1.3.0 (Matching major and minor versions)","categories":[],"tags":[{"name":"npm","slug":"npm","permalink":"https://over58.github.io/tags/npm/"}]},{"title":"bfc/float","slug":"bfc-float","date":"2019-03-02T17:17:26.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/03/02/bfc-float/","link":"","permalink":"https://over58.github.io/2019/03/02/bfc-float/","excerpt":"block format context 特点是内部子元素绝不会影响外部的元素如何触发一个盒子的bfc ? 1、body 根元素 2、浮动元素:float 除 none 以外的值 3、绝对定位元素:position (absolute、fixed) 4、display 为 inline-block、table-cells、flex 5、overflow 除了 visible 以外的值 (hidden、auto、scroll)","text":"block format context 特点是内部子元素绝不会影响外部的元素如何触发一个盒子的bfc ? 1、body 根元素 2、浮动元素:float 除 none 以外的值 3、绝对定位元素:position (absolute、fixed) 4、display 为 inline-block、table-cells、flex 5、overflow 除了 visible 以外的值 (hidden、auto、scroll) 浮动元素产生了浮动流 所有产生了浮动流的元素,块级元素看不到他们,产生了bfc的元素和文本类属性(inline)的元素以及文本都能看到浮动元素。 ==ps==: 能清除浮动的只有块级元素 BFC有以下用途, BFC 特性及应用1. 上线外边距发生折叠123456789101112<head> div{ width: 100px; height: 100px; background: lightblue; margin: 100px; }</head><body> <div></div> <div></div></body> 从效果上看,因为两个 div 元素都处于同一个 BFC 容器下 (这里指 body 元素) 。 所以第一个 div 的下边距和第二个 div 的上边距发生了重叠,所以两个盒子之间距离只有 100px,而不是 200px。 首先这不是 CSS 的 bug,我们可以理解为一种规范,如果想要避免外边距的重叠,可以将其放在不同的 BFC 容器中。1234567891011121314151617<div class="container"> <p></p></div><div class="container"> <p></p></div>.container { overflow: hidden;}p { width: 100px; height: 100px; background: lightblue; margin: 100px;}这时候,两个盒子边距就变成了 200px 2. BFC 可以包含浮动的元素我们都知道,浮动的元素会脱离普通文档流 由于容器内元素浮动,脱离了文档流,所以容器只剩下 2px 的边距高度。如果使触发容器的 BFC,那么容器将会包裹着浮动元素。 3. BFC 可以阻止元素被浮动元素覆盖先来看一个文字环绕效果: 我是一个左浮动的元素 我是一个没有设置浮动, 也没有触发 BFC 元素, width: 200px; height:200px; background: #eee; 这时候其实第二个元素有部分被浮动元素所覆盖,(但是文本信息不会被浮动元素所覆盖) 如果想避免元素被覆盖,可触第二个元素的 BFC 特性,在第二个元素中加入 overflow: hidden 这个方法可以用来实现两列自适应布局,效果不错,这时候左边的宽度固定,右边的内容自适应宽度(去掉上面右边内容的宽度)。","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}]},{"title":"overflow-x/overflow-y一个为visible,一个为非visible时候的怪异行为","slug":"overflow-x-overflow-y一个为visible-一个为非visible时候的怪异行为","date":"2019-03-02T17:12:50.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/02/overflow-x-overflow-y一个为visible-一个为非visible时候的怪异行为/","link":"","permalink":"https://over58.github.io/2019/03/02/overflow-x-overflow-y%E4%B8%80%E4%B8%AA%E4%B8%BAvisible-%E4%B8%80%E4%B8%AA%E4%B8%BA%E9%9D%9Evisible%E6%97%B6%E5%80%99%E7%9A%84%E6%80%AA%E5%BC%82%E8%A1%8C%E4%B8%BA/","excerpt":"","text":"MDN的官方解释: 1The computed values of ‘overflow-x’ and ‘overflow-y’ are the same as their specified values, except that some combinations with ‘visible’ are not possible: if one is specified as ‘visible’ and the other is ‘scroll’ or ‘auto’, then ‘visible’ is set to ‘auto’. The computed value of ‘overflow’ is equal to the computed value of ‘overflow-x’ if ‘overflow-y’ is the same; otherwise it is the pair of computed values of ‘overflow-x’ and ‘overflow-y’. overflow-x 和overflow-y的计算值和它们指定的值是相同的,除了某些与visible组合的是不可能的。 如果一个为visible, 另一个被指定为scroll、 auto 、hidden(实际测试所得) ,那么visible 将被设置为auto 如果overflow的指定值和overflow-x相等的话(如果overflow-y相同的话) 其余情况,overflow-x 和overflow-y的值和指定的值相同","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}]},{"title":"js/jquery判断是否包含类","slug":"js-jquery判断是否包含类","date":"2019-03-02T17:09:29.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/03/02/js-jquery判断是否包含类/","link":"","permalink":"https://over58.github.io/2019/03/02/js-jquery%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E5%8C%85%E5%90%AB%E7%B1%BB/","excerpt":"","text":"123456789新版本jQuery hasClass("className") is(".className")原生js element.classList.contains(className)旧版jQuery: hasClass(".className")","categories":[],"tags":[{"name":"jquery","slug":"jquery","permalink":"https://over58.github.io/tags/jquery/"}]},{"title":"双层边框样式","slug":"双层边框样式","date":"2019-02-28T16:00:13.000Z","updated":"2021-02-26T06:07:21.802Z","comments":true,"path":"2019/02/28/双层边框样式/","link":"","permalink":"https://over58.github.io/2019/02/28/%E5%8F%8C%E5%B1%82%E8%BE%B9%E6%A1%86%E6%A0%B7%E5%BC%8F/","excerpt":"","text":"outline用于创建两个边框的时候好处: outline的样式是可变的,缺点: 是border如果是圆角,outline仍然是矩形,经测试,outline只对right bottom占据空间,对left top不产生影响 box-shadow可以用于产生多个边框好处: border是什么样,投影就是什么样,完美贴合缺点: 指定描绘实线阴影,不能改变阴影的样式color inset box-shadow: h-shadow(水平偏移) v-shadow(垂直偏移) blur(模糊距离) spread(阴影尺寸) color inset(边框类型) 1234567891011121314.box{ height:50px; width: 100px; background: #666; border: solid 5px yellowgreen; border-radius: 10px; margin: 30px;}.outline-box{ outline: red 10px solid;}.shadow-box{ box-shadow: 0 0 0 10px red;","categories":[],"tags":[]},{"title":"button样式的实现","slug":"button样式的实现","date":"2019-02-28T15:57:49.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/02/28/button样式的实现/","link":"","permalink":"https://over58.github.io/2019/02/28/button%E6%A0%B7%E5%BC%8F%E7%9A%84%E5%AE%9E%E7%8E%B0/","excerpt":"","text":"使用stylus 编写工具函数: 1234567vendor(prop,value) -webkit-{prop} value -moz-{prop} value {prop} valueborder-radius(n = 5px) vendor(border-radius,n) 1234567891011121314.btn padding: 0 40px border:none height:40px border-radius(5px) background-color: #5df cursor pointer transition: all .5s ease &:hover background-color #666 color #fff transition: all .5s ease &:focus outline: none 12345678910.btn-3d position relative top 0 box-shadow: 0 7px 0 rgba(0,0,0,.2), 0 8px 3px #333 transition: all .15s ease &:active position relative top 5px box-shadow: 0 2px 0 rgba(0,0,0,.2), 0 3px 3px #333 transition: all .3s ease 12345678910.btn-glowing animation: glowing 3s infinite;@keyframes glowing from box-shadow: 0 0 0 rgba(44, 154, 219, 0.3) 50% box-shadow: 0 0 20px rgba(44, 154, 219, 0.8) to box-shadow: 0 0 0 rgba(44, 154, 219, 0.3) 按钮菜单 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647.btn-dropdown position relative overflow visible display inline-block &:hover,&:active .btn-dropdown-list display inline-block.btn-dropdown-list display none position absolute top 100% left 0 margin 0 padding 0 z-index 1000 min-width 100% list-style-type: none background: rgba(255, 255, 255, 0.95) border-style: solid border-width: 1px border-color: #d4d4d4 font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif -webkit-box-shadow: 0 2px 7px rgba(0, 0, 0, 0.2) box-shadow: 0 2px 7px rgba(0, 0, 0, 0.2) border-radius: 3px -webkit-box-sizing: border-box -moz-box-sizing: border-box box-sizing: border-box &:hover display inline-block.btn-dropdown-list>li padding: 0 margin: 0 display: block.btn-dropdown-list>li>a display: block line-height: 40px font-size: 12.8px padding: 5px 10px float: none color: #666 text-decoration: none &:hover color: #5e5e5e background: #f6f6f6 text-decoration: none 12345678910111213141516171819.btn-group position relative display inline-block &:after content: '' display block clear both.btn-group .btn border-radius: 0 float: left border: solid 1px #333 &:first-child border-top-left-radius: 5px border-bottom-left-radius: 5px &:last-child border-top-right-radius: 5px border-bottom-right-radius: 5px &:not(:last-child) border-right none 123456789.btn-raised border-color: #e1e1e1 border-style: solid border-width: 1px line-height: 38px background: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e1e1e1)) background: linear-gradient(#333, #e1e1e1) -webkit-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0 1px 2px rgba(0, 0, 0, 0.15) box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0 1px 2px rgba(0, 0, 0, 0.15) 123456789.btn-wrap display inline-block padding 9px border-radius 200px border solid 1px #e3e3e3 background linear-gradient(#f2f2f2,#fff) box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.04) & .btn border-radius: 200px","categories":[],"tags":[]},{"title":"css实现单行、多行文本溢出","slug":"css实现单行、多行文本溢出","date":"2019-02-28T15:09:23.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/02/28/css实现单行、多行文本溢出/","link":"","permalink":"https://over58.github.io/2019/02/28/css%E5%AE%9E%E7%8E%B0%E5%8D%95%E8%A1%8C%E3%80%81%E5%A4%9A%E8%A1%8C%E6%96%87%E6%9C%AC%E6%BA%A2%E5%87%BA/","excerpt":"","text":"### 单行: 123overflow: hidden;text-overflow:ellipsis;white-space: nowrap; ### 多行: 1234display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 3;overflow: hidden;","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}]},{"title":"timeline样式实现","slug":"timeline样式实现","date":"2019-02-28T14:59:51.000Z","updated":"2021-02-26T06:07:21.774Z","comments":true,"path":"2019/02/28/timeline样式实现/","link":"","permalink":"https://over58.github.io/2019/02/28/timeline%E6%A0%B7%E5%BC%8F%E5%AE%9E%E7%8E%B0/","excerpt":"","text":"12345678910111213141516171819202122232425262728293031323334<ul> <li> <div class="head"> <span class="title">标题1</span> <span class="extra">2019/01/03</span> </div> <div class="content">内容内容内容内容内容内容内容内容内容内容内容内容内容</div> </li> <li> <div class="head"> <span class="title">标题2</span> <span class="extra">2019/01/04</span> </div> <div class="content"> 内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内 </li></ul>.head{ overflow:hidden; height:25px; line-height:25px; padding:0 5px;}.extra{ float:right;}.content{ border-left:solid 3px #333; padding-left:5px;}","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"},{"name":"components","slug":"components","permalink":"https://over58.github.io/tags/components/"}]},{"title":"如何搭建hexo博客","slug":"如何搭建hexo博客","date":"2019-02-28T09:48:34.000Z","updated":"2021-02-26T06:07:21.810Z","comments":true,"path":"2019/02/28/如何搭建hexo博客/","link":"","permalink":"https://over58.github.io/2019/02/28/%E5%A6%82%E4%BD%95%E6%90%AD%E5%BB%BAhexo%E5%8D%9A%E5%AE%A2/","excerpt":"","text":"准备开发环境(默认已安装好node、git)1npm install -g hexo-cli 创建项目123mkdir blogcd blognpm install hexo-autoprefixer hexo-generator-feed hexo-generator-json-content hexo-generator-search hexo-helper-qrcode hexo-related-popular-posts hexo-renderer-less hexo-renderer-marked --save 此时是如下目录结构 .├── _config.yml├── package.json├── scaffolds├── source| ├── _drafts| └── _posts└── themes 更换配置更改项目根目录下的_config.ymal中的url、author、keywords等参数","categories":[{"name":"其他","slug":"其他","permalink":"https://over58.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"hexo","slug":"hexo","permalink":"https://over58.github.io/tags/hexo/"},{"name":"blog","slug":"blog","permalink":"https://over58.github.io/tags/blog/"}]},{"title":"ubuntu16.04更新源","slug":"ubuntu16-04更新源","date":"2019-02-27T17:48:39.000Z","updated":"2021-02-26T06:07:21.786Z","comments":true,"path":"2019/02/27/ubuntu16-04更新源/","link":"","permalink":"https://over58.github.io/2019/02/27/ubuntu16-04%E6%9B%B4%E6%96%B0%E6%BA%90/","excerpt":"1.备份source.list1cp /etc/apt/source.list /etc/apt/source.list.bak 2.打开source.list文件,删除全部内容1vim /etc/apt/sources.list 复制章节3中的源内容到sources.list文件并保存 3.国内主流的更新源","text":"1.备份source.list1cp /etc/apt/source.list /etc/apt/source.list.bak 2.打开source.list文件,删除全部内容1vim /etc/apt/sources.list 复制章节3中的源内容到sources.list文件并保存 3.国内主流的更新源 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374#sohu shangdongdeb http://mirrors.sohu.com/ubuntu/ trusty main restricted universe multiversedeb http://mirrors.sohu.com/ubuntu/ trusty-security main restricted universe multiversedeb http://mirrors.sohu.com/ubuntu/ trusty-updates main restricted universe multiversedeb http://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted universe multiversedeb http://mirrors.sohu.com/ubuntu/ trusty-backports main restricted universe multiversedeb-src http://mirrors.sohu.com/ubuntu/ trusty main restricted universe multiversedeb-src http://mirrors.sohu.com/ubuntu/ trusty-security main restricted universe multiversedeb-src http://mirrors.sohu.com/ubuntu/ trusty-updates main restricted universe multiversedeb-src http://mirrors.sohu.com/ubuntu/ trusty-proposed main restricted universe multiversedeb-src http://mirrors.sohu.com/ubuntu/ trusty-backports main restricted universe multiverse#163 guangdongdeb http://mirrors.163.com/ubuntu/ trusty main restricted universe multiversedeb http://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiversedeb http://mirrors.163.com/ubuntu/ trusty-updates main restricted universe multiversedeb http://mirrors.163.com/ubuntu/ trusty-proposed main restricted universe multiversedeb http://mirrors.163.com/ubuntu/ trusty-backports main restricted universe multiversedeb-src http://mirrors.163.com/ubuntu/ trusty main restricted universe multiversedeb-src http://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiversedeb-src http://mirrors.163.com/ubuntu/ trusty-updates main restricted universe multiversedeb-src http://mirrors.163.com/ubuntu/ trusty-proposed main restricted universe multiversedeb-src http://mirrors.163.com/ubuntu/ trusty-backports main restricted universe multiverse#aliyundeb-src http://archive.ubuntu.com/ubuntu xenial main restricteddeb http://mirrors.aliyun.com/ubuntu/ xenial main restricteddeb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universedeb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricteddeb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universedeb http://mirrors.aliyun.com/ubuntu/ xenial universedeb http://mirrors.aliyun.com/ubuntu/ xenial-updates universedeb http://mirrors.aliyun.com/ubuntu/ xenial multiversedeb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiversedeb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiversedeb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiversedeb http://archive.canonical.com/ubuntu xenial partnerdeb-src http://archive.canonical.com/ubuntu xenial partnerdeb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricteddeb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universedeb http://mirrors.aliyun.com/ubuntu/ xenial-security universedeb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse#tsinghua.edudeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial main restricteddeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates main restricteddeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial universedeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates universedeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial multiversedeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates multiversedeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-backports main restricted universe multiversedeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security main restricteddeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security universedeb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security multiverse#neu.edudeb-src http://mirror.neu.edu.cn/ubuntu/ xenial main restricted #Added by software-propertiesdeb http://mirror.neu.edu.cn/ubuntu/ xenial main restricteddeb-src http://mirror.neu.edu.cn/ubuntu/ xenial restricted multiverse universe #Added by software-propertiesdeb http://mirror.neu.edu.cn/ubuntu/ xenial-updates main restricteddeb-src http://mirror.neu.edu.cn/ubuntu/ xenial-updates main restricted multiverse universedeb http://mirror.neu.edu.cn/ubuntu/ xenial universedeb http://mirror.neu.edu.cn/ubuntu/ xenial-updates universedeb http://mirror.neu.edu.cn/ubuntu/ xenial multiversedeb http://mirror.neu.edu.cn/ubuntu/ xenial-updates multiversedeb http://mirror.neu.edu.cn/ubuntu/ xenial-backports main restricted universe multiversedeb-src http://mirror.neu.edu.cn/ubuntu/ xenial-backports main restricted universe multiversedeb http://archive.canonical.com/ubuntu xenial partnerdeb-src http://archive.canonical.com/ubuntu xenial partnerdeb http://mirror.neu.edu.cn/ubuntu/ xenial-security main restricteddeb-src http://mirror.neu.edu.cn/ubuntu/ xenial-security main restricted multiverse universedeb http://mirror.neu.edu.cn/ubuntu/ xenial-security universedeb http://mirror.neu.edu.cn/ubuntu/ xenial-security multiverse 4.执行update命令更新1apt-get update","categories":[],"tags":[{"name":"ubuntu","slug":"ubuntu","permalink":"https://over58.github.io/tags/ubuntu/"}],"author":"徐勇超"},{"title":"css各种情况下的居中","slug":"css各种情况下的居中","date":"2019-02-27T15:39:58.000Z","updated":"2021-02-26T06:07:21.766Z","comments":true,"path":"2019/02/27/css各种情况下的居中/","link":"","permalink":"https://over58.github.io/2019/02/27/css%E5%90%84%E7%A7%8D%E6%83%85%E5%86%B5%E4%B8%8B%E7%9A%84%E5%B1%85%E4%B8%AD/","excerpt":"1.水平居中行内元素只需要把行内元素包裹在一个属性display为block的父层元素中,并且把父层元素添加text-align: center即可。适用元素:文字,链接,及其其它inline或者inline-*类型元素(inline-block,inline-table,inline-flex ) 2.水平居中(多个块状元素居中)1. 如果页面里有多个块状元素需要水平排列居中,可以将元素的display属性设置为inline-block,并且把父元素的text-align属性设置为center即可实现。 2. 使用flex布局解决,父元素定义display:flex;3.垂直居中(单行、多行的元素居中)当一个行内元素,即inline,inline-*类型的元素需要居中的话,可以将它的height和line-height同时设置为父元素的高度即可实现垂直居中效果。 多行元素居中:组合使用display:table-cell和vertical-align:middle属性来定义需要居中的元素的父容器元素生成效果","text":"1.水平居中行内元素只需要把行内元素包裹在一个属性display为block的父层元素中,并且把父层元素添加text-align: center即可。适用元素:文字,链接,及其其它inline或者inline-*类型元素(inline-block,inline-table,inline-flex ) 2.水平居中(多个块状元素居中)1. 如果页面里有多个块状元素需要水平排列居中,可以将元素的display属性设置为inline-block,并且把父元素的text-align属性设置为center即可实现。 2. 使用flex布局解决,父元素定义display:flex;3.垂直居中(单行、多行的元素居中)当一个行内元素,即inline,inline-*类型的元素需要居中的话,可以将它的height和line-height同时设置为父元素的高度即可实现垂直居中效果。 多行元素居中:组合使用display:table-cell和vertical-align:middle属性来定义需要居中的元素的父容器元素生成效果 4.垂直居中(未知块状元素高度)12345.item{ top: 50%; position: absolute; transform: translateY(-50%); /* 这里我们使用css3的transform来达到类似效果 */} 5.水平垂直居中(使用flex布局实现)123456789.parent{ display: flex; justify-content:center; align-items: center; /* 注意这里需要设置高度来查看垂直居中效果 */ background: #AAA; height: 300px;} 6.水平垂直居中(已知高度和宽度的元素解决方案)设置元素定位为absolute,并且设置top, left绝对值为50%,margin-top和margin-left为元素高度一半的负值即可,如下:1234567.item{ position: absolute; top: 50%; left: 50%; margin-top: -75px; margin-left: -75px;} 7.水平垂直居中(未知高度和宽度元素解决方案)使用类似的transform属性来定义,即可实现,如下:123456.item{ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);} 8.水平居中总结 水平居中之inline-block+text-align 兼容性非常好 inline+zoom:1 //兼容IE8以下 child会继承text-align:center,需要对子元素进行reset 水平居中之table+margin table元素宽度为内容宽度 只需要设置child ,IE6 7可以child可以采用table的方式如th tr来实现 水平居中之absolute+transform 脱离文档流 不会对其他元素产生影响 不兼容低版本IE 水平居中之flex+justify-content 123.child{ margin: 0 auto} -不兼容低版本IE","categories":[],"tags":[{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"}],"author":"徐勇超"}],"categories":[{"name":"graph","slug":"graph","permalink":"https://over58.github.io/categories/graph/"},{"name":"js知识库","slug":"js知识库","permalink":"https://over58.github.io/categories/js%E7%9F%A5%E8%AF%86%E5%BA%93/"},{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/categories/webpack/"},{"name":"网络","slug":"网络","permalink":"https://over58.github.io/categories/%E7%BD%91%E7%BB%9C/"},{"name":"vue","slug":"vue","permalink":"https://over58.github.io/categories/vue/"},{"name":"typescript","slug":"typescript","permalink":"https://over58.github.io/categories/typescript/"},{"name":"工程化","slug":"工程化","permalink":"https://over58.github.io/categories/%E5%B7%A5%E7%A8%8B%E5%8C%96/"},{"name":"git","slug":"git","permalink":"https://over58.github.io/categories/git/"},{"name":"electron","slug":"electron","permalink":"https://over58.github.io/categories/electron/"},{"name":"node","slug":"node","permalink":"https://over58.github.io/categories/node/"},{"name":"svg","slug":"svg","permalink":"https://over58.github.io/categories/svg/"},{"name":"style","slug":"style","permalink":"https://over58.github.io/categories/style/"},{"name":"文档","slug":"文档","permalink":"https://over58.github.io/categories/%E6%96%87%E6%A1%A3/"},{"name":"可视化","slug":"可视化","permalink":"https://over58.github.io/categories/%E5%8F%AF%E8%A7%86%E5%8C%96/"},{"name":"其他","slug":"其他","permalink":"https://over58.github.io/categories/%E5%85%B6%E4%BB%96/"},{"name":"插件","slug":"插件","permalink":"https://over58.github.io/categories/%E6%8F%92%E4%BB%B6/"},{"name":"nginx","slug":"nginx","permalink":"https://over58.github.io/categories/nginx/"}],"tags":[{"name":"vue","slug":"vue","permalink":"https://over58.github.io/tags/vue/"},{"name":"highcharts","slug":"highcharts","permalink":"https://over58.github.io/tags/highcharts/"},{"name":"echarts","slug":"echarts","permalink":"https://over58.github.io/tags/echarts/"},{"name":"事件模型","slug":"事件模型","permalink":"https://over58.github.io/tags/%E4%BA%8B%E4%BB%B6%E6%A8%A1%E5%9E%8B/"},{"name":"performance","slug":"performance","permalink":"https://over58.github.io/tags/performance/"},{"name":"pageshow","slug":"pageshow","permalink":"https://over58.github.io/tags/pageshow/"},{"name":"css动画","slug":"css动画","permalink":"https://over58.github.io/tags/css%E5%8A%A8%E7%94%BB/"},{"name":"F2","slug":"F2","permalink":"https://over58.github.io/tags/F2/"},{"name":"css","slug":"css","permalink":"https://over58.github.io/tags/css/"},{"name":"git","slug":"git","permalink":"https://over58.github.io/tags/git/"},{"name":"hexo","slug":"hexo","permalink":"https://over58.github.io/tags/hexo/"},{"name":"lifecycle","slug":"lifecycle","permalink":"https://over58.github.io/tags/lifecycle/"},{"name":"webpack","slug":"webpack","permalink":"https://over58.github.io/tags/webpack/"},{"name":"js","slug":"js","permalink":"https://over58.github.io/tags/js/"},{"name":"es6","slug":"es6","permalink":"https://over58.github.io/tags/es6/"},{"name":"模拟函数","slug":"模拟函数","permalink":"https://over58.github.io/tags/%E6%A8%A1%E6%8B%9F%E5%87%BD%E6%95%B0/"},{"name":"类型转换","slug":"类型转换","permalink":"https://over58.github.io/tags/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/"},{"name":"类型","slug":"类型","permalink":"https://over58.github.io/tags/%E7%B1%BB%E5%9E%8B/"},{"name":"dataTable","slug":"dataTable","permalink":"https://over58.github.io/tags/dataTable/"},{"name":"codemirror","slug":"codemirror","permalink":"https://over58.github.io/tags/codemirror/"},{"name":"渲染过程","slug":"渲染过程","permalink":"https://over58.github.io/tags/%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B/"},{"name":"算法","slug":"算法","permalink":"https://over58.github.io/tags/%E7%AE%97%E6%B3%95/"},{"name":"graph","slug":"graph","permalink":"https://over58.github.io/tags/graph/"},{"name":"cytoscape","slug":"cytoscape","permalink":"https://over58.github.io/tags/cytoscape/"},{"name":"http","slug":"http","permalink":"https://over58.github.io/tags/http/"},{"name":"vscode","slug":"vscode","permalink":"https://over58.github.io/tags/vscode/"},{"name":"组件","slug":"组件","permalink":"https://over58.github.io/tags/%E7%BB%84%E4%BB%B6/"},{"name":"JSON","slug":"JSON","permalink":"https://over58.github.io/tags/JSON/"},{"name":"node","slug":"node","permalink":"https://over58.github.io/tags/node/"},{"name":"npm","slug":"npm","permalink":"https://over58.github.io/tags/npm/"},{"name":"jquery","slug":"jquery","permalink":"https://over58.github.io/tags/jquery/"},{"name":"components","slug":"components","permalink":"https://over58.github.io/tags/components/"},{"name":"blog","slug":"blog","permalink":"https://over58.github.io/tags/blog/"},{"name":"ubuntu","slug":"ubuntu","permalink":"https://over58.github.io/tags/ubuntu/"}]}