519618744

519618744 查看完整档案

深圳编辑湖南工业大学  |  金属材料工程 编辑  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

519618744 赞了文章 · 2020-07-19

15 行代码实现并发控制(javascript)

前言

首发于 github blog

做过爬虫的都知道,要控制爬虫的请求并发量,其实也就是控制其爬取频率,以免被封IP,还有的就是以此来控制爬虫应用运行内存,否则一下子处理N个请求,内存分分钟会爆。

python爬虫一般用多线程来控制并发,

然而如果是node.js爬虫,由于其单线程无阻塞性质以及事件循环机制,一般不用多线程来控制并发(当然node.js也可以实现多线程,此处非重点不再多讲),而是更加简便地直接在代码层级上实现并发。

为图方便,开发者在开发node爬虫一般会找一个并发控制的npm包,然而第三方的模块有时候也并不能完全满足我们的特殊需求,这时候我们可能就需要一个自己定制版的并发控制函数。

下面我们用15行代码实现一个并发控制的函数。

具体实现

参数

首先,一个基本的并发控制函数,基本要有以下3个参数:

  • list {Array} - 要迭代的数组
  • limit {number} - 控制的并发数量
  • asyncHandle {function} - 对list的每一个项的处理函数

设计

以下以爬虫为实例进行讲解

设计思路其实很简单,假如并发量控制是 5

  1. 首先,瞬发 5 个异步请求,我们就得到了并发的 5 个异步请求

    // limit = 5
    while(limit--) {
        handleFunction(list)
    }
  2. 然后,这 5 个异步请求中无论哪一个先执行完,都会继续执行下一个list

    let recursion = (arr) => {
        return asyncHandle(arr.shift())
            .then(()=>{
                // 迭代数组长度不为0, 递归执行自身
                if (arr.length!==0) return recursion(arr) 
                // 迭代数组长度为0,结束 
                else return 'finish';
            })
    }
  3. list所有的项迭代完之后的回调

    return Promise.all(allHandle)

代码

上述步骤组合起来,就是

/**
 * @params list {Array} - 要迭代的数组
 * @params limit {Number} - 并发数量控制数
 * @params asyncHandle {Function} - 对`list`的每一个项的处理函数,参数为当前处理项,必须 return 一个Promise来确定是否继续进行迭代
 * @return {Promise} - 返回一个 Promise 值来确认所有数据是否迭代完成
 */
let mapLimit = (list, limit, asyncHandle) => {
    let recursion = (arr) => {
        return asyncHandle(arr.shift())
            .then(()=>{
                if (arr.length!==0) return recursion(arr)   // 数组还未迭代完,递归继续进行迭代
                else return 'finish';
            })
    };
    
    let listCopy = [].concat(list);
    let asyncList = []; // 正在进行的所有并发异步操作
    while(limit--) {
        asyncList.push( recursion(listCopy) ); 
    }
    return Promise.all(asyncList);  // 所有并发异步操作都完成后,本次并发控制迭代完成
}

测试demo

模拟一下异步的并发情况

var dataLists = [1,2,3,4,5,6,7,8,9,11,100,123];
var count = 0;
mapLimit(dataLists, 3, (curItem)=>{
    return new Promise(resolve => {
        count++
        setTimeout(()=>{
            console.log(curItem, '当前并发量:', count--)
            resolve();
        }, Math.random() * 5000)  
    });
}).then(response => {
    console.log('finish', response)
})

结果如下:

clipboard.png




手动抛出异常中断并发函数测试:

var dataLists = [1,2,3,4,5,6,7,8,9,11,100,123];
var count = 0;
mapLimit(dataLists, 3, (curItem)=>{
    return new Promise((resolve, reject) => {
        count++
        setTimeout(()=>{
            console.log(curItem, '当前并发量:', count--)
            if(curItem > 4) reject('error happen')
            resolve();
        }, Math.random() * 5000)  
    });
}).then(response => {
    console.log('finish', response)
})

并发控制情况下,迭代到5,6,7 手动抛出异常,停止后续迭代:
clipboard.png

查看原文

赞 33 收藏 61 评论 10

519618744 收藏了文章 · 2019-05-04

2019前端面试题(持续更新)

最近也在准备换工作了,然后收集了一些我觉得今年面试会遇到常见的问题。

如果有机会,记得也帮忙分享我一下。
2019的行情确实很糟糕。看到这么多人收藏点赞。我的内心也是哇凉哇凉的。
我也给一些除了面试题之外的经验吧

