FisherKai

FisherKai 查看完整档案

上海编辑湖北大学  |  计算机科学与技术 编辑兴业数字金融服务有限公司  |  前端工程师 编辑填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

FisherKai 赞了文章 · 10月30日

webpack、gulp、rollup、tsc/babel 使用对比

本文档主要介绍四种工具的特点, 包括优点、缺点、 输入、输出、能够处理的文件类型,针对不同文件类型的处理方式, 以及其适用场景。

Rollup

简介

Rollup 是一个模块打包工具, 可以将我们按照 ESM (ES2015 Module) 规范编写的源码构建输出如下格式:

  • IIFE: 自执行函数, 可通过 <script> 标签加载
  • AMD: 通过 RequireJS 加载
  • CommonJS: Node 默认的模块规范, 可通过 Webpack 加载
  • UMD: 兼容 IIFE, AMD, CJS 三种模块规范
  • ESM: ES2015 Module 规范, 可用 Webpack, Rollup 加载

优点:

支持动态导入。

支持tree shaking。仅加载模块里用得到的函数以减小文件大小。

Scope Hoisting。 rollup可以将所有小文件生成到一个大文件中,所有代码都在同一个函数作用域里:, 不会像 Webpack 那样用很多函数来包装模块。

没有其他冗余代码, 执行很快。除了必要的 cjs, umd 头外,bundle 代码基本和源码差不多,也没有奇怪的 __webpack_require__, Object.defineProperty 之类的东西,

缺点:

不支持热更新功能;对于commonjs模块,需要额外的插件将其转化为es2015供rollup 处理;无法进行公共代码拆分。

输入:

options.input  单/多文件入口点

输出:

rollup支持生成 iife、cjs、amd 、esm、umd格式的文件; 单/多js文件输出

文件资源处理: 

rollup 通过插件来编译处理各类静态资源:

  • rollup-plugin-typescript2
  • rollup-plugin-babel
  • rollup-plugin-uglify
  • rollup-plugin-commonjs
  • rollup-plugin-postcss
  • rollup-plugin-img
  • rollup-plugin-json

基本使用参考

 https://www.cnblogs.com/tugenhua0707/p/8179686.html

适用场景:

由纯js开发的第三方库; 需要生成单一的umd文件的场景

案例:

纯js/ts编写的第三方库:

React、Vue

UI组件库 evergreen

使用 babel 将 js/ts 编译成  esm 和 cjs 格式的模块文件, 使用 rollup 将库打包成  umd 格式的 evergreen.min.js 和 evergreen.js ,  打包出来的代码比较干净。

gulp

简介

前端构建工具,gulp是基于Nodejs,自动化地完成 javascript、coffee、sass、less、html/image、css 等文件的测试、检查、合并、压缩、格式化、浏览器自动刷新、部署文件生成,并监听文件在改动后重复指定的这些步骤。

借鉴了Unix操作系统的管道(pipe)思想,前一级的输出,直接变成后一级的输入,使得在操作上非常简单。

gulp基于流式操作,通过各种 Transform Stream 来实现文件不断处理 输出。

优点:

gulp文档简单,学习成本低,使用简单;对大量源文件可以进行流式处理,借助插件,可以对文件类型进行多种操作处理。

缺点

不支持tree-shaking、热更新、代码分割等。 gulp 对 js 模块化方案无能为力,只是对静态资源做流式处理,处理之后并未做有效的优化整合。

输入:

输入(gulp.src) js,ts,scss,less 等源文件

输出:

对输入源文件依次执行打包(bundle)、编译(compile)、压缩、重命名等处理后输出(gulp.dest)到指定目录中去

适用场景:

静态资源密集操作型场景,主要用于css、图片等静态资源的处理操作。

文件处理:

gulp通过各种中间件处理静态资源的编译:

案例:

antd

gulp + webpack + tsc / babel

gulp的作用主要是打包流程管理, 拷贝文件(less/ts/ts类型声明文件),处理less, 拷贝并转译less 为css。

tsc及babel 则用于转译 静态ts文件, 逐个输出到指定目录es/lib目录下

webpack主要用于模块化处理,将打包后的模块编译到 dist下的  antd.js   antd.min.js 以及及其他css文件等。

Webpack

简介:

Webpack 是一种前端资源模块化管理和打包 工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分割,等到实际需要的时候再异步加载。

优点:

基本之前gulp 可以进行的操作处理,现在webpack也都可以做。同时支持热更新,支持tree shaking 、Scope Hoisting、动态加载、代码拆分、文件指纹、代码压缩、静态资源处理等,支持多种打包方式。(优点有很多,在这不做过多赘述)

缺点:

不支持 打包出esm格式的代码 (打包后的代码再次被引用时tree shaking 困难), 打包后亢余代码多,配置较为复杂。

输入:

入口文件 js/ts

输出

js、css、 img等静态资源文件

适用场景:

应用程序开发

案例:

react-bootstrap

react-bootstrap 使用babel进行tsx文件的编译,并且按照原有目录输出到 lib esm/cjs目录下;

同时使用shell 工具 拷贝 TS类型声明文件 到对应目录;

对于umd文件,则采用webpack打包生成了  react-bootstrap.min.js 及 react-bootstrap.js 输出到dist下。

打包umd方式非常简单,但文件中保留了许多webpack使用的到的冗余代码。生成效果不如上述 的 evergreen 纯净。

tsc / babel

简介

tsc/babel 可以将 ts 代码编译 js 代码。支持编译成 esm、cjs、amd 格式的文件

优点:

编译速度快,可以保留原有的目录相对位置,分目录保存各模块的代码,便于按需引用加载;

缺点:

只对语言本身进行编译转换,不支持tree shaking 等高级功能。

输入:

ts/js 文件

输出:

ts/ts对应的js文件,且一一对应

案例分析:

tsc/babel常与其他工具配合使用

打包使用方式推荐

第三方js类库:

  1.  rollup + 插件 (推荐)
  2.  babel/tsc + uglifyjs
  3.  webpack

UI类库开发(按需加载)

生成esm   tsc/babel  + gulp

生成cjs     tsc/babel + gulp

生成umd   rollup (js + css的合并文件)

开发应用程序

webpack + loader + plugin

上述打包方式各有其特点,根据当前需求及开发便利,酌情选择打包编译方式。

查看原文

赞 1 收藏 1 评论 0

FisherKai 收藏了文章 · 10月30日

webpack、gulp、rollup、tsc/babel 使用对比

