zoukankan      html  css  js  c++  java
  • JavaScript:关于"超长原型链"的解释

    前两天我在一篇文章中讲到了"超长原型链"这个东西,有些读者不是很明白.我觉的有必要再详细的解释一下,首先出道题:

    function Foo() {}                      //构造函数
     
    Foo.prototype.say
    = function () {
    return "I'm a Foo!";
    };
    //原型对象上添加一个say方法 var foo1 = new Foo; //生成一个Foo实例 foo1.say() //继承了原型对象上的say方法,输出"I'm a Foo!"
    Foo.prototype = {} //原型对象指向一个空对象 foo1.say() //输出什么呢?

    你也许会这么认为:

    foo1本身并没有say方法,所以在最后执行foo1.say()时,foo1会从Foo.prototype上找say方法,但这是Foo.prototype已经是个空对象,say属性肯定是undefined,所以上面题目的答案是抛出异常"TypeError: foo1.say is not a function"

    好像的确是这么回事,但是答案错了.正确的答案是仍然输出"I'm a Foo!".为什么呢?貌似上面的解释没问题啊?

    的确.上面这样的解释大部分都对,错就错在了唯一的细节上:

    foo1会从Foo.prototype上找say方法

    更严格的说法应该是:foo1会从foo1的内部属性[[prototype]]指向的对象上找say方法,也就是foo1.__proto__.say.虽然Foo.prototype指向了一个空对象,但foo1.__proto__仍然指向旧的Foo.prototype指向的那个对象.因此,可以继承到say方法.

    另一个需要注意的现象是,一旦你修改了Foo.prototype的指向.instanceof操作符不再认为foo1是Foo类型的实例了.

    foo1 instanceof Foo     //false

    因为instanceof运算符的工作原理就是检查当前的Foo.prototype等于还是不等于foo1.__proto__或者foo1.__proto__.__proto__等等,一直到原型链的尽头null.当前的Foo.prototype指向一个空对象,foo1.__proto__指向原先Foo.prototype指向的那个对象,两者并不相等.所以instanceof会返回false.接下来,让我们再看一个例子:

    function Foo() {}                      //构造函数
    
    console.log(Foo.prototype.__proto__ === Object.prototype)   //原型对象会自动建立,且类型是Object的实例.
    Foo.prototype.say = function () {
        return "I'm a Foo!";
    };                                     //原型对象上添加一个say方法
    var foo1 = new Foo;                    //生成一个Foo实例
    foo1.say()                             //继承了原型对象上的say方法,输出"I'm a Foo!"
    Foo.prototype = new Foo;               //Foo.prototype指向一个Foo实例,就像String.prototype是个String实例一样
    Foo.prototype.say = function () {
        return "I'm a new Foo!";
    };                                     //原型对象上添加一个新的say方法
    console.log(foo1.say())                //仍然输出"I'm a Foo!"
    var foo2 = new Foo;                    //再次新建一个Foo实例
    foo2.say()                             //输出"I'm a new Foo!"

    这时foo1和foo2的原型链是这样的.

    foo1->原来的Foo.prototype->Object.prototype->null

    foo2->新的Foo.prototype->原来的Foo.prototype->Object.prototype->null

    可以用我们上篇文章中写的getPrototypeChain函数验证一下:

    >getPrototypeChain(foo2).forEach(function (obj) {
        console.log(obj.say.toString())
    })
    function () {                                  //输出了新的Foo.say方法
        return "I'm a new Foo!";
    }
    function () {                                  //输出了旧的Foo.say方法
        return "I'm a Foo!";
    }
    TypeError: Cannot call method 'toString' of undefined  //Object.prototype没有say方法,抛出异常

    现在应该能理解上篇文章中给出的代码了:

    function Foo() {}
    
    for (var i = 0; i < 100; i++) {
        Foo.prototype["prop" + i] = i;
        Foo.prototype = new Foo;
    }
    var foo1 = new Foo;
    console.log(getPrototypeChain(foo1).length);          //包含null,一共有103个上层原型
    console.log(Object.getOwnPropertyNames(foo1));        //空数组,foo1没有任何自身属性
    for(var prop in foo1){console.log(prop)}              //foo1从100个不同的原型上继承了100个不同的属性
  • 相关阅读:
    .NET Core: 在.NET Core中进行单元测试
    .NET: 使用.NET Core CLI开发应用程序
    .NET: 谈谈C#中的扩展方法
    WPF: WPF 中的 Triggers 和 VisualStateManager
    WPF: 只读依赖属性的介绍与实践
    XAML: 自定义控件中事件处理的最佳实践
    .NET: 谈谈共享项目 (Shared Project) 的使用
    UWP: 实现 UWP 应用自启动
    UWP: 通过命令行启动 UWP 应用
    在 .NET中,一种更方便操作配置项的方法
  • 原文地址:https://www.cnblogs.com/ziyunfei/p/2731280.html
Copyright © 2011-2022 走看看