JavaScript高级进阶

258 阅读18分钟

JavaScript高级进阶

编程思想介绍:1、面向过程;2、面向对象

面向过程:分析出解决问题的所需步骤,然后用函数把这些步骤一步一步的实现,使用的时候再一个一个的一次调用就可以了;面向过程,就是按照我们分析好的步骤,按照步骤解决问题。

面向对象:是把事务分解成为每一个对象,然后由对象之间分工和合作;

面向对象:是以对象功能进行划分问题,而不是步骤;

面向对象的特性:1、继承性、2、封装性、3、多态性

面向对象的特点:

1、抽取对象公用的属性和行为组织(封装)成一个类(模板);

2、对类进行实例化,获取类的对象;

面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点不断的创建对象,使用对象,指挥对象做事情。

2.1对象Object:

是一个具体的事物;

在JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等;

对象是由属性和方法组成的;

属性:事物的特征,在对象中使用属性来表示(常用名词);

方法:事物的行为:在对象中用方法来表示(常用动词)

2.2类class

在ES6新增加了类的概念,可以使用class关键字声明一个类,之后以这个类来实例化对象,

类抽象了对象的公共部分,它泛指某一大类(class)

对象特指某一个,通过类实例化一个具体的对象;

3.1 继承

现实中的继承,子承父业,比如我们都继承了父母的姓

<script>
    class Father{
        constructor(){}
        money(){
            console.log(100+'元');
        }
    }    
    class Son extends Father{
        
    }
    vae son = new Son();
    son.money(); //输出结果100
</script>

3.2 super关键字

super关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数;

<html>
<script>
        class Person{   //父亲
            constructor(x,y){
                this.x = x;
                this.y = y;
            }
            sum (){
                console.log(this.x + this.y);
            }
        }
        class Man extends Person{   //子类
            constructor(x,y){
                //如果要使用父类的方法,利用super调用父类的构造函数,super必须在子类this之前调用
                super(x,y);
                this.x = x;
                this.y = y;
            }
            sub(){
                console.log(this.x - this.y);
            }
        }
        var man = new Man(3,2);
        man.sub();  //子类自身的方法
        man.sum();  //子类继承父类的方法
        注意:子类在构造函数中使用super,必须放到this前面,(必须先调用父类的构造函数,再使用子类构造函数)
        var son = new Son(); //或者这样写
        son.a =1;
        son.b =2;
        son.sub();
        son.sum();
    </script>
</html>
<script>
    class Human{
        say(){
            return '我是人类';
        }
    }    
    class Man extends Human{
        say(){
            console.log(super.say()+'的分支,男人');
        }
    }
    class Woman extends Human{
        say(){
            console.log(super.say(0 + '的分支,女人'))
        }
    }
        var man = new Man();
        var human = new Human();
        console.log(man.say());;
        console.log(human.say());
</script>

3.3使用的注意事项

1、在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象

2、类里面的共有的属性的方法一定要加this使用;

3、类里面的this指向问题;

4、constructor里面的this指向实例对象,方法里面的this指向这个方法的调用者。

<html>
    <body>
        <button>点击</button>
        <script>
            class Star(){
                constructor(uname,age){
                //constructor里面的this指向的是创建的实例对象
                    this.uname = uname;
                    this.age = age;
                    this.btn = document.querySector("button");
                }
                sing(){
                    console.log(this.uname);
                }
            };
            var ldh = new Star("刘德华");
        </script>
    </body>
</html>

二、构造函数和原型

1.1概述

在典型的OOP的语言中(如Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但是在ES6之前,JS中并没有引入类的概念。

ES6全称ECMScript6.0,2015.06发布,但是目前浏览器的JavaScript是ES5版本,大多数高版本的浏览器也支持ES6,不过只实现了ES6的部分特性和功能。

在ES6之前,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征;

创建对象可以通过以下三种方式:

1、对象字面量

2、new Object();

3、自定义构造函数

1.2构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,他总是与new一起使用,我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面;

在JS中,使用构造函数是要注意以下两点:

1、构造函数用于创建某一类对象,其首字母要大写;

2、构造函数要和new一起使用才有意义;

<script>
    // new
    var obj1 = new Object();
    //通过字面量的方式创建
    var obj2 = {};
    // 通过构造函数
    function Star(uname,age){
        this.uname = uname;
        this.age = age;
        this.sing = function(){
            console.log('我是歌手');
        }
    }
    var ldh = new Star("刘德华",18);
    console.log(ldh);
    ldh.sing();
    Star.sex = '男';
    console.log(Star.sex); //男
</script>

new 在执行时会做4件事情

1.在内存中创建一个新的空对象;

2.让this指向这个新的对象;

3.执行构造函数里面的代码,给这个新对象添加属性和方法;

4.返回这个新对象(所以构造函数里面不需要return)

1.2构造函数

JavaScript的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的this上添加,通过两种方式添加成员,就分别称为静态成员和实例成员;

1.静态成员:在构造函数上添加的成员称为静态成员,只能由构造函数本身来访问;

2.实例成员:在构造函数内部创建的成员称为实例成员,只能由实例化的对象来访问;

1.3构造函数的问题

构造函数方法很好用,但是存在浪费内存的问题 image-20211204105512954.png 那就需要我们解决这个问题,我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要则那样做呢?

1.4构造函数原型prototype

构造函数通过原型分配的函数是所有对象所共享的;

JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象,注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数都拥有;

我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法;

实例成员与静态成员:

实例成员:就是构造函数内部通过this添加的成员 ; 通过实例化的对象来访问

静态成员:在构造函数本身上添加的成员。只能由构造函数本身来访问

<script>
    // 通过构造函数
    function Star(uname,age){
        this.uname = uname;
        this.age = age;
    //通过创建原型对象的方式实现方法的共享
        Star.prototype.sing = function()}{
            console.log('我是歌手');
        }
    }
    var ldh = new Star("刘德华",18);
    var zxy = new Star("张学友",19);
    console.log(ldh.sing === zxy.sing);
    console.log(ldh); //对象身上系统自己添加一个__proto__指向我们构造函数的原型对象;
    console.log(ldh.__proto__ === Star.prototype)  // true
    //方法的查找规则:首先看ldh对象身上是否有sing方法,如果有就执行sing方法;
    //如果没有sing方法,因为有__proto__的存在,就去构造函数原型对象prototype身上去查找sing这个方法。
    ldh.sing();
    Star.sex = '男';
    console.log(Star.sex); //男
    //  一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们会放到原型对象上面;
    // 1、原型是什么?
    一个对象,我们称为prototype为原型对象
    2.原型的作用是什么?
    共享方法
