1. 什么是方法
首先让我们定义并调用常规函数:
function greet(who) {
return `Hello, ${who}!`;
}
greet('World'); // => 'Hello, World!'
常规函数定义的形式为关键字 function
后跟名称、参数和函数体:function greet(who) {...}
。
greet('World')
是常规函数调用。函数 greet('World')
从参数接受数据。
如果 who
是对象的属性怎么办?要想轻松访问对象的属性,可以将函数附加到该对象,也就是创建一个方法。
让我们把 greet()
作为对象 world
的一个方法:
const world = {
who: 'World',
greet() {
return `Hello, ${this.who}!`;
}
}
world.greet(); // => 'Hello, World!'
greet() { ... }
现在是属于 world
对象的一个方法。 world.greet()
是一种方法调用。
在 greet()
方法内部,this
指向该方法所属的对象 world
。这就是为什么 this.who
表达式能够访问属性 who
的原因。
this
也叫上下文(context)。
上下文是可选的
在上个例子中,我们用了 this
来访问该方法所属的对象,但是 JavaScript 并没有强制使用 this
的方法。
所以可以将对象作为方法的命名空间来使用:
const namespace = {
greet(who) {
return `Hello, ${who}!`;
},
farewell(who) {
return `Good bye, ${who}!`;
}
}
namespace.greet('World'); // => 'Hello, World!'
namespace.farewell('World'); // => 'Good bye, World!'
namespace
是一个拥有 2 种方法的对象:namespace.greet()
和 namespace.farewell()
。
这些方法没有用 this
,而 namespace
是方法的所有者。
2. 对象字面量方法
如上面所示,你可以直接在对象字面量中定义方法:
const world = {
who: 'World',
greet() {
return `Hello, ${this.who}!`;
}
};
world.greet(); // => 'Hello, World!'
greet(){....}
是在对象字面量上定义的方法。这种定义类型称为简写方法定义(ES2015+ 开始可用)。
方法定义的语法也更长:
const world = {
who: 'World',
greet: function() {
return `Hello, ${this.who}!`;
}
}
world.greet(); // => 'Hello, World!'
greet: function() {...}
是方法定义。注意冒号和 function
关键字。
动态添加方法
方法只是一个函数,它作为属性被保存在对象上。因此可以向对象动态添加方法:
const world = {
who: 'World',
greet() {
return `Hello, ${this.who}!`;
}
};
// 一个带有函数的新属性
world.farewell = function () {
return `Good bye, ${this.who}!`;
}
world.farewell(); // => 'Good bye, World!'
首先,world
对象没有 farewell
方法,它是被动态添加的。
调用动态添加的方法完全没有问题:world.farewell()
。
3. 类方法
在 JavaScript 中,class
语法定义了一个类,该类是它实例的模板。
一个类也可以有方法:
class Greeter {
constructor(who) {
this.who = who;
}
greet() {
console.log(this === myGreeter); // => true
return `Hello, ${this.who}!`;
}
}
const myGreeter = new Greeter('World');
myGreeter.greet(); // => 'Hello, World!'
greet() {...}
是在类内部定义的方法。
每次使用 new
操作符创建类的实例时(例如,myGreeter = new Greeter('World')
),都可以通过方法来创建实例。
myGreeter.greet()
是在实例上调用 greet()
方法的,方法内部的 this
等于实例本身,即 this
等于 greet() { ... }
方法内部的 myGreeter
。
4. 如何调用方法
4.1 方法调用
对象或类上定义方法只是完成了工作的一半。为了保持方法的上下文,你必须确保将其作为“方法”去调用。
回想一下带有 greet()
方法的 world
对象。让我们检查一下当方法和常规函数 greet()
被调用时,this
的值是什么:
const world = {
who: 'World',
greet() {
console.log(this === world); return `Hello, ${this.who}!`;
}
};
// 方法调用
world.greet(); // => true
const greetFunc = world.greet;
// 常规函数调用
greetFunc(); // => false
world.greet()
是一种方法调用。对象 world
,后跟一个点 .
,最后是方法本身,这就是方法调用。
greetFunc
与world.greet
的功能相同。但是当作为常规函数 greetFunc()
调用时,greet()
内部的 this
不等于 world
对象,而是等于全局对象(在浏览器中是 window
)。
命名类似 greetFunc = world.greet
的表达式,将方法与其对象分开。当稍后调用分离的方法 greetFunc()
时,会使 this
等于全局对象。
将方法与其对象分开可以采取不同的形式:
//方法是分开的! this 丢失!
const myMethodFunc = myObject.myMethod;
//方法是分开的! this 丢失!
setTimeout(myObject.myMethod, 1000);
//方法是分开的! this 丢失!
myButton.addEventListener('click', myObject.myMethod)
//方法是分开的! this 丢失!
<button onClick={myObject.myMethod}>My React Button</button>
为避免丢失方法的上下文,要确保使用方法调用 world.greet()
或将方法手动绑定到对象 greetFunc = world.greet.bind(this)
。
4.2 间接函数调用
在上一节中,常规函数调用已将 this
解析为全局对象。那么有没有一种方法可以使常规函数具有 this
的可自定义值?
可以使用下面的间接函数调用:
myFunc.call(thisArg, arg1, arg2, ..., argN);
myFunc.apply(thisArg, [arg1, arg2, ..., argN]);
myFunc.call(thisArg)
和 myFunc.apply(thisArg)
的第一个参数是间接调用的上下文(this
的值)。换句话说,你可以手动改变函数中 this
的值。
例如,让我们将 greet()
定义为常规函数,并定义一个具有 who
属性的对象 aliens
:
function greet() {
return `Hello, ${this.who}!`;
}
const aliens = {
who: 'Aliens'
};
greet.call(aliens); // => 'Hello, Aliens!'
greet.apply(aliens); // => 'Hello, Aliens!'
greet.call(aliens)
和 greet.apply(aliens)
都是间接方法调用。函数 greet()
中的 this
值等于 aliens
对象。
间接调用使你可以在对象上模拟方法调用。
4.3 绑定函数调用
最后是在对象上使函数作为方法调用的第三种方法:将函数绑定为具有特定上下文。
可以用特殊方法创建绑定函数:
const myBoundFunc = myFunc.bind(thisArg, arg1, arg2, ..., argN);
myFunc.bind(thisArg)
的第一个参数是函数要绑定到的上下文。
例如,让我们重用 greet()
并将其绑定到 aliens
上下文:
function greet() {
return `Hello, ${this.who}!`;
}
const aliens = {
who: 'Aliens'
};
const greetAliens = greet.bind(aliens);
greetAliens(); // => 'Hello, Aliens!'
调用 greet.bind(aliens)
会创建一个新函数,其中 this
绑定到 aliens
对象。
然后,当调用绑定函数 greetAliens()
时,this
等于该函数内部的 aliens
。
同样,使用绑定函数还可以模拟方法调用。
5. 箭头函数方法
不建议将箭头功能用作方法,原因如下:
//把 greet() 方法定义为箭头函数
const world = {
who: 'World',
greet: () => {
return `Hello, ${this.who}!`;
}
};
world.greet(); // => 'Hello, undefined!'
world.greet()
返回 'Hello, undefined!'
,而不是预期的 'Hello, World!'
。
问题是箭头函数内的 this
属于外部作用域,你想要 this
等于 world
对象,但是在浏览器中 this
是 window
。'Hello, ${this.who}!'
的计算结果为 Hello, ${windows.who}!
,所以最后的结果是 'Hello, undefined!'
。
尽管我很喜欢箭头函数,但是不能把它们用作方法。
总结
方法是属于对象的函数。方法的上下文(this
值)等于该方法所属的对象。
你还可以在类上定义方法。类方法中的 this
等于实例。
仅定义一个方法是不够的,还要能够调用才行。一般方法调用实用以下语法:
// 方法调用
myObject.myMethod('Arg 1', 'Arg 2');
在 JavaScript 中,你可以定义一个不属于对象的常规函数,然后将该函数作为对任意对象的方法来调用。你可以通过间接函数调用或将函数绑定到特定上下文来实现:
// 间接函数调用
myRegularFunc.call(myObject, 'Arg 1', 'Arg 2');
myRegularFunc.apply(myObject, 'Arg 1', 'Arg 2');
// 绑定函数
const myBoundFunc = myRegularFunc.bind(myObject);
myBoundFunc('Arg 1', 'Arg 2');
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你提高开发效率的现代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把HTML转成PDF的4个方案及实现
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。