"前"路上的小磕绊(当笔记用)

248 阅读6分钟

CSS

pageY、clientY与screenY的差别(X同理)

image.png html5adownload属性

  • 定义和用法 download 属性定义下载链接的地址指定下载文件的名称。文件名称没有限定值,浏览器会自动在文件名称末尾添加该下载文件的后缀 (.img, .pdf, .txt, .html, 等)

download 属性是HTML5中新增的 <a> 标签属性。

语法属性值值 描述
<a download="filename">filename指定文件名称。

检测浏览器是否支持download属性

'download' in document.createElement('a');

1px边框(解决不同分辨率屏幕1px的宽窄不同)

缩放原理


    .border-1px {
      position relative
    }
    .border-1px:after {
        display block
        content ''
        position absolute
        left 0
        bottom 0
        width 100%
        border-top 1px solid #ccc
     }
    @media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5) .border-1px::after {
          transform scaleY(0.7)
          -webkit-transform scaleY(0.7)
    }
    @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2)
      .border-1px::after {
          transform scaleY(0.5)
          -webkit-transform scaleY(0.5)
    }
    @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3)
      .border-1px::after {
          transform scaleY(0.3333)
          -webkit-transform scaleY(0.3333)
    }

隐藏移动端ios/android滚动条,使滚动流畅

隐藏滚动条,不影响滚动

::-webkit-scrollbar {
  display: none 
 }

流畅滚动

 //在滚动元素`css`中添加
 -webkit-overflow-scrolling: touch // IOS系统
    overflow-scrolling: touch // 安卓系统

伪元素(:或::都可以,::更准确,:兼容好)与伪类(只能:)的区别

伪类与伪元素都是用于向选择器加特殊效果
  • 伪类与伪元素的本质区别就是是否抽象创造了新元素
  • 伪类只要不是互斥可以叠加使用
  • 伪元素在一个选择器中只能出现一次,并且只能出现在末尾
  • 伪类与伪元素优先级分别与类、标签优先级相同

伪类标签只对可以插入内容的标签添加:div span

:focus-within 表示一个元素获得焦点,或,该元素的后代元素获得焦点

使用效果可参考掘金的登录框

::selection 被用户选中或处于高亮状态的部分

p::selection { color: white; background-color: red; }  //选中p标签文字时的样式是红底白字

只可以应用于少数的CSS属性:color, background, cursor,和outline

::-webkit-scrollbar 滚动条样式

div::-webkit-scrollbar {  /* 滚动条样式width等 */  }   

::-webkit-scrollbar-track 滚动条元素 即整个滚动条轨道

::-webkit-scrollbar-thumb 滚动条滑块样式

Vue中使用less根据分辨率给元素添加背景图片

按照less官方文档,url应当如下使用:

URLs
// Variables
@images: "../img";

// Usage
body {
  color: #444;
  background: url("@{images}/white-sand.png");
}

故而有了根据屏幕分辨率设置背景图片代码

.bg-image(@url) {
  background-image: url('@{url}@2x.png');
  @media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3){
    background-image: url('@{url}@3x.png');
  }
}  // 报错报错 找不到路径的

这里要使用“~”符号来告诉less引号里面的内容不需要编译。

正确代码:

.bg-image(@url) {
    background-image:~"url('@{url}@2x.png')";
    @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
        background-image: ~"url('@{url}@3x.png')";
    }
}

改变输入框inputtextareaplaceholder样式,去除输入框选中边框高亮

input::-webkit-input-placeholder, textarea::-webkit-input-placeholder  {
  //设置样式
}
/*  focus 标记*/
input:focus {  
  outline:none;
}  
 

(原谅我一直没找很完善的reset.css,这些在一些重置样式文件自带的有,大家有好的完善的也可以告知我一下 )

user-select 控制用户选中文本

user-select: [prop]
propresult
none元素及其子元素的文本不可选中
text用户可以选择文本
auto::before::after 伪元素上,计算属性是 none, 详情

移动端慎用user-select:none,会导致输入框等表单框失效

sticky 属性

在使用 position: sticky 的时候,如果不指定 top 属性是不会有效果的。 这个属性是用来实现滚动吸顶的,具体可了解position

列表自动计数器

  • couter-reset 创建/重置一个或多个计数器
  • counter-increment 递增一个或多个计数器值 通常作用于counter-resetcontent属性

