Array.prototype.slice应用和原理探析
Posted 程序猿子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Array.prototype.slice应用和原理探析相关的知识,希望对你有一定的参考价值。
问题由来
Array.prototype.slice常见于两种调用场景:
一是对函数arguments对象的转换
Array.prototype.slice.call(arguments)
二是类似jquery原型对象的定义中
toArray: function()
return slice.call( this, 0 );
,
有必要探究一番其使用方式和内部原理。
问题拆解
Array.prototype.slice作为Array.prototype对象的一个方法,对其调用在API中有详细说明,并不费解,对Array.prototype.slice的疑问可拆解为两个方面:一是该函数究竟返回什么对象?二是该函数可应用于什么对象?即非数组对象需要满足什么条件才能借用(复用)该函数?该函数究竟返回什么对象
为了探究返回对象的类型信息,写一个简单的对象工具集合:
var objs =
"descType":function(o)
console.info(o);
console.info("typeof o:" + (typeof o));
console.info("o.constructor:" + (o.constructor));
console.info("o instanceof Object:" + (o instanceof Object));
console.info("o instanceof Array:" + (o instanceof Array));
;
然后测试几个简单的例子:
var v = ['a',true,9].slice(0);
objs.descType(v);
VM1842:18 ["a", true, 9]
VM1842:19 typeof o:object
VM1842:20 o.constructor:function Array() [native code]
VM1842:21 o instanceof Object:true
VM1842:22 o instanceof Array:true
数组对象自身上调用slice方法当然不必再使用“var v = Array.prototype.slice.call(['a',true,9]);”这种方法,虽然是可以这样用的,结果表明返回的对象的确是Array类型的对象。
该函数可应用于什么对象
1 既然该方法是作为Array.prototype的方法,那么数组对象本身当然可以调用该方法。
2 Arguments类型的对象可正确调用该函数。
同样用一个简单的例子测试下arguments对象借用该函数后的对象类型。
function f()
objs.descType(arguments);
var newArg = Array.prototype.slice.call(arguments, 0);
objs.descType(newArg);
f('aa','bb','cc');
VM1842:18 ["aa", "bb", "cc"]
VM1842:19 typeof o:object
VM1842:20 o.constructor:function Object() [native code]
VM1842:21 o instanceof Object:true
VM1842:22 o instanceof Array:false
VM1842:18 ["aa", "bb", "cc"]
VM1842:19 typeof o:object
VM1842:20 o.constructor:function Array() [native code]
VM1842:21 o instanceof Object:true
VM1842:22 o instanceof Array:true
可以看到arguments对象本身并不是Array类型的--o instanceof Array:false,进一步展开该对象细节,可以看到其属性列表如下:
["aa", "bb", "cc"]
0:"aa"
1:"bb"
2:"cc"
callee:function f()
length:3
有length属性,有'0','1','2'属性,有'callee'属性,是一个Arguments类型的一个对象实例。而经过Array.prototype.slice.call调用后返回的对象就是一个真真正正的Array对象:
["aa", "bb", "cc"]
0:"aa"
1:"bb"
2:"cc"
length:3
没有callee属性且o.constructor:function Array() [native code] 。
这说明arguments对象和转换后的array对象非常类似--不仅属性类似,连用法也类似,比如arguments对象也支持[]运算符调用其属性值(其实本质是对象元素访问的通用语法,只不过属性名刚好为转换为字符串的整数而已)。所以结论是在js语言中,对arguments对象调用Array.prototype.slice.call(arguments, 0)纯属“闲的蛋疼”的做法(除非设计的函数用法非常的动态,比如需要对参数做shift等之类的数组操作)。
那么除了arguments对象之外,其他类型的对象可以借用Array.prototype.slice函数么?继续简单的测试几个例子:
objs.descType(Array.prototype.slice.call(true,0));
objs.descType(Array.prototype.slice.call('abc',0));
objs.descType(Array.prototype.slice.call(256,0));
objs.descType(Array.prototype.slice.call(,0));
objs.descType(Array.prototype.slice.call(undefined,0));
objs.descType(Array.prototype.slice.call(null,0));
objs.descType(Array.prototype.slice.call(NaN,0));
对undefined和null调用slice抛出异常,这是预期的行为,对NaN/true/256/返回一个空数组对象,对字符串'abc'返回一个数组:["a", "b", "c"],现在的重点是对对象调用的探究,传入一个初始化为空的对象直接量返回的是一个空数组对象,而上面测试在arguments对象上调用slice返回一个正常的数组--两者有何区别?
一个很容易联想的点子是该调用对象应该具备length属性才可以被slice正确处理,测试一下:
objs.descType(Array.prototype.slice.call('length':5,0));
结果果然是一个length值为5的数组对象,但其中5个元素值都为undefined。那么更进一步的想法应该是:调用对象除了有length属性之外,是不是也应该有索引属性名?即自动转换为字符类型的整数数字属性名?再试一下:
objs.descType(Array.prototype.slice.call('length':5,'0':'gebilaowang','1':'ximenqin',0));
ok,大功告成,结果果然是预期数组:["gebilaowang" ,"ximenqin",undefined, undefined, undefined]。因此结论是:只有具备"可转换为数值型的length属性"并且同时具备" 索引属性"的对象才可以正确被slice函数处理(返回或者说转换为预期数组),这种类型的对象经过查阅果然有一种专属称谓--Array-like Object。
3 Array-like对象可正确调用该函数。
现在来探究jQuery中对Array.prototype.slice方法的使用场景,jQuery对各种原型方法的应用当然是非常牛逼的甚至达到炉火纯青的境界,其实jQuery对象本身就是一个Array-like对象,因为其有length属性,也有索引属性,分析其初始化构造函数(1.6.1版本)即可明白(特别注意对this.length和this[index]属性的设置):
jQuery.prototype.init: function( selector, context, rootjQuery )
var match, elem, ret, doc;
// Handle $(""), $(null), or $(undefined)
if ( !selector )
return this;
// Handle $(DOMElement)
if ( selector.nodeType )
this.context = this[0] = selector;
this.length = 1;
return this;
// The body element only exists once, optimize finding it
if ( selector === "body" && !context && document.body )
this.context = document;
this[0] = document.body;
this.selector = selector;
this.length = 1;
return this;
// Handle html strings
if ( typeof selector === "string" )
// Are we dealing with HTML string or an ID?
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 )
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ];
else
match = quickExpr.exec( selector );
// Verify a match, and that no context was specified for #id
if ( match && (match[1] || !context) )
// HANDLE: $(html) -> $(array)
if ( match[1] )
context = context instanceof jQuery ? context[0] : context;
doc = (context ? context.ownerDocument || context : document);
// If a single string is passed in and it's a single tag
// just do a createElement and skip the rest
ret = rsingleTag.exec( selector );
if ( ret )
if ( jQuery.isPlainObject( context ) )
selector = [ document.createElement( ret[1] ) ];
jQuery.fn.attr.call( selector, context, true );
else
selector = [ doc.createElement( ret[1] ) ];
else
ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
return jQuery.merge( this, selector );
// HANDLE: $("#id")
else
elem = document.getElementById( match[2] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode )
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id !== match[2] )
return rootjQuery.find( selector );
// Otherwise, we inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
this.context = document;
this.selector = selector;
return this;
// HANDLE: $(expr, $(...))
else if ( !context || context.jquery )
return (context || rootjQuery).find( selector );
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
else
return this.constructor( context ).find( selector );
// HANDLE: $(function)
// Shortcut for document ready
else if ( jQuery.isFunction( selector ) )
return rootjQuery.ready( selector );
if (selector.selector !== undefined)
this.selector = selector.selector;
this.context = selector.context;
return jQuery.makeArray( selector, this );
,
这是分析并理解jQuery背后原理的最基础最前提的一步。
最后来看看或者猜想下Array.prototype.slice方法的原理或者内部实现
原理探析
function slice(start, end)
var startToUse = start || 0,
endToUse = end || ToUint32(this.length),
result = [];
for(var i = startToUse; i < endToUse; i++)
result.push(this[i]);
return result;
原理部分参考了以下文章:
http://www.cnblogs.com/littledu/archive/2012/05/19/2508672.html
http://www.jb51.net/article/24956.htm
以上是关于Array.prototype.slice应用和原理探析的主要内容,如果未能解决你的问题,请参考以下文章
String.prototype.slice() && Array.prototype.slice() 实现克隆数据
理解Array.prototype.slice.call(arguments)
Array.prototype.slice.call(arguments)探究