1. 函数的声明与使用
- 函数命名尽量做到语义化表达
- 函数定义时里面的代码不会执行, 函数必须调用才能执行
- 函数开可以多次调用, 调用几次执行几次
// function 函数名() { // 函数封装的代码; // } // 调用 : 函数名() function sayHello() { console.log("Hello"); } sayHello(); sayHello();
2. 函数的参数与返回值
- 形参(parmaters): 用来接收实际参数, 在函数内部作为变量使用
- 实参(arguments): 实际的参数, 用来把参数传递到函数内部
- var 变量 = 函数名() 函数执行后会返回一个值, 使用变量来进行接收
- 可以使用return关键字来返回结果
- 一旦函数中执行return语句, 那么当前函数就会被终止
- 函数都是有返回值的, 如果没有使用return语句, 那么函数有默认的返回值undefined
- 如果使用return语句, 但是return后面没有任何值, 那么函数的返回值也是undefined
// 1. 函数的参数 function sayHello(name, age) { // name, age: 形参, parmaters console.log(`Hello, my name is ${name}, ${age} yaers old`); } sayHello("zgc", 18); // "zgc", 18: 实参, arguments // 2. 函数的返回值 // var 变量 = 函数名() 函数执行后会返回一个值, 使用变量来进行接收 // 可以使用return关键字来返回结果 // 一旦函数中执行return语句, 那么当前函数就会被终止 // 函数都是有返回值的, 如果没有使用return语句, 那么函数有默认的返回值`undefined` // 如果使用return语句, 但是return后面没有任何值, 那么函数的返回值也是undefined function foo(name) { return name === "zgc"; console.log("这句不会执行"); } var bar = foo("zgc"); console.log(bar); // true
3. 数字的格式化工具
var count1 = 13687; // 13687 var count2 = 5433332; // 543万 var count3 = 8766633333; // 87亿 function formatCount(count) { var result = 0; if (count >= 10_0000_0000) { // _: es6语法糖, 无实际作用, 只是让数字更加直观, 可以加在数字的任何位置 result = Math.floor(count / 1_0000_0000) + "亿"; } else if (count >= 10_0000) { result = Math.floor(count / 1_0000) + "万"; } else { result = count; } return result; } var result1 = formatCount(count1); console.log(result1); var result2 = formatCount(count2); console.log(result2); var result3 = formatCount(count3); console.log(result3);
- arguments初识
- 在箭头函数中没有arguments
function foo(name, age) { // 函数中都存在着一个变量arguments console.log(arguments); // arguments是一个对象 console.log(typeof arguments); // 对象内部包含了所有传入的参数, 即使没有通过形参接收 console.log(arguments[0], arguments[1], arguments[2]); // zgc 19 1.88 } foo("zgc", 19, 1.88); // 案例: 求和 function sum() { var total = 0; for (var i = 0; i < arguments.length; i++) { var num = arguments[i]; total += num; } return total; } console.log(sum(10, 20)); // 30 console.log(sum(10, 20, 30)); // 60
4. 函数的递归调用
- 递归必须要有结束条件, 否则会产生无限调用, 造成bug
// 1. 函数调用其他函数 function bar() { console.log("bar函数执行"); } function foo() { console.log("top"); console.log("foo函数执行"); bar(); console.log("bottom"); } foo(); // 结果: // top; // foo函数执行; // bar函数执行; // bottom; // 2. 函数调用自己, 即递归 // 递归必须要有结束条件, 否则会产生无限调用, 造成bug // 3. 案例: 封装一个函数, 函数可以实现x的n次方 // 方法1: 循环实现 function pow2(x, n) { if (n === 0) return 1; var result = 1; for (var i = 0; i < n; i++) { result *= x; } return result; } console.log(pow2(5, 3)); // 125 // 方法2: 递归实现(必须有一个结束条件) // 递归的性能不如for循环, 但写出来的代码简洁 function pow3(x, n) { if (n === 0) return 1; if (n === 1) return x; return x * pow3(x, n - 1); } console.log(pow3(5, 3)); // 125
- 斐波那契数列
// 斐波那契数列: 1 1 2 3 5 8 13 21 34 55 // 1. 递归实现 function getNum1(n) { if (n === 1 || n === 2) return 1; return getNum1(n - 1) + getNum1(n - 2); } console.log(getNum1(6)); // 8 console.log(getNum1(10)); // 55 // 2. for实现 function getNum2(n) { if (n === 1 || n === 2) return 1; var n1 = 1; var n2 = 1; var num = 0; for (var i = 3; i <= n; i++) { num = n1 + n2; n1 = n2; n2 = num; } return num; } console.log(getNum2(6)); // 8 console.log(getNum2(10)); // 55
5. 局部-全局-外部变量
- 在ES5之前, JS是没有块级作用域的概念, 但函数是可以定义自己的作用域的
- 作用域: 表示一些标识符的有效范围
- 函数的作用域表示在函数内部定义的变量, 只有在函数内部可以被访问到
- 全局变量: 在全局(script)定义一个变量, 那么这个变量可以在定义之后的任何范围内被访问到, 那么这个变量就称之为是一个全局变量
- 通过var声明的全局变量会在window对象上添加一个属性
- 局部变量: 在函数内部定义的变量, 只有在函数内部才能进行访问, 称之为局部变量
- 外部变量: 在函数内部去访问函数之外的变量, 称之为外部变量
- 在函数内部访问变量, 先在自己的内部寻找, 如果没有找到就往上层作用域寻找, 一直到window对象都没有找到的话就报错
// 1. 全局变量: 在全局定义一个变量, 那么这个变量可以在定义之后的任何范围内被访问到, 那么这个变量就称之为是一个全局变量 var message = "Hello World"; if (true) { console.log(message); // Hello World } function foo() { // 2. 局部变量: 在函数内部定义的变量, 只有在函数内部才能进行访问, 称之为局部变量 var nickname = "zgc"; console.log("foo", message); // foo Hello World console.log("foo", nickname); // foo zgc function bar() { // 3. 外部变量: 在函数内部去访问函数之外的变量, 称之为外部变量 console.log("bar", message); // bar Hello World console.log("bar", nickname); // bar zgc } bar(); } foo(); // console.log("foo", nickname); // nickname is not defined // 4. 变量的访问顺序 // 在函数内部访问变量, 先在自己的内部寻找, 如果没有找到就往上层作用域寻找, 一直到window对象都没有找到的话就报错 var info = "wlc"; function name() { var info = "zgc"; function bar() { // var info = "wf"; console.log("访问顺序", info); } bar(); } name(); //访问顺序 zgc
6. 函数式编程
- 函数可以作为一等公民
- 函数可以赋值给变量(函数表达式写法)
- 函数可以在变量之间来回传递
- 函数可以作为另一个函数的参数
- 函数作为另一个函数的返回值
- 函数存储在另一个数据结构中
- 函数式编程: 通常我们对函数作为头等公民的编程方式, 称之为函数式编程
// 1. 函数的声明(推荐) function foo() { console.log("foo"); } // 2. 函数的表达式 var bar = function () { console.log("bar"); }; // 3. 函数可以作为一等公民 // 函数可以赋值给变量(函数表达式写法) var bar1 = function () { console.log("bar"); }; // 函数可以在变量之间来回传递 var bar2 = bar1; bar2(); // bar // 函数可以作为另一个函数的参数 function foo1(fn) { fn(); } foo1(bar1); // bar // 函数作为另一个函数的返回值 function foo2() { return bar; } foo2()(); //bar // 函数存储在另一个数据结构中 var obj = { name: "zgc", eating: function () { console.log("eating"); }, }; obj.eating(); // eating // 4. 函数式编程: 通常我们对函数作为头等公民的编程方式, 称之为函数式编程
7. 函数回调与匿名函数
- 回调函数被认为是
一种高级函数,一种被作为参数传递给另一个函数(在这称作"otherFunction")的高级函数
,回调函数会在otherFunction
内被调用(或执行)。 - 如果在传入一个函数时,
没有指定函数的名称
或者通过函数表达式指定函数对应的变量
, 那么这个函数称之为匿名函数
function foo(fn) { // 回调函数: 通过fn调用bar函数的过程, 称之为函数的回调 fn(); } function bar() { console.log("bar"); } bar(); // 函数的调用 foo(bar); // 案例: function request(url, callback) { // ..... var list = ["a", "b"]; callback(list); } // function getList(list) { // console.log(list); // } // request("www", getList); // 匿名函数写法: request("www", function (res) { console.log(res); });
8. 立即执行函数
- IIFE(立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript函数
- 第一部分是包围在 圆括号运算符
()
里的一个匿名函数,这个匿名函数拥有独立的词法作用域
。 - 第二部分再一次使用
()
创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。 - 当函数变成立即执行的函数表达式时,
表达式中的变量不能从外部访问
。 - 将 IIFE 分配给一个变量,
不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果
。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <button class="btn">1</button> <button class="btn">2</button> <button class="btn">3</button> <button class="btn">4</button> <script> // 1. 立即执行函数 // (function () { // 代码块; // })(); // 当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。 (function () { var nickname = "Barry"; })(); // 无法从外部访问变量 name // console.log(nickname); // 抛出错误:"Uncaught ReferenceError: name is not defined" // 将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果。 var result = (function () { var name = "Barry"; return name; })(); // IIFE 执行后返回的结果: console.log(result); // "Barry" // 2. 应用: // (1) 防止全局变量的命名冲突 // 写法一 var tmp = newData; processData(tmp); storeData(tmp); // 写法二 (function () { var tmp = newData; processData(tmp); storeData(tmp); })(); // (2) 形成单独的作用域, 避免外界访问和修改内部变量 var btnEls = document.querySelectorAll(".btn"); for (var i = 0; i < btnEls.length; i++) { var btn = btnEls[i]; btn.onclick = function () { // onclick回调是在点击时才会触发, 在这时for循环早已经执行完毕 alert(i); // 结果总是4, 而不是0,1,2, 3 }; } // 解决方法: // 1. for循环中用let代替var // 2. 立即执行函数 var btnEls = document.querySelectorAll(".btn"); for (var i = 0; i < btnEls.length; i++) { var btn = btnEls[i]; (function (m) { // 将i当参数传递进去, 立即执行函数有单独的作用域, 当点击事件回调时读取到作用域中的参数 m // i 是实参, m 是形参 btn.onclick = function () { alert(m); // 0, 1, 2, 3 }; })(i); } </script> </body> </html>
9. this关键字
在函数中this到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了,也就是说,this的指向完全取决于函数调用的位置。
- 函数调用: 当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象,立即执行函数,默认的定时器等函数,this也是指向window。
- 方法调用: 如果一个函数作为一个对象的方法来调用时,this指向这个对象。
- 构造函数调用: this指向这个用new新创建的对象。
- apply 、 call 和 bind 调用模式: 这三个方法都可以显示的指定调用函数的 this 指向。
- 箭头函数的this: 指向声明时所在作用域下 this 的值,即箭头函数的this去他的上级作用域下寻找,任何方法都改变不了他的指向
// 1. 函数调用 function foo() { console.log(this === window); //true } foo(); // 2. 方法调用 var obj = { name: "zgc", running: function () { // 这里的上级作用域是window, 对象是数据类型, 不是代码块, 没有作用域 console.log(this); }, }; obj.running(); //obj对象 var fn = obj.running; fn(); // window对象 function bar() { console.log(this); } var baz = { name: "wf", bar: bar, }; baz.bar(); // baz对象