Skip to content

Latest commit

 

History

History
242 lines (198 loc) · 6.12 KB

7. 函数表达式.md

File metadata and controls

242 lines (198 loc) · 6.12 KB

函数的创建

创建函数的方法有两种:函数声明和函数表达式。

函数声明

```function functionName(arg0, arg1, arg2) {}`

函数声明最重要的一个特征就是函数声明提升(function declaration hoisting)

sayHi(); // 不会报错
function sayHi() {
    alert("Hi!"); 
}

函数表达式

var functionName = function(arg0, arg1, arg2) {}

上面所创建的是一个匿名函数(拉姆达函数)。因为 function 关键字后面没有标识符。

sayHi(); // 出错
var sayHi = function() {
    alert("Hi!");
}

递归

在递归中利用 arguments.callee 代替函数名,可以确报无论怎样调用函数都不会出问题。但严格模式下访问该属性会出错,可以用命名函数表达式来代替。

// 原始写法
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * factorial(num-1);
    }
}

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); // 出错,因为 factorial 已经被设为 null 

// arguments.callee 写法
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}

// 命名函数写法
var factorial = (function f(num){
    if (num <= 1) {
        return 1;
    } else {
        return num * f(num-1);
    }
});

闭包

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

创建闭包的常见方式:在一个函数内部创建另一个函数。

作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

闭包与变量

作用域的配置机制导致闭包只能取得包含函数中任何变量的最后一个值:

function createFunctions() {
    var result = new Array();

    for (var i=0; i<10; i++) {
        result[i] = function() {
            return i;
        };
    }
    return result; // 每个函数都返回10
}

可以通过创建另一个匿名函数强制让闭包的行为符合预期:

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 一般指向 window

可以把外部作用域中的 this 对象保存在闭包能访问到的变量里,就能让闭包访问到该对象:

var name = "This Window";

var object = {
    name : "My Object",

    getNameFunc : function() {
        var that = this;
        return function() {
            return that.name;
        };
    }
};

alert(object.getNameFunc()()); // "My Object"

内存泄漏

如果闭包的作用域链中保存着一个 HTML 元素,该元素将无法被销毁。

模仿块级作用域

JS 没有块级作用域的概念 (注意!ES6新增了块级作用域)

在没有块级作用域之前,函数声明后面不能跟圆括号:

function() {
    // 这里是块级作用域
}(); // 出错

(function(){
    // 这里是块级作用域
})(); // 将函数声明转换成函数表达式就不会出错

私有变量

严格来讲 JS 没有私有成员的概念,所有对象属性都是公有的,不过有一个私有变量的概念。任何在函数中定义的变量都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

特权方法(privileged method):有权访问私有变量和私有函数的公有方法。

function Person(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"

静态私有变量

函数声明只能创建局部函数,初始化未经声明的变量总是会创建一个全局变量。

在私有作用域中定义私有变量或函数以创建特权方法,在这种模式下私有变量和函数是由实例共享的。例子:

function() {

    var name = "";

    Person = function(value) {
        name = value;
    };

    Person.prototype.getName = function() {
        return name;
    };

    Perosn.prototype.setName = function(value) {
        name = value;
    };
}();

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

var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"

模块模式 module pattern

为单例(只有一个实例的对象)创建私有变量和特权方法。

如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,就可以使用模块模式。

var application = function() {

    // 私有变量和函数
    var components = new Array();

    // 初始化
    components.push(new BaseComponent());

    // 公共
    return {
        getComponentCount : function(){
            return components.length;
        },

        registerComponent : function(component) {
            if (typeof component == "object") {
                components.push(component);
            }
        }
    };
}();

增强的模块模式

适合单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况:

var application = function() {

    // 私有变量和函数
    var components = new Array();

    // 初始化
    components.push(new BaseComponent());

    // 创建 application 的一个局部副本
    var app = new BaseComponent();

    // 公共接口
    app.getComponentCount = function() {
        return components.length;
    };

    app.registerComponent = function(component) {
        if (typeof component == "object") {
            components.push(component);
        }
    };

    // 返回这个副本
    return app;
}();