《你不知道的JavaScript上卷》知识点笔记

1、

尽管通常将JavaScript归类为“动态”或“解释执行”语言,但事实上他是一门编译语言。

2、传统编译语言编译流程:

1.分词/词法分析

2.解析/语法分析

3.代码生成

3.

对于JavaScript来说,大部分情况下编译发生在执行前的几微秒内,JavaScript引擎用尽了各种办法(比如JIT,可以延迟编译甚至实施重编译)来保证性能最佳

4.

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中申明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

5.

var a = 2; //LHS引用,写

console.log(a) //RHS引用,读

6.

var foo = function(){} //会进行LHS查询

7.

RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError异常,而LHS不会(非严格模式下)

8.

ES5中引入了严格模式

9.

作用域共有两种主要的工作模型,第一种是最为普遍的,被大多数编程语言所采用的词法作用域。另一种叫做动态作用域,仍有一些编程语言在使用(比如bash脚本、Perl中的一些模式等)

10.

JavaScript使用词法作用域

11.

词法作用域是由你在写代码时将变量和快作用于写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下)

12.

无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定

13.

欺骗词法作用域会导致性能下降

14.

在严格模式的程序中,eval()在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域

15.

JavaScript中有两个机制可以“欺骗”词法作用域:eval()和with

这两个机制的副作用是引擎无法再编译时对作用域查找进行优化,因为引擎只能谨慎的认为这样的优化是无效的

16.

最小特权原则:应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来,比如某个模块或对象的API设计

17.

模块只是利用作用域的规则强制所有标识符都不能注入到共享作用域中,而是保持在私有、无冲突的作用域中,这样可以有效规避掉所有的意外冲突

18.

如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用

19.

始终给函数表达式命名是一个最佳实践

20.

由于函数被包含在一对()内部,因此成为了一个表达式,通过在末尾加上另外一个()可以立即执行这个函数。IIFE

21.

表面上看JavaScript并没有块作用域

22.

with关键字,是块作用域的一个例子,用with从对象中创建出的作用域仅在with中而非外部作用域中有效

23.

try/catch的分句会创建一个块作用域,其中声明的变量仅在catch内部有效

24.

ES6引入了新的let关键字,可以将变量绑定到所在的任意作用域中(通常是{…}内部)

25.

块作用域可以让引擎清楚地知道没有必要继续保存块里面的数据了

26.

引擎会在解释JavaScript代码之前首先对其进行编译

27.

编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来,因此,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理

28.

当你看到var a = 2;时,可能会认为这是一个声明,但JavaScript实际上会将其看成两个声明:var a;和a = 2; 第一个定义声明是在编译阶段进行的,第二个赋值声明会被留在原地等待执行阶段

29.

先有声明后有赋值

30.

每个作用域都会进行提升操作

31.

函数声明会被提升,但是函数表达式却不会被提升

32.

函数会首先被提升,然后才是变量

33.

出现在后面的函数声明会覆盖之前的函数声明

34.

所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升

35.

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

36.

无论通过何种手段将内部函数传递到所在的词法作用域以外,他都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包

37.

IIFE经典例子p49

38.

大多数模块依赖加载器/管理器本质上都是将这种模块定义封装进一个有好的API。p53

39.

模块模式的两个特点:为函数定义引入包装函数,并保证它的返回值和模块的API保持一致

40.

es6的模块没有“行内”格式,必须被定义在独立的文件中(一个文件一个模块)。

41.

export会将当前模块的一个标识符(变量、函数)导出为公共api,这些操作可以在模块定义中根据需要使用任意多次

42.

动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心他们从何处调用

43.

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

44.

函数内部代码this.count中的this并不是指向那个函数对象,所以虽然属性名相同,根对象却并不相同

45.

有一种传统的但是现在已经被弃用和批判的用法,是使用 arguments. callee 来引用当前正在运行的函数对象。这是唯一一种可以从匿名函数对象 内部引用自身的方法。然而,更好的方式是避免使用匿名函数,至少在需要 自引用时使用具名函数(表达式)。arguments.callee 已经被弃用,不应该再 使用它。

46.

每当你想要把this和词法作用与的查找混合使用时,一定要提醒自己,这是无法实现的。

47.

this是在运行时绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件,this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式

48.

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象

49.

对象属性引用链中只有最顶层或者说最后一层会影响调用位置

50.

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值

51.

回调函数丢失this绑定是非常常见的,调用回调函数的函数可能会修改this

52.

