是ES6类只是在JavaScript中的原型模式语法糖?(are es6 classes just

2019-10-29 22:16发布

与ES6打后,我真的开始喜欢使用新的语法和功能,但我有一个关于类的问题。

是新ES6类只是语法糖旧原型模式呢? 或者是有更多回事幕后? 即:

class Thing {
   //... classy stuff
  doStuff(){}
}

VS:

var Thing = function() {
  // ... setup stuff
};

Thing.prototype.doStuff = function() {}; // etc

Answer 1:

是的,也许,但一些语法糖有牙齿。

声明一个类创建一个功能对象是类的构造函数,利用所规定的代码constructor类体之内,并且对于名为类,具有相同的名称作为类。

类的构造函数有一个正常的原型对象从类实例继承在正常的JavaScript时尚特性。 类主体内限定实例方法加入到该原型。

ES6不提供给类体内声明的类实例默认属性值(即值不属于的方法)的装置将被存储在原型和继承。 要初始化实例值,你可以将它们设置为构造函数内的局部的,非遗传性,或手动将它们添加到类的构造函数的prototype相同的方式对象的类定义之外的普通构造函数。 (我并不是优劣与否的JavaScript类设置继承的属性)。

类体中声明的静态方法被添加作为类构造函数的性质。 避免使用与继承的标准功能的属性和方法竞争静态类方法名Function.prototype诸如callapplylength

含糖分较少的是类声明和方法,在严格模式总是执行和功能,得到很少注意:在.prototype的类的构造函数属性为只读:你不能将其设置为你为创造了一些其他的对象一些特殊用途。

当你扩展一个类的一些有趣的事情发生:

  • prototype延伸类构造的对象属性自动原型上prototype的类被扩展对象。 这不是特别新,效果可以使用复制Object.create

  • 扩展的类构造函数(对象)被自动原型上的类的构造函数被扩展,而不是Function 。 虽然有可能复制上使用一个普通的构造函数的作用Object.setPrototypeOf甚至childClass.__proto__ = parentClass ,这将是一个极不寻常的编码实践,往往是JavaScript的文档中建议不要。

还有其他的差异如不被提升在命名的函数的方式,使用声明的类的对象function关键字。

我认为可能是天真的以为类声明和表达式将保持在ECMA脚本的所有未来版本不变,这将是有趣的,看看发生的事态发展,如果当。 可以说,它已经成为一种时尚,以“语法糖”在ES6(ECMA-262标准版6)推出类关联但我个人尽量避免重复它。



Answer 2:

是新ES6类只是语法糖旧原型模式呢?

是,它们是(几乎完全)一个方便的语法,语义几乎是相同的。 Traktor53的答案进入的差异。

资源

下面短代码示例说明如何在一个功能class是本上设置prototype对象。

class Thing {
   someFunc() {}
}

console.log("someFunc" in Thing.prototype); // true


Answer 3:

是。 但他们更严格。

有在你的例子有两个主要差别。

首先,用类语法,你不能没有初始化实例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{}


Answer 4:

不,ES6类不为原型模式只是语法糖。

而相反在很多地方被读取,虽然它似乎是真的在表面上,事情变得更加复杂,当你开始深入挖掘细节。

我是不是很满足于现有的答案。 做一些调查研究后,我这是怎么划分在我的脑海ES6类的特点:

  1. 语法糖为标准ES5 pseudoclassical遗传模式。
  2. 语法糖提供,但不切实际或少见ES5改进的pseudoclassical遗传模式。
  3. 语法糖改进的pseudoclassical遗传模式在ES5不可用,但可以在ES6来实现,而不类语法。
  4. 功能无法实现无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;
    }
}

1.句法糖为标准ES5 pseudoclassical遗传模式

在他们的核心,ES6类确实提供了标准的ES5 pseudoclassical遗传模式语法糖。

类声明/表达式

在背景中的类声明或类表达将创建一个构造函数具有相同的名称作为类,使得:

  1. 内部[[Construct]]构造的属性是指连接到该类的代码块constructor()方法。
  2. 该CLASSE”方法是在构造函数的定义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语法使得:

  1. prototype孩子构造函数的性质从继承prototype的父类的构造财产。
  2. super()调用相当于调用与父构造this绑定到当前上下文。
    • 这仅仅是由所提供的功能的粗略近似super()这也将设置隐new.target参数和触发内部[[Construct]]方法(而不是[[Call]]方法)。 super()调用将得到充分在第3节“脱”。
  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

