资讯专栏INFORMATION COLUMN

JavaScript函数调用的经典例题

cyqian / 2645人阅读

摘要:第二问直接调用函数,相当于调用,那么与函数无关。第三问先执行了函数,然后调用函数的返回值对象的属性函数。第四问再次调用函数时,此时函数已经被第三问执行时修改,所以结果为。

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;

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

    var a; 
    console.log(a);
    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函数修改了。

再看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。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95109.html

相关文章

  • 深入浅出Javascript闭包

    摘要:如果有时需要得到函数内的局部变量。上面代码中,函数就在函数内部,这时内部的所有局部变量,对都是可见的。所谓内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。闭包不能滥用,否则会导致内存泄露,影响网页的性能。 一、引子 闭包(closure)是 Javascript 语言的一个难点,面试时常被问及,也是它的特色,很多高级应用都要依靠闭包实现。本文尽可能用简单易懂的话,讲清楚闭包的概念、...

    tyheist 评论0 收藏0
  • JavaScript例题中彻底理解this

    摘要:最后重点理解结论箭头函数的,总是指向定义时所在的对象,而不是运行时所在的对象。输出,箭头函数不会绑定所以传入指向无效。原因是,要彻底理解应该是建立在已经大致理解了中的执行上下文,作用域作用域链,闭包,变量对象,函数执行过程的基础上。 本文共 2025 字,看完只需 8 分钟 概述 前面的文章讲解了 JavaScript 中的执行上下文,作用域,变量对象,this 的相关原理,但是我...

    Hwg 评论0 收藏0
  • 操作符sizeof与函数strlen哪些事

    摘要:因此二维数组的实质是一维数组,但是其元素类型是一维数组对数组地址解引用后得到是数组名数组首元素地址例题经典再现二维数组练习字符数组希望本文对你有所帮助 文章目录 ...

    BlackHole1 评论0 收藏0

发表评论

0条评论

cyqian

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<