为什么将变量(在对象作用域之外)设置为等于对象方法返回未定义的结果?

Why does setting a variable (outside of the object scope) equal to an object method return an undefined result?

本文关键字:对象 于对象 方法 返回 未定义 结果 设置 变量 作用域 为什么      更新时间:2023-09-26

下面的代码是用JavaScript从面向对象的MDN页面上复制粘贴的。我研究了关于JavaScript的OOP的一般问题。然而,我是一个初学者,对这个代码片段有以下问题:

  1. Person.prototype.gender = '';的目的是什么?如果我把它拿出来并运行代码,我得到相同的结果。

  2. 为什么调用genderTeller()会导致"未定义"警报?从初学者的角度来看,MDN的解释似乎有点单薄。这是范围问题吗?

function Person(gender) {
    this.gender = gender;
}
Person.prototype.gender = '';
Person.prototype.sayGender = function () {
    alert(this.gender);
};
var person1 = new Person('Male');
var genderTeller = person1.sayGender;
person1.sayGender(); // alerts 'Male'
genderTeller(); // alerts undefined
alert(genderTeller === person1.sayGender); // alerts true
alert(genderTeller === Person.prototype.sayGender); // alerts true

Person.prototype.gender = '';的目的是什么?如果我将其取出并运行代码,我将得到相同的结果。

这似乎为gender属性建立了一个默认值。使用它,即使在不调用构造函数的情况下创建实例,该属性仍然是设置的:

var person2 = Object.create(Person.prototype);
console.log(person2.gender); // ""

在创建子类型时可能很有用:

function Employee() {}
Employee.prototype = Object.create(Person.prototype);
console.log(new Employee().gender); // ""

为什么调用genderTeller()会导致"未定义"警报?

MDN关于this的文档应该进一步解释,特别是关于"函数上下文"的部分。但是,this的值是在调用function时确定的,而不是在何时何地定义的。

通过将person1.sayGender赋值给genderTeller,它将与person1分离。因此,它不再是一个特定对象的"方法"。

相反,它被作为一个常规函数调用,this的值被默认为全局对象,在浏览器中是window

window.gender = 'n/a';
var genderTeller = person1.sayGender;
genderTeller(); // logs: 'n/a'

对于问题1,行

Person.prototype.gender = '';

给gender属性一个默认值,这样即使在创建新的Person对象时没有给它赋其他值,它也有一个默认值不是undefined''undefined之间的区别有时很重要,有时不重要,但它们是有区别的。


对于问题2,当你这样做时:

var person1 = new Person('Male');
var genderTeller = person1.sayGender;
genderTeller(); // alerts undefined

变量genderTeller包含一个函数指针,并且只有一个函数指针。它与person1对象没有关联。当你把它作为一个函数调用时,函数内部的this指针将被设置为全局对象(在浏览器中是window)或undefined(如果在严格模式下运行),因此,因为this不是Person对象,this.gender将不包含适当的值,而可能是undefined


在这个错误中有一个非常重要的教训。在javascript中,this指针是根据调用方法的方式来设置的。当你调用genderTeller()时,你只是调用一个函数,因此与任何特定的Person对象没有任何关联。this指针不会指向Person对象,因此任何对this.xxx的引用都假定它是Person对象将不起作用。

当你调用person1.sayGender()时,这是调用同一个函数的一种非常不同的方式。因为你是通过对象引用来调用它的,javascript会将this指针设置为person1对象,并且它会工作。


这两种情况之间的区别是微妙的,但在javascript中非常重要,所以我将尝试更多地解释。

创建person1对象后,它是一个包含名为sayGender的属性的对象。该属性包含一个指向sayGender操作的函数的值。sayGender只是一个包含在属性中的函数(严格来说是在person1对象的原型链中,但你可以把它看作是该对象的属性)。

当你这样做的时候:

var genderTeller = person1.sayGender;

你正在创建一个名为genderTeller的新变量,现在它也持有指向同一函数的指针。但是,就像在sayGender属性中一样,它只是一个函数。它对任何对象都没有固有的绑定。它只有在通过对象引用调用时才会绑定到对象(或者使用.call().apply()强制对象引用,但这超出了您在这里所做的)。当你调用

genderTeller()

你只是调用一个函数,它没有对象引用,因此当函数运行时,this不会指向Person对象。


正如我上面提到的,可以强制对象引用。例如,您可以这样做:

var genderTeller = person1.sayGender;
genderTeller.call(person1);
// .apply() only differs from .call() in how 
// arguments are passed to the function
// since you have no arguments to sayGender() it looks the same as .call()
var genderTeller = person1.sayGender;
genderTeller.apply(person1);
var genderTeller = person1.sayGender.bind(person1);
genderTeller();

并且,它将再次工作,因为您在调用函数时强制与person1对象关联。

参见MDN的.call().apply().bind()的参考,如果你想要更多的信息,但你通常不需要使用那些只是为了调用对象上的方法,因为只是用obj.method()的形式调用它创建对象关联,并导致this指针为你设置适当。

我浏览了这篇文章,并被这对于不熟悉javascript的人来说是多么令人困惑所震惊。

关于#1,我相信这是对JIT编译器的一个提示,"gender"是一个字符串。Javascript JIT编译器喜欢对象属性保持相同类型(在本例中为String)。这篇文章没有描述这一点,或者这条线根本就在那里,这是愚蠢的。也许这是为了证明你可以在"new Person"实例中重写原型属性

关于#2,当你调用object.method()时,会自动调用方法,其中堆栈将"object"填充为"this"。但如果你做var detachedmethod = cuobject。方法,然后调用detachedmethod(),它不会调用绑定为curobject的"this"(相反,在方法体中,this === undefined)。或者"窗口",我不确定:-))

总而言之,这是一堆挑剔的东西,对于javascript的日常使用来说不是很重要,并且可以随着你的前进而拾取。

正确答案如下。

1)原型定义不是Person的默认值。事实上,如果您创建另一个没有性别的Person,您将看到它仍然是未定义的。

var person2 = new Person();
person2.sayGender;  // alerts undefined
这里的重点是要说明构造函数定义覆盖了原型定义。

2)调用genderTeller()会导致未定义,因为genderTeller是一个全局函数,恰好与从person1复制的函数定义相同。sayGender方法。genderTeller与window.genderTeller相同。

因此,当您执行genderTeller()时,'this' = window。因为window没有'gender'属性,你得到一个undefined。你可以通过下面的代码

看到它
genderTeller();  // returns undefined
gender = 'hi there';
genderTeller();  // returns 'hi there'

希望有帮助。这是一个普朗克。http://plnkr.co/edit/wwc2vYIvH9QdFYStesVW