《你不知道的JavaScript上卷》知识点整理与读书笔记

osc_jtviy1jl 2020-11-08 10:35:51
c++ javascript


之前被好友推荐过《你不知道的JavaScript》这一系列的书籍。上中下三本书都不算厚,内容也是比较独立。本来是打算一个月内看完这本上卷和下卷,可总是因为自己的懒惰读了一个月才断断续续读完上卷。不得不说单是粗略的读完上卷就让我很有收获,下面是对上卷部分的读书笔记,不是什么文档和教程,仅仅供自己学习记录,欢迎各位路过的大佬能指出理解错误的地方以及意见。

第一部分 作用域和闭包

第1章 作用域是什么

1.1编译原理

首先,我也是学习JavaScript一开始,找到资料里面就说JavaScript是一门“解释型”语言。但是其实JavaScript是一门编译语言。

在传统的编译语言中,一段源代码执行前会先经过一段叫做编译的操作,而编译一共有如下3个过程。

  • 分词/词法分析(Tokenizing/Lexing)
    这个过程将把编程语言的字符串分解为有意义的代码块,这些代码块被称为词法单元(token)
var a = 2;
//就会被拆解成 var、a、=、2、; 这些都是词法单元
//而空格是否属于此法单元取决于是否有意义,
//这里的var a = 2;中的任何一个空格都没有意义
  • 解析/语法分析(Parsing)
    把众多词法单元组成的词法单元流(数组)转换成一颗树的结构,叫抽象语法树(AbstractSyntaxTree,AST)
var a = 2;
//这句语句转化为树的结构是一个叫做VariableDeclaration

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4rnlCF0N-1604651680070)(./上img/抽象语法树.png)]

  • 代码生成
    将语法树转化为可执行的代码过程。

其他的语言的编译在构建前,而JavaScript在执行前也会进行这编译3部曲

1.2理解作用域

对var a = 2;处理时候,具体的编译器是这样工作的。

//1 遇到 var a 编译器会在当前作用域中的一个集合查询是否有a这个变量,
//如果有就继续进行第2步编译动作,
//如果没有会要求当前作用域在自己的这个集合中声明一个新的a变量
//2 遇到 a = 2 编译器为这条语句生成运行时候的代码,供运行时候使用。
//这时候编译器要在生成的代码中表达出 a=2这个意思,所以在生成代码时候,
//需要一个a变量,先去找当前的作用域中的集合是否含有这个变量,
//如果没有就去上一层找。直到找到为止,否者就是报错。

在这里注意的是在步骤2中,编译器会查找作用域集合中时候会有2种不同的查找方式,一种是LHS,一种是RHS。也就是左查询与右查询。这个和我在学习C++中左值右值时候,我觉得十分相识。也就是通过最原始的“=”进行理解,左边是被填充的,右边是去填充的。
在这里插入图片描述

var a = 2;//这里编译器生成代码时候,会去当前作用域集合中找a变量,然后赋值为2。
//你可以理解为是找到这片叫做a空间,然后填充上2这个数据,
//所以这里是左查询,也就是找到被填充的a空间
console.log(a)//这里编译器生成代码时候,会去当前作用域集合中找a变量,然后打印出来。
//你可以理解为是找到这个叫做a的变量里面的值,然后使用这个值,
//所以这里是右查询,也就是找到去填充(或者理解为取出来使用)的a空间里面本身的值!!!

1.3作用域嵌套

其实书中这部分的内容讲的就是作用域链,也就是对var a = 2;处理时候,具体的编译器工作的第二步中需要一个a变量,先去找当前的作用域中的集合是否含有这个变量,如果没有就去上一层找。直到找到为止,直到最顶层没有报错为止。这就是一直往上的查找就是顺着作用域链。

var b = 3
function foo(a){

console.log(a+b)
}
foo(2)//4
//这里的function内部是自己的函数作用域,function外面是全局作用域。
//function内部作用域没有b变量,为什么还能具体工作?
//就是因为function中没有,所以找上一级,然后上一级中就有b,就可以使用
//这种机制就是作用域嵌套,或者作用域链。
//我反正是理解为,子作用域可以使用父作用域,但是父作用域不可以反过来使用子作用域。
//当然这里的什么父、子作用域是个人为了理解,自己说的,不是什么术语。

1.5异常

前面都是顺着作用域链能查找成功的时候,但是我们也说过,如果找不到会报错。这里我们查找有2中情况。自然左右查找不到,报错是不一样的。
在这里插入图片描述

这里小小验证一下书上讲的左查询吧。

a = 2//这里没有var 声明。但是a=2是左查找,所以左查询热心的会在全局作用域中的集合里面创建一个空的a变量空间。
console.log(a)//右查找结果是2
"use strict"
a = 2//不好意思,这次左查询无法帮助生成一个空的a变量空间了,
//单单是这一句语句就会因为左查询失败,报ReferenceError
console.log(a)//右查找,肯定失败,这句本生也是右查找失败报ReferenceError,
//不过前一句本来就是错,压根也不会运行到这一句。

第2章 词法作用域

2.1词法阶段

首先词法作用域就是你写代码时候,书写的变量位置或者块作用域的位置就是词法作用域。而这个词法分析时候(也就前面的拆分语句var a = 2;为var、a、=、2、;),产生的作用域和写的时候一样。例如如下代码。

var a = 3;//这行语句是属于全局作用域的
function foo(a) {

//这里面是foo函数形成的块作用域
//写所以下面的b,bar(),以及自己的a。都是这个作用域的
//所以解析之后任然是满足这个关系
var b = a * 2;//这里的a是foo函数里面的,不是外面的var a = 3;里面的a
function bar(c) {

//这里面是bar函数形成的块作用域
//所以自己的c是属于这里 
console.log(a,b,c);
}
bar(b * 3);
}
foo(2);//2,4,12

所以词法作用域主要是体现了2点:

  • 一是一层一层往上的循着作用域找变量,这个案例中就是bar使用的a,b,c来自上一级的foo。
  • 二是,当子级的作用域有着与父级作用域同样的标识符(就是变量名、函数名)时候,子级的会覆盖(如果是知道方法重写,那么这里就理解为子类重写父类方法),这种叫做遮蔽效应。例如在foo作用域、以及子作用域中使用a时候,都是foo函数中的a,而不是foo作用域的父作用域中的var a = 3;中的a。当然,当bar中有a时候,bar中使用a时候,就会使用自己a,而覆盖foo(a)的a,与全局的var a = 3的a

2.2欺骗词法

首先记住,少使用欺骗词法,欺骗词法作用域会导致性能下降。使用eval与with关键字能实现这种。我这里就写个eval

首先eval()的用法是接受一串字符串,并把字符串的内容替换到调用的位置,有点抽象,请看代码:

function foo(str,a) {

//这里是foo形成的词法作用域
//先假装eval(str)不存在
//这里只有a与str是自己的,b是外面的
//所以调用foo("var b = 3;",1);应该是1,2的结果
//放开注释eval(str)
//输出为1,3
//因为eval("var b = 3;")等同于在这行eval位置替换为
//var b = 3;
//因此var b = 3中的b屏蔽外面的b 
//eval(str);
console.log(a,b);
}
var b =2;
foo("var b = 3;",1);//1,3

第3章 函数作用域和块作用域

3.1函数中的作用域

  • 函数作用域:属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)这种设计方案能充分利用JavaScript变量可以根据需要改变值类型的“动态”特性。

3.2隐藏内部实现

  • 最小暴露原则:在软件设计中,应该最小限度的暴露最少内容,而将其他内容隐藏起来。
  • 在JavaScript中的函数作用域就可以实现隐藏代码的效果
function doSomething(a) {

b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
function doSomethingElse(a) {

return a - 1;
}
var b;
doSomething(2);//15

很显然这和时候我们任然能够访问到b以及doSomethingElse()函数,但是目前就这些代码而言,b以及doSomethingElse()只是为了给doSomething()使用。因此我们不应该让外部也能够访问到这两个标识符。所以应该进行隐藏

function doSomething(a) {

function doSomethingElse(a) {

return a - 1;
}
var b;
b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
doSomething(2);//15

3.3函数作用域

尽管前面的使用函数包裹代码块达到了隐藏的意义,但是在某些时候任然是有一些缺点。一是用来包裹隐藏的函数名污染了全局变量,然后必须显示的调用函数名。但是幸好我们可以使用立即执行函数来解决。

(function foo() {

var a = 3;
console.log(a);
})();
console.log(a);
//这里就可以让函数当作一个表达式,而不是函数声明
//这里就没有让foo放出去污染全局,因为他不是声明
//这里最后紧紧跟了一个(),表示立即调用,因此不用显示使用标识符调用
//当然把最后的()放进去的效果也是一样的,仅仅是书写风格不同。如下
(function foo() {

var a = 3;
console.log(a);
}());
console.log(a);
  • 那么时候是函数表达式呢?

Good question

区分:书上的办法很简单,就是查看function关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果function是声明中的第一个词,那么就是一个函数声明,否者就是一个函数表达式。

上面的代码中(function foo(){…})()就不是function开头,这个时候就是作为表达式,而foo标识符就是被封锁在foo(){…}中…的部分中,所以就没有污染全局变量

  • 匿名和具名
    匿名函数就是没有名字的函数
setTimeout(function(){

console.log("I waited 1 second");
},1000);

这部分的片段代码中seTimeout中function()就是没有名字。所以就是匿名函数。

  • 注意:之后函数表达式才能拥有这种匿名函数,函数表达式没有函数名。请看前面的代码,这里可是函数表达式哈。

3.4块作用域

现在我们学会了词法作用域、函数作用域以及一些小的知识点。现在学习新的作用域、块作用域。

前面说过,使用函数作用域能够隐藏一段代码,而隐藏也就是具有隔离意思。把一段代码隔离到一个函数中,让其外界无法触碰。但是并不是使用外包函数就能解决。

//这行代码只是为了验证()中的var i,以及{}中的a,最后在外部均可以访问到这两个标识符
for(var i = 0; i < 10; i++){

console.log("for里面调用i",i);
var a = 5;
}
console.log("for外面调用for里面的a",a); //for外面调用for里面的a 5 
console.log("for外面for里面的i",i);//for外面for里面的i 10

下面是使用函数包裹

(function(){

for(var i = 0; i < 10; i++){

console.log("for里面调用i",i);
var a = 5;
}
}())
console.log("for外面调用for里面的a",a);//VM22:7 Uncaught ReferenceError: a is not defined 
console.log("for外面调用i",i);//Uncaught ReferenceError: i is not defined

是完成了封锁,可是这样情况并不简便,所以开发者为JavaScript提供了块作用域。我觉得其实就和完成了函数作用域一样的功能,封锁代码块在其{}中。

  • let

let声明的变量,是会和当前的作用域进行绑定。

for(let i = 0; i < 10; i++){

console.log("for里面调用i",i);
var a = 5;
}
console.log("for外面for里面的i",i);//Uncaught ReferenceError: i is not defined

恭喜,i已经绑定在for体内了,是不是方便很多了呢。

注意let i,不仅仅是把i绑定到了for循环的{}中,准确的说是绑定到了每一个迭代中,这点十分十分重要。后面闭包时候会再次提到这个事。同时let声明的标识符不会进行提升,这一点后面的提升相关部分会进行说明

  • const
    const声明的变量,是会和当前的作用域进行绑定,并且这个变量的值是固定的,不能再更改

  • try/cath
    没想到吧,居然每一个cath分支都是一个块作用域,不相信的话,可以动手试一试

第4章 提升

首先得出结论

  • 只有声明本身会被提升,而赋值或者其他运行的逻辑会留在原地。如果是提升改变的代码执行的顺序,会造成严重的破坏
  • 声明包括变量声明以及函数声明
  • 每个作用域都会进行提升
  • 函数和变量提升时候,函数会优先。因为函数是一等公民
  • let以及const声明的标识符不会提升
foo();//1
var foo;
function foo() {

console.log(1);
}
foo = function () {

console.log(2);
}

上面代码会被引擎理解为如下形式:

function foo() {

console.log(1);
}
foo();//1
foo = function () {

console.log(2);
}
foo();//2

所以函数是优先提升的。

第5章 作用域和闭包

5.1 总结

我喜欢开篇,直接就总结完。

闭包产生的2种情况

  • 当函数作为另一个函数的参数
  • 函数作为返回值返回
function foo() {

var a = 2;
function bar() {

console.log(a);
}
return bar;
}
var baz = foo();
baz();//2 朋友,这就是闭包效果

5.2 循环和闭包

要说明闭包,for循环是一个常见的例子

for ( i = 1; i <= 5; i++) {

setTimeout(function timer(){

console.log(i);
}, i*1000);
}

这段代码再运行时候会每秒一次的频率输出5次6.

延迟函数的回调会在全部循环迭代结束的之后时候进行调用(请查询宏任务、微任务相关知识点),而不是每次迭代时候调用。所以最后调用i,但是i是公共的,并且值为最后一个循环决定的6。所以结果是5次6

那怎么给每个迭代的版本获取一个实时的i,满足哪怕是最后循环迭代完再调用定时函数,但是每个定时函数都是调用自己版本的,而不是调用最后的公用6呢?

那就是每次循环迭代时候,我们给每一个迭代都绑定一个i。如下所示,我们使用let让每一个i都再内部迭代进行绑定。

for(let i = 1; i <= 5; i++){

setTimeout(function timer(){

console.log(i);
},i*1000);
}

5.3 模块

在js中的模块也是和闭包息息相关。

模块:

  • 必须要有外部的封闭函数,该函数必须要被调用一次
  • 封闭的函数至少要返回一个内部函数
  • 使用立即执行函数配合有奇效
var foo = (function(){

var something = "cool";
var another = [1,2,3];
function doSomething() {

console.log(something);
}
function doAnother() {

console.log(another.join("!"));
}
return {

doSomething,
doAnother
}
})();
foo.doSomething();//cool
foo.doAnother();//1!2!3

第二部分 this和对象原型

第1章 关于this

第2章 this全面解析

第3章 对象

第4章 混合对象“类”

第5章 原型

第6章 行为委托

未完待续,下个周六整理完第二部分

版权声明
本文为[osc_jtviy1jl]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4286896/blog/4707978

  1. [front end -- JavaScript] knowledge point (IV) -- memory leakage in the project (I)
  2. This mechanism in JS
  3. Vue 3.0 source code learning 1 --- rendering process of components
  4. Learning the realization of canvas and simple drawing
  5. gin里获取http请求过来的参数
  6. vue3的新特性
  7. Get the parameters from HTTP request in gin
  8. New features of vue3
  9. vue-cli 引入腾讯地图(最新 api,rocketmq原理面试
  10. Vue 学习笔记(3,免费Java高级工程师学习资源
  11. Vue 学习笔记(2,Java编程视频教程
  12. Vue cli introduces Tencent maps (the latest API, rocketmq)
  13. Vue learning notes (3, free Java senior engineer learning resources)
  14. Vue learning notes (2, Java programming video tutorial)
  15. 【Vue】—props属性
  16. 【Vue】—创建组件
  17. [Vue] - props attribute
  18. [Vue] - create component
  19. 浅谈vue响应式原理及发布订阅模式和观察者模式
  20. On Vue responsive principle, publish subscribe mode and observer mode
  21. 浅谈vue响应式原理及发布订阅模式和观察者模式
  22. On Vue responsive principle, publish subscribe mode and observer mode
  23. Xiaobai can understand it. It only takes 4 steps to solve the problem of Vue keep alive cache component
  24. Publish, subscribe and observer of design patterns
  25. Summary of common content added in ES6 + (II)
  26. No.8 Vue element admin learning (III) vuex learning and login method analysis
  27. Write a mini webpack project construction tool
  28. Shopping cart (front-end static page preparation)
  29. Introduction to the fluent platform
  30. Webpack5 cache
  31. The difference between drop-down box select option and datalist
  32. CSS review (III)
  33. Node.js学习笔记【七】
  34. Node.js learning notes [VII]
  35. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  36. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  37. 【JQuery框架,Java编程教程视频下载
  38. [jQuery framework, Java programming tutorial video download
  39. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  40. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  41. 【Vue,阿里P8大佬亲自教你
  42. 【Vue基础知识总结 5,字节跳动算法工程师面试经验
  43. [Vue, Ali P8 teaches you personally
  44. [Vue basic knowledge summary 5. Interview experience of byte beating Algorithm Engineer
  45. 【问题记录】- 谷歌浏览器 Html生成PDF
  46. [problem record] - PDF generated by Google browser HTML
  47. 【问题记录】- 谷歌浏览器 Html生成PDF
  48. [problem record] - PDF generated by Google browser HTML
  49. 【JavaScript】查漏补缺 —数组中reduce()方法
  50. [JavaScript] leak checking and defect filling - reduce() method in array
  51. 【重识 HTML (3),350道Java面试真题分享
  52. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  53. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  54. [re recognize HTML (3) and share 350 real Java interview questions
  55. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  56. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  57. 【重识 HTML ,nginx面试题阿里
  58. 【重识 HTML (4),ELK原来这么简单
  59. [re recognize HTML, nginx interview questions]
  60. [re recognize HTML (4). Elk is so simple