Javascript 如何检查ECMAScript 6类和函数之间的差异?

Javascript 如何检查ECMAScript 6类和函数之间的差异?,javascript,ecmascript-6,es6-class,Javascript,Ecmascript 6,Es6 Class,在ECMAScript 6中,根据规范,类的类型是“函数” 但是,根据规范,不允许将通过类语法创建的对象作为普通函数调用调用。换句话说,您必须使用new关键字,否则将抛出TypeError TypeError:无法调用函数类 因此,如果不使用try-catch(这将非常难看并破坏性能),您如何检查函数是否来自类语法或函数语法?如果我正确理解了ES6,那么使用类的效果与您键入的相同 var Foo = function(){} var Bar = function(){ Foo.call(thi

在ECMAScript 6中,根据规范,类的
类型是
“函数”

但是,根据规范,不允许将通过类语法创建的对象作为普通函数调用调用。换句话说,您必须使用
new
关键字,否则将抛出TypeError

TypeError:无法调用函数类


因此,如果不使用try-catch(这将非常难看并破坏性能),您如何检查函数是否来自
语法或
函数
语法?

如果我正确理解了ES6,那么使用
的效果与您键入的相同

var Foo = function(){}
var Bar = function(){
 Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
键入
MyClass()
而不键入
关键字new
时出现语法错误只是为了防止对象使用的变量污染全局空间

var MyClass = function(){this.$ = "my private dollar"; return this;}
如果你有

// $ === jquery
var myObject = new MyClass();
// $ === still jquery
// myObject === global object
但如果你这样做了

var myObject = MyClass();
// $ === "My private dollar"

因为
这个
在构造函数中被称为函数是指全局对象,但是当用关键字
new
调用时,Javascript首先创建新的空对象,然后调用构造函数。

我做了一些研究,发现ES6类的原型对象[]似乎不可写,不可枚举,不可配置

以下是一种检查方法:

class F { }

console.log(Object.getOwnPropertyDescriptor(F, 'prototype'));
// {"value":{},"writable":false,"enumerable":false,"configurable":false
常规函数默认为可写、不可枚举、不可配置

ES6小提琴:

因此,ES6类描述符将始终将这些属性设置为false,并且如果您试图定义描述符,将抛出错误

// Throws Error
Object.defineProperty(F, 'prototype', {
  writable: true
});
// Works
Object.defineProperty(G, 'prototype', {
  writable: false
});
但是,使用常规函数,您仍然可以定义这些描述符

// Throws Error
Object.defineProperty(F, 'prototype', {
  writable: true
});
// Works
Object.defineProperty(G, 'prototype', {
  writable: false
});
在常规函数上修改描述符并不常见,因此您可能可以使用它来检查它是否是类,但这当然不是真正的解决方案


@alexpods对函数进行字符串化并检查class关键字的方法可能是目前最好的解决方案。

我认为检查函数是否为ES6类的最简单方法是检查方法的结果。根据报告:

字符串表示必须具有FunctionDeclaration FunctionExpression、GeneratorDeclaration、GeneratorExpression、ClassDeclaration、ClassExpression、ArrowFunction、MethodDefinition或GeneratorMethod的语法,具体取决于对象的实际特征

所以check函数看起来非常简单:

function isClass(func) {
  return typeof func === 'function' 
    && /^class\s/.test(Function.prototype.toString.call(func));
}
在本线程中提到的不同方法上运行了一些,下面是一个概述:


本机类-Props方法(在大型示例中最快速度为56倍,在普通示例中最快速度为15倍):

这是有效的,因为以下是正确的:

> Object.getOwnPropertyNames(class A {})
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(class A { constructor (a,b) {} a (b,c) {} })
[ 'length', 'name', 'prototype' ]
> Object.getOwnPropertyNames(function () {})
[ 'length', 'name', 'arguments', 'caller', 'prototype' ]
> Object.getOwnPropertyNames(() => {})
> [ 'length', 'name' ]

本机类-字符串方法(比regex方法快约10%):


这也可用于确定常规类别:

// Character positions
const INDEX_OF_FUNCTION_NAME = 9  // "function X", X is at index 9
const FIRST_UPPERCASE_INDEX_IN_ASCII = 65  // A is at index 65 in ASCII
const LAST_UPPERCASE_INDEX_IN_ASCII = 90   // Z is at index 90 in ASCII

/**
 * Is Conventional Class
 * Looks for function with capital first letter MyClass
 * First letter is the 9th character
 * If changed, isClass must also be updated
 * @param {any} value
 * @returns {boolean}
 */
function isConventionalClass (value /* :any */ ) /* :boolean */ {
    if ( typeof value !== 'function' )  return false
    const c = value.toString().charCodeAt(INDEX_OF_FUNCTION_NAME)
    return c >= FIRST_UPPERCASE_INDEX_IN_ASCII && c <= LAST_UPPERCASE_INDEX_IN_ASCII
}
//字符位置
函数名=9/“函数X”的常数索引,X位于索引9处
常量第一个大写字母索引在ASCII=65//A在ASCII的索引65处
const LAST_大写字母_INDEX_IN_ASCII=90//Z位于ASCII的索引90处
/**
*这是传统的课程
*查找首字母大写的函数MyClass
*第一个字母是第9个字符
*如果更改,还必须更新iClass
*@param{any}值
*@returns{boolean}
*/
函数isConventionalClass(value/*:any*/)/*:boolean*/{
if(typeof value!=“function”)返回false
const c=value.toString().charCodeAt(函数名的索引)

return c>=FIRST\u UPPERCASE\u INDEX\u IN\u ASCII&&c查看由生成的编译代码,我认为无法判断函数是否用作类。在当时,JavaScript没有类,每个构造函数都只是一个函数。今天的JavaScript类关键字没有引入“类”的新概念,而是一个语法糖

ES6代码:

// ES6
class A{}
ES5产生于:

当然,如果您热衷于编码约定,您可以解析函数(类),并检查其名称是否以大写字母开头

function isClass(fn) {
    return typeof fn === 'function' && /^(?:class\s+|function\s+(?:_class|_default|[A-Z]))/.test(fn);
}
编辑:

已经支持class关键字的浏览器可以在解析时使用它。否则,您只能使用大写字母1

编辑:

正如balupton指出的,Babel为匿名类生成
函数_class(){}
,并在此基础上改进了正则表达式

编辑:

在正则表达式中添加了
\u default
,以检测
导出默认类{}

警告
BabelJS正在大量开发中,不能保证在这种情况下它们不会更改默认函数名。真的,你不应该依赖它。

因为现有的答案从ES5环境中解决了这个问题 我认为从ES2015的角度提供一个答案是值得的+ 观点;最初的问题没有具体说明,今天很多人没有 不再需要传输类,这稍微改变了情况

我特别想指出的是,可以明确回答这个问题 问题“这个值可以构造吗?”不可否认,这通常是没有用的 就其本身而言,同样的基本问题仍然存在,如果你需要知道的话 如果可以调用某个值

什么东西是可构造的? 首先,我认为我们需要澄清一些术语,因为询问 值是一个构造函数,它可以表示多种含义:

  • 从字面上看,该值是否有[[construct]]插槽?如果有,则为 可构造的。如果不可构造,则不可构造
  • 这个函数是要构造的吗?我们可以产生一些否定的东西:函数 不可能被建造的,我们不打算被建造,但我们不能 也可以说(不使用启发式检查)函数 constructable并不打算用作构造函数
  • 使2无法回答的是,使用
    fu创建的函数
    
    // ES5
    "use strict";
    
    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    var A = function A() {
        _classCallCheck(this, A);
    };
    
    function isClass(fn) {
        return typeof fn === 'function' && /^(?:class\s+|function\s+(?:_class|_default|[A-Z]))/.test(fn);
    }
    
    const isConstructable = fn => {
      try {
        new new Proxy(fn, { construct: () => ({}) });
        return true;
      } catch (err) {
        return false;
      }
    };
    
    isConstructable(class {});                      // true
    isConstructable(class {}.bind());               // true
    isConstructable(function() {});                 // true
    isConstructable(function() {}.bind());          // true
    isConstructable(() => {});                      // false
    isConstructable((() => {}).bind());             // false
    isConstructable(async () => {});                // false
    isConstructable(async function() {});           // false
    isConstructable(function * () {});              // false
    isConstructable({ foo() {} }.foo);              // false
    isConstructable(URL);                           // true
    
    const isCallable = fn => typeof fn === 'function';
    
    // Demonstration only, this function is useless:
    
    const isCallable = fn => {
      try {
        new Proxy(fn, { apply: () => undefined })();
        return true;
      } catch (err) {
        return false;
      }
    };
    
    isCallable(() => {});                      // true
    isCallable(function() {});                 // true
    isCallable(class {});                      // ... true!
    
    const isDefinitelyCallable = fn =>
      typeof fn === 'function' &&
      !isConstructable(fn);
    
    isDefinitelyCallable(class {});                      // false
    isDefinitelyCallable(class {}.bind());               // false
    isDefinitelyCallable(function() {});                 // false <-- callable
    isDefinitelyCallable(function() {}.bind());          // false <-- callable
    isDefinitelyCallable(() => {});                      // true
    isDefinitelyCallable((() => {}).bind());             // true
    isDefinitelyCallable(async () => {});                // true
    isDefinitelyCallable(async function() {});           // true
    isDefinitelyCallable(function * () {});              // true
    isDefinitelyCallable({ foo() {} }.foo);              // true
    isDefinitelyCallable(URL);                           // false
    
    const isProbablyNotCallable = fn =>
      typeof fn !== 'function' ||
      fn.toString().startsWith('class') ||
      Boolean(
        fn.prototype &&
        !Object.getOwnPropertyDescriptor(fn, 'prototype').writable // or your fave
      );
    
    isProbablyNotCallable(class {});                      // true
    isProbablyNotCallable(class {}.bind());               // false <-- not callable
    isProbablyNotCallable(function() {});                 // false
    isProbablyNotCallable(function() {}.bind());          // false
    isProbablyNotCallable(() => {});                      // false
    isProbablyNotCallable((() => {}).bind());             // false
    isProbablyNotCallable(async () => {});                // false
    isProbablyNotCallable(async function() {});           // false
    isProbablyNotCallable(function * () {});              // false
    isProbablyNotCallable({ foo() {} }.foo);              // false
    isProbablyNotCallable(URL);                           // true
    
    class Foo{
      constructor(){
        this.someProp = 'Value';
      }
    }
    Foo.prototype.isClass = true;
    
    function Foo(){
      this.someProp = 'Value';
    }
    Foo.prototype.isClass = true;
    
    if(Foo.prototype.isClass){
      //It's a class
    }
    
    class Person1 {
      constructor(name) {
        this.name = name;
        console.log(new.target) // => // => [Class: Person1]
      }
    }
    
    function Person2(){
      this.name='cc'
      console.log(new.target) // => [Function: Person2]
    }