函数

函数定义

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, 参数为空

JavaScript学习03(函数)的更多相关文章

  1. JavaScript学习03 JS函数

    JavaScript学习03 JS函数 函数就是包裹在花括号中的代码块,前面使用了关键词function: function functionName() { 这里是要执行的代码 } 函数参数 函数的 ...

  2. JavaScript学习09 函数本质及Function对象深入探索

    JavaScript学习09 函数本质及Function对象深入探索 在JavaScript中,函数function就是对象. JS中没有方法重载 在JavaScript中,没有方法(函数)重载的概念 ...

  3. Javascript学习5 - 函数

    原文:Javascript学习5 - 函数 在Javascript中,函数和对象是交织在一起的.有些函数的特性与对象相关联.这一点的内容在第六部分会讨论到. 这一部分主要讨论函数与其它比较熟悉的语言( ...

  4. Javascript学习之函数(function)

    在JS中,Function(函数)类型实际上是对象;每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法.由于函数是对象,因此函数名实际上也是一个指向函数对象的指针. 一 函 ...

  5. JavaScript学习笔记——函数

    javascript函数的声明和调用 将完成某一特定功能的代码集合起来,可以重复使用的代码块. 一.函数的声明方式(创建) A.基本语法 function 关键字 function 函数名([参数1] ...

  6. JavaScript学习笔记-函数

    函数的两种创建方式:函数定义表达式.函数声明语句 编译时,函数声明语句创建的函数会‘被提前’至外部函数的作用域顶部,在该作用域内可以被随意调用: 而函数表达式创建的函数,要调用它必须赋值给一个变量,编 ...

  7. JavaScript学习笔记-函数实例

    函数实例 var p = { a:15, b:'5', f1:function(){ var self = this; console.log(self.a+self.b); f2(); functi ...

  8. python学习 03 函数 (只会执行一次return就不会往下执行)

    1.调用函数只会执行一次return,而且执行return后不会往下执行

  9. JavaScript基础03——函数的作用域及变量提升

    1.作用域 作用域,变量在函数内部作用的范围/区域.有函数的地方就有作用域.   2.局部作用域和全局作用域 function fn(){ var a = 1; } console.log(a); / ...

随机推荐

  1. 面试题四:手写sql

    矫正数据,有以下2个表,建表语句如下所示 -- 订单表 create table t_order ( id int auto_increment primary key, name varchar(2 ...

  2. linux命令-------find命令之exec

    p.p1 { margin: 0; font: 18px "Hannotate SC"; color: rgba(4, 51, 255, 1); -webkit-text-stro ...

  3. 剑指 Offer 12. 矩阵中的路径

    题目描述 是一道很常见的深搜题目,不过里面要考虑一些边界问题,比如走过的路径是不能再次走入的,所以我这里我自己的 代码想到是利用一个新的二维的数组,记录走过的路径,不过题解的直接将原二维数组中的路径隐 ...

  4. vm虚拟机无法与本地ping通

    打本地靶场,发现无法找到localhost,而且也ping不通,当时挺迷的,所以百度了一下方法,以下是内容: 一.解决方式 1. 2.点击设置 3. 4. 点击nat模式 5. 通了, 二.nat模式 ...

  5. JUnit5的条件测试、嵌套测试、重复测试

    条件测试 JUnit5支持条件注解,根据布尔值判断是否执行测试. 自定义条件 @EnabledIf和@DisabledIf注解用来设置自定义条件,示例: @Test @EnabledIf(" ...

  6. ESP32音频输入-MAX4466,MAX9814,SPH0645LM4H,INMP441(翻译)

    有几种方法可以将模拟音频数据输入到ESP32中. 直接从内置的模数转换器(ADC)读取 这对于一次性读取很有用,但不适用于高采样率. 使用I2S通过DMA读取内置ADC 适用于模拟麦克风,例如MAX4 ...

  7. 入门Kubernetes-minikube本地k8s环境

    前言: 在上一篇 结尾中使用到了minikube方式来做k8s本地环境来学习k8s. 那么这篇先了解下minikube及使用 一.Minikube 简介 minikube 在 macOS.Linux ...

  8. [网络流24题]最长k可重线段集[题解]

    最长 \(k\) 可重线段集 题目大意 给定平面 \(x-O-y\) 上 \(n\) 个开线段组成的集合 \(I\) ,和一个正整数 \(k\) .试设计一个算法,从开线段集合 \(I\) 中选取开线 ...

  9. 【剑指offer】51.构建乘积数组

    51.构建乘积数组 知识点:数组: 题目描述 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0] * A[1] *... * A[i ...

  10. java并发编程基础——线程同步

    线程同步 一.线程安全问题 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安 ...