2

[关于这个主题还有其他类似的问题,但没有一个回答我在这里提出的问题,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需要冗长的论文,我不能合理地期待我的问题在这里得到完整的答案,但我希望我能得到一个指向这样完整答案的指针。


更新:我尝试了 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:。这意味着循环中的console.log语句永远不会被执行(并且对于回调的其余部分可能也是如此)。forEachMyErr 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

这表明,甚至不是以这种方式可访问messagetmp属性之一(尽管message确实出现tmp在 Firebug 的变量检查器中的 's 属性中,以及上面控制台输出中也没有显示的大量其他属性中)

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

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

this.message = message;

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

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


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

例如,以下内容是直接从 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 不适合扶手椅程序员!:)

4

5 回答 5

4

好吧,我不知道除了堆栈跟踪和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) 
于 2013-09-23T10:28:24.190 回答
2

对我来说,似乎下面创建的 Object 继承了 Error 对象的所有属性并且行为相同......也许你可以解释它如何/如果它不同?

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。

于 2013-09-22T12:05:16.057 回答
1

从 ES2015(又名“ES6”)开始,是的,您可以真正继承Error(and 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)。所以显然它是一个不可枚举的继承属性。

如果是这样,那是一个规范违规,此后已得到修复。当时的规范非常明确:调用Errorwithoutnew就像调用with new一样,当你调用Errorwithnew并传递一个参数时,它应该设置一个在新创建的实例上调用的自己的属性,初始化为。messageString(theArgumentYouPassed)

在 2017 年,它是正确的:Error("foo").hasOwnProperty("message")trueChrome 和 Firefox 上,这与旧规范和当前规范一致。


重新您的“更新 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.

于 2017-04-21T05:30:40.090 回答
0

用这个:

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}
});

这甚至会将不可枚举的属性从 复制tmpthis,它将重置该.constructor属性.name,并将按预期设置该属性.toString

> (new MyErr("another error")).toString()
"MyErr: another error"
> (new MyErr("another error")).constructor
Function MyErr
于 2013-09-22T12:13:19.440 回答
0

与其尝试从错误继承,不如尝试扩展一个新的错误对象并返回它我写了一个快速示例如何在这里实现这一点: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 的对象现在是您自己的方法的位置(替换原型实现......)。

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

于 2013-09-22T12:59:12.063 回答