16

我正在玩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 文档,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;

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

4

2 回答 2

18

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

它能做什么

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

注1:

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

// 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]]aka__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(内置对象构造函数)。

您可以将一个对象设置__prototype__为另一个对象,但是使用Object.create.

当在当前对象上找不到属性或方法时,[[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]; };

注释 3中,我讨论了__proto__aka[[PROTOTYPE]]以及如何设置它。编码

{ __proto__: [] } instanceof Array

是一个测试,以确定当前环境是否允许通过将文字对象的__proto__集合与带有 Array 内置函数的文字数组进行比较来设置此属性。

从上面回顾我的注释 1并记住 javascript instanceof 运算符返回 true 或 false ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof ) 如果environment 评估一个对象,其原型属性设置为内置数组,然后将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 2015 之前(并且根据https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto),它从来不是官方 ECMAScript 标准的一部分为了向后兼容)。如果支持,__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; }

如果 base (b)constructor函数恰好为 null,这将确保派生的prototype.

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

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

所有对象都将具有构造函数属性。未显式使用构造函数创建的对象(即对象和数组字面量)将具有指向该对象的基本对象构造函数类型的构造函数属性。

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

最后有这一行:

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

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

//  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);
于 2017-09-27T22:16:03.350 回答
2

这可以帮助理解 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'

于 2019-09-01T18:37:03.517 回答