5

一、函数调用的4种模式

(1) 方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this 被绑定到该对象。如果调用表达式包含一个提取属性的动作(即包含一个.点表达式或[subscript]下标表达式),那么它就是被当做一个方法来调用。

var myObj = {
    value: 0,
    increment: function (inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
};

myObj.increment();
console.log(myObj.value); // 1
myObj.increment(2);
console.log(myObj.value); // 3

(2) 函数调用模式

var add = function (a,b) {
    return a + b;
};
var sum = add(3,4); // sum的值为7

以此模式调用函数时,this 被绑定到全局对象

延伸:调用内部函数时,如何把 this 绑定到外部函数的 this 变量上

// 承接上面代码

// 给 myObj 增加一个 double 方法
myObj.double = function() {
    var that = this; // 解决方法
    var helper = function () {
        console.log(this); // this指向全局对象,如果写成this.value = add(this.value, this.value); 就获取不到正确的结果了
        that.value = add(that.value, that.value);
    };
    helper(); // 以函数的形式调用 helper
};

myObj.double(); // 以方法的形式调用 double
console.log(myObj.value); // 6

(3) 构造器调用模式

var Quo = function (string) {
    this.status = string;
}
var myQuo = new Quo("confused"); // 构造一个 Quo 实例
console.log(myQuo.status); // "confused"

一个函数,如果创建的目的就是希望结合new前缀来调用,那它就被称为构造(器)函数,函数内部的this 指向新创建的实例

(4) apply、call调用模式

js 是一门函数式的面向对象编程语言,函数也是一个对象,所以函数可以拥有自己的方法,apply、call就是其中的两种方法。

此种调用模式允许我们可以显式地设置 this 的指向,具体使用见下文。

二、函数常用的三个方法

1. fun.apply(thisArg[, argsArray])

在指定 this 值和参数(参数以数组类数组对象的形式存在)的情况下调用某个函数。
thisArg:在 fun 函数运行时指定的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象)
argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。也可以使用 arguments 对象作为 argsArray 参数,用arguments把所有的参数传递给被调用对象。

/* 求一个数组中的最大最小值 */
var numbers = [5, 6, 2, 3, 7];

/* simple loop based algorithm */
max = -Infinity, min = +Infinity;

for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] > max)
    max = numbers[i];
  if (numbers[i] < min) 
    min = numbers[i];
}

/* vs. using Math.min/Math.max apply */
var max = Math.max.apply(null, numbers); /* This about equal to Math.max(numbers[0], ...) or Math.max(5, 6, ..) */
var min = Math.min.apply(null, numbers);

从上面的例子可以看到:本来需要写成遍历数组变量的任务,apply使用内建的函数就完成了。据此,可以简化某些对数组的操作

var arr1 = [1,2,3];
var arr2 = [4,5,6];

/* 如果我们要把 arr2 展开,然后一个一个追加到 arr1 中去,最后让 arr1=[1,2,3,4,5,6]
 * arr1.push(arr2)是不行的,因为这样做会得到[1,2,3,[4,5,6]] 
 * 可以循环arr2,然后一个一个的push,但是这样比较麻烦,使用apply,就so easy了
 */
Array.prototype.push.apply(arr1,arr2); 
console.log(arr1); // [1,2,3,4,5,6]

/* 也可以用arr1.concat(arr2),但是concat方法返回的是一个新数组,并不改变arr1本身 */

2. fun.call(thisArg[, arg1[, arg2[, ...]]])

该方法的作用和 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组。

Math.max.apply(null, [1,2,3,4]);
Math.max.call(null, 1, 2, 3, 4);

/* eg. 使用call方法调用父构造函数 */
function Animal(name){      
    this.name = name;      
    this.showName = function(){      
        console.log(this.name);      
    }      
}      
    
function Cat(name){    
    Animal.call(this, name); // 此行代码中的this指向Cat的实例   
}      
    
var cat = new Cat("Black Cat");     
cat.showName(); // "Black Cat"

3. fun.bind(thisArg[, arg1[, arg2[, ...]]])

当在函数fun上调用bind( )方法并传入一个对象thisArg作为参数,这个方法将返回一个新函数。调用新的函数将会把原始的函数fun当做thisArg的方法来调用。
thisArg:当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效
arg1, arg2, ...:当绑定函数被调用时,这些参数加上绑定函数本身的参数会按照顺序作为原函数运行时的参数

// eg.1
var sum = function (x,y) { return x + y; };
var succ = sum.bind(null, 1);
succ(2); // => 3: x绑定到1,并传入2作为实参y

// eg.2
function f(y,z) { return this.x + y + z; };
var g = f.bind({x:1}, 2); // 绑定this和y
g(3); // =>6: this.x绑定到1,y绑定到2,z绑定到3

// eg.3 创建绑定函数
this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX(); // 9, because in this case, "this" refers to the global object

// Create a new function with 'this' bound to module
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

bind 函数在ES5版本中才被加入,ES3版本的bind( )方法实现如下(js权威指南p191):

if (!Function.prototype.bind) {
    Function.prototype.bind = function(o[, args]) {
        var self = this, boundArgs = arguments;
        
        // bind()方法的返回值是一个函数
        return function() {
            // 创建一个实参列表,将传入bind()的第二个及后续的实参都传入这个函数
            var args = [], i;
            for(i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
            for(i = 0; i < arguments.length; i++) args.push(arguments[i]);
            // 现在将self作为o的方法来调用,传入这些实参
            return self.apply(o, args);
        }
    }
}
/* 关键点有二:一是改变this的指向,二是改变传入参数的个数 
 *
 * 上述代码并未实现ES5中bind方法的全部特性,但思路比较清晰明了,且满足大部分需求了
 */

无名小贝勒
5.7k 声望324 粉丝