209

JavaScript 一直让我感到惊讶,这是另一个例子。我刚刚遇到了一些我一开始不理解的代码。所以我调试了它并得出了这个发现:

alert('a'['toUpperCase']());  //alerts 'A'

现在,如果将其定义为字符串类型的成员,这一定很明显toUpperCase(),但最初对我来说没有意义。

反正,

  • 这是因为toUpperCase是“a”的成员吗?还是在幕后发生了其他事情?
  • 我正在阅读的代码具有以下功能:

    function callMethod(method) {
        return function (obj) {
            return obj[method](); //**how can I be sure method will always be a member of obj**
        }
    }
    
    var caps2 = map(['a', 'b', 'c'], callMethod('toUpperCase')); // ['A','B','C'] 
    // ignoring details of map() function which essentially calls methods on every 
    // element of the array and forms another array of result and returns it
    

    在任何对象上调用任何方法都是一种通用函数。但这是否意味着指定的方法已经是指定对象的隐式成员?

我确信我对 JavaScript 函数的基本概念缺乏一些认真的理解。请帮助我理解这一点。

4

12 回答 12

294

把它分解。

  • .toUpperCase()是一种方法String.prototype
  • 'a'是一个原始值,但被转换成它的对象表示
  • 我们有两种可能的符号来访问对象属性/方法,点符号和括号符号

所以

'a'['toUpperCase'];

是通过对属性的括号表示法toUpperCase访问,从String.prototype. 由于这个属性引用了一个方法,我们可以通过附加来调用它()

'a'['toUpperCase']();
于 2013-03-27T13:22:49.807 回答
78

foo.bar并且foo['bar']相等,因此您发布的代码与

alert('a'.toUpperCase())

使用时foo[bar](注意没有引号),您不使用文字名称bar,而是使用变量bar包含的任何值。因此,使用foo[]符号而不是foo.允许您使用动态属性名称。


让我们看一下callMethod

首先,它返回一个以obj参数为参数的函数。执行该函数时,它将调用method该对象。所以给定的方法只需要存在于obj它自己或它的原型链上的某个地方。

如果toUpperCase该方法来自String.prototype.toUpperCase- 为每个存在的单个字符串拥有该方法的单独副本将是相当愚蠢的。

于 2013-03-27T13:20:16.880 回答
25

.propertyName您可以使用符号或符号访问任何对象的成员["propertyName"]。这就是 JavaScript 语言的特点。要确保该成员在对象中,只需检查它是否已定义:

function callMethod(method) {
    return function (obj) {
        if (typeof(obj[method]) == 'function') //in that case, check if it is a function
           return obj[method](); //and then invoke it
    }
}
于 2013-03-27T13:21:45.977 回答
17

基本上 javascript 将所有内容都视为对象,或者更确切地说,每个对象都可以视为字典/关联数组。并且函数/方法以完全相同的方式为对象定义 - 作为此关联数组中的条目。

因此,本质上,您正在引用/调用(注意 ' () ')'a' 对象(在本例中为字符串类型)的 'toUpperCase' 属性。

这是我头顶的一些代码:

function myObject(){
    this.msg = "hey there! ;-)";
    this.woop = function(){
        alert(this.msg); //do whatever with member data
    }
}

var obj = new myObject();
alert( obj.msg );
alert( obj['msg'] );
obj['woop']();
于 2013-03-27T13:30:06.640 回答
10

anyObject['anyPropertyName']与没有问题字符anyObject.anyPropertyName时相同。anyPropertyName

请参阅MDN 中的使用对象

toUpperCase方法附加到 String 类型。当您在原始值上调用函数时,here 'a',它会自动提升为对象,这里是String

在原始字符串上调用方法或发生属性查找的上下文中,JavaScript 将自动包装字符串原始并调用方法或执行属性查找。

您可以通过 logging 看到该功能是否存在String.prototype.toUpperCase

于 2013-03-27T13:21:31.133 回答
9

