函数

函数定义

JavaScript 函数是通过 function 关键词定义的。

声明定义

function functionName(parameters) {
要执行的代码
}

被声明的函数不会直接执行。它们被“保存供稍后使用”,将在稍后执行,当它们被调用时。

通过表达式来定义

var x = function (a, b) {
return a * b
};

在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量 x ,所以,通过变量 x 就可以调用该函数。

调用函数

调用函数时,按顺序传入参数即可:

abs(10); // 返回10
abs(-9); // 返回9

由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:

abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9

传入的参数比定义的少也没有问题:

abs(); // 返回NaN

此时 abs(x) 函数的参数 x将收到 undefined ,计算结果为 NaN 。要避免收到 undefined ,可以对参数进行检查:

function abs(x) {
  if (typeof x !== 'number') {
    throw 'Not a number';
 }
  if (x >= 0) {
    return x;
 } else {
    return -x;
 }
}

arguments 函数

JavaScript还有一个免费赠送的关键字 arguments ,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。

function foo(x) {
  console.log('x = ' + x); // 10
  for (var i=0; i<arguments.length; i++) {
    console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
 }
} foo(10, 20, 30);

利用 arguments ,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:

function abs() {
  if (arguments.length === 0) {
    return 0;
 }
  var x = arguments[0];
  return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9

实际上arguments 最常用于判断传入参数的个数。你可能会看到这样的写法:

// foo(a[, b], c)
// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo(a, b, c) {
  if (arguments.length === 2) {
    // 实际拿到的参数是a和b,c为undefined
    c = b; // 把b赋给c
    b = null; // b变为默认值
 }
  // ...
}

ret 参数

由于JavaScript函数允许接收任意个参数,于是我们就不得不用 arguments来获取所有参数:

function foo(a, b) {
  var i, rest = [];
  if (arguments.length > 2) {
    for (i = 2; i<arguments.length; i++) {
      rest.push(arguments[i]);
   }
 }
  console.log('a = ' + a);
  console.log('b = ' + b);
  console.log(rest);
}

为了获取除了已定义参数 a b之外的参数,我们不得不用 arguments ,并且循环要从索引2 开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的 rest 参数,有没有更好的方法?

ES6标准引入了rest参数,上面的函数可以改写为:

