如何引用Array.prototype.slice.call()?

6

我正在编写一个脚本,需要在多个不同的地方克隆数组。为此,我想要模拟克隆函数的操作,如下所示:

var clone = [].slice.call;
var arr1 = [1,2,3,4,5,6,7,8,9,10];
var arr2 = clone(arr1, 0);

不幸的是,上述代码会导致:TypeError: object is not a function。我知道有很多函数可以进行深度克隆和浅拷贝,但我只想使用内置的方法。有趣的是,以下代码确实有效:

var clone = [].slice;
var arr1 = [1,2,3,4,5,6,7,8,9,10];
var arr2 = clone.call(arr1, 0);

有人知道第一个代码块为什么不起作用而第二个可以吗?是否有办法在调用引用函数时避免出现错误而进行函数调用和应用函数?

5个回答

3
您可以直接调用 slice 来克隆一个数组:
var arr2 = arr1.slice();

如果你想要一个clone函数,可以这样做:

var clone = function(arr) { return arr.slice(); };

如果你真的想原型化函数(只要不覆盖函数就没有必要):
var clone = function(arr) { return [].slice.call(arr); };

为什么不能直接引用callapply

这是因为将对象的方法分配给变量时,与调用func.call()相同的原因。

如果你调用func.call(),那么call内部的this将是对函数对象func的引用。

如果你将call分配给一个变量,那么上下文就会丢失。你得到了一个对通用call函数的引用。因此,你必须再次将正确的上下文(你想要应用call的方法)作为第一个参数传递给call

var clone = [].slice.call;
var arr2 = clone.call([].slice, arr1);

这并不是真正的改进,相当令人困惑。

callapply都是每个函数从Function.prototype继承的方法。函数本身没有它们的版本。[].slice.call === [].splice.call输出true


感谢您的努力,但我不是很需要一个克隆函数,而是想知道为什么我似乎无法使用另一个函数的调用函数或应用函数的引用。 - Clarence Fredericks
[].slice.call === [].splice.call 的测试非常出色。 - Chris West

3

我必须完全同意Felix King和pimvdb的观点。 我认为使用Function.protoytpe.bind()函数的唯一缺点是这个函数并不适用于所有浏览器(例如IE6)。 另一个选择是使用提供curry()函数的JavaScript库。 还有一种选择是定义一个函数,使您能够检索任何其他函数的调用函数。 下面是我在我的博客上发布的这样一个函数的定义,我称之为getCall():

Function.prototype.getCall = function() {
  var realFn = this;
  return function(objThis) {
    return realFn.apply(objThis, Array.prototype.slice.call(arguments, 1));
  };
};

现在,有了这个定义,您可以执行以下操作来获取slice函数的call函数的引用:

var slice = [].slice.getCall();

太棒了!这几乎就是我要找的。你指出所有浏览器中缺少绑定函数的问题,我很高兴。非常感谢! - Clarence Fredericks
很棒的小方法。仅供参考,这里有一个.bind的替代方案:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind. 我更喜欢.getCall的解决方案 :) - pimvdb
请记住,如Mozilla所述,.bind替换只是部分解决方案。事实上,这就是我不会在jPaq的下一个版本中包含它的主要原因。 - Chris West

1

区别在于函数的作用域,即“this”的含义。我不确定正确的技术术语是什么,但当一个函数被“独立调用”或作为对象的属性时,“this”并不相同。

var myObj = {};
myObj.foo = function () {
    console.log(this.bar);
};
myObj.bar = 1234;

var fooRef = myObj.foo;

myObj.foo(); // 1234
fooRef(); // undefined

不过,您可以创建一个函数,该函数包装对函数的调用并传递所有参数:

var fooEncapsulated = function () {
    return myObj.foo.apply(myObj, arguments);
}

fooEncapsulated(); // 1234

记录一下,最常见的做法是:

Array.prototype.slice.call(myArray, other, arguments, here);

谢谢您的快速回复,但我真的想知道如何引用另一个函数的调用函数。我已经了解了切片函数。 - Clarence Fredericks
刚刚更新了我的回答。针对你的评论,你不能这样做。你需要进行封装。这是 JavaScript 的一个特性。 - August Lilleaas
我认为答案基本上是“你不能”。从这个答案基本上来说,你正在将函数保存到一个变量中,但它并没有保存它被调用的上下文。所以我认为你最好的选择是@felix的第三个答案,将其包装在一个函数中。 - James Montagne
@primvdb:bind在大量浏览器中不被支持,而且在技术上与手动包装是相同的。 - August Lilleaas

1
问题在于whatever_function.call等于Function.prototype.call。因此,您有效地保存了对Function.prototype.call的引用,并且它是切片函数的信息丢失了。
与自定义函数进行比较:
Function.prototype.custom = function() { console.log(this) };

[].slice.custom(); // logs slice function

var ref = [].slice.custom;
ref(); // logs window object

一种防止更改this值的方法是使用Function.prototype.bind
var ref = [].slice.call.bind([].slice);

现在,

ref([1,2,3], 1); // [2, 3]

因为在调用.call函数时,this值绑定到片段函数,并且一切都按预期工作。

Function.prototype.call.bind( Array.prototype.slice ) 不应该表现更好吗? - laconbass

0

简单而甜美:

slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1,2,3]); // [1,2,3]
slice({length:3,0:1,1:2,2:3}); // [1,2,3]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接