JavaScript 基础
#
JavaScript 的编译过程- 分词/词法分析(Tokenizing/Lexing)
- 解析/语法分析(Parsing)
生成 AST
- 代码生成
#
JavaScript 的作用域#
定义作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。
#
暂时性死区#
遍历嵌套作用域链的规则引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没有找到,查找过程都会停止。
#
遮蔽效应在多层的嵌套作用域中可以定义同名的标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)
#
动态修改(欺骗)此法作用域缺点:欺骗词法作用域会导致性能下降
- eval
- with
#
块级作用域的替代方案可以在 ES6 环境运行的代码
在 ES6 之前的环境的实现
#
编程语言作用域的工作模型- 词法作用域,被大多数编程语言所采用
- 动态作用域(Bash 脚本、Perl)
#
编程语言作用域单元- 函数作用域
- 块级作用域
#
JavaScript 中创建块级作用域的几种方式- with
- try/cacth
- let
- const
#
作用域闭包#
闭包的定义- 广义定义
当函数可以记住并访问所在的词法作用域时,就产生了闭包。
- 严格定义
函数是在当前词法作用域之外执行
bar()拥有涵盖 foo()内部作用域的闭包,使得该作用域能够一直存活,以供 bar()在之后任何时间进行引用。
bar()依然持有对该作用域的引用,而这个引用就叫做闭包。
本质上,无论何时何地,如果将(访问它们各自词法作用域的)函数当做第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务重,只要使用了回调函数,实际上就是在使用闭包!
#
循环和闭包#
闭包的应用-模块模式模块模式的两个主要特征:(1)为创建内部作用域而调用了一个包装函数;(2)包装函数的返回值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包。
- 普通模块
- 单例模块
#
关于 this当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this 就是这个记录的一个属性,会在函数执行的过程中得到。
#
this 的绑定规则默认绑定
隐式绑定
显式绑定
- Function.prototype.call
- Function.prototype.apply
- Function.prototype.bind
手写 bind
new 绑定
在 JavaScript 中,构造函数只是一些使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已
实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行[[Prototype]]连接。
- 这个对象会被绑定到函数调用的 this。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新的对象。
#
this 绑定规则优先级new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
#
原型原型是对象间的关联关系,这种关联关系可以称之为“委托”
#
继承传统面向对象的继承是复制操作(类复制到对象),原型继承是一种对象关联机制。
继承意味着复制操作,JavaScript(默认)并不会复制对象属性。相反 JavaScript 会在两个对象之间创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数。“委托”这个术语可以更加准确的描述 JavaScript 中对象的关联机制。
#
继承的实现- 原型继承
- 寄生继承
#
Object.create 的 polifill 代码#
“类”和委托设计模式面向对象风格(面向类设计模式)
参见上一节“原型继承”
该设计模式存在思维模式不匹配的问题
对象关联风格(行为委托设计模式)
#
内省内省就是检查实例的类型
instanceof
用于检查面向类设计模式创建对象的类型
Object.prototype.isPrototypeOf() 和 Object.getPrototypeOf()
用于检查使用行为委托设计模式创建对象的类型
#
for...in 和 in 操作符使用 for...in 遍历对象时原理和查找[[Prototype]]链类似,任何可以通过原型链访问到(并且是 enumberable)的属性都会被枚举。使用 in 操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链(无论属性是否可枚举)。
#
基本类型和复杂类型是储存在哪里?- 基本类型储存在栈中,但是一旦被闭包引用则成为常住内存,会储存在内存堆中。
- 复杂类型会储存在内存堆中
#
箭头函数相较于普通函数的特殊特性- 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
- 函数不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
- 不可以使用 new 命令,因为:没有自己的 this,无法调用 call,apply。没有 prototype 属性 ,而 new 命令在执行时需要将构造函数的 prototype 赋值给新的对象的 proto
#
ES 模块和 CommonJS 模块的差异- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的 require()是同步加载模块,ES6 模块的 import 命令是异步加载,有一个独立的模块依赖的解析阶段。
第二个差异是因为 CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。