JavaScript函数对象的属性

28

我有一个JavaScript函数对象,如下:

var addNum = function(num1, num2) {
        return num1 + num2;
}

现在如果我尝试访问

addNum.divide()

我想了解上述代码的原型链。我了解到在上面的示例中,会先搜索addNum以查找divide(),然后是Function.prototype,最后是Object.prototype。

但我的问题是,在上述示例中,如何将addNum搜索为divide()

它是否指的是类似于;的东西?

var addNum = function(num1, num2) {

this.divide = function(){}

            return num1 + num2;
    }

我不理解其中的一句话,即addNum将被搜索divide()。

请帮助我理解这句话。


我在Chrome中运行了你的代码,它报错:Uncaught TypeError: Object function (num1, num2) { return num1 + num2; } has no method 'divide'。 - andri
你确定这不是一个架构问题吗?实际上,除法不应该是addNum的子函数。相反,它们应该有一个父类/对象,可以共享变量和属性,并对它们进行数学运算。 - Dominic
我在http://doctrina.org/Javascript-Objects-Prototypes.html上读到了这行代码。 - copenndthagen
所以,你建议的这段代码是正确的吗?var addNum = function(num1, num2) { this.divide = function(){} return num1 + num2; } 我认为是的。 - Weishi Z
5个回答

57

我不确定这会不会回答你的问题,但可能会给你一些启示。考虑以下例子:

var Person = (function () {
    var Person = function (name) {
        this.name = name;
    }

    Person.greet = function () {
        console.log("Hello!");
    }

    Person.prototype = {
        greet: function () {
            console.log('Hello, my name is ' + this.name);
        }
    };
    return Person;
})();

var bob = new Person("Bob");

Person.greet(); // logs "Hello!"
bob.greet(); // logs "Hello, my name is Bob

函数对象"Person"有一个直接的'greet'属性,它是一个函数。从面向对象编程的角度来看,你几乎可以认为它是一个静态方法,可以直接从Person函数中调用(Person.greet())。一旦你从Person构造函数中实例化一个人对象,这个新对象"bob"现在引用了它从Person.prototype对象继承的方法。现在当你调用bob.greet()时,它使用原型对象中的greet函数。希望这有所帮助。

我喜欢这个例子,它很好地解释了原型继承的可能性。也许如果你再加上另一个部分来解释一下,你现在可以扩展Function构造函数,以进一步展示Javascript的可能性和原型继承背后的思想,那就更好了。 - gmaliar
1
谢谢提供示例。但是为什么Person.prototype.constructor会得到greet,而Person不会呢?如果您在控制台中键入它们。 - MartianMartian

26

如你所说: 你有一个函数对象。在JS中,函数就像Object字面量、数组或其他任何东西一样都是对象:函数可以随意分配属性和方法:

var someAnonFunction = function(foo)
{
    console.log(this);
    console.log(this === someAnonFunction);//will be false most of the time
};
someAnonFunction.x = 123;//assign property
someAnonFunction.y = 312;
someAnonFunction.divide = function()
{
    console.log(this === someAnonFunction);//will be true most of the time
    return this.x/this.y;//divide properties x & y
};
someAnonFunction.divide();

在这种情况下,函数对象被引用为someAnonFunction,已被分配一个对匿名函数的引用,称为divide(不过,引用匿名函数的引用被命名为divide)。所以这里根本没有涉及原型。请注意,正如你自己所说:所有对象都可以追溯到Object.prototype,只需尝试此操作即可:
console.log(someAnonFunction.toString === Function.prototype.toString);//functions are stringified differently than object literals
console.log(someAnonFunction.hasOwnProperty === Object.prototype.hasOwnProperty);//true

或许这样更加清晰:JS中一个方法/属性调用如何解析为一个值的简单方案:
[      F.divide      ]<=========================================================\ \
F[divide] ===> JS checks instance for property divide                           | |
 /\ ||                                                                          | |
 || || --> property found @instance, return value-------------------------------| |
 || ||                                                                          | |
 || ===========> Function.prototype.divide could not be found, check prototype  | |
 ||      ||                                                                     | |
 ||      ||--> property found @Function.prototype, return-----------------------| |
 ||      ||                                                                     | |
 ||      ==========> Object.prototype.divide: not found check prototype?        | |
 ||          ||                                                                 | |
 ||          ||--> property found @Object.prototype, return---------------------|_|
 ||          ||                                                                 |=|
 ||          =======>prototype is null, return "undefined.divide"~~~~~~~~~~~~~~~|X|
 ||                                                                             \ /
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~< TypeError can't read property 'x' of undefined

