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. JavaWeb02-Servlet

    Servlet概述 生命周期方法: l  void init(ServletConfig):出生之后(1次): l  void service(ServletRequest request, Serv ...

  2. python容器类型字典的操作

    字典(dict):由大括号进行描述一组键值对,其键值对之间使用冒号隔开,键值对与键值对之间使用逗号隔开: 注意:字典的key可以为数字,但是不可以重复,因为key是唯一标识符: 1.声明一个字典:语法 ...

  3. Nginx 本地建立负载均衡(Windows环境)

    需求: 现在有个需求:两台服务器,建立负载均衡. A服务器:IP:localhost:负载均衡主服务器:代理本地文件夹D:\\SampleData B服务器:IP:10.10.10.10:代理本地文件 ...

  4. Java流程控制之(一)条件

    目录 条件语句 单if情况 单if/else情况 if/else多分支情况 switch条件语句 条件语句+循环语句,直接甩图甩代码! 条件语句 Java希望在某个条件为真时执行相应的语句. 单if情 ...

  5. android clipChildren 的使用与遇到的困难

    案例 在一次我写画板模块的时候,布局比较普通,但是需要子元素溢出父元素.其中一小块布局如下所示: 红色部分需要溢出,这个时候我想到了clipChildren. clipChildren 就是说我可以不 ...

  6. 转载 :c# 获取net framework 版本(Environment 类)

    源链接:http://www.cnblogs.com/tobecrazy/p/3362446.html 根据自己使用情况,添加了部分备注 1.获取当前操作系统版本信息 使用Environment.OS ...

  7. ansible roles 介绍和使用

    目录 roles roles 介绍 创建role的步骤 role内个目录中可用的文件 案例 roles roles 介绍 ansible 自1.2版本引入的新特性,用于层次性.结构化地组织playbo ...

  8. Spring 框架基础(06):Mvc架构模式简介,执行流程详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.SpringMvc框架简介 1.Mvc设计理念 MVC是一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集 ...

  9. Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除

    上篇文章中,我们对Netty中ChannelPipeline的构造与初始化进行了分析与总结,本篇文章我们将对ChannelHandler的添加与删除操作进行具体的的代码分析: 一.ChannelHan ...

  10. SpringBoot打成war包,部署Tomcat服务器

      1: 创建spring boot项目 使用 Spring initializr  可以直接选择创建包的方式 也可以选择在Pom中更改 <groupId>com.dgw</grou ...