【问题标题】:Understanding the __extends function generated by typescript?理解 typescript 生成的 __extends 函数?
【发布时间】:2018-02-07 19:10:09
【问题描述】:

我正在玩Typescript 并试图理解编译器生成的编译后的Javascript代码

打字稿代码:

class A { }
class B extends A { }

生成的 Javascript 代码:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var A = /** @class */ (function () {
    function A() {
    }
    return A;
}());
var B = /** @class */ (function (_super) {
    __extends(B, _super);
    function B() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return B;
}(A));

Mozilla docs 的 Javascript 继承是这样的:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Typescript生成的代码中我看不懂的部分是这个

1。这条线的目的是什么?看起来它正在将 A 的所有键复制到 B 中?这是对静态属性的某种破解吗?

var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };

2。这是在做什么?

function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());

这部分没看懂:(__.prototype = b.prototype, new __())

为什么函数 B() 会返回这个?

return _super !== null && _super.apply(this, arguments) || this;

如果有人能逐行向我解释这一点,我将不胜感激。

【问题讨论】:

  • 您引用的 Mozilla 文档用于 基于原型的 继承,直到最近 (ES6) 才有这样的选择。通过使用class,您可以选择使用基于类的对象。就基于原型的对象而言,所有这些都是实现类继承所必需的。该函数在目标 JavaScript/ES 版本不支持它们时出现,否则它只会发出 class A {} 等等。
  • 好的,有道理。那么这是否意味着 mozilla 代码完成了 Typescript 生成的代码正在做的所有事情(或者它是否也在做一些额外的事情)?即使出于学习目的而过时,也仍然会感谢对神秘代码的解释
  • 它并没有过时,它只是两个不同的模型。例如,我认为类对象仍然有原型。您不需要理解该代码的每一行,但是根据原型模型实现类模型并支持一些怪癖(例如构造函数调用基本构造函数(这就是 super 的东西)就足够了是),甚至在某些情况下被允许返回不同的对象)。它看起来不干净的原因是因为它不是 - 它必须伪造一些尚未内置到您的版本的语言中的东西。
  • @Jesper class 仍然使用原型继承。

标签: javascript prototype javascript-objects proto javascript-inheritance


【解决方案1】:

这有助于理解 TypeScript 类扩展器中真正发生的事情。事实上,以下代码包含完全相同的逻辑,因为您甚至可以将其用作原始代码的替代品,而无需使用所有使其极难阅读的特殊技巧。

  • 前半部分只尝试为'setPrototypeOf'寻找浏览器兼容版本
  • 后半部分实现从基类继承

尝试将 TypeScript 的 __extends 函数替换为以下代码:

// refactored version of __extends for better readability

if(!(this && this.__extends))                      // skip if already exists
{
  var __extends = function(derived_cl, base_cl)    // main function
  {
    // find browser compatible substitute for implementing 'setPrototypeOf'

    if(Object.setPrototypeOf)                      // first try
      Object.setPrototypeOf(derived_cl, base_cl);
    else if ({ __proto__: [] } instanceof Array)   // second try
      derived_cl.__proto__ = base_cl;
    else                                           // third try
      for (var p in base_cl)
        if (base_cl.hasOwnProperty(p)) derived_cl[p] = derived_cl[p];

    // construct the derived class

    if(base_cl === null)
      Object.create(base_cl)                 // create empty base class if null
    else
    {
      var deriver = function(){}             // prepare derived object
      deriver.constructor = derived_cl;      // get constructor from derived class
      deriver.prototype = base_cl.prototype; // get prototype from base class
      derived_cl.prototype = new deriver();  // construct the derived class
    }
  }
}

在接下来的版本中,所有使它成为通用的东西都被去掉了,比如浏览器兼容性处理和'null'衍生。我不鼓励任何人将下面的代码作为永久替代品,但下面的版本确实展示了类继承如何与 TypeScript 一起工作的基本原理

// Barebone version of __extends for best comprehension

var __extends = function(derived_cl,base_cl)
{
  Object.setPrototypeOf(derived_cl,base_cl);
  var deriver = function(){}             // prepare derived object
  deriver.constructor = derived_cl;      // get constructor from derived class
  deriver.prototype = base_cl.prototype; // get prototype from base class
  derived_cl.prototype = new deriver();  // construct derived class
}

尝试以下工作示例:

var __extends = function(derived_cl,base_cl)
{
  Object.setPrototypeOf(derived_cl,base_cl);
  var deriver = function(){}		 // prepare derived object
  deriver.constructor = derived_cl;	 // get constructor from derived class
  deriver.prototype = base_cl.prototype; // get prototype from base class
  derived_cl.prototype = new deriver();  // construct derived class
}

// define the base class, and another class that is derived from base

var Base = function()
{
  this.method1 = function() { return "replace the batteries" }
  this.method2 = function() { return "recharge the batteries" }
}

