• 关卡三:面向对象编程


    【学习前提】

             完成基于ECharts数据可视化项目

    【阶段说明】

            本阶段主要是针对JS高级的一个学习。学习的主要内容有:

            JS知识的补充与拓展

            ES6语法     

    【学习时长】

             3天

    【学习技巧】

             本阶段的学习难点在于对“闭包”的理解。

    一、函数进阶

    一、函数的定义和调用

    1、定义方式

            方式一:函数声明方式 function 关键字(命名函数)

    function fn(){}

            方式二:函数表达式(匿名函数)

    var fn = function(){}

            方式三:new Function() 

    var fn = new Function('参数1','参数2'..., '函数体')

    注意:

            Function 里面参数都必须是字符串格式;

            第三种方式执行效率低,也不方便书写,因此较少使用;

            所有函数都是 Function 的实例(对象);

            函数也属于对象。

    2、调用方式

        1. 普通函数

    1. function fn() {
    2.     console.log('人生的巅峰');
    3. }
    4. fn(); // fn.call();

        2. 对象的方法

    1. var o = {
    2.   sayHi: function() {
    3.       console.log('人生的巅峰');
    4.         }
    5. }
    6. o.sayHi();

        3. 构造函数

    1. function Star() {};
    2. new Star();

        4. 绑定事件函数

    btn.onclick = function() {};   // 点击了按钮就可以调用这个函数

        5. 定时器函数  

    setInterval(function() {}, 1000); // 这个函数是定时器自动1秒钟调用一次

        6. 立即执行函数(自调用函数)

    1. (function() {
    2.     console.log('人生的巅峰');
    3. })();

    二、this

    1、函数内 this 的指向

            这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同,一般指向我们的调用者。

    调用方式this 的指向
    普通函数调用window
    对象方法调用实例对象  原型对象里面的方法也指向实例对象
    构造函数调用该方法所属对象
    事件绑定方法绑定事件对象
    定时器函数window
    立即执行函数window

    2、改变函数内部 this 的指向

            1. call 方法

                call() 方法调用一个对象。简单理解为调用函数的方式,但它可以改变函数的 this 指向。

                应用场景:实现继承

                代码示例:

    1. var o = {
    2. name: 'andy'
    3. }
    4. function fn(a, b) {
    5. console.log(this);
    6. console.log(a+b)
    7. };
    8. fn(1,2); // 此时的this指向的是window 运行结果为3
    9. fn.call(o,1,2); // 此时的this指向的是对象o,参数使用逗号隔开,运行结果为3
    10. // call 的主要作用 可以实现继承
    11. function Father (uname, age, sex) {
    12. this.uname = uname;
    13. this.age = age;
    14. this.sex = sex;
    15. }
    16. function Son (uname, age, sex) {
    17. Father.call(this, uname, age, sex);
    18. }
    19. var son = new Son('刘德华', 18, '男');
    20. console.log(son);

            2. apply 方法

                apply() 方法调用一个函数。简单理解为调用函数的方式,但它可以改变函数的 this 指向。

                应用场景:经常跟数组有关系

                代码示例:

    1. var o = {
    2. name: 'andy'
    3. }
    4. function fn(a, b) {
    5. console.log(this);
    6. console.log(a+b)
    7. };
    8. fn(); // 此时的this指向的是window 运行结果为3
    9. fn.apply(o,[1,2]); // 此时的this指向的是对象o,参数必须使用(伪)数组传递 运行结果为3
    10. // apply 的主要应用 比如利用 apply 借助Math内置对象求最大值、最小值
    11. var arr = [1,22,34,555,43,7];
    12. // var max = Math.max.apply(null, arr);
    13. var max = Math.max.apply(Math, arr);
    14. var min = Math.min.apply(Math, arr);
    15. console.log(max,min);

            3. bind 方法

                bind() 方法不会调用函数,但是能改变函数内部 this 指向。

                应用场景:不调用函数,但是还想改变this指向

                代码示例:

    1. var o = {
    2. name: 'andy'
    3. };
    4. function fn(a, b) {
    5. console.log(this);
    6. console.log(a + b);
    7. };
    8. var f = fn.bind(o, 1, 2); // 此处的f是bind返回的新函数
    9. f(); // 调用新函数 this指向的是对象o 参数使用逗号隔开
    10. // 例:按钮点击后禁用,3秒后又开启这个按钮
    11. var btn = document.querySelector('button');
    12. btn.onclick = function() {
    13. this.disabled = true; // 这个 this 指向的是 btn 按钮
    14. setTimeout(function() {
    15. this.disabled = false; // 此时定时器的 this 指向的不再是 window,而是 btn
    16. }.bind(this),3000) // 绑定 bind 方法使 this 指向 btn 这个对象
    17. }

            4.总结

                相同点:都可以改变函数内部的this指向

                区别:

                    ①call 和 apply 会调用函数,并且改变函数内部this指向;

                    ②call 和 apply 传递的参数不一样,call 传递参数 aru1,aru2..形式 apply 必须数组形式[arg];

                    ③bind 不会调用函数,可以改变函数内部 this 指向。

                主要应用场景:

                    ①call 经常做继承;

                    ②apply 经常跟数组有关系。比如借助于数学对象实现数组最大值最小值;

                    ③bind 不调用函数,但是还想改变this指向。比如改变定时器内部的this指向。

    三、严格模式

    1、什么是严格模式?

            JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。

            严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。

    严格模式对正常的 JavaScript 语义做了一些更改:

    • 消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为;
    • 消除代码运行的一些不安全之处,保证代码运行的安全;
    • 提高编译器效率,增加运行速度;
    • 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class, enum, export, extends, import, super 不能做变量名

    2、开启严格模式

            严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。

            情况一:为脚本开启严格模式

                为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句“use strict”;(或‘use strict’;)

    1. <script>
    2. (function (){
    3. // 在当前的这个自调用函数中有开启严格模式,当前函数之外还是普通模式
    4.     "use strict";
    5. var num = 10;
    6.     function fn() {}
    7. })();
    8. script>
    9. // 或者
    10. <script>
    11.  "use strict"; // 当前script标签开启了严格模式
    12. script>
    13. <script>
    14. // 当前script标签未开启严格模式
    15. script>

    因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。

            情况二:为函数开启严格模式

                要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前

    1. function fn(){
    2.   "use strict";
    3.   return "这是严格模式。";
    4. }
    5. //当前fn函数开启了严格模式

    将 "use strict" 放在函数体的第一行,则整个函数以 "严格模式" 运行。

    3、严格模式中的变化

            1.变量规定

                ①在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var 命令声明,然后再使用;

                ②严禁删除已经声明变量。例如,delete x; 语法是错误的。

            2.this 指向问题

                ①以前在全局作用域函数中的 this 指向 window 对象;

                ②严格模式下全局作用域中函数中的 this 是 undefined;

                ③以前构造函数时不加 new 也可以调用,当普通函数,this 指向全局对象;

                ④严格模式下,如果 构造函数不加 new 调用,this 指向的是 undefined 如果给他赋值则会报错;

                ⑤new 实例化的构造函数指向创建的对象实例;

                ⑥定时器 this 还是指向 window ;

                ⑦事件、对象还是指向调用者。

            3.函数变化

                ①函数不能有重名的参数;

                ②函数必须声明在顶层。新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨不允许在非函数的代码块内声明函数。

            4.更多严格模式要求参考

                MDN:严格模式

    四、高阶函数

            高阶函数是对其他函数进行操作的函数,它接收函数作为参数将函数作为返回值输出。

    1. <script>
    2. function fn(callback){
    3.     callback&&callback();
    4. }
    5. fn(function(){alert('hi')}
    6. script>
    1. <script>
    2. function fn(){
    3.     return function() {}
    4. }
    5. fn();
    6. script>

            此时 fn 就是一个高阶函数;

            函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。同理函数也可以作为返回值传递回来。

    五、闭包和递归

    1、闭包

        1.变量的作用域复习

            变量根据作用域的不同分为两种:全局变量和局部变量

                ①函数内部可以使用全局变量;

                ②函数外部不可以使用局部变量;

                ③当函数执行完毕,本作用域内的局部变量会销毁。

        2.什么是闭包?

            闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。

    1. <script>
    2. function fn1(){     // fn1 就是闭包函数
    3.     var num = 10;
    4.     function fn2(){
    5.         console.log(num); // 10
    6.     }
    7.     fn2();
    8. }
    9.  fn1();
    10. script>

    在 chrome 中调试闭包:

    • 打开浏览器,按 F12 键启动 chrome 调试工具。
    • 设置断点。
    • 找到 Scope 选项(Scope 作用域的意思)。
    • 当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
    • 当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。

        3.闭包的作用

            作用:延伸变量的作用范围。

            提问:我们怎么能在 fn() 函数外面访问 fn() 中的局部变量 num 呢 ?

    1. <script>
    2. function fn() {
    3. var num = 10;
    4. return function {
    5. console.log(num); // 10
    6. }
    7. }
    8. var f = fn();
    9. f()
    10. script>

        4.闭包案例   

    1. // 1、利用闭包的方式得到当前li 的索引号
    2. var lis = document.querySelector('.nav').querySelectorAll('li');
    3. for (var i = 0; i < lis.length; i++) {
    4. // 利用for循环创建了4个立即执行函数
    5. // 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这个变量
    6. (function (i) {
    7. lis[i].onclick = function () {
    8. console.log(i);
    9. }
    10. })(i);
    11. };
    12. // 2、闭包应用-3秒钟之后,打印所有li元素的内容
    13. for (var i = 0; i < lis.length; i++) {
    14. (function (i) {
    15. setTimeout(function () {
    16. console.log(lis[i].innerHTML);
    17. }, 3000)
    18. })(i);
    19. };
    20. // 3、闭包应用-计算打车价格
    21. // 需求分析
    22. // 打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车价格
    23. // 如果有拥堵情况,总价格多收取10块钱拥堵费
    24. var car = (function () {
    25. var start = 13; // 起步价 局部变量
    26. var total = 0; // 总价 局部变量
    27. return {
    28. // 正常的总价
    29. price: function (n) {
    30. if (n <= 3) {
    31. total = start;
    32. } else {
    33. total = start + (n - 3) * 5;
    34. }
    35. return total;
    36. },
    37. // 拥堵之后的费用
    38. jam: function (flag) {
    39. return flag ? total + 10 : total;
    40. }
    41. }
    42. })();
    43. console.log(car.price(5)); // 23
    44. console.log(car.jam(true)); // 33
    45. console.log(car.price(1)); // 13
    46. console.log(car.jam(false)); // 13

    2、递归

            递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己,,这个函数就是递归函数。

            注意:递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。

            案例:

    1. <script>
    2. // 利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
    3. function fn(n) {
    4. if (n == 1) { // 结束条件
    5. return 1;
    6. }
    7. return n * fn(n - 1);
    8. }
    9. console.log(fn(3));
    10. // 详细思路 用户输入 3
    11. // return 3 * fn(3 - 1)
    12. // return 3 * (2 * fn(2 - 1))
    13. // return 3 * (2 * 1)
    14. // return 3 * (2)
    15. // return 6
    16. script>
    17. <script>
    18. // 利用递归函数求斐波那契 Fibonacci 数列(兔子序列) 1、1、2、3、5、8、13、21...
    19. // 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
    20. // 我们只需要知道用户输入的 n 的前面两项(n-1 n-2)就可以计算出 n 对应的序列值
    21. function fb(n) {
    22. if (n === 1 || n === 2) {
    23. return 1;
    24. }
    25. return fb(n - 1) + fb(n - 2);
    26. }
    27. console.log(fb(4));
    28. script>
    29. <script>
    30. // 辗转相除法
    31. function measure(a, b) {
    32. var n = a % b // 取余数
    33. if (n == 0) { // 判断余数是否为0,为0输出b为最大公约数
    34. return b;
    35. } else {
    36. a = b; // 将上一轮的b的值给a
    37. b = n; // 将上一轮的n的值给b
    38. return measure(a, b); // 递归 继续调用函数直到余数n为0
    39. }
    40. }
    41. console.log(measure(9, 18));;
    42. script>
    43. <script>
    44. // 我们想要做输入id号,就可以返回的数据对象
    45. var data = [{
    46. id: 1,
    47. name: '家电',
    48. goods: [{
    49. id: 11,
    50. gname: '冰箱',
    51. goods: [{
    52. id: 111,
    53. gname: '海尔'
    54. }, {
    55. id: 112,
    56. gname: '美的'
    57. }]
    58. }, {
    59. id: 12,
    60. gname: '洗衣机'
    61. }]
    62. }, {
    63. id: 2,
    64. name: '服饰'
    65. }];
    66. // 1.利用 forEach 去遍历里面的每一个对象
    67. function getID(json, id) {
    68. var o = {};
    69. json.forEach(function (item) {
    70. // console.log(item); // 2个数组元素
    71. if (item.id == id) {
    72. // console.log(item);
    73. o = item;
    74. return o;
    75. // 2. 我们想要得里层的数据 11 12 可以利用递归函数
    76. // 里面应该有goods这个数组并且数组的长度不为 0
    77. } else if (item.goods && item.goods.length > 0) {
    78. o = getID(item.goods, id);
    79. }
    80. });
    81. return o;
    82. }
    83. console.log(getID(data, 1));
    84. console.log(getID(data, 2));
    85. console.log(getID(data, 11));
    86. console.log(getID(data, 12));
    87. console.log(getID(data, 111));
    88. console.log(getID(data, 112));
    89. script>
    90. <body>
    91. <button>赞(1)button>
    92. <button>赞(1)button>
    93. <button>赞(1)button>
    94. <button>赞(1)button>
    95. <script>
    96. // 闭包缓存数据
    97. function getValue() {
    98. var value = 2;
    99. return function () {
    100. // 每一次点击的时候,都应该改变当前点击按钮的value值
    101. this.innerHTML = "赞(" + (value++) + ")";
    102. }
    103. }
    104. // 获取所有的按钮
    105. var btnObjs = document.getElementsByTagName("button");
    106. // 循环遍历每个按钮,注册点击事件
    107. for (var i = 0; i < btnObjs.length; i++) {
    108. // 注册事件
    109. btnObjs[i].onclick = getValue();
    110. }
    111. script>
    112. body>

    二、正则表达式

    一、概述

    1、什么是正则表达式?

            正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。

            正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。 此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等。

    2、特点

    1. 灵活性、逻辑性和功能性非常的强。
    2. 可以迅速地用极简单的方式达到字符串的复杂控制。
    3. 对于刚接触的人来说,比较晦涩难懂。比如:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
    4. 实际开发,一般都是直接复制写好的正则表达式。但是要求会使用正则表达式并且根据实际情况修改正则表达式。   比如用户名:   /^[a-z0-9_-]{3,16}$/

    二、在JS中的使用

    1、正则表达式的创建

            方式一:通过调用 RegExp 对象的构造函数创建

    1. var 变量名 = new RegExp(/表达式/);
    2. var regexp = new RegExp(/123/);
    3. console.log(regexp);

            方式二:利用字面量创建

    1. var 变量名 = /表达式/;
    2. var rg = /123/;

    2、测试正则表达式 

            test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。

            语法:     

    1. regexObj.test(str);
    2. var rg = /123/;
    3. console.log(rg.test(123)); // 匹配字符中是否出现123 出现结果为true
    4. console.log(rg.test('abc')); // 匹配字符中是否出现123 未出现结果为false

    regexObj 是写的正则表达式

    str 是要测试的文本

    就是检测 str 文本是否符合我们写的正则表达式规范

    三、特殊字符

    1、正则表达式的组成

            一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。

    特殊字符非常多,可以参考:

    MDN

    jQuery 手册:正则表达式部分

    正则测试工具

    2、边界符

            正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符:

    边界符说明
    ^表示匹配行首的文本(以谁开始)
    $表示匹配行尾的文本(以谁结束)

            如果 ^和 $ 在一起,表示必须是精确匹配。

    1. var rg = /abc/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型
    2. // /abc/ 只要包含有abc这个字符串返回的都是true
    3. console.log(rg.test('abc'));
    4. console.log(rg.test('abcd'));
    5. console.log(rg.test('aabcd'));
    6. console.log('---------------------------');
    7. var reg = /^abc/;
    8. console.log(reg.test('abc')); // true
    9. console.log(reg.test('abcd')); // true
    10. console.log(reg.test('aabcd')); // false
    11. console.log('---------------------------');
    12. var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范
    13. console.log(reg1.test('abc')); // true
    14. console.log(reg1.test('abcd')); // false
    15. console.log(reg1.test('aabcd')); // false
    16. console.log(reg1.test('abcabc')); // false

    3、字符类

            字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。

        1.[ ] 方括号

    1. /[abc]/.test('andy')    // true  
    2. // 后面的字符串只要包含 abc 中任意一个字符,都返回 true

        2.[-] 方括号内部 范围符-

    1. /^[a-z]$/.test('c')    // true  
    2. // 方括号内部加上 - 表示范围,这里表示 a 到 z 26个英文字母都可以

        3.[^] 方括号内部 取反符^

    1. /[^abc]/.test('andy')    // false  
    2. // 方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false

            注意和边界符 ^ 区别,边界符写到方括号外面。

        4.字符组合

    1. /[a-z1-9]/.test('andy')    // true  
    2. // 方括号内部可以使用字符组合,这里表示包含 a 到 z 的26个英文字母和 1 到 9 的数字都可以

    4、量词符

            量词符用来设定某个模式出现的次数。

    量词符说明
    *重复0次或更多次
    +重复1次或更多次
    重复0次或1次
    {n}重复n次
    {n,}重复n次或更多次
    {n,m}重复n到m次

    5、括号总结

    括号

    说明

    { }  大括号

    量词符。里面表示重复次数

    [ ]  中括号

    字符集合。匹配方括号中的任意字符

    ( )  小括号

    表示优先级

    菜鸟工具(正则表达式在线测试)

    6、预定义类

            预定义类指的是某些常见模式的简写方式。

    预定义类

    说明

    \d

    匹配 0~9 之间的任一数字,相当于[0-9]

    \D

    匹配所有 0~9 以外的字符,相当于[^0-9]

    \w

    匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]

    \W

    除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]

    \s

    匹配空格(包括换行符、制表符、空格符等),相当于[\t\r\n\v\f]

    \S

    匹配非空格的字符,相当于[^\t\r\n\v\f]

    例:

    1. 手机号验证: /^1[3|4|5|7|8][0-9]{9}$/
    2. QQ号验证: /^[1-9]\d{4,}$/
    3. 昵称验证: /^[\u4e00-\u9fa5]{2,8}$/
    4. 验证码验证 : /^\d{6}$/
    5. 密码验证: /^[a-zA-Z0-9_-]{6,16}$/

    四、替换

    1、replace 替换

            replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。

          语法:  

    1. stringObject.replace(regexp/substr,replacement)
    2. // 第一个参数: 被替换的字符串或者正则表达式
    3. // 第二个参数: 替换为的字符串
    4. // 返回值是一个替换完毕的新字符串

    2、参数

    /表达式/[switch]

    switch(也称为修饰符) 按照什么样的模式来匹配。有三种值:

    g:全局匹配

    i:忽略大小写

    gi:全局匹配 + 忽略大小写

    3、代码示例

    1. var str = 'andy和red';
    2. var newStr = str.replace('andy', 'baby');
    3. console.log(newStr); // baby和red
    4. // 等同于 此处的andy可以写在正则表达式内
    5. var newStr2 = str.replace(/andy/, 'baby');
    6. console.log(newStr2); // baby和red
    7. // 全部替换
    8. var str = 'abcabc';
    9. var nStr = str.replace(/a/,'哈哈');
    10. console.log(nStr); // 哈哈bcabc
    11. // 全部替换g
    12. var nStr = str.replace(/a/a,'哈哈');
    13. console.log(nStr); // 哈哈bc哈哈bc
    14. // 忽略大小写i
    15. var str = 'aAbcAba';
    16. var newStr = str.replace(/a/gi,'哈哈'); // "哈哈哈哈bc哈哈b哈哈"

    过滤敏感词汇: