call、apply、bind的区别与应用场景

前言:读者在看这篇文章的时候,你必须弄懂做用域以及JavaScript中this的做用和运用场景。

1、概念

  • 为何会有call和apply? call和apply两个方法的做用基本相同,它们都是为了改变某个函数执行时的上下文(context)而创建的, 他的真正强大之处就是可以扩充函数赖以运行的做用域。通俗一点讲,就是改变函数体内部this 的指向javascript

举个栗子:

window.color = "red";
var o = {color: "blue"};
function sayColor(){
	alert(this.color);
}
sayColor();//red
sayColor.call(this);//red,把函数体sayColor内部的this,绑到当前环境(做用域)(这段代码所处的环境)
sayColor.call(window);//red,把函数体sayColor内部的this,绑到window(全局做用域)
sayColor.call(o);//blue复制代码

解释:上面的栗子,很明显函数sayColor是在全局做用域(环境/window)中调用的,而全局做用域中有一个color属性,值为"red",sayColor.call(this)这一行代码就是表示 把函数体sayColor内部的this,绑到当前环境(做用域),而sayColor.call(window)这一行代码就是表示 把函数体sayColor内部的this,绑到window(全局做用域),之因此这两行的输出都是"red"就是由于他当前做用域的this就是window(this === window); 最后,sayColor.call(o)这一行代码就表示 把函数体sayColor内部的this,绑到o这个对象的执行环境(上下文)中来,也就是说sayColor内部的this——> o

2、call( thisValue , arg1, arg2, ... )

window.color = "red";
var o = {color: "blue"};
function sayColor(){
	alert(this.color);
}
sayColor.call(this);//red
sayColor.call(window);//red
sayColor.call();
sayColor.call(null);
sayColor.call(undefined);
sayColor.call(o);//blue复制代码

注意:若是call方法没有参数,或者参数为 null或undefined,则等同于指向 全局对象

应用场景

  • 判断对象类型

var arr = [];
Object.prototype.toString.call(arr); // [object Array]
//把函数体Object.prototype.toString()方法内部的this,绑到arr的执行环境(做用域)复制代码

一样是检测对象类型,arr.toString()的结果和Object.prototype.toString.call(arr)的结果不同,这是为何?

这是由于toString()为Object的原型方法,而Array ,function等引用类型做为Object的实例,都重写了toString方法。不一样的对象类型调用toString方法时,根据原型链的知识,调用的是对应的 重写以后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串.....),而不会去调用Object上原型toString方法,因此采用arr.toString()不能获得其对象类型,只能将arr转换为字符串类型;所以,在想要获得对象的具体类型时,应该调用Object上原型toString方法。 参考: developer.mozilla.org/zh-CN/docs/…()_to_detect_object_class

手撕call

var foo = {
	  count: 1
	};
	function bar() {
	  console.log(this.count);
	}
	bar.myCall(foo); // 1
--------------------------------------------------------------------
	Function.prototype.myCall = function(context) {
	  // 取得传入的对象(执行上下文),好比上文的foo对象,这里的context就至关于上文的foo
	  // 不传第一个参数,默认是window,
	  var context = context || window;
	  // 给context添加一个属性,这时的this指向调用myCall的函数,好比上文的bar函数
	  context.fn = this;//这里的context.fn就至关于上文的bar函数
	  // 经过展开运算符和解构赋值取出context后面的参数,上文的例子没有传入参数列表
	  var args = [...arguments].slice(1);
	  // 执行函数(至关于上文的bar(...args))
	  var result = context.fn(...args);
	  // 删除函数
	  delete context.fn;
	  return result;
	};复制代码

3、apply( thisValue , [arg1, arg2, ...] )

很明显,咱们看标题的能够知道call和apply的一个区别了,它们两个惟一的区别就是传参列表的不一样,apply是接收的参数是一个数组。
java

手撕apply

var foo = {
    count: 1
};
function bar() {
    console.log(this.count);
}
bar.myApply(foo); // 1
--------------------------------------------------------------------
Function.prototype.myApply = function(context) {
      var context = context || window;
      context.fn = this;
      var result;
      // 判断第二个参数是否存在,也就是context后面有没有一个数组
      // 若是存在,则须要展开第二个参数
      if (arguments[1]) {
        result = context.fn(...arguments[1]);
      } else {
        result = context.fn();
      }
      delete context.fn;
      return result;
}复制代码

应用场景

  1. 找出数组中最大或最小的元素

var a = [10, 2, 4, 15, 9];
	Math.max.apply(Math, a);//15
	Math.min.apply(null, a);//2复制代码

2. 能够将一个相似(伪)数组的对象(好比arguments对象)转为真正的数组。 **前提:**被处理的对象必须有length属性,以及相对应的数字键。
git

//接收的是对象,返回的是数组
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]
//(切下)[].slice(1, n),返回索引为1到索引为n-1的数组复制代码

3. 数组追加
github

var arr1 = [1,2,3];
var arr2 = [4,5,6];
[].push.apply(arr1, arr2);
console.log(arr1);//[1, 2, 3, 4, 5, 6]
console.log(arr2);//[4, 5, 6]复制代码

4. 数组合并
数组

var arr1 = [1, 2, { id: 1, id: 2 }, [1, 2]];
var arr2 = ['ds', 1, 9, { name: 'jack' }];
// var arr = arr1.concat(arr2);//简单作法
Array.prototype.push.apply(arr1,arr2)
console.log(arr1);复制代码

4、bind( thisArg[, arg1[, arg2[, ...]]])

developer.mozilla.org/zh-CN/docs/…app

call和apply它们两个是改变this的指向以后当即调用该函数,而bind则不一样,它是建立一个新函数,咱们必须手动去调用它。 MDN说法:bind()方法建立一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表做为原函数的参数序列的前若干项。(虽然这句话我还不太懂)函数

  • bind()是ES5新增的一个方法ui

  • 传参和call或apply相似this

  • 不会执行对应的函数,call或apply会自动执行对应的函数spa

  • bind会返回对函数的引用

举个栗子

var a ={
       name : "Cherry",
       fn : function (a,b) {
           console.log( a + b)
       }
   }
   var b = a.fn;
   b.call(a,1,2);//当即调用该函数
   b.bind(a,1,2)();//手动调用(),它返回一个原函数的拷贝(新的,不是原函数),并拥有指定的this值和初始参数。复制代码

var obj = {
			name:'JuctTr',
			age: 18
		};
		/** * 给document添加click事件监听,并绑定ExecuteFun函数 * 经过bind方法设置ExecuteFun的this为obj */
		//document.addEventListener('click',ExecuteFun.call(obj),false);
		document.addEventListener('click',ExecuteFun.bind(obj),false);		
		function ExecuteFun(a,b){
		    console.log(this.name, this.age);
		}复制代码

若是把bind换成call或apply,页面控制台会直接输出JuctTr 18,由于call和apply是改变this的指向以后当即执行ExecuteFun函数,而bind它是返回一个新的函数

应用场景

手撕bind

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 由于返回了一个函数,咱们能够 new F(),因此须要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}复制代码


更多文章请转移:github.com/wangyicong

相关文章
相关标签/搜索