对象方法和方法中的 "this"

简介: 对象方法和方法中的 "this"

640.png

对象方法,"this"


通常创建对象来表示真实世界中的实体,如用户和订单等:


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


并且,在现实世界中,用户可以进行 操作:从购物车中挑选某物、登录和注销等。

在 JavaScript 中,行为(action)由属性中的函数来表示。


方法示例


刚开始,我们来教 user 说 hello:


let user = {
  name: "John",
  age: 30
};
user.sayHi = function() {
  alert("Hello!");
};
user.sayHi(); // Hello!


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

随后我们像这样 user.sayHi() 调用它。用户现在可以说话了!

作为对象属性的函数被称为 方法


所以,在这我们得到了 user 对象的 sayHi 方法。

当然,我们也可以使用预先声明的函数作为方法,就像这样:


let user = {
  // ...
};
// 首先,声明函数
function sayHi() {
  alert("Hello!");
};
// 然后将其作为一个方法添加
user.sayHi = sayHi;
user.sayHi(); // Hello!


面向对象编程:

当我们在代码中用对象表示实体时,就是所谓的 面向对象编程[1],简称为 "OOP"。

OOP 是一门大学问,本身就是一门有趣的科学。怎样选择合适的实体?如何组织它们之间的交互?这就是架构,有很多关于这方面的书,例如 E. Gamma、R. Helm、R. Johnson 和 J. Vissides 所著的《设计模式:可复用面向对象软件的基础》,G. Booch 所著的《面向对象分析与设计》等。


方法简写


在对象字面量中,有一种更短的(声明)方法的语法:


// 这些对象作用一样
user = {
  sayHi: function() {
    alert("Hello");
  }
};
// 方法简写看起来更好,对吧?
let user = {
  sayHi() { // 与 "sayHi: function()" 一样
    alert("Hello");
  }
};


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

说实话,这种表示法还是有些不同。在对象继承方面有一些细微的差别(稍后将会介绍),但目前它们并不重要。在几乎所有的情况下,较短的语法是首选的。


方法中的 "this"


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

例如,user.sayHi() 中的代码可能需要用到 user 的 name 属性。

为了访问该对象,方法中可以使用 this 关键字。


this 的值就是在点之前的这个对象,即调用该方法的对象。


举个例子:


let user = {
  name: "John",
  age: 30,
  sayHi() {
    // "this" 指的是“当前的对象”
    alert(this.name);
  }
};
user.sayHi(); // John


在这里 user.sayHi() 执行过程中,this 的值是 user

技术上讲,也可以在不使用 this 的情况下,通过外部变量名来引用它:


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 ); // 导致错误
  }
};
let admin = user;
user = null; // 重写让其更明显
admin.sayHi(); // TypeError: Cannot read property 'name' of null


如果我们在 alert 中以 this.name 替换 user.name,那么代码就会正常运行。


"this" 不受限制


在 JavaScript 中,this 关键字与其他大多数编程语言中的不同。JavaScript 中的 this 可以用于任何函数,即使它不是对象的方法。


下面这样的代码没有语法错误:


function sayHi() {
  alert( this.name );
}


this 的值是在代码运行时计算出来的,它取决于代码上下文。

例如,这里相同的函数被分配给两个不同的对象,在调用中有着不同的 "this" 值:


let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
  alert( this.name );
}
// 在两个对象中使用相同的函数
user.f = sayHi;
admin.f = sayHi;
// 这两个调用有不同的 this 值
// 函数内部的 "this" 是“点符号前面”的那个对象
user.f(); // John(this == user)
admin.f(); // Admin(this == admin)
admin['f']( "'f'"); // Admin(使用点符号或方括号语法来访问这个方法,都没有关系。)


这个规则很简单:如果 obj.f() 被调用了,则 thisf 函数调用期间是 obj。所以在上面的例子中 this 先是 user,之后是 admin


在没有对象的情况下调用:this == undefined


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


function sayHi() {
  alert(this);
}
sayHi(); // undefined


在这种情况下,严格模式下的 this 值为 undefined。如果我们尝试访问 this.name,将会报错。

在非严格模式的情况下,this 将会是 全局对象(浏览器中的 window,我们稍后会在 全局对象  一章中学习它)。这是一个历史行为,"use strict" 已经将其修复了。

通常这种调用是程序出错了。如果在一个函数内部有 this,那么通常意味着它是在对象上下文环境中被调用的。


解除 this 绑定的后果


如果你经常使用其他的编程语言,那么你可能已经习惯了“绑定 this”的概念,即在对象中定义的方法总是有指向该对象的 this


在 JavaScript 中,this 是“自由”的,它的值是在调用时计算出来的,它的值并不取决于方法声明的位置,而是取决于在“点符号前”的是什么对象。


在运行时对 this 求值的这个概念既有优点也有缺点。一方面,函数可以被重用于不同的对象。另一方面,更大的灵活性造成了更大的出错的可能。


这里我们的立场并不是要评判编程语言的这个设计是好是坏。而是要了解怎样使用它,如何趋利避害。


箭头函数没有自己的 "this"


箭头函数有些特别:它们没有自己的 this。如果我们在这样的函数中引用 thisthis 值取决于外部“正常的”函数。


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


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


这是箭头函数的一个特性,当我们并不想要一个独立的 this,反而想从外部上下文中获取时,它很有用。在后面的 箭头函数[2] 一章中,我们将深入介绍箭头函数。


总结


  • 存储在对象属性中的函数被称为“方法”。
  • 方法允许对象进行像 object.doSomething() 这样的“操作”。
  • 方法可以将对象引用为 this

this 的值是在程序运行时得到的。

  • 一个函数在声明时,可能就使用了 this,但是这个 this 只有在函数被调用时才会有值。
  • 可以在对象之间复制函数。
  • 以“方法”的语法调用函数时:object.method(),调用过程中的 this 值是 object


请注意箭头函数有些特别:它们没有 this。在箭头函数内部访问到的 this 都是从外部获取的。


作业题


先自己做题目再看答案。


1. 在对象字面量中使用 "this"


重要程度:⭐️⭐️⭐️⭐️⭐️


这里 makeUser 函数返回了一个对象。


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

function makeUser() {
  return {
    name: "John",
    ref: this
  };
}
let user = makeUser();
alert( user.ref.name ); // 结果是什么?


2. 创建一个计算器


重要程度:⭐️⭐️⭐️⭐️⭐️

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

  • read() 提示输入两个值,并将其保存为对象属性。
  • sum() 返回保存的值的和。
  • mul() 将保存的值相乘并返回计算结果。


let calculator = {
  // ……你的代码……
};
calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );


3. 链式(调用)

重要程度:⭐️⭐️

有一个可以上下移动的 ladder 对象:


let ladder = {
  step: 0,
  up() { 
    this.step++;
  },
  down() { 
    this.step--;
  },
  showStep: function() { // 显示当前的 step
    alert( this.step );
  }
};


现在,如果我们要按顺序执行几次调用,可以这样做:


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


修改 updownshowStep 的代码,让调用可以链接,就像这样:


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


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


答案:


在微信公众号「技术漫谈」后台回复 1-4-4 获取作业答案。

目录
相关文章
|
存储 Cloud Native 编译器
C++ 对象生成:构造函数
C++ 对象生成:构造函数
|
7天前
|
开发者
静态方法和实例方法的区别是什么?
静态方法和实例方法在面向对象编程中各自扮演着重要的角色,开发者需要根据具体的业务需求和设计原则来合理地使用它们,以实现高效、可读和易于维护的代码结构。
48 12
|
7天前
|
JavaScript 前端开发
静态方法和类的实例方法的执行顺序是怎样的?
静态方法和实例方法的执行顺序取决于具体的调用逻辑和代码结构,理解它们之间的执行顺序有助于更好地组织和编写面向对象的 JavaScript 代码,确保程序的逻辑正确和清晰。
34 10
|
3天前
静态方法和实例方法有何不同?
静态方法和实例方法的区别主要体现在两个方面: 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
|
8天前
在类中使用静态方法和实例方法有什么区别?
在类中合理地使用静态方法和实例方法,可以更好地组织和管理代码,提高代码的可读性、可维护性和性能,根据具体的业务需求和逻辑来选择使用哪种方法是编写高质量面向对象代码的关键。
|
3月前
|
存储 Java 程序员
08 Java面向对象基础(对象与类+实例变量与方法+构造方法+this关键字)
08 Java面向对象基础(对象与类+实例变量与方法+构造方法+this关键字)
77 4
|
6月前
|
设计模式 JavaScript 前端开发
创建对象的方法有哪些
创建对象的方法有哪些
46 11
|
Java API
Java反射(通过反射获取构造函数、方法、属性)
1.通过反射获取构造函数,2.通过反射获取方法,3.通过反射调用成员属性
117 0
|
Java
Java 类(私有属性、对象方法、类方法,构造函数)
Java 类(私有属性、对象方法、类方法,构造函数)
121 0
方法引用符、引用类方法、引用对象的实例方法、引用类的实例方法及引用构造器
方法引用符、引用类方法、引用对象的实例方法、引用类的实例方法及引用构造器
91 0