JavaScript高级-----3.构造函数和原型

类class是在ES6才新增的,在这之前是通过构造函数和原型来模拟类的实现机制

1. 构造函数和原型

1.1 概述

1.2 构造函数


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>

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

1.3 构造函数的问题

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

上述代码创建两个实例对象,开辟两个内存空间来存放同一个函数,如果避免呢?

1.4 构造函数原型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);
    ldh.sing(); //我会唱歌
    zxy.sing(); //我会唱歌
    // 2. !!!一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
</script>

上述代码执行console.dir(Star);会发现prototype这个属性,这个属性的值是用{...}表示的,可见Star的prototype属性就是一个对象。

疑问:sing这个方法是定义给Star这个构造函数的原型对象,为什么ldh这个对象就可以使用sing方法呢?ldh身上明明没有这个方法呀?

1.5 对象原型_proto_

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

上述代码console.log(ldh);打印的是:


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

1.6 构造函数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>

打印内容如下:因为ldh.__proto__就是指向Star.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);
    // console.log(Star.prototype);
    // console.log(ldh.__proto__);
    console.log(Star.prototype.constructor);
    console.log(ldh.__proto__.constructor);
</script>

打印constructor如下:

很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数:
我们常常把公共的方法放在原型对象中,但是这些公共的方法可能不止一个,比如sing和movie:

<script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    Star.prototype.sing = function() {
        console.log('我会唱歌');
    };
    Star.prototype.movie = function() {
        console.log('我会演电影');
    }
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 19);
    console.log(Star.prototype.constructor);
    console.log(ldh.__proto__.constructor);
</script>

由以下打印内容可见现在constructor指回的还是原来的构造函数Star。说明这两个原型都是通过构造函数Star来创建的。

但是当用对象的形式存储公共方法的时候

<script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    Star.prototype = {
        sing: function() {
            console.log('我会唱歌');
        },
        movie: function() {
            console.log('我会演电影');
        }
    }
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 19);
    console.log(Star.prototype.constructor);
    console.log(ldh.__proto__.constructor);
</script>

由以下打印内容可见现在constructor指回的不是原来的构造函数Star。

原因:Star.prototype是一个对象,如果采用Star.prototype.sing = function(){}的形式,那就是在这个对象里追加公共方法。但是若采用第二种方法Star.prototype = {}添加公共方法,这就是对Star.prototype这个对象赋值,将Star.prototype对象中原先的内容全部覆盖了,那么Star.prototype中就没有constructor这个属性了

<script>
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
    }
    Star.prototype = {
        sing: function() {
            console.log('我会唱歌');
        },
        movie: function() {
            console.log('我会演电影');
        }
    }
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 19);
    console.log(Star.prototype);
    console.log(ldh.__proto__);
    // console.log(Star.prototype.constructor);
    // console.log(ldh.__proto__.constructor);
</script>

打印

上述内容验证了原型中已经没有了这个属性。现在就不知道这个两个原型(Star.prototype和ldh.proto)是谁的孩子了,我们需要手动的利用constructor 这个属性指回 原来的构造函数Star

<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 = {
        // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
        constructor: Star,
        sing: function() {
            console.log('我会唱歌');
        },
        movie: function() {
            console.log('我会演电影');
        }
    }
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 19);
    console.log(Star.prototype);
    console.log(ldh.__proto__);
    console.log(Star.prototype.constructor);
    console.log(ldh.__proto__.constructor);
</script>


以上打印内容可以看出constructor又指回原来的原型函数了,我们就知道这两个对象(Star.prototype和ldh.proto)是通过Star这个构造函数创建出来的。

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

(1)每一个构造函数里面都有一个原型对象,是通过构造函数Star.prototype指向这个原型对象的
(2)原型对象里面有一个属性constructor,它又指回了构造函数

(3)构造函数可以创建一个实例对象,只要new了构造函数就会产生一个实例对象
(4)实例里面也有一个对象原型__proto_,它指向原型对象。所以__proto_和prototype完全一样
(5)对象实例里也有一个constructor,它也可以指回构造函数

(6)实际上ldh.__proto__指向的就是原型对象,原型对象里的constructor可以指回构造函数

举例证明:

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

    //第一段代码
    console.log(ldh);
    //第二段代码
    // console.log(Star.prototype);
    // console.log(ldh.__proto__);
    //第三段代码
    // console.log(Star.prototype.constructor);
    // console.log(ldh.__proto__.constructor);
</script>

执行第一段代码的打印结果:

执行第二部分代码打印结果:(由于ldh.__proto__指向Star.prototype,两者内容完全一样,所以下图只展示了Star.prototype的打印结果)

执行第三段代码的打印结果:打印的都是constructor,指回原本的构造函数

1.8 原型链

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__的内容是什么呢?答:空

<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)

1.9 JS中的成员查找机制


就近原则

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

1.10 原型对象中的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>

1.11 扩展内置对象


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

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

可见,新创建的sum方法已经再里面了

<script>
    // 原型对象的应用 扩展内置对象方法
    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>


以下是错误写法:

<script>
    // 以下是错误写法,如果这样写就会把原来数组里面的所有方法全部覆盖
    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());
</script>
原文地址:https://www.cnblogs.com/deer-cen/p/12382581.html