call-apply的理解

本文深入探讨了JavaScript中call和apply方法的区别与应用场景,包括改变this指向、判断对象类型、数组合并、获取最大最小值、类数组对象使用数组方法及继承实现。同时,对比了bind方法的不同。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 是怎么实现的呢? 【参考文章】

说一下在 ES5Object.prototype.toString 调用时,会进行的操作:

  • 如果 thisundefined ,返回 [object Undefined] ;
  • 如果 thisnull , 返回 [object Null] ;
  • O 为以 this 作为参数调用 ToObject 的结果;
  • classO 的内部属性 [[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]
复制代码

转载于:https://juejin.im/post/5c34864a518825261d016b0f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值