理解 javascript 的原型链及继承

作者: 你期待的花开 | 来源:发表于2017-09-14 11:26 被阅读213次

    理解 javascript 的原型链及继承

    class Shape{}
    class Circle extends Shape{}
    const circle = new Circle();
    
    console.log(circle.__proto__ === Circle.prototype);
    console.log(Circle.prototype.__proto__ === Shape.prototype);
    console.log(Shape.prototype.__proto__ === Object.prototype);
    console.log(Object.prototype.__proto__ === null);
    
    console.log(Circle.__proto__ === Shape);
    console.log(Shape.__proto__ === Function.prototype);
    console.log(Function.prototype.__proto__ === Object.prototype);
    console.log(Object.prototype.__proto__ === null);
    
    console.log(circle.constructor === Circle)
    

    以上所有的运行结果都是 true;

    三种构造对象的方法:

    1. 通过对象字面量构造。
    var person1 = {
        name: 'cyl',
        sex: 'male'
    };
    

    形如这个形式的叫做对象字面量。这样子构造出的对象,其[[prototype]]指向Object.prototype

    1. 构造函数构造。
    function Person(){}
    var person1 = new Person();
    

    通过new操作符调用的函数就是构造函数。由构造函数构造的对象,其[[prototype]]指向其构造函数的prototype属性指向的对象。每个函数都有一个prototype属性,其所指向的对象带有constructor属性,这一属性指向函数自身。(在本例中,person1的[[prototype]]指向Person.prototype)

    1. 函数Object.create构造的。
    var person1 = {
        name: 'cyl',
        sex: 'male'
    };
    
    var person2 = Object.create(person1);
    

    本例中,对象person2的[[prototype]]指向对象person1。在没有Object.create函数的日子里,人们是这样做的:

    Object.create = function(p) {
        function f(){}
        f.prototype = p;
        return new f();
    }
    

    Object 对象的属性

    属性 描述
    constructor 返回创建该对象的构造函数。
    prototype 返回创建该对象的函数的原型对象。

    一、原型

    __proto__(隐式原型)

    每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。

    对象proto属性的值就是它所对应的原型对象,隐式原型指向创建这个对象的函数(constructor)的prototype。

    JavaScript中任意对象都有一个内置属性[[prototype]],在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过proto来访问。ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf().
    Object.prototype 这个对象是个例外,它的proto值为null

    var one = {x: 1};
    var two = new Object();
    console.log(one.__proto__ === Object.prototype)//true
    console.log(two.__proto__ === Object.prototype)//true
    

    作用:

    隐式原型的作用:构成原型链,同样用于实现基于原型的继承。举个例子,当我们访问obj这个对象中的x属性时,如果在obj中找不到,那么就会沿着proto依次查找。

    prototype(显式原型)

    不像每个对象都有proto属性来标识自己所继承的原型,只有函数才有prototype属性。

    JS不像其它面向对象的语言,它没有类(class,ES6引进了这个关键字,但更多是语法糖)的概念。JS通过函数来模拟类。

    当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的proto指向承构造函数的prototype来实现这种继承)。

    JS是单继承的,Object.prototype是原型链的顶端,所有对象从它继承了包括toString等等方法和属性。Object.prototype.proto === null,说明原型链到Object.prototype终止。

    作用:

    显式原型的作用:用来实现基于原型的继承与属性的共享。


    Object本身是构造函数,继承了Function.prototype;Function也是对象,继承了Object.prototype。

    Object instanceof Function // true
    Function instanceof Object // true
    

    instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype 属性。

    ES规范是怎么说的?

    Function本身就是函数,Function.proto是标准的内置对象Function.prototype。
    Function.prototype.proto是标准的内置对象Object.prototype。

    例一

    //函数声明创一个空函数
    function foo(){}
    
    
    3229842-48de74a3cfec7e9d

    foo这个函数就有个prototype属性,并且它默认有两个属性:constructor和proto。constructor属性会指向它本身foo,proto是在chrome中暴露的(不是一个标准属性),那么foo.prototype的原型会指向Object.prototype。因此Object.prototype上的一些方法toString,valueOf才会被每个一般的对象所使用。

    例二

    function foo(){}
    foo.prototype.x = 1;
    var obj3 = new foo();//实例对象
    console.log(obj3.x); //1
    

    我们这里有个foo函数,这个函数有个prototype的对象属性,它的作用就是当使用new foo()去构造实例的时候,这个构造器的prototype属性会用作new出来的这些对象的原型。

    在这个例子中,obj3.proto===foo.prototype,即,每个对象都有一个proto属性,指向创建该对象的函数的prototype。Object.prototype是一个特例,它的proto指向的是null。

    例三

    // 声明构造函数
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    
    // 通过prototye属性,将方法放到原型对象上
    Person.prototype.getName = function() {
        return this.name;
    }
    
    var p1 = new Person('tim', 10);
    var p2 = new Person('jak', 22);
    
    3229842-3e7187f42cdfcda9

    构造函数的prototype与所有实例对象的proto都指向原型对象。而原型对象的constructor指向构造函数。

    二、原型链

    每一个构造函数都有一个原型对象,当我们让某一原型对象等于另一构造函数的实例,此时该原型对象就包含一个指针,该指针指向这一构造函数的原型对象,该指针指向的原型对象中包含一个指向这一构造函数的指针,同样我们可以令该指针指向的原型对象等于另一构造函数的实例,如此递进,则形成一条实例与原型的链条,即原型链。

     function Parent() {
            this.name = 'parent';
        };
        Parent.prototype.getName = function() {
            return this.name;
        };
        function Child() {
            this.childname = 'child';
        }
        Child.prototype = new Parent();
        Child.prototype.getChildName = function() {
            return this.childname;
        };
        var child = new Child();
        console.log(child.getName()); //输出parent
        console.log(child.getChildName());  //输出child
    

    在上面的例子中,child实例指向Child原型,Child原型等于Parent实例,即指向Parent原型。可见本质即是以一个构造函数的实例重写原型对象,形成原型链。

    总结

    1、所有的对象都有 proto 属性,该属性对应该对象的原型;
    2、所有的函数对象都有 prototype 属性,该属性的值会被赋值给该函数创建的对象的 proto属性;
    3、所有的原型对象都有constructor属性,该属性对应创建所有指向该原型的实例的构造函数;
    4、函数对象和原型对象通过prototype和constructor属性进行相互关联。

    extends

    用关键词extends声明子类关系:

        class Circle extends Shape {}
    

    extends后面可以接驳任意合法且拥有prototype属性的构造函数。它可以是:

    另一个类
    源自现有继承框架(译者注:作者指的是原型继承,即使在JavaScript中类继承的本质也是原型继承)的近类函数
    一个普通的函数
    一个包含一个函数或类的变量
    一个对象上的属性访问
    一个函数调用

    图示

    1. 在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。即:对象具有属性proto,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。
    2. 方法(Function)方法这个特殊的对象,除了和其他对象一样有上述proto属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

    上图解析

    1.构造函数Foo()构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。
    2.原型对象Foo.prototypeFoo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。
    3.实例f1和f2是Foo这个对象的两个实例,这两个对象也有属性proto,指向构造函数的原型对象,这样子就可以像上面1所说的访问原型对象的所有方法啦。另外:构造函数Foo()除了是方法,也是对象啊,它也有proto属性,指向谁呢?指向它的构造函数的原型对象呗。函数的构造函数不就是Function嘛,因此这里的proto指向了Function.prototype。其实除了Foo(),Function(), Object()也是一样的道理。原型对象也是对象啊,它的proto属性,又指向谁呢?同理,指向它的构造函数的原型对象呗。这里是Object.prototype.最后,Object.prototype的proto属性指向null。

    总结:

    • 对象有属性proto,指向该对象的构造函数的原型对象。
    • 方法除了有属性proto,还有属性prototype,prototype指向该方法的原型对象。

    相关文章

      网友评论

        本文标题:理解 javascript 的原型链及继承

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