JS之call、apply、bind的区别与实现

本文详细探讨了JavaScript中的call、apply、bind方法,它们的主要作用是改变函数内部的this指向。call和apply的主要区别在于参数传递方式,call接收参数列表,apply接收数组或类数组。bind则返回一个新函数,保留原函数的this并允许延迟传参。需要注意,当bind返回的函数用作构造函数时,绑定的this会失效。文中还给出了call、apply、bind的实现原理及示例,帮助开发者更深入地掌握这些概念。

call、apply、bind的区别

  • call、apply、bind相同点:都是改变this的指向,传入的第一个参数都是绑定this的指向,在非严格模式中,如果第一个参数是nul或者undefined,会把全局对象(浏览器是window)作为this的值,要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined
  • call和apply唯一的区别是:call传入的是参数列表,apply传入的是数组,也可以是类数组
  • bind和call、apply的区别: bind返回的是一个改变了this指向的函数,便于稍后调用,不像call和apply会立即调用;bind和call很像,传入的也是参数列表,但是可以多次传入,不需要像call,一次传入
  • 值得注意:当 bind 返回的函数 使用new作为构造函数时,绑定的 this 值会失效,this指向实例对象,但传入的参数依然生效 (new调用的优先级 > bind调用)

call实现

对象context想调用一个它没有的方法f 怎么办呢?f.call(context) 通过call来借用方法f ,怎么做到的呢?

1.对象context添加f方法
2.对象context执行f方法

Function.prototype.myCall = function(context, ...arg) {// 如果第一个参数传入的是undefined和null,context为window对象context = context || window;// 为context对象添加函数barcontext.fn = this; // this:bar,this指向调用myCall的bar // context对象执行函数bar,并返回结果returncontext.fn(...arg); 
}

// 测试一下
var value = 2;

var obj = {value: 1
}
function bar(name, age) {console.log(this.value);return {value: this.value,name: name,age: age}
}

bar.myCall(null); // 2

console.log(bar.myCall(obj, 'kevin', 18)); //1
// Object {
//value: 1,
//name: 'kevin',
//age: 18
// } 

apply实现

apply和call唯一的区别是:call传入的是参数列表,apply传入的是数组,也可以是类数组

Function.prototype.myApply = function(context, arg) {// 如果第一个参数传入的是undefined和null,context为window对象context = context || window;// context对象添加函数barcontext.fn = this; // this:bar,this指向调用myCall的函数bar // context对象执行函数bar,并返回结果let result = null;if (!arg) { // 没有传入数组result = context.fn();}else{// 传入了参数数组result = context.fn(...arg);} return result;
}

// 测试一下
var value = 2;

var obj = {value: 1
}
function bar(name, age) {console.log(this.value);return {value: this.value,name: name,age: age}
}

bar.myApply(null); // 2
console.log(bar.myApply(obj, ['kevin', 18])); // 1 

bind实现

  • bind和call、apply的区别: bind返回的是一个改变了this指向的函数,便于稍后调用,不像call和apply会立即调用;bind和call很像,传入的也是参数列表,但是可以多次传入,不需要像call,一次传入
  • 值得注意:当 bind 返回的函数 使用new作为构造函数时,绑定的 this 值会失效,this指向实例对象,但传入的参数依然生效 (new调用的优先级 > bind调用)

bind实现最为复杂,因为经过bind绑定过的函数,既可以被当作普通函数调用,又可以被当作构造函数调用

  • bind 返回的函数 作为普通函数调用
// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是参数传进来的name') // this:obj
bindFun('我是参数传进来的age') 
  • bind 返回的函数 作为构造函数调用,绑定的 this 值obj会失效,this指向实例对象a
// bind 返回的函数 作为构造函数调用
let bindFun = Person.myBind(obj, '我是参数传进来的name') // this:obj
let a = new bindFun('我是参数传进来的age') // this:a 

bind 返回的函数 作为普通函数调用 代码实现

// bind 返回的函数 作为普通函数调用
Function.prototype.myBind = function (context, ...args){// 如果第一个参数传入的是undefined和null,context为window对象context = context || window;// context对象(obj)添加函数normalFuncontext.fn = this;// this:normalFun, context.fn === normalFun,下面出现context.fn都可以直接看成normalFun// bind返回的函数 return function (...innerArgs) {// bind 返回的函数 作为普通函数被执行context.fn(...[...args,...innerArgs]);//相当于normalFun(...[...args,...innerArgs])}
}

