目录
今天在读程序题的时候,遇到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吧
- 先给Function原型上扩展个方法并接收2个参数,
Function.prototype.myApply = function (context, args) {
}
复制代码
- 因为不传context的话,this会指向window的,args也做一下容错处理
Function.prototype.myApply = function (context, args) {
//这里默认不传就是给window,也可以用es6给参数设置默认参数
context = context || window
args = args ? args : []
}
复制代码
- 需要回想一下绑定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)
}
复制代码
- 最后一步要返回函数调用的返回值,并且把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的实现了。