示例:

ul {
  counter-reset: counter;   /*这个counter只是个名字 和下边counter-increment和content对应*/
}

li::before {
  counter-increment: counter;
  content: counters(counter, '.') ' ';
}

列表渲染效果: 1. --- 2. -----

flex布局属性中inline-flex

inline-flexinline-block 一样,对内部元素来说是个 display:flex 的容器,对外部元素来说是个 inline-block 的块

textarea的换行符显示

        white-space: pre-wrap;
        word-wrap: break-word;
        word-break: break-all;

JS

fetch 请求的抛弃 以及抛弃请求的捕捉

 let controller = new AbortController();
 let signal = controller.signal;
 fetch(url, { ... , signal  }).then(res => { res.json() }).then(...)
 //抛弃请求 会抛出一个DOMException
 controller.abort();

关于抛出的DOMException 捕捉

err.name !== 'AbortError' && this.request_error('网络异常');

滚动条的控制

  • JS控制滚动条的位置:

     `ele.scrollTo(x,y);`  //滚动到指定位置
     `ele.scrollBy(x,y);`  //滚动指定距离
     `ele.scrollTop = y` //滚动指定位置 纵向
     `ele.scrollLeft = x` //滚动指定位置 横向
    

JS控制TextArea滚动条自动滚动到最下部 scrollHeight

`document.getElementById('textarea').scrollTop = document.getElementById('textarea').scrollHeight`
  • 获得滚动条距离顶部位置 ele.scrollTop 左边位置 ele.scrollLeft
function getScrollTop(){  
        var scrollTop=0;  
        if(document.documentElement&&document.documentElement.scrollTop){  
            scrollTop=document.documentElement.scrollTop;  
        }else if(document.body){  
            scrollTop=document.body.scrollTop;  
        }  
        return scrollTop;  
    }

addEventListener VS onclick孰优孰劣

两个都可以实现效果。 addEventListener 以及 IEattachEvent可以实现绑定多个事件,如果你有这方面的需求的话(奇怪的是你总会的)。

addEventListener的第三个参数可以用来控制监听器对于冒泡事件的响应,大部分情况是false,如果置为true,则响应事件的捕获阶段。事件的响应分区为三个阶段 :捕获、目标、冒泡阶段。

onclick绑定的多个事件会被覆盖,后者覆盖前者。 考虑到兼容ie,可以写一个原生的事件绑定兼容方案:

function addEvent(element, evnt, funct){
  if (element.attachEvent)
   return element.attachEvent('on'+evnt, funct);
  else
   return element.addEventListener(evnt, funct, false);
}

// example
addEvent(
    document.getElementById('myElement'),
    'click',
    function () { alert('hi!'); }
);

参考链接:addEventListener 与 onclick

addEventListener第三个参数的passive属性

最开始,addEventListener() 的参数约定是这样的: el.addEventListener(type, listener, useCapture)

  • el:事件对象
  • type:事件类型,clickmouseover
  • listener:事件处理函数,也就是事件触发后的回调
  • useCapture:布尔值,是否是捕获型,默认 false(冒泡)

2015年底,为了扩展新的选项,DOM 规范做了修订:

el.addEventListener(type, listener, {
    capture: false, // useCapture
    once: false,    // 是否设置单次监听
    passive: false  // 是否让阻止默认行为preventDefault()失效
})

三个属性的默认值都为 false。如果事件是 touchstarttouchmove的话,passive的默认值则会变成true

图片上传按钮以及预览(转载+解析)

//代码来源:https://www.jb51.net/article/120617.htm 这里解析一下
<template>
 <div class="admin">
 <div class="admin-content">
  <div class="edit">
  <div class="avatar">
   <div class="img">
   <img :src="avatar" @click="setAvatar">
   <span>更改</span>
   </div>
   //图片上传按钮
   <input type="file" name="avatar" accept="image/gif,image/jpeg,image/jpg,image/png" style="display:none" @change="changeImage($event)" ref="avatarInput">
  </div>
  <button type="button" @click="edit">确认修改</button>
  </div>
 </div>
 </div>
