原型

SY原创2022年7月30日
大约 8 分钟约 2473 字

导读

原型相关的笔记

prototype原型

1.原型是什么?

  • 原型是一个对象,我们也称为prototype为原型对象。

2.原型的作用是什么?

  • 共享方法
  • 避免多次开通内存空间,减小内存占用

一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上

function Star(uname, age) {
            this.uname = uname;
            this.age = age;
    // 不建议这样挂载方法
            // this.sing = function() {
            //     console.log('我会唱歌');
            // }
        }
// 建议这样将公共的方法挂载到Star的原型链上,这样以后的每个实例都可以访问
Star.prototype.sing = function () {
     console.log('我会唱歌');
}
console.dir(Star)
  • 打印一下Star会出现下面的样子
  • 将Start展开会出现待展开的prototype
  • 再次展开又会出现一个prototype

image-20220719175030567

因为每一个原型上都有一个 prototype ,所以还可以再套娃(目前只是玩玩儿,可用性待发掘)

Star.prototype.sing.prototype.sing2 = function () {
            console.log('我会唱歌2');
        }
  • 继续打印

image-20220719175925250

接下来创建实例,调用方法

var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing);
ldh.sing();
zxy.sing();
  • 控制台输出

image-20220719180112332

  • 所以可以将实例对象都能使用的方法,挂载到实例的prototype上,无论new了多少个实例,都可以实现数据共享
  • 简单的说就是让他们都指向同一片内存空间,所以ldh.sing === zxy.sing才会为true 是因为都同时指向同一个内存空间
  • 一般情况下,我们的公共属性定义到构造函数里面,公共方法我们放在原型对象身上

只有构造函数本身才有 prototype 这个属性

console.log(Star.prototype)

image-20220719183550578

对象原型__proto__

对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype

对象原型只有__proto__ 没有 prototype

 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.__proto__); // 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
        console.log(Star.prototype);
        ldh.sing();
        ldh.__proto__.sing2 = () => {
            console.log('我会唱歌2');
        }
        ldh.sing2();
        console.log(ldh.__proto__ === Star.prototype);
        // 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
        // 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法

image-20220719184224871

原型对象和对象原型

  • 我们称为 prototype 为原型对象,__proto__为对象原型
  • __proto__对象原型和原型对象 prototype 是等价的
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

image-20220719184548457

对象原型

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,
            // 不加上面的constructor,则会报错,因为我们的原型对象指回的是Star,而不是Star.prototype
            // 不加 constructor 再使用 Star.prototype = {} 就会将原来的Star构造函数给覆盖掉了
            sing() {
                console.log('我会唱歌');
            },
            movie() {
                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);

最终输出

image-20220719214356161

如果把constructor: Star 注释掉

Star.prototype = {
            // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
            // constructor: Star,
            // 不加上面的constructor,则会报错,因为我们的原型对象指回的是Star,而不是Star.prototype
            // 不加 constructor 再使用 Star.prototype = {} 就会将原来的Star构造函数给覆盖掉了
            sing() {
                console.log('我会唱歌');
            },
            movie() {
                console.log('我会演电影');
            }
        }

打印输出

image-20220719214510759

结论:
  • 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
构造函数、实例、原型对象三者之间的关系

image-20220719214732582

原型链

image-20220719214753030

JavaScript 的成员查找机制(规则)
  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
  • 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
  • 如果还没有就查找原型对象的原型(Object的原型对象)。
  • 依此类推一直找到 Object 为止(null)。
  • __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
原型对象this指向

向我们实例对象.

原型对象里面放的是方法, 这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象.

扩展内置对象

每一个内置对象都会在 prototype挂载对应的方法

		console.log(Array.prototype);
        console.log(Object.prototype);
        console.log(Number.prototype);
        console.log(String.prototype);
        console.log(Boolean.prototype);
        console.log(Date.prototype);
        console.log(RegExp.prototype);
        console.log(Error.prototype);
        console.log(Symbol.prototype);

image-20220720114625603

我们也可以自定义方法挂载到内置对象的 prototype

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

image-20220720114829169

call方法

  • call() 可以调用函数
  • call()方法是Function原型上的,所以要通过 方法.call('第一个参数为要将this指向的作用域',...args)
function fn(x, y) {
            console.log('我想喝手磨咖啡');
            this.testSum(x, y)
            console.log(this);
}
        const fn2 = () => {
            console.log(this);
        }
        var o = {
            name: 'andy',
            testSum(x, y) {
                console.log('值为' + (x + y));
            }
        };
       
        // 1. call() 可以调用函数
        // fn.call();
        // 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
        fn.call(o, 321, 2)

image-20220720121357273

  • 上图我们可以看到,在使用 fn.call(o, 321, 2)后,将fnthis指向了o这个对象
  • 我们在o上挂载一个testSum方法,在fn内通过this.testSum()调用,能在控制台中输出结果

借用父构造函数继承属性

		// 借用父构造函数继承属性
        // // 1. 父构造函数
        function Father(uname, age) {
            // this 指向父构造函数的对象实例
            this.uname = uname;
            this.age = age;
        }
        // 2 .子构造函数
        function Son(uname, age, score) {
            // this 指向子构造函数的对象实例
            Father.call(this, uname, age);
            this.score = score;
        }
        var son = new Son('刘德华', 18, 100);
        console.log(son);

// 传入对象形式的构造函数
function Father(obj) {
            // this 指向父构造函数的对象实例
            this.uname = obj.uname;
            this.age = obj.age;

        }
        // 2 .子构造函数
        function Son(obj) {
            // this 指向子构造函数的对象实例
            Father.call(this, {
                uname: obj.uname,
                age: obj.age
            });
            this.score = obj.score;
        }
        var son = new Son({
            uname: '刘德华',
            age: 18,
            score: 100

        });
        console.log(son);

image-20220720124840693

利用原型对象继承方法

// 借用父构造函数继承属性
        // 1. 父构造函数
        function Father(uname, age) {
            // this 指向父构造函数的对象实例
            this.uname = uname;
            this.age = age;
        }
        Father.prototype.money = function () {
            console.log(100000);

        };
        // 2 .子构造函数 
        function Son(uname, age, score) {
            // this 指向子构造函数的对象实例
            Father.call(this, uname, age);
            this.score = score;
        }
        // 使用这样赋值,会将 Father.prototype 父构造函数的内存地址赋值给 Son.prototype 子构造函数,那么接下来给子构造函数原型上添加方法,父构造函数也会出现
        // Son.prototype = Father.prototype; //这样直接赋值会有问题, 如果修改了子原型对象, 父原型对象也会跟着一起变化 
        Son.prototype = new Father();
        // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
        Son.prototype.constructor = Son;
        // 这个是子构造函数专门的方法
        Son.prototype.exam = function () {
            console.log('孩子要考试');
        }
        var son = new Son('刘德华', 18, 100);
        console.log(son);
        console.log(Father.prototype);
        Father.prototype.money()
        son.money()
        son.exam()
        console.log(Son.prototype.constructor);
        /**
         * 如果使用 Son.prototype = Father.prototype; 下面会输出 true
         * 如果使用 Son.prototype = new Father(); 下面会输出 false
         */
        console.log(Son.prototype === Father.prototype);

image-20220720131749583

Loading...