2.句法糖可用但不切实际或在ES5罕见改进的pseudoclassical遗传模式

ES6类还提供了改进,可能已经被在ES5实施pseudoclassical遗传模式,但往往忽略了,因为他们可能有点不切实际成立。

类声明/表达式

类声明或类表达以下列方式将进一步设置好了:

  1. 类声明或类表达式内的所有代码在严格模式下运行。
  2. 类的静态方法是在构造函数本身定义。
  3. 所有类方法(静态或不)是不可枚举。
  4. 构造函数的原型属性是不可写的。

使用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语法这样的:

  1. 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

3.语法糖改进的pseudoclassical遗传模式在ES5不可用

ES6类进一步提供改进不在ES5可用pseudoclassical遗传模式,而是可以以ES6,而不必使用类语法来实现。

类声明/表达式

ES6特性在别处找到了也进入类,具体为:

  1. 类的声明表现得像let声明-升起时,他们没有初始化和声明之前在颞死区结束。 (相关问题 )
  2. 类名的行为像一个const类的声明中绑定-它不能在一个类的方法中被覆盖,试图这样做会导致一个TypeError
  3. 类构造必须与内部被称为[[Construct]]方法中, TypeError ,如果它们被称为与内部普通函数被抛出[[Call]]方法。
  4. 类方法(与所述外constructor()方法),静态或不表现得像通过简洁的方法的语法,这是说,定义的方法:
    • 他们可以使用super通过关键字super.propsuper[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

衍生的类声明/表达式

除了对以下上述的组合也将保持派生类声明或派生类表达:

  1. 孩子从构造函数的父类的构造(即派生类继承静态成员)继承。
  2. 调用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]

4.特点不可能实现无class语法

ES6类进一步规定,不能在所有的不实际使用中实现以下功能class语法:

  1. 内部[[HomeObject]]的静态类的方法属性指向类的构造函数。
    • 没有办法来实现这个普通构造函数,因为这需要通过对象字面定义一个函数(也见上文第3节)。 这是派生类利用的的静态方法特别有问题的super喜欢我们的关键字Bird.isBird()方法。

这可以部分解决这个问题,如果父类是事先已知的。


结论

ES6类的某些功能为标准ES5 pseudoclassical遗传模式只是语法糖。 然而ES6班还配备了只能在ES6实现的功能和一些其它功能,甚至不能在ES6被模仿(即不使用类语法)。

综观上述,我认为这是公平地说,ES6类是更简洁,更方便,更安全比ES5 pseudoclassical遗传模式来使用。 他们也因此不太灵活(见这个问题为例)。


旁注

这是值得指出的那没有发现上述分类的地方班多几个特点:

  1. super()是在派生类的构造函数只有有效的语法,并可以仅调用一次。
  2. 试图访问this之前,在派生类的构造super()被调用的结果ReferenceError
  3. super()必须在派生类的构造函数被调用,如果没有对象被明确地从它返回。
  4. evalarguments是无效的类标识符(而他们在非严格模式下有效的功能识别符)。
  5. 派生类设置默认constructor()方法,如果没有提供(对应于constructor( ...args ) { super( ...args ); }
  6. 这是不可能在包含类声明或类表达(虽然可以其声明之后手动对类添加数据属性)类定义的数据属性。

更多资源

  • 本章了解ES6类由尼古拉斯Zakas 了解ES6是ES6类最佳写了,我所遇到的。
  • 由阿克塞尔Rauschmayer先生的2ality博客有一个非常彻底的岗位上ES6类。
  • 对象游乐场有很大的视频解释pseudoclassical遗传模式(并将其与类语法)。
  • 该巴贝尔 transpiler是探索你自己的东西的好地方。


Answer 5:

他们是完全语法糖。 有什么新的关于ES6原型继承是重新定义__proto__对象的属性。 __proto__现在是合法的,是如何排列的子类化已经成为可能的JS。



Answer 6:

是的,差不多。

随着ES6您可以扩展功能类和Array类,在ES5你不能有相同的行为:扩展功能并不能使一个可调用对象和扩展阵列不会继承ES5的。长度自动属性

对于剩下的原型逻辑和类JavaScript中的相同

是ES6类真正语义糖吗?



文章来源: are es6 classes just syntactic sugar for the prototypal pattern in javascript?