• 深入理解 new 操作符


    和其他高级语言一样 JavaScript 也有 new 操作符,我们知道 new 可以用来实例化一个类,从而在内存中分配一个实例对象。 但在 JavaScript 中,万物皆对象,为什么还要通过 new 来产生对象? 带着这个问题,我们一步步来分析和理解 new 的一些特性:

    认识 new 操作符

    function Animal(name){
        this.name = name;
    }
     Animal.color = "black";
     Animal.prototype.say = function(){
        console.log("I'm " + this.name);
     };
     var cat = new Animal("cat");
    
     console.log(
         cat.name,  //cat
         cat.height //undefined
     );
     cat.say(); //I'm cat
    
     console.log(
         Animal.name, //Animal
         Animal.color //back
     );
     Animal.say(); //Animal.say is not a function

    代码解读如下:

      L1-3: 创建了一个函数Animal,并在其 this 上定义了属性:name,name的值是函数被执行时的形参。

      L4 : 在 Animal 对象(Animal本身是一个函数对象)上定义了一个静态属性:color,并赋值“black”

      L5-7:在 Animal 函数的原型对象 prototype 上定义了一个 say() 方法,say方法输出了 this 的 name 值。

      L8: 通过 new 关键字创建了一个新对象 cat

      L10-14: cat 对象尝试访问 name 和 color 属性,并调用 say 方法。

      L16-20: Animal 对象尝试访问 name 和 color 属性,并调用 say 方法。

    剖析 new 的内部原理

     第8行代码是关键: 

    var cat = new Animal("cat");

    Animal 本身是一个普通函数,但当通过new来创建对象时,Animal 就是构造函数。

    JS引擎执行这句代码时,在内部做了很多工作,用伪代码模拟其内部流程如下:

    new Animal('cat') = {
        var obj = {};
        obj.__proto__ = Animal.prototype;
        var result = Animal.call(obj,"cat");
        return typeof result === 'object'? result : obj;
    }

    将上述流程分为 4 个步骤来理解:

    【1】创建一个空对象 obj;

    【2】把 obj 的__proto__ 指向构造函数 Animal 的原型对象 prototype,此时便建立了 obj 对象的原型链:obj->Animal.prototype->Object.prototype->null

    【3】在 obj 对象的执行环境调用 Animal 函数并传递参数 “ cat ” 。 相当于 var result = obj.Animal("cat")。

             当这句执行完之后,obj 便产生了属性 name 并赋值为 "cat"。关于 call 的用法请参考:深入理解 call、apply 和 bind

    【4】考察第 3 步的返回值,如果无返回值 或者 返回一个非对象值,则将 obj 作为新对象返回;否则会将 result 作为新对象返回。

    根据以上过程,我们发现 cat 其实就是【4】的返回值,因此我们对 cat 对象的认知就多了一些:

    • cat 的原型链是:cat->Animal.prototype->Object.prototype->null
    • cat上新增了一个属性:name

    分析完了 cat 的产生过程,我们再分析一下输出结果:

    • cat.name - 在【3】中,obj 对象就产生了 name 属性。因此 cat.name 就是这里的 obj.name
    • cat.color - cat 对象先查找自身的 color,没有找到便会沿着原型链查找,在上述例子中,我们仅在 Animal 对象上定义了 color,并没有在其原型链上定义,因此找不到。
    • cat.say -  cat会先查找自身的 say 方法,没有找到便会沿着原型链查找,在上述例子中,我们在 Animal 的 prototype 上定义了say,因此在原型链上找到了say 方法。

    另外,在 say 方法中还访问 this.name,这里的 this 指的是其调用者 obj,因此输出的是 obj.name 的值。

    对于Animal来说,它本身也是一个对象,因此它在访问属性和方法时也遵守上述查找规则,所以:

    • Animal.color >  " black "
    • Animal.name >  " Animal " 
    • Animal.say() >   Animal.say is not a function

    需要注意的是,Animal 先查找自身的 name,找到了 name,但这个 name 并不是我们定义的 name,而是函数对象内置的属性,一般情况下,函数对象在产生时会内置 name 属性并将函数名作为赋值(仅函数对象)。

    另外,Animal 在自身没有找到 say() 方法,也会沿着其原型链查找,Animal 的原型链如下所示:

     Animal 的原型链: Animal->Function.prototype->Object.prototype->null

     由于 Animal 的原型链上也没有定义 say 方法,因此返回异常提示。

    探索 new 的意义

    对 new 运算符有了较深入的理解之后,我们再回到开篇提到的问题:在JavaScript 中,万物皆对象,为什么还要通过 new 来产生对象?

    要弄明白这个问题,我们首先要搞清楚 cat 和 Animal 的关系:

    【1】cat 继承了 Animal 对象

    通过上面的分析我们发现, cat 通过原型链继承了 Animal 中的部分属性,因此我们可以简单的认为:Animal 和 cat 是继承关系。

    【2】cat 是 Animal 的实例

    cat 是通过 new 产生的对象,那么 cat 到底是不是 Animal 的实例对象? 我们先来了解一下JS是如何来定义实例对象

    A instanceof B

    如果上述表达式为 true,JavaScript 认为 A 是 B 的实例对象,我们用这个方法来判断一下 cat 和 Animal 。

    cat instanceof Animal; //true

    从结果看,cat 确实是 Animal 实例,要想更加证实这个结果,我们再来了解一下 instanceof 的内部原理:

    var L = A.__proto__;
    var R = B.prototype;
    if(L === R)
        return true;

    如果 A 的__proto__ 等价于 B 的 prototype,就返回 true 。

    在 new 的执行过程【2】中,cat 的 __proto__ 指向了Animal 的 prototype,所以 cat 和 Animal 符合 instanceof 的判断结果。

    因此,通过 new 创建的 对象 和 构造函数 之间建立了一条原型链,原型链的建立,让原本孤立的对象有了依赖关系和继承能力,让JavaScript 对象能以更合适的方式来映射真实世界里的对象,这是面向对象的本质。

    实战演练 

    下面是一个经典例子,涉及 new 、this、以及原型链相关问题,请看代码:

    function Foo(){
        getName = function(){
            console.log(1)
        }
        return this;
    }
    Foo.getName = function(){
        console.log(2)
    }
    Foo.prototype.getName = function(){
        console.log(3)
    }
    var getName = function(){
        console.log(4)
    }
    function getName(){
        console.log(5)
    }
    // ouput:
    Foo.getName(); 
    getName(); 
    Foo().getName(); 
    getName(); 
    new Foo.getName(); 
    new Foo().getName(); 
    new new Foo().getName();
    

        

    原创发布 by @一像素  2015.12

  • 相关阅读:
    输入函数
    pe结构讲解
    pe结构讲解
    小甲鱼pe结构讲解
    pe结构讲解
    pe结构讲解
    YUI3学习笔记 ( 7 )
    [转载 css]用CSS调整scrollbar(滚动条)的配色
    [转载 js]一次性解决png在i6下的透明问题(包括做为img和背景的两种情况)
    YUI3学习笔记 ( 7 )
  • 原文地址:https://www.cnblogs.com/onepixel/p/5043523.html
Copyright © 2020-2023  润新知