javascript之函数的四种调用方式

 函数的四种调用方式如下:

(1)函数调用模式
(2)方法调用模式
(3)构造器模式
(4)上下文模式
1. 函数调用 模式
要调用,就肯定要先定义,函数的定义方式:

声明式: function fuc() {}
表达式式: var func = function() {};
Function: new Function( ‘参数’,…,’函数体’ );
单独独立调用的,就是函数调用模式,即 函数名( 参数 ),不能加任何其他的东西, 对象 o.fuc() 就不是了。

在函数调用模式中, this 表示全局对象 window

任何自调用函数都是函数模式。

2. 方法调用 模式 method
所谓方法调用,就是用对象的方法调用。方法是什么,方法本身就是函数,但是,方法不是单独独立的,而是要通过一个对象引导来调用。

就是说方法对象一定要有宿主对象。

即 对象.方法(参数)

this表示引导方法的对象,就是指宿主对象

对比-函数调用模式:

方法调用模式是不是独立的,需要宿主,而函数调用模式是独立的
方法调用模式方式:obj.fuc(); 函数调用模式方式: fuc();
方法调用模式中,this指宿主。而函数调用模式中 this 指 全局对象window
美团的一道面试题
   

 var length = 10;
    function fn() {
        console.log( this.length ); // 10
    }
    var obj = {
        length: 5,
        method: function ( fn ) {
            fn();   // 10 前面没有引导对象,是函数调用模式
            arguments[ 0 ](); // 2
            // arguments是一个伪数组对象, 这里调用相当于通过数组的索引来调用.
            // 这里 this 就是 指的这个伪数组, 所以 this.length 为 2
        }
    };
    obj.method( fn, 1 );    // 打印 10 和 2
    //obj.method( fn, 1, 2, 3 );    // 打印 10 和 4


解析:

fn() 前面没有引导对象,是函数调用模式, this是全局对象,输出 10

arguments[ 0 ](),arguments是一个伪数组对象, 这里调用相当于通过数组的索引来调用.

这里引导对象即宿主就是 arguments对象。

所以,执行时,this 就是指 arguments,由于传了两个参数,所以 输出为 arguments.length 就是 2

3. 构造器模式(构造函数模式, 构造方法模式)
constructor

a.特点: 使用 new 关键字引导

b.执行步骤:var p = new Person();

new 是一个运算符, 专门用来申请创建对象, 创建出来的对象传递给构造函数的 this。然后利用构造函数对其初始化。

   

 function Person () {
        // new了 进入构造函数时, p 对象的原型 就指向了 构造函数 Person, 
        // p.__proto__.constructor = function Person() {};
        // 而 this 指的的是 p 对象
        this.name = 'jim',
        this.age = 19;
        this.gender = 'male';
    }
    var p = new Person();


执行完 new 进入构造函数时, p 对象的原型 就指向了 构造函数 Person

而 构造时,this 指的的是 p 对象,是通过对象动态添加属性来构造的

小贴士:如果调用构造函数的时候, 构造函数没有参数, 圆括号是可以省略的。

function Person() {
        this.name = 'jim';
    }
    var p = new Person; // 不传参,可以简写,不影响构造
    console.log( p );   // p 含有 name属性


↑ 不传参,可以简写,不影响构造

c..返回值

不写 return 语句, 那么 构造函数 默认返回 this

若构造函数 返回基本类型( 例如:return num, return 1223 ). 则忽略返回类型,返回this.

若构造函数返回引用类型(object和Function), 那么构造函数返回该引用类型数据, 而忽略 this.

 function Person () {
        this.name = 'Jepson';
        return 123;
    }
    var p1 = new Person();
    console.log( p1 );

↑ 忽略了 123,返回 this 对象, 指向构建的实例

 function Person () {
        this.name = 'Jepson';
        return { 'peter': 'nihao' };// 返回对象
    }
    var p1 = new Person();
    console.log( p1 );

