JavaScript中的call,apply,bind区别及应用(包含手写call/apply/bind)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_29918313/article/details/92767313

目录

一、使用目的

二、三者分别是如何定义的及区别(摘自MDN)

三、在程序中收获

四、三者的具体应用

四、手写bind,apply,call


今天在读程序题的时候,遇到call,apply,bind的使用。

const person = { name: 'Lydia' }

function sayHi(age) {
  console.log(`${this.name} is ${age}`)
}

sayHi.call(person, 21)
sayHi.bind(person, 21)

一、使用目的

三者都是用来改变this的指向。

二、三者分别是如何定义的及区别(摘自MDN)

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

MDN关于apply详细介绍:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply 

注意:call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组

bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。 

MDN关于bind详细介绍:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

MDN关于call详细介绍:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call 

注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

call()改过this的指向后,会再执行函数,bind()改过this后,不执行函数,会返回一个绑定新this的函数

function f(){
console.log("看我怎么被调用");
console.log(this) //指向this
}
var obj = {};
f.call(obj) //直接调用函数
var g = f.bind(obj); //bind()不能调用函数
g();  //此时才调用函数

三、在程序中收获

<script>
 	var name = 'Lily',
 		age = 13
 	var obj = {
 		name: 'Sam',
 		objAge: this.age,
 		myFunc: function (param1, param2) {
 		console.log(this.name + ' ' + this.objAge + ' ' + this.age + ' ' + param1 + ' '  + param2)
 		// Sam 13 undefined 1 2
 	    }
 	}
 	obj.myFunc(1, 2)
</script>

 非常明确,在obj.myFunc()中,通过this.age是获取不到定义在window中的age值,因为在该方法中,this指向的是obj对象。因此值为undefined,这个时候,可以通过call,apply,bind来更改this的指向,从而获取值。

<script>
 	var name = 'Lily',
 		age = 13
 	var obj = {
 		name: 'Sam',
 		objAge: this.age,
 		myFunc: function (param1, param2) {
 			console.log(this.name + ' ' + this.objAge + ' ' + this.age + ' ' + param1 + ' '  + param2)
 			// Amy 20 21 1 2
 		}
 	}
 	var db = {
 		name: 'Amy',
 		age: 21,
 		objAge: 20
 	}
 	obj.myFunc.call(db, '1', '2') // db为this要指向的对象,后面传入的是参数列表,参数可以是任意类型,当第一个参数为null、undefined的时候,默认指向window;
    obj.myFunc.apply(db, [1, 2]) // db为this要指向的对象,参数必须放在一个数组里面;
 	obj.myFunc.bind(db, '1', '2')() // db为this要指向的对象,返回的是一个新函数,必须调用才会去执行。
</script>
 /*定义一个人类*/  
    function Person(name,age)  
    {  
        this.name=name;  
        this.age=age;  
    }  
    /*定义一个学生类*/  
    function Student(name,age,grade)  
    {  
        Person.apply(this,arguments);  
        this.grade=grade;  
    }  
    //创建一个学生类  
    var student=new Student("zhangsan",21,"一年级");  
    //测试  
    alert("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);  
    //大家可以看到测试结果name:zhangsan age:21  grade:一年级  
    //学生类里面我没有给name和age属性赋值啊,为什么又存在这两个属性的值呢,这个就是apply的神奇之处.  

  Person.apply(this,arguments);

this:在创建对象在这个时候代表的是student

arguments:是一个数组,也就是[“zhangsan”,”21”,”一年级”];

就是通俗一点讲就是:用student去执行Person这个类里面的内容,在Person这个类里面存在this.name等之类的语句,这样就将属性创建到了student对象里面

call示例

在Studen函数里面可以将apply中修改成如下:

Person.call(this,name,age);

四、三者的具体应用

转自:https://blog.csdn.net/wyyandyou_6/article/details/81488103

(1)利用call()判断数据类型
在判断数据类形式使用typeof,一般不是太准确的,我们可以使用Object.prototype.toString.call()方法来判断一个数据的数据类型console.log(Object.prototype.toString.call("qq"))            // [Object String] 返回值都是字符串类型
console.log(Object.prototype.toString.call(12))              // [object Number]
console.log(Object.prototype.toString.call(false))           // [object Boolean]
console.log(Object.prototype.toString.call(undefined))       // [object Undefined]
console.log(Object.prototype.toString.call(null))            // [object Null]
console.log(Object.prototype.toString.call(function(){}))    // [object Function]
console.log(Object.prototype.toString.call([]))              // [object Array]
console.log(Object.prototype.toString.call({}))              // [object Object]

(2)利用call()翻转字符串

var str = "abcdefg";
var rs = Array.prototype.reverse.call(str.split("")).join(""); 
console.log(rs) // gfedcba

(3) 利用apply()求最大值

var arr =[2,6,8,3,4,9,7,23,56,889]; 
console.log(Math.max.apply(arr,arr)) //第一个arr表示让arr借用max这个方法,第二个arr表示传给max的数据

//apply()所执行的操作:1.执行Math.max(1,2,3,5,4) 2.把内部的this改成arr

(4)用 apply 将数组添加到另一个数组

我们可以使用push将元素追加到数组中。并且,因为push接受可变数量的参数,我们也可以一次推送多个元素。但是,如果我们传递一个数组来推送,它实际上会将该数组作为单个元素添加,而不是单独添加元素,因此我们最终得到一个数组内的数组。如果那不是我们想要的怎么办?在这种情况下,concat确实具有我们想要的行为,但它实际上并不附加到现有数组,而是创建并返回一个新数组。 但是我们想要附加到我们现有的阵列......那么现在呢? 写一个循环?当然不是吗?

var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]

