搞懂this的指向,并手写apply、call、bind的简单理解

190 阅读4分钟
  • 简单来说apply等方法的作用就是更改this的指向,让被绑定的对象可以调用别的对象的方法
let a = {
    name : 'a',
     sayName:function(){ 
        console.log(this.name)
    }
}

let b = {
    name : 'b',
}

a.sayName.apply(b)   		//b
							//我们可以看到b对象调用了本该属于a对象的方法

this指向

  • 这里我们讨论的是浏览器中的this指向,Node中的this 与浏览器中的this不同,Node中的this指向module.exports
  • 简单的我们可以将this的指向分为几种情况

没有被调用时

  • this会默认指向全局对象
(function a(){
    console.log(this)
})()    //window

被调用时

  • this会指向调用它所在函数的对象
let a = {
    name : 'a',
     sayName:function(){ 
        console.log(this)
    }
}

a.sayName();    			//{ name: 'a', sayName: [Function: sayName] }指向对象a

箭头函数

在其中会有两种情况

  • 父元素被调用,this会指向父元素的this指向
let a = {
    name : 'a',
     sayName:function(){
        (()=>{ 
            console.log(this)
        })()
    }
}
a.sayName();    			//{ name: 'a', sayName: [Function: sayName] }指向对象a
  • 元素没有被调用,this指向全局对象
let a = {
    name : 'a',
    sayName:()=>{ 
        console.log(this)
    }
}
a.sayName();    //window

注:

严格模式下,如果 this 没有被执行环境定义,那它将保持为 undefined
setTimeout除外,无论是否为严格模式,this都会指向全局对象

 "use strict";
 (function a(){
    console.log(this)
})()    //undefined

使用apply、call、bind改变this

  • this会指向apply、call、bind绑定的对象
let a = {
    name : 'a',
     sayName:function(a){ 
        console.log(this)
    }
}

let b = {
    name : 'b',
}

a.sayName.apply(b)  				//{ name: 'b' }指向对象b

使用new函数

  • this会指向构造函数
let A = function(){
  console.log(this)
}
let a = new A() //A

简单实现apply

  • 首先,我们根据上面的写法可知,apply是一个有着两个参数的函数并且可以被所有函数调用
Function.prototype.newApply = function(obj,arg){}
  • 第二步,假如没有参数,函数将绑定全局对象
obj = obj || window
  • 第三步,当拥有第二个参数且第二个参数不为数组时,整体会报错
  if(arg !== undefined){
    if(!Array.isArray(arg) ){
        throw new Error('it must be an Array');
    }
    for(let i = 0;i < arg.length;i++){		//将数组中转化为字符串
        newArg.push (' arg[' + i + ']');
    }
  }
  • 第四步,在绑定的对象上创建一个属性用以接收方法
let attr = Symbol();    					//为了避免出现属性重复

obj.attr = this;

let result = eval('obj.attr('+newArg+')');	//运行函数并接受值,使用eval函数运行
  • 第五步,删除创建的属性
delete obj.attr;
  • 第六步,返回运行值
return result;

完整版

Function.prototype.newApply = function(obj,arg){
  let newArg = [];
  
  if(arg !== undefined){
    if(!Array.isArray(arg) ){
        throw new Error('it must be an Array');
    }
    for(let i = 0;i < arg.length;i++){
        newArg.push (' arg[' + i + ']');
    }
  }

  obj = obj || window;
  
  obj.attr = Symbol();

  obj.attr = this;
  
  let result = eval('obj.attr('+newArg+')')
  
  delete obj.attr;

  return result;
}

简单实现call

  • 与apply唯一的不同就是,apply接收数组,而call则是一个一个接收参数
Function.prototype.newCall = function(obj,arg){
    let newArg = [];
    
    for(let i = 1;i < arguments.length;i++){
        newArg.push (' arguments[' + i + ']');	//相比于apply,可以直接使用arguments获取除第一位之外的参数
    }
  
    obj = obj || window;
    
    obj.attr = Symbol();
  
    obj.attr = this;
    
    let result = eval('obj.attr('+newArg+')')
    
    delete obj.attr;
  
    return result;
}

简单实现bind

  • 与apply、call区别最大的是,bind绑定的函数并不会直接被调用,所以bind最常被用于回调函数之中,既然他需要被调用,那么说明他返回一个函数
Function.prototype.newBind = function(obj,arg){			//这里的参数为bind方法的参数
  let _this = this;

  if(!(this instanceof Function)){						//可以进行简单的判断,因为bind只能被函数调用
      throw new Error('it must be a Function to use it');
  }

  let newArg = Array.prototype.slice.call(arguments,1);	//将伪数组转化为字符串

  return function(newArgu){								//这里的参数为bind绑定后的函数参数
    newArgu = Array.prototype.slice.call(arguments);
    return _this.apply(obj,newArg.concat(newArgu));		//如果函数有值,则返回值
  }
}
  • 除此之外,bind还可以绑定构造函数,但众所周知,new操作符会改变this的指向,虽然MDN不推荐使用,但姑且考虑一下:)
  • 我们可以将返回的函数提取出来,如果作为构造函数,它就相当于绑定后的构造函数,使用new方法后this指向实例,‘this instanceof funBind’返回true
  • 如果只是普通函数,this则指向window,‘this instanceof funBind’返回false
let funBind = function(){
	return _this.apply(this instanceof funBind ? this : obj,newArg.concat(newArgu));	//判断this指向
}

funBind.prototype = this.prototype;															//获取绑定函数原型上所有的数据

完整版

Function.prototype.newBind = function(obj,arg){
  let _this = this;
  
  if(!(this instanceof Function)){
    throw new Error('it must be a Function to use it');
  }
  
  let newArg = Array.prototype.slice.call(arguments,1);
  
  let funBind = function(newArgu){
    newArgu = Array.prototype.slice.call(arguments);
    return _this.apply(this instanceof funBind ? this : obj,newArg.concat(newArgu));
  }

  funBind.prototype = this.prototype;

  return funBind;
}

当然以上的写法只是一定程度上还原函数本身的作用,底层一定比这复杂得多,想要更加具体的了解请自行搜索

借鉴:网上查找 + 《JavaScript高级程序设计》 + MDN文档 + 自己的理解

如有错误,希望提出

希望大家都能早日拿到心仪的offer,加油,共勉