本文档主要介绍四种工具的特点, 包括优点、缺点、 输入、输出、能够处理的文件类型,针对不同文件类型的处理方式, 以及其适用场景。

Rollup

简介

Rollup 是一个模块打包工具, 可以将我们按照 ESM (ES2015 Module) 规范编写的源码构建输出如下格式:

  • IIFE: 自执行函数, 可通过 <script> 标签加载
  • AMD: 通过 RequireJS 加载
  • CommonJS: Node 默认的模块规范, 可通过 Webpack 加载
  • UMD: 兼容 IIFE, AMD, CJS 三种模块规范
  • ESM: ES2015 Module 规范, 可用 Webpack, Rollup 加载

优点:

支持动态导入。

支持tree shaking。仅加载模块里用得到的函数以减小文件大小。

Scope Hoisting。 rollup可以将所有小文件生成到一个大文件中,所有代码都在同一个函数作用域里:, 不会像 Webpack 那样用很多函数来包装模块。

没有其他冗余代码, 执行很快。除了必要的 cjs, umd 头外,bundle 代码基本和源码差不多,也没有奇怪的 __webpack_require__, Object.defineProperty 之类的东西,

缺点:

不支持热更新功能;对于commonjs模块,需要额外的插件将其转化为es2015供rollup 处理;无法进行公共代码拆分。

输入:

options.input  单/多文件入口点

输出:

rollup支持生成 iife、cjs、amd 、esm、umd格式的文件; 单/多js文件输出

文件资源处理: 

rollup 通过插件来编译处理各类静态资源:

  • rollup-plugin-typescript2
  • rollup-plugin-babel
  • rollup-plugin-uglify
  • rollup-plugin-commonjs
  • rollup-plugin-postcss
  • rollup-plugin-img
  • rollup-plugin-json

基本使用参考

 https://www.cnblogs.com/tugenhua0707/p/8179686.html

适用场景:

由纯js开发的第三方库; 需要生成单一的umd文件的场景

案例:

纯js/ts编写的第三方库:

React、Vue

UI组件库 evergreen

使用 babel 将 js/ts 编译成  esm 和 cjs 格式的模块文件, 使用 rollup 将库打包成  umd 格式的 evergreen.min.js 和 evergreen.js ,  打包出来的代码比较干净。

gulp

简介

前端构建工具,gulp是基于Nodejs,自动化地完成 javascript、coffee、sass、less、html/image、css 等文件的测试、检查、合并、压缩、格式化、浏览器自动刷新、部署文件生成,并监听文件在改动后重复指定的这些步骤。

借鉴了Unix操作系统的管道(pipe)思想,前一级的输出,直接变成后一级的输入,使得在操作上非常简单。

gulp基于流式操作,通过各种 Transform Stream 来实现文件不断处理 输出。

优点:

gulp文档简单,学习成本低,使用简单;对大量源文件可以进行流式处理,借助插件,可以对文件类型进行多种操作处理。

缺点

不支持tree-shaking、热更新、代码分割等。 gulp 对 js 模块化方案无能为力,只是对静态资源做流式处理,处理之后并未做有效的优化整合。

输入:

输入(gulp.src) js,ts,scss,less 等源文件

输出:

对输入源文件依次执行打包(bundle)、编译(compile)、压缩、重命名等处理后输出(gulp.dest)到指定目录中去

适用场景:

静态资源密集操作型场景,主要用于css、图片等静态资源的处理操作。

文件处理:

gulp通过各种中间件处理静态资源的编译:

案例:

antd

gulp + webpack + tsc / babel

gulp的作用主要是打包流程管理, 拷贝文件(less/ts/ts类型声明文件),处理less, 拷贝并转译less 为css。

tsc及babel 则用于转译 静态ts文件, 逐个输出到指定目录es/lib目录下

webpack主要用于模块化处理,将打包后的模块编译到 dist下的  antd.js   antd.min.js 以及及其他css文件等。

Webpack

简介:

Webpack 是一种前端资源模块化管理和打包 工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分割,等到实际需要的时候再异步加载。

优点:

基本之前gulp 可以进行的操作处理,现在webpack也都可以做。同时支持热更新,支持tree shaking 、Scope Hoisting、动态加载、代码拆分、文件指纹、代码压缩、静态资源处理等,支持多种打包方式。(优点有很多,在这不做过多赘述)

缺点:

不支持 打包出esm格式的代码 (打包后的代码再次被引用时tree shaking 困难), 打包后亢余代码多,配置较为复杂。

输入:

入口文件 js/ts

输出

js、css、 img等静态资源文件

适用场景:

应用程序开发

案例:

react-bootstrap

react-bootstrap 使用babel进行tsx文件的编译,并且按照原有目录输出到 lib esm/cjs目录下;

同时使用shell 工具 拷贝 TS类型声明文件 到对应目录;

对于umd文件,则采用webpack打包生成了  react-bootstrap.min.js 及 react-bootstrap.js 输出到dist下。

打包umd方式非常简单,但文件中保留了许多webpack使用的到的冗余代码。生成效果不如上述 的 evergreen 纯净。

tsc / babel

简介

tsc/babel 可以将 ts 代码编译 js 代码。支持编译成 esm、cjs、amd 格式的文件

优点:

编译速度快,可以保留原有的目录相对位置,分目录保存各模块的代码,便于按需引用加载;

缺点:

只对语言本身进行编译转换,不支持tree shaking 等高级功能。

输入:

ts/js 文件

输出:

ts/ts对应的js文件,且一一对应

案例分析:

tsc/babel常与其他工具配合使用

打包使用方式推荐

第三方js类库:

  1.  rollup + 插件 (推荐)
  2.  babel/tsc + uglifyjs
  3.  webpack

UI类库开发(按需加载)

生成esm   tsc/babel  + gulp

生成cjs     tsc/babel + gulp

生成umd   rollup (js + css的合并文件)

开发应用程序

webpack + loader + plugin

上述打包方式各有其特点,根据当前需求及开发便利,酌情选择打包编译方式。

查看原文

FisherKai 收藏了文章 · 10月9日

前端实用小工具(URL参数截取、JSON判断、数据类型检测、版本号对比等)

背景

在日常开发中,我们经常会用一些工具类方法来实现业务逻辑 下面列举几种最常用的

URL截取参数

//直接调用输入想要截取的参数名称几个
export function getParamFromUrl(key) {
    if (key === undefined) return null;
    let search = location.search.substr(1);
    let mReg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)');
    let mValue = search.match(mReg);
    if (mValue != null) return unescape(mValue[2]);
    return null;
}
//示例
let city = getParamFromUrl('city');

