函数用于封装一段具有特定功能代码,通过调用的形式执行。

  • 每个函数都有返回值,无 return 语句时返回 undefined

声明函数

  • 在浏览器中,在最外层声明的函数,都是 Window 对象的方法
  • 函数的声明会被提前:可以在函数声明之前,调用函数,因为声明的函数会在所有代码执行之前被创建。(与变量提升相同)
function sum(a, b) {
  return a + b;
}
  • 关键字 function
  • 函数名 sum (命名规则与变量名相同,通常以动词开头)
  • 形式参数 a 和 b ,用 , 分隔
  • 形式参数即形式上传递的参数,因为它们只是函数调用时,传入的实际参数的替身
  • 声明形参相当于在函数内部声明了对应的变量,但是并不赋值
  • 形参的长度可通过 函数名.length 获取
  • 返回值 a + b 的计算结果

函数的参数根据功能需要定义,也可以没有参数

function sayHi(){
  alert('Hi')
}

如果函数的参数超过 3 个,则建议将它们以对象的形式传入,好处如下:

  • 不用考虑参数的顺序
  • 更易扩展
let params = {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
};

function sum(params) {
  return params.a + params.b + params.c + params.d;
}

用解构赋值的写法更简洁

function sum({ a, b, c, d }) {
  return a + b + c + d;
}

给函数添加注释

/**
 * 功能:给定元素查找他的第一个元素子节点,并返回
 * @param 参数 ele -- DOM 元素
 * @returns 返回值 node -- DOM节点
 */
function getFirstNode(ele){
    let node = ele.firstElementChild || ele.firstChild;
    return node;
}

指定形式参数的默认值

// 给形式参数 b 指定了默认值 1
function sum(a, b = 1) {
  return a + b;
}
  • 指定默认值的参数,要放在无默认值参数的后面
  • 调用函数时,若传入了对应的实际参数,则使用实际参数,若没有传入实际参数,则使用形式参数的默认值
  • 参数的默认值可以是包含其他参数的表达式
function sum(a = 1, b = a + 1) {
  return a + b;
}
  • 默认参数有独立的作用域,介于函数外作用域和函数内作用域之间。
let x = 1;
function fn(x, y = x) {
  // 在参数作用域内,在函数调用时,传入的实际参数将 x 赋值为 2,和外部的 x 无关
  console.log(x, y);
}
fn(2); // 打印 2 2
let x = 1;
function fn(z, y = x) {
  // 在参数作用域内,未找到 x 的定义,只能从外层找到 x = 1
  console.log(z, y);
}
fn(2); // 打印 2 1

剩余参数 …

ES6 新增语法

用于便捷获取和使用调用函数时传入的超出声明时形式参数数量的实际参数。

function demo(a, ...moreParams) {
  console.log(moreParams); // 打印数组 ["2","3"]
}
demo("1", "2", "3");
  • ... 为前缀的形式参数即剩余参数
  • 剩余参数必须写在所有形式参数的末尾( 所以每个函数也只能声明一个剩余参数 )
  • 剩余参数的类型是数组,可使用所有的数组 API 来处理剩余参数。

匿名函数

没有函数名的函数,即匿名函数,通常在函数表达式和立即执行函数中使用。

高阶函数

将函数作为参数的函数称为高阶函数,如 setTimeout 和 setInterval 等。

setTimeout(function () {
    console.log('1秒后,执行setTimeout的回调函数');
},10000)

工厂函数

返回一个对象的函数即工厂函数

  • 优点:解决了创建多个相同属性对象的问题
  • 缺点:无法判断对象的类型,都是object
function fac(name){
	return{
		name:name,
		sayName:function(){
			console.log(this.name)
		}
	}
}
let person1 = fac('朝阳')
let person2 = fac('朝阳')
console.log(person1 === person2 )	//false,创建的对象互不影响

构造函数

用于创建对象,必须使用 new 调用!

  • 声明方式与普通函数类似,函数名通常为一个名词,且首字母大写
  • 构造函数中的 this 指向新创建的对象
// 构造函数 Person
function Person(name) {
  this.name = name;
}

// 通过构造函数 Person 创建对象 
new Person("朝阳")

new 运算符创建对象的过程

  1. 在堆内存中开辟空间,存储新创建的对象
  2. 将新创建的对象赋值给构造函数中的 this
  3. 执行构造函数中的代码(主要是设置对象的属性和方法)
  4. 将新创建的对象作为返回值返回
// 函数中注释的内容是伪代码,表示使用 new 关键字时,JS 背后帮我们做的事情。
function Person(name) {
    // this = {};
    // this.__proto__ = Person.prototype;
 
    this.name = name;
    
    // return this;
}

对任何函数都能使用 new 运算符,但只有对构造函数使用才有实际意义,其他函数使用大多没有影响,但也可能出现不易理解的结果。

调用函数

调用函数,即触发函数内代码的执行!

函数名后加 () ,并在() 内根据函数的声明,传入必要的参数,就能调用函数。

  • 调用函数时,除了传入形式参数列表中列出的参数外,还有两个隐含参数(未在形式参数列表中列出,也未在函数内声明,但可以直接在函数内使用的变量)this 和 arguments (下文有详解)
function sum(a, b) {
  return a + b;
}

// 调用函数,传入实际参数 1 和 2
let result = sum(1, 2);

console.log(result); // 打印 3

调用函数时传入的实际参数的顺序,与函数声明时形式参数的顺序一一对应!

上例中,实际参数 1 会作为形式参数 a 的值传入函数,实际参数 2 会作为形式参数 b 的值传入函数,即在此次函数调用过程中,a 的值为1,b 的值为 2。

之后再次调用函数时,又可以传入其他参数,如

sum(4,5) // 返回结果 9

此次函数调用过程中,a 的值为4,b 的值为 5。

所以函数就像一个破壁机一样,随传入参数的不同,得到的结果也不同,但执行的功能没有变化。(破壁机执行的功能是搅碎并混合均匀,投入黄豆和水得到豆浆,投入西瓜得到西瓜汁)

隐含参数 this

浏览器中,最外层的 普通函数 的 this 是 window 对象

function Person(name) {
  console.log(this); // 浏览器中,控制台打印  window 对象
  this.name = name;
}
Person("朝阳");

console.log(window.name); // 浏览器中,控制台打印  "朝阳"

可以这样理解:浏览器中,最外层的 普通函数 声明之后,其实是window 对象的方法,在 window 对象的方法内,this 自然指向 window 对象

此范例中的函数,若作为构造函数创建对象,则 this 将指向新对象

function Person(name) {
  // 作为构造函数创建新对象时,this 指向新对象
  console.log(this); // 控制台打印  Person 对象
  this.name = name;
}

let me = new Person("朝阳");
console.log(me.name); // 控制台打印  "朝阳"

隐含参数 arguments

  • 调用函数时,传递的所有实参(实际传递的参数)都存在 arguments中。
  • arguments 是一个对象,严格讲是类数组,不是真正的数组,很多特性和数组很像,比如包含了索引元素和 length属性,但不支持 sort()、slice() 等数组方法。
  • 不定义形参,也可以通过arguments 来使用实参(只不过比较麻烦):arguments[0] 表示第一个实参、arguments[1] 表示第二个实参…
// 函数--任意数量参数的求和
function sum() {
 var sum = 0;
 for(var i = 0; i < arguments.length; i++){
  sum += arguments[i];
 }
 return sum;
}

将类数组 arguments 转换数组

function f(){
    var args = [].slice.call(arguments);
    return args.reverse();
}

f(1,2,3,4); // 得到 [4,3,2,1]

函数表达式

函数的声明本身就是一个表达式,即函数声明表达式,所以可以赋值给一个变量。

let sum_func = function sum(a, b) {
  return a + b;
};

此时,变量 sum_func 内存储的是函数在堆内存中的地址。

这种方式创建的函数的函数名已无任何作用(无法用于调用函数),所以通常无需定义函数名

let sum_func = function (a, b) {
  return a + b;
};

立即执行函数

声明函数后,立即被调用执行的函数即立即执行函数(又名即时函数)

(function (a, b) {
  console.log(a + b);
})(1, 2); // 控制台打印 3

箭头函数 =>

ES6 新增语法

  • 简化了匿名函数的写法
  • 箭头函数中的 this 继承外层的 this (使用 call , bind , apply 都无法改变 this 的指向)
// 无参数
let a = () => {
  console.log(1);
};

// 一个参数
let b = (i) => {
  console.log(i);
};

// 多个参数
let c = (i, j) => {
  return i + j;
};

若函数内仅 return 语句,则可省略 { retrun },简写为

let c = (i, j) => i + j;

内置函数

内置函数即 JavaScript 默认已封装好的全局函数,都可以当作 window 对象的方法来调用

延时调用定时器 setTimeout ()

循环调用定时器 setInterval()

  • 参数1:回调函数,该函数会每隔一段时间被调用一次。
  • 参数2:每次调用的间隔时间,单位是毫秒。
let num = 1;
setInterval(function () {
    // 每间隔一秒,数字加1
    num ++;
    console.log(num);
}, 1000);

返回一个Number类型的数字,它是定时器的唯一标识,可用于清除定时器。

let num = 1;

    const timer = setInterval(function () {
        num ++;
        if(num === 5) {
            // 清除定时器
            clearInterval(timer);
        }
    }, 1000);

编码 encodeURI()

url 中的一些特殊字符(如空格)需要通过 encodeURI 编码后才方便使用。

let url = 'http:**?q=this and that';
// 将空格转换为了 %20
encodeURI(url); // 得到 "http:**?q=this%20and%20that"

反编码 decodeURI()

将被 encodeURI 编码的字符串恢复原样。

// %20 变回空格
decodeURI("http:**?q=this%20and%20that") // 得到 'http:**?q=this and that'

回调函数 callback

若函数 A 会被函数 B 在未来的某个合适的时间调用,则函数 A 就是函数 B 的回调函数,通常被作为函数的参数传入函数内。

function A (value) {
    alert(value);
}
function B (someFunction, value) {
    someFunction(value);
}

// 此时的函数 A 就是函数 B 的回调函数
B(A, 'hi');

回调函数也可以是一个匿名函数,比如 setTimeout 和 setInterval 的第一个参数。

setTimeout(function () {
    console.log('1秒后,执行setTimeout的回调函数');
},10000)

私有函数

在函数内声明的函数,用于限制该函数只能在函数内部被调用,也有利于减少命名冲突。

function sum(a, b) {
  // 私有函数 add
  function add(value) {
    return ++value;
  }
  return a + add(b);
}

let result = sum(1, 2); // 结果为 4

// 报错 add is not defined
add(6);

模拟函数重载

函数重载:定义几个函数名相同,但参数个数或类型不同的函数,在调用时传入不同的参数,编译器会自动调用适合的函数。

在JavaScript中,同一个作用域,出现两个名字一样的函数,后面的会覆盖前面的,所以 JavaScript 没有真正意义的重载,但可以模拟实现:

function overload () {
  if (arguments.length === 1) {
    console.log('一个参数')
  }
  if (arguments.length === 2) {
    console.log('两个参数')
  }
}

overload(1);      //一个参数
overload(1, 2);  //两个参数