apply、call、bind 异同
- 【同】都可改变 this 对象的指向;
- 【同】第一个参数都是 this 要指向的对象,若没有这个参数或参数为 undefined 或 null,则默认指向全局 window;
- 【同】都可以传参,但是 apply 是数组,而 call 是参数列表,且 apply 和 call 是一次性传入参数,而 bind 可以分为多次传入;
- 【异】bind 是返回 绑定 this 之后的函数,便于稍后调用;apply 、call 则是 立即执行 ;
- 【异】bind()会返回一个新的函数,如果这个返回的新的函数作为 构造函数 创建一个新的对象,那么此时 this 不再指向传入给 bind 的第一个参数,而是指向用 new 创建的实例。(重点 - 易忽略)
【异】
bind
可多次调用,this
始终指向第一个bind
绑定的第一个参数。(原理看bind
实现)call 函数实现
法一讲解:
判断调用对象是否为函数:
this
- 判断传入上下文是否存在,若不存在,设为
window
- 截取参数:
const args = [...arguments].slice(1)
- 将函数设为上下文对象的一个属性:
context.fn = this
- 使用上下文来调用这个方法,并返回结果:
return context.fn(...args)
- 删除上下文对象上的方法:
delete context.fn
- 返回结果 ```javascript // 法一: Function.prototype.myCall = function(context) { // console.log(this); // this 指向调用 myCall 的函数,如:test 函数 // 判断调用对象 if (typeof this !== “function”) { console.error(“type error”); } // 获取参数 let args = […arguments].slice(1), result = null; // 判断 context 是否传入,如果未传入则设置为 window context = context || window; // 将调用函数设为对象的方法 context.fn = this; // 调用函数 result = context.fn(…args); // 将属性删除 delete context.fn; return result; };
法二(推荐): Function.prototype.myCall = function (context, …args) { if (!context || context === null) { context = window; } // 创造唯一的key值 作为我们构造的context内部方法名 let fn = Symbol(); context[fn] = this; //this指向调用call的函数 // 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了 return contextfn; };
// 调用 var obj = { name: ‘chen’, getName: function test() { console.log(this.name) } } obj.getName.myCall({name: ‘lu’}, 1, 2, 3)
<a name="HeR15"></a>
## apply 函数实现
法一讲解:
1. 判断调用对象是否为函数:`this`
2. 判断传入上下文是否存在,若不存在,设为 `window`
3. 将函数设为上下文对象的一个属性:`context.fn = this`
4. 判断参数值是否有传入
5. 使用上下文来调用这个方法,并返回结果:`return context.fn(...args)`
6. 删除上下文对象上的方法:`delete context.fn`
7. 返回结果
```javascript
// 法一:
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let result = null;
// 判断 context 是否存在,若不存在设为 window
context = context || window;
// 将方法设为对象里面的方法
context.fn = this;
// 调用方法,判断是否传入参数
if(arguments[1]) {
console.log('有参数', arguments[1])
result = context.fn(...arguments[1]);
} else {
console.log('无参数')
result = context.fn();
}
// 删除 context 上的 fn 属性方法
delete context.fn;
return result;
};
// 法二(推荐):原理和 call 函数方法实现中法二一样
Function.prototype.myApply = function (context, ...args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this; //this指向调用apply 的函数
// 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了
return context[fn](...args);
};
// 调用
var obj = {
name: 'chen',
getName: function test() {
console.log(this.name)
}
}
obj.getName.myApply({name: 'lu'}, [1, 2])
bind 函数实现
bind
函数实现可能比较复杂一点,因为涉及到参数合并(类似:柯里化);
法一讲解:
- 判断调用对象是否为函数:
this
- 保存当前函数的引用,获取传入函数的其他参数
- 创建一个函数返回
- 函数内用
apply
来绑定函数调用,需判断函数作为构造函数的情况;若为构造函数使用需传入当前函数的this
给apply
调用,若不是传入指定上下文对象。 ```javascript 法一: Function.prototype.myBind = function(context) { // 判断调用对象是否为函数 if (typeof this !== ‘function’) {
} // 获取参数,注意此时变量声明用的是 var var args = […arguments].slice(1); var fn = this; // 创建函数并返回 return function Fn() { // 根据调用方式不同,传入不同的值 return fn.apply(throw new TypeError('Error')
); } }this instanceof Fn ? this : context,
args.concat(...arguments);
法二(推荐): Function.prototype.myBind = function(context, …args) { if(!context || context === null){ context = window; } // 创造唯一的key值 作为构造的context内部方法名 const fn = Symbol(); context[fn] = this; let this = this; // 重点,也是复杂点 const result = function (…innerAgs) { /* 第一中情况:用 bind 绑定后的函数作为构造函数,通过 new 操作符使用,则不绑定传入的 this, 而是将 this 指向实例对象。此时由于 new 操作符作用,this 指向 result 实例对象,而 result 又继承自传入的 this ,依原型链知识可知如下结论: this.proto === result.prototype // this instanceof result // true this.proto.proto === result.prototype.__proto === this.prototype // this instanceof _this // true */ if (this instanceof _this === true) { // 此时this指向指向result的实例 这时候不需要改变this指向 this[fn] = _this; thisfn; // ES6 方法来合并数组 } else { // 若只是普通函数调用,直接改变 this 指向为传入的 context contextfn; } }
// 若绑定的是构造函数,需继承构造函数的属性和方法 // 实现继承的方式是使用:Object.create() result.prototype = Object.create(this.prototype);
return result; } ``` 参考理解(TODO):