JavaScript函数
一、函数的作用
耦合:各个代码块重复的代码太多了。
if (3 > 0) {
for (var i = 0; i < 10; i++) {
console.log(i);
}
}
if (2 > 0) {
for (var i = 0; i < 10; i++) {
console.log(i);
}
}
if (1 > 0) {
for (var i = 0; i < 10; i++) {
console.log(i);
}
}
高内聚(开发的一个功能,称之为一个模块,它的代码相关性强,也就是它内部的代码紧密联系度比较强,紧密联系度比较强就会让这个模块的独立性强。希望一个模块独立的去完成一个任务,而这个任务完成的好坏是跟高内聚有关系的)。低耦合(希望把重复的代码提取出来,提取出来以后组成一个独立的模块去完成一个特定的功能)。
目的是让一个功能体(模块)有 强的功能性,高的独立性。
就是要用的时候直接调用。(模块的单一责任制)
重复性的代码太多就要解耦合,解耦合最好的方式就是函数。当然函数有很多种,也有很多种写法,有很多种解耦合的,但都是基于函数的。
var x = 5
if (x > 0) {
test()
}
if (x > 5) {
test()
}
if (x > 8) {
test()
}
function test() {
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(x + " window"); //5 window
}
二、函数的声明
// 最基本的函数写法--函数声明
function test(参数) {
//函数的执行语句
}
// 只有被调用才会执行
test()
function test() {
// a 是局部变量,b 相对于全局变量
var a = b = 1 //不正确
console.log(a, b); //1 1
}
test()
console.log(b); //1
// 表达式 字面量
var test = function test() {
var a=2, b = 1
console.log(a, b); //2 1
}
test()
//会报错:为什么?声明了一个变量把test1赋值给test的时候,就把test赋予了一个函数的功能,所以调用test
//的时候能够正确的执行,而test1会自动忽略,test1这里写与不写对于test这个函数的执行是没有影响的。
//但是函数内部可以调用test1(递归)
var test = function test1() {
var a=2, b = 1
console.log(a, b); //2 1
}
console.log(test.name); //test1
test1() //Uncaught ReferenceError: test1 is not defined
// 递归
var test = function test1() {
var a = 2, b = 1
console.log(a, b); //2 1
test1()
}
console.log(test.name); //test1
test()
// 字面量
var a={} //对象字面量、数组字面量、数字字面量...
三、函数的组成部分
// 组成部分
function 函数名 参数(可选) 返回值(可选) return
形参和实参可以不相等,
函数内部知道自己有哪些实参
function test(a, b, c) {
console.log(test.length); //3 形参
console.log(arguments); //Arguments(2) 实参
console.log(arguments[1]); // 2 第二个实参
}
test(1, 2)
// 一个函数被调用时,累加它的实参值
function test() {
var a = 0
for (var i = 0; i < arguments.length; i++) {
a += arguments[i]
}
console.log(a); //3
}
test(1, 2)
//实参求乘积
function test() {
var a = 1
for (var i = 0; i < arguments.length; i++) {
a *= arguments[i]
}
console.log(a); //2.3
}
test(1, 2.3)
//函数里面可以更改实参的值
function test(a, b) {
a = 10
console.log(a);//10
console.log(a + b);//12
// 实参多于形参,则会undefined
console.log(c); //Uncaught ReferenceError: c is not defined
}
test(1, 2, 3)
function test(a, b) {
b = 10
// 在实参里没有传入这个值,这个值就是undefined
console.log(arguments[1]); //undefined
}
test(1)
总结:在实参里面传了值的,可以在函数内部修改这个实参值,如果在实参里并没有传这个值,你给这个形参赋值是没有用的
function test(a, b) {
a = 3
console.log(arguments[0]);
}
console.log(1, 2);
function test(a, b) {
b = 3
console.log(arguments[1]); //undefined 在实参没有传入这个值,那么这个值就是undefined
console.log(b === arguments[1]); //false
}
test(1)
总结:在实参里面传了值的,可以在函数内部去修改这个实参的值,如果在实参没有传入这个值,你给这个形参赋值是没有用的。
function aaa(x, y) {
x = 3
console.log(arguments[0]); //3??? 不应该是undefined 吗,为什么我的是3
// a 和 arguments[0]不是同一个变量产生的值
// 因为a存在栈内存,arguments存在堆内存,
// 实参和形参虽然不是同一个东西,但是它们在系统内部,在函数的基础上其实是一个映射关系
console.log(x === arguments[0]); //true
}
aaa(1, 2)
四、函数参数默认值
// 参数默认值不设置就是undefined
function test(a, b) {
console.log(a, b); //1 undefined
}
test(1)
//a 用默认值,b 传入实参
function test(a = 1, b) {
console.log(a, b); //1 2
}
test(undefined, 2) //用 undefined 占位
//一样的
function test(a = undefined, b) {
console.log(a, b); //1 2
}
test(1, 2)
总结:谁不是undefined就找谁,,如果都是undefined,就是undefined,这也是映射关系,在形参赋值(a = 1,es6的写法。es5是不可以这样写的)一般浏览器是不兼容的。
function test(a, b) {
var a = arguments[0] || 1
var b = arguments[1] || 2
console.log(a + b); //3
}
test()
//有实参时
function test(a, b) {
var a = arguments[0] || 1 //这种写法更好点
var b = arguments[1] || 2
console.log(a + b); //10
}
test(4, 6)
function test(a, b) {
var a, b;
if (typeof (arguments[0]) !== 'undefined') {
a = arguments[0]
} else {
a = 1
}
// 如果 arguments[1]) 传了实参,那么就用传入的实参 6
if (typeof (arguments[1]) !== 'undefined') {
b = arguments[1]
} else {
b = 2
}
console.log(a, b); //4 6
}
test(4, 6)
//---------------------------------------------------
function test(a, b) {
var a, b;
if (typeof (arguments[0]) !== 'undefined') {
a = arguments[0]
} else {
a = 1
}
// 如果 arguments[1]) 没有传入实参,那么就用默认值 1
if (typeof (arguments[1]) !== 'undefined') {
b = arguments[1]
} else {
b = 2
}
console.log(a, b); //1,2
}
test()
//用三目运算写
function test(a, b) {
var a = typeof (arguments[0]) !== 'undefined' ? a = arguments[0] : a = 1
// 如果 arguments[1]) 没有传入实参,那么就用默认值 1
var b = typeof (arguments[0]) !== 'undefined' ? b = arguments[0] : b = 2
console.log(a, b); //1,2
}
test()
五、函数返回值
image.png
// return 的问题
function test() {
console.log('我正在执行');
console.log('我执行完了就结束这个函数');
//确实是走到这就结束了,但js引擎会默认添加 return;
//所以最后一条语句其实是 return;
}
test()
//其实是这样的,每个函数最后一条语句都是 return;
function test() {
console.log('我正在执行');
console.log('我执行完了就结束这个函数');
return;
}
test()
return; 的用法,终止函数的执行
function test() {
console.log('我正在执行');
// return; 放到哪里,return;之后的语句就不会执行了
return;
console.log('我执行完了就结束这个函数');
}
test()
function test(name) {
if (!name) {
return;
}
console.log(name); //li
}
test('li')
不仅仅可以终止函数执行,还可以返回一个变量,返回一个变量还是字符串,数字,null,undefined,function等等都可以
function test(name) {
if (!name) {
return '您没有填写姓名!';
}
return name
}
console.log(test('li')); //li
function test(name) {
if (!name) {
return '您没有填写姓名!';
}
return name
}
console.log(test()); //您没有填写姓名!
function test(name) {
if (!name) {
console.log('您没有填写姓名!'); //您没有填写姓名!
}
console.log(name); //undefined
}
test();
// 在里面 return;
function test(name) {
if (!name) {
console.log('您没有填写姓名!'); //您没有填写姓名!
return;
}
console.log(name);
}
test();
//用三目运算
function test(name) {
return name = !name ? '您没有填写姓名!' : name
}
console.log(test()); //您没有填写姓名!
//用或运算
function test(name) {
return name = '您没有填写姓名!' || name
}
console.log(test()); //您没有填写姓名!
六、全局变量与函数
b = 2
function test() {
var a = 1
console.log(b); //2
}
test()
console.log(a); //Uncaught ReferenceError: a is not defined
// 外面的可以访问里面的,里面的不能访问外面的 ,[[scope] 域的问题
a = 1 //全局变量
function test() {
var b = 2 //局部变量
console.log(a);//1
function test2() {
var c = 3;//局部变量
console.log(b);//2
}
test2()
console.log(c); //c is not defined
}
test()
对于外面的变量,里面的取值和赋值都可以
<script>
a = 1
function test() {
var b = 2
a = 4
function test2() {
var c = 3;
}
test2()
console.log(a); //4
}
test()
console.log(a); //4
</script>
<script>
a = 1
function test() {
var b = 2
a = 4
function test2() {
var c = 3;
a=5
}
test2()
console.log(a); //5
}
test()
console.log(a); //5
</script>
a = 1
function test() {
var b = 2
a = 4
function test2() {
var c = 3;
a=5
console.log(b); //2
}
test2()
console.log(a); //5
}
test()
console.log(a); //5
function 有自己独立的作用域,也就是声明了的变量它的可访问范围
function test() {
var a = 1
console.log(b); //b is not defined
}
function test2() {
var b = 2
console.log(a);
}
test()
test2()
function test() {
var a = 1
console.log(a); //1
}
function test2() {
var b = 2
test()
function test3() {
c = '我是test3'
console.log(c);
}
}
test2()
test3() //test3 is not defined
函数就是一个固定的功能或者程序段被封装的过程,实现一个固定的功能或者程序,在这个封装体中需要一个入口和一个出口,入口就是参数,出口就是返回。
function test(str1, str2, str3) {
console.log(str1);
console.log(str2);
console.log(str3);
}
test('元宵节', '端午节', '劳动节')
function test(str1, str2, str3) {
return str1
return str2
return str3
}
console.log(test('元宵节', '端午节', '劳动节'));
七、函数递归: 第一步 找到规律, 第二步 找到出口
// n的阶乘
// var num = 1
// for (var i = 1; i <= 5; i++) {
// num = num * i
// }
// console.log(num);
// n 的阶乘 -> 不能用for循环 ,用递归
// n! = n * (n-1)
function fact(n) {
if (n === 1) {
return 1
}
return n * fact(n - 1)
}
console.log(fact(5)); //120
function fb(n) {
//第二步 找到出口
if (n <= 0) {
return 0
}
if (n <= 2) {
return 1
}
// 第一步 找到规律,
return fb(n - 1) + fb(n - 2)
}
console.log(fb(6)); //8
console.log(fb(3)); //2
八、立即执行函数(IIFE-immediately-incoked function expression)和它的小秘密
自动执行,执行完成以后立即释放
写不写函数名是一样的,执行完成会立即销毁
// 两种写法
(function () {
})()
// 一样的
(function () {
}()) //w3c建议
仍然可以传参数
(function (a, b) { //传形参
console.log(a + b); //9
}(4, 5)) //传实参
返回值的问题:立即执行函数也是有返回值的,把返回值交给一个变量
var num = (function (a, b) { //传形参
return a + b;
}(4, 5)) //传实参
console.log(num); //9
// 括号括起来的就是表达式
(function () { }())
(function test() { })
(function () { })()
//一定是表达式才能被执行符号执行
(function test1() {
console.log('test1');//test1
})()
var test2 = function () {
console.log('test2'); //test2
}()
// function test3() {
// console.log('test3');
// }()
**函数声明变成表达式的方法 ! || + - && **
+ function test() {
console.log(1);
}()
console.log(test); //test is not defined
0 || function test() {
console.log(1); //1
}()
九、函数的call、apply方法
function test() {
console.log(1); //1
}
// 系统隐式给test添加了.call()
test.call()
function Test(id, name) {
this.id = id
this.name = name
}
var newTest = {
phone: '123456'
}
console.log(newTest);//{phone: "123456"}
Test.call(newTest, '001', 'mary') //把 Test 的 this 改变了,指向了 newTest
// call apply只有一个区别,作用都是更改 this 指向
// Test.apply(newTest, ['001', 'mary'])
console.log(newTest);//{phone: "123456", id: "001", name: "mary"}
var test = new Test('002', 'jack')
console.log(test);//{id: "002", name: "jack"}
call、apply好处:1.补充不完整的方法 2.分组来写,再去调用构造函数的属性和方法 3.分类来写,再汇总
function Compute() {
this.plus = function (a, b) {
console.log(a + b); //3
}
this.minus = function (a, b) {
console.log(a - b);
}
}
function FullCompute() {
Compute.call(this)
this.mul = function (a, b) {
console.log(a * b);
}
this.div = function (a, b) {
console.log(a / b);
}
}
var compute = new FullCompute()
compute.plus(1, 2)
十、callee 与 caller
callee
function test(a, b, c) {
console.log(arguments.callee);// 返回正在被执行的函数对象
console.log(arguments.callee.length);//返回 实参列表所对应的函数
console.log(arguments.callee.length === test.length); //true
console.log(test.length); //3 打印形参的长度
console.log(arguments.length); //3 打印实参的长度
}
test(1, 2, 3)//3
function sum(n) {
if (n <= 1) {
return 1
}
return n + sum(n - 1)
}
var res = sum(5)
console.log(res);//15
// 匿名函数用arguments.callee获取函数
var sum = (function (n) {
if (n <= 1) {
return 1
}
return n + arguments.callee(n - 1)
})(5)
console.log(sum);//15
caller
//'use strict' //caller 严格模式下会报错
test1()
function test1() {
test2()
}
function test2() {
console.log(test2.caller);//打印调用当前函数的函数引用,所以打印的是 test1
}