第8集丨JavaScript 中函数——Arguments 对象

目录

  • 一、类数组
    • 1.1 类数组和数组区别
    • 1.2 类数组使用数组的方法
    • 1.3 类数组转换成数组
    • 1.4 判断是否为类数组方法
  • 二、arguments对象初识
    • 2.1 arguments转数组
    • 2.2 arguments用于可变参数
    • 2.3 对参数使用 typeof
  • 三、arguments属性
    • 3.1 arguments.callee
      • 3.1.1 为什么 arguments.callee 从 ES5 严格模式中删除了?
      • 3.1.2 arguments.callee.caller
      • 3.1.3 在匿名递归函数中使用 arguments.callee
      • 3.1.4 没有替代方案的 arguments.callee
    • 3.2 arguments.length
  • 四、示例
    • 4.1 遍历参数求和
    • 4.2 定义连接字符串的函数
    • 4.3 定义创建 HTML 列表的方法
    • 4.4 剩余参数、默认参数和解构赋值参数

一、类数组

由于arguments 是一个对应于传递给函数的参数的类数组对象。因此,我们先来探究下什么是类数组?

类数组: 顾名思义,肯定是个长得像数组,但又不算数组的东东。就是指:可以通过索引属性访问元素并且拥有 length 属性的对象,并且属性的索引是从零开始。

1.1 类数组和数组区别

  • 都有length属性,都可以通过下标的方式访问
  • 类数组也可以for循环遍历
  • 类数组不具备数组的原型方法,因此类数组不可调用相关数组方法(如,push,slicec,concat 等等)
var arrayLike = {
      0:1,
      1:2
 }
 alert(likeArray[0])	//1

1.2 类数组使用数组的方法

那么我们希望类数组对象能够和数组一样使用数组的方法,应该怎么做呢?

  • 我们一般是通过 Function.call 或者 Function.apply 方法来间接调用。
var 
	arrayLike = {0: 'I', 1: 'Love', 2: 'You', 3:'!', length: 4 };
//I,Love,You,!
console.log(Array.prototype.join.call(arrayLike, ',')) 

//手动设置长度和元素个数不一致
arrayLike = {0: 'I', 1: 'Love', 2: 'You', 3:'!', length: 3 };
//I,Love,You
console.log(Array.prototype.join.call(arrayLike, ',')) 

1.3 类数组转换成数组

// 方式一
function Test() {
    var length = arguments.length
    var arr = []
    for(var i = 0; i < length; i++) {
        arr.push(arguments[i])
    }
    console.log(arr);   //[1, 2, 3, 4]
}
Test(1,2,3,4) 

// 方式二
function Test2() {
    var arr1 = Array.prototype.slice.call(arguments)
    var arr2 = [].slice.call(arguments)
    console.log(arr1)   //[5, 6, 7, 8]
    console.log(arr2)   //[5, 6, 7, 8]
}
Test2(5,6,7,8)

// 方式三
function Test3() {
    const arr3 = Array.from(arguments)
    const arr4 = [...arguments]
    console.log(arr3)   // [9, 10, 11, 12]
    console.log(arr4)   // [9, 10, 11, 12]
}
Test3(9,10,11,12)

1.4 判断是否为类数组方法

var shallowProperty = function(key) {
        return function(obj) {
            return obj == null ? void 0 : obj[key];
        };
    };

var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = shallowProperty('length');
var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

var 
    arrayLike = {
        length:1
    },
    arrayLike2 = {
        0:1
    }
// true
console.log((isArrayLike(arrayLike)))  
// false
console.log((isArrayLike(arrayLike2))) 

二、arguments对象初识

arguments 是一个对应于传递给函数的参数的类数组对象

arguments 对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引 0 处。例如,如果一个函数传递了三个参数,你可以以如下方式引用他们:

arguments[0]
arguments[1]
arguments[2]
  • 参数也可以被设置:
arguments[1] = 'new value';

2.1 arguments转数组

arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array

var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

// ES2015
const args = Array.from(arguments);
const args = [...arguments];

对参数使用 slice 会阻止某些 JavaScript 引擎中的优化 (比如 V8)。如果你关心性能,尝试通过遍历 arguments 对象来构造一个新的数组。另一种方法是使用被忽视的Array构造函数作为一个函数:

var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));

2.2 arguments用于可变参数