JSON是否为空判断

//输入想要检测的json数据 如果为空返回返回false
export function isNullObject(model) {
  if (typeof model === "object") {
    let hasProp = false;
    for (const prop in model) {
        hasProp = true;
        break;
    }
    if (hasProp) {
        return false;
    }
    return true;
  } else {
      throw "model is not object";
  }
}

image-20200929171431032

数据类型检测

//检测变量的数据类型
export function getParamType(item) {
    if (item === null) return null;
    if (item === undefined) return undefined;
    return Object.prototype.toString.call(item).slice(8, -1);
}
//返回String Function Boolean Object Number

image-20200929171150164

获取cookie

//获取document下cookie的具体某个参数值
export function getCookie(key) {
    if (key === undefined) {
        return undefined;
    }
    let cookies = document.cookie;
    let mReg = new RegExp('(^|;)\\s*' + key + '=([^;]*)(;|$)');
    let mValue = cookies.match(mReg);
    let ret = undefined;
    if (mValue != null) {
        ret = unescape(mValue[2]);
    }
    if (ret !== undefined) {
        ret = ret.replace(/^\"|\'/i, '').replace(/\"|\'$/i, '');
    }
    return ret;
}

image-20200930103240035

版本号对比

一般在做APP端开发的时候需要用到一些版本控制,那么就需要针对版本号来进行对比,高版本或者低版本做一些特殊的逻辑处理,下面就是提供版本对比的方法

//传入要对比的版本号,一般前面一个传入当前的版本号,后面一个写上要对比的版本号
export function versionCompare(higher, lower) {
    let sep = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '.';

    let higherAry = higher.split(sep),
        lowerAry = lower.split(sep);
    let l = Math.max(higherAry.length, lowerAry.length);
    for (let i = 0; i < l; i++) {
        let high = parseInt(higherAry[i] || 0);
        let low = parseInt(lowerAry[i] || 0);
        if (high > low) {
            return 1;
        }
        if (high < low) {
            return -1;
        }
    }
    return 0;
}
//返回值  higher > lower: 1;higher = lower: 0;higher < lower:-1

image-20200930103754427

数组去重

export function arrayUniq(array){
    let temp = []; 
    for(var i = 0; i < array.length; i++){
        if(temp.indexOf(array[i]) == -1){
            temp.push(array[i]);
        }
    }
    return temp;
}

image-20200930104914363

iPhone X系列机型判断

export function isIphoneX() {
    // iPhone X、iPhone XS
    var isIPhoneX =
        /iphone/gi.test(window.navigator.userAgent) &&
        window.devicePixelRatio &&
        window.devicePixelRatio === 3 &&
        window.screen.width === 375 &&
        window.screen.height === 812;
    // iPhone XS Max
    var isIPhoneXSMax =
        /iphone/gi.test(window.navigator.userAgent) &&
        window.devicePixelRatio &&
        window.devicePixelRatio === 3 &&
        window.screen.width === 414 &&
        window.screen.height === 896;
    // iPhone XR
    var isIPhoneXR =
        /iphone/gi.test(window.navigator.userAgent) &&
        window.devicePixelRatio &&
        window.devicePixelRatio === 2 &&
        window.screen.width === 414 &&
        window.screen.height === 896;
    if (isIPhoneX || isIPhoneXSMax || isIPhoneXR) {
        return true;
    }
    return false;
}
查看原文

FisherKai 发布了文章 · 9月18日

保姆级的使用angular搭建自己的组件库

如果使用npm install 就可以安装自定义的组件库呢

1、 ng new projectname

此处的projectname为项目名称,我创建的为gram-angular2

2、 以创建一个gram.module和header.component为例。可以用cli命令或者手动创建

ng generate module modules/gram
ng generate component modules/header

3、在gram.module.ts添加内容

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HeaderComponent } from '../header/header.component';

@NgModule({
 declarations: [
 HeaderComponent  //这里改动了
 ],
 imports: [
 CommonModule
 ],
 exports: [
 HeaderComponent   //这里改动了
 ]
})
export class GramModule { }

exports确保将该模块中导出的组件可以被其它模块引入并使用。

4、添加GramModule到app.module中:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { GramModule } from './modules/gram/gram.module';

