另一个值得注意的是,JavaScript没有”函数签名(function signature)”的概念,你可以传入零个或者多个参数去调用函数,而不用担心会报错;如果函数需要的参数你没传入,那么参数值默认就是undefined 。如果你想知道传入的参数是什么?你可以通过 arguments 对象来查看。示例如下:
函数没有明确要求一定得返回什么,如果一个函数没有明确返回的对象,那么默认返回 undefined. 谨记:
函数永远会返回一个值 —— 如果没明确提供返回的值,那么返回undefined(构造函数除外,它一定会返回新对象)
变量作用域决定了变量的可访问性(可见性) 在JavaScript中,有两种作用域—全局(global)和本地(local 主要是只函数内的 )
当使用var声明一个变量时,它会自动添加到最直接的可用范围中。在函数中,最直接的可用范围是函数的上下文环境。由于局部变量只能在函数内部识别,所以同名的变量可以用于不同的函数中
Javascript的一个特殊性是它没有块级范围。块级范围意味着如果一个变量在一个块内声明(for、while、if等),那么它只能在块内访问,不能在块外访问。例如,你可能期望这段代码能正常工作:
因为第一个 for 的变量 i 每次都会在第二个for中重新赋值,这个函数将在第一行之后退出。 为了解决这个问题,在最新版本的Javascript (ECMAScript 6)中引入了关键字let。在块内部使用let声明的变量将具有块级范围。所以简单地改变var,上面的例子就能按照你的期望执行
提升是Javascript中的一种默认行为,它表示在代码执行之前将所有变量的声明移到其作用域的顶部。所以在函数中声明一个变量并不重要,变量从执行上下文开始就属于那个范围。但是,提升只针对变量声明,而不是变量赋值。所以在到达变量被显式赋值的那一行之前,变量的值是undefined
因为变量提升,所以上面的代码相当于下面这段代码:
这适用于任何变量和函数表达式,但是,函数表达式和函数声明之间有一些细微的区别。提升会把声明移动到当前作用域最顶端,但是不包括赋值(初始化),在赋值(初始化)之前,变量的值是undefined
在继续之前,我有一件事要坦白。文章开头引人注目的引语是不完整的。我漏掉了一部分。让我们看看完整版:
区别在于浏览器如何将它们加载到执行上下文中。函数表达式的加载方式与其他变量完全相同。当编译器到达这行代码时,它们被赋值(在本例中是函数对象)
但是,函数声明在执行任何代码之前加载。因此,即使函数在调用它的代码行下面声明,它也能工作。谨记:
当你调用一个函数,哪怕这个函数在你调用的代码后面声明,它依然能正确执行(因为它在编译之前会被提升到当前作用域前面)
为什么函数有这两种定义?
问的好,函数声明是在ECMAScript 3中引入的,它让我们记住Javascript是浏览器的语言,而用户很少更新浏览器。很多时候,浏览器的新版本只有在用户更新操作系统时才会接触到他们。所以为了考虑兼容性,就有了这两种定义方式
如前所述,从数据类型的角度来看,每个函数都是一个对象。函数对象有三个非常重要的属性来表示函数的身份: this 、arguments 和 prototype
当从全局作用域简单地调用一个函数时,this 显然会指向全局作用域本身。这是造成混乱的一个重要原因,这就是为什么在严格模式下运行时,值是未定义的。通常,您不会对这样的函数使用 this , This 对于用作方法的函数非常有用。这样,该方法就可以访问对象属性
“This”在函数用作构造函数时也有重要作用。构造函数没有任何特殊的结构。当使用运算符new调用它时,它是一个构造函数。该操作符在底层创建一个新对象,并通过’ This ‘将其传递给函数
如前所述,声明中提供的参数不是可靠的信息来源。可以使用不同数量的参数调用该函数。如果它们太多,则忽略额外的参数。如果它们太少,丢失的值将是未定义的。
幸运的是,函数的arguments属性在这里可以帮助我们。它是一个类似数组的对象,包含调用时提供的参数。注意,它并不是一个真的数组,因为它不是从数组继承的,它唯一能调用的就是获取数组的长度,参数的存储方式与数组完全一样,索引从0开始
虽然arguments对象不是只读的,但是强烈建议不要修改它。更改arguments对象也会影响已命名的参数,这很容易引起混淆。
它的一个常见用法是创建具有未定义数量的参数的函数。例如,一个函数返回它的参数和:
函数的原型对象只有在函数用作构造函数时才有意义。它在 Javascript 中创建继承时起着关键作用。通过它,使用相同构造函数创建的对象可以共享相同的属性和行为