你不知道的JavaScript 上卷 Part1
这篇博客躺在我的草稿箱里有一阵子了,差点给遗忘了哈哈。
前言
最近开始喜欢读一些书,从书中找答案,在阅读中查漏补缺。
记得小学初中时候最爱看书了,如今却不知怎的,习惯性从网络中摄取知识,搜寻到的东西真真假假,甚至很多大同小异,读一些书,还是很有必要的。
《你不知道的JavaScript》上卷 直面当前 JavaScript 开发人员不求甚解的大趋势,嗯,说的就是我这种人,不求甚解,很多东西好像知道,问深了,又好像什么都不知道。
好了,长话短说,以便再次阅读,我阅读中做了以下笔记,希望帮到自己,同时也能帮到其他和我一样一直不求甚解的开发人员。
当然,对内容感兴趣的可以自己去看一下书,说不准有哪个地方重要但是我遗漏了哈哈。
文章目录
- 前言
- 第一部分 作用域与闭包
- 一、作用域是什么
- 1、编译原理
- 2、引擎、编译器、作用域
- 3、以 `var a = 2;`为例,处理过程
- 4、编译器的术语:LHS查询、RHS查询
- 5、作用域嵌套
- 6、异常
- 二、词法作用域
- 1、概念
- 2、查找
- 3、欺骗词法
- 三、函数作用域和块作用域
- 1、隐藏
- 2、函数作用域
- 3、匿名函数、行内函数、立即执行函数表达式
- 4、块作用域
- 四、提升
- 五、作用域闭包
- 1、闭包
- 2、循环和闭包
- 3、模块
第一部分 作用域与闭包
一、作用域是什么
1、编译原理
尽管通常将 JavaScript 归类为 “ 动态 ” 或 “ 解释执行 ” 语言,但事实上它是一门编译语言。但与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植。
尽管如此,JavaScript 引擎进行编译的步骤和传统的编译语言非常相似,在某些环节可能比预想的要复杂,例如,在下述的 语法分析和代码生成阶段 有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化 等。
在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。
- 分词/词法分析(Tokenizing/Lexing)
将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。例如var a = 2;
通常会被分解成var、a、=、2 、;
这些词法单元。 - 解析/语法分析(Parsing)
将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(AST) - 代码生成
将 AST 转换为可执行代码。简单来说就是有某种方法可以将var a = 2;
的 AST 转化为一组机器指令,用来创建一个叫作a
的变量(包括分配内存等),并将一个值储存在a
中。
2、引擎、编译器、作用域
- 引擎:从头到尾负责整个 JavaScript 程序的编译及执行过程。
- 编译器:负责语法分析及代码生成
- 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识付的访问权限。
3、以 var a = 2;
为例,处理过程
- 遇到
var a
,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。- 如果是,编译器会忽略该声明,继续进行编译;
- 如果否,它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为
a
- 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理
a = 2
这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作a
的 变量。- 如果是,引擎就会使用这个变量;
- 如果否,引擎会继续查找该变量。
- 如果引擎最终找到了
a
变量,就会将 2 赋值给它。否则引擎就会抛出异常
4、编译器的术语:LHS查询、RHS查询
- LHS 查询:试图找到变量的容器本身,从而可以对其赋值。
- 例如
a = 2;
对a
的引用是 LHS 引用,因为实际上我们并不关心当前的值是什么,只是想要为=2
这个赋值操作找到一个目标。 - 可以理解为找
a
的地址。
- 例如
- RHS 查询:简单地查找某个变量的值。
- 例如
console.log( a );
对a
的引用是一个 RHS 引用,因为这里 a 并没有赋予任何值,而是需要查找并取得a
的值,才能将值传递给console.log(..)
。 - 可以理解为找
a
的值。
- 例如
书中又举了个例子,如下:
function foo(a) {console.log(a); // 2
}
foo(2);
其中foo(..)
的调用需要对foo
进行 RHS引用,即需要查找并取得foo
的值;
在 2 被当作参数传递给 foo(..)
函数时,参数a
被(隐式地)分配值 2,需要进行一次 LHS 查询;
console. log(..)
的执行需要对 console
对象进行 RHS 查询,并且检查得到的值中是否有一个叫作 log 的方法;
最后需要对a
进行的 RHS 引用,并且将得到的值传给了 console.log(..)
;
如果将函数声明 function foo(a) {...}
概念化为普通的变量声明 和赋值,比如 var foo;foo = function(a) {...}
。如果这样理解,这个函数声明将需要进行 LHS 查询。
作者留下了个小测验:
function foo(a) { var b = a;return a + b;
}
var c = foo( 2 );
- 找出所有的LHS查询(这里有3处)
c = ..;
、a = 2
(隐式变量分配)、b = ..
- 找出所有的RHS查询(这里有4处)
foo(2..)
、= a;
、a ..
、.. b
5、作用域嵌套
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。
引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找,直到找到该变量。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。
6、异常
如果 RHS 查询在所有嵌套的作用域中找不到目标变量,引擎就会抛出ReferenceError
异常。
非 “严格模式” 下,如果 LHS 查询时也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎。
严格模式下 ,并不会创建并返回一个全局变量,引擎会抛出ReferenceError
异常。
如果找到了目标变量,但是对这个变量的值进行不合理的操作, 比如试图对一个非函数类型的值进行函数调用,引擎会抛出TypeError
异常。
总的来说,ReferenceError
同作用域判别失败相关,TypeError
则代表作用域判别成功了,但是对结果的操作是非法或不合理的。
二、词法作用域
1、概念
作用域共有两种主要的工作模型。
- 词法作用域:也叫静态作用域,是定义在词法阶段的作用域,是由写代码时将变量和块作用域写在哪里来决定的。它的作用域在写代码或者定义时确定,不会改变。
- 动态作用域:在运行时确定。
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
举个例子来做个区分。
function foo() {console.log(a); // 动态作用域是 3,词法作用域是 2
}
function bar() {var a = 3;foo();
}
var a = 2;
bar();
在动态作用域下,因为当foo()
无法找到 a
的变量引用时,会顺着调用栈在调用 foo()
的地 方查找a
,而不是在嵌套的词法作用域链中向上查找。由于foo()
是在 bar()
中调用的, 引擎会检查bar()
的作用域,并在其中找到值为 3 的变量 a
。
而词法作用域让 foo()
中的 a
通过 RHS
引用找到了全局作用域中的a
,因此会输出 2。
重点:
需要明确的是,JavaScript 并不具有动态作用域,它只有词法作用域。
2、查找
如下图所示,有三个逐级嵌套的作用域。
- ①包含着整个全局作用域,其中只有一个标识符:foo
- ②包含着 foo 所创建的作用域,其中有三个标识符:a、bar 和 b
- ③包含着 bar 所创建的作用域,其中只有一个标识符:c
引擎执行 console.log(..)
声明,并查找a
、b
和 c
三个变量的引用。
它首先从最内部的 bar(..)
函数作用域开始查找。在这找到了 c
,引擎便使用c
这个引用。
无法找到a
、b
,会去上一级到所嵌套的 foo(..)
的作用域中继续查找,找到后引擎便使用a
、b
这两个引用。
作用域查找始终从运行时所处的最内部作用域开始,逐级向外进行,直到遇见第一个匹配的标识符为止。即,作用域查找会在找到第一个匹配的标识符时停止。
在多层的嵌套作用域中可以定义同名的标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。
全局变量会自动成为全局对象(比如浏览器中的 window 对象)的属性,因此可以间接地通过对全局对象属性的引用来对其进行访问,如window.a
。这样便可以访问那些被同名变量所遮蔽的全局变量,但非全局的变量如果被遮蔽了,无论如何都无法被访问到。
词法作用域查找只会查找一级标识符,比如 a
、b
和 c
。如果代码中引用了 foo.bar.baz
, 词法作用域查找只会试图查找 foo 标识符,找到这个变量后,对象属性访问规则会分别接管对 bar 和 baz 属性的访问。
3、欺骗词法
JavaScript 中有两种机制可以实现 在运行时修改(也可以说欺骗)词法作用域的目的。分别是:eval(..)
和 with
。
eval(..)
函数如果接受了含有一个或多个声明的“ 代码 ” 字符串,就会修改其所处的词法作用域。
with
声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。
这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,因为引擎只能谨慎地认为这样的优化是无效的。使用这其中任何一个机制都将导致代码运行变慢,导致性能下降,且二者在严格模式下基本被禁用。不推荐使用。
(1)eval
eval(..)
函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。如下面的示例。
在执行 eval(..)
之后的代码时,引擎并不 “ 知道 ” 或并不 “ 在意 ” 前面的代码是以动态形式插入进来,并对词法作用域的环境进行修改的,引擎只会如往常地进行词法作用域查找。
由此可以理解 eval(..)
是如何通过代码欺骗和假装成词法期代码就在那,来实现修改词法作用域环境的。
示例:
function foo(str, a) {eval(str); // 欺骗! console.log( a, b );
}
var b = 2;
foo("var b = 3;", 1); // 1, 3
eval(..)
调用中的 var b = 3;
这段代码会被当作本来就在那里一样来处理。由于这段代码声明了一个新的变量 b
,这段代码实际上在 foo(..)
内部创建了一个变量b
,并遮蔽了外部作用域中的同名变量,因此它对已经存在的 foo(..)
的词法作用域进行了修改。
在严格模式下,eval(..)
在运行时有自己的词法作用域,意味着其中的声明无法修改所在的作用域。
(2)with
with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
比如:
var obj = {a: 1,b: 2,c: 3
};
// 单调乏味的重复 "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 简单的快捷方式
with (obj) {a = 3;b = 4;c = 5;
}
with
可以将一个 没有或有多个属性 的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符。
尽管 with
块可以将一个对象处理为词法作用域,但是这个块内部正常的 var
声明并不会被限制在这个块的作用域中,而是被添加到 with 所处的函数作用域中。
示例:
function foo(obj) {with (obj) {a = 2;}
}
var o1 = {a: 3
};
var o2 = {b: 3
};
foo(o1);
console.log(o1.a); // 2foo(o2);
console.log(o2.a); // undefinedconsole.log(a); // 2 a 被泄漏到全局作用域上了
可以这样理解,当我们传递 o1
给 with
时, with
所声明的作用域是 o1
,而这个作用域中含有一个同 o1.a
属性相符的标识符。但当我们将 o2
作为作用域时,其中并没有 a
标识符, 因此进行了正常的 LHS 标识符查找。o2
的作用域、foo(..)
的作用域和全局作用域中都没有找到 a
,因此当 a=2
执行时,自动创建了一个全局变量(非严格模式)。
三、函数作用域和块作用域
1、隐藏
可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域 来“隐藏”它们。如下所示:
var a = 2;
function foo() { var a = 3; console.log(a); // 3
}
console.log(a); // 2
一方面可以在功能性和最终效果没有受影响的情况下,在设计上将具体内容私有化,另一方面可以避免同名标识符之间的冲突。
但是这样也会有一些问题,必须声明一个具名函数,这个具名函数名称本身“污染”了所在作用域。其次,必须显式地通过函数名调用这个函数才能运行其中的代码。
好在JavaScript 提供了能够同时解决这两个问题的方案。
var a = 2;
(function foo() {var a = 3;console.log(a); // 3
})();
console.log(a); // 2
(function foo(){ .. })
作为函数表达式意味着foo
只能在..
所代表的位置中被访问,外部作用域则不行。foo
变量名被隐藏在自身中意味着不会非必要地污染外部作用域。
2、函数作用域
是指属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。
3、匿名函数、行内函数、立即执行函数表达式
函数表达式可以是匿名的,函数声明则不可以省略函数名。
匿名函数表达式:例如setTimeout( function() {}, 1000 );
因为 function()..
没有名称标识符。它有几个缺点,比如省略了对于代码可读性/可理解性很重要的函数名、在栈追踪中不会显示出有意义的函数名,使得调试很困难等缺点。
行内函数表达式:例如setTimeout( function timeoutHandler() {}, 1000 );
给函数表达式指定一个函数名
立即执行函数表达式:术语IIFE
,比如(function foo(){ .. })();
第一个( )将函数变成表达式,第二个( )执行了这个函数。另一个改进的形式是(function(){ .. }())
,二者的区别:
- 第一种形式中函数表达式被包含在 ( ) 中,然后在后面用另一个 () 括 号来调用。
- 第二种形式中用来调用的 () 括号被移进了用来包装的 ( ) 括号中。
- 这两种形式在功能上是一致的。
IIFE
的一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去。 例如:
var a = 2;
(function IIFE(global) {var a = 3;console.log(a); // 3 console.log(global.a); // 2
})(window);
console.log(a); // 2
4、块作用域
块作用域将代码从在函数中隐藏信息扩展为在块中隐藏信息。
(1)with
上面内容讨论过的with
同时也是块作用域的一种形式,用 with
从对象中创建出的作用域仅在 with
声明中而非外部作用域中有效。
(2)try/catch
JavaScript 的 ES3 规范中规定 try/catch
的 catch
分句会创建一个块作
用域,其中声明的变量仅在 catch
内部有效。
(3)let
ES6新引入的let
关键字可以将变量绑定到所在的任意作用域中(通常是 {..}
内部),换句话说,let
为其声明的变量隐式地附加在了所在的块作用域。如下示例:
var foo = true;
if (foo) {let bar = foo * 2;console.log(bar);
}
console.log(bar); // ReferenceError
如果这样,在开发和修改代码的过程中,如果没有密切关注哪些块作用域中有绑定的变量,并且习惯性地移动这些块或者将其包含在其他的块中,就会导致代码变得混乱。
为块作用域显式地创建块可以部分解决这个问题,使变量的附属关系变得更加清晰。如下示例:
var foo = true;
if (foo) {{let bar = foo * 2;console.log(bar);}
}
console.log(bar); // ReferenceError
只要声明是有效的,在声明中的任意位置都可以使用{ .. }
括号来为let
创建一个用于绑定的块。在这个例子中,我们在 if 声明内部显式地创建了一个块,如果需要对其进行重 构,整个块都可以被方便地移动而不会对外部 if 声明的位置和语义产生任何影响。
补充一个let
可以发挥优势的典型例子:let
循环。
for (let i = 0; i < 10; i++) {console.log(i);
}
console.log(i); // ReferenceError
for 循环头部的let
不仅将i
绑定到了for
循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。
下面通过另一种方式来说明每次迭代时进行重新绑定的行为:
{let j;for (j = 0; j < 10; j++) {let i = j; // 每个迭代重新绑定console.log(i);}
}
(4)const
除了let
以外,ES6 还引入了const
,同样可以用来创建块作用域变量,但其值是固定的,是个常量,之后任何试图修改值的操作都会引起错误。
四、提升
JavaScript 会将var a = 2;
看成两个声明:var a;
和a = 2;
。第一个定义声明是在编译阶段进行的,第二个赋值声明会被留在原地等待执行阶段。
这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动” 到了最上面。这个过程就叫作提升。
重点1:
函数声明会被提升,但是函数表达式却不会被提升。
函数声明提升示例:
foo();
function foo() {console.log(a); // undefined var a = 2;
}
上面的代码实际上会被理解为下面的形式:
function foo() {var a;console.log(a); // undefineda = 2;
}
foo();
函数表达式不会提升示例:
foo(); // 这里不是 ReferenceError, 而是 TypeError
var foo = function bar() { // ...
};
变量标识符foo()
被提升并分配给所在作用域,因此不会导致ReferenceError
。但是foo
此时并没有赋值(如果它是一个函数声明会赋值)。foo()
由于对undefined
值进行函数调用而导致非法操作, 因此抛出 TypeError
异常。
重点2:
函数优先:函数声明和变量声明都会被提升。但是函数会首先被提升,然后才是变量。
示例:
foo(); // 1
var foo;
function foo() {console.log(1);
}
foo = function () {console.log(2);
};
会输出 1 而不是 2,是因为代码会被引擎理解为如下形式:
function foo() {console.log(1);
}
foo(); // 1
foo = function () {console.log(2);
};
虽然var foo
出现在function foo()...
的声明之前,但它是重复的声明,因此被忽略了,因为函数声明会被提升到普通变量之前。
尽管重复的var
声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。如下示例:
foo(); // 3
function foo() {console.log(1);
}
var foo = function () {console.log(2);
};
function foo() {console.log(3);
}
五、作用域闭包
1、闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
如下示例,清晰的展示了闭包:
function foo() {var a = 2;function bar() {console.log(a);}return bar;
}
var baz = foo();
baz(); // 2 —— 这就是闭包的效果
函数bar()
的词法作用域能够访问 foo()
的内部作用域,然后我们将 bar()
函数本身当作一个值类型进行传递。在这个例子中,我们将 bar
所引用的函数对象本身当作返回值。在foo()
执行后,其返回值,也就是内部的 bar()
函数,赋值给变量 baz
并调用 baz()
,实际上只是通过不同的标识符引用调用了内部的函数bar()
,显然bar()
可以被正常执行,它是在自己定义的词法作用域以外的地方执行的。
我们知道引擎有垃圾回收器用来释放不再使用的内存空间,但是闭包可以阻止。
事实上这是因为bar()
所声明的位置使它拥有涵盖 foo()
内部作用域的闭包,使得该作用域能够一直存活,以供 bar()
在之后任何时间进行引用,所以内部作用域依然存在,bar()
本身在使用这个内部作用域,所以没有被回收。
书中有这样一句话:bar()
依然持有对该作用域的引用,而这个引用就叫作闭包。
IIFE 是最常用来创建可以被封闭起来的闭包的工具,本身并不会真的使用闭包。
2、循环和闭包
面试常问:如何输出数字 1~5,每秒一次,每次一个?
比如下面这段代码在运行时会以每秒一次的频率输出五次 6,这是因为循环的终止条件是i
为6,当定时器运行时即使执行的是setTimeout(.., 0)
,尽管循环中的五个函数是在各个迭代中分别定义的, 但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i
,所以所有函数共享一个 i
的引用,又因为所有的回调函数是在循环结束后才会被执行,所以会每次输出一个 6。
for (var i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i);}, i * 1000);
}
上面的代码显然无法达到要求,我们可以用 IIFE 可以实现。
for (var i = 1; i <= 5; i++) {(function () {var j = i;setTimeout(function timer() {console.log(j);}, j * 1000);})();
}
for (var i = 1; i <= 5; i++) {(function (j) {setTimeout(function timer() {console.log(j);}, j * 1000);})(i);
}
上面两种写法均可,这是因为在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,它有自己的变量,可以用来在每个迭代中储存 i
的值,这样每个迭代中都会含有一个具有正确值的变量供我们访问。
当然我们也可以用ES6新增的 let
实现,如下代码。这是因为for 循环头部的 let 声明会有一个特殊的行为,这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明,随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
for (let i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i);}, i * 1000);
}
3、模块
function CoolModule() {var something = "cool";var another = [1, 2, 3];function doSomething() {console.log(something);}function doAnother() {console.log(another.join(" ! "));}return {doSomething: doSomething,doAnother: doAnother};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
上面这个模式在 JavaScript 中被称为模块。
最常见的实现模块模式的方法通常被称为模块暴露, 这里展示的是其变体。
首先,CoolModule()
只是一个函数,必须要通过调用它来创建一个模块实例。如果不执行外部函数,内部作用域和闭包都无法被创建。
其次,CoolModule()
返回一个用对象字面量语法{ key: value, ... }
来表示的对象。这个返回的对象中含有对内部函数而不是内部数据变量的引用。我们保持内部数据变量是隐藏且私有的状态。可以将这个对象类型的返回值看作本质上是模块的公共 API。这个对象类型的返回值最终被赋值给外部的变量 foo,然后就可以通过它来访问 API 中的 属性方法,比如 foo.doSomething()
。
从模块中返回一个实际的对象并不是必须的,也可以直接返回一个内部函数。jQuery 和 $ 标识符就是 jQuery 模块的公 共 API,但它们本身都是函数(由于函数也是对象,它们本身也可以拥有属性)。
模块模式需要具备两个必要条件。
- 必须有外部的封闭函数,该函数必须至少被调用一次,每次调用都会创建一个新的模块实例。
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
import
可以将一个模块中的一个或多个 API 导入到当前作用域中,并分别绑定在一个变量上。module
会将整个模块的 API 导入并绑定到一个变量上。export
会将当前模块的一个标识符(变量、函数)导出为公共 API。
你不知道的JavaScript 上卷 Part1相关推荐
- 你不知道的javascript上卷
你不知道的javascript上卷 作用域 javascript是一门编译语言,它不是提前编译的,编译结果也不能在分布式系统中移植.编译的步骤一般如下: 分词/词法分析 词法分析是有状态的判断一个分词 ...
- 【你不知道的JavaScript上卷】——作用域与闭包
原文: [你不知道的JavaScript上卷]--作用域与闭包 JS语言万变不离其宗,其中最常用.最重要的也就是常用的几个大概念.数据类型.作用域.原型链.闭包.this指针.异步,不同的人理解不一样 ...
- JS闭包—你不知道的JavaScript上卷读书笔记(二)
关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...
- 《你不知道的JavaScript上卷》知识点整理与读书笔记
各位路过的的大佬.求关注.求点赞.谢谢 第一部分 作用域和闭包 第1章 作用域是什么 1.1编译原理 1.2理解作用域 1.3作用域嵌套 1.5异常 第2章 词法作用域 2.1词法阶段 2.2欺骗词法 ...
- 你不知道的JavaScript 上卷读书笔记
看了<你不知道的JavaScript 上>,为了防止自己忘记,特此记下与我而言的部分重点 任何足够先进的技术都和魔法无异. --Arthur C. Clarke 作用域和闭包 编译原理 分 ...
- 200530你不知道的JavaScript[上卷]所有小结汇总
JS上卷整理 说点啥 1504的书,现在(2005)才想起好好看,过去5年零1个月了,证明自己的技术能力真是水了5年多.抓紧补齐吧. S11 表示 <不知道系列> 上卷 第一部分 第一章 ...
- 你不知道的JavaScript上卷(一)
第一章 作用域 存储和访问变量,几乎是所有编程语言的基本功能之一.但如何将变量引入,如何存储,如何查找等这些问题,就需要一套设计良好的规则进行管理.这套规则则被称为作用域 1.1编译原理 尽管通过将J ...
- 你不知道的JavaScript上卷-作用域和闭包
1. LHS引用与RHS引用的区别: RHS:取到源值-得到某某的值 LHS:谁是赋值操作的源头-给谁赋值 function foo(a) {var b = a;return a + b; } var ...
- #你不知道的javascript上卷# 总结
上卷看完了 全程下来:晦涩难懂,收货颇丰 虽然具体回忆倒不知道看了什么知识点,但是会感觉眼前的代码亮堂了很多 这本书最大的特点就是:读它得跟读文言文一样,一句话得需要好几遍再加上思考才会明白 有好几次 ...
最新文章
- 002 PECompact 1.84
- 怎么在VS监视DataSet类型的数据
- mysql主从配置原理_MySQL主从复制原理
- 论文浅尝 - EMNLP2020 | 通过词重排序跨语言解析
- python 长度queue_python:常见的数据结构
- Cordova - 解决升级NDK之后无法编译!
- Bootstrap创建按钮下拉菜单
- C语言栈的面试题,[面试题]EMC易安信-C语言函数堆栈的思考
- SSL之父称SSL不会因被攻击而失去生命力
- 在ubuntu 20.04 上 安装 onnxruntime CUDA
- 绿幕抠图与无绿幕抠图区别
- java gb28181网关_视频网关GB28181协议转换解决方案
- java面试简历项目经验,java面试题项目中的难点
- 【最全解决方案】WPS软件复制PDF后粘贴到Word格式混乱,如图,如何解决?
- java7 pc6_GitHub - ShingmoYeung/oss-sdk-java at ec7c09757dcbcb2b934f46b12ab71f3e14d2df72
- java下的Http多线程下载与断点续传分析【转自酷勤网】
- bootstrap编写响应式页面
- ASAN中无崩溃测试方案实现
- Unity做360度的全景照片
- 转载 钽电容起火、钽电容爆炸、钽电容烧了