@NgModule({
 declarations: [
 AppComponent,
 ],
 imports: [
 BrowserModule,
 GramModule   //这里改动了
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

此时,就可以在app.component.html使用HeaderComponent组件了:

<app-header></app-header>

ng-packagr

ng-packagr可以将ng项目编译并打包成一个umd规范的library,以便可以被其它的ng项目所使用。

5、安装ng-packagr:

npm install ng-packagr --save-dev 

在项目根目录下添加两个文件 ng-package.json 和 public_api.ts。

ng-package.json内容:

{
 "$schema": "./node_modules/ng-packagr/ng-package.schema.json",
 "lib": {
 "entryFile": "public_api.ts"
 }
}

在public_api.ts中导出header.module.ts:

export * from './src/app/modules/header/header.module'

在package.json文件中添加packagr脚本命令,并将private属性设置为false:

"scripts": {
 "ng": "ng",
 "start": "ng serve",
 "build": "ng build",
 "test": "ng test",
 "lint": "ng lint",
 "e2e": "ng e2e",
 "packagr": "ng-packagr -p ng-package.json"
},
"private": false

删除掉dependencies没有使用的依赖

7、运行packagr脚本命令

npm run packagr

结束后会生成一个dist文件夹,就是我们需要的library包了。还可以进一步将dist打包成tgz文件:

cd dist
npm pack

dist文件夹里就会多出一个ng-packagr-test-0.0.0.tgz,名称和版本号均取自package.json。

自此,我们就可以通过磁盘相对路径来安装自己的library了,如:

npm install ../../program-name/dist/gram-angular2-0.0.0.tgz  

8、通过自己的npm账号发布到npmjs,前提是确保包名是唯一的:

npm publish dist

查看原文

赞 2 收藏 2 评论 0

FisherKai 收藏了文章 · 9月14日

API设计进阶:通过Node和Redis进行API速率限制

速率限制可以保护和提高基于API的服务的可用性。如果你正在与一个API对话,并收到HTTP 429 Too Many Requests的响应状态码,说明你已经被速率限制了。这意味着你超出了给定时间内允许的请求数量。你需要做的就是放慢脚步,稍等片刻,然后再试一次。

为什么要速率限制?

当你考虑限制你自己的基于API的服务时,你需要在用户体验、安全性和性能之间进行权衡。

控制数据流的最常见原因是保持基于API的服务的可用性。但也有安全方面的好处,一次无意或有意的入站流量激增,就会占用宝贵的资源,影响其他用户的可用性。

通过控制传入请求的速率,你可以:

  • 保障服务和资源不被“淹没”。
  • 缓和暴力攻击
  • 防止分布式拒绝服务(DDOS)攻击

如何实施限速?

速率限制可以在客户端级别,应用程序级别,基础架构级别或介于两者之间的任何位置实现。有几种方法可以控制API服务的入站流量:

  • 按用户:跟踪用户使用API密钥、访问令牌或IP地址进行的调用
  • 按地理区域划分:例如降低每个地理区域在一天的高峰时段的速率限制
  • 按服务器:如果你有多个服务器处理对API的不同调用,你可能会对访问更昂贵的资源实施更严格的速率限制。

你可以使用这些速率限制中的任何一种(甚至组合使用)。

无论你选择如何实现,速率限制的目标都是建立一个检查点,该检查点拒绝或通过访问你的资源的请求。许多编程语言和框架都有实现这一点的内置功能或中间件,还有各种速率限制算法的选项。

这是使用Node和Redis制作自己的速率限制器的一种方法:

  1. 创建一个Node应用
  2. 使用Redis添加速率限制器
  3. 在Postman中测试
💻 在GitHub上查看代码示例。

在开始之前,请确保已在计算机上安装了Node和Redis。

步骤1:建立Node应用程序

从命令行设置一个新的Node应用。通过CLI提示,或添加 —yes 标志来接受默认选项。

$ npm init --yes

如果在项目设置过程中接受了默认选项,则为入口点创建一个名为 index.js 的文件。

$ touch index.js

安装Express Web框架,然后在 index.js 中初始化服务器。

const express = require('express')
const app = express()
const port = process.env.PORT || 3000

app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

从命令行启动服务器。

$ node index.js

回到 index.js 中,创建一个路由,先检查速率限制,如果用户没有超过限制再允许访问资源。

app.post('/', async (req, res) => {
  async function isOverLimit(ip) {
    // to define
  }
  // 检查率限制
  let overLimit = await isOverLimit(req.ip)
  if (overLimit) {
    res.status(429).send('Too many requests - try again later')
    return
  }
  // 允许访问资源
  res.send("Accessed the precious resources!")
})

应用级速率限制

在下一步中,我们将定义速率限制器函数 isOverLimit

步骤2:使用Redis添加速率限制器

Redis是一个内存中键值数据库,因此它可以非常快速地检索数据。使用Redis实施速率限制也非常简单。

  • 存储一个像用户IP地址一样的key。
  • 增加从该IP发出的调用数量
  • 在指定时间段后使记录过期

下图所示的限速算法是一个滑动窗口计数器的例子。一个用户如果提交的调用数量适中,或者随着时间的推移将它们分隔开,就永远不会达到速率限制。超过10秒窗口内最大请求的用户必须等待足够的时间来恢复其请求。

限速算法:滑动窗口计数器

从命令行为Node安装一个名为ioredis的Redis客户端。

$ npm install ioredis

在本地启动Redis服务器。

$ redis-server

然后在 index.js 中要求并初始化Redis客户端。

const redis = require('ioredis')
const client = redis.createClient({
  port: process.env.REDIS_PORT || 6379,
  host: process.env.REDIS_HOST || 'localhost',
})
client.on('connect', function () {
  console.log('connected');
});

定义我们上一步开始写的isOverLimit函数,按照Redis的这个模式,按照IP来保存一个计数器。

async function isOverLimit(ip) {
  let res
  try {
    res = await client.incr(ip)
  } catch (err) {
    console.error('isOverLimit: could not increment key')
    throw err
  }
  console.log(`${ip} has value: ${res}`)
  if (res > 10) {
    return true
  }
  client.expire(ip, 10)
}

这就是速率限制器。

当用户调用API时,我们会检查Redis以查看该用户是否超出限制。如果是这样,API将立即返回HTTP 429状态代码,并显示消息 Too many requests — try again later 。如果用户在限制之内,我们将继续执行下一个代码块,在该代码块中,我们可以允许访问受保护的资源(例如数据库)。

在进行速率限制检查期间,我们在Redis中找到用户的记录,并增加其请求计数,如果Redis中没有该用户的记录,那么我们将创建一个新记录。最后,每条记录将在最近一次活动的10秒内过期。

在下一步中,请确保我们的限速器正常运行。

步骤3:在Postman中进行测试

保存更改,然后重新启动服务器。我们将使用Postman将 POST 请求发送到我们的API服务器,该服务器在本地运行,网址为 http:// localhost:3000

在速率限制内

继续快速连续发送请求以达到你的速率限制。

超过速率限制-HTTP 429请求过多

关于限速的最终想法

这是Node和Redis的速率限制器的简单示例,这只是开始。有一堆策略和工具可以用来架构和实现你的速率限制。而且还有其他的增强功能可以通过这个例子来探索,比如:

  • 在响应正文或作为 Retry-after 标头中,让用户知道在重试之前应该等待多少时间
  • 记录达到速率限制的请求,以了解用户行为并警告恶意攻击
  • 尝试使用其他速率限制算法或其他中间件

请记住,当你研究API限制时,你是在性能、安全性和用户体验之间进行权衡。你理想的速率限制解决方案将随着时间的推移而改变,同时也会考虑到这些因素。


微信搜索【前端全栈开发者】关注这个脱发、摆摊、卖货、持续学习的程序员的,第一时间阅读最新文章,会优先两天发表新文章。关注即可大礼包,准能为你节省不少钱!

image

查看原文

FisherKai 赞了文章 · 9月14日

API设计进阶:通过Node和Redis进行API速率限制

速率限制可以保护和提高基于API的服务的可用性。如果你正在与一个API对话,并收到HTTP 429 Too Many Requests的响应状态码,说明你已经被速率限制了。这意味着你超出了给定时间内允许的请求数量。你需要做的就是放慢脚步,稍等片刻,然后再试一次。

为什么要速率限制?

当你考虑限制你自己的基于API的服务时,你需要在用户体验、安全性和性能之间进行权衡。

控制数据流的最常见原因是保持基于API的服务的可用性。但也有安全方面的好处,一次无意或有意的入站流量激增,就会占用宝贵的资源,影响其他用户的可用性。

通过控制传入请求的速率,你可以:

  • 保障服务和资源不被“淹没”。
  • 缓和暴力攻击
  • 防止分布式拒绝服务(DDOS)攻击

如何实施限速?

速率限制可以在客户端级别,应用程序级别,基础架构级别或介于两者之间的任何位置实现。有几种方法可以控制API服务的入站流量:

  • 按用户:跟踪用户使用API密钥、访问令牌或IP地址进行的调用
  • 按地理区域划分:例如降低每个地理区域在一天的高峰时段的速率限制
  • 按服务器:如果你有多个服务器处理对API的不同调用,你可能会对访问更昂贵的资源实施更严格的速率限制。

你可以使用这些速率限制中的任何一种(甚至组合使用)。

无论你选择如何实现,速率限制的目标都是建立一个检查点,该检查点拒绝或通过访问你的资源的请求。许多编程语言和框架都有实现这一点的内置功能或中间件,还有各种速率限制算法的选项。

这是使用Node和Redis制作自己的速率限制器的一种方法:

  1. 创建一个Node应用
  2. 使用Redis添加速率限制器
  3. 在Postman中测试
💻 在GitHub上查看代码示例。

在开始之前,请确保已在计算机上安装了Node和Redis。

步骤1:建立Node应用程序

从命令行设置一个新的Node应用。通过CLI提示,或添加 —yes 标志来接受默认选项。

$ npm init --yes

如果在项目设置过程中接受了默认选项,则为入口点创建一个名为 index.js 的文件。

$ touch index.js

安装Express Web框架,然后在 index.js 中初始化服务器。

const express = require('express')
const app = express()
const port = process.env.PORT || 3000

app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

从命令行启动服务器。

$ node index.js

回到 index.js 中,创建一个路由,先检查速率限制,如果用户没有超过限制再允许访问资源。

app.post('/', async (req, res) => {
  async function isOverLimit(ip) {
    // to define
  }
  // 检查率限制
  let overLimit = await isOverLimit(req.ip)
  if (overLimit) {
    res.status(429).send('Too many requests - try again later')
    return
  }
  // 允许访问资源
  res.send("Accessed the precious resources!")
})

应用级速率限制

在下一步中,我们将定义速率限制器函数 isOverLimit

步骤2:使用Redis添加速率限制器

Redis是一个内存中键值数据库,因此它可以非常快速地检索数据。使用Redis实施速率限制也非常简单。

  • 存储一个像用户IP地址一样的key。
  • 增加从该IP发出的调用数量
  • 在指定时间段后使记录过期

下图所示的限速算法是一个滑动窗口计数器的例子。一个用户如果提交的调用数量适中,或者随着时间的推移将它们分隔开,就永远不会达到速率限制。超过10秒窗口内最大请求的用户必须等待足够的时间来恢复其请求。

限速算法:滑动窗口计数器

从命令行为Node安装一个名为ioredis的Redis客户端。

$ npm install ioredis

在本地启动Redis服务器。

$ redis-server

然后在 index.js 中要求并初始化Redis客户端。

const redis = require('ioredis')
const client = redis.createClient({
  port: process.env.REDIS_PORT || 6379,
  host: process.env.REDIS_HOST || 'localhost',
})
client.on('connect', function () {
  console.log('connected');
});

定义我们上一步开始写的isOverLimit函数,按照Redis的这个模式,按照IP来保存一个计数器。

async function isOverLimit(ip) {
  let res
  try {
    res = await client.incr(ip)
  } catch (err) {
    console.error('isOverLimit: could not increment key')
    throw err
  }
  console.log(`${ip} has value: ${res}`)
  if (res > 10) {
    return true
  }
  client.expire(ip, 10)
}

这就是速率限制器。

当用户调用API时,我们会检查Redis以查看该用户是否超出限制。如果是这样,API将立即返回HTTP 429状态代码,并显示消息 Too many requests — try again later 。如果用户在限制之内,我们将继续执行下一个代码块,在该代码块中,我们可以允许访问受保护的资源(例如数据库)。

在进行速率限制检查期间,我们在Redis中找到用户的记录,并增加其请求计数,如果Redis中没有该用户的记录,那么我们将创建一个新记录。最后,每条记录将在最近一次活动的10秒内过期。

在下一步中,请确保我们的限速器正常运行。

步骤3:在Postman中进行测试

保存更改,然后重新启动服务器。我们将使用Postman将 POST 请求发送到我们的API服务器,该服务器在本地运行,网址为 http:// localhost:3000

在速率限制内

继续快速连续发送请求以达到你的速率限制。

超过速率限制-HTTP 429请求过多

关于限速的最终想法

这是Node和Redis的速率限制器的简单示例,这只是开始。有一堆策略和工具可以用来架构和实现你的速率限制。而且还有其他的增强功能可以通过这个例子来探索,比如:

  • 在响应正文或作为 Retry-after 标头中,让用户知道在重试之前应该等待多少时间
  • 记录达到速率限制的请求,以了解用户行为并警告恶意攻击
  • 尝试使用其他速率限制算法或其他中间件

请记住,当你研究API限制时,你是在性能、安全性和用户体验之间进行权衡。你理想的速率限制解决方案将随着时间的推移而改变,同时也会考虑到这些因素。


微信搜索【前端全栈开发者】关注这个脱发、摆摊、卖货、持续学习的程序员的,第一时间阅读最新文章,会优先两天发表新文章。关注即可大礼包,准能为你节省不少钱!

image

查看原文

赞 9 收藏 6 评论 0

FisherKai 发布了文章 · 9月8日

Javascript 设计模式--Factory (工厂模式)

Factory (工厂模式)

Factory提供一个通用的接口来创建对象,我们可以指定我们所希望创建的工厂对象的类型。

何时使用Factory:

  • 当对象或组件设置涉及高复杂性时
  • 当需要根据所在不同环境轻松生成对象的不同实例时
  • 当处理很多共享相同属性的小型对象或组件时
  • 在编写只需要满足一个API契约的其他对象的实例对象时。对于解耦是很有用的。

示例代码:

// eg:
function Car(opt) {
    // 默认值
    this.doors = opt.doors || 4;
    this.state = opt.state || 'brand new';
    this.color = opt.color || 'silver';
}

function Truck(opt) {
    // 默认值
    this.state = opt.state || 'used';
    this.wheelSize = opt.wheelSize || 'large';
    this.color = opt.color || 'blue';
}

function Factory() { }

// 定义该工厂factory的原型和试用工具,默认为Car
Factory.prototype.class = Car;

Factory.prototype.createObj = function (opt) {
    if (opt.class === 'car') {
        this.class = Car;
    } else {
        this.class = Truck;
    }
    return new this.class(opt);
}

// TestCode
// 创建生成汽车的工厂实例
var carFactory = new Factory();
var car = carFactory.createObj({
    class: 'car',
    doors: '2',
    state: 'aodi',
    color: 'black'
});

// console.log(car instanceof Car);  // true
console.log(car);


// 抽象工厂模式
var AbstractFactory = (function () {
    // 存储车辆类型
    var types = {};

    return {
        getVehicle: function (type, customizations) {
            var Vehicle = types[type];
            return (Vehicle) ? new Vehicle(customizations) : null;
        },

        registerVehicle: function (type, Vehicle) {
            var proto = Vehicle.prototype;

            // 只注册实现车辆契约的类  只有实现这两个方法才能被注册
            // if (proto.dirve && proto.breakDown) {
            //     types[type] = Vehicle;
            // }
            types[type] = Vehicle;
            return AbstractFactory;
        }
    }
})();

AbstractFactory.registerVehicle('car', Car);
AbstractFactory.registerVehicle('truck', Truck);

var car2 = AbstractFactory.getVehicle('car', {
    color: 'lime green',
    state: 'like new'
})

console.log(car2);
查看原文

赞 0 收藏 0 评论 0

FisherKai 发布了文章 · 9月8日

angular引入ng-zorro的问题

ng-zorro
的官网上提供了两种在项目中添加ng-zorro的方法,下面记录其提供的第二种自行构建的方式。

第一步:执行该命令创建新的angular项目,若没安装angular/cli请执行安装

ng new new-project

第二步:添加ng-zorro (注:ng-zorro版本需要和angular版本一致)

npm install ng-zorro-antd --save

第三步:引入模块
在app.module.ts文件中引入
import { NgZorroAntdModule } from 'ng-zorro-antd';

......
imports: [ 
BrowserModule,
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
/** 导入 ng-zorro-antd 模块 **/ 
NgZorroAntdModule
]
......

第四步:在 angular.json 文件中引入样式

{ 
"assets": [
... 
], 
"styles": [ 
... 
"node_modules/ng-zorro-antd/ng-zorro-antd.min.css"
] }

第五步:在入口style.css或者style.less文件下引入组件样式

@import "~ng-zorro-antd/style/index.min.css"; /* 引入基本样式 */
@import "~ng-zorro-antd/button/style/index.min.css"; /* 引入组件样式 */

@import "~ng-zorro-antd/style/entry.less"; /* 引入基本样式 */
@import "~ng-zorro-antd/button/style/entry.less"; /* 引入组件样式 */

然后在所需要的组件中使用ng-zorro就可正常使用了。

查看原文

赞 0 收藏 0 评论 0

FisherKai 发布了文章 · 9月4日

Javascript 设计模式--Prototype(原型模式)

原型模式

原型模式是一种基于继承的模式。其为实现继承的一种简单方式,它还可以带来一些性能上的提升。因为在对象定义的一个函数,它们都是引用创建,而不是创建自己的单份拷贝。

方式一 使用Object.create方法,该方法可以传递两个参数Object.create(prototype,optionalDescriptorObjects)
Object.create(prototype,optionalDescriptorObjects)

方式二

var prototypeMode = (function () {
    function F() { }

    return function (proto) {
        F.prototype = proto;
        return new F();
    }
})()
查看原文

赞 0 收藏 0 评论 0

FisherKai 赞了文章 · 8月31日

30 多个有内味道且笑死的人代码注释

作者:xor
译者:前端小智
来源:medium
点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

代码注释,有些人说它太丑,也有些人说它是标准和良好的做法。在本文中, 列出了一些在编程中遇到的有趣的代码注释。

注释 1

// Weed Effect ! 

这是杂草效应的意思?不是很懂,有谁知道,可以留言一下。

注释 2

/**** 原文 *****/
//The following code was written by <developer name>.
// Unless it doesn't work, then I have no idea who wrote it.

/**** 自译 *****/
// 以下代码是由<developer name>编写的。
// 除非它不起作用,否则我不知道是谁写的。

注释 3

/**** 原文 *****/
// Ad Index scheming and plotting - Those with
//    heart conditions are advised to not continue

/**** 自译 *****/
// 广告索引的设计和绘图模块
// 建议不要深入阅读,否则容得得心脏病

注释 4

/**** 原文 *****/
// nobody read comments!

/**** 自译 *****/
// 没人会读的注释

注释 5

/**** 原文 *****/
i++; // increment i

/**** 自译 *****/
i++; // 自增 i

注释 6

/**** 原文 *****/
// Magic. Do not touch.


/**** 自译 *****/
// 这里已经结界,不要碰.

注释 7

/**** 原文 *****/
// sometimes I believe compiler ignores all my comments

/**** 自译 *****/
// 有时我相信编译器会忽略我的所有注释

注释 8

/**** 原文 *****/
// I am not responsible of this code.
// They made me write it, against my will.

/**** 自译 *****/
// 我对此代码不承担任何责任。
// 是他们强迫我让我写的,这已违背了我的意愿。

注释 9

/**** 原文 *****/
// 
// Dear maintainer:
// 
// Once you are done trying to 'optimize' this routine,
// and have realized what a terrible mistake that was,
// please increment the following counter as a warning
// to the next guy:
// 
// total_hours_wasted_here = 42

/**** 自译 *****/
// 亲爱的程序媛
// 
// 一旦你尝试“优化”此代码,并意识到这是一个可怕的错误,
// 请增加以下计数器,以警告下一个人
//  total_hours_wasted_here = 42

注释 10

/**** 原文 *****/

# To understand recursion, see the bottom of this file
At the bottom of the file
# To understand recursion, see the top of this file


/**** 自译 *****/

# 要了解递归,请参见此文件的底部

在文件的底部

# 要了解递归,请参见此文件的顶部

注释 11

/**** 原文 *****/

// I will give you two of my seventy-two virgins if you can fix this.

/**** 自译 *****/
// 如果你能解决这个问题,我就把我七十二个处女中的两个给你。

注释 12

/**** 原文 *****/
//Dear future me. Please forgive me. 
//I can't even begin to express how sorry I am.


/**** 自译 *****/
// 你好,未来帅气的我,请原谅我。
// 我现在无法表达我的歉意,因为我写下了这段代码。

注释 13

/**** 原文 *****/
//private instance variable for storing age
public static int age;

/**** 自译 *****/
// 用于存储年龄的私有实例变量
public static int age;

注释 14

/**** 原文 *****/
const int TEN=10; // s if the value of 10 will fluctuate...


/**** 自译 *****/
const int TEN=10; // 10 是会改变的值

注释 15

/**** 原文 *****/

/* You are not meant to understand this */


/**** 自译 *****/

/* 你不应该理解这一点 */

注释 16

/**** 原文 *****/
/*
* TODO: Remove this function

function remove($customer_id)
    {
        $this->Customer->remove($id);
    }

*/


/**** 自译 *****/
/*
* TODO: 删除函数

function remove($customer_id)
    {
        $this->Customer->remove($id);
    }

*/

注释 17

/**** 原文 *****/
//When I wrote this, only God and I understood what I was doing
//Now, God only knows


/**** 自译 *****/

// 当我写这段代码,只有上帝和我知道这是在做什么
...

// 现在,只有上帝知道这段代码在做什么

注释 18

/**** 原文 *****/

// drunk, fix later

/**** 自译 *****/

// 喝醉了,待会解决

注释 19

/**** 原文 *****/

// I'm sorry.

/**** 自译 *****/

// 我很抱歉

注释 20

/**** 原文 *****/

// I am not responsible of this code.

/**** 自译 *****/

// 我对此代码不承担任何责任。

注释 21

/**** 原文 *****/
// I have to find a better job

/**** 自译 *****/

// 我必须找到更好的工作

注释 22

/**** 原文 *****/

// Joe is sorry
A few hundred lines later...
// Harry is sorry too

/**** 自译 *****/

// 老五抱歉了

写了几百行代码之后...

// 小五对不住了

注释 23

/**** 原文 *****/
# Christmas tree initializer  
    toConnect = []  
    toRead =   [  ]  
    toWrite = [    ]   
    primes = [      ]  
    responses = {}  
    remaining = {}
    
    
/**** 自译 *****/

# 圣诞树初始化器
    toConnect = []  
    toRead =   [  ]  
    toWrite = [    ]   
    primes = [      ]  
    responses = {}  
    remaining = {} 

注释 24

/**** 原文 *****/

Catch (Exception e) {
 // who cares?
}

/**** 自译 *****/

Catch (Exception e) {
 // 管它呢?
}

注释 25

/**** 原文 *****/

// IE7 update. this is still bad code, but IE8 is probably a long way off :)

/**** 自译 *****/

IE7更新,这仍然是错误的代码,看来 IE8 还有很长的路要走:)

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

注释 26

/**** 原文 *****/

// If this code works, it was written by Paul DiLascia. If not, I don't know

/**** 自译 *****/

// 如果这个代码有效的话,它就是由 前端小智 编写的。如果出错了,我也不知道是谁写的。

注释 27

/**** 原文 *****/

// This comment is self explanatory.

/**** 自译 *****/

// 这个注释不言自明。

注释 28

/**** 原文 *****/

int main(void)
/* Program starts here */

/**** 自译 *****/

int main(void)
/* 程序从这里开始 */

注释 29

/**** 原文 *****/

// I am not sure if we need this, but too scared to delete.

/**** 自译 *****/

// 我不确定我们是否需要这个,但又害怕删除

注释 30

/**** 原文 *****/
/**
 * Always returns true.
 */
public boolean isAvailable() {
    return false;
}


/**** 自译 *****/
/**
 * 总是返回 true.
 */
public boolean isAvailable() {
    return false;
}

注释 31

/**** 原文 *****/

// Autogenerated, do not edit. All changes will be undone.

/**** 自译 *****/

// 自动生成,不要编辑。所有的更改都将被撤销。

注释 32

/**** 原文 *****/

// hack for ie browser (assuming that ie is a browser)


/**** 自译 *****/
// 破解ie浏览器(假设ie是一个浏览器)

注释 33

/**** 原文 *****/

/////////////////////////////////////// this is a well commented line

/**** 自译 *****/

/////////////////////////////////////// 这是一个很好的注释

注释 34

/**** 原文 *****/

// Mr. Compiler, please do not read this.

/**** 自译 *****/

// 编译先生,请不要读取这个

注释 35

/**** 原文 *****/

// All bugs added by David S. Miller

/**** 自译 *****/

// 所有 bug 都是由 老王 添加的

注释 36

/**** 原文 *****/

//If you're reading this, then my program is probably a success


/**** 自译 *****/

// 如果你现在在认真详细的阅读这段代码,那么写的这段代码应该是没有问题的。

注释 37

/**** 原文 *****/

Catch (Exception e) {
    //eat it
}

/**** 自译 *****/

Catch (Exception e) {
    // 吃掉它
}

注释 38

/**** 原文 *****/

//todo: never to be implemented

/**** 自译 *****/

// todo:永远不会执行

注释 39

/**** 原文 *****/

// simply copied from another code

/**** 自译 *****/

// 别问,问我也是从另一个代码拷贝过来的

注释 40

/**** 原文 *****/

//Please comment on your source code

/**** 自译 *****/

// 拜托啦,请把你写的代码注释一下

注释 41

/**** 原文 *****/
# as you can see: I comment the code!


/**** 自译 *****/

# 如你所见:我注释了代码!

注释 42

/**** 原文 *****/

// if i ever see this again i'm going to start bringing guns to work

/**** 自译 *****/

// 如果我再看到这个,我会带枪去工作

注释 43

def format_ticket_content(text, recursive = true)
  if text.is_a?(TicketNote)
    note = text
    text = note.content
  else
    note = nil
  end

  ## Safety pig has arrived!
  text = h(text)
  ##                               _
  ##  _._ _..._ .-',     _.._(`))
  ## '-. `     '  /-._.-'    ',/
  ##    )         \            '.
  ##   / _    _    |             \
  ##  |  a    a    /              |
  ##  \   .-.                     ;  
  ##   '-('' ).-'       ,'       ;
  ##      '-;           |      .'
  ##         \           \    /
  ##         | 7  .__  _.-\   \
  ##         | |  |  ``/  /`  /
  ##        /,_|  |   /,_/   /
  ##           /,_/      '`-'
  ##

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

注释 44

/**** 原文 *****/

long long ago; /* in a galaxy far far away */


/**** 自译 *****/

long long ago; /* 在遥远的星系中 */

注释 45

/**** 原文 *****/
try {

} finally { // should never happen 

}

/**** 自译 *****/
try {

} finally { // 这里永远不会被执行

}

注释 46

/**** 原文 *****/

/* Ah ah ah! You'll never understand why this one works. */


/**** 自译 *****/

// 啊啊啊!你永远都不会明白为什么这个方法有效。

注释 47

/**** 原文 *****/
// this formula is right, work out the math yourself if you don't believe me

/**** 自译 *****/

// 这个公式是正确的,如果你不相信我,自己动手算一下

注释 48

/**** 原文 *****/

// This was clearly written under duress

/**** 自译 *****/

// 这显然是在胁迫下写的

注释 49

 
                                   .='  ' .`/,/!(=)Zm.           
                     .._,,._..  ,-`- `,\ ` -` -`\\7//WW.         
                ,v=~/.-,-\- -!|V-s.)iT-|s|\-.'   `///mK%.        
              v!`i!-.e]-g`bT/i(/[=.Z/m)K(YNYi..   /-]i44M.       
            v`/,`|v]-DvLcfZ/eV/iDLN\D/ZK@%8W[Z..   `/d!Z8m       
           //,c\(2(X/NYNY8]ZZ/bZd\()/\7WY%WKKW)   -'|(][%4.      
         ,\\i\c(e)WX@WKKZKDKWMZ8(b5/ZK8]Z7%ffVM,   -.Y!bNMi      
         /-iit5N)KWG%%8%%%%W8%ZWM(8YZvD)XN(@.  [   \]!/GXW[      
        / ))G8\NMN%W%%%%%%%%%%8KK@WZKYK*ZG5KMi,-   vi[NZGM[      
       i\!(44Y8K%8%%%**~YZYZ@%%%%%4KWZ/PKN)ZDZ7   c=//WZK%!      
      ,\v\YtMZW8W%%f`,`.t/bNZZK%%W%%ZXb*K(K5DZ   -c\\/KM48       
      -|c5PbM4DDW%f  v./c\[tMY8W%PMW%D@KW)Gbf   -/(=ZZKM8[       
      2(N8YXWK85@K   -'c|K4/KKK%@  V%@@WD8e~  .//ct)8ZK%8`       
      =)b%]Nd)@KM[  !'\cG!iWYK%%|   !M@KZf    -c\))ZDKW%`        
      YYKWZGNM4/Pb  '-VscP4]b@W%     'Mf`   -L\///KM(%W!         
      !KKW4ZK/W7)Z. '/cttbY)DKW%     -`  .',\v)K(5KW%%f          
      'W)KWKZZg)Z2/,!/L(-DYYb54%  ,,`, -\-/v(((KK5WW%f           
       \M4NDDKZZ(e!/\7vNTtZd)8\Mi!\-,-/i-v((tKNGN%W%%            
       'M8M88(Zd))///((|D\tDY\\KK-`/-i(=)KtNNN@W%%%@%[           
        !8%@KW5KKN4///s(\Pd!ROBY8/=2(/4ZdzKD%K%%%M8@%%           
         '%%%W%dGNtPK(c\/2\[Z(ttNYZ2NZW8W8K%%%%YKM%M%%.          
           *%%W%GW5@/%!e]_tZdY()v)ZXMZW%W%%%*5Y]K%ZK%8[          
            '*%%%%8%8WK\)[/ZmZ/Zi]!/M%%%%@f\ \Y/NNMK%%!          
              'VM%%%%W%WN5Z/Gt5/b)((cV@f`  - |cZbMKW%%|          
                 'V*M%%%WZ/ZG\t5((+)L\'-,,/  -)X(NWW%%           
                      `~`MZ/DZGNZG5(((\,    ,t\\Z)KW%@           
                         'M8K%8GN8\5(5///]i!v\K)85W%%f           
                           YWWKKKKWZ8G54X/GGMeK@WM8%@            
                            !M8%8%48WG@KWYbW%WWW%%%@             
                              VM%WKWK%8K%%8WWWW%%%@`             
                                ~*%%%%%%W%%%%%%%@~               
                                   ~*MM%%%%%%@f`                 
                                       '''''

注释 50

/************************************************************
        *                                                           *
        *  .=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-.       *
        *   |                     ______                     |      *
        *   |                  .-"      "-.                  |      *
        *   |                 /            \                 |      *
        *   |     _          |              |          _     |      *
        *   |    ( \         |,  .-.  .-.  ,|         / )    |      *
        *   |     > "=._     | )(__/  \__)( |     _.=" <     |      *
        *   |    (_/"=._"=._ |/     /\     \| _.="_.="\_)    |      *
        *   |           "=._"(_     ^^     _)"_.="           |      *
        *   |               "=\__|IIIIII|__/="               |      *
        *   |              _.="| \IIIIII/ |"=._              |      *
        *   |    _     _.="_.="\          /"=._"=._     _    |      *
        *   |   ( \_.="_.="     `--------`     "=._"=._/ )   |      *
        *   |    > _.="                            "=._ <    |      *
        *   |   (_/                                    \_)   |      *
        *   |                                                |      *
        *   '-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-='      *
        *                                                           *
        *      LASCIATE OGNI SPERANZA, VOI CH'ENTRATE               *              LEAVE EVERY HOPE, YOU COME IN
        *************************************************************/

注释 51

/**** 原文 *****/
// This was clearly written under duress

/**** 自译 *****/
// 如果将来我读到这篇文章,我会回到过去并自杀。

注释 52

/**** 原文 *****/
// This was clearly written under duress

/**** 自译 *****/
// 乱七八糟的 SQL语句,但它有效,所以请不要触摸它

注释 53

//        .==.        .==.          
//       //`^\\      //^`\\         
//      // ^ ^\(\__/)/^ ^^\\        
//     //^ ^^ ^/6  6\ ^^ ^ \\       
//    //^ ^^ ^/( .. )\^ ^ ^ \\      
//   // ^^ ^/\| v""v |/\^ ^ ^\\     
//  // ^^/\/ /  `~~`  \ \/\^ ^\\    
//  -----------------------------
/// 这是条龙

注释54

/**** 原文 *****/
// I from the future read this I'll back in time and kill myself.

/**** 自译 *****/
// 这是垃圾代码,但现在是凌晨3点,我需要把它修好。

注释55

// 我不确定我做了什么

注释56

如果你想被解雇,请将其删除

注释 57

// 切勿动以下代码,否则我会踢你的 PP

编辑中可能存在的bug没法实时知道,事后为了解决这些bug,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://medium.com/javascript...

交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

赞 11 收藏 5 评论 4

认证与成就

  • 获得 2 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 1月15日
个人主页被 120 人浏览