</script>

1.5对象原型__ proto__

对象都会有一个属性 _ proto __指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有prototype原型的存在;

__ proto__对象原型和原型对象prototype是等价的

image-20211204111807201.png

1.6 constructor构造函数

对象原型(__ proto__)和构造函数(prototype) 原型对象里面都有一个属性constructor属性,constructor我们称之为构造函数,因为它指回构造函数本身。

<script>
    function Star(uname,age){
        this.uname = uname;
        this.age = age;
    }
//很多情况下,我们需要手动的利用constructor这个属性指回原来的构造函数
    Star.prototype = {
        //如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指         //回原来的构造函数
        constructor: Star,
        sing: function(){
            console.log("我会唱歌");
        }
        movie: function(){
            console.log("我是演员");
        }
    }
    var ldh = new Star("刘德华",18);
    var zxy = new Star("张学友",20);
    console.log(Star.prototype);
    console.log(ldh.__proto__);
    console.log(Star.prototype.constructor);  //Object Star
    console.log(ldh.__proto__.constructor);  //Object Star
</script>

1.7构造函数、原型对象、实例对象的关系;

image-20211204132845282.png

image-20211204133334183.png

1.9 JavaScript的成员查找机制(规则)

1.当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性;

2.如果没有就查找它的原型(也就是__ proto __指向的prototype原型对象);

3.如果还没有就查找原型对象的原型(Object的原型对象);

4.以此类推一直找到Object为止(null);

5.__ proto__对象原型的意义就在于对象成员查找机制提供一个方向,或者说是一条路线;

原型对象中this的指向

1、在构造函数中,里面this指向的是对象实例

2、原型对象函数里面的this,指向的是实例对象;

1.10原型对象this指向

①在构造函数中,里面this指向的是对象实例ldh

②原型对象函数里面的this指向的是实例对象ldh

<script>
    function Star(uname,age){
        this.uname= uname;
        this.age = age;
        var that;
        Star.prototype.sing(){
            console.log('我会唱歌');
            that = this;
        }
    }    
    console.log(that);  // Star()
    var ldh = new Star('刘德华',18);
    ldh.sing();
    console.log(that === ldh);
</script>

2.0扩展内置对象方法

<script>
    //推荐使用这种追加的方式使用
    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());
    consolr.log(Array.prototype);
    var arr1 = new Array(11,22,33);
</script>

2.继承

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

2.1 call()

调用这个函数,并且修改函数运行时的this指向;

fun.call(thisArg,arg1 , arg2 , ...)

thisArg : 当前 调用函数this的指向对象;

arg1 ,arg2 : 传递的其他参数

<script>
    function fn(){
        console.log("我是fn");
        console.log(this);
        console.log(x+y);
    };
    var o = {
        name: 'andy'
    };
    // fn();之前一直使用的调用
    // fn.call(); 使用call()进行调用
    // 2.call() 可以改变这个函数的this指向,此时这个函数的this就指向了o这个对象;
    // fn.call(o,1,2);
</script>

2.2借用构造函数继承父类型属性

核心原理:通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性;

<script>
    function Father(uname,age){
        this.uname = uname;
        this.age = age;
    };
    //Son.prototype = Father.prototype;  如果使用这种赋值方式,会把父亲的引用地址给儿子,当儿子修改时,父亲也会跟着改变
    Son.prototype = new Father();
    //如果利用对象的形式修改了原型对象,必须利用constructor指向原来的构造函数
    Son.prototype.constructor = Son;
    function Son(uname,age,gender){
        // this 指向子构造函数的实例对象
        Father.call(this,uname,age);
        this.gender = gender;   
    }
    Son.prototype.exam = function(){
        console.log("考试");
    }
    var son = new Son('刘德华', 18);
    console.log(son);
    console.log(Father.prototype);
    console.log(Son.prototype.constructor);
</script>

2.*寄生组合式继承

<script>
        function inheritPrototype(son,father){
            var prototype = Object.create(father.prototype); // 创建对象,创建父类原型的一个副本
            prototype.constructor = son;  //增强对象,弥补因为重写原型市区的默认的constructor属性
            son.prototype = prototype;  //指定对象,将新创建的对象赋值给子类的原型
        }
        //父亲的实例属性
        function Father(name){
            this.name = name;
            this.colors = ['red','blue','yellow'];
        }
        //父亲的公用原型方法
        Father.prototype.sayName = function(){
            console.log(this.name);
        }
        // 儿子的实例属性
        function Son(name,age){
            Father.call(this,name);// 借用构造函数,继承父类的实例属性(支持传参和避免篡改)
            this.age = age;
        }
        // 调用将父类原型指向子类
        inheritPrototype(Son,Father);
        Son.prototype.sayAge = function(){
            console.log(this.age);
        }
        var a = new Son('小明',28);
        var b = new Son('小王',25);
        a.colors.push('2');
        b.colors.push('5');
        console.log(a.colors);
        console.log(b.colors);
        console.log(Son.prototype);
</script>

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

<script>
    function Father(name,age){
        this.name = name;
        this.age = age;
    }
    Father.prototype.sayName = function(){
        console.log(this.name);
    }
    Son.prototype = Father.prototype;
    function Son(){
        Father.call(this,name,age);
    }
    var son = new Son();
    console.log(son.sayName());
</script>

2.4 类的本质

1、类的本质上还是一个函数

ES6之前通过 构造函数+原型实现面向对象编程;

特点:

(1)构造函数有原型对象prototype

(2)构造函数原型对象原型prototype里面有constructor

(3)构造函数可以通过原型对象添加方法

(4)构造函数创建的实例对象有__ proto__ 原型指向构造函数的原型对象

ES6通过类实现面向对象编程;

  1. 类的所有方法都定义在类的prototype属性上;
  2. 类创建的实例,里面也有proto指向类的prototype原型对象;
  3. 所以ES6类它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰,更加面向对象编程的语法而已;
  4. 所以ES6的类其实就是语法糖;
  5. 语法糖:语法糖就是一种便捷写法,简单理解,有两种方法可以实现同样的功能,但是一种写法更加清晰、方便,那么这个方法就是语法糖;
<script>
    //1、类的本质其实还是一个函数,我们也可以简单的认为类就是构造函数的另外一种写法
    class Star {
​
        }
        console.log(typeof Star);
        (1)类有原型对象prototype
        console.log(Star.prototype);
        (2)类原型对象prototype,里面有constructor指向类本身
        console.log(Star.prototype.constructor);
        //(3)类可以通过原型对象添加方法
        Star.prototype.sing = function(){
            console.log('离别');
        }
        console.log(new Star().sing());
</script>

3.ES5中新增的方法

3.1 ES5新增方法概述

ES5给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:“

1.数组方法;

2.字符串方法

3.对象方法;

3.2数组方法

迭代(遍历)方法:forEach()、map()、filter()、some()、every();

语法: array.forEach( function(currentValue , index, arr))

currentValue :数组当前项的值;

index:数组当前项的索引;

arr:数组对象本身;

<script>
    //forEach 迭代(遍历)数组
    var arr = [1,2,3];
    arr.forEach( function(value,index,array){
        console.log("每个数组元素"+value);
        console.log("每个数组元素的索引值"+index);
        console.log("数组本身" +array);
    });
    //第三个参数默认是数组本身,可以不写
        //筛选大于20的数组元素,并返回一个新的数组
    var newArr = arr.filter( function(value,index){
       return value >=20; 
    });
    console.log(newArr);
</script>

filter();

1.filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组汇总符合条件的所有元素,主要用于筛选数组;

2.注意它直接返回一个新数组;

3.currentValue:数组当前项的值;

4、index:数组当前项的索引;

5.arr:数组对象本身;

some();

1.some()方法用于检测数组中的元素是否满足指定条件,通俗点查找数组中是否有满足条件的元素;

2.注意它返回值是布尔值,如果查找到这个元素,就返回true,如果查找不到就返回false;

3.如果找到第一个满足条件的元素,则终止循环,不在继续查找,适合查询数组中唯一的元素,效率更高;

4.currentValue:数组当前项的值;

5.index:数组当期项的索引;

6.arr:数组对象本身

7.some不会对空数组进行检测;some()不会改* 变原始数组* ***

<script>
    var arr = [22,32,21,34];
    var flag = arr.some(function(value){
        return value >=20;
    });
    console.log(flag);
    var arr1 = ['red','green','pink','yellow'];
    var flag1 = arr1.some(function(){
        return value =='pink';
    });
    console.log(flag1);
</script>

every定义和用法

every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。

every() 方法使用指定函数检测数组中的所有元素:

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true。

注意: every() 不会对空数组进行检测。

注意: every() 不会改变原始数组。

Map定义和用法

map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。

map() 方法按照原始数组元素顺序依次处理元素。

注意: map() 不会对空数组进行检测。

注意: map() 不会改变原始数组。

var numbers = [4, 9, 16, 25];
​
function myFunction() {
   // x = document.getElementById("demo")
    x.innerHTML = numbers.map(Math.sqrt);
}
//输出结果:2,3,4,5

indexof();返回该元素在数组的位置;如果没有则返回-1

some()和forEach()的区别

<script>
        var arr = ['blue','red','green','yellow'];
        // arr.some(function(value){
        //     if(vlaue = 'red'){
        //         console.log("yes");
        //         return true;    //会终止迭代
        //     }
        //     console.log(1);
        // })
        // arr.forEach(function(value){
        //     if(vlaue = 'red'){
        //         console.log("yes"); //不会终止迭代
        //     }
        //     console.log(1);
        // })
        arr.filter(function(value){
            if(vlaue = 'red'){
                console.log("yes");
                return true;   //不会终止迭代
            }
            console.log(1);
        })
    </script>

