最近在读一本进阶的 JavaScript 的书《》,里面分析了很多基础性的概念。
可以更全面深入的理解 JavaScript 深层面的知识点。
1)函数作用域
就是作用域在一个 "Function" 里,属于这个函数的全部变量都可以在整个函数的范围内使用及复用。
- function foo(a) {
- var b = 2;
- function bar() {
- // ...
- }
- var c = 3;
- }
- bar(); // 失败
- console.log(a, b, c); // 三个全都失败
上面的 "foo" 函数内的几个标识符,放到函数外面访问就都会报错,。
2)立即执行函数表达式
在任意代码片段外部添加包装函数,可以将内部的变量和函数定义 "隐藏" 起来,外部作用域无法访问包装函数内部的任何内容。
例如上面的 bar、a 等几个标识符。这样能够保护变量不被污染。
在写插件的时候经常会用到立即执行函数表达式,为的就是保护里面的变量。
- var a = 2; (function foo() {
- var a = 3;
- console.log(a); // 3
- })();
- console.log(a); // 2
"foo" 中第一个 () 将函数变成表达式,第二个 () 执行了这个函数。
有一个专用术语:IIFE,代表立即执行函数表达式(Immediately Invoked Function Expression);
1. 进阶用法是把它们当作函数调用并传递参数进去
- (function IIFE(global) {
- var a = 3;
- console.log(a); // 3
- console.log(global.a); // 2
- })(window);
2. 一种变化的用途是倒置代码的运行顺序,在 CMD 或 AMD 项目中被广泛使用。
- (function IIFE(factory) {
- factory(window);
- })(function def(global) {
- var a = 3;
- console.log(a); // 3
- console.log(global.a); // 2
- });
JavaScript 不支持块作用域。
- for (var i = 0; i < 10; i++) {
- console.log(i);
- }
上面的代码中的 "i" 相当于下面的
- var i;
- for (i = 0; i < 10; i++) {
- console.log(i);
- }
但也有例外,"try/catch",catch 就是一个块作用域。
- try {
- undefined(); // 执行一个非法操作来强制制造一个异常
- } catch(err) {
- console.log(err); // 能够正常执行!
- }
- console.log(err); // ReferenceError: err not found
ES6 改变了现状,引入了新的 let 关键字,let 关键字可以将变量绑定到所在的任意作用域中(通常是 {..} 内部)。换句话说,let 为其声明的变量隐式地了所在的块作用域。
函数作用域和块作用域的行为是一样的,可以总结为:任何声明在某个作用域内的变量,都将附属于这个作用域。
1)编译与执行
变量和函数的所有声明都会在任何代码被执行前首先被处理,可以看下面的代码事例。
- a = 2;
- var a;
- console.log(a); //2
这段代码等价于:
- var a; //定义声明是在编译阶段进行
- a = 2; //赋值声明会被留在原地等待执行阶段
- console.log(a);
2)函数优先
函数会首先被提升,然后才是变量。
- foo(); // 1
- var foo;
- function foo() {
- console.log(1);
- }
- foo = function() {
- console.log(2);
- };
var foo 函数表达式尽管出现在 function foo() 的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。
而上面的代码相当于:
- function foo() {
- console.log(1);
- }
- foo(); // 1
- foo = function() {
- console.log(2);
- };
1)定义
当函数可以记住并访问所在的作用域时,就产生了闭包,即使函数是在当前作用域之外执行。
- function foo() {
- var a = 2;
- function bar() {
- console.log(a);
- }
- return bar;
- }
- var baz = foo();
- baz(); // 2 —— 这就是闭包的效果。
1. 将函数 "bar" 赋值给 "baz",执行 "baz",当前作用域并不在 "bar" 的作用域,但是可以执行。
2. 闭包还会阻止垃圾回收,当 "foo" 执行完后,内部作用域仍然存在。这样才能让 "baz" 执行。
2)将函数作为参数传递
- function foo() {
- var a = 2;
- function baz() {
- console.log(a); // 2
- }
- bar(baz);
- }
- function bar(fn) {
- fn(); //这就是闭包!
- }
把内部函数 baz 传递给 bar,当调用这个内部函数时(fn),它涵盖的 foo() 内部作用域的闭包就可以观察到了,因为它能够访问 a。
如果将函数当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。
在定时器、事件监听器、Ajax 请求、跨窗口通信、web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!
3)循环和闭包
- for (var i = 1; i <= 5; i++) {
- setTimeout(function timer() {
- console.log(i);
- },
- i * 1000);
- }
每次打印出来都将会是 6,延迟函数的回调会在循环结束时才执行,。
根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。
现在用闭包来实现每次打印不同的 i。
- for (var i = 1; i <= 5; i++) { (function(j) {
- setTimeout(function timer() {
- console.log(j);
- },
- j * 1000);
- })(i);
- }
IIFE 会通过声明并立即执行一个函数来创建作用域。setTimeout 中的回调可以记住当前的作用域,每个作用域中的参数 "j" 都是不同的。
来源: http://www.cnblogs.com/strick/p/5806427.html