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
View Code

(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'
View Code

(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
View Code

(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
View Code

 

保护对象

问题: 旧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
View Code

  

   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
View Code

         (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: 年龄超范围!
View Code

保护对象结构: 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: "埃里克"
View Code

 

 

 

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"
View Code

 

替换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
View Code

  (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
View Code

 

  (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
View Code

 

 

数组函数

判断: 

(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
View Code

(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
View Code

遍历: 对数组中每个元素执行相同的操作,包括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 吉吉-到!
View Code

(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]
View Code

 

(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]
View Code

 

(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
View Code

 (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);
}

 

posted @ 2021-02-05 20:45  ComeIntoBud  阅读(107)  评论(0)    收藏  举报