深入理解JavaScript
# 深入理解JavaScript
# 作用域
# 理解javascript的编译
尽管通常我们将JavaScript归类为“动态”或者“解释执行”的语言,但实际上它是一门编译的语言,只是与传统编译语言不同的是,它不是提前编译的,编译的结果也不能在不同的系统之前进行移植。
对于JavaScript来说,大部分情况下编译发生在代码执行前的几微秒的时间,简单的说,任何代码段在执行前都要进行编译(通常是执行前)。因此,JavaScript编译器首先就会对var a =2;这段代码进行编译,然后做好执行它的准备,并且通常马上就会执行它。
变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后再运行时引擎会在作用域中查找该变量,如果能找到就会对它进行赋值。
# 理解变量提升(编译器的作用)
编译器会对函数声明进行变量提升并且赋值 编译器会对var定义的变量进行变量提升,但不直接赋值,默认值是undefined。 编译器对let定义的变量不会进行变量提升,所以let变量,必须先声明才能使用。(例) 函数声明和变量声明都会提升,但如果名称相同,则函数会首先被提升,然后才是变量。如果同时定义了多个名称相同的函数,则后面的函数函数声明还是会覆盖掉前面的声明(例)
# 理解作用域(全局作用域、函数作用域)
作用域就是如何在程序中定义变量,以及之后如何方便的查找变量的规则,同时确定了当前代码对变量的访问方式。
作用域就是根据名称来查找变量的一套规则。
# 静态作用域 词法作用域(变量查找的原理)
javascript采用的是静态作用域,函数的作用域在函数定义的时候就决定了。
代码分析: 先从foo函数内部查找是否有局部变量value,如果没有,就根据书写的位置,查找上面一层的代码,也就是value等于1,所以结果是1。
# 全局作用域和函数作用域 块级作用域
变量定义在全局里面,任何位置都可以访问,即内部的代码可以访问内部变量的同时也可以访问它上一层空间定义的变量。 函数内部定义的变量,它的作用范围只是函数内部,函数外部的全局环境无法访问函数内部的变量,其他函数内部也无法访问内部的变量。 例子 块级作用域:es6中引入了let关键字,来进行变量声明,同时将变量绑定到所在的任意作用域中({})
# 作用域的嵌套
当一个块或函数嵌套在另一个块或者函数中,就会发生作用域的嵌套。因此,在当前作用域中无法找到某个变量时,js引擎就会在外层的作用域中查找,知道找到该变量
# 分析代码
function foo(a){
var b = a * 2
function bar(c) {
console.log(a, b, c)
}
bar(b*3)
}
foo( 2 )
2
3
4
5
6
7
8
9
10
# 闭包
# 闭包的定义
广义的闭包是当函数可以记住并访问所在的词法作用域时,就产生了闭包。 无论通过何种手段将内部函数传递到本身的词法作用域以外,他都会持有对原始定义作用域的引用,无论在何处执行这个函数都是会使用到闭包 一般主要包括,返回值是函数,参数是函数都会产生闭包。 自由变量: 不是函数的参数,并且函数中也没有定义的变量叫做自由变量,凡是使用了自由变量,也会产生闭包。
# 闭包的使用
例子(循环和闭包)
for(var i =0; i< 5; i++) {
setTimeOut(function(){
console.log(i)
}, i*1000)
}
for(var i =1; i<= 5; i++) {
(function(i){
setTimeOut(function(){
console.log(i)
}, i*1000)
}
})(i)
}
for(let i =0; i< 5; i++) {
setTimeout(function(){
console.log(i)
}, i*1000)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
闭包与模块
function CoolModule() {
var something = "cool"
var another = [1,2,3]
function doSomething() {
console.log(something)
}
function doAnother(){
console.log(another.join('-'))
}
return {
doSomething: doSomething,
doAnother: doAnother
}
}
var foo = CoolModule();
foo.doSomething()
foo.doAnother()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 执行上下文
JavaScript中,代码执行的基础单元是函数。函数之间也会进行相互的调用
JavaScript代码有两种类型,一种是全局代码,在所有函数外部定义;一种是函数代码,位于函数内部。JavaScript引擎在执行代码时候,每一条语句都处于特定的执行上下文中。
JavaScript有两种的执行上下文:全局执行上下文和函数执行上下文。二者的差别是:全局执行山下文只有一个,当JavaScript程序开始执行时就已经创建了全局上下文;而函数执行上下文是在每次调用函数时,就会创建新的。 一旦发生函数调用,当前的执行上下文就必须停止执行,并创建新的函数执行上下文来执行函数。当函数执行完成后,将函数执行的上下文销毁,并重新回到发生调用函数的执行上下文中。我们通常使用执行上下文栈来跟踪执行上下文。
例子:(函数的相互调用) 同时画出因调用而形成的执行上下文栈
# 执行上下文包含的属性
变量对象(VO)或者活动对象(AO)
作用域链(Scope chain)
this
# 了解执行上下文的处理过程
分析和执行 变量对象(活动对象) 1. 函数的所有形式参数 param1:'world'
2. 函数声明
abc: 函数abc
3. 变量声明
result: undefined
# 变量对象
变量对象是与执行山下文相关的数据作用域,存储在上下文中定义的变量和函数声明
aarguments
全局上下文中的变量对象就是全局对象
在函数上下文中,我们使用活动对象来表示变量对象
活动对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性进行初始化,arguments属性值是Arguments对象 函数调用时候,就会先创建函数的执行上下文,该执行上下文中包含着函数的活动对象,此时活动对象包括函数的所有形参、函数的声明、变量声明
# 作用域链
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(语法层面的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链
函数的作用域在函数定义的时候就决定了。
# 原型与原型链
javascript是一门基于原型进行继承的面向对象的语言。
# 构造函数
普通函数,但是利用new的方式来调用,调用函数的结果会生成一个新的对象 函数也是一个对象 (call apply length等属性 prototype也是一个关键的属性) 函数的prototype属性是一个对象,因此我们可以像使用对象一样去添加内容
# instanceof 操作符
可以判断复杂的数据类型,就是利用typeof来判断为object的数据类型
# 原型链
通过原型链可以去查找我们实例本身没有的属性,要查找实例的属性,一般先从自己查找起,如果自身没有,则系统会自动帮我们从原型链上去查找,即构造函数的prototype属性上去查找