• JavaScript 的原型与继承


    JavaScript 的原型(prototype)及其实例是不容易理解的东西,这里总结一下。

    一、原型与实例

    先看下面的例子:

    function Foo(value) {
      this.name = value;
      this.prototype.type = "example";
    }
    var foo1 = new Foo("hello");
    var foo2 = new Foo("world");
    

    在这个例子中,总共出现三种不同的东西:Foo 是构造函数(constructor),通过使用 new 关键字,我们调用这个构造函数,从 Foo.prototype 这个原型对象中生成 foo1 和 foo2 这两个实例(instance)。三者的关系图示如下:

                   Foo
    Foo.prototype =====> foo1 和 foo2

    举个实际的例子作类比:原型对象 Foo.prototype 相当于纸张,构造函数 Foo 相当于打印机,而实例 foo1 和 foo2 相当于我们打印出来的文章。

    默认情形 Foo 的原型 Foo.prototype 是一个空的对象,我们可以在 Foo 构造函数中添加该原型对象的一些属性,例如上例中的我们增加了 type 属性到 Foo.prototype 中。原型中的属性是被所有实例中共享的,因此 foo1.type 和 foo2.type 都等于 "example";但是构造函数中的属性是与实例有关的,因此 foo1.name = "hello" 不同于 foo2.name = "world"。

    对 Foo 的原型对象的修改也可以放在 Foo 外面,上面的例子这样写也是可以的:

    function Foo(value) {
      this.name = value;
    }
    Foo.prototype.type = "example";
    var foo1 = new Foo("hello");
    var foo2 = new Foo("world");
    

    对于构造函数,通过 prototype 属性可以访问它的原型;而对于实例 foo1 和 foo2,可以通过内部属性 __proto__ (在ECMAScript 标准中称为 [[Prototype]])来访问它们的原型。即有

    foo1.__proto__ === foo2.__proto__ === Foo.prototype
    

    这个 __proto__ 属性是隐藏的,在实际编程中最好不要使用它。因为利用构造函数的 prototype 属性就可以访问原型对象了。

    最后,利用 constructor 属性可以访问原型对象或者实例对象的构造函数。即有

    Foo.prototype.constructor === Foo
    foo1.constructor === foo2.constructor === Foo

    这样,对于这个简单的例子,三者的关系就明朗了。

    二、原型与继承

    在 JavaScript 中,一个构造函数的原型对象是可以自己设定的,如果我们将该原型对象指向另外一个对象,就达到了继承的目的。例如:

    function Parent() {};
    Parent.prototype.familyName = "Warner";
    
    function Child(name) {
      this.givenName = name;
    }
    Child.prototype = new Parent();
    
    var child1 = new Child("Tom");
    var child2 = new Child("Jerry");
    
    child1.gender = "male";
    child2.gender = "female";
    

    在这个例子中,我们指定 Child 构造函数的原型对象为 Parent 函数的原型对象的实例,从而 child1 和 child2 都继承了 Parent 函数的原型对象的 familyName 属性。即有

    child1.familyName === child2.familyName === "Warner"

    在使用原型链实现继承之后,当我们要查找实例的某个属性时,是沿着原型链逐级往上的。比如我们要获取 child1.familyName 的值,首先是在 child1 对象中查找,没找到;接着到 child1.__proto__ 即 Child.prototype 对象中查找,还是没找到。接着就到 child1.__proto__.__proto__ 即 Child.prototype.__proto__ 即 Parent.prototype 对象中查找,终于找到了child1.familyName = "Warner"。如果要获取 child1.givenName 的值,则需要两步。而获取 child1.gender,一步就找到了。

    console.log(child1.gender, child1.givenName, child1.familyName);
    // male Tom Warner
    

    另外,对于继承的情形, child1,child2,以及 Child.prototype 的 constructor 属性指向了 Parent 函数。即有

    child1.constructor === child2.constructor === Child.prototype.constructor === Parent

    三、默认的原型

    对于任何的函数(不管它是否作为构造函数)和对象,它们可以分别看成 Function() 和 Object() 构造函数的实例,因此它们实际上都有一个默认的原型对象,分别指向 Function.prototype 和 Object.prototype。即对于下面的例子:

    fun1 = function() {};
    fun2 = new Function();
    obj1 = {};
    obj2 = new Object();
    

    我们有如下的结果:

    fun1.__proto__ === fun2.__proto__ === Function.prototype;
    obj1.__proto__ === obj2.__proto__ === Object.prototype;
    

    有意思的是,Function() 和 Object() 自身也是函数,因此它们的原型对象,同样指向 Function.prototype。即有

    Object.__proto__ === Function.__proto__ === Function.prototype
    

    最后,Function.prototype 的 __proto__ 属性指向 Object.prototype,而 Object.prototype 的 __proto__ 为空。即有

    Function.prototype.__proto__ === Object.prototype
    Object.prototype.__proto__ === null
    

    实际上,在 JavaScript 中,所有数据类型都是对象的实例。而 Object.prototype 这个原型已经处于最顶层了。

    四、例子及解释

    function Foo() {};
    Foo.prototype.type = "example";
    var foo1 = new Foo();
    
    function Bar() {};
    Bar.prototype = {type: "example"};
    var bar1 = new Bar();
    

    上面两种方式看似一样,实际却不同:foo1 的构造函数为 Foo,而 bar1 的构造函数变成了 Object,即为

    foo1.constructor === Foo.prototype.constructor === Foo
    bar1.constructor === Bar.prototype.constructor === Object
    

    这是因为 Foo.prototype.type = "example"; 没有修改 Foo.prototype 的指向。而 Bar.prototype = {type: "example"};  修改了 Bar.prototype 的指向,相当于继承了 Object ,从而使得 bar1 的构造函数变成了 Object()。即上面的 Bar 代码相当于如下:

    var obj = new Object();
    obj.type = "example";
    function Bar() {};
    Bar.prototype = obj;
    var bar1 = new Bar();

    注记:如果一个函数作为构造函数,而在它内部又用 return 语句返回一个对象,将会导致它所生成的实例指向这个返回的对象。因此,在构造函数中使用 return 是画蛇添足,反而添乱。

    参考资料:
    [1] Javascript Object Hierarchy
    [2] Constructors considered mildly confusing
    [3] 构造函数 - JavaScript 秘密花园
    [4] new - JavaScript | MDN
    [5] constructor - JavaScript | MDN
    [6] __proto__ - JavaScript | MDN

  • 相关阅读:
    undefined与null的区别
    js 合并多个对象 Object.assign
    No 'Access-Control-Allow-Origin' Ajax跨域访问解决方案
    CSS3的REM设置字体大小
    延迟运行方法
    如何用 SQL Tuning Advisor (STA) 优化SQL语句
    Oracle数据库查找持有锁的SQL语句,而不是请求锁的SQL语句(原创)
    Oracle记录登录失败的触发器
    11g ASM新特性
    闪回事务(Flashback Transaction)
  • 原文地址:https://www.cnblogs.com/zoho/p/2855120.html
Copyright © 2020-2023  润新知