1.什么是函数
在编程中,函数就是一个功能、就是一个行为、 就是一个动作。从专业的角度说的话,函数就是一段具有独立功能的代码的集合,是一段有名称的 代码。函数也可以说是定义一次但却可以调用或执行任意多次的一段代码。
2. 函数的定义
函数对任何语言来说都是一个核心的概念。通过函数可以封装任意多条语句,而且可以 在任 何地方、任何时候调用执行。ECMAScript 中的函数使用 function 关键字来声明,后跟一组参数 以及函数体。
// 语法结构:
function 函数名称([参数列表]) {
// 函数体
// 如果存在返回值的话,使用return关键字返回
[return 返回值;]
}
3.函数调用
注意:函数的调用就是压栈的过程!!!
局部变量:定义在函数内部的变量。
注意:js中一定要使用var或者let定义变量,如果在函数 内部不使用关键字定义,则默认定义的是全局变量,而不是局部变量。
注意:JavaScript虽然也是解释性语言,和python这些语言一致。但是JavaScript中,如果函数 定义在调用之后,这个是可以的。(Python这些不行,因为代码是从上而下执行的,在调用时,还 没有定义,所以会报错的)。
之所以可行,是因为浏览器在解析JS文件时找到函数声明,并在执 行剩余语句之前设置好函数,这个过程被称为函数提升(function hoisting)。
4. 函数的分类
1.有无参数 函数可以通过有没有参数来讲函数分为有参函数和无参函数。
2. 有无返回值 可以根据函数是否存在返回值,将函数分为有返回值的函数和无返回值的函数。
3. 定义者 可以通过定义者来将函数分为 :系统函数和自定义函数
5. 值传递和引用传递
注意:值传递就是传递值,而引用传递本质传递地址。
<script>
// 值传递
a = 10
b = 20
function change(x, y) {
x = 30;
y = 50;
}
change(a, b);
alert(a +"--"+ b) //结果:a,b值没有改变
var arr = [1, 3, 4, 5]
// 引用传递
function change2(a) {
a.push(1000)
}
change2(arr)
alert(arr) //结果:arr变为 [1, 3, 4, 5,1000]
</script>
6.函数作为参数传递
在弱数据类型语言中,函数本身也是对象(强数据类型语言中不是),而函数参数,只要是对 象就可以充当参数,所以函数也可以当成参数传递进函数的。
function fn() {
alert("这个第一个函数")
}
function show(fn) {
// fn是一个函数,所以就可以直接调用
fn()
alert("js中函数是可以当成参数的哦~~")
}
// 注意:传递fn是相当于将fn对应的函数地址传递过去了,也就是C语言中的指针
// show(fn()) 传递的是用fn函数的返回值,如果fn没有返回值,则传递的就是null
show(fn)
7.默认值参数
如果有参数一般有一个固定值,为了调用者方便,可以将这样的参数设置为默认值参数。
注意:默认值参数必须在普通参数后面定义!!!
// 默认值参数 注意:默认值参数必须在普通参数后面定义!!!
function getCircleArea(r, PI=3.14) {
return PI * r * r
}
alert(getCircleArea(2, 3)) //结果:2*2*3=12
8.arguments 对象
ECMAScript函数不介意传递进来多少参数,也不会因为参数不统一而错误。实际上,函数体内 可以通过 arguments 对象来接收传递进来的参数。
<script>
// arguments对象
function test() {
console.info(arguments.length) //打印元素总个数 3
console.log(arguments) //打印传进来的所有元素 [5,67,'hi']
if (arguments.length > 0) {
console.log(arguments[0]) //打印下标为0的元素 5
}
}
test(5,67,'hi')
</script>
注意:JavaScript因为是脚本语言,代码从上而下执行,所以没有函数重载。如果需要的话, 我们就可以使用arguments对象模拟一个类似功能。
9. 匿名函数
顾名思义,就是在定义的时候没有给命名的函数,一般这种函数就是充当参数,只是使用一 次,那就没有必要取名了,或者定义的时候,赋给其他变量(当然这种情况下,本质还是有名字的)。
<script>
// 匿名函数
(function (){
alert("1.这个就是一个匿名函数");
})
();
function show(fn) {
fn();
alert("12345678")
}
// 匿名函数充当参数
show(function() {
alert("2.这个是匿名函数当参数");
})
// 此时将一个匿名函数让b变量指向,所以b变量就是这个匿名函数
var b = function () {
alert("3.这个就是一个匿名函数")
}
// 所以就可以调用哦
b();
</script>
10. lambda表示式
(箭头函数) lambda表示式是ES6提供的,有些人又叫做箭头函数。主要是简化函数的一种写法。
// 箭头函数,lambda表达式,简化函数写法
// 如果没有参数,则直接写个()
var fn = () => alert("hello");
fn(); //结果:hello
// 等价于 function myFunc(x) { return x + 5}
// 不用加return
let myFunc1=(x) => x + 5;
// 调用
alert(myFunc1(20)) //结果:25
// 只有一个参数是,可以有两种写法
x=>x+5;
// 但是建议使用第二种,因为这样更加清晰明了
(x)=>x+5;
// 如果存在多行代码,需要使用{}
let myFunc2 = (x, y) => {
let z = 5;
return (x + y) * z;
}
alert(myFunc2(2,8)); //结果:50
// 延伸学习:ES6解构语法
/* const {value, msg} = obj; */
//解构语法,相当于定义变量:
/* const value = obj.value;
const msg = obj.msg; */
// 当然如果变量名与属性名不一致,必须这样
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
console.log(baz); // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
console.log(f); // 'hello'
console.log(l); // 'world'
11. 递归
递归是指函数自身调用自身的现象,在函数中调用函数,会形成不断压栈的过程,这样就形成 了死循环,所以递归一定要有结束条件。
function sum(n) {
// 必须有结束条件
if (n <= 1) {
return 1
}
// 自身调用自身
return n + sum(n - 1)
}
// 求1~100的和
alert(sum(100)
12. 函数的属性
ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length 和 prototype。
对于 prototype 属性,它是保存实例方法的所在,也就是原型。
prototype 下的两个方法:apply()和 和 call() ,每个函数都包含这 两个非继承而来的方法。 这两个方法的用途都在特定的作用域中调用函数,实际上等于设置函数 体内 this 对象的值.
注意!call需要一个一个将参数传递,而apply使用数组,一次传递,这个就是两者的区别,其他都一样的。这些方法都可以修改this指针的作用域。还有一个bind函数和call类似。
function box(num1,num2){
return num1+num2; //原函数
}
// apply 方法
// 第二个参数是数组形式
function sayBox(num1,num2){
return box.apply(this,[num1,num2]);//this 表示作用域,是window对象,表示box属于
widow全局对象
}
alert(sayBox("可达鸭", "草莓熊"))
// 当然也可以使用arguments关键字代替
function sayBox2(num1,num2){
return box.apply(this,arguments); //arguments 对象表示box所需要的参数
}
alert(sayBox2(2, 200));
// call方法
// 从第二个参数开始,一个一个的传递
function sayBox3(num1,num2){
return box.call(this, num1, num2); // 一个一个写
}
alert(sayBox3(1000, 1000)
call()和apply()的一种运用
/**
* 当需要创建一个类的时候,设置类的属性和方法需要通过this关键字来引用
* 但是特别注意:this关键字在调用时会根据不同的调用对象变得不同
*/
var color = "red";
function showColor() {
alert(this.color);
}
/**
* 创建了一个类,有一个color的属性和一个show的方法
*/
function Circle(color) {
this.color = color;
}
var c = new Circle("yellow");
showColor.call(this); //使用上下文来调用showColor,结果是red
showColor.call(c); //上下文对象是c,结果就是yellow
/**
* 使用call和apply之后,对象中可以不需要定义方法了
*/
补充:函数的bind方法:bind是Function自带的方法,它不能让函数执行,但是可以改变函数this的 指向(它第一个参数可以改变this的指向,第二个及后面的参数用来给函数传实参),bind不传参 得情况下,this指向不改变,bind(null)和bind(undefined),this指向均不改变
13.全局函数
14. 闭包(closured)
闭包是可访问一个函数作用域里变量的函数。简而言之:闭包就是一个函数,这个函数能够访 问其他函数的作用域中的变量。
function outer() {
var a = '变量1'
var inner = function () {
console.info(a)
}
// inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
return inner
}
可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。
一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数,因此ES6有了块级作用域的概念)。
闭包的作用:
- 可以在函数的外部访问到函数内部的局部变量。
- 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>闭包实例</title>
</head>
<body>
<ul>
<li>click me</li>
<li>click me</li>
<li>click me</li>
<li>click me</li>
</ul>
<script>
var elements=document.getElementsByTagName('li');
var length=elements.length;
for(var i=0;i<length;i++){
elements[i].onclick=function(){
alert(i);
}
}
</script>
</body>
</html>
依次点击4个li标签,正确的运行结果是(依次弹出4,4,4,4 )
每个li标签的onclick事件执行时,本身onclick绑定的function的作用域中没有变量 i,i为undefined,则解析引擎会寻找父级作用域,发现父级作用域中有i,且for循环绑定事件结束 后,i已经赋值为4,所以每个li标签的onclick事件执行时,alert的都是父作用域中的i,也就是4。这是作用域的问题
闭包只能取得包含函数中任何变量的最后一个值。因为别忘了闭包所保存的是整个变量对 象,而不是某个特殊的变量。 其实,用两个函数形成闭包只是一般形式。闭包真正的含义是,如果一个函数访问了此函数 的父级及父级以上的作用域变量,就可以称这个函数是一个闭包。
let关键字,补上了JS没有块级作用域的短板。
<body>
<ul>
<li>click me</li> <!-- 点击弹0 -->
<li>click me</li> <!-- 点击弹1 -->
<li>click me</li> <!-- 点击弹2 -->
<li>click me</li> <!-- 点击弹3 -->
</ul>
<script>
var a = 1;
(function test (){
alert(a);
})
();
var elements=document.getElementsByTagName('li');
var length=elements.length;
for(let i=0;i<length;i++){
elements[i].onclick=function(){
alert(i);
}
}
</script>
</body>
所以,JavaScript中,推荐使用闭包,当页面中存在多个组件,多份js文件被引入后,容易触 发全局变量污染问题,而闭包就是一种解决的很好方案(匿名闭包函数)-- 立即执行函数。
如: 使用闭包有一个优点,也是它的缺点:就是可以把局部变量驻留在内存中,可以避免使用全局 变量。(全局变量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,所以推荐使用 私有的,封装的局部变量)。
在闭包中使用this对象也可能会导致一些问题,this对象是在运行时基于函数的执行环境绑定 的,如果this在全局范围就是window,如果在对象内部就指向这个对象。而闭包却在运行时指向 window,因为闭包并不属于这个对象的属性或方法。