如果调用的参数多于正式声明接受的参数,则可以使用arguments对象。这种技术对于可以传递可变数量的参数的函数很有用。使用 arguments.length来确定传递给函数参数的个数,然后使用arguments对象来处理每个参数。

注意:要确定函数签名中(输入)参数的数量,请使用Function.length属性。

2.3 对参数使用 typeof

// 'undefined'
console.log(typeof arguments);    
// arguments 对象只能在函数内使用
function test(a){
    // object
    console.log(typeof arguments);  
    // 1 "[object Arguments]"
    console.log(a,Object.prototype.toString.call(arguments));
    // 1 undefined
    console.log(arguments[0],arguments[1]);
    // number
    console.log(typeof arguments[0]);
}
test(1);

三、arguments属性

3.1 arguments.callee

指向参数所属的当前执行的函数。

calleearguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。这在函数的名称是未知时很有用,例如在没有名称的函数表达式 (也称为“匿名函数”) 内。

警告: 在严格模式 下,第 5 版 ECMAScript (ES5) 禁止使用 arguments.callee()。当一个函数必须调用自身的时候,避免使用 arguments.callee(),通过要么给函数表达式一个名字,要么使用一个函数声明。

3.1.1 为什么 arguments.callee 从 ES5 严格模式中删除了?

早期版本的 JavaScript 不允许使用命名函数表达式,出于这样的原因,你不能创建一个递归函数表达式。

例如,下边这个语法就是行的通的:

function factorial (n) {
    return !(n > 1) ? 1 : factorial(n - 1) * n;
}

[1,2,3,4,5].map(factorial);

但是:

[1,2,3,4,5].map(function (n) {
    return !(n > 1) ? 1 : /* what goes here? */ (n - 1) * n;
});

这个不行。为了解决这个问题, arguments.callee 添加进来了。然后你可以这么做:

[1,2,3,4,5].map(function (n) {
    return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
});

然而,这实际上是一个非常糟糕的解决方案,因为这 (以及其他的 arguments, callee, 和 caller 问题) 使得在通常的情况(你可以通过调试一些个别例子去实现它,但即使最好的代码也是次优选项,因为 (JavaScript 解释器) 做了不必要的检查)不可能实现内联和尾递归。另外一个主要原因是递归调用会获取到一个不同的this值,例如:

var global = this;

var sillyFunction = function (recursed) {
    if (!recursed) { return arguments.callee(true); }
    if (this !== global) {
        alert("This is: " + this);  //This is: [object Arguments]
    } else {
        alert("This is the global");
    }
}

sillyFunction();

ECMAScript 3 通过允许命名函数表达式解决这些问题。例如:

[1,2,3,4,5].map(function factorial (n) {
    return !(n > 1) ? 1 : factorial(n-1)*n;
});

这有很多好处:

  • 该函数可以像代码内部的任何其他函数一样被调用
  • 它不会在外部作用域中创建一个变量 (除了 IE 8 及以下)
  • 它具有比访问 arguments 对象更好的性能

3.1.2 arguments.callee.caller

另外一个被废弃的特性是 arguments.callee.caller,具体点说则是 Function.caller。为什么? 额,在任何一个时间点,你能在堆栈中找到任何函数的最深层的调用者,也正如我在上面提到的,在调用堆栈有一个单一重大影响:不可能做大量的优化,或者有更多更多的困难。比如,如果你不能保证一个函数 f 不会调用一个未知函数,它就绝不可能是内联函数 f。基本上这意味着内联代码中积累了大量防卫代码:

function f (a, b, c, d, e) { return a ? b * c : d * e; }

如果 JavaScript 解释器不能保证所有提供的参数数量在被调用的时候都存在,那么它需要在行内代码插入检查,或者不能内联这个函数。现在在这个特殊例子里一个智能的解释器应该能重排检查而更优,并检查任何将不用到的值。然而在许多的情况里那是不可能的,也因此它不能够内联。

3.1.3 在匿名递归函数中使用 arguments.callee

递归函数必须能够引用它本身。很典型的,函数通过自己的名字调用自己。然而,匿名函数 (通过 函数表达式 或者 函数构造器 创建) 没有名称。因此如果没有可访问的变量指向该函数,唯一能引用它的方式就是通过 arguments.callee

下面的例子定义了一个函数,按流程,定义并返回了一个阶乘函数。该例并不是很实用,并且几乎都能够用 命名函数表达式实现同样结果。

