函数进阶
1. 函数的定义和调用
1.1 函数的定义方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function fn ( ) { };var fun = function ( ) { };var f = new Function ('a' , 'b' , 'console.log(a + b)' );f (1 , 2 );console .dir (f);console .log (f instanceof Object );
1.2 函数的调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 function fn ( ) { console .log ('人生的巅峰' ); } var o = { sayHi : function ( ) { console .log ('人生的巅峰' ); } } o.sayHi (); function Star ( ) { };new Star ();(function ( ) { console .log ('人生的巅峰' ); })();
2. this指向
2.1 函数内部的this指向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function fn ( ) { console .log ('普通函数的this' + this ); } window .fn ();var o = { sayHi : function ( ) { console .log ('对象方法的this:' + this ); } } o.sayHi (); function Star ( ) { };Star .prototype .sing = function ( ) {} var ldh = new Star ();var btn = document .querySelector ('button' );btn.onclick = function ( ) { console .log ('绑定时间函数的this:' + this ); }; window .setTimeout (function ( ) { console .log ('定时器的this:' + this ); }, 1000 ); (function ( ) { console .log ('立即执行函数的this' + this ); })();
2.2改变函数内部 this 指向
2.3 call()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var o = { name : 'andy' } function fn (a, b ) { console .log (this ); console .log (a + b); }; fn.call (o, 1 , 2 ); function Father (uname, age, sex ) { this .uname = uname; this .age = age; this .sex = sex; } function Son (uname, age, sex ) { Father .call (this , uname, age, sex); } var son = new Son ('刘德华' , 18 , '男' );console .log (son);
2.4 apply()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 var o = { name : 'andy' }; function fn (arr ) { console .log (this ); console .log (arr); }; fn.apply (o, ['pink' ]); var arr = [1 , 66 , 3 , 99 , 4 ];var arr1 = ['red' , 'pink' ];var max = Math .max .apply (Math , arr);var min = Math .min .apply (Math , arr);console .log (max, min);
2.5 bind()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > Document</title > </head > <body > <button > 点击</button > <button > 点击</button > <button > 点击</button > <script > var o = { name : 'andy' }; function fn (a, b ) { console .log (this ); console .log (a + b); }; var f = fn.bind (o, 1 , 2 ); f (); var btns = document .querySelectorAll ('button' ); btns.forEach (function (value, i ) { btns[i].onclick = function ( ) { this .disabled = true ; setTimeout (function ( ) { this .disabled = false ; }.bind (this ), 2000 ); } }) </script > </body > </html >
2.6 call apply bind 总结
3. 严格模式
3.1 什么是严格模式
3.2 开启严格模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <!-- 1. 为整个脚本(script标签)开启严格模式 --> <script > 'use strict' ; </script > <script > (function ( ) { 'use strict' ; })(); </script > <!-- 2. 为某个函数开启严格模式 --> <script > function fn ( ) { 'use strict' ; } function fun ( ) { } </script >
3.3 严格模式中的变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 'use strict' ;var num = 10 ;console .log (num);function fn ( ) { console .log (this ); } fn ();function Star ( ) { this .sex = '男' ; } var ldh = new Star ();console .log (ldh.sex );setTimeout (function ( ) { console .log (this ); }, 2000 ); a = 1 ; a = 2 ; function fn (a, a ) { console .log (a + a); }; fn (1 , 2 );function fn ( ) { }
4. 高阶函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="jquery.min.js"></script> <style> div { position: absolute; width: 100px; height: 100px; background-color: pink; } </style> </head> <body> <div></div> <script> // 高阶函数- 函数可以作为参数传递 function fn(a, b, callback) { console.log(a + b); callback && callback(); } fn(1, 2, function () { console.log('我是最后调用的'); }); $("div").animate({ left: 500 }, function () { $("div").css("backgroundColor", "purple"); }) </script> </body> </html>
5. 闭包
5.1 变量作用域
5.2 什么是闭包
闭包是指在一个函数内部创建另一个函数,并返回该函数,从而形成一个闭合的函数环境,使得内部函数可以访问外部函数的变量和参数。
具体来说,当一个函数返回另一个函数时,如果内部函数引用了外部函数的变量或参数,那么这些变量或参数就会被保留在内存中,即使外部函数已经执行完成并返回了,这些变量或参数仍然可以被内部函数访问和使用,这种现象就称为闭包。
5.3 闭包的作用
闭包的用途非常广泛,可以用于封装变量,实现模块化,延迟执行函数等等。例如,下面的代码演示了如何使用闭包实现一个简单的计数器:
1 2 3 4 5 6 7 8 9 10 11 12 function createCounter ( ) { let count = 0 ; return function ( ) { count++; console .log (count); } } const counter = createCounter ();counter (); counter (); counter ();
在这个例子中,createCounter 函数返回了一个内部函数,该函数引用了外部函数的变量 count,并且每次被调用时都会将 count 的值加 1 并输出。由于内部函数和外部函数形成了闭合的环境,所以 count 的值得以保留,并且每次调用内部函数时都会更新。通过这种方式,我们就实现了一个简单的计数器功能,并且可以多次重复使用该计数器而不影响其他计数器的值。
5.4 闭包的案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > Document</title > </head > <body > <ul class ="nav" > <li > 榴莲</li > <li > 臭豆腐</li > <li > 鲱鱼罐头</li > <li > 大猪蹄子</li > </ul > <script > var lis = document .querySelectorAll ("li" ); for (var i = 0 ; i < lis.length ; i++) { (function (i ) { lis[i].onclick = function ( ) { console .log (i); } })(i); } </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > Document</title > </head > <body > <ul class ="nav" > <li > 榴莲</li > <li > 臭豆腐</li > <li > 鲱鱼罐头</li > <li > 大猪蹄子</li > </ul > <script > var lis = document .querySelector ('.nav' ).querySelectorAll ('li' ); for (var i = 0 ; i < lis.length ; i++){ (function (i ){ setTimeout (function ( ){ console .log (lis[i].innerHTML ); },3000 ) })(i) } </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // 闭包应用-计算打车价格 // 打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车价格 // 如果有拥堵情况,总价格多收取10块钱拥堵费 // function fn() {}; // fn(); var carPrice = (function () { var begin = 13 var total = 0 return { // 常规价 conventional: function (input) { if (input >= 3) { total += begin + (input - 3) * 5 } else { total += begin } return total }, // 拥堵价 congestion: function (flag) { flag === 'yes' ? flag = true : flag = false console.log(flag); return flag ? total + 10 : total } } })() var input = parseInt(prompt('请输入公里数')) alert('接下来判断是否拥堵,\n是就填yes,\n否就不填或者乱填') var input2 = prompt('是否拥堵(yes or not)') var sumPrice = carPrice.congestion(input2) + carPrice.conventional(input) alert('车费为:' + sumPrice);
5.5 思考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 var name = "The Window" ;var object = { name : "My Object" , getNameFunc : function ( ) { return function ( ) { console .log (this ); return this .name ; }; } }; console .log (object.getNameFunc ()())var name = "The Window" ;var object = { name : "My Object" , getNameFunc : function ( ) { var that = this ; console .log (this ); return function ( ) { return that.name ; }; } }; console .log (object.getNameFunc ()())
5.6 闭包总结
6. 递归
6.1 什么是递归
6.2 利用递归求数学题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function fn (n ) { if (n == 1 ) { return 1 ; } return n * fn (n - 1 ); } console .log (fn (4 ));
1 2 3 4 5 6 7 8 9 10 11 function fb (n ) { if (n === 1 || n === 2 ) { return 1 ; } return fb (n - 1 ) + fb (n - 2 ); } console .log (fb (3 ));
6.3 递归遍历数据
在JavaScript中,递归遍历数据通常通过使用函数来实现,该函数在遍历数据时递归调用自身。以下是一个简单的示例,演示如何使用递归遍历一个包含嵌套对象和数组的数据结构:
1 2 3 4 5 6 7 8 9 function traverse (data ) { if (Array .isArray (data)) { data.forEach (item => traverse (item)); } else if (typeof data === 'object' && data !== null ) { Object .values (data).forEach (value => traverse (value)); } else { console .log (data); } }
这个函数接受一个参数 data
,它可以是任意的 JavaScript 数据类型,包括嵌套的对象和数组。首先,它检查 data
是否是数组类型,如果是,则遍历数组中的每个元素,并递归调用 traverse
函数,以便处理嵌套的数据。如果 data
是对象类型,则遍历对象的所有属性值,并递归调用 traverse
函数,以便处理嵌套的数据。最后,如果 data
是基本数据类型,则直接输出它。
例如,假设有以下嵌套的数据结构:
1 2 3 4 5 6 7 8 9 10 const data = { name : 'Alice' , age : 25 , address : { street : '123 Main St' , city : 'Anytown' , state : 'CA' }, hobbies : ['reading' , 'hiking' , 'traveling' ] };
我们可以使用 traverse
函数遍历这个数据结构:
这将输出以下内容:
1 2 3 4 5 6 7 8 Alice 25 123 Main StAnytown CA reading hiking traveling
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 var data = [ { id : 1 , name : '家电' , goods : [ { id : 11 , gname : '冰箱' , goods : [ { id : 111 , gname : '海尔' }, { id : 112 , gname : '美的' } ] }, { id : 12 , gname : '洗衣机' } ] }, { id : 2 , name : '服饰' } ]; function getID (json, id ) { var screen = new Object () json.forEach (function (item, index ) { if (item.id == id) { screen = item return item } else if (item.goods && item.goods .length > 0 ) { screen = getID (item.goods , id) } }) return screen } console .log (getID (data, 1 ))console .log (getID (data, 11 ))console .log (getID (data, 111 ))console .log (getID (data, 112 ))console .log (getID (data, 12 ))console .log (getID (data, 2 ))
7. 浅拷贝
浅拷贝是一种对象复制的方法,它只复制对象本身以及对象中的基本数据类型的值,而不会复制对象中的引用类型的值。换句话说,浅拷贝只是复制了对象的第一层属性,而没有递归地复制对象的所有属性。
常见的浅拷贝方法包括:
手动遍历对象并复制属性:
1 2 3 4 5 6 7 8 9 function shallowCopy (obj ) { const newObj = {}; for (let key in obj) { if (obj.hasOwnProperty (key)) { newObj[key] = obj[key]; } } return newObj; }
使用 Object.assign()
方法(ES6):
1 2 const obj = { a : 1 , b : { c : 2 } };const newObj = Object .assign ({}, obj);
需要注意的是,使用浅拷贝方法复制对象时,如果对象中有引用类型的值,那么复制后的对象和原对象将共享这些引用类型的值。也就是说,如果修改了复制后的对象中的引用类型的值,那么原对象中相应的值也会受到影响。
例如,下面的代码演示了如何使用浅拷贝方法复制一个对象:
1 2 3 4 const obj = { a : 1 , b : { c : 2 } };const newObj = Object .assign ({}, obj);newObj.b .c = 3 ; console .log (obj.b .c );
在这个例子中,我们复制了一个包含了一个引用类型属性的对象 obj
,然后修改了复制后的对象 newObj
中的引用类型属性 b.c
的值,发现原对象 obj
中的相应属性也被修改了,说明复制后的对象和原对象共享了引用类型属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 var obj = { id : 1 , name : 'andy' , msg : { age : 18 } } var newObj = {}console .log ('---------------------' );Object .assign (newObj, obj)console .log (newObj);newObj.msg .age = 20 console .log (obj);
8. 深拷贝
深拷贝是一种对象复制的方法,它不仅复制对象本身,还递归地复制对象中的所有属性和子属性,包括值类型的属性和引用类型的属性。换句话说,深拷贝会完整地复制一个对象的所有内容,而不是只复制对象的第一层属性。
常见的深拷贝方法包括:
使用 JSON 序列化和反序列化:
1 2 const obj = { a: 1, b: { c: 2 } }; const newObj = JSON.parse(JSON.stringify(obj));
这种方法的缺点是,它无法复制对象中的函数和符号类型的值,并且会忽略对象中的循环引用。
使用递归遍历对象并复制属性:
1 2 3 4 5 6 7 8 9 10 11 12 function deepCopy(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } const newObj = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = deepCopy(obj[key]); } } return newObj; }
这种方法会递归地遍历对象并复制所有属性和子属性,即使对象中有函数和符号类型的值,也会被复制。但是这种方法也会忽略对象中的循环引用。
需要注意的是,深拷贝可能会导致性能问题,尤其是在拷贝大型复杂对象时,因为递归遍历对象的过程需要消耗大量的计算资源。因此,在选择深拷贝方法时需要权衡性能和功能需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 var obj = { id : 1 , name : 'andy' , msg : { age : 18 }, can : ['dance' , 'sing' , 'rap' ] } var newObj = {}function deepCopy (newObj, oldObj ) { for (var k in oldObj) { var item = oldObj[k] if (item instanceof Array ) { newObj[k] = new Array () deepCopy (newObj[k], item) } else if (item instanceof Object ) { newObj[k] = new Object () deepCopy (newObj[k], item) } else { newObj[k] = item } } } deepCopy (newObj, obj)console .log (newObj);newObj.msg .age = 20 console .log (obj);