函数表达式

来源:互联网 发布:cctv13网络电视 编辑:程序博客网 时间:2024/03/29 04:45

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

一、回顾

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

a();    // ab();    // TypeError: b is not a functionfunction 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

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

3 0