call、apply、bind的区别
bind
bind与call或apply最大的区别就是bind不会被立即调用,而是返回一个函数,函数内部的this指向与bind执行时的第一个参数,而传入bind的第二个及以后的参数作为原函数的参数来调用原函数。
用一个例子来理解一下吧
let obj = {
name: 'wujia',
fn: function (a, b, c) {
console.log(this.name, a, b, c)
}
}
window.name = '吴佳'
let nFn = obj.fn.bind(window, '第一个参数')
nFn('第二个参数', '第三个参数')
// 最后输出:吴佳,第一个参数,第二个参数,第三个参数
根据以上例子,不难看出,我们把obj.fn函数内部this改变成window了,所以this.name的输出实际就是获取window上面的name属性。但这里要注意的是参数方面,我这么写是为了让大家更容易看清楚,我们在bind的时候只传入了一个参数,然后在执行这个bind之后的新函数(这里后面就称之为绑定函数)又传入了两个参数,其实这中间有一个过程就是参数合并,合并后的顺序就是相当于把bind执行的第二参数及之后参数与新绑定函数参数做了一个合并,新绑定函数参数会基于bind方法函数第二参数及之后参数结束位置开始进行合并。当然,如果知道柯里化的同学,就会发现好像有点柯里化的感觉,对吧。
还需要注意的一个地方,就是通过new关键字去实例这个绑定函数时,也就是通过new的方式创建一个对象,bind()函数在this层面上是没有效的,但是在参数层面上是有效的。
同样,用一个例子理解一下吧
let obj = {
name: 'wujia',
fn: function (a, b, c) {
this.age = 20
console.log(this.name, a, b, c)
}
}
window.name = '吴佳'
let nFn = obj.fn.bind(window, '第一个参数')
new nFn('第二个参数','第三个参数')
// 最后输出结果:Undefined,第一个参数,第二个参数,第三个参数
根据上面例子的输出可以看到,我们通过bind为fn函数重新指定了this,this指向了window却并没有生效,但是参数生效了,都打印出来了。fn函数内部打印的this.name为Undefined的原因是因为this通过new关键字去实例化绑定函数的时候,因为bind方法内部做了特殊处理,这个处理可以看作成过滤了当前bind的本次this指向操作,让this指向就指向与现在自己。所以我们通过new去实例化对象的时候,实际上就是去new obj.fn() 而fn内部this指向的就是当前实例化对象,所以再从实例化对象上面去找name属性是肯定找不到的,但是一定会有一个age属性在里面。
call & apply
call、apply其实都是为了改变某个函数运行时的上下文而存在的,简单点说就是为了改变某个运行时函数内部this指向。
call、apply的调用会直接返回函数的执行结果。
使用call或者apply方法,它们第一个参数,都是设置函数内部this需要指向的目标。而区别就在于后续参数传递的不同,apply第二参数需要是一个参数数组,call的第二参数及其之后的参数需要是数组里面的元素。
其实可以看做成,apply第二参数需要一个聚合的参数数组列表,而call的第二参数及其之后的参数都需要展开数组挨个传递。
用个例子理解一下
let obj = {
name: 'wujia',
fn: function (a, b, c) {
this.age = 20
console.log(a, b, c)
return this.name
}
}
window.name = '吴佳'
const name1 = obj.fn.call(window, '第一个参数', '第二个参数', '第三个参数')
const name2 = obj.fn.apply(window, ['第一个参数', '第二个参数', '第三个参数'])
// 两个方法的打印输出:第一个参数, 第二个参数, 第三个参数
// name1 & name2 值都为吴佳
需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向window。如果指定为数字或字符串或者布尔值的this值,则会指向该值的包装对象。
请看以下例子
function fn () {
console.log(this)
}
// call方法的输出与apply一致
fn.apply(undefined) // window
fn.apply(null) // window
fn.apply('') // String {""}
fn.apply(1) // Number {1}
fn.apply(true) // Boolean {true}
call、apply、bind的实现
call
Function.prototype.call = function (context) {
// 基础类型转包装对象
if (context === undefined || context === null) {
context = window
} else if (typeof context === 'string') {
context = new String(context)
} else if (typeof context === 'number') {
context = new Number(context)
} else if (typeof context === 'boolean') {
context = new Boolean(context)
}
// 保存原函数至指定对象的fn属性上
context.fn = this
// 获取除第一个参数之后的所有参数
const args = Array.from(arguments).slice(1)
// 通过指定对象的fn属性执行原函数并出入参数
const fnValue = context.fn(...args)
delete context.fn // 从context中删除fn原函数
return fnValue
}
apply
Function.prototype.apply = function (context, arr) {
// 基础类型转包装对象
if (context === undefined || context === null) {
context = window
} else if (typeof context === 'string') {
context = new String(context)
} else if (typeof context === 'number') {
context = new Number(context)
} else if (typeof context === 'boolean') {
context = new Boolean(context)
}
// 非对象,非undefined,非null的值才会抛错
if (typeof arr !== 'object' && typeof arr !== 'undefined' && typeof arr !== 'null') throw new TypeError('CreateListFromArrayLike called on non-object')
arr = Array.isArray(arr) && arr || [] // 非数组就赋值空数组
// 保存原函数至指定对象的fn属性上
context.fn = this
// 通过指定对象的fn属性执行原函数并出入参数
const fnValue = context.fn(...arr)
delete context.fn // 从context中删除fn原函数
return fnValue
}
bind
Function.prototype.bind = function (context) {
// 保存原函数
const ofn = this
// 获取除第一个参数之后的所有参数
const args = Array.from(arguments).slice(1)
function O() {}
function fn() {
// 第一个参数的判断是为了忽略使用new实例化函数时让this指向它自己,否则就指向这个context指定对象
// 第二个参数的处理做了参数合并, 就是 bind & fn 两个函数的参数合并
ofn.apply(this instanceof O ? this : context, args.concat(Array.from(arguments)))
}
O.prototype = this.prototype
fn.prototype = new O()
return fn
}
如果new这个bind之后return的fn函数,this就会指向一个空对象,这个空对象的原型就会指向构造器的prototype。那么此时this instanceof O 就为true,所以返回的this就是当前被实例化的对象;这样就会忽略掉bind方法的this指向,实现上述new一个bind后的函数特性。
摘自:https://my.oschina.net/u/4153418/blog/4494166