【问题标题】:Can an object *truly* inherit from Error.prototype?一个对象*真的*可以从 Error.prototype 继承吗?
【发布时间】:2013-09-22 11:47:50
【问题描述】:

[关于这个主题还有其他类似的问题,但没有一个回答我在这里提出的问题,AFAICT。 (即,我读过的所有答案都解释了为什么特定构造无法与提问者尝试做的事情有关,并且在某些情况下,它们提供了获得所需结果的替代方法。但是没有线程回答如何实现 真正的继承来自Error.prototype。)]

我已经尽力做到的最好的仍然基本上是无用的:

function MyErr(message) {
  var tmp = Error.apply(this, arguments);
  for (var prop in tmp) { this[prop] = tmp[prop]; }
}
MyErr.prototype = Object.create(Error.prototype);

即使在“手动”复制属性之后(“继承失败”的明确迹象),new MyErr("whatever") 返回的对象仍然甚至没有远程接近我会考虑“从Error.prototype 继承的实例”。与预期行为的偏差实在太多,无法在此处列出——它们在我所见的任何地方都会弹出!——但是,对于初学者来说,

console.log((new Error("some error")).toString())     // "Error: some error"
console.log((new MyErr("another error")).toString())  // "Error"
                                                      // expected
                                                      // "MyError: another error"

console.log((new Error("some error")).constructor)    // "Error()"
console.log((new MyErr("another error")).constructor) // "Error()"
                                                      // expected:
                                                      // "MyError()"

(如果有人想知道,不,constructor 不是在MyErrfor 循环中复制的属性之一。我检查了。)

对象可以真正继承Error.prototype吗?

鉴于要回答Array 的类似问题需要a lengthy treatise,我无法合理地期待我的问题在这里得到完整的答案,但我希望我能得到一个指向这样完整答案的指针。


更新:我尝试了 Bergi 的提议。下面我提供了一个完整的、独立的实现。1

<!DOCTYPE html>
<html><head><meta charset="utf-8"></head><body>
  <script src="http://code.jquery.com/jquery-latest.min.js"></script>
  <script>
    jQuery(document).ready(function ($) {
      function MyErr(message) {
        var tmp = Error.apply(this, arguments);
        Object.getOwnPropertyNames(tmp).forEach(function(p) {
          console.log('property: ' + p);
          Object.defineProperty(this, p,
                                Object.getOwnPropertyDescriptor(tmp, p));
        }, this);
        console.log('done creating ' + this);
      }
      MyErr.prototype = Object.create(Error.prototype, {
        constructor: {value:MyErr, configurable:true},
        name: {value:"MyErr", configurable:true}
      });

      var error = new Error("some error");
      var myerr = new MyErr("another error");

      console.log(error.toString());
      console.log(myerr.toString());

      console.log(error.constructor);
      console.log(myerr.constructor);
    });
  </script></body></html>

我在 Firebug 控制台中得到的输出是这样的:

done creating MyErr                                         testjs.html (line 13)
Error: some error                                           testjs.html (line 23)
MyErr                                                       testjs.html (line 24)
Error()                                                     testjs.html (line 26)
MyErr(message)                                              testjs.html (line 27)

请特别注意,输出不包含任何以property: 开头的行。这意味着MyErrforEach 循环中的console.log 语句永远不会被执行(大概forEach 回调的其余部分也是如此)。

如果我将 forEach 循环替换为

        for (var p in tmp) {
          console.log('property: ' + p);
          Object.defineProperty(this, p,
                                Object.getOwnPropertyDescriptor(tmp, p));
        };

...然后在输出前添加三个新行(省略行号):

property: fileName
property: lineNumber
property: columnNumber

这表明,即使 message 也不在通过这种方式访问​​的 tmp 属性中(尽管 message 确实出现在 Firebug 的变量检查器中 tmp 的属性中,以及不计其数的 em> 其他也没有显示在上面的控制台输出中)。

此外,显式设置的name 属性确实出现在myerr.toString() 的输出中,但此方法的行为方式仍与error.toString() 不同。

如果我在 MyErr 函数的末尾添加以下行,我确实会从 myerr.toString() 获得预期的输出:

this.message = message;

...但是我并没有从中得到什么安慰,因为这个动作,连同我上面展示的大多数其他动作,都是针对我发布的特定示例的特设技巧,但这些只是为了作为一个似乎更深层次问题的说明,即预期继承模型的完全分解。

这个问题的目的是找出如何从Error.prototype实现“真正的继承”,如果这不可能,那么尽可能地模拟它,有一个清晰的图片 模拟未能实现完整继承模型的程度。我强调,这两个替代方案中的后者不应与每次我们碰巧注意到一些不符合预期的新方式时增量修补一些有缺陷的实现的策略相混淆。这种“增量修补”方法为无声的 bug 提供了理想的环境,因此是疯狂的秘诀。


