2020-06 前端技术汇总

本文详细介绍了前端开发中的URI编码方法,包括为什么URI不能包含特殊字符,JavaScript中的URI编码差异,以及标准规定。此外,还涵盖了输入法组合文字事件、vconsole工作原理、OBS录制视频全屏问题、数组去重、axios取消请求、前端缓存策略、浏览器缓存、ECharts自定义仪表盘、实时高亮输入内容、Vue自定义指令v-loading的实现、Symbol与Array.prototype.includes在IE中的兼容性问题以及Babel的工作原理。文章内容丰富,涵盖多个前端开发的实用技巧和知识点。
摘要由CSDN通过智能技术生成

2020/06/30 周二

#为什么有效的URI不能包含空格等其他字符,URI编码方法详解

在JS高程3里介绍URI编码方法时,有这样一个描述:有效的URI中不能包含某些字符,比如空格。使用URI编码方法可以把所有无效的字符替换为特殊的utf-8编码,从而让浏览器能够接受和理解

#为什么有效的URI不能包含空格等其他字符?

在《HTTP权威指南》第2章URL与资源 - 各种令人头疼的字符(p38)里有介绍原因:

URL是可移植的(portable),它要统一的命名网上所有的资源,意味着要通过各种不同的协议来传送这些资源,这些协议在传输数据时都会使用不同的机制,在设计URL时,需要满足以下特性:

  1. 可以通过任意协议进行安全的传输:完整/不丢失信息,以SMTP电子邮件的简单邮件传输协议为例,它使用的传输方法会剥去一些特定字符,为了避开这些问题,URL只能使用一些相对较小的、通用的安全字母表中的字符。
  2. URL具有可读性:即使是看不见,不可打印的字符也能在URL中使用,比如空格
  3. 完整性:人们可能会希望URL中可以包含除通用安全字母表之外的二进制数据或字符,因此需要一种转义机制,能够将不安全的字符编码转为安全字符再进行传输
#js中两种URI编码方法的区别

之前有简单说明encodeURIComponent与encodeURI的区别:encodeURI只转义空格,encodeURIComponent会转义所有的非字母数字字符

其实上面的说法是错误的,在mdn的一个示例中,有更加详细说明两者的区别

// encodeURI vs encodeURIComponent
// encodeURI() differs from encodeURIComponent() as follows:
var set1 = ";,/?:@&=+$#"; // Reserved Characters
var set2 = "-_.!~*'()";   // Unreserved Marks
var set3 = "ABC abc 123"; // Alphanumeric Characters + Space  [ˌælfənjuːˈmerɪk] 字母数字 + 空格

console.log(encodeURI(set1)); // ;,/?:@&=+$#
console.log(encodeURI(set2)); // -_.!~*'()
console.log(encodeURI(set3)); // ABC%20abc%20123 (the space gets encoded as %20)

console.log(encodeURIComponent(set1)); // %3B%2C%2F%3F%3A%40%26%3D%2B%24%23
console.log(encodeURIComponent(set2)); // -_.!~*'()
console.log(encodeURIComponent(set3)); // ABC%20abc%20123 (the space gets encoded as %20)

上面的例子中可以看到,URI编码方法一般有3种类型的字符

字符类型(中) 字符类型(英) 包含 encodeURI encodeURIComponent
URI保留字符 Reserved Characters “;,/?😡&=+$#” 不转义 转义(escaped)
非转义字符 Unreserved Marks or Alphanumeric Characters "-_.!~*’()"以及数字(0-9)、字母(a-zA-Z) 不转义 不转义
空格等其他字符/中文等 space or other character etc " 中文?"等其他字符 转义 转义

通过上面的表格我们可以看到

  • encodeURI不会编码URI保留字符/非转义字符,只会对空格等其他字符(如中文字符,中文等)进行编码
  • encodeURIComponent不会编码非转义字符,URI保留字符和其他字符都会编码

综上,encodeURI与encodeURIComponent的区别是:encodeURIComponent会对URI保留字符进行编码,而encodeURI则不会,其他逻辑基本一致

#标准

下面来看看ECMA262标准/规范(specification)对encodeURI()的定义(definition)

18.2.6.4 encodeURI ( uri )
The encodeURI function computes a new version of a UTF-16 encoded (6.1.4) URI in which each instance of certain code points is replaced by one, two, three, or four escape sequences representing the UTF-8 encoding of the code points.

The encodeURI function is the %encodeURI% intrinsic object. When the encodeURI function is called with one argument uri, the following steps are taken:

  1. Let uriString be ? ToString(uri).
  2. Let unescapedURISet be a String containing one instance of each code unit valid in uriReserved and uriUnescaped  
     plus "#".
  3. Return ? Encode(uriString, unescapedURISet).

