JavaScript函数的调用方式和传参方式

来源:互联网 发布:思科通过mac地址查询ip 编辑:程序博客网 时间:2024/06/10 05:21

JavaScript函数的调用方式和传参方式

了解函数的调用过程有助于深入学习与分析JavaScript代码。

在JavaScript中,函数是一等公民,函数在JavaScript中是一个数据类型,而非像C#或其他描述性语言那样仅仅作为一个模块来使用。函数有四种调用模式,分别是:函数调用形式方法调用形式构造器形式以及apply和call调用形式。这里所有模式中,最主要的区别在于关键字this的意义,下面分别介绍这几种调用形式。(注以下代码都是运行在浏览器环境中)
本文的主要内容:

  1. 分析函数的四种调用形式
  2. 弄清楚函数中this的意义
  3. 明确构造函数对象的过程
  4. 学会使用上下文调用函数

一、函数调用形式

函数调用形式是最常见的形式,也是最好理解的形式。所谓函数形式就是一般声明函数后直接调用即时。例如:

function foo(){    console.log(this);}foo();//Window

单独独立调用的,就是函数调用模式,即函数名(参数),不能加任何其他东西,对象o.fun()就不是了。在函数调用模式中,this表示全局对象window
任何自调用函数都是函数模式。

二、方法调用模式

函数调用模式很简单,是最基本的调用方式。但是同样的是函数,将其赋值给一个对象的成员以后,就不一样了。将函数赋值给对象的成员后,那么这个就不再称为函数,而应该称为方法。
所谓方法调用,就是对象的方法调用。方法是什么,方法本身就是函数,但是,方法不是单独独立的,而是要通过一个对象引导来调用。就是说方法对象一定要有宿主对象
对象.方法(参数)
this表示引导方法的对象,就是宿主对象。
对比函数调用模式:

  1. 方法调用模式不是独立的,需要宿主,而函数调用模式是独立的;
  2. 方法调用模式方式:obj.fun();函数调用模式方式:fun();
  3. 方法调用模式中,this指宿主;而函数调用模式中this指全局对象window。
//定义一个函数function foo(){    console.log(this);}//将其赋值给一个对象var o = {};o.fn = foo;//注意这里不要加括号//调用o.fn();//o

函数调用中,this专指全局对象window,而在方法中this专指当前对象,即o.fn中的this指的就是对象o。
美团的一道面试题:

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

解析:

  1. fn()前面没有引导对象,是函数调用模式, this是全局对象,输出 10;
  2. arguments0,arguments是一个伪数组对象, 这里调用相当于通过数组的索引来调用。所以,执行时,this就是指arguments,由于传了两个参数,所以 输出为arguments.length就是2。

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

三、构造器调用模式

同样是函数,在单纯的函数模式下,this表示window;在对象方法模式下,this值的是当前对象。除了这两种情况,JavaScript中函数还可以是构造器。将函数作为构造器来使用的语法就是在函数调用前面加上一个new关键字。例如:

//定义一个构造函数var Person = function(){    this.name = '程序员';    this.sayHello = function(){        console.log('Hello');    };};//调用构造器,创建对象var p = new Person();//使用对象p.sayHello();//Hello

