【问题标题】:Are ES6 classes just syntactic sugar for the prototypal pattern in Javascript?ES6 类只是 Javascript 中原型模式的语法糖吗?
【发布时间】:2016-07-25 00:02:24
【问题描述】:

在玩过 ES6 之后,我真的开始喜欢新的语法和可用的功能,但我确实对类有疑问。

新的 ES6 类只是旧原型模式的语法糖吗?还是在幕后发生了更多事情?例如:

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

对比:

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

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

【问题讨论】:

标签: javascript ecmascript-6


【解决方案1】:

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

虽然在很多地方都可以看到相反的情况,而且表面上似乎是正确的,但当您开始深入研究细节时,事情就会变得更加复杂。

我对现有的答案不太满意。在做了一些研究之后,这是我在脑海中对 ES6 类的特性进行分类的方式:

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

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


所以让我'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 伪经典继承模式的语法糖

在其核心,ES6 类确实为标准 ES5 伪经典继承模式提供了语法糖。

类声明/表达式

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

  1. 构造函数的内部[[Construct]]属性指的是附加到类的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;
    }
} );

初始类声明和上面的代码 sn-p 都会产生以下结果:

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
    }
})

初始派生类声明和上面的代码 sn-p 都会产生以下结果:

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 中可用但不切实际或不常见

ES6 类进一步改进了可能已经在 ES5 中实现的伪经典继承模式,但由于设置起来可能有点不切实际,因此经常被遗漏。

类声明/表达式

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

  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 中。

  • NB 2:虽然在 ES5 中可以毫无问题地定义静态属性,但这并不常见。造成这种情况的原因可能是,如果不使用当时非标准的 __proto__ 属性,就不可能建立静态属性的继承。

现在初始类声明和上面的代码 sn-p 也将产生以下结果:

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]() 调用的近似值,它不依赖于对父类的直接引用。如果不使用 class 语法,则无法完全模仿静态方法中的 super[method]() 调用,并在第 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 
});

现在初始派生类声明和上面的代码 sn-p 也将产生以下结果:

console.log( Bird.isBird( new Bird("Titi") ) )  // true

3。用于改进 ES5 中不可用的伪经典继承模式的语法糖

ES6 类进一步提供了对 ES5 中不可用的伪经典继承模式的改进,但可以在 ES6 中实现而无需使用类语法。

类声明/表达式

