JS知识点梳理(5)-函数

定义 函数有 2 种形式

  • 函数声明,它有一个重要特就是函数声明提升,即在执行代码之前会先读取函数声明。
  • 函数表达式,创建一个函数并将其赋值给变量,此种情况下创建的函数叫匿名函数,因为 function 关键字后面没有标识符

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,在一个函数内部创建另一个函数。

作用域链

作用域链的本质是一个指向变量对象的指针列表。无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。
当某个函数被调用时,会创建一个执行环境及相应的作用域链,这个作用域链被保存在内部的[[Scopes]]属性中。然后使用arguments和其他命名参数的值函数的活动对象,并将该活动对象放至作用域链前端,外部函数的活动对象位于作用域链第二位,外部函数的外部函数的活动对象处于第三位,….直至作为作用域链终点的全局执行环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createComparisonFunction(propertyName) {
return function(obj1, obj2) {
var val1 = obj1[propertyName];
var val2 = obj2[propertyName];

if (val1 < val2) {
return -1;
} else if (val1 > val2) {
return 1;
} else {
return 0;
}
};
}
var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });
// 解除对匿名函数(闭包)的引用(以便释放内存)
compare = null;

在匿名函数从 createComparisonFunction()中返回后,它的作用域链被初始化位包含 createComparisonFunction()函数的活动对象和全局变量对象。并且在 createComparisonFunction()函数执行完毕后,其执行环境的作用域链会被销毁,但它的活动对象会保留在内存中,因为匿名函数的作用域链仍然在引用这个活动对象。直到匿名函数被消耗,createComparisonFunction()的活动对象才会被销毁。

闭包作用

闭包有两个作用:

  • 可以读取自身函数外部的变量(沿着作用域链寻找)
  • 让这些外部变量始终保存在内存中

闭包与变量

一个值的注意的地方:闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某一个特殊的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function() {
return i;
};
}
return result;
}
var result = createFunctions();
console.log(result()[0]()); // 10
console.log(result()[1]()); // 10
console.log(result()[9]()); // 10

上述函数会返回一个函数数组,表面是,似乎每个函数都应返回自己的索引值,但实际上,每个函数都返回 10.因为每个函数的作用域链中都保存着 createFunctions()函数的活动对象,所以它们引用的都是同一变量 i,当 createFunctions()返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量对象,所以每个函数内部的 i 的值都是 10
closure $ var

要解决此问题,可以通过创建另一个匿名函数,并立即执行匿名函数,将其结果赋值给数组。这里的匿名函数中有一个参数 num,也就是最终函数要返回的值,即返回了一个返回 num 的闭包。

1
2
3
4
5
6
7
8
9
10
11
12
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = (function(num) {
return function() {
return num;
};
})(i);
}
return result;
}

关于 this 对象

this 对象是在运行时基于函数的执行环境绑定的:在全局函数中,this 指向 window,而当函数被作为某个对象的方法调用时,this 指向那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window
另外,每个函数在被调用时都会自动获取 2 个特殊变量:this 和 arguments。内部函数在搜索这 2 个 变量时,只会搜索到其活动对象为止,因此永远不能直接访问外部函数中的这两个变量。不过,可以把外部函数中的 this 对象保存在一个闭包能访问的变量里,就可以让闭包访问该对象了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var name = "The Window";
var obj = {
name: "My Object",
getName: function() {
return function() {
return this.name;
};
},
getName2: function() {
var that = this;
return function() {
return that.name;
};
}
};
let getNameFun = obj.getName();
console.log(getNameFun()); // The Window

let getName2Fun = obj.getName2();
console.log(getName2Fun()); // My Object

模仿块级作用域

JavaScript 没有块级作用域的概念,立即执行的匿名函数可以用来模仿块级作用域,语法如下:

1
2
3
(function() {
// 块级作用域
})();

这种技术经常用于全局作用域中的外部函数,从而限制向全局作用域中添加过多的变量和函数。通过创建私有作用域,每个开发人员既可以使用 自己的变量,又不必担心同全局变量和函数发生命名冲突。
此种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就立即销毁其作用域链了

1
2
3
4
5
6
(function(){
var now = new Date();
if(now.getMonth === 0 && now.getDate() === 1) {
alert("Happy new year!");
}
})();

私有变量

任何函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。它包括函数的参数,局部变量,和在函数定义的其他函数。
如果在函数内部创建一个闭包,那么闭包通过作用域链就可以访问这些变量,利用这一点,就可以创建用于访问私有变量的公用方法。
有权访问私有变量和私有函数的公用方法称为特权方法

在构造函数中定义特区方法

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
this.getName = function() {
return name;
};
this.setName = function(value) {
name = value;
};
}
var person = new Person("jiang");
console.log(person.getName()); // "jiang"
person.setName("tingting");
console.log(person.getName()); // "tingting"

2 个特权方法: getName()、 setName(),都可在构造函数外部使用,有权访问私有变量name.因为这 2 个方法都在构造函数内部定义,它们作为闭包能够通过作用域链访问到name.

模块模式

模块模式是为单例(只要一个实例的对象)创建私有变量和特权方法。 JavaScript 是以对象字面量的方式来创建单例对象的。
模块模式通过为单例添加私有变量和特权方法使其得到增强,语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var singleton = (function() {
var privateVariable = 10;
function privateFunction() {
return privateVariable;
}
// 公有属性,特权方法
return {
publicProperty: "publicProperty",
publicMethod: function() {
privateVariable++;
return privateFunction();
}
};
})();
console.log(singleton.publicProperty); // "publicProperty"
console.log(singleton.publicMethod()); // 11