function foo(a, b, ...rest) {
  console.log('a = ' + a);
  console.log('b = ' + b);
  console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []

rest参数只能写在最后,前面用 ... 标识,从运行结果可知,传入的参数先绑定 a b ,多余的参数以数组形式交给变量 rest ,所以,不再需要 arguments 我们就获取了全部参数。

变量的作用域

在JavaScript中,用 var 申明的变量实际上是有作用域的。

作用域类型:

  • 局部作用域
  • 全局作用域

如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量:

'use strict';
function foo() {
  var x = 1;
  x = x + 1;
}
x = x + 2; // ReferenceError! 无法在函数体外引用变量x

如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响。

由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。

'use strict';

function foo() {
  var x = 1;
  function bar() {
     var y = x + 1; // bar可以访问foo的变量x!
  }
  var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}

如果内部函数和外部函数的变量名重名怎么办?

function foo() {
  var x = 1;
  function bar() {
    var x = 'A';
    console.log('x in bar() = ' + x); // 'A'
 }
  console.log('x in foo() = ' + x); // 1
  bar();
}
foo();

这说明JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。

变量提升

JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:

'use strict';
function foo() {
  var x = 'Hello, ' + y;
  console.log(x);
  var y = 'Bob';
}
foo();

结果:Hello, undefined , 说明 y 的值为 undefined;

这正是因为JavaScript引擎自动提升了变量 y 的声明,但不会提升变量 y 的赋值。

对于上述 foo() 函数,JavaScript引擎看到的代码相当于:

function foo() {
  var y; // 提升变量y的申明,此时y为undefined
  var x = 'Hello, ' + y;
  console.log(x);
}

由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个 var 申明函数内部用到的所有变量:

function foo() {
  var
    x = 1, // x初始化为1
    y = x + 1, // y初始化为2
    z, i; // z和i为undefined
  // 其他语句:
  for (i=0; i<100; i++) {
    ...
 }
}

全局作用域

不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象 window ,全局作用域的变量实际上被绑定到 window 的一个属性:

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript

因此,直接访问全局变量course和访问window.course是完全一样的。

把自己的代码全部放入唯一的名字空间 MYAPP 中,会大大减少全局变量冲突的可能。

许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。

局部作用域

由于JavaScript的变量作用域实际上是函数内部,我们在 for 循环等语句块中是无法定义具有局部作

用域的变量的:

'use strict';

function foo() {
  for (var i=0; i<100; i++) {
    //
 }
  i += 100; // 仍然可以引用变量i
}

为了解决块级作用域,ES6引入了新的关键字 let ,用 let 替代 var 可以申明一个块级作用域的变量:

'use strict';

function foo() {
  var sum = 0;
  for (let i=0; i<100; i++) {
    sum += i;
}
  i += 1;
}

常量

由于 varlet 申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:

var PI = 3.14;

ES6标准引入了新的关键字 const 来定义常量, const 与 let 都具有块级作用域:

'use strict';

const PI = 3.14;
PI = 3;
PI; // 3.14

方法

定义方法

在一个对象中绑定函数,称为这个对象的方法。

在JavaScript中,对象的定义是这样的:

var xiaoming = {
  name: '小明',
  birth: 1990
};

但是,如果我们给xiaoming 绑定一个函数,就可以做更多的事情。比如,写个age() 方法,返

xiaoming 的年龄:

var xiaoming = {
  name: '小明',
  birth: 1990,
  age: function () {
    var y = new Date().getFullYear();
    return y - this.birth;
 }
}; xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了

绑定到对象上的函数称为方法,和普通函数也没啥区别,但是它在内部使用了一个 this 关键字

在一个方法内部, this 是一个特殊变量,它始终指向当前对象,也就是 xiaoming 这个变量。所

以,this.birth可以拿到xiaomingbirth 属性。

折开写:

function getAge() {
  var y = new Date().getFullYear();
  return y - this.birth;
} var xiaoming = {
  name: '小明',
  birth: 1990,
  age: getAge
}; xiaoming.age(); // 25, 正常结果
getAge(); // NaN

单独调用函数getAge()怎么返回了 NaN ?请注意,我们已经进入到了JavaScript的一个大坑里。

JavaScript的函数内部如果调用了 this ,那么这个 this 到底指向谁?答案是,视情况而定!

如果以对象的方法形式调用,比如xiaoming.age(),该函数的 this 指向被调用的对象,也就

xiaoming ,这是符合我们预期的。

如果单独调用函数,比如 getAge() ,此时,该函数的 this 指向全局对象,也就是 window

apply

要指定函数的this指向哪个对象,可以用函数本身的 apply 方法,它接收两个参数,第一个参数

就是需要绑定的 this 变量,第二个参数是Array,表示函数本身的参数。

function getAge() {
  var y = new Date().getFullYear();
  return y - this.birth;
} var xiaoming = {
  name: '小明',
  birth: 1990,
  age: getAge
}; xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空

最新文章

  1. java设计模式- (1)单例模式
  2. Mac下手动安装SafariDriver extension
  3. 缓存篇~第六回 Microsoft.Practices.EnterpriseLibrary.Caching实现基于方法签名的数据集缓存
  4. 【POJ 1279】Art Gallery
  5. 微信公众平台入门开发教程.Net(C#)框架
  6. 如何使用mybatis《一》
  7. [BZOJ 3038]上帝造题的7分钟2(树状数组)
  8. poj 3685 Matrix(二分搜索之查找第k大的值)
  9. HDU5692(线段树+dfs序)
  10. Web应用安全测试
  11. python—查找以XXX结尾的文件
  12. Java插入排序算法
  13. hdu 2899
  14. logback的使用和logback.xml详解,在Spring项目中使用log打印日志
  15. Java第四次实验
  16. 【java】解析java网络
  17. java-IO流(File对象-深度遍历指定目录下的文件夹和文件)
  18. WebService安全性的几种实现方法【身份识别】
  19. 乘风破浪:LeetCode真题_033_Search in Rotated Sorted Array
  20. SuperMap开发入门1——资源下载

热门文章

  1. Redis 底层数据结构之字典
  2. AcWing 105. 七夕祭
  3. Linux 动态库 undefined symbol 原因定位与解决方法
  4. 多es 集群数据迁移方案
  5. Log4cpp配置文件及动态调整日志级别的方法
  6. Luogu P2754 星际转移问题
  7. Django基础013--redis开发
  8. Linux磁盘管理与文件系统
  9. 使用BeautifulSoup自动爬取微信公众号图片
  10. 常见最基础的Dos命令.