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

news/2025/1/12 15:47:55/

目录

  • 一、类数组
    • 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.lengthvar 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){// objectconsole.log(typeof arguments);  // 1 "[object Arguments]"console.log(a,Object.prototype.toString.call(arguments));// 1 undefinedconsole.log(arguments[0],arguments[1]);// numberconsole.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><li>";var args = Array.prototype.slice.call(arguments, 1);result += args.join("</li><li>");result += "</li></" + type + "l>"; // end listreturn result;
}

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

var listHTML = list("u", "One", "Two", "Three");/* listHTML is:"<ul><li>One</li><li>Two</li><li>Three</li></ul>"*/

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] 同样更新了 aconsole.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 aconsole.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

http://www.ppmy.cn/news/860305.html

相关文章

荣耀V20为什么会选择神奇AR这个创业公司合作研发YOYO炫舞?

2018年12月26日&#xff0c;荣耀在北京发布了荣耀家族最新旗舰荣耀V20&#xff0c;此次荣耀V20带来了多项黑科技&#xff0c;例如最小的魅眼全面屏、荣耀magic上面的智慧生命体YOYO炫舞、4800万超高像素的相机。其中YOYO炫舞可谓是本次发布会的一个大亮点。 随着荣耀总裁明哥上…

荣耀发布了全球首款 4800 万像素手机,并推出 YOYO 智能音箱...

12 月 10 日&#xff0c;荣耀在香港发布了三项对行业具有里程碑意义的黑科技&#xff0c;分别在通信、屏幕、拍照三大领域&#xff0c;这三项科技分别是&#xff1a;Link Turbo 全网络聚合技术&#xff0c;屏下摄像头技术、全球首发的索尼 IMX 586 Sensor&#xff0c;并且已经确…

Python 量化投资(一):滑动均值、布林带、MACD、RSI、KDJ、OBV

滑动均值和标准差 为了更好利用向量化来加速&#xff0c;滑动窗口使用np.lib.stride_tricks.sliding_window_view(x, win)提取&#xff0c;它会返回所有x[i]开头并且长度为win的数组的数组。 def rolling(x, win):r np.lib.stride_tricks.sliding_window_view(x, win)pad n…

分布式锁【数据库乐观锁实现的分布式锁、Zookeeper分布式锁原理、Redis实现的分布式锁】(三)-全面详解(学习总结---从入门到深化)

目录 分布式锁解决方案_数据库乐观锁实现的分布式锁 分布式锁解决方案_Redis实现的分布式锁原理 分布式锁解决方案_Redis实现的分布式锁 分布式锁解决方案_Redis分布式锁误删除问题 分布式锁解决方案_Redis分布式锁不可重入问题 分布式锁解决方案_基于Redisson实现的分布式…

windows专业版升级企业版

windows专业版升级企业版 秘钥: NPPR9-FWDCX-D2C8J-H872K-2YT43 使用神龙kms激活系统[KMS在线激活windows 10 / 8 / 7和office 2019 / 2016 / 2013 /2010之MicroKMS 神龙版-网络教程与技术 -亦是美网络 (yishimei.cn)](

免费版、专业版、增值版各个版本之间的区别

微软对Power BI目前有三种授权方式&#xff0c;免费版&#xff08;Power BI Free&#xff09;&#xff0c;专业版&#xff08;Power BI Pro&#xff09;&#xff0c;以及增值版&#xff08;Power BI Premium&#xff09;。前两种主要适用于个人以及中小型企业&#xff0c;后一种…

win10哪个版本最好用,推荐win10企业版LTSC

win10企业版LTSC又被称为win10企业版2019长期服务版本&#xff0c;这个版本小编认为是目前最好用的win10版本,在win10企业版2016长期服务版本的基础上&#xff0c;微软做了大量优化和升级。win10企业版LTSC更加快捷和轻便。 现在很多网站上都是有人修改过的win10企业版LTSC安装…

Win10家庭版、专业版、企业版、教育版各版本功能区别对照表

关于Win10系统的版本问题&#xff0c;MS酋长之前曾经分享过Windows10有哪些版本&#xff0c;在这篇文章中简单地介绍了一下Win10各版本的功能区别及适宜用户群&#xff0c;但是并没有对各版本的功能区别做一详细的对比。日前微软发布了一份PDF文档&#xff0c;对Win10家庭版、专…