JavaScript 网页编程(五)——JavaScript 高级

一.JavaScript 面向对象

1.面向对象编程介绍

  • 面向过程:按照分析好的 步骤 解决问题。
  • 面向过程实例:打开冰箱门,大象装进去,关上冰箱门
  • 面向对象:以 对象功能 来划分问题,而不是步骤。
  • 面向对象实例:大象对象(进去),冰箱对象(打开、关闭),使用冰箱和大象的功能
  • 面向对象的特性: 封装性 、继承性 、多态性
  • 面向过程优缺点:性能高(单片机),不易维护复用扩展,是份蛋炒饭
  • 面向对象优缺点:易维护复用扩展,低耦合,性能低,是份盖浇饭

2.ES6 中的类和对象

  • JavaScript 中,对象 是一组无序的属性(事物特征)和方法(事物行为)的集合,所有的事物都是对象
  • 面向对象思维特点:
  1. 抽取(抽象)对象 共用的属性 和 行为 组织(封装)成一个 (模板)
  2. 对 类 进行实例化,获取 对象
  • constructor 构造函数:用于传递参数,返回实例对象(通过 new 自动调用)
  • 方法之间 不能 逗号分隔,同时不需要添加 function 关键字
class Person {
 constructor(name,age) { // constructor 构造函数:用于传递参数返回实例对象(通过 new 自动调用)
 this.name = name;
 this.age = age;
 }    // 方法之间不能加 逗号 分隔,不需要 function 关键字
 say() {    
 console.log(this.name + '你好');
 }}    // 下面是创建实例
var ldh = new Person('刘德华', 18);    // 类必须使用 new 实例化对象
ldh.say()

3.类的继承

  • extends 关键字:子类继承父类
  • super 关键字:调用父类的 构造函数 / 普通函数
  • 继承中的属性或者方法查找原则:就近原则
  • 子类在构造函数中使用 super, 必须放到 this 前面 (必须先调用父类构造方法,再使用子类构造方法)
  • ES6 中,类没有变量提升,所以必须先定义类,再实例化对象.
  • 类里的 共有属性和方法 一定要加 this 
  • constructor 里的 this 指向 实例对象,方法里的 this 指向这个方法的调用者
class Father {    // 创建类 类名后不要加小括号
  constructor(surname) {
      // 类的共有属性放到 constructor 里
      this.surname = surname;    // 构造函数 this 指向 实例对象,new 生城实例 自动调用构造函数
  }  
  saySurname() {
      return '我的姓是' + this.surname;
  }} 
class Son extends Father {    // 子类继承父类 extends
  constructor(surname, fristname) {
      super(surname);    // super:调用父类构造函数 constructor(surname)
      this.fristname = fristname;    //定义子类独有属性 子类使用super, 必须放 this 前面
  }  
  sayFristname() {
      return super.saySurname() + ",我的名字是:" + this.fristname);  // super 调用父类方法
  }} 
var damao = new Son('刘', "德华");    // 实例化子类对象 此时类名后记得加小括号
damao.sayFristname();    // 我的姓是刘,我的名字是:德华
// =======================================================================================
// 子类继承父类加法方法 同时 扩展减法方法
 constructor(x, y) {
                super(x, y);
                this.x = x;
                this.y = y;
            }
            subtract() {
                console.log(this.x - this.y);
                }}

4.面向对象 Tab栏切换

  • 功能需求:
  1. 点击 + 号, 可以添加 tab 项和内容项
  2. 点击 x 号, 可以删除当前的tab项和内容项
  3. 双击 tab项文字 或者 内容项文字,可以修改文字内容
  • 抽象对象(Tab 对象)功能:切换、添加、删除、修改

4.1 面向对象版 tab 栏切换 添加功能

  • 点击 + 可以实现添加新的选项卡和内容
  • 第一步: 创建新的选项卡li 和 新的内容 section
  • 第二步: 把创建的两个元素追加到对应的父元素中.
  • 以前的做法:动态创建元素 createElemen,用 innerHTML赋值,用 appendChild 将内容追加到父元素里
  • 现在高级做法:利用 insertAdjacentHTML() 可以直接把 字符串元素 添加到父元素中
  • appendChild:不支持追加字符串的子元素;insertAdjacentHTML:支持追加字符串的元素
  • insertAdjacentHTML(追加的位置,‘要追加的字符串元素’)
  • 追加的位置:before / end 
  • 该方法地址: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/insertAdjacentHTML

4.2 面向对象版 tab 栏切换 删除功能

  • 点击 × 可以删除当前的 li 选项卡 和 当前的 section
  • x 是没有索引号的,但是它的父亲 li 有索引号
  • 动态删除新的 li 和 索引号 时,需要重新获取 x 这个元素. 需要调用 init 方法

4.3 面向对象版 tab 栏切换 编辑功能

  • 双击选项卡 li 或者 section 里面的文字,实现修改功能
  • 双击事件:ondblclick
  • 双击文字,会默认选定文字,此时需要禁止默认行为 双击选中文字
  • window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
  • 双击文字时,生成一个文本框,当失去焦点 或者 按下回车,就把文本框输入的值给原先元素
var that;
class Tab {
    constructor(id) {    // 构造函数中都要添加 this 这里写的是 固定不变的元素 动态添加的不可以写这
        that = this;
        this.main = document.querySelector(id);
        this.add = this.main.querySelector('.tabadd');    // 添加按钮
        this.ul = this.main.querySelector('.fisrstnav ul:first-child');   // li的父元素 
        this.fsection = this.main.querySelector('.tabscon');   // section 父元素
        this.init();    // 初始化
    }
    init() {
            this.updateNode();    // 重新获取 动态添加的 以及 原来就有的 各种元素
            // init 初始化操作让相关的元素绑定事件
            this.add.onclick = this.addTab;    // 点击后调用 addTab(此处调用方法不加括号)
            for (var i = 0; i < this.lis.length; i++) {
                this.lis[i].index = i;    // 设置每个导航栏索引号
                this.lis[i].onclick = this.toggleTab;    // 点击切换功能
                this.remove[i].onclick = this.removeTab;    // 点击删除功能
                this.spans[i].ondblclick = this.editTab;    // 双击编辑导航栏标签功能
                this.sections[i].ondblclick = this.editTab;    // 双击编辑文本框内容
            }}
        // 因为动态添加元素 需要重新获取对应的元素
    updateNode() {
            this.lis = this.main.querySelectorAll('li');    // 导航栏标签
            this.sections = this.main.querySelectorAll('section');    // 内容部分
            this.remove = this.main.querySelectorAll('.icon-guanbi');    // 删除按钮
            this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');    // 导航栏标签修改
        }
        // 1. 切换功能
    toggleTab() {
            that.clearClass();
            this.className = 'liactive';
            that.sections[this.index].className = 'conactive';
        }
        // 清除所有li 和section 的类
    clearClass() {
            for (var i = 0; i < this.lis.length; i++) {
                this.lis[i].className = '';
                this.sections[i].className = '';
            }}
        // 2. 添加功能
    addTab() {
            that.clearClass();
            // (1) 创建li元素和section元素 
            var random = Math.random();
            var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"></span></li>';
            var section = '<section class="conactive">测试 ' + random + '</section>';
            // (2) 把这两个元素追加到对应的父元素里面
            that.ul.insertAdjacentHTML('beforeend', li);
            that.fsection.insertAdjacentHTML('beforeend', section);
            that.init();
        }
        // 3. 删除功能
    removeTab(e) {
            e.stopPropagation(); // 阻止冒泡 防止触发li 的切换点击事件
            var index = this.parentNode.index;    // 删除按钮的索引号 是 父亲li 的索引号
            // 根据索引号删除对应的li 和section   remove()方法可以直接删除指定的元素
            that.lis[index].remove();
            that.sections[index].remove();
            that.init();
            // 当删除的不是选中状态的li 时,原来的选中状态li 保持不变
            if (document.querySelector('.liactive')) return;
            // 当删除了选中状态的li 时, 让它的前一个li 处于选定状态 手动调用我们的点击事件
            index--;
            that.lis[index] && that.lis[index].click();
        }
        // 4. 修改功能
    editTab() {
        var str = this.innerHTML;
        // 双击禁止选定文字
        window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
        this.innerHTML = '<input type="text" />';    // 修改相当于写了一个文本框然后赋值给标签
        var input = this.children[0];
        input.value = str;
        input.select(); // 文本框里面的文字处于选定状态
        // 离开文本框就把文本框的值给span 
        input.onblur = function() {
            this.parentNode.innerHTML = this.value;
        };
        // 按下回车也可以把文本框里面的值给span
        input.onkeyup = function(e) {
            if (e.keyCode === 13) {    
                this.blur();    // 手动调用表单失去焦点事件 
            }}}}
new Tab('#tab');

 

二.构造函数和原型

1.基本概念

  • ES6,全称 ECMAScript 6.0,ES6之前 ,对象不基于类创建,而是用名为 构建函数 的特殊函数来定义
  • 创建对象可以通过以下三种方式:
  1. 对象字面量
  2. new Object()
  3. 自定义构造函数
        // 1. 利用 new Object() 创建对象
        var obj1 = new Object();
        // 2. 利用 对象字面量创建对象
        var obj2 = {};
        // 3. 利用构造函数创建对象
        function Star(uname, age) {
            this.uname = uname;
            this.age = age;
            this.sing = function() {
                console.log('我会唱歌');
            }}
        var ldh = new Star('刘德华', 18);
        ldh.sing();

1.1 构造函数、静态成员、实例成员

  • 构造函数:用来为对象成员变量赋初始值,它总与 new 一起使用,首字母要大写
  • new 在执行时会做四件事:
  1. 在内存中创建新的空对象
  2. 让 this 指向新的对象
  3. 执行构造函数代码,给这个新对象添加属性和方法
  4. 返回新对象(所以构造函数里不需要 return )
  • 成员:构造函数中的 属性和方法,成员可以添加
  • 静态成员:在构造函数 本身上添加的 成员称为 静态成员,只能由构造函数本身来访问
  • 实例成员:在构造函数 内部用this添加的 成员称为 实例成员,只能由实例化的对象来访问
        function Star(uname) {
            this.uname = uname;   // 1.实例成员:构造函数内部通过this添加的成员 uname 是实例成员
        }}
        var ldh = new Star('刘德华', 18);
        console.log(ldh.uname);    // 实例成员 只能通过 实例化的对象来访问
        // console.log(Star.uname); // 实例成员 不可以 通过构造函数来访问 

        Star.sex = '男';    // 2. 静态成员:在构造函数本身上添加的成员  sex 是静态成员
        console.log(Star.sex);    // 静态成员 只能通过 构造函数访问
        // console.log(ldh.sex); // 静态成员 不能通过 对象访问
  • 构造函数存在 内存浪费问题:

