编写高质量JS代码的68个有效方法(五)

十度 javaScript 2016年01月06日 收藏

No.21、使用apply方法通过不同数量的参数调用函数

Tips:

  1. 使用apply方法自定一个可计算的参数数组来调用可变参数的函数
  2. 使用apply方法的第一个参数给可变参数的方法提供一个接收者

//示例:计算给定数据的最大值
function getMaxNum(){
    var max = arguments[0];
    for(var i = 1, len = arguments.length;i < len; i++){
        if(max < arguments[i]){
            max = arguments[i];
        }
    }
    return max;
}
getMaxNum.apply(null,[1,3,4]);

该方法和call()方法功能基本类似,差别在于参数写法不一样。

No.22、使用arguments创建可变参数的函数

Tips:

  1. 使用隐式的arguments对象实现可变参数的函数
  2. 考虑对可变参数的函数提供一个额外的固定元数的版本,从而使用者无需借助apply方法。

每一个函数内部都有一个arguments对象包含所有传递的参数

function fun1(){
    console.log(arguments);
}
fun1('1');
fun1(1,'2','str');

No.23、永远不要修改arguments的值

Tips:

  1. 永远不要修改arguments的值
  2. 使用[].slice.call(arguments)将arguments对象赋值到一个真正的数组中再进行修改

arguments看起来像是数组,但是它并不是标准的数组,所以不支持数组的原型方法

function fun1(nums){
    var lastParam = arguments.pop(); //报错,undefined is not a function。
    console.log(arguments);
}

fun1([1, 2, 3]);

正确的做法是,将arguments转换为真正的数组,再进行操作,代码如下:

function fun1(nums){
    var argArr = [].slice.call(arguments);
    var lastParam = argArr.pop();
    console.log(arguments);
}

fun1([1, 2, 3]);

注意:永远不要修改arguments对象是更为安全的。

No.24、使用变量保存arguments的引用

Tips:

  1. 当引用arguments时当心函数嵌套层级
  2. 绑定一个明确作用域的引用到arguments变量,从而可以再嵌套的函数中引用它

首先,先来看一段代码的输出:

function fun1(){
    var i = 0;
    console.log(arguments);
    return {
        next:function(){
            return arguments[i++]; 
        }
    }
}
var f = fun1(1,2,3,4);
console.log(f.next()); //猜猜是啥?

arguments是函数中的隐式变量,每个函数都会有这样的一个隐式对象。所以最后一个console的结果可想而知。所以遇到这种场景,是建议用变量保存arguments的引用,也能让嵌套函数正确的进行对象引用,正确代码如下:

function fun1(){
    var i = 0;
    var args = arguments;
    return {
        next:function(){
            return args[i++]; 
        }
    }
}
var f = fun1(1,2,3,4);
console.log(f.next());

No.25、使用bind方法提取具有确定接收者的方法

Tips:

  1. 要注意,提取一个方法不会将方法的接收者绑定到该方法的对象上
  2. 当给高阶函数传递对象方法时,使用匿名函数在适当的接收者上调用该方法
  3. 使用bind方法创建绑定到适当接收者的函数

老规矩,看代码:(代码1)

var buffer = {
    entries: [],
    add: function(value){
        this.entries.push(value);
    },
    concat: function(){
        return this.entries.join('');
    }
};

该代码在直接使用时是没有问题的,思考下,由于高阶函数将函数/方法作为变量传递,那么可以有如下用法:(代码2)

var arr = ['Jay', '.M', '.Hu'];
arr.forEach(buffer.add);
console.log(buffer.concat()); //思考下这个结果是什么?

以上代码在arr.forEach处已经报错,Cannot read property 'push' of undefined。因为这个时候的涉及到this的指向问题。我们可以改造下buffer代码,输出this让我们看看:(代码3)

var buffer = {
    entries: [],
    add: function(value){
        console.log(this);
        this.entries.push(value);
    },
    concat: function(){
        return this.entries.join('');
    }
};

从输出结果我们可以看到这个this,在(代码2)的执行环境中,指向的是window对象,所以导致了报错,那么如何避免这样的问题呢?针对forEach,我们有三个方法:(代码4)

//方式一,去掉this,直接用buffer对象引用
var buffer = {
    entries: [],
    add: function(value){
        buffer.entries.push(value);
    },
    concat: function(){
        return buffer.entries.join('');
    }
};

//方式二,指定接收者,forEach方法提供,其他方法不一定提供
var arr = ['Jay', '.M', '.Hu'];
arr.forEach(buffer.add, buffer);
console.log(buffer.concat());

//方式三,通过用函数包装调用,来实现指定接收者
var arr = ['Jay', '.M', '.Hu'];
arr.forEach(function(s){
    buffer.add(s);
});
console.log(buffer.concat());

针对这样的问题,ES5标准库中提供了一个bind()函数来实现这样的方法。只需要如下代码:

var arr = ['Jay', '.M', '.Hu'];
arr.forEach(buffer.add.bind(buffer));
console.log(buffer.concat());

该bind()函数,利用buffer.add.bind(buffer)创建了一个新函数而不是修改了buffer.add函数。新函数行为就像原来函数的行为,但它的接收者被重新指定了。所以调用bind方法是安全的,即使是一个可能在程序的其他部分被共享的函数。