3.3字符串方法

trim()方法会从一个字符串的两端删除空白字符

str.trim();

    var str = ' w g f ';
    console.log(str.trim());
//输出结果
w g f
<script>
    
</script>

trim()方法并不影响原字符串本身,它返回的是一个新的字符串

3.4ObjectdefineProperty方法

image-20211207115236493.png

<script>
        window.onload = function(){
            var obj = {
                name:'小米',
                price:1999

            }
            //第一种:传统的方法修改添加属性
            // obj.num = 100;
            // obj.name = '华为'
            // console.log(obj);
            //第二种方法
            Object.defineProperty(obj,'num',{
                value:1211,
            });
            Object.defineProperty(obj,'name',{
                value:'1211',
            });
            Object.defineProperty(obj,'price',{
                value:12,
            });
    		Object.defineProperty(obj,'price',{
                writable: false, //禁止修改该属性
                enumerbale:true, //enumerable 如果值为false,则不允许遍历,默认为false
                configutable:true //如果为false则不允许删除这个属性或者修改这个特性,默认为false
            });
    		obj.price = 1222;
            console.log(obj);
        }
    </script>
# 三、函数进阶

1.1函数的定义和调用

1.函数声明方式function关键字(命名函数)

<script>
    function fn(){};
</script>

2.函数表达式(匿名函数)

<script>
    var fun = function(){};
</script>

3.new Function()

语法: var fn = new Function('参数1','参数2', ……,'函数体')

缺点:Function里面参数都必须是字符串格式,

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

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

函数也属于对象

image-20211207142117939.png

1.2函数的调用方式

1.普通函数
function fn(){
    console.log('人生的巅峰');
};
fn(); fn.call();
2.对象的方法
var obj = {
    sayHi: function(){
        console.log('人生的巅峰');
    }
};
obj.sayHi();
3.构造函数
var f = new Function();
new Star(); // 产生一个新的实例对象
4.绑定事件函数
btn.onclick = function(){
        console.log('人生的巅峰');
};  //当我们点击btn按钮的时候就会调用函数
5.定时器函数
setInterval( function(){
        console.log('人生的巅峰');
},1000); // 定时器每隔1s调用一次
6.立即执行函数
(function(){
        console.log('人生的巅峰');
})() //立即执行函数会立即调用

2、函数中this的指向问题

2.1函数内this的指向

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

image-20211207172314215.png

2.1改变函数内部this指向

JavaScript为我们专门提供了一些函数方法来帮助我们更优雅处理函数内部this的指向问题,常用的有bind()、call()、apply()三种方法;

1、call方法

作用:调用函数;改变函数的this指向

fun.call(thisArg , arg1 , arg2 , ....);

<script>
    var obj = {
        name:'andy',
        age:18,
        gender:'男'
    }
function fn(a,b){
    console.log(this);
    console.log(a+b);
}
fn.call(o,1,2);
function Father(uname,age,sex){
    this.uname = uname;
    this.age = age;
    this.sex = sex;
}
function Son(uname,age,sex){
    //call的作用 是一个可以调用函数,第二个可以改变函数内的this指向
    //call的主要作用可以实现继承
    Father.call(this,uname,age,sex);
}
</script>

2、apply方法

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

语法: fun.apply( thisArg, [argsArray])

thisArg: 在fun函数运行时指定的this值;

argArray:传递的值,必须包含在数组里面;

返回值就是函数的返回值,因为它就是调用函数

<script>
    var o = {
        name:'andy'
    }
    function fn(arr){
        console.log(this);
        console.log(arr);
    };
    // 1、调用函数,第二个可以改变函数内部的this指向
    // 2、但是他的参数必须是数组(伪数组)
    // 3、apply的主要应用,比如说我们可以利用apply借用与数学内置对象求最大值
    fn.apply(o,['pink']);
    var arr = [1,66,3,99,4];
    var max = Math.max.apply(Math,arr);  //使用null , 在非严格模式可以,严格模式就不可以
    console.log(max); // 99
    var min = Math.min.apply(Math,arr);
</script>

3.bind方法

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

语法:fun.bind(thisArg , arg1 , arg2, ...)

thisArg : 在fun函数运行时指定的this值;

arg1 , arg2 :传递的其它参数;

返回由指定的this 值和初始化参数改造的原函数拷贝,即返回一个新函数

<script>
    var o = {
        name: 'andy'
    };
    function fn(){
        console.log(this);
        console.log(a+b);
        
    }
var f = fn.bind(o,1,2);
f(); // 调用
    // 1、不会调用原来的函数,可以改变原来函数内部的this指向
    // 2、返回的是原函数改变this之后产生的新函数
    //场景 当我们进行短信验证码禁用时,不想直接立即调用的时候,就可以使用bind方法
    var btn = document.querySelector('.btn');
    btn.onclick = function(){
        btn.disabled = true;
        setTimeout(function(){
            this.disabled = false; // setTimeout中默认的this指向是window;
        }.bind(this),2000);
    }
</script>
<body>
         <button class="btn">提交</button>
</body>

2.this

2.2 call apply bind总结

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

区别点:1、call和apply会调用函数,并且改变函数内部this指向;

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

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

主要应用场景:

1、call经常做继承;

2.apply经常跟数组有关系,比如借助于数学对象实现数组最大值最小值

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

3、严格模式

3.1什么是严格模式

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

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

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

1、消除了JavaScript语法的一些不合理,不严谨之处,减少了一些怪异行为;

