1.函数介绍
函数允许我们封装一系列代码来完成特定任务。当想要完成某一任务时,只需要调用相应的代码即可。方法(method)一般为定义在对象中的函数。
JavaScript 使用关键字 function 定义函数。
函数可以通过声明定义,也可以是一个表达式。
函数的作用:
功能的封装,直接调用,代码复用率提高
构建对象的模板(构造函数)
函数实际上是对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法,由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。
2.函数声明
自定义函数
函数由function关键字声明,后面紧跟函数名,函数名后面为形参列表,列表后大括号括起来的内容为函数体。也可以将一个匿名函数(没有函数名的函数)赋值给一个函数变量,这种方式称为函数表达式。 解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用;当执行器执行到函数表达式的代码的时候才会真正的解释执行。
表示方法:
2.1 函数声明
function 函数名(形参列表){ //函数体 }
2.2 函数表达式
var 函数名 = function(形参列表){ //函数体 }
这种形式看起来像常规的变量赋值,先创建一个匿名函数,然后赋值给变量函数名。我们知道在JS中如果要想使用一个变量,必须先给这个变量赋值,所以函数表达式也不例外,在调用之前,必须先给它赋值。否则会报错。
2.3 函数声明与var变量声明类似,会进行提升
function add(a,b){ var result = a + b; return result;//返回值//返回执行的结果给被调用的 } var total = add(1,2) foo();//函数声明提升到代码的最前边,可以直接调用函数 function foo(){ console.log("hell world"); //return; //console.log("1");//return之后的语句不执行 //如果没有返回的内容,则在写代码的时候不关注返回值 //没有return:代码执行到大括号 } //变量声明提升 变量声明提升到代码的前边,函数声明之后正常代码之前 console.log(a); //undefined 这里不报错,因为后边有var a的声明。变量的声明进行提升到前边 var a = 'hello'; console.log(a); //'hello'
3.函数内部属性
只有在函数内部才能访问的属性。this也可以在函数外部进行使用。
3.1 arguments
ECMAScript函数的参数与大多数其他语言中的函数的参数有所不同,ECMAScript函数不介意传递参数的个数以及参数类型,这是因为函数的参数在函数内部是使用一个类数组对象来表示的。这个类数组对象就是arguments。
arguments是一个类数组对象,包含着传入函数中的所有参数。arguments主要用途是保存函数参数,但是这个对象还有一个名为callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。
//length声明时希望的参数的个数 function add(a,b){ var result = a + b; return result; } console.log(add.length);//表示函数希望接受的命名参数的个数,即形参的个数。 Object.getOwnPropertyDescriptor(add,'length'); add(22,42,12,44); /*参数:接收实参的快捷方式 函数外部:实参保存的就是原始数据 函数内部:arguments保存真正的实参 */ /*arguments类数组对象 arguments={ “0”:1, “1”:33, “2”:”zs” }; arguments[0],arguments[“1”] */ function add(a,b){ console.log(arguments[0],arguments[1],arguments[2],arguments[3]); console.log(a+b); } add(10); //10 undefined undefined undefined //NaN add(10,20); //10 20 undefined undefined //30 add(10,20,30); //10 20 30 undefined //30 add(10,20,30,40); //10 20 30 40 //30
callee 属性是 arguments 对象的一个成员,仅当相关函数正在执行时才可用。
callee 属性的初始值就是正被执行的 Function 对象。
// 实现匿名的递归函数 var sum = function (n) { if (1 == n) { return 1; } else { return n + arguments.callee(n - 1); } } console.log(sum(6));//输出结果:21
3.2 this
面向对象语言中 this 表示当前对象的一个引用。
但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。
- 在方法中,this 表示该方法所属的对象。
- 如果单独使用,this 表示全局对象。
- 在函数中,this 表示全局对象。
- 在事件中,this 表示接收事件的元素。
- 在显式函数绑定时,我们可以自己决定this的指向
3.3 实例
var person = { firstName: "LeBron", lastName : "James", id : 8888, fullName : function() { return this.firstName + " " + this.lastName; } };
3.4 方法中的 this
在对象方法中, this 指向调用它所在方法的对象。
在上面一个实例中,this 表示 person 对象。
fullName 方法所属的对象就是 person。
fullName : function() { return this.firstName + " " + this.lastName; }
3.5 单独使用 this
单独使用 this,则它指向全局对象。
在浏览器中,window 就是该全局对象为 [object Window]:
在node中,指向的是一个{}
var x = this;
3.6 函数中使用 this(默认)
在函数中,函数的所属者默认绑定到 this 上。
在浏览器中,window 就是该全局对象为 [object Window]:
在node中,指向的就是global对象
function myFunction() { return this; }
3.7 事件中的 this
在 HTML 事件句柄中,this 指向了接收事件的 HTML 元素:
<button οnclick="this.style.display='none'"> 点我后我就消失了 </button>
3.8 显式函数绑定
在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。
在下面实例中,当我们使用 person2 作为参数来调用 person1.fullName 方法时, this 将指向 person2, 即便它是 person1 的方法:
var person1 = { fullName: function () { return this.firstName + " " + this.lastName; } } var person2 = { firstName: "Zach", lastName: "Muyi", } var name = person1.fullName.call(person2); // 返回 "Zach Muyi" console.log(name);
4.IIFE
IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。
4.1 作用
- 页面加载完成后只执行一次的设置函数。
- 将设置函数中的变量包裹在局部作用域中,不会泄露成全局变量。
对比一下,这是不采用IIFE时的函数声明和函数调用:
function foo(){ var a = 10; console.log(a); } foo();
下面是IIFE形式的函数调用:
(function foo(){ var a = 10; console.log(a); })();
函数的声明和IIFE的区别在于,在函数的声明中,我们首先看到的是function关键字,而IIFE我们首先看到的是左边的(。也就是说,使用一对()将函数的声明括起来,使得JS编译器不再认为这是一个函数声明,而是一个IIFE,即需要立刻执行声明的函数。两者达到的目的是相同的,都是声明了一个函数foo并且随后调用函数foo。
4.2 IIFE的写法
1.对返回结果不进行处理
(function(形参){ 函数体内容 })(实参);
2.对返回结果不进行处理
(function(形参){ 函数体内容 }(实参));
3.返回的是一个布尔值,然后进行取反
!function(形参){ 函数体内容 }(实参)
4.对于数字返回的是原来的结果,非数字返回NaN
+function(形参){ 函数体内容 }(实参)
5.对于数字返回的是正负符号相反,非数字返回NaN
-function(形参){ 函数体内容 }(实参)
6.对于数字返回的是正负符号相反再减1,非数字返回-1
~function(形参){ 函数体内容 }(实参)
7.返回的结果是undefined
void function(形参){ 函数体内容 }(实参)
4.3 IIFE的基本使用
// 就像其它任何函数一样,一个立即执行函数也能返回值并且可以赋值给其它变量。 var sum = (function (a,b) { return a + b; }(1,2)) console.log(sum);
4.4 案列-经典面试题-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 的值 。 }
4.5 为什么需要IIFE?
如果只是为了立即执行一个函数,显然IIFE所带来的好处有限。实际上,IIFE的出现是为了弥补JS(ES5)在作用域方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。