本文是我在看《你不知道的JS》时随手摘录的,打算面试前再看两眼回忆用。可能没什么逻辑,见谅~
作用域与闭包
- 尽管
JavaScript
一般被划分到“动态”或者“解释型”语言的范畴,但是其实它是一个编译型语言 - 在一个作用域内的所有声明,不论它们出现在何处,都会在代码本身被执行前首先被处理
- 声明本身会被提升,但不是赋值,即便是函数表达式的赋值,也不会被提升
- 函数声明和变量声明都会被提升。但是,函数会首先被提升,然后才是变量
this 与对象原型
this
实际上是在函数被调用时建立的一个绑定,它指向什么是完全由函数被调用的调用点来决定的bind(..)
返回一个硬编码的新函数,它使用你指定的this环境来调用原本的函数- 许多函数都提供一个可选参数,通常称为“环境(context)”,这种设计作为一种替代方案来确保你的回调函数使用特定的
this
而不必非得使用bind(..)
new
是函数调用可以绑定this
的最后一种方式- 绑定的优先级,new绑定>明确绑定或硬绑定>隐含绑定>默认绑定,除非传递的绑定参数是
undefined
或null
- 创建完全为空的对象的最简单方法就是
Object.create(null)
- 箭头函数从封闭它的(
function
或global
)作用域采用this
绑定,其本质是用被广泛理解的词法作用域来禁止了传统的this
机制 - 一个常见的错误论断是“JavaScript中的一切都是对象”。这明显是不对的
function
是对象的一种子类型(技术上讲,叫做“可调用对象”)- 尽可能地使用字面形式的值,而非使用构造的对象形式
null
和undefined
没有对象包装的形式,仅有它们的基本类型值。相比之下,Date
的值仅可以由它们的构造对象形式创建,因为它们没有对应的字面形式。无论使用字面还是构造形式,Object
,Array
,Function
,和RegExp
都是对象- ES6加入了计算型属性名
- 在JavaScript中,“函数”和“方法”是可以互换使用的
- 在
Object.assign(..)
中发生的复制是单纯的=式赋值,所以任何在源对象属性的特殊性质(比如writable)在目标对象上都不会保留 - 对象可被防止扩展、封印、冻结
- 对于访问器描述符,它的
value
和writable
性质没有意义 - 有许多方法区分可枚举与不可枚举属性
for..of
循环要求被迭代的东西提供一个迭代器对象- 数组拥有内建的
@@iterator
。@@iterator
本身不是迭代器对象,而是一个返回迭代器对象的方法 JavaScript
实际上不拥有类。一般来讲,在JS中模拟类通常会比解决当前真正的问题埋下更多的坑in
操作符将会检查对象的整个链条上属性是否存在- 属性的隐式遮蔽很令人头疼
- 函数不是构造器,但是当且仅当
new
被使用时,函数调用是一个“构造器调用”
这块内容很复杂,作者的想法挺反主流……许多大幅段落很精彩但未能摘录,先这样放着吧……
类型与文法
typeof null === "object"; // true
typeof
常用于构建不带有抛出错误的检查- 要小心创建“稀散”的
array
- 如果一个可以被强制转换为10进制
number
的string
值被用作键的话,它会认为你想使用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:true
,for..of
循环会在下一次迭代时终结 - 我们将异步性,特别是
Promise
,作为一种实现细节 - “基准分析与调优”这一章很有趣,提供了若干统计工具
- 大量的针对微小操作的基准分析结果——比如
++x
对x++
的神话——完全是伪命题 - 应当测试真实的,有意义的代码段,并且在最接近你实际能够期望的真实条件下进行
- 虽然程序的关键路径性能非常重要,但它不是唯一的因素。在几种性能上大体相似的选择中,可读性应当是另一个重要的考量
- 尾部调用优化(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(..)
将一个对象的属性拷贝/混合到另一个对象中。非枚举属性和非自身属性会被忽略Math
、String
和Number
添加了不少有用的函数和属性- 元编程是针对程序本身的行为进行操作的编程
function
的name
属性是啥是个谜new.target
用来从一个构造器调用内部判定原来的new
的目标是什么- 可以通过设置
Symbol.iterator
属性来为任意对象定义我们自己的迭代器逻辑 ToPrimitive
抽象强制转换操作- 代理
proxy
是一种由你创建的特殊的对象,它“包”着另一个普通的对象 Reflect
对象是一个普通对象(就像Math
),不是其他内建原生类型那样的函数/构造器。它持有对应于你可以控制的各种元编程任务的静态函数- 在ES6中,罗列直属属性的属性是由
[[OwnPropertyKeys]]
算法定义的,这种顺序仅对Reflect.ownKeys(..)
有保证 - 所有四种机制(
Reflect.enumerate(..)
,Object.keys(..)
,for..in
,和JSON.stringify(..)
)都同样将与依赖于具体实现的顺序相吻合