call 和 apply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组
let a = {
value: 1
}
function getValue(name, age) {
console.log(name)
console.log(age)
console.log(this.value)
}
getValue.call(a, 'QJ', '20')
getValue.apply(a, ['QJ', '20'])
call:
1.将函数设为传入参数的属性
2.获取传入的参数,并执行函数
3.如果不传入参数或者参数为null,默认指向为 window / global
4.删除参数上创建的属性
Function.prototype._call = function (context) {
// 1. 如果第一个参数传入的是 null 或者是 undefined, 那么指向this指向 window/global
// 如果第一个参数传入的不是null或者是undefined, 那么必须是一个对象
if (!context) { //context为null或者是undefined
context = typeof window === 'undefined' ? global : window;
} else {
context = Object(context)
}
// 2. 将函数设置为对象的属性 (this指向的是当前的函数,即bar)
context.fn = this;
// 3. 获取除了this指向对象以外的参数
let args = [...arguments].slice(1);
// 4. 执行函数
let result = context.fn(...args);
// 5. 删除新创建的属性
delete context.fn;
// 6. 返回值
return result;
}
//测试代码
var foo = {
a: 1
}
function bar(name,age) {
console.log(name)
console.log(age)
console.log(this.a)
}
bar._call(foo,'qj',20) // qj 20 1
apply:
apply与call原理一致,不同的只是传参的方式。
Function.prototype._apply = function (context) {
var result
// 1. 如果第一个参数传入的是 null 或者是 undefined, 那么指向this指向 window/global
// 如果第一个参数传入的不是null或者是undefined, 那么必须是一个对象
if (!context) { //context为null或者是undefined
context = typeof window === 'undefined' ? global : window;
} else {
context = Object(context)
}
// 2. 将函数设置为对象的属性 (this指向的是当前的函数,即bar)
context.fn = this;
// 3. 执行函数
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
// 4. 删除新创建的属性
delete context.fn;
// 5. 返回值
return result;
}
//测试代码
var foo = {
a: 1
}
function bar(name,age) {
console.log(name)
console.log(age)
console.log(this.a)
}
bar._apply(foo,['qj',20]) // qj 20 1
bind
bind 和 call/apply 有一个很重要的区别,一个函数被 call/apply 的时候,会直接调用,但是 bind 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
Function.prototype._bind = function (context) {
// 调用bind的如果不是函数,抛出异常
if (typeof this !== "function") {
throw new TypeError("not a function");
}
//保存外部函数的this, 如果不保存的话,我们就无法在闭包中使用外部函数绑定的this
//self指向的就是我们被绑定的函数, eg: func.bind(obj). self指向的是 func
let self = this;
let args = [...arguments].slice(1);
// 使用原型式继承,避免修改bound.prototype 的时候,也会修改 this.prototype的值
var Fn = function () {};
Fn.prototype = this.prototype;
let bound = function () {
let res = [...args, ...arguments];
// (this instanceof self) === true时, this 就是运行 new bindFunc 时 this 的指向对象, 它指向的就是新创建的实例对象
// (this instanceof self) === false时,调用bindFunc的形式是直接调用, 此时让 this 指向我们传入的第一个参数
context = this instanceof Fn ? this : context;
return self.apply(context, res);
} //原型链
bound.prototype = new Fn();
return bound;
}
var foo = {
a: 1
}
function bar(name,age) {
console.log(name)
console.log(age)
console.log(this.a)
}
var myBind = bar._bind(foo,'qj',20)
myBind() // qj 20 1
console.log('-------')
var myBind2 = bar._bind(foo,'qj')
var myBind3 = new myBind2(20) // qj 20 undefined
一旦函数通过bind传递了有效的this对象,则该函数在运行期的this将指向这个对象,即使通过call或apply来试图改变this的指向也是徒劳的。