1、定义
Apply()/call()方法调用一个具有给定this值的函数,用数组或列表形式提供的参数。
Bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this指向bind()的第一个参数,其余的参数作为新函数的参数,供调用时使用。
在JS中,call和apply都是为了改变某个函数运行时上下文存在的(即改变函数内部this指向)
JS的一大特点:存在定义时上下文、运行时上下文和函数上下文是可以改变的
2、区别
- apply、call、bind都是用来改变this指向
- apply、call、bind 中第一个参数都是this要指向的对象,指定的上下文
- apply、call、bind都可以利用后续参数传值
- bind返回一个绑定上下文的函数,可以立即执行(在bind绑定函数后加括号),也可以稍后执行;apply、call则是立即执行
注意:多次bind()是无效的
3、常用用法
3.1数组之间的追加
Array.prototype.push.apply(array1,array2)
3.2验证是否是数组
Object.prototype.toString.call(obj)==='[object Array]'
3.3伪数组变真数组
Array.prototype.slice.call(arguments)
3.4获取数组中最大值和最小值
Let num = [1,3,5445,289]
Math.max.apply(Math,number)
Math.max.call(Math,3,6,78,1)
3.5 bind的使用
var name = '兔子'
var food = "草"
function Animal(name, food) {
this.name = name
this.food = food
this.eating = function () {
setTimeout(function () {
console.log(this)
console.log(this.name + '吃' + this.food)
}, 1000)
}
}
let cat = new Animal('猫', '鱼')
cat.eating()
正常执行这段代码我们想要输出的答案肯定是 “猫吃鱼”,但是实际上确是“兔子吃草”,为什么呢,因为setTimeout在全局环境中执行,所以哦this指向了window,如果要输出“猫吃鱼”,就要改变setTimeout函数执行时的this指向,如下
var name = '兔子'
var food = "草"
function Animal(name, food) {
this.name = name
this.food = food
this.eating = function () {
let _this = this //保存this
setTimeout(function () {
console.log(this)
console.log(_this.name + '吃' + _this.food)
}, 1000)
}
}
let cat = new Animal('猫', '鱼')
cat.eating()//猫吃鱼
或者
var name = '兔子'
var food = "草"
function Animal(name, food) {
this.name = name
this.food = food
this.eating = function () {
setTimeout(function () {
console.log(this)
console.log(this.name + '吃' + this.food)
}.bind(this), 1000)//通过bind改变this指向
}
}
let cat = new Animal('猫', '鱼')
cat.eating()//猫吃鱼
4、原理实现
4.1 call实现
4.1.1原理分析:
执行call时实际上是先改变执行函数的this指向,然后再进行函数执行
举例:
var foo = {
value:1
}
function bar() {
console.log(this.value)
}
bar.call(foo)//1
call调用后实际如下执行:
var foo = {
value: 1,
bar: function () {
console.log(this.value)
}
}
foo.bar()//1
这样就将this指向了foo,在真正执行call时用完bar属性还是会把他删除的,下面我们就来模拟实现一下call
4.1.2步骤如下:
- 将要执行函数(bar)设为传入对象(foo)的属性;
- 执行改函数(foo.bar());
- 删除该函数;
- 返回结果;
4.1.3实现代码如下:
Function.prototype._call = function (context = window, ...args) {
if (this === Function.prototype) {
return undefined; // 用于防止 Function.prototype.call() 直接调用
}
context = context;
const fn = Symbol();//键的唯一性,避免与context中的其他键发生冲突
context[fn] = this;//很多同学不明白这个this是什么意思,实际上这里符合了this绑定四项原则的隐式绑定原则,this相当于上面🌰中的bar函数,将此函数设为传入对象的属性
const result = context[fn](...args);//执行函数
delete context[fn];//删除该函数,避免对空间的浪费以及对执行上下文对象的污染
return result;
}
4.2 apply实现
apply的原理同call类似,只是apply中第二个参数传入的是一个数组
4.2.1代码实现
Function.prototype._apply = function (context = window, args) {
if (this == Function.prototype) {
return undefined
}
const fn = Symbol()
context[fn] = this
let result
if (Function.prototype.toString.call(args) === '[Object array]') {//Array.isArray()
result = context[fn](...args)
} else {
result = context[fn]()
}
delete context[fn]
return result
}
4.3 bind 实现
bind的实现和call,apply还不一样
bind特点
- 改变this指向
- 返回一个函数
- 可以传入参数
- 绑定函数也可以使用new操作符创建对象 接下来一步一步用js实现bind
4.3.1第一版改变this指向并返回函数
Function.prototype._bind = function (context) {
let fn = this
if (typeof this !== 'function') {
return undefined
} else {
return function () {
fn.call(context)
}
}
}
4.3.2第二版 传入参数并支持柯里化
Function.prototype._bind = function (context) {
let fn = this
if (typeof this !== 'function') {
return undefined
}
let args = Array.prototype.slice.call(arguments, 1)
return function () {
let bindArgs = Array.prototype.slice.call(arguments)
fn.apply(context, args.concat(bindArgs))
}
}
4.3.2 第三版(终版)
以上的实现即可满足bind的大部分使用场景,但是要搞就要搞明白呗; 仔细阅读MDN中# Function.prototype.bind()发现bind还有另外一个特性
当使用new构造绑定函数时,原来提供的this值会被忽略,提供的参数列表仍然会插入到构造函数调用时的参数列表之前。(以下面代码为参考,如果是new调用,bound函数的this指向实例,如果是普通函数调用this指向bind绑定的context)
Function.prototype._bind = function (context) {
let fn = this
if (typeof this !== 'function') {
return undefined
}
let args = Array.prototype.slice.call(arguments, 1)
let Fn_ = function () { }
let bound = function () {
let bindArgs = Array.prototype.slice.call(arguments)
// this instanceof Fn_ 也可以来判断绑定函数是不是进行new操作
fn.apply(this.constructor === bound ? this : context, args.concat(bindArgs))
}
Fn_.prototype = fn.prototype
bound.prototype = new Fn_()//如果直接使用bound.prototype=fn.prototype 会直接更改fn的原型函数
return bound
}
over~
5、参考地址
www.cnblogs.com/coco1s/p/48… blog.csdn.net/chern1992/a… zhuanlan.zhihu.com/p/94068275 www.muyiy.cn/blog/3/3.4.…