JavaScript的函数

in Notes with 0 comment

定义函数

函数的定义(也称为函数的声明)由一系列的函数关键词组成

例如

function square(number) {
  return number * number;
}

函数square使用了一个参数,叫作number
这个函数只有一个语句,它说明该函数会将函数的参数(即number)自乘后返回
函数的return语句确定了函数的返回值

如果你传递一个对象(pass an object),作为参数,而函数改变了这个对象的属性,这样的改变对函数外部是可见的

function myFunc(theObject) {
  theObject.make = "Toyota";
}

var mycar = {make: "Honda", model: "Accord", year: 1998},
var x, y;

x = mycar.make;     // x 获取的值为 "Honda"

myFunc(mycar);
y = mycar.make;     // y 获取的值为 "Toyota"
                    // (make属性的值在函数中被改变了)

函数表达式

var square = function(number) {
  return number * number
};
var x = square(4); // x 得到的值为16

函数表达式也可以提供函数名,用于在函数内部使用来代指其本身

var factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};
console.log(factorial(3));

函数表达式在将函数作为一个引数传递给其它函数时十分方便

下面的例子演示了一个叫map的函数如何被定义,而后调用一个匿名函数作为其第一个参数

function map(f,a) {
  var result = [], // 创建一个新的数组
      i;
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
  return result;
}
map(function(x) {return x * x * x}, [0, 1, 2, 5, 10]);
//return [0, 1, 8, 125, 1000]

根据条件来定义一个函数,例如下面的例子

var myFunc;
if (num == 0){
  myFunc = function(theObject) {
    theObject.make = "Toyota"
  }
}

当一个对象的属性是函数时,其称之为方法

调用函数

调用函数会以给定的参数真正执行这些动作

例如,定义了函数square,你可以如下这样调用它

square(5);

函数一定要处于调用它们的域中,但是函数的声明可以在它们的调用语句之后,例如

console.log(square(5));
/* ... */
function square(n) { return n*n }

函数域是指函数声明时的所在的地方,或者函数在顶级被声明时指整个程序

意只有使用如上的语法形式 function funcName(){} 才可以,下面的例子是无效的

console.log(square(5)); // square is not defined
square = function (n) {
  return n * n;
}

函数可以被递归;就是说函数可以调用其本身,下面的例子是计算递归的阶乘值

function factorial(n){
  if ((n == 0) || (n == 1))
    return 1;
  else
    return (n * factorial(n - 1));
}

var a, b, c, d, e;

a = factorial(1); // 1赋值给a
b = factorial(2); // 2赋值给b
c = factorial(3); // 6赋值给c
d = factorial(4); // 24赋值给d
e = factorial(5); // 120赋值给e

函数的作用域

一个函数可以取得在它的域中定义的任何变量和子函数

// 下面的变量定义在全局作用(global scope)域中
var num1 = 20,
    num2 = 3,
    name = "Chamahk";

// 本函数定义在全局作用域
function multiply() {
  return num1 * num2;
}
multiply(); // Returns 60

// 嵌套函数的例子
function getScore () {
  var num1 = 2,
      num2 = 3;
  function add() {
    return name + " scored " + (num1 + num2);
  }
  return add();
}
getScore(); // Returns "Chamahk scored 5"

作用域和函数堆栈

递归

个函数可以指向并调用自身(call itself),有三种方法:

  1. 通过使用函数名(the function's name)

  2. 使用arguments.callee(ECMAScript (ES5) forbids use of arguments.callee() in strict mode)

  3. 使用作用域下的一个变量名来指向函数(an in-scope variable refers to the function)

函数定义:

var foo = function bar() {
   // statements go here
};

在这个函数体内,以下语句是等价的

调用自身的函数我们称之为递归函数(recursive function)

在某种意义上说,递归近似于循环
两者都重复执行相同的代码,并且两者都需要一个终止条件以避免无限循环或者无限递归

var x = 0;
while (x < 10) { // "x < 10" is the loop condition
   // do stuff
   x++;
}

转化成一个递归函数和对其的调用

function loop(x) {
  if (x >= 10) // "x >= 10" is the exit condition (equivalent to "!(x < 10)")
    return;
  // do stuff
  loop(x + 1); // the recursive call
}
loop(0);

有些算法并不能简单的用循环来实现。例如,获取树结构中所有的节点时,递归来实现要容易得多

function walkTree(node) {
  if (node == null) // 
    return;
  // do something with node
  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);

// Output:

// begin:3
// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2
// end:3

嵌套函数和闭包

在一个函数里面嵌套另外一个函数
嵌套函数是容器函数的私有成员。它自身也形成了一个闭包
一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数)

嵌套函数是一个闭包,就意味着一个嵌套函数可以继承容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。

嵌套函数:

function addSquares(a,b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2,3); // returns 13
b = addSquares(3,4); // returns 25
c = addSquares(4,5); // returns 41

内部函数形成了闭包,你可以调用外部函数并且指定外部和内部函数的参数

function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}
// Think of it like: give me a function that adds 3 to whatever you give it
fn_inside = outside(3); 
result = fn_inside(5); // returns 8
result1 = outside(3)(5); // returns 8

保存变量

一个闭包必须保存它可见作用域中所有的参数和变量
每一次调用传入的参数都可能不同,每一次对外部函数的调用都实际上重新创建了一遍这个闭包
只有当inside的返回值没有再被引用时,内存才会被释放

多层嵌套函数

函数可以被多层嵌套

例如,函数A可以包含函数B,函数B可以再包含函数C。B和C都形成了闭包,所以B可以访问A,C可以访问B和A

闭包可以包含多个作用域,他们递归式的包含了所有包含它的函数作用域。这个称之为域链(scope chaining)

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

C可以访问B的y和A的x

反过来却不是这样。A不能访问C,因为A看不到B中的参数和变量,C是B中的一个变量,所以C是B私有的。

命名冲突

当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突

更近的作用域有更高的优先权,最远的优先级最低,这就是作用域链

链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域

function outside() {
  var x = 10;
  function inside(x) {
    return x;
  }
  return inside;
}
result = outside()(20); // returns 20 instead of 10

命名冲突发生在return x上,inside的参数x和外部变量x发生了冲突

作用链域是 {inside, outside, 全局对象}

inside具有最高优先权,返回了传入的20而不是外部函数的变量值10


阅读资料:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide

复习方式:实践(代码写一次、跑一次、测试一次),不懂的地方谷歌,阅读和做笔记

底线原则:宁可重写一次,也不复制粘贴

本次复习内容有:函数的一部分···

复习耗时:大概3小时···我也不知道为什么这么久···

Responses