函数, scope, execution context, scope chain

一、 函数

1 函数常见的四种形态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//函数的声明形态
function func() {
console.log("函数的声明形态")
}

//函数的表达式形态 之一
let func0 = function() {
console.log("函数的表达式形态");
}

//函数的表达式形态 之二
(function func1() {})

//函数的嵌套形态
let func2 = function() {
console.log("函数的嵌套形态");
let func3 = function() {
console.log("func2嵌套在func1里")
}
func3();
}

// 函数的闭包形态
let func4 = function() {
var a = "func4";
return function() {
console.log("我是以闭包形态存在的函数:" + a);
}
}
//所有的函数都通过一对括号“()”调用
func();
func0();
func1();
func2();
func4()();

2 函数声明提升

只有声明形态的函数,才具有提升的特性。即 代码的执行顺序提升排到最前面

3 箭头函数

用(参数) => { 表达式 }这种写法声明一个函数,就叫箭头函数。
一个明显作用是: 可以保持this的指向,总是指向定义它时所在的上下文环境。

4 高阶函数

若某个函数可以接收另一个函数作为参数,该函数就称之为高阶函数。

5 函数重载(overload)

就是函数名称一样,但是随着传入的参数个数不一样,调用的逻辑或返回的结果会不一样。

二、

在 ES6 之前,Javascript使用函数作用域全局作用域,直到ES6之后,**才有了块级作用域**

1 作用域(Scope)

作用域即函数或变量的可见区域。 函数或者变量不在这个区域内,就无法访问到。

1 函数作用域

用函数形式以function(){……}类似的代码包起来的(省略号……)区域,即函数作用域
任意代码片段外面用函数包装起来,就好像加了一层防护罩,可以将内部的变量和函数隐蔽起来,外部无法访问到内部的内容。

全局作用域,是定义在最外层的变量或者函数,可以在任何地方访问到它们。

2 ES6引入块级作用域

ES6规定,在某个花括号对{ }的内部用 let 关键字生声明的变量和函数拥有块级作用域,这些变量和函数它们只能被花括号对{ }的内部的语句使用,外部不可访问。

为什么要引进块级作用域?

首先, var声明的变量有副作用:声明提前
eg.

1
2
3
4
5
6
7
(function() {
console.log(a); //>> undefined
console.log(b); //>> ReferenceError
var a = "coffe"; //声明提前
let b = "1891"; //由let关键字声明的变量,不存在提前的特性
})();

上面这段代码,其中var a = “coffe” 含两个操作,一个是变量a的声明(也即var a),一个是赋值(也即a = “coffe”)。
声明提前的意思是,用var关键字声明的变量,其实可以看做是在**函数体内最顶端声明**的。
其次,var声明变量有污染。
eg.

1
2
3
4
5
6
7
8
9
10
(function() {
for (var i = 0; i < 100; i++) {
//……很多行代码
}
function func() {
//……很多行代码
}
//……很多行代码
console.log(i); //>> 100
})();

循环里面的i在循环完毕后就没用了,但并未被回收掉,而是一直存在的“垃圾”变量。而用let声明变量,事后这种垃圾变量会被回收掉。

2 执行上下文(Execution Context)

执行上下文就是当前 JavaScript 代码被解析和执行时所在的环境,也叫作执行环境。

  • 全局执行上下文
    这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。
    它做了两件事:
  1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象
  2. 将 this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
  • 函数执行上下文
    每次调用函数时,都会为该函数创建一个新的执行上下文。
    每个函数都拥有自己的执行上下文,只有在被调用时才会创建。
    一个程序中可以存在任意数量的函数执行上下文

在开始执行代码开始时,首先会产生一个全局执行上下文,
调用函数时,会产生函数执行上下文。
函数调用完成后,它的执行上下文以及其中的数据都会被销毁,重新回到全局执行环境,
网页关闭后全局执行环境也会销毁。
其实这是一个入栈出栈的过程,
全局上下文永远在栈底, 有且只有一个。
而当前正在函数执行上下文在栈顶。

3 作用域链(Scope Chain)

作用域链保证了当前执行上下文对符合访问权限的变量和函数的有序访问。
作用域链的最顶端一定是当前作用域(local scope)对应的变量对象,最底端一定是全局作用域对应的变量对象(全局VO)
作用域链就像一个蒸笼:
最底下的一屉,相当于全局作用域,它里面的蒸汽(变量和函数的可见性)可以渗透到整个蒸笼,底层之上的其他屉相当于局部作用域,这些上面屉的蒸汽只能影响更上面的屉。

4 变量/函数的查找机制

查找变量/函数时,JS引擎是从里离它最近的作用域开始的查找的,也即从离它最近的变量对象(VO)开始查找。
如果在当前的变量对象里面找不到目标变量/函数,就在上一级作用域的变量对象里面查找。若这时找到了目标变量/函数,则停止查找;若找不到,一直回溯到全局作用域的变量对象里查找,若仍找不到目标变量/函数,停止查找。

查看评论