NOTE
The code point # is not encoded to an escape sequence even though it is not a reserved or unescaped URI code point.

Runtime Semantics: Encode(string, unescapedSet)

The abstract operation Encode takes arguments string (a String) and unescapedSet (a String). It performs URI encoding and escaping. It performs the following steps when called:

1. Let strLen be the number of code units in string.
2. Let R be the empty String.
3. Let k be 0.
4. Repeat,
     a. If k equals strLen, return R.
     b. Let C be the code unit at index k within string.
     c. If C is in unescapedSet, then
          i. Set k to k + 1.
          ii. Set R to the string-concatenation of the previous value of R and C.
     d. Else,
          i. Let cp be ! CodePointAt(string, k).
          ii. If cp.[[IsUnpairedSurrogate]] is true, throw a URIError exception.
          iii. Set k to k + cp.[[CodeUnitCount]].
          iv. Let Octets be the List of octets resulting by applying the UTF-8 transformation to cp.[[CodePoint]].
          v. For each element octet of Octets in List order, do
               1. Set R to the string-concatenation of:
                   - the previous value of R
                   - "%"
                   - the String representation of octet, formatted as a two-digit uppercase hexadecimal number, padded to the left with a zero if necessary

以上,在总结某个知识点的过程中,我们可以发现介绍js相关知识点的平台很多,我们可以大致的判断出文档的详细程度如下:

菜鸟教程/w3school等 <= JS高程3等技术类书籍 <= MDN <= tc39 ECMA标准文档

js相关知识基本都是对API、tc39 ECMA标准文档的总结、梳理。区别在于:

  1. 整理的意图:是仅仅展示API调用,还是介绍原理(简单说明/详细介绍)
  2. 示例demo是否丰富?demo是否可以让人很好的理解知识点?

参考

#2020/06/28 周天

#输入法组合文字事件compositionstart等不能用on监听

今天用原生的js来写demo时发现使用oncompositoinstart无法监听到输入法组合文件的过程,后面替换为addEventListener就可以了。因此对于输入法组合文字过程事件必须要使用DOM2级事件监听

<body>
  <input id="input"></input>
  <script>
    let input = document.querySelector('#input')
    console.log('input', input)
    let composition = false

    // input.addEventListener('compositionstart', (e) => {
    //   console.log('oncompositionstart')
    //   composition = true
    // });
    // input.addEventListener('compositionend', (e) => {
    //   console.log('oncompositionend')
    //   composition = false
    // });

    input.oncompositionstart = (e) => {
      console.log('oncompositionstart', composition)
      composition = true
    }

    input.oncompositionend = (e) => {
      console.log('oncompositionend', composition)
      composition = false
    }
  </script>
</body>

#2020/06/26 周五

#为什么vconsole直接new一下就能引入,而且可以显示网络请求,console信息

在移动端真机调试时,一般会用到vconsole,那你会发现在vue中vconsole的引入非常简单,只需要在main.js里面引入,并new一下。相比其他组件需要Vue.use引入来说,会很迷惑,这个是怎么引入到项目的?页面底部时怎么显示vconsole的dom的?

// main.js
import VConsole from "vconsole";
new VConsole();

下面我们看看vconsole的源码,看看到底是怎么实现的:

入口在 src/core/core.js,有一个VConsole的class,可以看到这里用了单例模式,只允许有一个vconsole

// src/core/core.js
// ...
class VConsole {

  constructor(opt) {
    if (!!$.one(VCONSOLE_ID)) {
      console.debug('vConsole is already exists.');
      return;
    }
    // ....
// ...

Log、System、Network、Element、Storage都是单独的模块

// src/core/core.js
// ...
// built-in plugins
import VConsolePlugin from '../lib/plugin.js';
import VConsoleLogPlugin from '../log/log.js';
import VConsoleDefaultPlugin from '../log/default.js';
import VConsoleSystemPlugin from '../log/system.js';
import VConsoleNetworkPlugin from '../network/network.js';
import VConsoleElementPlugin from '../element/element.js';
import VConsoleStoragePlugin from '../storage/storage.js';
// ...

constructor 里面挂载dom的方法如下,直接监听window的事件,当确定页面加载ok后,再将vconsole相关dom挂载上去

// src/core/core.js
// ...
// try to init
let _onload = function() {
  if (that.isInited) {
    return;
  }
  that._render();
  that._mockTap();
  that._bindEvent();
  that._autoRun();
};
if (document !== undefined) {
  if (document.readyState === 'loading') {
    $.bind(window, 'DOMContentLoaded', _onload);
  } else {
    _onload();
  }
} else {
  // if document does not exist, wait for it
  let _timer;
  let _pollingDocument = function() {
    if (!!document && document.readyState == 'complete') {
      _timer && clearTimeout(_timer);
      _onload();
    } else {
      _timer = setTimeout(_pollingDocument, 1);
    }
  };
  _timer = setTimeout(_pollingDocument, 1);
}

下面看看console是怎么拦截的,通过下面的代码我们可以看到重写了window.console.log/info/warn等方法,改为执行自己的printLog方法,用于记录log,并渲染到vconsole面板里

// src/log/log.js
// ...
/**
 * replace window.console with vConsole method
 * @private
 */
mockConsole() {
  const that = this;
  const methodList = ['log', 'info', 'warn', 'debug', 'error'];

  if (!window.console) {
    window.console = {};
  } else {
    methodList.map(function (method) {
      that.console[method] = window.console[method];
    });
    that.console.time = window.console.time;
    that.console.timeEnd = window.console.timeEnd;
    that.console.clear = window.console.clear;
  }

  methodList.map(method => {
    window.console[method] = (...args) => {
      this.printLog({
        logType: method,
        logs: args,
      });
    };
  });

当vconsole remove后,还原现场

// src/log/log.js
// ...
/**
 * before remove
 * @public
 */
onRemove() {
  window.console.log = this.console.log;
  window.console.info = this.console.info;
  window.console.warn = this.console.warn;
  window.console.debug = this.console.debug;
  window.console.error = this.console.error;
  window.console.time = this.console.time;
  window.console.timeEnd = this.console.timeEnd;
  window.console.clear = this.console.clear;
  this.console = {};

  const idx = ADDED_LOG_TAB_ID.indexOf(this.id);
  if (idx > -1) {
    ADDED_LOG_TAB_ID.splice(idx, 1);
  }
}

我们再来看看Network相关,也是重写了原生的window.XMLHttpRequest.prototype.open/send等方法,在里面加入了拦截逻辑

// src/network/network.js
/**
 * mock ajax request
 * @private
 */
mockAjax() {
  let _XMLHttpRequest = window.XMLHttpRequest;
  if (!_XMLHttpRequest) { return; }

  let that = this;
  let _open = window.XMLHttpRequest.prototype.open,
      _send = window.XMLHttpRequest.prototype.send;
  that._open = _open;
  that._send = _send;

  // mock open()
  window.XMLHttpRequest.prototype.open = function() {
    let XMLReq = this;
    let args = [].slice.call(arguments),
  // ...
  // mock send()
  window.XMLHttpRequest.prototype.send = function() {
    let XMLReq = this;
    let args = [].slice.call(arguments),
        data = args[0];

综上,我们大致知道vconsole的套路了

  1. 通过window监听页面加载,加载ok后向页面追加vconsle相关的dom
  2. 像log、network等相关的渲染显示,都是通过重写window下对应的系统方法来加入一些自定义操作

完整源码可以先npm install vconsole后再在node_modules里面查看,或者在github上看 vconsole - github(opens new window)

#obs录制视频不是全屏的问题

解决方法:打开OBS => 点击顶部 “编辑” 按钮 => 选择 “变换” => 点击比例适配屏幕

#2020/06/21 周日

#视频拖动到imovie时间轴后,屏幕上方和下方被截断了

注意这种情况,需要在时间轴,选中视频,然后在预览器,点击 “全部还原”,就可以了。

#2020/06/19 周五

#数组去重,去掉id重复的元素

有一个需求,客户信息列表,需要去除重复的客户。于是想着怎么写去重的逻辑,可以思考下

let customerList = [ 
  { id: '1', info: 'xxx' },
  { id: '3', info: 'xxx' },
  { id: '1', info: 'xxx' },
  { id: '2', info: 'xxx' },
  { id: '3', info: 'xxx' },
]

// 去重
let tempSet = new Set()
let newList = customerList.filter(item => {
  if (tempSet.has(item.id)) {
    return false
  }
  tempSet.add(item.id)
  return true
})
console.log(newList)
// [
//   { id: '1', info: 'xxx' },
//   { id: '3', info: 'xxx' },
//   { id: '2', info: 'xxx' },
// ]

#2020/06/17 周三

#axios取消请求,以及具体使用场景

以下是两个会用到取消当前请求的场景

  1. tab切换刷新某个列表数据数据导致数据错乱的问题
  2. 导出文件或下载文件时,中途取消
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

// get 方法 假设这个接口需要50s才返回
axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

// post 方法时 假设这个接口需要50s才返回
// axios.post('/user/12345', {
//   name: 'new name'
// }, {
//   cancelToken: source.token
// })

// 3庙后取消请求
setTimeout(() => {
  // cancel the request (the message parameter is optional)
  source.cancel('Operation canceled by the user.');
}, 3000)

参考:axios cancellation | github(opens new window)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值