《你不知道的JS》复习

本文是我在看《你不知道的JS》时随手摘录的,打算面试前再看两眼回忆用。可能没什么逻辑,见谅~

作用域与闭包

  • 尽管JavaScript一般被划分到“动态”或者“解释型”语言的范畴,但是其实它是一个编译型语言
  • 在一个作用域内的所有声明,不论它们出现在何处,都会在代码本身被执行前首先被处理
  • 声明本身会被提升,但不是赋值,即便是函数表达式的赋值,也不会被提升
  • 函数声明和变量声明都会被提升。但是,函数会首先被提升,然后才是变量

this 与对象原型

  • this实际上是在函数被调用时建立的一个绑定,它指向什么是完全由函数被调用的调用点来决定的
  • bind(..)返回一个硬编码的新函数,它使用你指定的this环境来调用原本的函数
  • 许多函数都提供一个可选参数,通常称为“环境(context)”,这种设计作为一种替代方案来确保你的回调函数使用特定的this而不必非得使用bind(..)
  • new是函数调用可以绑定this的最后一种方式
  • 绑定的优先级,new绑定>明确绑定或硬绑定>隐含绑定>默认绑定,除非传递的绑定参数是undefinednull
  • 创建完全为空的对象的最简单方法就是Object.create(null)
  • 箭头函数从封闭它的(functionglobal)作用域采用this绑定,其本质是用被广泛理解的词法作用域来禁止了传统的this机制
  • 一个常见的错误论断是“JavaScript中的一切都是对象”。这明显是不对的
  • function是对象的一种子类型(技术上讲,叫做“可调用对象”)
  • 尽可能地使用字面形式的值,而非使用构造的对象形式
  • nullundefined没有对象包装的形式,仅有它们的基本类型值。相比之下,Date的值仅可以由它们的构造对象形式创建,因为它们没有对应的字面形式。无论使用字面还是构造形式,ObjectArrayFunction,和RegExp都是对象
  • ES6加入了计算型属性名
  • 在JavaScript中,“函数”和“方法”是可以互换使用的
  • Object.assign(..)中发生的复制是单纯的=式赋值,所以任何在源对象属性的特殊性质(比如writable)在目标对象上都不会保留
  • 对象可被防止扩展、封印、冻结
  • 对于访问器描述符,它的valuewritable性质没有意义
  • 有许多方法区分可枚举与不可枚举属性
  • for..of循环要求被迭代的东西提供一个迭代器对象
  • 数组拥有内建的@@iterator@@iterator本身不是迭代器对象,而是一个返回迭代器对象的方法
  • JavaScript实际上不拥有类。一般来讲,在JS中模拟类通常会比解决当前真正的问题埋下更多的坑
  • in操作符将会检查对象的整个链条上属性是否存在
  • 属性的隐式遮蔽很令人头疼
  • 函数不是构造器,但是当且仅当new被使用时,函数调用是一个“构造器调用”

这块内容很复杂,作者的想法挺反主流……许多大幅段落很精彩但未能摘录,先这样放着吧……


