es6类只是javascript中原型模式的语法糖吗?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了es6类只是javascript中原型模式的语法糖吗?相关的知识,希望对你有一定的参考价值。

在玩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继承的标准函数属性和方法竞争的静态类方法名称,如callapplylength

较少含糖的是类声明和方法总是以严格模式执行,并且一个很少引起注意的特性:类构造函数的.prototype属性是只读的:你不能将它设置为你为某些创建的其他对象特殊目的。

扩展一个类时会发生一些有趣的事情:

  • 扩展类构造函数的prototype对象属性自动在要扩展的类的prototype对象上进行原型化。这不是特别新的,可以使用Object.create复制效果。
  • 扩展类构造函数(object)在被扩展的类的构造函数上自动原型化,而不是Function。虽然可以使用Object.setPrototypeOf甚至childClass.__proto__ = parentClass复制对普通构造函数的影响,但这将是一种非常不寻常的编码实践,并且通常在JavaScript文档中被建议不要使用。

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

我相信认为类声明和表达式在ECMA Script的所有未来版本中都将保持不变是一种天真,并且看看是否以及何时发生这将是有趣的。可以说,将“语法糖”与ES6中引入的类(ECMA-262标准版本6)联系起来已经成为一种时尚,但我个人试图避免重复它。

另一答案

新的ES6类是旧的原型模式的语法糖吗?

是的,它们(几乎完全)是一种方便的语法,语义几乎完全相同。 Traktor53's answer进入差异。

Source

以下短代码示例显示了如何在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

Edit

正如@zeroflagL在他的评论中提到的那样,类声明也没有被提升。

console.log(Thing) //Uncaught ReferenceError: Thing is not defined
class Thing{}
另一答案

不,ES6课程不仅仅是原型模式的语法糖。

虽然相反的情况可以在许多地方阅读,虽然从表面上看似乎是正确的,但当你开始深入挖掘细节时,事情变得更加复杂。

我对现有的答案不太满意。在做了一些更多的研究之后,我就在脑海中对ES6类的特征进行了分类:

  1. 标准ES5伪经典继承模式的语法糖。
  2. 用于改进伪经典继承模式的句法糖可用但在ES5中不切实际。
  3. 用于改进伪经典继承模式的语法糖在ES5中不可用,但可以在没有类语法的情况下在ES6中实现。
  4. 即使在ES6中,没有类语法也无法实现这些功能。

(我试图让这个答案尽可能完整,结果变得很长。那些对好的概述更感兴趣的人应该看看traktor53’s answer。)


因此,让我'逐步'(尽可能)下面的类声明,以说明我们进行的事情:

// 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() {
        return super.walk();
    }
}

1. Syntactic sugar for the standard ES5 pseudoclassical inheritance pattern

从本质上讲,ES6课程确实为标准的ES5伪经典继承模式提供了语法糖。

Class Declarations / Expressions

在后台,类声明或类表达式将创建一个与该类同名的构造函数,以便:

  1. 构造函数的内部[[Construct]]属性是指附加到classe的constructor()方法的代码块。
  2. 类的方法是在构造函数的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 }

Derived Class Declarations / Expressions

除了上述内容之外,派生类声明或派生类表达式还将在构造函数的prototype属性之间建立继承,以便:

  1. 子构造函数的protoype属性继承自父构造函数的prototype属性。

因此,使用ES5语法,初始派生类声明大致相当于以下内容(现在省略constructor()主体和walk()方法):

function Bird() {}

Bird.prototype = Object.create( Vertebrate.prototype, {     // 1. Inheritance is established between the constructors' prototype properties
    constructor: {
        value: Bird,
        writable: true,
        configurable: true
    }
} );

初始派生类声明和上面的代码片段都将产生以下结果:

console.log( Object.getPrototypeOf( Bird.prototype ) )      // Vertebrate {}
console.log( Bird.protoype.constructor === Bird )           // true

2. Syntactic sugar for improvements to the pseudoclassical inheritance pattern available but impractical in ES5

ES6类进一步改进了已经在ES5中实现的伪经典继承模式,但是经常被忽略,因为它们设置起来有点不切实际。

Class Declarations / Expressions

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

  1. 类声明或类表达式中的所有代码都以严格模式运行。
  2. 类的静态方法是在构造函数本身上定义的。
  3. 所有类方法(静态或非静态)都是不可枚举的。

因此,使用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', {  // 2. Methods are defined to be non-enumerable
        value: function walk() {
            this.isWalking = true;
            return this;
        },
        writable: true,
        configurable: true
    } );

    Object.defineProperty( Vertebrate, 'isVertebrate', {    // 3. Static methods are defined on the constructor itself
        value: function isVertebrate( animal ) {            // 2. Methods are defined to be non-enumerable
            return animal.hasVertebrae;
        },
        writable: true,
        configurable: true
    } );

    return Vertebrate
})();
  • 注意1:如果周围的代码已经在严格模式下运行,那么当然不需要将所有内容都包装在IIFE中。
  • 注2:虽然可以在ES5中定义静态属性而没有问题,但这并不常见。这样做的原因可能是,如果不使用当时的非标准__proto__属性,则无法建立静态属性的继承。

现在,初始类声明和上面的代码片段也将产生以下内容:

console.log( Object.getOwnPropertyDescriptor( Vertebrate.prototype, 'walk' ) )      
// { value: [Function: kill],
//   writable: true,
//   enumerable: false,
//   configurable: true }

console.log( Object.getOwnPropertyDescriptor( Vertebrate, 'isVertebrate' ) )    
// { value: [Function: isVertebrate],
//   writable: true,
//   enumerable: false,
//   configurable: true }

Derived Class Declarations / Expressions

在此部分下无需添加任何内容。


3. Syntactic

以上是关于es6类只是javascript中原型模式的语法糖吗?的主要内容,如果未能解决你的问题,请参考以下文章

深入理解JS继承和原型链

一文读懂原型继承

JavaScript基于原型的面向对象系统

深入理解JS继承和原型链

ES6中的class(类)

对Javascript 类原型链继承的理解