我相信不景气也是相对的,提升自我也是必要的。我说说我最近在准备些什么。

  • 首先优化自己的博客。有技术博客的求职者,必定会给面试官筛选简历时一个很好的印象
  • 针对你想求职的企业规模,乘着这段时间,深入的了解一些源码。如中型企业大多都偏爱vue。我最近也在研究其中的源码。
  • 更高bigger的,可以在研究框架源码中,得出一些经验,写一套自己框架。目前我也正在准备。
  • 还有自己的npm的组件包。

1,讲讲浅拷贝、深拷贝之间的区别

核心:引用类型和非引用类型的拷贝结果是不同的

浅拷贝只是拷贝基本类型的数据,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,因此存在父对象被篡改的可能,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存

//简单的浅拷贝
var a = 1;
var b = a;//赋值
console.log(b) //1
a = 2;//改变a的值
console.log(b) //1
  • 如果要实现深拷贝,用什么方法来实现

JSON.parse() + JSON.stringify()(缺点:只能处理可以被枚举的属性);
for in 循环递归遍历;

深拷贝就是能够实现真正意义上的数组和对象的拷贝。递归调用"浅拷贝"。(深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象)

  • 如果你想要实现支持setter和getter特性的拷贝,该怎么实现?

Object.defineproperties (定义属性)、Object.getOwnPropertyDescriptors(es2017,获取对象的多个属性)、Object.getOwnPropertyDescriptor(老一点,获取对象的单个属性的属性),但babel可以解决。

2、原型链的prototype和__proto__的区别;

  • prototype 在 new 示例后会被转为 __proto__
  • __proto__是非标准化的;

所有东西的原型链向上延伸到原型链的顶端,是什么;

  • Object.prototype.__proto__,结果是null.
Function本身就是函数,
Function.__proto__
是标准的内置对象Function.prototype,
Function.prototype.__proto__
是标准的内置对象Object.prototype

3、如果想实现继承,说几种你知道的方法?

  • 原型链继承、构造继承、实例继承、拷贝继承、组合继承、寄生组合继承

原型链继承

3、var、let、const的区别;

  • var 会变量提升;
  • let 声明的变量只在它所在的代码块有效;
  • const 声明后不能再修改其指向的目标,假如const 指向的是一个对象/数组,那么虽然不能更改指向目标,但是可以更改对象和数组内部的值;

进阶一:说到变量提升,class 声明一个类时,存在变量提升么?为什么?

  • 不存在。因为要方便类的继承,先声明子类再声明父类;

进阶二:const 声明一个对象,如何让对象内部的属性的值也无法改变?

  • 使用Object.freeze()锁死(es5新增特性);
  • 对数组等引用类型的值,还是能修改的;

进阶三:全局作用域?函数作用域?块级作用域?作用域链?

js有哪些基本数据类型?

  • Boolean、Null、Undefined、Number、String、Object;
  • Symbol(es6新增)

进阶:es6新增的原型数据类型Symbol,特点是什么;

  • 表示独一无二的值;
  • 声明时不能使用new Symbol(),而是 Symbol();
  • 声明时可以加参数,用于描述;
  • 作为key时不能被遍历;

进阶2:如何声明两个相等的Symbol变量?

  • 使用Symbol.for,参数相同时;

let a = Symbol.for('a');
let b = Symbol.for('a');
a === b; // true

#### Promise是什么?

* 是异步编程的一种解决方案;
* 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

一般什么时候使用?

* 处理异步请求时使用,比如ajax请求;

有哪些状态?

* 状态有pending、resolved、rejected;

怎么捕获他的错误?

* 最后写catch;
* then里面第二个参数可以捕获;

如果内部抛错,但是没有被捕获,这个没被捕获的错误接下来会发生什么事情?

* 冒泡;

能不能被try catch捕获?为什么?

* 不能,因为是异步编程。

能不能被window.onerror捕获?

* 不能

new Promise((resolve,reject)=>throw new Error('a')).then(fn1).then(fn2,fn3).catch(fn4) 这个函数,会执行哪些函数
* fn3,没了

4,从输入URL到浏览器显示页面发生了什么。(特别注意)

这题可以了解一下,尽管面试时候造飞船,真的干活可能还是拧螺丝。
  • 1.在浏览器中输入url(解析IP地址)
  • 2.应用层DNS解析域名
  • 3.应用层客户端发送HTTP请求
  • 4.传输层TCP传输报文(3次握手)
  • 5.网络层IP协议查询MAC地址
  • 6.数据到达数据链路层
  • 7.服务器接收数据
  • 8.服务器响应请求
  • 9.服务器返回相应文件
  • 二、页面渲染:现代浏览器渲染页面的过程是这样的:解析HTML以构建DOM树 –> 构建渲染树 –> 布局渲染树 –> 绘制渲染树。
