网站倒计时代码,网站开发项目的简介,wordpress头部导航栏,西安seo主管Arguments 对象
arguments 基本定义
首先arguments是以内置对象出现的。换句话说#xff1a;你不能够直接的去访问arguments对象#xff0c;所以你会返现在浏览器中直接访问arguments对象是不存在的。 特别重要#xff1a; 那么arguments对象本质上是什么东西呢#xff…Arguments 对象
arguments 基本定义
首先arguments是以内置对象出现的。换句话说你不能够直接的去访问arguments对象所以你会返现在浏览器中直接访问arguments对象是不存在的。 特别重要 那么arguments对象本质上是什么东西呢其实arguments是一个对应于传递给函数参数的类数组对象arguments对象是所有非箭头函数中都可用的局部变量**。为什么箭头函数中不存在arguments对象呢这个问题我们等会再去讨论。 我们通过console.dir(Function.prototype)的方式发现arguments属性存在于Function.prototype对象上。为什么我们不能够直接通过Function.arguments的方式进行访问arguments呢因为我们说过arguments对象是内置对象所以你不能够直接去调用访问。 那么arguments对象本质上是什么东西呢其实arguments是一个对应于传递给函数参数的类数组对象arguments对象是所有非箭头函数中都可用的局部变量。**为什么箭头函数中不存在arguments对象呢这个问题我们等会再去讨论。 我们通过console.dir(Function.prototype)的方式发现arguments属性存在于Function.prototype对象上。为什么我们不能够直接通过Function.arguments的方式进行访问arguments呢因为我们说过arguments对象是内置对象所以你不能够直接去调用访问。 如果说我想要访问arguments的话我该如何操作呢因为arguments是所有非箭头函数中的局部变量所以在函数内部中可以通过arguments直接对其访问。例如下面的例子 我们终于揭开了arguments对象的面纱现在来分析一下arguments对象内部的属性
function fn() {console.log(arguments);
};
fn(1, 2, 3);先看arguments对象的形式arguments对象很显然是类数组对象Array-like。为什么说arguments对象是类数组对象呢首先arguments对象存在length属性其次arguments对象存在有顺序依据的属性0,1,2最后arguments对象本质上并不是数组对象。因为arguments的[[prototype]]属性是由Object构造器构造出来的对象。callee属性callee属性其实也比较简单这个callee属性会指向宿主函数。什么是宿主函数呢也就是指代当前正在执行的函数。比如说下面例子中通过arguments.callee能够访问到当前function fn其实我们也可以直接通过fn函数名称进行访问。为什么ES5严格模式要移除callee属性这个问题我们也等会解释。
function fn() {console.log(arguments.callee); // function fn(){}console.log(fn); // function fn(){}
}
fn();Symbol.iterator属性出现Symbol.iterator属性的话说明该数据是可以迭代的。比如说我通过generator生成器函数手动的对arguments对象进行迭代我也可以用for...of语句直接对其arguments对象进行迭代。
function fn() {const iterator generator(arguments);console.log(iterator.next()); // { value: 1, done: false }console.log(iterator.next()); // { value: 2, done: false }console.log(iterator.next()); // { value: 3, done: false }console.log(iterator.next()); // { value: undefined, done: true }
};
fn(1, 2, 3);
// 生成器函数
function * generator(args) {for (let i 0; i args.length; i) {yield args[i];}
}function fn() {for (let value of arguments) {console.log(value); // 1, 2, 3}
};
fn(1, 2, 3);特别重要分析完arguments对象的属性之后我们来证明一下arguments对象是不是数组形式的。我们分别通过Array.isArray方法、toString方法去证明arguments不是数组的形式。所以arguments对象是不可以直接继承到Array.prototype上的方法。
// 方式一
function fn() {console.log(Array.isArray(arguments)); // false
}
fn(1, 2, 3);// 方式二
function fn() {console.log(arguments.toString()); // [object Arguments]
}
fn(1, 2, 3);为什么箭头函数中不存在arguments
如果我们在箭头函数中访问arguments对象那么此时程序会抛出异常Uncaught ReferenceError: arguments is not defined。这说明什么问题呢说明箭头函数中是不存在arguments对象的。为什么箭头函数中不存在arguments对象呢如果说我想在箭头函数中获取实际参数列表我又该如何去操作呢 首先如果你想在箭头函数中获取实际参数列表的话此时你可以通过ES6的剩余语法来处理例如你会发现args变量此时是数组的形式并且数组内部的元素对应实际参数的值。这说明什么问题呢这说明箭头函数中剩余语法代替了原本的arguments对象。
const fn (...args) {console.log(args);
}
fn(1, 2, 3);ES6中为什么有一个arguments到args的演变过程呢其实也很简单我们先从arguments中分析首先arguments对象保存的是实际参数列表我们通过拿到实际参数列表之后一般都是要将arguments对象转为数组然后再去对数组进行操作。而现在args本身就是数组的形式所以能够直接调用Array.prototype上的方法。 其次是arguments.callee属性为什么args中不存在arguments.callee属性呢因为callee属性指向当前正在执行的函数而我们完全可以通过调用函数名称的方式去直接获取函数。所以此时callee属性存在与不存在的意义并不是很大。当然之后我们会说为什么arguments.callee在ES5严格模式下被移除的原因。 最后是arguments[Symbol.iterator]属性因为arguments对象存在Symbol.iterator属性所以arguments对象可以进行迭代。但是需要注意args数组虽然自身不存在Symbol.iterator属性但是Array.prototype对象是存在Symbol.iterator属性所以args自然也可以进行迭代操作。所以在箭头函数中arguments对象的代替方式是通过剩余语法的方式。 上面的原因只是针对于arguments到args演变的过程总结的原因。但是最重要的原因在于形式参数与实际参数对应的关系这点我们在接下来重点叙述。
arguments 对象转为数组的问题
说到arguments对象转为数组的问题可能我们一下子能够想到很多种。但是针对于ES5来说我们通过使用的是下面的方式 我们通过[].slice.call(arguments)的方式将arguments对象转换为数组的形式。因为slice方法能够返回一个新数组而我们改变slice方法内部的this指向。使arguments对象从类数组对象转换为数组对象。 这是ES5中比较常见的arguments对象转为数组方式但是这种方式对于V8引擎优化非常不友好。在这篇文档中Optimization-killer说明了针对于V8引擎优化不友好的方式其中下面的这种方式就囊括其中。
function fn() {const argArr [].slice.call(arguments);console.log(argArr); // [1, 2, 3]
};
fn(1, 2, 3);当然MDN上也指出了解决的方式
function fn() {var args (arguments.length 1 ? [arguments[0]] : Array.apply(null, arguments));console.log(args); // [1, 2, 3]
};
fn(1, 2, 3);形式参数与实际参数的对应关系、arguments对象的行为
ES5
我们在ES5学习函数的时候我们知道函数实际参数与形式参数一一对应比如说 调用fn(1, 2, 3)函数1,2,3在fn函数调用的时候作为实际参数传入到函数内部。而a,b,c作为形式参数来与其对应。也就是说现在形式参数a,b,c相当于fn函数内部的临时变量而a,b,c变量内部存储的值与实际参数一一对应所以a,b,c的值分别是1,2,3。
function fn(a, b, c) {console.log(a, b, c); // 1, 2, 3
};
fn(1, 2, 3);形式参数与实际参数的对应关系很好理解我们下面来看看比较奇怪的arguments对象行为 arguments对象表示的是实际参数列表所以打印arguments对象内部的元素是1,2,3。
function fn(a, b, c) {console.log(a, b, c); // 1, 2, 3console.log(arguments); // Arguments[1, 2, 3]
};
fn(1, 2, 3);如果说我们现在更改形式参数变量a的值那么arguments对象内部的元素会受影响吗 实际上是会发生变化的我们可以看到此时arguments对象内部的元素变为了100,2,3。这是什么原因导致的呢不是说arguments对象是实际参数列表吗arguments对象内部的元素不应该是1,2,3吗 特别重要其实这是由于形式参数与arguments在内存中存在映射关系实际上会使形式参数与arguments对象的元素产生对应关系共享关系。注意一下映射关系是指在内存中比如a映射ba b此时a、b的值可以不相等。而对应关系是a对应ba - b此时a、b值是相等的。 其实也就是说arguments对象内部元素与形式参数存在对应关系这也是arguments对象的行为。所以当形式参数a发生变化的时候arguments对象中对应的元素也会发生变化。
function fn(a, b, c) {a 100;console.log(arguments); // Arguments[100, 2, 3]
};
fn(1, 2, 3);当然如果你改变arguments对象内部的值此时对应的形式参数也会发生变化。这就是函数内部形式参数与arguments对象的特殊关系。
function fn(a, b, c) {arguments[0] 100;console.log(a); // 100
};
fn(1, 2, 3);ES6
如果arguments对象遇见某些ES6的语法时此时arguments会被弱化。换句话说在ES6中不希望你使用arguments对象而是利用剩余语法进行代替。比如说 下面的两个例子中存在ES6中的函数参数默认值的语法此时我们会发现形式参数与arguments之间对应关系似乎消失了。当我们手动去修改形式参数a的值或者是手动修改arguments对象的元素的时候与其对应的形式参数或者是arguments内部元素并不会与其对应。换句话说当在默认参数的存在导致arguments对象中的元素并不会再与形式参数的值对应。
function fn(a 1, b) {a 100;console.log(arguments); // Arguments[1, 2, 3]
};
fn(1, 2, 3);function fn(a 1, b) {arguments[0] 100;console.log(a); // 1
}
fn(1, 2, 3);通过下面的几个例子我们发现在非严格模式下ES6的剩余参数、默认参数、解构赋值语法存在时arguments对象中的值是不会追踪形式参数的值。也就是说arguments对象内部的值是不会对应形式参数的值。注意是非严格模式严格模式我们之后再进行讨论。 为什么ES6会出现这种现象呢其实这和arguments对象有关系之前我们在ES5讨论arguments对象的时候。我们发现arguments对象是存在与形式参数对应的行为但是这种行为很怪异。arguments对象本质上就是保存实际参数的类数组对象而更改形式参数的值或者是手动修改arguments对象中的值都会影响到与之对应的形式参数或者是arguments对象。 而这种行为伴随着剩余参数、默认参数、解构赋值语法出现ES6在慢慢的弱化arguments对象的能力我们在下面剩余参数、默认参数、解构赋值的例子中能够很明显的感受到。尤其是剩余参数的出现如果函数接收不定数量的参数时我们会发现形式参数已经完全的列表化形式参数通过...的语法进行收集形式参数在函数内部中显得及其重要并且arguments对象的行为也失去了效果。所以在ES6中它并不希望你使用arguments对象而是推荐你使用剩余参数的方式去代替arguments。
// ES5
function fn(a, b, c) {arguments[0] 100;arguments[1] 200;arguments[2] 300;console.log(a, arguments[0]); // 100 100console.log(b, arguments[1]); // 200 200console.log(c, arguments[2]); // 300 300
}
fn(1, 2, 3);// ES6参数默认值
function fn(a, b, c 1) {arguments[0] 100;arguments[1] 200;arguments[2] 300;console.log(a, arguments[0]); // 1 100console.log(b, arguments[1]); // 2 200console.log(c, arguments[2]); // 3 300
}
fn(1, 2, 3);// ES6剩余参数语法
function fn(...args) {arguments[0] 100;arguments[1] 200;arguments[2] 300;console.log(args[0], arguments[0]); // 1 100console.log(args[1], arguments[1]); // 2 200console.log(args[2], arguments[2]); // 3 300
};
fn(1, 2, 3);// ES6对象化解构
function fn({ a, b, c }) {arguments[0] 100;arguments[1] 200;arguments[2] 300;console.log(a, arguments[0]); // 1 100console.log(b, arguments[1]); // 2 200console.log(c, arguments[2]); // 3 300
};
fn({a:1,b:2,c:3
});ES5 严格模式中的arguments
我们上面讨论的例子都是处于非严格模式下的情况现在我们探讨一下如果在严格模式下的话arguments对象与形式参数之间的关系会不会受到影响。 观察下面的例子我们发现处于严格模式下的话arguments对象内部的元素并不会与形式参数对应实际上的效果与ES6相同。其实这真的很好理解arguments本质上就是保存实际参数的你更改形式参数的值本来就不应该去影响arguments对象内部的值。并且如果我手动修改arguments对象内部的值也是不应该去修改形式参数的值才对。 所以我们发现在严格模式下arguments对象失去了原本上对应的行为能力。而在非严格模式下arguments对象依旧保持着原本的对应行为能力。 特别重要严格模式下不仅仅移除了arguments对象的对应能力还将arguments对象中的callee、caller属性一并移除在严格模式下是不能够使用callee、caller属性。
// 非严格模式
function fn(a, b, c) {a 10;b 20;c 30;console.log(a, arguments[0]); // 10 10console.log(b, arguments[1]); // 20 20console.log(c, arguments[2]); // 30 30
};
fn(1, 2, 3);// 严格模式
function fn(a, b, c) {use strict;a 10;b 20;c 30;console.log(a, arguments[0]); // 10 1console.log(b, arguments[1]); // 20 2console.log(c, arguments[2]); // 30 3
};
fn(1, 2, 3);那么为什么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使得在通常的情况你可以通过调试一些个别的例子去实现它但即使最好的代码也是次优选择因为JS引擎做了不必要的解释不可能实现内联和尾递归。另外一个原因是递归调用会获取到一个不同的this值。
var global this;var sillyFunction function (recursed) {if (!recursed) { return arguments.callee(true); }if (this ! global) {alert(This is: this);} else {alert(This is the global);}
}sillyFunction();ECMAScript3通过允许命名函数表达式解决这些问题。例如
[1,2,3,4,5].map(function factorial (n) {return !(n 1) ? 1 : factorial(n-1)*n;
});