</template>
<script>
export default {
 data() {
     return {
      avatar: this.$store.state.administrator.avatar,
     }
 },
 methods: {
     edit() {
      // 确定修改后上传
      if (this.$refs.avatarInput.files.length !== 0) {
       var image = new FormData()
       image.append('avatar', this.$refs.avatarInput.files[0])
       this.axios.post('/avatar', image, {
           headers: {
            "Content-Type": "multipart/form-data"
           }
       })
      } 
     },
     setAvatar() {
      //触发按钮的上传事件 也会用label绑定for属性
      this.$refs.avatarInput.click()
     },
    
     changeImage(e) {
      //从事件对象里获取文件,利用文件流读取
      var file = e.target.files[0]
      var reader = new FileReader()
      var that = this
      reader.readAsDataURL(file)
      reader.onload = function(e) {
          that.avatar = this.result
      }
     }
   }
}
</script>

对象数组排序

主要用到Arraysort()方法:

  • 语法
arr.sort([compareFunction])
  • 参数 compareFunction 可选 用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。
  • 返回值 排序后的数组。请注意,数组已原地排序,并且不进行复制,即数组已修改

如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。例如 "Banana" 会被排列到 "cherry" 之前。当数字按由小到大排序时,9 出现在 80 之前,但因为(没有指明 compareFunction),比较的数字会先被转换为字符串,所以在Unicode顺序上 "80" 要比 "9" 要靠前。

如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 ab 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。 通俗点讲,那数字排序为例,return a - b: 从小到大;return b - a : 从大到小。 常用的是对象数组的排序,根据对象某一键值对排序,如下:
var compare = function (prop) {
    return function (obj1, obj2) {
        var val1 = obj1[prop];
        var val2 = obj2[prop];
        /*
        *异常处理,或者转型处理字符串转数字比较等
        */
        if (val1 < val2) {
            return -1;
        } else if (val1 > val2) {
            return 1;
        } else {
            return 0;
        }            
    } 
}

一键复制功能

主要用到 document.execCommand

copy() {
      let  googleSecuritKey = document.getElementById('googleSecuritKey');
      googleSecuritKey.select();
      try{
        if(document.execCommand('copy', false, null)){
          document.execCommand('copy');
          //一个弹窗组件而已
          this.$alert({
            msg: '已复制至剪贴板!'
          }, true);
        }
      } catch (err){
        this.$alert({
          msg: '请选中后按command/ctrl+C或右键复制!'
        }, true);
      }
    },

该方法限制条件

  1. DOM元素要可编辑,比如input或者eidtable属性的div
  2. 不可有disabled属性,inputtype不可以是hidden
  3. 内容宽度不可为0,至少为1px; 那么如果做一个只做信息展示的内容,允许用户一键复制,怎么做呢?

我这边选择用一个width:1pxinput用定位移动到旁边信息展示区下,用层级遮住。

<div class="text__strong">
    密钥:<input id="googleSecuritKey" v-model="googleSecuritKey" type="text" style="width: 1px;border: none;position: relative; left: 2px; z-index: -1;"/>
    {{googleSecuritKey}}
    <span class="ui-btn ui-btn-default ui-btn-short float-r clearfix" @click="copy">复制</span>
</div>

解决科学计数法显示数字问题

参考出处:JavaScript中科学计数法转化为数值字符串形式

以下两种情况,JavaScript 会自动将数值转为科学计数法表示

(1) 小于1且小数点后面带有6个0以上的浮点数值:

JavaScript 代码:
0.0000003    // 3e-7
0.00000033   // 3.3e-7
0.000003     // 0.000003

(2) 整数位数字多于21位:

JavaScript 代码:
1234567890123456789012     //1.2345678901234568e+21
1234567890123456789012.1   //1.2345678901234568e+21
123456789012345678901      //123456789012345680000

解决方案: JavaScript 代码:

function toNonExponential(num) {
    var m = num.toExponential().match(/\d(?:\.(\d*))?e([+-]\d+)/);
    return num.toFixed(Math.max(0, (m[1] || '').length - m[2]));
}
toNonExponential(3.3e-7)     // "0.00000033"
toNonExponential(3e-7)       // "0.0000003"
toNonExponential(1.401e10)   // "14010000000"
toNonExponential(0.0004)     // "0.0004"

解析一下:

.toExponential()将数字转化为科学记数法表示,匹配正则表达式/\d(?:\.(\d*))?e([+-]\d+)/,获取科学记数法中小数点后的字符及幂指数(e 后面的值),这样可以确定数字是几位小数。再用toFixed() 转换成数值表示。