上面的案例首先创建一个构造函数Person,然后使用构造函数创建对象p。这里使用new语法。然后再使用对象调用sayHello()方法。从案例可以看到,此时this指的是对象本身。此外,函数作为构造器还有几个变化,分别为:

  1. 所有需要由对象使用的属性,必须使用this引导;
  2. 函数的return语句意义被改写,如果返回非对象,就返回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);}Foo.getName();//输出2.//调用Foo函数作为对象动态添加的属性方法 getName//Foo.getName = function(){console.log(2);}getName();//输出4.//这里Foo函数还没有执行,getName还没有被覆盖//所以这里还是最上面的getName=function(){console.log(4);}Foo().getName();//输出1//Foo()执行,先覆盖全局的getName,再返回this//this是window,Foo().getName()就是调用window.getName//此时全局的getName已被覆盖成function(){console.log(1);}//所以输出为1//从这里开始window.getName已被覆盖为function(){console.log(1);}getName();//输出1//window.getName(),输出1new Foo.getName();//输出2//new 就是找构造函数(),由构造函数结合性,这里即使Foo无参,也不能省略(),所以不是Foo().getName()//所以Foo.getName为一个整体,等价于new (Foo.getName)();//而 Foo.getName其实就是函数function(){console.log(2);}的引用//那么new (Foo.getName)(),就是在以Foo.getName为构造函数,实例化对象。//就类似于 new Person();Person是一个构造函数new Foo().getName();//输出3//new就是找构造函数(),等价于(new Foo() ).getName();//执行new Foo() => 以Foo为构造函数,实例化一个对象//(new Foo() ).getName;访问这个实例化对象的getName属性//实例对象自己并没有getName属性,构造的时候也没有添加,找不到,就到原型中找//发现Foo.prototype.getName = fucntion(){console.log(3);}//原型中有,找到了,所以执行(new Foo() ).getName()结果为3new new Foo().getName();//输出为3//new就是找构造函数(),等价于new ( (new Foo() ).getName ) ()//先看里面的(new Foo() ).getName//new Foo() 以Foo为构造函数,实例化对象//new Foo().getName 找实例对象的 getName 属性,自己没有,就去原型中找//发现 Foo.prototype.getName = function() {console.log(3);}//所以里层(new Foo() ).getName就是以Foo为构造函数实例出的对象的一个原型属性//属性值为一个函数function(){console.log(3);}的引用//所以外层new ( (new Foo() ).getName )()在以函数function(){console.log(3);}为构造函数,构造实例//构造过程中执行了console.log(3),输出3

构造器中的this

分析创建对象的过程,理解this的意义。

var Person = function(){    this.name = '程序员';};var p = new Person(); 

这里首先定义了函数Person,下面分析一下整个执行:

  1. 程序在执行到这一句的时候,不会执行函数体,因此JavaScript的解释器并不知道这个函数的内容。
  2. 接下来执行new关键字,创建对象,解释器开辟内存,得到对象的引用,将新对象的引用交给函数。
  3. 紧接着执行函数,将传过来的对象引用交给this。也就是说,在构造方法中,this就是刚刚被new创建出来的对象。
  4. 然后为this添加成员,也就是为对象添加成员。
  5. 最后函数结束,返回this,将this交给左边的变量。

分析过构造函数的执行以后,可以得到,构造函数中的this就是当前对象。

构造器中的return

在构造函数中return的意义发生了变化,首先如果在构造函数中,如果返回的是一个对象,那么就保留原意。如果返回的是非对相,比如数字、布尔值和字符串,那么就返回this,如果没有return语句,那么也返回this,例如:

//返回一个对象的returnvar foo = function(){    this.name = '张三';    return {        name:'李四'    };};//创建对象var p = new foo();//访问name属性console.log(p.name);//李四

执行代码,这里输出的结果是“李四”。因为构造方法中返回的是一个对象,那么保留return的意义,返回内容为return后面的对象,再看如下代码:

//定义返回非对象数据的构造器var foo = fucntion() {    this.name = '张三';    return '李四';}//创建对象var p = new foo();console.log(p.name);//张三

执行代码,这里输出结果为“张三”,因为这里return的是一个字符串,属于基本类型,那么这里的return语句无效,返回的是this对象。

四、上下文调用模式

就是环境调用模式 => 在不同环境下的不同调用模式
简单说就是统一一种格式,可以实现函数模式与方法模式
语法

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

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

apply方法调用形式

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

  • 如果需要让函数以函数的形式调用,可以使用
    foo.apply(null);//上下文为window

  • 如果希望它是方法调用模式,注意需要提供一个宿主对象
    foo.apply(obj);//上下文为传入的obj对象

function foo(num1,num2){    console.log(this);    return num1+num2;}//函数调用模式var res1 = foo(123,567);//方法调用var o = { name: 'chenshsh' };o.func = foo;var res2 = o.func(123,567);

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

  1. 如果是null,就是函数调用
  2. 如果是对象就是方法调用,该对象就是宿主对象,后面紧跟一个数组参数,将函数所有的参数依次放在数组中
//函数模式foo(123,567);foo.apply(null,[123,567]);//以window为上下文执行apply//方法模式o.func(123,567);var o = { name:'chenshsh' };foo.apply(o,[123,567]);//以o为上下文执行apply

call方法调用

在使用apply调用的时候,函数参数必须以数组的形式存在。但是有些时候数组封装比较复杂,所以引入call调用,call调用与apply完全相同,唯一不同是参数不需要使用数组。

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

不传递参数时,apply和call完全一样

原创粉丝点击