函数以及函数作用域详解

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions_and_function_scope
Javascript 函数 函数作用域

概述

一般来说,函数就是一个能够被外部挪用的(或许函数本身递归挪用)的“子顺序”。和顺序本身一样,一个函数的函数体是由一系列的语句构成的,函数能够接收函数,也能够返回一个返回值。

在Javascript中函数也是对象,也能够像其他对象一样,增加属性和要领等。详细来说,它们是Function对象。

用法简介

在Javascript中,每一个函数实在都是一个Function对象,检察Function界面,相识Function属性和要领。

要想返回一个返回值,函数必需运用return语句指定所要返回的值(运用new关键词的组织函数除外)。假如一个函数没有return语句,则它默许返回undefined。

挪用函数时,传入函数的值为函数的实参,对应位置的函数参数名为形参。假如实参是一个包含原始值(数字,字符串,布尔值)的变量,则就算函数在内部转变了形参的值,返回后该实参的值也不会变坏。假如实参是一个对象援用,则对应形参和实参会指向同一个对象,假如函数内部形参援用对象的值发作了转变,则实参指向的援用对象的值也会转变:

/*定义函数 myFunc*/
function myFunc(theObject) {
    theObject.brand = "Toyota";
}

/*
 *定义变量 mycar;
 *建立并初始化一个对象;
 *将对象的援用赋值给变量mycar;
 */
var mycar = {
    brand : "Honda",
    model : "Accord",
    year : 1998
};

window.alert(mycar.brand);  //弹出“Honda”

myFunc(mycar);

window.alert(mycar.brand);  //弹出“Toyota”

函数实行时,this不会指向正在运转的函数本身,而是指向挪用该函数的对象。假如你想在函数内部猎取本身的援用,只能运用函数名或许arguments.callee属性。

定义函数

定义函数一共有三种要领:

函数声明(function语句)

有一个迥殊的语法来声明函数:

    function name([param,[param,[...param]]]) {
        //statements 
}

name
函数名

param
函数的参数称号,一个函数最多有255个参数。

statements
函数体内实行的语句

函数表达式(function操作符)

函数表达式很函数声明有着许多雷同之处,他们以至有着雷同的语法:

function[name]([param,[param,[...param]]]) {
        //statements
}

name
能够省略

param
函数的参数称号,一个函数最多有255个参数。

statements
函数体内实行的语句

Function组织函数

和其他范例一样,Function对象能够运用new操作符来建立:

[new] Function (arg1, arg2, ... argN, functionBody)

arg1, arg2, … argN
一个或多个变量称号,来作为函数的形参名.范例为字符串,值必需为一个正当的JavaScript标识符,比方Function(“x”, “y”,”alert(x+y)”),或许逗号衔接的多个标识符,比方Function(“x,y”,”alert(x+y)”)

functionBody
一个字符串,包含了构成函数的函数体的一条或多条语句.
纵然不运用new操作符,直接挪用Function函数,结果也是一样的,依然能够一般建立一个函数.

注重: 不引荐运用Function组织函数来建立函数.因为在这类状况下,函数体必需由一个字符串来指定.如许会阻挠一些JS引擎做顺序优化,还会引发一些其他题目.

arguments对象

在函数内部,你能够运用arguments对象猎取到该函数的一切传入参数. 检察 arguments.

作用域和函数客栈

挪用其他函数时的作用域部份和函数部份

递归

函数能指向并援用它自己,有三种援用要领援用函数它自己:

  1. 经由过程函数名

  2. arguments.callee

  3. 指向该函数的变量

斟酌到一下的函数定义:

var foo = function bar() {
    //statements
}

在这个函数内部,下面就是上面所说的三种:

  1. bar()

  2. arguments.callee

  3. foo()

一个函数挪用它自己叫做递归函数。在某些方面,递归函数像一个轮回(loop),一样的代码屡次实行同时有一个掌握语句(为了防止无穷轮回),比方下面的轮回:

