前端面试系列-JavaScript-call、applay、bind的区别及代码实现

我心飞翔 分类:javascript

call 和 apply 的主要作用,是改变对象的执行上下文,并且是立即执行的。它们在参数上的写法略有区别;bind 也能改变对象的执行上下文,它与 call 和 apply 不同的是,返回值是一个函数,并且需要稍后再调用一下,才会执行。

一、call

call 的写法

Function.call(obj,[param1[,param2[,…[,paramN]]]])
 
var name = 'name'
var obj = {
 name: 'objName'
}
function getName(p1, p2) {
 console.log(p1, p2,this.name)
}
getName(1, 2) //1 2 "name"
getName.call(obj, 1, 2)//1 2 "objName"
 

需要注意以下几点:

  • 调用 call 的对象,必须是个函数 Function。
  • call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。
  • 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 Function 对应的第一个参数上,之后参数都为空。
function func (a,b,c) {}

func.call(obj, 1,2,3)
// func 接收到的参数实际上是 1,2,3

func.call(obj, [1,2,3])
// func 接收到的参数实际上是 [1,2,3],undefined,undefined
 

call模拟实现

  1. 明确是谁调用call:函数。
  2. call接收的第一个参数是要改变的this指向(去执行这个函数)。若无指定,默认为window
  3. call接收的后续参数就是作为调用call的那个函数所需的参数。
function myCall(context) {
  //判断一下
  if (typeof this !== 'function'){
	throw new TypeError('error')
  }
  //this指向,谁去执行去这个函数
  context = context || window
  //要执行的函数
  context.fn = this
  //取出参数
  const args = [...arguments].slice(1)
  //执行函数
  const result = context.fn(...args)
  //删除fn
  delete context.fn
  return result
}
 

验证一下

Function.prototype.myCall = myCall
getName.myCall(obj, 1, 2)//1 2 "objName"
 

二、applay

apply使用与call大体一致,只是接受参数的方法不同。call可以接收多个参数。apply接收的第一个参数是this,第二个参数是 所需参数所组成的数组。

Function.apply(obj[,argArray])
 

applay模拟实现

function myApply(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }
  context = context || window;
  context.fn = this;
  var result;
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  delete context.fn;
  return result;
}
 

三、bind

Function.bind(thisArg[, arg1[, arg2[, ...]]])
 

bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。

bind模拟实现

function  myBind(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
const _this = this
const args = [...arguments].slice(1)
// 返回函数
return function F() {
  // 1 判断是否用作构造函数
  if (this instanceof F) {
    return new _this(...args, ...arguments)
  }
  // 2 用作普通函数
  return _this.apply(context, args.concat(...arguments))
 }
}
 

验证一下:
普通函数

Function.prototype.myBind = myBind

var name = 'name'
var obj = {
 name: 'objName'
}

function test(p1, p2){
  this.a = p1
  this.b = p2
  console.log(this.name,p1, p2)
}
var f1 = test.myBind(obj, 1)
f1(2)//objName 1 2
 

构造函数

var name = 'name'
var obj = {
 name: 'objName'
}

function test(p1, p2){
  this.a = p1
  this.b = p2
  console.log(this.name,p1, p2)//undefined 1 2
}
var f1 = test.myBind(obj, 1)
var f= new f1(2)
console.log(f)//test {a: 1, b: 2}
 

应用场景

1.对象的继承

function superClass () {
    this.value = 123;
    this.print = function () {
        console.log(this.value);
    }
}

function subClass () {
    superClass.call(this);
    this.print();
}

subClass();
//123
 

2.借用方法

使类数组可以使用Array 原型链上的方法

let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
 

3.Math.max||Math.min

let arr = [1,2,3,4,5];
console.log(Math.max.apply(this,arr))//5
console.log(Math.min.call(this,...arr))//1
 

4.数组合并

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
 

回复

我来回复
  • 暂无回复内容