万丈高楼平地起,学习基础很重要。前端
执行环境(execution context)是 JavaScript 中最为重要的一个概念。执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境都有一个与之关联的变量对象(variable object),环境中定义的全部变量和函数都保存在这个对象中。虽然咱们编写的代码没法访问这个对象,但解析器在处理数据时会在后台使用它。es6
每一个执行环境都有一个执行环境对象。
全局执行环境是最外围的一个执行环境。在 Web 浏览器中,全局执行环境被认为是 window 对象。浏览器
全局执行环境直到应用程序退出,例如关闭网页或浏览器时才会被销毁。ide
每一个函数都有本身的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。函数
在函数执行以后,栈将其环境弹出,把控制权返回给以前的执行环境。ECMAScript 程序中的执行流正是由这个方便的机制控制着。oop
做用域能够分为:学习
全局变量拥有全局做用域。变量和函数会挂载到 window 对象上。code
var scope = 'global'; // 声明一个全局变量 function checkScope () { var scope = 'local'; // 声明一个同名局部变量 myscope = 'local'; return scope; // 返回局部变量的值, 而不是全局变量的值 } window.myscope // undefined => checkScope() 还未执行,该变量未声明 checkScope(); // local window.myscope // local
局部变量是局部做用域,仅在函数体内有用。对象
var scope = 'global'; function checkScope () { var scope = 'local'; function nested() { var scope = 'nested'; return scope; } return nested(); } checkScope() // nested =>返回的是 nested() 的 scope window.scope // global => 全局变量 scope 并未被覆盖
函数体内局部变量优先级高于同名全局变量。同名全局变量会被覆盖。ip
scope = 'global'; // 声明一个全局变量,能够不用 var 声明 function checkScope2 () { scope = 'local'; // 修改了全局变量 scope myscope = 'local'; // 显式声明了一个新的全局变量 return [scope, myscope]; } checkScope2(); // [local, local] window.scope; // local => 全局变量修改了 window.myscope; // local =>全局命名空间搞乱了
var 声明变量会提高,内部变量可能会覆盖外层变量
var tmp = '哈哈'; function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined => 理想状况应该输出值 “ 哈哈 ”
缘由在于,预编译后,if 语句内的 temp 声明提高了
var tmp = '哈哈'; function f() { var tmp console.log(tmp); // 打印的是 if 里面提高 temp if (false) { tmp = 'hello world'; } } f();
ES5 没有块级做用域。使用不当会形成变量泄露。
for (var k = 0; k < 5; k++) { setTimeout(function () { console.log('inside', k); }, 1000); } console.log('outside', k); // outside 5 => 理想状况下,k 仅在 for 循环中有效,这里不该该输出 5,应该提示 k is not defined // 间隔1s,分别输出5个 inside 5 => 理想状况下,应该输出 0 1 2 3 4 window.k; // 5 => 可看出 k 是全局变量,因此当执行 for 里面的语句时,k已经循环完了5次,此时 k = 5
再来一题
var test = function() { var arr = []; for (var i = 0; i < 3; i++) { console.log('开始循环了', i) arr[i] = function() { return i * i; }; } return arr; }; var a = test(); // 输出 “开始循环了 0 1 2” => 此时 arr[i]是还未执行的,i 已经等于 3 了 a[1](); // 9 a[2](); // 9
经过前面的介绍,能够知道,ES5 是没有块级做用域的。变量使用不当,容易形成变量泄露,出现不少不合理场景。为了不这种状况出现,咱们可使用如下方法:
如下都是针对
ES5 没有块级做用域。使用不当会形成不合理场景
列举的例子进行修改。
for (var k = 0; k < 5; k++) { (function(k){ //这里是块级做用域 setTimeout(function (){ console.log('inside', k); },1000); })(k); } console.log('outside', k); // 输出 outside 5 // 再依次输出 inside 0 1 2 3 4
var _loop = function _loop(k) { //这里是块级做用域 setTimeout(function () { console.log(k); }, 1000); }; for (var k = 0; k < 5; k++) { _loop(k); } // 依次输出 0 1 2 3 4
一、2 写法都是利用了 JS 中调用函数传递参数都是值传递的特色
for (let k = 0; k < 5; k++) { setTimeout(function () { console.log(k); }, 1000, k); } // 依次输出 0 1 2 3 4
for (let k = 0; k < 5; k++) { setTimeout(function () { console.log(k); }, 1000); } console.log(k); // k is not defined // 间隔1s,分别输出inside 0 1 2 3 4
关注执行顺序
当代码在一个环境中执行时,会建立变量对象的一个做用域链(scope chain)。
上面的说明是从《JavaScript权威指南》指南中摘抄出来的。
简单点总结就是:
当你使用一个变量时,会先从当前做用域找,若是找不到就往上找,一层一层往直到找到全局做用域都还没找到,就抛出错误,说明没有这个变量。这种一层一层的关系,就是做用域链 。
var a = 100 function F1() { var b = 200 function F2() { var c = 300 console.log(a) // 100 顺做用域链向父做用域找 console.log(b) // 200 顺做用域链向父做用域找 console.log(c) // 300 本做用域的变量 console.log(d) // ReferenceError:d is not defined 找到window都找不到,变量不存在,报错 } F2() } F1()