call-apply的理解
- call 和 apply 都属于 Funtion.prototype 的方法,是 js 引擎内在实现的,作用都可以改变 this 的指向;
- 不同点是:call()方法接受的是若干个参数的列表,apply() 是以数组的形式传参;
eg:
var func = function(arg1, arg2) {
...
};
func.call(this, arg1, arg2); // 使用 call,参数列表
func.apply(this, [arg1, arg2]) // 使用 apply,参数数组
复制代码
使用场景:
1、判断 object 的类型 (验证是否是数组)
let arr = [1, 2, 3]
let obj = {
name: '清华',
id: '0001'
}
console.log(typeof arr); // object
console.log(typeof obj); // object
<!--验证对象类型-->
1, Object.prototype.toString.call()
console.log(Object.prototype.toString.call(arr)); // [object Array]
console.log(Object.prototype.toString.call(obj)); // [object Object]
2, isArray()
console.log(Array.isArray(arr)) // true
3, instanceof
let arr = [1, 2, 3];
console.log(arr instanceof Array) // true
复制代码
扩展: Object.prototype.toString 是怎么实现的呢? 【参考文章】
说一下在 ES5 中 Object.prototype.toString 调用时,会进行的操作:
- 如果
this
是undefined
,返回 [object Undefined] ; - 如果
this
是null
, 返回 [object Null] ; - 令
O
为以this
作为参数调用ToObject
的结果; - 令
class
为O
的内部属性 [[Class]] 的值; - 返回三个字符串 "[object", class, 以及"]" 拼接而成的字符串。
第 1, 2 步,是 ES5 新加的,在 ES3 的时候是没有单独判断 undefined 和 null 的,直接返回的是[object Object]
[[Class]]
[[Class]]
是一个内部属性,值为一个类型字符串,可以用来判断值的类型。
MDN 上的解释是:
本规范的每种内置对象都定义了 [[Class]] 内部属性的值。宿主对象的 [[Class]] 内部属性的值可以是除了 "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String" 的任何字符串。[[Class]] 内部属性的值用于内部区分对象的种类。注,本规范中除了通过 Object.prototype.toString ( 见 15.2.4.2) 没有提供任何手段使程序访问此值。
2、 合并两个数组
let a = [1, 2, 3]
let b = ['a', 'b', 'c']
// 相当于 a.push('a', 'b', 'c')
Array.prototype.push.apply(a, b)
console.log(a);
// [1, 2, 3, "a", "b", "c"]
<!-- 但是被 ES6 的扩展运算符替代 -->
a.push(...b)
console.log(a);
// [1, 2, 3, "a", "b", "c"]
复制代码
扩展: 当第二个数组(如示例中的 b )太大时不要使用这个方法来合并数组,JS核心限制在 65535,有些引擎会抛出异常,有些不抛出异常但丢失多余参数。
如何解决? 将参数数组切块后循环传入目标方法
function concatArray(arr1, arr2) {
let MAXNUM = 32768;
let len = arr2.length
for (let i = 0; i < len; i += MAXNUM) {
Array.prototype.push.apply(
arr1,
arr2.slice(i, Math.min(i + MAXNUM, 1000000) )
);
// console.log(arr2.slice(i, Math.min(i + MAXNUM, len) ));
}
return arr1;
}
let arr1 = [-3, -2, -1]
let arr2 = []
for (let i = 0; i < 1000000; i++) {
arr2.push(i)
}
// Array.prototype.push.apply(arr1, arr2);
// console.log(arr1);
// Uncaught RangeError: Maximum call stack size exceeded
console.log(concatArray(arr1, arr2));
// (1000003) [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, …]
复制代码
3、获取数组中的最大/小元素
let arr = [43, 567, 213, 4]
console.log(Math.max.apply(Math, arr)); // 567
console.log(Math.min.call(Math, 34, 567, 7, 2)); // 2
// ES6
console.log(Math.min(...arr)); // 4
复制代码
由于 JS 没有提供求数组最大/小元素的函数,所以只能套用 Math.max 函数,将数组转为一个参数序列,求最大/小元素;
4、类数组对象使用数组方法
let domNodes = document.querySelectorAll('p')
domNodes.push('div')
console.log(domNodes);
// Uncaught TypeError: domNodes.push is not a function
let domNodesArray = Array.prototype.slice.call(domNodes)
domNodesArray.push('div')
console.log(domNodesArray); // [p, p, p, p, "div"]
<!-- ES6 - 1 -->
let domNodes = document.querySelectorAll('p')
let domNodesArray = Array.from(domNodes)
domNodesArray.push('ul')
console.log(domNodesArray); // [p, p, p, p, "ul"]
<!-- ES6 - 2 -->
let domNodes = document.querySelectorAll('p')
let domNodesArray = [...domNodes]
domNodesArray.push('span')
console.log(domNodesArray); // [p, p, p, p, "ul"]
复制代码
类数组对象有下面两个特性:
- 具有:指向对象元素的数字索引下标和 length 属性;
- 不具有:比如 push 、shift、以及 indexOf等数组对象具有的方法
类数组 不是数组,要想使用数组中的方法,通过 Array.prototype.slice.call 转换成真正的数组,就可以使用 Array下所有方法。
PS扩展: 为什么要有类数组对象呢?或者说类数组对象是为什么解决什么问题才出现的?(网上看到的)
JavaScript 类型化数组是一种类似数组的对象,并提供了一种用于访问原始二进制数据的机制。 Array 存储的对象能动态增多和减少,并且可以存储任何 JavaScript 值。JavaScript 引擎会做一些内部优化,以便对数组的操作可以很快。然而,随着 Web 应用程序变得越来越强大,尤其一些新增加的功能例如:音频视频编辑,访问 WebSockets 的原始数据等,很明显有些时候如果使用 JavaScript 代码可以快速方便地通过类型化数组来操作原始的二进制数据,这将会非常有帮助。
总结就是,可以更快的操作复杂数据。
5、调用父构造函数实现继承
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
console.log('hello world');
}
function Worker(name, age, job) {
Person.call(this, name, age); // 构造继承
this.job = job;
}
// 原型继承
Worker.prototype = new Person();
let w = new Worker('zs', 20, 'aaa')
console.log(w.age);
w.sayHi()
复制代码
在子构造函数中,通过调用父构造函数的call方法来实现继承,于是SubType的每个实例都会将SuperType 中的属性复制一份。
说一下 bind
都改变 this 指向;
call,apply 都是立即执行函数;而 bind 是将函数复制一份,不直接执行;
bind 的用法:
filter(过滤函数), 大家都经常用到;
// filter (过滤函数)
let arr = [1, 2, 3, 4, 5, 6, 7, 8];
let resultArr = arr.filter(item => {
return item > 5
})
console.log(resultArr); // [6, 7, 8]
复制代码
那么可以模拟一下 filter 函数的实现 (不是源码实现)
- 第一个参数为一个匿名函数 第二个参数为this指向
- 迭代调用它的数组所以内部肯定是循环
Array.prototype.myFilter = function(fn, obj) {
let resultArr = []
let len = this.length
// 调整 this 指向
if (obj !== undefined) {
fn = fn.bind(obj)
}
for(let index = 0; index< len; index++) {
if (fn(this[index], index, obj)) { // 返回 true
resultArr.push(this[index])
}
}
return resultArr
}
复制代码
测试模拟函数:
let arr = [1, 2, 3, 4, 5, 6, 7, 8];
let resultArr = arr.myFilter(item => {
return item > 3
})
console.log(resultArr); // [4, 5, 6, 7, 8]
复制代码