ES602-原型

JS中创建对象的三种方式

  • 利用 new Object()创建对象
  • 利用对象字面量 {} 创建对象
  • 利用functon className() 结合 new className创建对象
        // 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);
        ldh.sing();
        zxy.sing();

静态成员和实例成员

静态成员
  • 静态成员是在构造函数上直接添加的,只能通过构造函数来访问
Star.sex = "男"
实例成员
  • 实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问
    // 构造函数中的属性和方法我们称为成员, 成员可以添加
    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 就是实例成员
    // 实例成员只能通过实例化的对象来访问
    console.log(ldh.uname);
    ldh.sing();
    // console.log(Star.uname); // 不可以通过构造函数来访问实例成员
    // 2. 静态成员 在构造函数本身上添加的成员  sex 就是静态成员
    Star.sex = '男';
    // 静态成员只能通过构造函数来访问
    console.log(Star.sex);
    console.log(ldh.sex); // 不能通过对象来访问

prototype原型对象

构造函数中存在的问题
  • 构造函数中成员变量实例方法的内存空间是不会共用的,这就会造成内存泄漏的问题。
  • 解决:通过将方法绑定到构造函数中的prototype对象上实现方法的内存共用,确保构造函数实例化出来的不同对象共用的是一个方法。
    /**
     * 在使用构造函数时,如果存在成员实例方法,因为方法是一个复杂对象,每次通过构造函数创建一个新的对象时,都会会这个复杂对象(方法)开辟一个新的内存空间存放,这样就会存在内存泄漏的问题
     * 解决:通过讲方法存放到prototype(原型对象上),做到所有对象公用一个内存地址的方法,节省内存
     * 在JS中,每一个构造函数中都会默认有一个prototype,指向另一个对象,这个对象的所有属性和方法,都会被构造函数所拥有
     * 而对于实例的对象,都会默认有一个 __proto__ 属性,该属性指向的就是构造函数中的prototype,二者等价,所以对象可以使用prototype中的方法和属性
     * 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
     * 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
     * 注意:不论sing在ldh实例对象上或者是__proto__原型对象上,sing中的this始终指向ldh这个实例对象,因为他是调用者。
     * @param uname
     * @param age
     * @constructor
     */
    // 构造函数中的属性和方法我们称为成员, 成员可以添加
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
        this.sing = function () {
            console.log('我会唱歌');

        }
    }

    Star.prototype.jump = function () {
        console.log('I can jump')
    };
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 20);

    console.dir(Star); //可以查看Star中的prototype对象
    console.log(ldh);
    console.log(ldh.sing === zxy.sing); // false,该方法没有绑定到prototype上,每次实例化对象都会开辟新的内存存储该方法
    console.log(ldh.jump === zxy.jump); // true,该方法有绑定到prototype上,每次实例化对象都会重复使用prototype中的jump
    // 调用的模式和sing一样
    ldh.jump();
    zxy.jump();

    // 对象原型
    // 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
    // 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
    console.log(ldh.__proto__ === Star.prototype);// true,二者等价
    console.log("============================");
    console.log(Star.prototype);
    console.log(ldh.__proto__)
对象原型
  • 对于构造函数中实例的对象,都会默认有一个 proto 属性,该属性指向的就是构造函数中的prototype,二者等价,所以对象可以使用prototype中的方法和属性

  • 方法的查找规则,首先看本对象上是否有该成员(变量或方法),有则引用,没有则会在 proto 原型对象上寻找该成员。

    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);
    // 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
    // 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
对象实例-原型对象-构造函数的关系

对象构造函数原型三角关系.png

  • 构造函数中protottpe属性,指向原型对象
  • 实例中 _proto_属性,指向原型对象
  • 原型对象中contrustor属性,指向构造函数
使用构造函数+原型对象模仿类的继承
  • 在构造函数中使用call方法,模拟super关键字,继承父类中成员变量
  • 将字类的原型对象指向父类的实例,并添加属性constructor
    指向字类的构造函数
function Father(uname, age) {
        this.uname = uname;
        this.age = age;
        this.sleep = function () {
            console.log("Father's sleep")
        }
    }

    Father.prototype.money = function () {
        console.log("Father's money")
    };

    function Son(uname, age, school) {
        // 这里借用了call修改Father中的this指向为son对象,从而达到给son赋值属性的目的
        Father.call(this, uname, age);
        this.school = school;
    }


    // 这样继承到Father类中原型对象的方法,而通过call可以继承Father原型类中的实例方法和属性
    Son.prototype.study = function () {
        console.log("Son's study")
    };
    Son.prototype.__proto__ = Father.prototype;  // constructor和实例对象都有原型对象(prototype和__proto___指向的是同一个原型对象),
                                                // 而原型对象也是一个对象,对象都拥有一个原型对象。而prototype中的原型对象__proto__指的就是他的父类的原型对象
    Son.prototype.smile = function () {
        console.log("Son's smile")
    };
    var son = new Son("周文军", 20, "复旦");
    // print(son.uname); // 调用print方法,打印机
    // print(son.age);

    console.log(son.uname);
    console.log(son.age);
    console.log(son);
    console.log(son.__proto__);
    son.money();
    son.study();
    son.sleep();
    son.smile();
    console.log(Father.prototype)
    console.log(son);

    console.log("================================老师的继承");
    // Son.prototype = Father.prototype;  这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
    Son.prototype = new Father(); //这样存在问题,每次继承的原型对象中的方法,都会是father中的实例方法,虽然能做到后面的都共享,
                                // 但是每次都会new一个Father对象。并且新添加的方法只可能在new操作后面执行。
                                // 这种继承方式的另一个缺陷就是Father中的成员变量 name,age会以undefined的形式存在在Son的prototype中
    // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数 !!!!!!!
    Son.prototype.constructor = Son;
    // 这个是子构造函数专门的方法
    Son.prototype.exam = function() {
        console.log('孩子要考试');
    };
    var son2 = new Son('刘德华', 18, 100);
    console.log(son2);
    son2.exam();
    console.log(Father.prototype);
    console.log(Son.prototype.constructor);

    var son3 = new Son('张学友', 18, 100);
    son3.exam()
原型链

原型链.png

    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);
    console.log(Star.prototype.__proto__ === Object.prototype);
    // 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
    console.log(Object.prototype.__proto__);
    // 3. 我们Object.prototype原型对象里面的__proto__原型  指向为 null
原型链中成员的查找规则
  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  • 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
  • 如果还没有就查找原型对象的原型(Object的原型对象)。
    依此类推一直找到 Object 为止(null)。
  • __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
        function Star(uname, age) {
            this.uname = uname;
            this.age = age;
        }
        Star.prototype.sing = function() {
            console.log('我会唱歌');

        };
        Star.prototype.sex = '女';
        Star.prototype.favorate = '唱跳rap篮球';
        // Object.prototype.sex = '男';
        var ldh = new Star('刘德华', 18);
        ldh.sex = '男';
        console.log(ldh.favorate); // 唱跳rap篮球
        console.log(ldh.sex); // 男
        console.log(Object.prototype);
        console.log(ldh);
        console.log(Star.prototype);
        console.log(ldh.toString());
原型对象中的this指向问题
  • 在构造函数中,里面this指向的是对象实例 ldh
  • 原型对象函数里面的this 指向的是 实例对象 ldh
    var that_;
    function Star(uname, age) {
        console.log("function",this);  // 对象实例 ldh
        that_ = this;
        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();
    console.log(that === ldh); // true
    console.log(that === that_); //true
    // 2.原型对象函数里面的this 指向的是 实例对象 ldh
利用原型对象扩展内置对象的方法
    // 原型对象的应用 扩展内置对象方法
    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());
    console.log(Array.prototype);
    var arr1 = new Array(11, 22, 33);
    console.log(arr1.sum());
ES6中类的本质
  • 类的本质其实就是一个函数,因此他也包含prototype,
    并且此prototype中的constructor指向的就是这个类本身,而通过类创建的对象则会含有__proto__原型对象
    // ES6 之前通过 构造函数+ 原型实现面向对象 编程
    // (1) 构造函数有原型对象prototype 
    // (2) 构造函数原型对象prototype 里面有constructor 指向构造函数本身
    // (3) 构造函数可以通过原型对象添加方法
    // (4) 构造函数创建的实例对象有__proto__ 原型指向 构造函数的原型对象
    // ES6 通过 类 实现面向对象编程 
    class Star {
    
    }
    console.log(typeof Star); // function
    // 1. 类的本质其实还是一个函数 我们也可以简单的认为 类就是 构造函数的另外一种写法
    // (1) 类有原型对象prototype 
    console.log(Star.prototype); // Object
    // (2) 类原型对象prototype 里面有constructor 指向类本身
    console.log(Star.prototype.constructor);
    // (3)类可以通过原型对象添加方法
    Star.prototype.sing = function() {
     console.log('冰雨');
    }
    var ldh = new Star();
    console.dir(ldh); 
    // (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
    console.log(ldh.__proto__ === Star.prototype);
    i = i + 1;
    i++
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值