1.2 prototype 和 __proto__

  • 原型对象 prototype:每个构造函数都有 prototype 对象,又名构造函数原型,用于共享方法(公共属性方法)
  • 一般情况下,公共属性 定义到 构造函数里, 公共方法 定义到 原型对象身上
  • 构造函数 和 原型对象prototype 里的 this 都指向 实例对象
       function Star(uname) {
            this.uname = uname; // 公共属性 定义到 构造函数里
        }
        Star.prototype.sing = function() { // 公共方法 放到 原型对象身上
            console.log('我会唱歌');
        }
        var ldh = new Star('刘德华');
        var zxy = new Star('张学友');
        console.log(ldh.sing === zxy.sing);    // true 此时表明 不会给相同的方法新开辟一个空间
        ldh.sing();
        console.log(ldh.__proto__ === Star.prototype);    // true 对象__proto__指向 原型对象
        // 方法的查找规则: 首先看ldh 对象身上是否有 sing 方法,如果有就执行
        // 没有sing 方法,因为__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing 方法
  • 对象原型 __proto__:每个对象都有,对象可以使用 构造函数原型 prototype 共享的方法,是通过这个实现的
  • constructor 属性:对象原型( __proto__)和构造函数(prototype)原型对象里面都有,称为构造函数
  • 如果原型对象赋值了一个对象,则必须手动调用 constructor属性 指回原来的构造函数
  •   Star.prototype = {
                // 如果修改了原来的原型对象,给原型对象赋值了一个对象
                // 则必须手动调用 constructor属性 指回原来的构造函数
                constructor: Star,
                sing: function() {   
                    console.log('我会唱歌');
                },
                movie: function() {
                    console.log('我会演电影');
                }}
            var ldh = new Star('刘德华');
            console.log(Star.prototype.constructor);    // 指回原来的构造函数
            console.log(ldh.__proto__.constructor);    // 指回原来的构造函数

    ​​​​​​

