prototype全解全析

prototype和原型链查找:
任何函数都有 prototype属性, prototype是英语“原型”的意思。prototype属性值是个对象,它默认拥有constructor属性指回函数。
举个栗子:

        function sum(a,b){
            return a+b;
        }
        console.log(sum.prototype); // {constructor: ƒ}
        console.log(typeof sum.prototype); // object
        console.log(sum.prototype.constructor == sum); // true

普通函数来说的prototype是没有任何用处的,但构造函数的prototype属性非常有用。
注意:构造函数的prototype属性是它的实例的原型
见图:
在这里插入图片描述
这里面的 _proto_不是W3C的标准属性,而是谷歌浏览器为了学习者学习而创造出现的属性。虽然 _proto_不是标准属性,但原型这条线是在所有浏览器中都存在的,只不过在谷歌浏览器中可以访问到这条线的。
举个栗子:

        function People(name,age,sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
// 实例化:
       var xiaoming = new People('xiaoming',18,'male');
// 测试:
        console.log(xiaoming.__proto__ == People.prototype); // true

原型链查找:
Javascript规定:实例可以打点访问它的原型的属性和方法,这被称为“原型链查找”
举个栗子:

    function People(name,age,sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // 往原型上添加nationality属性:
    People.prototype.nationality = '中国';
    var xiaoming = new People('xiaoming',18,'male');
    console.log(xiaoming.nationality); // 中国
    console.log(xiaoming);

在这里显示中国是没有任何问题的因为:
在这里插入图片描述
在原型链上是可以查找到的。
可以看这副图:

在这里插入图片描述
要打印的是xiaoming.nationality,因为xiaoming身上没有nationality属性,但People.prototype里面有nationality这个属性,所以就会通过_proto_这个链去查找nationality,所以打印出来结果就是中国。

说到这个给一个题目:
在这里插入图片描述
问:实例化一个对象Tom,在没有给Tom添加nationality属性时问console.log(Tom.nationality)的值和给Tom添加Tom.nationality = '美国';console.log(Tom.nationality)的值?
大家想想应该都能得出结果,在没有添加时,Tom没有nationality这个属性,因此就会往原型上找,所以打印出来的结果是中国,但如果一添加Tom.nationality,那么在打印Tom.nationality时就不需要往原型上找了(自身就有),所以打印出来的结果是美国。

    function People(name,age,sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // 往原型链上设置nationality属性:
    People.prototype.nationality = '中国';
    // 实例化Tom:
    var Tom = new People('Tom',20,'男');
    Tom.nationality = '美国';

    console.log(Tom.nationality); // 美国

这种情况也叫原型链的遮蔽效应。
hasOwnProperty:
hasOwnProperty方法可以检查对象是否真正“自己拥有”某种属性或者方法。
举例:

    function People(name,age,sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // 往原型链上设置nationality属性:
    People.prototype.nationality = '中国';
    // 实例化xiaoming:
    var xiaoming = new People('xiaoming',18,'male');

   console.log(xiaoming.hasOwnProperty('name')); // true
   console.log(xiaoming.hasOwnProperty('age')); // true
   console.log(xiaoming.hasOwnProperty('sex')); // true
// xiaoming不是自己用于nationality属性,所以结果是false
   console.log(xiaoming.hasOwnProperty('nationality')); // false
   

in:
in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法。
举例:

    function People(name,age,sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // 往原型链上设置nationality属性:
    People.prototype.nationality = '中国';
    // 实例化xiaoming:
    var xiaoming = new People('xiaoming',18,'male');

    console.log('name' in xiaoming); // true
    console.log('age' in xiaoming); // true
    console.log('sex' in xiaoming); //true
    console.log('nationality' in xiaoming); // true

在构造函数里字面量添加方法:
举例:

        function People(name,age,sex) {
            this.sayHello = function() {

            }
        }
        var xiaoming = new People();
        var xiaohong = new People();
        var xiaogang = new People();

        console.log(xiaoming.sayHello == xiaohong.sayHello); // false

在实例化的时候它们不是同一个引用,指向不同的堆内存。

缺点: 把方法直接添加到实例身上的缺点:每个实例和每个实例的方法函数都是内存中的不同函数,会造成内存的浪费。
解决方法: 将方法写到prototype上面。
图片演示:
在这里插入图片描述
在People.prototype上添加方法时,在实例化时都是顺着_proto_这个原型链往People.protype里面找,因为People.prototype是同一个引用,所以可以解决内存的浪费问题。

代码:

        function People(name,age,sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
        // 把方法写到原型上,工整,减少内存占用
        People.prototype.sayHello = function() {
            console.log('我是' + this.name + ',我' + this.age + '岁了');
        }
        People.prototype.grown = function() {
            this.age++;
        }

        var xiaoming = new People('小明',20,'男');
        var xiaohong = new People('小红',10,'女');

		console.log(xiaoming.sayHelllo() == xiaohong.sayHello()); // true
        xiaoming.sayHello(); // 我是小明,我20岁了
        xiaohong.sayHello(); // 我是小红,我10岁了

        xiaoming.grown();
        xiaoming.grown();
        xiaoming.grown();
        
        xiaoming.sayHello(); //  我是小明,我23岁了
        xiaohong.sayHello(); // 我是小红,我10岁了

原型链的终点:

大家先看这张图:
在这里插入图片描述
在这张图片中Peopel.prototype相当于Object new出来的,所以People可以通过_proto_原型链查找到原型Object.prototype,可以通过console.log(xiaoming.__proto__.__proto__ == Object.prototype);进行验证,还有一点这里也可以解释了:上面我们讲过hasOwnProperty()toString()方法,知道xioaming可以打点调用这两个方法,为什么可以调用?这里就得到了答案,因为hasOwnProperty()toString()方法是原型链终点Object.prototype的方法,所以xiaoming可以通过原型链查找的方式去找到hasOwnProperty()toString()方法,因此可以打点调用,还有一点就是Object.prototype是原型链的终点,这点可以通过代码console.log(Object.prototype.__proto__);为null进行解释。

具体见下面代码:

        function People(name,age) {
            this.name = name;
            this.age = age;
        }
        var xiaoming = new People('小明',30);
        console.log(xiaoming.__proto__.__proto__ == Object.prototype); // true
    
        console.log(Object.prototype.__proto__); // null

        console.log(xiaoming.toString()); // [object Object]

这里还有个数组的原型链:
在这里插入图片描述
任何数组都是Array实例化的表现(Array new 出来的),在数组的原型链Array.prototype上有push() pop() slice()等方法,所以任何数组都可以打点调用这些方法,通过这点我们就能知道为什么数组可以调用那些方法了,还有一点就是数组的原型链终点也是Object.prototype,所以数组也可以调用hasOwnProperty()toString()方法的。

下面见代码演示:

        var arr = [44,55,33,66];
// 等价于 var arr = new Array(44,55,33,66);
        console.log(arr.__proto__ == Array.prototype); // true
        console.log(arr.__proto__.__proto__ == Object.prototype); // true
        console.log(Array.prototype.hasOwnProperty('push')); // true
        console.log(Array.prototype.hasOwnProperty('pop')); // true

继承(面试题——手写继承):

1、实现继承的关键在于:子类必须拥有父类的全部属性和方法,同时子类还应该能定义自己特有的属性和方法。
2、使用Javascript特有的原型链特性来实现继承,是普遍的做法。
3、在今后学习ES6时会学习新的实现继承的方法。

见图:
在这里插入图片描述
讲解:先定义一个父类People,在父类身上添加方法sayHello(),sleep(),接着定义一个子类Student,然后将子类Student的原型Student.prototype指向父类People new 的实例,然后在子类的原型上(Student.prototype)添加方法study() exam()等,然后再实例化子类var hanmeimei = new Student(),然后hanmeimei就可以通过打点的方法调用各种方法(父类 子类均可以)。其中非常关键的一步就是:将子类Student的原型Student.prototype指向父类People new 的实例Student.prototype = new People();

见代码:

	// 父类:
        function People(name,age,sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
        People.prototype.sayHello = function() {
            console.log('你好我是'+this.name+',我今年'+this.age+'岁了');
        }
        People.prototype.sleep = function() {
            console.log(this.name + '开始睡觉了 zzzzz');
        }

        // 子类 学生
        function Student(name,age,sex,scholl,studentNumber) {
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.scholl = scholl;
            this.studentNumber = studentNumber;
        }
        // 关键语句,实现继承: 要写在子类添加方法之前
        Student.prototype = new People();


        Student.prototype.study = function() {
            console.log(this.name + '正在学习!');
        }
        Student.prototype.exam = function() {
            console.log(this.name + '正在考试!加油!');
        }

        // 实例化:
        var hanmeimei = new Student('韩梅梅',20,'女','爱大学','31299999');

        hanmeimei.study(); // 韩梅梅正在学习!
        hanmeimei.sayHello(); // 你好我是韩梅梅,我今年20岁了
        hanmeimei.exam(); // 韩梅梅正在考试!加油

拓展继承——手写父类的方法:

代码:

        function People(name,age,sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
        People.prototype.sayHello = function() {
            console.log('你好我是'+this.name+',我今年'+this.age+'岁了');
        }
        People.prototype.sleep = function() {
            console.log(this.name + '开始睡觉了 zzzzz');
        }

        // 子类 学生
        function Student(name,age,sex,scholl,studentNumber) {
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.scholl = scholl;
            this.studentNumber = studentNumber;
        }
        // 关键语句,实现继承:
        Student.prototype = new People();


        Student.prototype.study = function() {
            console.log(this.name + '正在学习!');
        }
        Student.prototype.exam = function() {
            console.log(this.name + '正在考试!加油!');
        }
///
        // 重写、复写(overwrite)父类的sayHello方法:
        Student.prototype.sayHello = function() {
            console.log('敬礼!我是' + this.name);
        }
//


        // 实例化:
        var hanmeimei = new Student('韩梅梅',20,'女','爱大学','31299999');

        hanmeimei.study(); // 韩梅梅正在学习!
        hanmeimei.sayHello(); // 敬礼!我是韩梅梅
        hanmeimei.exam(); // 韩梅梅正在考试!加油!
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值