阅读 675

this关键字你真的彻底搞懂了吗

前言

前段时间忙着找工作,好久没分享记录自己的笔记了。面试过程和技术面试官交流,使得自己对js基础理解更加深刻透彻。发现每一次技术的交流都是一次学习打磨的过程,是个很赞的过程~

为什么要学习this

学习this可以减少多余变量的定义,并且可以通过this定义变量,那么变量的声明周期就会和该对象本身的生命周期一致。

this到底是什么机制

学习this的第一步是要清楚this既不指向函数自身也不指向函数的词法作用域,但是它总是指向一个对象,具体指向哪个对象要看被谁调用了。

当函数被调用时,相当于活动记录(执行上下文),这个记录包含函数在哪里被调用了,函数的调用方式,传入的参数信息之类的。那么this就是这个活动记录的一个属性,会在函数执行的过程中用到。

确定的说:this是在执行的时候进行绑定的,不是在编写时绑定的,只取决于函数的调用方式。

this的绑定规则

刚刚上文所讲述的函数的this指向具体要看被哪个对象调用了,接下来this的绑定规则都是在论证这个结论。

默认绑定

写了一个函数,这个函数被独立调用了是window这个对象调用的,那么this的绑定过程就是默认绑定。通常在非严格模式下,this的绑定是window;严格模式下,this的绑定是undefined。

讨论非严格情况下:

var a=1;
function foo(){
	console.log(this,this.a);	//window,1
}
foo()		//可以理解成window.foo()
console.log(this)	//window
复制代码

可见:在非严格模式下,this就是window。那么函数内部的this指向window可这样理解成foo是this(window)的属性,window调用了foo函数,所有函数内部this指向window。所以foo是被window调用了

隐式绑定

隐式绑定在编码的过程当中是最容易混淆的,先康康如下代码输出什么结果:

function foo(){
    console.log(this.a);
}
var a=20
var objFoo={
    a:2,
    foo:foo,
}
objFoo.foo();		//结果1
var bar=objFoo.foo;
bar();		//结果2
function doFoo(fn){
    fn();		//结果3
}
doFoo(objFoo.foo);
复制代码
  • 结果1是输出=》2

因为objFoo这个对象的foo属性引用了foo函数,那么当调用objFoo.foo时this是指向objFoo,因为是objFoo对象调用了foo函数。

  • 结果2输出=》20

var bar=objFoo.foo;相当于浅拷贝一个对象,还是引用同一个地址。

因为objFoo.foo引用foo,所以bar也是引用foo这个函数,并且bar是在全局作用域定义的,所以是window对象调用了bar,那么this指向默认值window,即被window调用。

  • 结果3输出=》20

将objFoo.foo传给doFoo函数,其实就是一个LHS查询赋值的过程即fn=objFoo.foo。和结果二的原理又一致了,所以this指向默认值window。

总结:函数也是对象,用=赋值是一个浅拷贝的过程,还是引用相同的地址,那么this的指向判断关键还是看该函数被谁调用了。无论如何引用函数,最关键还是在调用的那时候是谁调用了。

显示绑定

通过apply,call,bind函数来显示改变this的指向。

var obj={a:2};
var a=20;
function c(){
  console.log(this,this.a) 
}
c()	//20
c.call(obj);	//2
复制代码

先谈如何使用再讲实现原理。

1.函数c通过调用call函数,并且传入参数obj改变了函数c的this指向。

为何这么神奇,关键还是call这个函数。call函数的内部实现关键就是先保存了c函数,并将c函数赋值给传入函数t的一个属性,最后执行变量的属性。

看看下面的伪代码可模仿call函数的实现原理:

function t(){
   console.log(this,1) 
}
function c(a){
  this.a=a;
  console.log(this,2) 
}	
t.c=c;  //将c函数赋值给t函数的一个属性c
t.c(3) 
console.log(t.a,'a')	
复制代码

最终结果: 最终还是回到谁调用了函数c。是函数t调用了,所以c函数的this指向t函数。所以无论是call,apply还是bind,显示的绑定this也是通过谁调用了函数,那么this就指向谁。

万变不离其宗,有了上面栗子的经验,那再康康下面的代码:

var a=2;
var obj={
    a:20,
    cc:function(){
        console.log(this.a) //this=>obj,20
    },
    bb:function(){
        console.log(this)	//obj
        return function(){
        	console.log(this.a,'a'this) //2 "a" window
        }
    },
     dd:function(){
        console.log(this)	//obj
        return function(){
        	console.log(this.a,'a'this) //20 "a" obj
        }.bind(obj)
    }
}
    obj.add.call(obj);
    obj.cc();
    obj.bb()();		//可改写成var bb=obj.bb(); bb();
复制代码

new 绑定

通过new 调用了一个函数,会做如下的操作:

1.创建了一个空对象,并且this指向了这个空对象

2.将对象进行初始化,将属性,方法绑定到这个创建的对象上。

3.如果构造函数没有返回其他对象,那么将创建的这个对象作为返回值。

4.将对象的原型连接到构造函数的prototype属性上。

实现new关键字的代码如下:

function myNew(...args){
  //创建一个空的对象
  let target={};
  // 第一个参数为要new的构造函数 其他的为该构造函数的参数
  let [fn,...params]=args;
  //原型连接
  target.__proto__=fn.prototype;
  let res=fn.apply(target,params);
  if(res && (typeof res=='object' || typeof res=='function')){
      return res;
  }
  return target;
}
复制代码

可见:内部也是通过apply显示绑定this(ps:显示绑定的原理可见上文的伪代码),当fn返回值不为对象时,fn的this指向了target。

所以:可证this的指向问题关键还是谁调用了函数fn。

function foo(a){
	this.a=a;
}
var bar=new foo(2);
console.log(bar.a);
复制代码

foo函数通过new调用,返回了bar实例对象,那么foo函数的this就是指向了bar实例对象(因为new内部通过apply显示绑定将实例传入foo函数),所以bar对象身上能访问到a属性。

但是如果foo return了一个对象,那么foo的this又指向什么呢?

function foo(a){
    this.c=a;
    console.log(this,this.c)
    return {a:a,b:a}
  }
  var bar=new foo(2);
  console.log(bar.a,bar.b,bar.c);

复制代码

结果截图: 如果foo return了一个对象,那么函数的this指向它自己。

总结

关于普通函数的this指向问题,关键还是分析谁调用了这个普通函数,那么这个this就指向哪个调用的对象。以上的栗子都可作为论证~~下次再遇到this得问题,好好分析到底是被谁调用了。稳住!!

觉得我的文章有帮到你的就给我点个赞哈~~