0
点赞
收藏
分享

微信扫一扫

javaScript高级[一]

编程练习生J 2022-04-14 阅读 35
javascript

javaScript高级[一]

面向对象

面向对象编程介绍

面向过程思想

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

举个栗子∶将大象装进冰箱,面向过程做法。
1、打开冰箱门
2、大象装进去
3、关闭冰箱

面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。

面向对象思想

  • 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。

举个栗子∶将大象装进冰箱,面向对象做法。
先找出对象,并写出这些对象的功能︰
1 大象对象
进去
⒉冰箱对象
打开
关闭
3 使用大象和冰箱的功能

面向对象是以对象功能来划分问题,而不是步骤。

ES6中的类和对象

面向对象的思维特点:
1.抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板);
2.对类进行实例化,获取类的对象。
面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点不断的创建对象,使用对象,指挥对象做事情

对象

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

  • 对象是由属性和方法组成的∶
  • 属性︰事物的特征,在对象中用属性来表示(常用名词)
  • 方法∶事物的行为,在对象中用方法来表示(常用动词)

类抽象了对象的公共部分,它泛指某一大类( class )对象特指某一个,通过类实例化一个具体的对象

创建类

语法

<script>
    // 1. 创建类class
    class className {
		// class body
    }
    // 2. 创建对象new
    new className();
</script>

类constructor构造函数

constructor()方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法。
如果没有显示定义,类内部会自动给我们创建一个constructor()

<script>
    // 1. 创建类class
    class Star {
        // new的时候自动调用constructor
        constructor(uname, age) { // constructor可以传递参数
            this.uname = uname;
            this.age = age;
        }
    }
    // 2. 创建对象new
    var ldh = new Star("刘德华", 18); // constructor可以返回实例对象
    var zxy = new Star("张学友");
    console.log(ldh); // Star {uname: "刘德华", age: 18}
    console.log(zxy); // Star {uname: "张学友", age: undefined}
    console.log(ldh.age); // 18
    console.log(zxy.uname); // 张学友

    // (1) 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
    // (2) 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
    // (3) constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
    // (4) 生成实例 new 不能省略
    // (5) 最后注意语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function(类中的方法都不加function)
</script>

在类中添加方法

语法:

class Person {
	constructor (name, age) {	// constructor构造器或者构造函数
	this.name = name;
	this.age = age;
	}
	say () {
		console. log (this.name + "你好");
	}
}

<script>
    // 1. 创建类class
    class Star {
        // 类的共有属性放到constructor里面
        constructor(uname, age) {
                this.uname = uname;
                this.age = age;
            } // ,这里不加逗号
        sing(song) {
            console.log(this.uname + song);
        }
    }
    // 2. 创建对象new
    var ldh = new Star("刘德华", 18);
    var zxy = new Star("张学友");
    console.log(ldh); // 依然打印 Star {uname: "刘德华", age: 18}   因为new的时候只调用constructor,所以这里打印的内容不包括sing
    console.log(zxy); // 依然打印 Star {uname: "张学友", age: undefined}

    ldh.sing('冰雨'); // 刘德华冰雨 ldh调用了sing函数,那么sing中的this就指向ldh,那么sing中的this.name就是ldh.name
    zxy.sing('李香兰'); // 张学友李香兰

    // (1) 我们类里面所有的函数不需要写function 
    // (2) 多个函数方法之间不需要添加逗号分隔
</script>

类的继承

语法

// 1. 类的继承
class Father {
}
class Son extends Father {
    // 此时已经实现了继承
}

举例

<script>
        // 1. 类的继承
        class Father {
            constructor() {

            }
            money() {
                console.log(100);
            }
        }
        class Son extends Father {
            //此时已经实现了继承
        }
        var son = new Son();
        son.money(); // 100
</script>

super关键字

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

super关键字调用父类构造函数举例

<script>
    class Father {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        sum() {
            console.log(this.x + this.y);
        }
    }
    class Son extends Father {
        constructor(x, y) {
            //错误写法
            // this.x = x;
            // this.y = y;

            //正确写法  调用父类中的构造函数
            super(x, y);
        }
    }
    var son = new Son(1, 2); //按照错误写法,这里的实参只能传到Son的构造函数里,传不到Father的构造函数中,那么sum就接收不到参数,无法进行运算
    son.sum(); //3    son自己本身没有sum方法,但是son继承了father的方法,所以这里执行father.sum()
</script>

但是这样就可以

