Fractal

Fractal 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织 example.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

Fractal 回答了问题 · 11月26日

添加资源失败的请求返回什么http状态码?

如果是 参数 不正确导致的资源添加失败,那么就是 400 Bad Request
如果是 服务器端代码、环境原因 添加资源失败,那么就是 500 Internal Server Error

关注 3 回答 2

Fractal 收藏了文章 · 11月24日

「 giao-js 」用js写一个js解释器

image

前言

在这篇文章中,我们将通过 JS 构建我们自己的 JS 解释器,用 JS 写 JS,这听起来很奇怪,尽管如此,这样做我们将更熟悉 JS,也可以学习 JS 引擎是如何工作的!

什么是解释器 (Interpreter) ?

解释器是在运行时运行的语言求值器,它动态地执行程序的源代码。
解释器解析源代码,从源代码生成 AST(抽象语法树),遍历 AST 并逐个计算它们。

解释器 (Interpreter) 工作原理

Interpreter

  • 词法分析 (Tokenization)
  • 语法解析 (Parsing)
  • 求值 (Evaluating)

词法分析 (Tokenization)

将源代码分解并组织成一组有意义的单词,这一过程即为词法分析(Token)。

在英语中,当我们遇到这样一个语句时:

Javascript is the best language in the world

我们会下意识地把句子分解成一个个单词:

+----------------------------------------------------------+
| Javascript | is | the | best | language | in |the |world |
+----------------------------------------------------------+

这是分析和理解句子的第一阶段。

词法分析是由词法分析器完成的,词法分析器会扫描(scanning)代码,提取词法单元。

var a = 1;

[
  ("var": "keyword"),
  ("a": "identifier"),
  ("=": "assignment"),
  ("1": "literal"),
  (";": "separator"),
];

词法分析器将代码分解成 Token 后,会将 Token 传递给解析器进行解析,我们来看下解析阶段是如何工作的。

语法解析 (Parsing)

将词法分析阶段生成的 Token 转换为抽象语法树(Abstract Syntax Tree),这一过程称之为语法解析(Parsing)。

在英语中,Javascript is the best language 被分解为以下单词:

+------------------------------------------+
| Javascript | is | the | best | language  |
+------------------------------------------+

这样我们就可以挑选单词并形成语法结构:

"Javascript": Subject
"is the best language": Predicate
"language": Object

Javascript 在语法中是一个主语名词,其余的是一个没有什么意义的句子叫做谓语,language 是动作的接受者,也就是宾语。结构是这样的:

Subject(Noun) -> Predicate -> Object

语法解析是由语法解析器完成的,它会将上一步生成的 Token,根据语法规则,转为抽象语法树(AST)。

{
  type: "Program",
  body: [
    {
      type: "VariableDeclaration",
      declarations: [
        {
          type: "VariableDeclarator",
          id: {
            type: "Identifier",
            name: "sum"
          },
          init: {
            type: "Literal",
            value: 30,
            raw: "30"
          }
        }
      ],
      kind: "var"
    }
  ],
}

求值阶段 (Evaluating)

解释器将遍历 AST 并计算每个节点。- 求值阶段
1 + 2
|
    |
    v
+---+  +---+
| 1 |  | 2 |
+---+  +---+
  \     /
   \   /
    \ /
   +---+
   | + |
   +---+
{
    lhs: 1,
    op: '+'.
    rhs: 2
}

解释器解析 Ast,得到 LHS 节点,接着收集到操作符(operator)节点+,+操作符表示需要进行一次加法操作,它必须有第二个节点来进行加法操作.接着他收集到 RHS 节点。它收集到了有价值的信息并执行加法得到了结果,3。

{
  type: "Program",
  body: [
    {
      type: "ExpressionStatement",
      expression: {
        type: "BinaryExpression",
        left: {
          type: "Literal",
          value: 1,
          raw: "1"
        },
        operator: "+",
        right: {
          type: "Literal",
          value: 2,
          raw: "2"
        }
      }
    }
  ],
}

实践

前面我们已经介绍了解释器的工作原理,接下来我们来动动手松松筋骨吧,实现一个 Mini Js Interpreter~

实践准备

  • Acorn.js
A tiny, fast JavaScript parser, written completely in JavaScript. 一个完全使用 javascript 实现的,小型且快速的 javascript 解析器

本次实践我们将使用 acorn.js ,它会帮我们进行词法分析,语法解析并转换为抽象语法树。

Webpack/Rollup/Babel(@babel/parser) 等第三方库也是使用 acorn.js 作为自己 Parser 的基础库。(站在巨人的肩膀上啊!)

  • The Estree Spec

最开始 Mozilla JS Parser API 是 Mozilla 工程师在 Firefox 中创建的 SpiderMonkey 引擎输出 JavaScript AST 的规范文档,文档所描述的格式被用作操作 JAvaScript 源代码的通用语言。

随着 JavaScript 的发展,更多新的语法被加入,为了帮助发展这种格式以跟上 JavaScript 语言的发展。The ESTree Spec 就诞生了,作为参与构建和使用这些工具的人员的社区标准。

acorn.js parse 返回值符合 ESTree spec 描述的 AST 对象,这里我们使用@types/estree 做类型定义。

  • Jest

号称令人愉快的 JavaScript 测试...我们使用它来进行单元测试.

  • Rollup

Rollup 是一个 JavaScript 模块打包器,我们使用它来打包,以 UMD 规范对外暴露模块。

项目初始化

// visitor.ts 创建一个Visitor类,并提供一个方法操作ES节点。
import * as ESTree from "estree";
class Visitor {
  visitNode(node: ESTree.Node) {
    // ...
  }
}
export default Visitor;
// interpreter.ts 创建一个Interpreter类,用于运行ES节点树。
// 创建一个Visitor实例,并使用该实例来运行ESTree节点
import Visitor from "./visitor";
import * as ESTree from "estree";
class Interpreter {
  private visitor: Visitor;
  constructor(visitor: Visitor) {
    this.visitor = visitor;
  }
  interpret(node: ESTree.Node) {
    this.visitor.visitNode(node);
  }
}
export default Interpreter;
// vm.ts 对外暴露run方法,并使用acorn code->ast后,交给Interpreter实例进行解释。
const acorn = require("acorn");
import Visitor from "./visitor";
import Interpreter from "./interpreter";

const jsInterpreter = new Interpreter(new Visitor());

export function run(code: string) {
  const root = acorn.parse(code, {
    ecmaVersion: 8,
    sourceType: "script",
  });
  return jsInterpreter.interpret(root);
}

实践第 1 弹: 1+1= ?

我们这节来实现 1+1 加法的解释。首先我们通过AST explorer,看看 1+1 这段代码转换后的 AST 结构。

1+1 ast

我们可以看到这段代码中存在 4 种节点类型,下面我们简单的介绍一下它们:

Program

根节点,即代表一整颗抽象语法树,body 属性是一个数组,包含了多个 Statement 节点。

interface Program {
  type: "Program";
  sourceType: "script" | "module";
  body: Array<Directive | Statement | ModuleDeclaration>;
  comments?: Array<Comment>;
}

ExpressionStatement

表达式语句节点,expression 属性指向一个表达式节点对象

interface ExpressionStatement {
  type: "ExpressionStatement";
  expression: Expression;
}

BinaryExpression

二元运算表达式节点,left 和 right 表示运算符左右的两个表达式,operator 表示一个二元运算符。
本节实现的重点,简单理解,我们只要拿到 operator 操作符的类型并实现,然后对 left,right 值进行求值即可。

interface BinaryExpression {
  type: "BinaryExpression";
  operator: BinaryOperator;
  left: Expression;
  right: Expression;
}

Literal

字面量,这里不是指 [] 或者 {} 这些,而是本身语义就代表了一个值的字面量,如 1,“hello”, true 这些,还有正则表达式,如 /\d?/。

type Literal = SimpleLiteral | RegExpLiteral;

interface SimpleLiteral {
  type: "Literal";
  value: string | boolean | number | null;
  raw?: string;
}

interface RegExpLiteral {
  type: "Literal";
  value?: RegExp | null;
  regex: {
    pattern: string;
    flags: string;
  };
  raw?: string;
}

废话少说,开撸!!!

// standard/es5.ts 实现以上节点方法

import Scope from "../scope";
import * as ESTree from "estree";
import { AstPath } from "../types/index";

const es5 = {
  // 根节点的处理很简单,我们只要对它的body属性进行遍历,然后访问该节点即可。
  Program(node: ESTree.Program) {
    node.body.forEach((bodyNode) => this.visitNode(bodyNode));
  },
  // 表达式语句节点的处理,同样访问expression 属性即可。
  ExpressionStatement(node: ESTree.ExpressionStatement>) {
    return this.visitNode(node.expression);
  },
  // 字面量节点处理直接求值,这里对正则表达式类型进行了特殊处理,其他类型直接返回value值即可。
  Literal(node: ESTree.Literal>) {
    if ((<ESTree.RegExpLiteral>node).regex) {
      const { pattern, flags } = (<ESTree.RegExpLiteral>node).regex;
      return new RegExp(pattern, flags);
    } else return node.value;
  },
  // 二元运算表达式节点处理
  // 对left/node两个节点(Literal)进行求值,然后实现operator类型运算,返回结果。
  BinaryExpression(node: ESTree.BinaryExpression>) {
    const leftNode = this.visitNode(node.left);
    const operator = node.operator;
    const rightNode = this.visitNode(node.right);
    return {
      "+": (l, r) => l + r,
      "-": (l, r) => l - r,
      "*": (l, r) => l * r,
      "/": (l, r) => l / r,
      "%": (l, r) => l % r,
      "<": (l, r) => l < r,
      ">": (l, r) => l > r,
      "<=": (l, r) => l <= r,
      ">=": (l, r) => l >= r,
      "==": (l, r) => l == r,
      "===": (l, r) => l === r,
      "!=": (l, r) => l != r,
      "!==": (l, r) => l !== r,
    }[operator](leftNode, rightNode);
  },
};
export default es5;
// visitor.ts
import Scope from "./scope";
import * as ESTree from "estree";
import es5 from "./standard/es5";

const VISITOR = {
  ...es5,
};
class Visitor {
  // 实现访问节点方法,通过节点类型访问对应的节点方法
  visitNode(node: ESTree.Node) {
    return {
      visitNode: this.visitNode,
      ...VISITOR,
    }[node.type](node);
  }
}
export default Visitor;

就这样,普通的二元运算就搞定啦!!!

实践第 2 弹: 怎么找到变量?

Javascript 的作用域与作用域链的概念想必大家都很熟悉了,这里就不再啰嗦了~

是的,我们需要通过实现作用域来访问变量,实现作用域链来搜寻标识符。

在这之前,我们先实现 Variable 类,实现变量的存取方法。

// variable.ts
export enum Kind {
  var = "var",
  let = "let",
  const = "const",
}
export type KindType = "var" | "let" | "const";
export class Variable {
  private _value: any;
  constructor(public kind: Kind, val: any) {
    this._value = val;
  }
  get value() {
    return this._value;
  }
  set value(val: any) {
    this._value = val;
  }
}
import { Variable, Kind, KindType } from "./variable";

