zoukankan      html  css  js  c++  java
  • 关于js原型链

    关于原型链,我们先贴上一张图(来自某知乎大佬专栏),然后听我娓娓道来。

    先来说说什么是原型?

    JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。但对象的 [[Prototype]] 链接可以为空,虽然很少见。

    所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。

    所有的函数默认都会拥有一个名为 prototype 的公有并且不可枚举的属性,它会指向另一个对象。这个对象通常被称为该函数的原型。

    function Foo () {}
    var f = new Foo()
    
    console.log(Foo.prototype) // {}
    Object.getPrototypeOf(f) === Foo.prototype; // true Object.getPrototypeOf()获取一个对象的[[Prototype]] 链
    //绝大多数(不是所有!)浏览器也支持一种非标准的方法来访问内部 [[Prototype]] 属性
    f.__proto__ === Foo.prototype // true

    当调用new Foo()时,会创建f并给f一个内部的[[Prototype]] 链接关联到Foo.prototype指向的那个对象。

    接着我们来看看“构造函数”

    先看一个例子,

    function Foo () {}
    Foo.prototype.constructor === Foo; // true
    var f = new Foo();
    f.constructor === Foo; // true

    从代码中,我们可以看到Foo.prototype默认的constructor属性引用的是对象所关联的函数Foo,通过构造函数调用创建的对象f也有一个constructor属性,并指向创建这个对象的函数Foo。但是f本身并没有constructor这个属性,而是一种委托。

    实际上,new会劫持所有普通函数并用构造对象的形式来调用它。Foo本身并不是一个构造函数,只是进行了一次构造函数调用。

    换句话说,在 JavaScript 中对于“构造函数”最准确的解释是,所有带 new 的函数调用。 函数不是构造函数,但是当且仅当使用 new 时,函数调用会变成“构造函数调用”。

    function Foo(name) { 
        this.name = name;
    }
    Foo.prototype.myName = function() { 
        return this.name;
    };
    var a = new Foo( "a" );
    var b = new Foo( "b" ); 
    a.myName(); // "a"
    b.myName(); // "b"

    在创建的过程中,a 和 b 的内部 [[Prototype]] 都会关联到 Foo.prototype 上。当 a 和 b 中无法找到 myName 时,它会(通过委托)在 Foo.prototype 上找到。

    在思考一下这个例子,

    function Foo() { /* .. */ }
    Foo.prototype = { /* .. */ }; // 创建一个新原型对象
    var a1 = new Foo();
    a1.constructor === Foo; // false 
    a1.constructor === Object; // true

    当我们创建了一个新对象替换了函数默认的prototype对象引用,那么新对象便不会自动获得constructor属性了。

    实际上,对象的 .constructor 会默认指向一个函数,这个函数可以通过对象的 .prototype 引用。此外,你可以给任意 [[Prototype]] 链中的任意对象添加一个名 为 constructor 的属性或者对其进行修改,你可以任意对其赋值。

    原型继承的机制

    例如,f可以“继承”Foo.prototype 并访 问 Foo.prototype 的 myName() 函数。

    function Foo(name) { 
        this.name = name;
    }
    Foo.prototype.myName = function() { 
        return this.name;
    };
    function Bar(name,label) { 
        Foo.call( this, name );
         this.label = label;
    }
    // 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype
    Bar.prototype = Object.create( Foo.prototype );
    // 注意!现在没有 Bar.prototype.constructor 了,如果你需要这个属性的话可能需要手动修复一下它
    Bar.prototype.myLabel = function() {
        return this.label;
    };
    var a = new Bar( "a", "obj a" );
    a.myName(); // "a"
    a.myLabel(); // "obj a"

    Object.create() 会创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你 指定的对象(本例中是 Foo.prototype)。

    ES6 添加了辅助函数 Object.setPrototypeOf(),可以用标准并且可靠的方法来修 改关联。

    // ES6 之前需要抛弃默认的 Bar.prototype
    Bar.ptototype = Object.create( Foo.prototype );
    // ES6 开始可以直接修改现有的 Bar.prototype 
    Object.setPrototypeOf( Bar.prototype, Foo.prototype );

    那么我们如何寻找出对象的祖先(委托关联)呢?

    1.站在“类”的角度判断

    a instanceof Foo; // true
    instanceof 操作符的左操作数是一个普通的对象,右操作数是一个函数。instanceof 回答
    的问题是:在 a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象?

    2.判断[[Prototype]]反射

    Foo.prototype.isPrototypeOf( a ); // true
    isPrototypeOf(..) 回答的问题是:在 a 的整 条 [[Prototype]] 链中是否出现过 Foo.prototype ?

    关于.__proto__(它不是标准,IE下没有这个属性)

    它可以引用函数内部的[[Prototype]]对象, 如果你想查找原型链,可以通过.__proto__.__proto__...的方式来遍历。它和其他常用函数,如.toString(), isPrototypeOf()...一样,存在于内置的Object.prototype中。

    它的大致实现是这样的,

    Object.defineProperty( Object.prototype, "__proto__", { 
        get: function() {
             return Object.getPrototypeOf( this ); 
        },
        set: function(o) {
             // ES6 中的 setPrototypeOf(..) 
             Object.setPrototypeOf( this, o );
             return o;
         } 
    });

    通常来说,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototype]] 关联的对象上进行查找。

    同理,如果在后者中也没有找到需要的 引用就会继续查找它的 [[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。一般会把对象共有的属性和方法都放在构造函数的原型对象上。

    然后,我们来说说原型链的指向

    1、通过字面量和 new Object() 所创建的对象,他们是构造函数是 function Object() 的实例,Object 构造函数的 prototype 指向原型对象 Object.prototypeObject.prototype 的 constructor 指向构造函数 Object,而实例的 __proto__ 也指向 Object.prototypeObject.prototype 的 __proto__ 指向 null,所以 Object.prototype 也叫做顶级原型对象

    2、最上面图中 new Foo() 创建的对象是构造函数 function Foo() 的实例,Foo 的 prototype 指向原型对象 Foo.prototypeFoo.prototype 的 constructor 指向构造函数 Foo,而实例的 __proto__ 也指向 Foo.prototype,并且 Foo.prototype 虽然是原型对象,但也是对象,所以是构造函数 Object 的实例,__proto__ 指向顶级原型对象 Object.prototype

    3、数组的构造函数是 function Array() 原型链的指向与其他除 Object 以外的构造函数相同,Array.prototype 的 __proto__ 也指向顶级原型对象 Object.prototype,每一个数组都是 Array 的实例,__proto__ 都指向 Array.prototype

    4、ObjectArrayFoo 等构造函数的本质也是对象,他们的构造函数是 function Function()Function 的 prototype指向 Function.prototypeFunction.prototype 的 constructor 指向 Function,所有的构造函数的 __proto__ 都指向 Function.prototype,包括 Function 本身,也就是说构造函数 Function 是由自己构造的,Function.prototype 的 __proto__ 同样指向顶级原型对象 Object.prototype

    下面是题外话:

    关于Object.create()

    Object.create(..) 会创建一个新对象并把它关联到我们指定的对象,这样 我们就可以充分发挥 [[Prototype]] 机制的威力(委托)并且避免不必要的麻烦(比如使 用 new 的构造函数调用会生成 .prototype 和 .constructor 引用)。

    注意:Object.create(null) 会 创 建 一 个 拥 有 空( 或 者 说 null)[[Prototype]] 链接的对象,这个对象无法进行委托。由于这个对象没有原型链,所以 instanceof 操作符(之前解释过)无法进行判断,因此总是会返回 false。 这些特殊的空 [[Prototype]] 对象通常被称作“字典”,它们完全不会受到原 型链的干扰,因此非常适合用来存储数据。

    Object.create()的polyfill代码:

    if (!Object.create) {
        Object.create = function(o) {
            function F(){}
            F.prototype = o; 
            return new F();
        }; 
    }

     参考: 《你不知道的javascript 上卷》

  • 相关阅读:
    真理
    使用C#调用QC的接口
    如何让asp.net应用程序定时自动执行代码
    对话
    科学●哲学●艺术●恶搞
    避免asp.net程序session过期的一个另类方法
    醉翁之意不在酒
    测试团队的新兴职位:测试设计师
    1和0的世界
    名词解释:高阻态,上拉电阻
  • 原文地址:https://www.cnblogs.com/zyl-Tara/p/9712406.html
Copyright © 2011-2022 走看看