JavaScript 中级(二)
ES5: ECMAScript标准的第五个版本
严格模式
什么是: 比旧的js运行机制要求更严格的运行机制。
为什么: 因为旧的js运行机制有很多广受诟病的缺陷!
何时:项目中,都要在严格模式下开发!
如何启用严格模式:只要在当前作用域的顶部添加"use strict";,那么,当前作用域内的程序,就运行在严格模式下了!
严格模式的新规定:
(1). 禁止给未声明的变量赋值:
a. 问题: 旧的js中,一个变量即使从未声明过,也可以用=强行赋值!但是!这个变量会被自动创建到全局!——极容易全局污染!
b. 解决: 只要启用严格模式,就不允许给未声明过的变量赋值!只要赋值就报错!"xxx is not defined"
c. 示例: 不小心拼错变量名了!
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 "use strict";//启用严格模式 11 12 function send(){ 13 var gf;//局部变量 14 //不小心拼错了! 15 //启用严格模式后,报错! 16 web2011="今晚308,w84u";//web2011未定义! 17 console.log(`gf收到:${gf}`) 18 } 19 send(); 20 console.log(`全班同学都看到:${web2011}`); 21 console.log(gf); 22 </script> 23 </body> 24 </html> 25 运行结果: 26 Uncaught ReferenceError: web2011 is not defined
(2). 所有静默失败升级为错误:
a. 什么是静默失败:旧js中,有些操作不成功!然后还不报错!
b. 问题: 极其不便于代码的调试
c. 解决: 启用严格模式后,所有的静默失败,都变成明确的报错!
d. 优点: 极其便于程序的调试!
e. 示例: 尝试修改只读属性:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 "use strict"; 11 12 //一个员工对象 13 var eric={ 14 eid:1001, 15 ename:"埃里克" 16 }; 17 //公司规定,员工的eid是只读的 18 //先不要问为什么,稍后会讲 19 // 定义 属性 eric的eid 20 Object.defineProperty(eric,"eid", { 21 //为不 可修改 22 writable:false 23 }); 24 25 //严格模式下,会报错 26 eric.eid=-2; 27 // 不能 赋值 给 只读 属性 eid 28 //Cannot assign to read only property 'eid' 29 console.log(eric); 30 </script> 31 </body> 32 </html> 33 运行结果: 34 Uncaught TypeError: Cannot assign to read only property 'eid'
(3). 普通函数调用或匿名函数自调或回调函数中的this不再指向window,而是undefined。
a. 旧js中,普通函数调用或匿名函数自调或回调函数中,因为之前既没有.又没有new,所以this都默认指向window!——语法规定,没有为什么!
b. 问题: 经常因为错误的使用this,导致全局污染!
c. 解决: 启用严格模式,可让普通函数调用和匿名函数自调中的this->undefined,不再指向window
d. 优点: 避免因为错误使用this导致全局污染
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 "use strict"; 11 12 function fun(){//普通函数 13 console.log(this); 14 }; 15 fun(); 16 (function(){//匿名函数自调 17 console.log(this); 18 })(); 19 //严格模式对定时器中的回调函数中的this无效 20 //依然默认指向window! 21 setTimeout(()=>{//回调函数 22 "use strict"; 23 console.log(this); 24 },1000); 25 26 function Student(sname, sage){ 27 //不用new,严格模式下,这里报错 28 this.sname=sname; 29 // this->undefined 30 //不能 设置 undefined的sname属性 31 //Cannot set property 'sname' of undefined 32 this.sage=sage; 33 } 34 35 var lilei=new Student("Li Lei",11); 36 //忘记写new了! 37 var hmm=Student("Han Meimei",12); 38 // 旧js中: this->window 39 // this.sname=Han Meimei 40 // window.sname=Han Meimei 41 42 console.log(lilei); 43 console.log(hmm); 44 console.log(window.sname, window.sage); 45 </script> 46 </body> 47 </html> 48 运行结果: 49 Uncaught TypeError: Cannot set property 'sname' of undefined
(4). 禁用了arguments.callee
a. 什么是arguments.callee: 专门在一个函数内,自动获得当前函数本身的关键字。
b. 为什么: 递归调用时,如果在函数内很深处写死函数名,那么,一旦将来函数名改变,而忘记修改内部写死的函数名!就会立刻出错!
c. 何时: 今后只要递归调用时,都要在当前函数内用arguments.callee代替写死的函数名
d. 优点: 函数内部不用写死函数名,arguments.callee也能自动获得当前函数名——即使修改了函数名,也不用修改内部的函数名!
e. 问题: arguments.callee明明是好东西,为什么严格模式要禁用arguments.callee?
f. 原因: 递归调用效率极低,重复计算量太大!所以,严格模式不建议频繁使用递归调用!
g. 解决: 绝大多数递归算法,都可以用循环代替!——难度极高!——但是,没有任何重复计算,效率极高!
h. 总结:
1). 今后,大多数递归算法,虽然有重复计算,但是效率还是可以接收的!我们就首选递归算法。只不过,在函数内部,暂时写死函数名。
2). 除非,递归极大的影响了效率!才被迫寻求用循环方式解决!
示例: 分别使用递归和循环实现计算斐波那契数列中第n个数
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 "use strict"; 11 12 //斐波那契数列 13 //1 1 2 3 5 8 13 21 34 55...? 14 //1 2 3 4 5 6 7 8 9 10...n 15 //前两个数都是1, 16 //从第三个数开始,每个数都是相邻的前两个数的和 17 //数学上: 18 //f(1)=1 f(2)=1 19 //f(n)=f(n-1)+f(n-2) 20 //程序中: 21 // function f(n){ 22 // if(n<3){ 23 // return 1 24 // }else{ 25 // return arguments.callee(n-1)+arguments.callee(n-2); 26 // //严格模式报错: Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them 27 // } 28 // } 29 30 //循环方式: 31 function f(n){ 32 if(n<3){ 33 return 1; 34 }else{ 35 //先准备三个杯子: 36 var f1=1, f2=1, f3; 37 //因为第1个月和第2个月不用算 38 //从第3个月才开始累加 39 //到用户要求计算到的月份结束 40 for(var i=3;i<=n;i++){ 41 //1. f1+f2=>f3; 42 f3=f1+f2;//=是将右边的值,赋值给左边 43 //2. f2->f1 44 f1=f2;//=是将右边的值,赋值给左边 45 //3. f3->f2 46 f2=f3;//=是将右边的值,赋值给左边 47 } 48 //最后,留在f3杯子里的豆子,就是用户想要的第n个月的数量 49 return f3; 50 } 51 } 52 console.log(f(1), f(2), f(3), f(4), f(10)) 53 // 1 1 2 3 55 54 </script> 55 </body> 56 </html> 57 运行结果: 58 1 1 2 3 55
保护对象
问题: 旧js中,一个对象的属性值或结构,都可随意修改。毫无自保能力!
解决: ES5标准中提供了保护对象的机制
保护单个属性:
(1). ES5标准中,将对象中每个属性,底层都变成了一个缩微的小对象:
(2). 每个属性的缩微小对象中都包含4个成员:
(3). 如何查看每个属性的缩微小对象及其内部4个成员:(了解)
var 缩微小对象=Object.getOwnPropertyDescriptor(对象, "属性名")
获得对象自己的属性的描述信息
(4). 如何修改开关的值来保护对象的属性:
a. 如果只修改一个属性的开关:
重新定义属性
Object.defineProperty(对象名, "属性名", {
开关名: true或false,
... : ...
})
b. 问题: 我们可以关闭开关,别人同样可以重新打开开关!
c. 解决: 只要关闭writable开关或enumerable开关时,都要同时关闭configurable:false。就禁止修改前两个开关了!——双保险!
d. 示例: 使用defineProperty()保护对象属性
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 "use strict"; 11 12 var eric={ 13 eid:1001, 14 ename:"埃里克", 15 salary:12000 16 } 17 18 //尝试获取eric对象的eid属性的缩微小对象: 19 var eric_eid=Object.getOwnPropertyDescriptor(eric,"eid"); 20 console.log(eric_eid); 21 22 //公司要求: 23 //1. 员工编号禁止修改 24 //本例中: 应该修改eric的eid属性的writable开关为false 25 Object.defineProperty(eric,"eid",{ 26 writable:false, 27 configurable:false//禁止别人重新打开writable开关!——双保险 28 }) 29 //2. 禁止删除员工姓名 30 //本例中: 应该修改eric的ename属性的configurable开关为false 31 Object.defineProperty(eric,"ename",{ 32 configurable:false 33 }) 34 //3. 禁止随意遍历薪资 35 //本例中: 应该修改eric的salary属性的enumerable开关为false 36 Object.defineProperty(eric,"salary",{ 37 enumerable:false, 38 configurable:false//禁止别人重新打开writable开关!——双保险 39 }) 40 41 42 43 //有人: 44 //尝试既打开已经关闭的writable开关,又打开configurable开关! 45 // Object.defineProperty(eric,"eid",{ 46 // writable:true, 47 // configurable:true 48 // });//Uncaught TypeError: Cannot redefine property: eid 49 //尝试修改员工变化 50 // eric.eid=-2;//Uncaught TypeError: Cannot assign to read only property 'eid' 51 //尝试删除员工姓名 52 // delete eric.ename;//Uncaught TypeError: Cannot delete property 'ename' 53 //尝试遍历薪资 54 for(var shuxingming in eric){ 55 console.log(`${shuxingming} : ${eric[shuxingming]}`) 56 } 57 58 console.log(eric); 59 console.log(eric.salary); 60 </script> 61 </body> 62 </html> 63 运行结果: 64 {value: 1001, writable: true, enumerable: true, configurable: true} 65 configurable: true 66 enumerable: true 67 value: 1001 68 writable: true 69 __proto__: Object 70 eid : 1001 71 ename : 埃里克 72 {eid: 1001, ename: "埃里克", salary: 12000} 73 eid: 1001 74 ename: "埃里克" 75 salary: 12000 76 __proto__: Object 77 12000
e. 问题: Object.defineProperty()一次只能修改一个属性的多个开关。如果对象中,多个属性都需要保护,则需要把Object.defineProperty()重复写很多遍!——繁琐!
f. 解决: 今后多数情况,一个对象中,都有多个属性需要保护,则应该换成:
Object.defineProperties(对象, {
属性名: {
开关: true或false,
... : ...
},
属性名: {
开关: true或false,
... : ...
}
})
g. 示例: 使用defineProperties()保护对象属性
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 "use strict"; 11 12 var eric={ 13 eid:1001, 14 ename:"埃里克", 15 salary:12000 16 } 17 //公司要求: 18 //1. 员工编号禁止修改 19 //本例中: 应该修改eric的eid属性的writable开关为false 20 //2. 禁止删除员工姓名 21 //本例中: 应该修改eric的ename属性的configurable开关为false 22 //3. 禁止随意遍历薪资 23 //本例中: 应该修改eric的salary属性的enumerable开关为false 24 Object.defineProperties(eric,{ 25 eid:{ 26 writable:false, 27 configurable:false 28 }, 29 ename:{ 30 configurable:false 31 }, 32 salary:{ 33 enumerable:false, 34 configurable:false 35 } 36 }) 37 38 39 40 //有人: 41 //尝试既打开已经关闭的writable开关,又打开configurable开关! 42 // Object.defineProperty(eric,"eid",{ 43 // writable:true, 44 // configurable:true 45 // });//Uncaught TypeError: Cannot redefine property: eid 46 //尝试修改员工变化 47 // eric.eid=-2;//Uncaught TypeError: Cannot assign to read only property 'eid' 48 //尝试删除员工姓名 49 // delete eric.ename;//Uncaught TypeError: Cannot delete property 'ename' 50 //尝试遍历薪资 51 for(var shuxingming in eric){ 52 console.log(`${shuxingming} : ${eric[shuxingming]}`) 53 } 54 55 console.log(eric); 56 console.log(eric.salary); 57 </script> 58 </body> 59 </html> 60 运行结果: 61 eid : 1001 62 ename : 埃里克 63 {eid: 1001, ename: "埃里克", salary: 12000} 64 eid: 1001 65 ename: "埃里克" 66 salary: 12000 67 __proto__: Object 68 12000
(5). 问题: 如果用更灵活的自定义规则保护属性值,则开关就无法实现了
(6). 解决: 今后,只要用自定义的规则,灵活保护属性值时,都要用访问器属性。
访问器属性
.什么是访问器属性: 自己不实际保存属性值,仅提供对另一个数据属性的保护!——保镖
.如何定义访问器属性: 2步:
1). 为对象添加一个新的半隐藏的属性,将旧对象中要保护的属性值转移到这个新的半隐藏属性中保存:
Object.defineProperty(对象, "_原属性名",{
value: 旧对象.要保护的属性,
writable: true, 可以修改
enumerable: false, 实际保存属性值的属性,应该隐姓埋名
configurable:false //双保险
})
2). 为对象添加一个与原属性同名的新访问器属性(替身)代替旧属性:
Object.defineProperty(对象, "原属性名",{
//i. 为替身属性请保镖:
//①一个保镖必须叫get,是一个函数,专门负责从受保护的_属性中读取出现在的属性值,返回给外部
get:function(){
return
this._属性名;
},
//②一个保镖必须叫set,也是一个函数,专门负责接收外部传来的新值,经过验证后决定是否保存到受保护的_属性中
set:function(value){ //将来value可自动接到外界传来的新值
if(value符合要求){
this._属性=value
}else{
不保存,且报错!
}
},
// ii. 因为访问器属性作为替身,必须抛头露面,所以enumerable:true;因为访问器属性替身和保镖不能随意删除,所以configurable:false。
enumerable:true,
configurable:false
//说明: 因为访问器属性不实际保存属性值,所以访问器属性没有value和writable。
})
.外界如何使用访问器属性: 因为外界根本对访问器属性不知情!所以,外界使用访问器属性替身的用法和使用普通的属性用法完全一样!
1). 外界想获取访问器属性的属性值时: 对象.属性名
底层: 当外界想获取访问器属性的属性值时,底层会自动调用访问器属性的get()方法,从实际保存数据的半隐藏的属性中,读取出现在的属性值,return给外部!
2). 外界想修改访问器属性的属性值时: 对象.属性名=新值
底层: 当外界想修改访问器属性的属性值时,底层会自动调用set()方法,并且自动将=右边的新值,传递给set()方法的value形参。在set()内部,先验证value的属性值.是否符合要求,再决定是实际保存属性值,还是报错!
示例: 使用访问器属性保护年龄属性:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var eric={ 11 eid:1001, 12 ename:"埃里克", 13 eage:25 14 } 15 //公司要求: 员工年龄可以修改,但是范围必须介于18~65之间。 16 //1. 先添加一个半隐藏的隐形埋名的属性实际保存原对象中原属性值: 17 //本例中: 新添加一个半隐藏的_eage属性,并将原对象中eage的属性值,转移到_eage中保存 18 Object.defineProperty(eric,"_eage",{ 19 value: eric.eage, 20 writable:true, 21 enumerable:false,//半隐藏 22 configurable:false//双保险 23 }) 24 //2.找替身,请保镖: 25 //2.1 找替身: 26 //本例中: 再创建一个eage属性,替换原对象中的eage。——冒名顶替 27 Object.defineProperty(eric,"eage",{ 28 //2.2 请保镖: 一请就是一对儿!名字都是定死的! 29 //2.2.1 第一个保镖,名字必须叫get, 30 get:function(){//专门负责从当前对象中的_eage属性中获取当前属性值,返回到外部 31 console.log(`自动调用了eric.eage中的get(),return ${this._eage}`); 32 return this._eage; 33 }, 34 //2.2.2 第二个保镖,名字必须叫set 35 set:function(value){//专门负责从外部接收进来一个新值,经过验证后,决定是否保存到受保护的_eage属性中。因为,要接受外部的新值,所以,要定义一个形参value,准备接外部传来的新值。 36 console.log(`自动调用eric.eage中的set(${value}),经过验证`) 37 //如果验证value符合要求 38 if(value>=18&&value<=65){ 39 console.log(` ${value}符合要求,才实际保存到_eage属性中`) 40 //才将新值实际保存回受保护的_eage属性中 41 this._eage=value; 42 }else{//否则如果验证value不符合要求 43 //则不保存,而是报错! 44 console.log(` ${value}不符合要求,不保存!直接报错!`) 45 throw Error("年龄超范围!") 46 } 47 }, 48 //2.3 因为替身要代替总统抛头露面,所以替身的enumerable应该为true。又因为替身和保镖绝对不能轻易被删除,所以configurable必须为false 49 enumerable:true, 50 configurable:false 51 //说明: 因为替身和保镖不实际保存属性值,所以,替身没有value属性和writable属性 52 }) 53 54 //外界想读取eric的eage属性值: 55 console.log(eric.eage);//自动调用eric.eage的get(),return 25 56 //外界想修改eric的eage 57 eric.eage=26;//自动调用eric.eage的set(26),经过验证26符合要求,所以保存到_eage中。 58 //外界想读取eric的eage属性值: 59 console.log(eric.eage);//自动调用eric.eage的get(),return 26 60 //外界想修改eric的eage 61 eric.eage=-2;//自动调用eric.eage的set(-2),经过验证-2不符合要求,所以不保存,直接报错! 62 </script> 63 </body> 64 </html> 65 运行结果: 66 自动调用了eric.eage中的get(),return 25 67 25 68 自动调用eric.eage中的set(26),经过验证 69 26符合要求,才实际保存到_eage属性中 70 自动调用了eric.eage中的get(),return 26 71 26 72 自动调用eric.eage中的set(-2),经过验证 73 -2不符合要求,不保存!直接报错! 74 Uncaught Error: 年龄超范围!
保护对象结构: 3个级别
(1). 防扩展:
a. 问题: 如果别人觉得你的属性管的太严,不好用!恶意添加一个类似的属性,另起炉灶!
b. 解决: 可以禁止给对象添加新属性:
Object.preventExtensions(对象)
(2). 密封: 既防扩展,又防删除
a. 问题: 对象中几乎所有的属性都禁止删除!那么,难道要给每个属性都加configurable:false吗!?
b. 解决: 只要密封一个对象,就可以禁止用户删除对象中任何一个属性!
Object.seal(对象)
c. 原理: seal()自动做了2件事:
1). 自动调用了preventExtensions,禁止添加新属性
2). 自动为每个对象设置configurable:false,所有属性禁止删除
d. 强调: 程序中的密封,只禁止添加和删除属性,不禁止修改属性值!属性值还是可以随意修改的!——多数情况,属性值都是可以修改的!
e. 总结: 今后大多数对象,只要保护到密封(seal)级别,就够了。
(3). 冻结: 既禁止添加删除属性,又禁止修改属性值——很少用
a. 如何: Object.freeze(对象)
冻结
b. 原理: freeze自动做了三件事:
1). 也自动调用preventExtensions,禁止扩展新属性
2). 也自动设置所有属性的configurable:false,所有属性禁止删除
3). 还自动设置所有属性的writable:false,所有属性禁止修改!
示例: 分别使用三个级别保护对象的结构
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 "use strict"; 11 12 var eric={ 13 eid:1001, 14 ename:"埃里克" 15 } 16 //eid只读,且禁止重新打开开关! 17 Object.defineProperty(eric,"eid",{ 18 writable:false, 19 // configurable:false 20 }); 21 //ename禁止删除 22 // object.defineProperty(eric,"ename",{ 23 // configurable:false 24 // }) 25 //禁止给eric对象添加任何其他属性! 26 // Object.preventExtensions(eric); 27 28 //密封 29 //一个seal()等于preventExtensions+所有configurable:false。 30 Object.seal(eric); //多数情况 31 32 //冻结 33 // Object.freeze(eric); //少用 34 35 //别人: 36 //尝试重新打开已经关闭的writable开关 37 // Object.defineProperty(eric,"eid",{ 38 // writable:true, 39 // configurable:true 40 // }) 41 42 //尝试修改只读属性eid的值 43 // eric.eid=-2; 44 //尝试为eric对象添加新属性 45 // eric.Eid=100; //报错 46 //Cannot add property Eid, object is not extensible 47 // 不能 添加 属性 eid(因为)对象是 不 可扩展的 48 49 //尝试删除eric对象的ename属性 50 // delete eric.ename;//报错: 51 //Cannot delete property 'ename' 52 53 //尝试修改eric对象的ename属性 54 // eric.ename="Li Lei";//冻结时才报错 55 //Cannot assign to read only property 'ename' 56 57 console.log(eric); 58 59 </script> 60 </body> 61 </html> 62 运行结果: 63 {eid: 1001, ename: "埃里克"} 64 eid: 1001 65 ename: "埃里克"
Object.create()
问题: 通常创建一个子对象,继承父对象,都要通过用new来调用构造函数创建。
比如: var lilei=new Student(...)
结果:会新建一个lilei对象,自动继承Student的原型对象
但是:如果没有构造函数,也想创建子对象,继承父对象?
解决:如果没有构造函数,只有一个父对象,也想创建子对象,继承父对象时,可用Object.create()
如何:
(1). 基本:只创建一个空对象,继承父对象
a. var 新子对象=Object.create(父对象)
b. 原理: 2件事: 完整了new的前2步!
1). 创建一个新的子对象
2). 自动设置子对象继承父对象
c. 问题: 新对象是空的。如果新对象也想有自己的自有属性,怎么办?
(2). 高级: 创建空对象,继承父对象,并给新对象添加自有属性
a. var 新对象=Object.create(父对象,{
//defineProperties中写的格式一样
新属性名:{
value:属性值,
writable:true,
enumerable:true
},
新属性名:{
value:属性值,
writable:true,
enumerable:true
},
})
b. 原理: 3件事: 完整了new的前3步!
1). 创建一个新的子对象
2). 自动设置子对象继承父对象
3). 给新子对象添加自有属性
示例: 使用Object.create代替new创建子对象继承父对象
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var father={ 11 money:10000000000, 12 car:"infiniti" 13 } 14 15 var hmm=Object.create(father,{ 16 phone:{ 17 value:"iPhone12", 18 writable:true, 19 enumerable:true 20 }, 21 bao:{ 22 value:"LV", 23 writable:true, 24 enumerable:true 25 } 26 }); 27 Object.seal(hmm); 28 29 console.log(hmm); 30 console.log(hmm.phone, hmm.bao); 31 console.log(hmm.money, hmm.car); 32 </script> 33 </body> 34 </html> 35 运行结果: 36 {phone: "iPhone12", bao: "LV"} 37 bao: "LV" 38 phone: "iPhone12" 39 __proto__: 40 car: "infiniti" 41 money: 10000000000 42 __proto__: Object 43 iPhone12 LV 44 10000000000 "infiniti"
替换this
问题: 有时,函数内的this指向的对象不是我们需要的!难道我们就只能被动接受?
解决: 今后只要函数内的this执行的对象不是我们想要的!都有办法更换成我们想要的对象!
如何:3种:
(1). 只在一次调用函数时,临时替换一次this:
a. 要调用的函数.call(替换this的对象)
b. 原理: call做了2件事:
1). 先调用函数执行,立刻执行
2). 将函数内的this都替换为()中想要的对象。
c. 问题: 有些函数调用时,需要传入实参值!
d. 解决: 其实,call()中,可以在替换this的对象后,接着写实参值!
要调用的函数.call(替换this的对象, 实参值1, 实参值2, ... )
e. 原理: call做了3件事:
1). 先调用函数执行,立刻执行
2). 将函数内的this都替换为()中想要的对象。
3). 将实参值传给函数的形参变量!
f. 示例: 定义计算函数,可以计算每个员工的工资:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 //有一个公共的全局函数,可以计算某个员工的总工资 11 // 底薪 奖金1 奖金2 12 function jisuan(base, bonus1, bonus2){ 13 console.log(`${this.ename}的总工资是:${ base+bonus1+bonus2}`) 14 } 15 16 //有两个员工都想计算自己的薪资 17 var lilei={//new Object 18 ename:"Li Lei" 19 }//_ _proto_ _->Object的原型对象; 20 var hmm={ename:"Han Meimei"}; 21 22 //lilei想用jisuan()计算自己的薪资: 23 //错误: 24 // jisuan(10000,1000,2000); 25 //this->window this.ename->undefined 26 27 //错误: 在李磊的原型链上根本就没有jisuan() 28 //所以,李磊用不了jisuan() 29 // console.log(lilei); 30 // lilei.jisuan(10000,1000,2000);//报错:lilei.jisuan is not a function 31 32 //正确: 33 //本例中,想调用jisuan(),但是又想替换其中的this为lilei,并且传入工资钱数 34 jisuan.call(lilei,10000,1000,2000); 35 36 jisuan();//this->undefined 37 38 //hmm也想调用jisuan(),计算自己的薪资: 3000,4000,5000? 39 </script> 40 </body> 41 </html> 42 运行结果: 43 Li Lei的总工资是:13000 44 undefined的总工资是:NaN 45 Han Meimei的总工资是:12000
(2)用apply 替换this:
g. 问题: 有时,函数需要传入多个实参值,但是,多个实参值却是放在一个数组中给的!出现了不一致!
h. 解决: 都可用apply代替call
要调用的函数.apply(替换this的对象, 包含实参值数组 )
i. 原理: apply做了3件事:
1). 先调用函数执行,立刻执行
2). 将函数内的this都替换为()中想要的对象。
3). 先将数组自动拆散为多个实参值,再分别传给函数的多个形参变量!
j. 示例: 使用apply,先打散数组,再传参:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 //有一个公共的全局函数,可以计算某个员工的总工资 11 // 底薪 奖金1 奖金2 12 function jisuan(base, bonus1, bonus2){ 13 console.log(`${this.ename}的总工资是:${ base+bonus1+bonus2}`) 14 } 15 16 //有两个员工都想计算自己的薪资 17 var lilei={ename:"Li Lei"} 18 var hmm={ename:"Han Meimei"}; 19 20 //lilei想用jisuan()计算自己的薪资: 21 //本例中,想调用jisuan(),但是又想替换其中的this为lilei,并且传入工资钱数 22 jisuan.call(lilei,10000, 1000, 2000); 23 // ↓ base bonus1 bonus2 24 // this 25 26 //hmm也想调用jisuan(),计算自己的薪资: 3000,4000,5000,但是,hmm的薪资是放在一个数组中给的 27 var arr=[3000,4000,5000]; 28 jisuan.apply(hmm, arr ) 29 // | 3000, 4000, 5000 30 // | base bonus1 bonus2 31 // ↓ 32 // this 33 </script> 34 </body> 35 </html> 36 运行结果: 37 Li Lei的总工资是:13000 38 Han Meimei的总工资是:12000
(3)用bind 永久替换this:
a. 问题: 如果一个函数需要反复使用,而每次使用时,都要用call或apply替换this!
b. 根源: 为什么每次都要替换this!函数本不属于对象,而对象强行要调用函数!用着就很不方便!
c. 解决: 如果可以创建一个对象专属的函数,永久将this替换为某个对象,则再使用时!就不用反复替换this了!
d. 如何: 2步:
1). 创建原函数副本,并永久替换this为指定对象
var 新函数=原函数.bind(替换this的对象)
强调: bind()并不会调用函数立刻执行,而是仅仅创建函数的一个一模一样的副本而已。只不过,把新函数中的this永久替换为指定的对象。
2). 调用新函数,传参,但是无需再call或apply替换this了!
新函数(传参);
e. 原理: bind()做2件事:
1). 创建一个新函数的一模一样的副本
2). 自动永久替换新函数的this为指定的对象
f. 强调: 用bind()永久绑定的this,再用call或apply,也是无法重新替换的!
g. 问题: 有些函数的个别实参值,也是几乎固定不变的!每次调用时,都要反复输入固定不变的实参值,也挺麻烦!
h. 解决: 其实, bind()不但可以永久绑定this,而且,还可以永久绑定部分实参值!
i. 如何: 2步:
1). 先创建函数副本,并永久绑定this和部分实参值
var 新函数副本=原函数.bind(替换this的对象, 固定的实参值1, ...)
2). 调用函数副本时,只需要传入剩余的可能变化的实参值即可!
新函数副本(剩余实参值,...)
j. 原理: bind()做3件事:
1). 创建一个新函数的一模一样的副本
2). 自动永久替换新函数的this为指定的对象
3). 永久绑定部分实参值
k. 示例: 为lilei创建专属的函数副本,永久绑定this和底薪
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 //有一个公共的全局函数,可以计算某个员工的总工资 11 // 底薪 奖金1 奖金2 12 function jisuan(base, bonus1, bonus2){ 13 console.log(`${this.ename}的总工资是:${ base+bonus1+bonus2}`) 14 } 15 16 //有两个员工都想计算自己的薪资 17 var lilei={ename:"Li Lei"} 18 var hmm={ename:"Han Meimei"}; 19 20 //lilei想用jisuan()计算自己的薪资: 21 //但是,李磊不想每次使用时,都临时替换this 22 //于是,李磊买了一个自己专属的新函数 23 var jisuan2=jisuan.bind(lilei); 24 //结果: 25 //jisuan2=function(base,bonus1,bonus2){ 26 // console.log(`${lilei.ename}的总工资.`) 27 //} 28 //从此,李磊再想计算薪资时 29 jisuan2(10000,1000,2000); 30 jisuan2(10000,2000,2000); 31 jisuan2(10000,2000,3000); 32 33 //hmm也想调用jisuan(),计算自己的薪资: 3000,4000,5000,但是,hmm想抢lilei的计算器使用! 34 jisuan2.call(hmm,3000,4000,5000) 35 //结果: Li Lei的总工资是:12000 36 // 不是Han Meimei 37 38 //lilei觉得每次都要重复输入底薪10000,也很麻烦 39 //重新定制了一个新jisuan函数 40 var jisuan3=jisuan.bind(lilei,10000); 41 //结果: 42 //jisuan3=function(10000, bonus1, bonus2){ 43 // console.log(`${lilei.ename}的总工资:10000+bonus1+bonus2`) 44 //} 45 //结果,新函数副本只剩两个参数不确定了! 46 jisuan3(1000,2000); 47 jisuan3(2000,2000); 48 jisuan3(2000,3000); 49 50 </script> 51 </body> 52 </html> 53 运行结果: 54 Li Lei的总工资是:13000 55 Li Lei的总工资是:14000 56 Li Lei的总工资是:15000 57 Li Lei的总工资是:12000 58 Li Lei的总工资是:13000 59 Li Lei的总工资是:14000 60 Li Lei的总工资是:15000
数组函数
判断:
(1). 判断数组中是否所有元素都符合要求
a. var 判断结果=数组.every(
function(当前元素值, 当前下标位置, 当前数组){
return 判断条件, 只判断当前一个元素是否符合要求!
}
)
b. 原理:
1). every内部自带for循环,自动遍历当前数组中每个元素
2). 每遍历一个元素,就自动调用一次回调函数,并且自动按顺序传入三个实参值:
i. 第一个实参值: 传入当前元素值
ii. 第二个实参值: 传入当前下标位置
iii. 第三个实参值: 传入当前数组对象
3). 在回调函数内部,利用我们提供的自定义的判断条件,判断当前元素是否符合要求!并返回判断结果给every()函数
4). every()函数拿到判断结果后:
i. 如果every(),拿到false,说明当前元素不符合要求!就立刻退出循环,并返回整体判断结果为false!说明当前数组中不是所以元素都符合要求的!
ii. 如果every(),拿到true,说明当前元素符合要求!但是,暂时无法判断后续其它元素是否都符合要去,所以要继续循环判断下一个元素。如果直到循环结束,所有元素经过判断都返回true!every()才返回整体判断结果为true。说明当前数组中所有元素都符合要求!
c. 示例: 判断是否数组中所有元素都是偶数
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var arr1=[1,2,3,4,5];//false 11 var arr2=[2,4,6,4,2];//true 12 //想判断哪个数组全由偶数组成! 13 console.log( 14 arr1.every( 15 function(当前元素值, 当前下标, 当前数组){ 16 console.log(`arr1.every()自动调用了一次回调函数, 17 自动传入(当前元素值=${当前元素值}, 当前下标=${当前下标}, 当前数组=${当前数组}), 18 本次判断结果:${当前元素值%2==0}`) 19 //只判断当前元素值是否为偶数 20 return 当前元素值%2==0 21 } 22 ) 23 ); 24 console.log( 25 arr2.every( 26 function(当前元素值, 当前下标, 当前数组){ 27 console.log(`arr2.every()自动调用了一次回调函数, 28 自动传入(当前元素值=${当前元素值}, 当前下标=${当前下标}, 当前数组=${当前数组}), 29 本次判断结果:${当前元素值%2==0}`) 30 //只判断当前元素值是否为偶数 31 return 当前元素值%2==0 32 } 33 ) 34 ); 35 </script> 36 </body> 37 </html> 38 运行结果: 39 arr1.every()自动调用了一次回调函数, 40 自动传入(当前元素值=1, 当前下标=0, 当前数组=1,2,3,4,5), 41 本次判断结果:false 42 false 43 arr2.every()自动调用了一次回调函数, 44 自动传入(当前元素值=2, 当前下标=0, 当前数组=2,4,6,4,2), 45 本次判断结果:true 46 arr2.every()自动调用了一次回调函数, 47 自动传入(当前元素值=4, 当前下标=1, 当前数组=2,4,6,4,2), 48 本次判断结果:true 49 arr2.every()自动调用了一次回调函数, 50 自动传入(当前元素值=6, 当前下标=2, 当前数组=2,4,6,4,2), 51 本次判断结果:true 52 arr2.every()自动调用了一次回调函数, 53 自动传入(当前元素值=4, 当前下标=3, 当前数组=2,4,6,4,2), 54 本次判断结果:true 55 arr2.every()自动调用了一次回调函数, 56 自动传入(当前元素值=2, 当前下标=4, 当前数组=2,4,6,4,2), 57 本次判断结果:true 58 true
(2). 判断数组中是否包含符合要求的元素
a. var 判断结果=数组.some(
function(当前元素值, 当前下标位置, 当前数组){
return 判断条件, 只判断当前一个元素是否符合要求!
}
)
b. 原理:
1). some内部自带for循环,自动遍历当前数组中每个元素
2). 每遍历一个元素,就自动调用一次回调函数,并且自动按顺序传入三个实参值:
i. 第一个实参值: 传入当前元素值
ii. 第二个实参值: 传入当前下标位置
iii. 第三个实参值: 传入当前数组对象
3). 在回调函数内部,利用我们提供的自定义的判断条件,判断当前元素是否符合要求!并返回判断结果给some()函数
4). some()函数拿到判断结果后:
i. 如果some (),拿到true,说明当前元素符合要求!就立刻退出循环,并返回整体判断结果为true!说明当前数组中包含符合要求的元素!
ii. 如果some(),拿到false,说明当前元素不符合要求!但是,暂时无法判断后续其它元素是否都不符合要求,所以要继续循环判断下一个元素。如果直到循环结束,所有元素经过判断都返回false!some()才返回整体判断结果为false。说明当前数组中不包含符合要求的元素!
c. 示例: 判断一个数组中是否包含奇数:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var arr1=[1,2,3,4,5];//true 11 var arr2=[2,4,6,4,2];//false 12 //想判断哪个数组包含奇数! 13 console.log( 14 arr1.some( 15 // 如果用不到,可省略 16 function(当前元素值, 当前下标, 当前数组){ 17 console.log(`arr1.some()自动调用了一次回调函数, 18 自动传入(当前元素值=${当前元素值}, 当前下标=${当前下标}, 当前数组=${当前数组}), 19 本次判断结果:${当前元素值%2!=0}`) 20 //只判断当前元素值是否为奇数 21 return 当前元素值%2!=0 22 } 23 ) 24 ); 25 console.log( 26 arr2.some( 27 function(当前元素值, 当前下标, 当前数组){ 28 console.log(`arr2.some()自动调用了一次回调函数, 29 自动传入(当前元素值=${当前元素值}, 当前下标=${当前下标}, 当前数组=${当前数组}), 30 本次判断结果:${当前元素值%2!=0}`) 31 //只判断当前元素值是否为奇数 32 return 当前元素值%2!=0 33 } 34 ) 35 ); 36 </script> 37 </body> 38 </html> 39 运行结果: 40 arr1.some()自动调用了一次回调函数, 41 自动传入(当前元素值=1, 当前下标=0, 当前数组=1,2,3,4,5), 42 本次判断结果:true 43 true 44 arr2.some()自动调用了一次回调函数, 45 自动传入(当前元素值=2, 当前下标=0, 当前数组=2,4,6,4,2), 46 本次判断结果:false 47 arr2.some()自动调用了一次回调函数, 48 自动传入(当前元素值=4, 当前下标=1, 当前数组=2,4,6,4,2), 49 本次判断结果:false 50 arr2.some()自动调用了一次回调函数, 51 自动传入(当前元素值=6, 当前下标=2, 当前数组=2,4,6,4,2), 52 本次判断结果:false 53 arr2.some()自动调用了一次回调函数, 54 自动传入(当前元素值=4, 当前下标=3, 当前数组=2,4,6,4,2), 55 本次判断结果:false 56 arr2.some()自动调用了一次回调函数, 57 自动传入(当前元素值=2, 当前下标=4, 当前数组=2,4,6,4,2), 58 本次判断结果:false 59 false
遍历: 对数组中每个元素执行相同的操作,包括2个:
(1). forEach: 单纯代替for循环,遍历数组中每个元素
a. 问题:
1). for的问题: 语法已经固定了,没有简写的余地了
2). for in的问题: 语法虽然简单,但是in的遍历,可能超出数字下标的范围,继续遍历父对象中的成员——不保险!
b. 解决: 今后如果只是想单纯遍历索引数组中每个元素,都可以用forEach代替for和for in
c. 如何: 数组.forEach(function(当前元素值, 当前下标位置, 当前数组){
对当前元素值执行相同的操作
})
d. 原理:
1). forEach内部也自带for循环,自动遍历数组中每个元素
2). 每遍历一个元素,都自动调用一次回调函数,每次调用回调函数时,都自动传入三个值:
i. 第一个实参值: 传入当前元素值
ii. 第二个实参值: 传入当前下标位置
iii. 第三个实参值: 传入当前数组对象
3). 在回调函数内部,可对当前元素值执行操作。
e. 示例: 点名:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var arr=["平平","安安","吉吉"]; 11 //点名 12 //for 13 // for(var i=0;i<arr.length;i++){ 14 // console.log(`${arr[i]} - 到!`) 15 // } 16 //for in 17 // for(var i in arr){ 18 // console.log(`${arr[i]} - 到!`) 19 // } 20 //forEach 21 // arr.forEach( 22 // // 形参:永远可以改名! 23 // function(yuansu,xiabiao,shuzu){ 24 // console.log(`${yuansu} - 到!`) 25 // } 26 // ) 27 //不用的参数可以省略,参数名可以极简化 28 // arr.forEach(function(ys){ 29 // console.log(`${ys} - 到!`) 30 // }) 31 //箭头函数简写 32 arr.forEach(ys=>console.log(`${ys}-到!`)) 33 </script> 34 </body> 35 </html> 36 运行结果: 37 平平-到! 38 安安-到! 39 吉吉-到!
(2). map: 基于原数组内容,经过修改后,生成一个新数组,原数组保持不变!
a. var 新数组=原数组.map(function(当前元素值, 当前下标, 当前数组){
return 将当前元素值经过修改后,获得的一个新元素值
})
b. 原理:
1). 先创建一个新的空数组等待。
2). map内部也自带for循环,自动遍历数组中每个元素
3). 每遍历一个元素,都自动调用一次回调函数,每次调用回调函数时,都自动传入三个值:
i. 第一个实参值: 传入当前元素值
ii. 第二个实参值: 传入当前下标位置
iii. 第三个实参值: 传入当前数组对象
4). 在回调函数内部,可对当前元素值执行修改操作,获得新的元素值,返回给map()。
5). map接到回调函数返回的新元素值之后,会将新元素值,自动添加到新数组中相同的位置上!
6). 遍历结束!新数组中放满了修改后的新元素值。然后map()就会将新数组返回!
c. 示例: 将原数组中每个元素值*2,获得一个新数组,原数组保持不变
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var arr=[1,2,3,4,5]; 11 //想获得一个新数组,是将原数组中每个数*2,但是,又要保持原数组内容不变! 12 var arr2=arr.map( 13 function(yuansu,xiabiao,shuzu){ 14 console.log(`arr.map()自动调用了一次回调函数。自动传入(yuansu=${yuansu},xiabiao=${xiabiao},shuzu=${shuzu})。返回了${yuansu*2}`) 15 return yuansu*2; 16 } 17 ); 18 console.log(arr2); 19 console.log(arr); 20 </script> 21 </body> 22 </html> 23 运行结果: 24 arr.map()自动调用了一次回调函数。自动传入(yuansu=1,xiabiao=0,shuzu=1,2,3,4,5)。返回了2 25 arr.map()自动调用了一次回调函数。自动传入(yuansu=2,xiabiao=1,shuzu=1,2,3,4,5)。返回了4 26 arr.map()自动调用了一次回调函数。自动传入(yuansu=3,xiabiao=2,shuzu=1,2,3,4,5)。返回了6 27 arr.map()自动调用了一次回调函数。自动传入(yuansu=4,xiabiao=3,shuzu=1,2,3,4,5)。返回了8 28 arr.map()自动调用了一次回调函数。自动传入(yuansu=5,xiabiao=4,shuzu=1,2,3,4,5)。返回了10 29 [2, 4, 6, 8, 10] 30 [1, 2, 3, 4, 5]
(3). 过滤:复制出数组中符合条件的个别元素,放入新数组中返回
(1). var 新数组=原数组.filter(function(当前元素值, 当前下标, 当前数组){
return 判断条件,判断当前元素值是否符合要求
})
(2). 强调: filter()是复制出符合要求的元素,原数组保持不变!
(3). 原理:
a. 先创建一个新的空数组等待。
b. filter内部也自带for循环,自动遍历数组中每个元素
c. 每遍历一个元素,都自动调用一次回调函数,每次调用回调函数时,都自动传入三个值:
1). 第一个实参值: 传入当前元素值
2). 第二个实参值: 传入当前下标位置
3). 第三个实参值: 传入当前数组对象
d. 在回调函数内部,只判断当前一个元素是否符合要求,并返回判断结果为filter()。
e. filter接到回调函数返回的判断结果后:
1). 只有判断结果为true的元素,才会被filter自动追加到新数组中
2). 如果判断结果为false,则什么也不干,继续遍历下一个元素
f. 遍历结束!新数组中只放了所有符合要求的元素。然后filter()就会将新数组返回!
示例: 过滤出数组中的偶数,放入新数组中返回,原数组保持不变
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var arr=[1,2,3,4,5]; 11 //想只过滤出数组中的偶数 12 var arr2=arr.filter( 13 function(elem,i,arr){ 14 console.log(`arr.filter()自动调用了一次回调函数,自动传入(elem=${elem},i=${i},arr=${arr}),判断结果为:${elem%2==0}`) 15 //只判断当前元素是不是偶数 16 return elem%2==0; 17 } 18 ); 19 console.log(arr2); 20 console.log(arr); 21 </script> 22 </body> 23 </html> 24 运行结果: 25 arr.filter()自动调用了一次回调函数,自动传入(elem=1,i=0,arr=1,2,3,4,5),判断结果为:false 26 arr.filter()自动调用了一次回调函数,自动传入(elem=2,i=1,arr=1,2,3,4,5),判断结果为:true 27 arr.filter()自动调用了一次回调函数,自动传入(elem=3,i=2,arr=1,2,3,4,5),判断结果为:false 28 arr.filter()自动调用了一次回调函数,自动传入(elem=4,i=3,arr=1,2,3,4,5),判断结果为:true 29 arr.filter()自动调用了一次回调函数,自动传入(elem=5,i=4,arr=1,2,3,4,5),判断结果为:false 30 (2) [2, 4] 31 (5) [1, 2, 3, 4, 5]
(4). 汇总: 对数组中所有元素值进行统计,最终得出一个结论
(只介绍对数组内容求和)
(1). var 统计结果=数组.reduce(
//第一个参数,是一个回调函数
function(临时汇总值, 当前元素值, 当前位置, 当前数组){
return 将当前元素值汇总到临时汇总值上,形成了新的临时汇总值。
},
//第二个参数,是统计的起始值
起始值
)
(2). 原理:
a. 先创建一个新的变量等待,变量的初始值就是reduce的第二个参数设置的统计起始值。
b. reduce内部也自带for循环,自动遍历数组中每个元素
c. 每遍历一个元素,都自动调用一次回调函数,每次调用回调函数时,都自动传入四个值:
1). 第一个实参值: 将保存在变量中的临时汇总值传给第一个形参
2). 第二个实参值: 传入当前元素值
3). 第三个实参值: 传入当前下标位置
4). 第四个实参值: 传入当前数组对象
d. 在回调函数内部,将当前元素值汇总到临时汇总值中,获得新的临时汇总值。并将新的临时汇总值,返回给reduce()函数
e. reduce接到回调函数返回的临时汇总值之后,会将临时汇总值保存到开局时创建的变量中暂存,为继续汇总下一个元素值做准备。
f. 遍历结束!开局时创建的变量里保存的就是这个数组的统计结果,并返回。
示例: 对数组中所有元素求和
reduceRight() 方法和的reduce()用法几乎一致,只是该方法是对数组进行倒序遍历的,而reduce()方法是正序遍历的。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 </head> 8 <body> 9 <script> 10 var arr=[1,2,3,4,5]; 11 //想统计数组中所有元素的和 12 var result=arr.reduce( 13 //第一个参数,是回调函数 14 function(上次临时汇总值, elem, i, arr){ 15 console.log(`arr.reduce()自动调用一次回调函数。自动传入四个值(上次汇总值=${上次临时汇总值},elem=${elem},i=${i},arr=${arr}),将当前元素值汇总到上次汇总值中,形成新的临时汇总值${上次临时汇总值+elem}`) 16 return 上次临时汇总值+elem 17 }, 18 //第二个参数,是统计的起始值 19 0 20 ); 21 console.log(result);//15 22 </script> 23 </body> 24 </html> 25 运行结果: 26 arr.reduce()自动调用一次回调函数。自动传入四个值(上次汇总值=0,elem=1,i=0,arr=1,2,3,4,5),将当前元素值汇总到上次汇总值中,形成新的临时汇总值1 27 arr.reduce()自动调用一次回调函数。自动传入四个值(上次汇总值=1,elem=2,i=1,arr=1,2,3,4,5),将当前元素值汇总到上次汇总值中,形成新的临时汇总值3 28 arr.reduce()自动调用一次回调函数。自动传入四个值(上次汇总值=3,elem=3,i=2,arr=1,2,3,4,5),将当前元素值汇总到上次汇总值中,形成新的临时汇总值6 29 arr.reduce()自动调用一次回调函数。自动传入四个值(上次汇总值=6,elem=4,i=3,arr=1,2,3,4,5),将当前元素值汇总到上次汇总值中,形成新的临时汇总值10 30 arr.reduce()自动调用一次回调函数。自动传入四个值(上次汇总值=10,elem=5,i=4,arr=1,2,3,4,5),将当前元素值汇总到上次汇总值中,形成新的临时汇总值15
(5)find()、findIndex()
find() 方法返回通过函数内判断的数组的第一个元素的值。当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。如果没有符合条件的元素返回 undefined。
findIndex() 方法返回传入一个测试函数符合条件的数组第一个元素位置(索引)。当数组中的元素在函数条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。如果没有符合条件的元素返回 -1。
两个方法的语法如下:
array.find(function(currentValue, index, arr),thisValue) array.findIndex(function(currentValue, index, arr), thisValue)
两个方法的第一个参数为回调函数,是必传的,它有三个参数:
- currentValue:必需。当前元素;
- index:可选。当前元素的索引;
- arr:可选。当前元素所属的数组对象
let arr = [1, 2, 3, 4, 5] arr.find(item => item > 2) // 输出结果:3 let arr = [1, 2, 3, 4, 5] arr.findIndex(item => item > 2) // 输出结果:2
find()和findIndex()两个方法几乎一样,只是返回结果不同:
find():返回的是第一个符合条件的值;findIndex:返回的是第一个返回条件的值的索引值。
注意:
- 两个方法对于空数组,函数是不会执行的;
- 两个方法否不会改变原数组。
(6). keys()、values()、entries()
三个方法都返回一个数组的迭代对象,对象的内容不太相同:
- keys() 返回数组的索引值;
- values() 返回数组的元素;
- entries() 返回数组的键值对。
三个方法的语法如下:
array.keys()
array.values()
array.entries()
这三个方法都没有参数:
let arr = ["Banana", "Orange", "Apple", "Mango"]; const iterator1 = arr.keys(); const iterator2 = arr.values() const iterator3 = arr.entries() for (let item of iterator1) { console.log(item); } // 输出结果: 0 1 2 3 for (let item of iterator2) { console.log(item); } // 输出结果:Banana Orange Apple Mango for (let item of iterator3) { console.log(item); }


浙公网安备 33010602011771号