class Scope {
  // 父作用域
  private parent: Scope | null;
  // 当前作用域
  private targetScope: { [key: string]: any };
  constructor(public readonly type, parent?: Scope) {
    this.parent = parent || null;
    this.targetScope = new Map();
  }
  // 是否已定义
  private hasDefinition(rawName: string): boolean {
    return Boolean(this.search(rawName));
  }
  // var类型变量定义
  public defineVar(rawName: string, value: any) {
    let scope: Scope = this;
    // 如果不是全局作用域且不是函数作用域,找到全局作用域,存储变量
    // 这里就是我们常说的Hoisting (变量提升)
    while (scope.parent && scope.type !== "function") {
      scope = scope.parent;
    }
    // 存储变量
    scope.targetScope.set(rawName, new Variable(Kind.var, value));
  }
  // let类型变量定义
  public defineLet(rawName: string, value: any) {
    this.targetScope.set(rawName, new Variable(Kind.let, value));
  }
  // const类型变量定义
  public defineConst(rawName: string, value: any) {
    this.targetScope.set(rawName, new Variable(Kind.const, value));
  }
  // 作用域链实现,向上查找标识符
  public search(rawName: string): Variable | null {
    if (this.targetScope.get(rawName)) {
      return this.targetScope.get(rawName);
    } else if (this.parent) {
      return this.parent.search(rawName);
    } else {
      return null;
    }
  }
  // 变量声明方法,变量已定义则抛出语法错误异常
  public declare(kind: Kind | KindType, rawName: string, value: any) {
    if (this.hasDefinition(rawName)) {
      console.error(
        `Uncaught SyntaxError: Identifier '${rawName}' has already been declared`
      );
      return true;
    }
    return {
      [Kind.var]: () => this.defineVar(rawName, value),
      [Kind.let]: () => this.defineLet(rawName, value),
      [Kind.const]: () => this.defineConst(rawName, value),
    }[kind]();
  }
}

export default Scope;

以上就是变量对象,作用域及作用域链的基础实现了,接下来我们就可以定义及访问变量了。

实践第 3 弹: var age = 18

var

从语法树中我们可以看到三个陌生的节点类型,来看看它们分别代表什么意思:

VariableDeclaration

变量声明,kind 属性表示是什么类型的声明,因为 ES6 引入了 const/let。
declarations 表示声明的多个描述,因为我们可以这样:let a = 1, b = 2;。

interface VariableDeclaration {
  type: "VariableDeclaration";
  declarations: Array<VariableDeclarator>;
  kind: "var" | "let" | "const";
}

VariableDeclarator

变量声明的描述,id 表示变量名称节点,init 表示初始值的表达式,可以为 null。

interface VariableDeclarator {
  type: "VariableDeclarator";
  id: Pattern;
  init?: Expression | null;
}

Identifier

顾名思义,标识符节点,我们写 JS 时定义的变量名,函数名,属性名,都归为标识符。

interface Identifier {
  type: "Identifier";
  name: string;
}

了解了对应节点的含义后,我们来进行实现:

// standard/es5.ts 实现以上节点方法

import Scope from "../scope";
import * as ESTree from "estree";

type AstPath<T> = {
  node: T;
  scope: Scope;
};

const es5 = {
  // ...
  // 这里我们定义了astPath,新增了scope作用域参数
  VariableDeclaration(astPath: AstPath<ESTree.VariableDeclaration>) {
    const { node, scope } = astPath;
    const { declarations, kind } = node;
    // 上面提到,生声明可能存在多个描述(let a = 1, b = 2;),所以我们这里对它进行遍历:
    // 这里遍历出来的每个item是VariableDeclarator节点
    declarations.forEach((declar) => {
      const { id, init } = <ESTree.VariableDeclarator>declar;
      // 变量名称节点,这里拿到的是age
      const key = (<ESTree.Identifier>id).name;
      // 判断变量是否进行了初始化 ? 查找init节点值(Literal类型直接返回值:18) : 置为undefined;
      const value = init ? this.visitNode(init, scope) : undefined;
      // 根据不同的kind(var/const/let)声明进行定义,即var age = 18
      scope.declare(kind, key, value);
    });
  },
  // 标识符节点,我们只要通过访问作用域,访问该值即可。
  Identifier(astPath: AstPath<ESTree.Identifier>) {
    const { node, scope } = astPath;
    const name = node.name;
    // walk identifier
    // 这个例子中查找的是age变量
    const variable = scope.search(name);
    // 返回的是定义的变量对象(age)的值,即18
    if (variable) return variable.value;
  },
};
export default es5;

实践第 4 弹: module.exports = 6

我们先来看看 module.exports = 6 对应的 AST。

module-exports

从语法树中我们又看到两个陌生的节点类型,来看看它们分别代表什么意思:

AssignmentExpression

赋值表达式节点,operator 属性表示一个赋值运算符,left 和 right 是赋值运算符左右的表达式。

interface AssignmentExpression {
  type: "AssignmentExpression";
  operator: AssignmentOperator;
  left: Pattern | MemberExpression;
  right: Expression;
}

MemberExpression

成员表达式节点,即表示引用对象成员的语句,object 是引用对象的表达式节点,property 是表示属性名称,computed 如果为 false,是表示 . 来引用成员,property 应该为一个 Identifier 节点,如果 computed 属性为 true,则是 [] 来进行引用,即 property 是一个 Expression 节点,名称是表达式的结果值。

interface MemberExpression {
  type: "MemberExpression";
  object: Expression | Super;
  property: Expression;
  computed: boolean;
  optional: boolean;
}

我们先来定义 module.exports 变量。

