理解typescript生成的__extends函数?

sup*_*san 11 javascript prototype javascript-objects proto javascript-inheritance

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

打字稿代码:

class A { }
class B extends A { }
Run Code Online (Sandbox Code Playgroud)

生成的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));
Run Code Online (Sandbox Code Playgroud)

根据Mozilla文档的Javascript继承是这样的:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
Run Code Online (Sandbox Code Playgroud)

在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]; };
Run Code Online (Sandbox Code Playgroud)

这是做什么的?

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

我不明白这一部分: (__.prototype = b.prototype, new __())

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

return _super !== null && _super.apply(this, arguments) || this;
Run Code Online (Sandbox Code Playgroud)

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

Mik*_*eel 13

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

它能做什么

__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;
Run Code Online (Sandbox Code Playgroud)

我提到这个是因为当我看到这个javascript并且在__extends函数定义中使用它时,这是最让我困惑的事情.

注2: 参数d(可能代表派生)和b(可能代表基数)既是构造函数,也不是实例对象.

注3:

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

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

function Person() {  
}

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

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

// true
console.log(Person.prototype.__proto__ === Object.prototype);
Run Code Online (Sandbox Code Playgroud)

它不是副本.这是对象.

当你创建一个文字对象时

var o = {};

// true    
console.log(o.__proto__ === Object.prototype);
Run Code Online (Sandbox Code Playgroud)

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

您可以使用将对象设置__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);
Run Code Online (Sandbox Code Playgroud)

注意我们如何将构造函数指向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 __());
    };
})();
Run Code Online (Sandbox Code Playgroud)

分解:

var __extends = (this && this.__extends) || (function () {
   // gobbledygook
})();
Run Code Online (Sandbox Code Playgroud)

记住我的注释1,第一部分(和结束)是创建一个名为__extends的变量,其目的是持有一个函数来设置派生类的原型.

(this && this.__extends) 
Run Code Online (Sandbox Code Playgroud)

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

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

var extendStatics = Object.setPrototypeOf ||
Run Code Online (Sandbox Code Playgroud)

名为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]; };
Run Code Online (Sandbox Code Playgroud)

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

{ __proto__: [] } instanceof Array
Run Code Online (Sandbox Code Playgroud)

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

请参阅上面的注释1,并记住javascript instanceof运算符返回true或false(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof)环境评估一个对象,其prototype属性设置为内置数组,然后将extendsStatics设置为

function (d, b) { d.__proto__ = b; })
Run Code Online (Sandbox Code Playgroud)

如果环境没有以这种方式评估它,那么extendStatics设置为:

function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }
Run Code Online (Sandbox Code Playgroud)

这样做是因为__proto__在ECMAScript 2015(并且根据https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)仅存在之前,它从未成为官方ECMAScript标准的一部分为了向后兼容).如果支持使用该__proto__函数,或者它使用'roll your own'版本',它从对象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 __());
    };
Run Code Online (Sandbox Code Playgroud)

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

extendStatics(d, b);
Run Code Online (Sandbox Code Playgroud)

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

function __() { this.constructor = d; }
Run Code Online (Sandbox Code Playgroud)

如果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 __());
Run Code Online (Sandbox Code Playgroud)

这设定prototype所导出的(d)的是一个新的空的对象,如果该基constructor函数恰好是空

//  b is null here so creates {}
Object.create(b)
Run Code Online (Sandbox Code Playgroud)

要么

将__ constructor函数设置为prototype基类prototype,然后调用__(),其效果是将派生函数设置constructor为派生函数.

(__.prototype = b.prototype, new __()
Run Code Online (Sandbox Code Playgroud)

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

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

return _super !== null && _super.apply(this, arguments) || this;
Run Code Online (Sandbox Code Playgroud)

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

apply()方法使用给定的此值调用函数,并将参数作为数组(或类数组对象)提供.

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

如果你有一个在构造函数中接受了name参数的Person类(构造函数),那么你可以使用girls name作为参数来调用派生的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);
Run Code Online (Sandbox Code Playgroud)

  • 哇,谢谢你的详细解释.它确实帮助我清楚地理解它,并且我也在这个过程中学到了一两件事.我正在评论中添加它,以便其他人阅读您的帖子: (2认同)