函数

196 阅读8分钟

函数的定义/声明

方式一:函数声明/利用函数关键字自定义函数

使用function 关键字来创建一个函数 语法:

function 函数名([形参1,形参2...形参N]){  // 备注:语法中的中括号,表示“可选”
	语句...
}

举例:

function fun1(a, b){
	return a+b;
}

解释如下:

  • function:是一个关键字。中文是“函数”、“功能”。

  • 函数名字:命名规定和变量的命名规定一样。只能是字母、数字、下划线、美元符号,不能以数字开头。

  • 参数:可选。

  • 大括号里面,是这个函数的语句。

方式二:函数表达式(匿名函数)

使用函数表达式来创建一个函数。采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。

语法:

var 变量名  = function([形参1,形参2...形参N]){
	语句....
}

举例:

var fun2 = function() {
	console.log("我是匿名函数中封装的代码");
};

方式三:使用构造函数 new Function()

使用构造函数new Function()来创建一个对象。这种方式,用的少。

语法:

var 变量名/函数名  = new Function('形参1', '形参2', '函数体');

注意,Function 里面的参数都必须是字符串格式。也就是说,形参也必须放在字符串里;函数体也是放在字符串里包裹起来,放在 Function 的最后一个参数的位置。

代码举例:

var fun3 = new Function('a', 'b', 'console.log("我是函数内部的内容");  console.log(a + b);');

fun3(1, 2); // 调用函数

函数声明和函数表达式的区别

  1. 提升 采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。
    f();
    function f() {}
  1. 在循环语句中,使用函数表达式而不是函数声明,因为后者存在变量提升,在条件语句中声明函数,可能是无效的,容易出错。

类数组 arguments

在调用函数时,浏览器每次都会传递进两个隐含的参数:

  • 1.函数的上下文对象 this

  • 2.封装实参的对象 arguments

例如:

function foo() {
    console.log(arguments);
    console.log(typeof arguments);
}

foo();

arguments 是一个类数组对象,它可以通过索引来操作数据,也可以获取长度。

arguments 代表的是实参。在调用函数时,我们所传递的实参都会在 arguments 中保存。有个讲究的地方是:arguments只在函数中使用

1、返回函数实参的个数:arguments.length

arguments.length 可以用来获取实参的长度

举例:

fn(2, 4);
fn(2, 4, 6);
fn(2, 4, 6, 8);

function fn(a, b) {
    console.log(arguments);
    console.log(fn.length); //获取形参的个数
    console.log(arguments.length); //获取实参的个数

    console.log('----------------');
}

我们即使不定义形参,也可以通过 arguments 来使用实参(只不过比较麻烦):arguments[0] 表示第一个实参、arguments[1] 表示第二个实参...

2、返回正在执行的函数:arguments.callee

arguments 里边有一个属性叫做 callee,这个属性对应一个函数对象,就是当前正在指向的函数对象。

function fun() {
    console.log(arguments.callee == fun); //打印结果为true
}

fun('hello');

在使用函数递归调用时,推荐使用 arguments.callee 代替函数名本身。

3、arguments 可以修改元素

之所以说 arguments 是伪数组,是因为:arguments 可以修改元素,但不能改变数组的长短。举例:

fn(2, 4);
fn(2, 4, 6);
fn(2, 4, 6, 8);

function fn(a, b) {
    arguments[0] = 99; //将实参的第一个数改为99
    arguments.push(8); //此方法不通过,因为无法增加元素
}

形参和实参

  • 形参
    声明形参就相当于在函数内部声明了对应的变量,但是并不赋值。
  • 实参 调用函数时传递的参数,实参将会传递给函数中对应的形参。 函数名.length获取形参长度 arguments.length 实参长度
  • 如果实参的数量多余形参的数量,多余实参不会被赋值。
  • 如果实参的数量少于形参的数量,多余的形参会被定义为 undefined。表达式的运行结果为 NaN。

代码举例:

	function sum(a, b) {
		console.log(a + b);
        console.log(sum.length);
        console.log(arguments.length);
	}

	sum(1, 2, 3);

打印结果:

3
2
3

arguments 的使用

当我们不确定有多少个参数传递的时候,可以用 arguments 来获取。在 JavaScript 中,arguments 实际上是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象(只有函数才有 arguments 对象),arguments 对象中存储了传递的所有实参.

arguments的展示形式是一个伪数组。伪数组具有以下特点:

  • 可以进行遍历;具有数组的 length 属性。

  • 按索引方式存储数据。

  • 不具有数组的 push()、pop() 等方法。

代码举例:利用 arguments 求函数实参中的最大值

代码实现:

	function getMaxValue() {
		var max = arguments[0];
		// 通过 arguments 遍历实参
		for (var i = 0; i < arguments.length; i++) {
			if (max < arguments[i]) {
				max = arguments[i];
			}
		}
		return max;
	}

	console.log(getMaxValue(1, 3, 7, 5));

