一个空间可以做多个网站吗,牡丹江建站,字体模板素材免费下载网站,今天重要新闻引用 《浏览器工作原理与实践》 在上篇文章中#xff0c;我们讲了词法作用域、作用域链以及闭包#xff0c;并在最后思考题中留了下面这样一段代码
var bar {myName:time.geekbang.com,printName: function () {console.log(myName)}
}
function foo() {le…引用 《浏览器工作原理与实践》 在上篇文章中我们讲了词法作用域、作用域链以及闭包并在最后思考题中留了下面这样一段代码
var bar {myName:time.geekbang.com,printName: function () {console.log(myName)}
}
function foo() {let myName 极客时间 return bar.printName
}
let myName 极客邦
let _printName foo()
_printName()
bar.printName()相信你已经知道了在 printName 函数里面使用的变量 myName 是属于全局作用域下面的所以最终打印出来的值都是“极客邦”。这是因为 JavaScript 语言的作用域链是由词法作用域决定的而词法作用域是由代码结构来确定的。
不过按照常理来说调用bar.printName方法时该方法内部的变量 myName 应该使用 bar 对象中的因为它们是一个整体大多数面向对象语言都是这样设计的比如我用 C 改写了上面那段代码如下所示
#include iostream
using namespace std;
class Bar{public:char* myName;Bar(){myName time.geekbang.com;}void printName(){cout myName endl;}
} bar;char* myName 极客邦 ;
int main() {bar.printName();return 0;
}在这段 C 代码中我同样调用了 bar 对象中的 printName 方法最后打印出来的值就是 bar 对象的内部变量 myName 值——“time.geekbang.com”而并不是最外面定义变量 myName 的值——“极客邦”所以在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点基于这个需求JavaScript 又搞出来另外一套this 机制。
所以在 JavaScript 中可以使用 this 实现在 printName 函数中访问到 bar 对象的 myName 属性了。具体该怎么操作呢你可以调整 printName 的代码如下所示
printName: function () {console.log(this.myName)
}接下来咱们就展开来介绍 this不过在讲解之前希望你能区分清楚作用域链和this是两套不同的系统它们之间基本没太多联系。在前期明确这点可以避免你在学习 this 的过程中和作用域产生一些不必要的关联。
一、JavaScript 中的 this 是什么
关于 this我们还是得先从执行上下文说起。在前面几篇文章中我们提到执行上下文中包含了变量环境、词法环境、外部环境但其实还有一个 this 没有提及具体你可以参考下图 从图中可以看出this 是和执行上下文绑定的也就是说每个执行上下文中都有一个 this。前面《调用栈为什么 JavaScript 代码会出现栈溢出》中我们提到过执行上下文主要分为三种——全局执行上下文、函数执行上下文和 eval 执行上下文所以对应的 this 也只有这三种——全局执行上下文中的 this、函数中的 this 和 eval 中的 this。
那么接下来我们就重点讲解下全局执行上下文中的 this和函数执行上下文中的 this。
二、全局执行上下文中的 this
首先我们来看看全局执行上下文中的 this 是什么。
你可以在控制台中输入console.log(this)来打印出来全局执行上下文中的 this最终输出的是 window 对象。所以你可以得出这样一个结论全局执行上下文中的 this 是指向 window 对象的。这也是 this 和作用域链的唯一交点作用域链的最底端包含了 window 对象全局执行上下文中的 this 也是指向 window 对象
三、函数执行上下文中的 this
现在你已经知道全局对象中的 this 是指向 window 对象了那么接下来我们就来重点分析函数执行上下文中的 this。还是先看下面这段代码
function foo(){console.log(this)
}
foo()我们在 foo 函数内部打印出来 this 值执行这段代码打印出来的也是 window 对象这说明在默认情况下调用一个函数其执行上下文中的 this 也是指向 window 对象的。估计你会好奇那能不能设置执行上下文中的 this 来指向其他对象呢答案是肯定的。通常情况下有下面三种方式来设置函数执行上下文中的 this 值 1. 通过函数的 call 方法设置
你可以通过函数的call方法来设置函数执行上下文的 this 指向比如下面这段代码我们就并没有直接调用 foo 函数而是调用了 foo 的 call 方法并将 bar 对象作为 call 方法的参数
let bar {myName : 极客邦 ,test1 : 1
}
function foo(){this.myName 极客时间
}
foo.call(bar)
console.log(bar)
console.log(myName)执行这段代码然后观察输出结果你就能发现 foo 函数内部的 this 已经指向了 bar 对象因为通过打印 bar 对象可以看出 bar 的 myName 属性已经由“极客邦”变为“极客时间”了同时在全局执行上下文中打印 myNameJavaScript 引擎提示该变量未定义。
其实除了 call 方法你还可以使用bind和apply方法来设置函数执行上下文中的 this它们在使用上还是有一些区别的如果感兴趣可以阅览之前的博文《call、bind、apply应用及实现》。
2. 通过对象调用方法设置
要改变函数执行上下文中的 this 指向除了通过函数的 call 方法来实现外还可以通过对象调用的方式比如下面这段代码
var myObj {name : 极客时间 , showThis: function(){console.log(this)}
}
myObj.showThis()在这段代码中我们定义了一个 myObj 对象该对象是由一个 name 属性和一个 showThis 方法组成的然后再通过 myObj 对象来调用 showThis 方法。执行这段代码你可以看到最终输出的 this 值是指向 myObj 的。
所以你可以得出这样的结论使用对象来调用其内部的一个方法该方法的 this 是指向对象本身的。
其实你也可以认为 JavaScript 引擎在执行myObject.showThis()时将其转化为了
myObj.showThis.call(myObj)接下来我们稍微改变下调用方式把 showThis 赋给一个全局对象然后再调用该对象代码如下所示
var myObj {name : 极客时间 ,showThis: function(){this.name 极客邦 console.log(this)}
}
var foo myObj.showThis执行这段代码你会发现 this 又指向了全局 window 对象。
所以通过以上两个例子的对比你可以得出下面这样两个结论
在全局环境中调用一个函数函数内部的 this 指向的是全局变量 window。
通过一个对象来调用其内部的一个方法该方法的执行上下文中的 this 指向对象本身。
3. 通过构造函数中设置
你可以像这样设置构造函数中的 this如下面的示例代码
function CreateObj(){this.name 极客时间
}
var myObj new CreateObj()在这段代码中我们使用 new 创建了对象 myObj那你知道此时的构造函数 CreateObj 中的 this 到底指向了谁吗
其实当执行 new CreateObj() 的时候JavaScript 引擎做了如下四件事
首先创建了一个空对象 tempObj接着调用 CreateObj.call 方法并将 tempObj 作为 call 方法的参数这样当 CreateObj 的执行上下文创建时它的 this 就指向了 tempObj 对象然后执行 CreateObj 函数此时的 CreateObj 函数执行上下文中的 this 指向了 tempObj 对象最后返回 tempObj 对象。
为了直观理解我们可以用代码来演示下
var tempObj {}
CreateObj.call(tempObj)
return tempObj这样我们就通过 new 关键字构建好了一个新对象并且构造函数中的 this 其实就是新对象本身。
四、this 的设计缺陷以及应对方案
就我个人而言this 并不是一个很好的设计因为它的很多使用方法都冲击人的直觉在使用过程中存在着非常多的坑。下面咱们就来一起看看那些 this 设计缺陷。
1. 嵌套函数中的 this 不会从外层函数中继承
结合下面这样一段代码来分析下
var myObj {name : 极客时间 , showThis: function(){console.log(this)function bar(){console.log(this)}bar()}
}
myObj.showThis()我们在这段代码的 showThis 方法里面添加了一个 bar 方法然后接着在 showThis 函数中调用了 bar 函数那么现在的问题是bar 函数中的 this 是什么
如果你是刚接触 JavaScript那么你可能会很自然地觉得bar 中的 this 应该和其外层 showThis 函数中的 this 是一致的都是指向 myObj 对象的这很符合人的直觉。但实际情况却并非如此执行这段代码后你会发现函数 bar 中的 this 指向的是全局 window 对象而函数 showThis 中的 this 指向的是 myObj 对象。这就是 JavaScript 中非常容易让人迷惑的地方之一也是很多问题的源头。
你可以通过一个小技巧来解决这个问题比如在 showThis 函数中声明一个变量 self 用来保存 this然后在 bar 函数中使用 self代码如下所示
var myObj {name : 极客时间 , showThis: function(){console.log(this)var self thisfunction bar(){self.name 极客邦 }bar()}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)执行这段代码你可以看到它输出了我们想要的结果最终 myObj 中的 name 属性值变成了“极客邦”。其实这个方法的的本质是把 this 体系转换为了作用域的体系。
其实你也可以使用 ES6 中的箭头函数来解决这个问题结合下面代码
var myObj {name : 极客时间 , showThis: function(){console.log(this)var bar (){this.name 极客邦 console.log(this)}bar()}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)执行这段代码你会发现它也输出了我们想要的结果也就是箭头函数 bar 里面的 this 是指向 myObj 对象的。这是因为 ES6 中的箭头函数并不会创建其自身的执行上下文所以箭头函数中的 this 取决于它的外部函数。
通过上面的讲解你现在应该知道了 this 没有作用域的限制这点和变量不一样所以嵌套函数不会从调用它的函数中继承 this这样会造成很多不符合直觉的代码。要解决这个问题你可以有两种思路
第一种是把 this 保存为一个 self 变量再利用变量的作用域机制传递给嵌套函数。第二种是继续使用 this但是要把嵌套函数改为箭头函数因为箭头函数没有自己的执行上下文所以它会继承调用函数中的 this
2. 普通函数中的 this 默认指向全局对象 window
上面我们已经介绍过了在默认情况下调用一个函数其执行上下文中的 this 是默认指向全局对象 window 的。
不过这个设计也是一种缺陷因为在实际工作中我们并不希望函数执行上下文中的 this 默认指向全局对象因为这样会打破数据的边界造成一些误操作。如果要让函数执行上下文中的 this 指向某个对象最好的方式是通过 call 方法来显示调用。
这个问题可以通过设置 JavaScript 的“严格模式”来解决。在严格模式下默认执行一个函数其函数的执行上下文中的 this 值是 undefined这就解决上面的问题了
五、总结
当函数作为对象的方法调用时函数中的 this 就是该对象当函数被正常调用时在严格模式下this 值是 undefined非严格模式下 this 指向的是全局对象 window 嵌套函数中的 this 不会继承外层函数的 this 值。箭头函数中因为箭头函数没有自己的执行上下文所以箭头函数的 this 就是它外层函数的 this。