函数的定义/声明
方式一:函数声明/利用函数关键字自定义函数
使用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); // 调用函数
函数声明和函数表达式的区别
- 提升 采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。
f();
function f() {}
- 在循环语句中,使用函数表达式而不是函数声明,因为后者存在变量提升,在条件语句中声明函数,可能是无效的,容易出错。
类数组 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));
伪数组转数组
- 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)- 解构赋值 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); }());
闭包+立即执行函数
- 实现循环定时器 for (var i = 1; i <= 5; i++) { (function(i){ setTimeout( function timer() { console.log(i); }, 1000 ); })(i); } // 1 2 3 4 5
- 函数作为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就会动态指向被点击的按钮对象。