JavaScript01(原型链,继承)

今天是2018年11月12日

1.原型

在业务中一定存在需要重复使用某一个对象方法,代码如下
function Person(name){
            this.name=name;
            this.eat=function(){
                console.log("i can eat!");
             }
        }
      
        var per=new Person("小明");
        per.eat();
        ......
        var per=new Person("小红");
        per.eat();
Person.eat()方法不断被调用的同时是要开辟内存空间的,那么业务一旦复杂起来就会有过多的内存被占用。因此就需要使用原型方法了。
function Person(name){
            this.name=name;
        }
        Person.prototype.eat=function (){
            console.log("i love eat!!");
        }
        var per=new Person("小明");
        per.eat();
        ......
        var per=new Person("小红");
        per.eat();
eat()方法添加到Person对象的prototype中可以使资源共享,每一次调用的eat()指向的都是同一个方法,因此会减少内存的占用.
而这个prototype指的就是原型对象,我们注意到上例中per是没有prototype属性的,但我们使用console.dir输出per后就会看到下面这个东西。
__proto__
而当我们展开实例对象per__proto__属性和Person构造函数的prototype属性时,我们可以看到如下
JavaScript01(原型链,继承)_第1张图片
Pserson-prototype

JavaScript01(原型链,继承)_第2张图片
per-__proto__
可以发现两者的结构几乎相同,于是我们直接使用console.log(per.__proto__===Person.prototype)进行对比结果发现两者指向的是同一个东西。
我们可以尝试使用console.dir(string)等语句尝试输出基本类型的结构,结果发现他们都无例外的具备如上特性。
于是我们得出了如下结论:任何一个对象都有__proto__,任何一个构造函数都有prototype,实例对象的__proto__与构造函数prototype本身指向的都是同一个东西,也就是原型对象.
在有了如上结论后,很容易发现,我们可以为基本类型的原型添加属性与方法,这样的操作相当于直接修改了源码。
事实上当我们使用一个构造函数来创建实例对象后,实例对象中的__proto__指向了构造函数的prototype,也就是原型对象,而在原型对象中存在着一个constructor,它指向的则是构造函数本身。如下图
JavaScript01(原型链,继承)_第3张图片
image.png

2.原型链

字面意思上来说,既然叫“链”,那么一定是链式结构了,原型链指的的是实例对象与原型对象之间的联系。
上面已经说了prototype__proto__,但其实prototype本身也是一个对象,那么它就应该有__proto__属性,事实上也的确如此,我们输出控制台看到如下内容:
JavaScript01(原型链,继承)_第4张图片
prototype的__proto__属性
展开
JavaScript01(原型链,继承)_第5张图片
prototype的__proto__属性
很容易发现它其实指向了Object对象,而Object对象则不再具有__proto__属性,我们打印Object.prototype.__proto__发现得到的结果是null,于是我们得到如下结论:
per.__proto__.__proto__.__proto__==null;
Person.prototype.__proto__.__proto__==null;
测试发现
JavaScript01(原型链,继承)_第6张图片
测试结果
那么我们很容易得出一个结论:实例对象的__proto__和构造函数的prototype指向同一个原型对象,该原型对象的__proto__指向Object对象的prototype,而Object对象的prototype作为对象也具有自己的__proto__则指向null。(我晕了你呢)
这样的链式结构就形成了原型链,我们可以修改原型链的指向,但在修改指向前添加进原型的方法将无法被调用。
到这里其实关于原型链的基本内容就算理清了,从修改指向的地方我们隐隐约约可以嗅到一种熟悉的味道:我们修改了原型链的指向以调用不同对象的原型方法与属性,似乎就是面向对象中的一个重要特性——多态。而事实上Js中似乎并没有使用多态(我™也嫌麻烦)

3.继承

JavaScript中没有Class的概念,因此我们的继承是

模拟的!!!

