zoukankan      html  css  js  c++  java
  • 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>
    
  • 相关阅读:
    小程序中template的用法
    小程序弹窗的几种形式
    js怎样截取以'-'分割的字符串
    js怎样截取字符串后几位以及截取字符串前几位
    局域网聊天软件项目小结(1)
    IPAddress类
    Combobox 成员添加
    tcpclient 类
    console.read()读入的内容
    技术带来的进步与退步---一点点反思
  • 原文地址:https://www.cnblogs.com/deer-cen/p/12382581.html
Copyright © 2011-2022 走看看