美文网首页
JavaScript深入理解 —— 原型、原型链和继承

JavaScript深入理解 —— 原型、原型链和继承

作者: fehysunny | 来源:发表于2017-09-17 15:53 被阅读40次

    普通对象和函数对象

    函数对象:使用函数声明函数表达式Function构造函数创建的对象

    函数实际上是对象,每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。

    普通对象:除了函数对象以外的对象,都是普通对象。

    示例:

    // 定义函数的三个方法
    function f1() {};       // 函数声明
    var f2 = function() {};         // 函数表达式
    var f3 = new Function("num1", "num2", "return num1 + num2");        // Function构造函数
    
    // 创建对象
    var o1 = {};        // 对象字面量
    var o2 = new Object();      // Object构造函数
    var o3 = new f1();      // f1构造函数
    
    // 检测类型
    console.log(typeof Function);  //function
    console.log(typeof Object);  //function
    
    console.log(typeof f1);  //function
    console.log(typeof f2);  //function
    console.log(typeof f3);  //function
    
    console.log(typeof o1);  //object
    console.log(typeof o2);  //object
    console.log(typeof o3);  //object
    

    构造函数

    构造函数: 通过new关键字方式调用的函数。

    在构造函数内部(也就是被调用的函数内):

    1. this 指向实例对象 Object
    2. 这个实例对象的 __proto__ 属性指向构造函数的 prototype
    3. 这个实例对象的constructor属性指向构造函数
    4. 如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 —— 也就是实例对象。

    示例:

    function Person(name) {
        this.name = name;
        this.sayName = function() {
            console.log(this.name);
        };
    }
    var person1 = new Person("Hysunny");
    var person2 = new Person("Max");
    
    console.log(person1 instanceof Person);     // true
    console.log(person1.constructor === person2.constructor);       // true
    
    // 实例的__proto__属性指向构造函数的prototype
    console.log(person1.__proto__ === Person.prototype);        // true
    
    // 实例的constructor指向构造函数
    console.log(person1.constructor === Person);        // true
    
    

    原型对象

    在 JavaScript 中,每当定义一个对象(函数)时候,对象中都会包含一些预定义的属性。其中就包含prototype属性,这个属性指向函数的原型对象

    1. 原型对象是一个普通对象(除Function.prototype之外),存储所有实例对象共享的方法和属性;
    2. 构造函数原型对象是构造函数的一个实例。
    3. 原型对象的constructor属性指向prototype属性所在的函数;
    4. 每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性,这两个属性指向函数的原型对象

    示例:

    function Person(name) {
      this.name = name;
    }
    
    Person.prototype. sayName = function() {
      console.log(this.name);
    }
    
    var person1 = new Person("Hysunny");
    var person2 = new Person("Max");
    
    // 构造函数、原型对象和实例之间有这样的联系
    Person.prototype.constructor === Person;
    person1.__proto__ === Person.prototype;
    person1.constructor === Person;
    
    console.log(person1.constructor === Person);        // true
    console.log(Person.prototype.constructor === Person);       // true
    console.log(Person.prototype.constructor === person1.constructor);      // true
    // => 原型对象是构造函数的一个实例
    
    console.log(Person.prototype.constructor == Person);        // true
    // => 原型对象的constructor属性指向prototype属性所在的函数
    
    console.log(person1.__proto__ === Person.prototype);        // true
    // => __proto__属性指向函数的原型对象
    
    

    注: Function.prototype虽为函数对象,但是是个空函数,没有prototype属性。

    console.log(typeof Function.prototype) // function
    console.log(typeof Function.prototype.prototype) // undefined
    

    原型链

    从上面的分析我们可以知道:JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个__proto__内置属性,用于指向创建它的函数对象的原型对象prototype

    以上面的例子为例:

    1. person1具有__proto__属性,指向Person.prototype
    2. Person.prototype具有__proto__属性,指向Object.prototype
    3. Object.prototype具有__proto__属性,指向null
    console.log(person1.__proto__ === Person.prototype) // true
    console.log(Person.prototype.__proto__ === Object.prototype) // true
    console.log(Object.prototype.__proto__) // null
    

    这些__proto__串起来,就构成了原型链,原型链的顶端为null。绘出原型链图如下:

    prototype-chain.png

    简化如下:

    prototype.png

    疑点解释:

    1. Object.__proto__ === Function.prototype  // true
    // Object是函数对象,是通过new Function()创建,所以Object.__proto__指向Function.prototype。
    
    2. Function.__proto__ === Function.prototype        // true
    // Function 也是对象函数,也是通过new Function()创建,所以Function.__proto__指向Function.prototype。
    
    3. Function.prototype.__proto__ === Object.prototype // true
    // Function本身也是一个构造函数,所以`Function.prototype.__proto__`指向`Object.prototype`
    
    

    继承

    继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式: 接口继承实现继承 。接口继承只继承方法签名,而实现继承则继承实际的方法.由于js中方法没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且其 实现继承 主要是依靠原型链来实现的.

    为什么要实现继承呢?

    最重要的原因之一就是为了抽象(复用代码)

    将公共的代码封装成一个基类,其他子类继承基类,并发展自己特有的属性和样式。

    关于实现继承的方式,我们将在下一篇文章中进行讨论。


    参考资料:

    《JavaScript 高级程序设计》 第三版

    js原型与原型链终极详解

    相关文章

      网友评论

          本文标题:JavaScript深入理解 —— 原型、原型链和继承

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