从this出发,综合理解作用域、原型等概念

268 阅读5分钟

前言

说起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的基本概念也就讲完了,总结下:

  1. 全局执行上下文中的thisWindow对象;
  2. 函数执行上下文中的this是谁调用函数,this就指代谁。

理解以上两点,就算明白this是什么了。再深入一点的话就是this会产生的问题:嵌套函数中的this是指代window对象,严格模式下是undefined;

这个问题的解决方案有两种:

  1. 在嵌套函数的上层,用变量self保存this, 利用变量的作用域机制传递给嵌套函数。(这里用到了作用域链和闭包,未完待续)
  2. 将嵌套函数写法改为箭头函数形式,因为箭头函数不会形成自己的执行上下文,也就不会有自己的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的应用:callapplybind

callapplybind这三个内置函数的方法都可以改变函数的this指向。

怎么理解内置函数的方法?这就需要提到原型和原型对象的概念了。

下图说明内置函数Function和Object的关系WechatIMG728.png

下图搞清楚原型和原型链image.png

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、作用域、原型等概念一起学习才能融会贯通的理解其运行机制。