2022 年 5 月 22 日

对象方法,“this”

对象通常用于表示现实世界中的实体,例如用户、订单等

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

在现实世界中,用户可以执行操作:从购物车中选择商品、登录、注销等。

在 JavaScript 中,操作由属性中的函数表示。

方法示例

首先,让我们教user打招呼

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

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

user.sayHi(); // Hello!

这里我们刚刚使用函数表达式创建了一个函数,并将其分配给对象的属性user.sayHi

然后我们可以将其称为 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

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

// method shorthand looks better, right?
user = {
  sayHi() { // same as "sayHi: function(){...}"
    alert("Hello");
  }
};

如所示,我们可以省略 "function",仅编写 sayHi()

说实话,这些符号并不完全相同。与对象继承相关的细微差别(稍后会介绍),但现在它们并不重要。在几乎所有情况下,都首选较短的语法。

方法中的“this”

对象方法通常需要访问存储在对象中的信息才能完成其工作。

例如,user.sayHi() 中的代码可能需要 user 的名称。

要访问对象,方法可以使用 this 关键字。

this 的值是“点之前”的对象,用于调用该方法的对象。

例如

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

  sayHi() {
    // "this" is the "current object"
    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(); // TypeError: Cannot read property 'name' of null

如果我们在 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 function 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)

规则很简单:如果调用 obj.f(),则在调用 f 期间 thisobj。因此,在上面的示例中,它要么是 user,要么是 admin

不带对象调用:this == undefined

我们甚至可以在没有对象的情况下调用函数

function sayHi() {
  alert(this);
}

sayHi(); // undefined

在这种情况下,在严格模式中 thisundefined。如果我们尝试访问 this.name,将出现错误。

在非严格模式中,在这种情况下 this 的值将是全局对象(浏览器中的 window,我们将在本章后面 全局对象 中介绍)。这是一个历史行为,"use strict" 修复了此行为。

通常,此类调用是编程错误。如果函数内部有 this,则它期望在对象上下文中调用。

未绑定 this 的后果

如果您来自另一种编程语言,那么您可能习惯于“绑定 this”的概念,其中在对象中定义的方法始终具有引用该对象的 this

在 JavaScript 中,this 是“自由”的,其值在调用时进行评估,并且不取决于方法声明的位置,而是取决于“点之前”的对象。

在运行时评估 this 的概念既有优点也有缺点。一方面,一个函数可以被不同的对象重用。另一方面,更大的灵活性为错误创造了更多可能性。

我们的立场不是判断这种语言设计决策的好坏。我们将了解如何使用它,如何从中受益以及如何避免问题。

箭头函数没有“this”

箭头函数很特殊:它们没有“自己的”this。如果我们从这样的函数中引用 this,它将从外部“普通”函数中获取。

例如,这里 arrow() 使用外部 user.sayHi() 方法中的 this

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 时,它取自外部。

任务

重要性:5

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

访问其 ref 的结果是什么?为什么?

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

let user = makeUser();

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

答案:一个错误。

尝试一下

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

let user = makeUser();

alert( user.ref.name ); // Error: Cannot read property 'name' of undefined

这是因为设置 this 的规则不会查看对象定义。只有调用时刻很重要。

此处 makeUser()this 的值是 undefined,因为它作为函数调用,而不是作为带有“点”语法的函数调用。

this 的值对整个函数来说是唯一的,代码块和对象字面量不会影响它。

因此,ref: this 实际上取函数当前的 this

我们可以重写函数,并用 undefined 值返回相同的 this

function makeUser(){
  return this; // this time there's no object literal
}

alert( makeUser().name ); // Error: Cannot read property 'name' of undefined

如你所见,alert( makeUser().name ) 的结果与前一个示例中 alert( user.ref.name ) 的结果相同。

以下是相反的情况

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

let user = makeUser();

alert( user.ref().name ); // John

现在它可以工作,因为 user.ref() 是一个方法。并且 this 的值在点 . 之前设置为对象。

重要性:5

创建一个具有三个方法的对象 calculator

  • read() 提示输入两个值,并将它们分别保存为具有名称 ab 的对象属性。
  • sum() 返回已保存值的总和。
  • mul() 乘以已保存的值并返回结果。
let calculator = {
  // ... your code ...
};

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

运行演示

打开一个带有测试的沙盒。

let calculator = {
  sum() {
    return this.a + this.b;
  },

  mul() {
    return this.a * this.b;
  },

  read() {
    this.a = +prompt('a?', 0);
    this.b = +prompt('b?', 0);
  }
};

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

在沙盒中打开带有测试的解决方案。

重要性:2

有一个 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.down();
ladder.showStep(); // 0

修改 updownshowStep 的代码,以使调用可链接,如下所示

ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0

这种方法在 JavaScript 库中广泛使用。

打开一个带有测试的沙盒。

解决办法是从每次调用中返回对象本身。

let ladder = {
  step: 0,
  up() {
    this.step++;
    return this;
  },
  down() {
    this.step--;
    return this;
  },
  showStep() {
    alert( this.step );
    return this;
  }
};

ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0

我们还可以每行写一个调用。对于长链来说,这样更易读

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

在沙盒中打开带有测试的解决方案。

教程地图

评论

在评论之前先阅读此内容…
  • 如果您有改进建议,请 提交 GitHub 问题 或提交拉取请求,而不是发表评论。
  • 如果您无法理解文章中的某些内容,请详细说明。
  • 要插入一些代码,请使用 <code> 标记,对于多行代码,请用 <pre> 标记将其包装起来,对于 10 行以上的代码,请使用沙盒 (plnkrjsbincodepen…)