JavaScript:函数和类的区别是什么?

36

我想知道函数和类之间的区别。虽然两者都使用函数关键字,但它们之间有明显的区别吗?

7个回答

46

技术上讲,它们都不是类,只是函数而已。通过关键字 new,任何函数都可以作为构造函数来调用,该函数的原型属性用于继承对象的方法。

“类”仅在概念上用于描述上述实践。

因此,当有人对您说“创建一个颜色类”或其他内容时,您需要执行以下操作:

function Color(r, g, b) {
    this.r = r;
    this.g = g;
    this.b = b;
}

Color.prototype.method1 = function() {

};

Color.prototype.method2 = function() {

};

当你分解它时,只有一个函数和一些对该函数的一个名为prototype属性的赋值,都是通用的javascript语法,没有什么花哨的东西。

当你执行var black = new Color(0,0,0)时,所有这一切变得稍微神奇起来。你将获得一个具有.r.g.b属性的对象。那个对象也会有一个隐藏的[[prototype]]链接到Color.prototype。这意味着你可以说black.method1(),即使black对象中不存在.method1()


1
如果是这样,为什么React要使用关键字class?请参见:https://www.tutorialspoint.com/reactjs/reactjs_jsx.htm - Harsha
11
@Harsha 在 ES6 中新增了 class 语法,但其实它们在底层仍然是函数。 - Feathercrown
3
请注意,此答案已过时。在发布三年后,ES2015将class语法添加到JavaScript中。请参阅自发布以来已发布的其他答案。 - T.J. Crowder
1
@T.J.Crowder 这仍然适用。 ES6“类”在幕后仍然以完全相同的方式工作。 ES6只是添加了一种新的语法。这是一篇关于这个主题的好文章:https://www.toptal.com/javascript/es6-class-chaos-keeps-js-developer-up - Joel Mellon
@JoelMellon - 是的,它仍然是原型继承,旧语法当然没有消失。 :-) 但是 class 语法不仅仅是语法糖,在底层工作方式上并不完全相同。但是当我说“过时”时,我的意思是 class 语法提供了一种更清晰、更方便、更具功能性的方法(例如私有字段和方法)。请参见我的另一个问题的答案以获取更多详细信息(如果您愿意,还可以查看我最近的书籍JavaScript: The New Toys的第4章和第18章)。 - T.J. Crowder

24

JavaScript 是 ECMAScript 标准最流行的实现。JavaScript 的核心功能基于 ECMAScript 标准,但 JavaScript 还有其他一些不在 ECMA 规范/标准中的附加功能。每个浏览器都有一个 JavaScript 解释器。


概述

« ECMAScript 最初被设计成一种 Web 脚本语言,提供了一种机制来使 Web 页面在浏览器中活跃,并作为基于 Web 的客户端-服务器架构的一部分执行服务器计算。脚本语言是一种用于操作、定制和自动化现有系统设施的编程语言。

ECMAScript 是一种面向对象的编程语言,用于在主机环境中执行计算并操作计算对象。 Web 浏览器为客户端计算提供了一个 ECMAScript 主机环境,包括表示窗口、菜单、弹出窗口、对话框、文本区域、锚点、帧、历史记录、Cookie 和输入/输出等对象。

ECMAScript 是基于对象的:基本语言和主机设施由对象提供,ECMAScript 程序是一组通信对象

对象 «

  • 每个构造函数都是一个函数,具有名为“prototype”的属性,用于实现基于原型的继承和共享属性。

  • 由构造函数创建的每个对象都有一个隐式引用(称为对象的原型)指向其构造函数的“prototype”属性的值。此外,原型可能具有对其原型的非空隐式引用,依此类推;这称为原型链


函数

JavaScript将函数视为一等对象,因此作为对象,您可以将属性分配给函数。

提升是JavaScript解释器将所有变量和函数声明移动到当前范围顶部的操作。 函数提升、声明和表达式

FunctionDeclaration:function BindingIdentifier (FormalParameters){ FunctionBody } FunctionExpression:function BindingIdentifier (FormalParameters){ FunctionBody }

ES5函数:

function Shape(id) { // Function Declaration
    this.id = id;
};
// prototype was created automatically when we declared the function
Shape.hasOwnProperty('prototype'); // true

// Adding a prototyped method to a function.
Shape.prototype.getID = function () {
    return this.id;
};