1.3 JavaScript 成员查找机制

  • 原型链:
        // 1.只要是对象,就有__proto__ 原型, 指向原型对象 prototype
        console.log(Star.prototype);
        console.log(Star.prototype.__proto__ === Object.prototype);    // true
        // 2.Star原型对象的__proto__指向 Object.prototype
        console.log(Object.prototype.__proto__);    // null
        // 3.Object.prototype原型对象的__proto__指向为 null
  • JavaScript 的成员查找机制:
  1. 当访问一个对象的属性(包括方法)时,首先查找 对象自身 有没有该属性
  2. 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象
  3. 如果还没有就查找原型对象的原型(Object 原型对象
  4. 依此类推一直找到 Object 为止(null
  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线

1.4 扩展内置对象

  • 可以通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给数组增加自定义求偶数和的功能
  • 数组和字符串内置对象 不能覆盖 原型对象 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式
Array.prototype.sum = function() {
            var sum = 0;
            for (var i = 0; i < this.length; i++) {
                sum += this[i];
            }
            return sum; };
        // Array.prototype = {    // 数组和字符串对象不可以对原型对象做覆盖赋值操作
        //     sum: function() {
        //         var sum = 0;
        //         for (var i = 0; i < this.length; i++) {
        //             sum += this[i];}
        //         return sum;}}

        var arr = [1, 2, 3];
        console.log(arr.sum());

2.继承

  • ES6之前并没有提供 extends 继承,可以通过 构造函数+原型对象 模拟实现继承,称为 组合继承

2.1 call()

  • call():调用函数,把父类型的 this 指向子类型的 this,实现子类型继承父类型的属性
 function Person(name) {    // 父类
 this.name = name;
 }
 function Student(name, score) {    // 子类
 Person.call(this, name); // 此时父类的 this 指向子类的 this,同时调用这个函数
 this.score = score;
 }
 var s1 = new Student('茶茶子',100);

2.2 借用原型对象继承父类方法

  • 让子类的 prototype 原型对象 = new 父类(),子类 constructor 重新指向子类的构造函数
  • 本质:子类原型对象 = 实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
        // 借用父构造函数继承属性
        function Father(uname, age) { // 1. 父构造函数 
            this.uname = uname;   // this 指向父构造函数的对象实例
            this.age = age;
        }
        Father.prototype.money = function() {
            console.log(100000);
        }; 
        function Son(uname, age, score) { // 2 .子构造函数 
            Father.call(this, uname, age);    // this 指向子构造函数的对象实例
            this.score = score;
        }
        // Son.prototype = Father.prototype;  直接赋值会有问题,修改子原型对象,父原型对象也会变化
        Son.prototype = new Father(); // 利用 对象赋值的形式 修改原型对象
        Son.prototype.constructor = Son;    // 此时要用 constructor 指回原来对象
        Son.prototype.exam = function() {    // 子构造函数专门的方法
            console.log('孩子要考试');
        }
        var son = new Son('刘德华', 18, 100);
        console.log(son);    // 刘德华实例
        console.log(Father.prototype);    // 父原型函数
        console.log(Son.prototype.constructor);    // 子构造函数

3.ES5 中的新增方法

3.1 数组方法 forEach()、filter()、some()

  • 迭代(遍历)方法:array.forEach(function(每个数组元素, 每个数组元素的索引号, 数组本身))
        var arr = [1, 2, 3];
        var sum = 0;
        arr.forEach(function(value, index, array) {    // forEach 遍历数组
            console.log('每个数组元素' + value);
            console.log('每个数组元素的索引号' + index);
            console.log('数组本身' + array);
            sum += value;
        })
        console.log(sum);
  • 筛选数组(返回新数组):array.filter(function(每个数组元素, 每个数组元素的索引号, 数组本身))
        var arr = [12, 66, 4, 88, 3, 7];    // filter 筛选数组
        var newArr = arr.filter(function(value, index) {    // 这个方法会返回新数组
            return value % 2 === 0;    // 返回值是偶数的数组
        });
        console.log(newArr);
  • 查找数组(返回布尔值,只查到第一个满足条件的):array.some(function(每个数组元素, 每个数组元素的索引号, 数组本身))
        var arr1 = ['red', 'pink', 'blue'];
        var flag1 = arr1.some(function(value) {    // 返回布尔值
            return value == 'pink';    
        });
        console.log(flag1);
  • filter :返回的是一个数组 把所有满足条件的元素返回
  • some :返回的是一个布尔值 把查找到第一个满足条件的元素返回
  • 关于 return true; 在 forEach 和 filter 里都不会种植迭代遍历,效率低,但是在 some 里可以终止迭代遍历,效率高
  • 查询商品案例:
  1. 把数据渲染到页面中 (forEach)
  2. 根据价格显示数据 (filter,可能有多个)
  3. 根据商品名称显示数据(some,只能有一个)
        // 利用新增数组方法操作数据
        var data = [{
            id: 1,
            pname: '小米',
            price: 3999
        }, {
            id: 2,
            pname: 'oppo',
            price: 999
        }, ....];
        // 1. 获取相应的元素
        var tbody = document.querySelector('tbody');    // 表格身体部分
        var search_price = document.querySelector('.search-price');    // 查询价格按钮
        var start = document.querySelector('.start');    // 最低价
        var end = document.querySelector('.end');    // 最高价
        var product = document.querySelector('.product');    // 产品名输入框
        var search_pro = document.querySelector('.search-pro');    // 查询产品名按钮
        setDate(data);
        // 2. 把数据渲染到页面中(forEach循环)
        function setDate(mydata) {
            tbody.innerHTML = '';    // 先清空原来tbody 里面的数据
            mydata.forEach(function(value) {
                var tr = document.createElement('tr');    // 创建行
                tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>';
                tbody.appendChild(tr);
            });}
        // 3. 根据价格查询商品(filter查询所有符合条件的商品) 点击按钮后触发
        search_price.addEventListener('click', function() {
            var newDate = data.filter(function(value) {
                return value.price >= start.value && value.price <= end.value;
            });  
            setDate(newDate);   // 把筛选完之后的对象渲染到页面中
        });
        // 4. 根据商品名称查找商品(some查询符合条件的唯一商品) 点击按钮后触发
        search_pro.addEventListener('click', function() {
            var arr = [];
            data.some(function(value) {
                if (value.pname === product.value) {    // 数组元素的pname属性 = 输入的商品名
                    arr.push(value);    // 将数组对象元素追加到空数组中
                    return true; // 在some 里面 遇到 return true 就是终止遍历 迭代效率更高
                }});
            setDate(arr);    // 把筛选完之后的对象渲染到页面中
        })

3.2 字符串方法 str.trim()

  • str.trim() :删除字符串两面的空白,返回新的字符串,不会影响字符串本身
  • 判断用户输入是否为空(直接清除空格判断):
        var input = document.querySelector('input');
        var btn = document.querySelector('button');
        var div = document.querySelector('div');
        btn.onclick = function() {
            var str = input.value.trim();    // 将输入框的值 去除空格后 存在新变量里
            if (str === '') {    // 判断这个新变量是否为空
                alert('请输入内容');
            } else {
                div.innerHTML = str;    // 不为空所以追加
            }}

3.3 对象方法 Object.keys()、Object.defineProperty()

  • Object.keys(obj) :获取对象自身所有的属性,返回一个所有元素为 字符串的 数组,效果类似 for...in
        var obj = {
            id: 1,
            pname: '小米',
            price: 1999,
            num: 2000
        };
        var arr = Object.keys(obj);
        arr.forEach(function(value) {
            console.log(value);
        })
  • Object.defineProperty(目标对象,需要定义/修改的属性名字,属性特性):定义新属性或修改原有的属性
  • 属性特性说明:
  1. value: 设置属性的值
  2. writable: 值是否可以重写 true | false(默认)
  3. enumerable: 目标属性是否可以被枚举(遍历)true | false(默认)
  4. configurable: 目标属性是否可以被删除 或 再次修改特性 true | false(默认)
        var obj = {
            id: 1,
            pname: '今夜不再',
            price: 9999
        };
        // obj.num = 1000; // 1. 以前的对象添加和修改属性的方式
        // 2. Object.defineProperty() 定义新属性或修改原有的属性
        Object.defineProperty(obj, 'address', {    // 这是给 obj对象 添加了一个属性
            value: '第五人格茶茶子的庄园', 
            writable: true, // writable:如果值为false 则不允许修改这个属性值 默认false  
            enumerable: true, // enumerable:如果值为false 则不允许遍历 默认false 
            configurable: true // configurable:如果值为false 则不允许删除这属性 默认false
        });
        console.log(obj.address);

三.函数进阶

1.定义和调用函数

1.1 定义方法

  1. 自定义函数(命名函数)
  2. 函数表达式(匿名函数)
  3. 利用 new Function('参数1','参数2', '函数体');
  4. 所有函数都是 Function 的实例(对象) 函数也属于对象
        // 1. 自定义函数(命名函数) 
        function hanshuming() {};
        // 2. 函数表达式 (匿名函数) fun是变量名不是函数名
        var fun = function() {};
        // 3. 利用 new Function('参数1','参数2', '函数体');
        var f = new Function('a', 'b', 'console.log(a + b)');
        f(1, 2);
        // 4. 所有函数都是 Function 的实例(对象) 函数也属于对象
        console.log(f instanceof Object);

1.2 调用方法

  1. 普通函数
  2. 对象内的函数
  3. 构造函数
  4. 绑定事件函数
  5. 定时器函数
  6. 立即执行函数
        // 1. 普通函数方法 this 指向window
        function fn() {
            console.log('普通函数的this' + this);    }
        window.fn();    // 可以省略 windows 直接调用方法

        // 2. 对象的方法 this指向的是对象 o
        var o = {
            sayHi: function() {
                console.log('对象方法的this:' + this);    }}
        o.sayHi();    // 通过对象调用

        // 3. 构造函数 this 指向 ldh 实例对象 原型对象的this 指向的也是 ldh实例对象
        function Star() {};
        Star.prototype.sing = function() {}
        var ldh = new Star();    // 构造函数通过 new 调用方法

        // 4. 绑定事件函数 this 指向的是函数的调用者 btn按钮对象
        var btn = document.querySelector('button');
        btn.onclick = function() {    // 点击后就会调用函数
            console.log('绑定时间函数的this:' + this);
        };

        // 5. 定时器函数 this 指向的也是window
        window.setTimeout(function() {
            console.log('定时器的this:' + this);
        }, 1000);    // 时间到了自动调用这个函数

        // 6. 立即执行函数 this还是指向window
        (function() {
            console.log('立即执行函数的this' + this);    // 直接自动调用函数
        })();

2.改变函数内部 this 指向 

  • 这些 this 的指向,调用函数的时候确定,调用方式的不同决定了 this 指向不同,一般指向调用者
  • 改变函数内部指向 this:bind()、call()、apply() 
  • 区别点:
  1. call 和 apply 会调用函数,并且改变函数内部this指向
  2. call 和 apply 传递的参数不一样,call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg]
  3. bind 不会调用函数,可以改变函数内部 this指向
  • 主要应用场景:
  1. call 经常做继承
  2. apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
  3. bind 不调用函数,但是还想改变this指向,比如改变定时器内部的this指向

2.1 call():继承相关

        // call 可以调用函数,可以改变函数内的this 指向,主要作用可以实现继承
        function Father(uname) {
            this.uname = uname;    }
        function Son(uname) {
            Father.call(this, uname);    }
        var son = new Son('刘德华');
        console.log(son);

2.2 apply():数组相关

        // apply 参数必须是数组(伪数组),主要应用 利用apply 借助于数学内置对象求数组最大值 
        var arr = [1, 66, 3, 99, 4];
        var max = Math.max.apply(Math, arr);
        var min = Math.min.apply(Math, arr);
        console.log(max, min);

2.3 bind():绑定相关

        // 1. 不会调用原来的函数   可以改变原来函数内部的this 指向
        // 2. 如果有的函数不需要立即调用,但是又想改变函数内部的this指向 此时用 bind
        var btns = document.querySelectorAll('button');
        for (var i = 0; i < btns.length; i++) {
            btns[i].onclick = function() {
                this.disabled = true;
                setTimeout(function() {
                    this.disabled = false; // 定时器原本的this指向windows,windows没有禁用属性
                }.bind(this), 2000);    // 修改绑定后this指的是定时器外面的 btns
            }}

3.严格模式

  • 严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略
  • 严格模式分为 为脚本开启严格模式(整个脚本)  为函数开启严格模式(个别函数) 两种情况
  • "use strict";
    <!-- 为整个脚本(script标签)开启严格模式 -->
    <script>
        'use strict';
    </script>
    <script>
        (function() {
            'use strict';    })();
    </script>

    <!-- 为某个函数开启严格模式 -->
    <script> 
        function fn() {    // 此时只是给fn函数开启严格模式
            'use strict';    // 下面的代码按照严格模式执行     }
        function fun() {    // 里面的还是按照普通模式执行     }
    </script>
  • 严格模式中的一些变化:
  • 变量规定:禁止没声明就赋值,禁止删除已经声明的变量
  • 函数规定:不能有重名参数,函数必须声明在顶层
  • this指向:以前全局作用域函数中的 this 指向 Windows,严格模式全局作用域函数中的 this 是 undefined
  • 构造函数this:以前构造函数不写 new,会指向全局对象,严格模式中不加new,会 undefined,若赋值就报错
  • 和以前没区别的:new 实例化的构造函数指向对象实例,定时器this 指向Windows,事件对象this 指向调用者

4.高阶函数

  • 高阶函数:接收函数作为参数(回调函数) 或 将函数作为返回值输出
// 接收函数作为参数(回调函数)
function fn(callback){
     callback&&callback();  // 如果有回调函数就直接调用  }
fn(function(){alert('hi')}

// 接收函数作为返回值
function fn(){
     return function() {}    }
fn();

5.闭包

  • 变量作用域:函数内可以使用全局变量,函数外不可以使用局部变量,函数执行完毕后本作用域内的局部变量会销毁
  • 闭包(closure):有权访问 另一个函数作用域中的 变量的函数,用于延伸变量的作用范围
  • 在 chrome 中调试闭包:
  1. 打开浏览器,按 F12 键启动 chrome 调试工具
  2. 设置断点,找到 Scope(作用域) 选项
  3. 重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)
  4. 执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包
  • 怎么能在 fn() 函数外面访问 fn() 中的局部变量 num 呢 ?
function fn() { 
     var num = 10; 
     return function { 
         console.log(num); // 10 
         }}
var f = fn();
f()
  • 循环注册点击事件
    <ul class="nav">
        <li>小可爱</li>
        ...
    </ul>
        // 闭包应用-点击li 输出 当前li 索引号
        var lis = document.querySelector('.nav').querySelectorAll('li');
        for (var i = 0; i < lis.length; i++) {
            lis[i].index = i;
            lis[i].onclick = function() {
                console.log(this.index);
            }}
        // 2. 利用闭包的方式得到当前小li 的索引号
        for (var i = 0; i < lis.length; i++) {
            // 利用 for循环创建了 4个立即执行函数
            // 立即执行函数也成为闭包 因为立即执行函数里的 任何函数都可以 使用它的i 变量
            (function(i) {
                lis[i].onclick = function() {
                    console.log(i);
                }})(i);}
  • 循环中的 setTimeout()
    <ul class="nav">
        <li>榴莲</li>
        ...
    </ul>
    <script>
        // 闭包应用-3秒钟之后,打印所有li元素的内容
        var lis = document.querySelector('.nav').querySelectorAll('li');
        for (var i = 0; i < lis.length; i++) {
            (function(i) {
                setTimeout(function() {
                    console.log(lis[i].innerHTML);
                }, 3000)
            })(i);}
  • 计算打车价格
        // 闭包应用-计算打车价格 
        // 打车起步价13(3公里内),之后每多一公里增加 5块钱.用户输入公里数就可以计算打车价格
        // 如果有拥堵情况,总价格多收取10块钱拥堵费
        var car = (function() {
            var start = 13; // 起步价  局部变量
            var total = 0; // 总价  局部变量
            return {
                price: function(n) {    // 正常的总价
                    if (n <= 3) {
                        total = start;
                    } else {
                        total = start + (n - 3) * 5     }
                    return total;
                }, 
                yd: function(flag) {     // 拥堵之后的费用
                    return flag ? total + 10 : total;
                }}})();
        console.log(car.price(5)); // 23
        console.log(car.yd(true)); // 33

 6.递归

  • 由于递归很容易发生“栈溢出”错误(stack overflow),所以 必须要加退出条件 return
  • 求 1 * 2 *3 ... * n 阶乘
        function fn(n) {
            if (n == 1) {
                return 1;    }
            return n * fn(n - 1);    }
  • 求斐波那契数列 (兔子序列)
        // 利用递归函数求斐波那契数列(兔子序列)  1、1、2、3、5、8、13、21...
        function fb(n) {
            if (n === 1 || n === 2) {
                return 1;    }
            return fb(n - 1) + fb(n - 2);    }
  • 根据id 返回对应的数据对象
        var data = [{
            id: 1,
            name: '家电',
            goods: [{
                id: 11,
                gname: '冰箱',
                goods: [{
                    id: 111,
                    gname: '海尔'
                }, {
                    id: 112,
                    gname: '美的'
                }, ]
            }, {
                id: 12,
                gname: '洗衣机'
            }]
        }, {
            id: 2,
            name: '服饰'
        }];
        // 1. 利用 forEach 遍历每一个对象
        function getID(json, id) {    // json数据集 id是用户输入的id
            var o = {};
            json.forEach(function(item) {    // item表示数组元素
                if (item.id == id) {    // 如果数组元素的id = 用户输入的id 
                    o = item;    // 把满足条件的对象存到 对象o 中
                    // 2. 想要得里层的数据 11 12 可以利用递归函数
                    // 里面有goods这个数组 并且 数组的长度不为 0 
                } else if (item.goods && item.goods.length > 0) {
                    o = getID(item.goods, id);
                } });
            return o;}
        console.log(getID(data, 112));

7.深拷贝、浅拷贝

  • 浅拷贝(Object.assign()):只是拷贝一层,更深层次对象级别的只拷贝引用(对象地址)
  • 深拷贝(自己封装函数):拷贝多层,每一级别的数据都会拷贝.
     var obj = {
            id: 1,
            name: 'andy',
            msg: {
                age: 18
            },
            color: ['pink', 'red']
        };
        var o = {};
        function deepCopy(newobj, oldobj) {    // 深拷贝封装函数
            for (var k in oldobj) {
                var item = oldobj[k];   // 1. 获取属性值  oldobj[k]
                if (item instanceof Array) {   // 2. 判断这个值是否是数组
                    newobj[k] = [];
                    deepCopy(newobj[k], item)
                } else if (item instanceof Object) {   // 3. 判断这个值是否是对象
                    newobj[k] = {};
                    deepCopy(newobj[k], item)
                } else {    // 4. 属于简单数据类型
                    newobj[k] = item;
                }}}
        deepCopy(o, obj);    // 深拷贝
        console.log('--------------');
        Object.assign(o, obj);    // 浅拷贝

 

四.正则表达式

1.创建使用正则表达式

  • 通过调用 RegExp 对象的构造函数创建 :var 变量名 = new RegExp(/表达式/);
  • 通过字面量创建 :var 变量名 = /表达式/;
  • 写好的正则表达式.test(测试文本)
  • 正则测试工具: http://tool.oschina.net/regex

2.特殊字符

  • 括号总结及在线测试:https://c.runoob.com/
  1. 大括号:量词符. 里面表示重复次数
  2. 中括号:字符集合 匹配方括号中的任意字符.
  3. 小括号:表示优先级
        var rg = /abc/;    // 包含 abc 整体字符串
        console.log(rg.test('abcd')); // true
        var reg = /^abc/;    // 以 abc 整体开头
        console.log(reg.test('abcd')); // true
        console.log(reg.test('aabcd')); // false
        var reg1 = /^abc$/; // 精确匹配 必须是 abc 字符串
        console.log(reg1.test('abcd')); // false
        var reg2 = /[abc]/; // 包含有a 或者 包含有b 或者包含有c 
        var reg3 = /^[a-z]$/; // 只有是a-z 任一字母
        var reg4 = /^[^a-zA-Z0-9_-]$/; // 中括号里面有^ 表示取反 任意一个值都不是
        var reg5 = /^[a-zA-Z0-9_-]{6,16}$/; // 标准用户名
        var reg6 = /^abc{3}$/; // 让c重复三次 abccc
        var reg7 = /^(abc){3}$/; // 让abc整体重复三次 abcabcabc
        var reg = /^\d{3,4}-\d{7,8}$/; // 座机号 0631-5129136 010-12345678
  • 常见表单验证:
  1. 手机号码: /^1[3|4|5|7|8][0-9]{9}$/
  2. QQ: [1-9][0-9]{4,} (腾讯QQ号从10000开始)
  3. 昵称是中文: ^[\u4e00-\u9fa5]{2,8}$
window.onload = function() {
    var regtel = /^1[3|4|5|7|8]\d{9}$/; // 手机号码的正则表达式
    var regqq = /^[1-9]\d{4,}$/; // qq号正则表达式
    var regnc = /^[\u4e00-\u9fa5]{2,8}$/;    // 昵称正则表达式(中文)
    var regmsg = /^\d{6}$/;    // 短信验证码正则表达式
    var regpwd = /^[a-zA-Z0-9_-]{6,16}$/;    // 密码正则表达式
    var tel = document.querySelector('#tel');
    var qq = document.querySelector('#qq');
    var nc = document.querySelector('#nc');
    var msg = document.querySelector('#msg');
    var pwd = document.querySelector('#pwd');
    regexp(tel, regtel); // 手机号码
    regexp(qq, regqq); // qq号码
    regexp(nc, regnc); // 昵称
    regexp(msg, regmsg); // 短信验证
    regexp(pwd, regpwd); // 密码框
    // 表单验证的函数
    function regexp(ele, reg) {
        ele.onblur = function() {    // 元素失去焦点后 开始和正则表达式作比较
            if (reg.test(this.value)) {
                this.nextElementSibling.className = 'success';
                this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 恭喜您输入正确';
            } else {
                this.nextElementSibling.className = 'error';
                this.nextElementSibling.innerHTML = '<i class="error_icon"></i> 格式不正确,请从新输入 ';
            }}};}

4.正则表达式中的替换

  • stringObject.replace(被替换的字符串,替换为的字符串
  • /表达式/[switch]switch(也称为修饰符) 按照什么样的模式来匹配. 有三种值:
  1. g:全局匹配
  2. i:忽略大小写
  3. gi:全局匹配 + 忽略大小写
  • 敏感词过滤:
        var text = document.querySelector('textarea');
        var btn = document.querySelector('button');
        var div = document.querySelector('div');
        btn.onclick = function() {
            div.innerHTML = text.value.replace(/激情|gay/g, '**');
        }
 

五.ES6

1.ES6 简介

  • ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的 一项脚本语言的标准化规范
  • ES6 实际上是一个泛指,泛指 ES2015 及后续的版本

2.ES6 的新增语法

2.1 Let(声明变量)

  • let 声明的变量只在所处于的块级有效,不存在变量提升,会有暂时性死区
if (true) {    // let 声明的变量只在所处于的块级有效
    let a = 10;
}
console.log(a) // a is not defined

console.log(b); // a is not defined 
let b = 20;    // 不存在变量提升

var tmp = 123;
if (true) {
 tmp = 'abc';
 let tmp;    // 暂时性死区
}
  • 使用 let 声明的变量才具有块级作用域,使用 var 声明的变量不具备块级作用域 

2.2 const(声明常量)

  • const 具有块级作用域,用于声明常量,常量就是值(内存地址)不能变化的量
if (true) {
     const a = 10; }
console.log(a) // a is not defined 具有块级作用域

const PI; // Missing initializer in const declaration 常量必须赋初值,不赋值会报错

const PI = 3.14;    
PI = 100; // Assignment to constant variable. 常量赋值后不可以被修改

const ary = [100, 200];
ary[0] = 'a';    // 数组可以用这种方式修改值
ary[1] = 'b';
console.log(ary); // ['a', 'b']; 
ary = ['a', 'b']; // Assignment to constant variable.    数组不可以用这种方式修改值
  • 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
  • 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
  • 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值

2.3 解构赋值

        // 数组解构:允许按照 一一对应的关系 从数组中提取值 然后将值赋值给变量
        let ary = [1,2,3];
        let [a, b, c, d, e] = ary;

        // 对象解构:允许 使用变量的名字 匹配 对象的属性 匹配成功 将对象属性的值赋值给变量
        let person = { name: 'zhangsan', age: 20 };
        let { name, age } = person;
        console.log(name); // 'zhangsan' 
        let {name: myName, age: myAge} = person; // myName myAge 属于别名
        console.log(myName); // 'zhangsan' 

2.4 箭头函数

  • 箭头函数中 如果函数体中只有一句代码 且执行结果是函数的返回值 函数体大括号可以省略
  • 箭头函数中 如果形参只有一个 形参外侧的小括号也是可以省略的
  • 箭头函数没有自己的this关键字 若有 则将指向 箭头函数定义位置中的this(经典面试)
		// 在箭头函数中 如果函数体中只有一句代码 且执行结果是函数的返回值 函数体大括号可以省略
		// const sum = (n1, n2) => n1 + n2;	 
		// 在箭头函数中 如果形参只有一个 形参外侧的小括号也是可以省略的
		// const fn = v => {    alert(v);    }

2.5 剩余参数

let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu' 
console.log(s2); // ['zhangsan', 'lisi']

3.ES6 的内置对象扩展

3.1 Array 的扩展方法

  • 扩展运算符...:将 数组或者对象 转为 用逗号分隔的参数序列
  • 合并数组
  • 将类数组或可遍历对象转换为真正的数组
let ary = [1, 2, 3];
console.log(...ary); // 1 2 3 将数组转换为字符串
// 合并数组 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];    // [1,2,3,4,5,6]
// 合并数组 方法二
ary1.push(...ary2);
// 将类数组或可遍历对象转换为真正的数组
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
  • 构造函数方法 Array.from():可以接受两个参数,类似于数组的map方法,对每个元素进行处理并返回的数组
let arrayLike = {
     "0": 1,
     "1": 2,
     "length": 2 }
let newAry = Array.from(aryLike, item => item *2) // [2,4]
  • find() :找出第一个符合条件的数组成员,没有就返回 undefined
let ary = [{
     id: 1,
     name: '张三‘
}, {
     id: 2,
     name: '李四‘
}];
let target = ary.find((item, index) => item.id == 2);
  • findIndex() :找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2 这是索引号
  • includes():表示某个数组是否包含给定的值,返回布尔值
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false

3.2 String 的扩展方法

  • 模板字符串${}:可以解析变量,可以换行,可以调用函数
let name = '张三';
let sayHello = `hello,my name is ${name}`; // hello, my name is zhangsan 解析变量

let result = {
     name: 'zhangsan',
     age: 20,
     sex: '男' }
let html = ` <div>
     <span>${result.name}</span>    // 实现换行
     <span>${result.age}</span>
     <span>${result.sex}</span>
    </div> `;

const sayHello = function () {
     return '哈哈哈哈 追不到我吧 我就是这么强大';
};
let greet = `${sayHello()} 23333';    // 调用函数
console.log(greet); // 哈哈哈哈 追不到我吧 我就是这么强大 23333
  • startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
  • endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
  • repeat() :表示将原字符串重复n次,返回一个新字符串
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"

3.3 Set 数据结构

  • 数据结构 Set:它类似于数组,但成员没有重复值
const s = new Set();    // Set本身是一个构造函数,用来生成 Set 数据结构
const set = new Set([7, 8, 4]);    // Set函数可以接受一个数组作为参数,用来初始化
s.add(1).add(2).add(3);    // 向 set 结构中添加值
s.delete(2)    // 删除 set 结构中的2值 
s.has(1)    // 表示 set 结构中是否有1这个值 返回布尔值
s.clear()    // 清除 set 结构中的所有值

s.forEach(value => console.log(value))    // 遍历 没有返回值

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyrelion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值