为什么slice方法在Array.prototype上而from、isArray方法在Array上

1,011 阅读4分钟

和这个问题相似的一个问题是:为什么isArrayArray上而不在Array.prototype上。即通常调用isArray方法是使用Array.isArray(),而不是Array.prototype.isArray()

方法放在原型对象上(即Array.prototype上)大家可以理解,以前在实现继承的时候,就使用过:

function Person() {
    this.name = 'fan';
}
// 方法放在原型对象上,达到共享方法、节省存储空间的效果
Person.prototype.sayHello = function () {
    console.log('hello');
};

先说明为什么方法可以放在构造函数上

把方法放在构造函数上很少使用,这就是问题的关键,实际上构造函数也是一个对象,它是Function构造函数的一个实例,既然是对象,那就可以有自己的属性和方法,当然就可以把方法放在构造函数上了,比如sayHello这个方法,可以把它放在Person构造函数(也是Person对象)上,当作Person的一个成员。每个JavaScript函数实际上都是一个Function实例。

Function构造函数自己也是一个对象,但它没有自己的属性和方法,它也有自己的原型对象,用来放一些属性和方法,比如常用的callbindapply方法实际上就是放在Function.prototype上的,所以,只要是函数,就能通过原型链访问到这些方法,因为JS函数是Function实例对象,会指向Function.prototype。 通过以下代码就可以看到一个JS函数是Function实例,并且上面有自己定义的方法:

1.png

接下来说明把方法放在原型对象上和放在构造函数上有什么区别

由于原型链的关系,凡是放在原型对象上的方法,都可以通过实例直接调用该方法。通俗地讲,就是一个实例只能访问到原型链上(各级原型对象上)的属性和方法,比如一个数组:

let arr = [1, 2, 3, 4, 5];
// 可以直接用arr调用slice方法
let arrCopy1 = arr.slice();
// 也可以用Array.prototype.slice.call调用
let arrCopy2 = Array.prototype.slice.call(arr);

但是,只要这个方法放在构造函数上,比如from方法,它就不能被实例直接调用,实例访问不到它,如下:

let arr = [1, 2, 3];
// 错误的调用
let arrCopy = arr.from();    // TypeError: arr.from is not a function
// 正确的调用
let arrCopy = Array.from(arr);

最后是为什么要把方法放在不同的地方

比如Array.isArray,它是判断一个变量是否是一个数组,如果把它放在Array.prototype上,根据前面讲的特性,那么只有当一个变量已经是数组的情况下,才能调用isArray,也就是说,一个变量arr,如果是数组,调用arr.isArray()就是true,如果不是数组,那它根本就没办法调用isArray。况且,变量可能是各种各样的值,比如基本类型的numberstringundefinednullnull是没有办法调用任何函数的。所以,isArray方法必须放在Array构造函数上,每次调用它都用Array.isArray()

不过,如果使用call强行用实例直接调用原型对象上的方法,也可以实现,但不提倡:

function Person() {}
Person.prototype.myIsArray = Array.isArray;

let person = new Person();
// 第一个参数是null,因为call是把函数内的this绑定为第一个参数,Array.isArray方法是判断参数是否为数组
console.log(person.myIsArray.call(null, [1, 2]));
// 经过测试,可以得到正确的结果,判断一个东西是否是数组

通过Array.isArray(),可以知道其他内置对象的方法放在原型对象上还是在构造函数上,原因都大同小异。

我个人认为from方法之所以放在Array构造函数上是因为不是所有数据类型都能转成数组的,所以在其函数体中,一定有对类型的判断,比如Array.from(null)就会报TypeError: Cannot convert undefined or null to object,显然,放在原型对象上就不合适了(null没法调用任何方法,而from方法必须对不合适的数据类型进行处理)。

对于slice,它可以切割数组,也可以切割字符串,还可以把类数组对象转成数组。对于其他数据类型的变量,是不允许切的,所以根本就不允许其他数据类型的变量调用slice