作者:Shadeed 译者:前端小智 来源:dmitripavlutin
有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
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
访问 word
属性的原因。
注意,this
也称为上下文。
在上一个示例中,我们使用this
来访问该方法所属的对象,但是 JS 没有强制让方法使用 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()
。
如前所述,我们可以直接在对象字面量中定义方法
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}!`;
}
};
// A a new property holding a function
world.farewell = function () {
return `Good bye, ${this.who}!`;
}
world.farewell(); // => 'Good bye, World!'
3.类方法
在 JavaScript 中,类别语法定义了一个类别,该类别将用作其实例的模板。
类也可以有方法:
class Greeter {
constructor(who) {
this.who = who;
}
greet() { console.log(this === myGreeter); // logs 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
。
JavaScript 特别有趣的是,在对象或类上定义方法只能算完成工作的一半。为了维护方法的上下文,我们必须确保将方法作为方法调用。
我们来看看为什么它很重要。
回忆一下有greet()
方法的world
对象。我们测试一下greet()
作为一个方法和一个常规函数调用时,this
值是什么:
const world = {
who: 'World',
greet() {
console.log(this === world); return `Hello, ${this.who}!`;
}
};
// 方法调用
world.greet(); // logs true
const greetFunc = world.greet;
// 常规函数调用
greetFunc(); // => logs false
world.greet()
是一个方法调用。对象world
,后面是一个点.
,最后是使方法调用的方法本身。
greetFunc
与world.greet
是同一个函数。但当作为常规函数greetFunc()
调用时,这个在greet()
中的并不等于world
对象,而是全局对象(在浏览器中是window
)
我们将诸如greetFunc = world.greet
之类的表达式命名为将方法与其对象分离的方法。 调用分离的方法greetFunc()
时,this
等于全局对象。
将方法与其对象分离可以采用不同的形式:
// 方法分离, this 丢失了!
const myMethodFunc = myObject.myMethod;
// 方法分离, this 丢失了!
setTimeout(myObject.myMethod, 1000);
// 方法分离, this 丢失了!
myButton.addEventListener('click', myObject.myMethod)
// 方法分离, this 丢失了!
My React Button
为了避免丢失方法的上下文,请确保使用方法调用world.greet()
或手动将方法绑定到对象greetFunc = world.greet.bind(this)
。
如上一节所述,常规函数调用已将this
解析为全局对象。 常规函数是否可以通过方法自定义 this
值?
欢迎使用以下间接函数调用:
myFunc.call(thisArg, arg1, arg2, ..., argN);
myFunc.apply(thisArg, [arg1, arg2, ..., argN]);
函数对象上可用的方法。
myFunc.call(thisArg)
和 myFunc.apply(thisArg)
的第一个参数是间接调用的上下文(this
值)。 换句话说,我们可以手动指定函数内部 this
的值。
例如,让我们将greet()
定义为一个常规函数,以及一个具有who
属性的对象alien
:
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()
函数中的值等于aliens
对象。
最后,还有一种在对象上使函数作为方法调用的第三种方法。 我们可以将函数绑定为具有特定上下文。
可以使用特殊方法创建绑定函数
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
。
不推荐使用箭头函数作为方法,原因如下。
我们将greet()
方法定义为一个箭头函数:
const world = {
who: 'World',
greet: () => {
return `Hello, ${this.who}!`;
}
};
world.greet(); // => 'Hello, undefined!'
不幸的是,world.greet()
返回'Hello, undefined!
而不是我们期待的'Hello, World!'
。
问题是箭头函数内部的this
等于外部作用域的this
。 但是,此时,我们想要的this
是world
对象。
上述箭头功能内部 this
等于全局对象:window
。 'Hello, ${this.who}!'
结果是 Hello, ${windows.who}!
,最后是 'Hello, undefined!'
。
我喜欢箭头功能, 但是它们不能用作方法。
6. 总结该方法是一个属于对象的函数。方法的上下文(this
)等于该方法所属的对象。
还可以在类上定义方法。这个类的方法内部等于实例。 JS 特有的一点是,仅仅定义一个方法是不够的。我们还需要确保使用方法调用。通常,方法调用具有以下语法
// Method invocation
myObject.myMethod('Arg 1', 'Arg 2');
有趣的是,在 JS 中,我们可以定义一个常规函数,但不属于一个对象,然后作为一个任意对象的方法调用该函数。可以使用间接函数调用或将函数绑定到特定上下文来实现这一点
// Indirect function invocation
myRegularFunc.call(myObject, 'Arg 1', 'Arg 2');
myRegularFunc.apply(myObject, 'Arg 1', 'Arg 2');
// Bound function
const myBoundFunc = myRegularFunc.bind(myObject);
myBoundFunc('Arg 1', 'Arg 2');
我是小智,我要去刷碗了,我们下期见~
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
原文:https://dmitripavlutin.com/javascript-method/
交流有梦想,有干货,微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。