共计 3218 个字符,预计需要花费 9 分钟才能阅读完成。
前言
万丈高楼平地起,学习根底很重要。
前置常识
执行环境
执行环境(execution context)是 JavaScript 中最为重要的一个概念。执行环境定义了变量或函数有权拜访的其余数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保留在这个对象中。尽管咱们编写的代码无法访问这个对象,但解析器在解决数据时会在后盾应用它。
每个执行环境都有一个执行环境对象。
全局执行环境
全局执行环境是最外围的一个执行环境。在 Web 浏览器中,全局执行环境被认为是 window 对象。
全局执行环境直到应用程序退出,例如敞开网页或浏览器时才会被销毁。
函数执行环境
每个函数都有本人的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。
在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript 程序中的执行流正是由这个不便的机制管制着。
作用域
作用域能够分为:
- 全局作用域
- 部分作用域
- 块级作用域(es6)
1. 全局作用域
全局变量领有全局作用域。变量和函数会挂载到 window 对象上。
var scope = 'global'; // 申明一个全局变量
function checkScope () {
var scope = 'local'; // 申明一个同名局部变量
myscope = 'local';
return scope; // 返回局部变量的值,而不是全局变量的值
}
window.myscope // undefined => checkScope() 还未执行,该变量未声明
checkScope(); // local
window.myscope // local
2. 部分作用域
局部变量是部分作用域,仅在函数体内有用。
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 并未被笼罩
3. 作用域优先级
函数体内局部变量优先级高于同名全局变量。同名全局变量会被笼罩。
scope = 'global'; // 申明一个全局变量,能够不必 var 申明
function checkScope2 () {
scope = 'local'; // 批改了全局变量 scope
myscope = 'local'; // 显式申明了一个新的全局变量
return [scope, myscope];
}
checkScope2(); // [local, local]
window.scope; // local => 全局变量批改了
window.myscope; // local => 全局命名空间搞乱了
4. 同名变量
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();
5.ES5 模仿块级作用域
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 没有块级作用域。使用不当会造成不合理场景
列举的例子进行批改。
1. 借助立刻执行函数
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
2. 定义函数并传值
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
1、2 写法都是利用了 JS 中调用函数传递参数都是值传递的特点
3. 应用 setTimeout 的第三个参数
for (let k = 0; k < 5; k++) {setTimeout(function () {console.log(k);
}, 1000, k);
}
// 顺次输入 0 1 2 3 4
4. 应用 let、const 申明变量
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)。
- 作用域链的用处,是保障对执行环境有权拜访的所有变量和函数的有序拜访。
- 作用域链的前端,始终都是以后执行的代码所在环境的变量对象。如果这个环境是函数,则将其流动对象(activation object)作为变量对象。流动对象在最开始时只蕴含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。
- 作用域链中的下一个变量对象来自蕴含(内部)环境,而再下一个变量对象则来自下一个蕴含环境。这样,始终连续到全局执行环境。
- 全局执行环境的变量对象始终都是作用域链中的最初一个对象。
下面的阐明是从《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()