对JavaScript中的this进行的系列解读:
- 全面解析JavaScript中this绑定
- 全面解析JavaScript中的call、apply、bind、new
call()和apply()
把call()
和apply()
放在一起,是因为这两个方法非常接近,区别在于调用的时候参数形式不同。
call和apply的原理及区别
直接看代码直观点,定义一个函数person
,分别用call()
和apply()
调用,我们来看下传递的参数的区别:
var func = function(name, age) {
console.log(`my name is ${name}, my age is ${age}`)
};
func('谷底飞龙', 28); // 直接调用方法
func.call(this, '谷底飞龙', 28); // call执行方法,参数是多个对象
func.apply(this, ['谷底飞龙', 28]) // apply执行方法,参数是多个对象组成的数组
使用上述三种方式执行方法,都能打印出my name is 谷底飞龙, my age is 28
call和apply使用方法及场景
从上面小结中,我们知道call和apply主要有两方面的特性,使用场景就是基于这两方面可以分为两类:
1.改变this指向
从上面小结中,我们知道call和apply的第一个特性通过第一个参数来改变函数内部的this指向
,利用这一特性的使用场景比较多。
1.1 解决隐式绑定丢失问题
我们来把this绑定之箭头函数绑定中的案例回顾下:
var name = "天下无敌";
var Person = {
name : "谷底飞龙",
getName: function () {
console.log(`my name is ${this.name}`)
},
consoleName: function () {
// 回调函数不使用箭头函数,属于隐式绑定中的隐式丢失的情况,this绑定的是全局对象window
setTimeout(function (){
this.getName()
},100);
}
};
// 执行函数
Person.consoleName()
这种情况下,执行方法Person.consoleName()
会报错Uncaught TypeError: this.getName is not a function
,除了把setTimeout()
的回调函数改用箭头函数外,还可以使用call和apply来改变this指向的方式解决。如下
var name = "天下无敌";
var Person = {
name : "谷底飞龙",
getName: function () {
console.log(`my name is ${this.name}`)
},
consoleName: function () {
// 回调函数不使用箭头函数,使用call/apply硬绑定到Person对象
setTimeout(function (){
this.getName()
}.call(Person),100);
}
};
// 执行函数
Person.consoleName()
通过setTimeout()
调用call()
,将回调函数中的this硬绑定到对象Person
,执行函数后,就能打印出my name is 谷底飞龙
。这里把call换成bind也可以
,具体详见本文后面对bind
的介绍。
1.2 合并两个数组
const arr1 = [1,2,3];
const arr2 = [4,5,6];
// 使用apply()合并数组,合并后的 arr1 为 [1、2、3、4、5、6]
Array.prototype.push.apply(arr1,arr2)
使用Array.prototype.push.apply(arr1,arr2)
时,apply()
将第一个参数arr1
硬绑定到Array内部的this,因此这里等价于arr1.push(arr2)
注:
由于浏览器对函数的参数个数限制(JS核心限制在 65535
),如果数组太长,不建议使用apply的方式,在不同浏览器可以会出现数据丢失或者报错。可以将参数数组分成多组,循环遍历执行push.apply()
来解决问题(但是遍历会损耗性能,不建议使用)
1.3 类型判断
咱们先来看看不同类型打印出来的日志
toString = Object.prototype.toString;
console.log(toString.call(['谷底飞龙'])); //[object Array]
console.log(toString.call('谷底飞龙')); //[object String]
console.log(toString.call({name: '谷底飞龙'})); //[object Object]
console.log(toString.call(/谷底飞龙/)); //[object RegExp]
console.log(toString.call(123)); //[object Number]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]
通过Object.prototype.toString.call(obj)
,输出的是[object xxx]
字符串,可以利用这一特点进行类型的精确判断,这里的call
的作用就是将toString()内部的this指向obj
。
我们先来运行下代码:
console.log(['谷底飞龙','天下无敌'].toString()); //谷底飞龙、天下无敌
console.log('谷底飞龙'.toString()); //谷底飞龙
直接调用obj.toString()
返回的是obj对应的字符串,而不是对象类型。因为toString()是Object的实例方法,而obj(如Array、Function等类型)是Object的实例,实例都对toString()进行了重写(注:重写后,Functionl类型返回的是字符串,Array类型返回的是数组元素组成的字符串
),根据原型链知识,调用obj.toString()
,执行的是重写后的方法,只能获取到对应的字符串,不能获取到对象类型。因此,应该通过原型方法Object.prototype.toString
去判断对象类型。
- 判断是否是数组,可以这样写:
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
2.解构参数数组
利用第二个特性调用apply()时,会将剩余参数数组解构成一系列对象
,我们可以利用这一特性对数组进行一些操作。
2.1 获取数组中的最大值和最小值
const arr = [88,78,128,89,45]
// 使用apply解构数组参数
Math.max.apply(null,arr)
// ES6中解构方法
Math.max(...arr)
// ES6中使用call
Math.max.call(null,...arr)
Math.max
和Math.min
本身是不能对数组直接计算的,可以把数组解构成一系列对象再操作。apply()
对第二个参数arr
的解构,类似ES6中解构方法...arr
,可以把数组解构成一系列对象。- 由于这里的this的绑定没影响,所以
apply()
和call()
的第一个参数可以随便设置,这里设置为null
。
call和apply的模拟实现
bind()
bind的原理及与call/apply的区别
我们来直接看个案例会更直观点
var name = '天下无敌'
var Person = {
name: '谷底飞龙',
}
var getName = function () {
console.log(`my name is ${this.name}`)
}
// 使用bind硬绑定到Person对象
const consoleName = getName.bind(Person)
// 执行函数
consoleName()
通过bind
将方法getName
内部的this指向Person
对象,并返回一个新函数consoleName
,执行新函数consoleName()
后,会打印出Person对象内部的name,也就是my name is 谷底飞龙
。
- 1、如果不调用
bind
,直接执行getName()
,打印的将是全局window的name,也就是my name is 天下无敌
。 - 2、如果将这里的
bind
改成call/apply
,由于call/apply
只执行函数,不会返回新函数,执行函数就会报错Uncaught TypeError: consoleName is not a function
bind使用方法及场景
bind的模拟实现
new()
new()的原理与分析
function Person(name){
// 属性
this.name = name;
}
// person的方法
Person.prototype.getAge = function() {
return 18;
}
// 通过new调用函数创建对象实例
var P = new Person('谷底飞龙')
console.log(`my name is ${P.name}, my age is ${P.getAge()}`)
打印出my name is 谷底飞龙, my age is 18
,我们可以得出new调用函数创建的实例对象有两个特性:
new()的模拟实现
参考文档:
- 深度解析 call 和 apply 原理、使用场景及实现
- 一次性搞懂 this、call、apply 、 bind
- 用Object.prototype.toString.call(obj)检测对象类型原因分析
结语
如果你也是一个对投资理财感兴趣的程序员,欢迎关注我的公众号「谷底飞龙」,一起成为技术界的投资大佬吧。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!