最后回归最初的读程序,结果是

Lydia is 21 function

undefined

使用这两种方法,我们都可以传递我们希望 this 关键字引用的对象。但是,.call 是立即执行的。

.bind 返回函数的副本,但带有绑定上下文!它不是立即执行的。

四、手写bind,apply,call

https://juejin.im/post/5d2ddd9be51d4556d86c7b79

实现apply

先来实现apply吧

  1. 先给Function原型上扩展个方法并接收2个参数,
Function.prototype.myApply = function (context, args) {

}
复制代码
  1. 因为不传context的话,this会指向window的,args也做一下容错处理
Function.prototype.myApply = function (context, args) {
    //这里默认不传就是给window,也可以用es6给参数设置默认参数
    context = context || window
    args = args ? args : []
}
复制代码
  1. 需要回想一下绑定this的五种方式,现在要来给调用的函数绑定this了, 这里默认绑定和new肯定用不了,这里就使用隐式绑定去实现显式绑定了
Function.prototype.myApply = function (context, args) {
    //这里默认不传就是给window,也可以用es6给参数设置默认参数
    context = context || window
    args = args ? args : []
    //给context新增一个独一无二的属性以免覆盖原有属性
    const key = Symbol()
    context[key] = this
    //通过隐式绑定的方式调用函数
    context[key](...args)
}
复制代码
  1. 最后一步要返回函数调用的返回值,并且把context上的属性删了才不会造成影响
Function.prototype.myApply = function (context, args) {
    //这里默认不传就是给window,也可以用es6给参数设置默认参数
    context = context || window
    args = args ? args : []
    //给context新增一个独一无二的属性以免覆盖原有属性
    const key = Symbol()
    context[key] = this
    //通过隐式绑定的方式调用函数
    const result = context[key](...args)
    //删除添加的属性
    delete context[key]
    //返回函数调用的返回值
    return result
}

这样一个简单的apply就实现了,可能会有一些边界问题和错误判断需要完善,这里就不做继续优化了

既然apply实现了,那么call同样也非常简单了,主要就是传参不一样

实现call

这里就直接上代码吧

//传递参数从一个数组变成逐个传参了,不用...扩展运算符的也可以用arguments代替
Function.prototype.myCall = function (context, ...args) {
    //这里默认不传就是给window,也可以用es6给参数设置默认参数
    context = context || window
    args = args ? args : []
    //给context新增一个独一无二的属性以免覆盖原有属性
    const key = Symbol()
    context[key] = this
    //通过隐式绑定的方式调用函数
    const result = context[key](...args)
    //删除添加的属性
    delete context[key]
    //返回函数调用的返回值
    return result
}

实现bind:https://blog.csdn.net/q3254421/article/details/82999718

该文章有详细解释:https://github.com/mqyqingfeng/Blog/issues/12

// 定义这个方法为myBind
Function.prototype.myBind = function(thisArg) {
  if (typeof this !== 'function') {
    return;
  }
  var _self = this;
  var args = Array.prototype.slice.call(arguments, 1) //从第二个参数截取
  return function() {
    return _self.apply(thisArg, args.concat(Array.prototype.slice.call(arguments))); // 注意参数的处理
  }
}

bind和apply的区别在于,bind是返回一个绑定好的函数,apply是直接调用.其实想一想实现也很简单,就是返回一个函数,里面执行了apply上述的操作而已.不过有一个需要判断的点,因为返回新的函数,要考虑到使用new去调用,并且new的优先级比较高,所以需要判断new的调用,还有一个特点就是bind调用的时候可以传参,调用之后生成的新的函数也可以传参,效果是一样的,所以这一块也要做处理 因为上面已经实现了apply,这里就借用一下,实际上不借用就是把代码copy过来

Function.prototype.myBind = function (context, ...args) {
    const fn = this
    args = args ? args : []
    return function newFn(...newFnArgs) {
        if (this instanceof newFn) {
            return new fn(...args, ...newFnArgs)
        }
        return fn.apply(context, [...args,...newFnArgs])
    }
}

以上所有实现可以再加点判断啊,例如调用的不是function就返回或者抛出错误啊之类的.我这里就不处理了

以上就是apply,call,bind的实现了。

猜你喜欢

转载自blog.csdn.net/qq_29918313/article/details/92767313