我正在玩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;
如果有人可以逐行向我解释,我将不胜感激。
答案 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类扩展器中的实际情况。实际上,以下代码包含了完全相同的逻辑,因为甚至可以将其用作原始代码的替代,而无需使用所有特别的技巧使之难以阅读。
尝试用以下代码替换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'