前言
前段时间自己忙着找工作,好久没分享记录自己的笔记了。面试过程和技术面试官交流,使得自己对js基础理解更加深刻透彻。发现每一次技术的交流都是一次学习打磨的过程,是个很赞的过程~
为什么要学习this
学习this可以减少多余变量的定义,并且可以通过this定义变量,那么变量的声明周期就会和该对象本身的生命周期一致。
this到底是什么机制
学习this的第一步是要清楚this既不指向函数自身也不指向函数的词法作用域,但是它总是指向一个对象,具体指向哪个对象要看被谁调用了。
当函数被调用时,相当于活动记录(执行上下文),这个记录包含函数在哪里被调用了,函数的调用方式,传入的参数信息之类的。那么this就是这个活动记录的一个属性,会在函数执行的过程中用到。
确定的说:this是在执行的时候进行绑定的,不是在编写时绑定的,只取决于函数的调用方式。
this的绑定规则
刚刚上文所讲述的函数的this指向具体要看被哪个对象调用了,接下来this的绑定规则都是在论证这个结论。
默认绑定
写了一个函数,这个函数被独立调用了是window这个对象调用的,那么this的绑定过程就是默认绑定。通常在非严格模式下,this的绑定是window;严格模式下,this的绑定是undefined。
讨论非严格情况下:
let 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的指向。
let obj={a:2};
let 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()(); //可改写成let 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得问题,好好分析到底是被谁调用了。稳住!!
觉得我的文章有帮到你的就给我点个赞哈~~