• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

4.4 对象方法,”this“

JS 教程 tiantian 204次浏览 0个评论 扫描二维码

对象经常被创建用来代表现实世界中的实体,比如用户,订单等等:

let user = {
  name: "John",
  age: 30
};

而且,在现实世界中,一个用户可以具有行为:从购物车中选择一个商品,登录,登出等等。

在 JavaScript 中,行为通常作为函数类型的对象属性而存在。

方法示例

首先,让我们使得user可以简单地问候:

let user = {
  name: "John",
  age: 30
};

user.sayHi = function() {
  alert("Hello!");
};

user.sayHi(); // Hello!

此处,我们使用了一个 函数表达式 来创建一个函数,并且将该函数赋值给 user 对象的 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是一个非常大的概念,具有它自己的一套理论。如何选择合适和正确的实体?如何组织它们之间的关系?那是结构方面的考量,而且在这方面存在大量的优秀图书,比如设计模式:可重用的面向对象软件开发技术,由 E.Gamma, R.Helm, R.Johnson, J.Vissides 编写,再比如应用的面向对象分析与设计,由 G.Booch 编写。当然,还有许多其他的好资料,我们将在 对象,类,继承 章节继续深入学习面向对象编程的知识。

方法速记

存在一个创建对象方法的简便语言:

// these objects do the same

let user = {
  sayHi: function() {
    alert("Hello");
  }
};

// method shorthand looks better, right?
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关键字同样可以访问对象,可以使用外部变量的方式来引用该对象:

let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert(user.name); // "user" instead of "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!

如果我们在alert方法中使用this.name代替上述的user.name,那么上述的代码就可以正常工作。

“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,我们将在后面的教程中学习它)。这是由于历史原因造成的错误,使用严格模式可以修正该错误。

请注意,不使用对象调用含有this的函数通常而言是一种错误的做法,但是并不是一个语法错误。如果一个函数具有this,那么该函数意味着需要在一个对象的上下文中被调用。

不绑定”this”关键字的后果

如果你曾经学习过其他的编程语言,那么你可能已经熟悉了”this”关键字绑定对象的理念:定义于对象内部的方法中的this关键字总是绑定到了该对象。

在 JavaScript 中,this关键字是自由的,它的值是在运行时才被计算,而且完全不依赖于该方法的定义位置,而是取决于“位于点运算符.之前”的那个对象。

这种在运行时才计算this关键字的值的理念有优点,也有缺点。一方面,一个函数可以被多个不同的对象重用。另一方面,更多的扩展性意味着更容易出错。

此处,我们无意讨论 JavaScript 语言中的此种设计this的方式是否合理。我们需要做的是理解如何使用this关键字,如何利用其优点,并且避免错误用法。

深入内部:Reference Type

深入语言特性

这个部分覆盖了一个高级的主题,掌握后可以更好地理解多个边界情况。

如果你希望快速学习本脚本,你也可以跳过本部分或者延期再学习。

一个错综复杂的方法调用可能丢失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.hiuser.bye之间进行选择。在上述示例中,最后的结果是user.hi

那个结果方法user.hi立即使用小括号来进行调用。但是,它并不能正常工作。

你可以看到,那个方法调用结果是一个错误,原因是因为调用的函数内部的this的值是undefined

我们知道,下面的代码(对象.方法)是可以正常工作的:

user.hi();

而,这样的代码(运算后的方法)却不能正常工作:

(user.name == "John" ? user.hi : user.bye)(); // Error!

为什么呢?如果我们想要理解它们背后的区别和原因,我们就需要深入底层,了解obj.method()背后的工作原理。

仔细查看,我们可以发现obj.method()中存在两个操作语句:

  1. 首先,那个点运算符.获取那个obj.method属性。
  2. 然后,那个小括号运算符()执行该属性。

那么,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 内部使用了一个技巧——那个点运算符.返回的不是一个函数,而是一个特殊类型Reference Type 的值。

那个 Reference Type 是一个“特殊的类型”。我们开发者不能明确地使用该类型,它仅仅被 JavaScript 语言在内部使用。