var expFn = Shape; // Function Expression
console.dir( expFn () ); // Function Executes and return default return type - 'undefined'

如果未指定返回值,则函数将返回undefined。如果使用new调用函数并且返回值不是对象,则返回this(新对象)。
注意:每个函数都会自动创建一个原型属性,以允许该函数用作构造函数的可能性。
  • constructor是一个创建和初始化对象的函数对象。
  • prototype是为其他对象提供共享属性的对象。
  • __proto__是它的超级对象原型的proto属性。如果您打开它,您将看到proto指向其超级对象的变量和函数。
要访问上述创建函数的原型方法,我们需要使用new关键字和构造函数创建对象。如果您使用new关键字创建Shape-Object,则它具有与函数的原型Shape内部(或)私有链接
ES5构造函数类:使用Function.prototype.bind创建的函数对象。
Shape.prototype.setID = function ( id ) {
    this.id = id;
};

var funObj = new Shape( );
funObj.hasOwnProperty('prototype'); // false
funObj.setID( 10 )
console.dir( funObj );

console.log( funObj.getID() );
/*
expFun                            funObj
    name: "Shape"                   id: 10
    prototype:Object
        constructor: function Shape(id)
        getID: function()
        setID: function( id )
    __proto__: function ()          __proto__: Object
                                        constructor: function Shape(id)
                                        getID: function()
                                        setID: function( id )
    <function scope>
*/

ES6引入了箭头函数:箭头函数表达式比函数表达式语法更简洁,不会绑定自己的this、arguments、super或new.target。这些函数表达式最适合用于非方法函数,并且不能用作构造函数。 ArrowFunction语法产生物没有prototype属性。

ArrowFunction : ArrowParameters => ConciseBody

  a => (a < 10) ? 'valid' : 'invalid'

  const fn = (item) => { return item & 1 ? 'Odd' : 'Even'; };
    console.log( fn(2) ); // Even
    console.log( fn(3) ); // Odd

在基于类的面向对象编程语言中,通常状态由实例承载,方法由类承载,继承仅涉及结构和行为。在 ECMAScript 中,状态和方法由对象承载,结构、行为和状态都可以被继承。

Babel 是一个 JavaScript 编译器。使用它将 ES6 转换为 ES5 格式 BABEL JS(或)ES6Console

ES6 类ES2015 classes 是原型模式的一种简单语法糖。有一个方便的声明形式使得类模式更易于使用,并鼓励互操作性。类支持基于原型的继承、超级调用、实例和静态方法以及构造函数。

class Shape {
  constructor(id) {
    this.id = id
  }

  get uniqueID() {
    return this.id;
  }
  set uniqueID(changeVal) {
    this.id = changeVal;
  }
}
Shape.parent_S_V = 777;

// Class Inheritance
class Rectangle extends Shape {

  constructor(id, width, height) {
    super(id)
    this.width = width
    this.height = height
  }
  // Duplicate constructor in the same class are not allowed.
  /*constructor (width, height) { this._width  = width; this._height = height; }*/

  get area() {
    console.log('Area : ', this.width * this.height);
    return this.width * this.height
  }
  get globalValue() {
    console.log('GET ID : ', Rectangle._staticVar);
    return Rectangle._staticVar;
  }
  set globalValue(value) {
    Rectangle._staticVar = value;
    console.log('SET ID : ', Rectangle._staticVar);
  }

  static println() {
    console.log('Static Method');
  }

  // this.constructor.parent_S_V - Static property can be accessed by it's instances
  setStaticVar(staticVal) { // https://sckoverflow.com/a/42853205/5081877
    Rectangle.parent_S_V = staticVal;
    console.log('SET Instance Method Parent Class Static Value : ', Rectangle.parent_S_V);
  }

  getStaticVar() {
    console.log('GET Instance Method Parent Class Static Value : ', Rectangle.parent_S_V);
    return Rectangle.parent_S_V;
  }
}
Rectangle._staticVar = 77777;

var objTest = new Rectangle('Yash_777', 8, 7);
console.dir( objTest );

ES5函数类: 使用Object.defineProperty ( O, P, Attributes )

Object.defineProperty()方法直接在对象上定义一个新属性或修改现有属性,并返回对象。

可用作构造函数的函数实例具有原型属性