var x = 0;
while (x < 10){
    x++
};
console.log(x);    //输出10

能变换为迭代函数并挪用该函数:

function loop(x) {
    if(x>=10){console.log(x);}
    else{loop(x+1);}
}
loop(0);    //返回10

然则,一些算法并非简朴的迭代轮回。比方,取得DOM节点运用递归就更随意马虎:

function walkTree(node) {
    if (node == null) return;
    for(var i = 0; i < node.childNodes.length; i++){
        walkTree(node.childNodes[i]);
    }
}

比较这些轮回方程,每一个递归挪用都邑挪用更多的挪用。
将递归算法转换成非递归算法大多是能完成的,然则常常逻辑会变得越发庞杂而且要运用更多的客栈,事实上,递归本身就运用客栈:函数客栈。
这类相似客栈的行动能鄙人图中看到:

function foo(i){
    if(i<0) return;
    console.log('begin: ' + i);
    foo(i - 1);
    console.log('end: ' + i);
}
foo(3);

嵌套函数和闭包

你能够将一个函数嵌套在另一个函数内部,被嵌套函数只是他外部函数的一个私有函数,这类状况我们将它叫做闭包。

闭包是一个具有自在接见另一个函数作用域的表达式(一般是一个函数)。

当一个被嵌套函数是一个闭包时,意味着一个被嵌套函数能继续它的外部函数的参数(arguments)和变量(variables)。意味着内部函数具有外部函数的作用域。
总而言之:

  • 内部函数能具有接见外部函数的语句

  • 内部的函数定义了一个闭包:内部函数能运用外部函数的参数(arguments)和变量(variables),然则外部函数不能运用内部函数的参数(arguments)和变量(variables)。
    下例演示一个函数闭包:

function addSquares(a,b){
    function square(x){
        return x*x;
        }
    return square(a)+square(b);
}
console.log(addSquares(1,2));      //返回5

因为内部函数形成了一个闭包,你能够把内部函数看成返回值返回:

function outSide(x){
    function inSide(y){
        return x + y;
    }
    return inSide;
}
fnInside = outSide(1);
console.log(fnInside(2));       //返回3
console.log(outSide(2)(10));       //返回12

变量的保留

注重,x变量是如安在inside函数返回后被保留的。一个闭包必需保留它的作用域中的参数和变量。每一次挪用有能够供应差别的参数,如许一个新的闭包都邑在外层挪用时被建立,只有当要返回的内部函数不再被接见时内存才会被清空。
这和保留来自其他对象的援用没有区分,然则后一种状况越发少见因为一个函数不会直接设置援用而且不能搜检它们。

多层嵌套

函数能多层嵌套,A函数中B函数,B函数中包含C函数。在这个函数里B和C都是闭包。因而B函数能接见A函数,C函数能接见B函数。而且,C函数也能接见A函数。如许看,闭包能包含多层嵌套。它们递归的包含外层的作用域,这类状况我们称之为作用域链。
例子以下:

function A(x){
    function B(y){
        function C(z){
            console.log(x + y + z);
        }
        C(2);
    }
    B(6);
}
A(1);

此例中C能接见B中y和A中的x,这些之所以能建立是因为:

1.B为A的闭包,B能接见A的变量和参数。
2.C为B的闭包
3.因为B包含A的作用域,所以C也包含A的作用域。换言之,C包含B和A的作用域链。

然则反过来却不可,A不能接见B,B也不能接见C

定名争执

当闭包中两个变量或许参数具有雷同的名字,这类状况叫做定名争执。越内层的函数具有越高的优先权,最内层的作用域具有最高的优先权,最外层的作用域的优先权最低。作用域链的道理云云。作用域最前端是最内层的作用域,末了是最外层。斟酌以下例子

function outside(){
    var x = 10;
    function inside(x){
    return x;
    }
    return inside;
}
result = outside()(20);       //返回20

