【十三】JavaScript执行(三):你知道现在有多少种函数吗
函数
在ES2018中,函数已经是一个很复杂的体系了,我在这里整体了一下。
1、普通函数:用function关键字定义的函数
function foo(){
// code
}
2、箭头函数:用=>运算符定义的函数
const foo = () => {
// code
}
3、方法:在class中定义的函数
class C {
foo(){
//code
}
}
4、生成器函数,用function*定义的函数
function foo*(){
// code
}
5、类:用class定义的类,实际上也是函数
class Foo {
constructor(){
//code
}
}
6-8、异步函数:普通函数、箭头函数、生成器函数加上async关键字
async function foo(){
// code
}
const foo = async () => {
// code
}
async function foo*(){
// code
}
对于普通变量而言,这些函数没有本质的区别,都遵循了“继承定义时环境”的规则,他们的一个行为差异在于this关键字
this 关键字的行为
this 是执行上下文中很重要的一个组成部分。同一个函数调用方式不同,得到的this值也不同
function showThis(){
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // o
普通函数的this值由“调用它所使用的引用”决定,其中奥秘就在于:我们获取函数的表达式,实际上返回的并非函数本身,而是一个Reference类型
Reference 类型由两部分组成:一个对象和一个属性值。
不难理解o.showThis产生的Reference类型,即由对象o和属性“showThis”构成。
当做一些算术运算(或者其他运算时),Reference 类型会被解引用,即获取真正的值(被引用的内容)来参与运算,而类似函数调用、delete 等操作,都需要用到Reference类型中的对象。
在这个例子中,Reference 类型中的对象被当作this值,传入了执行函数时的上下文当中。
至此,我们对this的解释已经非常清晰了:调用函数时使用的引用,决定了函数执行时刻的this值。
但是换一种方式:
const showThis = () => {
console.log(this);
}
var o = {
showThis: showThis
}
showThis(); // global
o.showThis(); // global
我们看到,改成箭头函数后,不论用什么引用来调取它,都不影响this的值
class C {
showThis() {
console.log(this);
}
}
var o = new C();
var showThis = o.showThis;
showThis(); // undefined
o.showThis(); // global
可以得出结论:生成器函数、异步生成器函数和异步普通函数跟普通函数行为是一致的,异步箭头函数与箭头函数行为是一致的。
this关键字的机制
函数能够弓用定义时的变量,如上文分析,函数也能记住定义时的this,因此,函数内部必定有一个机制来保存这些信息。
在JavaScript标准中,为函数规定了用来保存定义时上下文的私有属性[[Environment]。
当一个函数执行时,会创建一条新的执行环境记录, 记录的外层词法环境(outer lexicalenvironment)会被设置成函数的[Environment]。
操作this的内置函数
Function.prototype.call和Function.prototype.apply 可以指定函数调用时传入的this值
function foo(a, b, c){
console.log(this);
console.log(a, b, c);
}
foo.call({}, 1, 2, 3);
foo.apply({}, [1, 2, 3]);
另外,还有Function.prototype.bind 它可以生成一个绑定过的函数,这个函数的this值固定了参数
function foo(a, b, c){
console.log(this);
console.log(a, b, c);
}
foo.bind({}, 1, 2, 3)();