JavaScript Study Notes: Array-Like Objects and the arguments Mechanism
Array-like objects and arguments are foundational to understanding function behavior, borrowing prototype methods, and advanced techniques such as currying in JavaScript. This article examines their data structures, prototype differences, and practical uses.
1. Definition and Characteristics of Array-Like Objects
An array-like object meets two basic conditions:
It has a valid length property.
It has properties with numeric keys serving as indices.
// 标准数组
var array = ['name', 'age', 'sex'];
// 类数组对象
var arrayLike = {
0: 'name',
1: 'age',
2: 'sex',
length: 3
};1. Similarities to Standard Arrays
For basic reads, writes, and traversal, array-like objects behave much like standard arrays:
Reading and writing: both array[0] and arrayLike[0] access or modify a value at an index.
Length: both expose their length through .length.
Traversal: both can be traversed with a regular for loop and length.
2. The Difference: Prototype Chains
An array-like object's implicit prototype points to Object.prototype, while a standard array's points to Array.prototype. Because of this difference, array-like objects cannot directly call native array methods such as push, slice, and map.
arrayLike.push('4'); // 抛出异常:TypeError: arrayLike.push is not a function2. Borrowing Methods and Converting Types
1. Borrowing Array Methods Indirectly
Although array-like objects cannot call array methods directly, their structure fits the expected shape—duck typing. Function.prototype.call or apply can explicitly change this and borrow methods from Array.prototype:
var arrayLike = { 0: 'name', 1: 'age', 2: 'sex', length: 3 };
// 借用 join 方法
Array.prototype.join.call(arrayLike, '&'); // "name&age&sex"
// 借用 map 方法
Array.prototype.map.call(arrayLike, function(item){
return item.toUpperCase();
}); // ["NAME", "AGE", "SEX"]2. Four Common Ways to Convert to a Standard Array
Array-like objects are often converted into real arrays for easier processing. Four common approaches are:
Approach 1: Array.prototype.slice (the classic approach)
Array.prototype.slice.call(arrayLike);
Approach 2: Array.prototype.splice
Array.prototype.splice.call(arrayLike, 0);
Approach 3: apply with concat
Array.prototype.concat.apply([], arrayLike);
Approach 4: Array.from (ES6)
Array.from(arrayLike);
3. How the arguments Object Works
In a browser host environment, DOM APIs such as document.querySelectorAll return array-like objects such as NodeList. In a pure JavaScript environment, the central example is arguments inside an ordinary function.
The arguments object is available only within non-arrow function bodies and contains all arguments actually passed to the function.
1. length: Arguments vs Parameters
JavaScript doesn't enforce the number of arguments when calling a function, so the arguments supplied may differ from the parameters declared:
arguments.length: the number of actual arguments supplied to the call.
Function.length (that is, functionName.length): the number of formal parameters declared in the function definition.
function foo(b, c, d) {
console.log("实参的长度为:" + arguments.length);
}
console.log("形参的长度为:" + foo.length); // 3
foo(1); // 实参的长度为:12. Mapping Between arguments and Parameters
The mapping differs significantly between non-strict and strict mode, an important part of understanding JavaScript execution contexts.
Behavior in non-strict mode:
Argument Supplied? | Mapping Behavior | Conclusion |
|---|---|---|
The argument was supplied | Changing the parameter updates the corresponding | Two-way binding exists |
The argument was omitted | Changing the parameter doesn't change | Independent storage with no interference |
function foo(name, age, sex, hobbit) {
// 1. 已传入的参数(双向绑定)
name = 'new name';
console.log(arguments[0]); // 输出: new name
arguments[1] = 'new age';
console.log(age); // 输出: new age
// 2. 未传入的参数(互不干扰)
sex = 'new sex';
console.log(arguments[2]); // 输出: undefined
}
foo('name', 'age');The change in strict mode ("use strict"):
Strict mode completely removes the two-way binding between parameters and arguments. They are independent in memory, and changing either doesn't affect the other.
3. callee and Why It Was Deprecated
arguments.callee refers to the currently executing function. It was historically used for recursion in anonymous functions:
var data = [];
for (var i = 0; i < 3; i++) {
(data[i] = function () {
console.log(arguments.callee.i);
}).i = i;
}
data[0](); // 0Why it was deprecated:
Accessing arguments.callee in strict mode throws a TypeError. It breaks function encapsulation and prevents the JavaScript engine from performing inlining and tail-call optimization, seriously affecting performance. Modern JavaScript recommends named function expressions (NFEs) instead for recursion.
4. ES6 Rest Parameters as an Alternative
With ES6, the ... syntax for rest parameters offers a more standard and robust way to handle arguments. Rest parameters are instantiated as a real Array, requiring no extra conversion or prototype-method borrowing.
function func(...args) {
console.log(Array.isArray(args)); // true
args.push(4); // 能够直接调用数组原型方法
console.log(args);
}
func(1, 2, 3); // 输出: [1, 2, 3, 4]5. Practical Applications
Understanding arguments and array-like objects mainly helps with development and source reading in these scenarios:
Variable argument counts: implementing utilities such as console.log that accept a dynamic number of arguments.
Simulating overloading: branching within one function based on arguments.length.
Forwarding arguments: using Function.prototype.apply to pass received arguments into another call, as in bar.apply(this, arguments).
Currying and partial application: collecting and combining arguments supplied across calls when implementing bind or currying wrappers.