原文链接:http://javascript.info/object-methods,translate with ❤️ by zhangbao.
对象通常来用表示现实世界里的实体,像用户或者订单等:
let user = {
name: "John",
age: 30
};
而且,在现实世界里,用户可以行动:从购物车里选东西,登录,注销等。
在 JavaScript 中,行为是通过函数类型的属性体现的。
方法例子
开始,我们实现 user 说 say hi:
let user = {
name: "John",
age: 30
};
user.sayHi = function() {
alert("Hello!");
};
user.sayHi(); // Hello!
这里我们使用一个函数表达式来创建函数,并且将它赋值给对象 user.sayHi 属性。
然后我们调用它,用户就可以说话了!
作为对象属性的函数称为方法。
因此,我们可以说 user 上有一个方法 sayHi。
当然,我们也可以直接使用预定义函数作为方法使用,像这样:
let user = {
// ...
};
// first, declare
function sayHi() {
alert("Hello!");
};
// then add as a method
user.sayHi = sayHi;
user.sayHi(); // Hello!
⚠️面向对象编程
在代码里,如果我们用对象表示实体,就称为面向对象编程,简称“OOP”。
OOP是一件大事,它本身就是一门有趣的科学。如何选择正确的实体?如何组织它们之间的交互?这就是架构,有很多关于这个主题的书,比如“设计模式:可重用的面向对象软件的元素”by E.Gamma, R.Helm, R.Johnson, J.Vissides 或“应用程序的面向对象分析和设计”by G.Booch 等等。我们会在后面的章节《对象,类,继承》中简单提到这个话题。
方法简写
在对象字面量里可以使用方法的简写形式:
// 这些对象是同一个意思
let user = {
sayHi: function() {
alert("Hello");
}
};
// 方法的简写形式,这样更好,对吧
let user = {
sayHi() { // same as "sayHi: function()"
alert("Hello");
}
};
根据上面的写法,我们可以忽略“function”并且仅写作 sayHi()。
说实话,这些符号并不是完全相同的。与对象继承相关的细微差别(稍后将介绍),但现在它们并不重要。在几乎所有情况下,较短的语法都是首选。
方法中的“this”
很常见的是,对象方法需要访问储存在对象中的信息来完成它的工作。
例如,user.sayHi() 中的代码可能需要 user 的名字。
在方法里访问对象,可以使用 this 关键字。
this 的值是“点之前”的对象,也就是调用的主体。
例如:
let user = {
name: "John",
age: 30,
sayHi() {
alert(this.name);
}
};
user.sayHi(); // John
在 user.sayHi() 执行时,内部的 this 指的是 user。
从技术上将,也可以不用 this,直接通过外部变量来引用也 OK。
let user = {
name: "John",
age: 30,
sayHi() {
alert(user.name); // 用 "user" 而不是 "this"
}
};
但是这样的代码并不可靠,如果我们将 user 复制到另一个变量中,例如,admin = user,并且重写了 user 里面的一些东西。然后我们可能访问了错误的对象。
用下面的代码表示:
let user = {
name: "John",
age: 30,
sayHi() {
alert( user.name ); // leads to an error
}
};
let admin = user;
user = null; // overwrite to make things obvious
admin.sayHi(); // Whoops! inside sayHi(), the old name is used! error!
如果我们使用的 this.name 而不是 user.name 在 alert 里,那代码就能顺利执行了。
“this”没有绑定
在 JavaScript 中,“this”关键字不像其他的编程语言。首先,它可以在任何函数里使用:
下面的代码里不会出现语法错误:
function sayHi() {
alert( this.name );
}
this 值是在代码执行时确定的。可以是任何东西。
例如,相同的函数在执行时可能会有不同的“this”值,因为是在不同对象里调用的:
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
// use the same functions in two objects
user.f = sayHi;
admin.f = sayHi;
// these calls have different this
// "this" inside the function is the object "before the dot"
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
admin['f'](); // Admin (dot or square brackets access the method – doesn't matter)
实际上,我们也可以不再任何对象里调用这个方法。
function sayHi() {
alert(this);
}
sayHi(); // undefined
在这种情形下,严格模式里 this 是指 undefined。如果我们尝试访问 this.name,会报错。
在非严格模式下(如果不使用 use strict),this 指向的是全局对象(浏览器里的 window,滞后接触)。这是一个历史遗留问题,”use strict” 修复了它。
请注意,通常在没有对象的情况下在函数中使用 this 的调用不是正常的,而是一个编程错误。如果一个函数有这个,那么它通常是在一个对象的上下文中调用的。
⚠️不绑定 this 的后果
如果您来自另一种编程语言,那么您可能已经习惯了“绑定 this”的概念,在这个概念中,对象中定义的方法总是引用该对象。
在JavaScript中,this 是“自由的”,它的值是在调用时决定的,并不依赖于方法声明的位置,而是取决于“点之前”的对象是什么。
运行时决定 this 的概念有优点也有缺点。一方面,一个函数可以为不同的对象重用。另一方面,更大的灵活性为错误开了一扇窗。
在这里,我们的立场不是判断这个语言设计的决定是好是坏。我们将了解如何使用它,如何获得好处和逃避问题。
内部:引用类型
⚠️深入语言特性
这部分讨论的是高级内容,最好知道一些边缘情况。
如果你想快一点,可跳过或者之后再来看。
一种复杂的调用方法的方式,会导致 this 丢失,例如:
let user = {
name: "John",
hi() { alert(this.name); },
bye() { alert("Bye"); }
};
user.hi(); // John (the simple call works)
// now let's call user.hi or user.bye depending on the name
(user.name == "John" ? user.hi : user.bye)(); // Error!
在代码最后一行,有一个三目运算符选择是调用 user.hi 还是 user.bye。在这个例子里,结果是 user.hi。
然后方法立即用括号 () 进行调用。但是失败了!
可以看到调用出错了,调用方法里的 this 变成 undefiend 了。
这样是可以的(对象点方法):
user.hi();
这样就不行了(求得调用到的方法):
(user.name == "John" ? user.hi : user.bye)(); // Error!
为什么?如果我们想知道为啥会这样,就得理解 obj.method() 的调用过程。
拉近看,我们可以看到在 obj.method() 语句中有两个操作:
首先,点 . 返回属性值 obj.method。
圆括号 () 调用了它。
所以从第一部分跑到第二部分,this 的信息发生怎样的变化了呢?
如果我们将这步算法分开来看的话,this 肯定会丢失无疑了:
let user = {
name: "John",
hi() { alert(this.name); }
}
// split getting and calling the method in two lines
let hi = user.hi;
hi(); // Error, because this is undefined
这里 hi = user.hi 将函数放进了一个变量里,在下一行它就是完成独立的了,所以这里是没有 this 的了。
为了使 user.hi() 调用生效,JavaScript 使用了一个技巧——点 . 返回的不是一个函数,而是一个特殊的 引用类型 值。
引用类型是一个“特殊的类型”,我们不能精确使用它,但是被语言内部使用。
引用类型的值,是靠三个值组成的 (base, name, strict):
base 是指对象。
name 是指属性。
strict 在使用 use strict 的情况下为 true。
访问 user.hi 的结果不是一个函数,而是一个引用类型值。对在严格模式下调用 user.hi 来说是这样的:
// Reference Type value
(user, "hi", true)
当在引用类型上调用圆括号 () 时,它会返回对象和它的方法的完整信息,并且可以设置正确的 this 值(=user 在这里)。
任何其他像 hi = user.hi 这种方式赋值表达式都会整个丢失引用类型,会将 user.hi(一个函数)的值传递过去,因此进行更进一步就丢失了 this。
因此,如果这个函数是直接用点 obj.method() 直接调用或者使用 objmethod 的语法,那么它的值就会被正确地传递下去。在本教程的后面,我们将学习各种方法来解决这个问题,例如 func.bind()。
箭头函数里没有“this”
箭头函数比较特别,它是没有“自己”的 this 的。如果在箭头函数里使用了 this,那么它会从外部“正常”函数里获取。
比如,这里的 arrow() 中使用的 this 就是从外部方法 user.sauHi() 方法里获取的:
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // Ilya
这是箭头函数的一个特殊特征,当我们不想要一个单独的函数时,它是有用的,而不是从外部环境中取它。在后面的《重访箭头函数》章节中,我们将更深入地讨论箭头函数。
总结
作为对象属性存储的函数称为“方法”。
方法允许对象以 obj.doSomething() 的形式调用。
方法里可以使用 this 来引用对象。
this 的值是在运行时决定的。
当声明一个函数后,就可以使用 this 了,但 this 是直到函数调用时才有的。
函数可以在对象之间复制。
当一个函数以“方法”语法的形式调用的:object.method(),调用期间 this 的值就是指向 object 的了。
需要注意的是,箭头函数有些特别:它没有 this,当在箭头函数里访问 this 时,会从外部获取的。
(完)