理解typescript生成的__extends函数?

时间:2017-08-30 07:10:27

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

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

我在作品集生成的代码中不理解的部分是这个

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;

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

2 个答案:

答案 0 :(得分:10)

我自己很好奇并且无法找到快速答案,所以这是我的细分:

它的作用

__ 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(可能代表base)都是构造函数而不是实例对象。

注3:

prototype是函数的属性,它是&#39;构造函数&#39;使用的原型对象。函数(即使用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构造函数)。

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

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

注4 在Javascript中模拟继承时,&#39;构造函数&#39;功能&#39;原型设定。

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) 

正在做我的 Note 1 解释的内容。如果这个是真的并且这个.__ extends 是真实的,那么变量 __ extends 已经存在,因此设置为它自己的现有实例。如果没有,则设置为||之后的内容这是一个生命(立即调用函数表达式)。

现在为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__又名[[PROTOTYPE]]以及如何设置它。代码

{ __proto__: [] } instanceof Array

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

回到上面的 Note 1 ,并记住,如果环境评估一个具有其prototype属性集的对象,则javascript instanceof运算符返回true或false(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof)在内置数组中, 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__功能,否则它会使用自己的&#39;版本&#39;从对象b到d为用户定义的属性执行复制。

现在定义了 extendStatics 函数变量,返回一个调用 extendStatics (以及其他一些东西)内部内容的函数。请注意,参数&#39; d&#39;是子类(继承的子类)和&#39; b&#39;是超类(继承自的):

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

在下一行中,构造函数名为&#39; __&#39;声明将其构造函数指定为派生(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构造函数的引用   实例对象。请注意,此属性的值为a   引用函数本身,而不是包含该函数的字符串   功能的名称。

  

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

所以如果constructor功能&#39; __&#39;是新的,因为它会创建派生对象。

最后有这一行:

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

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

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

OR

将__ constructor函数prototype设置为基类prototype,然后调用__(),这样可以设置派生函数{{1}成为派生函数。

constructor

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

为什么函数B()会返回此内容?

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

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

  

apply()方法调用具有给定值的函数,和   作为数组(或类数组对象)提供的参数。

记住B是一个构造函数,它是B的定义中返回的函数。

如果你有一个在构造函数中接受了name参数的Person类(构造函数),那么你可以使用girls name作为参数来调用派生的Girl类(构造函数)。

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

答案 1 :(得分: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
    }
  }
}

在以下版本中,删除了所有使其通用的东西,例如浏览器兼容性处理和“空”派生类。我不鼓励任何人将以下代码用作永久替代,但是下面的版本确实展示了类继承与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'