JavaScript Scope
Scope is the set of variables, objects, and functions you have access to. 你可以获取的变量、对象和函数的集合称为 scope 作用域。
JS 的 block 没有作用域,只有 functions 有作用域。
Global Scope
通过全局作用域创建 Modules/APIs 实现功能复用。
jQuery('.myClass');
上面的代码表示在全局作用域里使用 jQuery,并作为 jQuery 函数库的命名空间 namespace(通常指最高一层作用域)。
Local Scope
每个定义的函数都有自己的局部作用域,local scope 可以层层嵌套。
无论何时执行一个函数,就有一个 scope 对象被创建,用于保存那个函数内部创建的局部变量。
在函数内部以 var 关键字声明的变量,是局部变量。声明在函数外部的变量,是全局变量。
局部作用域里的任何东西对全局作用域都是不可见,除非对外暴露。
Function Scope
JS中所有作用域只能由函数作用域创建,而不是由 for,while,或者 if,switch 等表达式语句创建。
Lexical Scope / Closures
在一个函数内声明的函数称为 Inner functions,能够使用 outer 父函数的作用域,这称为词法作用域或闭包,也叫 Static Scope 静态作用域。
任何定义在父作用域的变量、对象、函数,在作用域链上都是可用的。反之,则不然。
如果一个调用函数所依赖的少数函数对于其他代码没用,建议写在函数内,减少全局函数个数总是好的。
Scope Chain
任何定义在其他函数里的函数有一个局部作用域 linked to 外部函数。这个 link 叫做 chain 作用域链。代码里的位置决定了作用域。
当我们访问一个变量的时候,JavaScript 从最里面的作用域沿着作用域链向外部开始查找,直到找到我们想要的那个变量/对象/函数。
其他
在 HTML 中,全局 scope 就是窗口对象,因此 all global variables belong to the window object.
- 局部变量只可以被定义它的函数使用,可以与全局变量重名。
- 不使用关键字 var 创建的变量, are always global, 即使写在一个函数内部,也自动变为全局变量。
- 只要你的应用(浏览器窗口或网页)在运行,全局变量就存在。
- 局部变量在函数调用时创建,在函数执行结束时删除。
- function argument 作为局部变量在函数内被使用。
Closure 闭包
能够读取其他函数内部变量的函数,称为闭包。
var b; // 闭包
function f(){
var a = 'a';
b = function() {
return a + 'b';
};
return a;
}
下面的例子中要实现计数加1的功能,但因为局部变量的生命周期只维持在函数调用期间,所以无论调用多少次 add 函数,结果都是 1.
function add() {
var counter = 0;
counter += 1;
}
add();
add();
add();
// the counter should now be 3, but it does not work !
下面的例子使用闭包,解决了自增计数的问题。
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
add();
add();
add();
// the counter is now 3
结合上面提到的自调用解释一下:
- 自调用函数 only runs once,初始化变量 counter 赋值为0
- 将无名函数返回并赋值给变量 add
- 变量 add 成为一个可以获取父 scope 中变量 counter 的函数
- The counter is protected by the scope of the anonymous function(我认为这里指的是父函数), and can only be changed using the add function
闭包的特点与作用
特点:
- 在函数中定义有共享意义的局部变量
- 函数(f)中声明有内嵌函数(g),g对f中的局部变量进行访问
- 函数(f)向外返回此内嵌函数(g),外部通过g持有并访问声明在f中的局部变量
作用:
- 提供可共享的局部变量
- 保护共享的局部变量,提供专门的读写变量的(闭包)函数
- 避免全局污染
Changing scope with .call() .apply() 和 .bind()
有时需要改变作用域来进行操作,实现一些目标。看下例:
var links = document.querySelectorAll('nav li');
for (var i = 0; i < links.length; i++) {
console.log(this); // [object Window]
}
因为并没有 invoke 什么或者改变作用域,这里的 this 指的就是默认的全局作用域中拥有这段js的 window 对象。
使用 .call() 和 .apply() 可以把一个作用域传进函数中。改变运行时的 scope
var links = document.querySelectorAll('nav li');
for (var i = 0; i < links.length; i++) {
(function () {
console.log(this); // each element in the array
}).call(links[i]);
}
两者的区别在 JS Functions 章节已经讲过.
以下两种方法等价。
myFunction(); // invoke myFunction
myFunction.call(scope); // invoke myFunction using .call()
使用 bind()
绑定事件处理函数时,有时会遇到需要传参数
// works
nav.addEventListener('click', toggleNav, false);
// will invoke the function immediately
nav.addEventListener('click', toggleNav(arg1, arg2), false);
// 解决办法
nav.addEventListener('click', function () {
toggleNav(arg1, arg2);
}, false);
上面的办法改变了作用域,但是写了一个没什么用的函数。改进:使用 .bind()
改变定义时的 scope。不同于 call 和 apply,bind 不会 invoke 一个函数(即立即执行)。
nav.addEventListener('click', toggleNav.bind(scope, arg1, arg2), false);
此外,还可以借助闭包实现定义时改变上下文。
function A() {
init: function() {
this.$element.on('click', this._switchThis(this.ajaxSubmit, this))
},
_switchThis: function (fn, obj) {
return function () {
fn.apply(obj, arguments) // 将函数作为另外一个对象的方法运行
}
},
ajaxSubmit: function () {}
}
Private and Public Scope
JS 中没有私有域和共有域的说法,最简单的创建私有域的做法是,把函数写在另一个函数里。(如上面提到,函数创建 scope,将会把内容保持在全局作用域之外)
(function () {
// private scope inside here
var myFunction = function () {
// do some stuff here
};
})();
myFunction(); // Uncaught ReferenceError: myFunction is not defined
上面我们模拟出了私有作用域的效果,如何实现公有作用域呢?
// define module
var Module = (function () {
var privateMethod = function () {
};
return {
myMethod: function () {
},
publicMethod: function () {
// has access to `privateMethod`, we can call it:
// privateMethod();
}
};
})();
// call module + methods
Module.myMethod();
Module.publicMethod();
这里将返回对象赋给 Module,可以被全局作用域使用。Module 作为命名空间,其包含的方法作为公有函数使用。
没有写在 return
所含对象中的方法,就变成了私有函数(方法)。
下面举例如何通过返回一个对象,使用公有和私有的方法。
var Module = (function () {
var myModule = {};
var privateMethod = function () {
};
myModule.publicMethod = function () {
};
myModule.anotherPublicMethod = function () {
};
return myModule; // returns the Object with public methods
})();
// usage
Module.publicMethod();
我们可以通过在方法名字前加下划线来区分私有方法和公有方法。