在其他地方发现的 ES6 特性也将其纳入类,特别是:

  1. 类声明的行为类似于 let 声明 - 它们在提升时未初始化,并在声明之前位于 Temporal Dead Zone。 (相关question
  2. 类名的行为类似于类声明中的 const 绑定 - 它不能在类方法中被覆盖,尝试这样做将导致 TypeError
  3. 必须使用内部[[Construct]] 方法调用类构造函数,如果使用内部[[Call]] 方法将它们作为普通函数调用,则会抛出TypeError
  4. 类方法(constructor() 方法除外),无论是否静态,其行为都类似于通过简洁方法语法定义的方法,也就是说:
    • 他们可以通过super.propsuper[method] 使用super 关键字(这是因为他们被分配了一个内部[[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 引用在静态方法中的行为不会像预期的那样。实际上,Object.assign() 不会复制内部 [[HomeObject]] 属性。在静态方法上正确设置 [[HomeObject]] 属性需要我们使用对象字面量定义函数构造函数,这是不可能的。

  • NB 2:为了防止在没有 new 关键字的情况下调用构造函数,可以通过使用 instanceof 运算符在 ES5 中实现类似的保护措施。但这些并未涵盖所有情况(请参阅answer)。

现在初始类声明和上面的代码 sn-p 也将产生以下结果:

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()相当于使用当前new.target值调用父构造函数的内部[[Construct]]方法并将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__ 属性。

  • NB 2:使用this 上下文无法模拟super() 的效果,因此我们必须从构造函数显式返回不同的that 对象.

现在初始派生类声明和上面的代码 sn-p 也将产生以下结果:

console.log( Object.getPrototypeOf( Bird ) )        // [Function: Vertebrate]
console.log( Bird.isVertebrate )                    // [Function: isVertebrate]

4。没有class 语法就无法实现的功能

ES6 类进一步提供了以下如果不实际使用class 语法就无法实现的功能:

  1. 静态类方法的内部[[HomeObject]] 属性指向类构造函数。
    • 对于普通的构造函数没有办法实现这一点,因为它需要通过对象字面量定义一个函数(另请参见上面的第 3 节)。这对于使用super 关键字的派生类的静态方法(如我们的Bird.isBird() 方法)尤其成问题。

如果事先知道父类,可以部分work around这个问题。


结论

ES6 类的一些特性只是标准 ES5 伪经典继承模式的语法糖。然而,ES6 类也具有只能在 ES6 中实现的特性以及一些在 ES6 中甚至无法模仿的特性(即不使用类语法)。

看了以上,我觉得可以说 ES6 类比 ES5 伪经典继承模式更简洁、更方便、更安全。结果,它们也不太灵活(例如,请参阅this question)。


附注

值得指出一些在上述分类中没有找到位置的类的更多特性:

  1. super() 仅在派生类构造函数中是有效的语法,并且只能被调用一次。
  2. 在调用super() 之前尝试在派生类构造函数中访问this 会导致ReferenceError
  3. 如果没有显式返回任何对象,则必须在派生类构造函数中调用 super()
  4. evalarguments 不是有效的类标识符(在非严格模式下它们是有效的函数标识符)。
  5. 如果没有提供,派生类会设置默认的constructor() 方法(对应于constructor( ...args ) { super( ...args ); })。
  6. 不能使用类声明或类表达式在类上定义数据属性(尽管您可以在类声明后手动添加数据属性)。

更多资源

  • Nicholas Zakas 的 Understanding ES6 中的 Understanding ES6 Classes 章节是我遇到的关于 ES6 类的最佳文章。
  • Axel Rauschmayer 的 2ality 博客对 ES6 类有非常详尽的 post
  • Object Playground 有一个很棒的视频来解释伪经典继承模式(并将其与类语法进行比较)。
  • Babel 转译器是您自己探索事物的好地方。

【讨论】:

  • 对此+1:-“所有类方法(静态或非静态)都是不可枚举的。”我试图弄清楚为什么 console.log 不会为类的原型返回相同的结果。
  • 嗯,但除了[[HomeObject]]之外,其他的不都是语法糖,因为他们使用原型来实现吗?
  • 我正在阅读链接的 Understanding ES6 章节,发现了一些我在您的回答中没有看到的内容,我认为在第 4 类中值得一提,那就是class 语法允许您从内置对象(如 Array 及其 .length 属性的特殊行为)继承奇异的行为,这不能仅使用原型来完成。
  • @jw013 感谢您指出这一点,这非常重要。我会添加一个注释。这实际上属于第 3 节,不需要任何修改,因为可以通过 Reflect.construct() 捕获异常行为。例如:function MyArray() {return Reflect.construct(Array, [], new.target)}; Object.setPrototypeOf(MyArray.prototype, Array.prototype); Object.setPrototypeOf(MyArray, Array); const colors = new MyArray(); colors[0] = "red"; console.log(colors.length) // 1; console.log(colors instanceof MyArray) // true; console.log(colors instanceof Array) // true;
  • @BoLi - 是的,我想你在这个意义上是对的。然而,根据寻找这个问题的人来自哪里,他们可能对 syntactic sugar 有不同的含义。因此上面的区别......
【解决方案2】:

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

声明一个类会创建一个函数对象,该对象是该类的构造函数,使用为类主体内的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)中引入的类联系起来已经成为一种时尚,但我个人尽量避免重复。

【讨论】:

【解决方案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{}

【讨论】:

【解决方案4】:

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

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

Source

以下简短代码示例显示了如何在 prototype 对象上设置 class 中的函数。

class Thing {
   someFunc() {}
}

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

【讨论】:

  • 这个someFunc() 方法在类的prototype 中也是如此,因为OP 正在尝试使用es5 方式。
  • @Jai 是的,运行答案中的代码示例,您可以看到它的相同。
  • 哇!我问了这么愚蠢的问题。
  • @Jai 没有愚蠢的问题!
  • 您的消息来源还提到了与经典方法的细微差别。可惜你没有。
【解决方案5】:

ES6 类只是 Javascript 中原型模式的语法糖吗?

当被问到这个问题时,答案是几乎(但是,你知道,种糖)。

现在,答案是:

在 ES2015 中你可以用 class 做一些你在 ES5 和更早版本中无法做到的事情,但是即使没有 class,它们都是你可以在 ES2015 中以一种或另一种方式做的事情,因为新的ES2015 中添加了非class 功能,例如Reflect.constructnew.target。有些事情真的很尴尬,但仍有可能。除了语法糖之外,唯一可以考虑的是新的[[HomeObject]] 函数插槽。您可以通过其他方式获得相同的效果,但您实际上无法使用该新插槽。

但随着 class fieldsprivate methods and accessorsstatic private methods 即将到来(我在 2020 年 12 月下旬写这篇文章时的第三阶段,可能会在新的几个月内完成),有些事情几乎可以肯定超出了语法糖的水平。

例如,私有字段是对在class 语法之外不可用的对象的基本补充。私有字段存储在新的内部对象槽中,不能以任何其他方式访问(例如 [[HomeObject]] 是)。

这并不意味着你不能以不同的方式做类似的事情,只是你不能真正使用新的私人插槽。例如,考虑使用私有字段的此类:

class Person {
    #name;
    constructor(name) {
        this.#name = name;
    }
    getName() {
        return this.#name;
    }
}
const person = new Person("Joe");
console.log(person.name); // undefined
// console.log(person.#name); // Would be a SyntaxError
console.log(person.getName()); // "Joe"

这可以通过几种不同的方式在没有私有字段的情况下编写,例如通过在构造函数中定义getName(因此它关闭name参数)而不是继承它,或者使用WeakMap

const Person = (() => {
    const names = new WeakMap();
    return class Person {
        constructor(name) {
            names.set(this, name);
        }
        getName() {
            return names.get(this);
        }
    };
})();
const person = new Person("Joe");
console.log(person.name); // undefined
console.log(person.getName()); // "Joe"

虽然这样可行,但它并没有利用对象包含实际私有字段的新能力。

【讨论】:

    【解决方案6】:

    它们完全是语法糖。 ES6 中原型继承的新特性是对象的__proto__ 属性的重新定义。 __proto__ 现在是合法的,这就是数组子类化在 JS 中成为可能的方式。

    【讨论】:

    • __proto__ 从不违法。规范明确将其描述为遗留功能,并且(现在)可以在没有它的情况下对数组进行子分类。
    • @zeroflagL 不是“非法”,但现在已被正式弃用 :-) 始终使用 Object.get/setPrototypeOf
    【解决方案7】:

    是的,差不多。

    使用 es6 你可以扩展 Function 类和 Array 类,在 es5 中你不能有相同的行为: 扩展 Function 不会生成可调用对象,扩展 Array 不会继承 es5 中的 .length auto 属性

    其余的原型逻辑和类在 JavaScript 中是一样的

    Are the es6 classes really semantic sugar?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-09
      • 1970-01-01
      • 2022-11-29
      • 1970-01-01
      • 2011-03-08
      • 1970-01-01
      • 2015-09-04
      相关资源
      最近更新 更多