资讯专栏INFORMATION COLUMN

JavaScript函数表达式——“函数的递归和闭包”的注意要点

winterdawn / 3012人阅读

摘要:如调用函数声明函数不会报错使用函数表达式则不可以报错创建函数的两种方式,一个是函数声明如第一种方式一个是函数表达式如第二种方式。第二种函数创建方式创建的函数叫匿名函数或拉姆达函数,因为关键字后面没有标识符。

函数表达式的基本概念 name属性和函数提升

首先,name属性,通过这个属性可以访问到给函数指定的名字。(非标准的属性)如:

function People(){};
console.log(People.name); //People

其次,函数声明提升,意味着可以把函数声明放在调用它的语句后面。如:

sayHi(); //调用函数
function sayHi(){ //声明函数
    console.log("Hi");
} //不会报错

使用函数表达式则不可以:

sayHi();
var sayHi = function(){
    console.log("Hi");
} //报错

创建函数的两种方式,一个是函数声明(如第一种方式);一个是函数表达式(如第二种方式)。第二种函数创建方式创建的函数叫“匿名函数”或“拉姆达函数”,因为function 关键字后面没有标识符。

函数提升的常见错误

需要注意的是,作为对比,下面的两种代码中,第一个是错误的(会导致各浏览器出现不同的问题);第二个才使正确的。代码如下:

var condition = true;
if (condition){
    function sayHI(){
        console.log("hi")
    }
    sayHI(); //"hello"
}else{
    function sayHI(){
        console.log("hello")
    }
    sayHI();
}

报错

var condition = false;
var sayHi;
if(condition){
    sayHi = function(){
        console.log("hi")
    };
    sayHi();
}else{
    sayHi = function(){
        console.log("hello")
    };
    sayHi(); //hello
}

没有错误

var condition = true;
if(condition){
    var sayHi = function(){
        console.log("hi")
    };
    sayHi(); //hi
}else{
    var sayHi = function(){
        console.log("hello")
    };
    sayHi(); //hello
}

这里也不会出现问题。出现上面问题的根源就是函数提升,就是函数声明和函数表达式之间的区别所导致的。

函数的递归

递归函数就是在一个函数通过名字调用自身的情况下构成的。如:

function factorial(num){
    if(num <= 1){
        return 1;
    }else{
        return num * factorial(num - 1);
    }
}    
console.log(factorial(4)); //24 4*3*2*1

但是,函数里面包含了函数自身所以,在应该使用arguments.callee来解决该问题。如:

function factorial(num){
    if(num <= 1){
        return 1;
    }else{
        return num * arguments.callee(num - 1);
    }
}    
console.log(factorial(4)); //24 4*3*2*1

但如果使用上面这种方法,则在严格模式下行不通。不过可以使用命名函数表达式来达成相同的结果。如:

var factorial = (
    function f(num){
        if(num <= 1){
            return 1;
        }else{
            return num * f(num - 1);
        }
    }
);    
console.log(factorial(4)); //24 4*3*2*1

即,把它包含在一个变量里面,在遇到其他这种需要使用arguments.callee的时候都可以这样做。

函数的闭包

闭包就是有权访问另一个函数作用域中的变量的函数。常见的创建闭包的方式就是在一个函数内部创建另一个函数。在此之前应该先掌握作用域链的概念(见《Javascript执行环境和作用域的注意要点》一文《Javascript执行环境和作用域的注意要点》)

作用域链

以下面的代码为例

function compare(value1,value2){
    if (value1 > value2){
        return 1;
    }else if (value1 < value2){
        return -1;
    }else {
        return 0;
    }
}
var result = compare(5,10);

调用函数compare时,函数执行环境中的作用域链:

作用域链本质上是一个指向变量对象的指针列表。

另一个例子:

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;
        }
    };
}
var compare = createComparisonFunction("name");
var result = compare({name: "Nicholas"},{name: "Greg"});

这个就相当于:

