ECMAScript《JavaScript 闯关记》之函数

函数是同等段子代码,它仅仅定义一糟,但足让实践要调用任意次。在 JavaScript
里,函数即对象,程序可以随心所欲操控她。比如,可以拿函数赋值给变量,或者当做参数传递给其它函数,也得给她设置属性,甚至调用它们的法子。如果函数挂载在一个靶及,作为对象的一个性能,就如她也目标的主意。如果函数嵌套在旁函数中定义,这样它就足以看它们为定义时所处的作用域中之别变量。

函数定义

每当 JavaScript 中,函数实际上是目标,每个函数都是 Function
构造函数的实例,因此函数名为实际上也是一个针对性函数对象的指针,不见面和某函数绑定。函数通常有以下3受到定义方式。例如:

// 写法一:函数声明(推荐写法)
function sum (num1, num2) {
    return num1 + num2;
}

// 写法二:函数表达式(推荐写法)
var sum = function(num1, num2){
    return num1 + num2;
};

// 写法三:Function 构造函数(不推荐写法)
var sum = new Function("num1", "num2", "return num1 + num2"); 

是因为函数曰单纯是恃于函数的指针,因此函数称作及分包对象指针的旁变量没有呀两样。换句话说,一个函数可能会见生出多独名字。例如:

function sum(num1, num2){
    return num1 + num2;
}
console.log(sum(10,10));        // 20

var anotherSum = sum;
console.log(anotherSum(10,10)); // 20

sum = null;
console.log(anotherSum(10,10)); // 20

并未重载

以函数叫作想象吧指针,也推进了解为什么 JavaScript 中从不函数重载的概念。

function addSomeNumber(num){
    return num + 100;
}

function addSomeNumber(num) {
    return num + 200;
}

var result = addSomeNumber(100);    // 300

阳,这个例子中宣示了有限独跟名函数,而结果虽是末端的函数覆盖了前头的函数。以上代码实际上与下部的代码没有啊分别。

var addSomeNumber = function (num){
    return num + 100;
};

addSomeNumber = function (num) {
    return num + 200;
};

var result = addSomeNumber(100);    // 300

透过重新写代码之后可以很容易了解,在创立第二只函数时,实际上覆盖了援第一独函数的变量
addSomeNumber

函数声明与函数表达式

解析器在向阳行环境受到加载数据时,对「函数声明」和「函数表达式」并非一视同仁。解析器会首先读取函数扬言,并使该以执行外代码之前可用(可以拜);至于函数表达式,则须等交解析器执行到其所于的代码行,才会真正为诠释实施。例如:

console.log(sum(10,10)); // 20
function sum(num1, num2){
    return num1 + num2;
}

上述代码完全可以健康运转。因为当代码开始施行前,解析器就既由此一个称呼也函数声明提升(function
declaration
hoisting)的历程,读取并拿函数声明添加到实践环境被。对代码求值时,JavaScript
引擎在首先所有会声明函数并拿它坐源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript
引擎也能够把函数声明提升及顶部。把地方的「函数声明」改为等价的「函数表达式」,就会以执行中造成错误。例如:

console.log(sum(10,10)); // Uncaught TypeError: sum is not a function
var sum = function(num1, num2){
    return num1 + num2;
};

除此之外上述区别外,「函数声明」与「函数表达式」的语法是等价格的。

作为价值的函数

因 JavaScript
中之函数名本身就是是变量,所以函数也得看成值来使用。也就是说,不仅可像传递参数一样将一个函数传递给任何一个函数,而且可以以一个函数作为其他一个函数的结果回到。来拘禁无异收押下的函数。

function callSomeFunction(someFunction, someArgument){
    return someFunction(someArgument);
}

这函数接受两只参数。第一只参数应该是一个函数,第二个参数应该是要传送让该函数的一个值。然后,就足以像下的例子一样传递函数了。

function add10(num){
    return num + 10;
}

var result1 = callSomeFunction(add10, 10);
console.log(result1);   // 20

function getGreeting(name){
    return "Hello, " + name;
}

var result2 = callSomeFunction(getGreeting, "Nicholas");
console.log(result2);   // "Hello, Nicholas"

这里的 callSomeFunction()
函数是通用的,即无第一独参数中传递进入的是什么函数,它都见面回执行第一单参数后底结果。要顾函数的指针而不实行函数的言辞,必须去丢函数叫作后的那么针对圆括号。因此地方例子中传送给
callSomeFunction() 的是 add10
getGreeting,而休是执行其后的结果。

理所当然,还好由一个函数中归外一个函数,而且这吗是远有用的同样种技术。例如,假设有一个靶往往组,我们怀念使基于某对象属性对数组进行排序。而传递让数组
sort()
方法的较函数要接到两单参数,即如比较的价。可是,我们用平等栽艺术来指明按照哪个属性来排序。要解决之题目,可以定义一个函数,它接受一个属性名,然后因此特性名来创建一个较函数,下面就是是函数的概念。

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

