ECMAScript/JavaScript - 继承和新运算符

ECMAScript/JavaScript - inheritance and the new operator

我目前正在学习编程 JavaScript/ECMAScript,并且正在看书 "Professional JavaScript for Web Developers, 3rd Edition"。我很喜欢这本书,同时我在 Internet 上寻找描述同一主题的材料的支持。就像所有从一种语言出来学习另一种语言的 OO 程序员一样,我遇到了困难。我目前在第 6 章学习继承(PAG.201、202 - 如果有人有这本书,并希望看到这部分内容),我很困惑,注意力不集中。

我很了解这个主题背后的机制......但我不明白为什么。例如,本书作者展示了以下案例(部分行略微调整了注释和分隔符):

// SUPERTYPE
function SuperType()
{
    this.property = true;
}

SuperType.prototype.getSuperValue = function()
{
    return this.property;
};

// SUBTYPE
function SubType()
{
    this.subproperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function ()
{
    return this.subproperty;
};

// TEST
var instance = new SubType();
alert(instance.getSuperValue());    //true

我了解到,在 ECMAScript 中,类型的实例继承其原型上配置的属性和方法,并且通过原型 属性 继承是可能的。因此,将一种类型的原型更改为另一种类型会导致行为和属性被重用。

让我不知所措的事实是,将类型 A 的新实例分配为类型 B 的原型,据了解 B 可能有实例,A 也可能有实例(不是说这应该发生,但允许发生)。

我的意思是什么?好吧……假设我有一个名为 "Vehicle" 的超类型和一个名为 "Car" 的子类型。如果我使用 "Vehicle" 的实例作为 "Car" 的超类型(毕竟,子类型不知道他的超类型,而只知道它的原型 - 正如书中的 PAG.185 所说),我'我创造了一些完全抽象的东西来作为具体的东西。此外,我使用的可能只是抽象超类型 (?) 的许多可能实例之一,因为没有什么可以阻止超类型将来在此类代码中拥有更多自身实例,添加更多 code/methods/properties/etc。

我真正的问题是……这到底是什么?客气...开发人员应该担心关联正确的实例,而且只有其中一个?

另外,我对原型链有些疑惑。例如,我知道当 property/method 是 requested/used 时,它首先在对象中查找,然后是其原型(如果未找到)。这个故事中的上下文明显发生了什么?我的意思是,搜索仅在没有 运行 任何上下文的对象原型链中进行?我之前读到过,当使用一个变量时,它会在上下文堆栈中查找。 上下文栈和原型链是什么关系?

我知道我可能有点 boring/annoying 问这个问题,但是谁能简单详细地解释一下,好吗?

提前感谢您的耐心、时间和关注。

编辑

有些人真的不明白我在想什么,我很抱歉。以下是所有内容的摘要:

创建 A 的新实例,其中 A 是 B 的超类型,这是否允许且可能,即使 A 遵循某种抽象?如果允许这样做,其后果是什么?当实例使用某些东西(属性 或方法)时,上下文堆栈在哪里?

总结一下:虽然我在问问题,但要意识到我在问继承的含义。就是...

JavaScript 有两个大概念,您似乎在此处触及,Closure/ScopePrototyping/inheritance。这些与大多数其他现代语言的工作方式不同,因此可能会给从更经典的语言过渡到 JavaScript 的人们造成混淆。

原型

基本上 JavaScript 中的所有内容都是 Object。在 JavaScript 中,Objects 有一个叫做 prototype 的东西,它是对父对象的引用Object 可能具有由后代 Objects 继承的属性。 原型也可能是null,它发生在原型链.

的顶端

原型中的通常结构是让更具体的对象继承更抽象的对象,一直到 Object.prototypenull

在对象上查找 属性 时,您从该对象开始,如果它没有 属性,则您在原型链中上一级并再次查找。如果你达到 null 那么就没有这样的 属性.
这也意味着您可以通过在原型链下方设置同名属性来隐藏继承的属性。

function Foo() {
}
Foo.prototype = {};
Foo.prototype.foobar = 'foo';

function Bar() {
    // if required
    // Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype); // inhert
Bar.prototype.foobar = 'bar';

var baz = new Bar();
baz.foobar; // "bar"
baz instanceof Bar; // true
baz instanceof Foo; // true

原型可以是任何对象,因此使用另一个对象的实例来设置原型链没有任何违法行为,但是如果您试图扩展或创建子类型,这通常被认为是不好的做法

关闭

这是查找变量和标识符的方式。它与 prototype 无关,完全取决于定义的地方。标识符在范围内是 "trapped",其他范围可以 "close over"

JavaScript 中,作用域的主要提供者是 functions

var a = 1;
function foo() {
    var b = 2;
    // foo can see `a` and `b` as well as `fizz`, `foo`, `bar` and `baz`
    function bar() {
        var c = 3;
        // bar can see `a`, `b` and `c` as well as `fizz`, `foo`, `bar` and `baz`
    }
    function baz() {
        var d = 4;
        // baz can see `a`, `b` and `d` as well as `fizz`, `foo`, `bar` and `baz`
    }
}
function fizz() {
    var e = 1;
    // fizz can see `a` and `e` as well as `fizz` and `foo`
}

但是,如果您要在 foo 上创建一个试图查找 bar 的 属性,您将得到一个 ReferenceError(或 undefined), 如果你试图继承 foo

foo.meth = function () {return bar;};
foo.meth(); // undefined

function Hello() {
    this.bar; // undefined
}
Hello.prototype = foo;
(new Hello()).bar; // undefined

访问 bar 的唯一方法是让 foo 为您完成,例如通过 return bar;foo

末尾

如果bar是由foo返回的,那么变量foo可以在[=19=中看到"live on" ].如果 bar.

中没有 eval 样式的代码,智能垃圾收集器可能仍会清理无法访问的垃圾。

正如函数内的变量不会在多个调用之间共享一样,这些变量也不会在多个 foo 调用之间共享,但是它们将在多个 bar 调用之间共享

function foo() {
    var a = 0,
        b = -1;
    function bar() {
        return ++a;
    }
    return bar; // pass `bar` out of `foo`
}
var fn1 = foo(), fn2 = foo();
// by this point, the `b` created by `foo()` may already be cleaned up
fn1(); // 1
fn1(); // 2
fn2(); // 1, it's a different `a` to the `a` in `fn1`
fn2(); // 2