精华内容
下载资源
问答
  • 函数表达式

    万次阅读 2016-05-19 00:37:34
    函数表达式是javaScript中一个既强大又容易令人困惑的特性,在javaScript中定义函数的方式有两种,一种是函数声明,一种是函数表达式。函数声明的语法是这样的。function functionName(arg0, arg1) { }首先是...

    函数表达式是javaScript中一个既强大又容易令人困惑的特性,在javaScript中定义函数的方式有两种,一种是函数声明,一种是函数表达式。函数声明的语法是这样的。

    function functionName(arg0, arg1) {
    }

    首先是function关键字,然后是函数的名字,除过ie的主流浏览器都给函数定义了一个非标准的name属性,通过这个属性可以访问到给函数指定的名字。这个属性的值永远等于跟在function关键字后面的标识符。

    alert(functionName.name);//functionName

    关于函数声明,他的一个重要特性就是函数声明提升,意思是在执行代码之前会读取函数声明。这就意味着可以把函数声明放在调用他的语句后面。

    sayHi();
    function sayHi(){
        alert("Hi");
    }

    这个例子不会抛出错误,因为在代码执行之前会先读取函数声明。
    第二种创建函数的方式是使用函数表达式。函数表达式有几种不同的语法形式。下面是最常见的一种形式。

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

    这种形式看起来好像是常规的常量赋值语句,即创建一个函数并将他赋值给变量functionName,这种情况下常见的函数叫匿名函数。
    函数表达式与其他表达式一样,在使用前必须先赋值。以下代码会导致错误

    sayHi();//错误,函数不存在
    var sayHi = function() {
        alert("hi");
    }

    理解函数提升的关键就是理解函数声明与函数表达式之间的区别。例如,执行以下代码的结果可能会让人意想不到

    //不要这样做
    if (conditon){
        function sayHi(){
            alert("hi");
        }
    } else {
        function sayHi(){
            alert("yo");
        }
    }

    表面上看,以上代码表示在condtion为true时,使用一个sayHi()的定义,否则就使用另外一个定义。实际上,这在ECMAScript中属于无效语法,javaScript引擎会尝试修正错误,将其转化为合理的状态,但问题是浏览器尝试修正错误的做法并不一致,大部分浏览器会返回第二个声明。ff会在conditions为true时返回第一个声明。因此使用这种声明方式很危险。不过使用函数表达式就没什么问题了。

    递归

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

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

    这是个经典的递归阶乘函数,虽然这个函数表面看起来没什么错误,但是下面代码却可能会导致他出错。

    var anotherFactorial = factorial;
    factorial = null;
    alert(anotherFactorial(4));//出错

    以上代码先把factorial()函数保存在变量anotherFactorial中,然后将factorial变量置为null,结果指向原始函数的应用只剩下一个。但在接下来调用anotherFactorial()时,由于必须执行factorial()(因为递归函数体内要执行),而factorial已经不再是函数,所以就会导致错误。在这种情况下,使用arguments.callee可以解决这个问题。
    我们知道,arguments.callee是一个执行正在执行的函数的指针,因此可以用他来实现对函数的递归调用,例如:

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

    通过使用arguments.callee代替函数名,可以确保无论怎样调用函数都不会出现问题,因此在编写递归函数时,使用arguments.callee总比使用函数名更保险。
    但在严格模式下,不能通过脚本访问arguements.callee。访问这个属性会导致错误。不过,可以使用命名函数表达式来达成相同的结果。例如:

    var factorial = (function f(num){
        if (num <= 1) {
            return 1;
        } else {
            return num * f(num-1);
        }
    });

    以上代码创建了一个名为f()的命名函数表达式,然后将他赋值给变量factorial。即使把函数赋值给了另一个变量,函数的.名字f仍然有效,所以递归调用照样能正确完成,这种方式在严格模式和非严格模式下都行得通。

    闭包

    有不少开发人员总是搞不清匿名函数和闭包两个概念,因此经常混用。闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常用方式,就是在一个函数内部创建另一个函数,仍以前面的函数为例

    function ceeateComparisonFunction(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定义的那两行代码是内部函数(一个匿名函数)中的代码,这两行代码访问了外部函数中的变量propertyName,即使这个内部函数被返回了,而且是在其他地方被调用了,但他仍然可以访问变量propertyName。之所以还能访问这个变量,是因为内部函数的作用域链中包含了createComparisonFunciton()的作用域,要彻底搞清楚其中的细节,必须从理解函数被调用的时候都会发生什么入手。
    当某个函数被调用时,会创建一个执行环境及相应的作用域链,使用arguments和其他命名参数的值来初始化函数的活动对象,但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,。。。直至作为作用域链终点的全局执行环境。
    在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。
    作用域链本质上是一个指向变量对象的指针列表,他只引用但不实际包含变量对象。
    无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包的情况又有所不同。
    在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到他的作用域链中,因此,在createComparisonFunction()函数内部定义的匿名函数的作用域链中,实际上将会包含外部函数的createComparisonFunction()的活动对象。当ceateComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但他的活动对象仍然会留在内存中,知道匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。例如:

    //创建函数
    var compareNames = createComparisonFunction("name");
    //调用函数
    var result = compareNames({name: "Nicholas"},{name: "Greg"});
    //接触对匿名函数的引用(以便释放内存)
    compareNames = null;

    闭包与变量

    作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可以清晰的说明这个问题。

    function createFunction() {
        var result = new Array();
    
        for(var i = 0; i < 10; i++){
            result[i] = function() {
                return i;
            }
        }
        return result;
    }

    这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置0的函数返回0,位置1的函数返回1,,,。但实际上,每个函数都返回10。因为每个函数的作用域链中都保存着createFunction()函数的活动对象,所以他们引用的都是同一个变量i。当createFunction()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10,但是,我们可以通过创建另一个匿名函数强制让闭包的行为符合预期。如下所示:

    function createFunction(){
        var result = new Array();
    
        for (var i = 0; i < 10; i++){
            result[i] = function(num) {
                return function() {
                    return num;
                }
            }(i);
        }
        return result;
    }

    在重写了前面的createFunctions()函数后,每个函数就会返回各自不同的索引值,在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。这里的匿名函数哟一个参数num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量i。由于函数参数是按值传递的,所以就会把变量i的值复制给参数num,而在这个匿名函数内部,又创建并返回一个访问num的闭包,这样一来,result数组中每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值了。

    关于this对象

    在闭包中使用this对象也可能会导致一些问题。我们知道,this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行具有全局性,因此其this对象通常指向window。但有时候由于编写闭包的方式不同,这一点可能不会那么明显。下面来看一个例子。

    var name = "The window";
    
    var object = {
        name: "Object",
    
        getNameFunc: function(){
            return function(){
                return this.name;
            };
        }
    }
    
    alert(object.getNameFunc()());//"The window"(在非严格模式下)

    调用Object.getNameFunc()()就会立即调用他返回的函数,结果就是返回一个字符串,然而,这个例子返回的字符串是“The window”,即全局name变量的值,为什么匿名函数没有取得其包含作用域(或外部作用域)的this对象呢?
    前面曾经提到过,每个函数在调用时都会自动取得两个特殊变量:this和arguments,内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量,不过,把外部作用域中的this对象保存在一个闭包能够访问到变量里,就可以让闭包访问该对象了,如下所示:

    var name = "The window";
    
    var object = {
        name: "My object",
    
        getNameFunc: function(){
            var that = this;'
            return function(){
                return that.name;
            }
        }
    }
    alert(object.getNameFunct()());//"My Object"

    this和arguments也存在同样的问题。如果想访问作用域中的arguments对象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。
    有几个特殊情况下,this的值可能会意外改变。比如,下面的代码是修改前面例子的结果。

    var name = "The window";
    
    var object = {
        name: "My Object",
    
        getName: function(){
            return this.name;
        }
    }

    这里的getName()方法只简单地返回this.name的值,以下是几种调用object.getName()的方式以及各自的结果。

    object.getName();//My object
    
    (object.getName)();//My object
    
    (object.getName = object,getName)();//The window,在非严格模式下

    第一行和第二行this的值得到了维持,因为object.getName()和(object.getName)的定义是相同的。第三行代码先执行了一条赋值语句,然后再调用赋值后的结构。因此这个赋值表达式的值是函数本身(等于就是剥离出来),所以this的值不能得到维持,结果就返回了“The Window”。

    内存泄漏

    由于IE9之前的版本Jscipt对象和com对象使用不同的垃圾收集例程,引用计数,因此闭包在ie的这些版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个html元素,那么就意味着该元素无法被销毁。来看下面这个例子:

        function assignHandler () {
            var element = document.getElementById("someElemet");
            element.onclick = function  () {
                alert(element.id);
            }
        }

    以上代码常见了一个作为elemens元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用,由于匿名函数保存了一个对assignHandler()的活动对象的引用,因此就会导致无法减少element的引用数。只要匿名函数存在,element的引用至少也是1,因此他所占用的内存就永远不会回收。不过,这个问题可以通过稍微改写一下代码来解决,如下所示。

    
        function assignHandler () {
            var elements = document.getElementById("someElement");
            var id = element.id;
    
            element.onclick = function  () {
                alert(id);
            };
    
            element = null;
        }

    在上面的代码中,通过把element.id的副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一步,还是不能解决内存泄露的问题。必须要记住:闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不能直接引用element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把elemet变量设置为null,这样就能够解除对dom对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。

    模拟块级作用域

    javaScript没有块级作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的,来看下面的例子:

    function outputNumbers(count){
        for (var i = 0; i < count; i++){
            alert(i);
        }
        alert(i);//计数
    }

    这个函数中定义了一个for循环,而变量i的初始值被设置为0.在java和c++等语言中,变量i只会在for循环的语句块中有定义,循环一旦结束,变量i就会被销毁,可是在javaScript中,变量i是定义在outputNumbers()的活动对象中,因此从它有定义开始,就可以在函数内部随处访问它,即使像下面这样错误地重新声明同一个变量,也不会改变它的值。

    function outputNumber (count) {
        for (var i = 0; i < count; i++) {
                alert(i);
            }
            var i ;//重新声明变量
            alert(i);//计数
        }

    javaScript从来不会告诉你是否多次声明了同一变量,遇到这种情况,它只会对后续的声明视而不见(不过,他会执行后续声明中的变量初始化)。匿名函数可以用来模仿块级作用域并避免这个问题。用块级作用域(通常称为私有作用域)的匿名函数的语法如下

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

    以上代码定义并立即调用了一个匿名函数。将函数声明包含在一对圆括号中,表示他实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。如果感觉不好理解,来看下面这个例子

    var count = 5;
    outputNumbers(count);

    为了让代码更简洁,我们在调用函数时用5来代替变量count,如下所示:

    outputNumbers(5);

    这样做之所以可行,是因为变量只不过是值的另一种表现形式,因此用实际的值替换变量没有问题。再看下面的例子:

    var someFunction = function() {
    }
    someFunction();

    这个例子先定义了一个函数,然后立即调用了它。定义函数的方式是创建了一个匿名函数,并把匿名函数赋值给变量someFunction,而调用函数的方式是在函数名称后面添加一对圆括号,即someFunction(),通过前面的例子,我们知道可以用实际的值来取代变量count,那在这里是不是也可以用函数的值直接取代函数名呢?然而,下面的代码却会导致错误。

    function () {
    }()//出错

    这段代码会导致语法错误,是因为javaScript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转化成函数表达式,只要像下面这样给它加上一对圆括号即可。

    (function  () {
    
    })();

    无论在什么地方,只需要临时需要一些变量,就可以使用私有作用域,例如:

        function outputNumbers (count) {
            (function  () {
                for (var i = 0; i < count; i++) {
                    alert(i);
                }
            })();
    
            alert(i);//导致一个错误
        }

    在这个重写后的outputNumbers()函数中,我们在for循环外部插入了一个私有作用域,在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包,他能够访问包含作用域中的所有变量。
    这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽量少向全局作用域中添加变量和函数。在一个由很多开发人员参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员即可使用自己的变量,又不必担心搞乱全局作用域。例如:

    (function  () {
        var now = new Date();
        if (now.getMonth() == 0 && now.getDate() == 1) {
            alert("happy new year");
        }
    })()

    把上面这段代码放在全局作用域中,可以用来确定哪一天是1月1日,如果到了这一天,就会向用户显示一条祝贺新年的消息。其中的变量now现在是匿名函数中的局部变量,而我们不必在全局作用域中创建它。
    这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域链了。

    私有变量

    严格来讲,javaScript中没有私有成员的概念;所有对象属性都是共有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包含函数的参数,局部变量和在函数内部定义的其他函数。看下面例子:

    
        function add (num1, num2) {
            var sum = num1 + num2;
            return sum;
        }
    

    在这个函数内部,有3个私有变量:num1、num2和sum。在函数内部可以访问这几个变量,但在函数外部不能访问他们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而利用这一点,就可以创建用于访问私有变量的公有方法。
    我们把有权访问私有变量和私有函数的共有方法成为特权方法,有两种在对象上创建特权的方式,第一种是在构造函数中定义特权方法,基本模式如下:

        function MyObject () {
    
            //私有变量和私有函数
            var privateVariable = 10;
    
            function privateFunction () {
                return false;
            }
    
            //特权方法
            this.publicMethod = function  () {
                privateVariable++;
                return privateFunction();
            }
        }

    这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员的特权方法。能够在构造函数中定义特权方法。是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。对这个例子而言,变量privateVariabele和函数privateFunction()只能通过特权方法publicMethod()来访问,在创建MyObject的实例后,除了使用publicMethod()这一个途径外,没有任何办法可以直接访问privateVariable和privateFunction()
    利用私有和特权成员,可以隐藏那些不应该被直接修改的数据,例如:

    
        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"

    以上代码的构造函数中定义了两个特权方法,私有变量name在Person的每一个实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。不过,在构造函数中定义特权方法也有一个缺点,就是你必须使用构造函数模式来达到这个目的。构造函数的缺点是针对每个实例都会创建一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题。

    静态私有变量

    通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法,基本模式如下:

        (function  () {
    
            //私有变量和私有函数
            var privateVariable = 10;
    
            function privateFunction () {
                return false;
            }
    
            //构造函数
            MyObject = function  () {
    
            };
    
            //共有,特权方法
            MyObject.prototype.publicMethod = function  () {
                privateVariable++;
                return privateFunction();
            }
        })

    函数声明只能创建局部函数,但那不是我们想要的,出于同样的原因,我们也没有声明MyObject时使用var关键字,记住:初始化未经声明的变量,总是会创建一个全部变量,因此,myObject就成了一个全局变量,能够在私有作用域之外被访问到。但也要知道,在严格模式下给未经声明的变量赋值会导致错误。
    看下面这段代码

    (function  () {
            var name = "";
    
            Person = function  (value) {
                name = value;
            };
    
            Person.prototype.getName = function  () {
                return name;
            };
    
            Person.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"

    这个例子中的Person构造函数与setName()和getName()方法一样,都有权访问私有变量name,在这种模式下,变量name,就变成了一个静态的,由所有实例共享的属性
    多查找作用域链中的一个层次,就会在一定程度上影响查找的速度,而正式使用闭包和私有变量的一个明显的不足之处。

    展开全文
  • (1)函数声明和函数表达式(根据他的上下文来进行判断) 函数声明:就和我们一般写的函数是没有多大的区别的 function wh(){ //这里就是函数里面执行的语句 } 函数表达式:需要注意的就是函数表达式中函数名字...

    (1)函数声明和函数表达式(根据他的上下文来进行判断)

    函数声明:就和我们一般写的函数是没有多大的区别的

    function wh(){
           //这里就是函数里面执行的语句
    }
    函数表达式:需要注意的就是函数表达式中函数名字(可有可无),如果没有函数名字的话称为匿名函数表达式,如果有名字的话称为命名函数表达式

    函数表达式的几种形式

    >1。将函数赋给一个变量

    var  a = function (){};
    
    >2 将函数放在一个圆括号里面

    (function f(){})

    >3其中给函数前面加上了位运算符(!,+,-...)

    (2)函数声明,函数表达式的区别:

    1.函数声明是可以直接在后面加上括号进行执行的,但是函数表达式是不可以的

    2.函数声明是可以被提前解析出来的,函数表达式只能等到逐行解析到本行的时候才可以被解析

    (3)匿名函数表达式和命名函数表达式的区别

    var a = function aaa(){alert(1);};
    如果说直接为:a();在这里相当于执行aaa函数,这里就会弹出1

    如果说这里为:aaa();这个时候就会出错,因为在这个函数的名字只有在内部的时候才能被访问

    看一个面试题:

    (function aaa(){alert(1)};)
    aaa();
    这里就会报错因为在函数表达式中,函数名字在外面是访问不到的



    展开全文
  • JavaScript函数表达式

    千次阅读 2016-02-28 21:57:12
    在ECMAScript中,有两个最常用的创建函数对象的方法,即使用函数表达式或者使用函数声明。对此,ECMAScript规范明确了一点,即是,即函数声明 必须始终带有一个标识符(Identifier),也就是我们所说的函数名,而...

    在ECMAScript中,有两个最常用的创建函数对象的方法,即使用函数表达式或者使用函数声明。对此,ECMAScript规范明确了一点,即是,即函数声明 必须始终带有一个标识符(Identifier),也就是我们所说的函数名,而函数表达式则可以省略。

    函数声明的语法是这样的:

    <script>
    	function functionName(){
    		函数体
    	}
    </script>

    首先是function关键字,这个关键字表示我声明的是一个函数,或者说我将要声明一个函数了,告诉浏览器一下;然后是函数的名字,我们讲函数的名字一定要见名知义,接着里面是我们的函数体,也可以了解为函数表达式;在各大主流浏览器里面都给函数定义了一个非标准的name属性,通过这个属性可以访问到给函数指定的名字,这个属性的值永远等同于function后面的标识符;

    function functionName(){
    		
    	}
    	alert(functionName.name);

     函数声明解析过程如下:
      1. 创建一个new Function对象,FormalParameterList指定参数,FunctionBody指定函数体。将当前正在运行环境中作用域链作为它的作用域。
      2. 为当前变量对象创建一个名为Identifier的属性,值为Result(1)。

    函数声明提升

    关于函数有一个重要的特性就是函数声明提升.意思是在读取代码前会先读取函数声明。这就意味着可以把函数声明放在调用它额语句后面;例如一下;

    alert(functionName.name);
    	function functionName(){	
    	}
    这个例子不会抛出错误,因为在alert之前会先去读取这个函数声明;

    函数表达式

    第二种创建函数的方法是使用函数表达式:

    函数表达式:
      (函数表达式分为匿名和具名函数表达式)
      function Identifier opt( FormalParameterList opt){ FunctionBody }  //这里是具名函数表达式
      具名函数表达式的解析过程如下:
      1. 创建一个new Object对象
      2. 将Result(1)添加到作用域链的顶端
      3. 创建一个new Function对象,FormalParameterList指定参数,FunctionBody指定函数体。将当前正在运行的执行环境中作用域链作为它的作用域。
      4. 为Result(1)创建一个名为Identifier 的属性,其值为为Result(3),只读,不可删除
      5. 从作用域链中移除Result(1)
      6. 返回Result(3)
    简单来说,ECMAScript是通过上下文来区分这两者的:假如 function foo(){} 是一个赋值表达式的一部分,则认为它是一个函数表达式。而如果 function foo(){} 被包含在一个函数体内,或者位于程序(的最上层)中,则将它作为一个函数声明来解析。显然,在省略标识符的情况下,“表达式” 也就只能是表达式了。
    function foo(){}; // 声明,因为它是程序的一部分
    var bar = function foo(){}; // 表达式,因为它是赋值表达(AssignmentExpression)的一部分
    new function bar(){}; // 表达式,因为它是New表达式(NewExpression)的一部分
    (function(){
        function bar(){}; // 声明,因为它是函数体(FunctionBody)的一部分
    })(); 
     还有一种情况:
    (function foo(){})
      这种情况也是函数表达式,它被包含在一对圆括号中的函数,在其上下文环境中,()构成了一个分组操作符,而分组操作符只能包含表达式,更多的例子:
    function foo(){}; // 函数声明
    (function foo(){}); // 函数表达式:注意它被包含在分组操作符中
     try {
    (var x = 5); // 分组操作符只能包含表达式,不能包含语句(这里的var就是语句)
    } 
    catch(err) {
    // SyntaxError(因为“var x = 5”是一个语句,而不是表达式——对表达式求值必须返回值,但对语句求值则未必返回值。——译
    }
      下面简单说说函数声明与函数表达式的异同。声明和表达式的行为存在着十分微妙而又十分重要的差别。
      首先,函数声明会在任何表达式被解析和求值之前先行被解析和求值。即使声明位于源代码中的最后一行,它也会先于同一作用域中位于最前面的表达式被求值。
    简单总结,区别在什么地方呢?
    1. 声明总是在作用域开始时先行解析; 
    2. 表达式在遇到时候才运算。
    函数声明还有另外一个重要的特点,即通过条件语句控制函数声明的行为并未标准化,因此不同环境下可能会得到不同的结果。即是: 
    // 千万不要这样做!
    // 不同浏览器会有不同返回结果,
    if (true) {
    function foo() {
    return 'first';
    }
    }
    else {
    function foo() {
    return 'second';
    }
    }
    foo();
    // 记住,这种情况下要使用函数表达式:
    var foo;
    if (true) {
    foo = function() {
    return 'first';
    };
    }
    else {
    foo = function() {
    return 'second';
    };
    }
    foo();
     那么,使用函数声明的实际规则到底是什么? 
      FunctionDeclaration(函数声明)只能出现在Program(程序)或FunctionBody(函数体)内。从句法上讲,它们 不能出现在Block(块)({ ... })中,例如不能出现在 if、while 或 for 语句中。因为 Block(块) 中只能包含Statement(语句), 而不能包含FunctionDeclaration(函数声明)这样的SourceElement(源元素)。
      另一方面,仔细看一看产生规则也会发现,唯一可能让Expression(表达式)出现在Block(块)中情形,就是让它作为ExpressionStatement(表达式语句)的一部分。但是,规范明确规定了ExpressionStatement(表达式语句)不能以关键字function开头。而这实际上就是说,FunctionExpression(函数表达式)同样也不能出现在Statement(语句)或Block(块)中(别忘了Block(块)就是由Statement(语句)构成的)。
     
      由于存在上述限制,只要函数出现在块中(像上面例子中那样),实际上就应该将其看作一个语法错误,而不是什么函数声明或表达式。
      那么我们应该在什么时候使用函数声明或函数表达式呢?函数声明只能出现在“程序代码”中,意味着只能在其它函数体中或者全局空间;它们的定义不能不能赋值给一个变量或属性,或者作为一个参数传递出现在函数调用中;下面的例子是函数声明的允许的用法,foo(),bar()和local()都是通过函数声明模式声明: 
    // 全局环境
    function foo() {}  
     
    function local() {  
    // 局部环境  
        function bar() {}  
            return bar;  
    }
    当你在语法上不能使用函数声明的时候,你就可以使用函数表达式。比如:传递一个函数作为参数或者在对象字面量中定义一个函数: 
    // 这是一个匿名函数表达式
    callMe(function () {  
     
    //传递一个函数作为参数
    });

    // 这是一个具名函数表达式
    callMe(function me() {  
    // 传递一个函数作为参数,函数名为me
    });  
    // 其他函数表达式
    var myobject = {  
        say: function () {  
     
    // I am a function expression  
    }  
    }; 
    这种情况看起来好像是常规的变量赋值语句,即创建一个函数并将它复制给变量functionName.这种情况下创建的函数叫匿名函数。因为function关键字后面没有标识符。匿名函数的name属性是空字符串。函数表达式与其他表达式一样,在使用前必须先复制。而且函数表达式并不会函数生, 提升,即先执行函数表达式,再声明会报错;

    Javascript有很多有趣的用法,在Google Code Search里能找到不少,举一个例子:

    <script>
    ~function() {
        alert("hello, world.");
    }();
    </script>
    试一下就知道这段代码的意思就是声明一个函数,然后立刻执行,因为Javascript中的变量作用域是基于函数的,所以这样可以避免变量污染,但这里的位运算符『~』乍一看让人摸不到头脑,如果去掉它再运行则会报错:SyntaxError。

    为什么去掉位操作符『~』后运行会报错,这是因为从语法解析的角度看,Javascript不允许在函数声明的后面直接使用小括号,而函数表达式则没有这个限制,通过在函数声明前面加上一个『~』操作符,就可以让语法解析器把后面看成是函数表达式,同样的,在函数声明前面加上『!,+,-』等操作符也是可行的。

    那我们为什么不使用下面这种函数表达式的方式呢?

    <script>
    var foo = function() {
        alert("hello, world.");
    }();
    </script>

    虽然从语法解析的角度看没有问题,但是上面的代码存在弊端,它引入了一个变量,可能会污染现有的运行环境,带来潜在的问题。

    使用位操作符“~”的方法显得有点奇技淫巧,其实把函数声明用小括号套起来更易读:

    <script>
    (function() {
        alert("hello, world.");
    })();
    </script>

    展开全文
  • JavaScript-函数表达式

    2017-03-17 22:15:39
    JavaScript-函数表达式

    函数表达式

    定义函数的方式有两种:一种是函数声明,另一种是函数表达式。

    函数声明的语法

    function functionName(arg0,arg1,arg2){
      //函数体
    }

    函数声明,有一个重要特征就是函数声明提升,意思是在执行代码之前会先读取函数声明。这意味着可以把函数声明放在调用它的语句后面。

    sayHi();
    function sayHi(){
      alert("Hi!");
    }

    第二种创建函数的方式是使用函数表达式。

    var functionName=function(arg0,arg1,arg2){
      //函数体
    };//

    这种形式看起来好像是常规的赋值语句,即创建一个函数并将它赋值给变量functionName。这种情况下创建的函数叫做匿名函数,因为function关键字 后面没有标识符。(匿名函数有时候也叫拉姆达表达式)。

    函数表达式与其他表达式,在使用前必须先赋值。

    sayHi();//会报错,因为函数还不存在
    var sayHi=function(){
      alert("Hi!");
    };

    模仿块级作用域

    匿名函数可以用来模仿块级作用域。

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

    将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。

    例如:

    var count=5;
    outputNumbers(count);

    我们在调用函数时可以用5来代替变量count

    outputNumbers(5);

    这样做之所以可行,是因为变量只不过是值的另一种表现形式,因此用实际的值替换变量没有问题。

    再看下面的例子

    var someFunction=function(){
    
    };
    someFunction();

    这个例子定义函数的方式是创建一个匿名函数,并把匿名函数赋值给变量someFunction。而调用函数的方式是在函数名称后面添加一对圆括号,即someFunction()。通过前面的例子我们知道,可以使用实际的值来取代变量count,那这里是不是可以用函数的值直接取代函数名呢?

    function(){
    
    }();

    这段代码会导致语法错误,因为JavaScript将function关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号。然后,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要加上一对圆括号即可

    (function(){
    
    })();

    无论在什么地方,只要临时需要一些变量,就可以使用私有作用域。

    function outputNumbers(count){
      (function(){
        for(var i=0;i<count;i++){
          alert(i);
        }
      })();
    
      alert(i);//会出错
    }

    在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁,而在私有作用域中能够访问变量count,是因为这个匿名函数是一个闭包(闭包是指有权访问另一个函数作用域中的函数的变量的函数。),它能够访问包含作用域中的所有变量。

    这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽量少向全局作用域中添加变量和函数。在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。

    而通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域。

    展开全文
  • 函数表达式 除了用function命令声明函数,还可以采用变量赋值的写法。 这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(Function Expression),因为赋值语句的等号右侧只能放表达式。 ...
  • Javascript 函数表达式

    千次阅读 2016-04-27 18:07:20
    第二:函数表达式函数声明提升sayHi(); function sayHi(){ alert("Hello world!") }7.1 递归递归函数是在一个函数通过名字调用自身的情况下构成的。function fac(num) { if (num ) { return 1; } else { return
  • 在学习函数声明和函数表达式之前如果你对作用域和作用域链掌握的不是特别的好,建议您先看完js深入理解函数作用域和作用域链,再进行接下来的学习函数声明:function 函数名(){}函数表达式:function 函数名(){},...
  • JavaScript函数表达式的和闭包的特征 定义函数有两种方式,一种是函数声明,一种是函数表达式。 函数声明 ,函数声明会提升,解释器会在代码执行之前先读取函数声明,所以函数的调用语句可以出现在函数声明语句之前。 ...
  • javascript中函数声明、函数表达式以及匿名函数和自执行函数 1.函数声明和函数表达式  1)函数声明必须始终带有一个标识符(Identifier),也就是我们所说的函数名,而函数表达式则可以省略  //函数声明 ...
  • 一般调用函数的写法: ...函数表达式后面可以跟括号,上面写法还可改写成: var someFunction=function(){ alert("立即调用的函数表达式") }(); 函数声明后面不能跟括号,就是说下面的写法是错误的: func
  • 首先来看一个题目 var f = function g(){return 23;} typeof g(); 会输出什么 function 还是什么??? 答案 会发生错误 error! 还有就是下面两种声明方式有...//函数表达式 首先让我们看看函数和函数表达
  • 函数表达式的后面可以跟括号,将函数声明转换成函数表达式,只要将函数声明部分加上一对括号。 可以限制向全局作用域中添加过多的变量和函数。 3、私有变量 函数中定义的变量可以认为是私有变量 ...
  • 另外一种可以取代函数声明的方式是函数表达式,解释如下: 函数表达式 函数表达式(缩写为FE)是这样一种函数: 在源码中须出现在表达式的位置有可选的名称不会影响变量对象在代码执行阶段创建 这种函数类型的...
  • 函数表达式与函数语句的区别

    千次阅读 2012-06-10 18:10:02
    函数表达式就是将一段匿名函数表达式存储在一个变量中;如:var a = function(){statement} 函数语句是使用function关键字声明一个函数。如:function a(){statement} 函数表达式与函数语句的区别: 1) 当函数...
  • IIFE(Immediately Invoked Function Expression)指立即调用函数表达式。如下: (function(){ // 函数体 })() 为什么函数体外需要包裹括号? function(){ // 函数体 }() //这样写IIFE报错 这是因为...
  • 匿名与具名函数 这属于常识性问题,但是还是有必要说说 没有函数名的函数就叫匿名函数,有函数名的函数就叫具名函数 ... var funA = function(){//匿名函数表达式 ... } var funB = funct...
  • IIFE(立即调用函数表达式,自执行匿名函数) IIFE(Imdiately Invoked Function Expression 立即执行的函数表达式)顾名思义声明立即调用执行这个函数,立即调用的函数表达式可用于避免块内的变量提升,防止污染...
  • 网上还没用发现有人对命名函数表达式进去重复深入的讨论,正因为如此,网上出现了各种各样的误解,本文将从原理和实践两个方面来探讨JavaScript关于命名函数表达式的优缺点。 简单的说,命名函数表达式只有一个用户...
  • lambda函数表达式写法

    千次阅读 2016-01-19 15:40:15
    C++11提供了对匿名函数的支持,称为Lambda函数(也叫Lambda表达式). Lambda表达式具体形式如下:  [capture](parameters)->return-type{body}  如果没有参数,空的括号()可以省略.返回值也可以省略,如果函数体...
  • 定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。//函数声明 function functionName(arg0, arg1, arg2) { //函数体 }//函数表达式 var functionName = function(arg0, arg1, arg2) { //函数体 } ...
  • Kotlin学习笔记--函数、函数表达式、默认参数、具名参数
  • 立即调用的函数表达式(IIFE) 在Javascript中,一对括号()是一种运算符,跟在函数名之后,表示调用该函数。比如,print()就表示调用print函数。 有时,我们需要在定义函数之后,立即调用该函数。这时,你不...
  • 记得在面试腾讯实习生的时候,面试官问了我这样一道问题。 //下述两种声明方式有什么不同 ...当初只知道两种声明方式一个是函数声明一个是函数表达式,具体有什么不同没能说得很好。最近正好看
  • 本篇文章,主要讲解的立即执行函数或自执行匿名函数的含义、用法、以及使用它的主要场景。系列的前面几篇文章主要讲解了作用域、原型、执行上下文,本篇文章一样起到了承上启下的作用。
  • 第一种格式:var a=3;...由于函数被包含在一对()括号内部,因此成为了一个表达式,通过在末尾加上别外一个()可以立即执行这个函数。第一个()将函数变成表达式,第二个()执行了这个函数。第二种格式:(...
  • 函数表达式和函数声明:

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 85,489
精华内容 34,195
关键字:

关于圆的函数表达式