在浏览器还没接收到完整的HTML文件时,它就开始渲染页面了,在遇到外部链入的脚本标签或样式标签或图片时,会再次发送HTTP请求重复上述的步骤。在收到CSS文件后会对已经渲染的页面重新渲染,加入它们应有的样式,图片文件加载完立刻显示在相应位置。在这一过程中可能会触发页面的重绘或重排。

5、async、await 的使用场景是什么?

连续的异步请求,下一步的异步请求依赖于前一步的异步请求结果;
进阶一:假如有A、B、C三个异步请求,异步请求C依赖于异步请求A和B的结果(即A和B完成后再发起C),那么你会如何实现它?

Promise.all();
设置状态分别标记A和B,A、B完成后会去修改自己的完成标记,然后检查所有的状态标记,假如都是完成状态,然后去执行异步请求C。

6、Promise是什么?

是异步编程的一种解决方案;
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
一般什么时候使用?

处理异步请求时使用,比如ajax请求;
有哪些状态?

状态有pending、resolved、rejected;
怎么捕获他的错误?

最后写catch;
then里面第二个参数可以捕获;
如果内部抛错,但是没有被捕获,这个没被捕获的错误接下来会发生什么事情?

冒泡;
能不能被try catch捕获?为什么?

不能,因为是异步编程。
能不能被window.onerror捕获?

不能
new Promise((resolve,reject)=>throw new Error('a')).then(fn1).then(fn2,fn3).catch(fn4) 这个函数,会执行哪些函数 * fn3,没了

7、数字计算:请问在js中,输入表达式 0.1 + 0.2 的结果是什么?

  • 0.30000000000000004(能回答出来不是0.3,而是0.3后有若干个0和一个数字即可)

进阶一:为什么?

  • 原因是浮点数和整数,在存储时的方法是不同的,因此相加的规则也是不同的;

进阶二:浮点数是怎么存储的(本问题比较难)(可以跳到进阶三,比本题简单)

而double类型就是双精度浮点数,这种指使用64位(8字节)来存储一个浮点数。

根据规定,这64位bit里,分为三部分:

第一部分(1bit):

符号位,表示正负,正数为0,负数为1。

第二部分(11bit):

阶码位,也可以称为指数位。

第三部分(52bit)

尾数位,即表示实际数字的。

假如正负符号的值为S,正数S为1,负数S为-1;
假如指数位表示的值为E(计算后),指数位表示的值为2的E次方;
假如尾数位表示的值为M,尾数位表示的值为M;

根据科学表示法,任何一个范围内的浮点数可以通过以下方法来表示:(别问我为啥,我没去谷歌……)

浮点数 = S * Math.pow(2,E) * M;

进阶三:我们在实际开发中,如果遇见了这种浮点数计算的情况,怎么处理比较合适?

8、移动端开发的时候,一般怎么实现自适应?

  • rem
  • vw和vh
  • 媒体查询(bootstrap);

进阶一:他们的特点分别是什么?或者说实现原理是什么?

  • rem 根据 html 的 font-size;
  • vw是浏览器窗口宽度,vh是高度;
  • 媒体查询是根据浏览器窗口宽度或高度,进行响应式选择显示哪个css;

进阶二:弹出输入框会发生定位错误,fixed布局,怎么解决?

9、在我们将开发好的页面,进入线上环境的时候,肯定要最大化性能优化,那么我们常见的做法有哪些?

  • https://csspod.com/frontend-performance-best-practices/
  • gzip、雪碧图、减少http请求数、减少DNS请求、避免重定向、缓存ajax请求、延迟加载、预加载、减少DOM数、使用框架例如React的虚拟DOM树、减少DOM操作、使用CDN、减少 css 里 @import 写法、

10、图片使用 雪碧图 和 base64字符串 你觉得哪个好?为什么?

  • 雪碧图可以缓存;
  • base64可以减少请求数;

进阶一:如果你决定使用雪碧图/base64字符串,你会怎么做?

  • webpack 配 url-loader(base64);
  • webpack 的雪碧图插件 webpack-spritesmith;
查看原文

519618744 评论了文章 · 2019-04-21

2019前端面试题汇总(主要为Vue)

毕业之后就在一直合肥小公司工作,没有老司机、没有技术氛围,在技术的道路上我只能独自摸索。老板也只会画饼充饥,前途一片迷茫看不到任何希望。于是乎,我果断辞职,在新年开工之际来到杭州,这里的互联网公司应该是合肥的几十倍吧。。。。
刚来3天,面试了几家公司,有些规模比较小,有些是创业公司,也有些已经发展的不错了;今天把最近的面试题目做个汇总,也给自己复个盘,由于我的技术栈主要为Vue,所以大部分题目都是Vue开发相关的。

