函数
1.函数介绍
函数允许我们封装一系列代码来完成特定的任务。当想要完成某一任务时,只需要调用相应的代码即可。
函数的作用:
功能的封装,直接调用,代码复用率提高
构造对象的模板(构造函数)
函数实际上是对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法,由于函数都是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定
2.函数声明
自定义函数:
1.由function关键字声明 函数声明
function 函数名(形参裂变){
//函数体
}
2.将一个匿名函数(没有函数名的函数)赋值给一个函数变量 函数表达式
在调用之前必须先给它赋值
var 函数名=function(形参列表){
//函数体
}
函数声明与var变量声明类似,会进行提升
// function foo(){}
//函数声明提升到代码的最前边,可以直接调用函数
foo();
function foo(){
console.log("我是一个函数");
}
foo();
//变量声明提升 变量声明提升到代码的前边,函数声明之后正常代码之前
console.log(a); //undefined 这里不报错,因为后边有var a的声明。变量的声明进行提升到前边
var a = 'hello';
console.log(a); //'hello'
3.函数内部属性
只有在函数内部才能访问的属性,this也可以在函数外部进行使用
arguments
ECMAScript函数不介意传递参数的个数以及参数类型, arguments就是一个类数组对象,包含着传入函数中的所有参数。
// arguments 主要用途是保存函数参数
function foo(a,b) {
console.log(a,b);//1 2
// arguments
console.log(arguments,typeof arguments);//[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 } object
console.log(arguments[2]);//3
console.log(arguments.length);//5
console.log(foo.length);//2
}
foo(1,2,3,4,5)
callee是arguments对象的一个属性,是一个指针,指向拥有这个arguments对象的函数,callee属性的初始值就是正在被执行的Function对象
// 通过arguments中的callee属性去实现递归
var sum = function(n){
if(n==1)
{
return 1;
}
return n+arguments.callee(n-1);
//等同于 return n+sum(n-1);
}
console.log(sum(8));//36
this
面向对象语言中this表示当前对象的一个引用,在JavaScript中this不是固定不变的,它会随着执行环境的改变而改变
//实例
var person = {
firstName: "LeBron",
lastName : "James",
id : 8888,
fullName : function() {
return this.firstName + " " + this.lastName;
}
};
- 在方法中,this表示该方法所属的对象
this指向上面实例中的person对象,fullName方法所属的对象就是person
fullName : function() {
return this.firstName + " " + this.lastName;
}
-
单独使用this
如果单独使用,this表示全局对象
浏览器中指向window的全局对象为[object Window];
在node中指向一个{}
var x=this; console.log(x);
-
在函数中使用this(默认)
函数的所属者默认绑定到this上
在浏览器中指向window的全局对象[object Window]
在 node中指向global对象
function foo(){ return this;}console.log(foo());
-
在事件的this
表示接收事件的元素
<button onclick="this.style.display='none'"> 点我后我就消失了 </button>
- 显示函数绑定
在JavaScript中函数也是对象,对象则有方法,apply和call就是函数对象的方法。
apply和call两种方法允许切换函数执行的上下文环境(context),即this绑定的对象。
var person1 = { fullName: function () { return this.firstName + " " + this.lastName; } } var person2 = { firstName: "Zach", lastName: "Muyi", } //call将函数执行环境切换到person1 var name = person1.fullName.call(person2); // 返回 "Zach Muyi" console.log(name);
4.IIFE 立即函数
立即调用的函数表达式,声明函数的同时立即调用这个函数。
**作用:**页面加载完成后只执行一次的设置函数
将设置函数中的变量包裹在局部作用域中,不会泄露成全局变量
函数的声明和IIFE立即执行函数的区别:
//函数的声明function foo(){ var a = 10; console.log(a);}foo();//立即执行函数 IIFE(function foo(){ var a = 10; console.log(a);})();
函数的声明是以function关键字开头的,立即执行函数是使用一对括号将函数的声明括起来,两者达到的目的是相同的,都是声明另一个函数foo并且随后调用函数foo。
注意:如果立即执行函数前出现小括号 记得用; 分隔开
可以不命名,匿名立即执行函数
IIFE立即执行函数的多种写法:
1.对返回结果不进行处理
(function(形参){ 函数体内容})(实参);
2.对返回结果不进行处理
(function(形参){ 函数体内容}(实参));
3.返回的是一个布尔值 ,然后进行取反
var bools=!function(){ return true}()console.log(bools);//false
4.对于数字返回的是原来的结果,非数字返回NaN
+function(形参){ 函数体内容}(实参)
5.对于数字返回的是正负符号相反,非数字返回NaN
-function(形参){ 函数体内容}(实参)
6.对于数字返回的是正负符号相反再减1,非数字返回NaN
~function(形参){ 函数体内容}(实参)
7.返回的结果是undefined
var a=void function(){ return 123}()console.log(a);//undefined
IIFE的基本使用
// 就像其它任何函数一样,一个立即执行函数也能返回值并且可以赋值给其它变量。var sum = (function (a,b) { return a + b;}(1,2))console.log(sum);
案列-经典面试题-IIFE
for (var i = 0; i < 6; i++) { function output() { console.log(i); // 为什么输出的是6,而不是0,1,2,3,4,5 // 因为输出的 i 是全局作用域的,当循环结束后 i 的值是 6,所以输出的 i 就是6。 }}output()for (var i = 0; i < 6; i++) { (function (j) { console.log(j); //0,1,2,3,4,5 })(i) // 因为 JS 中调用函数传递参数都是值传递 ,所以当立即执行函数执行时,首先会把参数 i 的值复制一份,然后再创建函数作用域来执行函数,循环5次就会创建5个作用域,所以每个输出访问的都是不同作用域的 i 的值 。}
IIFE所带来的好处:
弥补了js(es5)在作用域方面的缺陷:js只有全局作用域,函数作用域,从es6开始才有块级作用域
对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。
5.作用域
ES5:
函数作用域:在JavaScrip函数中声明的变量,会成为函数的局部变量
函数内部声明的变量,在函数外部不能访问
全局作用域:函数之外声明的变量,会成为全局变量
函数外部声明的变量,在函数内部可以访问
当函数嵌套,在这个时候内部函数与外部函数的这个变量就组成了闭包
// 块级作用域{ var a =10; }console.log(a);// 全局作用域 在函数外部定义的变量 函数内部是可以访问到的//var v1=3;//v2=2;// 函数作用域function foo() { var v1=123 var a=123; console.log(a); console.log(v1); console.log(v2); }foo();// 定义在函数后面 不能使用var v1=3;v2=2;// console.log(a);// is not defined 函数定义的变量在外部是访问不到的
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突
作用域链
- 什么是自由变量
当前作用域没有定义的变量,这就成为自由变量。
获取自由变量的值需要到创建这个函数的那个父级作用域找,如果没有就一直向上级祖先元素寻找(所谓的静态作用域)
var a = 100function fn() { var b = 200 console.log(a) // 这里的a在这里就是一个自由变量 // 100 console.log(b)}fn()
- 什么是作用域链
从父级、一层层上级祖先元素、再到全局作用域,这样一层一层的关系就是作用域链
var a = 100function F1() { var b = 200 function F2() { var c = 300 console.log(a) // 自由变量,顺作用域链向父作用域找 //100 console.log(b) // 自由变量,顺作用域链向父作用域找 //200 console.log(c) // 本作用域的变量 //300 } F2()}F1()
6.函数调用
函数声明好之后并不会直接运行,需要进行调用才能运行
调用函数的方式不仅限于()执行:
- 函数名(实参列表)
- 函数名.call(执行环境对象,实参列表数组)
- 函数名.apply(执行环境对象,实参列表数组)
- 函数名.bind(执行环境对象,实参列表)
当我们不得不把一个对象保存在一个变量中,且需要调用相应的函数的属性时,this该如何指向函数
改变函数内部this指针的指向方法:
- call(执行环境对象,实参列表)
调用call方法,第一个参数就是要把b添加到那个环境中,也就是this指向那个对象
- apply(执行环境对象,实参列表数组)
apply也可以有多个参数,但是第二参数必须是一个数组
- bind(执行环境对象,实参列表)
bind一样可以有多个参数,并且参数可以执行的时候再次添加,参数是按照形参的顺序进行的
注意:
-
如果call和apply的第一个参数是null,那么this在node环境下指向的是global对象,在HTML中指向的是window对象
-
call 和apply都是改变上下文中的this并立即执行这个函数;bind方法通过赋值给新的变量,让对应的函数想什么时候调用就什么时候调用,并且可以将参数在执行的时候添加
7.函数的应用
- 回调函数
主函数的事先做完再回头调用传进来的那个函数
案例:
//定义主函数,回调函数作为参数function A(callback) { callback(); console.log('我是主函数');}//定义回调函数function B() { // 模仿延时操作 setTimeout(() => { console.log('我是回调函数'); }, 3000);}//调用主函数,将函数B传进去A(B);
作用在于:回调函数一般都用在操作上面,因为主函数不用等待回调函数执行完,可以直接先执行自己的代码。例如ajax请求,处理文件等
- 作为返回值
在作用域链中,就使用到函数作为返回值得用法:
x var a = 10function fn() { var b = 20 function bar() { console.log(a + b) //30 } return bar} var x = fn(), // 执行fn() 返回的是barb = 200x() //执行x,就是执行bar函数
8.闭包 是一种特殊的对象
- 什么是闭包:闭包是指有权访问另一个函数作用域中变量得函数,闭包是一个不会销毁的函数空间
闭包生成的条件:
- 函数嵌套函数
- 内部函数引用了外部函数中的数据(属性、函数)
- 参数和变量不会被回收
创建闭包就是一个函数里包含另一个函数,最后在函数返回另一个函数。以下closure就是一个闭包
// 简单创建一个闭包// 函数嵌套函数function foo() { var a=1,b=2; function closure() { //内部可以调用外部作用域的属性 console.log(a,b); console.log(this.a);//内部函数没声明a变量 } // 把内部函数当作一个返回值进行返回 return closure;}var result=foo();result();//1 2 //undefined
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
function f1() { var n = 999; nAdd = function () { n += 1 } function f2() { console.log(n); } return f2;}var result = f1();result(); // 999nAdd();result(); // 1000
函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
在"nAdd=function(){n+=1}"这一行要注意,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
使用闭包需要注意的点:
- 因为闭包会让函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包。解决方法是在退出函数之前,将不使用的局部变量全部删除
- 闭包会在父函数外部,改变父函数内部变量的值。当把父函数当作对象使用,闭包当作公用方法,把内部变量当作私有属性,这是不能随便改变父函数内部变量的值。因为多个子函数的scope都是同时指向父级,是完全共享的,当父级的变量对象被修改时,所有子函数都受影响。
闭包this指向的问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var name = "window-closure";
var obj = {
name: "obj-closure",
say: function () {
console.log(this.name);//obj-clowindow
var that = this;
return function () {
console.log(that.name);//obj-closure
console.log(this.name);//window-closure
console.log(this);//window对象
}
}
}
var x = obj.say();
x();
</script>
</body>
</html>