前言
说起JavaScript的基础,就会涉及到作用域,作用域链,原型,原型链,闭包,this等基本概念。大多数前端程序员在日常开发过程中,对这些概念都是一知半解的。下文从this什么,this的由来和this的应用三个方面出发,彻底搞懂这些概念。
什么是this?
this的字面意思,查了下字典表示:这个,指前面曾经提及的或暗指的人或物。我自己的理解就是中文里面的“那个”。“那个”在不同的语境下会表示不同的意思。同理,this在不用的执行上下文中指代的对象不一样。
注意,这里提到了执行上下文,那执行上下文是什么呢?
执行上下文是JavaScript执行一段代码时的运行环境,执行上下文是由变量对象,词法对象,outer环境,this等组成,执行上下文分为全局执行上下文,函数执行上下文,eval执行上下文,其中eval不太常用,这里不提。
全局执行上下文中的this就是Window对象。
那函数执行上下文中的this是什么呢?
一句话概括,函数被谁调用,函数里的this就指向谁。
既然一句话就可以说清楚,那为什么还说难以理解呢?因为语境(执行上下文)不一样,就导致this的指向不一样。
在全局执行上下文中调用函数,函数中的this就指代Window对象。
function select() {
console.log(this)
}
select() // Window
这是this和作用域交集的地方,即Window对象。
在函数执行上下文中,this指代调用函数的对象。
var name = '诸葛亮'
function select() {
console.log(this.name)
}
select() // 诸葛亮
上面的代码为什么是诸葛亮呢,函数调用好像也没有写明是谁在调用。其实js引擎做了一些事情。这里的select()调用其实是 Window.select(), 因为在当前执行上下文中声明的变量和函数,会保存在当前执行上下文中的变量对象里面。而全局执行上下文的变量对象就是Window对象,还有,全局执行上下文中的this也是Window对象。简单理解就是,全局执行上下文就是Window对象(前提在浏览器,如果是在node环境,那就是Global对象)。
const hero = {
name: "夏侯惇",
select: function() {
console.log(this.name)
}
}
hero.select() // 夏侯惇
因为是hero对象调用的select方法,所以select里面的this就指代hero。
其实讲到这里,this的基本概念也就讲完了,总结下:
- 全局执行上下文中的
this是Window对象;- 函数执行上下文中的
this是谁调用函数,this就指代谁。
理解以上两点,就算明白this是什么了。再深入一点的话就是this会产生的问题:嵌套函数中的this是指代window对象,严格模式下是undefined;
这个问题的解决方案有两种:
- 在嵌套函数的上层,用变量
self保存this, 利用变量的作用域机制传递给嵌套函数。(这里用到了作用域链和闭包,未完待续) - 将嵌套函数写法改为
箭头函数形式,因为箭头函数不会形成自己的执行上下文,也就不会有自己的this,其内的this会继承上层函数中的this。
this的由来
讲了this是什么,接下来讲讲this的由来,为什么要有this?上代码
var hero = {
name: '夏侯惇',
selectHero: function() {
console.log(name)
// console.log(this.name)
}
}
function autoSelect() {
let name = '周瑜'
return hero.selectHero
}
var name = '东皇太一'
let _selectHero = autoSelect()
_selectHero()
hero.selectHero()
在浏览器控制台中运行一下,查看结果发现都是 “东皇太一”,这是由js作用域机制所决定的。因为js作用域是词法作用域,即作用域是由代码中函数声明的位置来决定的。
这样就产生一个需求:在对象内部的方法中可以直接使用对象内部的属性,于是this出来了。
注意this的机制和作用域链的机制的区别,两者唯一交集的地方就是在全局执行上下文中都指向window。
作用域链是指通过作用域查找变量的链条。即沿着当前执行上下文中的词法环境->变量环境->外层执行上下文的词法环境->变量环境······->直到全局执行上下文中的词法环境和变量环境。而全局执行上下文的变量环境中的变量对象就是window。
作用域就是变量和函数声明的可生成区域或范围。作用域分为全局作用域,函数作用域,块级作用域。
怎么改变this?
讲了this的由来,接下来讲讲this的应用:call、apply、bind。
call、apply、bind这三个内置函数的方法都可以改变函数的this指向。
怎么理解内置函数的方法?这就需要提到原型和原型对象的概念了。
下图说明内置函数Function和Object的关系
下图搞清楚原型和原型链
call、apply、bind都是内置函数 Function 的 prototype 对象中的方法,简称内置函数的方法。
Function.prototype.binfFunc = function(context) {
if (context === null) {
return window
}
if (typeof context !== 'object') {
context = new Object(context)
}
const key = Symbol()
context[key] = this
const args = [...arguments].slice(1)
return function() {
return context[key](...args, ...arguments)
}
}
bind的实现方法中不仅使用了this,而且还用到了作用域和闭包。闭包简而言之就是在嵌套函数中,内层函数使用了外层函数的变量,如果内层函数被其他变量所引用,再重新调用就会创建闭包,如果不被引用或单独调用,就不会形成闭包。
总结
通过一个知识点很难理解js的设计思想,将this、作用域、原型等概念一起学习才能融会贯通的理解其运行机制。