为啥在 JavaScript 中函数既是构造函数又是对象?

Posted

技术标签:

【中文标题】为啥在 JavaScript 中函数既是构造函数又是对象?【英文标题】:Why in JavaScript is a function considered both a constructor and an object?为什么在 JavaScript 中函数既是构造函数又是对象? 【发布时间】:2010-09-27 04:43:58 【问题描述】:

我最近对此进行了大量研究,但还没有得到一个非常好的可靠答案。我在某处读到,当 javascript 引擎遇到函数语句时会创建一个新的 Function() 对象,这让我相信它可能是一个对象的子对象(从而成为一个对象)。所以我给 Douglas Crockford 发了邮件,他的回答是:

不完全是,因为一个函数 语句不调用编译器。

但它会产生类似的结果。

另外,据我所知,您不能在函数构造函数上调用成员,除非它已被实例化为新对象。所以这行不通:

function myFunction()
    this.myProperty = "Am I an object!";

myFunction.myProperty; // myFunction is not a function
myFunction().myProperty; // myFunction has no properties

但是,这将起作用:

function myFunction()
    this.myProperty = "Am I an object!";

var myFunctionVar = new myFunction();
myFunctionVar.myProperty;

这只是语义的问题吗...在整个编程世界中,对象何时真正成为对象,以及如何映射到 JavaScript?

【问题讨论】:

【参考方案1】:

函数和构造函数没有什么神奇之处。 JavaScript 中的所有对象都是……嗯,对象。但是有些对象比其他对象更特殊:即内置对象。区别主要在于以下几个方面:

    对象的一般处理。例子: 数字和字符串是不可变的(⇒ 常量)。没有定义任何方法来在内部更改它们——结果总是产生新的对象。虽然它们有一些先天方法,但您无法更改它们或添加新方法。任何这样做的尝试都将被忽略。 nullundefined 是特殊对象。对这些对象使用方法或定义新方法的任何尝试都会导致异常。 适用的运算符。 JavaScript 不允许(重新)定义运算符,所以我们坚持使用可用的。 数字与算术运算符有一种特殊的方式:+-*/。 字符串有一种特殊的方式来处理连接运算符:+。 函数有一种特殊的方式来处理“调用”运算符:()new 运算符。后者拥有关于如何使用构造函数的prototype 属性、构造具有与原型的适当内部链接的对象以及调用构造函数正确设置this 的先天知识。

如果您查看 ECMAScript 标准 (PDF),您会发现所有这些“额外”功能都被定义为方法和属性,但其中许多功能不能直接供程序员使用。其中一些将在标准 ES3.1 的新修订版中公开(截至 2008 年 12 月 15 日的草案:PDF)。一个属性 (__proto__) 是 already exposed in Firefox。

现在我们可以直接回答您的问题。是的,函数对象有属性,我们可以随意添加/删除它们:

var fun = function()/* ... */;
fun.foo = 2;
console.log(fun.foo);  // 2
fun.bar = "Ha!";
console.log(fun.bar);  // Ha!

函数实际上做什么并不重要——它永远不会发挥作用,因为我们没有调用它!现在让我们定义它:

fun = function() this.life = 42; ;

它本身不是一个构造函数,它是一个对其上下文进行操作的函数。我们可以轻松地提供它:

var context = ford: "perfect";

// now let's call our function on our context
fun.call(context);

// it didn't create new object, it modified the context:
console.log(context.ford);           // perfect
console.log(context.life);           // 42
console.log(context instanceof fun); // false

如您所见,它为现有对象添加了一个属性。

为了将我们的函数用作构造函数,我们必须使用new 运算符:

var baz = new fun();

// new empty object was created, and fun() was executed on it:
console.log(baz.life);           // 42
console.log(baz instanceof fun); // true

如您所见,new 使我们的函数成为构造函数。以下操作由new完成:

    已创建新的空对象 ()。 其内部原型属性设置为fun.prototype。在我们的例子中,它将是一个空对象 (),因为我们没有以任何方式对其进行修改。 fun() 是用这个新对象作为上下文调用的。

由我们的函数来修改新对象。通常它设置对象的属性,但它可以为所欲为。

有趣的琐事:

因为构造函数只是一个我们可以计算的对象:

var A = function(val) this.a = val; ;
var B = function(val) this.b = val; ;
var C = function(flag) return flag ? A : B; ;

// now let's create an object:
var x = new (C(true))(42);

// what kind of object is that?
console.log(x instanceof C); // false
console.log(x instanceof B); // false
console.log(x instanceof A); // true
// it is of A

// let's inspect it
console.log(x.a); // 42
console.log(x.b); // undefined

// now let's create another object:
var y = new (C(false))(33);

// what kind of object is that?
console.log(y instanceof C); // false
console.log(y instanceof B); // true
console.log(y instanceof A); // false
// it is of B

// let's inspect it
console.log(y.a); // undefined
console.log(y.b); // 33

// cool, heh?

构造函数可以返回一个覆盖新创建对象的值:

var A = function(flag)
  if(flag)
    // let's return something completely different
    return ford: "perfect";
  
  // let's modify the object
  this.life = 42;
;

// now let's create two objects:
var x = new A(false);
var y = new A(true);

// let's inspect x
console.log(x instanceof A); // true
console.log(x.ford);         // undefined
console.log(x.life);         // 42

// let's inspect y
console.log(y instanceof A); // false
console.log(y.ford);         // perfect
console.log(y.life);         // undefined

如您所见,x 是带有原型的A,而y 是我们从构造函数返回的“裸”对象。

【讨论】:

【参考方案2】:

你的理解是错误的:

myFunction().myProperty; // myFunction has no properties

它不起作用的原因是因为“.myProperty”应用于“myFunction()”的返回值,而不是应用于对象“myFunction”。也就是说:

$ js
js> function a()  this.b=1;return b: 2;
js> a().b
2
js> 

请记住,“()”是一个运算符。 “myFunction”与“myFunction()”不同。使用 new 实例化时不需要“返回”:

js> function a()  this.b=1;
js> d = new a();
[object Object]
js> d.b;
1

【讨论】:

嘿,这是一个命令行 javascript 解释器吗?我在哪里可以买到?似乎比使用 firebug 控制台测试东西容易得多 刚刚做了“yum install js”;它实际上是 spidermonkey,Firefox js 引擎编译为命令行可执行文件。如果您使用较小的操作系统,则始终可以使用 Java 版本的 rhino。【参考方案3】:

要回答您的具体问题,技术上的功能始终是对象。

例如,您总是可以这样做:

function foo()
  return 0;

foo.bar = 1;
alert(foo.bar); // shows "1"

当Javascript函数使用this指针时,它们的行为有点像其他OOP语言中的类。它们可以通过 new 关键字实例化为对象:

function Foo()
  this.bar = 1;

var foo = new Foo();
alert(foo.bar); // shows "1"

现在这种从其他 OOP 语言到 Javascript 的映射将很快失败。例如,实际上 Javascript 中没有类之类的东西——对象使用原型链来代替继承。

如果您要使用 Javascript 进行任何类型的重要编程,我强烈推荐您发送电子邮件的那个人 Crockford 的 Javascript: The Good Parts。

【讨论】:

【参考方案4】:

Javascript 的“全局”范围(至少在浏览器中)是 window 对象。

这意味着当您执行 this.myProperty = "foo" 并将函数调用为纯 myFunction() 时,您实际上是在设置 window.myProperty = "foo"

myFunction().myProperty 的第二点是,这里您正在查看myFunction()返回值,因此它自然不会有任何属性,因为它返回 null。

你的想法是这样的:

function myFunction()

    myFunction.myProperty = "foo";


myFunction();
alert(myFunction.myProperty); // Alerts foo as expected

这(几乎)与

相同
var myFunction = new Function('myFunction.myProperty = "foo";');
myFunction();

当您在new 上下文中使用它时,“返回值”是您的新对象,“this”指针变为您的新对象,因此它可以按您的预期工作。

【讨论】:

这可能是我读过的对该概念的最佳描述。感谢发帖!【参考方案5】:

确实,函数是“一等公民”:它们是一个对象。

每个对象都有一个Prototype,但只能直接引用一个函数的原型。当以函数对象为参数调用new时,以函数对象的原型为原型构造一个新对象,并在进入函数之前将this设置为新对象。

所以您可以将每个函数都称为构造函数,即使它只留下this

那里有关于构造函数、原型等的非常好的教程……我个人从Object Oriented Programming in JavaScript 学到了很多东西。它显示了“继承”其原型但使用this 填充新对象属性的函数和使用特定原型的函数对象的等价性:

function newA()  this.prop1 = "one";  // constructs a function object called newA
function newA_Too()  // constructs a function object called newA_Too
newA_Too.prototype.prop1 = "one";

var A1 = new newA();
var A2 = new newA_Too();
// here A1 equals A2.

【讨论】:

【参考方案6】:

首先,JavaScript 处理对象的方式与 C++/Java 不同,因此您需要抛开这些想法,才能理解 javascript 的工作原理。

当这行执行时:

var myFunctionVar = new myFunction();

那么myFunction() 内部的this 指的是您正在创建的这个新对象-myFunctionVar。因此这行代码:

 this.myProperty = "Am I an object!";

基本上有

的结果
 myFunctionVar.myProperty = "Am I an object!";

查看new 运算符的一些文档可能会对您有所帮助。在 JS 中,new 运算符本质上允许您从函数中创建对象——任何普通的旧函数。与 new 运算符一起使用的函数没有什么特别之处,它将其标记为构造函数,就像在 C++ 或 Java 中一样。正如文档所说:

创建用户定义的对象类型需要两个步骤:

    通过编写函数定义对象类型。 使用 new 创建对象的实例。

那么你对代码做了什么

function myFunction()
    this.myProperty = "Am I an object!";

是创建一个可用作构造函数的函数。代码myFunction.myProperty 失败的原因是没有名为myFunctionreference

【讨论】:

【参考方案7】:

JavaScript 基于 ECMA 脚本。它的规范使用原型模型使其成为 OOP。然而,ECMA 脚本并不强制执行严格的数据类型。 需要实例化对象的原因与 ECMA 脚本需要“新”调用的原因相同,该调用将为属性分配内存,否则它将保持为函数,您可以根据需要调用它,在这种情况下,属性将初始化然后在函数结束时销毁。

【讨论】:

【参考方案8】:

只有在使用 new 关键字实例化时,该函数才会充当构造函数。

结果是一个可以使用“this”关键字访问成员属性的对象。当函数以任何其他方式使用时,方法中的 this 关键字没有任何意义。

【讨论】:

hmm ... javascript:newobj=function()return (); alert(newobj) 创建 [object Object] ... gnu(而不是牛肉)在哪里? @Ekim 我看不出你的演示是如何关联的。一个函数可以返回一个对象是你所展示的?如果你做了一个new newobj(),这可能会让谈论变得有趣。您只是返回一个对象,而不是创建自己的类型。

以上是关于为啥在 JavaScript 中函数既是构造函数又是对象?的主要内容,如果未能解决你的问题,请参考以下文章

浅析JavaScript中Function对象

为啥要设置原型构造函数?

javascriptnew的时候为啥会执行构造函数?

JavaScript-- 函数既是函数又是对象

JavaScript设计模式之构造函数模式

JavaScript构造函数 原型 原型链