前言
实现模块化的先决得理解作用域
函数中的作用域
// b1
var a = function b() {
b(); // ①
}
// b2
function b() {
console.log(444);
}
a();
b(); // ②
这里有个很神奇的地方就是①处的b方法调用的是b1,②处调用的b方法是b2,且如果没有b2那么②处将抛异常,查询不到b方法。
这个原因在于该函数声明是作为表达式使得其具名被限定在函数之内,使得在其函数内部仍可引用
隐藏内部实现
最小特权原则:在软件设计中应该最小限度的暴露内容
就是声明一个函数,然后把相应的函数写入其内作为内部函数,达到隐藏目的。
函数作用域
具名函数至少暴露了该函数(容器),而且还得显示调用,显得不够优雅
有俩解决方式:
- 使用匿名回调函数(函数声明必须具名,表达式的话就可有可无):setTimeout
- 立即执行函数(jQuery就采取这种,然后将接口挂载在window之上)
具名函数好处
栈追踪、事件监听触发之后解绑需要引用自身、函数有意义一目了然
块作用域
块作用域
先考虑以下俩段代码,我们通常习惯于第一种写法,但是其实它和第二种写法无区别(变量提升),也就是在for循环外面i也可以被引用。
但是很多时候它并不被需要在外部,所以我们需要块作用域,让其限定在块内。
for (var i = 0; i < 10; i++) {
// ......
}
// <===>
var i;
for (i = 0; i < 10; i++) {
// ......
}
实现的几种方法
1. with
上文说的with就可以
2. try catch
这里说的不是它的{}带有限定作用,而是下面的error只会生效在catch块内,算是一个奇技淫巧。如下a、b都会提升到全局作用域,而error相当于在catch声明了一个error变量,它被限定在catch之内。
不过很尴尬的是它只接受一个参数,所以声明俩个就无能为力了(个人觉得可以把变量变成对象的属性)
try {
var a = 1;
throw 3;
} catch (error) {
var b = 2;
}
console.log(a); // 1
console.log(b); // 2
console.log(error); // Uncaught ReferenceError: error is not defined
3. let
这是ES6提供的。以前只有var声明变量,现在终于不再只有var了。
它可以把变量限定在作用域之内(通常是{ ... }(if、for等等))。顺带提下它不会变量提升
if (true) {
let a = 1;
}
console.log(a); // ReferenceError
// 不过上面这种写法不好,它是附在已存在的块作用域之上,此为隐式块
// 推荐做法是显示块,如此移动块安全
if (true) {
{
let a = 1;
}
}
console.log(a); // ReferenceError
俩个好处
一则因为闭包导致的一些数据结构会被引擎保存,用块作用域包裹就会让引擎明白无需保存
二则let置于for循环内除了其声明的变量限定在for块作用域之内而且还会重新绑定(就是把上次循环迭代结束的值重新赋给循环的每一次迭代)
for (let i = 0; i < 10; i++) {
console.log(i);
}
// <====>
{
let j;
for (j = 0; j < 10; j++) {
let i = j;
console.log(i);
}
}
4. const
cosnt和let是一样的,区别在于它声明的是常量
转换工具(变通写法)
算是奇技淫巧吧
// babel
"use strict";
{
var _a = 1;
console.log(_a);
}
console.log(a);
// traceur
{
var a$__0 = 1;
console.log(a$__0);
}
console.log(a);