JavaScript中this的原理

一、问题的由来

学懂 JavaScript 语言,一个标志就是理解下面两种写法,可能有不一样的结果。

> var obj = {
>   foo: function () {}
> };
>
> var foo = obj.foo;
>
> // 写法一
> obj.foo()
>
> // 写法二
> foo()

上面代码中,虽然obj.foofoo指向同一个函数,但是执行结果可能不一样。请看下面的例子。

> var obj = {
>   foo: function () { console.log(this.bar) },
>   bar: 1
> };
>
> var foo = obj.foo;
> var bar = 2;
>
> obj.foo() // 1
> foo() // 2

这种差异的原因,就在于函数体内部使用了this关键字。很多教科书会告诉你,this指的是函数运行时所在的环境。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样。

这种解释没错,但是教科书往往不告诉你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foofoo()就变成在全局环境执行?

本文就来解释 JavaScript 这样处理的原理。理解了这一点,你就会彻底理解this的作用。

二、内存的数据结构

JavaScript 语言之所以有this的设计,跟内存里面的数据结构有关系。

> var obj = { foo:  5 };

上面的代码将一个对象赋值给变量obj。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj

也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。

> {
>   foo: {
>     [[value]]: 5
>     [[writable]]: true
>     [[enumerable]]: true
>     [[configurable]]: true
>   }
> }

注意,foo属性的值保存在属性描述对象的value属性里面。

三、函数

这样的结构是很清晰的,问题在于属性的值可能是一个函数。

> var obj = { foo: function () {} };

这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。

> {
>   foo: {
>     [[value]]: 函数的地址
>     ...
>   }
> }

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

> var f = function () {};
> var obj = { f: f };
>
> // 单独执行
> f()
>
> // obj 环境执行
> obj.f()

四、环境变量

JavaScript 允许在函数体内部,引用当前环境的其他变量。

> var f = function () {
>   console.log(x);
> };

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

> var f = function () {
>   console.log(this.x);
> }

上面代码中,函数体里面的this.x就是指当前运行环境的x

> var f = function () {
>   console.log(this.x);
> }
>
> var x = 1;
> var obj = {
>   f: f,
>   x: 2,
> };
>
> // 单独执行
> f() // 1
>
> // obj 环境执行
> obj.f() // 2

上面代码中,函数f在全局环境执行,this.x指向全局环境的x



obj环境执行,this.x指向obj.x



回到本文开头提出的问题,obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。

深入理解this原理(JavaScript)的更多相关文章

  1. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  2. 理解和使用 JavaScript 中的回调函数

    理解和使用 JavaScript 中的回调函数 标签: 回调函数指针js 2014-11-25 01:20 11506人阅读 评论(4) 收藏 举报  分类: JavaScript(4)    目录( ...

  3. 摘抄--全面理解面向对象的 JavaScript

    全面理解面向对象的 JavaScript JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来人们对这一门语言的误解,即认为 JavaScript 不是一门面向对象的语言,或 ...

  4. 深入理解Delete(JavaScript)

    深入理解Delete(JavaScript) Delete  众所周知是删除对象中的属性. 但如果不深入了解delete的真正使用在项目中会出现非常严重的问题 (: Following 是翻译  ka ...

  5. 深入全面理解面向对象的 JavaScript

    深入全面理解面向对象的 JavaScript (原著: 曾 滢, 软件工程师, IBM,2013 年 4 月 17 日) JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来 ...

  6. 深入理解 ProtoBuf 原理与工程实践(概述)

    ProtoBuf 作为一种跨平台.语言无关.可扩展的序列化结构数据的方法,已广泛应用于网络数据交换及存储.随着互联网的发展,系统的异构性会愈发突出,跨语言的需求会愈加明显,同时 gRPC 也大有取代R ...

  7. 深入理解Faiss 原理&源码 (一) 编译

    目录 深入理解Faiss 原理&源码 (一) 编译 mac下安装 安装mac xcode工具包 安装 openblas 安装swig 安装libomp 编译faiss 附录 深入理解Faiss ...

  8. Atitit 图像处理 深刻理解梯度原理计算.v1 qc8

    Atitit 图像处理 深刻理解梯度原理计算.v1 qc8 1.1. 图像处理  梯度计算  基本梯度 内部梯度 外部梯度 方向梯度1 2. 图像梯度就是图像边缘吗?2 1.1. 图像处理  梯度计算 ...

  9. 深入理解PHP原理之变量作用域

    26 Aug 08 深入理解PHP原理之变量作用域(Scope in PHP)   作者: Laruence(   ) 本文地址: http://www.laruence.com/2008/08/26 ...

随机推荐

  1. 百度杯 十一月 的一道pwn题复现

    拿到题后,就直接开鲁.. /ctf/pwn# checksec pwnme [*] '/ctf/pwn/pwnme' Arch: amd64--little RELRO: Full RELRO Sta ...

  2. supervisor 安装配置详解

    一.安装 源码安装 先下载最新的supervisor安装包:https://pypi.python.org/pypi/supervisor , 如: (python3命令为 pip install g ...

  3. python内置的进制转换方法

    python进制转换方法总结表: ↓ 2进制 8进制 10进制 16进制 2进制 - bin(int(x, 8)) bin(int(x, 10)) bin(int(x, 16)) 8进制 oct(in ...

  4. VS Code实现markdown画流程图

    安装最新的vscode编辑器,原生支持markdown语法.不会markdow的人可以去好好学下,写文档神器!!! 1.安装Markdown Preview Enhanced插件 2.本地新建test ...

  5. 【前端】之jQuery基础知识

    jQuery 简介 在项目中引入jQuery: 去jQuery官网下载jQuery包:jquery-3.2.1.min.js 将下载的jQuery包添加到项目目录中 在标签下添加jQuery引用:&l ...

  6. Java秋招面试复习大纲(二):Spring全家桶+MyBatis+MongDB+微服务

    前言 对于那些想面试高级 Java 岗位的同学来说,除了算法属于比较「天方夜谭」的题目外,剩下针对实际工作的题目就属于真正的本事了,热门技术的细节和难点成为了面试时主要考察的内容. 这里说「天方夜谭」 ...

  7. linux搭建ftp出错汇总

    重启vsftpd出现”500 OOPS: vsftpd: cannot open config file:restart” 2008-05-09 21:33 进到/etc/init.d/目录 输入: ...

  8. 【10分钟学Spring】:@Profile、@Conditional实现条件化装配

    根据不同的环境来装配不同的bean 企业级开发中,我们一般有多种环境,比如开发环境.测试环境.UAT环境和生产环境.而系统中有些配置是和环境强相关的,比如数据库相关的配置,与其他外部系统的集成等. 如 ...

  9. .Net Core+Vue.js模块化前后端分离快速开发框架NetModular更新日志(2019-12-08)

    源码 GitHub:https://github.com/iamoldli/NetModular 码云:https://gitee.com/laoli/NetModular 欢迎star~ 文档 ht ...

  10. Vue.js命名风格指南

    前言 本命名风格指南推荐了一种统一的命名规范来编写 Vue.js 代码.这使得代码具有如下的特性: 统一团队的命名规范,其它开发者或是团队成员更容易上手阅读和理解. IDEs 更容易理解代码,从而提供 ...