JS引擎
在执行栈
中执行源代码时,会先将源代码进行编译
,拿到计算机看得懂的可执行代码(直接操作内存的机器代码),再JS引擎执行
。
引擎
。 从头到尾的编译与执行,最重要。编译器
。引擎好朋友之一,语法分析以及代码生成。作用域
。引擎好朋友之一,一套规则,确定标志符的访问权限。词法分析
(涉及到引擎、作用域概念)。比如var a = 2分解为词法单元var、a、=、2。语法分析
。词法单元流(数组),转为抽象语法树AST。代码生成
。AST转为可执行代码(比如定义内存、告诉js引擎如何操作内存等机器执行代码)。举例1,按照JS引擎和它的朋友们去思考变量赋值,看他们如何协作:var a = 2
词法分析、语法分析
,生成AST;编译器AST到代码生成时,为变量分配内存,命名为a,然后将a = 2生成机器代码(如何操作内存),好供JS引擎直接执行
。
举例2,一个函数被调用就会创建一个新的执行环境(也叫上下文环境),里面会有作用域嵌套的概念。
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
// 意味着每个执行环境在概念上作为一个对象并带有三个属性
executionContextObj = {
//作用域链:{变量对象+所有父执行环境的变量对象}
scopeChain: {
/* variableObject + all parent execution context's variableObject */
},
//变量对象:{函数形参+内部的变量+函数声明(但不包含表达式)}
variableObject: {
/* function arguments / parameters, inner variable and function declarations */
},
this: {}
}
闭包:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。简单说,指有权访问另一个函数作用域中的变量的函数
函数作用域是最常见的作用域单元。作用域下保存对应的变量或者函数,同时作用域是嵌套的
,当在查找当前作用域下的变量时,找不到会递归去上层的作用域查找,直到找到为止;当找不到时则直接报错。
var globalScope = true
if (globalScope) {
var innerScope = 123
}
console.log(innerScope) // 123
块作用域本质上是将块变成一个可以被关闭的作用域。
// let关键字隐式的将变量innerScope绑定到花括号{..}作用域中
let globalScope = true
if (globalScope) {
let innerScope = 123
}
console.log(innerScope) // innerScope is not defined
this在运行时绑定
,并不是在编写时绑定。执行上下文
,this是执行上下文的一个属性。执行上下文包含:
var name = 222
var obj = {
name: '111',
func: function() { return this.name }
}
obj.func() // 111 等价于 obj.func.call(obj)
// 运行时绑定this
obj.func.call(window) // 222
let { func } = obj
func() // 222 顶层作用域默认是window
默认绑定
严格模式绑定到undefined,否则绑定到window隐式绑定
绑定到上下文对象显示绑定
call/apply/bind绑定到指定对象new绑定
绑定到新创建的对象ES6的箭头函数并不会使用以上四种绑定规则,而是根据当前的词法作用域来决定this(等同于ES5 self = this机制)
// 隐式绑定
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo() // 2
// 隐式丢失
var bar = obj.foo // bar 引用的是foo函数本身,而不是对象obj
bar() // undefined
// 最常见的隐式丢失
// 等同于 fn = obj.foo
setTimeout(obj.foo, 100) // undefined
函数 ~= 对象
原生函数
(它也可以使用构造函数创建实例)
typeof 42 === 'number' // true
// 特殊
typeof null === 'object'
typeof function a() {} === 'function'
typeof [] === 'object'
js变量没有类型,只有值才有
// 只有值才有类型。变量只作为一种标记
var a = 42 // typeof a === 'number'
a = true // typeof a === 'boolean'
Array.prototype.slice.call(args)与Array.from(args) 都是把类似数组对象,转换为真正的数组对象。
JS为基本数据类型值提供了封装对象,称为原生对象。
// 必要时js引擎会自动把字符串字面量转换成String对象
// 所以该字符串字面量可以有属性和方法
var str = 'hello' // 文字形式
str.length // 5
str.charAt(0) // l
var strObj = new String('hello') // 构造形式
回调函数表达异步和并发有两个主要缺陷:缺乏顺序性和可信任性
。
Promise封装了依赖时间的状态
--等待底层值的完成或拒绝,所以Promise本身与时间无关。因此Promise可以按照可预测的方式组成(组合),而不用关心时序或底层的结果。
另外,一旦Promise决议,它就永远保持在这个状态。
Promise解决了因只用回调的代码而备受困扰的控制反转
问题。
但Promise也没有摈弃回调,只是把回调的安排转交给一位可信任的中介机制。
生成器
是一类特殊的函数((关键字:* 和yield)),可以一次或多次启动和停止,并不一定要完成。
生成器对象
是由一个 generator function 返回的,并且它符合可迭代协议([Symbol.iterator])和迭代器协议(next)。这两个协议在ES6的for of/解构等起很大作用。
使用生成器对象,解决异步代码同步问题:
function *main() {
let text = yield foo(1, 2)
console.log(text)
}
function foo(x, y) {
let url = `http://xxx/${x + y}`
// it来自main()
// it.next再次进入main中,第二次返回data,赋值给text变量
ajax(url, data => it.next(data))
}
let it = main() // 生成器对象
// 启动,执行foo(1, 2)。遇到yield,暂停,第一次返回undefined
// ajax是异步,所以不受暂停影响
it.next()
以上yield出来的是数据,可以构造为一个promise,通过生成器把它yield出来。
foo(1, 2).then(...).then(...)
// 工具库辅助,自动执行同时返回Promise值。
// ES9的async/await就是帮你干run的活
function run(gen) {
var args = [].slice.call(argments, 1);
var it;
it = gen.apply(this, args)
// 返回promise对象
return Promise.resolve().then(function handleNext(value){
var next = it.next(value) // 启动
return (function handleResult(){
if (next.done) return next.result
return Promise.resolve(next.value).then(handleNext)
})(next)
})
}
run(main) // main见上面