为啥在 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 中的所有对象都是……嗯,对象。但是有些对象比其他对象更特殊:即内置对象。区别主要在于以下几个方面:
-
对象的一般处理。例子:
数字和字符串是不可变的(⇒ 常量)。没有定义任何方法来在内部更改它们——结果总是产生新的对象。虽然它们有一些先天方法,但您无法更改它们或添加新方法。任何这样做的尝试都将被忽略。
null
和 undefined
是特殊对象。对这些对象使用方法或定义新方法的任何尝试都会导致异常。
适用的运算符。 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
失败的原因是没有名为myFunction
的reference。
【讨论】:
【参考方案7】:JavaScript 基于 ECMA 脚本。它的规范使用原型模型使其成为 OOP。然而,ECMA 脚本并不强制执行严格的数据类型。 需要实例化对象的原因与 ECMA 脚本需要“新”调用的原因相同,该调用将为属性分配内存,否则它将保持为函数,您可以根据需要调用它,在这种情况下,属性将初始化然后在函数结束时销毁。
【讨论】:
【参考方案8】:只有在使用 new 关键字实例化时,该函数才会充当构造函数。
结果是一个可以使用“this”关键字访问成员属性的对象。当函数以任何其他方式使用时,方法中的 this 关键字没有任何意义。
【讨论】:
hmm ...javascript:newobj=function()return (); alert(newobj)
创建 [object Object]
... gnu(而不是牛肉)在哪里?
@Ekim 我看不出你的演示是如何关联的。一个函数可以返回一个对象是你所展示的?如果你做了一个new newobj()
,这可能会让谈论变得有趣。您只是返回一个对象,而不是创建自己的类型。以上是关于为啥在 JavaScript 中函数既是构造函数又是对象?的主要内容,如果未能解决你的问题,请参考以下文章