name | title | tags | categories | info | time | desc | keywords | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
你不知道的JavaScript学习笔记(一) |
《你不知道的JavaScript》学习笔记(一) |
|
学习笔记 |
你不知道的JavaScript 第1章 作用域是什么 第2章 词法作用域 第3章 函数作用域和块作用域 |
2019/2/26 |
你不知道的JavaScript, 资料下载, 学习笔记, 第1章 作用域是什么, 第2章 词法作用域, 第3章 函数作用域和块作用域 |
|
资料下载地址(pdf压缩文件):
提取码: 7hik
本资料仅用于学习交流,如有能力请到各大销售渠道支持正版 !
本书上中下三卷共有762页正文可读内容
第一部分:作用域和闭包
JavaScript虽然被称为"动态"和"解释执行的语言",但它归根结底其实还是一门编译语言,而每一门编译语言在执行之前都会经历三个步骤,统称"编译":
- 分词/词法分析
- 解析/语法分析
- 代码生成
但与其他语言不同的是,JavaScript并不是先编译后执行,而是一边编译一边执行的,一般来说,编译一段语句到执行这段语句之间只有几微秒甚至更短的时间。
RHS查询:取到变量的值,比如说console.log(a)
中的a
变量就需要执行一次RHS查询
LHS查询:为一个变量赋值,比如说a = 2
语句就需要执行一次LHS查询,找到a
变量并将2赋予它。
本节描述了作用域、引擎和编译器在执行语句时的详细流程,emmm,虽然有点gay里gay气但还是蛮好理解的。
作用域是根据名称查找变量的一套规则,当一个块或函数在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套中继续查找,直到找到该变量或是抵达全局作用域为止。
当RHS查询无法找到变量时,会抛出ReferenceError
错误,提示该变量未定义。
而在执行LHS查询时,若程序执行在严格模式下,对未定义的变量进行赋值也会抛出错误,然而在非严格模式下,该操作会让引擎在全局作用域中创建一个具有该名称的变量。
作用域一般分为两种,词法作用域和动态作用域。大多数语言包括JavaScript使用的都是前者。
简单来说,词法作用域就是定义在词法阶段的作用域,也就是说该作用域是由你在写代码时将变量和块作用域卸载哪里来决定的。
作用域查找会在找到第一个匹配的标识符时停止,在多层的嵌套作用域中可以定义同名的标识符,这称为"遮蔽效应"。
使用某些特定的方法和机制可以欺骗词法作用域。但这种方式不仅危险,还会导致性能的下降
function foo (str, a) {
eval(str) // 欺骗
console.log(a, b)
}
var b = 2
foo('var b = 3', 1)
// 若不进行欺骗,输出本应是 1, 2
// 但此时实际输出为 1, 3
在以上的例子中,eval
函数插入了一段声明,使得词法作用域出现了遮蔽效应,外部的变量并没有被访问到,反而是定义了一个本不该被引用的变量b。
与eval
函数功能相似的还有以下几种:
setTimeout()
和setInterval()
方法的第一个参数传入字符串- 进行
new Function(..)
操作
使用with
操作符可以延展当前作用域,在当前作用域中套用其他任意作用域。但要注意的是,with
操作符并不能新增或更改其延展的作用域中没有的属性,因此很容易会造成变量被泄漏到全局作用域中的情况。
function foo (obj) {
with (obj) {
a = 2
}
}
var o1 = {a:3}
var o2 = {b:1}
foo(o1)
o1.a // 2 被with更改
foo(o2)
o2.a // undefined 由于o2中本身并没有a属性,所以无法新增和更改
window.a // 2 反而是将不应该定义的属性定义在了全局作用域上,造成了变量泄漏
过分的使用eval
和with
不仅会导致代码结构混乱,而且会大大增多查找词法作用域的操作,造成性能的降低。在真正的代码编写时,不要使用它们。
本章探讨哪些操作会创建一个作用域。
JavaScript中,每生成一个函数都会生成一个对应的作用域。
可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来隐藏它们,更好的符合最小授权原则。
最小授权/最小暴露原则:在软件设计中,应该最小限度地暴露必要内容,将其内部隐藏起来。
使用一个立即执行的函数表达式((function foo () {})()
)可以让你达到既不污染全局作用域,又能完成想要做的变量定义和方法执行的目的。
function () ...
没有名称和标识符,称为匿名函数表达式。函数表达式可以是匿名的,而函数声明则不可以(会报错)。
也称为IIFE形式,具体表现形式为(function foo () {})()
或(function foo () {}())
,这两种形式功能是完全一样的。
改形式除了实现上面所述的用途以外,还可以用来进行倒置代码
var a = 2
(function IIFE (def) {
def(window)
})(function def (global) {
var a = 3
console.log(a) // 3
console.log(global.a) // 2
})
在ES6之前,JavaScript是没有块作用域的。看似在块级作用域中定义的变量最终还是在函数作用域中执行。
for (var i = 0; i < 5; i++) {console.log(i)}
// 上面这段代码和下面这段代码作用是一毛一样的
var i = 0
for (i = 0; i < 5; i++) {console.log(i)}
这在ES6之前就会导致一个问题,开发者需要检查自己的代码,避免在作用范围外以外地使用(或复用)某些变量,而这是绝大多数其他语言中不会出现的问题。
当然,除了ES6以外,还有一些特殊的方法会产生块作用域,但很难用于正经开发上。
用with
是会产生一个块作用域的。
try/catch中的catch
语句也会生成一个块作用域。
try {
throw new Error('异常')
} catch (err) {
console.log(err)
}
console.lor(err) // 报错
上述代码中的err
变量就处于一个块作用域中,外部无法访问。
-
let
关键字可以将变量绑定到所在的任意作用域中。let
为其声明的变量隐式地声明了所在的块作用域。 -
用
let
进行的声明不会再块作用域中进行提升 -
块作用域还可以用于促进垃圾回收,优化代码,如下
function process (data) {} // 使用这种方式显式声明块作用域,可以告诉引擎对这部分的变量进行垃圾回收 { let someBigData = {} process(someBigData) } var btn = document.getElementById('btn') btn.onclick = function click (e) { console.log('button clicked') }
-
let
循环,更常用的循环方式
const
同样由ES6引入,同样可以用来创建块作用域变量,之后任何试图修改值得操作都会引起错误。