call、apply、bind是个啥
javaScript中,如果我们需要改变this的指向,那么我们可以借助call,apply,bind这是三个函数原型上的方法来实现。call和apply会立即执行该函数,call接收的第二个参数一个参数列表,而apply接收的第二个参数是一个数组,而bind会返回一个新函数。
模拟实现一个call
先写一个demo
let obj = {
name:'chw'
}
function printName(){
console.log(this.name)
}
printName.call(obj)
//chw
第一版实现
在上面的demo中我们可以看出,call是函数原型上的方法,执行call的时候做了两件事情
- 将this执行obj
- 立即执行printName方法 如何将this执行obj呐,我们知道如果将printName变成obj的方法,是不是就可以改变this,同时也可以执行该方法了呐,最后再将该函数删除,那么我们可以很轻松的写出代码
Function.prototype.myCall = function(context){
//context 就是传入需要改变this指向的对象
//这里的this可以取到执行的函数
context.fn = this
context.fn()
delete context.fn
}
//test
let obj = {
name:'chw'
}
function printName(){
console.log(this.name)
}
printName.myCall(obj)
//chw
第二版,解决参数问题
由于我们还需要传递给执行函数参数,同时函数的参数长度是不固定的,这个我们可以通过arguments对象取出,同时我们还需要除去第一个传递进来context,这个时候你可能会提出可以利用es6的...语法可以收集参数,其实也是可以,我们还可以使用eval函数,不过这个是一个危险函数,项目中尽量避免使用,他会将传入的字符串当成js代码执行。
Function.prototype.myCall = function(context){
//context 就是传入需要改变this指向的对象
//这里的this可以取到执行的函数
context.fn = this
//收集参数
let args = []
for(let i=1;i<arguments.length;i++){
args.push(arguments[i])
}
// eval('context.fn('+args+')') //args 会自动调用toSting()
//or
context.fn(...args)
delete context.fn
}
//test
let obj = {
name:'chw'
}
function printName(a,b){
console.log(this.name,a,b)
}
printName.myCall(obj,12,13)
//chw,12,13
第三版,细节优化
- context可以传递null,这个时候this指向widow
- 有返回值时候,需要将返回值返回
Function.prototype.myCall = function(context){
context = context || window
context.fn = this
let args = []
for(let i=1;i<arguments.length;i++){
args.push(arguments[i])
}
let result = eval('context.fn('+args+')')
delete context.fn
return result
}
//test
let obj = {
name:'chw'
}
function printName(a,b){
console.log(this.name,a,b)
return {a:1}
}
printName.myCall(obj,12,13)
//chw,12,13
//{a:1}
模拟实现一个apply
apply和call的区别仅仅在参数传递的过程,我们可以很轻松的写出代码
Function.prototype.myApply = function(context,arr){
context = context || widnow
context.fn = this
let result
if(!arr){
result = context.fn()
}else{
result = eval('context.fn('+arr+')')
}
delete context.fn
return result
}
//test
let obj = {
name:'chw'
}
function printName(a,b){
console.log(this.name,a,b)
}
printName.myApply(obj,[12,13])
模拟实现一个bind
写一个demo
let obj = {
name:'chw'
}
function printName(){
console.log(this.name)
}
let res = printName.bind(obj)
res()
//chw
第一版
bind的定义:创建一个新函数,bind()函数执行的时候,将this指向第一个参数对象,其余的参数作为新函数的参数,供调用者使用。 那么这个过程有两步:
- 创建一个新函数并返回
- 将this 执行第一个参数
Function.prototype.myBind = function(context){
//闭包保存执行的函数
let self = this
//返回一个函数
return function(){
//改变this指向
return self.call(context)
}
}
//test
let obj = {
name:'chw'
}
function printName(){
console.log(this.name)
}
let res = printName.myBind(obj)
res()
//chw
第二版,解决参数问题
let obj = {
name:'chw'
}
function printName(age,work){
console.log(this.name,age,work)
}
let res = printName.bind(obj,18)
res('coding')
//chw,18,coding
从上面demo可以看出参数可以分两次传递,那么我们需要将第一次bind传递的参数和返回的参数合并起来再执行
Function.prototype.myBind = function(context){
//保存执行的函数
let self = this
//取出bind到第二个到最后一个参数
let arg = [].slice.call(arguments,1)
return function(){
let args = [].slice.call(arguments)
let allArg = arg.concat(args)
return self.apply(context,allArg)
}
}
//test
let obj = {
name:'chw'
}
function print(age,work){
console.log(this.name,age,work)
}
let res = print.myBind(obj,18)
res('coding')
//chw,18,coding
第三版,实现对bind返回的函数进行new操作(改变this优先级问题)
最难的点是,可以对放回的函数进行new构造对象,原来bind提供的对象会失效,这个时候的this是指向new出来的实例对象,看个demo
let obj = {
value:'cc'
}
function print(name){
this.habit = 'play'
console.log(this.value,name)
}
let res = print.bind(obj)
let obj2 = new res('chw')
//undefined,chw //这个时候this不在指向obj,而是obj2
//obj2 === {habit:'paly'}
实现过程如下:如果返回函数是被new实例化过的,那么我们可以判断this的指向来决定绑定的this
Function.prototype.myBind = function(context){
//保存执行的函数
let self = this
//取出bind到第二个到最后一个参数
let arg = [].slice.call(arguments,1)
let bunod = function(){
let args = [].slice.call(arguments)
let allArg = arg.concat(args)
//如果this的instance指向bound,那么就被new实例化了
//否则如果是被当成普通函数执行,this就指向全局window了
let obj = this instanceof bunod ? bunod : context
return self.apply(obj,allArg)
}
//这样可以继承绑定函数上的原型了
bunod.prototype = this.prototype
return bunod
}
//test
let obj = {
value:'cc'
}
function print(name,age){
this.habit = 'play'
console.log(this.value,name,age)
}
let res = print.bind(obj,'cc')
let obj2 = new res(18)
//undefined , chw,18
至此已经完结call、apply、bind 的实现过程,有不对地方欢迎指出