《你不知道的 javascript》是一个前端学习必读的系列,让不求甚解的JavaScript开发者迎难而上,深刻语言内部,弄清楚JavaScript每个零部件的用途。本书介绍了该系列的两个主题:“做用域和闭包”以及“this和对象原型”。这两块也是值得咱们反复去学习琢磨的两块只是内容,今天咱们用思惟导图的方式来精读一遍。(思惟导图图片可能有点小,记得点开看,你会有所收获)javascript
做用域是一套规则,用于肯定在何处以及如何查找变量(标识符)。若是查找的目的是对 变量进行赋值,那么就会使用 LHS 查询;若是目的是获取变量的值,就会使用 RHS 查询。赋值操做符会致使 LHS 查询。 的赋值操做。 =操做符或调用函数时传入参数的操做都会致使关联做用域的赋值操做。 JavaScript 引擎首先会在代码执行前对其进行编译,在这个过程当中,像 var a = 2 这样的声 明会被分解成两个独立的步骤:前端
LHS 和 RHS 查询都会在当前执行做用域中开始,若是有须要(也就是说它们没有找到所 需的标识符),就会向上级做用域继续查找目标标识符,这样每次上升一级做用域(一层 楼),最后抵达全局做用域(顶层),不管找到或没找到都将中止。java
不成功的RHS引用会致使抛出 ReferenceError 异常。不成功的 LHS 引用会致使自动隐式地建立一个全局变量(非严格模式下),该变量使用 LHS 引用的目标做为标识符,或者抛 出 ReferenceError 异常(严格模式下)。git
词法做用域意味着做用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段 基本可以知道所有标识符在哪里以及是如何声明的,从而可以预测在执行过程当中如何对它 们进行查找。github
JavaScript 中有两个机制能够“欺骗”词法做用域: eval(..) 和 with 。 前者能够对一段包 含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法做用域(在 运行时)。后者本质上是经过将一个对象的引用 看成 做用域来处理,将对象的属性看成做 用域中的标识符来处理,从而建立了一个新的词法做用域(一样是在运行时)。设计模式
这两个机制的反作用是引擎没法在编译时对做用域查找进行优化,由于引擎只能谨慎地认 为这样的优化是无效的。使用这其中任何一个机制都 将 致使代码运行变慢。 不要使用它们。数组
函数是 JavaScript 中最多见的做用域单元。本质上,声明在一个函数内部的变量或函数会 在所处的做用域中“隐藏”起来,这是有意为之的良好软件的设计原则。安全
但函数不是惟一的做用域单元。块做用域指的是变量和函数不只能够属于所处的做用域, 也能够属于某个代码块(一般指 { .. } 内部)。微信
从 ES3 开始, try/catch 结构在 catch 分句中具备块做用域。在 ES6 中引入了 let 关键字( var 关键字的表亲), 用来在任意代码块中声明变量。 if(..) { let a = 2; } 会声明一个劫持了 if 的 { .. } 块的变量,而且将变量添加到这个块 中。数据结构
有些人认为块做用域不该该彻底做为函数做用域的替代方案。两种功能应该同时存在,开 发者能够而且也应该根据须要选择使用何种做用域,创造可读、可维护的优良代码。
咱们习惯将 var a = 2; 看做一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a 和 a = 2 看成两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。
这意味着不管做用域中的声明出如今什么地方,都将在代码自己被执行前 首先 进行处理。 能够将这个过程形象地想象成全部的声明(变量和函数)都会被“移动”到各自做用域的最顶端,这个过程被称为提高。
声明自己会被提高,而包括函数表达式的赋值在内的赋值操做并不会提高。
要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一块儿的时候,不然会引 起不少危险的问题!
闭包就好像从 JavaScript 中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人 才可以到达那里。但实际上它只是一个标准,显然就是关于如何在函数做为值按需传递的 词法环境中书写代码的。
当函数能够记住并访问所在的词法做用域,即便函数是在当前词法做用域以外执行,这时 就产生了闭包。
若是没能认出闭包,也不了解它的工做原理,在使用它的过程当中就很容易犯错,好比在循 环中。但同时闭包也是一个很是强大的工具,能够用多种形式来实现 模块 等模式。模块有两个主要特征:
(1)为建立内部做用域而调用了一个包装函数; (2)包装函数的返回 值必须至少包括一个对内部函数的引用,这样就会建立涵盖整个包装函数内部做用域的闭 包。
如今咱们会发现代码中处处都有闭包存在,而且咱们可以识别闭包而后用它来作一些有用 的事!
若是要判断一个运行中函数的 this 绑定,就须要找到这个函数的直接调用位置。找到以后 就能够顺序应用下面这四条规则来判断 this 的绑定对象。
由 new 调用?绑定到新建立的对象。
由 call 或者 apply (或者 bind )调用?绑定到指定的对象。
由上下文对象调用?绑定到那个上下文对象。
默认:在严格模式下绑定到 undefined ,不然绑定到全局对象。
必定要注意,有些调用可能在无心中使用默认绑定规则。若是想“更安全”地忽略 this 绑 定,你可使用一个 DMZ 对象,好比 ø = Object.create(null) ,以保护全局对象。ES6中的箭头函数并不会使用四条标准的绑定规则, 而是根据当前的词法做用域来决定 this ,具体来讲,箭头函数会继承外层函数调用的 this 绑定(不管 this 绑定到什么)。这 其实和 ES6 以前代码中的 self = this 机制同样。
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() 方法来遍历数据值。
类是一种设计模式。 许多语言提供了对于面向类软件设计的原生语法。 JavaScript 也有类 似的语法,可是和其余语言中的类彻底不一样。
类意味着复制。
传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类 中。
多态(在继承链的不一样层次名称相同可是功能不一样的函数)看起来彷佛是从子类引用父 类,可是本质上引用的实际上是复制的结果。
JavaScript 并不会(像类那样)自动建立对象的副本。
混入模式(不管显式仍是隐式)能够用来模拟类的复制行为,可是一般会产生丑陋而且脆 弱的语法,好比显式伪多态( OtherObj.methodName.call(this, ...) ),这会让代码更加难 懂而且难以维护。
此外, 显式混入实际上没法彻底模拟类的复制行为, 由于对象(和函数!别忘了函数也 是对象)只能复制引用, 没法复制被引用的对象或者函数自己。 忽视这一点会致使许多 问题。
总地来讲,在 JavaScript 中模拟类是得不偿失的,虽然能解决当前的问题,可是可能会埋下更多的隐患。
若是要访问对象中并不存在的一个属性, [[Get]] 操做(参见第 3 章)就会查找对象内部 [[Prototype]] 关联的对象。这个关联关系实际上定义了一条“原型链”(有点像嵌套的做用域链),在查找属性时会对它进行遍历。
全部普通对象都有内置的 Object.prototype ,指向原型链的顶端(好比说全局做用域),如 果在原型链中找不到指定的属性就会中止。 toString() 、 valueOf() 和其余一些通用的功能 都存在于 Object.prototype 对象上,所以语言中全部的对象均可以使用它们。
关联两个对象最经常使用的方法是使用 new 关键词进行函数调用, 在调用的 章)中会建立一个关联其余对象的新对象。4个步骤(第2章)中会建立一个关联其余对象的新对象。
使用 new 调用函数时会把新对象的 .prototype 属性关联到“其余对象”。带 new 的函数调用 一般被称为“构造函数调用”,尽管它们实际上和传统面向类语言中的 类构造函数 不同。
JavaScript 是 中的机制有一个核心区别, 那就是不会进行复制, 对象之间是经过内部的
虽然这些 机制和传统面向类语言中的“类初始化”和“类继承”很类似, 可是 javascript 机制和传统面向对象类语言中的“类初始化”和“类继承”很类似可是 javascript 中的机制有一个核心区别,就是不会进行复制,对象之间是经过内部的 [[Prototype]] 链关联的。
出于各类缘由,以“继承”结尾的术语(包括“原型继承”)和其余面向对象的术语都无 法帮助你理解 JavaScript 的 真实 机制(不只仅是限制咱们的思惟模式)。
相比之下,“委托”是一个更合适的术语,由于对象之间的关系不是 复制 而是委托。
在软件架构中你能够 选择是否 使用类和继承设计模式。大多数开发者理所固然地认为类是 惟一(合适)的代码组织方式,可是本章中咱们看到了另外一种更少见可是更强大的设计模式: 行为委托 。
行为委托认为对象之间是兄弟关系, 互相委托, 而不是父类和子类的关系。 JavaScript 的 [[Prototype]] 机制本质上就是行为委托机制。也就是说,咱们能够选择在 JavaScript 中努 力实现类机制(参见第 4 和第 5 章),也能够拥抱更天然的 [[Prototype]] 委托机制。
当你只用对象来设计代码时,不只可让语法更加简洁,并且可让代码结构更加清晰。
对象关联(对象以前互相关联)是一种编码风格,它倡导的是直接建立和关联对象,不把 它们抽象成类。对象关联能够用基于 [[Prototype]] 的行为委托很是天然地实现。
思惟导图能比较清晰的还原整本书的知识结构体系,若是你还没用看过这本书,能够按照这个思惟导图的思路快速预习一遍,提升学习效率。学习新事物总容易遗忘,我比较喜欢在看书的时候用思惟导图作些记录,便于本身后期复习,若是你已经看过了这本书,也建议你收藏复习。若是你有神马建议或则想法,欢迎留言或加我微信交流:646321933