JS上卷整理

说点啥

1504的书,现在(2005)才想起好好看,过去5年零1个月了,证明自己的技术能力真是水了5年多。抓紧补齐吧。

  • S11 表示 《不知道系列》 上卷 第一部分 第一章
  • Z11 表示 《不知道系列》 中卷 第一部分 第一章
  • X11 表示 《不知道系列》 下卷 第一部分 第一章

S11-作用域是什么

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对 变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。

赋值操作符会导致 LHS 查询。=操作符或调用函数时传入参数的操作都会导致关联作用域 的赋值操作。

JavaScript 引擎首先会在代码执行前对其进行编译,在这个过程中,像 var a = 2 这样的声 明会被分解成两个独立的步骤:

  • 1.首先,var a 在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。
  • 2.接下来,a = 2 会查询(LHS 查询)变量 a 并对其进行赋值。

LHS 和 RHS 查询都会在当前执行作用域中开始,如果有需要(也就是说它们没有找到所 需的标识符),就会向上级作用域继续查找目标标识符,这样每次上升一级作用域(一层 楼),最后抵达全局作用域(顶层),无论找到或没找到都将停止。

不成功的 RHS 引用会导致抛出 ReferenceError 异常。不成功的 LHS 引用会导致自动隐式 地创建一个全局变量(非严格模式下),该变量使用 LHS 引用的目标作为标识符,或者抛 出 ReferenceError 异常(严格模式下)。

function foo(a) {var b = a;return a + b;
}
var c = foo( 2 );1. 找出所有的 LHS 查询(这里有 3 处!)
2. 找出所有的 RHS 查询(这里有 4 处!)
  • c = …;、a = 2(隐式变量分配)、b = …
  • foo(2…、= a;、a …、… b

S12-词法作用域是什么

词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段 基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对它 们进行查找。

JavaScript 中有两个机制可以“欺骗”词法作用域:eval(…) 和 with。前者可以对一段包 含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在 运行时)。后者本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作 用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)。

这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,因为引擎只能谨慎地认 为这样的优化是无效的。使用这其中任何一个机制都将导致代码运行变慢。不要使用它们。

S13-函数作用域和块级作用域

函数是 JavaScript 中最常见的作用域单元。本质上,声明在一个函数内部的变量或函数会 在所处的作用域中“隐藏”起来,这是有意为之的良好软件的设计原则。

但函数不是唯一的作用域单元。块作用域指的是变量和函数不仅可以属于所处的作用域, 也可以属于某个代码块(通常指 { … } 内部)。

从 ES3 开始,try/catch 结构在 catch 分句中具有块作用域。

在 ES6 中引入了 let 关键字(var 关键字的表亲),用来在任意代码块中声明变量。if (…) { let a = 2; } 会声明一个劫持了 if 的 { … } 块的变量,并且将变量添加到这个块 中。

有些人认为块作用域不应该完全作为函数作用域的替代方案。两种功能应该同时存在,开 发者可以并且也应该根据需要选择使用何种作用域,创造可读、可维护的优良代码。

S14-提升

我们习惯将 var a = 2; 看作一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a 和 a = 2 当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。

这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。 可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的 最顶端,这个过程被称为提升。

声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。

要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候,否则会引 起很多危险的问题!

S15-作用域闭包

闭包就好像从 JavaScript 中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人 才能够到达那里。但实际上它只是一个标准,显然就是关于如何在函数作为值按需传递的 词法环境中书写代码的。

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时 就产生了闭包。

如果没能认出闭包,也不了解它的工作原理,在使用它的过程中就很容易犯错,比如在循 环中。但同时闭包也是一个非常强大的工具,可以用多种形式来实现模块等模式。

模块有两个主要特征:

  • (1)为创建内部作用域而调用了一个包装函数;
  • (2)包装函数的返回 值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭 包。

现在我们会发现代码中到处都有闭包存在,并且我们能够识别闭包然后用它来做一些有用 的事!

SFA-动态作用域

需要明确的是,事实上 JavaScript 并不具有动态作用域。它只有词法作用域,简单明了。 但是 this 机制某种程度上很像动态作用域。

主要区别:词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定 的。(this 也是!)词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用

SFB-块作用域的替代方案

最后简单地看一下 try/catch 带来的性能问题,并尝试回答“为什么不直接使用 IIFE 来创 建作用域”这个问题。

首先,try/catch 的性能的确很糟糕,但技术层面上没有合理的理由来说明 try/catch 必 须这么慢,或者会一直慢下去。自从 TC39 支持在 ES6 的转换器中使用 try/catch 后, Traceur 团队已经要求 Chrome 对 try/catch 的性能进行改进,他们显然有很充分的动机来 做这件事情。