1. 谈谈你对MVVM开发模式的理解

MVVM分为Model、View、ViewModel三者。
Model 代表数据模型,数据和业务逻辑都在Model层中定义;
View 代表UI视图,负责数据的展示;
ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
ModelView 并无直接关联,而是通过 ViewModel 来进行联系的,ModelViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
这种模式实现了 ModelView 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom

2. Vue 有哪些指令?

v-html、v-show、v-if、v-for等等

3. v-if 和 v-show 有什么区别?

v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。

4. 简述Vue的响应式原理

当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。
每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

5. Vue中如何在组件内部实现一个双向数据绑定?

假设有一个输入框组件,用户输入时,同步父组件页面中的数据
具体思路:父组件通过 props 传值给子组件,子组件通过 $emit 来通知父组件修改相应的props值,具体实现如下:

import Vue from 'vue'

const component = {
  props: ['value'],
  template: `
    <div>
      <input type="text" @input="handleInput" :value="value">
    </div>
  `,
  data () {
    return {
    }
  },
  methods: {
    handleInput (e) {
      this.$emit('input', e.target.value)
    }
  }
}

new Vue({
  components: {
    CompOne: component
  },
  el: '#root',
  template: `
    <div>
      <comp-one :value1="value" @input="value = arguments[0]"></comp-one>
    </div>
  `,
  data () {
    return {
      value: '123'
    }
  }
})

可以看到,当输入数据时,父子组件中的数据是同步改变的:
Image 1.png

Image 2.png

我们在父组件中做了两件事,一是给子组件传入props,二是监听input事件并同步自己的value属性。那么这两步操作能否再精简一下呢?答案是可以的,你只需要修改父组件:

template: `
    <div>
      <!--<comp-one :value1="value" @input="value = arguments[0]"></comp-one>-->
      <comp-one v-model="value"></comp-one>
    </div>
  `

v-model 实际上会帮我们完成上面的两步操作。

6. Vue中如何监控某个属性值的变化?

比如现在需要监控data中,obj.a 的变化。Vue中监控对象属性的变化你可以这样:

watch: {
      obj: {
      handler (newValue, oldValue) {
        console.log('obj changed')
      },
      deep: true
    }
  }

deep属性表示深层遍历,但是这么写会监控obj的所有属性变化,并不是我们想要的效果,所以做点修改:

watch: {
   'obj.a': {
      handler (newName, oldName) {
        console.log('obj.a changed')
      }
   }
  }

还有一种方法,可以通过computed 来实现,只需要:

computed: {
    a1 () {
      return this.obj.a
    }
}

利用计算属性的特性来实现,当依赖改变时,便会重新计算一个新值。

7. Vue中给data中的对象属性添加一个新的属性时会发生什么,如何解决?

示例:

<template>
  <div>
    <ul>
      <li v-for="value in obj" :key="value">
        {{value}}
      </li>
    </ul>
    <button @click="addObjB">添加obj.b</button>
  </div>
</template>
<script>
export default {
  data () {
    return {
      obj: {
        a: 'obj.a'
      }
    }
  },
  methods: {
    addObjB () {
      this.obj.b = 'obj.b'
      console.log(this.obj)
    }
  }
}
</script>
<style></style>

点击button会发现,obj.b 已经成功添加,但是视图并未刷新:
Image 3.png

Image 4.png

原因在于在Vue实例创建时,obj.b并未声明,因此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局api $set():

addObjB () {
      // this.obj.b = 'obj.b'
      this.$set(this.obj, 'b', 'obj.b')
      console.log(this.obj)
    }

$set()方法相当于手动的去把obj.b处理成一个响应式的属性,此时视图也会跟着改变了:
Image 5.png

8. delete和Vue.delete删除数组的区别

delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
Vue.delete直接删除了数组 改变了数组的键值。

    var a=[1,2,3,4]
    var b=[1,2,3,4]
    delete a[1]
    console.log(a)
    this.$delete(b,1)
    console.log(b)

Image 6.png

Image 7.png

9.如何优化SPA应用的首屏加载速度慢的问题?

  • 将公用的JS库通过script标签外部引入,减小app.bundel的大小,让浏览器并行下载资源文件,提高下载速度;
  • 在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某个组件时再加载对应的js文件;
  • 加一个首屏 loading 图,提升用户体验;