此属性具有以下特性{ [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }。

    'use strict';
var Shape = function ( superClass ) {
    var currentClass = Shape;
    _inherits(currentClass, superClass); // Prototype Chain - Extends

    function Shape(id) { superClass.call(this); // Linking with SuperClass Constructor.
        // Instance Variables list.
        this.id = id;   return this;
    }
    var staticVariablesJOSN = { "parent_S_V" : 777 };
    staticVariable( currentClass, staticVariablesJOSN );

    // Setters, Getters, instanceMethods. [{}, {}];
    var instanceFunctions = [
        {
            key: 'uniqueID',
            get: function get() { return this.id; },
            set: function set(changeVal) { this.id = changeVal; }
        }
    ];
    instanceMethods( currentClass, instanceFunctions );

    return currentClass;
}(Object);

var Rectangle = function ( superClass ) {
    var currentClass = Rectangle;

    _inherits(currentClass, superClass); // Prototype Chain - Extends

    function Rectangle(id, width, height) { superClass.call(this, id); // Linking with SuperClass Constructor.

        this.width = width;
        this.height = height;   return this;
    }

    var staticVariablesJOSN = { "_staticVar" : 77777 };
    staticVariable( currentClass, staticVariablesJOSN );

    var staticFunctions = [
        {
            key: 'println',
            value: function println() { console.log('Static Method'); }
        }
    ];
    staticMethods(currentClass, staticFunctions);

    var instanceFunctions = [
        {
            key: 'setStaticVar',
            value: function setStaticVar(staticVal) {
                currentClass.parent_S_V = staticVal;
                console.log('SET Instance Method Parent Class Static Value : ', currentClass.parent_S_V);
            }
        }, {
            key: 'getStaticVar',
            value: function getStaticVar() {
                console.log('GET Instance Method Parent Class Static Value : ', currentClass.parent_S_V);
                return currentClass.parent_S_V;
            }
        }, {
            key: 'area',
            get: function get() {
                console.log('Area : ', this.width * this.height);
                return this.width * this.height;
                }
        }, {
            key: 'globalValue',
            get: function get() {
                console.log('GET ID : ', currentClass._staticVar);
                return currentClass._staticVar;
            },
            set: function set(value) {
                currentClass._staticVar = value;
                console.log('SET ID : ', currentClass._staticVar);
            }
        }
    ];
    instanceMethods( currentClass, instanceFunctions );

    return currentClass;
}(Shape);

// ===== ES5 Class Conversion Supported Functions =====
function defineProperties(target, props) {
    console.log(target, ' : ', props);
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
    }
}
function staticMethods( currentClass, staticProps ) {
    defineProperties(currentClass, staticProps);
};
function instanceMethods( currentClass, protoProps ) {
    defineProperties(currentClass.prototype, protoProps);
};
function staticVariable( currentClass, staticVariales ) {
    // Get Key Set and get its corresponding value.
    // currentClass.key = value;
    for( var prop in staticVariales ) {
        console.log('Keys : Values');
        if( staticVariales.hasOwnProperty( prop ) ) {
            console.log(prop, ' : ', staticVariales[ prop ] );
            currentClass[ prop ] = staticVariales[ prop ];
        }
    }
};
function _inherits(subClass, superClass) {
    console.log( subClass, ' : extends : ', superClass );
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, 
            { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
    if (superClass)
        Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

var objTest = new Rectangle('Yash_777', 8, 7);
console.dir(objTest);

以下代码片段是用于测试每个实例都有自己的实例成员和共同的静态成员。

var obj1 = new Rectangle('R_1', 50, 20);
Rectangle.println(); // Static Method
console.log( obj1 );    // Rectangle {id: "R_1", width: 50, height: 20}
obj1.area;              // Area :  1000
obj1.globalValue;       // GET ID :  77777
obj1.globalValue = 88;  // SET ID :  88
obj1.globalValue;       // GET ID :  88  

var obj2 = new Rectangle('R_2', 5, 70);
console.log( obj2 );    // Rectangle {id: "R_2", width: 5, height: 70}
obj2.area;              // Area :  350    
obj2.globalValue;       // GET ID :  88
obj2.globalValue = 999; // SET ID :  999
obj2.globalValue;       // GET ID :  999

console.log('Static Variable Actions.');
obj1.globalValue;        // GET ID :  999

console.log('Parent Class Static variables');
obj1.getStaticVar();    // GET Instance Method Parent Class Static Value :  777
obj1.setStaticVar(7);   // SET Instance Method Parent Class Static Value :  7
obj1.getStaticVar();    // GET Instance Method Parent Class Static Value :  7


Major Differences between functions and classes are:

  • Function Declarations get Hoisted to Top of the context, where as classes declarations and function Expressions are not Hoisted.
  • Function Declarations, Expression can be Overridden as they are like a Variable - var if multiple declaration are available then it overrides its parent scope. Where as the Classes are not Overridden they are like let | const, let doesn't allows multiple declaration with same name inside its scope.
  • Function's / classes allows only single constructor for its object scope.
  • Computed method names are allowed to ES6 classes having class keyword, but function keyword is not allows it

    function myFoo() {
     this.['my'+'Method'] = function () { console.log('Computed Function Method'); };
    }
    class Foo {
        ['my'+'Method']() { console.log('Computed Method'); }
    }
    

现在,由于私有字段私有方法等功能正在不断发展,class已经不再像您最初发布答案时那样只是语法糖了。 - T.J. Crowder
1
为什么要使用 this.['my'+'Method']?如果有方括号,我们就不需要点运算符。所以应该使用 this['my'+'Method']。 - Damodara Sahu
是的,@DamodaraSahu,你说得对。我在这里写了一些样例代码:链接 - Hadi KAR

7
在JavaScript中,不存在类。 JavaScript使用原型继承而不是基于类的继承。许多人会提到JavaScript中的类,因为这样更容易理解,但这仅仅是一种比喻。
在基于类的继承中,您创建一个类(如果您愿意,可以称之为“蓝图”),然后从该类实例化对象。
在原型继承中,对象直接从另一个父对象实例化,而无需任何“蓝图”。
有关类与原型继承的更多信息,请参见维基百科页面

1
请注意,此答案已过时。在发布三年后,ES2015将class语法添加到JavaScript中。请参阅自发布以来已发布的其他答案。 - T.J. Crowder

6

构造函数和类的区别

在JavaScript中,class关键字与构造函数相似:

  • 它们都使用了JavaScript原型继承系统
  • 它们都可以使用以下语法创建对象:new myObj(arg1, arg2)

构造函数和类非常相似,可以根据个人偏好互换使用。然而,JavaScript类的私有字段功能是构造函数无法实现的(除非使用作用域黑科技)。

示例:

class PersonClass {
  constructor(name) {
    this.name = name;
  }
  
  speak () { console.log('hi'); }
}

console.log(typeof PersonClass); 
// logs function, a class is a constructor function under the hood.

console.log(PersonClass.prototype.speak);
// The class's methods are placed on the prototype of the PersonClass constructor function

const me = new PersonClass('Willem');

console.log(me.name); 
// logs Willem, properties assinged in the constructor are placed on the newly created object


// The constructor function equivalent would be the following:
function PersonFunction (name) {
  this.name = name;
}

PersonFunction.prototype.speak = function () { console.log('hi'); }


1
这正是我自己在问的。谢谢! - Adam
1
现在,由于私有字段私有方法等功能正在不断发展,所以class已不再像您最初发布答案时那样只是一种语法糖。 - T.J. Crowder
感谢T.J.Crowder指出这一点,我已经更新了答案。 - Willem van der Veen

2

类声明的作用域限定在其包含的块内。在同一块中声明两个相同的类名是错误的。类定义不会被提升。

函数

在严格模式下,函数声明具有块级作用域。

下表总结了类和函数之间的区别:enter image description here


1

这篇讲演强调了函数和类之间的一个关键区别,即函数是可以携带数据的行为,而相反地,类则是可以携带行为的数据。


0

这个术语通常在面向对象编程语言的上下文中使用。类是将在实例化时创建的对象的模板。JavaScript是一种基于原型的编程语言,因此使用术语类来描述JavaScript原型有点奇怪。在JavaScript中,原型是作为函数创建的。


如果几乎没有人使用“类”这样的术语,那将是奇怪的,但显然相反的情况是真实的,所以我并不认为这很奇怪。我们基于原型来构建对象,就像我们基于类/模板来构建它们一样。 - Jānis Elmeris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接