伪数组转数组

  1. Array.prototype.slice.call(arrayLike); ES5 function list() { return Array.prototype.slice.call(arguments); }

var list1 = list(1, 2, 3); // [1, 2, 3]

也可以使用 `[].slice.call(arguments)` 来代替。 2. Array.from ES6 let arr = Array.from(arrayLike)
  1. 解构赋值 function args(){ console.log(arguments);//Arguments(7) [1, 2, 3, 23, 2, 42, 34, callee: ƒ, Symbol(Symbol.iterator): ƒ] let newArr = [...arguments]; console.log(newArr);//(7) [1, 2, 3, 23, 2, 42, 34] } args(1,2,3,23,2,42,34);

闭包

闭包就是能够读取其他函数内部变量的函数。可以简单理解成“定义在一个函数内部的函数”。

本质上,闭包是将函数内部和函数外部连接起来的桥梁。闭包的形成原因是外层函数调用后,作用域内的变量无法释放,被内层函数引用着,无法自动释放内存,闭包比普通函数占更多的内存。

  • 作用 1· 可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中
function foo() {
    var a = 2
    function bar() {
        console.log(a)
    }
    return bar
}

var baz = foo()
baz()           // 2
这个bar函数就是一个闭包,它可以访问foo词法作用域里的a,而且因为bar()还在使用,所以foo()的整个内部作用域不会被销毁。

再比如
function closure2(){
    let num = 2;
    return function(){
        let n = 0;
        console.log(n++);
        console.log(num++);
    }
}

let fn2 = closure2();
fn2();  // 0 2
fn2();  // 0 3

执行两次函数实例fn2(),可以看到结果是略有差异的:
n++两次输出一致:
变量n是匿名函数的内部变量,在匿名函数调用结束后,它这块内存空间就会被正常释放,即被垃圾回收机制回收。
num++两次输出不一致:
匿名函数内引用了其外层函数的局部变量num,即使匿名函数的调用结束了,但是这种依赖关系依然存在,所以变量num就无法被销毁。一直保存在内存中,匿名函数下次调用时,就会继续沿用上次的调用结果。

2· 封装对象的私有属性和私有方法。

function f1(n) {
  return function () {
    return n++;
  };
}
var a1 = f1(1);
a1() // 1
a1() // 2
a1() // 3
var a2 = f1(5);
a2() // 5
a2() // 6
a2() // 7
//这段代码中,a1 和 a2 是相互独立的,各自返回自己的私有变量。
  • 谨慎使用闭包 · 注意,外层函数调用后,作用域内的变量无法释放,被内层函数引用着,无法自动释放内存,闭包比普通函数占更多的内存。因此不能滥用闭包,否则会造成网页的性能问题。

立即执行函数IIFE

匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。 (function () { var tmp = newData; processData(tmp); storeData(tmp); }());

闭包+立即执行函数

  1. 实现循环定时器 for (var i = 1; i <= 5; i++) { (function(i){ setTimeout( function timer() { console.log(i); }, 1000 ); })(i); } // 1 2 3 4 5
  2. 函数作为API的形参传入 希望你能编写一个mySort()方法:可以按照指定的任意属性值降序排列数组元素。
var arr = [{
    name:"code",age:19,grade:98
},{
    name:"zevin",age:12,grade:94
},{
    name:"j",age:15,grade:91
}];

function mySort(arr,property){ arr.sort((function(prop){ return function(a,b){ return b-a;//降序排列b-a } })(property)); };

测试一下:比如我们想让数组按照成绩降序排列。 mySort(arr,"grade"); console.log(arr); ——————OUTPUT—————— [ { name: 'code', age: 19, grade: 98 }, { name: 'zevin', age: 12, grade: 94 }, { name: 'j', age: 15, grade: 91 } ]

call apply bind区别

call正常调用,指定this,传入参数列表; apply作用和 call 一样,只是在调用的时候,传参数的方式不同。apply 接受的是数组参数,call 接受的是连续参数 比如

var name = 'p'
function fn(a,b){
		console.log(this.name) 
		a>b?console.log(`${a}>${b}`):console.log(`${a}<${b}`) 

}


var bar = {
	name: 'K'
}

var bu = {
	name: 'l'
}
fn.call(bar, 1,2)//
fn.apply(bu, [9,3])


bind

箭头函数

var x = function (xa){ return a; };

可以改写为 var x = a => a;

  • 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分 var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };

  • return多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

简化回调函数 // 正常函数写法 [1,2,3].map(function (x) { return x * x; });

// 箭头函数写法 [1,2,3].map(x => x * x);

与rest参数结合 const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5) // [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5) // [1,[2,3,4,5]]

注意

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

不应该使用箭头函数de场合

第一个场合是定义对象的方法,且该方法内部包括this。

const cat = { lives: 9, jumps: () => { this.lives--; } }

需要动态this的时候,也不应使用箭头函数。 var button = document.getElementById('press'); button.addEventListener('click', () => { this.classList.toggle('on'); }); 上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。