var compare = function(object1,object2){
    var value1 = object1["name"];
    var value2 = object2["name"];
    
    if (value1 < value2){
        return -1;
    }else if(value1 > value2){
        return 1;
    }else{
        return 0;
    }
};
var result = compare({name: "Nicholas"},{name: "Greg"});

相当于:

var result = function(){
    var value1 = {name: "Nicholas"}["name"];
    var value2 = {name: "Greg"}["name"];
    
    if (value1 < value2){
        return -1;
    }else if(value1 > value2){
        return 1;
    }else{
        return 0;
    }
};
console.log(result()); //1

所以,完整的代码如下:

var compareNames = createComparisonFunction("name");

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;
        }
    };
}

var result = compareNames({name: "Nicholas"},{name: "Greg"});

compareNames = null;

调用compareNames()函数的过程中产生的作用域链之间的关系图如下:

常见的闭包的模式一般都是这样:

var X = function A(a){
    return function(b1,b2){...a...} //匿名函数
};    
var Y = X(b1,b2);

举个例子:

var obj1 = {
    name: "co",
    color: ["white","black"]
};
var obj2 = {
    name: "lor",
    color: ["yellow","red"]
};

function displayProperty(propertyName){
    return function(obj1,obj2){
        var value1 = obj1[propertyName];
        var value2 = obj2[propertyName];
        
        if(typeof value1 === "string"){
            return value1 + " and " + value2 + "
"; }else if(value1 instanceof Array){ return value1.toString() + "
" + value2.toString(); }else{ return false; } }; } var displayP = displayProperty("name"); var displayStr = displayP(obj1,obj2); document.write(displayStr); displayP = null; var displayP = displayProperty("color"); var displayStr = displayP(obj1,obj2); document.write(displayStr); /* co and lor white,black yellow,red */

闭包会携带他包含的函数的作用域,因此会更多的占用内存资源,建议只有在绝对必要的时候再考虑使用闭包。V8 优化后的js 引擎会尝试收回被闭包占用的内存。

闭包与变量

闭包的副作用是闭包只能取得包含函数中任何变量的最后一个值。

this对象

全局函数中this = window;当函作为位某个对象的方法调用时this = 那个对象;但是匿名函数的执行环境又全局性,this 通常指向window;但也有例外:

var name = "the window";

var obj = {
    name: "the obj",
    
    getNameFunc: function(){
        return function(){
            return this.name;
        };
    }
};

console.log(obj.getNameFunc()()); //"the window" 别忘了要写两个小括号

因为每个函数在被调用的时候都会自动取得两个特殊的变量:this 和arguments;内部函数在搜索这两个变量时,只会搜索到其活动对象为止。

var obj = {
    name: "Oliver",
    age: 18,
    friends: ["alice","troy"],
    sayName: function(){
        return this.name;
    },
    sayFriends: function(){
        return function(){
            return this.friends;
        };
    }
    
};

console.log(obj.sayFriends()()); //undefined

上面这个代码就是因为闭包的问题,导致错误。又如:

var friends = "window"s friends";

var obj = {
    name: "Oliver",
    age: 18,
    friends: ["alice","troy"],
    sayName: function(){
        return this.name;
    },
    sayFriends: function(){
        return function(){
            return this.friends;
        };
    }
    
};

console.log(obj.sayFriends()()); //window"s friends 匿名函数执行环境的全局性

解决这个问题的方法是:

var friends = "window"s friends";

var obj = {
    name: "Oliver",
    age: 18,
    friends: ["alice","troy"],
    sayName: function(){
        return this.name;
    },
    sayFriends: function(){
        var outer = this;
        return function(){
            return outer.friends;
        };
    }
    
};

console.log(obj.sayFriends()()); //["alice","troy"] 这样就可以正常搜索到了

又如:

var friends = "window"s friends";

var obj = {
    name: "Oliver",
    age: 18,
    friends: ["alice","troy"],
    sayWindowFriends: function(){
        return function(){
            return this.friends;
        };
    },
    sayFriends: function(){
        var outer = this;
        return function(){
            return function(){
                return function(){
                    return outer.friends
                };
            };
        };
    }
};

console.log(obj.sayWindowFriends()()); //"window"s friends"
console.log(obj.sayFriends()()()()); //["alice", "troy"]

再举个例子:

var obj = {
    name: "Oliver",
    age: 18,
    friends: ["alice","troy"],
    sayFriends: function(i){
        var outer = this;
        return function(j){
            return outer.friends[i] + outer.friends[j];
        };
    }
};

console.log(obj.sayFriends(0)(1)); //alicetroy

另外,在几种特殊情况下,this 的值可能会发生改变。如:

var name = "the window";

var obj = {
    name: "my object",
    
    getName: function(){
        return this.name;
    }
};

console.log(obj.getName()); //my object
console.log((obj.getName)()); //my object 与上面的是相同的,多了个括号而已
console.log((obj.getName = obj.getName)()); //the window

只要不用下面两种方式调用函数即可。

内存泄露

经过上面的一番折腾,最明显的就是window.name 一直占用内存,无法清空。必须手动把它清理掉,用window.name = null来操作。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78219.html

相关文章

  • JavaScript函数达式——“函数模仿块级作用域及函数私有变量”注意要点

    摘要:模仿块级作用域在块级语句中定义的变量,实际上是包含函数中而非语句中创建的。避免对全局作用域产生不良影响这种方式可以通过创建私有作用域,避免对全局作用域产生不良影响。一般包括函数的参数局部变量和内部定义的其他函数。 模仿块级作用域 在块级语句中定义的变量,实际上是包含函数中而非语句中创建的。如: function outputNumbers(x){ for (var i = 0;...

    archieyang 评论0 收藏0
  • JavaScript引用类型——“单体内置对象”注意要点

    摘要:单体内置对象单体内置对象就是开发人员不必显式地实例化内置对象,因为他们已经实例化了。前面的章节讨论过了大多数内置对象,还定义了两个单体内置对象和。 单体内置对象 单体内置对象就是开发人员不必显式地实例化内置对象,因为他们已经实例化了。前面的章节讨论过了大多数内置对象,ECMA-262 还定义了两个单体内置对象:Global 和Math。 Global 对象 所有在全局作用域中定义的属性...

    xushaojieaaa 评论0 收藏0
  • JavaScript引用类型——“Function类型”注意要点

    摘要:类型每个函数都是类型的实例。如以上代码可行,是因为在代码开始值钱,解析器就已经通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中去。也可同时使用函数声明和函数表达式,但在浏览器中会出错。 Function 类型 每个函数都是Function 类型的实例。函数名实际上就是一个指向函数对象的指针,不会与某个函数绑定。 函数声明方式创建Function,语法如下: functi...

    fantix 评论0 收藏0
  • JavaScript引用类型——“RegExp类型”注意要点

    摘要:类型通过类型来支持正则表达式。如由于构造函数的模式参数是字符串,所以在某些情况下要对字符串进行双重转义。而第二个循环使用构造函数在每次循环冲创建正则表达式。如另外,还有个用于存储捕获组的构造函数属性。 EegExp 类型 ECMAScript 通过RegExp 类型来支持正则表达式。语法如下: var expression = / pattern / flags; 每个正则表达式都可...

    mochixuan 评论0 收藏0
  • 深入理解javascript原型闭包

    摘要:深入理解原型和闭包王福朋博客园深入理解原型和闭包一切都是对象原文链接本文要点一切引用类型都是对象,对象是属性的集合。每个对象都有一个,可称为隐式原型。另外注意,构造函数的函数名第一个字母大写规则约定。 深入理解javascript原型和闭包 王福朋 - 博客园 —— 《 深入理解javascript原型和闭包》 1. 一切都是对象 原文链接:http://www.cnblogs.com...

    jemygraw 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<