其次,IIFE 和 try/catch 并不是完全等价的,因为如果将一段代码中的任意一部分拿出来 用函数进行包裹,会改变这段代码的含义,其中的 this、return、break 和 contine 都会 发生变化。IIFE 并不是一个普适的解决方案,它只适合在某些情况下进行手动操作。

最后问题就变成了:你是否想要块作用域?如果你想要,这些工具就可以帮助你。如果不 想要,继续使用 var 来写代码就好了!

SFC-this词法

var self = this 这种解决方案圆满解决了理解和正确使用 this 绑定的问题,并且没有把 问题过于复杂化,它使用的是我们非常熟悉的工具:词法作用域。self 只是一个可以通过 词法作用域和闭包进行引用的标识符,不关心 this 绑定的过程中发生了什么。

var obj = { count: 0, cool: function coolFn() {if (this.count < 1) { setTimeout( () => { // 箭头函数是什么鬼东西? this.count++; console.log( "awesome?" ); }, 100 ); } }
};obj.cool(); // 很酷吧 ?

简单来说,箭头函数在涉及 this 绑定时的行为和普通函数的行为完全不一致。它放弃了所 有普通 this 绑定的规则,取而代之的是用当前的词法作用域覆盖了 this 本来的值。

S21-关于this

对于那些没有投入时间学习 this 机制的 JavaScript 开发者来说,this 的绑定一直是一件非常令人困惑的事。this 是非常重要的,但是猜测、尝试并出错和盲目地从 Stack Overflow 上复制和粘贴答案并不能让你真正理解 this 的机制。

学习 this 的第一步是明白 this 既不指向函数自身也不指向函数的词法作用域,你也许被 这样的解释误导过,但其实它们都是错误的。

this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

S22-this全面解析

如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后 就可以顺序应用下面这四条规则来判断 this 的绑定对象。

  • 1.由 new 调用?绑定到新创建的对象。
  • 2.由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
  • 3.由上下文对象调用?绑定到那个上下文对象。
  • 4.默认:在严格模式下绑定到 undefined,否则绑定到全局对象。

一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑 定,你可以使用一个 DMZ 对象,比如 ø = Object.create(null),以保护全局对象。

ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样。

S23-对象

JavaScript 中的对象有字面形式(比如 var a = { … })和构造形式(比如 var a = new Array(…))。字面形式更常用,不过有时候构造形式可以提供更多选项。

许多人都以为“JavaScript 中万物都是对象”,这是错误的。对象是 6 个(或者是 7 个,取 决于你的观点)基础类型之一。对象有包括 function 在内的子类型,不同子类型具有不同 的行为,比如内部标签 [object Array] 表示这是对象的子类型数组。

对象就是键 / 值对的集合。可以通过 .propName 或者 [“propName”] 语法来获取属性值。访 问属性时,引擎实际上会调用内部的默认 [[Get]] 操作(在设置属性值时是 [[Put]]), [[Get]] 操作会检查对象本身是否包含这个属性,如果没找到的话还会查找 [[Prototype]] 链(参见第 5 章)。

属性的特性可以通过属性描述符来控制,比如 writable 和 configurable。此外,可以使用 Object.preventExtensions(…)、Object.seal(…) 和 Object.freeze(…) 来设置对象(及其 属性)的不可变性级别。

属性不一定包含值——它们可能是具备 getter/setter 的“访问描述符”。此外,属性可以是 可枚举或者不可枚举的,这决定了它们是否会出现在 for…in 循环中。

你可以使用 ES6 的 for…of 语法来遍历数据结构(数组、对象,等等)中的值,for…of 会寻找内置或者自定义的 @@iterator 对象并调用它的 next() 方法来遍历数据值。

S24-混合对象”类“

类是一种设计模式。许多语言提供了对于面向类软件设计的原生语法。JavaScript 也有类 似的语法,但是和其他语言中的类完全不同。

类意味着复制。

传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类 中。

多态(在继承链的不同层次名称相同但是功能不同的函数)看起来似乎是从子类引用父 类,但是本质上引用的其实是复制的结果。

JavaScript 并不会(像类那样)自动创建对象的副本。

混入模式(无论显式还是隐式)可以用来模拟类的复制行为,但是通常会产生丑陋并且脆 弱的语法,比如显式伪多态(OtherObj.methodName.call(this, …)),这会让代码更加难 懂并且难以维护。

此外,显式混入实际上无法完全模拟类的复制行为,因为对象(和函数!别忘了函数也 是对象)只能复制引用,无法复制被引用的对象或者函数本身。忽视这一点会导致许多 问题。

总地来说,在 JavaScript 中模拟类是得不偿失的,虽然能解决当前的问题,但是可能会埋 下更多的隐患。

S25-原型

如果要访问对象中并不存在的一个属性,[[Get]] 操作(参见第 3 章)就会查找对象内部 [[Prototype]] 关联的对象。这个关联关系实际上定义了一条“原型链”(有点像嵌套的作用域链),在查找属性时会对它进行遍历。

所有普通对象都有内置的 Object.prototype,指向原型链的顶端(比如说全局作用域),如 果在原型链中找不到指定的属性就会停止。toString()、valueOf() 和其他一些通用的功能 都存在于 Object.prototype 对象上,因此语言中所有的对象都可以使用它们。

关联两个对象最常用的方法是使用 new 关键词进行函数调用,在调用的 4 个步骤(第 2 章)中会创建一个关联其他对象的新对象。

使用 new 调用函数时会把新对象的 .prototype 属性关联到“其他对象”。带 new 的函数调用 通常被称为“构造函数调用”,尽管它们实际上和传统面向类语言中的类构造函数不一样。

虽然这些 JavaScript 机制和传统面向类语言中的“类初始化”和“类继承”很相似,但 是 JavaScript 中的机制有一个核心区别,那就是不会进行复制,对象之间是通过内部的 [[Prototype]] 链关联的。

出于各种原因,以“继承”结尾的术语(包括“原型继承”)和其他面向对象的术语都无 法帮助你理解 JavaScript 的真实机制(不仅仅是限制我们的思维模式)。

相比之下,“委托”是一个更合适的术语,因为对象之间的关系不是复制而是委托。

S26-行为委托

在软件架构中你可以选择是否使用类和继承设计模式。大多数开发者理所当然地认为类是 唯一(合适)的代码组织方式,但是本章中我们看到了另一种更少见但是更强大的设计模式:行为委托。

行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系。JavaScript 的 [[Prototype]] 机制本质上就是行为委托机制。也就是说,我们可以选择在 JavaScript 中努 力实现类机制(参见第 4 和第 5 章),也可以拥抱更自然的 [[Prototype]] 委托机制。

当你只用对象来设计代码时,不仅可以让语法更加简洁,而且可以让代码结构更加清晰。

对象关联(对象之前互相关联)是一种编码风格,它倡导的是直接创建和关联对象,不把 它们抽象成类。对象关联可以用基于 [[Prototype]] 的行为委托非常自然地实现。

S2A-ES6的class

除了语法更好看之外,ES6 还解决了什么问题呢?

  • 1.(基本上,下面会详细介绍)不再引用杂乱的 .prototype 了。
  • 2.Button 声 明 时 直 接“ 继 承 ” 了 Widget, 不 再 需 要 通 过 Object.create(…) 来 替 换 .prototype 对象,也不需要设置 .proto 或者 Object.setPrototypeOf(…)。
  • 3.可以通过 super(…) 来实现相对多态,这样任何方法都可以引用原型链上层的同名方 法。这可以解决第 4 章提到过的那个问题:构造函数不属于类,所以无法互相引用—— super() 可以完美解决构造函数的问题。
  • 4.class 字面语法不能声明属性(只能声明方法)。看起来这是一种限制,但是它会排除 掉许多不好的情况,如果没有这种限制的话,原型链末端的“实例”可能会意外地获取 其他地方的属性(这些属性隐式被所有“实例”所“共享”)。所以,class 语法实际上 可以帮助你避免犯错。
  • 5.可以通过 extends 很自然地扩展对象(子)类型,甚至是内置的对象(子)类型,比如 Array 或 RegExp。没有 class …extends 语法时,想实现这一点是非常困难的,基本上 只有框架的作者才能搞清楚这一点。但是现在可以轻而易举地做到!

class 很好地伪装成 JavaScript 中类和继承设计模式的解决方案,但是它实际上起到了反作 用:它隐藏了许多问题并且带来了更多更细小但是危险的问题。

class 加深了过去 20 年中对于 JavaScript 中“类”的误解,在某些方面,它产生的问题比 解决的多,而且让本来优雅简洁的 [[Prototype]] 机制变得非常别扭。

结论:如果 ES6 的 class 让 [[Prototype]] 变得更加难用而且隐藏了 JavaScript 对象最重要 的机制——对象之间的实时委托关联,我们难道不应该认为 class 产生的问题比解决的多 吗?难道不应该抵制这种设计模式吗?

我无法替你回答这些问题,但是我希望本书能从前所未有的深度分析这些问题,并且能够 为你提供回答问题所需的所有信息。

行动计划

  • 理解消化概念后整理为自己话同时有代码辅助例证
  • 作用域和闭包
  • this和对象原型

200530你不知道的JavaScript[上卷]所有小结汇总相关推荐

  1. 你不知道的javascript上卷

    你不知道的javascript上卷 作用域 javascript是一门编译语言,它不是提前编译的,编译结果也不能在分布式系统中移植.编译的步骤一般如下: 分词/词法分析 词法分析是有状态的判断一个分词 ...

  2. 【你不知道的JavaScript上卷】——作用域与闭包

    原文: [你不知道的JavaScript上卷]--作用域与闭包 JS语言万变不离其宗,其中最常用.最重要的也就是常用的几个大概念.数据类型.作用域.原型链.闭包.this指针.异步,不同的人理解不一样 ...

  3. JS闭包—你不知道的JavaScript上卷读书笔记(二)

    关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...

  4. 你不知道的JavaScript 上卷 Part1

      这篇博客躺在我的草稿箱里有一阵子了,差点给遗忘了哈哈. 前言   最近开始喜欢读一些书,从书中找答案,在阅读中查漏补缺.   记得小学初中时候最爱看书了,如今却不知怎的,习惯性从网络中摄取知识,搜 ...

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

    各位路过的的大佬.求关注.求点赞.谢谢 第一部分 作用域和闭包 第1章 作用域是什么 1.1编译原理 1.2理解作用域 1.3作用域嵌套 1.5异常 第2章 词法作用域 2.1词法阶段 2.2欺骗词法 ...

  6. 你不知道的JavaScript 上卷读书笔记

    看了<你不知道的JavaScript 上>,为了防止自己忘记,特此记下与我而言的部分重点 任何足够先进的技术都和魔法无异. --Arthur C. Clarke 作用域和闭包 编译原理 分 ...

  7. 你不知道的JavaScript上卷(一)

    第一章 作用域 存储和访问变量,几乎是所有编程语言的基本功能之一.但如何将变量引入,如何存储,如何查找等这些问题,就需要一套设计良好的规则进行管理.这套规则则被称为作用域 1.1编译原理 尽管通过将J ...

  8. 你不知道的JavaScript上卷-作用域和闭包

    1. LHS引用与RHS引用的区别: RHS:取到源值-得到某某的值 LHS:谁是赋值操作的源头-给谁赋值 function foo(a) {var b = a;return a + b; } var ...

  9. #你不知道的javascript上卷# 总结

    上卷看完了 全程下来:晦涩难懂,收货颇丰 虽然具体回忆倒不知道看了什么知识点,但是会感觉眼前的代码亮堂了很多 这本书最大的特点就是:读它得跟读文言文一样,一句话得需要好几遍再加上思考才会明白 有好几次 ...

最新文章

  1. 明白了这十个故事,你也就参悟了人生
  2. 《jQuery EasyUI开发指南》——10.4 迭代开发
  3. 2007年3月东北微软技术活动预告
  4. centos6.6 安装python环境及Django 1.9.0
  5. [USACO08MAR]土地征用Land Acquisition
  6. logistic公式形式的由来,从广义线性回归说起
  7. OneNote中到底能放多少种东西?
  8. Centos下oracle11g R2的启动与关闭监听、数据库
  9. TFS dataserver故障测试
  10. wait()、notify()、notifyAll()原理用法详解sleep()与wait()区别
  11. 联想9439微型计算机拆机,e43a 拆解文章.docx
  12. 华为内部流程管理系统(附关键流程图)
  13. 20年在线考试计算机应用基础,20年春福师《计算机应用基础》在线作业一【参考答案】...
  14. 利用函数wavread对语音信号进行采样_语音信号的语谱图特征提取(一)
  15. android:ems 属性详细分析
  16. 在SATA SSD + NVMe SSD双硬盘中安装ubuntu双系统
  17. volatile关键字简单理解
  18. 【Unity3D小功能】Unity3D中在创建完项目后自动创建文件夹列表
  19. 用苹果电脑开发Android应用,MAC OS Android Studio环境安装
  20. apr 移植android平台,omap3530移植android4.0

热门文章

  1. Android - - - Paging3
  2. 循环结构中的三大循环语句
  3. el-table使用lazy-tree模式,数据重载节点中的tree数据不会更新的问题
  4. 问道服务器etc修改教程,问道自定义宠物etc写法教程
  5. 26个新媒体运营数据来源渠道
  6. 携手唐天下:牛气冲天创富无限,赢2021网络营销
  7. 通过sourcetree为仓库添加子模块
  8. 一套手机点餐收银系统源码,系统功能完善、页面美观,开源分享!
  9. 角色转移服务器维护怎么回事,梦幻西游角色转移热点问题解答
  10. 划重点 | 如何让App开发及运营更走心,并兼具不可复制性?