深入理解JavaScript引用类型与this指向机制

作者:闭包旅人时间:2026-10-27 19:57:39浏览:433

本文深入解析JavaScript中的引用类型(Reference Type),阐述其由Base和属性名构成的内部结构。通过GetValue算法说明如何获取引用类型的真实值,并重点分析引用类型在函数调用中对this指向的决定作用,区分全局、局部及非引用类型调用的不同表现。

JavaScript引用类型概述

概述 1. 引用类型(Reference type) 引用类型是JavaScript中的一种内部类型,主要作为变量的指代,在需要获取真实值时,可通过它定位到实际的值。

引用类型的内部结构

2. 引用类型的结构 引用类型的值由两部分组成:一是指代对象所属的对象(base),二是base中该对象的属性名称。伪代码表示如下:

代码如下:

var valueOfReferenceType = {
base: <base object>,
propertyName: <property name>
};

引用类型的使用场景

3. 使用场景 引用类型主要出现在以下两种场景中:

(1) 处理标识符时

标识符包括变量名、函数名、函数参数名以及全局对象中未识别的属性名。

(2) 处理属性访问器时

代码如下:

var foo = 10;
function bar( ){}

在操作的中间结果中,引用类型对应如下结构:

代码如下:

var fooReference = {
base: global,
propertyName: 'foo'
    };
var barReference = {
base: global,
propertyName: 'bar'
    };

这里有必要解释一下base的概念。在JavaScript中,所有对象或函数都有所属对象。如前文所述,每个执行上下文都有一个变量对象(Variable Object)来管理该上下文中的变量或函数。

因此,当处理标识符时:

在全局上下文中,base === globalVO === global

在函数的执行上下文中,base === VO/AO

而当处理对象属性时:

情况更为简单,base === 拥有者对象(ownerObject)

获取引用类型的真实值

4. 获取引用类型的真实值 前文提到,引用类型仅是一个指代,并不直接保存真实值。当需要获取真实值时,可通过内部一系列算法实现。该算法可用以下伪代码描述:

代码如下:

function GetValue(value) {
if (Type(value) != Reference) {
return value;
  }
var base = GetBase(value);
if (base === null) {
throw new ReferenceError;
  }
return base.[[Get]](GetPropertyName(value));
}

内部的[[Get]]方法返回对象属性的真实值,包括对原型链中继承属性的分析。通过GetValue方法,我们可以轻松获取引用类型的真实值。示例如下:

代码如下:

GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"

那么,我们何时需要获取引用类型的真实值呢?

通常在对引用类型进行赋值、参与运算或被调用时,需要通过GetValue方法获取真实值。(注意:通过GetValue获取到的结果不再是引用类型)

引用类型与this指向的关系

引用类型与this的关系 引用类型主要与函数上下文中的this指向密切相关,且在不同场景下表现差异较大,因此引入引用类型来专门解释函数上下文中this的行为。

确定函数上下文中this值的通用规则如下:

在函数调用中,this由调用者提供,具体取决于调用方式。如果调用括号()左侧是引用类型的值,this将被设置为该引用类型值的base对象;在其他情况下(非引用类型的其他属性),this值为null。不过,实际中this为null的情况不存在,因为当this为null时,其值会被隐式转换为全局对象。注:在ECMAScript第5版中,不再强制转换为全局变量,而是赋值为undefined。

下面根据调用括号左侧的不同情况分为三种进行讨论:

(1) 调用括号左侧是引用类型的值

无需过多分析,base对象即为this值,找到base即可。若为全局变量声明,则指向全局对象。

代码如下:

var myObject = {
foo : function(){
console.log(this);
      }  
}
myObject.foo(); // 毫无疑问,foo的base是myObject,故foo方法中的this指向myObject。

(2) 调用括号左侧是引用类型的值,但该值为null

代码如下:

function myFunction() {
var foo = function(){
console.log(this);
      }  
foo(); // AO.foo() => null.foo()
}
myFunction(); // 输出: Window {top: Window, window: Window...}

当内部函数被调用时,其base应为当前执行上下文的活动对象(AO)。但在JavaScript内部,当AO作为base时,均视为null处理。JavaScript不允许this为null的情况发生,因此将base设置为global对象(这是前文this调用模式设计错误的源头)。因此,在此情况下,this均指向全局对象。

(3) 调用括号左侧不是引用类型的值

代码如下:

// 简单示例
(function () {
console.log(this); // null => global
})();
// 复杂示例
var foo = {
 bar: function () {
 console.log(this);
 }
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
(foo.bar = foo.bar)(); // global
(false || foo.bar)(); // global
(foo.bar, foo.bar)(); // global

当调用括号左侧不是引用类型而是其他类型时,this自动设置为null,结果为全局对象。

第一个例子中,立即执行函数的小括号左侧是一个表达式,而非引用。

第二个例子较为复杂,我们逐一分析:

foo.bar(),毫无疑问,base为foo,this指向foo。

(foo.bar)(),此处使用了小括号作为分组符,它不会迫使引用类型执行GetValue方法,其执行结果与上述情况完全一致。

后三个例子中,小括号内依次是赋值运算、或运算和逗号运算,它们都会迫使引用类型执行GetValue方法,从而返回一个函数对象。这样,函数调用小括号左侧不再是引用类型,因此this均指向全局对象。

总结与思考

总结

关于引用类型,其实我一直不太了解,只是看到汤姆大叔博客中关于this的那一章,为了解释函数调用模式中this的取值原理而专门分析了一下。这一分析结果出乎意料,我之前一直认为引用类型和引用传值可能存在某种关系,没想到汤姆大叔的博客中只是用来辅助理解this。至于二者之间是否有关系,如果有,具体是一种什么关系,还需我继续学习研究。

希望大家多交流。在此还要感谢汤姆大叔。

作者最新文章