JS红皮书读书笔记-07-函数表达式

TOC
  1. 1. 递归
  2. 2. 闭包
    1. 2.1. 闭包与变量
    2. 2.2. this对象
    3. 2.3. 内存泄露
  3. 3. 模仿块级作用域
  4. 4. 私有变量
    1. 4.1. 静态私有变量
    2. 4.2. 模块模式

第五章讲了函数的使用方式, 要么函数声明, 要么函数表达式.

关于函数声明, 有一个重要的特征, 就是会函数声明提升:

sayHi();	// 这样是可以的, 如果是函数表达式 就会报错
function sayHi(){
alert("Hi!");
}

因为函数声明的这个特性, 千完不要在判断语句中使用函数声明:

// 不要这么做!
if(condition){
function sayHi(){
alert("Hi!");
}
} else {
function sayHi(){
alert("Yo!");
}
}

递归

我们看三个示例.
示例1:

function factorial(num){
if (num <= 1){
return 1;
} else {
return num * factorial(num-1); // 耦合问题
}
}

示例2:

function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1); //严格模式下会报错
}
}

示例3:

// 这里使用了函数表达式, 实际上就是示例1的包装
var factorial = (function f(num){
// 同时使用函数表达式和函数声明, 那么函数声明的函数名只能在本函数中被访问
if (num <= 1){
return 1;
} else {
return num * f(num-1);
}
});

闭包

闭包是指有权访问另一个函数作用域中的变量的函数

我们看这个示例:

function createComparisonFunction(propertyName) {

return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];

if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}

其中返回的匿名函数就是一个闭包.

闭包与变量

function createFunctions(){ 

var result = new Array();

for (var i=0; i < 10; i++){
result[i] = function(){ // 闭包保存的是包含环境的变量, 而这个变量经过多次改变, 根据闭包的特性, 只能获取到最后一个改变的值.
return i;
};
}

return result; // 注意这里返回的是闭包数组
}

发现了这个特点, 我们可以再创建多一层闭包:

function createFunctions(){ 

var result = new Array();

for (var i=0; i < 10; i++){
result[i] = (function(index){
return function(){ //注意这里是没有参数的
return index; //强行保留起来
};
})(i);
}

return result;
}

this对象

我们在第五章讲过, 函数内部始终有两个特殊对象, 一个是arguments, 一个是this.

this指的是最后调用它的环境对象, 在返回的匿名函数中, this指向window,在构造函数中, this就是将要生成的对象, 在DOM中, this指向当前的html节点(这个后面的章节会讲).

你可能不太理解 this为啥指的是最后调用它的环境对象, 我们来看几个例子:

function a(){
console.log('this in a:',this) //window
function b(){
console.log('this in b:',this) //window
}
b()
return function(){
console.log('this in c:',this) //window
}
}
var c = a();
c();

可以看得出, 无论你函数有没有嵌套, 或者是否为匿名函数, 只要它没有被某一个明确的非window对象显式调用, 那么this都指向window. 这是JS的设计,便于this在函数中的表现一致性.

我们再来看一个例子:

var name = 'window';
var object = {
name:'object'
}
function sayName(){
console.log(this.name);
}
sayName(); // 'window', 没毛病
// 但是我想让object调用sayName怎么办? 前面我们学的call和apply就排上用处了
sayName.call(object); // 'object';

//或者用es5的bind方法, 手动添加到object上
sayName.bind(object)();// 'object';

内存泄露

主要是针对旧版IE, 略

模仿块级作用域

ES5没有块级作用域:

function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //依然可以访问
}

为了实现块级作用域, 我们通常用立即执行的匿名函数实现:

(fnuction(){
// your code is here
var i =10;
})();
alert(i); //报错

著名的jQuery就是用这种包装形式.

私有变量

严格来讲,JavaScript 中没有私有成员的概念;所有对象属性都是公有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。

我们来看一个例子:

function Person(name){ 
// this.name = name; // 通常我们会这样设置属性
this.getName = function(){
return name;
};

this.setName = function (value) {
name = value;
};
}

var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"

上面的示例可以看出, 对象实例无法直接访问name这个属性, 只能通过方法去访问或者修改.但是要记住本示例的代码也是有缺陷的, 为了访问私有变量而生成许多功能相同的特权方法, 是一种资源的浪费.

静态私有变量

模块模式


后面会另外写一篇文章讲设计模式

本章完

访客评论