10. 前端如何优化网站性能?

  1. 减少 HTTP 请求数量

在浏览器与服务器进行通信时,主要是通过 HTTP 进行通信。浏览器与服务器需要经过三次握手,每次握手需要花费大量时间。而且不同浏览器对资源文件并发请求数量有限(不同浏览器允许并发数),一旦 HTTP 请求数量达到一定数量,资源请求就存在等待状态,这是很致命的,因此减少 HTTP 的请求数量可以很大程度上对网站性能进行优化。

    • CSS Sprites:国内俗称 CSS 精灵,这是将多张图片合并成一张图片达到减少 HTTP 请求的一种解决方案,可以通过 CSS background 属性来访问图片内容。这种方案同时还可以减少图片总字节数。
    • 合并 CSS 和 JS 文件:现在前端有很多工程化打包工具,如:grunt、gulp、webpack等。为了减少 HTTP 请求数量,可以通过这些工具再发布前将多个 CSS 或者 多个 JS 合并成一个文件。
    • 采用 lazyLoad:俗称懒加载,可以控制网页上的内容在一开始无需加载,不需要发请求,等到用户操作真正需要的时候立即加载出内容。这样就控制了网页资源一次性请求数量。
    1. 控制资源文件加载优先级

    浏览器在加载 HTML 内容时,是将 HTML 内容从上至下依次解析,解析到 link 或者 script 标签就会加载 href 或者 src 对应链接内容,为了第一时间展示页面给用户,就需要将 CSS 提前加载,不要受 JS 加载影响。
    一般情况下都是 CSS 在头部,JS 在底部。

    1. 利用浏览器缓存
      浏览器缓存是将网络资源存储在本地,等待下次请求该资源时,如果资源已经存在就不需要到服务器重新请求该资源,直接在本地读取该资源。
    2. 减少重排(Reflow)
      基本原理:重排是 DOM 的变化影响到了元素的几何属性(宽和高),浏览器会重新计算元素的几何属性,会使渲染树中受到影响的部分失效,浏览器会验证 DOM 树上的所有其它结点的 visibility 属性,这也是 Reflow 低效的原因。如果 Reflow 的过于频繁,CPU 使用率就会急剧上升。

    减少 Reflow,如果需要在 DOM 操作时添加样式,尽量使用 增加 class 属性,而不是通过 style 操作样式。

    1. 减少 DOM 操作
    2. 图标使用 IconFont 替换

    11. 网页从输入网址到渲染完成经历了哪些过程?

    大致可以分为如下7步:

    1. 输入网址;
    2. 发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;
    3. 与web服务器建立TCP连接;
    4. 浏览器向web服务器发送http请求;
    5. web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);
    6. 浏览器下载web服务器返回的数据及解析html源文件;
    7. 生成DOM树,解析css和js,渲染页面,直至显示完成;

    12. jQuery获取的dom对象和原生的dom对象有何区别?

    js原生获取的dom是一个对象,jQuery对象就是一个数组对象,其实就是选择出来的元素的数组集合,所以说他们两者是不同的对象类型不等价。

    • 原生DOM对象转jQuery对象:
    var box = document.getElementById('box');
    var $box = $(box);
    • jQuery对象转原生DOM对象:
    var $box = $('#box');
    var box = $box[0];

    13. jQuery如何扩展自定义方法

    (jQuery.fn.myMethod=function () {
           alert('myMethod');
    })
    // 或者:
    (function ($) {
            $.fn.extend({
                 myMethod : function () {
                      alert('myMethod');
                 }
            })
    })(jQuery)

    使用:

    $("#div").myMethod();

    目前来看公司面试的问题还是比较基础的,但是对于某些只追求会用并不研究其原理的同学来说可能就没那么容易了。所以大家不仅要追求学习的广度,更要追求深度。
    OK,希望自己能早日拿到心仪的offer.

    参考:
    浅谈网站性能之前端性能优化

    查看原文

    519618744 赞了文章 · 2019-02-27

    JavaScript:函数防抖与函数节流

    防抖(debounce)

    名词解释:在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。

    使用场景:以百度输入框例,比如你要查询XXx,想实现输完了XXx之后,再进行搜索请求,这样可以有效减少请求次数,节约请求资源。

    函数防抖简单实现

    <script type="text/javascript">
        window.onload = function () {            
            function ajax(content) {//模拟ajax请求
                console.log('ajax request ' + content)
            }
            function debounce(fun, delay) {
                return function (arguments) {
                    //获取函数的作用域和变量
                    let that = this;
                    let args = arguments;                    
                    clearTimeout(fun.id)// 清除定时器
                    fun.id = setTimeout(function () {
                        fun.call(that, args )
                    }, delay)
                }
            }            
            let debounceAjax = debounce(ajax, 1000)
            XXX.addEventListener('keyup', function (e) {
                debounceAjax(e.target.value)
            })
        }
    </script>
    

    节流(throttle)

    名词解释:连续执行函数,每隔一定时间执行函数。规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

    使用场景:鼠标连续多次click事件,mousemove 事件,监听滚动事件,比如是否滑到底部自动加载更多等等...

    函数节流简单实现

    function throttle(fn, delay) {
        let lastTime; 
        let timer; 
        delay || (delay = 300); // 默认间隔为300ms
        return function(arguments) {
            let context = this;
            let args = arguments;
            let nowTime = +new Date(); // 获取系统当前的时间
            if (lastTime && nowTime < lastTime+ delay) { // 当前距离上次执行的时间小于设置的时间间隔
                clearTimeout(timer); // 清除定时器
                timer = setTimeout(function() { // delay时间后,执行函数
                    lastTime= nowTime ;
                    fn.apply(context, args);
                }, delay);
            } else { // 当前距离上次执行的时间大于等于设置的时间,直接执行函数
                lastTime= nowTime ;
                fn.apply(context, args);
            }
        };
    }
    
    

    区别:

    可以用日常进入电梯来举例,形象地描述节流和防抖的区别

    函数防抖:如果A在10:00:00开门走入电梯内(触发事件),如果后续没有人进入电梯,电梯将在5秒钟之后10:00:05关门(执行事件监听器)。这时如果B在10:00:04走入电梯内,电梯会在10:00:09才关门。

    函数节流 :如果A在10:00:00开门走入电梯内(触发事件),如果后续没有人进入电梯,电梯将在5秒钟之后10:00:05关门(执行事件监听器)。这时如果B在10:00:04走入电梯内,电梯同样是在10:00:05关门。这个时间从第一个人进入电梯开始计时,不管在5秒之内进来多少人,电梯都会在10:00:05关门。如果一直没有人进来,则电梯不运行。

    总结:

    根据实际业务场景,合理的利用debounce(防抖)和throttle(节流)可以优化性能和提高用户体验。

    效果:
    函数防抖是某一段时间内只执行一次;
    函数节流是间隔时间执行,不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。

    原理:
    防抖是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,都会清除当前的 timer 重新计时。这样一来,只有最后一次操作事件才被真正触发。
    节流是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。

    查看原文

    赞 86 收藏 65 评论 2

    519618744 赞了文章 · 2019-02-27

    你知道前端对图片的处理方式吗?

    前言

    作为前端工程师 de 我们,日常少不了会跟图片打交道。在各大电商平台工作的前端工程师们,感受可能会更加的明显。

    以下是我之前跟图片打交道踩到的坑,跟大家分享一下经验。

    一、情景再现

    用postman请求接口的时候,返回的是这个图片(二进制)

    postman

    在chrome的network查看的时候,返回的也是这个图片(二进制)

    network

    可是,在debug打印的时候,返回的却是乱码

    debug

    很明显,数据的类型已经被改动了。思考原因,唯一有可能改变数据类型的地方是在axios。

    我去翻看了一下axios的文档,里面是这样描述的

    // `responseType` indicates the type of data that the server will respond with
    // options are 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
    responseType: 'json', // default

    因此,乱码出现的原因是因为:axios默认返回的是json文本形式,二进制图片数据被强制转换成了json文本形式。

    找到了原因,解决方案就好办了。我们在axios里面,responseType默认返回数据类型是json,将其改为返回数据类型blob。

    export function miniprogramQrcode (params) {
      return axios.post(
        env.MI_URL + '/XXXX/XXX/XXXX',
        params,
        // 将responseType的默认json改为blob
        {
        responseType: 'blob',
        emulateJSON: true
      }).then(res => {
        if (res.data) {
          return Promise.resolve(res.data)
        } else {
          throw res
        }
      }).catch(err => {
        return Promise.reject(err)
      })
    }

    接下来的问题是,如何处理blob对象,将其显示在前端页面呢?

    代码如下:

    createMiniQrcode (blob) {
      let img = document.createElement('img')
      img.onload = function (e) {
        // 元素的onload 事件触发后将销毁URL对象, 释放内存。
        window.URL.revokeObjectURL(img.src)
      }
      // 浏览器允许使用URL.createObjectURL()方法,针对 Blob 对象生成一个临时 URL。
      // 这个 URL 以blob://开头,表明对应一个 Blob 对象。
      img.src = window.URL.createObjectURL(blob)
      document.querySelector('.imgQrCode').appendChild(img)
    }

    是不是以为就这样结束了? No, No, No. 了解如何解决问题还不够,还需要透过表象进行发散思考。

    二、 发散思考

    一般来说,图片在后端的存储方式分为两种:
    其一:可以将图片以独立文件的形式存储在服务器的指定文件夹中,再将路径存入数据库字段中;
    其二:将图片转换成二进制流,直接存储到数据库的 Image 类型字段中.
    
    
    

    对于第一种存储方式,我们前端直接将存储路径赋值给src属性即可轻松显示。

    对于第二种存储方式,我们前端需要将其二进制流交由blob对象处理,然后通过blob的API生成临时URL赋值给src属性来显示。

    两种存储方式都有对应的解决方案,似乎已经完美解决了关于图片显示的问题。但是,我们的业务场景是多样且多变的。有时候我们也会遇到这样的场景,比如图片拖拽上传插件后,自动返回给你了Blob对象,但不幸的是,你发现你又用了一个第三方的服务接口只接收 base64 格式的数据,是否有点欲哭无泪?

    那么,图片的三种表现形式url、base64、blob,三者之间是否可以转化以满足需求呢?

    流程图

    1. url 转 base64

    url to base64 的方法封装

    // 原理: 利用canvas.toDataURL的API转化成base64
    
    urlToBase64(url) {
      return new Promise ((resolve,reject) => {
          let image = new Image();
          image.onload = function() {
            let canvas = document.createElement('canvas');
            canvas.width = this.naturalWidth;
            canvas.height = this.naturalHeight;
            // 将图片插入画布并开始绘制
            canvas.getContext('2d').drawImage(image, 0, 0);
            // result
            let result = canvas.toDataURL('image/png')
            resolve(result);
          };
          // CORS 策略,会存在跨域问题https://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
          image.setAttribute("crossOrigin",'Anonymous');
          image.src = url;
          // 图片加载失败的错误处理
          image.onerror = () => {
            reject(new Error('图片流异常'));
        };
    }

    你可以这样调用:

    let imgUrL = `http://XXX.jpg`
    
    this.getDataUri(imgUrL).then(res => {
      // 转化后的base64图片地址
      console.log('base64', res)
    })

    2. base64 转 blob

    base64 to blob 的方法封装

    // 原理:利用URL.createObjectURL为blob对象创建临时的URL
    
    base64ToBlob ({b64data = '', contentType = '', sliceSize = 512} = {}) {
        return new Promise((resolve, reject) => {
          // 使用 atob() 方法将数据解码
          let byteCharacters = atob(b64data);
          let byteArrays = [];
          for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
            let slice = byteCharacters.slice(offset, offset + sliceSize);
            let byteNumbers = [];
            for (let i = 0; i < slice.length; i++) {
                byteNumbers.push(slice.charCodeAt(i));
            }
            // 8 位无符号整数值的类型化数组。内容将初始化为 0。
            // 如果无法分配请求数目的字节,则将引发异常。
            byteArrays.push(new Uint8Array(byteNumbers));
          }
          let result = new Blob(byteArrays, {
            type: contentType
          })
          result = Object.assign(result,{
            // jartto: 这里一定要处理一下 URL.createObjectURL
            preview: URL.createObjectURL(result),
            name: `图片示例.png`
          });
          resolve(result)
        })
     }

    你可以这样调用:

    let base64 = base64.split(',')[1]
    
    this.base64ToBlob({b64data: base64, contentType: 'image/png'}).then(res => {
        // 转后后的blob对象
        console.log('blob', res)
    })
    
    
    

    3. blob 转 base64

    blob to base64 的方法封装

    // 原理:利用fileReader的readAsDataURL,将blob转为base64
    
    blobToBase64(blob) {
        return new Promise((resolve, reject) => {
          const fileReader = new FileReader();
          fileReader.onload = (e) => {
            resolve(e.target.result);
          };
          // readAsDataURL
          fileReader.readAsDataURL(blob);
          fileReader.onerror = () => {
            reject(new Error('文件流异常'));
          };
        });
    }

    你可以这样调用:

    this.blobToBase64(blob).then(res => {
        // 转化后的base64
        console.log('base64', res)
    })

    ps: 以上方法是针对玩转图片流的优化,感谢原作者。

    在这里贴出url转base64, base64与blob的相互转化的demo,其它的会更新在这里,有兴趣可以戳一下这里

    三、图片处理方式的归纳

    1. 后端的图片的存储方式

    在前面我们提到过,图片在后端的存储有两种方式,我们回顾一下:其一:可以将图片以独立文件的形式存储在服务器的指定文件夹中,再将路径存入数据库字段中;其二:将图片转换成二进制流,直接存储到数据库的 Image 类型字段中;

    那么这两种存储方式,哪种更优呢?

    据我了解,在互联网环境中,大访问量,数据库速度和性能方面很重要。一般在数据库存储图片的做法比较少,更多的是将图片路径存储在数据库中,展示图片的时候只需要连接磁盘路径把图片载入进来即可。因为图片是属于大字段。一张图片可能1m到几m。这样的大字段数据会加重数据库的负担,拖慢数据库。在大并发访问的情况下很重要。这是一个经验。去看看dba对数据库性能调优方面的分析都能得到这个答案的:就是图片不要存储在数据库中。

    因此,如果你司的后端小哥哥经常将图片以二进制的形式存储到数据库然后返回给你对接,你应该知道如何去dui他了吧(滑稽脸)。

    更多关于图片或者文件在数据库的存储方式的归纳请戳这里

    2. 前端的图片的显示方式

    对于前端来说:
    图片在前端显示有三种方式:url、base64、blob

    三种显示方式,哪种更优雅呢?

    url: 一般来说,图片的显示还是建议使用url的方式比较好。如果后端传过来的字段是图片路径的话。

    base64:如果图片较大,图片的色彩层次比较丰富,则不适合使用这种方式,因为其Base64编码后的字符串非常大,会明显增大HTML页面,影响加载速度。
    如果图片像loading或者表格线这样的,大小极小,但又占据了一次HTTP请求,而很多地方都会使用。则非常适用“base64:URL图片”技术进行优化了!详细的张鑫旭的Demo演示,请戳这里一下

    blob: 当后端返回特定的图片二进制流的时候,就像我第一part里的情景再现说的,前端用blob容器接收。图片用blob展示会比较好。

    四、感想

    付出,记录,总结。在项目中遇到的问题我都会一点一滴的记录整理下来。我相信,这些都是一片一片散落的枝叶,随着项目经验的增多,这些枝叶最终一定能够成长为一棵参天大树。

    文中的观点受限于本人当前的技术水平,难免会有讲错的地方,欢迎评论区留言交流指正。

    随着技术水平的提升,文章会不定期的迭代而优化~你可以通过下面的方式联系到我。

    关于我

    微信公众号二维码

    文章回顾:

    参考资料:

    查看原文

    赞 93 收藏 69 评论 1

    519618744 关注了专栏 · 2018-07-20

    前端进阶计划

    前端面试专题与日常研究

    关注 86

    519618744 关注了问题 · 2018-05-23

    解决Chrome 里的请求报错 "CAUTION: Provisional headers are shown" 是什么意思?

    这次是在 Chrome 扩展里提交请求遇到的... 以前也遇到过几次,,
    这个究竟是出了什么问题呢?

    关注 50 回答 12

    519618744 提出了问题 · 2018-04-27

    像这种数据格式如何递归

    let arr = [{
        role: 'admin',
        left: 'fzz',
        children: [{
            role: 'other',
            right: 'pdd',
            children: [{
                role: 'admin'
            }]
        }] 
    }]

    像这种数据格式如何递归返回一个数组,数据格式保持不变,但是取出里面role为admin的呢?(包括children里面的数据也要对role进行筛选)如果父级role不是admin,则该级和它的children都丢弃
    返回:

    arr = [{
        role: 'admin',
        left: 'fzz',
        children: [{
            role: 'admin',
            right: 'pdd'
        }]
    }]

    关注 3 回答 2

    519618744 关注了用户 · 2018-03-27

    soul @soulsxd

    _
    | |__ __ _
    | '_ | | | |/ _` |
    | |_) | |_| | (_| |
    |_.__/ \__,_|\__, |

             |___/ 

    关注 4

    519618744 回答了问题 · 2017-12-06

    解决vue动态prop绑定的是一个Object类型的变量,子组件可以修改其中的属性吗

    vue中是可以修改对象的属性的,这样做好不好看你自己的取舍吧,我觉得修改没关系

    关注 6 回答 5

    认证与成就

    • 获得 10 次点赞
    • 获得 57 枚徽章 获得 1 枚金徽章, 获得 15 枚银徽章, 获得 41 枚铜徽章

    擅长技能
    编辑

    (゚∀゚ )
    暂时没有

    开源项目 & 著作
    编辑

    (゚∀゚ )
    暂时没有

    注册于 2016-10-26
    个人主页被 786 人浏览