2、消除代码运行的一些不安全之处,保证代码运行的安全;

3、提高编译器效率,增加运行速度;

4、禁用了ECMAScript的未来版本中可能会定义的一些语法,为未来新版的JavaScript做好铺垫,如一些保留字如:class,enum,export,extends,import,super不能做变量名

3.2开启严格模式

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

1、为脚本开启严格模式

为整个脚本开启严格模式,需要在所有语句之前放一个特定语句 "use strict";或( 'use strict';)

有的script基本是正常模式,这样不利于文件合并,所以可以将整个文件放在一个立即执行的匿名函数之中,这样独立创建一个作用域而不影响其他script脚本文件;

<script>
    //IE10版本以上支持严格模式
    //script脚本使用严格模式
    'use strict';
    function fn(){
        // 函数体内部使用严格模式
        'use strict';
    }
    function fn(){
        // 函数体内部使用普通模式
        
    }
</script>

3.4严格模式中的变化

严格模式对JavaScript的语法和行为,都做了一些改变

2.严格模式下this的指向问题

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

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

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

④严格模式下,如果构造函数不加new调用,this会报错;

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

⑥定时器this还是指向window;

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

3.函数的变化

① 函数不能有重名的参数

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

<script>
    'use strict';
    //在严格模式下, a a 不能重名
    function a (a,a){
        console.log(a+a);
    }
    a(1,2); // 非严格模式下会输出4
    if( a > 10){
        function fn(){
                //语法错误,不能在代码块中写函数
        }
    }   
    function fn (){
        function fa (){
                //合法,可以在函数体内部书写新的函数
        }
    }
</script>

4、高阶函数

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

<script>
    function fn(callback){
        callback && callback();
    };    
    fn( function(){ 
        alert('hi');
    });
    function fn(){
        return function(){};
    }
    fn();
</script>

此时fn就是一个高阶函数,函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用,最典型的就是作为回调函数;

5.闭包

5.2什么是闭包? 答:闭包是一个函数(一个作用域可以访问另外一个函数的局部变量)

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

<script>
    function fn(){
    var num = 10;
        function fun(){
            console.log(num);
        };
        fun();
    }    
    fn();
</script>

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

闭包的作用1.点击li

<script>
    var lis = document.querySelectorAll('li');
    for(var i =0; i < lis.length; i++){
        lis[i].index = i;
        lis[i].onclick = function(){
            console.log(i);
        }
    };
    //利用for循环创建4个立即执行函数与
    //立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的变量i
    for( var i =0; i < lis.length;i++){
        (function(i){
            lis[i].onclick = function(){
                console.log(i);
            };
        })(i);
    }
</script>
<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
   
</body>

异步事件: callback 定时器回调函数,绑定事件中的回调函数

闭包的应用2:3秒钟之后显示内容

<body>
    <ul>
        <li>1</li>
        <li>3</li>
        <li>2</li>
    </ul>
</body>
<script>
        var ul = document.getElementsByTagName('ul')[0];
        console.log(ul);
        var lis = ul.querySelectorAll('li');
        console.log(lis);
        for (var i = 0; i < lis.length; i++) {
            (function(i){
                setTimeout(function(){
                    console.log(lis[i].innerHTML);
                },3000)
            })(i)
        }
</script>

闭包的应用3 出租车扣费问题

<script>
    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;
            },
            traffic:function(flag){
                return flag ? total = total + 10 : total;
            }
            
        }
    })();
    console.log(car.price(5));
    console.log(car.traffic(true));
    console.log(car.price(1));
    console.log(car.traffic(false));
</script>

闭包中this的指向问题

<script>
        var name ='The window';
        var object = {
            name: 'my object',
            getName: function(){
                return function(){
                    return this.name;
                }    //.bind(object)
            }
        };
        console.log(object.getName()());
        // 输出结果为 The window
        等价于如下:
        var f = function(){
            return function(){
                return this.name;
            }
        }
        f();
        var object = {
            name: 'my object',
            getName: function(){
                var that = this;
                return function(){
                    return that.name;
                }
            }
        };
        console.log(object.getName()());
</script>

6.递归

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

<script>
    var num =1;
    function(){
        console.log('我是函数');
        if( num == 6){
            return;
        }
        num++;
        fn();
    }
    fn();
    function fn(n){
        if(n == 1){
            return 1;
        }
        return n * fn(n-1);
    }
    fn();
    console.log(fn(3));
    function fn(n){ //1 1 2 3 5 8 13   斐波那契数列
        if(n == 1 || n ==2){
            return 1;
        }
        return fn(n-2) + fn(n-1);
    }
    console.log(fn(2));
</script>
<script>
var data = [{
            id: 1,
            name: '家电',
            goods: [
                {
                    id: 11,
                    gname: '冰箱',
                    goods:[{
                        id: 111,
                        gname: '海尔'
                    },{
                        id: 112,
                        gname: '美的'
                    }]
                },
                {
                    id:12,
                    gname:'洗衣机'
                }
            ]
        },
        {
            id: 2,
            name:'服装',
            clothes:[
                {
                    id: 21,
                    cname: '棉衣'
                },
                {
                    id: 22,
                    cname: '工装裤'    
                }
            ]
        }]
        function getID(json,id){
            var oList = {};
            json.forEach( function(item){
                if (item.id ==id) {
                    // console.log(item);
                    oList = item;
                }else if(item.goods && item.goods.length >0){
                    oList = getID(item.goods,id);
                }
            });
            return oList;
        }
        // getID(data,21);
        console.log(getID(data,12));