因此,如果您希望使用原型使上述代码工作,则需要增加某种原型(在本例中为Function.prototype)。请注意,这并不被推荐,实际上,更改“本地”原型经常会受到反对。但仍然可以这样做:
Function.prototype.divide = function (a, b)
{
    a = +(a || 0);//coerce to number, use default value
    b = +(b || 1) || 1;//division by zeroe is not allowed, default to 1
    return a/b;
};
function someFunction ()
{
    return 'someString';
};
var another = function(a, b)
{
    return a + b;
};
someFunction.divide(12, 6);//will return 2
another.divide(12, 4);//3

在这两种情况下,函数对象,由名称(someFunctionanother)引用,将被扫描以查找名为divide的属性,但未找到。然后它将扫描Function.prototype,在那里找到了这样的属性。
如果不是这样,JS还会检查Object.prototype,如果失败,最终会抛出错误。
我之前在SO上发布过关于这个主题的相当详细的答案:

什么使得my.class.js如此快速?(处理原型链)
JavaScript中的对象和函数(函数、对象和构造函数的回顾)
这三种JavaScript中"class"定义的模式有何不同?(更多信息,但仍需澄清)
JavaScript - 动态更改函数的内容(略带匿名函数,分配给变量和属性并更改其上下文)


我第二了。 - The Dembinski

3
你可以将divide创建为一种[类似于] 静态方法:
var addNum = function(num1, num2) {
  addNum.divide = function(){return num1/num2;};
  return num1 + num2;
}
// now you first have to run addNum
var onethirds = addNum(1,3); //=> 4
addNum.divide(); //=> 0.333333...

但这并不可取。最好创建一个构造函数

function Pair(n1,n2){
   n1 = n1 || 1;
   n2 = n2 || 1;
   // create instance methods
   this.add      = function(){return n1+n2;};
   this.divide   = function(){return n1/n2;};
   this.multiply = function(){return n1*n2;}
}
var pair1 = new Pair(2,6)
   ,pair2 = new Pair(1,2);
pair1.add();    //=> 8
pair2.divide(); //=> 0.5
//etc.

或者采用更原型化的方法(将方法添加到构造函数原型中,而不是每个实例中):

function Pair(n1,n2){
   this.n1 = n1 || 1;
   this.n2 = n2 || 1;
   // create prototype methods (once)
   if (!Pair.prototype.add){
    var proto      = Pair.prototype;
    proto.add      = function(){return this.n1+this.n2;};
    proto.divide   = function(){return this.n1/this.n2;};
    proto.multiply = function(){return this.n1*this.n2;}
   }
}

阅读材料


2
不,你的最后一段代码只有在使用addNum作为构造函数时才有意义。
var instance = new addNum();
instance.divide();

然而,由于函数是对象,以下代码也是有效的:
var addNum = function(num1, num2) {
        return num1 + num2;
}
addNum.divide = function() {}

在这种情况下,divide 将是 addNum 本身的属性,而不是其原型之一。

是的,没错。我更新了最后一句话以澄清一下。 - Fabian Schmengler

0
理解原型继承起初可能有些模糊,但可以将其视为名称所示,在JavaScript中有几个原型,其中Function是其中之一。
每当您创建一个新函数时,可以使用typeof命令检查其类型。在您的情况下:
var a = function(a,b) { return a + b; }

它将返回“function”,所以有两种方法可以添加更多方法到变量a中。一种方法是像@Keith Morris建议的那样创建一个新的构造函数,并在内部定义其方法并返回它。这也是首选的方法,因为这样你就不会污染基本对象的原型方法,这些方法被扩展到每个由它们表示的对象。

也就是说,如果我改为这样做:

Function.prototype.divide = function(a, b) { return a / b; }

我现在可以执行 a.divide(2, 1); 并返回 2。但是例如,如果我使用 jQuery 并执行 jQuery.divide(2,1),我也会得到 2,因为它试图在函数的直接作用域中查找它。如果没有找到,它就会去原型中找。

希望这能更好地解释给你听。


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