对象*真的可以*从Error.prototype继承吗?

时间:2013-09-22 11:47:51

标签: javascript oop inheritance prototypal-inheritance

[关于这个主题还有其他类似的问题,但没有一个回答我在这里提出的问题,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不是在MyErr的{​​{1}}循环中复制的属性之一。我检查了。)

对象 能否真正从for继承

鉴于回答Error.prototype需要a lengthy treatise的类似问题,我不能合理地期望在这里完整回答我的问题,但我希望我能指出这样的完整答案。


更新:我尝试了Bergi的提案。下面我提供一个完整的,自包含的实现。 1

Array

我在Firebug控制台中获得的输出是:

<!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>

请特别注意,输出不包含以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循环中的forEach语句永远不会被执行(对于MyErr的其余部分,可能会相同回调)。

如果我用

替换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确实出现在Firebug变量检查器中的message属性中 abazillion 的其他一些也未在上面的控制台输出中显示。)

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

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

MyErr

......但是我对此感到很不安,因为这个动作,以及我上面展示的大多数其他动作,都是针对我发布的具体例子的临时克隆,但这些仅仅是为了作为一个看似更深层次问题的例证,即预期继承模型的完全细分。

这个问题的目的是找出如何从this.message = message; 实现“真正的继承”,如果不可能,那么尽可能地模拟它,有一个清晰的图片< / em>模拟无法实现完整继承模型的程度。我强调,每当我们碰巧注意到一些不符合期望的新方法时,不应将这两个备选方案中的后者与逐步修补一些有缺陷的实现的策略相混淆。这种“增量修补”方法为静默错误提供了理想的环境,因此是疯狂的一种方法。


UPDATE2: JS的不可预测性似乎没有尽头。我刚刚发现,至少在Firebug控制台中,Error.prototype会产生不同的值,具体取决于其参数是非原子表达式还是先前已分配了相同非原子表达式的变量。 <子> WTF?

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

Object.getOwnPropertyNames

(分配给>>> 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 n。相同的输出即使删除该行中throw左侧的所有内容,也会显示。)

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

new

Firebug控制台中的输出是

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

我不知道这种不稳定的行为是由于ECMAScript5,还是JavaScript,Firefox,还是Firebug,或者是什么,但它让我疯了......


1 我发布了这个自包含的实现,以鼓励其他人尝试。如果有一件事我已经了解了JavaScript,无论代码多么简单,无论你认为你对JavaScript了解多少,了解一些JavaScript代码将要做的唯一方法就是运行它。可悲的是,JavaScript编程仍然是一个令人尴尬的实验性活动。 JS不适合扶手椅程序员! :)

5 个答案:

答案 0 :(得分: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) 

答案 1 :(得分: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从错误中获取。

答案 2 :(得分:1)

从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非常清楚:在没有Error的情况下拨打new就像使用 new呼叫一样,当您拨打Error时使用new并传递一个参数,它应该在新创建的实例上设置一个名为message拥有属性,并使用String(theArgumentYouPassed)进行初始化。

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

重新启用“更新2”:

  

例如,以下内容直接从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"]

如果它在2013年在Firebug中这样做,你会很高兴知道它在2017年的Firefox内置开发者工具中没有这样做。也不是Chrome的。对Object.getOwnPropertyNames的两次调用的结果是一致的(现在)。你所描述的行为是奇怪的,我希望我有机会尝试使用2013的Firefox + Firebug。购买时很难简单地将对象引用分配给变量会对将该引用传递给Object.getOwnPropertyNames时得到的内容产生影响。

答案 3 :(得分: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}
});

这甚至会将不可枚举的属性从tmp复制到this,它将reset the .constructor property并按{4}}的预期设置.name属性

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

答案 4 :(得分: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函数的对象现在是您自己的方法的位置(替换原型实现......)。

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