其一函数定义看起有点复杂,但骨子里只有就是是以一个函数中嵌套了其他一个函数,而且其中函数前面加了一个
return 操作符。在其间函数接收及 propertyName
参数后,它见面使方括号表示法来取让定属性的价。取得了相思如果的属于性值之后,定义比较函数就非常简单了。上面这函数可以像于底下例子中如此使。

var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];

data.sort(createComparisonFunction("name"));
console.log(data[0].name);  // Nicholas

data.sort(createComparisonFunction("age"));
console.log(data[0].name);  // Zachary

这里,我们创建了一个含有两只目标的多次组 data。其中,每个对象都富含一个
name 属性和一个 age 属性。在默认情况下,sort()
方法会调用每个对象的 toString()
方法以确定它的次序;但得到的结果往往并无切合人类的思维习惯。因此,我们调用
createComparisonFunction("name")
方法创建了一个比函数,以便按照每个对象的 name
属性值进行排序。而结果排在前头的首先宗是 name"Nicholas"age
29 的靶子。然后,我们还要动了 createComparisonFunction("age")
返回的比较函数,这次是随目标的age属性排序。得到的结果是 name 值为
"Zachary"age 值是 28 的目标排在了第一个。

函数的形参和实参

于函数内部,有三三两两只独特的靶子:argumentsthis。其中,arguments
是一个类数组对象,包含在传播函数中之具备参数。虽然 arguments
的主要用途是保留函数参数,但以此目标还有一个称呼 callee
的属性,该属性是一个指针,指向拥有此 arguments
对象的函数。请看下这个深经典的阶乘函数。

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

概念阶乘函数一般还如因此到递归算法,如上面的代码所示,在函数有名字,而且名字下吧未见面转换的情状下,这样定义尚无问题。但问题是是函数的尽与函数名
factorial
紧紧耦合在了联合。为了消弭这种环环相扣耦合的场景,可以像下这样用
arguments.callee

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

以斯更写后的 factorial() 函数的函数体内,没有更引用函数名
factorial。这样,无论引用函数时采取的是什么名字,都得保证正常完成递归调用。例如:

var trueFactorial = factorial;

factorial = function(){
    return 0;
};

console.log(trueFactorial(5));  // 120
console.log(factorial(5));      // 0

在此,变量 trueFactorial 获得了 factorial
的价值,实际上是于其它一个岗位上保存了一个函数的指针。然后,我们同时以一个简便地赶回
0 的函数赋值给 factorial 变量。如果如原来的 factorial() 那样不采取
arguments.callee,调用 trueFactorial() 就会返回
0。可是,在去掉了函数体内的代码和函数誉为的耦合状态下,trueFactorial()
仍然会正常地精打细算阶乘;至于 factorial(),它本只是一个返回 0
的函数。

函数内部的任何一个特种对象是 this,其作为同 Java 和 C# 中的 this
大致相近。换句话说,this
引用的凡部数据以行之条件目标(当在网页的全局作用域中调用函数时,this
对象引用的便是 window)。来拘禁下面的例子。

window.color = "red";
var o = { color: "blue" };

function sayColor(){
    console.log(this.color);
}
sayColor();     // "red"

o.sayColor = sayColor;
o.sayColor();   // "blue"

上面这函数 sayColor() 是在大局作用域中定义的,它引用了 this
对象。由于在调用函数之前,this 的价值并无确定,因此 this
可能会见以代码执行过程遭到援不同的对象。当在大局作用域中调用 sayColor()
时,this 引用的凡全局对象 window;换句话说,对 this.color
求值会转换成对 window.color 求值,于是结果就是归了
"red"。而当把此函数赋给目标 o 并调用 o.sayColor() 时,this
引用的是目标 o,因此对 this.color 求值会转换成为对 o.color
求值,结果虽回来了 "blue"

恳请大家一定要是牢记,函数的名字就是一个蕴含指针的变量而已。因此,即使是于不同的环境遭受实行,全局的
sayColor() 函数与 o.sayColor() 指向的仍然是跟一个函数。

ECMAScript 5为规范化了别样一个函数对象的属性
caller。这个特性被保留着「调用当前函数的函数的援」,如果是以大局作用域中调用当前函数,它的价为
null。例如:

function outer(){
    inner();
}

function inner(){
    console.log(arguments.callee.caller);
} 

outer();

上述代码会促成警告框中显示 outer() 函数的源代码。因为 outer() 调用了
inter(),所以 arguments.callee.caller 就指向 outer()

在严格模式下,访问 arguments.callee属性,或为函数的 caller
属性赋值,都见面促成错误。

函数的性质与办法