// 测试
let obj = {objName: '我是obj传进来的name',objAge: '我是obj传进来的age'
}

// 普通函数
function normalFun(name, age) {console.log(name);//'我是第一次参数传进来的name被args接收'console.log(age); //'我是第二次参数传进来的age被innerArgs接收'console.log(this === obj);// true,this指向objconsole.log(this.objName);//'我是obj传进来的name'console.log(this.objAge); //'我是obj传进来的age'
}

// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是第一次参数传进来的name被args接收'); // this指向obj 
bindFun('我是第二次参数传进来的age被innerArgs接收'); 

bind 返回的函数 作为构造函数调用

// bind 返回的函数 再经过new调用 
Function.prototype.myBind = function (context, ...args){// 如果第一个参数传入的是undefined和null,context为window对象context = context || window;// context对象添加函数Personcontext.fn = this; // this:Person,context.fn:Person,_this:Personlet _this = this;// bind返回的函数 const result = function (...innerArgs) { if (this instanceof _this ) { // this:a (new出来的实例对象) ,_this:Person// 为实例对象a添加Person方法this.fn = _this;// 实例对象a执行Person方法this.fn(...[...args,...innerArgs]);}}result.prototype = Object.create(this.prototype);// 为什加这一句?看原型图下面会解释return result;
}

// 测试
function Person(name, age) {console.log(name); //'我是第一次参数传进来的name被args接收'console.log(age); //'我是第二次参数传进来的age被innerArgs接收'console.log(this); //构造函数this指向实例对象
}
// 构造函数原型的方法
Person.prototype.say = function() {console.log(123);
}

let obj = {objName: '我是obj传进来的name',objAge: '我是obj传进来的age'
}

// bind 返回的函数 作为构造函数调用
let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
let a = new bindFun('我是第二次参数传进来的age被innerArgs接收') // this:a
a.say() //123 

画以下两条语句的原型图方便理解

let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
let a = new bindFun('我是第二次参数传进来的age被innerArgs接收') // this:a 

当执行下面语句时,原型图如下:

let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj 

当执行下面语句时,bindFun就是result看代码,原型图如下:

let a = new bindFun('我是第二次参数传进来的age被innerArgs接收') // this:a 

在这里实例对象a还需要继承构造函数Person的原型,所以加上了

result.prototype = Object.create(this.prototype); 

原型图最终如下:

bind代码最终实现

Function.prototype.myBind = function (context, ...args){// 如果第一个参数传入的是undefined和null,context为window对象context = context || window;// context对象添加函数Personcontext.fn = this; // this:Person,context.fn:Person,_this:Personlet _this = this;// bind返回的函数 const result = function (...innerArgs) { if (this instanceof _this ) { // this:a (new出来的实例对象) ,_this:Person// 为实例对象a添加Person方法this.fn = _this;// 实例对象a执行Person方法this.fn(...[...args,...innerArgs]);}else{// 普通函数被调用context.fn(...[...args,...innerArgs]);}}result.prototype = Object.create(this.prototype);// 为什加这一句?看原型图下面会解释return result;
}

// 测试
// function Person(name, age) {
// console.log(name); //'我是第一次参数传进来的name被args接收'
// console.log(age); //'我是第二次参数传进来的age被innerArgs接收'
// console.log(this); //构造函数this指向实例对象
// }
// // 构造函数原型的方法
// Person.prototype.say = function() {
// console.log(123);
// }

// let obj = {
// objName: '我是obj传进来的name',
// objAge: '我是obj传进来的age'
// }

// // bind 返回的函数 作为构造函数调用
// let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
// let a = new bindFun('我是第二次参数传进来的age被innerArgs接收') // this:a
// a.say() //123

// 测试
let obj = {objName: '我是obj传进来的name',objAge: '我是obj传进来的age'
}

// 普通函数
function normalFun(name, age) {console.log(name);//'我是第一次参数传进来的name'console.log(age); //'我是第二次参数传进来的age'console.log(this === obj);// trueconsole.log(this.objName);//'我是obj传进来的name'console.log(this.objAge); //'我是obj传进来的age'
}

// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是第一次参数传进来的name被args接收'); // this指向obj 
bindFun('我是第二次参数传进来的age被innerArgs接收'); 

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值