JavaScript是一个词法作用域的编程语言,词法作用域和动态作用域的区别我也说过了,具体在我的《深入理解JavaScript之词法域》https://blog.csdn.net/qq_41889956/article/details/83061472中有介绍,
总的来说
动态作用域是根据调用栈关系来确定变量值的,比如在当前的函数找不到,那么就会到它调用的函数中找。
词法作用域是在词法分析阶段(代码编写时)就已经决定的了。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
今天要学习的 this机制 与 动态作用域差不多。
this是JavaScript中最为重要的机制之一,同时也是最为复杂的机制之一,在大型项目里,this来this去会让你感到一头雾水。这个 this 与英语单词中理解的 this 不太一样,并不是单纯的指这个函数对象的本身。
1.1为什么要用this
接下来请看看例子,让我们来看看this的用处
function identify() {
return this.name.toUpperCase();
}
function speack() {
var greeting="Hello,I'm"+identify.call(this);
console.log(greeting);
}
var me={
name:"Kyle"
};
var you={
name:"Reader"
};
console.log(identify.call(me)); //KYLE
console.log( identify.call( you )); //READER
speack.call(me); //Hello,I'm KYLE
speack.call(you); //Hello,I'm RAEDER
在理解这篇代码之前,让我们先来看看,.call()是什么,这是一个能够修改" this"指针的方法,作用是将函数内的"this"指针对象转移到"()"内的对象中
接下来我们看看代码,代码中,identify(...)函数将"this.name.toUpperCase()"作为返回值,此函数是修改传入对象的name值,使其输出大写。
这段代码可以在不同的上下文对象(me和you)中重复使用函数identify(...)以及speack(...)来输出不同的结果,而不用对不同的对象编写不同的结果。
如果不使用 this 的话,我们就需要显示的创建一个上下文对象,
function identify(context) {
return context.name.toUpperCase();
}
function speak(context) {
var greeting = "Hello, I'm " + identify(context);
console.log( greeting );
}
var me = {
name: "Kyle"
};
var you = {
name: "Reader"
};
console.log(identify(me)); //KYLE
console.log( identify( you )); //READER
speak( me ); // Hello, 我是 KYLE
speak( you ); // Hello, 我是 READER
使用 this 能够隐式的传递对象的引用,让你的代码变得更加的优美,简洁,易于复用。
随着你使用的模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this 能够很好的解决这个问题
1.2 误解
在我们正式了解 this 的作用机制的时候,先让我们来消除一些对 this 常有的误解。
太拘泥于 this 的字面意思会产生一个误解。有两种对 this 的误解,它们都是错误的
1.2.1 误解一:指向自身
在英语字面上解释 this 的话,通常是指向函数本身,在函数作用域中使用它,目的也是指向自己(函数内部调用自己),那么在函数内部指向自己(执行自己)是在什么情况呢?
一般是在递归的情况下,比如:阶乘函数,就需要不断的调用自己、或者在第一次调用时自己解绑的事件处理器。
在JavaScript一些开发者很容易混淆存储状态的位置(存储状态=属性的值),因为JavaScript中有多种模式可以存储状态,有一部分人认为,函数是一个对象(在JavaScript中,函数看作对象),那么就可以在调用函数时存储状态 这是可行的,但是你要记住,存储状态不止这一个位置。
接下来我们看看这个代码
function foo(num) {
console.log("foo"+num);
this.count++;
}
foo.count=0;
var i;
for(i=0;i<10;i++){
if(i>5){
foo(i); //6-7-8-9
}
}
console.log("foo被调用:"+foo.count+"次"); //0
console.log(...)输出了四条语句,证明foo(...)函数确实被调用了四次,但是foo(...)函数的计数器并没有发生计数,很明显,是foo(...)中this 对象的错误。
执行 foo.count=0 时,确实是向函数对象 foo 添加了一个属性 count。但是函数内部代码 this.count依然是0,那就证明 this.count中的 this 并不是指向 foo 这个对象,属性相同但是对象不同。
实际上如果深究的话,foo(...)中 this.conut 是创建了一个全局变量conut,它的值为NaN。
为什么呢?还记得我们之前讲过的查找方式吧,this.count++ 使用的是LHS查找,在当前作用域中查找不到count变量,于是它返回到上一层查找,这时,在上一层(词法作用域)中同样没有查找到,因为进行的是LHS查找,所以它会自动在全局作用域中创建一个变量count,它的值为NaN
-------------------------------------------这充分说明了 this 并不是指向函数本身
要解决上面的问题有多种方法
- 创建全局变量count,此方法没有用到 this 而是用到了词法作用域
- 利用foo代替this,此方法同样没有用到 this 用到的是foo(...)的函数作用域
- 利用call强制foo(...)中的 this 与foo进行绑定,在foo(i)------->>>foo.call(foo,i) call正确用法是:this存在的函数.call(需要this绑定的函数,传入this存在的函数的参数)
1.2.2 误解二:它的作用域
第二种常见的误区是将this指向它的作用域。这个问题涉及到了很多东西,在某些情况下,它是正确的,在某些情况下,它是错误的。
this在任何情况下都不会指向函数的词法作用域,在JavaScript内部,作用域和对象类似,可见的标识符都是它的属性。但是作用域对象无法通过JavaScript代码访问,它存在于JavaScript引擎内部。
我们看看以下的代码,
function foo() {
var a=2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo();
这个代码中有很多个错误,这段代码似图跨过边界,使用 this 来隐式引用函数的词法作用域。但这是无法实现的
首先,这段代码似图通过 this.bar() 来引用bar(...)函数。这个是不可能成功的,要调用bar(...)最好的方法是直接使用词法引用标识符bar(...),不要 this。
此外这段代码还试图用 this 沟通foo()、bar()的词法域,从而让bar()能够访问foo()中的变量a,这是不可能实现的,
因为你无法使用this来引用词法作用域里面的东西。
1.3 this到底是什么?
我们之前说过 this 是在运行时进行绑定的,就同动态作用域一样,它的上下文取决于函数调用的各种条件
this的绑定和函数声明的位置没有任何的关系,只取决于函数调用的方式
简单说下 this 。当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪被调用(调用栈),函数调用的方法,传入的参数等等。this就是记录其中的一个属性而已,会在函数执行时用到。