JavaScript
中的函数是目标,因此函数也出性和法。每个函数都蕴含两只特性:length
prototype。其中,length
属性表示函数希望接的命名参数的个数,如下面的事例所示。

function sayName(name){
    console.log(name);
}

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

function sayHi(){
    console.log("hi");
}

console.log(sayName.length);      // 1
console.log(sum.length);          // 2
console.log(sayHi.length);        // 0

于 JavaScript 中的援类型而言,prototype
是保存其拥有实例方法的真所在。换句话说,诸如 toString()
valueOf() 等艺术其实还保留于 prototype
名下,只不过是经过独家对象的实例访问罢了。在创建于定义引用类型及落实持续时,prototype
属性的企图是极为重要的。在 ECMAScript 5吃,prototype
属性是不可枚举的,因此下 for-in 无法察觉。

每个函数都带有两独非继承而来之章程:apply()
call()。这有限单办法的用还是于一定的作用域中调用函数,实际上等于设置函数体内
this 对象的值。首先,apply()
方法接收两个参数:一个是在其中运行函数的作用域,另一个凡参数数组。其中,第二独参数可以是
Array 的实例,也堪是 arguments 对象。例如:

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

function callSum1(num1, num2){
    return sum.apply(this, arguments);  // 传入 arguments 对象
}

function callSum2(num1, num2){
    return sum.apply(this, [num1, num2]);  // 传入数组
}

console.log(callSum1(10,10));   // 20
console.log(callSum2(10,10));   // 20

在点是例子中,callSum1() 在执行 sum() 函数时传入了
this(因为是在全局作用域中调用的,所以传入的虽是 window 对象)和
arguments 对象。而 callSum2 同样也调用了 sum()
函数,但它们传到的虽然是 this
和一个参数数组。这点儿独函数都见面健康履行并返回正确的结果。

call() 方法与 apply()
方法的来意一样,它们的区分只在接收参数的主意各异。对于 call()
方法而言,第一只参数是 this
值没有变,变化之是别参数还直接传送让函数。换句话说,在应用 call()
方法时,传递给函数的参数必须逐一个列举出,如下面的例证所示。

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

function callSum(num1, num2){
    return sum.call(this, num1, num2);
}

console.log(callSum(10,10));   // 20

在使用 call() 方法的情事下,callSum()
必须明确地传每一个参数。结果及运用 apply() 没有啊不同。至于是动
apply() 还是
call(),完全在于你使用哪种让函数传递参数的方最好有益。如果你打算直接招入
arguments 对象,或者隐含函数中优先接到及之也罢是一个屡组,那么用
apply() 肯定再也方便;否则,选择 call()
可能又适合。(在非让函数传递参数的情事下,使用谁方法还无所谓。)
实质上,传递参数并非 apply()call()
真正的用武之地;它们确实强的地方是能壮大函数赖以运行的作用域。下面来拘禁一个例证。

window.color = "red";
var o = { color: "blue" };

function sayColor(){
    console.log(this.color);
}
sayColor();                // red

sayColor.call(this);       // red
sayColor.call(window);     // red
sayColor.call(o);          // blue

此事例是在前方说明 this
对象的言传身教基础及改要改为的。这同糟,sayColor()
也是当全局函数定义的,而且当以大局作用域中调用它经常,它实在会显
"red",因为对 this.color 的求值会更换成为对 window.color 的求值。而
sayColor.call(this)
sayColor.call(window),则是少栽显式地在全局作用域中调用函数的道,结果自然还见面显示
"red"。但是,当运行 sayColor.call(o)
时,函数的履行环境就是不等同了,因为这时候函数体内的 this 对象指为了
o,于是结果显示的是 "blue"

使用 call()apply()
来扩充作用域的不过特别益处,就是目标非待以及办法有其它耦合关系。在前方例子的第一只版本中,我们是事先拿
sayColor() 函数放到了靶 o 中,然后再通过 o
来调用它的;而当此地还写的事例中,就未待先十分多余的步骤了。

关卡

// 挑战一,合并任意个数的字符串
var concat = function(){
    // 待实现方法体
}
console.log(concat('st','on','e'));  // stone

// 挑战二,输出指定位置的斐波那契数列
var fioacciSequece = function(count){
    // 待实现方法体
}
console.log(fioacciSequece(12));  // 0、1、1、2、3、5、8、13、21、34、55、89

// 挑战三,三维数组或 n 维数组去重,使用 arguments 重写
var arr = [2,3,4,[2,3,[2,3,4,2],5],3,5,[2,3,[2,3,4,2],2],4,3,6,2];
var unique = function(arr){
    // 待实现方法体
}
console.log(unique(arr)); // [2,3,4,5,6]

更多

关爱微信公众号「劼哥舍」回复「答案」,获取关卡详解。
关注
https://github.com/stone0090/javascript-lessons,获取最新动态。