↑ 忽略了 this,返回 { ‘peter’: ‘nihao’ } 对象

构造函数结合性
如果构造函数没有参数, 可以省略 圆括号

var p = new Person;

如果希望创建对象并直接调用其方法

( new Person () ).sayHello()

-> 可以省略调整结核性的圆括号 new Person().sayHello()

-> 如果想要省略构造函数的圆括号, 就必须添加结核性的圆括号 (new Person).sayHello()

面试题
一道面试题,大家可以自己尝试先做一下,再看下面的答案和解析

请问顺序执行下面代码,会怎样 alert   

 function Foo(){
        getName = function(){ alert(1); };
        return this;
    }
    Foo.getName = function(){ alert(2); };
    Foo.prototype.getName = function(){ alert(3); };
    var getName = function(){ alert(4); };
    function getName(){ alert(5); }

    Foo.getName();  // alert ??
    getName();  // alert ??
    Foo().getName(); // alert ??
    getName(); // alert ??
    new Foo.getName(); // alert ??
    new Foo().getName(); // alert ??
    new new Foo().getName(); // alert ??


预解析,简化后的代码,以及答案 

  /* function getName(){ alert(5); } 执行到下面被覆盖了,直接删除 */
    function Foo() {
        getName = function () { alert(1); };
        return this;
    }
    Foo.getName = function () { alert(2); };
    Foo.prototype.getName = function () { alert(3); };
    var getName = function () { alert(4); };

    Foo.getName();  // ------- 输出 2 -------
    getName();      // ------- 输出 4 -------
    Foo().getName();    // ------- 输出 1 -------
    getName();  // ------- 输出 1 -------
    new Foo.getName();     // ------- 输出 2 -------
    new Foo().getName();    // ------- 输出 3 -------
    var p = new new Foo().getName();     // ------- 输出 3 -------


全部解析过程 ↓ 

  function Foo() {
        getName = function () { alert(1); };
        return this;
    }
    Foo.getName = function () { alert(2); };
    Foo.prototype.getName = function () { alert(3); };
    var getName = function () { alert(4); };

    Foo.getName();  // ------- 输出 2 -------
    // 调用 Foo函数 作为 对象 动态添加的属性方法 getName
    // Foo.getName = function () { alert(2); };

    getName();      // ------- 输出 4 -------
    // 这里 Foo函数 还没有执行,getName还没有被覆盖
    // 所以 这里还是 最上面的 getName = function () { alert(4); };

    Foo().getName();    // ------- 输出 1 -------
    // Foo()执行,先覆盖全局的 getName 再返回 this,
    // this 是 window, Foo().getName() 就是调用 window.getName
    // 此时 全局的 getName已被覆盖成 function () { alert(1); };
    // 所以 输出 1
    /* 从这里开始 window.getName 已被覆盖 alert 1 */

    getName();  // -------- 输出 1 --------
    // window.getName alert(1);

    new Foo.getName();     // ------- 输出 2 -------
    // new 就是 找 构造函数(),由构造函数结合性,这里即使 Foo无参,也不能省略 (),所以不是 Foo().getName()
    // 所以 Foo.getName 为一个整体,等价于 new (Foo.getName)();
    // 而 Foo.getName 其实就是函数 function () { alert(2); } 的引用
    // 那 new ( Foo.getName )(), 就是在以 Foo.getName 为构造函数 实例化对象。
    // 就 类似于 new Person(); Person 是一个构造函数

    // 总结来看 new ( Foo.getName )(); 就是在以 function () { alert(2); } 为构造函数来构造对象
    // 构造过程中 alert( 2 ),输出 2

    new Foo().getName();    // ------- 输出 3 -------
    // new 就是 找 构造函数(),等价于 ( new Foo() ).getName();
    // 执行 new Foo() => 以 Foo 为构造函数,实例化一个对象
    // ( new Foo() ).getName; 访问这个实例化对象的 getName 属性
    // 实例对象自己并没有 getName 属性,构造的时候也没有 添加,找不到,就到原型中找
    // 发现 Foo.prototype.getName = function () { alert(3); };
    // 原型中有,找到了,所以 ( new Foo() ).getName(); 执行,alert(3)

    var p = new new Foo().getName();     // ------- 输出 3 -------
    // new 就是 找 构造函数(),等价于 new ( ( new Foo() ).getName )() 输出 3

    // 先看里面的 ( new Foo() ).getName
    // new Foo() 以Foo为构造函数,实例化对象
    // new Foo().getName 找 实例对象的 getName属性,自己没有,去原型中找,
    // 发现 Foo.prototype.getName = function () { alert(3); }; 找到了

    // 所以里层 ( new Foo() ).getName 就是 以Foo为构造函数实例出的对象的 一个原型属性
    // 属性值为一个函数 function () { alert(3); } 的引用

    // 所以外层 new ( (new Foo()).getName )()在以该函数 function () { alert(3); } 为构造函数,构造实例
    // 构造过程中 执行了 alert(3), 输出 3