function create() {
   return function(n) {
      if (n <= 1)
         return 1;
      return n * arguments.callee(n - 1);
   };
}

var result = create()(5); // returns 120 (5 * 4 * 3 * 2 * 1)

3.1.4 没有替代方案的 arguments.callee

当你必须要使用 Function 构造函数时,下面的例子是没有可以替代 arguments.callee 的方案的,因此弃用它时会产生一个 BUG

function createPerson (sIdentity) {
    var oPerson = new Function("alert(arguments.callee.identity);");
    oPerson.identity = sIdentity;
    return oPerson;
}

var john = createPerson("John Smith");

john();

利用命名函数表达式也可以实现上述例子的同样效果

function createPerson (identity) {
    function Person() {
        console.log(Person.identity);
    }
    Person.identity = identity;
    return Person;
}
var john = createPerson("John Smith");

john(); //John Smith

3.2 arguments.length

传递给函数的参数数量。

四、示例

4.1 遍历参数求和

function add() {
    var sum =0,
        len = arguments.length;
    for(var i=0; i<len; i++){
        sum += arguments[i];
    }
    return sum;
}
add()                           // 0
add(1)                          // 1
add(1,2,3,4);                   // 10

4.2 定义连接字符串的函数

这个例子定义了一个函数来连接字符串。这个函数唯一正式声明了的参数是一个字符串,该参数指定一个字符作为衔接点来连接字符串。该函数定义如下,你可以传递任意数量的参数到该函数,并使用每个参数作为列表中的项创建列表。

function myConcat(separator) {
  var args = Array.prototype.slice.call(arguments, 1);
  return args.join(separator);
}


// returns "red, orange, blue"
myConcat(", ", "red", "orange", "blue");

// returns "elephant; giraffe; lion; cheetah"
myConcat("; ", "elephant", "giraffe", "lion", "cheetah");

// returns "sage. basil. oregano. pepper. parsley"
myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");

4.3 定义创建 HTML 列表的方法

这个例子定义了一个函数通过一个字符串来创建 HTML 列表。这个函数唯一正式声明了的参数是一个字符。当该参数为 "u" 时,创建一个无序列表 (项目列表);当该参数为"o"时,则创建一个有序列表 (编号列表)。该函数定义如下:

function list(type) {
  var result = "<" + type + "l>
  • "; var args = Array.prototype.slice.call(arguments, 1); result += args.join("
  • "); result += "
  • + type + "l>"; // end list return result; }

    你可以传递任意数量的参数到该函数,并将每个参数作为一个项添加到指定类型的列表中。例如:

    var listHTML = list("u", "One", "Two", "Three");
    
    /* listHTML is:
    
    "
    • One
    • Two
    • Three
    " */

    4.4 剩余参数、默认参数和解构赋值参数

    arguments对象可以与剩余参数、默认参数和解构赋值参数结合使用。

    function foo(...args) {
      return args;
    }
    foo(1, 2, 3);  // [1,2,3]
    

    在严格模式下,剩余参数、默认参数和解构赋值参数的存在不会改变 arguments对象的行为,但是在非严格模式下就有所不同了。

    当非严格模式中的函数没有包含剩余参数、默认参数和解构赋值,那么arguments对象中的值会跟踪参数的值(反之亦然)。看下面的代码:

    function func(a) {
      arguments[0] = 99;   // 更新了 arguments[0] 同样更新了 a
      console.log(a);
    }
    func(10); // 99
    

    并且

    function func(a) {
      a = 99;              // 更新了 a 同样更新了 arguments[0]
      console.log(arguments[0]);
    }
    func(10); // 99
    

    当非严格模式中的函数有包含剩余参数、默认参数和解构赋值,那么arguments对象中的值不会跟踪参数的值(反之亦然)。相反,arguments反映了调用时提供的参数:

    function func(a = 55) {
      arguments[0] = 99; // updating arguments[0] does not also update a
      console.log(a);
    }
    func(10); // 10
    

    并且

    function func(a = 55) {
      a = 99; // updating a does not also update arguments[0]
      console.log(arguments[0]);
    }
    func(10); // 10
    

    并且

    function func(a = 55) {
      console.log(arguments[0]);
    }
    func(); // undefined
    

    你可能感兴趣的:(JavaScript,江湖,javascript,前端,开发语言)