定义与参数
函数式的不同点
函数是程序执行过程中的主要模块单元。除了全局JavaScript代码是在页面构建的阶段执行的。由于我们的大多数代码会作为函数调用来执行,通用强大的构造器赋予代码很大的灵活性和控制力。常用功能:
- 对象可通过字面量来创建{}
- 对象可以复制给变量、数组项、或其他对象的属性。
var ninja = {};
ninjaArray.push({});
ninja.data = {};
- 对象可以作为参数传递给函数
function hide(ninja){
ninja.visibility = false;
} hide({});
- 对象可以作为函数的返回值
function returnNewNinja(){
return {};
}
- 对象能够具有动态创建和分配的属性
var ninja = {};
ninja.name = "Hanzo";
函数是第一类对象
JavaScript中函数拥有对象的所有能力,也因此函数可被作为任意其他类型对象来对待。函数是第一类对象则函数也能实现以下的功能:
- 通过字面量创建
function ninjaFunction(){}
- 复制给变量、数组项或其他对象的属性
var ninjaFunction = function() {};
ninjaArray.push(function(){});
ninja.data = function(){};
- 作为函数的参数来传递
function call(ninjaFunction){
ninjaFunction();
}
call(function(){});
- 作为函数的返回值
function returnNewNinjaFunction(){
return function(){};
}
- 具有动态创建和分配的属性
var ninjaFunction = function(){};
ninjaFunction.ninja = "Hanzo";
对象能做的任何一件事,函数也能做。函数也是对象,唯一的特殊之处在于它是可调的。即函数会被调用以便执行某项动作。第一类对象的特点之一是,它能够作为参数传入函数。如果我们将某个函数作为参数传入另一个函数,传入函数会在应用程序执行的某个时间点才执行。这就是所谓的回调函数(callback funciton)。
回调函数
每当我们建立一个将在随后调用的函数时,无论是在事件处理阶段通过浏览器还是通过其他代码,我们都是建立一个回调。
function useless(ninjaCallback) {
return ninjaCallback();
}
函数作为另外一个函数的参数,随后通过参数来调用该函数。
var text = "Domo arigato!";
console.log("Before defining functions");
function useless(ninjaCallback) {
console.log("In useless function");
return ninjaCallback();
}
function getText() {
console.log("In getText function");
return text;
}
function assert(condition,text) {
if(condition){
console.log(text);
}
}
console.log("Before making all the calls");
assert(useless(getText) === text,"The useless function works! " + text);
console.log("After the calls have been made");
输出:
Before defining functions
Before making all the calls
In useless function
In getText function
The useless function works! Domo arigato!
After the calls have been made
getText函数作为参数传入useless函数。回调函数callback()的调用让getText函数得到执行。
JavaScript的重要特征之一就是可以在表达式出现的的任意位置创建函数,初次之外这种方式能使代码更紧凑和易于理解。
使用比较器排序
var values = [0, 3, 2, 5, 7, 4, 8, 1];
values.sort(function(value1, value2) {
return value2 - value1;
});
console.log(values)
JavaScript每次需要比较两个值的时候都会调用我们提供的回调函数。
函数作为对象
- 在集合中存储函数使我们轻易管理关联的函数
- 记忆让函数能够记住上次计算得到的值,从而提高后续调用的性能。
存储函数
在某些例子中,我们会存储唯一的函数集合。当我们向这样的集合中添加函数时,会面临的两个问题:哪些函数对集合来说是新函数,从而加入该集合中。哪些函数已经存在集合中,从而不需要添加到集合中。管理回调函数集合时,我们并不希望存在重复函数,否则一个事件会导致同一个回调函数被多次调用。
function assert(condition, text) {
if(condition){
console.log(text);
}
}
var store = {
nextId : 1,
cache : {},
add : function(fn){
if (!fn.id) {
fn.id = this.nextId++;
this.cache[fn.id] = fn;
return true;
}
}
};
function ninja() {}
assert(store.add(ninja), "Funciton was safely added.");
assert(!store.add(ninja), "But it was only added once.");
Funciton was safely added.
But it was only added once.
在这程序中我们创建了一个对象赋值给变量store,这个变量中存储的是唯一的函数集合,这个对象有两个属性:一个是下一个可用的id,另外一个缓存着已经保存的函数。函数通过add()方法添加到缓存中。在add函数中,我们首先检查给函数是否已经存在id属性了,如果该函数已经存在id属性了,那么我们假设该函数已经被处理了,如果没有则分配一个id给这个函数,然后添加到缓存中。
自记忆函数
记忆化(memoization)是一种构建函数的处理过程,能够记住上次的计算结果。这样既避免复杂又重复的计算可以显著的提升性能。
function assert(condition, text) {
if(condition) {
console.log(text);
}
}
function isPrime(value){
//如果不存在属性就创建
if (!isPrime.answers) {
isPrime.answers = {};
}
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
}
//0和1 不是素数
var prime = value !==0 && value !==1 ;
for (var i = 2; i <value; i++) {
if (value %i === 0) {
prime = false;
break;
}
}
return isPrime.answers[value] = prime;
}
assert(isPrime(5),"5 is prime!");
assert(isPrime.answers[5], "The answer was cached")
输出
5 is prime!
The answer was cached
优点:
- 性能较高
- 不需要额外的初始化
缺点:
- 任何的缓存都是以牺牲内存为代价的
- 缓存逻辑不应该和业务逻辑混合
- 很难估计算法的复杂度
函数的定义
JavaScript函数通常由函数数字面量(function literal)来创建函数值,就像数字字面量创建一个数字值一样。
函数定义:
- 函数声明(function declarations)和函数表达式(function expressions)
- 箭头函数(lambda函数)ES6新增的JavaScript标准
- 函数构造函数
- 生成器函数——ES6新增功能,不同与普通函数的函数,在应用程序执行过程中,这种函数能够退出再重新进入。
函数声明和函数表达式
JavaScript中定义函数最常用的方式是函数声明和函数表达式。
function myFunctionName(myFirstArg, mySecondArg ) {
myStatement1;
myStatement2;
}
示例
function samurai() {
return "samurai here";
}
function ninja() {
function hiddenNinja() {
return "ninja here";
}
return hiddenNinja();
}
可以看到在函数中定义函数。
函数表达式
JavaScript中的函数是第一类对象。
使用数字字面量
var a = 3;
myFunction(4);
使用函数字面量
var a = function(){};
myFunction(function(){});
这种叫做函数表达式。
var myFunc = function(){};
对于函数声明来说,函数名是强制的,但是函数表达式来说,函数名则完全是可选的。
函数声明必须具有函数名是因为它们是独立语句。一个函数的基本要求是它应该能够被调用,必须具有一种被引用方式。
立即函数
函数表达式可以放在有些奇怪的位置上。
myFunctionNmae(3);//标准函数调用
(function(){})(3);//立即调用
这种函数叫做立即调用函数表达式(IIFE)或者简写为立即函数。这个特性能够模拟JavaScript中的模块化。
加括号的函数表达式
JavaScript解析器必须能够轻易区分函数声明和函数表达式之间的区别,如果去掉包裹函数表达式的扩展,把立即调用作为一个独立语句function(){}(3)
,JavaScript开始解析就会结束。
在JavaScript库中经常会见到的几种形式:
+function(){}();
-function(){}();
!function(){}();
~function(){}();
不同于加括号的方式区分函数表达式还是函数声明,这里使用的是一元操作符+,-,!,~
。这里应用一元操作符得到的结果没有存储到任何地方并不重要,只有调用IIFE才重要。
箭头函数
箭头函数是JavaScript标准中ES6新增项。
由于JavaScript中会使用大量的函数,增加简化创建函数方式的语法十分有意义。
var values = [0, 3, 2, 5, 7, 4, 8, 1];
values.sort((value1,value2)=>value1-value2);
console.log(values)
最简形式:
param => expression
函数的实参与形参
- 形参是我们定义函数时所列举的变量
- 实参是我们调用函数时所传递给函数的值
如果实参的数量大于形参,则额外的实参不会赋值给任何形参,如果形参大于实参,则多余的形参则会被设为undefined。
剩余参数
function multMax(first, ...remainingNumbers) {
var sorted = remainingNumbers.sort((a,b)=>b-a);
return first * sorted[0];
}
if(multMax(3,1,2,3) === 9)
console.log("3 * 3 =9")
为函数的最后一个命名参数前加上省略号(…)前缀,这个参数就变成了一个叫做剩余参数的数组,数组内包含着传入的剩余的参数。
默认参数
默认参数已经加入ES6标准
var assert = (condition, text)=>{
if(condition) {
console.log(text);
}
}
function performAction(ninja ,action = "skulking") {
return ninja + " " + action;
}
assert(performAction("Fuma") === "Fuma skulking","The default value is used for Fuma");
assert(performAction("Yoshi") === "Yoshi skulking","The default value is used for Yoshi");
assert(performAction("Hattori") === "Hattori skulking", "The default value is used for Hattori");
assert(performAction("Yagyu", "sneaking") === "Yagyu sneaking", "Yagyu can do whatever he pleases, even sneak!");
The default value is used for Fuma
The default value is used for Yoshi
The default value is used for Hattori
Yagyu can do whatever he pleases, even sneak!
默认的参数可以是任何值,包括数字、字符串、数组、对象,甚至是函数。每次函数的调用都是从左到右求得参数的值。