JS进阶:函数

学习自:JS忍者秘籍

推荐Mozilla的HTML5指南,可通过https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5查看。

Mozilla也为DOM提供了一份报告,可以通过https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model进行查看。

Web应用的生命周期

页面构建

每当解析到脚本元素时,浏览器就会停止从HTML构建DOM,并开始执行JavaScript代码。JavaScript代码由浏览器的JavaScript引擎执行,例如,Firefox的Spidermonkey引擎,Chrome和Opera和V8引擎和Edge的(IE的)Chakra引擎。

浏览器暴露给JavaScript引擎的主要全局对象是window对象,它代表了包含着一个页面的窗口。全局window对象会存在于整个页面的生存期之间,在它上面存储着所有的JavaScript变量。所以所有在某个JavaScript代码执行期间用户创建的全局变量都能正常地被其他脚本元素中的JavaScript代码所访问到。

下面两个步骤都会一直交替执行。
1.将HTML构建为DOM。
2.执行JavaScript代码。

最后,当浏览器处理完所有HTML元素后,页面构建阶段就结束了。随后浏览器就会进入应用生命周期的第二部分:事件处理。

事件处理

浏览器执行环境的核心思想基于:同一时刻只能执行一个代码片段,即所谓的单线程执行模型。所以,浏览器需要一种方式来跟踪已经发生但尚未处理的事件。为实现这个目标,浏览器使用了事件队列

事件是异步的,如下类型的事件会在其他类型事件中发生。
● 浏览器事件,例如当页面加载完成后或无法加载时;
● 网络事件,例如来自服务器的响应(Ajax事件和服务器端事件);
● 用户事件,例如鼠标单击、鼠标移动和键盘事件;
● 计时器事件,当timeout时间到期或又触发了一次时间间隔。

事件处理器是当某个特定事件发生后我们希望执行的函数。有两种方式注册事件。

● 通过把函数赋给某个特殊属性;
window.onload = function( ){};通过这种方式,事件处理器就会注册到load事件上(当DOM已经就绪并全部构建完成,就会触发这个事件)。
这种做法会带来缺点:对于某个事件只能注册一个事件处理器。也就是说,一不小心就会将上一个事件处理器改写掉。

● 通过使用内置addEventListener方法。让我们能够注册尽可能多的事件

函数

JavaScript中最关键的概念是:函数是第一类对象(first-class objects),或者说它们被称作一等公民(first-class citizens)。JavaScript中函数拥有对象的所有能力,也因此函数可被作为任意其他类型对象来对待。

如具有动态创建和分配的属性。

回调函数

第一类对象的特点之一是,它能够作为参数传入函数。对于函数而言,这项特性也表明:如果我们将某个函数作为参数传入另一个函数,传入函数会在应用程序执行的未来某个时间点才执行。大家所知道的更一般的概念是回调函数(callback function)。

更通俗的理解是被其他函数在稍后的某个合适时间点“再回来调用”。

1
2
3
var values = [0, 3, 2, 5, 7, 4, 8, 1];
values.sort(function(value1, value2) {
return value1- value2; });

匿名函数的好处:当一个函数不会在代码的多处位置被调用时,该特性可以避免用非必须的名字污染全局命名空间。

函数声明和函数表达式

最后4个表达式都是立即调用函数。首先创建了一个函数,然后立即调用这个新创建的函数。这种函数叫作立即调用函数表达式(IIFE)

函数声明和函数表达式除了在代码中的位置不同以外,还有一个更重要的不同点是:对于函数声明来说,函数名是强制性的,而对于函数表达式来说,函数名则完全是可选的。
函数声明必须具有函数名是因为它们是独立语句。一个函数的基本要求是它应该能够被调用,所以它必须具有一种被引用方式,于是唯一的方式就是通过它的名字。

箭头函数

箭头函数是JavaScript标准中的ES6新增项

形参和实参

如果实参的数量大于形参,那么额外的实参不会赋值给任何形参。反之,如果形参的数量大于实参,那么那些没有对应实参的形参则会被设为undefined。

剩余参数和默认参数

都已被加入ES6标准。

为函数的最后一个命名参数前加上省略号(…)前缀,这个参数就变成了一个叫作剩余参数的数组,数组内包含着传入的剩余的参数。

只有函数的最后一个参数才能是剩余参数。试图把省略号放在不是最后一个形参的任意形参之前都会报错,错误以SyntaxError: parameter after rest parameter的形式展现。

JavaScript不支持函数重载,对于参数不足的情况,可以用创建默认参数的方式为函数的形参赋值。

可以为默认参数赋任何值,它既可以是数字或者字符串这样的原始类型,也可以是对象、数组,甚至函数这样的复杂类型。每次函数调用时都会从左到右求得参数的值,并且当对后面的默认参数赋值时可以引用前面的默认参数,但是会降低可读性。

适当地使用默认参数——避免空值,或作为配置函数的简单标记能够带来简洁优雅的代码。

隐式函数参数this和arguments

参数this表示被调用函数的上下文对象,而arguments参数表示函数调用过程中传递的所有参数。两者会被静默地传递给函数,并且可以像函数体内显式声明的参数一样被正常访问。

arguments对象有一个名为length的属性,表示实参的确切个数。通过数组索引的方式可以获取单个参数的值。但它并非JavaScript数组,如果你尝试在arguments对象上使用数组的方法,会发现最终会报错。arguments对象仅是一个类数组的结构。而剩余参数是真正的Array实例

arguments对象可作为函数参数的别名。如果为arguments[0]赋一个新值,那么,同时也会改变第一个参数的值。将arguments对象作为函数参数的别名使用时会影响代码的可读性,因此在JavaScript提供的严格模式(strict mode)中将无法再使用它。

this参数的指向不仅是由定义函数的方式和位置决定的,同时还严重受到函数调用方式的影响。

函数的调用方式影响this的取值。

  • 如果作为函数调用,在非严格模式下,this指向全局window对象;在严格模式下,this指向undefined。
  • 作为方法调用,this通常指向调用的对象。
  • 作为构造函数调用,this指向新创建的对象。
  • 通过call或apply调用,this指向call或apply的第一个参数。
    ● 箭头函数没有单独的this值,this在箭头函数创建时确定。
    ● 所有函数均可使用bind方法,创建新函数,并绑定到bind方法传入的参数上。被绑定的函数与原始函数具有一致的行为。

可以通过4种方式调用一个函数:

● 作为一个函数(function)直接被调用。当以这种方式调用时,函数上下文(this关键字的值)有两种可能性:在非严格模式下,它将是全局上下文(window对象),而在严格模式下,它将是undefined。

● 作为一个方法(method)关联在一个对象上,实现面向对象编程。当函数作为某个对象的方法被调用时,该对象会成为函数的上下文,并且在函数内部可以通过参数访问到。这样你就可以通过this在任何方法中引用该方法的“宿主”对象

通过this返回的函数上下文取决于whatsMyContext的调用方式。

● 作为一个构造函数(constructor)new实例化一个新的对象。

不要把函数的构造器和构造函数混为一谈。new Function('a', 'b', 'return a+b')函数的构造器将动态创建的字符串创建为函数。构造函数是我们用来创建和初始化对象实例的函数。

使用关键字new调用函数会触发以下几个动作。
1.创建一个新的空对象。
2.该对象作为this参数传递给构造函数,从而成为构造函数的函数上下文。
3.新构造的对象作为new运算符的返回值。

当构造函数自身有返回值时会是什么结果?
● 如果构造函数返回一个对象,则该对象将作为整个表达式的值返回,而传入构造函数的this将被丢弃。
● 但是,如果构造函数返回的是非对象类型,则忽略返回值,返回新创建的对象。

解决函数上下问题

● 通过函数的apply或者call方法

浏览器的事件处理系统将把调用的上下文定义为事件触发的目标元素,因此常会产生一些问题。

使用apply和call方法可以显式地指定任何对象作为函数的上下文。

使用apply方法调用函数,需要为其传递两个参数:作为函数上下文的对象和一个数组作为函数调用的参数。call方法的使用方式类似,不同点在于是直接以参数列表的形式,而不再是作为数组传递。apply和call之间唯一的不同之处在于如何传递参数如果有一组无关的值,则直接使用call方法。若已有参数是数组类型,apply方法是更佳选择。

在执行回调函数时可能会经常用到。

forEach遍历函数将每个元素传给回调函数,将当前元素作为回调函数的上下文。


使用call方法调用回调函数,将当前遍历到的元素作为第一个参数,循环索引作为第二个参数,使得当前元素作为函数上下文,循环索引作为回调函数的参数。

使用箭头函数绕过函数上下文。箭头函数没有单独的this值。箭头函数的this与声明所在的上下文的相同。

箭头函数在构造函数内部,this指向新创建的对象本身。在全局代码中定义对象字面量,在字面量中定义箭头函数,那么箭头函数内的this指向全局window对象

函数还可访问bind方法创建新函数。无论使用哪种方法调用,bind方法创建的新函数与原始函数的函数体相同,新函数被绑定到指定的对象上。

所有函数均可访问bind方法,可以创建并返回一个新函数,并绑定在传入的对象上