4. 上下文调用模式
就是 环境调用模式 => 在不同环境下的不同调用模式

简单说就是统一一种格式, 可以实现 函数模式与方法模式

-> 语法(区分)

call 形式, 函数名.call( … )
apply 形式, 函数名.apply( … )

这两种形式功能完全一样, 唯一不同的是参数的形式. 先学习 apply, 再来看 call 形式

apply方法的调用形式


存在上下文调用的目的就是为了实现方法借用,且不会污染对象。

如果需要让函数以函数的形式调用, 可以使用

foo.apply( null ); // 上下文为 window

如果希望他是方法调用模式, 注意需要提供一个宿主对象

foo.apply( obj ); // 上下文 为 传的 obj 对象

    function foo () {
        console.log( this );
    }
    var o = { name: 'jim' };

    // 如果需要让函数以函数的形式调用, 可以使用
    foo.apply( null ); //  this => window  // 或 foo.apply()

    // 如果希望他是方法调用模式, 注意需要提供一个宿主对象
    foo.apply( o )  // this => o 对象

带有参数的函数如何实现上下文调用?
 

  function foo ( num1, num2 ) {
        console.log( this );
        return num1 + num2;
    }

    // 函数调用模式
    var res1 = foo( 123, 567 );

    // 方法调用
    var o = { name: 'jim' };
    o.func = foo;
    var res2 = o.func( 123, 567 );


使用 apply 进行调用, 如果函数是带有参数的. apply 的第一个参数要么是 null 要么是对象

如果是 null 就是函数调用

如果是 对象就是 方法调用, 该对象就是宿主对象, 后面紧跟一个数组参数, 将函数所有的参数依次放在数组中.

    例如: 函数模式        foo( 123, 567 );
          apply         foo.apply( null, [ 123, 567 ] ) 以 window 为上下文执行 apply

    如果有一个函数调用:   func( '张三', 19, '男' ),
    将其修改成 apply 模式:  func.apply( null, [ '张三', 19, '男'] )

    方法模式:           o.func( 123, 567 )
    apply               var o = { name: 'jim' };
                        foo.apply( o, [ 123, 567 ] ); 以 o 为上下文执行 apply

call 调用

在使用 apply 调用的时候, 函数参数, 必须以数组的形式存在. 但是有些时候数组封装比较复杂

所以引入 call 调用, call 调用与 apply 完全相同, 唯一不同是 参数不需要使用数组

foo( 123, 567 );
foo.apply( null, [ 123, 567 ] );
foo.call( null, 123, 567 );
  1. 函数调用: 函数名.call( null, 参数1,参数2,参数3… );

  2. 方法调用: 函数名.call( obj, 参数1,参数2, 参数3… );

  3. 不传参时,apply 和 call 完全一样

该文转自:https://blog.csdn.net/qq_16415157/article/details/53033953

猜你喜欢

转载自blog.csdn.net/line233/article/details/83150732