jjzjj

javascript - 为什么在 JavaScript 中函数既是构造函数又是对象?

coder 2025-01-23 原文

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

Not exactly, because a function statement does not call the compiler.

But it produces a similar result.

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

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?

最佳答案

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

  1. 对象的一般处理。例子:
    • 数字和字符串是不可变的(⇒ 常量)。没有定义任何方法来在内部更改它们——结果总是产生新对象。虽然它们有一些固有方法,但您无法更改它们或添加新方法。任何这样做的尝试都将被忽略。
    • nullundefined 是特殊对象。任何对这些对象使用方法或定义新方法的尝试都会导致异常。
  2. 适用的运营商。 JavaScript 不允许(重新)定义操作符,因此我们坚持使用可用的操作符。
    • 数字对算术运算符有特殊的处理方式:+-*/
    • 字符串有一种特殊的方式来处理连接运算符:+
    • 函数有一种特殊的方式来处理“调用”运算符:()new 运算符。后者天生就知道如何使用构造函数的 prototype 属性,构造一个具有适当内部链接到原型(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 完成了以下操作:

  1. 已创建新的空对象 ({})。
  2. 它的内部原型(prototype)属性被设置为fun.prototype。在我们的例子中,它将是一个空对象 ({}),因为我们没有以任何方式修改它。
  3. 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
    

    如您所见,xA 的原型(prototype)和所有内容,而 y 是我们从构造函数。

关于javascript - 为什么在 JavaScript 中函数既是构造函数又是对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/372202/

有关javascript - 为什么在 JavaScript 中函数既是构造函数又是对象?的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  5. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  7. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  8. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  9. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  10. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

随机推荐