美文网首页Js前端开发者联盟
一个例子让你彻底明白原型对象和原型链

一个例子让你彻底明白原型对象和原型链

作者: Pursue | 来源:发表于2015-06-04 09:26 被阅读21267次

    开篇

    之前对js中的原型链和原型对象有所了解,每当别人问我什么是原型链和原型对象时,我总是用很官方(其实自己不懂)的解释去描述。有一句话说的好:如果你不能把一个很复杂的东西用最简单的话语描述出来,那就说明你没有真正的理解。最近正在读《Javascript高级程序设计》,书中对原型对象和原型链的描述让我受益匪浅,下面仅用一个对比性的例子来说明。

    我们经常会这么写

        function Person () {
            this.name = 'John';
        }
        var person = new Person();
        Person.prototype.say = function() {
            console.log('Hello,' + this.name);
        };
        person.say();//Hello,John
    

    上述代码非常简单,Person原型对象定义了公共的say方法,虽然此举在构造实例之后出现,但因为原型方法在调用之前已经声明,因此之后的每个实例将都拥有该方法。从这个简单的例子里,我们可以得出:
    原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。所有就会有如下等式成立:

                                person.say == new Person().say
    

    可能我们也会这么写

        function Person () {
            this.name = 'John';
        }
        var person = new Person();
        Person.prototype = {
            say: function() {
                console.log('Hello,' + this.name);
            }
        };
        person.say();//person.say is not a function
    

    很不幸,person.say方法没有找到,所以报错了。其实这样写的初衷是好的:因为如果想在原型对象上添加更多的属性和方法,我们不得不每次都要写一行Person.prototype,还不如提炼成一个Object来的直接。但是此例子巧就巧在构造实例对象操作是在添加原型方法之前,这样就会造成一个问题:
    var person = new Person()时,Person.prototype为:Person {}(当然了,内部还有constructor属性),即Person.prototype指向一个空的对象{}。而对于实例person而言,其内部有一个原型链指针proto,该指针指向了Person.prototype指向的对象,即{}。接下来重置了Person的原型对象,使其指向了另外一个对象,即
    Object {say: function}
    这时person.proto的指向还是没有变,它指向的{}对象里面是没有say方法的,因为报错。
    从这个现象我们可以得出:
    在js中,对象在调用一个方法时会首先在自身里寻找是否有该方法,若没有,则去原型链上去寻找,依次层层递进,这里的原型链就是实例对象的__proto__属性

    若想让上述例子成功运行,最简单有效的方法就是交换构造对象和重置原型对象的顺序,即:

        function Person () {
            this.name = 'John';
        }
        Person.prototype = {
            say: function() {
                console.log('Hello,' + this.name);
            }
        };
        var person = new Person();
        person.say();//person.say is not a function
    

    一张图让你秒懂原型链

    其实,只需要明白原型对象的结构即可:

        Function.prototype = {
            constructor : Function,
            __proto__ : parent prototype,
            some prototype properties: ...
        };
    

    总结:函数的原型对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针指向上一层的原型对象,而上一层的原型对象的结构依然类似,这样利用__proto__一直指向Object的原型对象上,而Object的原型对象用Object.prototype.__proto__ = null表示原型链的最顶端,如此变形成了javascript的原型链继承,同时也解释了为什么所有的javascript对象都具有Object的基本方法。

    注:本文与Github同步GIthub文章请戳

    相关文章

      网友评论

      • 0100dbcd1448:最后的prototype结构是精髓:smile:
      • 范仁镗:原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。
        这句话没办法解释如下代码:

        var Obj=function(){
        this.param1="hello";
        }
        Obj.prototype.param2="world";

        obj1=new Obj();
        obj2=new Obj();

        obj1.param2="hello";
        console.log(obj2.param1+obj2.param2);//如果param2是所有实例对象共享的,这里应该输出“hello hello”才对,实际输出还是“helloworld”。
        这应该如何解释这个共享的运作机理?
        范仁镗:@Pursue 明了,obj1.param2=‘hello’的定义绑定的不是Obj类原型对象的param2,而是自身对象属性的param2,思维中老是绕不过静态类型语言的思维固化,谢谢!
        Pursue:@范仁镗 因为obj2自身属性里没有param2,需要在原型链上找,自然找到的world而不是hello,你给obj1定义了param2,跟obj2没有关系,它并没有;多看看上面的图,会先找自身属性,找不到才找原型链。
      • 8391aef3a534:// 每一个构造函数都有一个属性 原型 / 原型对象
        function Student(name, age, sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;

        // this.sayHi = function () {
        // console.log('test');
        // }
        }
        Student.prototype.sayHi = function () {
        console.log('大家好,我是' + this.name);
        }

        // 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
        var s1 = new Student('lilei', 18, '男');
        var s2 = new Student('hmm', 18, '女');

        s1.sayHi();
        console.dir(s1);

        // 当调用对象的属性或者方法的时候,先去找对象本身的属性/方法 ,如果对象没有该属性或者方法。此时去调用原型中的属性/方法
        // 如果对象本身没有该属性/方法,原型中也没有该属性或者方法,此时会报错
        // s1.__proto__ 对象的__proto__ 等于 构造函数的Student.prototype
        // __proto__属性是非标准的属性
        //
        // console.log(s1.__proto__ === Student.prototype);
        // console.dir(s1.__proto__);
        // console.dir(Student.prototype);
      • 757cf267bc0c:第二个例子返回 person.say();//Hello,John
        可能是手误
        0b73aee6b452:@balaballl 原博没错。例一中的say()是函数表达式形式直接添加到Person的prototype属性中,而例二则不同,它相当于采用对象字面量的方式创建了一个Person.prototype的新对象 这里constructor属性不再指向Person函数了 它指向一个新对象的constructor属性(即Object构造函数 因为{}字面量形式创建的对象 相当于 new Object()),故此处person.constructor===Object!
        757cf267bc0c:@balaballl 是 我是说结果你可能忘了改 应该是hellojohn
        Pursue:并没有啊,引用的圆形对象已经变了啊
      • d2c0393d4a9a:还是不理解 原型链是什么东西诶
        Francis__:@赵大树_a158 准确……我在其他文章里看到类似的观点,不过没有您总结的好。好像从E6之后也引入了类的概念,实际上还是个语法糖,本质还是用原型链和prototype属性完成了高级语言中的继承……大概是这样吧
        786eda403562:我的理解是原型链就是一条长河道,从组先级对象到当前对象之间每个对象的新添加属性和方法就相当于小溪会汇入河道。处于下游的就可以使用上游的水(属性和方法)
        404http:文章意思就是在构造函数person里面找,通过person.__proto__里面查找say方法,如果找不到,会往上一层继续找,没找到say方法就直接报错,而它的原型链最顶端是null,里面啥也没有,我理解是这样的
      • cb4c42ab6d48:文章最后一段有误,Object.__proto__ === Function.prototype,Object.prototype.__proto__才等于null
        小宇_fdeb:诶?话说不是Object.prototype=null吗
        Pursue:是的,谢谢指出,已更正
      • cf64450c22cd:因为在学这个内容,理解不透,搜索到您这里。虽然还不太懂,但我想问的是另外的问题:既然声明会提升,那这个交换为什么又会起作用呢。
        进化的叶子:@蛮吉 函数表达式不会被提升
        cf64450c22cd:@Pursue 变量的声明会提升。
        Pursue:@蛮吉 不是特别明白你的问题,能否具体点?

      本文标题:一个例子让你彻底明白原型对象和原型链

      本文链接:https://www.haomeiwen.com/subject/dmudqttx.html