JavaScript 方法的4种调用模式

JavaScript 原型链 闭包 构造函数 调用模式 JSON

函数(Function)是JavaScript的基本模块单元,JavaScript的代码重用, 信息隐藏,对象组合等都可以借助函数来实现。 JavaScript中的函数有4种调用模式:

  • 方法调用(Method Invocation Pattern)
  • 函数调用(Function Invocation Pattern)
  • 构造函数调用(Constructor Invocation Pattern)
  • apply调用(Apply Invocation Pattern)

与其他语言不通,JavaScript的函数是一种特殊的对象 (事实上,JavaScript函数是一级对象)。 这意味着函数可以有属性,可以有方法,也可以传参、返回、赋值给变量、加入数组。 与对象不同的是函数可以被调用。

既然是对象便会有原型。我们知道通过花括号语法创建的对象的原型是Object.prototype。 通过函数语法(function关键字或new Function())创建的函数,其原型自然是Function.prototype。 而Function.prototype的原型是Object.prototype

注意上述『原型』指的是原型链上的隐式原型,区别于prototype原型属性。

调用模式

我们知道在函数里可见的名称包括:函数体内声明的变量、函数参数、来自外部的闭包变量。 此外还有两个:thisarguments

this在面向对象程序设计中非常重要,而它的值在JavaScript中取决于调用模式。 JavaScript中的函数有4种调用模式:方法调用、函数调用、构造函数调用、apply调用。

arguments是一个类数组变量(array like),拥有length属性并可以取下标, 它存着所有参数构成的有序数组。 在JavaScript中,函数调用与函数签名不一致(个数不正确、类型不正确定) 时不会产生运行时错。少了的参数会被置为undefined,多了的参数会被忽略。

方法调用模式

在面向对象程序设计中,当函数(Function)作为对象属性时被称为方法(Method)。 方法被调用时this会被绑定到对应的对象。在JavaScript中有两种语法可以完成方法调用: a.func()a['func']()

var obj = {
    val: 0,
    count: function(){
        this.val ++;
        console.log(this.val);
    }
};

obj.count();    // 1
obj.count();    // 2

值得注意的是,thisobj的绑定属于极晚绑定(very late binding), 绑定发生在调用的那一刻。这使得JavaScript函数在被重用时有极大的灵活性。

函数调用模式

当函数不是对象属性时,它就会被当做函数来调用,比如add(2,3)。 此时this绑定到了全局对象global

那些 JavaScript 的优秀特性一文中曾提到, JavaScript的编译(预处理)需要global对象来链接所有全局名称。

其实this绑定到global是JavaScript的一个设计错误(可以说是最严重的错误), 它导致了对象方法不能直接调用内部函数来做一些辅助工作, 因为内不函数里的this的绑定到了global。 所以如果要重新设计语言,方法调用的this应该绑定到上一级函数的this

然而共有方法总是需要调用内部辅助函数,于是产生了这样一个非常普遍的解决方案:

man.love = function(){
    var self = this;
    function fuck(){
        self.happy++;
    }
    function marry(){
        self.happy--;
    }
    fuck() && marry();
}

有些场景下用Function.prototype.bind会更加方便:

man.love = function(){
    function fuck(girl1, girl2, ...){
        this.happy++;
    }
    fuck.bind(this)();
    ...
}

构造函数调用模式

Classically inspired syntax obscures the language’s true prototypal natur.

JavaScript中,那些用来new对象的函数成为构造函数。

JavaScript采用原型继承方式。这意味着一个对象可以从另一个对象直接继承属性, JavaScript是class free的~ 但JavaScript为了迎合主流的基于类声明的继承方式, 同时也给出了构造函数机制:使用new关键字,便会创建一个对象, 根据prototype属性创建原型链,并以该对象为this执行指定的(构造)函数。

function Man(name, age){
    this.sex = 'man';
    this.name = name;
    this.age = age;
}
Man.prototype.fuck = function(girl1, girl2, ...){}
var man = new Man();
man.fuck();

当构造函数有很多参数时,它们的顺序很难记住,所以通常使用对象进行传参:

var man = new Man({
    name: 'bob',
    age: 18
});

给参数起名字以达到顺序无关的做法在Python中也存在,但JavaScript的对象传参还将带来另一个好处: JSON兼容。因为JavaScript常常需要数据库(例如MongoDB)或网络(application/json)传来的JSON数据,这一点使得对象构造非常方便。

Apply 调用模式

JavaScript函数是一种特殊的对象,而对象可以有属性和方法。 其中的apply方法提供了一个更加特殊的调用方式。 它接受两个参数:第一个是该函数要绑定的this,第二个是参数数组。

var args = [girl1, girl2];
var animal = new Animal();
Man.prototype.fuck.apply(animal, args);

Apply使得一个方法可以用不同的对象对象来调用,比如animal也可以用Man的方式来fuck

Harttle

致力于简单的、一致的、高效的前端开发

看看这个?