</script>

浅拷贝

1、浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用

2、深拷贝拷贝多层,每一级别的数据都会拷贝

3、Object.assign(target,……source) ES6新增方法可以浅拷贝

<script>
        var obj = {
            name: 'andy',
            sex: '男',
            msg: {
                id:1
            }
        }
        var o ={};
        for (const key in obj) {
            o[key] = obj[key];
        }
        console.log(obj);
        console.log(o);
//如果对o或者obj进行修改,会相互受到影响
        o.msg.id = 2;
        console.log(obj);
        console.log(o);
    </script>

深拷贝

<script>
var obj = {
            name: 'andy',
            sex: '男',
            msg: {
                id:1
            }
            color:['pink','yellow']
        }
        var o ={};
        function deepCopy(newobj,oldobj){
            for (var k in oldobj) {
                var item = oldobj[k];
                if(item instanceof Array){
                    newobj[k] = [];
                    deepCopy(newobj[k],item)
                }else if(item instanceof Object){
                    newobj[k] = {};
                    deepCopy(newobj[k],item)
                }else{
                    //基本类型的拷贝
                    newobj[k] = item;
                }
            }
        }
        deepCopy(o,obj)
        console.log(o);
        o.msg.id =2;
        console.log(obj); //1
        console.log(o); // 2
</script>

5.正則表達式

1.1什么是正则表达式

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

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

1.2正则表达式的特点

1.灵活性。逻辑性和功能性非常的强;

2、可以迅速的拥既简单的方式达到字符串的复杂控制

3、对于刚接触的人来说,比较晦涩难懂,比如邮箱验证:^\w+([-+.]\w+)@\w+([-.]\w+) .\w+([-.]\w+) $

2.1创建正则表达式

在JavaScript中,可以通过两种方式创建一个正则表达式;

1.通过调用RegExp对象的构造函数创建;

2、通过字面量的方式创建;

var rg = /123/;
var regexp = new RegExp(/123/);
console.log(regexp);
​

2.2测试正则表达式test

test()正则表达式对象方法,用于检测字符串是否符合该规则,该对象

会返回true或false,其参数是检测字符串;

regexpObj.test(str);

ES6简介

ES6的全称是ECMAScript,它是由ECMA国际标准化组织指定的一项脚本语言的标准化规范。

image-20211209142017754.png ES6的新增语法

ES6新增的用于声明变量的关键字

1.1let声明的变量只在所处于的块级有效

if(true){
    let a = 10;
}
conole.log(a); // a is not defined

1.2.不存在变量提升

console.log(a);
// 此部分为缓存死区  块级绑定
let a = 100;
// 在ES6中使用let声明的变量不存在变量提升

案例

<script>
    var arr = [];
    for(var i = 0; i <2; i++){
        arr[i] = function(){
            console.log(i);
        }
    }
    arr[0]();  // 2
    arr[1]();  //2
    var arr = [];
    for(let i = 0; i <2; i++){
        arr[i] = function(){
            console.log(i);
        }
    }
    arr[0]();  // 1
    arr[1]();  // 1
</script>

2.1 const 作用:声明常量 具有块级作用域

if(true){
    const a=10;
    if(true){
        const a=20;
        console.log(a); // 20
    }
    console.log(a); //10
}
console.log(a); // a is not undefined;

2.2 作用:声明常量,常量就是值(内存地址)不能变化的量。

具有块级作用域
if(true){
    const a =10;
}
console.log(a);
// 声明常量时必须赋值
const PI ; // Missing initializer in const declaration.
// 声明常量,常量就是值(内存地址)不能改变的量
const PI = 3.14;
PI = 100; //常量赋值之后,不能再次修改

3 let、const、var的区别

1、使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象;

2、使用let声明的变量,其作用域为所在的代码块内,不存在变量提升;

3、使用const声明的常量,在后面出现的代码中不能再修改该常量的值

二、解构赋值

ES6允许从数组中提取值,按照对应的位置,对变量赋值,对象也可以实现解构;

数组解构

<script>
 let [a,b,c,d] = [1,2,3];
console.log(a); //1
console.log(b);  //2
console.log(c);  //3
console.log(d);//解构失败,会报undefined
// 部分解构
let [x,[y],z] = ['a',[1,2]];
console.log(y);
// 失败解构
let [f] = false;
console.log(f);
//ES6 内部使用严格相等运算符( ===  ),
//判断一个位置是否有值。
//所以,只有当一个数组成员严格等于 undefined  ,默认值才会生效。
let[x=1] = [undefined];
console.log(x);
let[y=1] = [null];
console.log(y);
</script>

按照一定的模式,从数组中或对象中提取值,将提取出来的值赋值给另外的变量;

对象解构

<script>
let person = {name: '张三',age:20};
let (name,age) = person;
console.log(name); // ‘张三
console.log(age); // 20
let {name: myName,age:myAge} = person;
console.log(myName); // 张三
console.log(myAge) //20let person = {name:'张三',age:23};
        let {name:Myname,age:Myage} = person;
        console.log(Myage);
</script>

三、箭头函数

箭头函数没有原型属性。 箭头函数与普通函数的区别

1.没有this、super、arguments和new.target绑定, 箭头函数 中 的 this、super、arguments及new.target这些值由外围最近一层非 箭头函数 决定 ;

2.不能通过new关键字调用 箭头函数 没有construct方法,所以不能被用作构造 函数 ,如果通过new关键字调用 箭头函数 ,程序会抛出错误;

