发表于: 2017-05-05 21:23:34

1 1085


【js-04】原型链和访问原型的办法

小课堂【郑州第100期】

分享人:董瑞


1.背景介绍

继承:继承是OO语言(面对对象语言)的一个重要概念,许多OO语言支持两种继承方式:接口继承和实现继承。

接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承。 并且其实现继承主要是依靠原型链实现的。--JavaScript高级程序设计

2.知识剖析


2.1原型prototype JavaScript的每个对象都继承另一个对象,后者称为“原型”(prototype)对象。只有null除外,它没有自己的原型对象。

原型对象上的所有属性和方法,都能被派生对象共享。这就是JavaScript继承机制的基本设计。 通过构造函数生成实例对象时,会自动为实例对象分配原型对象。每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。


                    //创建构造函数Animal
                    function Animal(name) {
                    this.name = name;
                    }

                    // 创建两个实例对象
                    var cat1 = new Animal('大毛');
                    var cat2 = new Animal('二毛');

                    //构造函数Animal的prototype对象,就是实例对象cat1和cat2的原型对象。
                    // 在原型对象上添加一个color属性。结果,实例对象都能读取该属性。
                    Animal.prototype.color = 'white';

                    console.log(cat1.color);// 'white'
                    console.log(cat1.color); // 'white'

                    //原型对象的属性不是实例对象自身的属性。
                    // 但只要修改原型对象,变动就立刻会体现在所有实例对象上。
                    Animal.prototype.color = 'yellow';
                    console.log(cat1.color); // "yellow"
                    console.log(cat1.color); // "yellow"
                    //这是因为实例对象其实没有color属性,都是读取原型对象的color属性。
                    // 也就是说,当实例对象本身没有某个属性或方法的时候,
                    // 它会到构造函数的prototype属性指向的对象,去寻找该属性或方法。
                    // 这就是原型对象的特殊之处。

                    //如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
                    cat1.color = 'black';
                    Animal.prototype.color = 'yellow';

                    console.log(cat1.color);// black
                    console.log(cat2.color);// "yellow";

                    //上面代码中,实例对象cat1的color属性改为black
                    // 就使得它不再去原型对象读取color属性,但cat2的值依然为yellow。                
            

总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的含义,而实例对象可以视作从原型对象衍生出来的子对象。JS中所有对象都有自己的原型对象


2.2 原型链 对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。

比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。 “原型链”的作用是,读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。


2.3constructor属性 prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。 constructor属性的作用,是分辨原型对象到底属于哪个构造函数。

                    
                    //demo2  原型链
                    //2.1
                    function P() {};
                    P.prototype.constructor === P;
                    //定义了一个函数P,P的原型的构造全等于P

                    //2.2
                    //创建实例函数小p
                    var p = new P();

                    p.constructor;
                    // function P() {}

                    //小p的contructor属性全等于大P的contructor属性
                    p.constructor === P.prototype.constructor;// true

                    //但是小P自身没有contructor属性,
                    // 该属性其实是读取原型链上面的P.prototype.constructor属性。
                    p.hasOwnProperty('constructor');// false

                    //2.3
                    //constructor属性的作用,是分辨原型对象到底属于哪个构造函数
                    //新建一个构造函数大F,创建实例对象小f
                    function F() {};
                    function G() {};
                    var f = new F();

                    //实例对象小f的构造函数是大F,而不是G
                    f.constructor === F ;// true
                    f.constructor === G ;// false                

3.常见问题

访问对象原型的方法有哪些?

4.解决方法

获取实例对象obj的原型对象,有三种方法

  • obj.__proto__
  • obj.constructor.prototype
  • Object.getPrototypeOf(obj)

上面三种方法之中,前两种都不是很可靠。
最新的ES6标准规定,__proto__属性只有浏览器才需要部署,其他环境可以不部署。而obj.constructor.prototype在手动改变原型对象时,可能会失效。

5.编码实战


                    //demo3  获取原型对象方法的比较
                    //3.1
                    function H() {};
                    var h = new H();
                    //    三种方法都能获取到当前对象的原型对象
                    h.__proto__===H.prototype;// true
                    h.constructor.prototype===H.prototype;// true
                    Object.getPrototypeOf(h)===H.prototype;// true

                    //注意:最新的ES6标准规定,__proto__属性只有浏览器才需要部署,
                    // 其他环境可以不部署,所以不推荐使用这种方法。
                    //而obj.constructor.prototype在手动改变原型对象时,可能会失效。
                    //见下方demo
                    ////3.2
                    var M = function () {};
                    var m = new M();

                    var N = function () {};
                    N.prototype = m;
                    //N构造函数的原型对象被改成了m

                    //N.prototype.constructor = N;
                    //在改变原型对象时,一般要同时设置constructor属性才能成功。

                    var n = new N();
                    n.constructor.prototype === m ;// false
                    //导致了n.constructor.prototype失真
                    //综上,推荐使用第三种Object.getPrototypeOf方法,获取原型对象。                

6.扩展思考

instanceof运算符的原型链原理? instanceof运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。


                    var v = new Vehicle();
                    v instanceof Vehicle // true                

instanceof运算符的左边是实例对象,右边是构造函数。它的运算实质是检查右边构建函数的原型对象,是否在左边对象的原型链上。 由于instanceof对整个原型链上的对象都有效,因此同一个实例对象,可能会对多个构造函数都返回true。

7.参考文献

参考一:阮一峰

参考二:接口继承

参考三:并没有实现继承

参考四:原型对象

参考五:派生对象

参考六:实例对象

参考七:构造函数

参考八:面向对象语言

参考九:constructor属性

8.更多讨论

从原型对象生成新的实例对象除了new命令外有没有其他方法?




返回列表 返回列表
评论

    分享到