UPDATE2: JS 的不可预测性似乎没有尽头。我刚刚发现,至少在 Firebug 控制台中,Object.getOwnPropertyNames 会根据其参数是非原子表达式还是先前分配了相同非原子表达式的变量而产生不同的值。 wtf?

例如,以下内容是直接从 Firebug 控制台中的交互复制粘贴的(为了便于阅读,我在每个命令前添加了一个空行):

>>> Object.getOwnPropertyNames(new Error("not assigned"))
[]

>>> errorvar = new Error("assigned")
Error: assigned
(no source for debugger eval code)

>>> Object.getOwnPropertyNames(errorvar)
["fileName", "lineNumber", "message", "stack"]

(分配给errorvar 之后的输出似乎与创建Error 对象这一事实有关,即使它不是thrown。即使删除所有内容也会出现相同的输出在该行中new 的左侧。)

如果这还不够,如果我在脚本中运行它

console.log(Object.getOwnPropertyNames(new Error("not assigned")))
var errorvar = new Error("assigned");
console.log(Object.getOwnPropertyNames(errorvar));

Firebug 控制台中的输出是

[ ]
[ ]

我不知道这种不稳定的行为是由 ECMAScript5、JavaScript、Firefox、Firebug 还是什么引起的,但它让我发疯了......


1 我发布了这个独立的实现来鼓励其他人尝试一下。如果我从 JavaScript 中学到了一件事,那就是,无论代码多么简单,无论你认为自己对 JavaScript 了解多少,了解 JavaScript 代码要做什么的唯一方法就是运行它。遗憾的是,JavaScript 编程仍然是令人尴尬的实验性活动。 JS 不适合扶手椅程序员! :)

【问题讨论】:

  • @FelixKling:谢谢。我已经阅读了这些以及其他类似的 SO 线程。他们都解释了为什么一个特定的 构造不能工作。这并没有解决我的问题。我的问题是“如何实现一个完全继承自 Error.prototype 的对象”。请参阅我链接的Array 上的文章,了解我尝试使用Error 实现的目标。
  • 错误具有本机代码 ctor 和方法。您只能 a) 扩展错误对象,或编写执行您想要的操作并在内部使用错误对象的包装器。
  • firebug 和 chrome 控制台是神奇的环境,它们没有与普通 js 环境完全相同的属性。在 chrome 控制台中特别困扰我的一件事是,如果您在控制台中将对象设置为 null,则对象将永远保留。

标签: javascript oop inheritance prototypal-inheritance


【解决方案1】:

嗯,除了堆栈跟踪和instanceof,我不知道您还需要什么。试试这个:

function MyError(message) {
    this.name = this.constructor.name;
    this.message = message;
    if (Error.captureStackTrace) {
        Error.captureStackTrace(this, this.constructor);
    } else {
        var stack = new Error().stack;
        if (typeof stack === "string") {
            stack = stack.split("\n");
            stack.shift();
            this.stack = stack.join("\n");
        }
    }
}
MyError.prototype = Object.create(Error.prototype, {
    constructor: {
        value: MyError,
        writable: true,
        configurable: true
    }
});
try {
    throw new MyError("message");
} catch (e) {
    console.log(e + "");
    console.log(e.stack + "");
}
try {
    throw new Error("message");
} catch (e) {
    console.log(e + "");
    console.log(e.stack + "");
}

http://jsfiddle.net/VjAC7/1/