var Derived = function(_super) {
  function Derived() {
    __extends(this, _super); _super.apply(this, arguments);

    this.method3 = function() { return "reverse the batteries" }
    this.method4 = function() { return "read the damn manual" }
  }
  return Derived
}(Base)

// Let's do some testing: create the objects and call their methods

var oBase = new Base();             // create the base object
var oDerived = new Derived();       // create the derived object

console.log(oDerived.method2());    // result: 'recharge the batteries'
console.log(oDerived.method4());    // result: 'read the damn manual'

console.log(oBase.method1()) ;      // result: 'replace the batteries'
try{ console.log(oBase.method3()) }
catch(e) {console.log(e.message)};  // result: 'oBase.method3 is not a function'

最后,当你厌倦了 TypeScript 的混淆继承机制时,我发现 __extend 函数甚至没有必要,只需让原生 JavaScript 函数 'apply' 完成工作 ,通过原型链正好实现了我们的目标继承机制。

试试最后一个例子……忘记其他一切,还是我错过了什么?

// Short, readable, explainable, understandable, ...
// probably a 'best practice' for JavaScript inheritance !

var Base = function()
{
  this.method1 = function() { return "replace the batteries" }
  this.method2 = function() { return "recharge the batteries" }
}

var Derived = function(){
    Base.apply(this, arguments);  // Here we inherit all methods from Base!
    this.method3 = function() { return "reverse the batteries" }
    this.method4 = function() { return "read the damn manual" }
}

var oDerived = new Derived();       // create the derived object
console.log(oDerived.method2());    // result: 'recharge the batteries'

【讨论】:

  • __extends 是一个在面向对象语言中模拟单类继承的函数,并为派生函数返回一个新的构造函数,该派生函数可以创建从基础对象继承的对象....我试图理解这与调用 Object.setPrototypeOf() 有何不同...所以如果您请...
【解决方案2】:

我自己对此很好奇,找不到快速答案,所以这是我的细分:

它的作用

__extends 是一个在面向对象语言中模拟单类继承的函数,并为派生函数返回一个新的构造函数,该派生函数可以创建从基对象继承的对象。

注 1:

我自己实际上并没有意识到这一点,但是如果您执行以下操作,其中所有值都是真实的,则变量将设置为最后一个正在测试的项目的值,除非其中一个是虚假的,在这种情况下变量被设置为假:

// value1 is a function with the definition function() {}
var value1 = true && true && function() {};

// value2 is false
var value2 = true  && false && function() {};

// value3 is true
var value3 = true && function() {} && true;

我之所以提到这一点,是因为这是我看到这个 javascript 时最让我困惑的事情,它在 __extends 函数定义中被使用了几次。

注意 2: 参数 d(可能代表派生)和 b(可能代表基)都是构造函数,而不是实例对象。

注 3:

prototype 是函数的属性,它是“构造函数”函数(即使用new <function name>() 创建的对象)使用的原型对象。

当您使用new 运算符构造新对象时,新对象的内部[[PROTOTYPE]] 又名__proto__ 被设置为函数的原型属性。

function Person() {  
}

// Construct new object 
var p = new Person();

// true
console.log(p.__proto__ === Person.prototype);

// true
console.log(Person.prototype.__proto__ === Object.prototype);

这不是副本。它是对象。

当你创建像

这样的文字对象时
var o = {};

// true    
console.log(o.__proto__ === Object.prototype);

新对象的__proto__ 设置为Object.prototype(内置对象构造函数)。

您可以使用Object.create 将对象的__prototype__ 设置为另一个对象。

当在当前对象上找不到属性或方法时,检查对象的[[PROTOTYPE]]。如果未找到,则检查该对象的原型。因此它会检查原型,直到到达最终的原型对象Object.prototype。请记住,没有什么是复制品。

注意事项 4 在 Javascript 中模拟继承时,会设置“构造函数”的原型。

function Girl() {  
}

Girl.prototype = Object.create(Person.prototype);

// true
console.log(Girl.prototype.__proto__ === Person.prototype);

// true
console.log(Girl.constructor === Function);

// Best practices say reset the constructor to be itself
Girl.constructor = Girl;

// points to Girl function
console.log(Girl.constructor);

请注意我们如何将构造函数指向 Girl,因为 Person 的构造函数指向内置的 Function

你可以在http://jsbin.com/dutojo/1/edit?js,console看到上面的代码

原文:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();

细分:

var __extends = (this && this.__extends) || (function () {
   // gobbledygook
})();

记住我上面的注 1,第一部分(和结尾)是创建一个名为 __extends 的变量,其目的是保存一个函数来设置原型派生类的。

(this && this.__extends) 

正在按照我的注释 1 解释。如果 this 为真且 this.__extends 为真,则变量 __extends 已存在,因此设置为自身的现有实例。如果不是,则设置为 || 之后的内容这是一个 iife(立即调用的函数表达式)。