import Scope from "./scope";
import Visitor from "./visitor";
import * as ESTree from "estree";
class Interpreter {
  private scope: Scope;
  private visitor: Visitor;
  constructor(visitor: Visitor) {
    this.visitor = visitor;
  }
  interpret(node: ESTree.Node) {
    this.createScope();
    this.visitor.visitNode(node, this.scope);
    return this.exportResult();
  }
  createScope() {
    // 创建全局作用域
    this.scope = new Scope("root");
    // 定义module.exports
    const $exports = {};
    const $module = { exports: $exports };
    this.scope.defineConst("module", $module);
    this.scope.defineVar("exports", $exports);
  }
  // 模拟commonjs,对外暴露结果
  exportResult() {
    // 查找module变量
    const moduleExport = this.scope.search("module");
    // 返回module.exports值
    return moduleExport ? moduleExport.value.exports : null;
  }
}
export default Interpreter;

ok,下面我们来实现以上节点函数~

// standard/es5.ts 实现以上节点方法

import Scope from "../scope";
import * as ESTree from "estree";

type AstPath<T> = {
  node: T;
  scope: Scope;
};

const es5 = {
  // ...
  // 这里我们定义了astPath,新增了scope作用域参数
  MemberExpression(astPath: AstPath<ESTree.MemberExpression>) {
    const { node, scope } = astPath;
    const { object, property, computed } = node;
    // property 是表示属性名称,computed 如果为 false,property 应该为一个 Identifier 节点,如果 computed 属性为 true,即 property 是一个 Expression 节点
    // 这里我们拿到的是exports这个key值,即属性名称
    const prop = computed
      ? this.visitNode(property, scope)
      : (<ESTree.Identifier>property).name;
    // object 表示对象,这里为module,对module进行节点访问
    const obj = this.visitNode(object, scope);
    // 访问module.exports值
    return obj[prop];
  },
  // 赋值表达式节点
  (astPath: AstPath<ESTree.>) {
    const { node, scope } = astPath;
    const { left, operator, right } = node;
    let assignVar;
    // LHS 处理
    if (left.type === "Identifier") {
      // 标识符类型 直接查找
      const value = scope.search(left.name);
      assignVar = value;
    } else if (left.type === "MemberExpression") {
      // 成员表达式类型,处理方式跟上面差不多,不同的是这边需要自定义一个变量对象的实现
      const { object, property, computed } = left;
      const obj = this.visitNode(object, scope);
      const key = computed
        ? this.visitNode(property, scope)
        : (<ESTree.Identifier>property).name;
      assignVar = {
        get value() {
          return obj[key];
        },
        set value(v) {
          obj[key] = v;
        },
      };
    }
    // RHS
    // 不同操作符处理,查询到right节点值,对left节点进行赋值。
    return {
      "=": (v) => {
        assignVar.value = v;
        return v;
      },
      "+=": (v) => {
        const value = assignVar.value;
        assignVar.value = v + value;
        return assignVar.value;
      },
      "-=": (v) => {
        const value = assignVar.value;
        assignVar.value = value - v;
        return assignVar.value;
      },
      "*=": (v) => {
        const value = assignVar.value;
        assignVar.value = v * value;
        return assignVar.value;
      },
      "/=": (v) => {
        const value = assignVar.value;
        assignVar.value = value / v;
        return assignVar.value;
      },
      "%=": (v) => {
        const value = assignVar.value;
        assignVar.value = value % v;
        return assignVar.value;
      },
    }[operator](this.visitNode(right, scope));
  },
};
export default es5;

ok,实现完毕,是时候验证一波了,上 jest 大法。

// __test__/es5.test.ts

