函数表达式

下述内容主要讲述了《JavaScript高级程序设计(第3版)》第7章关于“函数表达式”。

一、回顾

定义函数的方式有两种:第一种是“函数声明”,另一种就是“函数表达式”。 “函数声明”会被提升,意味着把函数声明放在调用它的语句后面。 示例1:

a();    // a
b();    // TypeError: b is not a function
function a() {
    console.log("a");
}
var b = function() {
    console.log("b");
};

声明本身会被提升,而包含函数表达式在内的赋值并不会被提升。 函数提升的关键,就是理解函数声明与函数表达式之间的区别。 了解更多的变量提升问题,请查看JavaScript提升(你不知道的JavaScript) 示例2:

if(true) {
    function sayHi() {
        console.log("Hi, Jerry!")
    }
} else {
    function sayHi() {
        console.log("Hi, Tang!");
    }
}
sayHi();
// 在chrome、firefox下输出:Hi, Jerry!
// 在Safari下输出:Hi, Tang!

示例3:

var sayHi;
if(true) {
    sayHi = function() {
        console.log("Hi, Jerry!")
    }
} else {
    sayHi = function() {
        console.log("Hi, Tang!");
    }
}
sayHi();
// 全部输出:Hi, Jerry!

示例4:

function sayHi() {
    console.log("Hi, Jerry!")
}
function sayHi() {
    console.log("Hi, Tang!");
}
sayHi(); // 全部输出:Hi, Tang!

说明:后面的函数声明可以覆盖前面的。

二、递归

示例5:

function factorial(num) {
    if(num <= 1) {
        return 1;   // 书写递归函数,尽量要先写结束条件
    } else {
        return num * factorial(num-1);  // num--
    }
}
factorial(4);   // 24
var anotherFactorial = factorial;
factorial = null;
anotherFactorial(3);    // TypeError: factorial is not a function

原因:在调用anotherFactorial()时,由于必须执行factorial(),而factorial已经不再是函数,所以就会导致错误。

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

注意:在严格模式下,不允许使用arguments.callee

示例6 – 具名函数:

var factorial = function fn(num) {
    if(num <= 1) {
        return 1;
    } else {
        return num * fn(num-1);     // num--
    }
}
var anotherFactorial = factorial;
factorial = null;
anotherFactorial(3);    // 6

三、闭包

形式:在一个函数内部创建另外一个函数。 定义:指有权访问另一个函数作用域中的变量的函数。 当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。 JavaScript作用域闭包(你不知道的JavaScript)

示例7:

function createComparisonFunction(propertyName) {
    return function(obj1, obj2) {
        var value1 = obj1[propertyName],
            value2 = obj2[propertyName];
        return value1 - value2;
    }
}

var p1 = { age: 25 };
var p2 = { age: 26 };
var compareAge = createComparisonFunction("age");
var result = compareAge(p1, p2);
if(result === 0) {
    console.log("一样大!")
}else if(result > 0) {
    console.log("p1大!")
}else {
    console.log("p2大!");
}
compareAge = null;  // 释放内存

作用:一般来讲,函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。 上述createComparisonFunction执行完毕,其作用域会被销毁,但其活动对象仍然保存在内存中,直到匿名函数被销毁后。 解释:每个执行环境都有一个表示变量的对象–变量对象。作用域链本质是一个指向变量对象的指针列表,它只引用但不实际包含变量对象!!! 建议:过多的闭包可能会导致内存占用过多,建议只在绝对必要时使用闭包。

示例8–经典面试题(为每个li绑定click事件,并输入对应的顺序号。):

<ul>
    <li>第一条</li>
    <li>第二条</li>
    <li>第三条</li>
    <li>第四条</li>
</ul>

每个函数的作用域中都保存着活动对象,所以它们引用的都是一个变量i。

var list = document.getElementsByTagName("li");

for(var i = 0, len = list.length; i < len; i++) {
    list[i].onclick = function() {
        console.log(i); // 4
    }
}

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

for(var i = 0, len = list.length; i < len; i++) {
    list[i].addEventListener("click", (function(i) {
        return function() {     // function(i)
            console.log(i);
        }
    })(i))
}

更巧妙的方法

list.onclick = function() {
    console.log($(this).prevAll().length);
}

四、this对象

this对象在运行时基于函数的执行环境绑定。 在全局函数中,this等于window,当函数作为某个对象的方法调用时,this等于当前对象。 JavaScript中的this(你不知道的JavaScript)

示例9:

var name = "window";
var object = {
    name: "current object",
    getName: function() {
        return this.name;
    }
};
object.getName();   // current object
var name = "window";
var object = {
    name: "current object",
    getName: function() {
        return function() {
            return this.name;
        }
    }
};
object.getName()();  // window
// var getNameByWindow = object.getName(); 
// getNameByWindow();
var name = "window";
var object = {
    name: "current object",
    getName: function() {
        var that = this;
        return function() {
            return that.name;
        }
    }
};
object.getName()(); // current object

五、内存溢出

JavaScript对象和Dom对象循环引用,导致内存不能释放,内存溢出! 示例10:

var element = document.getElementById("id");    // Dom对象
var id = element.id;                            // JavaScript对象
element.onclick = function() {
    console.log(id);
};
element = null;

必须记住:闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用。因此有必要把element设置为null,解除对DOM对象的引用,顺利减少其引用数,收回占用内存。

六、块级作用域

JavaScript词法作用域(你不知道的JavaScript) 示例11:

for(var i = 0; i < 10; i++) {}
var i;  // 被忽略,变量提示
console.log(i); // 10
(function() {
    for(var i = 0; i < 10; i++) {}
})();
var i;
console.log(i);  // undefined

七、私有变量

JavaScript函数作用域,使得函数中定义的变量,都可以被认为是私有变量。 示例12 –构造函数模式:

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

var p1 = new Person("Jerry");
var p2 = new Person("Tang");
p1.getName();
p2.getName();

每个实例都会创建上述同样的方法

示例13 – 静态私有变量:

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

var p1 = new Person("Jerry");
var p2 = new Person("Tang");
p1.getName();
p2.getName();

代码得到了复用,但是所有实例返回相同值

模块模式:创建的每个单例都是Object的实例。

var getSingle = function(fn) {
    var result;
    return function() {
        return result || (result = fn.apply(this, arguments));
    };
};

// 测试
function testSingle(){}
getSingle(testSingle)() === getSingle(testSingle)();    // true

所以,要视情况而定,选择何种方式创建私有变量!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券