现在是 gobbledygook,它是 __extends 的实际定义:

var extendStatics = Object.setPrototypeOf ||

名为 extendStatics 的变量设置为脚本运行环境的内置 Object.setPrototypeOf 函数 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf)

它创建自己的版本

({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };

Note 3 中,我讨论了 __proto__ aka [[PROTOTYPE]] 以及如何设置它。代码

{ __proto__: [] } instanceof Array

是通过比较文字对象的__proto__设置为文字数组与Array内置函数来确定当前环境是否允许设置此属性的测试。

从上面参考我的 注 1 并记住,如果环境评估一个对象的原型属性设置为内置数组,然后 extendsStatics 设置为

function (d, b) { d.__proto__ = b; })

如果环境不以这种方式评估它,则将 extendStatics 设置为:

function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }

之所以这样做,是因为 __proto__ 从未成为 ECMAScript 官方标准的一部分,直到 ECMAScript 2015(并且根据 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)才出现,只是为了向后兼容)。如果支持,则使用__proto__ 函数,否则它使用“滚动您自己的”版本,该版本为用户定义的属性从对象 b 复制到 d。

现在定义了 extendStatics 函数变量,返回一个调用 extendStatics 中的任何内容(以及其他一些内容)的函数。请注意,参数“d”是子类(继承者),“b”是超类(继承者):

return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };

将其分解为调用extendStatics,第一个参数对象(d)的原型设置为(b)(回忆上面的注3):

extendStatics(d, b);

在下一行中,声明了一个名为“__”的构造函数,将其构造函数指定为派生的 (d) 构造函数:

function __() { this.constructor = d; }

如果基 (b) constructor 函数恰好为 null,这将确保派生函数将保留其自己的 prototype

来自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor,Object.prototype.constructor(所有对象都是javascript中的对象):

返回对创建的 Object 构造函数的引用 实例对象。请注意,此属性的值为 引用函数本身,而不是包含 函数名。

所有对象都有一个构造函数属性。没有创建的对象 显式使用构造函数(即对象和数组 literals) 将有一个构造函数属性指向 该对象的基本对象构造函数类型。

因此,如果 constructor 函数 '__' 是新的,它将创建一个派生对象。

最后是这一行:

d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());

如果基础constructor 函数恰好为空,则将派生(d) 的prototype 设置为新的空对象

//  b is null here so creates {}
Object.create(b)

将__constructor函数的prototype设置为基类prototype,然后调用__(),其作用是将派生函数constructor设置为派生函数。

(__.prototype = b.prototype, new __()

所以基本上返回的最终函数创建了一个派生构造函数,该构造函数原型继承自基本构造函数。

为什么函数 B() 会返回这个?

return _super !== null && _super.apply(this, arguments) || this;

根据:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

apply() 方法调用具有给定 this 值的函数,并且 以数组(或类似数组的对象)形式提供的参数。

记住 B 是一个构造函数,这就是 B 的定义中返回的内容。

如果您有一个 Person 类(构造函数)在构造函数中接受名称参数,那么您可以调用派生的 Girl 类(构造函数),并将女孩的名字作为参数。

// Base constructor function
function Person(n) {
  // Set parameter n to the name property of a Person
  this.name = n;
}

function Girl() {
   // Call the Person function with same arguments passed to new Girl
   Person.apply(this, arguments);
   // Set it so all Girl objects created inherit properties and methods from Person
   Girl.prototype = Object.create(Person.prototype);  
   // Make sure the constructor is not set to Person
   Girl.prototype.constructor =  Girl;
}

var p = new Person("Sally");
var g = new Girl("Trudy");
console.log(p.name);
console.log(g.name);

【讨论】:

  • 哇,感谢您的详细解释。它确实帮助我清楚地理解了它,并且在此过程中我也学到了一两件事。我将其添加到 cmets 以供其他阅读您的帖子的人使用:
  • { __proto__: [] } instanceof Array - 这是将空对象的原型设置为新数组,然后检查由此创建的空对象是否是数组的实例?这是其中最棘手的部分,因为正如您所解释的那样,可能存在 proto 也可能无法正常工作的情况!
  • 当你设置 var a = ((function(){console.log(123);})(), 2) 时,它会丢弃第一个参数并设置 a = 2(尽管它仍然执行第一个参数)。这也是设置 d.prototype 时发生的情况。
  • 我已经更新了我的答案,进行了澄清和一些更正。
  • 除了上面的详细解释,这篇文章还有一些很好的补充信息basarat.gitbooks.io/typescript/content/docs/classes-emit.html
猜你喜欢
  • 2016-08-02
  • 2021-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-21
  • 1970-01-01
  • 2018-12-04
  • 2018-02-22
相关资源
最近更新 更多