import { run } from "../src/vm";
describe("giao-js es5", () => {
  test("assign", () => {
    expect(
      run(`
      module.exports = 6;
    `)
    ).toBe(6);
  });
}

jest

实践第 5 弹: for 循环

var result = 0;
for (var i = 0; i < 5; i++) {
  result += 2;
}
module.exports = result;

for-loop

到这一弹大家都发现了,不同的语法其实对应的就是不同的树节点,我们只要实现对应的节点函数即可.我们先来看看这几个陌生节点的含义.

ForStatement

for 循环语句节点,属性 init/test/update 分别表示了 for 语句括号中的三个表达式,初始化值,循环判断条件,每次循环执行的变量更新语句(init 可以是变量声明或者表达式)。
这三个属性都可以为 null,即 for(;;){}。
body 属性用以表示要循环执行的语句。

interface ForStatement {
  type: "ForStatement";
  init?: VariableDeclaration | Expression | null;
  test?: Expression | null;
  update?: Expression | null;
  body: Statement;
}

UpdateExpression

update 运算表达式节点,即 ++/--,和一元运算符类似,只是 operator 指向的节点对象类型不同,这里是 update 运算符。

interface UpdateExpression {
  type: "UpdateExpression";
  operator: UpdateOperator;
  argument: Expression;
  prefix: boolean;
}

BlockStatement

块语句节点,举个例子:if (...) { // 这里是块语句的内容 },块里边可以包含多个其他的语句,所以有一个 body 属性,是一个数组,表示了块里边的多个语句。

interface BlockStatement {
  0;
  type: "BlockStatement";
  body: Array<Statement>;
  innerComments?: Array<Comment>;
}

废话少说,盘它!!!

// standard/es5.ts 实现以上节点方法

import Scope from "../scope";
import * as ESTree from "estree";

type AstPath<T> = {
  node: T;
  scope: Scope;
};

const es5 = {
  // ...
  // for 循环语句节点
  ForStatement(astPath: AstPath<ESTree.ForStatement>) {
    const { node, scope } = astPath;
    const { init, test, update, body } = node;
    // 这里需要注意的是需要模拟创建一个块级作用域
    // 前面Scope类实现,var声明在块作用域中会被提升,const/let不会
    const forScope = new Scope("block", scope);
    for (
      // 初始化值
      // VariableDeclaration
      init ? this.visitNode(init, forScope) : null;
      // 循环判断条件(BinaryExpression)
      // 二元运算表达式,之前已实现,这里不再细说
      test ? this.visitNode(test, forScope) : true;
      // 变量更新语句(UpdateExpression)
      update ? this.visitNode(update, forScope) : null
    ) {
      // BlockStatement
      this.visitNode(body, forScope);
    }
  },
  // update 运算表达式节点
  // update 运算表达式节点,即 ++/--,和一元运算符类似,只是 operator 指向的节点对象类型不同,这里是 update 运算符。
  UpdateExpression(astPath: AstPath<ESTree.UpdateExpression>) {
    const { node, scope } = astPath;
    // update 运算符,值为 ++ 或 --,配合 update 表达式节点的 prefix 属性来表示前后。
    const { prefix, argument, operator } = node;
    let updateVar;
    // 这里需要考虑参数类型还有一种情况是成员表达式节点
    // 例: for (var query={count:0}; query.count < 8; query.count++)
    // LHS查找
    if (argument.type === "Identifier") {
      // 标识符类型 直接查找
      const value = scope.search(argument.name);
      updateVar = value;
    } else if (argument.type === "MemberExpression") {
      // 成员表达式的实现在前面实现过,这里不再细说,一样的套路~
      const { object, property, computed } = argument;
      const obj = this.visitNode(object, scope);
      const key = computed
        ? this.visitNode(property, scope)
        : (<ESTree.Identifier>property).name;
      updateVar = {
        get value() {
          return obj[key];
        },
        set value(v) {
          obj[key] = v;
        },
      };
    }
    return {
      "++": (v) => {
        const result = v.value;
        v.value = result + 1;
        // preifx? ++i: i++;
        return prefix ? v.value : result;
      },
      "--": (v) => {
        const result = v.value;
        v.value = result - 1;
        // preifx? --i: i--;
        return prefix ? v.value : result;
      },
    }[operator](updateVar);
  },
  // 块语句节点
  // 块语句的实现很简单,模拟创建一个块作用域,然后遍历body属性进行访问即可。
  BlockStatement(astPath: AstPath<ESTree.BlockStatement>) {
    const { node, scope } = astPath;
    const blockScope = new Scope("block", scope);
    const { body } = node;
    body.forEach((bodyNode) => {
      this.visitNode(bodyNode, blockScope);
    });
  },
};
export default es5;

上 jest 大法验证一哈~

test("test for loop", () => {
  expect(
    run(`
      var result = 0;
      for (var i = 0; i < 5; i++) {
        result += 2;
      }
      module.exports = result;
    `)
  ).toBe(10);
});

for-loop-jest

你以为这样就结束了吗? 有没有想到还有什么情况没处理? for 循环的中断语句呢?

var result = 0;
for (var i = 0; i < 5; i++) {
  result += 2;
  break; // break,continue,return
}
module.exports = result;

感兴趣的小伙伴可以自己动手试试,或者戳源码地址

结语

giao-js目前只实现了几个语法,本文只是提供一个思路。

有兴趣的同学可以查看完整代码

觉得有帮助到你的话,点个 star 支持下作者 ❤️ ~

参考

bramblex/jsjs

使用 Acorn 来解析 JavaScript

Build a JS Interpreter in JavaScript Using Acorn as a Parser

查看原文

Fractal 回答了问题 · 11月16日

求教 用echarts 怎么实现一个图表 每个月都有一个峰值曲线图那种

横坐标 按日期(每天),只显示 月份的 标签(应该有回调自定义函数的)
数据 是每天的,这样应该就是了

关注 3 回答 2

Fractal 收藏了文章 · 11月13日

2020前端开发者11个必备的网站

网上有很多很棒的工具,让作为前端开发人员的我们生活的更加轻松。在这篇文章中,我将快速介绍一下我在开发工作中经常使用的11种工具。

Node.green

用来查询当前 Node 版本是否某些功能。例如,对象展开符( Rest/Spread Properties)

1582372545876.jpg

可以看到在 Node v8.3.0 以下是不支持的。分别在 Node v8.5.0v8.2.1 下运行以下代码片段

const a = { foo: 1};
console.log({...a, b: 2}); 

1582372779948.jpg

当你遇到以上错误,那大多就是 Node 版本问题啦。

在线地址: https://node.green/

CanIUse

当你想要确定某个 Web API 的兼容性的时候,这个在线工具将轻松搞定。

假设我们想知道哪些浏览器及其版本将支持 Web Share API:navigator.share(...

1_pq1UczjJ8dhTsO6hCPntyw.png

查看结果。浏览器和支持navigator.share(…)的版本都列出了。

在线地址: https://caniuse.com/

Minify

为了减少应用程序代码的包大小,我们在发布到到生产环境的时候,需要使它们最小化。 最小化消除了空格,无效代码等。这能够使应用程序包大小的显着减小,从而节省浏览器上的加载时间。(虽然在当下,有 webpack uglifyJS 等插件,但是当我在开发非打包的简单应用的时候,这个是一个不错的选择。 )

1582373652825.jpg

在线地址: https://www.minifier.org/

Bit.dev

Bit.dev是一个非常棒的组件中心。 可以用它来托管,记录和管理来自不同项目的可复用组件。 这是增加代码复用,加速开发并优化团队协作的好方法。

这也是从头开始构建设计系统的不错选择(因为它本质上具有设计系统所需的一切)。 Bit.devBit完美配合,Bit是处理组件隔离和发布的开源工具。

Bit.dev支持React,带有TypeScriptReactAngularVue等。

1_Nj2EzGOskF51B5AKuR-szw.gif

在线地址: https://bit.dev/

Unminify

免费的在线工具,用于最小化(解压,反混淆)JavaScript,CSS和HTML代码,使其可读性强,美观

1582375400913.jpg

在线地址: https://unminify.com/

Stackblitz

这是每个人都喜欢的工具。Stackblitz使我们能够使用世界上最流行和使用最多的IDE,即web上的Visual Studio代码。

只需单击一下,Stackblitz 即可快速提供AngularReactVueVanillaRxJSTypeScript项目的框架。

当你想从浏览器中尝试一段代码或任何当前JS框架中的功能时,Stackblitz非常有用。 假设你正在阅读Angular文章,并且遇到了想要尝试的代码。 您可以最小化您的浏览器并快速搭建一个新的Angular项目。

还有其他很棒的在线IDE,但是我相信Stackblitz的转折点是使用每个人都喜欢的 Visual Studio Code感觉和工具。 (ps: 本人使用体验,非常快速流畅, 附上图,比 sandbox 快很多)

1582374042909.jpg

在线地址: https://stackblitz.com/

JWT.io

如果您使用JSON Web令牌(JWT)保护应用程序安全,或者使用JWT允许用户访问后端的受保护资源。

决定是否应访问路线或资源的一种方法是检查令牌的到期时间。 有时候我们想要解码JWT以查看其有效 payload,jwt.io恰好提供了这一点。

这个在线工具使我们能够插入令牌以查看其有效 payload。 一旦我们粘贴了令牌,jwt.io便对该令牌进行解码并显示其有效payload

1582374387059.jpg

在线地址: https://jwt.io/

BundlePhobia

您是否曾经不确定过node_modules的大小,或者只是想知道将pakckage.json安装在您的计算机中的大小? BundlePhobia提供了答案

1582374462632.jpg

该工具使我们能够加载package.json文件,并显示将从package.json安装的依赖项的大小,也可以查询单包的体积。

在线地址: https://bundlephobia.com/

Babel REPL

Babel是一个免费的开放源代码JS转编译器,用于将现代ES代码转换为普通的 ES5 JavaScript。

该工具是Babeljs团队在网上建立的Web应用,可以将 ES6 +代码转换为ES5。

本人总结的两个比较方便的使用方式

  1. 方面面试时在线写高级语法。
  2. 可以快速查看某些 polyfill 是怎么写的。

1582374539633.jpg

在线地址: https://babeljs.io/en/repl

Prettier Playground

Prettier是一个自以为是的JS代码格式化程序。 它通过解析代码并使用JS最佳编码实践将其重新打印来实施一致的样式。

该工具已在我们的开发环境中广泛使用,但它也具有一个在线地址,你可以在其中美化您的代码。

1582375260418.jpg

在线地址: https://prettier.io/playground

postwoman

postwoman 是一款功能强大的网页调试和模拟发送HTTP请求的Chrome插件,支持几乎所有类型的HTTP请求,操作简单且方便。可用于接口测试,比如测试你用easy-mock生成的接口。

1582374841427.jpg

在线地址: https://postwoman.io/

本文翻译自 https://blog.bitsrc.io/12-use... 但是不仅仅是单纯地翻译,替换了原文中一些我觉得不太实用的并加入一些自己的总结。

最后

如果我的文章有帮助到你,希望你也能帮助我,欢迎关注我的微信公众号 秋风的笔记,回复好友 二次,可加微信并且加入交流群,秋风的笔记 将一直陪伴你的左右。

image

查看原文

Fractal 收藏了文章 · 11月13日

2020前端开发者11个必备的网站

网上有很多很棒的工具,让作为前端开发人员的我们生活的更加轻松。在这篇文章中,我将快速介绍一下我在开发工作中经常使用的11种工具。

Node.green

用来查询当前 Node 版本是否某些功能。例如,对象展开符( Rest/Spread Properties)

1582372545876.jpg

可以看到在 Node v8.3.0 以下是不支持的。分别在 Node v8.5.0v8.2.1 下运行以下代码片段

const a = { foo: 1};
console.log({...a, b: 2}); 

1582372779948.jpg

当你遇到以上错误,那大多就是 Node 版本问题啦。

在线地址: https://node.green/

CanIUse

当你想要确定某个 Web API 的兼容性的时候,这个在线工具将轻松搞定。

假设我们想知道哪些浏览器及其版本将支持 Web Share API:navigator.share(...

1_pq1UczjJ8dhTsO6hCPntyw.png

查看结果。浏览器和支持navigator.share(…)的版本都列出了。

在线地址: https://caniuse.com/

Minify

为了减少应用程序代码的包大小,我们在发布到到生产环境的时候,需要使它们最小化。 最小化消除了空格,无效代码等。这能够使应用程序包大小的显着减小,从而节省浏览器上的加载时间。(虽然在当下,有 webpack uglifyJS 等插件,但是当我在开发非打包的简单应用的时候,这个是一个不错的选择。 )

1582373652825.jpg

在线地址: https://www.minifier.org/

Bit.dev

Bit.dev是一个非常棒的组件中心。 可以用它来托管,记录和管理来自不同项目的可复用组件。 这是增加代码复用,加速开发并优化团队协作的好方法。

这也是从头开始构建设计系统的不错选择(因为它本质上具有设计系统所需的一切)。 Bit.devBit完美配合,Bit是处理组件隔离和发布的开源工具。

Bit.dev支持React,带有TypeScriptReactAngularVue等。

1_Nj2EzGOskF51B5AKuR-szw.gif

在线地址: https://bit.dev/

Unminify

免费的在线工具,用于最小化(解压,反混淆)JavaScript,CSS和HTML代码,使其可读性强,美观

1582375400913.jpg

在线地址: https://unminify.com/

Stackblitz

这是每个人都喜欢的工具。Stackblitz使我们能够使用世界上最流行和使用最多的IDE,即web上的Visual Studio代码。

只需单击一下,Stackblitz 即可快速提供AngularReactVueVanillaRxJSTypeScript项目的框架。

当你想从浏览器中尝试一段代码或任何当前JS框架中的功能时,Stackblitz非常有用。 假设你正在阅读Angular文章,并且遇到了想要尝试的代码。 您可以最小化您的浏览器并快速搭建一个新的Angular项目。

还有其他很棒的在线IDE,但是我相信Stackblitz的转折点是使用每个人都喜欢的 Visual Studio Code感觉和工具。 (ps: 本人使用体验,非常快速流畅, 附上图,比 sandbox 快很多)

1582374042909.jpg

在线地址: https://stackblitz.com/

JWT.io

如果您使用JSON Web令牌(JWT)保护应用程序安全,或者使用JWT允许用户访问后端的受保护资源。

决定是否应访问路线或资源的一种方法是检查令牌的到期时间。 有时候我们想要解码JWT以查看其有效 payload,jwt.io恰好提供了这一点。

这个在线工具使我们能够插入令牌以查看其有效 payload。 一旦我们粘贴了令牌,jwt.io便对该令牌进行解码并显示其有效payload

1582374387059.jpg

在线地址: https://jwt.io/

BundlePhobia

您是否曾经不确定过node_modules的大小,或者只是想知道将pakckage.json安装在您的计算机中的大小? BundlePhobia提供了答案

1582374462632.jpg

该工具使我们能够加载package.json文件,并显示将从package.json安装的依赖项的大小,也可以查询单包的体积。

在线地址: https://bundlephobia.com/

Babel REPL

Babel是一个免费的开放源代码JS转编译器,用于将现代ES代码转换为普通的 ES5 JavaScript。

该工具是Babeljs团队在网上建立的Web应用,可以将 ES6 +代码转换为ES5。

本人总结的两个比较方便的使用方式

  1. 方面面试时在线写高级语法。
  2. 可以快速查看某些 polyfill 是怎么写的。

1582374539633.jpg

在线地址: https://babeljs.io/en/repl

Prettier Playground

Prettier是一个自以为是的JS代码格式化程序。 它通过解析代码并使用JS最佳编码实践将其重新打印来实施一致的样式。

该工具已在我们的开发环境中广泛使用,但它也具有一个在线地址,你可以在其中美化您的代码。

1582375260418.jpg

在线地址: https://prettier.io/playground

postwoman

postwoman 是一款功能强大的网页调试和模拟发送HTTP请求的Chrome插件,支持几乎所有类型的HTTP请求,操作简单且方便。可用于接口测试,比如测试你用easy-mock生成的接口。

1582374841427.jpg

在线地址: https://postwoman.io/

本文翻译自 https://blog.bitsrc.io/12-use... 但是不仅仅是单纯地翻译,替换了原文中一些我觉得不太实用的并加入一些自己的总结。

最后

如果我的文章有帮助到你,希望你也能帮助我,欢迎关注我的微信公众号 秋风的笔记,回复好友 二次,可加微信并且加入交流群,秋风的笔记 将一直陪伴你的左右。

image

查看原文

Fractal 赞了文章 · 11月13日

2020前端开发者11个必备的网站

网上有很多很棒的工具,让作为前端开发人员的我们生活的更加轻松。在这篇文章中,我将快速介绍一下我在开发工作中经常使用的11种工具。

Node.green

用来查询当前 Node 版本是否某些功能。例如,对象展开符( Rest/Spread Properties)

1582372545876.jpg

可以看到在 Node v8.3.0 以下是不支持的。分别在 Node v8.5.0v8.2.1 下运行以下代码片段

const a = { foo: 1};
console.log({...a, b: 2}); 

1582372779948.jpg

当你遇到以上错误,那大多就是 Node 版本问题啦。

在线地址: https://node.green/

CanIUse

当你想要确定某个 Web API 的兼容性的时候,这个在线工具将轻松搞定。

假设我们想知道哪些浏览器及其版本将支持 Web Share API:navigator.share(...

1_pq1UczjJ8dhTsO6hCPntyw.png

查看结果。浏览器和支持navigator.share(…)的版本都列出了。

在线地址: https://caniuse.com/

Minify

为了减少应用程序代码的包大小,我们在发布到到生产环境的时候,需要使它们最小化。 最小化消除了空格,无效代码等。这能够使应用程序包大小的显着减小,从而节省浏览器上的加载时间。(虽然在当下,有 webpack uglifyJS 等插件,但是当我在开发非打包的简单应用的时候,这个是一个不错的选择。 )

1582373652825.jpg

在线地址: https://www.minifier.org/

Bit.dev

Bit.dev是一个非常棒的组件中心。 可以用它来托管,记录和管理来自不同项目的可复用组件。 这是增加代码复用,加速开发并优化团队协作的好方法。

这也是从头开始构建设计系统的不错选择(因为它本质上具有设计系统所需的一切)。 Bit.devBit完美配合,Bit是处理组件隔离和发布的开源工具。

Bit.dev支持React,带有TypeScriptReactAngularVue等。

1_Nj2EzGOskF51B5AKuR-szw.gif

在线地址: https://bit.dev/

Unminify

免费的在线工具,用于最小化(解压,反混淆)JavaScript,CSS和HTML代码,使其可读性强,美观

1582375400913.jpg

在线地址: https://unminify.com/

Stackblitz

这是每个人都喜欢的工具。Stackblitz使我们能够使用世界上最流行和使用最多的IDE,即web上的Visual Studio代码。

只需单击一下,Stackblitz 即可快速提供AngularReactVueVanillaRxJSTypeScript项目的框架。

当你想从浏览器中尝试一段代码或任何当前JS框架中的功能时,Stackblitz非常有用。 假设你正在阅读Angular文章,并且遇到了想要尝试的代码。 您可以最小化您的浏览器并快速搭建一个新的Angular项目。

还有其他很棒的在线IDE,但是我相信Stackblitz的转折点是使用每个人都喜欢的 Visual Studio Code感觉和工具。 (ps: 本人使用体验,非常快速流畅, 附上图,比 sandbox 快很多)

1582374042909.jpg

在线地址: https://stackblitz.com/

JWT.io

如果您使用JSON Web令牌(JWT)保护应用程序安全,或者使用JWT允许用户访问后端的受保护资源。

决定是否应访问路线或资源的一种方法是检查令牌的到期时间。 有时候我们想要解码JWT以查看其有效 payload,jwt.io恰好提供了这一点。

这个在线工具使我们能够插入令牌以查看其有效 payload。 一旦我们粘贴了令牌,jwt.io便对该令牌进行解码并显示其有效payload

1582374387059.jpg

在线地址: https://jwt.io/

BundlePhobia

您是否曾经不确定过node_modules的大小,或者只是想知道将pakckage.json安装在您的计算机中的大小? BundlePhobia提供了答案

1582374462632.jpg

该工具使我们能够加载package.json文件,并显示将从package.json安装的依赖项的大小,也可以查询单包的体积。

在线地址: https://bundlephobia.com/

Babel REPL

Babel是一个免费的开放源代码JS转编译器,用于将现代ES代码转换为普通的 ES5 JavaScript。

该工具是Babeljs团队在网上建立的Web应用,可以将 ES6 +代码转换为ES5。

本人总结的两个比较方便的使用方式

  1. 方面面试时在线写高级语法。
  2. 可以快速查看某些 polyfill 是怎么写的。

1582374539633.jpg

在线地址: https://babeljs.io/en/repl

Prettier Playground

Prettier是一个自以为是的JS代码格式化程序。 它通过解析代码并使用JS最佳编码实践将其重新打印来实施一致的样式。

该工具已在我们的开发环境中广泛使用,但它也具有一个在线地址,你可以在其中美化您的代码。

1582375260418.jpg

在线地址: https://prettier.io/playground

postwoman

postwoman 是一款功能强大的网页调试和模拟发送HTTP请求的Chrome插件,支持几乎所有类型的HTTP请求,操作简单且方便。可用于接口测试,比如测试你用easy-mock生成的接口。

1582374841427.jpg

在线地址: https://postwoman.io/

本文翻译自 https://blog.bitsrc.io/12-use... 但是不仅仅是单纯地翻译,替换了原文中一些我觉得不太实用的并加入一些自己的总结。

最后

如果我的文章有帮助到你,希望你也能帮助我,欢迎关注我的微信公众号 秋风的笔记,回复好友 二次,可加微信并且加入交流群,秋风的笔记 将一直陪伴你的左右。

image

查看原文

赞 26 收藏 19 评论 0

Fractal 回答了问题 · 11月12日

多重条件判读查询,代码如何优化?

大概是这么个思路,没有用类的方式,看懂就行

function build_filter($mapParam, $mapConfig)
{
   $arrFilter = [];
   foreach($mapParam as $k => $v)
   {
       if(array_key_exists($k, $mapConfig) === false)
           continue;
       $op = $mapConfig[$k];
       if(is_callable($op) === true)
       {
           $arr = call_user_func($op, $v);
           foreach($arr as $filter)
               array_push($arrFilter, $filter);
       }
       else
       {
           $arrFilter[] = [$k, $op, $v];
       }
    }
    return $arrFilter;
}
$requestParams = [
    'id' => 2,
    'name' => 'test',
    'dates' => '2020-11-01,2020-11-02',
];
$mapFilterConfig = [
    'id' => '=',
    'name' => '=',
    'score' => '>',
    'dates' => function($data){
        $arrDate = explode(',', $data);
        return [
            ['date', '>', $arrDate[0]],
            ['date', '<', $arrDate[1]],
        ];
    },
];
$arrFilter = build_filter($requestParams, $mapFilterConfig);
var_dump(json_encode($arrFilter, JSON_UNESCAPED_UNICODE));

输出 [["id","=",2],["name","=","test"],["date",">","2020-11-01"],["date","<","2020-11-02"]]

关注 4 回答 4

Fractal 回答了问题 · 11月9日

CSS这种写法 height: 5.00004e+06px; 中,应该怎么理解?哪里有文档可以参考呢?

搜索关键字 科学计数法

关注 3 回答 2

Fractal 回答了问题 · 11月9日

js时间段内并行执行多个方法?

实际来讲,比较好的做法不是用 setTimeout 应该用 window.requestAnimationFrame
参考 MDN 的文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

关注 3 回答 3

Fractal 回答了问题 · 11月9日

Vue 在低版本安卓不显示

Vue 2.0 需要 Android 6.0 以上

关注 3 回答 2

认证与成就

  • 获得 182 次点赞
  • 获得 16 枚徽章 获得 2 枚金徽章, 获得 5 枚银徽章, 获得 9 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-11-15
个人主页被 1.4k 人浏览