如何让 Chrome 的调用者/参数显示一些东西

How to get Chrome's caller / arguments to show something

我使用以下内容创建了一个函数和原型:

function Range(from, to) {
    if (new.target === undefined) {
        throw new Error("Range must be invoked with new.")
    }
    Object.assign(this, {from, to});
}
Range.prototype = {
    includes: function(x) {
        return x >= this.from && x <= this.to;
    },
    toString: function() {
        return `[${this.from}, ${this.to}]`;
    }
}

下面是它在 Chrome 的控制台中显示的内容:

includes函数为例,name是函数名,length是函数的元数。而[[FunctionLocation]]似乎是JS引擎底层实现的一部分,所以对于实际的js调试并不重要。但是接下来是什么:

这两个属性仅在函数执行时才被填充。所以要在调试器中看到它们,你需要用断点停止函数。

如果这样做,Range.arguments 将包含有关参数的信息,Range.caller 将包含调用 Range() 的函数(这将是 null 如果 Range() 是从 top-level) 调用的。

function Range(from, to) {
    if (new.target === undefined) {
        throw new Error("Range must be invoked with new.")
    }
    Object.assign(this, {from, to});
    debugger;
}
Range.prototype = {
    includes: function(x) {
        return x >= this.from && x <= this.to;
    },
    toString: function() {
        return `[${this.from}, ${this.to}]`;
    }
}

function callRange() {
  x = new Range(1, 10);
  console.log(x);
}

callRange();

适合 ES6 初学者

如果您使用推荐的 class 语法,您将获得自动错误,而无需手动检查 thisnew.target -

class Range {
  constructor(from, to) {
    Object.assign(this, {from, to})
  }
}

// with `new`
const p = new Range(1,2)
console.log(p)

// without `new`
const q = Range(1,2)
console.log(q)

{ from: 1, to: 2 }
TypeError: Cannot call a class constructor without |new|

您可以直接在 class -

中定义 so-called 实例方法,而不是用手写对象覆盖 Range.prototype

class Range {
  constructor(from, to) {
    Object.assign(this, {from, to})
  }
  includes(x) {
    return x >= this.from && x <= this.to
  }
  toString() {
    return `[${this.from}, ${this.to}]`
  }
}

const p = new Range(3,6)
console.log(`p: ${p}`)
console.log(`p includes 4: ${p.includes(4)}`)
console.log(`p includes 9: ${p.includes(9)}`)

p: [3, 6]
p includes 4: true
p includes 9: false

ES2020 及以后

我要提到的是,越来越多的人支持大型 classes 上的模块化设计。如果 Range 要包含 20 个方法,但调用者仅使用其中的 1 个或 2 个,则 18 个方法将捆绑到最终应用程序中,但仍未使用。需要一个极其复杂的编译器来删除这些死代码,这个过程称为 tree shaking。下面我们看到 range 写成模块而不是 class。注意只有 public 方法可以使用 export 访问,允许模块作者在 she/he 希望的任何地方限制对私有方法的访问 -

// range.js
const tag = Symbol()
const range = (from, to) => ({ tag, from, to })
const isRange = (t) => t.tag === tag
const includes = (t, x) => x >= t.from && x <= t.to
const toString = (t) => `[${t.from}, ${t.to}]`
export { range, isRange, includes, toString }

main 模块中,调用者仅 import 需要什么,允许编译器有效地修剪导入模块的任何未使用部分 -

// main.js
import { range, includes, toString } from "./range.js"
const p = range(3,6)
console.log(`p: ${toString(p)}`)
console.log(`p includes 4: ${includes(p,4)}`)
console.log(`p includes 9: ${includes(p,9)}`)
p: [3, 6]
p includes 4: true
p includes 9: false

有关 import 的更多信息,请参阅 Alex Rauschmayer 的 Dynamically Importing ES modules

有关不断发展的 ES 模块计划的更多见解,请参阅 v9 of Google Firebase's new API.

等项目
  • Version 8. This is the JavaScript interface that Firebase has maintained for several years and is familiar to Web developers with existing Firebase apps. Because Firebase will remove support for this version after one major release cycle, new apps should instead adopt version 9.
  • Modular version 9. This SDK introduces a modular approach that provides reduced SDK size and greater efficiency with modern JavaScript build tools such as webpack or Rollup.

Version 9 integrates well with build tools that strip out code that isn't being used in your app, a process known as "tree-shaking." Apps built with this SDK benefit from greatly reduced size footprints. Version 8, though available as a module, does not have a strictly modular structure and does not provide the same degree of size reduction. ...

吃蛋糕也吃

使用一种简单的技术,我们可以编写 modular-based 代码并仍然提供 object-oriented 接口(如果需要)。下面我们写一个 Range class 作为上面模块函数的简单包装 -

// range.js

// ...

class Range {
  constructor(t) { this.t = t }
  isRange() { return isRange(this.t) }
  includes(x) { return includes(this.t, x) }
  toString() { return toString(this.t) }
  static new(a,b) { return new Range(range(a,b)) }
}

export { Range, ... }

在您的主模块中,您可以导入 Range class 以通过 object-oriented 接口访问该模块的所有功能 -

// main.js
import { Range } from "./range"

const p = Range.new(3,6)
console.log(`p: ${p}`)
console.log(`p includes 4: ${p.includes(4)}`)
console.log(`p includes 9: ${p.includes(9)}`)

现在您的模块可以在函数式风格 object-oriented 风格中使用。请注意,当以这种方式使用 object-oriented 接口时,其代价是无法 tree-shake 这部分代码。但是,希望利用死代码消除的程序可以改用函数式风格的界面。

在下面这个可运行的演示中验证 -

// range.js
const tag = Symbol()
const range = (from, to) => ({ tag, from, to })
const isRange = (t) => t.tag === tag
const includes = (t, x) => x >= t.from && x <= t.to
const toString = (t) => `[${t.from}, ${t.to}]`

class Range {
  constructor(t) { this.t = t }
  isRange() { return isRange(this.t) }
  includes(x) { return includes(this.t, x) }
  toString() { return toString(this.t) }
  static new(a,b) { return new Range(range(a,b)) }
}

// main.js
const p = Range.new(3,6)
console.log(`p: ${p}`)
console.log(`p includes 4: ${p.includes(4)}`)
console.log(`p includes 9: ${p.includes(9)}`)

p: [3, 6]
p includes 4: true
p includes 9: false

我在其他答案中写过这个技巧 -