所以在 Javascript 中,objectsobjects. 那就是他们具有这种性质{}。可以使用以下任一设置对象属性:a.greeting = 'hello';a['greeting'] = 'hello';。两种方式都有效。

检索工作相同。a.greeting(不带引号)是'hello'a['greeting']'hello'。例外:如果属性是数字,则只有括号方法有效。点方法没有。

'a'具有属性的对象也是如此'toUpperCase',它实际上是一个函数。您可以检索该函数并随后以任何一种方式调用它:'a'.toUpperCase()'a'['toUpperCase']().

但是 imo 编写 map 函数的更好方法是

var caps = ['a','b','c'].map( function(char) { return char.toUpperCase(); } )

Who need the callMethodfunction then?

于 2013-03-27T16:03:56.793 回答
6

每个 JavaScript 对象都是一个哈希表,因此您可以通过指定一个键来访问它的成员。例如,如果一个变量是一个字符串,那么它应该有 toUpperCase 函数。所以,你可以通过

var str = "a"
str['toUpperCase'](). // you get the method by its name as a key and invoke it.

所以,通过内联 str,你可以有以下

"a"["toUpperCase"]()
于 2013-03-27T13:23:19.100 回答
6

toUpperCase 是一个标准的 javascript 方法:https ://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toUpperCase

它工作的原因'a'['toUpperCase']()是 toUpperCase 函数是字符串对象的一个​​属性'a'object[property]您可以使用或引用对象的属性object.property。语法 'a''toUpperCase' 表示您正在引用 'a' 字符串对象的 'toUppercase' 属性,然后调用它 ()。

于 2013-03-27T13:24:55.260 回答
4

javascript 中的几乎所有内容都可以视为对象。在您的情况下,字母表本身充当字符串对象,toUpperCase可以作为其方法调用。方括号只是访问对象属性的另一种方式,因为它是一种方法,因此在 forms旁边需要toUpperCasesimplebracket 。()['toUpperCase']['toUpperCase']()

'a'['toUpperCase']()相当于'a'.toUpperCase()

'a'['toUpperCase']() // returns A
'a'.toUpperCase() // returns A
于 2013-03-28T10:07:27.377 回答
4

但这是否意味着指定的方法已经是指定对象的隐式成员?

不,有人可以传入一个对象

  1. 没有名为 的属性toUpperCase;或者
  2. 有一个名为toUpperCase不是函数的属性

在第一种情况下,会抛出错误,因为访问不存在的属性会返回undefined,并且我们不能undefined作为函数调用。

在第二种情况下,将抛出错误,因为我们不能将非函数作为函数调用。

请记住,JavaScript 是一种类型非常松散的语言。除非必须这样做,否则很少或根本不会进行类型检查。您显示的代码在某些情况下有效,因为在这些情况下,传递的对象有一个名为 的属性toUpperCase,即一个函数。

可以说,不能保证参数具有正确类型的属性这一事实obj根本不会困扰 JavaScript。它采取“观望”的态度,直到运行时出现实际问题才抛出错误。

于 2013-03-27T21:34:10.557 回答
3

这里要注意的重要一点是,由于 Javascript 是一种动态语言,所以每个对象本质上只是一个美化的哈希映射除了少数例外)。Javascript 对象中的所有内容都可以通过两种方式访问​​——括号表示法和点表示法。

我将快速回顾回答您问题第一部分的两个符号,然后我将进入第二部分。

括号符号

这种模式更类似于在其他编程语言中访问哈希图和数组。您可以使用此语法访问任何组件(数据(包括其他对象)或函数)。

这正是您在示例中所做的。你有'a',它是一个字符串(而不是字符文字,就像它在诸如 C++ 之类的语言中一样)。

使用括号表示法,您可以访问其toUpperCase方法。但是访问它仍然不够;例如,在 Javascript 中简单地键入alert不会调用该方法。这只是一个简单的陈述。为了调用该函数,您需要添加括号:alert()显示一个包含 的简单对话框undefined,因为它没有接收任何参数。我们现在可以使用这些知识来破译您的代码,它变成:

alert('a'.toUpperCase());