对此我很心痛,当然主要原因是,这个模拟看起来好麻烦。。。跟Javaextends关键字完全没法儿比。。。
先看一下修改原型对象的指向
function Person(name){
            this.name=name;
            this.hhh=function(){
                console.log("Person:hhh");
            }
        }
        Person.prototype.sayHello=function(){
            console.log("person,hello!");
        };

        function Student(name){
            this.name=name;
            this.hhh=function(){
                console.log("Student:hhh");
            }
        }
        Student.prototype.sayHello=function(){
            console.log("student,hello!");
        };


        Student.prototype=new Person("小明");

        Student.prototype.eat=function(){
            console.log("i love eat!");
        };
        var stu=new Student("小明");
        stu.sayHello();
        stu.eat();
        stu.hhh();
运行结果
image.png
需要说明的是,这个例子不仅演示了修改原型对象的指向,同时也表达了修改原型对象指向后再给原型添加方法是可以使用的。反之则会报错。(这一点很容易理解,如果我在房间A中放了一本书,然后让你去B房间,你肯定是拿不到这本书的。)
另外有意思的是,在两个构造函数中的不同名属性是都可以被访问的,于是乎这就为我们“模拟”继承留下了伏笔。既然属性可共用,那么我们就可书写如下代码来“模拟”继承
function Person(name,age,sex){
            this.name=name;
            this.age=age;
            this.sex=sex;
        }
        Person.prototype.eat=function(){
            console.log("eat!");
        };

        function Student(score){
            this.score=score;
        }
        Student.prototype=new Person("小明","29","男");
        Student.prototype.study=function(){
            console.log("study!")
        };

        var xiaoming=new Student("100");
        console.log(xiaoming.name+""+xiaoming.age+""+xiaoming.sex+""+xiaoming.score);
        xiaoming.eat();
        xiaoming.study();
        ......
很容易发现这种继承是没有办法使用的,在修改原型对象的指向时我们写死了Person对象的属性,这样即使能继承“父类”的方法与属性,也是没有办法使用的。于是我们使用如下方法改进
function Person(name,age,sex){
            this.name=name;
            this.age=age;
            this.sex=sex;
        }
        Person.prototype.eat=function(){
            console.log("eat!");
        };

        function Student(name,age,sex,score){
            Person.call(this,name,age,sex);
            this.score=score;
        }
        Student.prototype=new Person();
        Student.prototype.study=function(){
            console.log("study!")
        };

        var xiaoming=new Student("100");
        console.log(xiaoming.name+""+xiaoming.age+""+xiaoming.sex+""+xiaoming.score);
        xiaoming.eat();
        xiaoming.study();
        var xiaohong=new Student("小红","21","女","90");
        ......
使用该方法可以解决属性不能修改的问题,但方法却不能通过这种方式得到“继承”,于是我们采用了结合的方法模拟继承,代码如下
function Person(name,age,sex){
            this.name=name;
            this.age=age;
            this.sex=sex;
        }
        Person.prototype.eat=function(){
            console.log("eat!");
        };

        function Student(name,age,sex,score){
            Person.call(this,name,age,sex);
            this.score=score;
        }
        Student.prototype=new Person();
        Student.prototype.study=function(){
            console.log("study!")
        };

        var xiaoming=new Student("小明","20","男","100");
        console.log(xiaoming.name+""+xiaoming.age+""+xiaoming.sex+""+xiaoming.score);
        xiaoming.eat();
        xiaoming.study();
        var xiaohong=new Student("小红","21","女","90");
        console.log(xiaohong.name+""+xiaohong.age+""+xiaohong.sex+""+xiaohong.score);
        xiaohong.eat();
        xiaohong.study();
        var xiaogang=new Student("小刚","19","男","89");
        console.log(xiaogang.name+""+xiaogang.age+""+xiaogang.sex+""+xiaogang.score);
        xiaogang.eat();
        xiaogang.study();
使用这种模拟方式便可以实现继承,同时还有另外一种方法叫“拷贝继承”,就是把“父类”属性与方法全部拷贝的到“子类”中,俺寻思着这可能是真“继承”了。。。

4.杂谈

到这里关于原型链和继承的基本内容以及写完了,鼓捣了一天才学这么点东西也是醉了= =!隐约觉得这些东西还有更大的用处的。。。现就这样吧。

你可能感兴趣的:(JavaScript01(原型链,继承))