大额数量转换

需求:

  • 数量保持最多5个字符。

  • 当数量<1,展示为0.003

  • 当数量为1-1000,展示为1.234,12.34,123.4

  • 当数量>1000,带上单位K,展示为1.54K,15.4K,154K

  • 当数量>1000000时,带上单位M,展示为1.23M,12.3M,123M

  • 数值采用向下取数展示的方式。如实际数量为15345,则展示为15.3K

项目中使用vue过滤器做的处理:

export default (vol) => {
  const val = parseFloat(vol) + '';
  if (isNaN(val)) return vol;
  let num = val.indexOf('.') ? val.split('.')[0].length : val.length;
  const getFiv = function(v) {
    let a = v.indexOf('.') ? v.substring(0, 5) : v;
    let b = a.replace(/[.]$/, '');
    return b;
  };
  if (num < 4) {
    let v = val + '';
    return getFiv(v);
  } else if (num < 7) {
    let v = val / 1000 + '';
    return getFiv(v) + 'K';
  } else {
    let v = val / 1000000 + '';
    return getFiv(v) + 'M';
  }
};

可以抽取方法的,调整5相关,可以获取指定位数的缩写。

含有变量的正则表达式

value = value.replace(new RegExp(`^(\\-)*(\\d+)\\.(\\d{${count}}).*$`), '$1$2.$3');//只能输入count位小数

比如count4

value = value.replace(/^(\-)*(\d+)\.(\d{4}).*$/, '$1$2.$3');//只能输入4位小数

Blob文件下载 借鉴

  • 把请求responseType设置为 blob,在response.body中拿数据(例子中使用了封装过的axios,data处理过);

    const { data } = await http({
        method: 'POST',
        headers: defaultHeaders,
        url: ...,
        responseType: 'blob',
        data: ...
      });
    
  • 把接受的data处理成blob对象,关于Blob

      let blob = new Blob([data], { type: data.type });
    

以下来自MDN

语法参数
var aBlob = new Blob( array, options );array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 BlobDOMStrings会被编码为UTF-8
options 是一个可选的BlobPropertyBag字典,它可能会指定如下两个属性:
- type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型。
- endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持blob中保存的结束符不变
  • 创建url

    URL.createObjectURL()方法会根据传入的参数创建一个指向该参数对象的URL. 这个URL的生命仅存在于它被创建的这个文档里. 新的对象URL指向执行的File对象或者是Blob对象. let url = window.URL.createObjectURL(blob); let fileName = '分析师列表.xlsx';

  • 创建a标签实现自动下载或者手动下载

     if ('download' in document.createElement('a')) {
       const a = document.createElement('a');
       a.href = url;
       a.download = fileName;
       a.style.display = 'none';
       document.body.appendChild(a);
       a.click();
       URL.revokeObjectURL(a.href);
       document.body.removeChild(a);
     } else {
       navigator.msSaveBlob(blob, fileName);
     }
    

日期格式化

RegExp.$1...$9属性用于返回正则表达式模式中某个子表达式匹配的文本。正则表达式中每个小括号内的部分表达式就是一个子表达式。

注意:这里的RegExp是全局对象,RegExp.$1...$9是全局属性。当执行任意正则表达式匹配操作时,JavaScript会自动更新全局对象RegExp上的全局属性,用以存储此次正则表达式模式的匹配结果。当再次执行正则表达式匹配时,RegExp上的全局属性又会更新,覆盖掉之前的存储数据,以反映本次正则表达式模式的匹配结果。