JavaScript提供的绝大多数函数以及你自己创建的所有函数都可以使用call()和apply()方法

53.

var bar = foo.bind(obj) //这样不管foo怎么执行,this都是指向obj。

bind()会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数

54.

new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定

55.

默认绑定,隐式绑定,显式绑定,new绑定

56.

判断this

1、函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象 var bar = new foo()

2、函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象 var bar = foo.call(obj2)

3、函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象 var bar = obj.foo();

4、如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象 var bar = foo()

57.

如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则

58.

Object.create(null)和null很像,但是并不会创建Object.prototype这个委托,所以它比{}更“空”

59.

箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this

60.

如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置,找到之后按56条的规则来判断

61.

对象可以通过两种形式定义:声明(文字)形式和构造形式

62.

简单基本类型(string, boolean, number, null和undefined)本身并不是对象,对null执行typeof null时会返回字符串“object”,实际上,null本身是基本类型

63.

JavaScript内置对象:String, Number, Boolean, Object, Function, Array, Date, RegExp, Error

64.

检查对象类型:Object.prototype.toString.call(strObject)

65.

存储在对象容器内部的是这些属性的名称,它们就像指针一样,指向这些真正的存储位置

66.

ES6中增加了可以算属性名,可以在文字形式中使用[]包裹一个表达式来当做属性名

67.

即使你再对象的文字形式中声明一个函数表达式,这个函数也不会“属于”这个对象——它们只是对于相同函数对象的引用

68.

如果你试图向数组添加一个属性,但是属性名“看起来”像一个数字,那么它会变成一个数值下标(因此会修改数组的内容而不是添加一个属性)

69.

相比深复制,浅复制非常易懂并且问题要少的多,所以ES6定义了Object.assign()方法来实现浅复制,Object.assign()就是使用=操作符来赋值

70.

结合writable:false和configurable:false就可以创建一个真正的常量属性(不可修改、重定义或者删除)

71.

如果你想禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions()

72.

in操作符会检查属性是否在对象及其prototype原型链中,相比之下,hasOwnProperty()只会检查属性是否在对象中

73.

4 in [2, 4, 6]的结果并不是你期待的true,因为[2, 4, 6]这个数组中包含的属性名是0、1、2没有4

74.

forEach()会遍历数组中的所有值并忽略回调函数的返回值。every()会一直运行直到回调函数返回false(或者“假”值)

some()会一直运行直到回调函数返回true(或者“真”值)

75.

for..in遍历键

for..of遍历值

76.

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

77.

任何可以通过原型链访问到(并且是enumerable)的属性都会被枚举

78.

继承以为这复制操作,JavaScript(默认)不会复制对象属性。相反,JavaScript会在两个对象之间创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数

79.

.constructor引用同样被委托给了原型,而原型的.constructor默认指向“创建这个对象的函数”

80.

实际上,new会劫持所有普通函数并用构造对象的形式来调用他

81.

在JavaScript中对于“构造函数”的最准确的解释是,所有带new的函数调用

82.

Object.create(Foo.prototype)会凭空创建一个“新”对象并把新对象内部的prototype关联到你指定的对象(本例中是Foo.prototype),换句话说:创建一个新的Bar.prototype对象并把他关联到Foo.prototype

83.

Bar.prototype = Foo.prototype并不会创建一个关联到Bar.prototype的新对象,因此当你执行类似Bar.prototype.myLabel = …的赋值语句时会直接修改Foo.prototype对象本身

84.

ES6可以使用Object.setPrototypeOf(Bar.prototype, Foo.prototype)

85.

a instanceof Foo回答的问题是:在a的整条prototype链中是否有指向Foo.prototype的对象

86.

prototype机制就是存在于对象中的一个内部链接,它会引用其他对象

87.

JavaScrpt机制有一个核心区别,那就是不会进行复制,对象之间是通过内部的prototype链关联的

88.

原型链换句话说就是对象之间的关联关系

89.

委托行为意味着某些对象在找不到属性或方法引用时会把这个请求委托给另一个对象

90.

如果对象关联风格的代码能够实现类风格代码的所有功能并且更加简洁易懂,那他是不是比类风格更好?

91.

行为委托关键: Object.create(obj)

92.

匿名函数缺点:

1.调试栈更难追踪

2.自我引用(递归、事件绑定等等)更难

3.代码更难理解

93.

行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系

94.

对象关联是一种编码风格,他倡导的是直接创建和关联对象,不把他们抽象成类