JavaScript基础—函数

一、函数的定义

函数是JavaScript中最复杂的数据类型,它既有对象的复杂度,又有函数独特的特性,尤其是函数中的this的指向。

函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。它体现的是一种封装的思想。

在JavaScript中,有三种声明函数的方式。

(1)function关键字

function关键字声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。

function add (arg1,arg2){
  //func_body
  return arg1+arg2
}

(2)函数表达式

函数表达式的方式与function关键字的方式有点类似,但是它采用的是变量赋值的写法进行定义的。以下通过函数表达式的方式定义了两个函数

var add = function add_name(arg1,arg2){
  return arg1+arg2
};
add(1,2) // 3
add_name(1,2) //Uncaught ReferenceError: add_name is not defined

var reduce = functin (arg1,arg2){
  return arg1-arg2
};

可以对比(1)和(2)中add函数的定义,发现(2)中等号右侧与(1)中没有任何区别。但是(2)中的add_name这个函数名在外部是没法使用,只能在add_name函数体内部使用,它指向函数表达式本身。

var use_outside = function use_inside(arg1,arg2){
  console.log(typeof use_inside)
  return arg1+arg2
}
use_outside(2,3)
// function
// 5

这种写法好处:一、可以在函数体内部调用自身(递归调用),二、方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)

通常情况下,函数表达式的写法需要以分号结尾,以表示语句的结束

(3)Function构造函数

Function构造函数的方式,其实也很简单。通常构造函数接收至少一个参数,而最后一个参数被认为是函数体。语法如下:

var fun = new Function([arg1,arg2…,argn])
// 其中argn被认为是函数体,其他的参数被认为是函数需要的参数

var foo = new Function(
  'return "hello world"'
);

foo() // hello world

Function构造函数可以不使用new关键字,返回结果是一样的。

总的来说,这种声明函数的方式非常不直观,几乎无人使用。

当一个函数被多次重复声明的时候,后面的声明会覆盖前面的声明,这是由于函数名的提升导致的。

(4)ES6箭头函数

箭头函数语法=>提供了一种指定匿名函数的速记方法。其语法如下

([arg] [, arg]) => {
    statements
}

箭头左侧的值(可能由括号括起)指定传递给函数的参数。  函数的单个参数不要求使用括号。  如果未传入任何参数,则要求使用括号。  箭头右侧的函数定义可以是表达式(如 v + 1)或括在大括号 ({}) 中的语句块。

与标准函数不同,箭头函数外层代码共享相同的this对象,由此不需要var self = this;等运算

var bob = {
  _name: "Bob",
  _friends: ["Pete", "Joe", "Larry"],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f));
  }
}
bob.printFriends()
// Output:
// Bob knows Pete
// Bob knows Joe
// Bob knows Larry

二、函数的属性及应用

(1)name属性

name属性返回函数的名字。

// 函数声明的方式定义函数
function f1() {}
f1.name // "f1"

// 函数表达式的方式:1、等号右边是个匿名函数
var f2 = function () {};
f2.name // "f2"

// 函数表达式的方式:2、等号右边是个具名函数(function关键字后面又名称)
var f3 = function myName() {};
f3.name // "myName"

以上是几种情况下具体返回的name。

(2)length属性

函数的length属性返回函数在定义时,预期要传入的参数个数,也就是定义时()中的变量个数,也就是形参的个数

function f1() {}
f1.length //0

function f2(ac_a,ac_b,ac_c){}
f2.length //3

length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的”方法重载“(overload)

(3)arguments对象

由于JavaScript允许函数有不定数目的参数,所以需要一种机制,可以再函数体内部读取到所有的实际传入的参数(实参),这样arguments对象就诞生了。

arguments是一个运行时的对象,只有在函数运行的时候才会产生。它的数据方式访问类似数组的形式:arguments[0],第一个实参,依次类推;它也有一个length属性,记录实参个数

function arg(arg1,arg2){
  console.log('arguments.length:'+arguments.length)
  console.log(arguments[0])
  console.log(arguments[1])
  console.log(arguments[2])
}

arg(1) 
// arguments.length:1
// 1
// undefined
// undefined
arg(2,3,5)
// arguments.length:3
// 2
// 3
// 5

在正常模式下,arguments是可以被修改的,但是在严格模式下,arguments是只读的。

arguments这种像数组的对象,我们通常称为伪数组。

arguments对象还有个callee属性,返回它所对应的原函数

var f = function () {
  console.log(arguments.callee === f);
}

f() // true

4)递归的应用:斐波拉契

// 斐波那契数列
function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}

fib(6) // 8

三、函数的作用域

作用域(scope):变量存在的范围。在 ES5 的规范中,Javascript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。ES6 又新增了块级作用域。

函数外部声明的变量是全局变量,函数内部声明的变量是局部变量,函数内部可以都取全局变量。

(1)函数名的提升

JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错

f();
function f() {}

表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。

f();
var f = function (){};
// TypeError: undefined is not a function

上面的代码等同于下面的形式。

var f;
f();
f = function () {};

所以在实际调用函数的时候需要注意这一点。

(2)函数内部的变量提升

在全局作用域存在变量提升和函数提升,在函数作用域同样存在。var关键字声明的变量,不论在什么位置,变量声明都会被提升到函数体的头部。

function foo(x) {
  if (x > 100) {
    var tmp = x - 100;
  }
}

// 等同于
function foo(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  };
}

通常在实际工作中,我们推荐在代码的一开始集中定义变量,就是因为这个原因,让代码一目了然。

(3)函数本身的作用域

函数本身也是一个变量,也是一个值,它也有自己的作用域。它的作用域与变量一样,就在其声明时所在的作用域,与其运行时所在的作用域无关。

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1

上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。

很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。

var x = function () {
  console.log(a);
};

function y(f) {
  var a = 2;
  f();
}

y(x)
// ReferenceError: a is not defined

总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

我们在查找一个变量的时候,实际上就是在作用域中查找,一层一层的向上查找,这样就形成了作用域链

以上是一些JavaScript函数的一些基础知识,希望能对你有所帮助。欢迎关注同步微信公众号:前端小菜的进阶之路

公众号:Mr-Small_Teem

 

猜你喜欢

转载自blog.csdn.net/jingsi1991/article/details/83934124