那个 Referenc Type 的值是一个三个值组合的形式:(base, name, strict),其中:

  • base 是那个对象。
  • name 是那个属性。
  • strict 在严格模式下为 true

一个点运算符.的属性访问user.hi的结果不是一个函数,而是一个 Reference Type 类型的值。对于严格模式下的 user.hi 而言,其值是:

// Reference Type value
(user, "hi", true)

当小括号()调用位于 Reference Type 的后面时,它就可以接受到关于对象和方法的全部信息,也就可以设置正确的this值,在此示例中是 =user

另一个操作,比如赋值操作hi = user.hi,会丢失掉 Reference Type 作为一个整体的信息,仅仅获取了 user.hi的值(一个函数),并将它传递下去。所以,任何的后期操作都无法获得正确的this值。

所以,结论很明显:this的值只在函数是直接使用点运算符.调用obj.method()或者采用方括号[]调用语法obj[method]()的情况下才能正确的被传递,它们在此处具有相同的功效。

箭头函数没有”this”

箭头函数非常特殊:它们没有它们自己的this。如果我们在箭头函数中引用一个this,它的值会从外部的正常函数中获取。

举个例子,此处的arrow()使用的this来自于外部的user.sayHi()方法:

let user = {
  firstName: "Ilya",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // Ilya

这个箭头函数的一个特殊性质,这在我们不希望函数拥有独立的this时,而是从其外部上下中获得其值时非常实用。稍后,我们将在深入学习箭头函数章节中进行深入学习箭头函数的特性。

总结

  • 作为属性,存储于对象内部的函数被称为方法。
  • 方法使得对象具有行为,比如object.doSomething()
  • 方法可以通过this关键字引用对象。

this关键字的值在运行时被计算。

  • 当一个函数被定义时,它可能使用this,但是此时的this并没有任何值,直到该函数被调用.
  • 那些函数可以在对象之间进行复制。
  • 当函数以方法的语法被调用时:object.method(),此调用期间的this的值就是object

请注意:箭头函数在this关键字方面的特殊性:它们没有this关键字。当在一个箭头函数的内部使用this时,其值来源于箭头函数的外部。

任务


语法检查

下来代码的执行结果是什么?

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)()

解释”this”的值

在下列的代码中,我们4次调用user.go()方法。

但是,调用(1)(2)(3)(4)的执行结果却不一样,为什么?

let obj, method;

obj = {
  go: function() { alert(this); }
};

obj.go();               // (1) [object Object]

(obj.go)();             // (2) [object Object]

(method = obj.go)();    // (3) undefined

(obj.go || obj.stop)(); // (4) undefined

在对象字面量中使用”this”

此处有一个makeUser函数可以返回一个对象。

下列代码的结果是什么?为什么?

function makeUser() {
  return {
    name: "John",
    ref: this
  };
};

let user = makeUser();

alert( user.ref.name ); // What's the result?

创建一个计算器

创建一个计算器对象calculator,它具有三个方法:

  • read() 提示用户输入两个值,并将它们保存为对象的属性。
  • sum() 返回已保存属性的累加结果。
  • mul() 返回已保存属性的相称结果。
let calculator = {
  // ... your code ...
};

calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );

链式调用

存在一个ladder对象,允许上升和下降操作:

let ladder = {
  step: 0,
  up() {
    this.step++;
  },
  down() {
    this.step--;
  },
  showStep: function() { // shows the current step
    alert( this.step );
  }
};

现在,加入我们需要执行几个连续的操作,我们可以如下操作:

ladder.up();
ladder.up();
ladder.down();
ladder.showStep(); // 1

请修改上述的 ladder 对象,使得可以对其进行链式调用,比如:

ladder.up().up().down().showStep(); // 1

实际上,这样的一个链式调用语法在很多的 JavaScript 函数库中存在。


天天编码 , 版权所有丨本文标题:4.4 对象方法,”this“
转载请保留页面地址:http://www.tiantianbianma.com/object-method-this.html/
喜欢 (1)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址