上例中的定名争执发作在return的x是返回inside里的参数x照样外层代码的x,此处的作用域链为(inside,outside,全局),然则内层的x优先级比外层的x优先级高,因而返回的是内层的x。

函数组织器vs.函数声明vs.函数表达式

比较以下:
1.用函数组织器组织函数并增加参数:

var multiply = new Function(“x”, “y”, “return x*y;”);

2.函数声明:

function multiply(x, y) {
    return x*y; 
}

3.函数表达式:

var multiply = function(x, y) {
    return x*y;
}

4.函数定名表达式

var multiply = function func_name(x, y) {
    return x*y;
}

上面的代码约莫都是干的一样的事变,除了一些玄妙的区分:

  • 函数名和函数指定的变量名是有区分的:

    • 函数名是不能转变的,函数指定变量名是能够转变的

    • 函数名的运用只能和函数体一同运用,假如尝试在函数体外运用函数名的话会报错error或许返回undefined。比方:

    1. y = function x(){};
      alert(x);

    另一方面,函数指定变量名(比方末了一例中的multiply)只被它本身(变量名)的作用域所束缚,这个作用域肯定包含函数声明的作用域。

    • 犹如以上四个例子所示,函数名与函数指定变量名差别。它们彼此之间没有关联。

  • 函数声明一样会建立一个和函数名一样的变量。不像那些在函数表达式中定义的那样,在函数声明中定义的函数能在它们定义的作用域中经由过程它们的定名被接见:

    function x(){
    }
    console.log(x);   //返回function(){};

    接下来的例子会展现函数名和函数指定变量是如何无关的。纵然函数名被指定给了其他的变量名,返回的依然是函数名:

    function foo(){};
    console.log(foo);
    var bar = foo;
    console.log(bar);
  • 一个由“new Function()”建立的新函数是没有函数名的。在spiderMonkey的javascript引擎中,假如函数名为“anonymous”序列化的函数将会显现,比方下例中运用“console.log(new Function())”的输出:

    function anonymous(){};
    console.log(new Function());  //返回“function anonymous(){}” 
  • 不像函数表达式定义的函数挪用必需在函数表达式背面,函数声明的函数挪用能够在函数的前面,以下:

    foo();
    function foo(){
        alert("FOOO");
    }
  • 函数表达式中定义的函数会继续当前的作用域。就是说该函数是一个闭包。而运用Function建立的函数不会继续任何的作用域除了全局作用域(一切的函数都邑继续)。

  • 函数表达式和函数声明定义的函数只会剖析一次,但那些运用Function组织的却不是。就是说,经由过程new Function体式格局构建函数时内部的字符每一次都邑重剖析,函数本身不会重剖析,因而函数表达式比“new Function(…)”这类体式格局要快。所以经由过程函数组织体式格局分组织函数不管什么时候都是应该被防止的。这应该被记下来,然则,函数表达式和函数声明嵌套在经由过程函数组织器组织的中且马上实行就不会反复剖析屡次,以下例:

var foo = (new Function("var bar=\"fooo!\";\nreturn(function(){\n\talert(\"bar\");\n})"))();
foo();

函数声明一般会很随意马虎的变成函数表达式,函数声明不再是函数声明的缘由能够是因为它满足以下两个前提:
1.变成了表达式的一部份。
2.它不再是函数的“资本元素”,一个“资本元素”是剧本或许函数中的一段非嵌套语句:

var x = 0;                  //资本元素
if (x == 0) {               //资本元素
    x = 10;                 //不是资本元素
    function boo() {}       //不是资本元素
}
function foo(){              //资本元素
    var y = 20;              //资本元素
    function bar() {}           //资本元素
    while(y == 10){             //资本元素
        function blah(){}      //不是资本元素
        y++;                   //不是资本元素
    }
}

比方:

//函数声明
function foo(){}

//函数表达式
(function bar(){})