<script>
    class Father {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        sum() {
            console.log(this.x + this.y);
        }
    }
    class Son extends Father {
        constructor(x, y) {
            super(x, y);
            this.x = x;
            this.y = y;
        }
    }
    var son = new Son(1, 2); //按照错误写法,这里的实参只能传到Son的构造函数里,传不到Father的构造函数中,那么sum就接收不到参数,无法进行运算
    son.sum(); //3
</script>

这样的写法也没错

<script>
    class Father {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        sum() {
            console.log(this.x + this.y);
        }
    }
    class Son extends Father {
    }
    var son = new Son(1, 2);// son自己没有构造函数,那就是执行了父亲的构造函数,1和2都传给了父亲
    son.sum(); // 3
</script>

但是下面这样写就会报错

<script>
    class Father {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        sum() {
            console.log(this.x + this.y);
        }
    }
    class Son extends Father {
        constructor(x, y) {
            // 这样就会报错
        }
    }
    var son = new Son(1, 2);
    son.sum(); 
</script>
  • 上述举例说明,子类重写父类的方法,就会覆盖从父类继承过来的方法,以下例子也是同样的道理
<script>
    class Father {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        sum() {
            console.log(this.x + this.y);
        }
    }
    class Son extends Father {
        constructor(x, y) {
            super(x, y);
        }
        sum() {
            console.log(this.x - this.y);
        }
    }
    var son = new Son(1, 2);
    son.sum(); // -1   子类的sum将父类的sum覆盖掉了
</script>

super关键字调用父类普通函数举例

<script>
    // super 关键字调用父类普通函数
    class Father {
        say() {
            return '我是爸爸';
        }
    }
    class Son extends Father {
        say() {
            console.log(super.say() + '的儿子');
            // super.say() 就是调用父类中的普通函数 say()
        }
    }
    var son = new Son();
    son.say();
    // 继承中的属性或者方法查找原则: 就近原则
    // 1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
    // 2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
</script>

子类继承父类方法的同时扩展自己的方法

注意:子类在构造函数中使用super,必须放到this前面(必须先调用父类的构造方法,在使用子类构造方法)

<script>
    // 父类有加法方法
    class Father {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        sum() {
            console.log(this.x + this.y);
        }
    }
    // 子类继承父类加法方法 同时 扩展减法方法
    class Son extends Father {
        constructor(x, y) {
            // super 必须在子类this之前调用  super()放在第一行
            super(x, y); //利用super 调用父类的构造函数  这样子类的参数5,3才会传给父类的构造函数,sum()才会接收到5,3
            this.x = x;
            this.y = y;
        }
        subtract() {
            console.log(this.x - this.y);
        }
    }
    var son = new Son(5, 3);
    son.subtract();// 2
    son.sum();// 8
</script>
  • 以下这样的写法也是正确的,以下是子类有自己的构造方法,只是在自己的构造方法中调用了父类的构造方法
<script>
    // 父类有加法方法
    class Father {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        sum() {
            console.log(this.x + this.y);
        }
    }
    // 子类继承父类加法方法 同时 扩展减法方法
    class Son extends Father {
        constructor(x, y) {
            // super 必须在子类this之前调用  super()放在第一行
            super(x, y); // 利用super 调用父类的构造函数  这样子类的参数5,3才会传给父类的构造函数,sum()才会接收到5,3
            console.log(this);// Son {x: 5, y: 3}
        }
        subtract() {
            console.log(this.x - this.y);
        }
    }
    var son = new Son(5, 3);
    son.subtract();//2   
    son.sum();//8
</script>
  • 注意:子类继承父类的构造方法以及子类,在自己的构造方法中调用父类构造方法的区别

ES6中类和对象的3个注意点

  • 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象。
  • 类里面的共有属性和方法一定要加this使用
<script>
    class Star {
        constructor(uname, age) {
            this.uname = uname;
            this.age = age;
            this.sing(); // 在构造函数内部调用普通函数前面要加this 函数前面的this
        }
        sing() {
            console.log(this.uname);// 函数里面的this
        }
    }
    var ldh = new Star('刘德华'); // 类实例化对象放在类定义的后面    实例化的时候自动调用sing()
    console.log(ldh); // Star {uname: "刘德华", age: undefined}
</script>

函数前面的this和函数里面的this都指向函数的调用者。
更深入的理解:

<button>点击</button>
<script>
    class Star {
        constructor(uname, age) {
            this.uname = uname;
            this.age = age;
            this.btn = document.querySelector('button');
            this.btn.onclick = this.sing;
            // 对于这些共有的属性和方法的前面一定要加this ,因为这些属性和方法是属于实例对象的,而this就是指向我们的实例对象
            // this.btn.onclick = this.sing;表示点击完成之后调用该函数
            // this.btn.onclick = this.sing();表示实例化对象的时候就调用该函数
        }
        sing() {
            console.log(this.uname);
        }
    }
    var ldh = new Star('刘德华'); // 类实例化对象放在类定义的后面    
    console.log(ldh); // Star {uname: "刘德华", age: undefined, btn: button}
    // 点击按钮之后调用sing()打印undefine
</script>
  • 类里面的this指向问题。
  • constructor里面的this指向实例对象,方法里面的this指向这个方法的调用者
<body>
    <button>点击</button>
    <script>
        var that;
        var _that;
        class Star {
            constructor(uname, age) {
                this.uname = uname;
                this.age = age;
                this.btn = document.querySelector('button');
                this.btn.onclick = this.sing;

                // 1. constructor 里面的this 指向的是 创建的实例对象
                that = this;
                console.log(this); // Star {uname: "刘德华", age: undefined, btn: button}
            }
            sing() {
                // 2. 普通函数的this指向的是它的调用者
                console.log(this); // <button>点击</button>   这个sing方法里面的this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数
                console.log(this.uname); // undefine     btn中没有uname这个属性,所以打印undefine
                console.log(that.uname); // 刘德华       that里面存储的是constructor里面的this,所以打印刘德华
            }
            dance() {
                // 2. 普通函数的this指向的是它的调用者
                _that = this; // 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
                console.log(this); // Star {uname: "刘德华", age: undefined, btn: button}
            }
        }
        var ldh = new Star('刘德华');
        console.log(that === ldh); // true  证明了constructor 里面的this 指向的是 创建的实例对象
        ldh.dance();
        console.log(_that === ldh); // true  证明了dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
    </script>
</body>

构造函数和原型

概述

在典型的OOP的语言中(如Java ),都存在类的概念,类就是对象的模板,对象就是类的实例,但在ES6之前,JS中并没用引入类的概念。
ES6,全称ECMAScript 6.0,2015.06发版。但是目前浏览器的JavaScript是ES5版本,大多数高版本的览器也支持ES6,不过只实现了ES6的部分特性和功能。
在ES6之前,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。

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

  • 1.对象字面量
  • 2.new Object
  • 3.自定义构造函数

构造函数

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

new在执行时会做四件事情:

  1. 在内存中创建一个新的空对象。
  2. 让this指向这个新的对象。
  3. 执行构造函数里面的代码,给这个新对象添加属性和方法。
  4. 返回这个新对象(所以构造函数里面不需要return)。

例子

  • ES5中创建对象的方式(复习)
<script>
    // 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);
    var zxy = new Star('张学友', 19);
    console.log(ldh);//Star {uname: "刘德华", age: 18, sing: ƒ}
    ldh.sing();
    zxy.sing();
    console.log(ldh.uname);//刘德华
</script>
  • ES6中创建对象的方式(类)

<script>
    // 1. 创建类class
    class Star {
        // new的时候自动调用constructor
        constructor(uname, age) { // constructor可以传递参数
            this.uname = uname;
            this.age = age;
        }
    }
    // 2. 创建对象new
    var ldh = new Star("刘德华", 18); // constructor可以返回实例对象
    console.log(ldh); // Star {uname: "刘德华", age: 18}
</script>

JavaScript的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的this上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。
静态成员∶在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
实例成员∶在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问

<script>
    // 构造函数中的属性和方法我们称为成员, 成员可以添加
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
        this.sing = function() {
            console.log('我会唱歌');
        }
    }
    var ldh = new Star('刘德华', 18);

    // 1.实例成员就是构造函数内部通过this添加的成员 uname age sing height就是实例成员
    // JS是一门动态语言,可以直接这样添加实例成员,如下
    ldh.height = 180;
    // 实例成员只能通过实例化的对象来访问,如下
    console.log(ldh.uname);
    console.log(ldh.height);
    ldh.sing();
    console.log(ldh); //Star {uname: "刘德华", age: 18, height: 180, sing: ƒ}
    // 实例成员不可以通过构造函数来访问实例成员,如下
    // console.log(Star.uname);


    // 2. 静态成员 在构造函数本身上添加的成员,如下:  
    Star.sex = '男'; //sex 就是静态成员
    // 静态成员只能通过构造函数来访问
    console.log(Star.sex); // JS是一门动态语言,可以直接这样添加静态成员
    console.log(ldh); // {uname: "刘德华", age: 18, height: 180, sing: ƒ}
    // 不能通过对象来访问
    // console.log(ldh.sex);
</script>

构造函数的问题

在这里插入图片描述

<script>
    // 1. 构造函数的问题. 
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
        this.sing = function() {
            console.log('我会唱歌');
        }
    }
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 19);
    console.log(ldh.sing === zxy.sing);//false   比较这两个函数的时候比较的是地址
</script>

构造函数原型prototype

构造函数通过原型分配的函数是所有对象所共享的。
JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。注意这个prototype就是个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。

<script>
    // 1. 构造函数的问题. 
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
        // this.sing = function() {
        //     console.log('我会唱歌');

        // }
    }
    Star.prototype.sing = function() { //在原型对象里添加sing这个方法   因为对象可以动态地添加属性和方法
        console.log('我会唱歌');
    }
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 19);
    console.log(ldh.sing === zxy.sing); //true   比较这两个函数的时候比较的是地址
    // console.dir(Star);  // 发现prototype这个属性,这个属性的值是用{}表示的,可见Star的prototype属性就是一个对象
    ldh.sing(); //我会唱歌
    zxy.sing(); //我会唱歌
    // 2.一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
</script>

对象原型__proto__

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

  • __proto__对象原型和原型对象prototype是等价的
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype
<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);
    ldh.sing();
    console.log(ldh); // 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
    console.log(ldh.__proto__ === Star.prototype);// true 
    // 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
    // 如果没有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
</script>

在这里插入图片描述

  • 对象原型指向的是构造函数的原型对象,对象原型是一个非标准属性,实际开发的时候,不可以对其进行赋值等操作。

构造函数constructor

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

<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(Star.prototype);
    console.log(ldh.__proto__);
</script>
  • constructor主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
<script>
        function Star(uname, age) {
            this.uname = uname;
            this.age = age;
        }
        // 很多情况下,需要利用constructor 这个属性指回原来的构造函数
        // Star.prototype.sing = function() {
        //     console.log('我会唱歌');
        // }
        // Star.prototype.movie = function() {
        //     console.log('我会演电影');
        // }
        // Star.prototype是一个对象,
        // 如果采用Star.prototype.sing = function(){}的形式,那就是在这个对象里追加公共方法。
        // 但是若采用第二种方法Star.prototype = {}添加公共方法,
        // 这就是对Star.prototype这个对象赋值,将Star.prototype对象中原先的内容全部覆盖了,那么Star.prototype中就没有constructor这个属性了
        Star.prototype = {
            // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
            constructor: Star,
            sing: function() {
                console.log('我会唱歌');
            },
            movie: function() {
                console.log('我会演电影');
            }
        }
        var ldh = new Star('刘德华', 18);
        var zxy = new Star('张学友', 19);
        ldh.sing();
        // console.log(Star.prototype);
        // console.log(ldh.__proto__);
        console.log(Star.prototype.constructor);
        console.log(ldh.__proto__.constructor);
    </script>

构造函数、原型对象、实例之间的关系

(1)每一个构造函数里面都有一个原型对象,是通过构造函数Star.prototype指向这个原型对象的
(2)原型对象里面有一个属性constructor,它又指回了构造函数
在这里插入图片描述
(3)构造函数可以创建一个实例对象,只要new了构造函数就会产生一个实例对象
(4)实例里面也有一个对象原型__proto__,它指向原型对象。所以__proto__和prototype完全一样
(5)对象实例里也有一个constructor,它也可以指回构造函数
在这里插入图片描述
(6)实际上ldh.__proto__指向的就是原型对象,原型对象里的constructor可以指回构造函数
在这里插入图片描述

原型链

Star.prototype和ldh一样也是一个对象,只要是对象那么就有对象原型__proto__的存在;只要是对象原型那么它们指向的都是原型对象

<script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    Star.prototype.sing = function() {
        console.log('我会唱歌');
    }
    var ldh = new Star('刘德华', 18);
    // 1. 只要是对象就有__proto__ 原型, 指向原型对象
    console.log(Star.prototype);//打印该对象,发现存在__proto__,__proto__里面的constructor指向的是Objcet.prototype
    console.log(Star.prototype.__proto__ === Object.prototype);//true
</script>

在这里插入图片描述
Object.prototype是由Object创建出来的,同理在Object.prototype一定有一个constructor指回Object。
Object.prototype也是一个原型对象,那么他也有__proto__,那么Object.prototype.__proto__的内容是什么呢?答:null

<script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    Star.prototype.sing = function() {
        console.log('我会唱歌');
    }
    var ldh = new Star('刘德华', 18);
    // 1. 只要是对象就有__proto__ 原型, 指向原型对象
    console.log(Star.prototype);//打印该对象,发现存在__proto__,__proto__里面的constructor指向的是Objcet.prototype
    console.log(Star.prototype.__proto__ === Object.prototype);
    // 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
    console.log(Object.prototype.__proto__);//null
    // 3. 我们Object.prototype原型对象里面的__proto__原型  指向为 null
</script>

在这里插入图片描述
原型链就像一个链路一样,让我们在查找对象成员的时候一层一层地往上查找,如果最终还是没有找到,那么就返回null。
先看对象实例上有没有待查找的成员,如果无,那么就去Star的原型对象上查看是否有该成员,如果无,再去Object上的原型对象上查看是否有这个成员,如果还没有就返回null。(每一级的原型对象prototype里存放的是该机的constructor、__proto__以及公共方法eg:sing)
在这里插入图片描述

JS中的成员查找机制

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)
  3. 如果还没有就查找原型对象的原型( Object的原型对象)。
  4. 依此类推一直找到Object为止( null )。
  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
<script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    Star.prototype.sing = function() {
        console.log('我会唱歌');
    }
    // Star.prototype.sex = '女';
    // Object.prototype.sex = '男';
    var ldh = new Star('刘德华', 18);
    // ldh.sex = '男';
    console.log(ldh.sex);// 上述3行注释,消除任意一行注释,都可以使这里正常输出
    console.log(ldh.toString()); // toString()方法是Object才有的,这里根据链式查找,也是可以使用这个方法的
</script>

原型对象中的this指向问题

<script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    var that;
    Star.prototype.sing = function() {
        console.log('我会唱歌');
        that = this;
    }
    var ldh = new Star('刘德华', 18);
    // 1. 在构造函数中,里面this指向的是对象实例 ldh
    ldh.sing();
    // 2.原型对象的函数里面的this 指向的是 实例对象 ldh(函数指向它的调用者)
    console.log(that === ldh);//true
</script>

扩展内置对象

可以通过原型对象,对原来的内置对象进行扩展自定义的方法。
比如给数组增加自定义求偶数和的功能。

对于数组本身就有很多内置的方法:反转数组,排序数组等。

<script>
    console.log(Array.prototype);// 通过打印原型对象可以查看数组所有的内置方法
</script>

现在考虑为数组这些内置方法中再追加求和的方法

<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]; // arr是通过Array的构造函数创建出来的,这就是一个实例化的对象(像之前的ldh)
    console.log(arr.sum()); // 6
    console.log(Array.prototype);// 再次打印原型对象可以查看数组所有的内置方法
    // 声明数组的另一个方法
    var arr1 = new Array(11, 22, 33);
    console.log(arr1.sum());// 66
</script>

注意∶数组和字符串内置对象不能给原型对象覆盖操作Array.prototype ={},只能是Array.prototype.xxx = function(){}的方式

继承

继承

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

call()方法

  • 可以调用某个函数
  • 可以修改函数运行时的this指向(注意是修改此次运行时的this指向,而不是永久的this指向)
fun.call (thisArg, arg1, arg2, ...)
  • thisArg:当前调用函数this的指向对象
  • arg1 , arg2∶传递的其他参数
<script>
    // call 方法
    function fn(x, y) {
        console.log(this); //修改之前this指向的是window,修改之后this指向的是o这个对象
        console.log(x + y); //3
    }
    var o = {
        name: 'andy'
    };
    // fn();传统的函数调用方法
    // 1. call() 可以调用函数
    fn.call(); //Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
    // 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
    fn.call(o, 1, 2); //{name: "andy"}
</script>

利用构造函数继承父类的属性

  • ES6之前是利用构造函数继承父类的属性;利用原型对象继承父类的方法。
  • 利用构造函数继承父类的属性的核心方法是:通过call把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性。
<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname,age){
        // 这里的this原本指向父构造函数的实例对象
        this.uname = uname;
        this.age = age;
    }
    // 2. 子构造函数
    function Son(uname, age){
        // 这里的this指向子构造函数的对象实例
    }
</script>

现在上述两个构造函数没有任何关系,那么子构造函数如何才可以使用父构造函数的两个属性呢?
思路:将父构造函数中的this指向改成子构造函数的实例对象。

<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname, age) {
        // this 指向父构造函数的对象实例father
        console.log(this);
        this.uname = uname;
        this.age = age;
    }
    // 2 .子构造函数 
    function Son(uname, age, score) {
        // this 指向子构造函数的对象实例
        Father.call(this, uname, age); // 在调用父类构造方法的同时,将父类的this指向改为son
        console.log(this);
		this.uname = uname;
		this.age = age;
        this.score = score; // 这是子构造函数独有的属性
    }
    var son = new Son('刘德华', 18, 100); // Son {uname: "刘德华", age: 18, score: 100}
    console.log(son); // Son {uname: "刘德华", age: 18, score: 100}
    var farther = new Father('张学友', 20); // 父构造函数的this指向还是指向父构造函数的实例对象 Father {uname: "张学友", age: 20}
</script>
  • 在子构造函数中写上Father.call(this, uname, age);这行代码就相当于将父构造函数的代码

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

之前提到:共有的属性写在构造函数里面,共有的方法写在原型对象里
(1)在父亲的原型对象中添加一个公共方法money()

<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    // 父构造函数的方法
    Father.prototype.money = function() {
        console.log(1000000);
    }
    // 2 .子构造函数 
    function Son(uname, age, score) {
        Father.call(this, uname, age);
        this.score = score;
    }
    var son = new Son('刘德华', 18, 100);
    console.log(son);
    // 通过打印儿子可以看出,儿子没有继承父类的money(),儿子只是调用了父亲的构造函数,没有调用父亲的原型对象,所以儿子找不到父亲的money()
</script>

(2)若将父构造函数的原型对象赋值给子构造函数的原型对象,如下:

<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    //父构造函数的方法
    Father.prototype.money = function() {
        console.log(1000000);
    }
    // 2 .子构造函数 
    function Son(uname, age, score) {
        Father.call(this, uname, age);
        this.score = score;
    }
    // 若将父构造函数的原型对象赋值给子构造函数的原型对象,如下
    Son.prototype = Father.prototype;
    var son = new Son('刘德华', 18, 100);
    console.log(son);
    // 打印儿子,可以发现在儿子的对象原型的指向中(指向Son构造函数的对象原型)果然出现了money方法
</script>

(3)再追加子构造函数专门的方法

<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    // 父构造函数的方法
    Father.prototype.money = function() {
        console.log(1000000);
    }
    // 2 .子构造函数 
    function Son(uname, age, score) {
        Father.call(this, uname, age);
        this.score = score;
    }
    // 若将父构造函数的原型对象赋值给子构造函数的原型对象,如下
    Son.prototype = Father.prototype;
    // 子构造函数专门的方法
    Son.prototype.exam = function() {
        console.log("考试");
    }
    var son = new Son('刘德华', 18, 100);
    console.log(son);
    //此时再打印一下父构造函数的原型对象
    console.log(Father.prototype);
    // 此时出现问题:子构造函数的原型对象有从父构造函数中继承过来的money()也有自己独有的exam(),但是父构造函数的原型对象中也有子构造函数原型对象独有的方法exam()
</script>

出错原因:Son.prototype = Father.prototype;相当于将父原型对象的地址给子原型对象,即让子原型对象指向父原型对象。此时若修改子原型对象,那么父原型对象回一起被修改。那么正确的继承方法是什么呢?

<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname, age) {
        // this 指向父构造函数的对象实例father

        this.uname = uname;
        this.age = age;
    }
    //父构造函数的方法
    Father.prototype.money = function() {
        console.log(1000000);
    }


    // 2 .子构造函数 
    function Son(uname, age, score) {
        // this 指向子构造函数的对象实例
        Father.call(this, uname, age); //在调用父类构造方法的同时,将父类的this指向改为son
        this.score = score; //这是子构造函数独有的属性
    }
    
    //正确的继承方法
    Son.prototype = new Father(); 
 
    //子构造函数专门的方法
    Son.prototype.exam = function() {
        console.log("考试");
    }

    var son = new Son('刘德华', 18, 100);
    console.log(son); 
    console.log(Father.prototype); // 父亲的原型对象中没有多添加子类的exam()
    // console.log(Son.prototype.constructor);的时候,发现Son.prototype中的constructor指回的是父亲的构造函数
</script>

在这里插入图片描述

说明:Son.prototype = new Father();

  • 父构造函数创建了一个实例对象,同时将其地址赋值给Son的原型对象,即让Son的原型对象指向这个Father的实例对象
  • 而Father的实例对象可以通过自己的对象原型__proto__访问Father的原型对象prototype(链式查找)
  • 而Father的原型对象prototype中存放着money这个方法

原因:Son.prototype = new Father();等同于Son.prototype={}说明Son.prototype中的方法完全被覆盖了,Son就没有自己原先的constructor,修改办法:Son.prototype.constructor = Son;如下

<script>
    // 借用父构造函数继承属性
    // 1. 父构造函数
    function Father(uname, age) {
        // this 指向父构造函数的对象实例father

        this.uname = uname;
        this.age = age;
    }
    // 父构造函数的方法
    Father.prototype.money = function() {
        console.log(1000000);
    }
    // 2 .子构造函数 
    function Son(uname, age, score) {
        // this 指向子构造函数的对象实例
        Father.call(this, uname, age); //在调用父类构造方法的同时,将父类的this指向改为son
        this.score = score; //这是子构造函数独有的属性
    }

    Son.prototype = new Father(); 

    // 修改办法:如果利用对象的形式修改了原型对象,别忘了用constructor指回原来的构造函数
    Son.prototype.constructor = Son;

    // 子构造函数专门的方法
    Son.prototype.exam = function() {
        console.log("考试");
    }
    var son = new Son('刘德华', 18, 100);
    console.log(son); //Son {uname: "刘德华", age: 18, score: 100}
    console.log(Father.prototype);
    console.log(Son.prototype.constructor); //!!!指向了父构造函数
</script>

ES5新增方法

  • 数组方法
  • 字符串方法
  • 对象方法

数组方法

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

forEach()方法

array.forEach ( function (currentvalue,index,arr))
  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
<script>
    // forEach 迭代(遍历)数组
    var arr = [1, 2, 3];
    var sum = 0;
    arr.forEach(function(value, index, array) {
        console.log("每个数组的元素" + value);
        console.log("每个数组的索引号" + index);
        console.log("数组本身" + array);
        // 求和
        sum += value;
    })
</script>

filter()方法

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

array.filter (function (currentvalue,index,arr))
  • 注意它直接返回一个新数组
  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身
<script>
    // filter筛选数组
    var arr = [12, 66, 4, 883,7];
    var newArr = arr.filter(function(value, index) {
        // 返回数组中值大于20的元素
        // return value >= 20;
        // 筛选所有的偶数
        return value % 2 === 0
    });
    console.log(newArr); // [66, 88] //  [12, 66, 4, 88]
</script>

some()方法

  • some()方法用于检测数组中的元素是否满足指定条件。通俗点查找数组中是否有满足条件的元素
  • 注意它返回值是布尔值如果查找到这个元素,就返回true,如果查找不到就返回false。
  • 如果找到第一个满足条件的元素,则终止循环不在继续查找。
array.some (function (currentvalue,index,arr))
  • currentValue:数组当前项的值
  • index:数组当前项的索引
  • arr:数组对象本身

例子

<script>
    // some 查找数组中是否有满足条件的元素
    var arr = [10, 30, 4];
    var flag = arr.some(function(value) {
        return value >= 20;
    });
    console.log(flag); // true 数组中有大于20的元素,所以返回true

	var arr1 = ['red', 'pink', 'blue'];
    var flag1 = arr1.some(function(value) {
        return value == 'pink';  // 检查数组中是否有'pink'元素
    });
    console.log(flag1);// true
</script>
  1. filter 也是查找满足条件的元素,返回的是一个数组,而且是把所有满足条件的元素返回回来
  2. some也是查找满足条件的元素是否存在,返回的是一个布尔值,如果查找到第一个满足条件的元素就终止循环;value/index/array用到哪个参数就写哪个参数,用不到就省略不写

forEach和some的区别

<script>
    var arr = ['red', 'green', 'blue', 'pink'];
    // 1. forEach
    arr.forEach(function(value, index) {
        if (value == 'green') {
            console.log('找到了该元素');
            return true; // 在forEach里面return不会终止迭代,仅仅终止了本轮循环
        }
        console.log(index);
    })
    
    // 2.some
    // 如果查询数组中唯一的元素,用some合适
    arr.some(function(value, index) {
        if (value == 'green') {
            console.log('找到了该元素');
            return true; // 在some里面return true终止迭代
        }
        console.log(index);
    })
    // 3.filter
    arr.filter(function(value, index) {
        if (value == 'green') {
            console.log('找到了该元素');
            return true; // 在filter里面return true不终止迭代,和forEach一样
        }
        console.log(index);
    })
</script>

字符串方法

  • trim()方法会从一个字符串的两端删除空白字符。
str.trim ()
  • trim()方法并不影响原字符串本身,它返回的是一个新的字符串
<script>
    //trim方法去除字符串两倍空格  不去除中间空格
    var str = '  an  dy  ';
    console.log(str); //  an  dy  
    var str1 = str.trim();
    console.log(str1);//an  dy
</script>
<body>
    <input type="text"><button>点击</button>
    <div></div>
    <script>
        var btn = document.querySelector('button');
        var input = document.querySelector('input');
        var div = document.querySelector('div');
        //在input中输入:空格空格andy空格空格
        btn.onclick = function() {
        	// 改进
        	var str = input.value.trim();
            // if (input.value.trim() === '') {
            if (str === '') {
                alert('内容不能为空');
            } else {
                console.log(input.value); //空格空格andy空格空格
                console.log(input.value.length); //8
                div.innerHTML = input.value;
            }
        }
    </script>
</body>

对象方法

Object.keys()方法

  • Object.keys()用于获取对象自身所有的属性
object.keys(obj)
  • 效果类似for…in
  • 返回一个由属性名组成的数组
<script>
    // 用于获取对象自身所有的属性名(得不到属性值)
    var obj = {
        id: 1,
        pname: '小米',
        price: 1999,
        num: 2000
    };
    // 返回的是一个数组
    var arr = Object.keys(obj);
    console.log(arr);
    // 后面需要使用属性名的时候将数组拿出来遍历就可以了
    arr.forEach(function(value) {
        console.log(value);
    })
</script>

Object.defineProperty()方法

  • Object.defineProperty()定义对象中新属性或修改原有的属性。
object.defineProperty(obj, prop,descriptor)
  • obj :必需。目标对象
  • prop :必需。需定义或修改的属性的名字
  • descriptor :必需。目标属性所拥有的特性

以下是对第3个参数descriptor的说明,以对象{}的形式书写(以下的默认值都是在使用Object.defineProperty方法定义的属性时的默认值)

  • value:设置属性的值,默认undefined;
  • writable:属性值是否可以重写,true|false,默认false,其中true可以修改属性值;
  • enumerable:目标属性是否可以被枚举遍历,true|false;默认false,false不允许遍历;
  • configurable:目标属性是否可以被删除 或 是否可以再次修改特性,true|false;默认false,表示该属性不可以删除,而且 不可以再次修改descriptor里面所有参数
<script>
    // Object.defineProperty() 定义新属性或修改原有的属性
    var obj = {
        id: 1,
        pname: '小米',
        price: 1999
    };
    // 1. 以前的对象添加和修改属性的方式
    // obj.num = 1000;
    // obj.price = 99;
    // console.log(obj);
    // 2. Object.defineProperty() 定义新属性或修改原有的属性
    //(2)
    Object.defineProperty(obj, 'num', {
        value: 1000
    });
    console.log(obj); // {id: 1, pname: "小米", price: 1999, num: 1000}

    //(3)
    Object.defineProperty(obj, 'price', {
        value: 9.9
    });
    console.log(obj); // {id: 1, pname: "小米", price: 9.9, num: 1000}

    //(4)
    Object.defineProperty(obj, 'id', {
        writable: false,
    });
    obj.id = 2;// id无法被修改
    console.log(obj); // {id: 1, pname: "小米", price: 9.9, num: 1000}

    //(5)
    Object.defineProperty(obj, 'address', {
        value: '中国',  
        enumerable: false
    });

    console.log(obj); // { id: 1, pname: "小米", price: 9.9, num: 1000, address: "中国"}
    console.log(Object.keys(obj)); // ["id", "pname", "price"]   num和address都遍历不出来

    //(6)
    Object.defineProperty(obj, 'address', {
        value: '中国',  
        configurable: false
    });
    delete obj.address;
    console.log(obj); // {id: 1, pname: "小米", price: 9.9, num: 1000, address: "中国"} 无法删除address
    delete obj.pname;
    console.log(obj); // {id: 1, price: 9.9, num: 1000, address: "中国"}  可以删除pname,因为pname不是通过Object.defineProperty这个方法添加的属性

    //(7)
    Object.defineProperty(obj, 'address', {
        value: '中国',
        enumerable: true,
        configurable: true
    });
    console.log(obj.address); 
    // 会报错 因为在(6)中configurable: false;所以该属性的value、writable、enumerable、configurable都不可以在修改
</script>
举报

相关推荐

0 条评论