3.没有原型 由于不可以通过new关键字调用箭头... ES6标准新增了一种新 的函数 :Arrow Function( 箭头函数 ).

() =>{}
<script>
   const f() =>{}
   function sum (num1,num2){
       return num1+num2;
   };
//箭头函数
const sum = (num1 , num2) => num1 + num2;
// 2、如果形参只有一个可以省略小括号
function fn(v){
    return v;
}
const fn = v => v;
//箭头函数的this指向
//箭头函数不绑定this关键字,箭头函数中this,指向的是函数定义位置的上下文this
const obj = {name: '张三'};
function fn(){
    console.log(this);
    return ()=>{
        console.log(this);
    }
}
fn.call(obj) // 结果 {name: '张三'};
const resFn = fn.call(obj);
resFn(); //{name: '张三'};//面试题
var age = 100;
var obj ={
    age: 20,
    say:() =>{
        alert(this.age)
    }
}
//对象是不产生作用域的,
</script>

四、剩余参数

剩余参数语法允许我们将一个补丁数量的参数表示为一个数组;

function sum(first,...args){
    console.log(first); //10
    console.log(args);// [20,30]
}
sum(10,20,30);//箭头函数中不能使用arguments
const sum = (...args)=>{
    let total = 0;
    args.forEach(item => totle +=item);
    return total;
};
console.log(sum(10,20,30)); //60
//剩余参数的解构
let students = ['张三','王五','马三'];
let[s1,...s2] = students;
console.log(s1);  // '张三'
console.log(s2); // ['王五','马三']

五、扩展运算符

扩展运算符可以将数组或者对象转化为用逗号分隔的参数序列

<script>
    let ary = [1,2,3];
     ...ary //1,2,3
     var arr = [1,2,3];
        console.log(...arr); // 1 2 3数字型
// Array 的扩展方法 扩展运算符(展开语法)
扩展运算符可以应用于合并数组
//方法一
let ary1 = [1,2,3];
let ary2 = [4,5,6];
let ary3 = [...ary1,...ary2];
console.log(...ary1,...ary2); // 1 2 3 4 5 6
//方法二
ary1.push(...ary2);
</script>

Array的扩展方法

1.1Array.from()

<script>
    构造函数方法:Array.from();
作用一:    将类数组或可遍历对象转换为真正的数组
    let arrayLike ={
        '0': 'a',
        '1': 'b',
        '2': 'c',
        'length': 3
    };
let arr2 = Array.from(arrayLike); // ['a','b','c']
//作用二: 作用类似于数组的map方法,用来对每一个元素进行处理,将处理的值返回原来的数组
</script>

1.2实例方法:find()

返回要查询的元素

<script>
let ary = [{
            id:1,
            name:'张三'
        },
        {
            id:2,
            name:'老师'
        }]
        let target = ary.find((item,index) => item.id==2);
        console.log(target);
//如果查询不到就返回undefined
</script>

1.3 实例方法findIndex();

返回查询元素的索引值

<script>
    //用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
    let ary =[1,5,10,15];
    let index =ary.findIndex((value,index) =>value>9);
    console.log(index); //2
</script>

1.4实例方法 includes();

表示某个数组是否包含给定的值,返回布尔值

<script>
    var a=[1,2,3].includes(2);
        console.log(a);  // true
    var b=[1,2,3].includes(4);
        console.log(b);  //false
</script>

String的扩展方法

1.1 模板字符串
<script>
    //末班字符串中可以解析变量
    let name = '张三';
    let sayHello = `hello, my name is ${name}`;
    //hello, my name is 张三
</script>
1.2实例方法:startsWith() 和endsWith();

startsWith() 表示字符串是否在原字符串的头部,返回布尔值

endsWith()表示字符串是否在原字符串的尾部,返回布尔值

<script>
    let str = 'Hello world !';
    str.startsWith('Hello'); //true;
    str.endsWith('!'); // true
</script>

1.3 实例方法: repeat();

repeat方法表示将原字符串重复n次,返回一个新字符串
'x'.repeat(3); // 'xxx'
'hello'.repeat(02); // 'hellohello'

Set数据结构

ES6提供了新的数据结构,它类似于数组,但是成员的值都是唯一的,没有重复值

Set本身就是一个构造函数,用来生成Set数据结构

<script>
    const s = new Set();
    //初始化参数
    const set = new Set([1,2,3,4]);
//应用一 使用set做数组去重
    const s2 = new Set(['a','a','b','b']);
    var arr = [...s2];
    console.log(arr); // ['a','b']
</script>

实例方法

1、add(value):添加某个值,返回Set结构本身;

2、delete(value): 删除某个值,返回一个布尔值,表示删除是否成功;

3、has(value): 返回一个布尔值,表示该值是否为Set的成员

4、clear():清除所有成员,没有返回值

<script>
    const s = new Set();
s.add(1).add(2).add(3); //向set结构中添加值
s.delete(2);  //删除set结构中的2值
s.has(1);   //表示set结构中是否有1这个值,返回布尔值
s.clear(); //清除set结构中的所有值
</script>

数据遍历

Set结构的实例与数组一样,也拥有forEach方法,用于每个成员执行某种操作,没有返回值

<script>
    //遍历
    const set = new Set([1,2,3,4]);
    s.forEach( value => console.log(value) );