function formatDate(date, fmt) {
  let fmtstr = fmt; //* 初始化一个结果赋值,由于结果字符串是对应格式化fmt的,这里逐个替换完成 如:年替换y
  let dateObj = new Date(date);
  if (!date) return;
  const o = {
    'M+': dateObj.getMonth() + 1, // * 月 从 0 开始要加 1
    'd+': dateObj.getDate(), // * 日
    'h+': dateObj.getHours(), // * 时  24H制
    'm+': dateObj.getMinutes(), // * 分
    's+': dateObj.getSeconds(), // * 分
    'S+': dateObj.getMilliseconds(), //* 毫秒 常用在倒计时
    'q+': Math.floor((dateObj.getMonth() + 3) / 3), //* 季
  };
  // ? 如果fmt中包含年份的需求
  if (/(y+)/.test(fmt)) {
    fmtstr = fmtstr.replace(RegExp.$1, (dateObj.getFullYear() + '').substr(4 - RegExp.$1.length()));
  }
  for (let k in o) {
    // * 这里不要忘了() 正则分区
    if (new RegExp(`(${k})`).test(fmt)) {
      // *考虑: 月日时分秒 最多两位
      // *如果只给一个标识位 则引用匹配的默认值,不用考虑补0
      // *否则规范化显示考虑补0,从两位数考虑 0012  009 发现 取补完0后的字符串最后几位就好了 字符串原本有两位就从2位置取(起始是0,subtr包含起始位置) 1位也是同理 直接去想我感觉我是想不到这里 我就想直接写-2  -3了 哈哈
      let padZero = k.indexOf('S') > -1 ? '000' : '00'; // *毫秒可以取三位  取两位 
      fmtstr = fmtstr.replace(
        RegExp.$1,
        RegExp.$1.length === 0 ? o[k] : `${padZero}${o[k]}`.substr(('' + o[k]).length, RegExp.$1.length)
      );
    }
  }
  return fmtstr;
}

一般网上搜到的 for循环里

fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));

然而在处理三位数毫秒时001会被处理成01是不准的。

深拷贝

// 获取变量类型 
function getType(el) {
  //? 注意: null 返回 Null
  return Object.prototype.toString.call(el).replace(/^\[object\s*(\S+)\]$/, '$1');
}
/**
 * deepClone 深拷贝
 * @param {object} obj 需要克隆的对象 
 * @param {array} cache 克隆过得对象,解决循环调用的无尽循环
 */
function deepClone (obj, cache = []) {
  // 基本类型
  if (typeof obj !== 'object' || obj === null ) {
    return obj;
  };
  // 对象 === 对象 ? 是的 因为这里拷贝过得的对象存储时是引用关系,所以比较结果为true
  let item = cache.filter(e => e.origin === obj)[0];
  if (item) return item.copy;
  let type = getType(obj);
  let copy = type === 'Array' ? [] : {};
  cache.push({
    origin: obj,
    copy
  });
  switch (type) {
    case 'Error':
      copy = new Error(obj);
      break;
    case 'RegExp':
      copy = new RegExp(obj);
      break;
    case 'Date':
      copy = new Date(obj)
      break;
    case 'Array':
    case 'Object':
      // 对象类型
      for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
          const element = obj[key];
          copy[key] = deepClone(element, cache);
        }
      }
      break;
  
    default:
      break;
  }
  return copy;
}

let cloneObj = deepClone(obj);
obj.number = 77;
obj.obj.s = obj;
// console.log(obj)
console.log(deepClone(obj))

scrollTop 无效?

页面指定DTD,即指定了DOCTYPE时,使用document.documentElement。 页面没有指定DTD,即没指定DOCTYPE时,使用document.body。

DTD 简介 DTD全称为,Document Type Definition,中文翻译为文档类型定义,是一套为了进行程序间的数据交换而建立的关于标记符的语法规则。 文档类型定义(DTD)可定义合法的XML文档构建模块。

document.documentElement.scrollTop = document.body.clientHeight;  //滚动到底

纯函数 (Pure Function)

纯函数是指不依赖于且不改变它作用域之外的变量状态的函数。

Array.from()

Array.from() 方法从一个类似数组可迭代对象中创建一个新的数组实例。

  • Array.from(arrayLike[, mapFn[, thisArg]]) - 第一个必选参数 类数组对象 - 每个元素的回调函数 - this对象
  • 返回新数组

常见用法,不多说:

Array.from('string');     // ["s", "t", "r", "i", "n", "g"]
Array.from({a: 'a', b: 'b'});     // []
Array.from({1: 'a', 2: 'b'});     // []
Array.from([1, 2, 3], x => x + x));          // [2, 4, 6]

Array.from() 可以通过以下方式来创建数组对象:

  • 伪数组对象(拥有一个 length 属性和若干索引属性的任意对象)
  • 可迭代对象(可以获取对象中的元素,如 Map和 Set 等)

拥有一个 length 属性这是我一直忽略的,这一点还是蛮有技巧性的,有时候能简化不少步骤

先看使用中的多种情况:

Array.from({1: 'a', 2: 'b', length: 2});     // [undefined, "a"]
Array.from({1: 'a', 2: 'b', length: 3});     // [undefined, "a", "b"]
Array.from({a: 'a', b: 'b',length: 3});      // [undefined, undefined, undefined]

还算比较智能。

看一下应用,来自Daily-Interview-Question2019-04-16的面试题:

第 55 题:某公司 1 到 12 月份的销售额存在一个对象里面,如下:{1:222, 2:123, 5:888},请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]。

我最开始直接来了个基本的遍历法...还在new Array(13)或者[].length=13

简化版:

// 13长度的数组对应进去保持了key与索引一致,最终去除掉第一个元素就好

Array.from({1:222, 2:123, 5:888, length: 13}).slice(1).map(e => e || null);

let obj = {1:222, 2:123, 5:888};
Array.from({length: 12}).fill(null).map((e, index) => obj[index +1] || null);

相比起来,Array.fill()没有什么容易忽略的地方。

ios浏览器后退页面数据不刷新

场景:在ios(安卓正常 因为安卓不触发BFC)的环境下我调用histroy.go(-1)back()等后退操作(其实是B页面可能有多个前置页面,然后B操作完跳回原页面),页面直接从缓存读取,无刷新,导致数据异常

解决方案:

window.addEventListener('pageshow',function(e){
    // 通过persisted属性判断是否存在 BF Cache

    if(e.persisted) {
      location.reload();
    }
})

原理onpageshow 事件在用户浏览网页时触发。onpageshow 事件类似于 onload 事件,onload 事件在页面第一次加载时触发, onpageshow 事件在每次加载页面时触发,即 onload 事件在页面从浏览器缓存中读取时不触发。

为了查看页面是直接从服务器上载入还是从缓存(指 BFC缓存)中读取,你可以使用 PageTransitionEvent 对象的 persisted 属性来判断。 如果页面从浏览器的缓存中读取该属性返回 ture,否则返回 false

防抖

const debounce = (cb, delay = 1000) => {
  let timer = null;
  return function (...args) {
    const context = this;
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      cb.apply(context, args);
      timer = null;
    }, delay);
  }
}

节流函数(使用时间戳)

const throttleUseTimeStamp = (cb, delay = 1000) => {
  let startTime = Date.now();  
 return function(...args) {      
  const context = this;                        
  const now = Date.now();           
  if (now - startTime >= delay) {
      cb.apply(context, args);
      startTime = Date.now();        
  }
 } 
}

若delay=1000,则在1s内只会执行一次回调函数。

节流函数的实现(使用定时器)

const throttleUseTimer = (cb, delay) => {
  let timer = null;
  return function(...args) {
    const context = this;
    if (!timer) {
      timer = setTimeout(() => {
        cb.apply(context, args);      
        timer = null;      
      }, delay);  
    } 
  }
}

若delay=1000,则在1s内只会执行一次回调函数。

节流函数的实现(第一次触发立即执行,最后一次触发也会执行)

const throttleExecMore = function(cb, delay) {
  let timer = null; 
  let startTime = Date.now();
  return function(...args) {        
    const curTime = Date.now(); 
    const remaining = delay - (curTime - startTime); 
    const context = this;           
    timer && clearTimeout(timer);
    if (remaining <= 0) {
      // 第一次触发执行               
      cb.apply(context, args);              
      startTime = Date.now();         
    } else {
      // 最后一次触发也会执行             
      timer = setTimeout(() => {
        cb.apply(context, args);
        timer = null;
      }, remaining);          
    }
  }
}

第一次触发会立即执行回调函数,最后一次触发也会执行一次回调函数。

react 动态设置 backgroundImage 图片不显示bug

问题描述: 在做宽高都不一定,但有宽高比的图片显示需求时,采取width: 100%;padding-top: ${portion * 100}%;background: url(...)的方案,最终发现图片链接带空格的无法正常显示图片,直接用img标签则正常显示

解决方案:

  1. 渲染的链接地址使用""包裹
 <div
  style={{
      paddingTop: `${100 / (Number(article.articleProportion) || 0.882352)}%`,
      background: `url("${imgList[0] || ImagePlaceHolder}") center center / cover no-repeat`,
        }}
 ></div>

  1. 转义空格
img = img.replace(/\s/g, encodeURIComponent(' '))
$0.style.backgroundImage = `url(${img})`

未完待续