JavaScript函数调用的经典例题

很多初学JavaScript的人对函数的调用理解有些模糊的,话不多说,直接上题:

    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);
    }

请写出以下每行代码的输出结果

     Foo.getName();  
     getName(); 
     Foo().getName(); 
     getName(); 
     new Foo.getName();  
     new Foo().getName();  

此题涉及的知识点比较广泛,包括变量的作用域、变量提升、this指针指向、运算符优先级、原型、继承、等等。

解题之前,我们先分析一下代码:

    //① 函数声明,声明了一个Foo函数,里面有个全局变量getName指向一个匿名函数
    function Foo() {
        getName = function () { 
        console.log(1); 
        };
        return this;
    }
 
    //② Foo函数的创建了一个静态属性getName指向一个匿名函数
    Foo.getName = function () {
        console.log(2);
    };
    
    //③ Foo函数的原型上创建了一个getName方法
    Foo.prototype.getName = function () {
        console.log(3);
    };
    
    //④ 函数表达式,定义一个变量getName指向一个匿名函数 
    var getName = function () { 
        console.log(4);
    };
    
    //⑤ 函数声明,声明了一个叫getName的有名函数
    function getName() { 
        console.log(5);
    }

第一问

Foo.getName(); //2
访问Foo函数上存储的静态属性getName,所以结果为2。

第二问

getName(); //4
直接调用getName函数,相当于调用window.getName,那么与函数①、②、③无关。在分析题目前我们首先知道什么变量提升以及JS引擎工作的执行顺序。

变量提升

所有的变量声明都会被提升到它所在作用域的最开始的部分
例如:

    console.log(a); //undifined
    var a = 10;

例如:

    var a = 10;
    function test(){
        conlose.log(a); //undifined
        var a = 20;
    }
    test();

js引擎执行代码的顺序为:

    var a;
    a = 10;
    function test(){
        var a;
        conlose.log(a); //undifined
        a = 20;
    }

JS引擎工作的执行顺序:

  • 解析器会预先读取函数声明和变量声明,并使其在执行任何代码前可以访问;
  • 再由上而下逐行执行,函数表达式必须等到解析器执行到它所在的代码行才会真正被解释执行

我们再看原题,函数④的变量会被提升,再结合上述JS引擎工作的执行顺序,因此代码的执行顺序是:

    function Foo(){
        getName = function(){
        console.log(1); 
        };
        return this;
    }
    var getName;
    function getName(){
        console.log(5);
    }
    Foo.getName = function(){ 
        console.log(2);
    };
    Foo.prototype.getName = functio() { 
        console.log(3);
    };
    getName = function(){ 
        console.log(4); //最终的赋值覆盖function getName声明
    };

所以结果为4。

第三问

Foo().getName(); //1
Foo().getName(); 先执行了Foo函数,然后调用Foo函数的返回值对象的getName属性函数。

先看前面的Foo()
显然调用了函数①,Foo函数第一句getName = function(){alert(1);};是一句赋值语句,因为它没有var声明,所以先向当前Foo函数作用域内寻找getName变量,没有。再向当前函数作用域上层,即外层作用域内寻找是否含有getName变量,找到函数④var getName=function(){alert(1)};,将此变量getName赋值为function(){alert(1)}。

我们将相关的代码单独拿出:

    function Foo() {
        getName = function () { //全局变量 
        console.log(1); 
        };
    }
    var getName = function () { 
        console.log(4);
    };
    Foo();
    console.log(getName);  //function(){console.log(1); }
 

实质上就是将外层作用域内的getName函数修改了。
2.再看Foo().getName()
Foo函数的返回值结果是this那么Foo().getName()=>this.getName()。
this的指向在函数定义的时候是确定不了的,只有函数被调用执行时才能确定this到底指向谁,谁调用指向谁。而此处是直接调用,this指向window对象。
this.getName()=> window.getName(),而window中的getName已经被修改为console.log(1),所以最终会输出1。
此处考察了两个知识点,一个是变量作用域,一个是this指向。

关于变量作用域,如果将代码变为:

    function Foo() {
        var getName = function () { // 局部变量,只能在函数内部访问
        console.log(1); 
        };
     }
     var getName = function () { 
         console.log(4);
     };
     Foo();
     console.log(getName);//function(){console.log(4);}

那么此题结果则为4了。

第四问

getName(); //1
再次调用getName函数时,此时函数④已经被第三问执行Foo()时修改,所以结果为1。

第五问

new Foo.getName(); //2
–> new(Foo.getName)()

此处考察的是js的运算符优先级。
.的优先级大于new,因此应该先执行Foo.getName(),那么执行函数②,结果是2,然后new了Foo的实例对象即new(Foo.getName)()。
最终结果为2。

第六问

new Foo().getName();//3
–> (new Foo()).getName()

先执行new Foo(),那么执行函数①,返回this,this在构造函数中代表当前实例化对象,所以Foo函数产生一个新的实例对象。
之后调用实例对象的getName方法即(new Foo()).getName()=>实例对象.getName()。
接着我们就要说到构造函数和原型实例和实例对象。
实例对象能够调用的方法和属性只能是定义在自身函数内方法和继承了构造函数原型中的方法和属性。
实例对象.getName()本身并没有定义属性和方法,则继承其构造函数Foo原型中的方法,执行函数③,结果为3。

猜你喜欢

转载自blog.csdn.net/qq_38932474/article/details/108956946