</script>
<script>
    // 在set中, 如果添加两个 NaN的话,之后追加1个;
    const set1 = new Set();
    let a = NaN;
    let b = NaN;
    set1.add(a);
    set1.add(b);
    console.log(set1);
    // 在Set 中追加两个空对象是不相等的
    const set2 = new Set();
    set1.add({});
    set1.add({});
    console.log(set2);
    // Set 实例的方法分为两大类:操作方法(用于操作数据和遍历成员)。下面先介绍四个操作方法
    Set.prototype.add(value) : 添加某个值,返回Set结构本身;
    Set.prototype.delete(value) : 删除某个值,返回一个布尔值表示是否删除成功
    Set.prototype.has(value): 返回一个布尔值,表示该值是否为Set成员
    Set.prorotype.clear();:清除所有成员,没有返回值
    // 
    const set = new Set([1,2,3,4,5]);
    set.add(1).add(2).add(6);
    console.log(set);
    console.log(set.size)
    console.log(set.has(2));
    console.log(set.delete(2));
    console.log(set);
    console.log(set.has(2));
    // const properties = {
    //     'width' : 1,
    //     'height': 1
    // };
    // if (properties['width']){
    //     console.log(1)   // 结果1
    // }else {
    //     console.log(2)
    // }
        const properties = new Set();
        properties.add('width');
        properties.add('height');
        if (properties['height']){
            console.log(1)    // 结果 1
        }else {
            console.log(2)
        }
</script>

Set结构的实例有四个遍历方法,可以用于遍历成员

Set.prototype.keys() : 返回键名的遍历器
Set.prorotype.values(): 返回键值的遍历器
Set.prototype.entries(): 返回键值对的遍历器
Set.prototype.forEach(): 使用回调函数遍历每个成员
Set的遍历顺序就是插入的顺序
    set.forEach(
    (value,key) => console.log(key));
// 使用forof 遍历set对象
for (let item of set) {
    console.log(item)
}
    const color = new Set(['red','blue','green']);
    let arr = [...color];
    console.log(arr)
    const newSet = new Set([...set].map(x=> x*2));
    console.log(newSet);
    const filterData = new Set([...set].filter(x => (x%2) == 0));
    console.log(filterData);
//  实现并集交集和补集
const a = new Set([1,2,3]);
 const b = new Set([4,3,2]);
 const union = new Set([...a,...b]);
 const intersect = new Set([...a].filter(x=> b.has(x)));
 const difference = new Set([...a].filter(x=> !b.has(x)));
console.log(intersect)
console.log(difference)
console.log(union);
//
    const set = new Set([1,2,3]);
    set = new Set([...set].map(val => val *2));
    console.log(set)   // 2,4,6
    set = new Set(Array.from(set, val => val*2));
    console.log(set)   //2,4,6

Set方法的应用

<script>
        var arr = [2,2,2,2,34,1];
        console.log([...new Set(arr)]); // 数组去重 一
       // 数组去重 二
        const array = Array.from(arr);
        console.log(array)
        console.log([...new Set('aaaabbbbb')].join(''))  //字符串去重
</script>

补充 weakSet

1. WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。首先,WeakSet 的成员只能是对象,而不能是其他类型的值;
2 .WeakSet获取 size 和 forEach 属性,结果都不能成功。

WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能 刚刚遍历结束,成员就取不到了。WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从 文档移除时,会引发内存泄漏。

WeakSet 没有Size属性,没有办法遍历她的成员

Map

 <script>
        const m = new Map();
        const o = {p: 'hello world'};
        m.set(o,'content');
        console.log(m.get(o));
        m.forEach( item => console.log(item));
        const map = new Map([['name','张三'],['title','Author']]);
        console.log(map.size);
        console.log(map.has('name'));
        console.log(map.get('name'));
        console.log(map.get('title'));
        const set = new Set(
        [['foo',1],['bar',2]]);
        const m1 = new Map(set);
        console.log( m1.get('foo'));
        const m2 = new Map([['baz',3]]);
        const m3 = new Map(m2);
        console.log(m3.get('baz'));
            // 如果对同一个键多次赋值,后面会覆盖前面的值
        map.set(1,'aaa');
        map.set(1,'bbb');
        console.log(map.get(1));
        // 如果读取到一个位置的键,就会报错undefined
        new Map().get('assdsads'); // undefined 
//变量 k1  和 k2  的值是一样的,但是它们在 Map 结构中被视为两个键。
//由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题
        const map = new Map();
        const k1 = ['a'];
        const k2 = ['a'];
        map.set(k1,111);
        map.set(k2,222);
        console.log(map.get(k1));
        console.log(map.get(k2));
        //Map 值的设置与获取
        m.set(-0,123);
        console.log(m.get(+0));
        m.set(true,1);
        m.set('true',2);
        console.log(m.get('true'));
        m.set(undefined,3)
        console.log(m.get(undefined));
        m.set(null,4);
        console.log(m.get(null));
        m.set(NaN,123);
// map 可以使用链式set()
        let map = new Map().set(1,'a').set(2,'b').set(3,'c');
        console.log(map);
        // 删除某个元素  清空整个map
        const hello = function(){console.log('hello')};
        m.set(hello,'Hello ES6!');
        console.log(m.get(hello));
        m.delete(hello)
        m.clear();
        console.log(m.has(hello));
//  Map 的遍历
        for (const key of m.keys()) {
            console.log(key);
        };
        for (const value of m.values()) {
            console.log('value='+value);
        };
        for (const [key,value] of m.entries()) {
            console.log(key,value);
        }
        //  遍历器
        console.log(m[Symbol.iterator] === m.entries);
</script>