MyError: message
MyError: message
    at window.onload (http://fiddle.jshell.net/VjAC7/1/show/:44:11)
Error: message
Error: message
    at window.onload (http://fiddle.jshell.net/VjAC7/1/show/:51:11)

火狐

MyError: message
window.onload@http://fiddle.jshell.net/VjAC7/1/show/:44
Error: message
window.onload@http://fiddle.jshell.net/VjAC7/1/show/:51

IE10

Error: message 
Error: message
    at onload (http://fiddle.jshell.net/VjAC7/1/show/:44:5) 
Error: message 
Error: message
    at onload (http://fiddle.jshell.net/VjAC7/1/show/:51:5) 

【讨论】:

    【解决方案2】:

    在我看来,下面创建的对象似乎继承了错误对象的所有属性并且行为相同...也许您可以解释一下/如果它不同?

    var MyErr = function(message) {
      this.name = 'My Error';
      this.message = message;
    }
    MyErr.prototype = new Error();
    MyErr.prototype.constructor = MyErr;
    
    throw new MyErr('Uhoh!') // My Error: Uhoh!
    

    在原型上,我可以看到 get stackset stack 继承自 Error。

    【讨论】:

    • 我发布了显式示例,展示了实际行为与预期行为有何不同。
    • @kjo 对不起,我想我不明白你所指的差异。
    【解决方案3】:

    从 ES2015(又名“ES6”)开始,是的,您可以真正继承 Error(和 Array)。在任何最近的 Chrome、Firefox、Safari 或 Edge 上:

    class MyError extends Error {
      myOwnMethod() {
        console.log("MyError method");
      }
    }
    try {
      throw new MyError("Thrown");
    }
    catch (e) {
      console.log(e instanceof Error);   // true
      console.log(e instanceof MyError); // true
      console.log(e.message);            // "Thrown"
      e.myOwnMethod();
    }

    这是您“更新”的一部分:

    这表明即使message 也不在以这种方式访问​​的tmp 属性中(尽管消息确实出现在 Firebug 变量检查器中的 tmp 属性中,以及控制台中也未显示的大量其他属性中上面的输出)。

    这告诉我们message 不是“自己的”属性(所以没有出现在Object.getOwnPropertyNames 的结果中),也不是可枚举的(所以没有出现在for-in 中)。所以显然它是一个不可枚举的继承属性。

    如果是这样,那是违反规范的行为,现已修复。 spec at the time 非常清楚:在没有 new 的情况下调用 Error 就像在 with new 的情况下调用它一样,当你用 new 调用 Error 并传递一个参数时,它应该在新创建的实例上设置一个名为message自己的 属性,并使用String(theArgumentYouPassed) 进行初始化。

    在 2017 年,这是正确的:Error("foo").hasOwnProperty("message") 在 Chrome 和 Firefox 上是 true,这与旧规范和 current one 保持一致。


    你的“更新 2”:

    例如,以下内容是直接从 Firebug 控制台中的交互复制粘贴的(为了便于阅读,我在每个命令前添加了一个空行):

    >>> Object.getOwnPropertyNames(新错误(“未分配”)) [] >>> errorvar = new Error("已分配") 错误:已分配 (没有调试器评估代码的来源) >>> Object.getOwnPropertyNames(errorvar) ["fileName", "lineNumber", "message", "stack"]

    如果它在 2013 年在 Firebug 中做到了这一点,那么您会很高兴知道它在 2017 年在 Firefox 的内置开发人员工具中没有做到这一点。在 Chrome 中也不行。对Object.getOwnPropertyNames 的两次调用的结果是一致的(现在)。您所描述的行为很奇怪,我希望我有机会使用 2013 年的 Firefox+Firebug 进行尝试。仅将对象引用分配给变量会影响将引用传递给 Object.getOwnPropertyNames 时返回的内容,这是非常难以接受的。

    【讨论】:

      【解决方案4】:

      使用这个:

      function MyErr(message) {
        var tmp = Error.apply(this, arguments);
        Object.getOwnPropertyNames(tmp).forEach(function(p) {
          Object.defineProperty(this, p, Object.getOwnPropertyDescriptor(tmp, p));
        }, this);
      }
      MyErr.prototype = Object.create(Error.prototype, {
        constructor: {value:MyErr, configurable:true},
        name: {value:"MyErr", configurable:true}
      });
      

      这甚至会将不可枚举的属性从tmp 复制到this,它将reset the .constructor property 并将.name 属性设置为.toString 所期望的那样。

      > (new MyErr("another error")).toString()
      "MyErr: another error"
      > (new MyErr("another error")).constructor
      Function MyErr
      

      【讨论】:

      • 这很奇怪。你能发布你使用的是什么版本的FF吗?
      • 23.01 在 Windows 7 上;我发布的自包含实现有什么结果?
      【解决方案5】:

      与其尝试从错误继承,不如尝试扩展一个新的错误对象并返回它 我在这里写了一个简单的例子来实现这一点:http://jsfiddle.net/Elak/tvXqC/2/

      基本上,您可以扩展返回的对象并覆盖错误名称(然后在控制台或您记录该错误的任何地方使用

      var Errors = {};
      (function(){
          var myError = function(message){
              return $.extend(new Error(arguments[0]),{
                  printExample : function(){
                      $("#out").html("Error: " + this.name + ": " + this.message);
                  }
              },
              // overwriting error object properties. default for name is Error...
              // this way you can control the log output
              {
                  name: "ExceptionNameWhichWillShowInLog"
              }
              );
          };
          Errors.MyError = myError;
      })();
      

      具有函数 printExample 的对象现在是您自己的方法的位置(替换原型实现...)。

      您实际上对 this 具有相同的访问权限,现在 this 是“真实”错误对象的实例,您可以随心所欲地使用该实例...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-11-11
        • 2012-05-19
        • 1970-01-01
        • 1970-01-01
        • 2020-06-11
        • 1970-01-01
        • 2016-02-08
        • 1970-01-01
        相关资源
        最近更新 更多