你不知道的Javascript(上卷)读书笔记之三 ---- 函数作用域与块作用域
1. 函数中的作用域
函数作用域的含义是指属于这个函数的全部变量都可以在整个函数范围内使用以及复用
2. 隐藏内部实现
函数经常使用于隐藏”内部实现”,可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来隐藏它们。
这种基于作用域的隐藏方法基于软件设计中的最小特权原则(最小授权/最小暴露原则),比如模块的API设计。
引申一下,如果变量和函数都放在全局作用域中,那么就会暴露过多的变量和函数,从而违背了最小特权原则,而这些变量本该是私有的,应该阻止外部访问的。额外的多余的访问权限可能被有意或者无意的以非预期的方式调用,从而导致超出了本来应该的使用条件,会使程序变得”危险”。这有点类似于Java的OOP中的封装,我们可以通过函数隐藏内部,从而封装好程序。
隐藏内部实现是为了规避冲突
为了保持变量或者函数的可读性,我们总是希望尽可能想要起更加简单的标识符。但是,在大型多人开发项目中,标识符越简单,越容易重名冲突。标识符越复杂,虽然不容易发生重名冲突,但是可读性会变弱,而且繁琐冗长的代码容易引起程序员的反感,特别是在API十分的繁杂的情况下。
同时,在软件设计过程中,存在某些情况要求使用同样的标识符名称,这种情况不是没有,恰恰相反,十分常见。(就比如Java的集合库,假如常用的集合类的添加方法,名称完全不一样,那真的是一件很恐怖的事情,这意味着我们需要耗费很多脑容量去记忆这些API)
在这种情况下,隐藏内部声明是唯一的最佳选择。可以使用Javascript Object 去存储标识符、封装成对象、并隐藏内部函数的具体实现。
a.全局命名空间
变量冲突的一个典型例子存在于全局作用域中。当程序加载了多个第三方库时,如果他没有妥善的将内部私有的变量或者函数隐藏起来,就会很容易引发冲突
这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象被用作库的命名空间,所有要暴露给外界的功能都会变成这个对象的属性。
b.模块管理
还有另外一种避免冲突的方法,它与现代的模块机制十分接近,这种方式并不是将库加载到全局作用域中,而是通过依赖管理器将库的标识符显示的导入到另外一个特定的作用域中.
3. 函数作用域
函数表达式和函数声明
函数声明进行调用:
function foo() {
var a = 3;
console.log(a);
}
foo();
函数表达式:
(function foo(){
var a = 3;
console.log(a);
})();
3.1 匿名和具名
如果你还是不理解函数表达式是什么意思,我们其实经常用到:
setTimeout(function() {
console.log(“I waited 1 second”);
}, 1000);
这叫匿名函数表达式。函数表达式是可以匿名的,函数声明则不可以。看起来函数表达式简单快捷,几近完美,但是它还是有缺点的:
A.匿名函数在栈追踪时,不会显示有意义的函数名,调试困难
B.由于没有函数名,因此函数在引用自身只能使用已经过期的arguments.callee 引用,一种例子是递归,还有一种例子是事件触发后事件监听器要解绑自身
C.匿名函数省略了对于代码理解比较重要的函数名,一个描述性的名称可以让代码不言自明。
3.2 立即执行函数表达式(IIFE)
给一个函数包含在()括号内部,因此成为了一个表达式,通过在末尾加上一个(),会使 得这个表达式立即执行。
IIFE的一个进阶用法就是把他们当作函数调用并且传递参数进去。
例如:
var a = 2;
(function() IIFE(global){
var a = 3;
console.log(a);
console.log(global.a);
})(window);
console.log(a);
IIFE有一种变化的用户是倒置代码的运行顺序,将要运行的函数放在第二位,在IIFE 执行之后当作参数传递进去。
var a = 2;
(function IIFE(def){
def(window);
})(function def(global){
var a = 3;
console.log(a);
console.log(global.a);
});
4 块作用域
4.1 with关键字是Js中为数不多的块作用域的一个例子,用with从对象中创建出的作用域仅仅在with声明中有效
4.2 try、catch
4.3 let
ES6提供了let关键字,提供了除了var之外的另外一种变量声明方式,它会将变量绑定在循环所在的块作用域上。
可以使用显式的{}块,显式地将变量使用let绑定在块作用域上
但是使用let声明的变量不会进行变量提升。
使用let 还有额外的好处:
a. 垃圾收集
function process(data) {
// 在这里做点有趣的事情
}
var someReallyBigData = { .. };
process( someReallyBigData );
var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt) {
console.log("button clicked");
}, /*capturingPhase=*/false );
click 函数的点击回调并不需要 someReallyBigData 变量。 理论上这意味着当 process(..) 执行后, 在内存中占用大量空间的数据结构就可以被垃圾回收了。 但是, 由于 click 函数形成了一个覆盖整个作用域的闭包, JavaScript 引擎极有可能依然保存着这个结构( 取决于具体 实现)。
块作用域可以打消这种顾虑, 可以让引擎清楚地知道没有必要继续保存 someReallyBigData 了:
function process(data) {
// 在这里做点有趣的事情
}
//在这个块中定义的内容可以销毁了!
{
let someReallyBigData = { .. };
process( someReallyBigData );
}
var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt){
console.log("button clicked");
}
为变量显式声明块作用域,并对变量进行本地绑定是非常有用的工具, 可以把它添加到你的代码工具箱中了。
b.let循环
for(let i = 0; i < 10; i++)
console.log(i);
与正常使用var变量的for循环不同,var变量声明可能会污染全局空间,将变量绑定在全局作用域上,使用let进行变量声明可以直接把变量绑定在块级作用域上。
4.4 const
除了 let 以外, ES6 还引入了 const, 同样可以用来创建块作用域变量, 但其值是固定的( 常量)。 之后任何试图修改值的操作都会引起错误。
你不知道的Javascript(上卷)读书笔记之三 ---- 函数作用域与块作用域的更多相关文章
- JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)
前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...
- JS闭包—你不知道的JavaScript上卷读书笔记(二)
关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...
- JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)
this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...
- 你不知道的javascript 上卷 读书笔记
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- JavaScript中的对象与原型—你不知道的JavaScript上卷读书笔记(四)
一.对象 对象可以通过两种形式定义:声明(文字)形式和构造形式.即: var myObj = { key: value // ... }; 或: var myObj = new Object(); m ...
- 《你不知道的javascript》读书笔记2
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...
- 《你不知道的JavaScript》读书笔记(一)作用域
名词 引擎:从头到尾负责整个 JavaScript 程序的 编译 及 执行 过程. 编译器:负责 语法分析 及 代码生成. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套 ...
- 《你不知道的JavaScript》读书笔记(二)词法作用域
JavaScript 采用的是 词法作用域 的工作模型. 定义 词法化:大部分标准语言编译器的第一个工作阶段叫词法化(单词化),这个过程会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词 ...
- 《你不知道的javascript》读书笔记1
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...
随机推荐
- 取SQL分组中某几行数据
常用的方法有:子查询.ROW_NUMBER.APPLY,总体感觉还是ROW_NUMBER比较直观 if OBJECT_ID('testGroup') is not null drop table te ...
- MySQL怎样存储IP地址
为什么要问如何存储IP 首先就来阐明一下部分人得反问:为什么要问IP得怎样存,直接varchar类型不就得了吗? 其实做任何程序设计都要在功能实现的基础上最大限度的优化性能.而数据库设计是程序设计中不 ...
- Java并发编程(二):JAVA内存模型与同步规则
一.Java内存模型(JMM) 它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.一个线程如何和何时能看到其他线程共享变量的值,以及在 ...
- ((void *) 0)的含义和void的一些细节
一.在c语言中,0是一个特殊的值,它可以表示:整型数值0,空字符,逻辑假(false).表示的东西多了,有时候不好判断.尤其是空字符和数字0之间. 为了明确的指出,0是空字符的含义,用用到了: (() ...
- Atitit.swt 线程调用ui控件的方法
Atitit.swt 线程调用ui控件的方法 1 SwingUtilities.invokeLater1 2 display.asyncExec方法1 3 display.timerExec(500 ...
- iOS7 SDK新特性
春风又绿加州岸.物是人非又一年.WWDC 2013 keynote落下帷幕,新的iOS开发旅程也由此开启.在iOS7界面重大变革的背后,开发人员们须要知道的又有哪些呢.同去年一样,我会先简单纵览地介绍 ...
- C++类成员指针(指向类成员的指针)
1.指向类的数据成员的指针: 声明格式如下: <类型说明符> <类名>::* <指针变量名>; 2.指向类的成员函数的指针: 声明格式如下: <类型说明符 ...
- [J2EE]MyBatis HelloWorld
一.MyBatis简单介绍 iBatis是apche的一个开源项目.2010年迁移到google code后改名为MyBatis,2013年前已到github.MyBatis是一个基于java的持久层 ...
- 把查询到结果合并放在一行 JOIN非union
select one.max,one.min,one.low sts,c.high ens,one.time from ( select a.max max,a.min min,b.low low,a ...
- CocosCreate 与 Netty 开发斗地主 (一步一步开发)
CocosCreate 与 Netty 开发斗地主 开发此教程的目的是为了教会大家基本的使用Netty,通过一个小例子来教会大家制作斗地主游戏服务器,采用WebSocket方式! 目前正在制作阶段, ...