手机网站营销页,静海区网站建设推广,html5网页设计软件,一家专门做灯的网站在上篇文章中我们了解到了执行上下文是什么#xff0c;也知道了任何语句的执行都会依赖特定的上下文。一旦上下文被切换#xff0c;整个语句的效果可能都会发生变化。那么#xff0c;切换上下文的时机就显得非常重要。在JavaScript中#xff0c;切换上下文最主要的场景就是…在上篇文章中我们了解到了执行上下文是什么也知道了任何语句的执行都会依赖特定的上下文。一旦上下文被切换整个语句的效果可能都会发生变化。那么切换上下文的时机就显得非常重要。在JavaScript中切换上下文最主要的场景就是函数调用。在这篇文章中我们就来讲讲函数调用切换上下文的事情。在我们讲函数调用之前我们先来认识一下函数家族。函数在ES2018中函数已经是一个很复杂的体系了我在这里整理了一下。第一种普通函数用function关键字定义的函数。示例function foo(){//code
}
第二种箭头函数用 运算符定义的函数。示例const foo (){//code
}
第三种在class中定义的函数。示例class C {foo(){//code}
}
第四种生成器函数用 function* 定义的函数。示例function* foo(){//code
}
第五种类。用class定义的类实际上也是函数。示例class Foo {constructor(){//code}
}
第六七八种异步函数普通函数箭头函数和生成器函数前加上async关键字。示例async function foo(){// code
}
const foo async () {// code
}
async function foo*(){// code
}
ES6以来大量加入的新语法极大地方便了我们编程的同时也增加了很多我们理解的心智负担。要想认识这些函数的执行上下文切换我们必须要对他们行为上的区别有所了解。对普通变量而言这些函数并没有本质区别都是遵循了继承定义时的环境的规则它们的一个行为差异在于this关键字。那么this 关键字是什么呢this 关键字行为this 是JavaScript中的一个关键字它的使用方法类似一个变量但是this跟变量有很多不同。this是执行上下文中很重要的一个组成部分。同一个函数调用方式不同得到的this值也不同我们看一个例子function showThis(){console.info(this)
}var o {showThis: showThis
}showThis(); //global
o.showThis(); //o
在这个例子中我们定义了函数showThis我们把它赋值给了一个对象o的属性然后尝试分别使用两个引用来调用同一个函数结果得到了不同的this值。普通函数的 this 值由调用它所使用的的引用来决定其中奥秘就在于我们获取函数的表达式它实际上返回的并非函数本身而是一个 Reference 类型其中标准类型之一。Reference 类型由两部分组成一个对象和一个属性值。不难理解 o.showThis 产生的Reference类型即由对象 o 和属性showThis构成。当做一些算术运算时Reference类型会被解引用即获取真正的值来参与运算而类似函数调用delete等操作都需要用到 Reference 类型中的对象。在这个例子中Reference类型中的对象被当做this值传入了执行函数的上下文中。至此我们对this的解释已经非常清晰了调用函数时使用的引用决定了函数执行时刻的this值。实际上从运行时的角度来看this跟面向对象毫无关联它是与函数调用的表达式相关。这个设计来自JavaScript早年通过这样的方式巧妙地模拟了Java的语法但是仍然保留了纯粹的无类运行时设施。如果我们把这个例子稍作修改换成箭头函数结果就不一样了。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(); // o
这里我们创建了一个类C并且实例化出对象o再把 o 的方法赋值给了变量 showThis。这时候我们使用 showThis 这个引用去调用方法时得到了undefined。所以在方法中我们看到this的行为也不大一样它得到了undefined的结果。按照我们上面的方法不难验证出生成器函数异步函数和异步普通函数跟普通函数行为是一致的异步箭头函数与箭头函数的行为是一致的。this关键字机制说完了this行为我们再来简单谈谈在JavaScript内部实现this这些行为的机制。函数能够引用定义时的变量如上文分析函数能记住定义时的this因此函数内部必须有一个机制来保存这些信息。在JavaScript标准中为函数规定了用来保存定义是上下文的私有属性[[Environment]]。当一个函数执行时会创建一条新的执行环境记录记录的外层词法环境outer lexical environment会被设置成函数的[[Environment]]。这个动作就是切换上下文了我们假设有这样的代码var a 1;
foo();
在别处定义了foovar b 2;
function foo(){console.log(b); // 2console.log(a); // error
}
这里的foo能够访问 b (定义时的词法环境)却不能访问 a 执行时的词法环境这就是执行上下文的切换机制了。JavaScript用一个栈来管理执行上下文这个栈的每一项又包含一个链表。如下图所示当函数调用时会入栈一个新的执行上下文函数调用结束时执行上下文被出栈。而this则是一个更为复杂的机制JavaScript标准定义了[[thisMode]]私有属性。[[thisMode]]私有属性有三个取值lexical表示从上下文中找this这对应了箭头函数。global表示this为undefined时取全局对象对应了普通函数。strict当严格模式时使用this严格按照调用时传入的值可能为null或者undefined。非常有意思的是方法的行为跟普通函数有差异恰恰是因为class设计成了默认按照strict模式执行。我们可以用strict达成与上一节中方法的例子中一样的效果。use strict
function showThis(){console.log(this);
}var o {showThis: showThis
}showThis(); // undefined
o.showThis(); // o
函数创建新的执行上下文中词法环境记录时会根据[[thisMode]]来标记新纪录的[[ThisBindingStatus]]私有属性。代码执行遇到this时会组成检查当前词法环境记录中的[[ThisBindingStatus]]当我们找到有this的环境记录时获取this的值。这样的规则的实际效果时嵌套的箭头函数中的代码都指向外层this例如var o {}
o.foo function foo(){console.log(this);return () {console.log(this);return () console.log(this);}
}o.foo()()(); // o, o, o
在上面的例子中我们定义了三层嵌套的函数最外层的是普通函数两层都是箭头函数。这里调用三个函数获得的this值是一样的对象都是o。JavaScript还提供了一系列函数的内置方法来操作this值下面我们来了解一下。操作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]);
这里call和apply作用是一样的只是传参方式有区别。此外还有Function.prototype.bind 它可以生成一个绑定的函数这个函数的this值固定了参数。function foo(a, b, c){console.log(this);console.log(a, b, c);
}
foo.bind({}, 1, 2, 3)();
有趣的是callbind和apply用于不接受this的函数类型如箭头class都不会报错。这时候它们无法实现改变this的能力但是可以实现传参。