你不懂的javascript!!!

上卷

上卷一共分为俩部分,读过之后,觉得第一部分翻译的很好,第二部分可能由于翻译者是某学校大四学生,感觉很多地方讲解的不是很清楚。现在读完本书,完善了一下自己脑中js的知识体系。通过本书也深刻感受到学英语的重要性,读翻译太痛苦了,因为你可能会因为翻译者的水平而不理解原文作者所要表达的意思。总体来说,读完本书,很多理论变得清晰了,但是深深知道这只是冰山一角,任道而重远。现在也可以开始在编译器的层面上来看js代码了(:^^:恭喜恭喜)。在读本书最后俩章的时候,觉得很没意思,一方面可能因为自己平时用不到,但总觉得说的不清不楚。硬着头皮读过之后,基本上没有收获,感觉就是把我自己以前对原型和行为委托的理解,变得更加复杂了(这个世界上有很多简单的东西搞得很复杂来让人们看不懂,凸显自己的独一无二,有感而发!)。如今读完,重点也没有记录,想起潘大大的一番话,记不住还不会写下来啊,于是翻了翻每章的总结,觉得挺符合的。于是摘抄到本页中。

第一部分

第一章 作用域是什么

作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量进行赋值,那么就会使用LHS查询;如果目的是获取变量的值,就会使用RHS查询。赋值操作符会导致LHS查询。=操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。
JavaScript引擎首先会在代码执行前对其进行编译,在这个过程中,像var a = 2这样的生命会被分解成俩个独立的步骤:
1、首先,var a 在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。
2、接下来,a = 2 会查询(LHR查询) 变量a并对其进行赋值。
LHS和RHS查询都会在当前执行作用域中开始,如果有需要(也就是说他们没有找到所需要的标识符),就会向上级作用域继续查找目标标识符,这样每次上升一级作用域(一层楼),最后抵达全局作用域(顶层),无论找到或没找到都将停止。
不成功的RHS引用会导致跑出ReferenceError异常。不成功的LHS引用会导致自动隐式地创建一个全局变量(非严格模式下),该变量使用LHS引用的目标作为标识符,或者抛出ReferenceError异常(严格模式下)。

第二章 词法作用域

词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及如何声明的,从而能够预测在执行过程中如何对它们进行查找。
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机制的JavaScript开发者来说,this的绑定是一直是一件非常令人困惑的事。this是非常重要的,但是猜测、尝试并出错和盲目地从StackOverflow上复制粘贴答案并不能让你真正理解this机制。
学习this的第一步是明白this既不指向函数自身也不指向函数的词法作用域 ,你也许被这样的解释误导过,但其实塔木德都是错误的。
this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪来被调用。

第二章 this全面解析

如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面四条规则来判定this的绑定对象。
1、由new调用?绑定到新创建的对象。(ps:其实其他规则也是new的简写)
2、由call或者apply(或者bind)调用?绑定到指定的对象。
3、由上下文对象调用?绑定到那个上下文对象。
4、默认:在严格模式下绑定到undefined,否则绑定到全局对象。

一定要注意,有些调用可能再无意中使用默认绑定规则。如果想“更安全”地忽略this绑定,你可以使用一个DMZ对象,比如a = Object.create(null),以保护全局对象。
ES6种的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。这其实和ES6之前的代码中的self = this 机制一样。(ps:机制不一样,,,效果一样)

第三章 对象