//函数表达式
var x = function hello(){}
if(x){
    //函数表达式
    function hello(){}
}
//函数声明
function a(){
    //函数声明
    function b(){}
    if(0) {
        //函数表达式
        function c(){}
    }
}

前提实行函数

函数能够定义在前提语句里经由过程一般的function语句和new Function语句。但请注重以下这类状况ECMAScript5中不再许可涌现函数语句,所以这个特征在跨浏览器中并不能表现的很好,你不能再编程中完整依靠它。
鄙人面的代码中,这个zero函数永久都不能定义和实行。因为if(0)永久都返回false;

if(0) {
    function zero(){
        document.writeln("This is a zero.");
    }
}

假如这个前提发作转变,if(1)那末zero就会被定义。

注重:只管这一类函数看上去就像是函数声明,然则它实际上是函数表达式。因为它被包含在其他的前提语句中。看函数表达式和函数声明有何差别.

注重:一些javascript的剖析器,不包含SpiderMonkey,毛病的把定名函数表达式当成了函数定义。如许会致使zero会被定义纵然if返回的是false。平安的要领是将匿名函数指定给变量:

if(0) {
    var zero = function(){
        document.writeln("This is zero.");
    }
}   

函数和事宜处置惩罚顺序

在JavaScript中, DOM事宜处置惩罚顺序只能是函数(相反的,其他DOM范例的绑定言语还能够运用一个包含handleEvent要领的对象做为事宜处置惩罚顺序)(译者注:这句话已过期,现在在大部份非IE[6,7,8]的浏览器中,都已支撑一个对象作为事宜处置惩罚顺序). 在事宜被触发时,该函数会被挪用,一个 event 对象会作为第一个也是唯一的一个参数传入该函数.像其他参数一样,假如不需要运用该event对象, 则对应的形参能够省略.

一般的HTML事宜对象包含:window(Window对象,包含frames),document(HTMLdocument对象和elements(种种元素对象)。在HTMLDOM中,事宜对象具有时候处置惩罚顺序属性,这个属性一般是小写的有on前缀的,比方:onfocus。一个越发天真的增加事宜对象的要领有DOM2级事宜

注重:事宜是DOM的一部份,而不是javascript的一部份。

下例会个window对象的onfocus事宜绑定一个函数:

window.onfocus = function() {
    document.body.style.backgroundColor = 'blue';
}

假如函数绑定到了变量上,那末能够将事宜指向该变量。以下:

var setBgColor = new Function('document.body.style.backgroundColor = "while" ');

你能够像以下的要领那样运用它们:
1.给DOM的事宜属性指向变量:

document.form1.colorbutton.onclick = setBgColor;

2.HTML标签属性:

<input name="colorbutton" type="button" onclick="setBgColor()" />

上例在实行过程当中的结果以下代码:

document.form1.colorbutton.onclick = function onclick(){
    setBgColor();
}

注重:如何在HTML中挪用一个onclick返回事宜的属性。能够像以下如许运用:

<input ...
    onclick="alert(event.target.tagName)"

就像其他的参数参考的函数一样,事宜处置惩罚顺序一样是要领,在函数中返回的this对象会指向挪用该要领的对象。比方:

window.onfocus

上例中在onfocus上绑定的事宜处置惩罚顺序的this对象就会指向window对象。
给事宜绑定的有传参的事宜处置惩罚顺序,必需被包含在其他的函数中:

document.form1.button1.onclick = function(){
    setBgColor("some color");
}

函数的局部变量

arguments:一个”类数组”的对象,包含了传入当前函数的一切实参;
arguments.callee:指向当前函数;
arguments.caller:指向挪用当前函数的函数,请运用arguments.callee.caller替代;
arguments.length:arguments对象的中元素的个数。

同步于个人博客:http://penouc.com

    原文作者:penouc
    原文地址: https://segmentfault.com/a/1190000000510474
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