这更具可读性。

实际上,更好地理解这一点的一个好方法是执行以下 Javascript:

alert(alert)

alert通过将函数对象传递给它来调用,同样alert,也不执行第二个警报。显示的内容(至少在 Chrome 26 中)如下:

function alert() { [native code] } 

来电:

alert(alert())

显示两个连续的消息框,其中包含undefined. 这很容易解释:内部alert()首先执行,显示undefined(因为它没有任何参数)并且什么都不返回。外部警报接收内部警报的返回值 - 什么都没有,并且还显示undefined在消息框中。

尝试 jsFiddle 上的所有案例!

点符号

这是更标准的方法,它允许使用点 ( .) 运算符访问对象的成员。这是您的代码在点表示法中的样子:

alert('a'.toUpperCase())

更具可读性。那么什么时候应该使用点符号,什么时候应该使用括号符号呢?

比较

这两种方法的主要区别在于语义。还有一些其他细节,但我会在稍后讨论。最重要的是您实际想要做的事情 - 经验法则是您使用点表示法表示对象具有的成熟字段和方法,而括号表示法表示您实际将对象用作哈希映射.

在您的示例中可以显示为什么此规则如此重要的一个很好的示例 - 因为代码在点表示法更明智的地方使用括号表示法,它使代码更难阅读。这是一件坏事,因为代码的读取次数比编写次数多得多

在某些情况下,即使使用点表示法更明智,您也必须使用方括号表示法:

  • 如果对象的成员的名称包含一个或多个空格或任何其他特殊字符,则不能使用点表示法:foo.some method()不起作用,但foo["some method"]()可以;

  • 如果你需要动态访问一个对象的成员,你也会被括号符号卡住;

例子:

for(var i = 0; i < 10; ++i) {
   foo["method" + i](); 
 }

最重要的是,在将对象用作哈希映射 () 时应使用括号语法,在使用foods["burger"].eat()“实际”字段和方法时应使用点语法 ( enemy.kill())。由于 Javascript 是一种动态语言,对象的“实际”字段和方法与存储在其中的“其他”数据之间的界限会变得非常模糊。但只要你不以令人困惑的方式混合它们,你应该没问题。


现在,继续你的问题(最后!:P)。

我怎么能确定方法将永远是 obj 的成员

你不能。试试看。尝试调用derp字符串。您将在以下行中收到错误:

Uncaught TypeError: Object a has no method 'derp' 

在任何对象上调用任何方法都是一种通用函数。但这是否意味着指定的方法已经是指定对象的隐式成员?

是的,在你的情况下,它必须是。否则,您最终会遇到我上面提到的错误。但是,您不必函数return obj[method]();中使用。callMethod()您可以添加自己的功能,然后由地图功能使用。这是一个硬编码的方法,可以将所有字母变成大写字母:

function makeCap()
{
    return function(obj) {
        return obj.toUpperCase();
    }
}

var caps2 = map(['a', 'b', 'c'], makeCap()); // ['A','B','C'] 
console.log(caps2)

您链接到的教程中的代码使用部分函数。它们本身就是一个棘手的概念。阅读更多关于该主题的内容应该有助于使事情变得比我所能做的更清楚。


注意:这是问题中代码使用的地图功能的代码,来源here

function map(arr, iterator) {
  var narr = [];
  for (var i = 0; i < arr.length; i++) narr.push(iterator(arr[i], i));
  return narr;
}
于 2013-04-05T13:50:42.037 回答
2

如果你问它实际上是如何工作的,我就是这么读的。好的,这是一个简单的数学函数。要理解它,您需要查看 ascii 表。它为每个字母分配一个数值。为了掩饰它,竞争者只需使用逻辑语句来掩饰,例如 If(ChcrValue > 80 && charValue < 106) //这是一组小写字母 then charValue = charValue - 38; // 下集和上集之间的距离

就这么简单,我实际上并没有费心查找正确的值,但这基本上是将所有小写字母转换为大写值。

于 2013-04-02T20:35:08.697 回答