JavaScript中的对象由字面形式(比如 var a = { .. })和构造形式(比如 var a = new Array(..) ).字面形式更常用,不过有时候构造形式可以提供更多选项。
许多人都以为“JavaScript中万物都是对象”,这是错误的。对象是6个(或者7个,取决于你的观点:String,Number,Boolean,Object,Function,Array,null?)基础类型之一。对象有包括function在内的子类型,不同子类型具有不同的行为,比如内部标签[object Array]表示这是对象的子类型数组。
对象就是键/值对的集合。可以通过.propName或者[“propName”]语法来获取属性值。访问属性时,引擎实际上会调用内部的默认[[Get]]操作(在设置属性值时是[[Put]]),[[Get]]操作会检查对象本身是否包含这个属性,如果没找到的话还会查找[[Prototype]]链。
属性的特性可以通过属性描述符来控制,不如writable和configurable。此外,可以使用Object.preventExtensions(..)、Object.seal(..)和Object.freeze(..)来设置对象(及其属性)的不可变性级别。
属性不一定包含值————他们可能是具备getter/setter的“访问描述符”。此外,属性可以是可以枚举或者不可枚举的,这决定了它们是否会出现在for..in循环中。
你可以使用ES6的for..of语法来遍历数据结构(数组、对象,等等)中的值,for..of会寻找内置或者自定义的@@iterator对象并调用他的next()方法来遍历数据值。

第四章 混合对象“类”

类是一种设计模式。许多语言提供了对于面向类软件设计的原生语法。JavaScript也有类似的语法,但是和其他语言中的类完全不同。
类意味着复制。
传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类中。
多态(在继承链的不同层次名称相同但是功能不同的函数)看起来似乎是从子类引用父类,但是本质上引用的其实是复制的结果。
JavaScript并不会(像类那样)自动创建对象的副本。
混入模式(无论是显式还是隐式)可以用来模拟类的复制行为,但是通常会产生丑陋并且脆弱的语法,比如显式伪多态,这会让代码更加难懂并且难以维护。
此外,显式混入实际上无法完全模拟类的复制行为,因为对象(和函数!别忘了函数也是对象)只能复制引用,无法复制被引用的对象或者函数本身。忽视这一点会导致许多问题。
总的来说,在JavaScript中模拟类是得不偿失的,虽然能解决当前的问题,但是可能会埋下更多的隐患。

第五章 原型

如果要访问对象中并不存在的一个属性,[[Get]]操作就会查找对象内部[[Prototype]]关联的对象。这个关联关系实际上定义了一条“原型链”(有点像嵌套的作用域链),在查找属性时会对它进行遍历。
所有普通对象都有内置的Object.prototype,指向原型链的顶端(比如说全局作用域),如果在原型链中找不到指定的属性就会停止。toString()、valueOf()和其他一些通用的功能都存在于Object.prototype对象上,因此语言中所有的对象都可以使用它们。
关联俩个对象最常用的方法是使用new关键词进行函数调用,在调用的4个步骤中会创建一个关联其他对象的新对象。
使用new调用函数时会把新对象.prototype属性关联到“其他对象”。带new的函数调用通常被称为“构造函数调用”,尽管它们实际上和传统面向类语言中的类构造函数不一样。
虽然这些JavaScript机制和传统面向类语言中的“类初始化”和“类继承”很相似,但是JavaScript中的机制有一个核心区别,那就是不会进行复制,对象之间是通过内部的[[Prototype]]链关联的。
处于各种原因,以“继承”结尾的术语(包括“原型继承”)和其他面向对象的术语都无法帮助你理解JavaScript的真是机制。
相比之下,“委托”是一个更合适的术语,因为对象之间的关系不是复制而是委托。

第六章 行为委托

就是一种设计模式
行为委托意味着对象彼此是对等的,在它们自己当中相互委托,而不是父类与子类的关系。JavaScript的[[Prototype]]机制的设计本质,就是行为委托机制。这意味着我们可以选择挣扎着在JS上实现类机制,也可以欣然接受[[Prototype]]作为委托机制的本性。

当你仅用对象设计代码时,它不仅能简化你使用的语法,而且它还能实际上引领更简单的代码结构设计。

OLOO(链接到其他对象的对像)是一种没有类的抽象,而直接创建和关联对象的代码风格。OLOO十分自然地实现了基于[[Prototype]]的行为委托。