zoukankan      html  css  js  c++  java
  • 原型的深度认识(一)

    JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引

    用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。

    var anotherObject = { 
        a:2
    };
    // 创建一个关联到 anotherObject 的对象
    var myObject = Object.create( anotherObject ); 
    myObject.a; // 2
    

    现在 myObject 对象的 [[Prototype]] 关联到了 anotherObject。显然 myObject.a 并不存在, 但是尽管如此,属性访问仍然成功地(在 anotherObject 中)找到了值 2。

    但是,如果 anotherObject 中也找不到 a 并且 [[Prototype]] 链不为空的话,就会继续查找 下去。

    这个过程会持续到找到匹配的属性名或者查找完整条 [[Prototype]] 链。如果是后者的话, [[Get]] 操作的返回值是 undefined。

    使用 for..in 遍历对象时原理和查找 [[Prototype]] 链类似,任何可以通过原型链访问到的属性都会被枚举。使用 in 操作符来检查属性在对象

    中是否存在时,同样会查找对象的整条原型链(无论属性是否可枚举):

    var anotherObject = {
         a:2
    };
    // 创建一个关联到 anotherObject 的对象
    var myObject = Object.create( anotherObject );
    for (var k in myObject) { 
        console.log("found: " + k);
    }
    // found: a
    ("a" in myObject); // true
    

    但是到哪里是 [[Prototype]] 的“尽头”呢?

    所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。由于所有的“普通” (内置,不是特定主机的扩展)对象都“源于”(或者说把 [[Prototype]] 链的顶端设置为)

    这个 Object.prototype 对象,所以它包含 JavaScript 中许多通用的功能。

    myObject.foo = "bar";
    

    在于原型链上层时 myObject.foo = "bar" 会出现的三种情况。

    1. 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性并且没 有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新 属性,它是屏蔽属性。

    2. 如果在[[Prototype]]链上层存在foo,但是它被标记为只读(writable:false),那么 无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会 抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。

    3. 如果在[[Prototype]]链上层存在foo并且它是一个setter,那就一定会 调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这 个 setter

    var anotherObject = { 
        a:2
    };
    var myObject = Object.create( anotherObject );
    anotherObject.a; // 2
    myObject.a; // 2
    anotherObject.hasOwnProperty( "a" ); // true
    myObject.hasOwnProperty( "a" ); // false
    myObject.a++; // 隐式屏蔽! anotherObject.a; // 2
    myObject.a; // 3
    myObject.hasOwnProperty( "a" ); // true
    

    尽管 myObject.a++ 看起来应该(通过委托)查找并增加 anotherObject.a 属性,但是别忘 了 ++ 操作相当于 myObject.a = myObject.a + 1。因此 ++ 操作首先会通过 [[Prototype]] 查找属性 a 并从 anotherObject.a 获取当前属性值 2,然后给这个值加 1,接着用 [[Put]] 将值 3 赋给 myObject 中新建的屏蔽属性 a

    function Foo() { 
        // ...
    }
    Foo.prototype; // { }
    

    这个对象通常被称为 Foo 的原型,因为我们通过名为 Foo.prototype 的属性引用来访问它。 然而不幸的是,这个术语对我们造成了极大的误导,稍后我们就会看到。如果是我的话就 会叫它“之前被称为 Foo 的原型的那个对象”。

    最直接的解释就是,这个对象是在调用new Foo()时创建的,最后会被(有点武断地)关联到这个“Foo.prototype”对象上。

    我们来验证一下:

    function Foo() { 
        // ...
    }
    var a = new Foo();
    Object.getPrototypeOf( a ) === Foo.prototype; // true
    

    调用new Foo()时会创建a,其中一步就是将a内部的 [[Prototype]] 链接到 Foo.prototype 所指向的对象。

    实际上,绝大多数 JavaScript 开发者不知道的秘密是,new Foo() 这个函数调用实际上并没 有直接创建关联,这个关联只是一个意外的副作用。new Foo()只是间接完成了我们的目 标:一个关联到其他对象的新对象。

    function Foo() {
         // ...
    }
    var a = new Foo();  

    以上的这个构造函数看起来很像一个类。其实它就相当与是调用执行了一个函数,只不过是前面加了new

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

    实际上 a 本身并没有 .constructor 属性。而且,虽然 a.constructor 确实指 向 Foo 函数,但是这个属性并不是表示 a 由 Foo“构造”

    实际上,Foo 和你程序中的其他函数没有任何区别。函数本身并不是构造函数,然而,当 你在普通的函数调用前面加上 new 关键字之后,就会把这个函数调用变成一个“构造函数 调用”。实际上,new 会劫持所有普通函数并用构造对象的形式来调用它。

    关于constructor,通常我们可能理解成,a.constructo指的是a的构造函数是谁。

    其实这是错误的,不妨把它看作是普通的一个属性,也是沿着原型链查找而已。

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

    foo本身的原型被重新赋值了,导致原型链断裂,所以并不是指向foo,而是一直查找到了顶端,指向了Object.prototype,它的上面刚好有constructor属性,一个内置的object函数。

    所以日常不要直接写成foo.prototype = {//...},可以写成foo.prototype.fn = function(){},采取这种单一赋值方法。

    当然,也有弥补的办法,如下:

    function Foo() { 
        /* .. */ 
    }
    Foo.prototype = {
         /* .. */ 
    }; // 创建一个新原型对象
    // 需要在 Foo.prototype 上“修复”丢失的 .constructor 属性 
    Object.defineProperty( Foo.prototype, "constructor" , {
        enumerable: false,
        writable: true,
        configurable: true,
        value: Foo // 让 .constructor 指向 Foo
    } );  

    或者是在重写foo.ptototype时候重新声明一下。

    foo.prototype = {
         constructor:Foo,
         //......  
    }
    

    实际上,对象的 .constructor 会默认指向一个函数,这个函数可以通过对象的 .prototype 引用。“constructor”和“prototype”这两个词本身的含义可能适用也可能不适用。最好的 办法是记住这一点“constructor 并不表示被构造”。  

  • 相关阅读:
    多环境
    Date的after()与before()方法的使用
    Centos6.8 yum报错及修复YumRepo Error: All mirror URLs are not using ftp, http[s] or file. Eg. Invalid
    JSON格式数据解析看这一个就足够了
    widnows下lua开发环境luadist LuaRocks搭建
    树的相关定义及遍历
    win10 启动redis服务的bat
    PageHelper分页失效的可能原因之一
    el-table多选表头复选框不对齐
    好用的软件推荐
  • 原文地址:https://www.cnblogs.com/hjj2ldq/p/9315735.html
Copyright © 2011-2022 走看看