类型与文法

  • typeof null === "object"; // true
  • typeof常用于构建不带有抛出错误的检查
  • 要小心创建“稀散”的array
  • 如果一个可以被强制转换为10进制numberstring值被用作键的话,它会认为你想使用number索引而不是一个string
  • 最好不要向array添加string键/属性
  • 如果slice()没有用其他额外的参数调用,它会具有复制array的效果
  • string上没有一个方法是可以原地修改它的内容的,而是创建并返回一个新的string
  • JS中,“整数”只是没有小数部分的小数值,42.0和42一样是“整数”
  • null是一个特殊的关键字,不是一个标识符。undefined(不幸地一个标识符,可以被赋值
  • NaN的类型是number
  • window.isNaN(..)工具有重大缺陷,最好使用Number.isNaN(..)
  • JS中可以除以零,结果是Infinity(用正数除)或-Infinity(用负数除)
  • Infinity / Infinity在JS中的结果为NaN
  • JavaScript拥有普通的零0(也称为正零+0)和一个负零-0
  • 如果负零转换为字符串,结果为”0”,但从数字转为字符串不会撒谎
  • 在ES6中,Object.is(..)可用于测试两个值的绝对等价性,而没有任何例外
  • 引用与其他语言中的引用/指针不同——它们从不指向其他的变量/引用,而仅指向底层的值
  • 永远也不要做new String("abc")这样的事情
  • Array(..)构造器不要求在它前面使用new关键字
  • Symbol不是object,它们是简单的基本标量
  • Symbol(..)原生类型“构造器”不能与new一起使用
  • Function.prototype是一个空函数,RegExp.prototype是一个“空”正则表达式(也就是不匹配任何东西),而Array.prototype是一个空数组,这使它们成了可以赋值给变量的,很好的“默认”值
  • toJSON()应当被翻译为:“变为一个适用于字符串化的JSON安全的值”
  • toNumber()很有趣
  • falsy对象是历史遗留问题
  • 各种类型的转换方式要详细看
  • 一个&&||操作符产生的值不见得是Boolean类型。这个产生的值将总是两个操作数表达式其中之一的值。这些特性使得它有很多有趣的用法
  • Symbol是个很大的例外
  • 永远不要在任何情况下,使用== true== false
  • ToPrimitive很奇怪
  • 强制转换存在诸多例外情况,比较时发生的转换也很奇葩
  • 所有语句都有完成值(即使这个值只是undefined
  • var a = b = 42的写法是错误的。它要么抛出一个错误(严格模式),要么创建一个全局变量(非严格模式)
  • JS的确支持一种有限的,特殊形式的goto:标签跳转
  • var { a , b } = ..是ES6解构赋值的一种形式
  • 右结合性的意思不是从右到左求值,它的意思是从右到左分组
  • typeof有一个例外,它对于未声明的变量是安全的,但是对于TDZ引用却没有这样的安全例外
  • 当使用ES6的参数默认值时,如果你省略一个参数,或者你在它的位置上传递一个undefined值的话,就会应用这个默认值
  • 绝不同时引用一个被命名参数和它相应的arguments值槽
  • (由于浏览器的遗留行为)使用id属性创建DOM元素会创建同名的全局变量

异步与性能

  • 在ES6中,在事件轮询队列之上引入了一层新概念,称为“工作队列”
  • 链式调用.then(..)创建了另一个promise。虽没有在这第二个then(..)的末尾链接任何操作,它也已经创建了另一个promise,可以监听/使用它
  • 因为Promise一旦被解析就是外部不可变的,所以现在将这个值传递给任何其他团体都是安全的,而且它不会被意外或恶意地被修改
  • “如果它看起来像一只鸭子,并且叫起来像一只鸭子,那么它一定是一只鸭子”
  • 当一个Promise被解析时,所有在then(..)上注册的回调都将被立即,按顺序地,在下一个异步机会时被调用,而且没有任何在这些回调中发生的事情可以影响/推迟其他回调的调用
  • 绝不应该依靠任何跨Promise的回调顺序/排程
  • Promise使用一个称为“竞赛(race)”的高级抽象
  • Promise.resolve(..)会接受任何thenable,而且将它展开直至非thenable值。但你会从Promise.resolve(..)那里得到一个真正的,纯粹的Promise,一个你可以信任的东西
  • 可以将多个Promise串联在一起来表达一系列的异步步骤
  • 错误持续地在Promise链上传播,直到遇到一个明确定义的拒绝处理器
  • 如果一个Promise被拒绝,可以选择明确地处理这个报告(使用defer()
  • Promise.all([..])Promise.race([..])是相对的
  • 一个generator的声明被格式化为function* foo() {..}
  • 一般来说,你所拥有的next(..)调用的数量,会比你所拥有的yield语句的数量多一个
  • yield ..next(..)一起成对地在generator运行期间构成了一个双向消息传递系统
  • 在ES6中,从一个iterable中取得一个迭代器的方法是,iterable 上必须有一个函数,它的名称是特殊的ES6符号值Symbol.iterator
  • 当调用it.return(..)时,它会立即终结generator,从而运行finally从句。而且,它会将返回的value设置为你传入return(..)的任何东西。也不必使用break,因为generator的迭代器会被设置为done:truefor..of循环会在下一次迭代时终结
  • 我们将异步性,特别是Promise,作为一种实现细节
  • “基准分析与调优”这一章很有趣,提供了若干统计工具
  • 大量的针对微小操作的基准分析结果——比如++xx++的神话——完全是伪命题
  • 应当测试真实的,有意义的代码段,并且在最接近你实际能够期望的真实条件下进行
  • 虽然程序的关键路径性能非常重要,但它不是唯一的因素。在几种性能上大体相似的选择中,可读性应当是另一个重要的考量
  • 尾部调用优化(TCO)是一个ES6要求的优化机制
  • JavaScript当前并没有任何特性可以支持多线程运行,但是浏览器等环境可以很容易地提供多个JavaScript引擎实例,每个都在自己的线程上,并允许你在每个线程上运行不同的程序
  • Worker内部,不能访问主程序的任何资源,但可以实施网络操作和设置定时器等
  • 可以创建一个单独的中心化Worker,网站或应用的所有网页实例可以共享它
  • SIMD提议将CPU级别的并行数学操作映射到JavaScriptAPI上来提供高性能数据并行操作
  • asm.js描述了一个JavaScript的小的子集,它回避了JS中不易优化的部分(比如垃圾回收与强制转换)并让JS引擎通过主动优化识别并运行这样的代码

ES6与未来

  • 在一个let ..声明/初始化之前访问一个用let声明的变量会导致一个错误,而对于var声明来说这个顺序无关紧要(除了文体上的区别)
  • 值不会因为const而冻结或不可变,只是它的赋值被冻结了
  • 块作用域内的函数声明会被提升
  • ...经常扮演apply(..)方法的简约语法替代品
  • 另一种...的用法常见于一种实质上相反的操作;与将值散开不同,...将一组值收集到一个数组中
  • 默认值表达式是被懒惰地求值的,这意味着他们仅在被需要时运行
  • 结构、计算型属性名和对象属性复制模式可以联用
  • 对于对象解构形式来说,当我们省略了var/let/const声明符时,就必须将整个赋值表达式包含在()中,否则左手边作为语句第一个元素的{..}将被视为一个语句块而不是一个对象
  • 可以不使用临时变量来解决传统的“交换两个变量”的问题:[y,x]=[x,y];
  • 插值型字符串字面量的一个真正的好处是他们允许被分割为多行
  • 在一个插值型字符串字面量中,任何合法的表达式都被允许出现在${..}内部,包括函数调用,内联函数表达式调用,甚至是另一个插值型字符串字面量
  • 一个标签型字符串字面量像是一个在插值表达式被求值之后,但是在最终的字符串被编译之前的处理步骤,允许你在从字面量中产生字符串的过程中进行更多的控制
  • ES6带来了一个内建函数,它可以用做字符串字面量的标签:String.raw(..)
  • 在短的内联函数表达式的地方采用=>,但保持一般长度的主函数原封不动
  • for..in循环遍历数组a中的键/索引,而for..of循环遍历a中的值
  • for (XYZ of ABC)..中,XYZ子句既可以是一个赋值表达式也可以是一个声明
  • 新的ES6数字字面形式:var dec = 42,oct = 0o52,hex = 0x2a,bin = 0b101010;
  • 如果一个迭代器也是可迭代对象,它就可以与for..of循环一起使用
  • 扩散操作符...将完全耗尽一个迭代器
  • yield ..a = 3这样的赋值表达式拥有相同的“表达式优先级”
  • generator实际上只是状态机逻辑的简单语法
  • 任何你没有使用export标记的东西将在模块作用域的内部保持私有
  • 与对象字面量不同的是,在一个class内容的部分没有逗号分隔各个成员
  • 一个thenable没有一个纯粹的Promise那么可信
  • 类型化数组使用类似数组的语义提供对二进制数据的结构化访问
  • 制造一个map的拷贝十分简单:var m2 = new Map( m.entries() );var m2 = new Map( m );
  • WeakMap(仅)接收对象作为键。这些对象被弱持有,如果对象本身被垃圾回收,那么在WeakMap中的记录也会被移除
  • WeakMap没有size属性和clear()方法
  • WeakMap只弱持有它的键,而不是它的值
  • 一个WeakSet弱持有它的值(不存在真正的键)
  • WeakSet的值必须是对象,在set中被允许的基本类型值是不行的
  • Array.of(..)作为数组首选的函数型构造器取代了Array(..),因为Array.of(..)没有那种单数字参数值的情况
  • Array.from(..)从不产生空值槽
  • Array.from(..)的第二个参数值是一个映射函数
  • Array#copyWithin(..)是一个对所有数组可用的新修改器方法
  • Array#fill(..)方法原生地支持使用一个指定的值来完全地(或部分地)填充一个既存的数组
  • find(..)从数组检索中给出匹配的值。接收一个可选的第二参数
  • findIndex(..)可寻找匹配的值的位置索引
  • Array提供了与集合相同的迭代器方法
  • 想要严格地识别NaN-0值的情况下,Object.is(..)是现在的首选方式
  • Object.setPrototypeOf(..)工具,它为了行为委托的目的(意料之中地)设置一个对象的[[Prototype]]
  • Object.assign(..)将一个对象的属性拷贝/混合到另一个对象中。非枚举属性和非自身属性会被忽略
  • MathStringNumber添加了不少有用的函数和属性
  • 元编程是针对程序本身的行为进行操作的编程
  • functionname属性是啥是个谜
  • new.target用来从一个构造器调用内部判定原来的new的目标是什么
  • 可以通过设置Symbol.iterator属性来为任意对象定义我们自己的迭代器逻辑
  • ToPrimitive抽象强制转换操作
  • 代理proxy是一种由你创建的特殊的对象,它“包”着另一个普通的对象
  • Reflect对象是一个普通对象(就像Math),不是其他内建原生类型那样的函数/构造器。它持有对应于你可以控制的各种元编程任务的静态函数
  • 在ES6中,罗列直属属性的属性是由[[OwnPropertyKeys]]算法定义的,这种顺序仅对Reflect.ownKeys(..)有保证
  • 所有四种机制(Reflect.enumerate(..)Object.keys(..)for..in,和JSON.stringify(..))都同样将与依赖于具体实现的顺序相吻合