关于 JavaScript 中的方法的全部知识点都在这了
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 关键字。
3. 动态添加方法
方法只是一个函数,它作为属性被保存在对象上。因此可以向对象动态添加方法:
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 中,你可以定义一个不属于对象的常规函数,然后将该函数作为对任意对象的方法来调用。你可以通过间接函数调用或将函数绑定到特定上下文来实现:
在 JavaScript 中,你可以定义一个不属于对象的常规函数,然后将该函数作为对任意对象的方法来调用。你可以通过间接函数调用或将函数绑定到特定上下文来实现: