与ES6打后,我真的开始喜欢使用新的语法和功能,但我有一个关于类的问题。
是新ES6类只是语法糖旧原型模式呢? 或者是有更多回事幕后? 即:
class Thing {
//... classy stuff
doStuff(){}
}
VS:
var Thing = function() {
// ... setup stuff
};
Thing.prototype.doStuff = function() {}; // etc
与ES6打后,我真的开始喜欢使用新的语法和功能,但我有一个关于类的问题。
是新ES6类只是语法糖旧原型模式呢? 或者是有更多回事幕后? 即:
class Thing {
//... classy stuff
doStuff(){}
}
VS:
var Thing = function() {
// ... setup stuff
};
Thing.prototype.doStuff = function() {}; // etc
是的,也许,但一些语法糖有牙齿。
声明一个类创建一个功能对象是类的构造函数,利用所规定的代码constructor
类体之内,并且对于名为类,具有相同的名称作为类。
类的构造函数有一个正常的原型对象从类实例继承在正常的JavaScript时尚特性。 类主体内限定实例方法加入到该原型。
ES6不提供给类体内声明的类实例默认属性值(即值不属于的方法)的装置将被存储在原型和继承。 要初始化实例值,你可以将它们设置为构造函数内的局部的,非遗传性,或手动将它们添加到类的构造函数的prototype
相同的方式对象的类定义之外的普通构造函数。 (我并不是优劣与否的JavaScript类设置继承的属性)。
类体中声明的静态方法被添加作为类构造函数的性质。 避免使用与继承的标准功能的属性和方法竞争静态类方法名Function.prototype
诸如call
, apply
或length
。
含糖分较少的是类声明和方法,在严格模式总是执行和功能,得到很少注意:在.prototype
的类的构造函数属性为只读:你不能将其设置为你为创造了一些其他的对象一些特殊用途。
当你扩展一个类的一些有趣的事情发生:
该prototype
延伸类构造的对象属性自动原型上prototype
的类被扩展对象。 这不是特别新,效果可以使用复制Object.create
。
扩展的类构造函数(对象)被自动原型上的类的构造函数被扩展,而不是Function
。 虽然有可能复制上使用一个普通的构造函数的作用Object.setPrototypeOf
甚至childClass.__proto__ = parentClass
,这将是一个极不寻常的编码实践,往往是JavaScript的文档中建议不要。
还有其他的差异如不被提升在命名的函数的方式,使用声明的类的对象function
关键字。
我认为可能是天真的以为类声明和表达式将保持在ECMA脚本的所有未来版本不变,这将是有趣的,看看发生的事态发展,如果当。 可以说,它已经成为一种时尚,以“语法糖”在ES6(ECMA-262标准版6)推出类关联但我个人尽量避免重复它。
是新ES6类只是语法糖旧原型模式呢?
是,它们是(几乎完全)一个方便的语法,语义几乎是相同的。 Traktor53的答案进入的差异。
资源
下面短代码示例说明如何在一个功能class
是本上设置prototype
对象。
class Thing {
someFunc() {}
}
console.log("someFunc" in Thing.prototype); // true
是。 但他们更严格。
有在你的例子有两个主要差别。
首先,用类语法,你不能没有初始化实例new
关键字。
class Thing{}
Thing() //Uncaught TypeError: Class constructor Thing cannot be invoked without 'new'
var Thing = function() {
if(!(this instanceof Thing)){
return new Thing();
}
};
Thing(); //works
第二个是,与类语法定义的类是嵌段作用域。 它类似于定义与变量let
关键字。
class Thing{}
class Thing{} //Uncaught SyntaxError: Identifier 'Thing' has already been declared
{
class Thing{}
}
console.log(Thing); //Uncaught ReferenceError: Thing is not defined
作为@zeroflagL在他的评论中提到,类声明中也没有悬挂。
console.log(Thing) //Uncaught ReferenceError: Thing is not defined
class Thing{}
不,ES6类不为原型模式只是语法糖。
而相反在很多地方被读取,虽然它似乎是真的在表面上,事情变得更加复杂,当你开始深入挖掘细节。
我是不是很满足于现有的答案。 做一些调查研究后,我这是怎么划分在我的脑海ES6类的特点:
class
语法,甚至在ES6。 (我试图让这个答案尽可能完整,并成为其结果是相当长的。那些更感兴趣的是一个很好的概述应该看看traktor53的回答 。)
因此,让我“desugar”循序渐进(和尽可能地)以下类声明来说明,因为我们走的事情:
// Class Declaration:
class Vertebrate {
constructor( name ) {
this.name = name;
this.hasVertebrae = true;
this.isWalking = false;
}
walk() {
this.isWalking = true;
return this;
}
static isVertebrate( animal ) {
return animal.hasVertebrae;
}
}
// Derived Class Declaration:
class Bird extends Vertebrate {
constructor( name ) {
super( name )
this.hasWings = true;
}
walk() {
console.log( "Advancing on 2 legs..." );
return super.walk();
}
static isBird( animal ) {
return super.isVertebrate( animal ) && animal.hasWings;
}
}
在他们的核心,ES6类确实提供了标准的ES5 pseudoclassical遗传模式语法糖。
在背景中的类声明或类表达将创建一个构造函数具有相同的名称作为类,使得:
[[Construct]]
构造的属性是指连接到该类的代码块constructor()
方法。 prototype
属性(我们不包括现在的静态方法)。 使用ES5语法,初始类声明因此大致相当于以下(留出静态方法):
function Vertebrate( name ) { // 1. A constructor function containing the code of the class's constructor method is defined
this.name = name;
this.hasVertebrae = true;
this.isWalking = false;
}
Object.assign( Vertebrate.prototype, { // 2. Class methods are defined on the constructor's prototype property
walk: function() {
this.isWalking = true;
return this;
}
} );
初始类声明和上面的代码片段都将产生以下
console.log( typeof Vertebrate ) // function
console.log( typeof Vertebrate.prototype ) // object
console.log( Object.getOwnPropertyNames( Vertebrate.prototype ) ) // [ 'constructor', 'walk' ]
console.log( Vertebrate.prototype.constructor === Vertebrate ) // true
console.log( Vertebrate.prototype.walk ) // [Function: walk]
console.log( new Vertebrate( 'Bob' ) ) // Vertebrate { name: 'Bob', hasVertebrae: true, isWalking: false }
除了上述之外,派生类声明或派生类表达式也将建立一个继承的构造之间prototype
属性和利用的super
语法使得:
prototype
孩子构造函数的性质从继承prototype
的父类的构造财产。 super()
调用相当于调用与父构造this
绑定到当前上下文。 super()
这也将设置隐new.target
参数和触发内部[[Construct]]
方法(而不是[[Call]]
方法)。 该super()
调用将得到充分在第3节“脱”。 super[method]()
调用量调用父的方法prototype
与对象this
绑定到当前上下文(我们不包括现在的静态方法)。 super[method]()
其不依赖于直接引用父类的呼叫。 super[method]()
调用将在第3节得到完全复制 。 使用ES5语法,初始派生类声明因此大致相当于以下(留出静态方法):
function Bird( name ) {
Vertebrate.call( this, name ) // 2. The super() call is approximated by directly calling the parent constructor
this.hasWings = true;
}
Bird.prototype = Object.create( Vertebrate.prototype, { // 1. Inheritance is established between the constructors' prototype properties
constructor: {
value: Bird,
writable: true,
configurable: true
}
} );
Object.assign( Bird.prototype, {
walk: function() {
console.log( "Advancing on 2 legs..." );
return Vertebrate.prototype.walk.call( this ); // 3. The super[method]() call is approximated by directly calling the method on the parent's prototype object
}
})
初始派生类声明和上面的代码片段都将产生以下
console.log( Object.getPrototypeOf( Bird.prototype ) ) // Vertebrate {}
console.log( new Bird("Titi") ) // Bird { name: 'Titi', hasVertebrae: true, isWalking: false, hasWings: true }
console.log( new Bird( "Titi" ).walk().isWalking ) // true
ES6类还提供了改进,可能已经被在ES5实施pseudoclassical遗传模式,但往往忽略了,因为他们可能有点不切实际成立。
类声明或类表达以下列方式将进一步设置好了:
使用ES5语法,初始类声明因此更精确地(但仍只是部分地)等效于以下语句:
var Vertebrate = (function() { // 1. Code is wrapped in an IIFE that runs in strict mode
'use strict';
function Vertebrate( name ) {
this.name = name;
this.hasVertebrae = true;
this.isWalking = false;
}
Object.defineProperty( Vertebrate.prototype, 'walk', { // 3. Methods are defined to be non-enumerable
value: function walk() {
this.isWalking = true;
return this;
},
writable: true,
configurable: true
} );
Object.defineProperty( Vertebrate, 'isVertebrate', { // 2. Static methods are defined on the constructor itself
value: function isVertebrate( animal ) { // 3. Methods are defined to be non-enumerable
return animal.hasVertebrae;
},
writable: true,
configurable: true
} );
Object.defineProperty( Vertebrate, "prototype", { // 4. The constructor's prototype property is defined to be non-writable:
writable: false
});
return Vertebrate
})();
注1:如果周围代码已经在严格模式下运行,所以当然没有必要在IIFE包一切。
注2:虽然它可以定义静态属性,而不在ES5的问题,这是不是很常见。 这样做的原因可能是,建立静态特性的继承是不可能不使用则非标准的__proto__
属性。
现在初始类声明和上述代码段还将既产生以下
console.log( Object.getOwnPropertyDescriptor( Vertebrate.prototype, 'walk' ) )
// { value: [Function: walk],
// writable: true,
// enumerable: false,
// configurable: true }
console.log( Object.getOwnPropertyDescriptor( Vertebrate, 'isVertebrate' ) )
// { value: [Function: isVertebrate],
// writable: true,
// enumerable: false,
// configurable: true }
console.log( Object.getOwnPropertyDescriptor( Vertebrate, 'prototype' ) )
// { value: Vertebrate {},
// writable: false,
// enumerable: false,
// configurable: false }
除上述外,派生类中声明或派生类表达式也将利用的super
语法这样的:
super[method]()
内的静态方法数额的话费调用与对父类的构造方法this
绑定到当前上下文。 super[method]()
其不依赖于直接引用父类的呼叫。 super[method]()
调用在静态方法不能完全被不使用的模仿class
的语法和在第4中列出。 使用ES5语法,初始派生类声明从而更精确地(但仍只是部分地)等效于以下语句:
function Bird( name ) {
Vertebrate.call( this, name )
this.hasWings = true;
}
Bird.prototype = Object.create( Vertebrate.prototype, {
constructor: {
value: Bird,
writable: true,
configurable: true
}
} );
Object.defineProperty( Bird.prototype, 'walk', {
value: function walk( animal ) {
return Vertebrate.prototype.walk.call( this );
},
writable: true,
configurable: true
} );
Object.defineProperty( Bird, 'isBird', {
value: function isBird( animal ) {
return Vertebrate.isVertebrate.call( this, animal ) && animal.hasWings; // 1. The super[method]() call is approximated by directly calling the method on the parent's constructor
},
writable: true,
configurable: true
} );
Object.defineProperty( Bird, "prototype", {
writable: false
});
现在初始派生类声明和上述代码段还将既产生以下
console.log( Bird.isBird( new Bird("Titi") ) ) // true
ES6类进一步提供改进不在ES5可用pseudoclassical遗传模式,而是可以以ES6,而不必使用类语法来实现。
ES6特性在别处找到了也进入类,具体为:
let
声明-升起时,他们没有初始化和声明之前在颞死区结束。 (相关问题 ) const
类的声明中绑定-它不能在一个类的方法中被覆盖,试图这样做会导致一个TypeError
。 [[Construct]]
方法中, TypeError
,如果它们被称为与内部普通函数被抛出[[Call]]
方法。 constructor()
方法),静态或不表现得像通过简洁的方法的语法,这是说,定义的方法: super
通过关键字super.prop
或super[method]
(这是因为他们会被分配一个内部[[HomeObject]]
属性)。 prototype
属性和内部[[Construct]]
属性。 使用ES6语法,初始类声明因此更精确地(但仍只是部分地)等效于以下语句:
let Vertebrate = (function() { // 1. The constructor is defined with a let declaration, it is thus not initialized when hoisted and ends up in the TDZ
'use strict';
const Vertebrate = function( name ) { // 2. Inside the IIFE, the constructor is defined with a const declaration, thus preventing an overwrite of the class name
if( typeof new.target === 'undefined' ) { // 3. A TypeError is thrown if the constructor is invoked as an ordinary function without new.target being set
throw new TypeError( `Class constructor ${Vertebrate.name} cannot be invoked without 'new'` );
}
this.name = name;
this.hasVertebrae = true;
this.isWalking = false;
}
Object.assign( Vertebrate, {
isVertebrate( animal ) { // 4. Methods are defined using the concise method syntax
return animal.hasVertebrae;
},
} );
Object.defineProperty( Vertebrate, 'isVertebrate', {enumerable: false} );
Vertebrate.prototype = {
constructor: Vertebrate,
walk() { // 4. Methods are defined using the concise method syntax
this.isWalking = true;
return this;
},
};
Object.defineProperty( Vertebrate.prototype, 'constructor', {enumerable: false} );
Object.defineProperty( Vertebrate.prototype, 'walk', {enumerable: false} );
return Vertebrate;
})();
注1:虽然实例和静态方法都用简洁的方法的语法定义, super
预期的静态方法的引用不会做人。 事实上,内部[[HomeObject]]
属性不被复制Object.assign()
设置[[HomeObject]]
静态方法正确属性将要求我们定义使用对象文本,这是不可能的函数的构造。
注2:被调用为防止构造没有new
关键字,类似的保护措施可能已经在ES5通过利用来实现instanceof
运算符。 这些并非涵盖所有情况,但(看到这个答案 )。
现在初始类声明和上述代码段还将既产生以下
Vertebrate( "Bob" ); // TypeError: Class constructor Vertebrate cannot be invoked without 'new'
console.log( Vertebrate.prototype.walk.hasOwnProperty( 'prototype' ) ) // false
new Vertebrate.prototype.walk() // TypeError: Vertebrate.prototype.walk is not a constructor
console.log( Vertebrate.isVertebrate.hasOwnProperty( 'prototype' ) ) // false
new Vertebrate.isVertebrate() // TypeError: Vertebrate.isVertebrate is not a constructor
除了对以下上述的组合也将保持派生类声明或派生类表达:
super()
中派生类的构造相当于调用父构造的内部[[Construct]]
与当前方法new.target
值和结合this
上下文返回的对象。 使用ES6语法,初始派生类声明从而更精确地(但仍只是部分地)等效于以下语句:
let Bird = (function() {
'use strict';
const Bird = function( name ) {
if( typeof new.target === 'undefined' ) {
throw new TypeError( `Class constructor ${Bird.name} cannot be invoked without 'new'` );
}
const that = Reflect.construct( Vertebrate, [name], new.target ); // 2. super() calls amount to calling the parent constructor's [[Construct]] method with the current new.target value and binding the 'this' context to the returned value (see NB 2 below)
that.hasWings = true;
return that;
}
Bird.prototype = {
constructor: Bird,
walk() {
console.log( "Advancing on 2 legs..." );
return super.walk(); // super[method]() calls can now be made using the concise method syntax (see 4. in Class Declarations / Expressions above)
},
};
Object.defineProperty( Bird.prototype, 'constructor', {enumerable: false} );
Object.defineProperty( Bird.prototype, 'walk', {enumerable: false} );
Object.assign( Bird, {
isBird: function( animal ) {
return Vertebrate.isVertebrate( animal ) && animal.hasWings; // super[method]() calls can still not be made in static methods (see NB 1 in Class Declarations / Expressions above)
}
})
Object.defineProperty( Bird, 'isBird', {enumerable: false} );
Object.setPrototypeOf( Bird, Vertebrate ); // 1. Inheritance is established between the constructors directly
Object.setPrototypeOf( Bird.prototype, Vertebrate.prototype );
return Bird;
})();
NB 1:作为Object.create()
只能被用于设置一个新的非功能对象的原型,建立本身只能在ES5通过操纵然后非标准来实现构造之间的继承__proto__
属性。
注2:这是不可能模仿的效果super()
使用this
背景下,所以我们不得不返回不同的that
从构造函数明确反对。
现在初始派生类声明和上述代码段还将既产生以下
console.log( Object.getPrototypeOf( Bird ) ) // [Function: Vertebrate]
console.log( Bird.isVertebrate ) // [Function: isVertebrate]
class
语法 ES6类进一步规定,不能在所有的不实际使用中实现以下功能class
语法:
[[HomeObject]]
的静态类的方法属性指向类的构造函数。 super
喜欢我们的关键字Bird.isBird()
方法。 这可以部分解决这个问题,如果父类是事先已知的。
ES6类的某些功能为标准ES5 pseudoclassical遗传模式只是语法糖。 然而ES6班还配备了只能在ES6实现的功能和一些其它功能,甚至不能在ES6被模仿(即不使用类语法)。
综观上述,我认为这是公平地说,ES6类是更简洁,更方便,更安全比ES5 pseudoclassical遗传模式来使用。 他们也因此不太灵活(见这个问题为例)。
这是值得指出的那没有发现上述分类的地方班多几个特点:
super()
是在派生类的构造函数只有有效的语法,并可以仅调用一次。 this
之前,在派生类的构造super()
被调用的结果ReferenceError
。 super()
必须在派生类的构造函数被调用,如果没有对象被明确地从它返回。 eval
和arguments
是无效的类标识符(而他们在非严格模式下有效的功能识别符)。 constructor()
方法,如果没有提供(对应于constructor( ...args ) { super( ...args ); }
他们是完全语法糖。 有什么新的关于ES6原型继承是重新定义__proto__
对象的属性。 __proto__
现在是合法的,是如何排列的子类化已经成为可能的JS。
是的,差不多。
随着ES6您可以扩展功能类和Array类,在ES5你不能有相同的行为:扩展功能并不能使一个可调用对象和扩展阵列不会继承ES5的。长度自动属性
对于剩下的原型逻辑和类JavaScript中的相同
是ES6类真正语义糖吗?