bind、call、apply 的区别
手动修改 this 的三个方法
显示手动修改 this 可以使用 bind、call、apply 这三个方法,这三个方法都是写在 Object.prototype 上面的,可以直接使用。
apply
apply
接受两个参数,第一个参数是 this
的指向,第二个参数是函数接受的参数,以数组的形式传入
改变 this
指向后原函数会立即执行,且此方法只是临时改变 this
指向一次
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
fn.apply(obj,[1,2]); _// this会变成传入的obj,传入的参数必须是一个数组;_
fn(1,2) _// this指向window_
当第一个参数为 null
、undefined
的时候,默认指向 window
(在浏览器中)
fn.apply(null,[1,2]); // this指向window
fn.apply(undefined,[1,2]); // this指向window
手写apply
// 缺点: 容易变量名冲突 fn
Function.prototype.myApply = function(ctx,args){
//保留原有函数
const fn = this;
// 在ctx 上添加属性
ctx.fn = fn;
// 通过ctx 调用函数, 将obj作为 fn 函数的上下文
const ret = ctx.fn(...args);
// 删除 fn 属性
delete ctx.fn
return ret
}
function fun(a,b,c){
console.log(a,b,c)
return this.a
}
const obj1 = {a:10};
console.log(fun.myApply(obj1,[1,2,3]))
使用Symbol
//使用Symbol 防止命名冲突
Function.prototype.myApply2 = function (ctx, arg) {
// 处理参数为 null 和undefined 情况
ctx = (ctx === null || ctx === undefined) ? globalThis : Object(ctx)
//保留原有函数
const fn = this;
const key = Symbol("apply");
// 在ctx 上添加属性
// Object.defineProperty(ctx,key,{
// enumerable:false,
// value:this
// })
ctx[key] = fn;
// 通过ctx 调用函数, 将obj作为 fn 函数的上下文
const ret = ctx[key](...arg);
//删除原有属性
delete ctx[key];
return ret
}
const obj = {a:10};
function test(){
return this;
}
console.log(test.myCall2(obj))
在线地址: 手写apply
call
call
方法的第一个参数也是 this
的指向,后面传入的是一个参数列表
跟 apply
一样,改变 this
指向后原函数会立即执行,且此方法只是临时改变 this
指向一次
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
fn.call(obj,1,2); _// this会变成传入的obj,传入的参数必须是一个数组;_
fn(1,2) _// this指向window_
同样的,当第一个参数为 null
、undefined
的时候,默认指向 window
(在浏览器中)
fn.call(null,[1,2]); // this指向window
fn.call(undefined,[1,2]); // this指向window
手写call 方法:
Function.prototype.myCall = function(context) {
let ctx = context || window || global;
// 修改 this 指向
ctx.fn = this;
// 收集参数
const arr = [];
for(let i = 1; i < arguments.length; i++) {
arr.push(arguments[i]);
}
const res = eval(`ctx.fn(${arr.join(',')})`);
delete ctx.fn;
return res;
}
上面这个代码中使用的收集参数的方法是,通过 arguments 和函数拼接成字符串,然后使用 eval 来执行函数。但是上面的代码中还是存在问题,原因就是原本上面的代码是不打算使用 ES6 的语法,但是使用了 ES6 的地方,修改都是很简单的事情。
上面代码中存在的问题就是,关于将函数绑在传入的参数上的时候,取得名字是一个固定的,而如果原来的对象上存在这个名字的属性,就会对原来的对象造成破坏。
目前可以想到的解决方法就是使用 ES6 引入的 symbol 包装一下。
Function.prototype.myCall = function(context) {
let ctx = context || window || global;
const fn = Symbol('fn');
ctx[fn] = this;
// 收集参数
const arr = [];
for(let i = 1; i < arguments.length; i++) {
arr.push(arguments[i]);
}
const res = eval(`ctx[fn](${arr.join(',')})`);
delete ctx[fn];
return res;
}
使用 ES6 的语法写的话,在参数收集的地方可以换成 ...
运算符:
Function.prototype.myCall = function(context, ...arg) {
let ctx = context || window || global;
const fn = Symbol('fn');
ctx[fn] = this;
const res = ctx[fn](...arg);
delete ctx[fn];
return res;
}
在线地址:手写call
bind
bind 方法和 call 很相似,第一参数也是 this
的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)
改变 this
指向后不会立即执行,而是返回一个永久改变 this
指向的函数
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
const bindFn = fn.bind(obj); _// this 也会变成传入的obj ,bind不是立即执行需要执行一次_
bindFn(1,2) _// this指向obj_
fn(1,2) _// this指向window_
总结
从上面可以看到,apply
、call
、bind
三者的区别在于:
- 三者都可以改变函数的
this
对象指向 - 三者第一个参数都是
this
要指向的对象,如果如果没有这个参数或参数为undefined
或null
,则默认指向全局window
- 三者都可以传参,但是
apply
是数组,而call
是参数列表,且apply
和call
是一次性传入参数,而bind
可以分为多次传入 bind
是返回绑定 this 之后的函数,apply
、call
则是立即执行。
手写 bind
实现 bind
的步骤,我们可以分解成为三部分:
- 修改
this
指向 - 动态传递参数
// 方式一:只在bind中传递函数参数
fn.bind(obj,1,2)()
// 方式二:在bind中传递函数参数,也在返回函数中传递参数
fn.bind(obj,1)(2)
- 兼容
new
关键字
整体实现代码如下:
Function.prototype.myBind = function(context,...args){
console.log(this)
const fn = this;
if(!Object.prototype.toString.call(fn) === "[Object function]") return new TypeError("is not function");
const Fun = function(){
// 如果是通过 new 调用 ,this 是指向 obj 对象;
const _this = this instanceof Fun ? this : context
return fn.apply(_this,[...args,...arguments])
}
// 改变Fun 的 原型,为this.prototype
Fun.prototype = Object.create(fn.prototype)
return Fun
}
const obj = {a:3}
const obj2 = {a:3}
function test(){
console.log(JSON.stringify(this))
console.log(this === obj)
}
const test2 = test.bind(obj); // 得到的是Fun 函数
const test3 = test2.bind(obj2);
test2()
test3()
const testObj = new test2()
const test4 = test.myBind(obj); // 得到的是Fun 函数
const test5 = test4.myBind(obj2);
test4()
test5()
在线地址: 手写bind