上下文与做用域之间有什么样的关系? 这一律念看似简单,但不少人都讲不清楚之间的关系。上下文和做用域都是编译原理的知识,具体编程语言有具体的实现规则,本文关注的是 JavaScript 语言的实现。编程
上下文
(context)是一段程序运行所须要的最小数据集合。咱们能够从上下文交换
(context switch)来理解上下文,在多进程或多线程环境中,任务切换时首先要中断当前的任务,将计算资源交给下一个任务。由于稍后还要恢复以前的任务,因此中断的时候要保存现场,即当前任务的上下文,也能够叫作环境。segmentfault
做用域
(scope)是标识符(变量)在程序中的可见性范围。做用域规则是按照具体规则维护标识符的可见性,以肯定当前执行的代码对这些标识符的访问权限。做用域是在具体的做用域规则之下肯定的。浏览器
上下文、环境有时候也称做用域,即这两个概念有时候是混用的;不过,上下文指代的是总体环境,做用域关注的是标识符(变量)的可访问性(可见性)。上下文肯定了,根据具体编程语言的做用域规则,做用域也就肯定了。这就是上下文与做用域的关系。缓存
function callWithContext(fn, context) {
return fn.call(context);
}
let name = 'Banana';
const apple = {
name: "Apple"
};
const orange = {
name: "Orange"
};
function echo() {
console.log(this.name);
}
echo(); // Banana
callWithContext(echo, apple); // Apple
callWithContext(echo, orange); // Orange
复制代码
var a = 1;
function foo(){
// 返回一个箭头函数
return () => {
// this 继承自 foo()
console.log( this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
foo()() // 1
var bar = foo.call( obj1 ); // 调用位置
bar.call( obj2 ); // 2
foo.call( obj2 )(); // 3
复制代码
JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段做用域规则会肯定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段建立。多线程
当JavaScript代码执行进入一个环境时,就会为该环境建立一个执行上下文
,它会在你运行代码前作一些准备工做,如肯定做用域,建立局部变量对象等。闭包
JS代码的执行环境app
执行上下文的类型编程语言
JavaScript运行时首先会进入全局环境,对应会生成全局上下文。程序代码中基本都会存在函数,那么调用函数,就会进入函数执行环境,对应就会生成该函数的执行上下文。模块化
函数编程中,代码中会声明多个函数,对应的执行上下文也会存在多个。在JavaScript中,经过栈的存取方式来管理执行上下文,咱们可称其为执行栈,或函数调用栈(Call Stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。函数
程序执行进入一个执行环境时,它的执行上下文就会被建立,并被推入执行栈中(入栈);程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。栈结构
由于JS执行中最早进入全局环境,因此处于"栈底的永远是全局环境的执行上下文"。而处于"栈顶的是当前正在执行函数的执行上下文",当函数调用完成后,它就会从栈顶被推出。
"全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭以后它才会从执行栈中被推出,不然一直存在于栈底"
let color = 'blue';
function changeColor() {
let anotherColor = 'red';
function swapColors() {
let tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();
复制代码
做用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
在 JavaScript 中,这个具体的做用域规则就是词法做用域
(lexical scope),也就是 JavaScript 中的做用域链的规则。词法做用域是的变量在编译时(词法阶段)就是肯定的,因此词法做用域又叫静态做用域
(static scope),与之相对的是动态做用域
(dynamic scope)。
let a = 2;
function foo() {
console.log(a);
// 会输出2仍是3?
}
function bar() {
let a = 3;
foo();
}
bar();
复制代码
前面说过,词法做用域也叫静态做用域,变量在词法阶段肯定,也就是定义时肯定。虽然在 bar 内调用,但因为 foo 是闭包函数,即便它在本身定义的词法做用域之外的地方执行,它也一直保持着本身的做用域。所谓闭包函数
,即这个函数封闭了它本身的定义时的环境
,造成了一个闭包
。(即闭包是由函数以及建立该函数的词法环境组合而成。这个环境包含了这个闭包建立时所能访问的全部局部变量)因此 foo 并不会从 bar 中寻找变量,这就是静态做用域的特色。
而动态做用域并不关心函数和做用域是如何声明以及在何处声明的,只关心它们从何处调用。换句话说,做用域链是基于调用栈的,而不是代码中的做用域嵌套。
词法做用域是在写代码或者定义时肯定的,而动态做用域是在运行时肯定的。词法做用域关注函数在何处声明,而动态做用域关注函数从何处调用。
function foo() {
let a = 0;
function bar() {
console.log(a);
}
return bar;
}
let a = 1;
let sub = foo();
sub(); // 0;
复制代码
一旦设置了参数的默认值,函数进行声明初始化时,参数会造成一个单独的做用域(context)。等到初始化结束,这个做用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
复制代码
上面代码中,函数foo
的参数造成一个单独做用域。这个做用域里面,首先声明了变量x
,而后声明了变量y
,y
的默认值是一个匿名函数。这个匿名函数内部的变量x
,指向同一个做用域的第一个参数x
。函数foo
内部又声明了一个内部变量x
,该变量与第一个参数x
因为不是同一个做用域,因此不是同一个变量,所以执行y
后,内部变量x
和外部全局变量x
的值都没变。
模块化、柯里化、模拟块级做用域、命名空间、缓存数据
const tar = (function () {
let num = 0;
return {
addNum: function () {
num++;
},
showNum: function () {
console.log(num);
}
}
})()
tar.addNum();
tar.showNum();
复制代码
let add = function(x){
return function(y){
return x + y
}
}
console.log(add(2)(4)) // 6
复制代码
for (var i = 1; i < 5; i++) {
setTimeout(function timer() {
console.log(i);
}, 0);
}
function func(){
for(var i = 0; i < 5; i++){
+ (i => { setTimeout(() => console.log(i),300) })(i)
}
}
func()
复制代码
var MyNamespace = {};
MyNamespace.doSomething = function (){
//使用闭包产生的私有类变量
var label, icon;
//可访问私有变量,但不可被外部访问的私有方法
function setLabel(){
// do something...
}
//可访问私有变量,也可被外部访问的方法
this.getLabel = function(){
// do something...
};
}
// 该方法可被外部访问,却只能经过取/赋值器访问私有类变量
MyNamespace.TreeItem.prototype = {
print: function(){
console.log( this.getLabel() );
}
}
复制代码
import {readFileSync, readdirSync} from 'fs';
var readContent = (function(){
let contentCache = {};
return (bookName)=>{
let content = contentCache[bookName];
if (!content){
content = readFileSync(bookName+".txt", "utf8");
contentCache[bookName] = content;
}
return content;
};
})();
复制代码
参考文章: