• js原型链继承的傻瓜式详解


    本文争取用最简单的语言来讲解原型链继承的OOP原理

    0.如果对原型继承还没有大致了解,完全一头雾水,请先阅读

    《JavaScript高级程序设计》第六章最后部分的寄生组合式继承

    或者_廖雪峰js教程里面面向对象部分的原型承部分https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014344997013405abfb7f0e1904a04ba6898a384b1e925000

    1.类,构造函数和实例

    如果对函数使用 new,那么会默认返回一个对象,这个对象是该函数的实例。

    var x = new Object();

    此时x是一个Object的实例

    假如我们有两种构造函数

    //情况A
    function Person(name,age){
        this.name = name;
        this.age = age;
    
        this.sayHi = function (){
            alert("Hi,I'm "  + this.name);
        }
    }
    
    var wang = new Person('老王',30);
    var li = new Person('老李',22);
    
    wang.sayHi == li.sayHi //false
    
    
    //情况B
    
    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    
    Person.prototype.sayHi = function (){
        alert("Hi,I'm "  + this.name);
    }
    
    var wang = new Person('老王',30);
    var li = new Person('老李',22);
    
    wang.sayHi == li.sayHi //true

    wang,li是两个Person类的实例,Person是他们的构造函数。

    第一种情况A,方法被重复定义,各实例的方法实际上是被分别定义了一遍。第二种情况B,实现了类的方法的复用。

    显然,第一种方法里面,sayHi函数是各实例自有的属性,第二种方法,sayHi被定义在了Person的prototype上面。

    要点1:当实例对象本身不存在某个属性时,js会查找该实例的【构造函数】的【prototype】属性

    2.如何继承

    问题来了,如果定义一个子类,比如Student,如何继承Person类。

    按照廖雪峰老师和书上比较推荐的方法

    function Student(name, age, grade){
        Person.call(this, name, age);
        this.grade = grade;
    }
    
    
    //--------
    function F(){};
    F.prototype = Person.prototype;
    Student.prototype = new F();
    Student.prototype.constructor = Student;
    //--------
    
    Student.prototype.sayHello =function(){
            alert("Hello, I'm "+ this.name +", I'm in grade " + this.grade + ", nice to meet you.");
        };
    
    var wang_son = new Student('老王儿子',8,'二年级');
    wang_son.sayHi();//Hi,I'm 老王儿子
    wang_son.sayHello();//Hello, I'm 老王儿子, I'm in grade 二年级, nice to meet you.
    wang_son.sayHi == wang.sayHi; //结果是true

    结果来看确实比较良好的实现了继承,可是中间那四行是什么玩意,那个F又是什么?好像除了中间这四行,其它的都挺容易懂。简单的四行代码,把人绕的云里雾里的。

    道格拉斯·克罗克福德在 2006年写了一篇文章,题为 Prototypal Inheritance in JavaScript (JavaScript中的原型式继承)。在这篇文章中,他介绍了这种实现继承的方法

    如果不是很熟悉js的细节,这个方法看起来多少有点耍杂技。乍一看很绕,理解之后会发现它的精妙之处。

    先来看一些假设:如果不这么搞行不行?

    不就是继承父类的方法吗,直接Student.prototype = Person.prototype不就得了,

    结果是 wang_son.sayHi()返回结果正确,但是父类wang有了儿子用的sayHello()了,这明显不对。

    如果wang_son的sayHi需要重写成alert("叔叔好"),那么老王跟你打招呼也会叫叔叔好,这下彻底乱了。

    不就是不重叠不覆盖吗,我把父类的prototype里面有的东西浅复制一份,把那四行代码替换成

    Object.getOwnPropertyNames(Person.prototype).forEach(function(key){
        Student.prototype[key]=Person.prototype[key]
    });

    看似没什么问题,改子类代码不会影响父类,但是在后续代码中,如果修改或者添加父类的方法,子类是不会随着变化的。二者的状态只是在复制的一瞬间进行了同步。

    那。。。试试把他俩连起来呢? Student.prototype.prototype = Person.prototype  这不就是标准的继承了吗

    想得美!!

    要点2:只有函数才有prototype属性。普通实例对象没有!!!

    从要点1中得知,访问实例对象没有的属性,会向上追溯,去查该对象的【构造函数的prototype】。

    那问题来了,prototype里面也没有怎么办?查prototype的【构造函数的prototype】啊。毕竟prototype也是个实例对象啊。

    还是上面那些代码,使用typeof Person.prototype 查看类型,返回的是"Object" 也就是说Person.prototype本身是一个Object的实例。

    Person.valueOf();
    //显示结果
    //ƒ Person(name,age){
    //    this.name = name;
    //    this.age = age;
    //}
    Person.valueOf == Object.prototype.valueOf  //返回true

    你看Person一路向上查,顺利的调用到了Object的prototype的valueOf方法

    那么问题就好办了,如果要Student类要继承Person,或者说,  Student.prototype里面查不到,能继续沿着这个链条往上查 Person.prototype,最简单的办法是什么?

    Student.prototype 是 Person 构造出来的就好了。 就这么简单。。。。。。。

    你看看最开始那段代码,有个叫老李的是吧,就是li,他是用Person构造出来的,就先让他当老王儿子的干爹吧( ̄︶ ̄)↗

    把中间那四行代码替换一下

    function Person(name,age){
        this.name = name;
        this.age = age;
    }
    
    Person.prototype.sayHi = function (){
        alert("Hi,I'm "  + this.name);
    }
    
    var wang = new Person('老王',30);
    var li = new Person('老李',22);
    
    
    function Student(name, age, grade){
        Person.call(this, name, age);
        this.grade = grade;
    }
    
    
    //--------下面四行注释掉
    //function F(){};
    //F.prototype = Person.prototype;
    //Student.prototype = new F();
    //Student.prototype.constructor = Student;
    
    Student.prototype = li;
    //--------
    
    Student.prototype.sayHello =function(){
            alert("Hello, I'm "+ this.name +", I'm in grade " + this.grade + ", nice to meet you.");
        };
    
    var wang_son = new Student('老王儿子',8,'二年级');
    wang_son.sayHi();//Hi,I'm 老王儿子
    wang_son.sayHello();//Hello, I'm 老王儿子, I'm in grade 二年级, nice to meet you.
    wang_son.sayHi == wang.sayHi; //结果是true

    然而最神奇的是这么干竟然成了,这叫啥?干爹继承法?

    有点反直觉吧。控制台里面直接打li看输出,干爹老李背负了儿子辈的sayHello方法。

    Person {name: "老李", age: 22, sayHello: ƒ}
    age: 22
    name: "老李"
    sayHello: ƒ ()
    __proto__: Object

    其实没必要非得老李,现场临时生成一个干爹就成了,把老李那句改成。

    Student.prototype = new Person();

    一样的效果。

    问题来了,继承问题好像完美解决了,一句话能搞定,干嘛写四句。原因是这么干还不完美。

    主要问题

    1.Student.prototype.constructor 应该指向该类的构造函数,既Student。如果只找个干爹,不做处理,那么会沿着原型链向上查找,一直找到有这个属性的prototype为止。

    上面的代码构造出来的实例,wang_son.constructor返回的结果是Person。对应这个问题,一句话修正掉,这里不难理解。

    Student.prototype.constructor = Student;

    2.wang_son构造函数里面调用了父类Person的构造函数来初始化自己的属性name,age,而此时li里面也有老李的name,age。虽然老李是老王儿子构造函数的prototype,但是老王儿子自己有name,age属性了,wang_son是访问不到老李的属性的。同理,如果是new Person()构造一个实例充当prototype,里面也有name和age,只不过值是undefined,也是死活访问不到的。可见,我们对这个干爹的属性是毫不关心的。

    这些属性铁定会被子类实例自己的属性覆盖掉,留着没用,那干脆就不要了,同时也省内存开销。

    只要这个干爹能引导我们找到Person的prototype就行,其它无所谓。于是有了下面的代码

    function F(){};
    F.prototype = Person.prototype;
    Student.prototype = new F();

    把构造函数弄成空的,prototype设成父类的,这样一来生成的中间实例里面干干净净,并没有多余的属性。而且继承了父类的原型链。

    看吧,很简单吧。

    把这四句单独打包成函数,往后就可以随便继承了

    function inherits(Child, Parent) {
        var F = function () {};
        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.prototype.constructor = Child;
    }

    没了

  • 相关阅读:
    驱动开发之基本
    Bitmap文件格式+生成一个BMP文件
    PPP 转义字符 编码 和 解码
    数组数据整体按位左移或右移一位
    一个assert的写法
    c++11 右值引用 && std::move()
    openMP一小时初探
    linux命令学习_实验楼(一)
    50 行 Python 代码完成图片转字符
    LFW精确度验证__c++双线程读写txt
  • 原文地址:https://www.cnblogs.com/gk2017/p/9736233.html
Copyright © 2020-2023  润新知