文章目录
严格模式
什么是: 比旧js运行机制要求更严格的运行机制
为什么: 为了屏蔽js中很多广受诟病的缺陷
何时: 今后所有代码都要运行在严格模式下
如何启用严格模式: 在当前代码段或js文件顶部添加:“use strict”; 使用 严格
新要求:
Ⅰ 禁止给未声明过的变量赋值!
① 旧js中: 可以给未声明的变量赋值,但是会在全局自动创建该变量!——极易造成全局污染,造成歧义
② 严格模式中: 禁止给未声明过的变量赋值,如果强行赋值会报错: xxx变量 未定义——避免了全局污染,也减少了歧义
"use strict";
var eric={
eid:1001,
ename:"埃里克"
}
//禁止修改eric的eid属性
Object.defineProperty(eric,"eid",{
writable:false
});
eric.eid=1002;
console.log(eric);
Ⅱ 静默失败升级为错误:
① 静默失败: 程序执行不成功!也不报错!
② 旧js中: 有些操作,虽然执行不成功,也不报错!——极其不便于调试
③ 严格模式中: 静默失败升级为错误——便于调试
"use strict";
function Student(sname, sage){
//undefined
this.sname=sname;
this.sage=sage;
}
var lilei=new Student("Li Lei",11);
console.log(lilei);
//var hmm=Student("Han Meimei",12);
var hmm=new Student("Han Meimei",12)
console.log(hmm);
console.log(window.sname)
console.log(window.sage)
Ⅲ 普通函数调用和匿名函数调用中的this不再指向window,而是undefined!
① 旧js中: 普通函数调用和匿名函数调用中的this都指window——极易造成全局污染
arguments.callee()
Ⅳ 禁用了arguments.callee()
① 什么是arguments.callee: 专门在当前函数执行时,获得当前函数本身的关键词。
② 何时: 递归
③ 问题1: 紧耦合: 递归都是在函数内,写死当前函数的名字,如果函数名改变,必须同时修改函数内部写死的函数名。如果忘记修改,就会出错!
④ 解决: 用arguments.callee代替函数内部写死的函数名——自动获得当前函数的名字——松耦合: 函数名改变,函数内容无需修改!
⑤ 问题2: 有些递归有致命问题——极慢!——重复计算量太大!所以,严格模式禁用了arguments.callee,就是在暗示,尽量少用递归算法。
⑥ 解决: 今后绝大多数递归,都能用循环代替!
"use strict";
//斐波那契数列:
//1 1 2 3 5 8 13 21 34 55
//1 2 3 4 5 6 7 8 9 10
//数学:
//f1=1 f2=1 fn=f(n-1)+f(n-2)
//前两个月都是1,从第三个月开始,每月的数量等于相邻的前两个月的数量之和
//计算第n个月共有多少对儿兔子
//递归:
// function f(n){
// if(n<3){
// return 1;
// }else{
// return arguments.callee(n-1)+arguments.callee(n-2)
// }
// }
function f(n){
if(n<3){
return 1;
}else{
var f1=1, f2=1, fn;
for(var i=3;i<=n;i++){
fn=f1+f2;
f1=f2;
f2=fn;
}
return fn;
}
}
console.log(f(100));
保护对象
什么是: 防止其他人对对象的结构或属性值做任何非法的修改
为什么: 因为js中的对象默认毫无自保能力!可随意修改属性值,可随意添加删除属性。
何时: 只要防止其他人对对象的结构或属性值做任何非法的修改时,都要对对象提供保护
分为两个层面的保护: 保护单个属性和保护整个对象的结构
保护单个属性:
ES5将对象的属性重新分类:
Ⅰ 命名属性: 凡是能用.访问到的属性都是命名属性
又分为两类:
① 数据属性: 直接保存属性值的属性
② 访问器属性: 不实际保存属性值,而是从另一个数据属性获取值——对另一个数据属性提供保护——保镖
Ⅱ内部属性: 不能用.访问到的属性,但是却客观存在在对象内——不需要保护
如何保护数据属性:
ES5规定,每个数据属性在内存中,其实都是一个缩微的小对象。每个属性的小对象中都包含一个value特性和三个开关
如何获取一个数据属性的缩微对象内容:
var obj=Object.getOwnPropertyDescriptor(对象, “属性名”)
获得自己的属性的描述信息
如何修改属性的缩微对象内的开关:
① 错误: 一定不能用.访问:eric.eid.writable=false
② 正确: 必须用专门的函数:
Object.defineProperty(
哪个对象的,
哪个属性,
{
开关: true/false,
... : ... ,
}
)
③ 问题1: 前脚关上的开关,其他人后脚就可随意打开!
④ 解决: 修改前两个开关时,通常都会同时设置configurable:false!以此来禁止在之后重新打开前两个开关!
⑤ 问题2: configurable:false,难道就不能被重新打开吗?
⑥ 答: 一旦configurable:false,则不可逆!(不能重新打开!)
"use strict"
var eric={
eid:1001,//只读
ename:"埃里克"
}
//尝试获得eid属性的缩微对象,查看其内容
var obj=Object.getOwnPropertyDescriptor(eric,"eid");
console.log(obj);
//设置eric的eid属性只读
Object.defineProperty(eric, "eid", {
writable:false, //控制不能修改
configurable:false //控制不能修改writable——双保险!
})
//以身试法:
//尝试修改eid属性的值:
// Object.defineProperty(eric, "eid", {
// writable:true,
// configurable:true //报错:不可逆!
// })
// eric.eid=1002;
console.log(eric);
Object.defineProperties
⑦ 问题3: 一个Object.defineProperty()只能保护对象中的一个属性。如果对象中多个属性都要保护,则需要些好几遍!
⑧ 解决: 其实如果对象中多个属性都需要保护,则可用:
Object.defineProperties(
哪个对象的,
{
哪个属性: {
开关: true/false
},
哪个属性: {
开关: true/false
},
}
)
"use strict"
var eric={
eid:1001,//只读
ename:"埃里克",//禁止删除
salary:12000//禁止被for in遍历
}
Object.defineProperties(eric,{
eid:{
writable:false,
configurable:false
},
ename:{
configurable:false
},
salary:{
enumerable:false,
configurable:false
}
})
//以身试法:
//尝试重新打开已经关闭的开关
// Object.defineProperty(eric, "eid", {
// writable:true,
// configurable:true //报错:不可逆!
// })
//尝试修改eid属性的值:
//eric.eid=1002;
//尝试删除ename属性
//delete eric.ename;
//尝试遍历eric中所有属性(包括salary)
for(var key in eric){
console.log(`${key} : ${eric[key]}`)
}
console.log(eric);
⑨ 问题4: 如果所有员工对象都需要被保护:
⑩ 解决: 将保护放在创建员工对象的构造函数里
"use strict"
//如果希望反复创建员工对象,一定会定义构造函数
function Emp(eid, ename, salary){
//以.方式强行添加的属性,默认所有开关都是开着的!
// this.eid=eid;
// this.ename=ename;
// this.salary=salary;
Object.defineProperties(this,{
//如果对象中没有要设置的属性,则自动添加该属性,只不过必须写完整value+三个开关!不能偷懒!
eid:{
value:eid,
writable:false,
enumerable:true,
configurable:false
},
ename:{
value:ename,
writable:true,
enumerable:true,
configurable:false
},
salary:{
value:salary,
writable:true,
enumerable:false,
configurable:false
}
})
}
var eric=new Emp(1001, "埃里克", 12000)
for(var key in eric){
console.log(`${key} : ${eric[key]}`)
}
console.log(eric);
访问器 半隐藏
如何使用访问器属性来保护数据属性:
问题: 数据属性虽然都有三个开关,但是保护太弱,不够灵活!
什么是访问器: 自己不保存属性值,而是专门提供对另一个数据属性的保护
何时: 只要数据属性自己保护不了自己时,就可请访问器属性当保镖!
如何:
① 新建一个_eage属性,半隐藏,并把原eage属性值搬家到_eage属性中。——隐姓埋名,半隐藏
② 创建一个与原eage属性同名的访问器属性,当保镖,冒名顶替原eage属性,并保护半隐藏的_eage属性。其中,保镖一请,就是2个人
i. get()函数,专门负责从受保护的_eage属性中获取属性值
ii. set(value)函数,专门负责将要赋值的新值保存到受保护的_eage属性中实际保存起来。
结果: 外人,访问eage访问器属性,以为访问的还是原eage属性。
① 当外人试图获取eage的属性值时,eage访问器属性会自动调用内部的get()函数,从受保护的_eage属性中取出属性值
② 当外人试图修改eage属性值时,eage访问器属性自动调用内部的set(value)函数,并将要赋值的新值传递给set的参数变量value。由set()函数经过验证,再决定是否将新值value保存到受保护的属性_eage中.
var eric={
ename:"埃里克",
eage:25 //必须18~65之间
}
//2步:
//1. 为eric添加一个_eage属性,且半隐藏,然后将原eage属性值保存到_eage中
Object.defineProperties(eric,{
_eage:{ //隐姓埋名,半隐藏
value:eric.eage,//搬家
writable:true,
enumerable:false,
configurable:false
},
//2. 定义一个新的访问器属性eage,冒名顶替原eage属性
eage:{
//专门负责返回受保护的_eage属性值
get:function(){
return this._eage;
},
//专门负责修改受保护的_eage属性值
set:function(value){
//value会自动接到要赋值的新值
if(value>=18&&value<=65){
this._eage=value;
}else{
throw Error("年龄必须介于18~65之间")
}
},
//保镖顶替受保护的属性抛头露面
enumerable:true,
configurable:false
}
})//外人:
//尝试输出eage属性值: 都会自动调用保镖的get()函数
console.log(eric.eage);
//尝试修改eage的属性值为正确的值: 会自动调用保镖的set(27)函数
eric.eage=27; //正常
console.log(eric);
//尝试修改eage的属性值为错误的值: 会自动调用保镖的set(-2)函数
eric.eage=-2; //报错
console.log(eric);
问题: 为什么访问器属性eage内部的get()和set()中,可以用this访问eric对象的_eage属性?
答: 原eage访问器属性内部的{ get(), set() }两个函数,
编译后被打散,get()和set()两个函数直接隶属于eric对象,和_eage属性平级,所以可以this._eage
保护结构
(1). 防扩展: Object.preventExtensions(obj)
(2). 密封: Object.seal(obj);
(3). 冻结: Object.freeze(obj)
保护对象结构:
问题:
- 属性的开关,只能防止删除现有属性,无法防止添加新属性
- 如果每个属性都禁止删除的话,只能反复写configurable:false!
- 什么是保护对象结构: 禁止外人为对象添加新属性,或删除现有属性
- 包括三个级别:
防扩展 Object.preventExtensions(obj)
①禁止给对象添加新属性
② Object.preventExtensions(obj)
阻止 扩展
③结果: 禁止了给obj对象添加新属性
④原理: (了解)
i. 其实每个对象内部都有一个隐藏的属性extensible:true
ii. preventExtensions()函数,将对象的extensible:false
"use strict";
var eric={
eid:1001,
ename:"埃里克"
}
//阻止对eric对象添加新属性
Object.preventExtensions(eric);
//尝试添加新属性
//eric.Eid=1002;
//尝试删除现有属性
delete eric.ename;
console.log(eric);
密封 Object.seal(obj)
① 问题: preventExtensions()函数只能防止添加新属性,不能防止删除现有属性
② 解决: 用密封
③ 什么是密封: 既阻止添加新属性,又阻止删除现有属性
④ 如何:Object.seal(obj)
⑤ 结果: 既阻止向对象obj中添加新属性,又阻止从obj对象中删除现有属性
⑥ 原理: 2件事:
i. 将对象的extensible:false
ii. 还自动将对象中所有属性的configurable:false
⑦结论: 今后只要保护对象结构,都用Object.seal()
即可,同时,不用在每个属性都写configurable:false
了
"use strict";
var eric={
eid:1001,
ename:"埃里克"
}
//密封: 既阻止对eric对象添加新属性,同时又阻止删除eric对象的现有属性
Object.seal(eric);
//尝试添加新属性
//eric.Eid=1002;
//尝试删除现有属性
//delete eric.ename;
console.log(eric);
冻结 Object.freeze(obj)
① 既禁止添加和删除属性,甚至禁止修改所有属性值
② Object.freeze(obj)
③结果: 既禁止给obj对象添加和删除属性,设置禁止修改obj对象中所有属性值
④何时: 如果多个模块共用的对象,就不应该被其中某一个模块擅自修改。
⑤ 原理: 3件事:
- 将对象的extensible:false
- 还自动将对象中所有属性的configurable:false
- 自动把每个属性的writable:false
"use strict";
var dbconfig={
ip:"127.0.0.1",
port:3306,
db:"xz"
}
Object.freeze(dbconfig);
//试图添加新属性:
//dbconfig.Ip="localhost";
//试图删除现有属性:
//delete dbconfig.db;
//试图修改现有属性的值
dbconfig.ip="localhost";
总结: 保护对象
保护属性:
(1). 保护数据属性:
a. 只保护一个属性:
Object.defineProperty(对象, "属性名", {
开关: true/false,
configurable:false
})
b. 保护多个属性:
Object.defineProperties(对象,{
属性名:{
开关: true/false,
configurable:false
},
属性名:{
开关: true/false,
configurable:false
},
})
c. 保护所有孩子的属性:
function 构造函数(形参列表){
Object.defineProperties(this,{
属性名: {
value: 形参,
writable:true/false,
enumerable:true/false,
configurable: false
},
属性名: {
value: 形参,
writable:true/false,
enumerable:true/false,
configurable: false
},
})
}
d. 其实构造函数里也可写访问器属性(了解)
但是访问器属性本质其实是一个函数,所以,最好将访问器属性放在原型对象中, 最像Java的构造函数
创建子对象继承 Object.create();
- 什么是: 创建子对象,继承现有的父对象
- 为什么: 有时,没有构造函数,也想创建子对象
- 何时: 只要在没有构造函数的情况下也想创建子对象,继承父对象时,就用Object.create()
- 如何:
var 子对象=Object.create(
父对象,
{ //为子对象添加自有属性: 方式类似于definePropeties()
属性名:{
value:属性值,
开关: true/false,
... : ...
},
属性名:{
value:属性值,
开关: true/false,
... : ...
}
}
)
- 结果:
(1). 创建一个新的空的子对象
(2). 让新的子对象,继承指定的父对象
(3). 为子对象添加自有属性
var father={
bal:10000000000,
car:"infiniti"
}
var hmm=Object.create(father,{
//defineProperties()
bao:{
value:"LV",
writable:true,
enumerable:true,
configurable:false
},
phone:{
value:"iPhone11",
writable:true,
enumerable:true,
configurable:false
}
});
//2件事:
//1. 创建空对象hmm
//2. 设置空对象hmm的__proto__继承father
//3. 为新对象hmm,添加自有属性: bao和phone
console.log(hmm);
console.log(hmm.bal, hmm.car);
替换函数中的this
- 什么是: 将函数中不想要的this,指向想要的对象
- 何时: 只要一个函数中的this不是想要的,都可修改!
call
如何: 2种情况
在本次调用函数时,临时替换一次this为指定的对象
默认: 要调用的函数.call(替换this的对象, 其他实参列表)
call做了3件事:
① Call是调用函数的意思,所以会调用函数执行一次
② Call可自动将函数内的this临时替换为call的第一个参数
③ 将实参值列表传给函数的形参变量!
//假设有一个公共的计算器,可以计算总工资
// 底薪 奖金1 奖金2
function calc(base, bonus1, bonus2){
console.log(`${this.ename}的总工资是:${base+bonus1+bonus2}`)
}
var lilei={ename:"Li Lei"}
var hmm={ename:"Han Meimei"}
//李磊想用计算器计算自己的薪资:
//错误: lilei.calc(10000,2000,3000);
//因为: calc不在李磊对象的原型链上!
//正确: 因为calc是全局函数,所以调用时前边什么都不能加
//错误: calc(10000,2000,3000);
//因为: calc调用时,前边什么都没有,所以this->window,而我们希望this->lilei
//正确:
calc.call(lilei,10000,2000,3000)
// 调用
//3件事:
//1. 调用calc函数执行
//2. *替换calc中的this为lilei*
//3. 将实参值列表传给函数的形参变量!
//但是,是临时替换一次!
//calc(10000,2000,3000);
//韩梅梅也想使用计算器
calc.call(hmm,3000,4000,5000)
apply
问题: 如果要传入函数的实参值列表是放在一个数组中整体传入的,而函数的形参列表却要求分别接住每个对应实参值,就出现了不一致!
解决: 用apply()代替call()
要调用的函数.apply(替换this的对象, 保存实参值的数组) f. apply()做了3件事:
① Apply也是使用函数的意思,所以也会调用函数执行一次
② Apply也可自动将函数内的this临时替换为apply的第一个参数
③ 但是, apply可先将数组打散为单个值,再顺序传入函数中,交给对应的形参变量!
bind
基于原函数创建一个一模一样的新函数副本,并永久替换新函数中的this为指定对象
var 新函数=原函数.bind
(替换this的对象, 部分实现值)
bind:3件事:
① 创建一个和原函数一模一样的函数副本
② 永久替换函数副本中的this为指定的对象
③ 同时还替换函数副本中的部分形参为固定的值!
//假设有一个公共的计算器,可以计算总工资
// 底薪 奖金1 奖金2
function calc(base, bonus1, bonus2){
console.log(`${this.ename}的总工资是:${base+bonus1+bonus2}`)
}
var lilei={ename:"Li Lei"}
var hmm={ename:"Han Meimei"}
//李磊想买一个自己专属一模一样的计算器!
//创建原calc函数的一个副本,并永久修改副本中的this为lilei对象
var lcalc=calc.bind(lilei,10000);
// ↓ ↓
// lcalc ( this, base)
// lcalc ( bonus1,bonus2)
//从此:
lcalc(1000,2000);
lcalc(2000,3000);
//hmm想抢李磊专属的计算器函数
lcalc.call(hmm,4000,5000)
结果:
① 从此使用函数副本,不用再反复call(),反复传替换this的对象
② 被bind绑定的this,即使再用call也无法修改
总结: call apply bind
- 如果只临时替换一次this,call或apply
(1). 如果需要传入多个参数值,而给的参数值也是多个,没有出现不一致,则首选call()
(2). 如果需要传入多个参数值,但是给的参数值却是放在一个数组中给的,出现不一致,则用apply可打散数组后再传参 - 如果要永久替换一个函数中的this,反复使用时,采用bind
记住: 今后如果想更换回调函数中的this,一定用bind!
因为回调函数别调用几次,咱们不知道!
ES5 新增数组函数
Every
- 判断:
(1). 判断数组中是否每个元素都符合要求
var bool=arr.every(//要求提供一个回调函数参数
//回调函数要求
function(elem, i, arr){ //1. 三个形参
return 判断条件 //2.
}
)
强调: 形参变量,elem,i,arr可随意改名
原理:
① Every中自带for循环,自动遍历arr中每个元素
② 每遍历到一个元素,就自动调用一次回调函数
③ 每次调用回调函数时,都自动传入三个实参值给回调函数:
elem
: 获得当前正在遍历的一个元素值i
: 获得当前正在遍历的位置arr
: 获得当前正在遍历的完整数组对象的引用
④ 回调函数内,根据传入的实参值,验证当前正在遍历的到一个元素是否符合要求,并返回判断结果
⑤ Every只要调用回调函数时碰上一个返回false,就立刻停止遍历,返回最终结论false,表示当前数组不是所有元素都符合要求
⑥ 如果Every可以走到最后元素,所有元素经过判断都返回true,才返回最终结论true,表示当前数组中所有元素都符合要求
示例: 判断哪个数组全由偶数组成
var arr1=[1,2,3,4,5];
var arr2=[2,4,6,4,2];
//判断哪个数组*都是*由偶数组成
var result1=arr1.every(
function(elem){
console.log(`arr1.every()自动调用一次回调函数,elem=${elem},返回${elem%2==0}`)
//判断当前元素是不是偶数!
return elem%2==0
}
)
var result2=arr2.every(
function(elem){
console.log(`arr2.every()自动调用一次回调函数,elem=${elem},返回${elem%2==0}`)
//判断当前元素是不是偶数!
return elem%2==0
}
)
console.log(result1, result2)
// false true
d. 示例: 判断哪个数组是升序
var arr1=[1,2,3,4,5];
var arr2=[2,4,6,4,2];
//哪个数组是升序排列?
//*每个*元素都<=它的后一个元素
var result1=arr1.every(
function(elem,i,arr){
//如果i没到最后一个位置
//*每个*元素都<=它的后一个元素
//否则如果i已经是最后一个位置
//直接返回true
return i<arr.length-1?elem<=arr[i+1]:true
}
)
var result2=arr2.every(
function(elem,i,arr){
//如果i没到最后一个位置
//*每个*元素都<=它的后一个元素
//否则如果i已经是最后一个位置
//直接返回true
return i<arr.length-1?elem<=arr[i+1]:true
}
)
console.log(result1, result2)
// true false
some
判断数组中是否包含一些符合条件的元素
var bool=arr.some(function(elem,i,arr){
return 判断条件
})
原理:
① some中自带for循环,自动遍历arr中每个元素
② 每遍历到一个元素,就自动调用一次回调函数
③ 每次调用回调函数时,都自动传入三个实参值给回调函数:同every
④ 回调函数内,根据传入的实参值,验证当前正在遍历的到一个元素是否符合要求,并返回判断结果
⑤ some只要调用回调函数时碰上一个返回true,就立刻停止遍历,返回最终结论true,表示当前数组包含至少一个符合要求的元素
⑥ 如果some走到最后元素,所有元素经过判断都返回false,才返回最终结论false,表示当前数组中不包含任何一个符合要求的元素。
示例: 判断哪个数组中包含奇数:
var arr1=[2,4,6,8,10];
var arr2=[1,2,3,4,5];
//判断按个函数中*包含*奇数
var result1=arr1.some(function(elem){
console.log(`arr1.some(elem=${elem})自动调用了一次回调函数, 判断结果: ${elem%2==1}`)
return elem%2==1
})
var result2=arr2.some(function(elem){
console.log(`arr2.some(elem=${elem})自动调用了一次回调函数, 判断结果: ${elem%2==1}`)
return elem%2==1
})
console.log(result1, result2)
// false true
forEach
仅遍历原数组中每个元素
arr.forEach(function(elem,i,arr){
对每个elem执行相同的操作
})
强调: 回调函数中不用返回值
原理:
① forEach中自带遍历,自动遍历数组中每个元素
② 每遍历一个元素,就自动调用回调函数,自动传入当前元素值elem
、当前位置 i
、当前数组对象arr
③ 在回调函数内部,对每个元素执行相同的操作,无需返回值
- 何时: 只要遍历索引数组中每个元素,都可用forEach代替普通for循环,因为可以进一步用es6简写为箭头函数!
- 强调: 类数组对象和字符串虽然也是数字下标,但是不能使用forEach,因为他们不是数组家孩子!
- 示例:遍历数组中每个元素:
var names=["qq","ww","ee"];
//普通for循环遍历
// for(var i=0;i<names.length;i++){
// alert(`${names[i]} - 到!`)
// }
//forEach遍历
names.forEach(function(elem){
alert(`${elem}-到!`)
})
//更简化
//names.forEach(elem=>alert(`${elem}-到!`))
示例: 将原数组中每个元素值*2
var arr=[1,2,3,4,5];
//想对原数组中每个元素值*2
//for循环
// for(var i=0;i<arr.length;i++){
// arr[i]*=2;
// }
//forEach
arr.forEach(
// [ 1 , 2 , 3 , 4 , 5]
// ↓按值传递: 原始类型传副本
function(elem,i,arr){
// 1 副本
//错: elem*=2;
//在函数内修改副本不影响原数组中的元素
//正确:
arr[i]*=2
//用数组地址访问数组中的元素等效于直接修改原数组内容
}
)
//arr.forEach((elem,i,arr)=>arr[i]*=2)
console.log(arr);
var arr2=[
/*0:0x9091----------->*/{x:1},
/*1:0x3456->*/{x:2},
/*2:0x7812----->*/{x:3},
/*3:0x1234------------------>*/{x:4},
/*4:0x9012---------->*/{x:5}
]
//想对原数组中每个对象的x属性值*2
arr2.forEach(
// [0x9091,0x3456,0x7812,0x1234,...]
// ↓按值传递: 引用类型传地址
function(elem){
// 0x9091
elem.x*=2;
//0x9091.x*=2 //直接通过地址修改原对象内容
}
)
//arr2.forEach(elem=>elem.x*=2)
console.log(arr2);
Map
遍历原数组中每个元素,执行相同操作后,放入新数组返回
何时 : 只要希望保护原数组,生成一个改造后的新数组,都用map
var 新数组=arr.map(function(elem,i,arr){
return 当前元素改造后的新元素值
})
原理:
① Map先创建一个空数组等着
② 然后才自带遍历,自动遍历原数组中每个元素
③ 每遍历一个元素就自动执行一次回调函数,并自动传入三个参数: 当前元素值elem, 当前位置i, 当前数组对象arr
④ 在回调函数中,对获得的当前元素值,加工后,返回出来给map函数
⑤ Map获得回调函数返回的新元素值后,自动放入新数组中相同位置.
⑥ 遍历结束,map会将新数组返回出来!
示例: 取出原数组中值,*2后,放入新数组中,原数组保持不变
var arr=[1,2,3,4,5];
//希望原数组保持不变,又能对原数组中元素都*2后,放入新数组返回!
var newArr=arr.map(
//var newArr=[];
function(elem){
return elem*2;
}
//return newArr;
)
console.log(newArr);
console.log(arr);
示例: 根据数组内容,动态加载ul中的li内容
var names=["qq","ww","ee"];
//根据数组内容,动态加载ul中的li内容
//for循环:jQuery中
// //先定义一个空字符串:
// var html="";
// //然后遍历数组中每个元素
// for(var i=0;i<names.length;i++){
// //每遍历一个元素,就向空字符串中拼接一个<li>下标+1 - 元素值</li>
// html+=`<li>${i+1} - ${names[i]}</li>`
// }
// //将拼接后的html内容,填充到ulNames的内容中
// ulNames.innerHTML=html;
//map方式: react框架中!
//将原数组中每个元素都转化为"<li>下标+1 - 元素值</li>"字符串,放入新数组中返回
var newArr=names.map(function(elem,i){
return `<li>${i+1} - ${elem}</li>`
})
console.log(newArr);
//将新数组中每个<li>字符串无缝拼接为一个大的HTML片段
var html=newArr.join("");
console.log(html);
//将HTML片段填充到ulNames的内容中
ulNames.innerHTML=html
//map方式可简化为:
//ulNames.innerHTML=names.map(
// (elem,i)=>`<li>${i+1} - ${elem}</li>`
// ).join("")
过滤和汇总
过滤: 复制出数组中符合条件的元素,放入新数组中返回,原数组保持不变
var 新数组=arr.filter(function(elem,i,arr){
return 判断条件
})
原理:
① Filter先创建一个空数组等待
② Filter也自带遍历,自动遍历数组中每个元素
③ 每遍历一个元素,就自动调用一次回调函数,并自动传入三个参数: 当前元素值elem, 当前位置i, 当前数组对象arr
④ 在回调函数内,通过一个条件判断当前元素是否符合要求,并返回判断结果给filter函数
⑤ Filter函数每收到一个判断为true的元素,就追加到新数组中
⑥ 最后,返回新数组
示例: //想只取出arr中的偶数
var arr=[1,2,3,4,5];
//想只取出arr中的偶数
var newArr=arr.filter(
//var newArr=[]
//for(var i=0;i<arr.lenght;i++){
function(elem){
//只有偶数才加入新数组中
return elem%2==0
}
//newArr.push(判断结果为true的元素)
//}
//return newArr;
)
console.log(newArr);
console.log(arr);
汇总: 对数组中所有元素进行统计,求出最终的统计结果
var 结果=arr.reduce(
function(prev, elem, i, arr){
return 新的临时的汇总结果
},
起始值
)
原理:
① Reduce中也自带遍历,自动遍历原数组中每个元素
② 每遍历一个元素,就自动调用回调函数,并传入4个参数值:
- prev: 截止到当前元素之前的临时汇总值
- elem: 当前元素值
- i : 当前位置
- arr: 当前数组对象
在回调函数中,应该将当前元素值elem汇总到截止目前的临时汇总值prev中,形成新的汇总值,返回
③ Reduce函数会用返回的新的临时汇总值,代替旧的临时汇总值
④ 遍历结束,剩下的汇总值,就是最终的汇总结果
示例: 想对arr1数组中的所有元素求和
var arr1=[1,3,5,7,9];
//想对arr1数组中的所有元素求和
var result=arr1.reduce(
function(prev,elem){
//将截止目前的临时汇总值于当前元素值相加,获得新的临时汇总值返回!
return prev+elem
},
0 //简写: 如果省略起始值,则自动将第一个元素值作为起始值——不推荐省略
)
console.log(result)